2025-09-11 14:31:16 +00:00

100 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import subprocess
import json
import os
import glob
def run_ffmpeg_cmd(cmd):
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
return result.stdout.strip()
# 1. Ask for directory
input_dir = input("Enter the directory containing your .m4b files: ").strip()
# Collect and sort files
input_files = sorted(glob.glob(os.path.join(input_dir, "*.m4b")))
if not input_files:
raise SystemExit("❌ No .m4b files found in that directory.")
# 2. List files with numbering
print("\nAvailable files:")
for i, f in enumerate(input_files, 1):
print(f"[{i}] {os.path.basename(f)}")
# 3. Ask user for start-end range
user_range = input("\nEnter the range of files to merge (e.g. 1-10): ").strip()
try:
start, end = map(int, user_range.split("-"))
selected_files = input_files[start-1:end]
except Exception:
raise SystemExit("❌ Invalid range format. Use start-end (e.g. 1-5).")
# 4. Ask for output name
output_name = input("\nEnter the final audiobook name (without extension): ").strip()
final_output = f"{output_name}.m4b"
# 5. Extract metadata (for chapters & global tags)
chapters = []
current_start = 0
global_tags = None
for f in selected_files:
probe = subprocess.check_output([
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", f
])
meta = json.loads(probe)
# Duration
duration = float(meta["format"]["duration"])
# Title for chapter name (falls back to filename)
title = meta["format"]["tags"].get("title") if "tags" in meta["format"] else None
if not title:
title = os.path.splitext(os.path.basename(f))[0]
chapters.append({
"start": current_start,
"end": current_start + duration,
"title": title
})
current_start += duration
# Save global tags from the first file
if global_tags is None and "tags" in meta["format"]:
global_tags = meta["format"]["tags"]
# 6. Write FFmetadata chapters file
with open("chapters.txt", "w", encoding="utf-8") as f:
f.write(";FFMETADATA1\n")
if global_tags:
# Copy album/author/etc. from first file
for key, value in global_tags.items():
if key.lower() not in ["title", "name"]: # dont overwrite audiobook title
f.write(f"{key}={value}\n")
for ch in chapters:
f.write("\n[CHAPTER]\n")
f.write("TIMEBASE=1/1000\n")
f.write(f"START={int(ch['start']*1000)}\n")
f.write(f"END={int(ch['end']*1000)}\n")
f.write(f"title={ch['title']}\n")
# 7. Create file list for ffmpeg concatenation
with open("file_list.txt", "w", encoding="utf-8") as f:
for fpath in selected_files:
f.write(f"file '{os.path.abspath(fpath)}'\n")
# 8. Concatenate into merged.m4b
run_ffmpeg_cmd([
"ffmpeg", "-f", "concat", "-safe", "0", "-i", "file_list.txt",
"-c", "copy", "merged.m4b"
])
# 9. Add chapter metadata + final output name
run_ffmpeg_cmd([
"ffmpeg", "-i", "merged.m4b", "-i", "chapters.txt",
"-map_metadata", "1", "-c", "copy", final_output
])
print(f"\n✅ Done! Created '{final_output}' with {len(chapters)} chapters.")