# LIB · TTS 配音模块 # D180 · 2026-07-10 """Edge-TTS 配音引擎 · 多角色音色""" import asyncio, edge_tts, os # 角色音色表 VOICES = { "林昊": "zh-CN-YunxiNeural", # 青年男声·迷茫虚弱 "旁白": "zh-CN-YunyangNeural", # 新闻男声 "默认女": "zh-CN-XiaoxiaoNeural", # 女声 "默认男": "zh-CN-YunxiNeural", } FFMPEG = os.path.expanduser("~/.workbuddy/binaries/ffmpeg.exe") def tts(text, output_path, voice="zh-CN-YunxiNeural", rate="+0%"): """生成语音 mp3""" os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) async def _run(): await edge_tts.Communicate(text, voice, rate=rate).save(output_path) asyncio.run(_run()) return output_path if os.path.exists(output_path) else None def tts_for_character(text, character, output_path, rate="+0%"): """按角色名查音色生成""" voice = VOICES.get(character, "zh-CN-YunxiNeural") return tts(text, output_path, voice, rate) def mix_audio_video(video_path, audio_path, output_path, video_vol=0.3): """FFmpeg 合成视频+音频""" if not os.path.exists(FFMPEG): return f"ffmpeg not found at {FFMPEG}" os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) import subprocess r = subprocess.run([ FFMPEG, "-y", "-i", video_path, "-i", audio_path, "-filter_complex", f"[0:a]volume={video_vol}[va];[1:a]volume=1.0[aa];[va][aa]amix=inputs=2:duration=first", "-c:v", "copy", output_path ], capture_output=True, text=True) return output_path if r.returncode == 0 else r.stderr def add_subtitle(video_path, subtitle_text, output_path): """FFmpeg 添加字幕""" if not os.path.exists(FFMPEG): return f"ffmpeg not found at {FFMPEG}" import subprocess r = subprocess.run([ FFMPEG, "-y", "-i", video_path, "-vf", f"drawtext=text='{subtitle_text}':fontsize=28:fontcolor=white:borderw=2:bordercolor=black:x=(w-text_w)/2:y=h-120", "-c:a", "copy", output_path ], capture_output=True, text=True) return output_path if r.returncode == 0 else r.stderr if __name__ == "__main__": import sys if len(sys.argv) < 2: print("Usage: python tts.py tts [voice]") print(" python tts.py char ") print(" python tts.py mix