FFmpeg and FFmpeg-adjacent snippets.
ffmpeg -codecs | rg nvencffmpeg -filters | rg scaleffmpeg \
-i "in.mkv" \
-map 0:a:0 \
-map 0:a:2 \
-map 0:a:4 \
-c copy \
-map_metadata 0 \
-movflags +faststart \
"out.mka"-map 0:a:0
first input, first audio track (OBS combined audio)-map 0:a:2
first input, third audio track (OBS mic audio)-map 0:a:4
first input, fifth audio track (OBS discord audio)-c copy
do not re-encode-map_metadata 0
use applicable metadata from first input
for file in *.mkv; do \
ffmpeg \
-i "${file}" \
-map 0:a:0 \
-c:a copy \
-map_metadata 0 \
-movflags +faststart \
"temp/${file}"; \
done-map 0:a:0
first input, first audio track (combined audio)
whisper "recording.mkv" \
--device cuda \
--task transcribe \
--language EnglishCombines recorded audio, transcribed subtitles, and generated black-frame video into single output.
ffmpeg \
-i "recording.mkv" \
-i "subtitles.srt" \
-f lavfi -i color=c=black:s=720x480:r=1 -shortest \
-map 2:v:0 \
-map 0:a:0 \
-map 1:s:0 \
-c:a copy \
-c:s copy \
-map_metadata 1 \
-metadata:s:a:0 "title=" \
-metadata:s:a:0 "language=eng" \
-metadata:s:s:0 "language=eng" \
-movflags +faststart \
"/g/marked for deletion/out.mkv"-f lavfi -i color=c=black:s=720x480:r=1 -shortest
generates a low resolution all-black video track at 1fps with the same duration as the specified recording file.-map 2:v:0
third input, first video track (all-black)-map 0:a:0
first input, first audio track (OBS combined audio)-map 1:s:0
second input, first subtitle track (transcribed audio via whisper)
Modifying metadata so audio track and subtitle track auto-selection in media players plays nicely.
-metadata:s:a:0 title=""
metadata > track selection > audio tracks > first audio track - erase track's title-metadata:s:a:0 language=eng
metadata > track selection > audio tracks > first audio track - set track's language to english-metadata:s:s:0 language=eng
metadata > track selection > subtitle tracks > first subtitle track - set track's language to english
For greatest chance of success, all files should have same codecs and ideally same fps.
ffmpeg \
-f concat \
-safe 0 \
-i concat.txt \
-map 0 \
-c copy \
-map_metadata 0 \
-movflags +faststart \
"concatenated.mkv"Contents of concat.txt:
file 'input1.mkv'
file 'input2.mkv'
ℹ Note
May not need to specify video codec, or may want to specify a different codec.
ffmpeg \
-i "input.mkv" \
-map 0 \
-c:v h264_nvenc \
-c:a copy \
-vf fps=1 \
-movflags +faststart \
"out.mkv"One-liner. Really just fantastic to read. Even better copying into the terminal.
⚠ TODO
Test>=works (previously was==).
for file in *.mkv; do \
if [ $(ffprobe -v quiet -print_format json -select_streams v -show_streams -i "${file}" | jq -r '.streams | length') >= 2 ]; \
then echo "❌ ${file}"; \
fi done;- Loops over all
*.mkvfiles in folder, stores current file name in$filevariable. - Use
ffprobeandjqto count number of video streams. - Echoes current file to console if two or more video tracks, the not-first track(s) likely being thumbnail "video" tracks...
It probably doesn't matter but I think video stream should come first, then audio, then anything else. This simply checks if the first stream is a video stream.
for file in *.mkv; do \
if [ $(ffprobe -v quiet -print_format json -select_streams v -show_streams -i "${file}" | jq -r '.streams.[0].codec_type') != "video" ]; \
then echo "❌ ${file}"; \
fi done;- Loops over all
*.mkvfiles in folder, stores current file name in$filevariable. - Use
ffprobeandjqto check if first stream is video or not. - Echoes current file to console if first stream is not video.
for file in *.opus; do \
ffmpeg -y \
-i "${file}" \
-an \
-c:v copy \
"${file%.opus}.jpg"; \
done;-an
disables audio streams-c:v
copies video codec (does not re-encode)
for file in *.mkv; do \
ffmpeg -y \
-i "${file}" \
-an \
-map 0:v:1 \
-c copy \
"${file%.mkv}.jpg"; \
done;-an
disables audio streams-map 0:v:1
first input, second video track (most likely thumbnail "video" track)-c
do not re-encode
ffmpeg -y \
-i "in.mkv" \
-map 0 \
-c copy \
-map_metadata 0 \
-attach "thumb.jpg" \
-metadata:s:t "mimetype=image/jpeg" \
-metadata:s:t "filename=cover.jpg" \
-movflags +faststart \
"out.mkv"This is a rudimentary solution... For a more robust solution, see make_mka.sh.
for file in *.mkv; do \
ffmpeg -y \
-i "${file}" \
-map 0:a \
-c:a copy \
-map_metadata 0 \
-attach "${file%.mkv}.jpg" \
-metadata:s:t "mimetype=image/jpeg" \
-metadata:s:t "filename=cover.jpg" \
-movflags +faststart \
"${file%.mkv}.mka"; \
done;-attach "${file%.mkv}.jpg"
It's just easier to attach a standalone file as coverart...%.mkv
a bash thing that trims.mkvfrom the end of the string.
⚠ TODO
Check how necessary the metadata flags are...
I seem to recall *.opus metadata mapping to *.mka's audio track (instead of mapping to "global" metadata).
for file in *.opus; do \
ffmpeg -y \
-i "${file}" \
-map 0:a \
-c:a copy \
-map_metadata 0 \
-attach "${file%.opus}.jpg" \
-metadata:s:t "mimetype=image/jpeg" \
-metadata:s:t "filename=cover.jpg" \
-metadata:g "title=${metadata_title}" \
-metadata:g "description=${metadata_description}" \
-metadata:g "comment=${metadata_comment}" \
-metadata:g "artist=${metadata_artist}" \
-metadata:g "date=${metadata_date}" \
-metadata:g "year=${metadata_year}" \
-metadata:g "purl=${metadata_purl}" \
-metadata:s:a "filename=" \
-metadata:s:a "mimetype=" \
-metadata:s:a "title=" \
-metadata:s:a "description=" \
-metadata:s:a "comment=" \
-metadata:s:a "artist=" \
-metadata:s:a "date=" \
-metadata:s:a "year=" \
-metadata:s:a "purl=" \
-movflags +faststart \
"${file%.opus}.mka"; \
done;This example has a more complex stream and metadata mapping than other examples.
ffmpeg -y \
-i "in.mkv" \
-i "in.mka" \
-map 0:v \
-map 1:a:1 \
-map 0:s \
-c copy \
-map_metadata 0 \
-map_metadata:s:a:0 1:s:a:1 \
-map_metadata:s:s:0 0:s:s:0 \
-movflags +faststart \
"out.mkv"- -map 0:v
Video tracks from input 0. - -map 1:a:1
Audio track 1 from input 1. - -map 0:s
Subtitle tracks from input 0. map_metadata 0
Should copy global and video track metadata from input 0.map_metadata:s:a:0 1:s:a:1
Maps metadata from input 1, stream selection > audio track 1 to output, stream selection > audio track 0.map_metadata:s:s:0 0:s:s:0
Maps metadata from input 0, stream selection > subtitle track 0 to output, stream selection > subtitle track 0.
Contents of batch_vmaf_comparison.sh:
#!/bin/bash
reference=(
"ref1.mkv"
)
compare=(
"comp1.mkv"
)
length=${#reference[@]}
for (( i=0; i<${length}; i++ ));
do
#printf "Current index %d with value %s\n" $i "${reference[$i]}"
ffmpeg \
-i "${compare[$i]}" \
-i "${reference[$i]}" \
-filter_complex " \
[0:v]scale=1920:1080:flags=bicubic[comp]; \
[1:v]scale=1920:1080:flags=bicubic[ref]; \
[comp][ref]libvmaf=log_fmt=json:log_path=vmaf_scores_${i}.json" \
-f null -
done[0:v]scale=1920:1080:flags=bicubic[comp]
Ensure first video dimensions are 1080p; output labelled 'comp' for use in other filters.[1:v]scale=1920:1080:flags=bicubic[ref]
Ensure second video dimensions are 1080p; output labelled 'ref' for use in other filters.[comp][ref]libvmaf=log_fmt=json:log_path=vmaf_scores_${i}.json
Takecompandrefinputs and run them throughlibvmaf; log in json format and save log to disk, using corresponding array index.
Contents of detect_attachments.sh:
#!/bin/bash
#for file in **/*/*.mk*; do
find . -type f -name "*.mk*" -print0 | while IFS= read -r -d '' file; do
#echo "🔎 checking ${file}"
if ffprobe -v quiet -print_format json -show_streams "${file}" | jq -e '.streams[] | select(.codec_type == "attachment" and .tags.mimetype == "application/json")' > /dev/null; then
echo "📄 infojson ${file}"
fi
if ffprobe -v quiet -print_format json -show_streams "${file}" | jq -e '.streams[] | select(.codec_type == "attachment" and .tags.mimetype == "image/webp")' > /dev/null; then
echo "🖼 webp ${file}"
fi
doneSee: https://docs.nvidia.com/video-technologies/video-codec-sdk/ffmpeg-with-nvidia-gpu/index.html
I don't remember why I needed this but archiving in case.
ffmpeg -y \
-vsync 0 \
-hwaccel cuda \
-hwaccel_output_format cuda \
-i "input.mkv" \
-vf scale_cuda=X:Y \
-c:a copy \
-c:d copy \
-c:s copy \
-c:t copy \
-c:v hevc_nvenc \
"output.mkv"Was using this as part of a workflow to clip a title/credit sequence from an H265 source.
Sometimes we need exact segments (i.e. start and end times), and to avoid black frames before/after, we can re-encode.
ffmpeg -i "h265_10bit_input.mkv" \
-map 0 \
-ss "00:00:02.112" -to "00:01:17.060" \
-c:v h264_nvenc -pix_fmt yuv420p -preset p7 -profile high \
-vf scale=1280:-2 \
-c:a libopus -b:a 320k \
-af "channelmap=channel_layout=5.1" \
-movflags +faststart \
"output.720p.h264.mp4"Optional swaps:
# H265 NVENC 10,000Kbps
-c:v hevc_nvenc -preset slow -profile:v main10 -rc vbr -b:v 10000k -maxrate 13000k -bufsize 20000k -temporal-aq 1 -spatial-aq 1 -rc-lookahead 98 -2pass true -multipass 2 \
# Downmix audio to stereo; don't use -af flag if using this.
-c:a libopus -b:a 120k -ac 2 \-ss "00:00:02.112" -to "00:01:17.060"
Specific Segment from in_time to out_time; can quickly get values from LosslessCut.-c:v h264_nvenc -pix_fmt yuv420p
Converts down to 8-bit colour format (H264 doesn't support 10-bit).-vf scale=1280:-2
Scale video down, maintaining aspect ratio.-1may work, but some codecs require height or width to be a multiple ofn, in which case,-2works (see: https://trac.ffmpeg.org/wiki/Scaling#KeepingtheAspectRatio).-c:a libopus -b:a 320k
Re-encode audio into opus format for space efficiency.-af "channelmap=channel_layout=5.1"
Opus doesn't automatically remap channels, so if input media is 5.1, pass this in manually (see: https://trac.ffmpeg.org/ticket/5718).
In one of my D&D recordings, my mic had fallen back to a more basic processing chip, something to that effect. Windows usually detects the mic as a Yeti Blue, but this time, the mic was recognized as a generic mic. That is the long way to say, it sounded like garbage, and was extremely loud at times.
This snippet demonstrates applying an audio filter to one track before merging it with another for final output.
ffmpeg -i "input.mkv" \
-filter_complex "[0:a:2]acompressor=threshold=0.05:ratio=10:attack=200:release=1000[mic]; \
[0:a:4][mic]amix=inputs=2[aout]" \
-map "[aout]" \
-c:a libopus \
-b:a 320k \
-map_metadata 0 \
-movflags +faststart \
"out.fixed.mka"-filter_complex "[0:a:2]...[mic]"
Selects the 3rd audio track (0-indexed) for filtering; maps result to "mic" for subsequent operations.[0:a:4][mic]amix=inputs=2[aout]
Selects the 5th audio track (0-indexed) and the "mic" track from previous step for filtering; maps result to "aout" for subsequent operations.-map "[aout]"
Select "aout" track from previous step for output.
In the situation you have two or more audio tracks of different lengths, and want to pad the front of the audio so they all end at the same time, we can figure out the offset (in ms) and pad the audio in a filter_complex step.
Get the offset:
ffprobe -i "a1.mka" -show_entries format=duration -v quiet -of csv="p=0"
ffprobe -i "a2.mka" -show_entries format=duration -v quiet -of csv="p=0"Subtract the shorter from the longer, and take note of the number. I believe the output format here is X.YYY, where X is seconds, and YYY is ms. The adelay filter accepts ms, not sure if it accepts seconds.
ffmpeg \
-i "a1.mka" \
-i "a2.mka" \
-filter_complex " \
[0]adelay=750|750[padded]; \
[padded][1]amix=inputs=2:duration=longest" \
"out.mp3"[0]...[padded]
Selects the first input (0-indexed) for filtering; maps result to "padded" for subsequent operations.adelay=750|750
Adds 750ms of delay to the front of the current audio item being filtered.
Mostly used for quickly extracting funnies from longer session recordings.
ffmpeg -y \
-i "in.mkv" \
-map 0:a:0 -map_metadata 0 \
-ss "hh:mm:ss.000" -to "hh:mm:ss.000" \
"funny.mp3"-ss "00:00:02.112" -to "00:01:17.060"
Specific Segment from in_time to out_time; can quickly get values from LosslessCut.
for file in *.mkv; do
echo "📄 ${file}"
# Limited to audio tracks only.
ffprobe -v quiet -print_format json -show_streams "${file}" | jq -c '.streams[] | select(.codec_type == "audio") | {index, title: .tags.title}'
echo ""
doneExample output:
📄 2025-10-29.mkv
{"index":1,"title":"Main (Game, Mic, Music, Discord)"}
{"index":2,"title":"Mic"}
{"index":3,"title":"Discord"}
{"index":4,"title":"Desktop"}