aboutsummaryrefslogtreecommitdiff
path: root/tools/make_audiocpp_server_json.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/make_audiocpp_server_json.py')
-rwxr-xr-xtools/make_audiocpp_server_json.py88
1 files changed, 42 insertions, 46 deletions
diff --git a/tools/make_audiocpp_server_json.py b/tools/make_audiocpp_server_json.py
index 1273e43..d24f895 100755
--- a/tools/make_audiocpp_server_json.py
+++ b/tools/make_audiocpp_server_json.py
@@ -14,28 +14,30 @@ v3 TTS 4B, VoxCPM2-2B, and IndexTTS-2 / 2.5 (see the "Option 4" section
of the README). The converter works with other audio.cpp TTS families
too; host them by writing server.json by hand.
-Reference .wav files for voice cloning (a directory argument or an
-interactive prompt) are transcribed with a local Whisper backend
-(faster_whisper or whisper) and added as voice_presets on the cloning
-model entry.
+Reference .wav files for voice cloning (the required WAV_DIR argument)
+are transcribed with a local Whisper backend (faster_whisper or whisper)
+and added as voice_presets on the cloning model entry.
Every value can also be supplied as a command-line flag; anything missing
is asked interactively with the default shown in brackets. Pressing Enter
-accepts the default, so running the tool with no arguments and pressing
-Enter through produces a server.json hosting both Qwen3-TTS models on
-127.0.0.1:8080 with the cuda backend.
+accepts the default.
Usage:
- python tools/make_audiocpp_server_json.py [WAV_DIR] [--output PATH]
+ python tools/make_audiocpp_server_json.py WAV_DIR [--output PATH]
[--family {qwen3_tts,higgs_audio_tts,voxcpm2,index_tts2,index_tts2_5}]
[--model-id ID] [--model-path PATH]
[--host HOST] [--port PORT] [--models {both,custom,clone}]
[--backend {cuda,vulkan,hip,cpu}] [--lazy-load]
[--whisper-model NAME] [--force]
+
+WAV_DIR is required: a directory of .wav reference files used as voice
+cloning presets. It is checked up front and reported with its resolved
+absolute path if it does not exist.
"""
import argparse
import json
+import os
import re
import sys
import urllib.parse
@@ -108,6 +110,19 @@ FAMILY_KEYS = tuple(entry["key"] for entry in FAMILY_ENTRIES)
FAMILY_BY_KEY = {entry["key"]: entry for entry in FAMILY_ENTRIES}
+def resolve_wav_dir_arg(value: str) -> Path:
+ """Normalize a user-supplied wav directory argument.
+
+ Strips surrounding quotes (a common copy-paste artifact), expands a
+ leading ``~``, and resolves the result to an absolute path so relative
+ paths are always validated against the current working directory.
+ """
+ cleaned = value.strip()
+ if len(cleaned) >= 2 and cleaned[0] == cleaned[-1] and cleaned[0] in "\"'":
+ cleaned = cleaned[1:-1]
+ return Path(os.path.expanduser(cleaned)).resolve()
+
+
def find_wav_files(input_dir: Path) -> list:
"""Return the .wav files in INPUT_DIR, sorted alphabetically by name."""
return sorted(
@@ -241,23 +256,6 @@ def ask_distinct_clone_id(primary_id: str) -> str:
f"equal to '{primary_id}'.")
-def ask_wav_dir() -> Optional[Path]:
- """Prompt for a directory of .wav clone references; Enter skips."""
- while True:
- try:
- answer = input("Directory with .wav files to clone "
- "(Enter to skip): ").strip()
- except EOFError:
- return None
- if not answer:
- return None
- path = Path(answer)
- if path.is_dir():
- return path
- print(f"[WARNING] {answer} is not a directory; try again "
- "(or press Enter to skip).")
-
-
def config_port() -> int:
"""Return the port of AUDIOCPP_API_URL in converter/config.py."""
try:
@@ -452,27 +450,20 @@ def _ask_host_port_backend_lazy(args: argparse.Namespace
def _collect_voice_presets(args: argparse.Namespace,
include_clone: bool) -> Dict[str, dict]:
- """Resolve the clone-reference wav directory and transcribe it.
+ """Transcribe the wav directory into the voice_presets mapping.
- Returns the voice_presets mapping (empty when no wavs were given or
- found). Cloning entries only: a run without any cloning model ignores
- the wav directory entirely.
+ Returns the voice_presets mapping (empty when no wavs were found).
+ Cloning entries only: a run without any cloning model ignores the wav
+ directory entirely.
"""
- wav_dir: Optional[Path] = None
- if args.input_dir is not None:
- if include_clone:
- wav_dir = args.input_dir
- else:
- print(f"[WARNING] Ignoring {args.input_dir}: no cloning model "
- "selected, so voice presets are not used")
- elif include_clone:
- wav_dir = ask_wav_dir()
- if wav_dir is None:
+ if not include_clone:
+ print(f"[WARNING] Ignoring {args.input_dir}: no cloning model "
+ "selected, so voice presets are not used")
return {}
- wav_files = find_wav_files(wav_dir)
+ wav_files = find_wav_files(args.input_dir)
if not wav_files:
- print(f"[WARNING] No .wav files found in {wav_dir}; writing the "
+ print(f"[WARNING] No .wav files found in {args.input_dir}; writing the "
"config without voice presets")
return {}
if whisper_backend_available() is None:
@@ -512,9 +503,9 @@ def main() -> int:
parser = argparse.ArgumentParser(
description="Generate a server.json for the audio.cpp audiocpp_server "
"hosting a TTS model used by this converter.")
- parser.add_argument("input_dir", type=Path, nargs="?", default=None,
- help="Optional directory with .wav reference files "
- "to add as voice cloning presets")
+ parser.add_argument("input_dir", type=resolve_wav_dir_arg, metavar="WAV_DIR",
+ help="Directory with .wav reference files to add as "
+ "voice cloning presets (required)")
parser.add_argument("--output", type=Path, default=Path("server.json"),
help="Output path for server.json (default: "
"server.json in the current directory)")
@@ -551,8 +542,13 @@ def main() -> int:
help="Overwrite the output file without prompting")
args = parser.parse_args()
- if args.input_dir is not None and not args.input_dir.is_dir():
- parser.error(f"WAV directory not found: {args.input_dir}")
+ if not args.input_dir.is_dir():
+ parser.error(
+ f"WAV directory not found: {args.input_dir}\n"
+ f" (resolved from the current working directory: "
+ f"{Path.cwd()})\n"
+ " WAV_DIR must be a directory containing the .wav "
+ "reference files to use as voice cloning presets")
if args.output.exists() and not args.force \
and not prompt_overwrite(args.output):