I am trying to generate 8 screenshots for an uploaded video using FFMPEG. I currently have:
ffmpeg -i Trailer-720p.mov -r .2
-vcodec png Preview-%d.png
Which generates a screenshot every 5 seconds. How can I add the ability to generate a screenshot for frames distributed over a percentage of total time. Thanks. Furthermore, is it possible to generate a screenshot at 50% for example? Thanks.
If you run ffmpeg with just the -i parameter, it will provide you with the length of the video on stderr (among lots of other things). You could write something around that, converting the duration and the intended number of frames into the correct -r parameter.
Here is an quick example in python which basically does what I have described. For some reason the first two stills generated by my version of ffmpeg both show frame 0, but Preview-3 to Preview-n are in the correct intervals. Run it with the second parameter set to '1' and it will generate the middle frame as Preview-3.png.
#!/usr/bin/env python
import sys,os,re
from subprocess import *
if len(sys.argv)<=1:
print("usage: python oneinn.py filename frames")
sys.exit(0)
try:
fvideo = sys.argv[1]
frames = float(sys.argv[2])
except:
sys.stderr.write("Failed to parse parameters.\n")
sys.exit(1)
output = Popen(["ffmpeg", "-i", fvideo], stderr=PIPE).communicate()
# searching and parsing "Duration: 00:05:24.13," from ffmpeg stderr, ignoring the centiseconds
re_duration = re.compile("Duration: (.*?)\.")
duration = re_duration.search(output[1]).groups()[0]
seconds = reduce(lambda x,y:x*60+y,map(int,duration.split(":")))
rate = frames/seconds
print("Duration = %s (%i seconds)" % (duration, seconds))
print("Capturing one frame every %.1f seconds" % (1/rate))
output = Popen(["ffmpeg", "-i", fvideo, "-r", str(rate), "-vcodec", "png", 'Preview-%d.png']).communicate()
Or just with a shell command:
ffmpeg -i input.m4v -vf fps=1/$(echo 'scale=6;' $(ffprobe -loglevel
quiet -of 'compact=nokey=1:print_section=0' -show_format_entry
duration input.m4v) ' / 10' | bc) -vframes 10 -qscale:v 2
thumbnail-%d.png
This creates 10 thumbnails with the same dimensions as the source video.
Here's one in Ruby:
#!/usr/bin/env ruby
# pass the video source file(s) into the command line args
# resulting images are jpg, just change the appropriate ffmpeg option for png.
# the last line uses ImageMagick to stitch the images together into a strip.
# the first image is thrown away, since it's a duplicate of the second image.
ARGV.each do|a|
total_shots = 4
size = '200x200'
meta = %x(ffmpeg -i #{a} 2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//)
time_parts = meta.match /(\d\d):(\d\d):(\d\d)\.(\d\d)/
duration_seconds = time_parts[1].to_i*60*60+time_parts[2].to_i*60+time_parts[3].to_i+time_parts[4].to_f/100
puts "*** Duration seconds: " + duration_seconds.to_s
%x(ffmpeg -i #{a} -r #{total_shots/duration_seconds} -s #{size} -f image2 -vframes #{total_shots+1} foo-%03d.jpg )
files = (1..total_shots+1).map{|i| 'foo-' + ("%03d" % i) + '.jpg'}
files.delete_at 0
%x(convert -append #{files.join ' '} shot-strip.jpg)
end
I could not get Manfred Stienstra's brilliant oneliner to generate frames at the exact right spot. If I specified to generate 8 images from a 240 secs movie, the first one would be 15, the second at 45, etcetera. I wanted the first one to be 0, the second at 30, etcetera.
So I took his oneliner apart and created this
ffmpeg=../bin/ffmpeg
ffprobe=../bin/ffprobe
outdir=../test
infile=../testmovie.mp4
outbase=testmovie
steps=8
len=`$ffprobe -loglevel quiet -of 'compact=nokey=1:print_section=0' -show_format_entry duration $infile`
echo length $len
secs=`echo 'scale=6;' $len ' / ' $steps | bc`
echo secs $secs
for ((i=0; i <= $steps ; i++)); do
echo =========================
echo $ffmpeg -nostats -loglevel 0 \
-i $infile -f image2 -ss `echo $secs \* $i | bc` \
-vframes 1 "$outdir/$outbase"-$i.jpg
$ffmpeg -nostats -loglevel 0 \
-i $infile -f image2 -ss `echo $secs \* $i | bc` \
-vframes 1 "$outdir/$outbase"-$i.jpg
done