Created
May 8, 2020 17:52
-
-
Save jsundram/1731da16f7580df4220bff8156bfc9c3 to your computer and use it in GitHub Desktop.
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
""" | |
Takes a Met Opera json file and parses it and then writes a .inf file suitable for creating video chapters. | |
ffmpeg -i opera.mp4 -i opera.json.inf -map_metadata 1 -codec copy new_opera.mp4 | |
""" | |
import json | |
import sys | |
from datetime import datetime | |
def parse_metadata(s): | |
"title=Le Nozze di Figaro: Opening credits|artists=|featured=false|isFree=false|ISRC=USY3P0803132" | |
"title=Act I: La vendetta . . . Tutto ancor non ho perso|artists=Paul Plishka, Wendy White, Cecilia Bartoli|featured=false|isFree=false|ISRC=USY3P0803137" | |
fields = s.split("|") | |
return dict(tuple(field.split("=")) for field in fields) | |
def get_chapters(filename): | |
with open(filename) as f: | |
data = json.load(f) | |
# other interesting fields: | |
# poster | |
# custom_fields has a lot of useful track metadata | |
md = data['custom_fields'] | |
def parse_date(s): | |
return datetime.strptime(s, "%m/%d/%Y") | |
metadata = { | |
'duration': data['duration'], | |
'work': md.get('searchtitle', "TITLE"), | |
'composer': md.get('composerlast', md.get('composer', "COMPOSER")), | |
'librettist': md.get('librettist', "LIBRETTIST"), | |
'conductor': md.get('conductor', "CONDUCTOR"), | |
'date': parse_date(md.get('performancedate', "01/01/01")), | |
'description': data.get('long_description', ""), | |
'artists': json.loads(md.get('artistjson', "")), | |
'synopsis_url': md.get('synopsis', ''), | |
} | |
metadata['title'] = "{composer} - {work}".format(**metadata) | |
last_index = 1 | |
chapters = [] | |
for track in data['cue_points']: | |
if track['type'] == 'AD': | |
continue | |
track_metadata = parse_metadata(track['metadata']) | |
chapters.append(dict( | |
index=last_index, | |
start_time=int(track['time'] * 1000), | |
title=track_metadata['title'], | |
)) | |
last_index += 1 | |
return chapters, metadata | |
def fill_ends(chapters, end): | |
filled = [] | |
for ch0, ch1 in zip(chapters, chapters[1:]): | |
ch0['end_time'] = ch1['start_time'] | |
filled.append(ch0) | |
# deal with the last one ... | |
ch = chapters[-1] | |
ch['end_time'] = end | |
filled.append(ch) | |
return filled | |
def write_chapters(filename, chapters, metadata): | |
# adapted from: | |
# https://medium.com/@dathanbennett/adding-chapters-to-an-mp4-file-using-ffmpeg-5e43df269687 | |
# see fields here: | |
# https://wiki.multimedia.cx/index.php/FFmpeg_Metadata | |
# and file format here: | |
# https://ffmpeg.org/ffmpeg-formats.html#Metadata-1 | |
def escape(s): | |
"""https://ffmpeg.org/ffmpeg-formats.html#Metadata-1""" | |
return s.translate(str.maketrans({ | |
"=": r"\=", | |
";": r"\;", | |
"#": r"\#", | |
'\\': r"\\" | |
})) | |
with open(filename, 'w') as f: | |
f.write(";FFMETADATA1\n") | |
f.write("title=%s\n" % escape(metadata['title'])) | |
f.write("artist=%s\n\n" % metadata['conductor']) | |
f.write("composer=%s\n\n" % metadata['conductor']) | |
f.write("year=%s\n\n" % metadata['date'].year) | |
f.write("synopsis=%s\n\n" % metadata['description']) | |
f.write("description=%s\n\n" % metadata['synopsis_url']) | |
f.write("copyright=The Metropolitan Opera\n\n") | |
for chapter in chapters: | |
f.write("[CHAPTER]\n") | |
f.write("TIMEBASE=1/1000\n") | |
f.write("START=%s\n" % chapter['start_time']) | |
f.write("END=%s\n" % chapter['end_time']) | |
f.write("title=%s\n\n" % escape(chapter['title'])) | |
f.write("[STREAM]\n") | |
f.write("title=%s\n" % escape(metadata['title'])) | |
print("Wrote chapter data to %s. Next: " % filename) | |
print("\tffmpeg -i opera.mp4 -i %s -map_metadata 1 -codec copy out.mp4" % filename) | |
def write_metadata(filename, metadata): | |
with open(filename, 'w', encoding="utf-8") as f: | |
f.write("# {work}\n".format(**metadata)) | |
f.write("{composer} - {librettist}\n".format(**metadata)) | |
f.write("Metropolitan Opera House\n") | |
f.write("{date:%A, %B %d, %Y} Telecast\n".format(**metadata)) | |
f.write("\n\n## Cast \n") | |
for artist in metadata.get('artists', []): | |
f.write("* {role}: {name}\n".format(**artist)) | |
f.write("\n\n## Description \n") | |
f.write("{description}".format(**metadata)) | |
f.write("\n\nSynopsis: {synopsis_url}".format(**metadata)) | |
def main(filename): | |
chapters, metadata = get_chapters(filename) | |
chapters = fill_ends(chapters, metadata['duration']) | |
write_chapters(filename + '.inf', chapters, metadata) | |
write_metadata(filename + '.md', metadata) | |
if __name__ == '__main__': | |
try: | |
main(sys.argv[1]) | |
except IndexError: | |
print("usage: python write_chapters.py metadata.json") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment