scripts/generate_video_side_by_side_standalone.py (192 lines of code) (raw):
#!/usr/bin/python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
"""
Used to produce comparisons of browsertime videos between a base
and a new revision.
From: https://github.com/mozilla/mozperftest-tools/blob/master/generate_side_by_side_standalone.py
"""
import argparse
import cv2
import gc
import numpy as np
import os
import pathlib
import shutil
import subprocess
def side_by_side_parser():
parser = argparse.ArgumentParser(
"You can use this tool to make arbitrary side-by-side videos of any combination of videos. "
"Use --remove-orange if you are comparing browsertime videos with orange frames (note that "
"this requires matplotlib). "
)
parser.add_argument(
"--base-video",
type=str,
required=True,
help="The path to the base/before video.",
)
parser.add_argument(
"--new-video",
type=str,
default="autoland",
help="The path to the new/after video.",
)
parser.add_argument(
"--remove-orange",
action="store_true",
default=False,
help="If set, orange frames are removed.",
)
parser.add_argument(
"--output",
type=str,
default=os.getcwd(),
help="This is where the data will be saved. Defaults to CWD. "
+ "You can include a name for the file here, otherwise it will "
+ "default to side-by-side.mp4.",
)
return parser
def remove_orange_frames(video):
"""Removes orange frames."""
try:
from matplotlib import pyplot as plt
except:
print("Missing matplotlib, please install")
raise
allframes = []
orange_pixind = 0
orange_frameind = 0
frame_count = 0
check_for_orange = True
while video.isOpened():
ret, frame = video.read()
if ret:
# Convert to gray to simplify the process
allframes.append(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY))
# Check if it's orange still
if check_for_orange:
frame = allframes[-1]
histo, _, _ = plt.hist(np.asarray(frame).flatten(), bins=255)
maxi = np.argmax(histo)
if not orange_pixind:
if maxi > 130:
continue
orange_pixind = maxi
elif maxi == orange_pixind:
orange_frameind = frame_count
else:
check_for_orange = False
frame_count += 1
else:
video.release()
break
return allframes[orange_frameind:], orange_frameind
def build_side_by_side(base_video, new_video, base_ind, new_ind, output_dir, filename):
before_vid = pathlib.Path(output_dir, "before.mp4")
after_vid = pathlib.Path(output_dir, "after.mp4")
before_cut_vid = pathlib.Path(output_dir, "before-cut.mp4")
after_cut_vid = pathlib.Path(output_dir, "after-cut.mp4")
before_rs_vid = pathlib.Path(output_dir, "before-rs.mp4")
after_rs_vid = pathlib.Path(output_dir, "after-rs.mp4")
for apath in (
before_vid,
after_vid,
before_cut_vid,
after_cut_vid,
before_rs_vid,
after_rs_vid,
):
if apath.exists():
apath.unlink()
overlay_text = (
"fps=fps=60,drawtext=text={}\\\\ :fontsize=(h/30):fontcolor=black:y=10:"
+ "timecode=00\\\\:00\\\\:00\\\\:00:rate=60*1000/1001:fontcolor=white:x=(w-tw)/2:"
+ "y=10:box=1:boxcolor=0x00000000@1[vid]"
)
common_options = [
"-map",
"[vid]",
"-c:v",
"libx264",
"-crf",
"18",
"-preset",
"veryfast",
]
# Cut the videos
subprocess.check_output(
["ffmpeg", "-i", str(base_video), "-vf", "select=gt(n\\,%s)" % base_ind]
+ [str(before_cut_vid)]
)
subprocess.check_output(
["ffmpeg", "-i", str(new_video), "-vf", "select=gt(n\\,%s)" % new_ind]
+ [str(after_cut_vid)]
)
# Resample
subprocess.check_output(
["ffmpeg", "-i", str(before_cut_vid), "-filter:v", "fps=fps=60"]
+ [str(before_rs_vid)]
)
subprocess.check_output(
["ffmpeg", "-i", str(after_cut_vid), "-filter:v", "fps=fps=60"]
+ [str(after_rs_vid)]
)
# Generate the before and after videos
subprocess.check_output(
[
"ffmpeg",
"-i",
str(before_rs_vid),
"-filter_complex",
overlay_text.format("firefox"),
]
+ common_options
+ [str(before_vid)]
)
subprocess.check_output(
[
"ffmpeg",
"-i",
str(after_rs_vid),
"-filter_complex",
overlay_text.format("chrome"),
]
+ common_options
+ [str(after_vid)]
)
subprocess.check_output(
[
"ffmpeg",
"-i",
str(before_vid),
"-i",
str(after_vid),
"-filter_complex",
"[0:v]pad=iw*2:ih[int];[int][1:v]overlay=W/2:0[vid]",
]
+ common_options
+ [str(pathlib.Path(output_dir, filename))]
)
if __name__ == "__main__":
args = side_by_side_parser().parse_args()
if shutil.which("ffmpeg") is None:
raise Exception(
"Cannot find ffmpeg in path! Please install it before continuing."
)
# Parse the given output argument
filename = "side-by-side.mp4"
output = pathlib.Path(args.output)
if output.exists() and output.is_file():
print("Deleting existing output file...")
output.unlink()
elif not output.suffixes:
output.mkdir(parents=True, exist_ok=True)
else:
filename = output.name
output = output.parents[0]
output.mkdir(parents=True, exist_ok=True)
def _open_data(file):
return cv2.VideoCapture(str(file))
base_video_path = str(pathlib.Path(args.base_video).resolve())
new_video_path = str(pathlib.Path(args.new_video).resolve())
base_ind = 0
new_ind = 0
if args.remove_orange:
_, base_ind = remove_orange_frames(_open_data(base_video_path))
_, new_ind = remove_orange_frames(_open_data(new_video_path))
output_name = str(pathlib.Path(output, "cold-" + filename))
build_side_by_side(
args.base_video,
args.new_video,
base_ind,
new_ind,
output,
"custom-" + filename,
)
print("Successfully built a side-by-side comparison: %s" % output_name)