FFMPEG:从一段长度不等的视频中提取20幅图像。

问题描述 投票:15回答:7

我在互联网上非常密集地浏览了一下,但我没有找到我所需要的东西,只有它的变体,这并不是我想要使用的东西。

我有几个不同长度的视频,我想从每个视频中提取20张图片,从开始到结束,以显示视频的最广泛印象。

所以一个视频长16m 47s => 总共1007s => 我必须每50秒对视频进行一次快照。

所以,我想使用-r开关的ffmpeg与0.019860973(eq 201007)的值,但ffmpeg告诉我,帧率太小,它......

我想出的唯一办法是写一个脚本,用一个操纵的-ss开关和使用-vframes 1来调用ffmpeg,但这很慢,而且对我来说有点不合适,因为ffmpegs本身就是数字图像......。

有什么建议或指导吗?

谢谢,Vapire

ffmpeg video-processing thumbnails
7个回答
17
投票

我也在试图找到这个问题的答案。我利用radri的答案,但发现它有一个错误。

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

每2秒产生一帧,因为-r意味着帧率。在这种情况下,0.5帧每秒,或每2秒1帧。

同样的逻辑,如果你的视频长度是1007秒,而你只需要20帧,那么每50.53秒就需要一帧。换算成帧率,就是每秒0.01979帧。

所以你的代码应该是

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

我希望这能帮助别人,就像它帮助我一样。


17
投票

我知道我来得有点晚,但我想这可能会有帮助。

对于每一个有问题的人来说 ffmpeg每一帧都会生成一个图像 这是我解决的方法(使用blahdiblah的答案)。

首先,我抓取了视频中的总帧数。

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

然后我试着用select来抓取帧数。

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

但是,不管mod(n,100)设置成什么样子 ffmpeg都会吐出太多的帧数。我不得不添加-vsync 0来纠正它,所以我的最终命令是这样的。

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

(其中100是你想使用的帧频,例如,每100帧)

希望能给大家省点麻烦!


6
投票

你可以尝试将视频转换为N个数量的图像?

ffmpeg -i video.avi image%d.jpg

更新。

或者每2秒提取一帧图像

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

更新:

要得到视频的持续时间。

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

然后,取决于你的编程语言,你把它转换成秒。例如,在PHP中,你可以这样做。

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

其中$time是视频的持续时间. 你可以执行$duration_in_seconds 20。

ffmepg -i video.avi -r $duration_in_seconds/20 -f image2 output_%05d.jpg

5
投票

一般来说,ffmpeg会在帧数到来时进行处理,所以任何基于总持续时间和总帧数的东西都需要一些预处理。

我建议你写一个简短的shell脚本来获取总帧数,使用类似于

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

然后使用 select 视频过滤器 来选择你需要的帧。 因此,ffmpeg命令的内容如下

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

除了不是每百帧。100 会被shell脚本中计算出的数字所取代,从而使你总共有20帧。


3
投票

我也有同样的问题,于是想出了这个脚本,似乎可以解决这个问题。

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

要使用它,把它保存为一个.sh文件,然后用以下参数运行它,导出20帧。

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

这将给你指定的帧数 在视频中平均分配。


1
投票

我有一个类似的问题,即如何提取一帧一半的未知长度的电影。由于这里的答案,我想出了这个解决方案,它的工作确实非常好,我想发布的PHP代码将是有用的。

<?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 );
?> 

0
投票

我也遇到了同样的问题, 使用选择过滤器对我来说并不奏效, 而使用速率对大视频来说又很慢, 所以我用python写了这个脚本.

它所做的是以秒为单位获取持续时间,根据截图数量计算时间间隔,最后在输入文件前使用seek进行每隔一段时间的截图。

#!/usr/bin/python3
import re
from os import makedirs
from os.path import basename, join, dirname, isdir
from sys import argv, exit
from subprocess import call, run, Popen, PIPE, STDOUT, CalledProcessError
from ffmpy3 import FFmpeg, FFRuntimeError, FFExecutableNotFoundError

argv.pop(0)

for ifile in argv:
    #Get video info
    duration = run(['ffprobe', '-hide_banner', '-i', ifile], stderr=PIPE, universal_newlines=True)
    #Extract duration in format hh:mm:ss
    duration = re.search(r'Duration: (\d{2}:\d{2}:\d{2}\.\d{2})', duration.stderr)
    #Convert duration to seconds
    duration = sum([a*b for a,b in zip([3600, 60, 1],
                    [float(i) for i in duration.group(1).split(':')])])
    #Get time interval to take screenshots
    interval = int (duration / 20)

    fname = basename(ifile)[:-4]
    odir = join(dirname(ifile),fname)

    if not isdir(odir):
        makedirs(odir, mode=0o750)

    ofile = join(odir,fname)

    #Take screenshots at every interval
    for i in range (20):
        run([])
        ff = FFmpeg(
            global_options='-hide_banner -y -v error',
            inputs={ifile: f'-ss {i * interval}'},
            outputs={f'{ofile}_{i:05}.jpg': f'-frames:v 1 -f image2'}
            )
        ff.run()
© www.soinside.com 2019 - 2024. All rights reserved.