基于Python和Tkinter實現(xiàn)文本文件轉換為MP3語音文件
更新時間:2026年02月11日 08:32:48 作者:溫輕舟
本文介紹了基于Python和Tkinter的圖形界面應用程序,該程序用于將文本文件轉換為MP3語音文件,功能包括文本轉語音轉換、文件處理、語音參數(shù)調(diào)節(jié)等,用戶界面設計合理,具有多線程處理、自動保存和錯誤處理等技術特點,使用流程簡單易懂,附帶幫助文檔
一:效果展示
本項目是基于Python和Tkinter的圖形界面應用程序,用于將文本文件轉換為MP3語音文件


二:功能描述
(1)文本轉語音轉換
- 使用
pyttsx3引擎將文本轉換為語音并保存為MP3文件 - 支持中文語音合成(默認使用"
Microsoft Huihui"語音)
(2)文件處理
- 單文件轉換:選擇單個文本文件進行轉換
- 批量轉換:支持同時選擇多個文本文件進行批量轉換
- 自動保存:在預覽窗口中修改文本內(nèi)容會自動保存到源文件
(3)語音參數(shù)調(diào)節(jié)
- 語速調(diào)節(jié)(1-100范圍)
- 音量調(diào)節(jié)(1-100范圍)
1. 用戶界面功能
(1)主界面布局
- 標題欄:顯示應用名稱"文本轉語音轉換器"和幫助按鈕
- 左右分欄:左側為輸入和設置面板,右側為文本預覽和轉換面板
- 狀態(tài)欄:顯示當前狀態(tài)信息和版本信息
(2)左側面板功能
- 文本文件選擇
- 通過文件對話框選擇要轉換的文本文件
- 自動填充輸出目錄為文本文件所在目錄
- 自動加載并顯示文本內(nèi)容到預覽區(qū)域
- 輸出設置
- 可自定義輸出目錄
- 可設置輸出文件名前綴(默認"wen_qing_zhou")
- 語音設置
- 顯示當前使用的語音名稱
- 語速調(diào)節(jié)滑塊和實時顯示
- 音量調(diào)節(jié)滑塊和實時顯示
(3)右側面板功能
- 文本預覽
- 顯示選定文本文件的內(nèi)容
- 支持直接編輯文本內(nèi)容(自動保存到源文件)
- 帶滾動條的可編輯文本框
- 轉換控制
- "轉換為MP3"按鈕:執(zhí)行單文件轉換
- "批量轉換"按鈕:執(zhí)行多文件批量轉換
- "打開輸出目錄"按鈕:快速訪問保存的MP3文件
- 進度指示
- 轉換過程中顯示進度條
- 狀態(tài)欄顯示轉換進度信息
2. 技術特點
- 多線程處理
- 使用單獨的線程執(zhí)行轉換任務,避免界面凍結
- 轉換過程中禁用按鈕防止重復操作
- 錯誤處理
- 對文件操作、語音引擎配置等關鍵操作進行異常捕獲
- 通過消息框向用戶顯示錯誤信息
- 自適應參數(shù)映射
- 將用戶友好的1-100范圍參數(shù)映射到語音引擎實際需要的參數(shù)范圍
- 語速映射采用非線性調(diào)整,提供更精細的慢速控制
- 跨平臺支持
- 使用pathlib處理路徑,確??缙脚_兼容性
- 根據(jù)不同操作系統(tǒng)使用適當?shù)姆椒ù蜷_目錄
- UI美化
- 使用ttk的clam主題
- 自定義按鈕樣式(如綠色強調(diào)按鈕)
- 一致的字體設置(微軟雅黑)
3. 使用流程
- 單文件轉換
- 點擊"選擇文件"選擇文本文件
- (可選)調(diào)整語音參數(shù)或修改文本內(nèi)容
- 點擊"轉換為MP3"開始轉換
- 轉換完成后在輸出目錄查找MP3文件
- 批量轉換
- 點擊"批量轉換"選擇多個文本文件
- 設置統(tǒng)一的輸出參數(shù)
- 程序會自動為每個文件生成帶序號的輸出文件名
- 查看結果
- 使用"打開輸出目錄"快速訪問輸出文件
- 狀態(tài)欄和消息框顯示轉換結果
4. 輔助功能
- 幫助文檔: 通過"幫助"按鈕查看詳細的使用說明
- 自動保存: 預覽區(qū)域的修改會自動保存到源文件
- 狀態(tài)反饋: 狀態(tài)欄實時顯示當前操作狀態(tài)
三:完整代碼
import pathlib
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
import pyttsx3
from PIL import Image, ImageTk
import threading
import os
import platform
import webbrowser
from datetime import datetime
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("文本轉語音轉換器")
self.geometry("1200x700")
self.minsize(700, 400)
self.resizable(True, True)
self.minsize(700, 400)
self.engine = pyttsx3.init()
self.file_path = tk.StringVar()
self.output_dir = tk.StringVar()
self.rate_var = tk.DoubleVar(value=50.00)
self.volume_var = tk.DoubleVar(value=50.00)
self.display_rate_var = tk.StringVar()
self.display_volume_var = tk.StringVar()
self.update_display_vars()
self.rate_var.trace_add('write', self.update_display_vars)
self.volume_var.trace_add('write', self.update_display_vars)
self.target_voice = "Microsoft Huihui"
self.voice_id = None
self.style = ttk.Style()
self.style.theme_use('clam')
self.style.configure('TButton', font=('微軟雅黑', 10), padding=5)
self.style.configure('TEntry', font=('微軟雅黑', 10))
self.style.configure('TLabel', font=('微軟雅黑', 10))
self.style.configure('TFrame', background='#f0f0f0')
self.style.configure('TLabelframe', font=('微軟雅黑', 10, 'bold'))
self.style.configure('Accent.TButton', foreground='white', background='#4CAF50', font=('微軟雅黑', 11, 'bold'))
self.style.map('Accent.TButton', background=[('active', '#45a049'), ('!active', '#4CAF50')])
self.init_ui()
self.load_voices()
def init_ui(self):
main_frame = ttk.Frame(self, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
title_frame = tk.Frame(main_frame, bd=0, highlightthickness=0, bg=self.cget('bg'))
title_frame.pack(fill=tk.X, pady=(0, 20))
title_label = tk.Label(
title_frame,
text="文本轉語音轉換器",
font=('微軟雅黑', 16, 'bold'),
bg=self.cget('bg')
)
title_label.pack(side=tk.LEFT)
help_btn = ttk.Button(title_frame, text="幫助", command=self.show_help, width=8)
help_btn.pack(side=tk.RIGHT, padx=(10, 0))
pw = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
pw.pack(fill=tk.BOTH, expand=True)
left_panel = ttk.Labelframe(pw, text="輸入和設置", padding=15)
pw.add(left_panel, weight=2)
right_panel = ttk.Labelframe(pw, text="文本預覽和轉換", padding=15)
pw.add(right_panel, weight=3)
self.create_left_panel(left_panel)
self.create_right_panel(right_panel)
self.status_var = tk.StringVar()
self.status_var.set("準備就緒")
status_frame = ttk.Frame(main_frame)
status_frame.pack(fill=tk.X, pady=(10, 0))
status_label = ttk.Label(status_frame, textvariable=self.status_var, foreground='#666')
status_label.pack(side=tk.LEFT)
version_label = ttk.Label(status_frame, text="溫輕舟", foreground='#999')
version_label.pack(side=tk.RIGHT)
def update_display_vars(self, *args):
self.display_rate_var.set(f"{self.rate_var.get():.2f}")
self.display_volume_var.set(f"{self.volume_var.get():.2f}")
def create_left_panel(self, parent):
file_frame = ttk.LabelFrame(parent, text="文本文件", padding=10)
file_frame.pack(fill=tk.X, pady=(0, 10))
entry_frame = ttk.Frame(file_frame)
entry_frame.pack(fill=tk.X, expand=True)
self.txt_path = ttk.Entry(entry_frame, textvariable=self.file_path, width=30)
self.txt_path.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
btn_sel = ttk.Button(entry_frame, text="選擇文件", command=self.select_file, width=10)
btn_sel.pack(side=tk.RIGHT)
output_frame = ttk.LabelFrame(parent, text="輸出設置", padding=10)
output_frame.pack(fill=tk.X, pady=(0, 10))
dir_frame = ttk.Frame(output_frame)
dir_frame.pack(fill=tk.X, expand=True)
self.txt_output_dir = ttk.Entry(dir_frame, textvariable=self.output_dir, width=30)
self.txt_output_dir.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
btn_dir = ttk.Button(dir_frame, text="選擇目錄", command=self.select_output_dir, width=10)
btn_dir.pack(side=tk.RIGHT)
prefix_frame = ttk.Frame(output_frame)
prefix_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Label(prefix_frame, text="文件名前綴:").pack(side=tk.LEFT)
self.prefix_entry = ttk.Entry(prefix_frame, width=15)
self.prefix_entry.pack(side=tk.LEFT, padx=(5, 0))
self.prefix_entry.insert(0, "wen_qing_zhou")
voice_frame = ttk.LabelFrame(parent, text="語音設置", padding=10)
voice_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(voice_frame, text=f"當前語音: {self.target_voice}").pack(side=tk.LEFT)
rate_frame = ttk.Frame(voice_frame)
rate_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Label(rate_frame, text="語速:").pack(side=tk.LEFT)
self.rate_scale = ttk.Scale(rate_frame, from_=1, to=100, variable=self.rate_var, orient=tk.HORIZONTAL)
self.rate_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
self.rate_label = ttk.Label(rate_frame, textvariable=self.display_rate_var, width=6)
self.rate_label.pack(side=tk.LEFT)
volume_frame = ttk.Frame(voice_frame)
volume_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Label(volume_frame, text="音量:").pack(side=tk.LEFT)
self.volume_scale = ttk.Scale(volume_frame, from_=1, to=100, variable=self.volume_var, orient=tk.HORIZONTAL)
self.volume_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
self.volume_label = ttk.Label(volume_frame, textvariable=self.display_volume_var, width=6)
self.volume_label.pack(side=tk.LEFT)
def create_right_panel(self, parent):
preview_frame = ttk.LabelFrame(parent, text="文本預覽", padding=10)
preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
self.text_preview = tk.Text(preview_frame, height=8, font=('微軟雅黑', 10), wrap=tk.WORD)
self.text_preview.pack(fill=tk.BOTH, expand=True)
self.text_preview.bind('<<Modified>>', self.on_text_modified)
scrollbar = ttk.Scrollbar(preview_frame, orient=tk.VERTICAL, command=self.text_preview.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.text_preview.configure(yscrollcommand=scrollbar.set)
convert_frame = ttk.Frame(parent)
convert_frame.pack(fill=tk.X, pady=(10, 0))
self.btn_convert = ttk.Button(
convert_frame,
text="轉換為MP3",
command=self.convert_to_mp3,
style='Accent.TButton'
)
self.btn_convert.pack(fill=tk.X, pady=(10, 0))
btn_batch = ttk.Button(
convert_frame,
text="批量轉換",
command=self.batch_convert,
style='Accent.TButton'
)
btn_batch.pack(fill=tk.X, pady=(5, 0))
btn_open_dir = ttk.Button(
convert_frame,
text="打開輸出目錄",
command=self.open_output_dir
)
btn_open_dir.pack(fill=tk.X, pady=(5, 0))
self.progress = ttk.Progressbar(convert_frame, mode='indeterminate', length=200)
def load_voices(self):
try:
voices = self.engine.getProperty('voices')
for voice in voices:
if self.target_voice in voice.name:
self.voice_id = voice.id
break
if not self.voice_id:
if voices:
self.voice_id = voices[0].id
self.status_var.set(f"未找到 {self.target_voice} 語音,將使用 {voices[0].name}")
else:
raise Exception("未找到任何可用語音")
except Exception as e:
self.status_var.set(f"加載語音列表失敗: {str(e)}")
def select_file(self):
current_file = self.file_path.get()
if current_file and os.path.exists(current_file):
initial_dir = os.path.dirname(current_file)
else:
initial_dir = str(pathlib.Path.home())
txt_file = filedialog.askopenfilename(
initialdir=initial_dir,
title="選擇文本文件",
filetypes=(('文本文件', '*.txt'), ('所有文件', '*.*'))
)
if txt_file:
self.file_path.set(txt_file)
file_dir = os.path.dirname(txt_file)
self.output_dir.set(file_dir)
self.status_var.set(f"已選擇文件: {pathlib.Path(txt_file).name}")
try:
with open(txt_file, 'r', encoding='utf8') as f:
content = f.read()
self.text_preview.delete(1.0, tk.END)
self.text_preview.insert(tk.END, content)
self.text_preview.edit_modified(False)
except Exception as e:
messagebox.showwarning("警告", f"無法讀取文件內(nèi)容: {str(e)}")
def select_output_dir(self):
current_file = self.file_path.get()
if current_file and os.path.exists(current_file):
initial_dir = os.path.dirname(current_file)
elif self.output_dir.get():
initial_dir = self.output_dir.get()
else:
initial_dir = str(pathlib.Path.home())
directory = filedialog.askdirectory(
initialdir=initial_dir,
title="選擇輸出目錄"
)
if directory:
self.output_dir.set(directory)
self.status_var.set(f"輸出目錄設置為: {directory}")
def on_text_modified(self, event):
if self.text_preview.edit_modified():
current_file = self.file_path.get()
if current_file and os.path.exists(current_file):
try:
content = self.text_preview.get(1.0, tk.END)
with open(current_file, 'w', encoding='utf8') as f:
f.write(content)
self.status_var.set(f"已自動保存修改到文件: {os.path.basename(current_file)}")
except Exception as e:
self.status_var.set(f"自動保存失敗: {str(e)}")
self.text_preview.edit_modified(False)
def configure_engine(self):
try:
if self.voice_id:
self.engine.setProperty('voice', self.voice_id)
rate = self.rate_var.get()
volume = self.volume_var.get()
if rate <= 50:
progress = rate / 50.0
mapped_rate = 20 + (150 - 20) * (progress ** 1.5)
else:
progress = (rate - 50) / 50.0
mapped_rate = 150 + (300 - 150) * progress
mapped_volume = volume / 100.0
mapped_rate = max(20, min(300, mapped_rate))
mapped_volume = max(0.0, min(1.0, mapped_volume))
self.engine.setProperty('rate', int(mapped_rate))
self.engine.setProperty('volume', mapped_volume)
except Exception as e:
messagebox.showwarning("警告", f"配置語音引擎失敗: {str(e)}")
def convert_to_mp3(self):
if not self.file_path.get():
messagebox.showwarning("警告", "請先選擇文本文件!")
return
def convert_thread():
try:
self.btn_convert.state(['disabled'])
self.progress.pack(fill=tk.X, pady=(10, 0))
self.progress.start()
self.status_var.set("正在轉換...")
self.update()
text = self.text_preview.get(1.0, tk.END)
if not text.strip():
raise ValueError("文本內(nèi)容為空!")
self.configure_engine()
prefix = self.prefix_entry.get() or "TTS_Output"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_name = f"{prefix}_{timestamp}.mp3"
output_dir = self.output_dir.get() or os.path.dirname(self.file_path.get()) or str(pathlib.Path.home())
output_path = pathlib.Path(output_dir) / output_name
self.engine.save_to_file(text, str(output_path))
self.engine.runAndWait()
self.progress.stop()
self.progress.pack_forget()
self.status_var.set(f"轉換成功! 文件已保存為: {output_path.name}")
messagebox.showinfo("成功", f"文件轉換成功!\n已保存為: {output_path.name}")
except Exception as e:
self.progress.stop()
self.progress.pack_forget()
self.status_var.set("轉換失敗")
messagebox.showerror("錯誤", f"轉換過程中出錯:\n{str(e)}")
finally:
self.btn_convert.state(['!disabled'])
threading.Thread(target=convert_thread, daemon=True).start()
def batch_convert(self):
files = filedialog.askopenfilenames(
initialdir=os.path.dirname(self.file_path.get()) if self.file_path.get() else str(pathlib.Path.home()),
title="選擇多個文本文件",
filetypes=(('文本文件', '*.txt'), ('所有文件', '*.*'))
)
if not files:
return
def batch_thread():
try:
self.btn_convert.state(['disabled'])
self.progress.pack(fill=tk.X, pady=(10, 0))
self.progress.start()
self.status_var.set("正在批量轉換...")
self.update()
success_count = 0
prefix = self.prefix_entry.get() or "TTS_Output"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.configure_engine()
for i, file_path in enumerate(files, 1):
if not file_path:
continue
try:
with open(file_path, 'r', encoding='utf8') as f:
text = f.read()
if not text.strip():
continue
file_prefix = f"{prefix}_batch_{timestamp}_{i}"
output_name = f"{file_prefix}.mp3"
output_dir = self.output_dir.get() or os.path.dirname(file_path) or str(pathlib.Path.home())
output_path = pathlib.Path(output_dir) / output_name
self.engine.save_to_file(text, str(output_path))
self.engine.runAndWait()
success_count += 1
self.status_var.set(f"正在批量轉換... 已完成 {i}/{len(files)}")
self.update()
except Exception as e:
self.status_var.set(f"轉換 {os.path.basename(file_path)} 失敗: {str(e)}")
continue
self.progress.stop()
self.progress.pack_forget()
self.status_var.set(f"批量轉換完成! 成功轉換 {success_count}/{len(files)} 個文件")
messagebox.showinfo("完成", f"批量轉換完成!\n成功轉換 {success_count}/{len(files)} 個文件")
except Exception as e:
self.progress.stop()
self.progress.pack_forget()
self.status_var.set("批量轉換失敗")
messagebox.showerror("錯誤", f"批量轉換過程中出錯:\n{str(e)}")
finally:
self.btn_convert.state(['!disabled'])
def open_output_dir(self):
dir_path = self.output_dir.get()
if not dir_path and self.file_path.get():
dir_path = os.path.dirname(self.file_path.get())
if not dir_path or not os.path.isdir(dir_path):
messagebox.showwarning("警告", "無效的輸出目錄!")
return
try:
if platform.system() == "Windows":
os.startfile(dir_path)
elif platform.system() == "Darwin":
webbrowser.open(f"file://{dir_path}")
else:
webbrowser.open(dir_path)
except Exception as e:
messagebox.showerror("錯誤", f"無法打開目錄:\n{str(e)}")
def show_help(self):
help_text = """文本轉語音轉換器使用說明
1. 基本使用:
- 點擊"選擇文件"按鈕選擇要轉換的文本文件
- 在右側預覽區(qū)域可以查看和編輯文件內(nèi)容(修改后會自動保存)
- 點擊"轉換為MP3"進行轉換(使用編輯后的內(nèi)容)
2. 高級設置:
- 輸出目錄: 默認使用當前文件所在目錄,可手動修改
- 文件名前綴: 設置輸出文件的前綴名稱
- 語音設置: 固定使用Microsoft Huihui語音,可調(diào)整語速和音量
3. 批量轉換:
- 點擊"批量轉換"按鈕可以選擇多個文本文件進行批量轉換
- 轉換后的文件會添加序號以避免重名
4. 其他功能:
- 打開輸出目錄: 快速訪問保存的MP3文件
"""
messagebox.showinfo("幫助", help_text)
if __name__ == "__main__":
app = Application()
app.mainloop()
以上就是基于Python和Tkinter實現(xiàn)文本文件轉換為MP3語音文件的詳細內(nèi)容,更多關于Python文本文件轉MP3語音文件的資料請關注腳本之家其它相關文章!
相關文章
Java實現(xiàn)的執(zhí)行python腳本工具類示例【使用jython.jar】
這篇文章主要介紹了Java實現(xiàn)的執(zhí)行python腳本工具類,結合實例形式分析了java使用jython.jar執(zhí)行Python腳本的具體操作技巧,需要的朋友可以參考下2018-03-03
Qt通過QGraphicsview實現(xiàn)簡單縮放及還原效果
本文主要介紹通過QGraphicsview實現(xiàn)簡單的縮放以及縮放后還原原始大小,通過scale可以對view進行放大或縮小,具體內(nèi)容詳情跟隨小編一起看看吧2021-09-09
python庫lxml在linux和WIN系統(tǒng)下的安裝
這篇內(nèi)容我們給大家分享了lxml在WIN和LINUX系統(tǒng)下的簡單快速安裝過程,有興趣的朋友參考學習下。2018-06-06

