import os import threading import shutil import subprocess import wx AUDIO_FORMATS = ["mp3", "wav", "aac", "flac", "ogg", "m4a"] VIDEO_FORMATS = ["mp4", "mkv", "mov", "webm"] class ConverterFrame(wx.Frame): def __init__(self): super().__init__(parent=None, title="Audio/Video Converter", size=(720, 460)) panel = wx.Panel(self) self.input_picker = wx.FilePickerCtrl(panel, message="Select input file") self.mode_choice = wx.Choice(panel, choices=["Audio", "Video"]) self.mode_choice.SetSelection(0) self.format_choice = wx.Choice(panel, choices=AUDIO_FORMATS) self.format_choice.SetSelection(0) self.output_picker = wx.FilePickerCtrl( panel, message="Select output file", style=wx.FLP_SAVE | wx.FLP_OVERWRITE_PROMPT, ) self.audio_bitrate = wx.TextCtrl(panel, value="", size=(120, -1)) self.video_bitrate = wx.TextCtrl(panel, value="", size=(120, -1)) self.copy_streams = wx.CheckBox(panel, label="Copy streams (fast, no re-encode)") self.start_btn = wx.Button(panel, label="Convert") self.log_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY) self.status = wx.StaticText(panel, label="Ready") self.audio_bitrate.SetHint("e.g. 192k") self.video_bitrate.SetHint("e.g. 2000k") form = wx.FlexGridSizer(cols=3, hgap=8, vgap=8) form.Add(wx.StaticText(panel, label="Input"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(self.input_picker, 1, wx.EXPAND) form.Add((1, 1)) form.Add(wx.StaticText(panel, label="Mode"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(self.mode_choice, 0) form.Add((1, 1)) form.Add(wx.StaticText(panel, label="Format"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(self.format_choice, 0) form.Add((1, 1)) form.Add(wx.StaticText(panel, label="Output"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(self.output_picker, 1, wx.EXPAND) form.Add((1, 1)) form.Add(wx.StaticText(panel, label="Audio bitrate"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(self.audio_bitrate, 0) form.Add(wx.StaticText(panel, label="Optional"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(wx.StaticText(panel, label="Video bitrate"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(self.video_bitrate, 0) form.Add(wx.StaticText(panel, label="Optional"), 0, wx.ALIGN_CENTER_VERTICAL) form.Add(wx.StaticText(panel, label=""), 0) form.Add(self.copy_streams, 0) form.Add((1, 1)) form.AddGrowableCol(1, 1) main = wx.BoxSizer(wx.VERTICAL) main.Add(form, 0, wx.EXPAND | wx.ALL, 12) main.Add(self.start_btn, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 12) main.Add(self.log_ctrl, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 12) main.Add(self.status, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 12) panel.SetSizer(main) self.mode_choice.Bind(wx.EVT_CHOICE, self.on_mode_change) self.format_choice.Bind(wx.EVT_CHOICE, self.on_format_change) self.input_picker.Bind(wx.EVT_FILEPICKER_CHANGED, self.on_input_change) self.start_btn.Bind(wx.EVT_BUTTON, self.on_start) self.update_format_choices() self.Center() def on_mode_change(self, _event): self.update_format_choices() self.suggest_output_path() def on_format_change(self, _event): self.suggest_output_path() def on_input_change(self, _event): self.suggest_output_path() def update_format_choices(self): mode = self.mode_choice.GetStringSelection() if mode == "Audio": choices = AUDIO_FORMATS else: choices = VIDEO_FORMATS self.format_choice.Set(choices) self.format_choice.SetSelection(0) def suggest_output_path(self): in_path = self.input_picker.GetPath() if not in_path: return out_ext = self.format_choice.GetStringSelection() base, _ = os.path.splitext(in_path) suggested = base + "." + out_ext self.output_picker.SetPath(suggested) def on_start(self, _event): if not shutil.which("ffmpeg"): wx.MessageBox("ffmpeg not found on PATH.", "Error", wx.ICON_ERROR) return in_path = self.input_picker.GetPath() out_path = self.output_picker.GetPath() if not in_path or not out_path: wx.MessageBox("Please choose input and output files.", "Error", wx.ICON_ERROR) return self.log_ctrl.Clear() self.status.SetLabel("Running...") self.start_btn.Disable() thread = threading.Thread(target=self.run_ffmpeg, args=(in_path, out_path), daemon=True) thread.start() def run_ffmpeg(self, in_path, out_path): mode = self.mode_choice.GetStringSelection() a_bitrate = self.audio_bitrate.GetValue().strip() v_bitrate = self.video_bitrate.GetValue().strip() copy = self.copy_streams.GetValue() cmd = ["ffmpeg", "-y", "-i", in_path] if mode == "Audio": cmd.append("-vn") if copy: cmd += ["-c:a", "copy"] elif a_bitrate: cmd += ["-b:a", a_bitrate] else: if copy: cmd += ["-c:v", "copy", "-c:a", "copy"] else: if v_bitrate: cmd += ["-b:v", v_bitrate] if a_bitrate: cmd += ["-b:a", a_bitrate] cmd.append(out_path) self.append_log("Command: " + " ".join(cmd)) try: proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, ) for line in proc.stdout: self.append_log(line.rstrip()) proc.wait() if proc.returncode == 0: self.finish("Done") else: self.finish("Failed") except Exception as exc: self.append_log(f"Error: {exc}") self.finish("Failed") def append_log(self, text): wx.CallAfter(self.log_ctrl.AppendText, text + "\n") def finish(self, status_text): wx.CallAfter(self.status.SetLabel, status_text) wx.CallAfter(self.start_btn.Enable) class App(wx.App): def OnInit(self): frame = ConverterFrame() frame.Show() return True if __name__ == "__main__": app = App() app.MainLoop()