57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a WAV file using Qwen3-TTS with Damien's voice cloning."""
|
|
import sys
|
|
sys.path.insert(0, "/opt/Kazeia/qnn_venv/lib/python3.10/site-packages")
|
|
import warnings; warnings.filterwarnings("ignore")
|
|
|
|
from qwen_tts import Qwen3TTSModel
|
|
import soundfile as sf
|
|
import numpy as np
|
|
|
|
MODEL = "/home/alf/.cache/huggingface/hub/models--Qwen--Qwen3-TTS-12Hz-0.6B-Base/snapshots/5d83992436eae1d760afd27aff78a71d676296fc"
|
|
VOICE = "/opt/Kazeia/voix/damien_15s_24k.wav"
|
|
TEXT = "Bonjour, je m'appelle Kazeia, je suis encore en phase de développement."
|
|
OUTPUT = "/opt/Kazeia/kazeia_damien_pc.wav"
|
|
|
|
print("Loading model...")
|
|
tts = Qwen3TTSModel.from_pretrained(MODEL, local_files_only=True, device_map="cpu")
|
|
|
|
# Also generate phrase_embeds.bin for tablet use
|
|
import torch, struct
|
|
tokenizer = tts.processor.tokenizer
|
|
talker = tts.model.talker
|
|
|
|
ids = tokenizer.encode(TEXT, add_special_tokens=False)
|
|
print(f"Tokens ({len(ids)}): {ids}")
|
|
|
|
with torch.no_grad():
|
|
raw = talker.model.text_embedding(torch.tensor(ids))
|
|
projected = talker.text_projection(raw)
|
|
|
|
with open("/tmp/phrase_embeds.bin", "wb") as f:
|
|
f.write(struct.pack("<i", len(ids)))
|
|
for i in range(len(ids)):
|
|
f.write(projected[i].numpy().astype(np.float32).tobytes())
|
|
print(f"phrase_embeds.bin: {len(ids)} tokens saved")
|
|
|
|
# Generate speech
|
|
print(f"Generating: '{TEXT}'")
|
|
print(f"Voice: {VOICE}")
|
|
audio_list, sr = tts.generate_voice_clone(
|
|
text=TEXT,
|
|
ref_audio=VOICE,
|
|
language="french",
|
|
x_vector_only_mode=True,
|
|
non_streaming_mode=True,
|
|
)
|
|
|
|
audio = audio_list[0]
|
|
print(f"Audio: {len(audio)} samples, {len(audio)/sr:.2f}s, SR={sr}")
|
|
|
|
# Check audio is not silent
|
|
rms = np.sqrt(np.mean(audio**2))
|
|
print(f"RMS: {rms:.4f} (should be > 0.01)")
|
|
|
|
sf.write(OUTPUT, audio, sr)
|
|
print(f"Saved: {OUTPUT}")
|