我们将使用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 = root
self.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 = False
self.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("无文件", "请先添加要转换的音频文件")
return
output_folder = self.output_entry.get().strip()
if not output_folder:
messagebox.showwarning("无输出目录", "请选择输出目录")
return
if 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 = True
self.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 = 0
failed_files = []
for i, file_path in enumerate(self.files_to_convert):
if self.cancel_requested:
break
self.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_path
sound = 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 += 1
except Exception as e:
failed_files.append(os.path.basename(file_path))
print(f"转换失败: {str(e)}")
# 更新进度
self.progress_var.set((i + 1) / total_files * 100)
# 更新UI
self.root.after(0, self.conversion_completed, success_count, total_files, failed_files)
def conversion_completed(self, success_count, total_files, failed_files):
self.converting = False
self.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 = True
self.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("未选择", "请先选择一个音频文件")
return
selected_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界面效果: