62 lines
2.8 KiB
Python
62 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""One-off speech asset generation on the credential-owning speech host.
|
|
|
|
Resumes completed files and reuses the live helper's existing cache. It never
|
|
changes that cache, credentials, mission config or the running helper.
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from welcome_voice import ENDPOINT, VETERAN_PROMPT, save_clip, synthesize, validate_wav
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--manifest', type=Path, required=True)
|
|
parser.add_argument('--output', type=Path, required=True)
|
|
parser.add_argument('--cache', type=Path)
|
|
parser.add_argument('--project')
|
|
args = parser.parse_args()
|
|
manifest = json.loads(args.manifest.read_text())
|
|
voice, language = manifest['voice'], manifest['language']
|
|
identity = [ENDPOINT, voice, language, '', VETERAN_PROMPT]
|
|
args.output.mkdir(parents=True, exist_ok=True)
|
|
report = []
|
|
for entry in manifest['clips']:
|
|
name, text = entry['id'], entry['text']
|
|
assert name.replace('_', '').isalnum() and 1 <= len(text) <= 800
|
|
path = args.output / (name + '.wav')
|
|
marker = args.output / (name + '.json')
|
|
key = hashlib.sha256(json.dumps([identity, text], ensure_ascii=False).encode()).hexdigest()
|
|
origin = 'generated'
|
|
if path.exists() and marker.exists() and json.loads(marker.read_text())['cache_key'] == key:
|
|
audio = validate_wav(path.read_bytes())
|
|
origin = 'resumed'
|
|
elif args.cache and (args.cache / (key + '.wav')).is_file():
|
|
audio = validate_wav((args.cache / (key + '.wav')).read_bytes())
|
|
origin = 'cached'
|
|
else:
|
|
import google.auth
|
|
from google.auth.transport.requests import AuthorizedSession
|
|
credentials, _ = google.auth.default(
|
|
scopes=['https://www.googleapis.com/auth/cloud-platform'], quota_project_id=args.project)
|
|
with AuthorizedSession(credentials, max_refresh_attempts=0) as session:
|
|
audio = synthesize(session, ENDPOINT, voice, language, text)
|
|
save_clip(path, audio)
|
|
record = {'id': name, 'cache_key': key, 'sha256': hashlib.sha256(audio).hexdigest(),
|
|
'bytes': len(audio), 'origin': origin, 'voice': voice}
|
|
marker.write_text(json.dumps(record, indent=2) + '\n')
|
|
report.append(record)
|
|
print(f"{name}: {origin}, {len(audio)} bytes", flush=True)
|
|
(args.output / 'generation.json').write_text(json.dumps(report, indent=2) + '\n')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except Exception as exc:
|
|
# Provider exceptions can contain credentials/URLs; retain a fixed error.
|
|
raise SystemExit(f'Bundled speech generation failed ({type(exc).__name__}); completed assets retained.') from None
|