tts-test / backend /batch.py
aaxaxax's picture
feat: add full project source for HF Spaces deployment
10ea63e
Raw
History Blame Contribute Delete
4.5 kB
"""
CLI batch tool. Imports inference.py directly. No uvicorn needed.
Usage (emotion shorthand — maps to EMOTION_TEMPLATES for speaker/language):
python batch.py --file script.txt --speaker Yash --language Gujarati \
--emotion happy --output ./out/ [--seed 42] [--resume]
Usage (raw description — full control):
python batch.py --file script.txt --speaker Yash --language Gujarati \
--description "Yash speaks very fast with a very high-pitched, extremely joyful voice." --output ./out/
--emotion and --description are mutually exclusive.
"""
import argparse
import os
import sys
import json
from pathlib import Path
VALID_EMOTIONS = ["calm", "happy", "narrative", "angry", "command", "excited", "sad", "fearful"]
# Gujarati is not in the official Rasmalai emotion benchmark — all output is cross-language transfer.
# "medium" emotions have higher variability and may sound neutral.
MEDIUM_RELIABILITY: set[str] = {"angry", "command", "excited", "sad", "fearful"}
def resolve_description(args) -> str:
if args.description:
return args.description
from app.constants import EMOTION_TEMPLATES, EMOTION_METADATA
templates = EMOTION_TEMPLATES.get(args.language, {}).get(args.speaker, {})
template = templates.get(args.emotion)
if not template:
sys.exit(
f"ERROR: No template for emotion '{args.emotion}' with speaker '{args.speaker}' "
f"in language '{args.language}'. Use --description for a custom description."
)
if args.emotion in MEDIUM_RELIABILITY:
print(
f"WARNING: '{args.emotion}' has medium reliability for Gujarati — "
f"emotion output is not officially benchmarked and may sound neutral.",
file=sys.stderr,
)
return template
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--file", required=True)
parser.add_argument("--speaker", default="Yash")
parser.add_argument("--language", default="Gujarati")
parser.add_argument("--output", default="./out/")
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--resume", action="store_true")
desc_group = parser.add_mutually_exclusive_group(required=True)
desc_group.add_argument("--description", help="Freeform description of how to speak")
desc_group.add_argument(
"--emotion",
choices=VALID_EMOTIONS,
help="Emotion shorthand — maps to EMOTION_TEMPLATES for the given speaker/language",
)
args = parser.parse_args()
from app.constants import SPEAKERS_BY_LANGUAGE
valid_speakers = SPEAKERS_BY_LANGUAGE.get(args.language, [])
if not valid_speakers:
sys.exit(f"ERROR: Language '{args.language}' not supported. Valid: {list(SPEAKERS_BY_LANGUAGE)}")
if args.speaker not in valid_speakers:
sys.exit(f"ERROR: Speaker '{args.speaker}' not valid for language '{args.language}'. Valid: {valid_speakers}")
description = resolve_description(args)
out_dir = Path(args.output)
out_dir.mkdir(parents=True, exist_ok=True)
lines = Path(args.file).read_text().strip().splitlines()
from app.service import tts_service
from app.config import settings
import torch
device = "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Loading model on {device}...")
tts_service.load(settings.model_name, device)
print(f"Ready. Processing {len(lines)} lines...")
manifest = []
for i, line in enumerate(lines):
out_path = out_dir / f"{i+1:04d}.wav"
if args.resume and out_path.exists():
print(f" [{i+1}/{len(lines)}] skip (exists): {out_path}")
manifest.append({"index": i+1, "text": line, "file": str(out_path), "skipped": True})
continue
print(f" [{i+1}/{len(lines)}] {line[:60]}...")
audio_bytes = tts_service.run_inference(line, description)
if len(audio_bytes) < 1000:
print(f" WARNING: output suspiciously small ({len(audio_bytes)} bytes), skipping")
manifest.append({"index": i+1, "text": line, "file": None, "error": "too_short"})
continue
out_path.write_bytes(audio_bytes)
manifest.append({"index": i+1, "text": line, "file": str(out_path)})
manifest_path = out_dir / "manifest.json"
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2))
print(f"Done. Manifest: {manifest_path}")
if __name__ == "__main__":
main()