Add main.py
This commit is contained in:
commit
68247bf615
99
main.py
Normal file
99
main.py
Normal file
@ -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.")
|
||||||
Loading…
x
Reference in New Issue
Block a user