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:
• genericpath
• heapq
• io
+ • json
• keyword
• linecache
• locale
@@ -7206,6 +7207,7 @@ imported by:
• json.decoder
• json.encoder
• json.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}"