Files
tts/scripts/generate/generate_chapter8_guests.py
2026-01-19 10:27:41 +08:00

216 lines
8.6 KiB
Python
Raw Permalink 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
"""
VoxCPM嘉宾语音生成脚本 - 第八章:韩信的入场券
功能为四位嘉宾Graham、Dmitri、Amita、穆罕默德生成语音
"""
import os
import sys
import soundfile as sf
import numpy as np
import time
# 设置路径
WORKSPACE = "/root/tts"
VOXCPM_DIR = os.path.join(WORKSPACE, "VoxCPM")
OUTPUT_DIR = os.path.join(WORKSPACE, "podcast_audios", "chapter8_voxcpm")
REFERENCE_DIR = os.path.join(WORKSPACE, "hosts")
# 确保目录存在
os.makedirs(OUTPUT_DIR, exist_ok=True)
print(f"✅ 输出目录创建成功: {OUTPUT_DIR}")
# 添加VoxCPM到Python路径
sys.path.insert(0, os.path.join(VOXCPM_DIR, "src"))
print(f"✅ 添加VoxCPM路径: {os.path.join(VOXCPM_DIR, 'src')}")
# 导入VoxCPM
from voxcpm.core import VoxCPM
# 模型路径
LOCAL_MODEL_PATH = os.path.join(VOXCPM_DIR, "models", "openbmb__VoxCPM1.5")
if not os.path.exists(LOCAL_MODEL_PATH):
LOCAL_MODEL_PATH = os.path.join(VOXCPM_DIR, "models", "VoxCPM1.5")
if not os.path.exists(LOCAL_MODEL_PATH):
print(f"❌ 找不到模型路径")
sys.exit(1)
print(f"✅ 模型路径: {LOCAL_MODEL_PATH}")
# 嘉宾配置
GUESTS = {
"graham": {
"name": "Graham Cox",
"reference_file": None, # 使用默认音色
"description": "Palo Alto科技巨头CMO技术乐观主义者",
"dialogues": [
{
"id": "tech_gap",
"text": "等等主持人我觉得你漏掉了一个关键变量——技术代差。2003年伊拉克战争美军只用42天就推翻了萨达姆。2001年阿富汗美军用精确制导炸弹摧毁了所有塔利班据点。这说明什么战争形态已经变了。你还在用冷战思维分析地缘政治不好意思在这个时代芯片比坦克好使代码比航母管用。",
"filename": "graham_tech_gap.wav"
},
{
"id": "tom_clancy",
"text": "说到这个我必须提一下《熊与龙》2000年出版预言了中俄联合对抗美国。当时所有人都在笑说这是科幻小说。结果呢2022年俄乌战争中俄真的无上限了这就是为什么我收集了60本签名版——克兰西是地缘政治界的先知",
"filename": "graham_tom_clancy.wav"
}
]
},
"dmitri": {
"name": "Dmitri Volkov",
"reference_file": None, # 使用默认音色
"description": "莫斯科国际关系学院副教授,能源地缘政治专家",
"dialogues": [
{
"id": "energy_ace",
"text": "主持人我同意技术很重要但让我补充一点——能源才是终极王牌。2006年天然气涨价欧洲人是怎么颤抖的中国能成为世界工厂恰恰是因为俄罗斯的能源支撑。西伯利亚的天然气管道才是真正的入场券。没有俄罗斯的能源中国凭什么24小时开工",
"filename": "dmitri_energy_ace.wav"
},
{
"id": "russia_pain",
"text": "因为你没打过真正的仗年轻人。俄罗斯在车臣打了两场仗死了2万人才学会什么叫持久战。中国选择忍不是怂是聪明。等你的航母掉头去阿富汗我就可以闷声发大财。这就是战略耐心。",
"filename": "dmitri_russia_pain.wav"
}
]
},
"amita": {
"name": "Amita Sharma",
"reference_file": None, # 使用默认音色
"description": "孟买政策研究中心高级研究员,印度视角",
"dialogues": [
{
"id": "india_alternative",
"text": "等一下两位。你们说的世界工厂好像默认了中国模式是唯一的。但让我提醒一下——2008年之后班加罗尔正在崛起。印度的软件外包墨西哥的近岸制造越南的流水线...世界工厂不只有一个。主持人,你为什么只讲中国?",
"filename": "amita_india_alternative.wav"
}
]
},
"mohammed": {
"name": "穆罕默德 Al-Fayed",
"reference_file": None, # 使用默认音色
"description": "开罗大学政治学教授,中东问题专家",
"dialogues": [
{
"id": "factory_trap",
"text": "各位说的都很好但我想问一个更根本的问题——世界工厂这个概念本身是不是一个陷阱中国用70%的外贸依存度换来了什么?换来了美国航母可以随时切断马六甲海峡。换来了鸡蛋放在一个篮子里的风险。主持人,你管这叫入场券?我倒觉得这像是一张——请君入瓮的请帖。",
"filename": "mohammed_factory_trap.wav"
}
]
}
}
# 初始化模型
print(f"\n🚀 开始初始化VoxCPM模型...")
start_time = time.time()
try:
model = VoxCPM(
voxcpm_model_path=LOCAL_MODEL_PATH,
enable_denoiser=False,
optimize=False
)
print(f"✅ 模型初始化完成,耗时: {time.time()-start_time:.2f}")
except Exception as e:
print(f"❌ 模型初始化失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# 生成所有嘉宾的语音
print(f"\n🎙️ 开始生成嘉宾语音...")
total_start = time.time()
for guest_id, guest_info in GUESTS.items():
print(f"\n{'='*60}")
print(f"嘉宾: {guest_info['name']}")
print(f"描述: {guest_info['description']}")
print(f"{'='*60}")
for dialogue in guest_info['dialogues']:
print(f"\n📄 生成对话: {dialogue['id']}")
print(f"文本: {dialogue['text'][:50]}...")
dialogue_start = time.time()
try:
# 生成音频
audio = model.generate(
text=dialogue['text'],
prompt_wav_path=guest_info['reference_file'],
prompt_text=None,
cfg_value=2.0,
inference_timesteps=20,
normalize=True,
denoise=False,
retry_badcase=True
)
# 保存音频
output_file = os.path.join(OUTPUT_DIR, dialogue['filename'])
sf.write(output_file, audio, model.tts_model.sample_rate)
# 验证
if os.path.exists(output_file):
file_size = os.path.getsize(output_file)
duration = len(audio) / model.tts_model.sample_rate
print(f"✅ 生成成功!")
print(f" 文件: {output_file}")
print(f" 大小: {file_size} 字节")
print(f" 时长: {duration:.2f}")
print(f" 耗时: {time.time()-dialogue_start:.2f}")
else:
print(f"❌ 保存失败")
except Exception as e:
print(f"❌ 生成失败: {e}")
import traceback
traceback.print_exc()
# 生成主持人语音
print(f"\n{'='*60}")
print(f"主持人: Sonia")
print(f"{'='*60}")
host_dialogue = {
"id": "host_intro",
"text": "1999年5月8日贝尔格莱德的火光中三位中国记者的生命换来的是什么是广东南海流水线上MADE IN CHINA标签的加速缝制。两年后同样是这群年轻人在大学操场上疯狂嘶吼I enjoy losing face! 这不是精神分裂,这是——卧薪尝胆。",
"filename": "host_intro.wav"
}
print(f"\n📄 生成主持人介绍")
print(f"文本: {host_dialogue['text'][:50]}...")
try:
audio = model.generate(
text=host_dialogue['text'],
prompt_wav_path=None,
prompt_text=None,
cfg_value=2.0,
inference_timesteps=20,
normalize=True,
denoise=False
)
output_file = os.path.join(OUTPUT_DIR, host_dialogue['filename'])
sf.write(output_file, audio, model.tts_model.sample_rate)
if os.path.exists(output_file):
print(f"✅ 主持人语音生成成功!")
print(f" 文件: {output_file}")
else:
print(f"❌ 主持人语音保存失败")
except Exception as e:
print(f"❌ 主持人语音生成失败: {e}")
print(f"\n{'='*60}")
print(f"🎉 所有语音生成完成!")
print(f"总耗时: {time.time()-total_start:.2f}")
print(f"输出目录: {OUTPUT_DIR}")
print(f"{'='*60}")
# 列出所有生成的文件
print(f"\n📋 生成的文件列表:")
for file in os.listdir(OUTPUT_DIR):
if file.endswith('.wav'):
file_path = os.path.join(OUTPUT_DIR, file)
size = os.path.getsize(file_path)
print(f" - {file} ({size} 字节)")