Files
enshrouded-ru-voice/ensvoice/tts/local.py
T

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)}")