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