FFMPEG: извлечение 20 изображений из видео переменной длины

Я очень интенсивно просматривал интернет, но я не нашел то, что мне было нужно, только его варианты, которые не совсем то, что я хочу использовать.

У меня есть несколько видеороликов разной длины, и я хочу извлечь 20 изображений из каждого видео от начала до конца, чтобы показать самое большое впечатление от видео.

Итак, одно видео длино 16 м 47 с = > 1007 с = = Мне нужно сделать один снимок видео каждые 50 секунд.

Итак, я решил использовать ключ -r ffmpeg со значением 0.019860973 (экв 20/1007), но ffmpeg сообщает мне, что частота кадров слишком мала для него...

Единственный способ, которым я решил это сделать, - написать script, который вызывает ffmpeg с помощью управляемого переключателя -ss и использования -vframes 1, но это довольно медленно и немного для меня, так как ffmpegs называет сами изображения...

Любые предложения или указания?

Спасибо, Vapire

Ответ 1

Я тоже пытался найти ответ на этот вопрос. Я использовал ответ radri, но обнаружил, что он ошибается.

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

создает кадр каждые 2 секунды, потому что -r означает частоту кадров. В этом случае 0,5 кадра в секунду или 1 кадр каждые 2 секунды.

С той же логикой, если ваше видео длится 1007 секунд, и вам нужно всего 20 кадров, вам нужен кадр каждые 50,53 секунды. Переведенный на частоту кадров, будет составлять 0,01979 кадров в секунду.

Итак, ваш код должен быть

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

Я надеюсь, что это поможет кому-то, как это мне помогло.

Ответ 2

Я знаю, что немного опаздываю на вечеринку, но я подумал, что это может помочь:

Для всех, у кого есть проблема, когда 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-й кадр)

Надеюсь, что это избавит вас от неприятностей!

Ответ 3

В общем случае ffmpeg обрабатывает кадры по мере их поступления, поэтому все, что основано на общей длительности/общем количестве кадров, требует некоторой предварительной обработки.

Я бы рекомендовал написать короткую оболочку script, чтобы получить общее количество кадров, используя что-то вроде

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 будет заменено числом, вычисленным в оболочке script, чтобы дать вам всего 20 кадров.

Ответ 4

Вы можете попробовать конвертировать видео в N количество изображений?

ffmpeg -i video.avi image%d.jpg

Update:

Или извлеките кадр каждые 2 секунды:

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

Update:

Чтобы получить продолжительность видео:

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

У меня была такая же проблема, и я придумал этот script, который, кажется, делает трюк:

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

Это даст вам определенное количество кадров, равномерно распределенных по всему видео.

Ответ 6

У меня был аналогичный вопрос, а именно, как извлечь ОДИН кадр на полпути фильма неизвестной длины. Благодаря ответам здесь я придумал это решение, которое работает очень хорошо, я думал, что публикация 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 );
?>