Python的语音配音软件,使用edge-tts进行文本转语音,支持多种声音选择和语速调节
功能特性
✅ 多种中文语音选择(微软Edge TTS引擎)
✅ 语速调节(0.5倍速到2倍速)
✅ 文本输入和编辑
✅ 实时试听功能
✅ 保存为MP3文件
✅ 简洁易用的GUI界面
安装依赖
pip install edge-tts pygame
使用方法
运行程序:
python voice_assistant.py
界面操作:
选择声音: 从下拉菜单选择喜欢的语音
调节语速: 使用滑块调整语速(左慢右快)
输入文本: 在文本框中输入要转换的文字
试听: 点击"试听"按钮预览生成的语音
保存: 点击"保存MP3"将语音保存为文件
清除: 点击"清除"清空文本框
支持的声音
晓晓 (zh-CN-XiaoxiaoNeural) - 女声
晓伊 (zh-CN-XiaoyiNeural) - 女声
云健 (zh-CN-YunjianNeural) - 男声
云希 (zh-CN-YunxiNeural) - 男声
云夏 (zh-CN-YunxiaNeural) - 男声
云扬 (zh-CN-YunyangNeural) - 男声
技术栈
Python 3.6+
tkinter (GUI界面)
edge-tts (文本转语音)
pygame (音频播放)
asyncio (异步处理)
打包成EXE
用下面代码:
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import os
import edge_tts
import asyncio
import tempfile
import subprocess
import platformclass VoiceAssistant:def __init__(self, root):self.root = rootself.root.title("语音配音助手")self.root.geometry("800x600")# 音频播放设置self.audio_player = self.get_audio_player()# 创建主框架main_frame = ttk.Frame(root, padding="20")main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))# 配置网格权重root.columnconfigure(0, weight=1)root.rowconfigure(0, weight=1)main_frame.columnconfigure(1, weight=1)main_frame.rowconfigure(4, weight=1)# 声音选择ttk.Label(main_frame, text="选择声音:").grid(row=0, column=0, sticky=tk.W, pady=5)self.voice_var = tk.StringVar()self.voice_combo = ttk.Combobox(main_frame, textvariable=self.voice_var, state="readonly")self.voice_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)# 语速调节ttk.Label(main_frame, text="语速:").grid(row=1, column=0, sticky=tk.W, pady=5)self.speed_var = tk.DoubleVar(value=1.0)self.speed_scale = ttk.Scale(main_frame, from_=0.5, to=2.0, variable=self.speed_var, orient=tk.HORIZONTAL)self.speed_scale.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)ttk.Label(main_frame, textvariable=tk.StringVar(value="慢")).grid(row=1, column=2, sticky=tk.W, pady=5)ttk.Label(main_frame, textvariable=tk.StringVar(value="快")).grid(row=1, column=3, sticky=tk.W, pady=5)# 文本输入ttk.Label(main_frame, text="输入文本:").grid(row=2, column=0, sticky=tk.W, pady=5)self.text_input = tk.Text(main_frame, height=10, wrap=tk.WORD)self.text_input.grid(row=2, column=1, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)# 按钮框架button_frame = ttk.Frame(main_frame)button_frame.grid(row=3, column=0, columnspan=4, pady=10)ttk.Button(button_frame, text="试听", command=self.preview_audio).pack(side=tk.LEFT, padx=5)ttk.Button(button_frame, text="保存MP3", command=self.save_audio).pack(side=tk.LEFT, padx=5)ttk.Button(button_frame, text="清除", command=self.clear_text).pack(side=tk.LEFT, padx=5)# 状态标签self.status_var = tk.StringVar(value="就绪")ttk.Label(main_frame, textvariable=self.status_var).grid(row=4, column=0, columnspan=4, pady=5)# 加载可用声音self.load_voices()def load_voices(self):"""加载可用的声音列表"""try:# 这里可以添加更多声音选项voices = ["zh-CN-XiaoxiaoNeural", # 晓晓-女声"zh-CN-XiaoyiNeural", # 晓伊-女声"zh-CN-YunjianNeural", # 云健-男声"zh-CN-YunxiNeural", # 云希-男声"zh-CN-YunxiaNeural", # 云夏-男声"zh-CN-YunyangNeural" # 云扬-男声]self.voice_combo['values'] = voicesself.voice_combo.set(voices[0]) # 默认选择第一个声音except Exception as e:messagebox.showerror("错误", f"加载声音列表失败: {e}")async def generate_audio_async(self, text, voice, rate, output_file):"""异步生成音频文件"""try:communicate = edge_tts.Communicate(text, voice, rate=rate)await communicate.save(output_file)return True, ""except Exception as e:return False, str(e)def generate_audio(self, text, voice, rate, output_file):"""生成音频文件"""loop = asyncio.new_event_loop()asyncio.set_event_loop(loop)try:success, error = loop.run_until_complete(self.generate_audio_async(text, voice, rate, output_file))return success, errorfinally:loop.close()def preview_audio(self):"""试听功能"""text = self.text_input.get("1.0", tk.END).strip()if not text:messagebox.showwarning("警告", "请输入要转换的文本")returnvoice = self.voice_var.get()rate = f"+{(self.speed_var.get() - 1) * 100:.0f}%"self.status_var.set("正在生成试听音频...")# 在新线程中生成音频threading.Thread(target=self._preview_thread, args=(text, voice, rate), daemon=True).start()def _preview_thread(self, text, voice, rate):"""试听线程"""try:# 创建临时文件with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as tmp_file:temp_path = tmp_file.name# 生成音频success, error = self.generate_audio(text, voice, rate, temp_path)if success:# 在主线程中播放音频self.root.after(0, lambda: self._play_audio(temp_path))self.root.after(0, lambda: self.status_var.set("试听音频生成完成"))else:self.root.after(0, lambda: messagebox.showerror("错误", f"生成音频失败: {error}"))self.root.after(0, lambda: self.status_var.set("生成失败"))except Exception as e:self.root.after(0, lambda: messagebox.showerror("错误", f"试听过程中发生错误: {e}"))self.root.after(0, lambda: self.status_var.set("错误"))def get_audio_player(self):"""获取系统音频播放器"""system = platform.system()if system == "Windows":return "start"elif system == "Darwin": # macOSreturn "afplay"else: # Linuxreturn "aplay"def _play_audio(self, file_path):"""播放音频"""try:if self.audio_player == "start": # Windowssubprocess.Popen(["cmd", "/c", "start", "", "/min", file_path], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)else: # macOS or Linuxsubprocess.Popen([self.audio_player, file_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)# 延迟后清理文件def cleanup():try:os.unlink(file_path)except:passself.root.after(10000, cleanup) # 10秒后清理except Exception as e:messagebox.showerror("错误", f"播放音频失败: {e}")def save_audio(self):"""保存MP3文件"""text = self.text_input.get("1.0", tk.END).strip()if not text:messagebox.showwarning("警告", "请输入要转换的文本")return# 选择保存路径file_path = filedialog.asksaveasfilename(defaultextension=".mp3",filetypes=[("MP3文件", "*.mp3"), ("所有文件", "*.*")],title="保存音频文件")if not file_path:returnvoice = self.voice_var.get()rate = f"+{(self.speed_var.get() - 1) * 100:.0f}%"self.status_var.set("正在生成并保存音频...")# 在新线程中保存音频threading.Thread(target=self._save_thread, args=(text, voice, rate, file_path), daemon=True).start()def _save_thread(self, text, voice, rate, file_path):"""保存线程"""try:success, error = self.generate_audio(text, voice, rate, file_path)if success:self.root.after(0, lambda: messagebox.showinfo("成功", f"音频已保存到: {file_path}"))self.root.after(0, lambda: self.status_var.set("保存完成"))else:self.root.after(0, lambda: messagebox.showerror("错误", f"保存音频失败: {error}"))self.root.after(0, lambda: self.status_var.set("保存失败"))except Exception as e:self.root.after(0, lambda: messagebox.showerror("错误", f"保存过程中发生错误: {e}"))self.root.after(0, lambda: self.status_var.set("错误"))def clear_text(self):"""清除文本"""self.text_input.delete("1.0", tk.END)self.status_var.set("已清除文本")def main():root = tk.Tk()app = VoiceAssistant(root)root.mainloop()if __name__ == "__main__":main()
pyinstaller --onefile --windowed --name="语音配音助手" voice_assistant.py