10

I have a short video and I'm trying to cut-out all the blank or near blank frames. This isn't a case where I care about the breaks in a video that could be dark. I literally want all near black-frames out.

What I didn't realize with ffmpeg at first was that the 'blackframe' or 'blackdetect' filters, is that they didn't actually filter. They just seem to show you what you can possibly filter, with other command(s).

What is the best way in 'Windows' to actually filter out the resulting found frames?

So with ffmpeg I'm actually concat-(ting) the forward and reverse of the same file than I'm running some filters on those files. See my current command:

ffmpeg.exe -y -i "2013-10-14_14-30-55.mov" -filter_complex "[0:v]transpose=3,split[tp][tp2];[tp]reverse[vr];[tp2][vr]concat=n=2:v=1:a=0[vbf];[vbf]blackframe=98:32[v]" -map "[v]" -vcodec wmv2 -q 10 -trellis 2 -r 16 "g:\gd5.wmv"

Then I get this output: (Just the tail end of it anyway...)

[Parsed_blackframe_4 @ 00000000003bf320] frame:650 pblack:99 pts:21821999 t:21.821999 type:B last_keyframe:639
[Parsed_blackframe_4 @ 00000000003bf320] frame:651 pblack:99 pts:21855366 t:21.855366 type:P last_keyframe:639
[Parsed_blackframe_4 @ 00000000003bf320] frame:652 pblack:99 pts:21888733 t:21.888733 type:B last_keyframe:639
[Parsed_blackframe_4 @ 00000000003bf320] frame:653 pblack:99 pts:21922099 t:21.922099 type:B last_keyframe:639
[Parsed_blackframe_4 @ 00000000003bf320] frame:654 pblack:99 pts:21955466 t:21.955466 type:I last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:655 pblack:99 pts:21988833 t:21.988833 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:656 pblack:99 pts:22022199 t:22.022199 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:657 pblack:99 pts:22055566 t:22.055566 type:P last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:658 pblack:99 pts:22088933 t:22.088933 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:659 pblack:99 pts:22122299 t:22.122299 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:660 pblack:99 pts:22155666 t:22.155666 type:P last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:661 pblack:99 pts:22189033 t:22.189033 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:662 pblack:99 pts:22222399 t:22.222399 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:663 pblack:99 pts:22255766 t:22.255766 type:P last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:664 pblack:99 pts:22289133 t:22.289133 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:665 pblack:99 pts:22322499 t:22.322499 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:666 pblack:99 pts:22355866 t:22.355866 type:P last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:667 pblack:99 pts:22389233 t:22.389233 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:668 pblack:99 pts:22422599 t:22.422599 type:B last_keyframe:654
[Parsed_blackframe_4 @ 00000000003bf320] frame:669 pblack:99 pts:22455966 t:22.455966 type:I last_keyframe:669
frame=  362 fps= 14 q=10.0 Lsize=    9004kB time=00:00:22.62   bitrate=3260.2kbits/s dup=1 drop=309
video:8922kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.918756%

So what do I do with this blackframe information in Windows? Or is there a better method with the 'blackdetect' filter?

2 Answers 2

12

Here's a way to do it with the blackdetect and trim filters.

First, a better way to get the blackdetect output is via ffprobe because it is capable of writing structured data such as XML, JSON or one key=value per line.

You do this using metadata injection:

ffprobe -f lavfi -i "movie=/path/to/input.mp4,blackdetect[out0]" -show_entries tags=lavfi.black_start,lavfi.black_end -of default=nw=1 -v quiet TAG:lavfi.black_start=0 TAG:lavfi.black_end=5.42208 TAG:lavfi.black_start=73.4067

As you can see it returns the start and end time of each black section. You use the returned info to trim the segments between each end and the next start:

ffmpeg -i /path/to/input.mp4 -filter_complex "[0:v]trim=start=5.42208:end=73.4067,setpts=PTS-STARTPTS[v1];[0:a]atrim=start=5.42208:end=73.4067,asetpts=PTS-STARTPTS[a1]" -map [v1] -map [a1] output.mp4

You can chain multiple trim filters in the same command. See this answer for a detailed example.

To automate this in Windows you can install and use PowerShell or another scripting language of your choice (PHP, Python etc).

10
  • Your help is appreciated, and seems to be the right path. However I tried the ffprobe command you suggested: ffprobe -f lavfi -i "movie=2013-10-14_14-30-55.mov,blackdetect[out0]" -show_entries tags=lavfi.black_start,lavfi.black_end -of default=nw=1 -v quiet But no entries come out at all. Could this be, because the frames in question are not black enough, or am I doing it wrong? I don't understand some of the commands. Commented Oct 8, 2015 at 13:22
  • @RobertKoernke I used the default blackdetect settings, check its arguments and example on ffmpeg.org/ffmpeg-filters.html#blackdetect. Make sure you also have a recent version of ffmpeg and not a default distro installation.
    – aergistal
    Commented Oct 8, 2015 at 13:43
  • The command means: do blackdetect, show only black_start and black_end tags, use the default output format -of default with no wrappers nw=1 and suppress all other output -v quiet
    – aergistal
    Commented Oct 8, 2015 at 13:46
  • btw... You are giving me Windows instructions correct? Ok I temporarily took off the -v quiet and I get this: [Parsed_movie_0 @ 0056f560] Failed to avformat_open_input 'C' [lavfi @ 0056e2a0] Error initializing filter 'movie' with args 'C:UsersDOCUME~12013-10-14_14-30-55.mov' movie=C:\Users\DOCUME~1\2013-10-14_14-30-55.mov,blackdetect=d=0:pix_th=51.80[out0]: No such file or directory` Commented Oct 8, 2015 at 17:41
  • 1
    When you use a path inside a command argument you have to escape the special characters or quote it, or use shorter, simple paths. ffmpeg.org/ffmpeg-utils.html#toc-Quoting-and-escaping
    – aergistal
    Commented Oct 9, 2015 at 7:42
2

I've put together a Python script that will do all of this. Generates the ffprobe and ffmpeg commands and runs them And outputs a file without black clips

GitHub repo: https://github.com/FarisHijazi/deblack

GitHub gist (archived): https://gist.github.com/FarisHijazi/eff7a7979440faa84a63657e085ec504

"""
@author: https://github.com/FarisHijazi
Use ffprobe to extract black frames and ffmpeg to trim them and output a new video
"""

# help from:
# https://video.stackexchange.com/questions/16564/how-to-trim-out-black-frames-with-ffmpeg-on-windows#new-answer?newreg=d534934be5774bd1938b535cd76608cd
# https://github.com/kkroening/ffmpeg-python/issues/184#issuecomment-493847192


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 -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 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


timepairs = get_blackdetect(args.input, invert=args.invert)
cmd = construct_ffmpeg_trim_cmd(timepairs, args.input, outpath)

print(cmd)
os.system(cmd)

Combining multiple answers together:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.