Merge Files with Chapters
import datetime | |
import json | |
import os | |
import subprocess | |
import sys | |
############# | |
### USAGE ### | |
############# | |
if len(sys.argv) < 4: | |
print("Usage:") | |
print("{} [input file] [input file] [output file]".format(sys.argv[0])) | |
print("") | |
print("Both files are assumed to have their chapters") | |
print("entered correctly and completely.") | |
sys.exit(0) | |
######################## | |
### Get Chapter List ### | |
######################## | |
def getChapterList(videoFile): | |
# Get chapter list as JSON | |
result = subprocess.run( | |
["ffprobe", "-print_format", "json", "-show_chapters", videoFile], | |
capture_output=True, | |
text=True | |
) | |
# Load the JSON | |
fileJson = json.loads(result.stdout)['chapters'] | |
# Map to Python object: | |
# { | |
# id: 1 | |
# start: 123.456 | |
# end: 789.012 | |
# } | |
chapters = list(map( | |
lambda c: { | |
'index': c['id'], | |
'start': float(c['start_time']), | |
'end': float(c['end_time']), | |
'title': c['tags']['title']}, | |
fileJson)) | |
return list(chapters) | |
######################## | |
### Video 1 Duration ### | |
######################## | |
# Get the duration of the first video | |
result = subprocess.run( | |
["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", sys.argv[1]], | |
capture_output=True, | |
text=True | |
) | |
# Get the result and trim off the trailing newline. | |
file1duration = float(result.stdout.rstrip()) | |
print("{} duration is {} seconds.".format(sys.argv[1], file1duration)) | |
############################ | |
### Video 2 Chapter List ### | |
############################ | |
def chapterMigrator(chapter): | |
startTime = chapter['start'] | |
endTime = chapter['end'] | |
offsetStartTime = file1duration + startTime | |
offsetEndTime = file1duration + endTime | |
return {'index': chapter['index'], 'start': offsetStartTime, 'end': offsetEndTime, 'title': chapter['title']} | |
video2rawchapters = getChapterList(sys.argv[2]) | |
# Migrate these chapters to be offset from the end of the first file | |
video2chapters = list(map(chapterMigrator, video2rawchapters)) | |
print("{} has {} chapters.".format(sys.argv[2], len(video2chapters))) | |
########################### | |
### Get file 1 metadata ### | |
########################### | |
result = subprocess.run( | |
["ffmpeg", "-i", sys.argv[1], "-f", "ffmetadata", "-"], | |
capture_output=True, | |
text=True | |
) | |
metadata = result.stdout | |
################################## | |
### Append file 2 chapter list ### | |
################################## | |
metadataFileName = "metadata.txt" | |
# Note the timestamps are in milliseconds, and should be integers. | |
for c in video2chapters: | |
metadata += f""" | |
[CHAPTER] | |
TIMEBASE=1/1000 | |
START={int(c['start'] * 1000)} | |
END={int(c['end'] * 1000)} | |
title={c['title']}""" | |
with open(metadataFileName, "w") as metadataFile: | |
metadataFile.write(metadata) | |
############################ | |
### Join two video files ### | |
############################ | |
fileListFileName = "files.txt" | |
fileList = f""" | |
file {sys.argv[1]} | |
file {sys.argv[2]}""" | |
with open(fileListFileName, "w") as fileListFile: | |
fileListFile.write(fileList) | |
if os.path.exists(sys.argv[3]): | |
os.remove(sys.argv[3]) | |
print("Joining {} and {} into {}...".format(sys.argv[1], sys.argv[2], sys.argv[3])) | |
result = subprocess.run( | |
["ffmpeg", "-f", "concat", "-i", fileListFileName, "-i", metadataFileName, "-map_metadata", "1", "-c", "copy", sys.argv[3]], | |
capture_output=False | |
) | |
print("...file {} created.".format(sys.argv[3])) | |
# Clean up. | |
os.remove(metadataFileName) | |
os.remove(fileListFileName) |
This comment has been minimized.
This comment has been minimized.
@eugenesvk Huh, thanks for pointing that out. This most recent activity does not instill confidence, however:
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Just FYI ffmpeg does indeed seem to miss this concat feature as acknowledged in their bug tracker