cang-ying/engines/tts-engine.py
铸渊 ICE-GL-ZY001 a2e5214f03 LL-172-20260707 · cang-ying 仓初始化 · 苍耳+鉴影的干净之家
铸渊 ICE-GL-ZY001
LL-172-20260707
冰朔委托: 新建第 5 子仓, 给苍耳(人类主控) + 鉴影(人格体) 专用
原 guanghulab/video-ai-system/ 东西太多(225 文件) · 找不到 · 乱

迁移:
  ⊢ 16 个核心 .hdlp (VA-GATE / VA-LIGHTHOUSE / VA-BROADCAST / VA-SYSTEM-STATUS 等)
  ⊢ 17 个子目录 (agents/engines/protocols/tasks/tools/assets/knowledge/memory/docs/config/brain/director-brain/experience/feedback/issues/plans/reference-analysis)

排除:
  ⊢ outputs/ (视频产物)
  ⊢ test-input/ test-output/ (测试)
  ⊢ data/ (临时数据)
  ⊢ preview-001/002 (旧产片)
  ⊢ 旧分镜/旧提示词/旧导演编码

后续:
  ⊢ 老仓 guanghulab/video-ai-system/ 改写为已迁出占位
  ⊢ 苍耳+鉴影 写新东西进本仓
  ⊢ GLOBAL-SEARCH 加 cang-ying 仓库

铸渊 ICE-GL-ZY001 · 2026-07-07 D167
冰朔 ICE-GL∞ 主权
2026-07-07 10:20:10 +08:00

