Files
blind/audio_1/scripts/02_gen_tts_cosyvoice2.py
T
xslandClaude Sonnet 4.6 e5d3d44f9d 移除 GPT-SoVITS 和 Fish Speech,整理 audio_1 脚本与文档
- 删除 03_gen_tts_gptsovits.py、00_transcribe_refs.py、ref_texts.json
- 删除 02_gen_tts_fishspeech.py 及 tts/v2_fishspeech/ 生成结果
- 保留 VoxCPM2 / ACE-Step / Stable Audio 三条链路
- 更新执行文档和技术报告,清除两款 TTS 的所有引用

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 23:12:01 +08:00

154 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
步骤 2TTS v2 — CosyVoice2-0.5B 批量生成
输出目录:audio_1/tts/v2_cosyvoice2/
使用方法:
conda activate cosyvoice
python audio_1/scripts/02_gen_tts_cosyvoice2.py
python audio_1/scripts/02_gen_tts_cosyvoice2.py --dry-run
"""
import os, sys, glob, json, time, tempfile, subprocess, argparse
import numpy as np
PROJECT_DIR = "/home/xsl/blind"
VOICE_DIR = f"{PROJECT_DIR}/voice"
OUT_DIR = f"{PROJECT_DIR}/audio_1/tts/v2_cosyvoice2"
REF_TEXTS = f"{PROJECT_DIR}/audio_1/ref_texts.json"
MODEL_DIR = "/home/xsl/tools/CosyVoice/pretrained_models/CosyVoice2-0.5B"
COSYVOICE_DIR = "/home/xsl/tools/CosyVoice"
os.makedirs(OUT_DIR, exist_ok=True)
sys.path.insert(0, COSYVOICE_DIR)
sys.path.insert(0, f"{PROJECT_DIR}/audio")
from batch_tts_voxcpm import ROLES, LINES
with open(REF_TEXTS, encoding="utf-8") as f:
REF_TEXT_MAP = json.load(f)
# ──────────────────────────────────────────────────────────────
def find_ref_audio(role_key):
role_dir = os.path.join(VOICE_DIR, ROLES[role_key]["dir"])
for ext in ("*.wav", "*.mp3", "*.flac"):
files = sorted(glob.glob(os.path.join(role_dir, ext)))
if files:
return files[0]
return None
def load_audio_16k(path):
"""加载音频,重采样到 16kHz,返回 numpy float32 array"""
import librosa
audio, _ = librosa.load(path, sr=16000, mono=True)
return audio
def wav_to_mp3(src, dst):
subprocess.run(["ffmpeg", "-y", "-i", src, "-codec:a", "libmp3lame",
"-b:a", "128k", "-ar", "24000", "-ac", "1", dst],
check=True, capture_output=True)
def save_wav(audio_tensor, sr, path):
"""保存 torch tensor 或 numpy array 到 WAV"""
import soundfile as sf
if hasattr(audio_tensor, 'numpy'):
audio = audio_tensor.squeeze().numpy()
else:
audio = np.squeeze(audio_tensor)
sf.write(path, audio, sr)
# ──────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--role", help="只合成指定角色")
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--resume", action="store_true", default=True)
args = parser.parse_args()
lines = LINES
if args.role:
lines = [(f,r,t) for f,r,t in lines if r == args.role]
print(f"\n{'='*60}")
print(f" TTS v2 — CosyVoice2-0.5B ({len(lines)} 条)")
print(f" 输出: {OUT_DIR}")
print(f"{'='*60}\n")
# 预检参考音频
ref_cache = {}
for role_key in ROLES:
ref_path = find_ref_audio(role_key)
if ref_path:
ref_cache[role_key] = {
"path": ref_path,
"audio16k": None, # 懒加载
"text": REF_TEXT_MAP.get(role_key, ""),
}
if args.dry_run:
for f,r,t in lines:
out = os.path.join(OUT_DIR, f"{f}_wav_v1.mp3")
done = "✓" if os.path.exists(out) else "·"
ref_ok = "✓" if r in ref_cache else "✗"
print(f" [{done}] ref={ref_ok} {f} [{r}] {t[:40]}")
return
# 加载模型
print("加载 CosyVoice2 模型...", end=" ", flush=True)
t0 = time.time()
from cosyvoice.cli.cosyvoice import CosyVoice2
cosyvoice = CosyVoice2(MODEL_DIR)
print(f"就绪 ({time.time()-t0:.1f}s) SR={cosyvoice.sample_rate}")
tmp = tempfile.mkdtemp(prefix="cosyvoice2_")
ok = fail = skip = 0
for i, (fname, role, text) in enumerate(lines, 1):
out_mp3 = os.path.join(OUT_DIR, f"{fname}_wav_v1.mp3")
if args.resume and os.path.exists(out_mp3):
print(f" [{i:3d}/{len(lines)}] SKIP {fname}")
skip += 1; continue
if role not in ref_cache:
print(f" [{i:3d}/{len(lines)}] MISS {fname} (无参考音频)")
fail += 1; continue
speed = ROLES[role]["speed"]
ref = ref_cache[role]
print(f" [{i:3d}/{len(lines)}] {fname} [{role}] {text[:30]}... ", end="", flush=True)
t0 = time.time()
try:
# 懒加载参考音频
if ref["audio16k"] is None:
ref["audio16k"] = load_audio_16k(ref["path"])
# 合成:zero-shot 语音克隆
import torch
prompt_speech = torch.from_numpy(ref["audio16k"]).unsqueeze(0)
audio_chunks = []
for result in cosyvoice.inference_zero_shot(
tts_text=text,
prompt_text=ref["text"],
prompt_speech_16k=prompt_speech,
speed=speed,
stream=False,
):
audio_chunks.append(result['tts_speech'].squeeze().numpy())
# 合并并保存
full_audio = np.concatenate(audio_chunks) if audio_chunks else np.array([])
out_wav = os.path.join(tmp, f"{fname}.wav")
import soundfile as sf
sf.write(out_wav, full_audio, cosyvoice.sample_rate)
wav_to_mp3(out_wav, out_mp3)
print(f"ok ({time.time()-t0:.1f}s {os.path.getsize(out_mp3)//1024}KB)")
ok += 1
except Exception as e:
print(f"FAIL: {e}"); fail += 1
print(f"\n完成: {ok} ok, {skip} skip, {fail} fail → {OUT_DIR}")
if __name__ == "__main__":
main()