-
Notifications
You must be signed in to change notification settings - Fork 7
/
audiblex.py
executable file
·110 lines (84 loc) · 3.79 KB
/
audiblex.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/usr/bin/env python3
import argparse, subprocess, os, re, csv
from shutil import which
from enum import Enum
class Colors:
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
class Filetypes(Enum):
M4B = 'M4B'
M4A = 'M4A'
MP3 = 'MP3'
cach_path = '/tmp/audiblex_cache'
sp = os.path.dirname(os.path.realpath(__file__))
def decrypt(filepath: str):
with open(filepath, 'rb') as f:
f.seek(653)
checksum = f.read(20).hex()
print(Colors.BLUE + '::' + Colors.ENDC, 'Audio book checksum:', checksum)
print(Colors.BLUE + '::' + Colors.ENDC, 'Looking up activation bits for checksum...')
cache = loadCache()
cache_bits = cache.get(checksum, None)
if cache_bits:
print(Colors.BLUE + '::' + Colors.ENDC, 'Activation bits in cache')
return cache_bits
process = subprocess.Popen(['./rcrack', 'tables', '-h', checksum], cwd=os.path.join(sp, 'bin/rcrack'), stdout=subprocess.PIPE)
result = process.communicate()[0].decode('UTF-8')
result = re.search(r'hex:([a-fA-F0-9]{8})', result)
if result:
bits = result.groups()[0]
cache[checksum] = bits
saveCache(cache)
return bits
else:
return None
def convert(type: Filetypes, activation: str, filepath: str, single: bool):
print(Colors.BLUE + '::' + Colors.ENDC, 'Starting to convert the audio book')
cargs = [os.path.join(sp, 'bin/converters/AAXto' + type.value), activation, filepath];
if single: cargs.insert(1, "--single")
subprocess.run(cargs, stdout=subprocess.PIPE)
def loadCache():
try:
reader = csv.reader(open(cach_path, 'r'))
return {rows[0]:rows[1] for rows in reader}
except:
return {}
def saveCache(ab: dict):
try:
writer = csv.writer(open(cach_path, 'w'))
{writer.writerow([key, val]) for key, val in ab.items()}
except:
print(Colors.FAIL + '::' + Colors.ENDC, 'Faild to save activation bits cache')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert audible AAX files to M4B, M4A or MP3')
parser.add_argument('file', help='The aax file to convert')
parser.add_argument('-t', '--type', help='The destination filetype M4B, M4A or MP3', type=Filetypes)
parser.add_argument('-a', '--activation', help='Define the activation bits to use')
parser.add_argument('-s', '--single', help='Convert to a single file', action='store_true')
parser.add_argument('-l', '--lookup', help='Lookup the activation bits in the rainbow table', action='store_true')
parser.add_argument('-c', '--clear', help='Clear the activation bits cache', action='store_true')
args = parser.parse_args()
try:
if not which('ffmpeg'):
print(Colors.FAIL + '::' + Colors.ENDC, 'ffmpeg not found, not installed?')
exit(1)
if args.clear:
saveCache({})
activation_bits = args.activation if args.activation and not args.lookup else decrypt(args.file)
if len(activation_bits) != 8:
print(Colors.FAIL + '::' + Colors.ENDC, 'Activation bits must be exactly 8 characters long')
else:
if activation_bits:
print(Colors.GREEN + '::' + Colors.ENDC, 'Activation bits found:', activation_bits)
if not args.lookup:
ftype = args.type if args.type else Filetypes.M4B
convert(ftype, activation_bits, args.file, args.single)
else:
print(Colors.FAIL + '::' + Colors.ENDC, 'Failed to find activation bits')
except FileNotFoundError as e:
print(Colors.FAIL + '::' + Colors.ENDC, 'No such file:', '\'' + args.file + '\'')
except:
pass