import argparse
import os
import sys
import logging
import yaml
import subprocess
def path_to_aax ( aax : str ) :
if not os . path . isfile ( aax ) :
raise argparse . ArgumentTypeError ( f " Pfad { aax } ist keine gueltige Datei. " )
if not aax . endswith ( ' .aax ' ) :
raise argparse . ArgumentTypeError ( f " Pfad { aax } ist keine gueltige aax-Datei. " )
return aax
def decrypt_aax_file ( aax_path : str , profile : str ) :
logging . info ( " Ich decryyypte... " )
try :
#Probe auf die aax Datei fuer Checksum
cmd_probe = f " ffprobe { aax_path } 2>&1 | grep checksum "
result_probe = subprocess . run ( cmd_probe , shell = True , check = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE , text = True )
tmp_checksum = result_probe . stdout
checksum = tmp_checksum . split ( ' == ' ) [ 1 ]
checksum = checksum . split ( ' \n ' ) [ 0 ] # Ein Line-Break wird am Ende immer noch angehängt -> Muss weg sonst gibts beim nächsten CMD Probleme
logging . debug ( f " Checksum: { checksum } " )
#Decrypt der Checksum via Rainbowtables für UserId
cmd_crack = f " ./rcrack . -h { checksum } | grep hex "
cwd_tables = os . path . join ( os . getcwd ( ) , ' tables ' )
result_crack = subprocess . run ( cmd_crack , shell = True , check = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE , text = True , cwd = cwd_tables )
tmp_profile_id = result_crack . stdout
profile_id = tmp_profile_id . split ( ' hex: ' ) [ 1 ]
profile_id = profile_id . split ( ' \n ' ) [ 0 ] # Ein Line-Break wird am Ende immer noch angehängt -> Muss weg
logging . info ( f " Profile-Id extrahiert: { profile_id } " )
#Erstellen von Profil
with open ( ' profiles.yml ' , ' w ' ) as profile_yaml :
yaml . dump ( { profile : profile_id } , profile_yaml , default_flow_style = False )
except subprocess . CalledProcessError as err :
logging . info ( f " Fehler bei Prozess ausfuehrung. { err } " )
def convert_to_m4b ( aax_path : str , profile : str ) :
logging . info ( " Ich converteeee... " )
try :
#Laden des Profils
profiles = None
with open ( ' profiles.yml ' , ' r ' ) as profile_yaml :
profiles = yaml . safe_load ( profile_yaml )
profile_id = profiles . get ( profile )
logging . info ( f ' Loaded Profile: { profile } ' )
aax_name = aax_path . split ( ' / ' ) [ 1 ] # Fuer vollen Datei-Name
aax_name = aax_name . split ( ' . ' ) [ 0 ] # Fuer Datei-Namen ohne Endung
#Konvertierung der aax Datei zu m4b
cmd_ffmpeg = f " ffmpeg -activation_bytes { profile_id } -i { aax_path } -c copy m4b_outputs/ { aax_name } .m4b "
result_ffmpeg = subprocess . run ( cmd_ffmpeg , shell = True , check = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE , text = True )
logging . info ( f " Konvertierung abgeschlossen. m4b_outputs/ { aax_name } .m4b erstellt. " )
except subprocess . CalledProcessError as err :
logging . info ( f " Fehler bei Prozess ausfuehrung. { err } " )
# ffmpeg -activation_bytes fc07e32c -i aax_inputs/HowTo-WiemanshinkriegtAbsurdewirklichwissenschaftlicheEmpfehlungenfralleLebenslagen_ep7.aax -c copy output.m4b
if __name__ == " __main__ " :
argparser = argparse . ArgumentParser ( description = " Dieses Script konvertiert verschluesselte Audible Buecher zu unverschluesselte m4b-Audiobooks. " )
cmd_parser = argparser . add_subparsers ( dest = ' cmd ' , title = ' Commands ' , description = " Erlaubte Commands " , required = True )
decrypt_parser = cmd_parser . add_parser ( ' decrypt ' , help = " Extrahiert die User-spezifische ID zur Profil-Bildung von einer bestehenden aax-Datei. " )
decrypt_parser . add_argument ( ' aax ' , type = path_to_aax , help = ' Path zu einem zu entschluesselnden aax file des neuen Profils ' )
decrypt_parser . add_argument ( ' profile ' , type = str , help = ' Profil-Name, zu welchem die extrahierte ID gespeichert wird. (ID ist notwendig fuer Entschluesselung der eigenen Hoerbuecher) ' )
convert_parser = cmd_parser . add_parser ( ' convert ' , help = " Konvertiert ein aax-Audiobook in ein m4b-Audiobook. " )
convert_parser . add_argument ( ' aax ' , type = path_to_aax , help = ' Path zu einem zu entschluesselnden aax file ' )
convert_parser . add_argument ( ' profile ' , type = str , help = " Zugehoeriges Profile fuer Entschluesselung " )
convert_all_parser = cmd_parser . add_parser ( ' convert-all ' , help = " Konvertiert alle aax-Audiobooks in m4b-Audiobooks. " )
convert_all_parser . add_argument ( ' profile ' , type = str , help = " Zugehoeriges Profile fuer Entschluesselung " )
args : argparse . Namespace = argparser . parse_args ( args = None if sys . argv [ 1 : ] else [ ' --help ' ] )
logging . basicConfig ( level = logging . INFO , format = " %(asctime)s | %(levelname)s | %(message)s " , datefmt = " %d - % m- % Y % H: % M: % S " )
profiles = None
try :
with open ( ' profiles.yml ' , ' r ' ) as file :
profiles = yaml . safe_load ( file )
except IOError :
with open ( ' profiles.yml ' , ' w ' ) as file :
profiles = yaml . safe_load ( ' ' )
if args . cmd == " decrypt " :
logging . info ( f " Decrypt wird gestartet... " )
aax_file = args . aax
profile = args . profile
if profiles is not None and profiles . get ( args . profile ) is not None :
logging . info ( f " Profil { args . profile } existiert bereits. Konvertierung kann starten. " )
sys . exit ( )
decrypt_aax_file ( aax_file , profile )
elif args . cmd == " convert " :
if profiles is None or profiles . get ( args . profile ) is None or args . profile not in profiles :
logging . info ( f " Profil { args . profile } existiert nicht. Bitte ueber decrypt anlegen lassen. " )
sys . exit ( )
logging . info ( f " Convert wird gestartet... " )
aax_file = args . aax
profile = args . profile
convert_to_m4b ( aax_file , profile )
elif args . cmd == " convert-all " :
logging . info ( f " Convert wird fuer alle aax im Input-Ordner durchgefuehrt " )
#TODO Implement