Jak zwiększyć numery plików w pętli for (ffmpeg)

1

Próbuję znaleźć rozwiązanie do tworzenia ekranów z filmów za pomocą ffmpeg. Większość znalezionych przykładów obejmuje dekodowanie całego wideo w celu uzyskania obrazów. Jest to - w przypadku większych filmów - raczej wolne.
Lepsza próba została z grubsza opisana w: znaczących miniaturach dla wideo przy użyciu ffmpeg

Był tam pseudo kod:

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`

Gdzie:

  • D - czas trwania wideo odczytany z samego ffmpeg -i lub ffprobe, który ma fajny program do zapisywania danych wyjściowych JSON btw
  • N - całkowita liczba miniaturek, które chcesz
  • X - liczba miniatur, od 1 do N.
  • T - punkt czasowy dla Tumbnaila

Wymyśliłem działające rozwiązanie oparte na tym „pseudokodzie” i połączyłem je z montażem miniatur w 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

To działa, ale mam problemy z utworzonymi nazwami plików.
Ponieważ wywołanie ffmpeg odbywa się w pętli for, oczywisty name%03d.jpg wzorzec nie będzie działał dla pliku wyjściowego.

Wynik ostatniej iteracji:

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

Próbowałem i umieściłem zmienną iteracyjną z wiodącymi zerami w nazwie pliku wyjściowego thumb00$c.jpg. Działa to do tej pory - ale: Ponieważ iteracja trwa ponad 10, moje nazwy plików nie są już uporządkowane, co oznacza, że ​​następujące polecenie montażu imagemagick umieszcza je w niewłaściwej kolejności. Oto co otrzymuję:

-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

Jak widać, wiodące zera są tam, ale pliki nie są w porządku.
Zgubiłem się tutaj.
Jak mogę uzyskać z tego odpowiednie rosnące nazwy plików?

GPM
źródło

Odpowiedzi:

1

Możesz użyć printfpolecenia, aby zerować liczbę na określoną szerokość.

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.

Realizacja Bash będzie zaakceptować dspecyfikator, o szerokości i 0flagą z C jest printf()funkcją .

Przykład:

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

Zamiast więc na stałe wpisywać liczbę zer w nazwie pliku wyjściowego thumb00$c.jpg, możesz użyć printf, aby obliczyć potrzebną liczbę zer i doprowadzić liczbę do określonej szerokości:

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

Wyjaśnienie:

  • $()jest podstawieniem procesu. Pozwala na użycie standardowego wyjścia polecenia jako części innego polecenia.
  • printfuruchamia printfpolecenie.
  • '%03d' defines the format ofwyjście printf`.
    • % oznacza, że ​​chcemy użyć specyfikatora formatu.
    • 0 oznacza, że ​​chcemy wyzerować pad.
    • 3 to długość, do której padamy.
    • djest specyfikatorem formatu, w szczególności oznacza to, że przekażemy printfkolejny parametr i ten parametr powinien zostać użyty jako liczba całkowita dziesiętna ze znakiem.
  • $cto parametr, do którego wysyłamy printf.
8bittree
źródło
Nie do końca to rozumiem. Jest to wyjście ffmpeg w pętli for. Co mogę z printftym zrobić ? Czy możesz poprawić swoją odpowiedź, biorąc pod uwagę to?
GPM
@GPM Zaktualizowałem swoją odpowiedź, czy to pomaga?
8bittree
Właśnie tego szukałem, działa idealnie. Dziękuję bardzo!
GPM
1

Wygląda na to, że chcesz podzielić plik na N przedziałów, po jednym dla każdej miniatury i wybrać pierwszą klatkę kluczową po środku każdego przedziału. Można to zrobić szybko za pomocą jednego uruchomienia ffmpeg, biorąc pod uwagę czas trwania każdego interwału (D / N; nazwijmy to 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 nokeymówi demuxerowi, aby pomijał klatki kluczowe. -skip_frame nokeyrobi to dla dekodera. W przypadku MP4..etc będzie to działać około 15-20 razy szybciej, niż gdyby te opcje zostały pominięte.

Gyan
źródło
Łał! Po pierwsze nie rozumiałem, co z tym zrobić. Pomyślałem, że powinienem umieścić go w mojej pętli for i to oczywiście nie zadziałało. Ale potem to zrozumiałem: nie potrzebowałem pętli i szybko uruchomiłem ją i działającą błyskawicę! Potrzebna była jednak jedna drobna zmiana: umieść -vf w cudzysłowie "select = 'eq (n, 0 ... trim = 1" Dziękuję bardzo za skierowanie mnie w tym kierunku!
GPM