batch_convert_gui.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import tkinter as tk
  2. from tkinter import ttk, filedialog, messagebox
  3. import os
  4. import threading
  5. import sys
  6. from convert_audio_to_p3 import encode_audio_to_opus
  7. from convert_p3_to_audio import decode_p3_to_audio
  8. class AudioConverterApp:
  9. def __init__(self, master):
  10. self.master = master
  11. master.title("音频/P3 批量转换工具")
  12. master.geometry("680x600") # 调整窗口高度
  13. # 初始化变量
  14. self.mode = tk.StringVar(value="audio_to_p3")
  15. self.output_dir = tk.StringVar()
  16. self.output_dir.set(os.path.abspath("output"))
  17. self.enable_loudnorm = tk.BooleanVar(value=True)
  18. self.target_lufs = tk.DoubleVar(value=-16.0)
  19. # 创建UI组件
  20. self.create_widgets()
  21. self.redirect_output()
  22. def create_widgets(self):
  23. # 模式选择
  24. mode_frame = ttk.LabelFrame(self.master, text="转换模式")
  25. mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
  26. ttk.Radiobutton(mode_frame, text="音频转P3", variable=self.mode,
  27. value="audio_to_p3", command=self.toggle_settings,
  28. width=12).grid(row=0, column=0, padx=5)
  29. ttk.Radiobutton(mode_frame, text="P3转音频", variable=self.mode,
  30. value="p3_to_audio", command=self.toggle_settings,
  31. width=12).grid(row=0, column=1, padx=5)
  32. # 响度设置
  33. self.loudnorm_frame = ttk.Frame(self.master)
  34. self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
  35. ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整",
  36. variable=self.enable_loudnorm, width=15
  37. ).grid(row=0, column=0, padx=2)
  38. ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs,
  39. width=6).grid(row=0, column=1, padx=2)
  40. ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2)
  41. # 文件选择
  42. file_frame = ttk.LabelFrame(self.master, text="输入文件")
  43. file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
  44. # 文件操作按钮
  45. ttk.Button(file_frame, text="选择文件", command=self.select_files,
  46. width=12).grid(row=0, column=0, padx=5, pady=2)
  47. ttk.Button(file_frame, text="移除选中", command=self.remove_selected,
  48. width=12).grid(row=0, column=1, padx=5, pady=2)
  49. ttk.Button(file_frame, text="清空列表", command=self.clear_files,
  50. width=12).grid(row=0, column=2, padx=5, pady=2)
  51. # 文件列表(使用Treeview)
  52. self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
  53. show="headings", height=8)
  54. self.tree.heading("selected", text="选中", anchor=tk.W)
  55. self.tree.heading("filename", text="文件名", anchor=tk.W)
  56. self.tree.column("selected", width=60, anchor=tk.W)
  57. self.tree.column("filename", width=600, anchor=tk.W)
  58. self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2)
  59. self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
  60. # 输出目录
  61. output_frame = ttk.LabelFrame(self.master, text="输出目录")
  62. output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
  63. ttk.Entry(output_frame, textvariable=self.output_dir, width=60
  64. ).grid(row=0, column=0, padx=5, sticky="ew")
  65. ttk.Button(output_frame, text="浏览", command=self.select_output_dir,
  66. width=8).grid(row=0, column=1, padx=5)
  67. # 转换按钮区域
  68. button_frame = ttk.Frame(self.master)
  69. button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew")
  70. ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True),
  71. width=15).pack(side=tk.LEFT, padx=5)
  72. ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False),
  73. width=15).pack(side=tk.LEFT, padx=5)
  74. # 日志区域
  75. log_frame = ttk.LabelFrame(self.master, text="日志")
  76. log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
  77. self.log_text = tk.Text(log_frame, height=14, width=80)
  78. self.log_text.pack(fill=tk.BOTH, expand=True)
  79. # 配置布局权重
  80. self.master.columnconfigure(0, weight=1)
  81. self.master.rowconfigure(2, weight=1)
  82. self.master.rowconfigure(5, weight=3)
  83. file_frame.columnconfigure(0, weight=1)
  84. file_frame.rowconfigure(1, weight=1)
  85. def toggle_settings(self):
  86. if self.mode.get() == "audio_to_p3":
  87. self.loudnorm_frame.grid()
  88. else:
  89. self.loudnorm_frame.grid_remove()
  90. def select_files(self):
  91. file_types = [
  92. ("音频文件", "*.wav *.mp3 *.ogg *.flac") if self.mode.get() == "audio_to_p3"
  93. else ("P3文件", "*.p3")
  94. ]
  95. files = filedialog.askopenfilenames(filetypes=file_types)
  96. for f in files:
  97. self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
  98. def on_tree_click(self, event):
  99. """处理复选框点击事件"""
  100. region = self.tree.identify("region", event.x, event.y)
  101. if region == "cell":
  102. col = self.tree.identify_column(event.x)
  103. item = self.tree.identify_row(event.y)
  104. if col == "#1": # 点击的是选中列
  105. current_val = self.tree.item(item, "values")[0]
  106. new_val = "[√]" if current_val == "[ ]" else "[ ]"
  107. self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
  108. def remove_selected(self):
  109. """移除选中的文件"""
  110. to_remove = []
  111. for item in self.tree.get_children():
  112. if self.tree.item(item, "values")[0] == "[√]":
  113. to_remove.append(item)
  114. for item in reversed(to_remove):
  115. self.tree.delete(item)
  116. def clear_files(self):
  117. """清空所有文件"""
  118. for item in self.tree.get_children():
  119. self.tree.delete(item)
  120. def select_output_dir(self):
  121. path = filedialog.askdirectory()
  122. if path:
  123. self.output_dir.set(path)
  124. def redirect_output(self):
  125. class StdoutRedirector:
  126. def __init__(self, text_widget):
  127. self.text_widget = text_widget
  128. self.original_stdout = sys.stdout
  129. def write(self, message):
  130. self.text_widget.insert(tk.END, message)
  131. self.text_widget.see(tk.END)
  132. self.original_stdout.write(message)
  133. def flush(self):
  134. self.original_stdout.flush()
  135. sys.stdout = StdoutRedirector(self.log_text)
  136. def start_conversion(self, convert_all):
  137. """开始转换"""
  138. input_files = []
  139. for item in self.tree.get_children():
  140. if convert_all or self.tree.item(item, "values")[0] == "[√]":
  141. input_files.append(self.tree.item(item, "tags")[0])
  142. if not input_files:
  143. msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件"
  144. messagebox.showwarning("警告", msg)
  145. return
  146. os.makedirs(self.output_dir.get(), exist_ok=True)
  147. try:
  148. if self.mode.get() == "audio_to_p3":
  149. target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None
  150. thread = threading.Thread(target=self.convert_audio_to_p3, args=(target_lufs, input_files))
  151. else:
  152. thread = threading.Thread(target=self.convert_p3_to_audio, args=(input_files,))
  153. thread.start()
  154. except Exception as e:
  155. print(f"转换初始化失败: {str(e)}")
  156. def convert_audio_to_p3(self, target_lufs, input_files):
  157. """音频转P3转换逻辑"""
  158. for input_path in input_files:
  159. try:
  160. filename = os.path.basename(input_path)
  161. base_name = os.path.splitext(filename)[0]
  162. output_path = os.path.join(self.output_dir.get(), f"{base_name}.p3")
  163. print(f"正在转换: {filename}")
  164. encode_audio_to_opus(input_path, output_path, target_lufs)
  165. print(f"转换成功: {filename}\n")
  166. except Exception as e:
  167. print(f"转换失败: {str(e)}\n")
  168. def convert_p3_to_audio(self, input_files):
  169. """P3转音频转换逻辑"""
  170. for input_path in input_files:
  171. try:
  172. filename = os.path.basename(input_path)
  173. base_name = os.path.splitext(filename)[0]
  174. output_path = os.path.join(self.output_dir.get(), f"{base_name}.wav")
  175. print(f"正在转换: {filename}")
  176. decode_p3_to_audio(input_path, output_path)
  177. print(f"转换成功: {filename}\n")
  178. except Exception as e:
  179. print(f"转换失败: {str(e)}\n")
  180. if __name__ == "__main__":
  181. root = tk.Tk()
  182. app = AudioConverterApp(root)
  183. root.mainloop()