| """Build the precomputed example cache. |
| |
| Inputs: |
| example_decomps.json β phrase decompositions authored by a LARGE model |
| (so the examples are reliably correct, unlike the |
| small on-device model). |
| Pipeline: |
| decomposition -> translate.build_layers() (deterministic 7 layers) |
| -> Qwen3-TTS audio per layer, fetched from the deployed Modal |
| endpoint, downloaded and saved under static/example_audio/ |
| Outputs: |
| examples_cache.py β full results (layers/links + per-layer `audio` URL) |
| static/example_audio/ β committed .wav files (instant, no model at runtime) |
| |
| Run: python build_examples.py |
| """ |
| import hashlib |
| import json |
| import time |
| import urllib.request |
|
|
| import config |
| import examples |
| import translate |
|
|
| REMOTE = config.TTS_REMOTE_URL.rstrip("/") |
| AUDIO_DIR = config.STATIC_DIR / "example_audio" |
|
|
|
|
| def fetch_tts(text: str, lang: str) -> bytes: |
| """Fetch one utterance from the deployed Qwen3-TTS endpoint (retry for the |
| L4 cold-start blip; reject fallback beeps).""" |
| payload = json.dumps({"text": text, "lang": lang}).encode("utf-8") |
| last = None |
| for attempt in range(4): |
| try: |
| req = urllib.request.Request( |
| REMOTE + "/api/tts", data=payload, |
| headers={"Content-Type": "application/json"}, |
| ) |
| with urllib.request.urlopen(req, timeout=300) as r: |
| info = json.load(r) |
| with urllib.request.urlopen(REMOTE + info["url"], timeout=120) as r: |
| data = r.read() |
| if len(data) < 25000: |
| raise RuntimeError("remote returned fallback audio (beep)") |
| return data |
| except Exception as e: |
| last = e |
| print(f" retry {attempt+1} ({str(e)[:60]})") |
| time.sleep(5) |
| raise last |
|
|
|
|
| def main(): |
| decomps = json.load(open("example_decomps.json", encoding="utf-8")) |
| AUDIO_DIR.mkdir(parents=True, exist_ok=True) |
| cache = {} |
| seen_audio = set() |
|
|
| for text, src, tgt in examples.EXAMPLES: |
| k = f"{src}|{tgt}|{text}" |
| if k not in decomps: |
| print("!! missing decomposition:", k) |
| continue |
| d = decomps[k] |
| decomp = {"final": d["final"], "units": d["units"], "source_text": text} |
| result = translate.build_layers(decomp, src, tgt) |
|
|
| for layer in result["layers"]: |
| idx = layer["index"] |
| lang = src if idx == 0 else tgt |
| ltext = layer["text"] |
| h = hashlib.sha1(f"{lang}|{ltext}".encode("utf-8")).hexdigest()[:16] |
| fpath = AUDIO_DIR / f"{h}.wav" |
| if h not in seen_audio and not fpath.exists(): |
| print(f" {src}->{tgt} L{idx} [{lang}]: {ltext[:34]}") |
| fpath.write_bytes(fetch_tts(ltext, lang)) |
| seen_audio.add(h) |
| layer["audio"] = f"/static/example_audio/{h}.wav" |
|
|
| cache[k] = result |
| print("done:", k) |
|
|
| blob = json.dumps(cache, ensure_ascii=False) |
| with open("examples_cache.py", "w", encoding="utf-8") as f: |
| f.write('"""Auto-generated by build_examples.py β do not edit by hand.\n') |
| f.write("Precomputed example results (layers + per-layer audio) so the\n") |
| f.write('Surprise-me button needs no LLM and no TTS at runtime."""\n') |
| f.write("import json\n\nCACHE = json.loads(%r)\n" % blob) |
| print(f"\nwrote examples_cache.py: {len(cache)} examples, " |
| f"{len(seen_audio)} audio clips in {AUDIO_DIR}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|