123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- import tkinter as tk
- from tkinter import ttk, filedialog, messagebox
- import os
- import threading
- import sys
- from convert_audio_to_p3 import encode_audio_to_opus
- from convert_p3_to_audio import decode_p3_to_audio
- class AudioConverterApp:
- def __init__(self, master):
- self.master = master
- master.title("音频/P3 批量转换工具")
- master.geometry("680x600") # 调整窗口高度
- # 初始化变量
- self.mode = tk.StringVar(value="audio_to_p3")
- self.output_dir = tk.StringVar()
- self.output_dir.set(os.path.abspath("output"))
- self.enable_loudnorm = tk.BooleanVar(value=True)
- self.target_lufs = tk.DoubleVar(value=-16.0)
- # 创建UI组件
- self.create_widgets()
- self.redirect_output()
- def create_widgets(self):
- # 模式选择
- mode_frame = ttk.LabelFrame(self.master, text="转换模式")
- mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
-
- ttk.Radiobutton(mode_frame, text="音频转P3", variable=self.mode,
- value="audio_to_p3", command=self.toggle_settings,
- width=12).grid(row=0, column=0, padx=5)
- ttk.Radiobutton(mode_frame, text="P3转音频", variable=self.mode,
- value="p3_to_audio", command=self.toggle_settings,
- width=12).grid(row=0, column=1, padx=5)
- # 响度设置
- self.loudnorm_frame = ttk.Frame(self.master)
- self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
-
- ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整",
- variable=self.enable_loudnorm, width=15
- ).grid(row=0, column=0, padx=2)
- ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs,
- width=6).grid(row=0, column=1, padx=2)
- ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2)
- # 文件选择
- file_frame = ttk.LabelFrame(self.master, text="输入文件")
- file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
-
- # 文件操作按钮
- ttk.Button(file_frame, text="选择文件", command=self.select_files,
- width=12).grid(row=0, column=0, padx=5, pady=2)
- ttk.Button(file_frame, text="移除选中", command=self.remove_selected,
- width=12).grid(row=0, column=1, padx=5, pady=2)
- ttk.Button(file_frame, text="清空列表", command=self.clear_files,
- width=12).grid(row=0, column=2, padx=5, pady=2)
- # 文件列表(使用Treeview)
- self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
- show="headings", height=8)
- self.tree.heading("selected", text="选中", anchor=tk.W)
- self.tree.heading("filename", text="文件名", anchor=tk.W)
- self.tree.column("selected", width=60, anchor=tk.W)
- self.tree.column("filename", width=600, anchor=tk.W)
- self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2)
- self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
- # 输出目录
- output_frame = ttk.LabelFrame(self.master, text="输出目录")
- output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
-
- ttk.Entry(output_frame, textvariable=self.output_dir, width=60
- ).grid(row=0, column=0, padx=5, sticky="ew")
- ttk.Button(output_frame, text="浏览", command=self.select_output_dir,
- width=8).grid(row=0, column=1, padx=5)
- # 转换按钮区域
- button_frame = ttk.Frame(self.master)
- button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew")
-
- ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True),
- width=15).pack(side=tk.LEFT, padx=5)
- ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False),
- width=15).pack(side=tk.LEFT, padx=5)
- # 日志区域
- log_frame = ttk.LabelFrame(self.master, text="日志")
- log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
-
- self.log_text = tk.Text(log_frame, height=14, width=80)
- self.log_text.pack(fill=tk.BOTH, expand=True)
- # 配置布局权重
- self.master.columnconfigure(0, weight=1)
- self.master.rowconfigure(2, weight=1)
- self.master.rowconfigure(5, weight=3)
- file_frame.columnconfigure(0, weight=1)
- file_frame.rowconfigure(1, weight=1)
- def toggle_settings(self):
- if self.mode.get() == "audio_to_p3":
- self.loudnorm_frame.grid()
- else:
- self.loudnorm_frame.grid_remove()
- def select_files(self):
- file_types = [
- ("音频文件", "*.wav *.mp3 *.ogg *.flac") if self.mode.get() == "audio_to_p3"
- else ("P3文件", "*.p3")
- ]
-
- files = filedialog.askopenfilenames(filetypes=file_types)
- for f in files:
- self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
- def on_tree_click(self, event):
- """处理复选框点击事件"""
- region = self.tree.identify("region", event.x, event.y)
- if region == "cell":
- col = self.tree.identify_column(event.x)
- item = self.tree.identify_row(event.y)
- if col == "#1": # 点击的是选中列
- current_val = self.tree.item(item, "values")[0]
- new_val = "[√]" if current_val == "[ ]" else "[ ]"
- self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
- def remove_selected(self):
- """移除选中的文件"""
- to_remove = []
- for item in self.tree.get_children():
- if self.tree.item(item, "values")[0] == "[√]":
- to_remove.append(item)
- for item in reversed(to_remove):
- self.tree.delete(item)
- def clear_files(self):
- """清空所有文件"""
- for item in self.tree.get_children():
- self.tree.delete(item)
- def select_output_dir(self):
- path = filedialog.askdirectory()
- if path:
- self.output_dir.set(path)
- def redirect_output(self):
- class StdoutRedirector:
- def __init__(self, text_widget):
- self.text_widget = text_widget
- self.original_stdout = sys.stdout
- def write(self, message):
- self.text_widget.insert(tk.END, message)
- self.text_widget.see(tk.END)
- self.original_stdout.write(message)
- def flush(self):
- self.original_stdout.flush()
- sys.stdout = StdoutRedirector(self.log_text)
- def start_conversion(self, convert_all):
- """开始转换"""
- input_files = []
- for item in self.tree.get_children():
- if convert_all or self.tree.item(item, "values")[0] == "[√]":
- input_files.append(self.tree.item(item, "tags")[0])
-
- if not input_files:
- msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件"
- messagebox.showwarning("警告", msg)
- return
-
- os.makedirs(self.output_dir.get(), exist_ok=True)
- try:
- if self.mode.get() == "audio_to_p3":
- target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None
- thread = threading.Thread(target=self.convert_audio_to_p3, args=(target_lufs, input_files))
- else:
- thread = threading.Thread(target=self.convert_p3_to_audio, args=(input_files,))
-
- thread.start()
- except Exception as e:
- print(f"转换初始化失败: {str(e)}")
- def convert_audio_to_p3(self, target_lufs, input_files):
- """音频转P3转换逻辑"""
- for input_path in input_files:
- try:
- filename = os.path.basename(input_path)
- base_name = os.path.splitext(filename)[0]
- output_path = os.path.join(self.output_dir.get(), f"{base_name}.p3")
-
- print(f"正在转换: {filename}")
- encode_audio_to_opus(input_path, output_path, target_lufs)
- print(f"转换成功: {filename}\n")
- except Exception as e:
- print(f"转换失败: {str(e)}\n")
- def convert_p3_to_audio(self, input_files):
- """P3转音频转换逻辑"""
- for input_path in input_files:
- try:
- filename = os.path.basename(input_path)
- base_name = os.path.splitext(filename)[0]
- output_path = os.path.join(self.output_dir.get(), f"{base_name}.wav")
-
- print(f"正在转换: {filename}")
- decode_p3_to_audio(input_path, output_path)
- print(f"转换成功: {filename}\n")
- except Exception as e:
- print(f"转换失败: {str(e)}\n")
- if __name__ == "__main__":
- root = tk.Tk()
- app = AudioConverterApp(root)
- root.mainloop()
|