FFMPEG: извлечение 20 изображений из видео переменной длины - PullRequest
14 голосов
/ 30 декабря 2011

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

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

Таким образом, одно видео имеет длину 16 м 47 с => 1007 с. => Я должен сделать один снимок видео каждые 50 секунд.

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

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

Есть предложения или указания?

Спасибо, Vapire

Ответы [ 6 ]

16 голосов
/ 01 октября 2012

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

Для всех, у кого есть проблема, когда ffmpeg генерирует изображение для каждого отдельного кадра, вот как я могурешил (используя ответ бладиблы):

Сначала я собрал общее количество кадров в видео:

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>

Но, независимо от того, какой мод (n, 100) был установлен, ffmpeg выплевывал слишком много кадров.Мне пришлось добавить -vsync 0, чтобы исправить это, поэтому моя последняя команда выглядела так:

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

(где 100 - частота кадра, которую вы хотели бы использовать, например, каждый 100-й кадр)

Надеюсь, это спасет кого-то от неприятностей!

15 голосов
/ 05 февраля 2013

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

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

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

5 голосов
/ 10 января 2012

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

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

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

5 голосов
/ 30 декабря 2011

Можно попробовать преобразовать видео в 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
3 голосов
/ 12 ноября 2013

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

#/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 голос
/ 26 июля 2013

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