Jest to aktualizacja / uzupełnienie dla wpisu Obróbka Audio-Video.
Informacja o strumieniach
ffmpeg -i INPUT.mkv
lub
ffprobe INPUT.mkv
Zamieszczam też skrypt podający w zwięzłej postaci najważniejsze (z mojego punktu widzenia) informacje.
Dzięki jednoliniowemu outputowi może być łatwo użyty na wszystkich plikach video (np. for f in *.mkv; do [ -f "$f" ] && media-info "$f"; done) celem uzyskania zbiorczej informacji.
Ekstrakcja strumieni z kontenera
# pierwsza ścieżka audio
ffmpeg -i INPUT.mkv -map 0:a:0 -c copy audio1.mka
# druga ścieżka audio
ffmpeg -i INPUT.mkv -map 0:a:1 -c copy audio2.mka
# video
ffmpeg -i INPUT.mkv -map 0:v:0 -c copy video.mkv
# okładka (druga ścieżka video)
ffmpeg -i INPUT.mkv -map 0:v:1 -c copy cover.jpg
# pierwsza ścieżka napisów tekstowych
# (z konwersją na srt)
# (to nie zadziała przy napisach wyrenderowanych)
ffmpeg -i input.mkv -map 0:s:1 subtitles.srt
# pierwsza ścieżka napisów wyrenderowanych (pgssub)
# (to nie zadziała przy napisach wyrenderowanych)
ffmpeg -i input.mkv -map 0:s:1 -c copy subtitles.sub
Budowanie kontenera
ffmpeg -i video.mp4 -i pl.m4a -i en.m4a -i pl.srt -i en.srt -map 0:v:0 \
-map 1:a:0 -metadata:s:a:0 "language=pl" -metadata:s:a:0 "title=Polski" -disposition:a:0 default \
-map 2:a:0 -metadata:s:a:1 "language=en" -metadata:s:a:1 "title=English" -disposition:a:1 0 \
-map 3:s:0 -metadata:s:s:0 "language=pl" -metadata:s:s:0 "title=Polski" -disposition:s:0 0 \
-map 4:s:0 -metadata:s:s:1 "language=en" -metadata:s:s:1 "title=English" -disposition:s:1 0 \
-default_mode infer_no_subs \
-max_interleave_delta 0 \
-metadata title="TYTUŁ" -metadata year="ROK" \
-attach cover*.jpg -metadata:s:t mimetype=image/jpeg \
-c copy OUTPUT.mkv
Modyfikacje bez rekodowania
# zmień prędkość audio.m4a (prędkość odtwarzania 96%, 1.041666666666 = 1/.96)
ffmpeg -itsscale 1.041666666666 -i video.mp4 -i audio.m4a -c copy OUTPUT.mkv
# przesunięcie (opóźnienie startu) audio.m4a o 2.4s
ffmpeg -itsoffset 2.4s -i video.mp4 -i audio.m4a -c copy OUTPUT.mkv
# przesunięcie (przyspieszenie startu) audio.m4a o 2.4s
ffmpeg -ss 2.4s -i video.mp4 -i audio.m4a -c copy OUTPUT.mkv
# użyj jedynie fragmentu pliku video.mp4 (od 2.8s do 10.1 sekundy)
ffmpeg -ss 2.8s -to 10.1s -i video.mp4 -i audio.m4a -c copy OUTPUT.mkv
# zmień (display) aspekt ratio na 800x600 (4:3) reskalujac obraz
ffmpeg -i INPUT.mkv -aspect 800:600 -c copy OUTPUT.mkv
Omówienie opcji
-map
- Definiuje co ma zostać dodane do pliku wynikowego.
- Posiada trzy argumenty rozdzielane
:- numer pliku wejściowego (w kolejności podawania opcji
-i) - typ strumienia (
a- audio,v- video,s- subtitles) - numer kolejny strumienia danego typu w pliku wejściowym
- numer pliku wejściowego (w kolejności podawania opcji
- Kolejność strumieni w pliku wynikowym odpowiada kolejności
-map
-metadata
- Po dwukropku określane jest czego metadane są ustawiane, m.in.:
g- całego pliku (domyślne gdy nie podano)s- strumienia, po czym następuje jego określenie typ (jak w opcji-map) oraz numer kolejny danego typu w pliku wynikowym
-disposition
- Dyspozycje dla strumienia (np. ustawienie jako domyślna ścieżka), specyfikacja strumienia jak w
-metadata:s
-default_mode infer_no_subs
- Wyłącza ustawianie pierwszej ścieżki napisów jako domyślnej gdy nie określono domyślnej.
-max_interleave_delta 0
- Zapobiega Starting new cluster due to timestamp powodującego problemy z audio przy przewijaniu.
-c copy
- Zapobiega reencodowaniu materiału
- Można określać dla poszczególnych strumieni – np.
-c:a copy -c:v libx265spowoduje przekopiowanie audio bez zmiany kodowania i przekodowanie video z uzyciem wskazanego kodeka (libx265)
-ss
- Określa timestamp początku fragmentu strumienia wejściowego (dla następującej po niej opcji
-i), który ma być użyty - Przy braku reenkodowania nie będzie precyzyjny (mogą znaleźć się fragmenty wcześniejsze, ale synchronizacja chwili 0 będzie zachowana)
-tt
- Określa timestamp końca fragmentu strumienia wejściowego (dla następującej po niej opcji
-i), który ma być użyty - Przy braku reenkodowania nie będzie precyzyjny (mogą znaleźć się fragmenty późniejsze, ale synchronizacja chwili 0 będzie zachowana)
-itsoffset
- Opóźnienie startu strumienia wejściowego (czas od 0 do podanego w tej opcji będzie czarnym ekranem / ciszą)
- Nie wymaga reenkodowania
-itsscale
- Skalowanie timestampów strumienia wejściowego przez podaną wartość (zmiana prędkości odtwarzania strumienia)
- Nie wymaga reenkodowania
Rekodowanie
video
ffmpeg -threads 8 -i input.mkv -map 0:v:0 -c:v libx265 -profile:v main10 -pix_fmt yuv420p10le -crf 22 output.mkv
crfokreśla jakość obrazu vs wielkość pliku, im niższa wartość tym mniej skompresowany obraz (zależnie od materiału 20, 22 daje dobrą jakość przy akceptowalnej wielkości pliku)- ze względu na
-map 0:v:0w pliku wynikowym będzie tylko video, alternatywnie można użyć zamiast tej opcji-c:a copyaby ścieżki audio zostało przekopiowane (bez reenkodowania)
audio
ffmpeg -i input.mkv -c:a aac -b:a 225k output.mkv
Rekodowanie fragmentu pliku
Strumienie (przynajmniej w większości współczesnych kodeków) video są podzielone na segmenty rozpoczynające się od klatek kluczowych. Umożliwia to m.in. odtwarzanie filmów na www od wskazanego miejsca. Pozwala jedak także na podmianę fragmentu pliku wideo bez potrzeby reenkodowania całości.
przykład 1 – prosty
# wydzielenie segmentów pliku bazowego
#
# `-f segment` zapewnia podział w miejscach klatek kluczowych
# (i działa lepiej od -to /-ss nie dodaje powtórzeń pierwszej/ostatniej klatki, itd)
#
# w opcji -segment_time podawany jest minimalny czas trwania segmentu
# wartości te dobieramy tak aby znajdowały się w okolicy miejsca fragmentu do wycięcia
# w pierwszej komendzie trochę przed początkiem usuwanego fragmentu
# w drugiej trochę przed końcem usuwanego fragmentu
ffmpeg -i INPUT.mkv -c copy -map 0:v -segment_time 4304 -f segment pre%03d.mkv
ffmpeg -i INPUT.mkv -c copy -map 0:v -segment_time 4317 -f segment post%03d.mkv
# wydzielamy też wycięty fragment do osobnego pliku
ffmpeg -i pre001.mkv -c copy -map 0:v -segment_time 12 -f segment edit%03d.mkv
# edycja i reencodowanie segmentu
# - do łaczenia używamy filtra concat
# - jako że aspect ratio obu mteriałów było inne, konieczne jest użycie filtra wideo do dostosowawnia aspect ratio
ffmpeg -to 7.333s -i edit000.mkv -ss 01:11:52.45 -to 01:12:01.666 -i FIX.mkv -ss 9.458 -i edit000.mkv \
-filter_complex "
[1:v]scale=1920:804:force_original_aspect_ratio=decrease:eval=frame,pad=1920:804:-1:-1:color=black[f]; \
[0:v] [f] [2:v] concat=n=3:v=1:a=0 [v]" \
-map "[v]" -c:v libx265 -profile:v main10 -pix_fmt yuv420p10le -b:v 2500k edited-v.mkv
# konwersja na ts (z zresetowanym czasem)
# bez tego kroku (łączenie mkv) w wynikowym pliku pierwsza ramka post001.mkv była umieszana przed dwiema ostatnim edited-v.mkv
# nie mam pewności czy obie opcje są wymagane, czy tylko jedna z nich oraz czy musi to być ts ... ale ta kombinacja działa ...
ffmpeg -i pre000.mkv -fflags +genpts -reset_timestamps 1 -c copy pre000.ts
ffmpeg -i edited-v.mkv -fflags +genpts -reset_timestamps 1 -c copy edited-v.ts
ffmpeg -i post001.mkv -fflags +genpts -reset_timestamps 1 -c copy post001.ts
# połączenie segmentów
echo -e 'file pre000.ts\nfile edited-v.ts\nfile post001.ts\n' > join.txt
ffmpeg -f concat -i join.txt -c copy OUTPUT-video.mkv
# edycja audio z pełnym reencodowaniem
ffmpeg -to 01:11:52.45 -i INPUT.mkv -ss 01:11:52.45 -to 01:12:01.666 -i FIX.mkv -ss 01:11:54.625 -i INPUT.mkv \
-filter_complex "[0:a:1] [1:a] [2:a:1] concat=n=3:v=0:a=1 [a]" \
-map "[a]" -c:a aac -b:a 224k OUTPUT-audio.mkv
przykład 2
- użyty filtr video do precyzyjnego docięcia montowanych fragmentów w oparciu o numer ramki
- audio montowane z bez pełnego rekodowania
# cutting input video
ffmpeg -i INPUT.mkv -c copy -map 0:v -segment_time 976 -f segment apre%03d.mkv
ffmpeg -i INPUT.mkv -c copy -map 0:v -segment_time 986 -f segment post%03d.mkv
# cuting patch
ffmpeg -i FIX.mkv -c copy -map 0:v -segment_time 965 -f segment fix%03d.mkv
ffmpeg -i fix001.mkv -fflags +genpts -reset_timestamps 1 -c copy fix001.ts
ffmpeg \
-i fix001.ts \
-filter_complex "[0:v]trim=start_frame=102:end_frame=419,setpts=PTS-STARTPTS[v];" \
-map "[v]" -c:v libx264 -profile:v High -pix_fmt yuv420p -crf 20 fix.mkv
# składanie
for f in *.mkv; do ffmpeg -i $f -fflags +genpts -reset_timestamps 1 -c copy ts/$f.ts; done
ffmpeg -f concat -i lista -c copy video-fixed.mkv
# cutting broken audio
ffmpeg -i INPUT.mkv -c copy -map 0:a -segment_time 976 -f segment apre%03d.mkv
ffmpeg -i INPUT.mkv -c copy -map 0:a -segment_time 991 -f segment post%03d.mkv
# cuting patch
ffmpeg -i afix.mp4 -c copy -map 0:a -segment_time 965 -f segment fix%03d.mkv
ffmpeg -i fix001.mkv -fflags +genpts -reset_timestamps 1 -c copy fix001.ts
ffmpeg -i fix001.ts -af "atrim=4.253:23.988;" -c:a aac -b:a 225k fix.mkv
# składanie
for f in *.mkv; do ffmpeg -i $f -fflags +genpts -reset_timestamps 1 -c copy ts/$f.ts; done
ffmpeg -f concat -i lista -c copy audio-fix.mkv
przykład 3
Samo polecenie reenkodowania fragmentu dla przypadku:
- wielokrotnego użycia fragmentów z pliku podstawowego i łatki
- usuwania zakodowanych w pliku łaty czarnych pasków (dostosowujących aspect ratio)
ffmpeg \
-i BASE.mkv -i FIX.mkv \
-filter_complex "[0:v]setdar=15/8[v0]; \
[1:v]crop=1920:1024,setdar=15/8[v1]; [v1]split=4[v11][v12][v13][v14]; \
[v0]trim=start_frame=0:end_frame=37,setpts=PTS-STARTPTS[v0a]; \
[v0]trim=start_frame=37:end_frame=1606,setpts=PTS-STARTPTS[v0b]; \
[v0]trim=start_frame=1606:end_frame=2503,setpts=PTS-STARTPTS[v0c]; \
[v0]trim=start_frame=2503:end_frame=3906,setpts=PTS-STARTPTS[v0d]; \
[v0]trim=start_frame=3906:end_frame=4059,setpts=PTS-STARTPTS[v0e]; \
[v11]trim=start_frame=37:end_frame=118,setpts=PTS-STARTPTS[v1a]; \
[v12]trim=start_frame=1687:end_frame=1880,setpts=PTS-STARTPTS[v1b]; \
[v13]trim=start_frame=2777:end_frame=2933,setpts=PTS-STARTPTS[v1c]; \
[v14]trim=start_frame=4336:end_frame=4418,setpts=PTS-STARTPTS[v1d]; \
[v0a] [v1a] [v0b] [v1b] [v0c] [v1c] [v0d] [v1d] [v0e] concat=n=9:v=1:a=0 [v]"\
-map "[v]" -c:v libx264 -profile:v High -pix_fmt yuv420p -crf 20 edited-v.mkv
# kodowanie z `-crf 20` bo przy `-b:v 2000k` rozwalona jest pierwsza klatka
# z jakiś powodów split dla v1 jest wymagany, a dla v0 nie ...
Przydatne funkcje shellowe
dodaje okładkę
add_cover() {
in="$1"; out="x_${in%.*}.mkv"; shift;
ffmpeg -i "$in" \
-map 0 \
-copy_unknown -map_metadata 0 \
-default_mode infer_no_subs -max_interleave_delta 0 \
"$@" \
-attach cover.jpg -metadata:s:t mimetype=image/jpeg \
-c copy "$out";
media-info "$in";
media-info "$out";
}
modyfikuje plik z okładką
jest to ominięcie prawdopodobnego bugu w ffmpeg związanego ze sposobem obsługi plików jpg załączanych poprzez -attach
edit_file() {
in="$1"; out="x_${in%.*}.mkv"; shift;
ffmpeg -i "$in" -map 0:v:1 -c copy cover.jpg
ffmpeg -i "$in" \
-map 0:v:0 -map 0:a: -map 0:s: \
-copy_unknown -map_metadata 0 \
-default_mode infer_no_subs -max_interleave_delta 0 \
"$@" \
-attach cover.jpg -metadata:s:t mimetype=image/jpeg \
-c copy "$out";
media-info "$in";
media-info "$out";
}
Dokumentacja
- https://www.ffmpeg.org/ffmpeg.html
- https://trac.ffmpeg.org/wiki/Encode/H.264
- https://trac.ffmpeg.org/wiki/Encode/H.265
- https://trac.ffmpeg.org/wiki/Encode/AV1