79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
"""节拍行者 — 语音对白生成(Qwen3-TTS CustomVoice)。
|
||
|
||
用法(必须 flock,GPU 与其他 agent 共享):
|
||
flock /home/xsl/SoundGame/.gpu.lock \
|
||
/home/xsl/SoundGame/.venv-tts/bin/python gen_voice.py
|
||
|
||
产出 web/rhythm/audio/voice/*.wav(24kHz),随后由 gen_convert.sh 转 ogg/m4a。
|
||
"""
|
||
import os
|
||
|
||
os.environ.setdefault("HF_HOME", "/home/xsl/SoundGame/models/hf")
|
||
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
|
||
|
||
import numpy as np
|
||
import soundfile as sf
|
||
import torch
|
||
from qwen_tts import Qwen3TTSModel
|
||
|
||
OUT = "/home/xsl/SoundGame/web/rhythm/audio/voice"
|
||
os.makedirs(OUT, exist_ok=True)
|
||
|
||
# (id, 文本, 情感指令)
|
||
LINES = [
|
||
("welcome",
|
||
"欢迎来到节拍行者!规则很简单:仔细听提示音,在下一个节拍,做出对应的动作。"
|
||
"建议用有线耳机或扬声器游玩,蓝牙耳机会有延迟。",
|
||
"热情活力的游戏解说语气"),
|
||
("rules",
|
||
"听好了:上行音,向上滑;下行音,向下滑;低音,向左滑;高音,向右滑;"
|
||
"一声轻响,单击;两声轻响,快速双击。",
|
||
"清晰利落的教学语气,节奏感强"),
|
||
("tut_start",
|
||
"先来四小节练习。跟着节拍器,听到提示音,就在下一拍做动作。大胆做!",
|
||
"鼓励的语气"),
|
||
("tut_done",
|
||
"练习结束!正式挑战开始,一共四十个指令。预备!",
|
||
"兴奋、充满干劲的语气"),
|
||
("grade_s",
|
||
"评级,S!完美无缺,你就是节拍大师!",
|
||
"极度兴奋、欢呼的语气"),
|
||
("grade_a",
|
||
"评级,A!非常出色,差一点就完美了!",
|
||
"开心赞许的语气"),
|
||
("grade_b",
|
||
"评级,B。不错不错,继续加油!",
|
||
"温和鼓励的语气"),
|
||
("grade_c",
|
||
"评级,C。别灰心,多听节拍,你一定行!",
|
||
"安慰打气的语气"),
|
||
("restart",
|
||
"单击屏幕,再来一局。",
|
||
"轻松愉快的语气"),
|
||
]
|
||
|
||
model = Qwen3TTSModel.from_pretrained(
|
||
"Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
|
||
device_map="cuda:0",
|
||
dtype=torch.bfloat16,
|
||
)
|
||
|
||
wavs, sr = model.generate_custom_voice(
|
||
text=[t for _, t, _ in LINES],
|
||
language=["Chinese"] * len(LINES),
|
||
speaker=["Vivian"] * len(LINES),
|
||
instruct=[i for _, _, i in LINES],
|
||
)
|
||
|
||
for (vid, text, _), wav in zip(LINES, wavs):
|
||
wav = np.asarray(wav, dtype=np.float32)
|
||
dur = len(wav) / sr
|
||
peak = float(np.max(np.abs(wav))) if len(wav) else 0.0
|
||
assert dur > 0.3, f"{vid}: 时长异常 {dur:.2f}s"
|
||
assert peak > 0.05, f"{vid}: 峰值异常 {peak:.3f}"
|
||
path = os.path.join(OUT, vid + ".wav")
|
||
sf.write(path, wav, sr)
|
||
print(f"{vid}: {dur:.2f}s peak={peak:.3f} -> {path}")
|
||
|
||
print("ALL OK")
|