You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
6.3 KiB
Python

from enum import Enum
import ctypes
from ctypes import byref
import wave
class MetaData(Enum):
album_artist = 0x40617574
publisher = 0x4070726F
artist = 0x406E6172
title = 0x406C6465 # TIT3
copyright = 0x40636F70
date = 0x40706461
JPEG = 0x40636172
audible_id = 0x40706964
album = 0x40746974
# _unknown1 = 0x40707469
# _unknown2 = 0x40736465 #SHORTDESC
# _unknown3 = 0x4075616 #STILLNOKLINGON?
def main(in_file, out_file, dll_path=None):
if dll_path is None:
dll_path = 'C:/Program Files (x86)/Audible/Bin/AAXSDKWin.dll'
dll = ctypes.CDLL(dll_path)
handle = open_file(dll, in_file)
a = (dll, handle)
channels = get_audio_channel_count(*a)
sample_rate = get_sample_rate(*a)
seek(*a, 0)
authenticate(*a)
print('c', channels, 's', sample_rate)
n_chapters = get_chapter_count(*a)
chapter_times = [get_chapter_start_time(*a, i) for i in range(n_chapters)]
with open(out_file + '.txt', 'w') as f:
[f.write(str(ti) + '\n') for ti in chapter_times]
enc_buf = ctypes.create_string_buffer(0x400)
dec_buf = ctypes.create_string_buffer(0x400 * 200)
length = 0
with wave.open(out_file, 'wb') as wav:
wav.setnchannels(channels)
wav.setframerate(sample_rate)
wav.setsampwidth(2)
while True:
enc_data, enc_len = get_encoded_audio(*a, enc_buf)
if enc_len == 0:
break
dec_data, dec_len = decode_pcm_frame(*a, enc_buf, enc_len, dec_buf)
length += dec_len
wav.writeframes(bytearray(dec_buf)[:dec_len])
close_file(*a)
def open_file(dll, filename):
handle = ctypes.pointer(ctypes.c_byte())
res = dll.AAXOpenFileWinW(byref(handle), filename)
if res != 0:
raise Exception('AAXOpenFileWinW: {}'.format(res))
return handle
def close_file(dll, handle):
res = dll.AAXCloseFile(handle)
if res != 0:
raise Exception('AAXCloseFile: {}'.format(res))
def get_audio_channel_count(dll, handle):
n_channels = ctypes.c_uint()
res = dll.AAXGetAudioChannelCount(handle, byref(n_channels))
if res != 0:
raise Exception('AAXGetAudioChannelCount: {}'.format(res))
return n_channels.value
def get_sample_rate(dll, handle):
sample_rate = ctypes.c_uint()
res = dll.AAXGetSampleRate(handle, byref(sample_rate))
if res != 0:
raise Exception('AAXGetSampleRate: {}'.format(res))
return sample_rate.value
def seek(dll, handle, position=0):
res = dll.AAXSeek(handle, ctypes.c_int(position))
if res != 0:
raise Exception('AAXSeek: {}'.format(res))
def authenticate(dll, handle):
res = dll.AAXAuthenticateWin(handle)
if res != 0:
raise Exception('AAXAuthenticateWin: {}'.format(res))
def get_encoded_audio(dll, handle, buf=None):
if buf is None:
buf = ctypes.create_string_buffer(0x400)
data_len = ctypes.c_uint()
res = dll.AAXGetEncodedAudio(handle, buf, len(buf), byref(data_len))
if res not in (0, -24):
#-24 is "read past end of data"
raise Exception('AAXGetEncodedAudio: {}'.format(res))
return buf, data_len.value
def decode_pcm_frame(dll, handle, in_buf, in_len, out_buf=None):
if out_buf is None:
out_buf = ctypes.create_string_buffer(0x400 * 200)
data_len = ctypes.c_uint()
res = dll.AAXDecodePCMFrame(handle, in_buf, in_len, out_buf, len(out_buf), byref(data_len))
if res != 0:
raise Exception('AAXDecodedPCMFrame: {}'.format(res))
return out_buf, data_len.value
def get_chapter_text(dll, handle, chapter_num, buf=None):
'''
Possibly doesn't exist?
'''
if buf is None:
buf = ctypes.create_string_buffer(0x800)
data_len = ctypes.c_uint()
res = dll.AAXGetChapterText(handle, ctypes.c_uint(chapter_num), buf, len(buf), byref(data_len))
if data_len.value == 0:
return ''
if res != 0:
raise Exception('AAXGetChapterText: {}'.format(res))
return bytearray(buf[:data_len.value]).decode('utf-8')
def get_metadata(dll, handle, mdn):
data_len = ctypes.c_uint()
res = dll.AAXGetMetadataInfo(handle, ctypes.c_int(mdn), None, byref(data_len))
if res != 0:
raise Exception('AAXGetMetadataInfo: {}'.format(res))
buf = ctypes.create_string_buffer(data_len)
res = dll.AAXGetMetadata(handle, ctypes.int(mdn), buf, byref(data_len))
if res != 0:
raise Exception('AAXGetChapterText: {}'.format(res))
return buf, data_len.value
def seek_to_chapter(dll, handle, chapter):
res = dll.AAXSeekToChapter(handle, ctypes.c_uint(chapter))
if res != 0:
raise Exception('AAXSeekToChapter: {}'.format(res))
def get_chapter_count(dll, handle):
n_chapters = ctypes.c_uint()
res = dll.AAXGetChapterCount(handle, byref(n_chapters))
if res != 0:
raise Exception('AAXGetChapterCount: {}'.format(res))
return n_chapters.value
def get_current_chapter(dll, handle):
chapter = ctypes.c_uint()
res = dll.AAXGetCurrentChapter(handle, byref(chapter))
if res != 0:
raise Exception('AAXGetCurrentChapter: {}'.format(res))
return chapter.value
def get_duration(dll, handle):
duration = ctypes.c_uint()
res = dll.AAXGetDuration(handle, byref(duration))
if res != 0:
raise Exception('AAXGetDuration: {}'.format(res))
return duration.value
def get_playback_position(dll, handle):
position = ctypes.c_uint()
res = dll.AAXGetPlaybackPosition(handle, byref(position))
if res != 0:
raise Exception('AAXGetPlaybackPosition: {}'.format(res))
return position.value
def get_chapter_start_time(dll, handle, chapter):
position = ctypes.c_uint()
res = dll.AAXGetChapterStartTime(handle, ctypes.c_uint(chapter), byref(position))
if res != 0:
raise Exception('AAXGetChapterStartTime: {}'.format(res))
return position.value
def get_avg_bitrate(dll, handle):
bitrate = ctypes.c_uint()
res = dll.AAXGetAvgBitrate(handle, byref(bitrate))
if res != 0:
raise Exception('AAXGetAvgBitrate: {}'.format(res))
return bitrate.value
def get_max_bitrate(dll, handle):
bitrate = ctypes.c_uint()
res = dll.AAXGetMaxBitrate(handle, byref(bitrate))
if res != 0:
raise Exception('AAXGetAvgBitrate: {}'.format(res))
return bitrate.value