aboutsummaryrefslogtreecommitdiff
path: root/tools/make_faster_voices_json.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/make_faster_voices_json.py')
-rwxr-xr-xtools/make_faster_voices_json.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/tools/make_faster_voices_json.py b/tools/make_faster_voices_json.py
new file mode 100755
index 0000000..2e00f72
--- /dev/null
+++ b/tools/make_faster_voices_json.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Generate a voices.json file for the faster-qwen3-tts server.
+
+Scans a directory for .wav files, transcribes each with a local Whisper
+backend (faster_whisper or whisper), and writes a voices.json
+
+Usage:
+ python tools/make_faster_voices_json.py INPUT_DIR [--output PATH]
+ [--language LANG]
+ [--whisper-model NAME] [--force]
+
+The output can be passed to the faster server:
+ python examples/openai_server.py --voices voices.json --port 8000
+"""
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+# Allow running from any working directory.
+sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
+
+from converter.tts import normalize_language, transcribe_reference_audio
+
+
+def find_wav_files(input_dir: Path) -> list:
+ """Return the .wav files in INPUT_DIR, sorted alphabetically by name."""
+ return sorted(
+ (path for path in input_dir.iterdir()
+ if path.is_file() and path.suffix.lower() == ".wav"),
+ key=lambda path: path.name.lower(),
+ )
+
+
+def prompt_overwrite(output_path: Path) -> bool:
+ """Ask whether to overwrite an existing output file."""
+ while True:
+ try:
+ answer = input(f"{output_path} already exists. Overwrite? (y/n): ").strip().lower()
+ except EOFError:
+ print("\n[WARNING] No interactive input available; keeping existing file")
+ return False
+ if answer in ("y", "yes"):
+ return True
+ if answer in ("n", "no"):
+ return False
+ print("Please answer 'y' or 'n'.")
+
+
+def build_voices(wav_files: list, language: str, whisper_model: str) -> dict:
+ """Transcribe each wav file and build the voices mapping."""
+ voices = {}
+ for wav_file in wav_files:
+ name = wav_file.stem
+ print(f"[INFO] Transcribing {wav_file.name} (voice '{name}')...")
+ text = transcribe_reference_audio(str(wav_file), model_name=whisper_model)
+ if text:
+ print(f"[OK] {name}: {text}")
+ else:
+ print(f"[WARNING] No transcript for '{name}'; the faster backend "
+ "strongly recommends an accurate transcript — consider editing "
+ "voices.json by hand before starting the server")
+ voices[name] = {
+ "ref_audio": str(wav_file.resolve()),
+ "ref_text": text or "",
+ "language": language,
+ }
+ return voices
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(
+ description="Generate a voices.json for the faster-qwen3-tts server "
+ "from a directory of .wav reference files.")
+ parser.add_argument("input_dir", type=Path,
+ help="Directory containing .wav reference audio files")
+ parser.add_argument("--output", type=Path, default=None,
+ help="Output path for voices.json "
+ "(default: INPUT_DIR/voices.json)")
+ parser.add_argument("--language", type=str, default="English",
+ help="Language for all voices, as passed to the TTS model "
+ "(default: English; names and short codes accepted)")
+ parser.add_argument("--whisper-model", type=str, default="base",
+ help="Whisper model size for transcription "
+ "(default: base)")
+ parser.add_argument("--force", action="store_true",
+ help="Overwrite the output file without prompting")
+ args = parser.parse_args()
+
+ try:
+ language = normalize_language(args.language)
+ except ValueError as exc:
+ parser.error(str(exc))
+
+ if not args.input_dir.is_dir():
+ parser.error(f"Input directory not found: {args.input_dir}")
+
+ wav_files = find_wav_files(args.input_dir)
+ if not wav_files:
+ parser.error(f"No .wav files found in {args.input_dir}")
+
+ output_path = args.output if args.output is not None \
+ else args.input_dir / "voices.json"
+ if output_path.exists() and not args.force and not prompt_overwrite(output_path):
+ print("[INFO] Aborted; existing voices.json kept")
+ return 1
+
+ voices = build_voices(wav_files, language, args.whisper_model)
+
+ with output_path.open("w", encoding="utf-8") as handle:
+ json.dump(voices, handle, indent=4, ensure_ascii=False)
+ handle.write("\n")
+
+ print(f"[OK] Wrote {output_path} with {len(voices)} voice(s): "
+ f"{', '.join(voices)}")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())