I am working on an ETL process, and I’m now in the final stage of preprocessing my videos. I used the script below (reference: @FarisHijazi) to first auto detected black-screen frames using ffprobe and trim them out using ffmpeg.
The script worked for me but the problems are:
- It cut off all other good frames together with the first bad frames. e.g. if gBgBgBgB represents a sequence of good and BAD frames for 5sec each, the script only returned the first g(5sec) and cut off the other BgBgBgB after it. I want to have only g g g g where all B B B B has been removed
- I also want to detect other colors aside black-screen e.g. green-screen or red-screen or blurry part of video
- Script doesn’t work if video has no audio in it.
"""
@author:
Use ffprobe to extract black frames and ffmpeg to trim them and output a new video
"""
import argparse
import os
import shlex
import subprocess
parser = argparse.ArgumentParser(
__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument("input", type=str, help="input video file")
parser.add_argument(
"--invert",
action="store_true",
help="remove nonblack instead of removing black",
)
args = parser.parse_args()
##FIXME: sadly you must chdir so that the ffprobe command will work
os.chdir(os.path.split(args.input)[0])
args.input = os.path.split(args.input)[1]
spl = args.input.split(".")
outpath = (
".".join(spl[:-1])
+ "."
+ ("invert" if args.invert else "")
+ "out."
+ spl[-1]
)
def delete_back2back(l):
from itertools import groupby
return [x[0] for x in groupby(l)]
def construct_ffmpeg_trim_cmd(timepairs, inpath, outpath):
cmd = f'ffmpeg -i "{inpath}" -y -r 20 -filter_complex '
cmd += '"'
for i, (start, end) in enumerate(timepairs):
cmd += (
f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS,format=yuv420p[{i}v]; "
+ f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[{i}a]; "
)
for i, (start, end) in enumerate(timepairs):
cmd += f"[{i}v][{i}a]"
cmd += f"concat=n={len(timepairs)}:v=1:a=1[outv][outa]"
cmd += '"'
cmd += f' -map [outv] -map [outa] "{outpath}"'
return cmd
def get_blackdetect(inpath, invert=False):
ffprobe_cmd = f'ffprobe -f lavfi -i "movie={inpath},blackdetect[out0]" -show_entries tags=lavfi.black_start,lavfi.black_end -of default=nw=1 -v quiet'
print("ffprobe_cmd:", ffprobe_cmd)
lines = (
subprocess.check_output(shlex.split(ffprobe_cmd))
.decode("utf-8")
.split("\n")
)
times = [
float(x.split("=")[1].strip()) for x in delete_back2back(lines) if x
]
assert len(times), "no black scene detected"
if not invert:
times = [0] + times[:-1]
timepairs = [
(times[i], times[i + 1]) for i in range(0, len(times) // 2, 2)
]
return timepairs
exec
if __name__ == "__main__":
timepairs = get_blackdetect(args.input, invert=args.invert)
cmd = construct_ffmpeg_trim_cmd(timepairs, args.input, outpath)
print(cmd)
os.system(cmd)