"""CLI: voice-cloning TTS over synthetic transcripts (paper ยง4.1 Step 2 / App. D). Conditions the TTS model on random in-domain ViMedCSS reference clips so the synthetic speech mimics real speakers (the paper's voice cloning). python scribe/training/scripts/voice_clone_tts.py \ --input artifacts/synthetic/synthetic_clean_smoke.jsonl \ --output artifacts/synthetic/synthetic_audio_manifest_smoke.jsonl \ --provider xtts --ref-dataset tensorxt/ViMedCSS --ref-count 20 --limit 10 --resume Use --provider mms for the single-speaker (no-clone) labeled fallback. """ from __future__ import annotations import argparse import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "scribe" / "training")) sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "scribe")) from gec.cliutil import configure_stdout # noqa: E402 configure_stdout() from gec.data import read_jsonl # noqa: E402 from gec.tts import ( # noqa: E402 ReferenceClipSampler, build_synthesizer, load_completed_audio_ids, synthesize_manifest, ) def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--input", required=True, help="JSONL from gen_synthetic.py") parser.add_argument("--output", default="artifacts/synthetic/synthetic_audio_manifest.jsonl") parser.add_argument("--audio-dir", default="artifacts/synthetic/audio") parser.add_argument("--provider", default="xtts", choices=["xtts", "mms"]) parser.add_argument("--model", default=None) parser.add_argument("--language", default="vi") parser.add_argument("--no-gpu", action="store_true") parser.add_argument("--ref-dataset", default="tensorxt/ViMedCSS") parser.add_argument("--ref-split", default="train") parser.add_argument("--ref-count", type=int, default=20) parser.add_argument("--ref-dir", default=None, help="Use local wavs instead of a dataset.") parser.add_argument("--ref-cache", default="artifacts/synthetic/reference_clips") parser.add_argument("--limit", type=int, default=None) parser.add_argument("--resume", action="store_true") args = parser.parse_args() rows = read_jsonl(Path(args.input)) if args.limit: rows = rows[: args.limit] sampler = None if args.provider == "xtts": if args.ref_dir: sampler = ReferenceClipSampler.from_directory(Path(args.ref_dir)) else: sampler = ReferenceClipSampler.from_dataset( args.ref_dataset, args.ref_split, args.ref_count, Path(args.ref_cache) ) synthesizer = build_synthesizer( provider=args.provider, model=args.model, language=args.language, use_gpu=not args.no_gpu ) output_path = Path(args.output) completed = load_completed_audio_ids(output_path) if args.resume else set() written = synthesize_manifest( clean_rows=rows, synthesizer=synthesizer, audio_dir=Path(args.audio_dir), sampler=sampler, output_path=output_path, completed_ids=completed, ) print(f"Wrote {written} synthetic audio rows (provider={synthesizer.provider}) to {output_path}") if __name__ == "__main__": main()