OpCode.eu.org

Blog O autorze O serwisie
2025-09-01

FFmpeg

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

-metadata

-disposition

-default_mode infer_no_subs

-max_interleave_delta 0

-c copy

-ss

-tt

-itsoffset

-itsscale

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

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

# 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:

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

Tagi: multimedia