diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 765a8d8..cdc3c17 100644 Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ diff --git a/build/main/Analysis-00.toc b/build/main/Analysis-00.toc index f465d4c..95676c2 100644 --- a/build/main/Analysis-00.toc +++ b/build/main/Analysis-00.toc @@ -380,18 +380,6 @@ ('pathlib._abc', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\_abc.py', 'PYMODULE'), - ('json', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\__init__.py', - 'PYMODULE'), - ('json.encoder', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\encoder.py', - 'PYMODULE'), - ('json.decoder', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\decoder.py', - 'PYMODULE'), - ('json.scanner', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\scanner.py', - 'PYMODULE'), ('__future__', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\__future__.py', 'PYMODULE'), @@ -503,18 +491,18 @@ ('multiprocessing', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\multiprocessing\\__init__.py', 'PYMODULE'), - ('_colorize', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_colorize.py', - 'PYMODULE'), - ('_py_abc', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_py_abc.py', - 'PYMODULE'), ('stringprep', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\stringprep.py', 'PYMODULE'), ('tracemalloc', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tracemalloc.py', 'PYMODULE'), + ('_colorize', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_colorize.py', + 'PYMODULE'), + ('_py_abc', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_py_abc.py', + 'PYMODULE'), ('wx.media', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\media.py', 'PYMODULE'), @@ -670,6 +658,18 @@ 'PYMODULE'), ('_threading_local', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_threading_local.py', + 'PYMODULE'), + ('json', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\__init__.py', + 'PYMODULE'), + ('json.encoder', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\encoder.py', + 'PYMODULE'), + ('json.decoder', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\decoder.py', + 'PYMODULE'), + ('json.scanner', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\scanner.py', 'PYMODULE')], [('python313.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\python313.dll', @@ -749,82 +749,34 @@ ('wx\\wxmsw32u_core_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_core_vc140_x64.dll', 'BINARY'), - ('wx\\wxbase32u_vc140_x64.dll', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_vc140_x64.dll', - 'BINARY'), ('VCRUNTIME140_1.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\VCRUNTIME140_1.dll', 'BINARY'), + ('wx\\wxbase32u_vc140_x64.dll', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_vc140_x64.dll', + 'BINARY'), ('wx\\wxmsw32u_media_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_media_vc140_x64.dll', 'BINARY'), ('wx\\wxmsw32u_html_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_html_vc140_x64.dll', 'BINARY'), - ('MSVCP140.dll', 'C:\\Windows\\System32\\MSVCP140.dll', 'BINARY'), ('wx\\wxbase32u_net_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_net_vc140_x64.dll', - 'BINARY')], + 'BINARY'), + ('MSVCP140.dll', 'C:\\Windows\\System32\\MSVCP140.dll', 'BINARY')], [], [], [('base_library.zip', 'D:\\audio\\build\\main\\base_library.zip', 'DATA')], - [('posixpath', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\posixpath.py', + [('io', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\io.py', 'PYMODULE'), - ('copyreg', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\copyreg.py', - 'PYMODULE'), - ('reprlib', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\reprlib.py', - 'PYMODULE'), - ('linecache', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\linecache.py', - 'PYMODULE'), - ('sre_constants', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_constants.py', - 'PYMODULE'), - ('_collections_abc', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_collections_abc.py', + ('operator', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\operator.py', 'PYMODULE'), ('types', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\types.py', 'PYMODULE'), - ('genericpath', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\genericpath.py', - 'PYMODULE'), - ('traceback', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\traceback.py', - 'PYMODULE'), - ('codecs', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\codecs.py', - 'PYMODULE'), - ('_weakrefset', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_weakrefset.py', - 'PYMODULE'), - ('ntpath', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ntpath.py', - 'PYMODULE'), - ('io', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\io.py', - 'PYMODULE'), - ('abc', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\abc.py', - 'PYMODULE'), - ('functools', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\functools.py', - 'PYMODULE'), - ('locale', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\locale.py', - 'PYMODULE'), - ('collections.abc', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\collections\\abc.py', - 'PYMODULE'), - ('collections', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\collections\\__init__.py', - 'PYMODULE'), - ('sre_parse', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_parse.py', - 'PYMODULE'), ('encodings.zlib_codec', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\zlib_codec.py', 'PYMODULE'), @@ -1191,18 +1143,51 @@ ('encodings', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\__init__.py', 'PYMODULE'), - ('sre_compile', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_compile.py', + ('sre_constants', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_constants.py', 'PYMODULE'), - ('weakref', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\weakref.py', + ('collections.abc', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\collections\\abc.py', 'PYMODULE'), - ('heapq', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\heapq.py', + ('collections', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\collections\\__init__.py', + 'PYMODULE'), + ('keyword', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\keyword.py', + 'PYMODULE'), + ('genericpath', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\genericpath.py', 'PYMODULE'), ('enum', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\enum.py', 'PYMODULE'), + ('reprlib', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\reprlib.py', + 'PYMODULE'), + ('_collections_abc', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_collections_abc.py', + 'PYMODULE'), + ('warnings', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\warnings.py', + 'PYMODULE'), + ('linecache', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\linecache.py', + 'PYMODULE'), + ('posixpath', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\posixpath.py', + 'PYMODULE'), + ('functools', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\functools.py', + 'PYMODULE'), + ('codecs', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\codecs.py', + 'PYMODULE'), + ('copyreg', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\copyreg.py', + 'PYMODULE'), + ('traceback', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\traceback.py', + 'PYMODULE'), ('re._parser', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\_parser.py', 'PYMODULE'), @@ -1218,17 +1203,32 @@ ('re', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\__init__.py', 'PYMODULE'), - ('keyword', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\keyword.py', + ('_weakrefset', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_weakrefset.py', + 'PYMODULE'), + ('abc', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\abc.py', + 'PYMODULE'), + ('locale', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\locale.py', + 'PYMODULE'), + ('weakref', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\weakref.py', 'PYMODULE'), ('stat', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\stat.py', 'PYMODULE'), - ('operator', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\operator.py', + ('ntpath', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ntpath.py', 'PYMODULE'), - ('warnings', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\warnings.py', + ('sre_parse', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_parse.py', + 'PYMODULE'), + ('sre_compile', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_compile.py', + 'PYMODULE'), + ('heapq', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\heapq.py', 'PYMODULE'), ('os', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\os.py', diff --git a/build/main/EXE-00.toc b/build/main/EXE-00.toc index ea3ffa1..33c1106 100644 --- a/build/main/EXE-00.toc +++ b/build/main/EXE-00.toc @@ -136,27 +136,27 @@ ('wx\\wxmsw32u_core_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_core_vc140_x64.dll', 'BINARY'), - ('wx\\wxbase32u_vc140_x64.dll', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_vc140_x64.dll', - 'BINARY'), ('VCRUNTIME140_1.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\VCRUNTIME140_1.dll', 'BINARY'), + ('wx\\wxbase32u_vc140_x64.dll', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_vc140_x64.dll', + 'BINARY'), ('wx\\wxmsw32u_media_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_media_vc140_x64.dll', 'BINARY'), ('wx\\wxmsw32u_html_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_html_vc140_x64.dll', 'BINARY'), - ('MSVCP140.dll', 'C:\\Windows\\System32\\MSVCP140.dll', 'BINARY'), ('wx\\wxbase32u_net_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_net_vc140_x64.dll', 'BINARY'), + ('MSVCP140.dll', 'C:\\Windows\\System32\\MSVCP140.dll', 'BINARY'), ('base_library.zip', 'D:\\audio\\build\\main\\base_library.zip', 'DATA')], [], False, False, - 1770832619, + 1770922631, [('run.exe', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\run.exe', 'EXECUTABLE')], diff --git a/build/main/PKG-00.toc b/build/main/PKG-00.toc index 235ee59..c0ae411 100644 --- a/build/main/PKG-00.toc +++ b/build/main/PKG-00.toc @@ -114,22 +114,22 @@ ('wx\\wxmsw32u_core_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_core_vc140_x64.dll', 'BINARY'), - ('wx\\wxbase32u_vc140_x64.dll', - 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_vc140_x64.dll', - 'BINARY'), ('VCRUNTIME140_1.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\VCRUNTIME140_1.dll', 'BINARY'), + ('wx\\wxbase32u_vc140_x64.dll', + 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_vc140_x64.dll', + 'BINARY'), ('wx\\wxmsw32u_media_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_media_vc140_x64.dll', 'BINARY'), ('wx\\wxmsw32u_html_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxmsw32u_html_vc140_x64.dll', 'BINARY'), - ('MSVCP140.dll', 'C:\\Windows\\System32\\MSVCP140.dll', 'BINARY'), ('wx\\wxbase32u_net_vc140_x64.dll', 'C:\\Users\\aaron\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\wx\\wxbase32u_net_vc140_x64.dll', 'BINARY'), + ('MSVCP140.dll', 'C:\\Windows\\System32\\MSVCP140.dll', 'BINARY'), ('base_library.zip', 'D:\\audio\\build\\main\\base_library.zip', 'DATA')], 'python313.dll', False, diff --git a/build/main/base_library.zip b/build/main/base_library.zip index 4b4bf8c..d0689c0 100644 Binary files a/build/main/base_library.zip and b/build/main/base_library.zip differ diff --git a/build/main/main.pkg b/build/main/main.pkg index 62c5f5b..5a0f730 100644 Binary files a/build/main/main.pkg and b/build/main/main.pkg differ diff --git a/build/main/xref-main.html b/build/main/xref-main.html index e99529c..2dd644d 100644 --- a/build/main/xref-main.html +++ b/build/main/xref-main.html @@ -152,6 +152,7 @@ imports: • genericpathheapqio + • jsonkeywordlinecachelocale @@ -7206,6 +7207,7 @@ imported by: • json.decoderjson.encoderjson.scanner + • main.py diff --git a/dist/main.exe b/dist/main.exe index da3f3a5..5d00521 100644 Binary files a/dist/main.exe and b/dist/main.exe differ diff --git a/main.py b/main.py index 4a114f2..1d03de2 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ import subprocess import wx import wx.media -AUDIO_FORMATS = ["mp3", "wav", "aac", "flac", "ogg", "m4a"] +AUDIO_FORMATS = ["mp3", "wav", "aac", "flac", "ogg", "m4a", "m4b"] VIDEO_FORMATS = ["mp4", "mkv", "mov", "webm"] @@ -41,6 +41,10 @@ class ConverterFrame(wx.Frame): self.log_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY) self.status = wx.StaticText(panel, label="Ready") self.status.SetForegroundColour(label_color) + self.progress_gauge = wx.Gauge(panel, range=1000, style=wx.GA_HORIZONTAL) + self.progress_gauge.SetValue(0) + self.progress_label = wx.StaticText(panel, label="Progress") + self.progress_label.SetForegroundColour(label_color) self.media = wx.media.MediaCtrl(panel, style=wx.SIMPLE_BORDER) self.play_btn = wx.Button(panel, label="&Play/Pause") @@ -101,6 +105,8 @@ class ConverterFrame(wx.Frame): main.Add(form, 0, wx.EXPAND | wx.ALL, 10) main.Add(self.start_btn, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) main.Add(self.build_player(panel), 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) + main.Add(self.progress_label, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 4) + main.Add(self.progress_gauge, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) main.Add(self.log_ctrl, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) main.Add(self.status, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) @@ -356,6 +362,7 @@ class ConverterFrame(wx.Frame): self.log_ctrl.Clear() self.status.SetLabel("Running...") self.start_btn.Disable() + self.reset_progress() thread = threading.Thread(target=self.run_ffmpeg, args=(in_path, out_path), daemon=True) thread.start() @@ -370,9 +377,20 @@ class ConverterFrame(wx.Frame): output_format = self.format_choice.GetStringSelection().lower() should_split_mp3 = split_mp3 and output_format == "mp3" + duration = self.get_media_duration(in_path) + self.prepare_progress(duration) + cmd = ["ffmpeg", "-y"] + cmd += ["-progress", "pipe:1", "-nostats"] if activation_bytes: cmd += ["-activation_bytes", activation_bytes] + aaxc_key, aaxc_iv = self.get_aaxc_key_iv(in_path) + if aaxc_key and aaxc_iv: + cmd += ["-audible_key", aaxc_key, "-audible_iv", aaxc_iv] + elif os.path.splitext(in_path)[1].lower() == ".aaxc": + self.append_log("Missing or invalid .voucher key/iv for .aaxc file.") + self.finish("Failed") + return cmd += ["-i", in_path] if mode == "Audio": @@ -423,9 +441,14 @@ class ConverterFrame(wx.Frame): universal_newlines=True, ) for line in proc.stdout: - self.append_log(line.rstrip()) + line = line.rstrip() + if self.handle_progress_line(line, duration): + continue + if line: + self.append_log(line) proc.wait() if proc.returncode == 0: + self.set_progress_complete() self.finish("Done") else: self.finish("Failed") @@ -440,6 +463,114 @@ class ConverterFrame(wx.Frame): wx.CallAfter(self.status.SetLabel, status_text) wx.CallAfter(self.start_btn.Enable) + def reset_progress(self): + self.progress_gauge.SetRange(1000) + self.progress_gauge.SetValue(0) + + def prepare_progress(self, duration_seconds): + if duration_seconds and duration_seconds > 0: + wx.CallAfter(self.progress_gauge.SetRange, 1000) + wx.CallAfter(self.progress_gauge.SetValue, 0) + else: + wx.CallAfter(self.progress_gauge.Pulse) + + def set_progress_value(self, ratio): + ratio = max(0.0, min(1.0, ratio)) + value = int(ratio * 1000) + wx.CallAfter(self.progress_gauge.SetValue, value) + + def set_progress_complete(self): + wx.CallAfter(self.progress_gauge.SetValue, 1000) + + def handle_progress_line(self, line, duration_seconds): + if not line: + return False + if line.startswith("out_time_ms="): + if duration_seconds and duration_seconds > 0: + try: + out_ms = int(line.split("=", 1)[1].strip()) + ratio = out_ms / (duration_seconds * 1_000_000) + self.set_progress_value(ratio) + except (ValueError, ZeroDivisionError): + pass + else: + wx.CallAfter(self.progress_gauge.Pulse) + return True + if line.startswith("progress="): + if line.split("=", 1)[1].strip() == "end": + self.set_progress_complete() + return True + return False + + def get_media_duration(self, in_path): + if not shutil.which("ffprobe"): + return 0 + cmd = [ + "ffprobe", + "-v", + "error", + "-show_entries", + "format=duration", + "-of", + "default=nk=1:nw=1", + in_path, + ] + try: + proc = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + if proc.returncode != 0: + return 0 + value = (proc.stdout or "").strip() + return float(value) if value else 0 + except Exception: + return 0 + + def get_aaxc_key_iv(self, in_path): + if os.path.splitext(in_path)[1].lower() != ".aaxc": + return None, None + voucher_path = os.path.splitext(in_path)[0] + ".voucher" + if not os.path.isfile(voucher_path): + return None, None + try: + with open(voucher_path, "r", encoding="utf-8") as handle: + data = json.load(handle) + except Exception: + return None, None + if not isinstance(data, dict): + return None, None + + # Support multiple voucher shapes: + # - content_license.license_response (current Audible export shape) + # - lilicense_response (legacy/typo compatibility) + # - license_response (flat shape fallback) + license_resp = None + content_license = data.get("content_license") + if isinstance(content_license, dict): + candidate = content_license.get("license_response") + if isinstance(candidate, dict): + license_resp = candidate + if license_resp is None: + candidate = data.get("lilicense_response") + if isinstance(candidate, dict): + license_resp = candidate + if license_resp is None: + candidate = data.get("license_response") + if isinstance(candidate, dict): + license_resp = candidate + if not isinstance(license_resp, dict): + return None, None + + key = license_resp.get("key") + iv = license_resp.get("iv") + if not key or not iv: + return None, None + return str(key), str(iv) + def build_capture_output_pattern(self, out_path): base, ext = os.path.splitext(out_path) return f"{base}_capture_%03d{ext}"