7

I have a videofile and like to extract a single image from (about 1500) specific timepoints.(expl. -ss 00:50, ss 00:67, ss: 01:70 ...)

Is there a way to do this? I tried out to use this command:

ffmpeg.exe -ss 00:50 -i all.avi -t 00:00:01 -vframes 1 images-filename_%03d.png

This works great for a single picture, but is there a way to do this for such a lot of specific timepoints?

Does anyone have an advice?

davidbaumann
  • 2,289
Jakob Winter
  • 71
  • 1
  • 2

3 Answers3

13

This answer improves upon @slhck's answer, which may fail to work in some situations.

One subtlety in selecting frames by (approximate) timestamp is that the actual timestamp of the frame may not be exactly equal to the desired timestamp. For example, in a video with framerate 23.98 (24000/1001) there is no frame with timestamp 1.0 - the closest frame has a timestamp value of 1.001.

The expression eq(t, specific_timepoint) evaluates to true only if t is exactly equal to specific_timepoint. Therefore it will fail to select a frame in the situation described above. As a workaround, we can select the first frame immediately following the desired timestamp, i.e. the frame whose timestamp value is greater than or equal to the specified timepoint, while the timestamp of its preceding frame was less than the specified timepoint. The selection expression for a single timepoint will be

lt(prev_pts * TB,timepoint) * gte(pts * TB,timepoint)

Note that I deliberately did not use a shorter variant

between(timepoint, prev_pts*TB, pts*TB)

which is equivalent to

lte(prev_pts * TB,timepoint) * gte(pts * TB,timepoint)

as it would select two consecutive frames when the timestamp value of (the first) one of them exactly matches the specified timepoint value.

Example selecting the frames corresponding to or immediately following the timepoints 1.5, 10 and 20:

ffmpeg -i input.mp4 -filter:v \
    "select='lt(prev_pts*TB\,1.5)*gte(pts*TB\,1.5) \
            +lt(prev_pts*TB\,10)*gte(pts*TB\,10)   \
            +lt(prev_pts*TB\,20)*gte(pts*TB\,20)'" \
    -vsync drop images-filename_%03d.png
Leon
  • 366
3

Add multiple timestamps as options for the select filter.

ffmpeg -i input.mp4 -filter:v \
"select='eq(t\,1.5)+eq(t\,10)+eq(t\,20)'" \
-vsync drop images-filename_%03d.png

If the filter evaluates to true, it'll output a frame, so the addition of checks comparing the frame timestamp t against a particular timestamp in seconds (e.g., 1.5) will give you all the frames at those timestamps.

slhck
  • 235,242
2

The OP asks to extract images for "a lot of specific timepoints" and gives 3 examples. To me, 3 is not a lot, so I extended the answer posted by @Leon to allow file input of a lot of timepoints. The python code constructs and then executes a long ffmpeg command. It assumes timepoints are the 2nd and 3rd items in a comma-separated line of text beginning with 'Dialogue:', as in the .ssa and .ass filetypes used by Aegisub. Since subtitle timepoints are a range, the program asks what fraction of that range you want to capture the image at. I've found that I usually get the images I want by using values of 0.5 and 0.9

'''
Splits a video into images at timepoints defined in a subtitle file
supply: .mp4 video and .ass subtitle file into the program directory
output: .png files within an img directory (which program makes if you don't)
to run: enter the .mp4 & .ass filename (same) and the timepoint fraction
timepoint fraction=0.5 means midway between each subtitle start & end point

code constructs and then executes a long ffmpeg command based on this post: https://superuser.com/a/1330042/752104 ''' import datetime as dt import os os.system("mkdir img")

def get_sec(stringHMS): timedeltaObj = dt.datetime.strptime( stringHMS, "%H:%M:%S.%f") - dt.datetime(1900, 1, 1) return timedeltaObj.total_seconds()

defName = 'm' defFrac = '0.5' prompt1 = 'Enter file name for both .mp4 and .ass file [' + defName + ']:' prompt2 = 'Fraction between subtitle start & end points (0-1)[' +
defFrac + ']:' fname = input(prompt1) if fname == '': fname = defName fh = open(fname + ".ass") frac = input(prompt2) if frac == '': frac = defFrac cmd = "ffmpeg -i " + fname + ".mp4 -filter:v "select='" # construct ffmpeg cmd plus = "" for line in fh: if not line.startswith('Dialogue:'): continue timePt = line.split(",") t1 = get_sec(timePt[1]) t2 = get_sec(timePt[2]) t = str(t1 + float(frac) * (t2 - t1)) cmd = cmd + plus + "lt(prev_ptsTB," + t + ")gte(pts*TB," + t + ")" plus = " +" # plus is added only after the first lt() cmd = cmd + "'" -vsync drop img/img%03d.png" os.system(cmd) # execute the ffmpeg command

Tony M
  • 253