-
-
Save verityj/5eec7bcacfe60c3fb418f28a4688a61b to your computer and use it in GitHub Desktop.
Merge Files with Chapters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This script creates metadata and file list for unchapterized files. | |
# | |
# Usage: python3 <script>.py input.files | |
# | |
# Example: | |
# python3 <script>.py *.mp3 | |
# | |
# Command used (to get duration in (s)): | |
# ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input | |
# | |
# Based on: https://gist.github.com/cliss/53136b2c69526eeed561a5517b23cefa | |
# and https://gist.github.com/philthompson/cdca6c5b4486fa6bb8a55a09731826f6 | |
import os | |
import subprocess | |
import sys | |
from pathlib import Path | |
################## | |
### How to use ### | |
################## | |
if len(sys.argv) < 3: | |
print("\nUsage:") | |
print("python3 {} <input file> <input file> [<input file> ...]".format(sys.argv[0])) | |
print("\nExample:") | |
print("python3 {} *.mp3".format(sys.argv[0])) | |
print("\nNotes:") | |
print("The order of the <input file> is the order the files are processed.") | |
print("All files are assumed to have no chapter information.") | |
print("Script will ask for author, book title, and year.\n") | |
sys.exit(0) | |
metadata_path = Path("x.metadata.txt") | |
file_list_path = Path("x.list.txt") | |
############################# | |
### Duration of each file ### | |
############################# | |
def get_file_duration(input_file_path): | |
# ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input | |
result = subprocess.run( | |
["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(input_file_path)], | |
capture_output=True, | |
text=True | |
) | |
# Get the result as float value in seconds. | |
# Trim off the trailing newline. | |
# Multiply by 1000 to get milliseconds. | |
# Convert to integer for metadata. | |
duration = int(float(result.stdout.rstrip()) * 1000) | |
#print("{} duration is {} seconds.".format(input_file_path, duration)) | |
return duration | |
############################################### | |
### Create audiobook metadata and file list ### | |
############################################### | |
metadata_file = None | |
file_list = None | |
total_duration = 0 | |
artist = input("\n Author: ") | |
album = input(" Book title: ") | |
date = input(" Year: ") | |
# get duration and chapters from each video, and | |
# increment the total running offset of all | |
# videos to be concatenated ahead of them | |
for i in range(1, len(sys.argv)): | |
file_path = Path(sys.argv[i]) | |
file_duration = get_file_duration(file_path) | |
# File name becomes chapter title, without file extension: | |
file_title = os.path.splitext(file_path)[0] | |
# Show the sequence of files: | |
if i < 10: | |
print("Chapter 0{} title: {}".format(i, file_title)) | |
else: | |
print("Chapter {} title: {}".format(i, file_title)) | |
if i == 1: | |
file_list = "file '{}'".format(file_path) | |
metadata_file = f""";FFMETADATA1 | |
artist={artist} | |
album_artist={artist} | |
album={album} | |
date={date} | |
[CHAPTER] | |
TIMEBASE=1/1000 | |
START=0 | |
END={file_duration} | |
title={file_title}""" | |
total_duration += file_duration | |
else: | |
file_list += "\nfile '{}'".format(file_path) | |
metadata_file += "\n\n[CHAPTER]\nTIMEBASE=1/1000\nSTART={}".format(total_duration) | |
total_duration += file_duration | |
metadata_file += "\nEND={}".format(total_duration) | |
metadata_file += "\ntitle={}".format(file_title) | |
print("All files read.") | |
print("Check metadata and file list, then run:") | |
print("ffmpeg -f concat -safe 0 -i {} -i {} -c copy output".format(file_list_path, metadata_path)) | |
# options that did not work: -map_metadata 1 -map_chapters 1 | |
# to encode mp3 as m4a: | |
# 1. check the mp3 bitrate | |
# 2. -c:a aac -b:a 320k -map 0:a | |
################################################### | |
### Write/oveerwrite new metadata and file list ### | |
################################################### | |
with open(file_list_path, "w") as file_list_f: | |
file_list_f.write(file_list) | |
with open(metadata_path, "w") as metadata_f: | |
metadata_f.write(metadata_file) | |
# Clean up (for later, if concatenation is done in this script and these are no longer needed): | |
#os.remove(file_list_path) | |
#os.remove(metadata_path) | |
# or, shorter but less clear: | |
""" | |
if i == 1: | |
file_list = "file '{}'".format(file_path) | |
metadata_file = ";FFMETADATA1\nartist={}".format(artist) | |
metadata_file += "\nalbum_artist={}".format(artist) | |
metadata_file += "\nalbum={}".format(album) | |
metadata_file += "\ndate={}".format(date) | |
metadata_file += "\n\n[CHAPTER]\nTIMEBASE=1/1000\nSTART=0\nEND={}".format(file_duration) | |
metadata_file += "\ntitle={}".format(file_title) | |
total_duration += file_duration | |
""" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment