|
@@ -0,0 +1,113 @@ |
|
|
|
|
|
#!/bin/python |
|
|
|
|
|
"""jsonclips.py - generate highlights based on JSON |
|
|
|
|
|
|
|
|
|
|
|
usage: jsonclips.py clips.json |
|
|
|
|
|
|
|
|
|
|
|
JSON file can specify: |
|
|
|
|
|
- output filename |
|
|
|
|
|
- source filepath |
|
|
|
|
|
- start time |
|
|
|
|
|
- duration |
|
|
|
|
|
- transition type [default: luma] |
|
|
|
|
|
- transition duration [default: 30] |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_TRANSITION_DURATION = 30 # 30 frames, ie 0.5s at 60FPS |
|
|
|
|
|
DEFAULT_TRANSITION_TYPE = "luma" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_highlights(jsonfile): |
|
|
|
|
|
"""Go through highlights, make clips, join to a video""" |
|
|
|
|
|
import tempfile |
|
|
|
|
|
|
|
|
|
|
|
def make_clips(sources=None, outputdir=None): |
|
|
|
|
|
"""Use ffmpeg to create output from processed JSON""" |
|
|
|
|
|
import subprocess |
|
|
|
|
|
import os |
|
|
|
|
|
|
|
|
|
|
|
ffmpeg_common_args = ["ffmpeg", "-loglevel", "quiet", "-hide_banner"] |
|
|
|
|
|
clipnum = 0 |
|
|
|
|
|
|
|
|
|
|
|
for source in sources: |
|
|
|
|
|
print("Making {}s clip from {} at {}".format( |
|
|
|
|
|
source["duration"], source["filepath"], source["time"])) |
|
|
|
|
|
|
|
|
|
|
|
ffmpeg_file_args = ["-ss", str(source["time"]), |
|
|
|
|
|
"-i", source["filepath"], |
|
|
|
|
|
"-t", str(source["duration"]), |
|
|
|
|
|
"-c", "copy", |
|
|
|
|
|
os.path.join(outputdir, |
|
|
|
|
|
"clip{}.mkv".format(clipnum))] |
|
|
|
|
|
|
|
|
|
|
|
ffmpeg_args = ffmpeg_common_args + ffmpeg_file_args |
|
|
|
|
|
print("Running ffmpeg with args:\n\n{}". |
|
|
|
|
|
format(ffmpeg_args)) |
|
|
|
|
|
subprocess.run(ffmpeg_args) |
|
|
|
|
|
|
|
|
|
|
|
clipnum += 1 |
|
|
|
|
|
|
|
|
|
|
|
def make_output(workingdir=None, outputfile=None, numsources=None): |
|
|
|
|
|
"""Use MLT (melt) to join clips""" |
|
|
|
|
|
import subprocess |
|
|
|
|
|
import os |
|
|
|
|
|
|
|
|
|
|
|
melt_args = ["melt"] |
|
|
|
|
|
|
|
|
|
|
|
for i in range(numsources): |
|
|
|
|
|
if i == 0: |
|
|
|
|
|
melt_args.extend([os.path.join(workingdir, "clip{}.mkv" |
|
|
|
|
|
.format(i))]) |
|
|
|
|
|
else: |
|
|
|
|
|
melt_args.extend([ |
|
|
|
|
|
os.path.join(workingdir, "clip{}.mkv".format(i)), |
|
|
|
|
|
"-mix", str(DEFAULT_TRANSITION_DURATION), |
|
|
|
|
|
"-mixer", str(DEFAULT_TRANSITION_TYPE)]) |
|
|
|
|
|
|
|
|
|
|
|
melt_args.extend(["-consumer", "avformat:{}".format(outputfile), |
|
|
|
|
|
"crf=20"]) |
|
|
|
|
|
|
|
|
|
|
|
print("Running melt with args:\n\n{}".format(melt_args)) |
|
|
|
|
|
subprocess.run(melt_args) |
|
|
|
|
|
|
|
|
|
|
|
def parse_json(jsonfile=None): |
|
|
|
|
|
"""Parse global / per--clip options from jsonfile |
|
|
|
|
|
|
|
|
|
|
|
Requires: |
|
|
|
|
|
- outputfile |
|
|
|
|
|
- list of sources |
|
|
|
|
|
""" |
|
|
|
|
|
import json |
|
|
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
if jsonfile: |
|
|
|
|
|
with open(jsonfile, "r") as fh: |
|
|
|
|
|
json_in = json.load(fh) |
|
|
|
|
|
|
|
|
|
|
|
if "outputfile" not in json_in: |
|
|
|
|
|
print("Please specify an outputfile to write to") |
|
|
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
|
|
|
if "sources" not in json_in: |
|
|
|
|
|
print("Please specify some sources to process") |
|
|
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
|
|
|
return json_in |
|
|
|
|
|
|
|
|
|
|
|
# Start |
|
|
|
|
|
|
|
|
|
|
|
if jsonfile: |
|
|
|
|
|
json_in = parse_json(jsonfile) |
|
|
|
|
|
|
|
|
|
|
|
# in temporary dir |
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir: |
|
|
|
|
|
# make clips |
|
|
|
|
|
make_clips(sources=json_in["sources"], outputdir=tmpdir) |
|
|
|
|
|
# join clips using melt |
|
|
|
|
|
make_output(workingdir=tmpdir, outputfile=json_in["outputfile"], |
|
|
|
|
|
numsources=len(json_in["sources"])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
process_highlights(sys.argv[1]) |