FFMPEG: Extracting 20 images from a video of varia

2019-01-13 11:24发布

I've browsed the internet for this very intensively, but I didn't find what I needed, only variations of it which are not quite the thing I want to use.

I've got several videos in different lengths and I want to extract 20 images out of every video from start to the end, to show the broadest impression of the video.

So one video is 16m 47s long => 1007s in total => I have to make one snapshot of the video every 50 seconds.

So I figured using the -r switch of ffmpeg with the value of 0.019860973 (eq 20/1007) but ffmpeg tells me that the framerate is too small for it...

The only way I figured out to do it would be to write a script which calls ffmpeg with a manipulated -ss switch and using -vframes 1 but this is quite slow and a little bit off for me since ffmpegs numerates the images itself...

Any suggestions or directions?

Thanks, Vapire

6条回答
看我几分像从前
2楼-- · 2019-01-13 11:55

In general, ffmpeg processes frames as they come, so anything based on the total duration/total number of frames requires some preprocessing.

I'd recommend writing a short shell script to get the total number of frames using something like

ffprobe -show_streams <input_file> | grep "^nb_frames" | cut -d '=' -f 2

and then use the select video filter to pick out the frames you need. So the ffmpeg command would look something like

ffmpeg -i <input_file> -vf "select='not(mod(n,100))'" <output_file>

except that instead of every hundredth frame, 100 would be replaced by the number calculated in the shell script to give you 20 frames total.

查看更多
Luminary・发光体
3楼-- · 2019-01-13 11:57

You could try convert video to N number of images ?

ffmpeg -i video.avi image%d.jpg

Update:

Or extract frame every 2 seconds:

ffmepg -i video.avi -r 0.5 -f image2 output_%05d.jpg

Update:

To get the video duration:

ffmpeg -i video.avi 2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//

Then, depends on your programming language, you convert it into seconds. For example, in PHP, you could do it like this:

$duration = explode(":",$time); 
$duration_in_seconds = $duration[0]*3600 + $duration[1]*60+ round($duration[2]);

where $time is the video duration. The you can execute $duration_in_seconds / 20

ffmepg -i video.avi -r $duration_in_seconds/20 -f image2 output_%05d.jpg
查看更多
我只想做你的唯一
4楼-- · 2019-01-13 11:57

I was having the same problem and came up with this script which seems to do the trick:

#/bin/sh
total_frames=`ffmpeg -i ./resources/iceageH264.avi -vcodec copy -acodec copy -f null dev/null 2>&1 | grep 'frame=' | cut -f 3 -d ' '`
numframes=$3 
rate=`echo "scale=0; $total_frames/$numframes" | bc`
ffmpeg -i $1 -f image2 -vf "select='not(mod(n,$rate))'" -vframes $numframes -vsync vfr $2/%05d.png

To use it, save it as a .sh file and run it using the following parameters to export 20 frames:

./getFrames.sh ~/video.avi /outputpath 20

This will give you the specified number of frames distributed equally throughout the video.

查看更多
够拽才男人
5楼-- · 2019-01-13 12:03

I know I'm a bit late to the party, but I figured this might help:

For everyone having the issue where ffmpeg is generating an image for every single frame, here's how I solved it (using blahdiblah's answer):

First, I grabbed the total number of frames in the video:

ffprobe -show_streams <input_file> | grep "^nb_frames" | cut -d '=' -f 2

Then I tried using select to grab the frames:

ffmpeg -i <input_file> -vf "select='not(mod(n,100))'" <output_file>

But, no matter what the mod(n,100) was set to, ffmpeg was spitting out way too many frames. I had to add -vsync 0 to correct it, so my final command looked like this:

ffmpeg -i <input_file> -vsync 0 -vf "select='not(mod(n,100))'" <output_file>

(where 100 is the frame-frequency you'd like to use, for example, every 100th frame)

Hope that saves someone a little trouble!

查看更多
做个烂人
6楼-- · 2019-01-13 12:08

i had a similar question, namely how to extract ONE frame halfway a movie of unknown length. Thanks to the answers here, I came up with this solution which works very well indeed, I thought posting the php code would be useful:

<?php 
header( 'Access-Control-Allow-Origin: *' );
header( 'Content-type: image/png' );
// this script returns a png image (with dimensions given by the 'size' parameter as wxh)
// extracted from the movie file specified by the 'source' parameter
// at the time defined by the 'time' parameter which is normalized against the 
// duration of the movie i.e. 'time=0.5' gives the image halfway the movie.
// note that this can also be done using php-ffmpeg..
$time = floatval( $_GET[ 'time' ] );
$srcFile = $_GET[ 'source' ];
$size = $_GET[ 'size' ];
$tmpFile = tempnam( '/tmp', 'WTS.txt' );
$destFile = tempnam( '/tmp', 'WTS.png' );
$timecode = '00:00:00.000';

// we have to calculate the new timecode only if the user 
// requests a timepoint after the first frame
if( $time > 0.0 ){
    // extract the duration via a system call to ffmpeg 
    $command = "/usr/bin/ffmpeg -i ".$srcFile." 2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,// >> ".$tmpFile;
    exec( $command );

    // read it back in from tmpfile (8 chars needed for 00:00:00), skip framecount
    $fh = fopen( $tmpFile, 'r' );
    $timecode = fread( $fh, 8 );
    fclose( $fh );

    // retieve the duration in seconds
    $duration = explode( ":", $timecode ); 
    $seconds = $duration[ 0 ] * 3600 + $duration[ 1 ] * 60 + round( $duration[ 2 ] );
    $timepoint = floor( $seconds * $time );

    $seconds = $timepoint % 60;
    $minutes = floor( $timepoint / 60 ) % 60;
    $hours = floor( $timepoint / 3600 ) % 60;

    if( $seconds < 10 ){ $seconds = '0'.$seconds; };
    if( $minutes < 10 ){ $minutes = '0'.$minutes; };
    if( $hours < 10 ){ $hours = '0'.$hours; };

    $timecode = $hours.':'.$minutes.':'.$seconds.'.000';
}

// extract an image from the movie..
exec( '/usr/bin/ffmpeg -i '.$srcFile.' -s '.$size.' -ss '.$timecode.' -f image2 -vframes 1 '.$destFile );

// finally return the content of the file containing the extracted frame
readfile( $destFile );
?> 
查看更多
放我归山
7楼-- · 2019-01-13 12:11

I was trying to find the answer to this question too. I made use of radri's answer but found that it has a mistake.

ffmpeg -i video.avi -r 0.5 -f image2 output_%05d.jpg

produces a frame every 2 seconds because -r means frame rate. In this case, 0.5 frames a second, or 1 frame every 2 seconds.

With the same logic, if your video is 1007 seconds long and you need only 20 frames, you need a frame every 50.53 seconds. Translated to frame rate, would be 0.01979 frames a second.

So your code should be

ffmpeg -i video.avi -r 0.01979 -f image2 output_%05d.jpg

I hope that helps someone, like it helped me.

查看更多
登录 后发表回答