????????我們將使用Python的Tkinter庫來構建一個音頻格式轉換器界面。由于音頻轉換需要實際的處理,我們將使用`pydub`庫(需要安裝)來進行音頻格式轉換。同時,我們會使用`ffmpeg`作為后端,因此請確保系統中已安裝ffmpeg并添加到環境變量,或者我們將提供一種方式讓用戶指定ffmpeg路徑。 功能要求:
1. 支持選擇多個音頻文件(批量轉換)或單個文件(單獨轉換)
2. 支持選擇輸出格式(如mp3, wav, flac, aac等)
3. 支持選擇輸出目錄
4. 開始轉換按鈕,顯示轉換進度(由于批量轉換,我們可以用進度條或者日志方式)
5. 轉換過程中顯示轉換狀態(成功/失敗) 界面設計:
????????- 文件選擇:一個按鈕用于添加文件,一個按鈕用于添加文件夾(批量添加),一個列表顯示已選文件。
????????- 輸出格式選擇:下拉菜單(Combobox)列出支持的格式。
????????- 輸出目錄選擇:一個按鈕用于選擇目錄,一個標簽顯示當前選擇的目錄。
????????- 轉換按鈕:開始轉換。
????????- 日志區域:用于顯示轉換進度和結果(可以用Text組件)。
步驟:
1. 導入必要的庫:tkinter, ttk, filedialog, messagebox, 以及pydub(需要安裝:pip install pydub)。
2. 確保ffmpeg可用。如果不可用,可以提示用戶安裝或指定路徑(我們可以在程序初始化時設置pydub的ffmpeg路徑,但為了簡便,我們假設已安裝)。
3. 創建主窗口,設置標題。
4. 創建并布局各個組件。 注意:由于音頻轉換可能耗時,我們使用多線程來避免界面卡死。 我們將創建以下類: - AudioConverterApp: 主應用類,包含UI和事件處理。
具體實現: 首先,我們導入必要的庫:
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import threading
from pygame import mixer
from pydub import AudioSegment
注意:需要用到ffmpeg工具,請提前下載,代碼需要調用該工具進行格式轉換(可設置全局環境變量,這樣可直接調用,也可放入代碼包里面,引用包路徑即可)
#局部引入
base_dir = os.path.dirname(os.path.abspath(__file__))
ffmpeg_path = os.path.join(base_dir, "ffmpeg", "bin", "ffmpeg.exe")
AudioSegment.converter = ffmpeg_path
編寫主體代碼:
class AudioConverterApp:def __init__(self, root):self.root = rootself.root.title("音頻格式轉換器")self.root.geometry("800x800")self.root.resizable(True, True)# 設置應用圖標try:self.root.iconbitmap("audio_icon.ico")except:pass# 創建樣式self.style = ttk.Style()self.style.configure('TFrame', background='#f0f0f0')self.style.configure('Header.TLabel', font=('Arial', 16, 'bold'), background='#4a6baf', foreground='white')self.style.configure('TButton', font=('Arial', 10), padding=5)self.style.map('TButton', background=[('active', '#4a6baf')])self.style.configure('Progress.Horizontal.TProgressbar', thickness=20, background='#4a6baf')# 創建主框架self.main_frame = ttk.Frame(root)self.main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)# 創建標題self.header_frame = ttk.Frame(self.main_frame)self.header_frame.pack(fill=tk.X, pady=(0, 20))self.title_label = ttk.Label(self.header_frame,text="音頻格式轉換器",style='Header.TLabel',anchor=tk.CENTER)self.title_label.pack(fill=tk.X, ipady=10)# 創建控制面板self.control_frame = ttk.LabelFrame(self.main_frame, text="轉換設置")self.control_frame.pack(fill=tk.X, pady=10)# 輸入文件選擇self.input_frame = ttk.Frame(self.control_frame)self.input_frame.pack(fill=tk.X, padx=10, pady=10)ttk.Label(self.input_frame, text="輸入文件:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))self.input_entry = ttk.Entry(self.input_frame, width=50)self.input_entry.grid(row=0, column=1, sticky=tk.EW, padx=5)self.browse_button = ttk.Button(self.input_frame,text="瀏覽文件...",command=self.browse_files,width=10)self.browse_button.grid(row=0, column=2, padx=(5, 0))# 輸出格式選擇self.format_frame = ttk.Frame(self.control_frame)self.format_frame.pack(fill=tk.X, padx=10, pady=10)ttk.Label(self.format_frame, text="輸出格式:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))self.format_var = tk.StringVar(value="mp3")self.format_combobox = ttk.Combobox(self.format_frame,textvariable=self.format_var,width=8,state="readonly")self.format_combobox['values'] = ('mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a')self.format_combobox.grid(row=0, column=1, sticky=tk.W, padx=5)# 輸出目錄選擇self.output_frame = ttk.Frame(self.control_frame)self.output_frame.pack(fill=tk.X, padx=10, pady=10)ttk.Label(self.output_frame, text="輸出目錄:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))self.output_entry = ttk.Entry(self.output_frame, width=50)self.output_entry.grid(row=0, column=1, sticky=tk.EW, padx=5)self.output_button = ttk.Button(self.output_frame,text="瀏覽目錄...",command=self.browse_output,width=10)self.output_button.grid(row=0, column=2, padx=(5, 0))# 添加批量轉換區域self.batch_frame = ttk.LabelFrame(self.control_frame, text="批量轉換")self.batch_frame.pack(fill=tk.X, padx=10, pady=10)self.batch_button = ttk.Button(self.batch_frame,text="添加文件夾...",command=self.browse_folder,width=15)self.batch_button.pack(side=tk.LEFT, padx=5, pady=5)self.clear_button = ttk.Button(self.batch_frame,text="清空列表",command=self.clear_file_list,width=10)self.clear_button.pack(side=tk.RIGHT, padx=5, pady=5)# 文件列表self.list_frame = ttk.LabelFrame(self.main_frame, text="待轉換文件")self.list_frame.pack(fill=tk.BOTH, expand=True, pady=10)# 創建帶滾動條的文件列表self.scrollbar = ttk.Scrollbar(self.list_frame)self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.file_list = tk.Listbox(self.list_frame,yscrollcommand=self.scrollbar.set,selectmode=tk.EXTENDED,height=8)self.file_list.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)self.scrollbar.config(command=self.file_list.yview)# 狀態和進度條self.status_frame = ttk.Frame(self.main_frame)self.status_frame.pack(fill=tk.X, pady=(10, 0))self.progress_var = tk.DoubleVar()self.progress_bar = ttk.Progressbar(self.status_frame,variable=self.progress_var,style='Progress.Horizontal.TProgressbar',mode='determinate',length=500)self.progress_bar.pack(fill=tk.X, pady=5)self.status_var = tk.StringVar(value="就緒")self.status_label = ttk.Label(self.status_frame,textvariable=self.status_var,anchor=tk.W)self.status_label.pack(fill=tk.X)# 控制按鈕self.button_frame = ttk.Frame(self.main_frame)self.button_frame.pack(fill=tk.X, pady=10)self.convert_button = ttk.Button(self.button_frame,text="開始轉換",command=self.start_conversion,width=15)self.convert_button.pack(side=tk.LEFT, padx=5)self.play_button = ttk.Button(self.button_frame,text="播放音頻",command=self.play_audio,width=15,state=tk.DISABLED)self.play_button.pack(side=tk.LEFT, padx=5)self.cancel_button = ttk.Button(self.button_frame,text="取消",command=self.cancel_conversion,width=15)self.cancel_button.pack(side=tk.RIGHT, padx=5)# 初始化變量self.files_to_convert = []self.converting = Falseself.cancel_requested = False# 初始化音頻播放器mixer.init()# 配置網格權重self.input_frame.columnconfigure(1, weight=1)self.output_frame.columnconfigure(1, weight=1)def browse_files(self):files = filedialog.askopenfilenames(title="選擇音頻文件",filetypes=(("音頻文件", "*.mp3 *.wav *.ogg *.flac *.aac *.m4a"),("所有文件", "*.*")))if files:self.files_to_convert.extend(files)self.update_file_list()def browse_folder(self):folder = filedialog.askdirectory(title="選擇包含音頻文件的文件夾")if folder:# 查找文件夾中的音頻文件audio_files = []for root, _, files in os.walk(folder):for file in files:if file.lower().endswith(('.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a')):audio_files.append(os.path.join(root, file))if audio_files:self.files_to_convert.extend(audio_files)self.update_file_list()else:messagebox.showinfo("無音頻文件", "所選文件夾中沒有找到支持的音頻文件")def browse_output(self):folder = filedialog.askdirectory(title="選擇輸出文件夾")if folder:self.output_entry.delete(0, tk.END)self.output_entry.insert(0, folder)def update_file_list(self):self.file_list.delete(0, tk.END)for file in self.files_to_convert:self.file_list.insert(tk.END, os.path.basename(file))def clear_file_list(self):self.files_to_convert = []self.file_list.delete(0, tk.END)def start_conversion(self):if not self.files_to_convert:messagebox.showwarning("無文件", "請先添加要轉換的音頻文件")returnoutput_folder = self.output_entry.get().strip()if not output_folder:messagebox.showwarning("無輸出目錄", "請選擇輸出目錄")returnif not os.path.exists(output_folder):try:os.makedirs(output_folder)except Exception as e:messagebox.showerror("錯誤", f"無法創建輸出目錄: {str(e)}")return# 禁用控制按鈕self.convert_button.config(state=tk.DISABLED)self.browse_button.config(state=tk.DISABLED)self.output_button.config(state=tk.DISABLED)self.batch_button.config(state=tk.DISABLED)self.clear_button.config(state=tk.DISABLED)self.play_button.config(state=tk.DISABLED)# 重置進度條self.progress_var.set(0)self.converting = Trueself.cancel_requested = False# 在后臺線程中運行轉換threading.Thread(target=self.convert_files, args=(output_folder,), daemon=True).start()def convert_files(self, output_folder):total_files = len(self.files_to_convert)success_count = 0failed_files = []for i, file_path in enumerate(self.files_to_convert):if self.cancel_requested:breakself.status_var.set(f"正在轉換: {os.path.basename(file_path)} ({i + 1}/{total_files})")self.progress_var.set(i / total_files * 100)try:#引入ffmpeg工具路徑,非全局變量,如果為全局變量,則下方三行代碼可注掉base_dir = os.path.dirname(os.path.abspath(__file__))ffmpeg_path = os.path.join(base_dir, "ffmpeg", "bin", "ffmpeg.exe") AudioSegment.converter = ffmpeg_pathsound = AudioSegment.from_file(file_path)output_format = self.format_var.get()output_path = os.path.join(output_folder,os.path.splitext(os.path.basename(file_path))[0] + '.' + output_format)sound.export(output_path, format=output_format)success_count += 1except Exception as e:failed_files.append(os.path.basename(file_path))print(f"轉換失敗: {str(e)}")# 更新進度self.progress_var.set((i + 1) / total_files * 100)# 更新UIself.root.after(0, self.conversion_completed, success_count, total_files, failed_files)def conversion_completed(self, success_count, total_files, failed_files):self.converting = Falseself.progress_var.set(100)# 啟用按鈕self.convert_button.config(state=tk.NORMAL)self.browse_button.config(state=tk.NORMAL)self.output_button.config(state=tk.NORMAL)self.batch_button.config(state=tk.NORMAL)self.clear_button.config(state=tk.NORMAL)self.play_button.config(state=tk.NORMAL)# 顯示結果if success_count == total_files:self.status_var.set(f"轉換完成! 成功轉換 {success_count} 個文件")messagebox.showinfo("完成", f"成功轉換 {success_count} 個文件")else:self.status_var.set(f"轉換完成! 成功: {success_count}, 失敗: {total_files - success_count}")if failed_files:failed_list = "\n".join(failed_files)messagebox.showwarning("部分完成",f"成功轉換 {success_count} 個文件\n失敗 {len(failed_files)} 個文件:\n{failed_list}")def cancel_conversion(self):if self.converting:self.cancel_requested = Trueself.status_var.set("正在取消轉換...")else:self.root.destroy()def play_audio(self):if not self.files_to_convert:messagebox.showwarning("無文件", "沒有可播放的音頻文件")return# 獲取選中的文件selected_indices = self.file_list.curselection()if not selected_indices:messagebox.showwarning("未選擇", "請先選擇一個音頻文件")returnselected_index = selected_indices[0]if 0 <= selected_index < len(self.files_to_convert):file_path = self.files_to_convert[selected_index]try:# 停止當前播放if mixer.music.get_busy():mixer.music.stop()# 加載并播放音頻mixer.music.load(file_path)mixer.music.play()self.status_var.set(f"正在播放: {os.path.basename(file_path)}")except Exception as e:messagebox.showerror("播放錯誤", f"無法播放音頻文件: {str(e)}")if __name__ == "__main__":root = tk.Tk()app = AudioConverterApp(root)root.mainloop()
UI界面效果: