Як збільшити число файлів у циклі for (ffmpeg)


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-записвач btw
  • 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 loop' очевидному 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

Як бачимо, провідні нулі є, але файли не в порядку.
Я загубився тут.
Як я можу отримати належне збільшення імен файлів з цього?

Відповіді:


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 прапор від C 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.

Я не розумію цього. Це висновок ffmpeg у циклі for. Що я можу зробити printf у цьому? Чи можете ви поліпшити свою відповідь, враховуючи це?
G.P.M

@ G.P.M Я оновив свою відповідь, це допоможе?
8bittree

Це те, що я шукав, чудово працює. Дуже дякую!
G.P.M

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 разів швидше, ніж якщо б ці опції були опущені.


Оце Так! Спочатку я не розумів, що з цим робити. Я подумав, що я поставив його в моїй петлі, і це, звичайно, не спрацювало. Але тоді я отримав це: Ні для петлі не потрібно, і я побіг він сам по собі, і його робота блискавка швидко! Одна незначна зміна була потрібна, хоча: Помістіть -vf у лапки "select = 'eq (n, 0 ... trim = 1") Велике спасибі за вказівку в цьому напрямку!
G.P.M
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.