Skip to content

Instantly share code, notes, and snippets.

@jsundram
Created May 8, 2020 17:52
Show Gist options
  • Save jsundram/1731da16f7580df4220bff8156bfc9c3 to your computer and use it in GitHub Desktop.
Save jsundram/1731da16f7580df4220bff8156bfc9c3 to your computer and use it in GitHub Desktop.
"""
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