1

Я пытаюсь найти решение сделать экраны из видео с помощью 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

Как можно видеть, ведущие нули есть, но файлы не в порядке.
Я потерян здесь.
Как я могу получить правильное увеличение имен файлов из этого?

2 ответа2

1

Похоже, что вы хотите сделать, это разделить файл на N интервалов, по одному для каждого эскиза, и выбрать первый ключевой кадр после средней точки каждого интервала. Это можно сделать быстро с помощью одного запуска ffmpeg, учитывая длительность каждого интервала (D /N; назовем это I).

ffmpeg -discard nokey -skip_frame nokey -i input -vf select='eq(n,0)+gte(mod(t,I),I/2)*gte(t-prev_selected_t,I/2)',trim=1 -vsync 0 -vframes N image%d.jpg

-discard nokey говорит демультиплексору пропускать не ключевые кадры. -skip_frame nokey делает это для декодера. Для MP4..etc, это будет работать примерно в 15-20 раз быстрее, чем если бы эти опции были опущены.

1

Вы можете использовать команду printf чтобы обнулить число до фиксированной ширины.

printf [-v var] format [arguments]
       Write  the  formatted arguments to the standard output under the
       control of the format.  The -v option causes the  output  to  be
       assigned  to  the  variable var rather than being printed to the
       standard output.

       The format is a character string which contains three  types  of
       objects:  plain  characters, which are simply copied to standard
       output, character escape  sequences,  which  are  converted  and
       copied  to  the standard output, and format specifications, each
       of which causes printing of the next  successive  argument.

Реализация Bash примет спецификатор d с шириной и флагом 0 из printf() Си.

Пример:

$ for c in {8..11}; do
> printf -v result '%03d' $c
> echo $result
> done
008
009
010
011

Таким образом, вместо жесткого кодирования числа нулей в имени выходного файла, например thumb00$c.jpg , вы можете использовать printf, чтобы выяснить необходимое количество нулей, чтобы привести число к указанной ширине:

thumb$(printf '%03d' $c).jpg

Объяснение:

  • $() - это подстановка процесса. Это позволяет использовать вывод команды std как часть другой команды.
  • printf запускает команду printf .
  • '%03d' defines the format of вывода printf`.
    • % означает, что мы хотим использовать спецификатор формата.
    • 0 означает, что мы хотим обнулить панель.
    • 3 - это длина, на которую мы накладываем.
    • d - спецификатор формата, в частности это означает, что мы собираемся передать printf другой параметр, и этот параметр должен использоваться как десятичное целое число со знаком.
  • $c - это параметр, который мы отправляем в printf .

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .