Spaces:
Running on Zero
Running on Zero
Download tests/fixtures/make_golden_timeline.py from WolfDavid/japanese-learning-avatar: direct link, hf CLI and curl.
- Browser
- Download file 3.5 kB
-
https://huggingface.co/spaces/WolfDavid/japanese-learning-avatar/resolve/e3ebc72c752b778bfbdc8c18ebc39d56d5e7201a/tests/fixtures/make_golden_timeline.py
- Command line
-
hf download hf://spaces/WolfDavid/japanese-learning-avatar@e3ebc72c752b778bfbdc8c18ebc39d56d5e7201a/tests/fixtures/make_golden_timeline.py
-
curl -L -o make_golden_timeline.py https://huggingface.co/spaces/WolfDavid/japanese-learning-avatar/resolve/e3ebc72c752b778bfbdc8c18ebc39d56d5e7201a/tests/fixtures/make_golden_timeline.py
3.5 kB
| """Regenerate ``tests/fixtures/golden_timeline.json`` and the demo array for the stage harness. | |
| uv run python tests/fixtures/make_golden_timeline.py | |
| Downstream of ``make_synth_fixtures.py``: it reads ``audio_query_short.json`` (γγγ«γ‘γ―) and | |
| runs the real builder over it, so regenerating the synthesis fixtures without regenerating this | |
| one leaves the golden file describing an engine that is no longer installed. | |
| Needs no VOICEVOX wheel - ``visemes.build_timeline`` is a pure function over the committed JSON. | |
| The golden file is the regression guard behind ``test_golden_timeline``: if a ``voicevox_core`` | |
| bump changes a single mora length, every lip-sync in the app shifts, and this file is what turns | |
| that from an invisible aesthetic regression into a failing test. | |
| It also prints the ``DEMO_TIMELINE`` array for ``avatar/stage.html``, so the debug harness and the | |
| builder can never drift apart. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| from pathlib import Path | |
| FIXTURES = Path(__file__).resolve().parent | |
| REPO_ROOT = FIXTURES.parents[1] | |
| sys.path.insert(0, str(REPO_ROOT / "src")) | |
| SOURCE = "audio_query_short.json" | |
| GOLDEN = "golden_timeline.json" | |
| # γγγ«γ‘γ― = k o N n i ch i w a. The N gives a closed mouth sandwiched between two vowels, | |
| # which is what makes this the right five-mora sentence for a visual smoke test. | |
| EXPECTED_VISEMES = [ | |
| "closed", # prePhonemeLength | |
| "closed", # k | |
| "oh", # γ³ | |
| "closed", # γ³ | |
| "closed", # n | |
| "ih", # γ | |
| "closed", # ch | |
| "ih", # γ | |
| "closed", # w | |
| "aa", # γ― | |
| "closed", # postPhonemeLength | |
| ] | |
| def main() -> int: | |
| from japanese_avatar.voice.visemes import FRAMERATE, build_timeline | |
| query = json.loads((FIXTURES / SOURCE).read_text(encoding="utf-8")) | |
| events = build_timeline(query) | |
| rows = [{"t": e.t, "dur": e.dur, "viseme": e.viseme, "weight": e.weight} for e in events] | |
| visemes = [e.viseme for e in events] | |
| if visemes != EXPECTED_VISEMES: | |
| print( | |
| f"FAIL: {SOURCE} no longer produces the γγγ«γ‘γ― mouth-shape sequence.\n" | |
| f" expected {EXPECTED_VISEMES}\n got {visemes}", | |
| file=sys.stderr, | |
| ) | |
| return 1 | |
| meta = json.loads((FIXTURES / "synth_meta.json").read_text(encoding="utf-8")) | |
| true_duration = meta["cases"]["short"]["duration_seconds"] | |
| total = sum(e.dur for e in events) | |
| if abs(total - true_duration) > 1 / FRAMERATE: | |
| print( | |
| f"FAIL: the built timeline totals {total} s but speech_ja.wav is {true_duration} s. " | |
| f"Regenerate the synthesis fixtures first.", | |
| file=sys.stderr, | |
| ) | |
| return 1 | |
| # Explicit LF: the default translates to CRLF on Windows, which would make a committed | |
| # fixture differ byte-for-byte depending on which OS last regenerated it. | |
| (FIXTURES / GOLDEN).write_text( | |
| json.dumps(rows, indent=2) + "\n", encoding="utf-8", newline="\n" | |
| ) | |
| print(f"wrote {FIXTURES / GOLDEN}: {len(rows)} events, {total} s") | |
| print("\nDEMO_TIMELINE for avatar/stage.html:\n") | |
| labels = ["prePhonemeLength", "k", "o", "N", "n", "i", "ch", "i", "w", "a", "postPhonemeLength"] | |
| for row, label in zip(rows, labels, strict=True): | |
| print( | |
| f" {{ t: {row['t']}, dur: {row['dur']}, " | |
| f"viseme: '{row['viseme']}', weight: {row['weight']} }}, // {label}" | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |