commit 68247bf6158bc5af42995384cbf5f6c7b14edbe0 Author: tyler Date: Thu Sep 11 14:31:16 2025 +0000 Add main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..54fd888 --- /dev/null +++ b/main.py @@ -0,0 +1,99 @@ +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"]: # don’t 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.")