Python+Tkinter制作音频格式转换器

发布于:2025-07-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

        我们将使用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界面效果:


网站公告

今日签到

点亮在社区的每一天
去签到