Run Qwen3-TTS with CUDA graph acceleration on NVIDIA DGX Spark (ARM64 / SM 121 / CUDA 13) as an OpenAI-compatible TTS API server. - Dockerfile targeting nvidia/cuda:13.0.2-base-ubuntu24.04 with ARM64 cu130 PyTorch wheels - Patch for max-seq-len support to handle long reference audio without crashes - OpenWebUI + SillyTavern compatible API endpoints (/v1/models, /v1/audio/voices, /speakers) - Voice management: auto-generate voices.json from speaker reference audio files - Auto-transcription helper script for generating reference text from audio Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""
|
|
Scan the speakers directory for reference audio files and generate voices.json.
|
|
|
|
Runs inside the container at startup to map all available voice reference
|
|
audio files into a format the OpenAI-compatible TTS server understands.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
|
|
# Internal container paths
|
|
speaker_dir = "/config/speakers"
|
|
output_file = "/config/voices.json"
|
|
|
|
voices = {}
|
|
|
|
if os.path.exists(speaker_dir):
|
|
for filename in os.listdir(speaker_dir):
|
|
if filename.endswith((".wav", ".mp3")):
|
|
base_name = os.path.splitext(filename)[0]
|
|
|
|
# Determine language based on file prefixes
|
|
lang = "Auto"
|
|
if base_name.startswith("EN_") or base_name.startswith("basic_ref_en"):
|
|
lang = "English"
|
|
elif base_name.startswith("DE_"):
|
|
lang = "German"
|
|
elif base_name.startswith("basic_ref_zh"):
|
|
lang = "Chinese"
|
|
|
|
# Create a clean voice ID
|
|
voice_id = base_name.lower()
|
|
prefixes_to_strip = ["en_m_", "en_f_", "de_m_", "de_f_"]
|
|
for prefix in prefixes_to_strip:
|
|
if voice_id.startswith(prefix):
|
|
voice_id = voice_id.replace(prefix, "", 1)
|
|
break
|
|
|
|
entry = {
|
|
"ref_audio": f"/config/speakers/{filename}",
|
|
"language": lang
|
|
}
|
|
|
|
# Look for matching reference text files
|
|
ref_txt_path = os.path.join(speaker_dir, f"{base_name}.reference.txt")
|
|
txt_path = os.path.join(speaker_dir, f"{base_name}.txt")
|
|
|
|
if os.path.exists(ref_txt_path):
|
|
with open(ref_txt_path, 'r', encoding='utf-8') as f:
|
|
entry["ref_text"] = f.read().strip()
|
|
elif os.path.exists(txt_path):
|
|
with open(txt_path, 'r', encoding='utf-8') as f:
|
|
entry["ref_text"] = f.read().strip()
|
|
|
|
voices[voice_id] = entry
|
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
json.dump(voices, f, indent=2, ensure_ascii=False)
|
|
|
|
print(f"Success! Generated voices.json with {len(voices)} mapped voices.")
|
|
else:
|
|
print(f"Warning: Directory {speaker_dir} not found. Skipping voice generation.")
|