182 lines
6.3 KiB
Python
Executable File
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())
|