312 lines
9.4 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
"""
TTS Engine · Edge-TTS 配音引擎
==================================
为视频AI系统提供文本转语音能力支持多角色音色配置。
依赖:
pip install edge-tts
用法:
# 基本用法(默认中文女声)
python tts-engine.py --text "你好世界" --output output.mp3
# 指定角色(从配置文件读取音色)
python tts-engine.py --text "未来的天下第一宗!" --character "苏白" --output su-bai.mp3
# 指定语音Edge-TTS 语音名)
python tts-engine.py --text "Hello World" --voice "en-US-JennyNeural" --output hello.mp3
# 调整语速/音调/音量
python tts-engine.py --text "你好" --rate "+20%" --pitch "+5Hz" --volume "+10%" --output output.mp3
# 批量生成从SRT字幕文件
python tts-engine.py --srt input.srt --output-dir ./audio/ --character "苏白"
# 作为模块导入
from tts_engine import generate_speech
generate_speech("你好世界", "output.mp3", voice="zh-CN-XiaoxiaoNeural")
角色音色配置:
video-ai-system/config/voices.json
路径:
video-ai-system/engines/tts-engine.py
"""
import argparse
import asyncio
import json
import os
import sys
from pathlib import Path
try:
import edge_tts
except ImportError:
print("[ERROR] 缺少依赖edge-tts")
print("请先安装pip install edge-tts")
sys.exit(1)
# 默认角色音色配置
DEFAULT_VOICES = {
"苏白": {
"voice": "zh-CN-XiaoxiaoNeural", # 阳光少年音
"rate": "+5%",
"pitch": "+0Hz"
},
"诸葛风": {
"voice": "zh-CN-YunxiNeural", # 沉稳男声
"rate": "+0%",
"pitch": "-5Hz"
},
"萧灵汐": {
"voice": "zh-CN-XiaoyiNeural", # 清冷女声
"rate": "+0%",
"pitch": "+0Hz"
},
"王执事": {
"voice": "zh-CN-YunyangNeural", # 中年男声
"rate": "+0%",
"pitch": "-10Hz"
}
}
def load_voice_config(config_path: str = None) -> dict:
"""加载角色音色配置文件"""
if config_path and os.path.isfile(config_path):
try:
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
print(f"[WARN] 无法读取配置文件 {config_path}{e}")
print("[INFO] 使用默认音色配置")
return DEFAULT_VOICES
async def generate_speech_async(
text: str,
output_path: str,
voice: str = "zh-CN-XiaoxiaoNeural",
rate: str = "+0%",
pitch: str = "+0Hz",
volume: str = "+0%"
) -> bool:
"""
异步生成语音Edge-TTS
:param text: 要合成的文本
:param output_path: 输出音频文件路径
:param voice: Edge-TTS 语音名
:param rate: 语速(如 "+20%""-10%"
:param pitch: 音调(如 "+5Hz""-10Hz"
:param volume: 音量(如 "+10%""-5%"
:return: 是否成功
"""
try:
# 确保输出目录存在
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
communicate = edge_tts.Communicate(
text, voice,
rate=rate, pitch=pitch, volume=volume
)
await communicate.save(output_path)
# 验证输出文件
if os.path.isfile(output_path):
file_size = os.path.getsize(output_path)
print(f"[OK] 生成语音:{output_path} ({file_size // 1024} KB)")
print(f" 语音:{voice} | 语速:{rate} | 音调:{pitch}")
return True
else:
print(f"[ERROR] 输出文件未生成:{output_path}")
return False
except Exception as e:
print(f"[ERROR] 生成语音失败:{e}")
return False
def generate_speech(
text: str,
output_path: str,
voice: str = "zh-CN-XiaoxiaoNeural",
rate: str = "+0%",
pitch: str = "+0Hz",
volume: str = "+0%"
) -> bool:
"""
同步包装器(供外部调用)
:param text: 要合成的文本
:param output_path: 输出音频文件路径
:param voice: Edge-TTS 语音名
:param rate: 语速
:param pitch: 音调
:param volume: 音量
:return: 是否成功
"""
return asyncio.run(generate_speech_async(text, output_path, voice, rate, pitch, volume))
def generate_by_character(
text: str,
output_path: str,
character: str,
config: dict = None
) -> bool:
"""
按角色名生成语音(自动读取音色配置)
:param text: 要合成的文本
:param output_path: 输出音频文件路径
:param character: 角色名(如 "苏白"
:param config: 音色配置字典(可选,默认加载 DEFAULT_VOICES
:return: 是否成功
"""
if config is None:
config = load_voice_config()
if character not in config:
print(f"[WARN] 角色 '{character}' 未配置音色,使用默认音色")
return generate_speech(text, output_path)
voice_config = config[character]
return generate_speech(
text, output_path,
voice=voice_config.get("voice", "zh-CN-XiaoxiaoNeural"),
rate=voice_config.get("rate", "+0%"),
pitch=voice_config.get("pitch", "+0Hz")
)
def process_srt(srt_path: str, output_dir: str, character: str = None, config: dict = None):
"""
从SRT字幕文件批量生成语音
:param srt_path: SRT 文件路径
:param output_dir: 输出目录
:param character: 角色名(所有台词用同一音色)
:param config: 音色配置字典
"""
if not os.path.isfile(srt_path):
print(f"[ERROR] SRT 文件不存在:{srt_path}")
return False
os.makedirs(output_dir, exist_ok=True)
# 简单SRT解析按空行分割
with open(srt_path, "r", encoding="utf-8") as f:
content = f.read()
blocks = content.strip().split("\n\n")
print(f"[INFO] 找到 {len(blocks)} 条字幕,开始生成语音...")
success = 0
for block in blocks:
lines = block.strip().split("\n")
if len(lines) < 3:
continue
idx = lines[0].strip()
# timeine = lines[1].strip() # 暂不使用时序
text = " ".join(lines[2:]).strip()
if not text:
continue
output_path = os.path.join(output_dir, f"{idx.zfill(4)}.mp3")
if character:
ok = generate_by_character(text, output_path, character, config)
else:
ok = generate_speech(text, output_path)
if ok:
success += 1
print(f"\n[INFO] SRT批量生成完成{success}/{len(blocks)} 成功")
return success == len(blocks)
def list_voices():
"""列出所有可用的Edge-TTS语音"""
print("[INFO] 正在获取可用语音列表...\n")
asyncio.run(_list_voices_async())
async def _list_voices_async():
"""异步列出语音"""
voices = await edge_tts.list_voices()
print("中文语音:")
for v in voices:
if v["Locale"].startswith("zh-"):
print(f" {v['ShortName']:30s} - {v['FriendlyName']}")
print(f"\n{len([v for v in voices if v['Locale'].startswith('zh-')])} 个中文语音")
def main():
parser = argparse.ArgumentParser(
description="TTS Engine · Edge-TTS 配音引擎",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python tts-engine.py --text "你好世界" --output output.mp3
python tts-engine.py --text "未来的天下第一宗!" --character "苏白" --output su-bai.mp3
python tts-engine.py --text "Hello" --voice "en-US-JennyNeural" --output hello.mp3
python tts-engine.py --srt input.srt --output-dir ./audio/ --character "苏白"
python tts-engine.py --list-voices
"""
)
parser.add_argument("--text", help="要合成的文本")
parser.add_argument("--output", help="输出音频文件路径")
parser.add_argument("--voice", default="zh-CN-XiaoxiaoNeural", help="Edge-TTS 语音名")
parser.add_argument("--character", help="角色名(从配置文件读取音色)")
parser.add_argument("--rate", default="+0%", help="语速(如 +20%%、-10%%")
parser.add_argument("--pitch", default="+0Hz", help="音调(如 +5Hz、-10Hz")
parser.add_argument("--volume", default="+0%", help="音量(如 +10%%、-5%%")
parser.add_argument("--srt", help="从SRT字幕文件批量生成")
parser.add_argument("--output-dir", help="批量生成时的输出目录")
parser.add_argument("--config", help="角色音色配置文件路径")
parser.add_argument("--list-voices", action="store_true", help="列出所有可用语音")
args = parser.parse_args()
if args.list_voices:
list_voices()
sys.exit(0)
if args.srt:
# 批量模式
if not args.output_dir:
print("[ERROR] 批量模式需要指定 --output-dir")
sys.exit(1)
config = load_voice_config(args.config)
ok = process_srt(args.srt, args.output_dir, args.character, config)
sys.exit(0 if ok else 1)
if not args.text or not args.output:
print("[ERROR] 需要指定 --text 和 --output")
parser.print_help()
sys.exit(1)
# 单文件模式
if args.character:
config = load_voice_config(args.config)
ok = generate_by_character(args.text, args.output, args.character, config)
else:
ok = generate_speech(
args.text, args.output,
voice=args.voice, rate=args.rate,
pitch=args.pitch, volume=args.volume
)
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()