34 lines
1.7 KiB
Python
34 lines
1.7 KiB
Python
"""qwen-tts (Qwen3-TTS 1.7B, fine-tune siriusfreak/qwen3-tts-12hz-1.7b-ru-stress-cf) on a local GPU.
|
|
|
|
ICL voice clone: the reference clip and its exact text are encoded once; texts are spoken in batches
|
|
of tts.batch with torch.manual_seed(seed + batch start), so a seed is repeatable for the same segments
|
|
in the same order. fp16 on Volta/Turing, bfloat16 on Ampere and newer (tts.dtype).
|
|
"""
|
|
import soundfile as sf
|
|
import torch
|
|
|
|
|
|
class LocalTTS:
|
|
def __init__(self, cfg, ref_wav, ref_text):
|
|
from qwen_tts import Qwen3TTSModel
|
|
t = cfg["tts"]
|
|
self.model = Qwen3TTSModel.from_pretrained(t.get("model", "siriusfreak/qwen3-tts-12hz-1.7b-ru-stress-cf"),
|
|
device_map=t.get("device", "cuda:0"),
|
|
dtype=getattr(torch, t.get("dtype", "bfloat16")))
|
|
self.batch = int(t.get("batch", 8))
|
|
self.set_voice(ref_wav, ref_text)
|
|
|
|
def set_voice(self, ref_wav, ref_text):
|
|
self.prompt = self.model.create_voice_clone_prompt(ref_audio=str(ref_wav), ref_text=ref_text)
|
|
|
|
def speak(self, items, seed, log=print):
|
|
items = sorted(items, key=lambda x: len(x[0])) # similar lengths in one batch: less padding
|
|
for i in range(0, len(items), self.batch):
|
|
b = items[i:i + self.batch]
|
|
torch.manual_seed(seed + i)
|
|
wavs, sr = self.model.generate_voice_clone(text=[t for t, _ in b], language=["Russian"] * len(b),
|
|
voice_clone_prompt=self.prompt * len(b))
|
|
for (_, out), w in zip(b, wavs):
|
|
sf.write(out, w, sr)
|
|
log(f"seed {seed}: {min(i + self.batch, len(items))}/{len(items)}")
|