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