tts-dgx-spark-faster-qwen3-tts/config/auto_transcribe.py
mARTin 60db1b181f Initial release: Faster-Qwen3-TTS for DGX Spark GB10
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>
2026-04-14 00:06:05 +02:00

94 lines
3.3 KiB
Python

"""
Batch-transcribe speaker reference audio files using a local Whisper-compatible API.
Creates .reference.txt files alongside each audio file in the speakers directory.
These transcriptions are used by generate_voices.py to build the voice registry.
Usage:
python auto_transcribe.py [--api-url http://localhost:8010/v1/audio/transcriptions]
IMPORTANT: Reference audio for voice cloning should be 5-15 seconds long.
Longer files will produce poor cloning results and slow down inference.
"""
import os
import sys
import json
import argparse
import requests
def main():
parser = argparse.ArgumentParser(description="Batch-transcribe speaker reference audio")
parser.add_argument("--api-url", default="http://localhost:8010/v1/audio/transcriptions",
help="Whisper-compatible transcription API URL")
parser.add_argument("--speaker-dir", default="./speakers",
help="Directory containing speaker audio files")
parser.add_argument("--model", default="whisper-1",
help="Transcription model name")
args = parser.parse_args()
speaker_dir = args.speaker_dir
# Verify API is reachable
try:
requests.get(args.api_url.rsplit('/', 2)[0], timeout=5)
except requests.ConnectionError:
print(f"Error: Cannot reach transcription API at {args.api_url}")
print("Make sure your Whisper/ASR service is running.")
sys.exit(1)
if not os.path.exists(speaker_dir):
print(f"Error: Speaker directory not found: {speaker_dir}")
sys.exit(1)
audio_files = [f for f in os.listdir(speaker_dir)
if f.endswith(('.wav', '.mp3')) and not f.startswith('.')]
print(f"Found {len(audio_files)} audio files in {speaker_dir}")
for filename in sorted(audio_files):
base_name = os.path.splitext(filename)[0]
ref_txt_path = os.path.join(speaker_dir, f"{base_name}.reference.txt")
if os.path.exists(ref_txt_path):
print(f" Skipping {filename} (already transcribed)")
continue
filepath = os.path.join(speaker_dir, filename)
print(f" Transcribing {filename}...", end=" ", flush=True)
try:
with open(filepath, 'rb') as f:
response = requests.post(
args.api_url,
files={"file": (filename, f)},
data={"model": args.model},
timeout=60,
)
if response.status_code == 200:
# Handle both JSON and plain text responses
try:
text = response.json().get("text", "").strip()
except (json.JSONDecodeError, AttributeError):
text = response.text.strip()
if text:
with open(ref_txt_path, 'w', encoding='utf-8') as f:
f.write(text)
print(f"OK ({len(text)} chars)")
else:
print("EMPTY (no speech detected)")
else:
print(f"FAILED (HTTP {response.status_code})")
except requests.Timeout:
print("TIMEOUT")
except Exception as e:
print(f"ERROR: {e}")
print("Done.")
if __name__ == "__main__":
main()