Last active
March 12, 2023 08:58
-
-
Save mhay10/fcde6400a2321a9cbc41a48b52708c7c to your computer and use it in GitHub Desktop.
Chapterize downloaded audiobooks from audible chapters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import subprocess as sp | |
import requests | |
import readline | |
import glob | |
import os | |
import re | |
# Setup path autocomplete | |
def completer(text, state): | |
line = readline.get_line_buffer().split() | |
return [x for x in glob.glob(text + "*")][state] | |
readline.set_completer_delims("\t") | |
readline.parse_and_bind("tab: complete") | |
readline.set_completer(completer) | |
# Converts mp3 files to m4b | |
glob_path = input("Path to mp3 files: ") | |
mp3_files = glob.glob(glob_path + "/*.mp3") | |
mp3_files.sort(key=lambda f: int(re.findall(r"\d+", os.path.basename(f))[-2])) | |
folder = os.path.dirname(mp3_files[0]) or "./" | |
m4b_file = os.path.abspath(os.path.join(folder, f"{os.path.basename(folder)}.m4b")) | |
input_file = os.path.abspath(os.path.join(folder, "input.txt")) | |
with open(input_file, "w") as f: | |
for mp3_file in mp3_files: | |
mp3_file = mp3_file.replace("'", "'\\''") | |
f.write(f"file '{os.path.abspath(mp3_file)}'\n") | |
sp.run( | |
f'ffmpeg -f concat -safe 0 -i "{input_file}" -vn -acodec aac -ab 112000 -ar 44100 -y "{m4b_file}"', | |
) | |
# Get chapters from audible api | |
asin = input("Audible ID: ") | |
url = f"https://api.audnex.us/books/{asin}/chapters" | |
res = requests.get(url) | |
chapters = res.json()["chapters"] | |
# Get book cover from audible api | |
url = f"https://api.audnex.us/books/{asin}" | |
res = requests.get(url) | |
cover_url = res.json()["image"] | |
cover_file = os.path.join(folder, "cover.jpg") | |
with open(cover_file, "wb") as f: | |
f.write(requests.get(cover_url).content) | |
# Add chapter buffer if intro not present | |
has_intro = input("Has intro? (y/n): ").lower() == "y" | |
buffer = 0 if has_intro else 4000 | |
# Convert chapters into txt | |
chapters_file = os.path.join(folder, "chapters.txt") | |
with open(chapters_file, "w") as f: | |
for i, chapter in enumerate(chapters): | |
title = chapter["title"] | |
start = chapter["startOffsetMs"] - buffer | |
end = start + chapter["lengthMs"] | |
f.write( | |
"[CHAPTER]\n" | |
"TIMEBASE=1/1000\n" | |
f"START={start}\n" | |
f"END={end}\n" | |
f"title={title}\n\n" | |
) | |
# Add chapters to m4b | |
m4b_chapterized = os.path.abspath(os.path.splitext(m4b_file)[0] + "_chapterized.m4b") | |
cmd = ( | |
f'ffmpeg -i "{m4b_file}" -f ffmetadata -i "{chapters_file}" ' | |
"-map 0:a -map_chapters 1 -map_metadata 1 " | |
f'-acodec copy -y "{m4b_chapterized}"' | |
) | |
sp.run(cmd, shell=True) | |
# Add cover to m4b | |
cmd = ( | |
f'ffmpeg -i "{m4b_chapterized}" -hwaccel_output_format cuda -i "{cover_file}" ' | |
"-map 0:0 -map 1:0 " | |
'-id3v2_version 3 -metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" ' | |
"-c:v h264_nvenc " | |
f'-y "{m4b_file}"' | |
) | |
sp.run(cmd, shell=True) | |
# Cleanup | |
os.remove(m4b_chapterized) | |
os.remove(chapters_file) | |
os.remove(cover_file) | |
os.remove(input_file) | |
remove_mp3 = input("Remove mp3 files? (y/n): ").lower() == "y" | |
if remove_mp3: | |
for mp3_file in mp3_files: | |
os.remove(mp3_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment