- stt_local: Whisper large-v3-turbo MLX, transcript + timestamped json - lipsync_local: Rhubarb visemes (CPU lane), dialog-guided - voice_clone_local: Chatterbox on MPS (needs setuptools<81 for perth/pkg_resources) - wan_video: Wan 2.2 TI2V-5B t2v/i2v via resident ComfyUI, frames->mp4 - llm_local: Qwen3-30B-A3B-4bit dialogue writer (primary-only) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
45 lines
1.3 KiB
Python
45 lines
1.3 KiB
Python
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
os.environ.setdefault("TQDM_DISABLE", "1") # keep sampling bars out of job logs
|
|
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--input", action="append", default=[])
|
|
ap.add_argument("--outdir", required=True)
|
|
ap.add_argument("--params", default="{}")
|
|
a = ap.parse_args()
|
|
p = json.loads(a.params)
|
|
|
|
text = (p.get("text") or "").strip()
|
|
if not text:
|
|
print("ERROR: text is required")
|
|
sys.exit(1)
|
|
if not a.input:
|
|
print("ERROR: needs a reference voice WAV as input")
|
|
sys.exit(1)
|
|
|
|
import torch # noqa: E402
|
|
import torchaudio # noqa: E402
|
|
from chatterbox.tts import ChatterboxTTS # noqa: E402
|
|
|
|
device = "mps" if torch.backends.mps.is_available() else "cpu"
|
|
t0 = time.time()
|
|
model = ChatterboxTTS.from_pretrained(device=device)
|
|
print(f"model loaded on {device} in {time.time() - t0:.1f}s", flush=True)
|
|
|
|
t1 = time.time()
|
|
wav = model.generate(
|
|
text,
|
|
audio_prompt_path=a.input[0],
|
|
exaggeration=float(p.get("exaggeration", 0.5)),
|
|
cfg_weight=float(p.get("cfg_weight", 0.5)),
|
|
)
|
|
out = Path(a.outdir) / f"clone_{Path(a.input[0]).stem}.wav"
|
|
torchaudio.save(str(out), wav, model.sr)
|
|
dur = wav.shape[-1] / model.sr
|
|
print(f"done in {time.time() - t1:.1f}s: {out.name} ({dur:.1f}s)", flush=True)
|