Я пытаюсь найти решение сделать экраны из видео с помощью ffmpeg.
Большинство найденных примеров включают декодирование всего видео для получения изображений. Это - для больших видео - довольно медленно.
Лучшая попытка была описана примерно в: значимых-миниатюрах для видео-использования-ffmpeg
Там был псевдокод:
for X in 1..N
T = integer( (X - 0.5) * D / N )
run `ffmpeg -ss <T> -i <movie>
-vf select="eq(pict_type\,I)" -vframes 1 image<X>.jpg`
Куда:
- D - длительность видео, считываемого с одного ffmpeg -i или с ffprobe, у которого, кстати, есть хороший обработчик вывода JSON
- N - общее количество миниатюр, которые вы хотите
- X - номер миниатюры, от 1 до N
- T - момент времени для миниатюры
Я придумал рабочее решение на основе этого «псевдокода» и соединил его с монтажом миниатюр в imagemagick
:
#!/bin/bash
# some of them not used here
MOVIE=$1
D= # D - video duration
N=30 # N - wanted number of thumbnails
X=1 # X - thumbnail number
T= # T - time point for thumbnail
Y= # Y - nth frame to take
Z= # Z - total number of frames
W= # W - fps of the video
SP= # SP - starting point of extraction
OUTPUT=$2
# some of them from another approach - setting defaults
if [ -z "$N" ]; then N=30; fi
if [ -z "$COLS" ]; then COLS=3; fi
if [ -z "$ROWS" ]; then ROWS=10; fi
if [ -z "$HEIGHT" ]; then HEIGHT=360; fi
if [ -z "$SIZE" ]; then SIZE=3600; fi
# get video name without the path and extension
MOVIE_NAME=$(basename $MOVIE)
OUT_DIR=$(pwd)
if [ -z "$OUTPUT" ]; then OUTPUT=$(echo ${MOVIE_NAME%.*}_preview.jpg); fi
# get duration of input:
D=$(echo "$(ffprobe -hide_banner -i $MOVIE 2>&1 | sed -n "s/.*: \(.*\), start:.*/\1/p")" | sed 's/:/*60+/g;s/*60/&&/' | bc)
D=$(echo "$D/1" | bc) # get rid of the floating point part (make integer)
# get fps of input:
W=$(ffprobe $MOVIE 2>&1| grep ",* fps" | cut -d "," -f 5 | cut -d " " -f 2)
# get frame number of input:
Z=$(ffprobe -hide_banner -show_streams $MOVIE 2>&1 | grep nb_frames | head -n1 | sed 's/.*=//')
# as a fallback we'll calculate the frame count
# from duration and framerate, very unprecise, though
if [ "$Z" = "N/A" ]; then Z=$(echo "$D * $W / 1" | bc); fi
echo "Duration is: $D seconds / $Z frames @ $W fps"
# generate thumbnails in the /tmp folder
TMPDIR=/tmp/thumbnails-${RANDOM}/
mkdir $TMPDIR
for (( c=X; c<=N; c++ ))
do
Y=$(echo "($Z / $N)/1" | bc)
T=$(echo "(($c - 0.5) * $Y/1)" | bc)
SP=$(echo "$T / $W" | bc)
ffmpeg -hide_banner -ss $SP -i $MOVIE -an -sn -vf select="eq(pict_type\,I),scale=-1:360" -vframes 1 ${TMPDIR}thumb00$c.jpg
done
# mount the pics in one page neatly
montage ${TMPDIR}thumb*.jpg -background white -geometry +5+5 -tile ${COLS}x ${TMPDIR}output.jpg
rm -R $TMPDIR
echo $OUT_FILEPATH
Это работает, но я борюсь с созданными именами файлов.
Поскольку вызов ffmpeg происходит в цикле for, шаблон с очевидным name%03d.jpg
не будет работать для выходного файла.
Вывод последней итерации:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf54.59.106
Duration: 00:08:41.17, start: 0.023220, bitrate: 1866 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1280x720, 1731 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc
(default)
Metadata:
handler_name : VideoHandler
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
handler_name : SoundHandler
[swscaler @ 0x15a85a0] deprecated pixel format used, make sure you did set range correctly
Output #0, image2, to '/tmp/thumbnails-13957/thumb0030.jpg':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf56.40.101
Stream #0:0(und): Video: mjpeg, yuvj420p(pc), 640x360, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc (default)
Metadata:
handler_name : VideoHandler
encoder : Lavc56.60.100 mjpeg
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> mjpeg (native))
Press [q] to stop, [?] for help
frame= 1 fps=0.0 q=3.2 Lsize=N/A time=00:00:00.04 bitrate=N/A dup=1 drop=1
video:8kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Я попытался поместить переменную итерации с ведущими нулями в имя выходного файла thumb00$c.jpg
. Пока это работает, но: поскольку итерация превышает 10, мои имена файлов уже не в порядке, что означает, что следующая команда монтажа imagemagick размещает их в неправильном порядке.
Вот что я получаю:
-rw-rw-r-- 1 gpm gpm 17303 Sep 8 19:32 thumb0010.jpg
-rw-rw-r-- 1 gpm gpm 16474 Sep 8 19:32 thumb0011.jpg
- - " -
-rw-rw-r-- 1 gpm gpm 6323 Sep 8 19:32 thumb001.jpg
-rw-rw-r-- 1 gpm gpm 14789 Sep 8 19:32 thumb0020.jpg
-rw-rw-r-- 1 gpm gpm 18429 Sep 8 19:32 thumb0021.jpg
- - " -
-rw-rw-r-- 1 gpm gpm 18870 Sep 8 19:32 thumb002.jpg
-rw-rw-r-- 1 gpm gpm 7926 Sep 8 19:32 thumb0030.jpg
-rw-rw-r-- 1 gpm gpm 18312 Sep 8 19:32 thumb003.jpg
-rw-rw-r-- 1 gpm gpm 18274 Sep 8 19:32 thumb004.jpg
Как можно видеть, ведущие нули есть, но файлы не в порядке.
Я потерян здесь.
Как я могу получить правильное увеличение имен файлов из этого?