Skip to content

Instantly share code, notes, and snippets.

@yamamushi
Created January 21, 2026 03:53
Show Gist options
  • Select an option

  • Save yamamushi/d3cdc6bdbc622cd1867c593746acd0ce to your computer and use it in GitHub Desktop.

Select an option

Save yamamushi/d3cdc6bdbc622cd1867c593746acd0ce to your computer and use it in GitHub Desktop.
Audiobook Creator
#!/usr/bin/env python3
import glob
import os
import subprocess
import sys
def die(msg):
print(f"Error: {msg}", file=sys.stderr)
sys.exit(1)
mp3s = sorted(glob.glob("*.mp3"))
if not mp3s:
die("No .mp3 files found in current directory")
covers = (
sorted(glob.glob("*.jpg")) +
sorted(glob.glob("*.jpeg")) +
sorted(glob.glob("*.JPG")) +
sorted(glob.glob("*.JPEG"))
)
if not covers:
die("No .jpg/.jpeg cover image found in current directory")
cover = covers[0]
book = input("Book name: ").strip()
author = input("Author: ").strip()
if not book:
die("Book name cannot be empty")
if not author:
die("Author cannot be empty")
def duration_ms(path):
try:
out = subprocess.check_output(
[
"ffprobe",
"-v", "error",
"-show_entries", "format=duration",
"-of", "default=nw=1:nk=1",
path
],
text=True
).strip()
return int(round(float(out) * 1000))
except Exception as e:
die(f"Failed to read duration of {path}: {e}")
# Write concat list
with open("list.txt", "w", encoding="utf-8") as f:
for p in mp3s:
f.write("file '" + p.replace("'", r"'\''") + "'\n")
# Write chapters
current = 0
with open("chapters.ffmeta", "w", encoding="utf-8") as f:
f.write(";FFMETADATA1\n")
for p in mp3s:
dur = max(duration_ms(p), 1)
title = os.path.splitext(os.path.basename(p))[0]
f.write("\n[CHAPTER]\n")
f.write("TIMEBASE=1/1000\n")
f.write(f"START={current}\n")
f.write(f"END={current + dur}\n")
f.write(f"title={title}\n")
current += dur
output = f"{book}.m4b"
cmd = [
"ffmpeg", "-y",
"-f", "concat", "-safe", "0", "-i", "list.txt",
"-i", "chapters.ffmeta",
"-i", cover,
"-map", "0:a",
"-map", "2:v",
"-map_metadata", "1",
"-map_chapters", "1",
"-c:a", "aac",
"-b:a", "128k",
"-c:v", "copy",
"-disposition:v:0", "attached_pic",
"-metadata", f"title={book}",
"-metadata", f"album={book}",
"-metadata", f"artist={author}",
"-metadata", f"album_artist={author}",
"-movflags", "+faststart",
output
]
print(f"Cover image : {cover}")
print(f"Output file : {output}")
print("Building audiobook…")
subprocess.run(cmd, check=True)
print("Done.")
@yamamushi
Copy link
Author

Takes all mp3's in the local directory (make sure that they are named in order!) and the jpg file in the directory (or first one if there are multiples), to output an audiobook m4b with chapters. Just a quick script for this, don't blame me if something goes wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment