Skip to content

Instantly share code, notes, and snippets.

@mhay10
Last active March 12, 2023 08:58
Show Gist options
  • Save mhay10/fcde6400a2321a9cbc41a48b52708c7c to your computer and use it in GitHub Desktop.
Save mhay10/fcde6400a2321a9cbc41a48b52708c7c to your computer and use it in GitHub Desktop.
Chapterize downloaded audiobooks from audible chapters
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