From ee9deff6fecf1354ea3e529dda3b7b850a81050f Mon Sep 17 00:00:00 2001
From: Chris Pomeroy <chris.pomeroy@hotmail.com>
Date: Sat, 17 Jan 2026 20:24:48 +0000
Subject: [PATCH] Adding back to source control

---
 queryAudiobookServer.py  |   14 
 .gitignore               |    2 
 fix-asin.py              |    6 
 getMissingFromAudible.py |   63 ++++++-
 aaxConvert.py            |  343 ++++++++++++++++++++++++------------------
 5 files changed, 256 insertions(+), 172 deletions(-)

diff --git a/.gitignore b/.gitignore
index 224556d..6c21d68 100755
--- a/.gitignore
+++ b/.gitignore
@@ -46,3 +46,5 @@
 
 .idea
 .idea/*
+audible_auth*
+.devcontainer/
diff --git a/aaxConvert.py b/aaxConvert.py
index 459398c..d3386d0 100755
--- a/aaxConvert.py
+++ b/aaxConvert.py
@@ -4,139 +4,159 @@
 import subprocess
 import json
 import re
-import requests
+import httpx
 import sys
-from queryAudiobookServer import findalbumbyname
 
 # arguments
 # activation_key, file name, codec(default to mp3)
+# Globals
 
-parser = argparse.ArgumentParser()
-parser.add_argument("-s", "--single", help="Use this option to create a single\
-                     file. This is false by default", action="store_true")
-parser.add_argument("-d", "--dpath", help="Use this to set the destination\
-                    path. Otherwise I will use the current directory")
-parser.add_argument("-v", "--verbose", help="Send output to stdout", 
-                    action="store_true")
-parser.add_argument("filename", help="Filename to convert, or directory to\
-                     look in")
-
-args = parser.parse_args()
-
-act_byte = ""
-metadata = ""
+_act_byte = ""
+_metadata = ""
 mode = ""
 stats = ""
-
-if args.dpath:
-    path = args.dpath
-else:
-    path = os.getcwd()
-
-if args.single:
-    mode = 'single'
-else:
-    mode = 'chapter'
-
-if args.verbose:
-    stats = "-stats"
-else:
-    stats = "-nostats"
+path = ""
 
 
-def sendtodiscord(audiofile):
+def get_args():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-s", "--single", help="Use this option to create a\
+                        single file. This is false by default",
+                        action="store_true")
+    parser.add_argument("-d", "--dpath", help="Use this to set the destination\
+                        path. Otherwise I will use the current directory")
+    parser.add_argument("-v", "--verbose", help="Send output to stdout",
+                        action="store_true")
+    parser.add_argument("filename", help="Filename to convert, or directory to\
+                        look in")
+    parser.add_argument("-a", "--actvation_bytes", help='The activate bytes\
+                        provided by audible')
+
+    args = parser.parse_args()
+
+    if args.dpath:
+        path = args.dpath
+    else:
+        path = os.getcwd()
+
+    if args.single:
+        mode = 'single'
+    else:
+        mode = 'chapter'
+
+    if args.verbose:
+        stats = "-stats"
+    else:
+        stats = "-nostats"
+
+    return args
+
+
+def sendtodiscord(message, audiofile):
+    '''
+    Send a message to the discord group
+
+    Arg:
+        message (str):  Message to send.
+        audiofile (str): File name that was processed
+
+    Ret:
+        Boolean
+    '''
     webhookurl = "https://discord.com/api/webhooks/764667082272145418/vorf2JdFG47WAmQP3yZhgHH12wW_qUXG0bS0SG8INLYVwU0HcDFajq9doaDgi_hnI00-"  # noqa E501
     data = {
-        "content": f"There was a problem with file{audiofile}",
+        "content": f"{message}, {audiofile}",
         "username": "Captain Audio",
         }
 
-    with requests.Session() as r:
+    with httpx.Client() as r:
         resp = r.post(webhookurl, json=data)
         if resp.status_code == 204:
             return True
+        else:
+            return False
 
 
-def getmetadata(aaxfile):
-    # Returns the metadata from an aax file
+def get_metadata(aaxfile):
+    # Returns the _metadata from an aax file
     try:
         ret = subprocess.run(["ffprobe", "-v", "info", "-hide_banner",
                               "-show_format", "-show_chapters",
-                              "-print_format", "json", 
+                              "-print_format", "json",
                               os.path.abspath(aaxfile)], capture_output=True)
-
-        mdata = json.loads(ret.stdout)
-        aret = ret.stderr.decode().split('\n')[0]
-        mdata["checksum"] = aret.split()[-1]
-        return mdata
+        if ret.returncode == 0:
+            mdata = json.loads(ret.stdout)
+            aret = ret.stderr.decode().split('\n')[0]
+            print(aret)
+            mdata["checksum"] = aret.split()[-1]
+            return mdata
+        else:
+            sys.stderr.write(ret.stderr)
+            return "None"
     except Exception as err:
-        sys.stderr.write("Error processing metadata: {}\n".format(err))
+        sys.stderr.write("Error processing _metadata: {}\n".format(err))
         return "None"
 
 
-def getmetabitrate():
+def get_bitrate():
     # Return the bitrate of the media
-    bit_rate = metadata['format']['bit_rate']
+    bit_rate = _metadata['format']['bit_rate']
     return bit_rate[:2]
 
 
-def getmetacopyright():
+def get_copyright():
     # Return normalized copyright data
-    copyright = normalize_data(metadata['format']['tags']['copyright'])
+    copyright = normalize_data(_metadata['format']['tags']['copyright'])
     return copyright
 
 
-def getmetadatatags(key):
+def get_metadata_tags(key):
     # get specific data
-    tag = metadata['format']['tags'][key]
+    tag = normalize_data(_metadata['format']['tags'][key])
     return " ".join(tag.split())
 
 
 def normalize_data(data):
     # Return a normalized title
-    pattern = re.compile('[^\p{Latin}]', u'', data)
-    return re.sub(pattern, '', data)
+    data = data.replace("'", "").replace('(', '').replace(')', '').replace(':', '').replace('/', '-').replace('\\', '-').replace('"', '').replace('?', '').replace('!', '').strip()
+    # pattern = re.compile('[^\p{Latin}]', data)
+    return data
 
 
-def reencode(aaxfile, outpath):
+def reencode(aaxfile: str,
+             outpath: str,
+             act_byte: str = None,
+             key_iv: dict = None
+             ):
     # decrypt and reencode to mp3
-    command = (
-        "ffmpeg -loglevel error {} -activation_bytes {} -i"
-        " {} -vn -codec:a libmp3lame -ab {}k -map_metadata -1"
-        "-metadata \"title={}\" -metadata 'artist={}' -metadata"
-        " 'album_artist={}' -metadata \"album={}\" -metadata 'date={}' "
-        "-metadata track=1/1 -metadata 'genre={}' -metadata "
-        "'copyright={}' \"{}\" ").format(
-            stats, act_byte, aaxfile, getmetabitrate(),
-            getmetadatatags('title'), getmetadatatags('artist'),
-            getmetadatatags('album_artist'), getmetadatatags('album'),
-            getmetadatatags('date'), getmetadatatags('genre'),
-            getmetacopyright(), outpath)
-    if args.verbose:
-        print(command)
-        process = subprocess.run(command, shell=True, capture_output=True)
-        # while True:
-        #     output = process.stdout.readline()
-        #     if output == '' and process.poll() is not None:
-        #         break
-        #     if output:
-        #         print(output.strip())
-        rc = process.stdout
-        return rc
+    if get_metadata_tags('major_brand') == 'aaxc':
+        decrypt = f"-audible_key {key_iv['key']} -audible_iv {key_iv['iv']}"
     else:
-        process = subprocess.run(command, shell=True)
-    return
+        decrypt = f"-activation_bytes = {act_byte}"
+    command = (
+        "ffmpeg -loglevel error {} {} -i"
+        " {} -vn -codec:a libmp3lame -qscale:a 2 -map_metadata -1"
+        " -metadata \"title={}\" -metadata 'artist={}' -metadata"
+        " 'album_artist={}' -metadata \"album={}\" -metadata 'date={}' "
+        " -metadata track=1/1 -metadata 'genre={}' -metadata "
+        "'copyright={}' \"{}\" ").format(
+            stats, decrypt, aaxfile,
+            get_metadata_tags('title'), get_metadata_tags('artist'),
+            get_metadata_tags('album_artist'), get_metadata_tags('album'),
+            get_metadata_tags('date'), get_metadata_tags('genre'),
+            get_copyright(), outpath)
+    process = subprocess.run(command, shell=True)
 
 
 def getchaptercount():
     # Get the number of chapters
-    ccount = metadata['chapters']
+    ccount = _metadata['chapters']
     return len(ccount)
 
 
-def getchaptermetadata(cid, key):
-    # get the Chapter metadata
-    for i in metadata['chapters']:
+def get_chapter_metadata(cid, key):
+    # get the Chapter _metadata
+    for i in _metadata['chapters']:
         if i['id'] == cid:
             return i[key]
 
@@ -144,25 +164,12 @@
 def movetochapters(path, outpath, chapter, title, start, end):
     # Creating individual chapters
 
-    outfile = "{}/Ch-{}_{}.mp3".format(
-        outpath, chapter, title.replace(' ', '_')
-    )
+    outfile = "{}/Ch-{} {}.mp3".format(
+        outpath, chapter, title)
     command = "ffmpeg -loglevel error {} -i \"{}\" -ss {} -to {} -codec:a copy -metadata 'track={}' \"{}\"".format(stats, path,
                                                                                                                    start, end,
                                                                                                                    chapter, outfile)
-    if args.verbose:
-        print(command)
-        process = subprocess.run(command, shell=True, capture_output=True)
-        # while True:
-        #     output = process.stdout.readline()
-        #     if output == '' and process.poll() is not None:
-        #         break
-        #     if output:
-        #         print(output.strip())
-        rc = process.poll()
-        return rc
-    else:
-        process = subprocess.run(command, shell=True)
+    subprocess.run(command, shell=True)
     return
 
 
@@ -170,59 +177,99 @@
     # Pull the coverart from the file
     command = "ffmpeg -loglevel error -activation_bytes {} -i \"{}\" -an -codec:v copy \"{}/cover.jpg\"".format(act_byte,
                                                                                                                 path, outpath)
-    if args.verbose:
-        print(command)
-        subprocess.run(command, shell=True)
-    return
+    subprocess.run(command, shell=True)
 
 
-def getcorrectkey():
-    # request the key for the checksum
-    try:
-        r = requests.post('http://faas.darkurthe.net/function/checkkey',
-                          metadata['checksum'], verify=False, timeout=None)
-        return r.text.strip()
-    except requests.exceptions.HTTPError as err:
-        raise err
+def convert_aax(
+                rfile: str,
+                activation_bytes: str = None,
+                key_iv: dict = None
+                ) -> bool:
+    '''
+    Do the work of convering the file to mp3.
 
+    Args:
+        rfile (str): The file to convert
+        activation_bytes Optional(str): The unique activation bytes from audible. # noqa E501
 
-def findalbumbyname_stub(album):
-    return False
-
-
-if args.filename.find("aax"):
-    rfile = args.filename
-    metadata = getmetadata(rfile)
-    if metadata == "None":
-        sendtodiscord(rfile)
-        album = getmetadatatags('album')
-        # See if we got it already
-        if not findalbumbyname(album):
-            artist = normalize_data(getmetadatatags('artist'))
-            title = normalize_data(getmetadatatags('title'))
-            act_byte = getcorrectkey()
-            if act_byte is None or act_byte == '':
-                sendtodiscord(rfile)
-                sys.exit(f"Can't continue with this file {rfile}")
-            else:
-                ddir = "%s/%s/%s" % (path, artist, title)
-                single_file_path = "/processing/%s.mp3" % (title)
-                if not os.path.exists(ddir):
-                    os.makedirs(ddir)
-                print(ddir)
-                reencode(rfile, single_file_path)
-                if mode == 'chapter':
-                    chapter = 0
-                    numchapters = getchaptercount()
-                    while (numchapters > 0):
-                        cstart = getchaptermetadata(chapter, 'start_time')
-                        cend = getchaptermetadata(chapter, 'end_time')
-                        chapter += 1
-                        numchapters -= 1
-                        schap = str(chapter).zfill(2)
-                        movetochapters(single_file_path, ddir, schap, title,
-                                       cstart, cend)
-                    os.remove(single_file_path)
-                getcoverart(rfile, ddir)
+    Ret:
+        Boolean if successful
+    '''
+    chapter = 0
+    global _metadata
+    _metadata = get_metadata(rfile)
+    global act_byte
+    act_byte = activation_bytes
+    if _metadata == "None":
+        sendtodiscord("There was a problem processing ", rfile)
+    else:
+        # artist = normalize_data(_metadata['format']['tags']['artist'])
+        # title = normalize_data(_metadata['format']['tags']['title'])
+        artist = normalize_data(_metadata['format']['tags']['artist'])
+        title = normalize_data(_metadata['format']['tags']['title'])
+        if activation_bytes is None or activation_bytes == '':
+            sendtodiscord("We couldn't get the activation bytes", rfile)
+            return False
         else:
-            print('We have that book already')
+            dest_dir = f"/workspaces/audiobooks/{artist}/{title}"
+            processing_file = f"/tmp/{title}.mp3"
+            if not os.path.exists(dest_dir):
+                os.makedirs(dest_dir)
+            reencode(rfile, processing_file, activation_bytes, key_iv)
+            print(f"Splitting {title} into chapters")
+            numchapters = len(_metadata['chapters'])
+            while (numchapters > 0):
+                cstart = get_chapter_metadata(chapter, 'start_time')
+                cend = get_chapter_metadata(chapter, 'end_time')
+                chapter += 1
+                numchapters -= 1
+                schap = str(chapter).zfill(2)
+                movetochapters(processing_file, dest_dir, schap, title,
+                               cstart, cend)
+            os.remove(processing_file)
+            getcoverart(rfile, dest_dir)
+            sendtodiscord(f"We have processed the book {title} ", rfile)
+            return True
+
+
+def main():
+    '''
+    Main excution
+    '''
+    args = get_args()
+    rfile = args.filename
+    _metadata = get_metadata(rfile)
+    if _metadata == "None":
+        sendtodiscord("There was a problem processing ", rfile)
+    else:
+        artist = normalize_data(get_metadata_tags('artist'))
+        title = normalize_data(get_metadata_tags('title'))
+        act_byte = args.activation_bytes
+        if act_byte is None or act_byte == '':
+            sendtodiscord("We couldn't get the activation bytes", rfile)
+            sys.exit(f"We couldn't get the activation bytes {rfile}")
+        else:
+            ddir = "%s/%s/%s" % (path, artist, title)
+            single_file_path = "/processing/%s.mp3" % (title)
+            if not os.path.exists(ddir):
+                os.makedirs(ddir)
+            print(ddir)
+            reencode(rfile, single_file_path)
+            if mode == 'chapter':
+                chapter = 0
+                numchapters = getchaptercount()
+                while (numchapters > 0):
+                    cstart = get_chapter_metadata(chapter, 'start_time')
+                    cend = get_chapter_metadata(chapter, 'end_time')
+                    chapter += 1
+                    numchapters -= 1
+                    schap = str(chapter).zfill(2)
+                    movetochapters(single_file_path, ddir, schap, title,
+                                   cstart, cend)
+                os.remove(single_file_path)
+            getcoverart(rfile, ddir)
+            sendtodiscord(f"We have added the book {title} ", rfile)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fix-asin.py b/fix-asin.py
index 5b4f126..fad1123 100644
--- a/fix-asin.py
+++ b/fix-asin.py
@@ -48,7 +48,7 @@
     if search:
         return search[0]['asin']
     else:
-        print(f"Title not found {title}")
+        print(f"{title}")
 
 
 def update_bookshelf(book_id, book_title, book_asin):
@@ -71,8 +71,10 @@
 for x in books:
     if x['media']['metadata']['asin'] is None:
         # print({x['media']['metadata']['title']})
-        asin = find_asin_for_book(x['media']['metadata']['title'], audible_books)
+        asin = find_asin_for_book(x['media']['metadata']['title'],
+                                  audible_books)
         x_title = x['media']['metadata']['title']
         x_id = x['id']
         if asin:
+            print(f"Found ASIN {asin} for {x_title}")
             update_bookshelf(x_id, x_title, asin)
diff --git a/getMissingFromAudible.py b/getMissingFromAudible.py
index a82b381..a5b893a 100755
--- a/getMissingFromAudible.py
+++ b/getMissingFromAudible.py
@@ -5,6 +5,7 @@
 import audible
 import json
 import time
+import os
 from audible.aescipher import decrypt_voucher_from_licenserequest
 from audible.activation_bytes import (
     extract_activation_bytes,
@@ -22,6 +23,22 @@
 _metadata = {}
 
 
+def get_auth_files():
+    '''
+    Gets the auth files for audible and activation bytes
+
+    Returns:
+        audible auth, activation bytes
+    '''
+    
+    with open('./audible_auth_', 'r') as f:
+        auth_data = json.load(f)
+    audible_auth = audible.Authenticator.from_dict(auth_data)
+    act_bytes = fetch_activation_sign_auth(auth=audible_auth)
+    act_bytes = extract_activation_bytes(act_bytes)
+    return audible_auth, act_bytes
+
+
 def get_args():
     '''
     Parses the args for runtime
@@ -130,6 +147,9 @@
     Ret:
         str: The url to download the file from
     '''
+    # print(licenserequest)
+    if 'Denied' in licenserequest{'content_license']['status'}:
+        raise Exception("License request was denied. For ")
     return licenserequest['content_license']['content_metadata'][
         'content_url']['offline_url']
 
@@ -147,7 +167,7 @@
         f.write(dl.content)
 
 
-def download_books(asin: str) -> None:
+def download_book(asin: str) -> None:
     '''
     Download the book and hand it off to the converter
 
@@ -156,8 +176,11 @@
         asin (str):  The amazon number of the book being downloaded
     '''
     lr = get_license_request(asin)
-    print(ab)
-    get_content(asin, get_content_url(lr))
+    # see if we already downloaded the file
+    if os.path.exists(f"/tmp/{asin}.aax"):
+        print(f"{asin} already downloaded, skipping download.")
+    else:
+        get_content(asin, get_content_url(lr))
     decrypted_voucher = decrypt_voucher_from_licenserequest(_audible_auth, lr)
     key_iv = {
         'key': decrypted_voucher['key'],
@@ -167,23 +190,33 @@
     aaxConvert.convert_aax(f"/tmp/{asin}.aax", _actbytes, key_iv)
 
 
+def list_missing_books():
+    audible_books = get_audible_books()
+    missing_books = []
+    library_ids = get_library_ids()
+    for id in library_ids:
+        bookshelf_books = get_bookshelf_books(id)
+        for book in audible_books:
+            if book['content_type'] == 'Product':
+                search = search_local_books(book, bookshelf_books)
+                if search:
+                    missing_books.append(f"{book['title']}: {book['asin']}")
+    return missing_books
+
+
 def main():
     args = get_args()
     asin_manual = args.asin
-    audible_books = get_audible_books()
     if asin_manual is not None:
-        download_books(asin_manual)
+        download_book(asin_manual)
     else:
-        library_ids = get_library_ids()
-        for id in library_ids:
-            bookshelf_books = get_bookshelf_books(id)
-            for book in audible_books:
-                if book['content_type'] == 'Product':
-                    search = search_local_books(book, bookshelf_books)
-                    if search:
-                        print(f"{book['title']}, {book['asin']}")
-                        download_books(book['asin'])
-                        time.sleep(20)
+        missing_books = list_missing_books()
+        print(missing_books)
+        answer = input("Would you like to download the missing books? (y/n)\n")
+        if answer.lower() == 'y':
+            for book in missing_books:
+                download_book(book.split(": ")[1])
+                time.sleep(20)
 
 
 if __name__ == "__main__":
diff --git a/queryAudiobookServer.py b/queryAudiobookServer.py
index 0a609e1..de96762 100755
--- a/queryAudiobookServer.py
+++ b/queryAudiobookServer.py
@@ -36,13 +36,13 @@
 
 
 def findalbumbyname(album):
-    album = album.strip()
-    alj = getalbumlist('testuser', 'testpass')
-    for item in alj["subsonic-response"]["albumList"]["album"]:
-        sablum = str(item['album'])
-        sablum = sablum.strip()
-        if sablum.lower() == album.lower():
-            return True
+    # album = album.strip()
+    # alj = getalbumlist('testuser', 'testpass')
+    # for item in alj["subsonic-response"]["albumList"]["album"]:
+    #     sablum = str(item['album'])
+    #     sablum = sablum.strip()
+    #     if sablum.lower() == album.lower():
+    #         return True
     return False
 
 

--
Gitblit v1.10.0