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?
printf
tym zrobić ? Czy możesz poprawić swoją odpowiedź, biorąc pod uwagę to?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).
-discard nokey
mówi demuxerowi, aby pomijał klatki kluczowe.-skip_frame nokey
robi 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.źródło