Files
2026-07-27 00:33:25 +08:00

182 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Create and optionally render fresh Local Voice acceptance plans."""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
RENDERER = ROOT / "scripts" / "local_voice.py"
CASES = (
"donna",
"chris-engineer",
"grandpa-bomber",
"val-holiday",
"ryan-pitch-meeting",
)
def run(command: list[str]) -> None:
subprocess.run(command, check=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--runtime-root", required=True, type=Path)
parser.add_argument("--output-dir", required=True, type=Path)
parser.add_argument("--device", choices=("metal", "cpu", "cuda"), default="metal")
parser.add_argument("--render", action="store_true")
parser.add_argument(
"--voices",
help="Comma-separated subset of profile IDs; defaults to every case.",
)
args = parser.parse_args()
args.output_dir.mkdir(parents=True, exist_ok=True)
selected_cases = (
tuple(item.strip() for item in args.voices.split(",") if item.strip())
if args.voices
else CASES
)
unknown = sorted(set(selected_cases) - set(CASES))
if unknown:
parser.error("Unknown acceptance voice(s): " + ", ".join(unknown))
results = []
for voice in selected_cases:
script = ROOT / "tests" / "acceptance" / f"{voice}.md"
plan = args.output_dir / f"{voice}.json"
audio = args.output_dir / f"{voice}.mp3"
job_id = f"acceptance-v9-{voice}"
run(
[
sys.executable,
str(RENDERER),
"plan",
"--voice",
voice,
"--script",
str(script),
"--output-plan",
str(plan),
"--output-audio",
str(audio),
"--job-id",
job_id,
]
)
if voice == "ryan-pitch-meeting":
data = json.loads(plan.read_text(encoding="utf-8"))
data["segments"] = [
{
"id": "01-producer",
"role": "producer",
"text": "So, you have a Monday briefing for me?",
"context_after": "Yes sir, I do!",
"pause_after_ms": 95,
},
{
"id": "02-yes-sir",
"role": "writer",
"text": "Yes sir, I do!",
"fixed_asset": (
"voices/ryan-pitch-meeting/signature-exchanges/"
"yes-sir-i-do/lotr_two_towers.wav"
),
"pause_after_ms": 55,
},
{
"id": "03-writer",
"role": "writer",
"text": (
"The Phoenix hardware shipped, but the final quantity "
"approval is still missing!"
),
"context_before": "Yes sir, I do!",
"context_after": (
"Oh, paperwork blocking shipped hardware is tight!"
),
"pause_after_ms": 95,
},
{
"id": "04-tight",
"role": "producer",
"text": "Oh, paperwork blocking shipped hardware is tight!",
"tts_text": "Oh, paperwork blocking shipped hardware",
"context_before": (
"The final quantity approval is still missing!"
),
"context_after": "It sure is!",
"suffix_asset": (
"voices/ryan-pitch-meeting/signature-exchanges/"
"tight-endings/is/disclosure.wav"
),
"suffix_gap_ms": 130,
"pause_after_ms": 95,
},
{
"id": "05-producer",
"role": "producer",
"text": (
"Is it going to be hard to clear that before noon?"
),
"context_before": (
"Oh, paperwork blocking shipped hardware is tight!"
),
"context_after": (
"Actually, it's gonna be super easy, barely an "
"inconvenience! Oh, really?"
),
"pause_after_ms": 95,
},
{
"id": "06-super-easy",
"role": "writer",
"text": (
"Actually, it's gonna be super easy, barely an "
"inconvenience! Oh, really?"
),
"fixed_asset": (
"voices/ryan-pitch-meeting/signature-exchanges/"
"super_easy_oh_really.wav"
),
"pause_after_ms": 95,
},
]
plan.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
run([sys.executable, str(RENDERER), "validate", str(plan)])
if args.render:
run(
[
sys.executable,
str(RENDERER),
"render",
str(plan),
"--runtime-root",
str(args.runtime_root),
"--device",
args.device,
"--resume",
]
)
results.append({"voice": voice, "plan": str(plan), "audio": str(audio)})
summary = args.output_dir / "acceptance-summary.json"
existing = []
if summary.exists():
existing = json.loads(summary.read_text(encoding="utf-8"))
merged = {item["voice"]: item for item in existing}
merged.update({item["voice"]: item for item in results})
summary.write_text(
json.dumps([merged[key] for key in sorted(merged)], indent=2) + "\n",
encoding="utf-8",
)
print(f"Acceptance summary: {summary}")
return 0
if __name__ == "__main__":
raise SystemExit(main())