1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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())
|