This how-to is primarily targeted at those who need to recursively transcode or remux their media using a shell environment. Just click on whichever description you're interested in and the code will output below it.

If you have any suggestions/comments leave me a comment below (login isn't required to post and there's no waiting period).

📌
You can also access the same info in my Github Gist.
ESSENTIAL ENCODING ACRONYMS/TERMINOLOGIES


      Bitrate & Quality Control Modes

      CBR = Constant Bit Rate
      VBR = Variable Bit Rate
      CRF = Constant Rate Factor
      ABR = Average Bit Rate
      VBV = Video Buffering Verifier
      CQP = Constant Quantization Parameter
      
      Framerate & Timing
      
      CFR = Constant Frame Rate
      VFR = Variable Frame Rate
      PTS = Presentation Time Stamp
      DTS = Decoding Time Stamp
      
      Codecs & Compression Standards
      
      AVC = Advanced Video Coding
      HEVC = High Efficiency Video Coding
      AV1 = AOMedia Video 1
      VP8 & 9 = Googles open-source video codec
      MPEG = Moving Picture Experts Group
      AAC = Advanced Audio Coding
      ALAC = Apple Lossless Audio Codec
      FLAC = Free Lossless Audio Codec

      Metadata & Image Information

      GOP = Group of Pictures
      SAR = Sample Aspect Ratio
      DAR = Display Aspect Ratio
      PIX_FMT = Pixel Format
      IDR = Instantaneous Decoder Refresh
    

2-Pass Video Buffering Verifier (VBV) Encoding


Use this rate control mode if you are targeting a specific output file size, and if output quality from frame to frame is of less importance. This is best explained with an example. Your video is 10 minutes (600 seconds) long and an output of 200 MiB is desired. The first step is the conversion of the wanted filesize to kbit/s (kilobits per second).

💾 understanding_rate_control_modes_x264_x265.pdf

🧠
CONVERSION TIP:
1 MiB/s (Mebibyte per second) equals 8388.608 kbit/s (kilobits per second). This is based on 1 MiB = 220 bytes = 1,048,576 bytes, and since 1 byte = 8 bits, 1 MiB = 8,388,608 bits. To convert MiB/s to kbit/s, multiply the value by 8388.608.
The reason why this conversion formula seems so wacky is because we are doing a conversion from a binary byte unit (IEC) which uses powers of 2, specifically basing them on 1024 (210) into a decimal bits per second unit (SI) which uses base-10 aka powers of 10 (103, 104, 105 etc). You can find more info about the differences here and here.

Since bitrate = file size / duration, you can also forgo the bitrate calculation if you already know what final (average) bitrate you need.

(200 MiB * 8388.608 [converts MiB to kbit/s (kilobits per second); note:
not 8192 as 1 kBit is always 1000 bit]) / 600 seconds = ~2796 kBit/s total bitrate

2796 - 128 kBit/s (desired audio bitrate) = 2668 kBit/s video bitrate

# H.265/HEVC 2-PASS ENCODING: libx265
ffmpeg -i <input> -c:v libx265 -b:v 1M -x265-params pass=1:vbv-maxrate=1000:vbv-bufsize=2000 -f null /dev/null
ffmpeg -i <input> -c:v libx265 -b:v 1M -x265-params pass=2:vbv-maxrate=1000:vbv-bufsize=2000 <output>

# H.264/AVC 2-PASS ENCODING: libx264
ffmpeg -i <input> -c:v libx264 -b:v 1M -maxrate 2M -bufsize 2M -pass 1 -f null /dev/null
ffmpeg -i <input> -c:v libx264 -b:v 1M -maxrate 2M -bufsize 2M -pass 2 <output>

Useful links:
https://trac.ffmpeg.org/wiki/Encode/H.264#twopass
https://trac.ffmpeg.org/wiki/Encode/H.265#Two-PassEncoding
https://slhck.info/video/2017/03/01/rate-control.html
https://blog.programster.org/ffmpeg-hevc-encoding
https://superuser.com/questions/1362800/ffmpeg-2-pass-encoding

Strip all subtitles from input file

# standard
ffmpeg -i inputfile -map 0:v -map 0:a -c:v copy -c:a copy outputfile
# alternatively you can use the '-sn' parameter:
ffmpeg -i inputfile -map 0 -sn -codec copy outputfile
# for loop
for f in *.mkv; do ffmpeg -i "$f" -map 0:v -map 0:a -c:v copy -c:a copy ./working/"${f/.mkv/.x264.mkv}"; done

Remove chapters if they exist

# standard
ffmpeg -i inputfile -map_chapters -1 outputfile
# for loop
for f in *.mkv; do ffmpeg -map 0 -map_chapters -1 -codec copy "${f/.mkv/-modified.mkv}"; done

Convert audio stream to AAC and insert subtitles stream from external srt file

# standard
ffmpeg -i inputfile -f srt -i subtitlefile -map 0:v -map 0:a -map 1:0 -c:v copy -c:a aac -c:s srt outputfile
# for loop
# requires subtitle files in same directory as input file with the same base name and srt file extension
for f in *.mkv; do ffmpeg -i "$f" -f srt -i "${f/.mkv/.srt}" -map 0:v -map 0:a -map 1:0 -c:v copy -c:a aac -c:s srt ./working/"${f/.mkv/.modified.mkv}"; done

Determine if audio stream is not using AAC codec and transcode into AAC, or not

for f in *.mkv; do
audioformat=$(ffprobe -loglevel error -select_streams a:0 -show_entries stream=codec_name -of default=nw=1:nk=1 "$f")
if [ "$audioformat" = "aac" ]; then
  echo -e '\nffprobe detected aac audio streams\nPreparing to copy english audio stream' ; sleep 4
  ffmpeg -i "$f" -map 0:v:0 -map 0:a:m:language:ger -map 0:a:m:language:eng -map 0:s:m:language:eng -movflags faststart -c:v libx264 -crf 23 -c:a copy -c:s copy "${f/x265/x264}"
  else
  echo -e '\nffprobe detectable audio streams not equal to aac\nPreparing to transcode with aac' ; sleep 4
  ffmpeg -i "$f" -map 0:v:0 -map 0:a:m:language:ger -map 0:a:m:language:eng -map 0:s:m:language:eng -movflags faststart -c:v libx264 -crf 23 -c:a aac -c:s copy "${f/x265/x264}"
fi
done

Convert 10/8bit HEVC/H.265 to 8bit AVC/H.264 MKV high profile slow preset, insert external SRT subtitle file and not-DEFAULT with English language

# standard
ffmpeg -y -i inputfile -f srt -i subtitlefile -map 0:v:0 -map 0:a:0 -map 1:0 -movflags faststart -profile:v high -pix_fmt yuv420p -preset slow -disposition:s:0 0 -metadata:s:s:0 language=eng -codec:v libx264 -codec:a copy -codec:s srt outputfile
# for loop
for f in *.mkv; do ffmpeg -y -i "$f" -f srt -i "${f/.mkv/.srt}" -map 0:v:0 -map 0:a:0 -map 1:0 -movflags faststart -profile:v high -pix_fmt yuv420p -preset slow -disposition:s:0 0 -metadata:s:s:0 language=eng -codec:v libx264 -codec:a copy -codec:s srt "${f/.mkv/-modified.mkv}"; done

How to map existing subtitle streams only if they exist

# standard
ffmpeg -y -i inputfile -map "0:s?" -codec:s copy outputfile
# for loop
for f in *.mkv; do ffmpeg -y -i "$f" -map "0:s?" -codec:s copy "${f/.mkv/-modified.mkv}"; done

Increase volume to 150% of original media

for f in *.mkv; do ffmpeg -i "$f" -map 0 -filter:a "volume=1.5" -c:v copy -c:a aac -c:s copy "${f/.mkv/-modified.mkv}"; done

Remove all data streams from the container

ffmpeg -i in.mp4 -c copy -dn -map_metadata:c -1 out.mp4
for f in *.mp4; do ffmpeg -i "$f" -c copy -dn -map_metadata:c -1 "$f"; done

Remove closed captions (CC)

ffmpeg -y -i input.mkv -map 0 -c copy -bsf:v "filter_units=remove_types=6" output.mkv
for f in *.mkv; do ffmpeg -y -i "$f" -default_mode infer_no_subs -map 0:v -map 0:a -map 0:s? -c:v copy -c:a copy -c:s copy -bsf:v "filter_units=remove_types=6" "${f/.mkv/-modified.mkv}"; done

Insert srt subtitle to mp4

ffmpeg -i inputfile -i subtitlefile -c:v copy -c:a copy -c:s mov_text outputfile
for f in *.mp4; do ffmpeg -y -i "$f" -i "${f/.mp4/.srt}" -c:v copy -c:a copy -c:s mov_text "${f/.mp4/-modified.mp4}"; done

Export clean Subrip (SRT) subtitle file without annoying font HTML tagging

# the trick is to use '-c:s text' instead of '-c:s srt' or '-c:s mov_text' in MP4's
for f in *.mp4; do ffmpeg -y -i "$f" -map 0:s? -c:s text "${f/.mp4/.srt}"; done

Change Display Aspect Ratio (DAR) in the container

# Sample Aspect Ratio = (SAR) and Display Aspect Ratio (DAR)
ffmpeg -i input.mp4 -map 0 -aspect 1:1 -codec copy output.mp4
ffmpeg -i input.mkv -map 0 -aspect 2.39 -codec copy output.mkv

Change the Sample Aspect Ratio (SAR) in the video stream

ffmpeg -i in.mp4 -c copy -bsf:v "h264_metadata=sample_aspect_ratio=4/3" out.mp4

Increase max muxing queue size

ffmpeg -i 'input.mkv' -max_muxing_queue_size 400 'output.mkv'

Remove subtitle and audio description/title and add english metadata

for f in *.mkv; do ffmpeg -y -i "$f" -codec copy -metadata:s:a:0 language=eng -metadata:s:a:0 title= -metadata:s:s:0 language=eng -metadata:s:s:0 title= ./working/"${f}"; done

Add text metadata to various sections of file

ffmpeg -y -i input.mkv -codec copy -metadata comment="Matroska muxer also accepts free-form key/value metadata pairs" -metadata foo="bar" -metadata description="Matroska muxer description" -metadata:s:v:0 title="Video stream title/description" -metadata:s:a:0 title="Audio stream title/description" -metadata:s:s:0 language=eng -metadata:s:s:0 title="Subtitle stream title/description" output.mkv
for f in *.mkv; do ffmpeg -y -i "$f" -codec copy -metadata comment="Matroska muxer also accepts free-form key/value metadata pairs" -metadata foo="bar" -metadata description="Matroska muxer description" -metadata:s:v:0 title="Video stream title/description" -metadata:s:a:0 title="Audio stream title/description" -metadata:s:s:0 language=eng -metadata:s:s:0 title="Subtitle stream title/description" ./working/"${f}"; done

Add Matroska Title metadata to the container while removing Title metadata from all of its streams (video, audio, subtitle)

ffmpeg -y -i input.mkv -f srt -i input.en.srt -map 0:v -map 0:a:1 -map 1:0 -movflags +faststart -metadata:s:v:0 language= -metadata:s:s:0 language=eng -metadata:s:v:0 Title= -metadata:s:a:0 Title= -metadata:s:s:0 Title= -metadata Title="Who Am I (1998)" -disposition 0 -disposition:s:1 default -c:v copy -c:a copy -c:s srt output.mkv

Change the Constant Rate Factor (CRF) which modifies output video quality and file size

ffmpeg -i inputfile -vcodec libx264 -crf 23 outputfile

Change/scale the video screen-size (for example to 1080p or half its pixel size)

ffmpeg -i inputfile -vf "scale=iw/2:ih/2" outputfile

# This method takes care of error: (libx264) "height not divisible by 2"
# https://stackoverflow.com/questions/20847674/ffmpeg-libx264-height-not-divisible-by-2
# https://superuser.com/questions/624563/how-to-resize-a-video-to-make-it-smaller-with-ffmpeg
ffmpeg -y -i "input.mp4" \
-map 0:v -map 0:a -dn -map_chapters -1 -filter:v scale="1920:trunc(ow/a/2)*2" \
-movflags faststart -profile:v high -pix_fmt yuv420p -metadata:s:a:0 language=eng \
-c:v libx264 -c:a copy "output.mp4"

##### Another one but different
ffmpeg -y -i "input.mp4" -map 0:v -map 0:a \
-dn -map_chapters -1 -metadata:s:v:0 handler_name= -metadata:s:a:0 handler_name= \
-vf pad=ceil(iw/2)*2:ceil(ih/2)*2 -movflags faststart -profile:v high -pix_fmt yuv420p \
-metadata:s:a:0 language=eng -c:v libx264 -c:a copy "output.mp4"

Brighten only dark scenes in a media file

# https://ffmpeg.org/ffmpeg-filters.html#eq
# To brighten only the dark scenes in a media file using FFmpeg, you can use the eq filter with a higher brightness value for dark scenes and a lower value for bright scenes. This will make the darker areas appear brighter while maintaining the brightness of the brighter areas.

1. Identify the threshold:
Determine a value for the brightness increase. This value will determine how much brighter the dark scenes will become.
2. Use the eq filter:
FFmpeg\'s eq filter allows you to adjust brightness, contrast, and gamma. Use the following format:

    ffmpeg -i input.mp4 -vf "eq=brightness=brightness_value:contrast=contrast_value:saturation=saturation_value:gamma=gamma_value" output.mp4

* brightness_value: The value to increase brightness by (e.g., 0.1 for a 10% increase).
* contrast_value: The value to adjust the contrast by (e.g., 1.0 for no change, 1.2 for a 20% increase).
* saturation_value: The value to adjust saturation by (e.g., 1.0 for no change, 1.5 for a 50% increase).
* gamma_value: The value to adjust gamma by (e.g., 1.0 for no change, 1.5 for a 50% increase).

## Important Notes

> IMPORTANT TIP: The legacy FFmpeg video filter for color correction, including brightness, contrast, and saturation, was mp=eq2. It has since been deprecated and replaced by the eq filter.
> Attempting to use mp=eq2 in modern FFmpeg versions will result in an error.

The eq filter provides the same functionality with updated syntax. Here is an example of how the syntax changed:

### Legacy mp=eq2 syntax

ffmpeg -i input.mp4 -vf mp=eq2=contrast:brightness:saturation:gamma output.mp4

### Modern eq Filter Syntax

ffmpeg -i input.mp4 -vf eq=contrast=1.5:brightness=-0.1:saturation=1.2 output.mp4

The options can be specified by name, separated by colons (:), for clarity and to avoid the order-dependent issues of the older filter.

### Trial and Error

You might need to experiment with different brightness_value settings to achieve the desired result.

### Over-brightening

Be careful not to over-brighten the video, as it can cause white parts to appear washed out.

### Other Filters

You could also use other filters like gamma=1:1:1 for adjusting gamma.

## Example with Gamma Adjustment

    ffmpeg -i input.mp4 -vf "eq=brightness=0.1:contrast=1.2:saturation=1.5:gamma=1.2" output.mp4

This command will increase the brightness by 10% (brightness=0.1), increase contrast by 20% (contrast=1.2), increase the saturation by 50% (saturation=1.5), and increase gamma by 20% (gamma=1.2).
In summary, by using the eq filter and experimenting with brightness, contrast, saturation, and gamma values, you can effectively brighten only the dark scenes in your media file while preserving the brightness of the already bright scenes.

ffprobe: Output filename, audio codec name, and number of channels

find . -mount -depth -maxdepth 1 -regextype posix-extended -regex '.*\.mkv$|.*\.mp4$' -type f -exec bash -c 'echo "{}" $(ffprobe -loglevel error -select_streams a:0 -show_entries stream=codec_name,channels -of default=nw=1:nk=1 "{}")' \;

ffprobe: Output filename, and video codecs

find . -mount -depth -maxdepth 1 -regextype posix-extended -regex '.*\.mkv$|.*\.mp4$' -type f -exec bash -c 'echo "{}" $(ffprobe -loglevel error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 "{}")' \;

ffprobe: Output filename and video bit depth

find . -mount -depth -maxdepth 1 -regextype posix-extended -regex '.*\.mkv$|.*\.mp4$' -type f -exec bash -c 'echo "{}" $(ffprobe -loglevel error -select_streams v:0 -show_entries stream=bits_per_raw_sample -of default=nw=1:nk=1 "{}")' \;

Incorporating Bash arithmetic into ffmpeg command

for f in *.mkv; do ffmpeg -y -i "${f}" -i "${f/.mkv/.m4a}" \
-c:v copy -b:a:0 128k -filter_complex "[a:0]aresample=async=1000[a:0]" -movflags faststart -c:a:0 aac -c:a:1 copy -c:s copy \
-map 0:v:0 -map 1:0 -map 0:a:0 $(i=1; while [ $i -lt 28 ]; do echo -n "-map 0:s:$((i++)) "; done) \
-metadata:s:a:0 language=eng -disposition 0 -disposition:a:0 default -disposition:s:0 default -metadata:s:s:0 title= \
-metadata:s:a comment= -shortest "${f/.mkv/-modified.mkv}"; done

Change the H.264 profile to Baseline

ffmpeg -i inputfile -profile:v baseline outputfile

Concatenate multiple media files

# https://trac.ffmpeg.org/wiki/Concatenate
for f in *.mkv; do echo "file '$f'" >> mylist.txt; done
ffmpeg -f concat -safe 0 -i "mylist.txt" "output.mkv"

Map only the languages you want in the audio and subtitle streams

# With this example we will only map the English audio and subtitle streams if they exist:
ffmpeg -y -i "input.mkv" -map 0:v:0 -map 0:a:m:language:eng -map "0:s:m:language:eng?" -c:v copy -codec:a copy -c:s srt "output.mkv"
for f in *.mkv; do ffmpeg -y -i "$f" -map 0:v:0 -map 0:a:m:language:eng -map "0:s:m:language:eng?" -c:v copy -c:a copy -c:s copy "path/$f"; done

Exclude only VobSub subtitles while copying all other subtitle streams using stream-based mapping with a negative filter

ffmpeg -i input.mkv -map 0 -map -0:s:m:handler_name:!dvd_subtitle -c copy output.mkv
for f in *.mkv; do ffmpeg -y -i "$f" -map 0 -map -0:s:m:handler_name:!dvd_subtitle -c copy "path/$f"; done

Insert arbitrary audio stream and alter the starttime offset with '-itsoffset' paramter

ffmpeg -y -i inputfile -itsoffset -500ms -f aac -i 'audio.aac' -map 0:v -map 1:0 -map 0:a:0 \
-disposition:a:0 default -disposition:a:1 0 -metadata:s:a:0 language=eng -metadata:s:a:1 \
language=eng -fflags +bitexact -flags:v +bitexact -flags:a +bitexact -ac 2 -c:v copy \
-c:a:0 aac -c:a:1 copy outputfile

Bitrate Guidelines

# Calculate the bitrate you need by dividing your target size (in bits)
# by the video length (in seconds). For example for a target size of
# 1 GB (one gigabyte, which is 8 gigabits) and 10,000 seconds of video
# (2:h 46 min 40 s), use a bitrate of 800 000 bit/s (800 kbit/s):
ffmpeg -i input.mp4 -b 800k output.mp4

Transcode avi files into standard mp4 containers

for f in **/*.avi; do ffmpeg -i "$f" -map 0:v:0 -map 0:a -dn -map_chapters -1 \
-movflags faststart -profile:v high -pix_fmt yuv420p -metadata:s:a:0 language=eng \
-metadata:s:v:0 title= -metadata:s:a:0 title= -c:v libx264 -c:a aac \
"../.working/${f/.avi/.mp4}"; done

Convert interlaced to progressive (1080i -> 1080p)

ffmpeg -i inputfile -vf yadif -c:v libx264 -preset slow -crf 19 -c:a aac -b:a 192k -c:s srt -max_muxing_queue_size 400 outputfile
ffmpeg -i "interlaced.mkv" -vf yadif=1 -movflags +faststart -y -preset fast -profile:v high -crf 20 -ac 2 -b:a 192k -strict experimental -c:v libx264 -c:a aac "progressive.mkv"
# yadif=0 is normally the preferred option when deinterlacing video as yadif=1 will double the frame-rate of the output video
ffmpeg -i "interlaced.mkv" -vf yadif=0 -y -preset fast -profile:v high -crf 20 -ac 2 -b:a 192k -strict experimental -c:v libx264 -c:a aac "progressive.mkv"

H.264 interlaced BT709 > H.264 progressive BT709

ffmpeg -i inputfile -default_mode infer_no_subs -map 0 -map_chapters -1 -dn -vf "yadif=0, colorspace=all=bt709:iall=bt601-6-625:fast=1" -colorspace 1 -color_primaries 1 -color_trc 1 -b:v 6M -bufsize 6M -minrate 3M -maxrate 10M -profile:v high -pix_fmt yuv420p -preset slow -channel_layout "5.1" -strict experimental -max_muxing_queue_size 400 -c:v libx264 -c:a aac -c:s copy outputfile

Nvidia Hardware Accelerated Latency-Tolerant High-Quality H.264 Transcoding

# standard
ffmpeg -y -vsync 0 -hwaccel cuda -hwaccel_output_format cuda -i inputfile -c:a copy -c:v h264_nvenc -preset p6 -tune hq -b:v 5M -bufsize 5M -maxrate 10M -qmin 0 -g 250 -bf 3 -b_ref_mode middle -temporal-aq 1 -rc-lookahead 20 -i_qfactor 0.75 -b_qfactor 1.1 outputfile

Nvidia HW accelerated H264 10-bit to 8-bit BT709 color

# for loop
for f in *.mkv; do ffmpeg -vsync 0 -hwaccel cuda -y -i "$f" -map 0 -profile:v high -pix_fmt yuv420p -preset slow -vf "colorspace=all=bt709:iall=bt601-6-625:fast=1" -colorspace 1 -color_primaries 1 -color_trc 1 -codec:v h264_nvenc -codec:a copy -codec:s copy "${f/.mkv/-modified.mkv}"; done

Nvidia HW Accelerated H.264 interlaced BT709 > H.264 progressive BT709

# standard
ffmpeg -vsync 0 -hwaccel cuda -y -i inputfile -map 0 -vf "yadif=0, colorspace=all=bt709:iall=bt601-6-625:fast=1" -profile:v high -pix_fmt yuv420p -preset slow -c:v h264_nvenc -c:a copy -c:s copy outputfile
# real-world example
ffmpeg -vsync 0 -hwaccel cuda -y -i inputfile.mkv -default_mode infer_no_subs -map 0 -map_chapters -1 -dn -metadata title="Movie Title" -metadata:s:v:0 title= -metadata:s:a:0 title= -vf "yadif=0, colorspace=all=bt709:iall=bt601-6-625:fast=1" -colorspace 1 -color_primaries 1 -color_trc 1 -b:v 6M -bufsize 6M -minrate 3M -maxrate 10M -profile:v high -pix_fmt yuv420p -preset slow -channel_layout "5.1" -strict experimental -max_muxing_queue_size 400 -c:v h264_nvenc -c:a aac -c:s copy outputfile.mkv
    

For looped Nvidia HW-accelerated h264_nvenc PAL bt601/bt470bg > NTSC transcoding

# verbose for loop:
for VAR in 01 02 03 04 05 06 07 08 09 10 11 12 13; do ffmpeg -vsync 0 -hwaccel cuda -y -i "VTS_$(echo $VAR)_1.VOB" -movflags faststart -map 0:v:0 -map 0:a:0 -dn -map_chapters -1 -profile:v high -pix_fmt yuv420p -preset slow -x264-params "iall=bt601-6-625:fast=1" -color_range 1 -colorspace bt470bg -color_primaries bt470bg -color_trc gamma28 -metadata title="Title (year) S01E$(echo $VAR)" -metadata:s:a:0 title= -metadata:s:a:0 language=eng -disposition:s 0 -b:v 2500k -minrate 1000k -maxrate 2500k -b:a 128k -codec:v h264_nvenc -channel_layout:a:0 "stereo" -codec:a aac "Title - S01E$VAR.mp4"; done
# less-verbose for loop:
for VAR in {01..13}; do ffmpeg -vsync 0 -hwaccel cuda -y -i "VTS_$(echo $VAR)_1.VOB" -movflags faststart -map 0:v:0 -map 0:a:0 -dn -map_chapters -1 -profile:v high -pix_fmt yuv420p -preset slow -x264-params "iall=bt601-6-625:fast=1" -color_range 1 -colorspace bt470bg -color_primaries bt470bg -color_trc gamma28 -metadata title="Title (year) S01E$(echo $VAR)" -metadata:s:a:0 title= -metadata:s:a:0 language=eng -disposition:s 0 -b:v 2500k -minrate 1000k -maxrate 2500k -b:a 128k -codec:v h264_nvenc -channel_layout:a:0 "stereo" -codec:a aac "Title - S01E$VAR.mp4"; done
    

H264 10-bit to 8-bit BT709 color

for f in *.mkv; do ffmpeg -y -i "$f" -default_mode infer_no_subs -map 0:v:0 -map 0:a -map "0:s?" -dn -map_chapters -1 -profile:v high -pix_fmt yuv420p -preset slow -vf "colorspace=all=bt709:iall=bt601-6-625:fast=1" -colorspace 1 -color_primaries 1 -color_trc 1 -metadata:s:a:0 title= -codec:v libx264 -codec:a copy -codec:s copy "${f/.mkv/-modified.mkv}"; done
    

Convert H.265 > H.264 and convert 6CH AC3 audio to AAC and copy only the first subtitle stream

find . -depth -name '*.mkv' -type f -exec bash -c 'ffmpeg -i "$0" -map 0:v:0 -map 0:a:0 -map 0:s:0? -movflags faststart -c:v libx264 -ac 6 -ar 48000 -b:a 768k -channel_layout "5.1" -c:a aac -c:s copy -crf 23 "${0/x265/x264}"' {} \;
    

Convert H.265 > H.264 and convert 6CH AC3 audio to AAC and copy all subtitle streams

find . -depth -name '*x265*.mkv' -type f -exec bash -c 'ffmpeg -i "$0" -map 0:v:0 -map 0:a:0 -map 0:s? -movflags faststart -c:v libx264 -ac 6 -ar 48000 -b:a 768k -channel_layout "5.1" -c:a aac -c:s copy -crf 23 "${0/x265/x264}"' {} \;; find . -name '*x265*' -type f -delete
    

Convert H.265 > H.264 and retain original audio and all subtitles

find . -depth -name '*x265*.mkv' -type f -exec bash -c 'ffmpeg -i "$0" -map 0:v:0 -map 0:a:0 -map 0:s? -movflags faststart -c:v libx264 -preset slow -c:a copy -c:s copy -crf 23 "${0/x265/x264}"' {} \;; find . -name '*x265*' -type f -delete
    

Copy original video stream and convert 6CH AC3 audio to AAC and copy all subtitle streams

find . -depth -name '*x265*.mkv' -type f -exec bash -c 'ffmpeg -i "$0" -map 0:v:0 -map 0:a:0 -map 0:s? -c:v copy -ac 6 -ar 48000 -b:a 384k -channel_layout "5.1" -c:a aac -c:s copy "${0/x265/x264}"' {} \;; find . -name '*x265*' -type f -delete
    

Constant frame-rate with variable bitrate (VBR) nvidia hw-accel transcoding

# one-liner: Constant frame-rate with variable bitrate (VBR) nvidia hw-accel transcoding
for f in *.mkv; do ffmpeg -vsync 2 -hwaccel cuda -y -i "$f" -default_mode infer_no_subs -map 0 -map_chapters -1 -dn -metadata title="${f/.mkv/}" -metadata:s:v:0 title= -metadata:s:a:0 title= -colorspace 1 -color_primaries 1 -color_trc 1 -r 24 -b:v 5M -minrate 3M -maxrate 8M -bufsize 10M -profile:v high -pix_fmt yuv420p -preset slow -strict experimental -max_muxing_queue_size 400 -c:v h264_nvenc -c:a copy -c:s copy "$f"; done

# multi-lined: Constant frame-rate with variable bitrate (VBR) nvidia hw-accel transcoding
for f in *.mkv; do ffmpeg -vsync 2 -hwaccel cuda -y -i "$f" -default_mode infer_no_subs \
-map 0 -map_chapters -1 -dn -metadata title="${f/.mkv/}" -metadata:s:v:0 title= \
-metadata:s:a:0 title= -colorspace 1 -color_primaries 1 -color_trc 1 -r 24 \
-b:v 5M -minrate 3M -maxrate 8M -bufsize 10M -profile:v high -pix_fmt yuv420p -preset slow \
-strict experimental -max_muxing_queue_size 400 -c:v h264_nvenc -c:a copy -c:s copy \
"$f"; done
    

Generate thumbnails from video

# adjust the fps interval to change the number of thumbnails generated
ffmpeg -loglevel verbose -i 'input.mkv' -vf fps=1/1000 thumb-%03d.jpg
# add "-discard nokey" in order to generate thumbnails faster since it does not need to scan the file for every keyframe
ffmpeg -discard nokey -i 'input.mkv' -vf fps=1/300 thumb-%03d.png
    

vsync 1 CFR, cq = 30, avi > mp4

for f in *.avi; do ffmpeg -loglevel verbose -vsync 1 -hwaccel cuda -y -i "$f" -f srt -i "${f/avi/srt}" -map 0:v:0 -map 0:a -map 1:0 -dn -map_chapters -1 -movflags faststart -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -metadata:s:a:0 language=eng -metadata:s:s:0 title= -metadata:s:s:0 language=eng -pix_fmt yuv420p -disposition:s:0 0 -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 30 -level:v 4.1 -rc vbr -cbr:v false -channel_layout "stereo" -c:a aac -c:s mov_text "${f/.avi/.mp4}"; done
    

vsync 2 VFR, cq = 28, avi > mp4

for f in *.avi; do ffmpeg -loglevel verbose -vsync 2 -hwaccel cuda -y -i "$f" -f srt -i "${f/avi/srt}" -map 0:v:0 -map 0:a -map 1:0 -dn -map_chapters -1 -movflags faststart -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -metadata:s:a:0 language=eng -metadata:s:s:0 title= -metadata:s:s:0 language=eng -pix_fmt yuv420p -disposition:s:0 0 -r 24 -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 28 -level:v 4.1 -rc vbr -cbr:v false -channel_layout "stereo" -c:a aac -c:s mov_text "${f/.avi/.mp4}"; done
    

vsync 1 CFR, cq = 30, mp4 > mp4

for f in *.mp4; do ffmpeg -loglevel verbose -vsync 1 -hwaccel cuda -y -i "$f" -f srt -i "${f/avi/srt}" -map 0:v:0 -map 0:a -map 1:0 -dn -map_chapters -1 -movflags faststart -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -metadata:s:a:0 language=eng -metadata:s:s:0 title= -metadata:s:s:0 language=eng -pix_fmt yuv420p -disposition:s:0 0 -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 30 -level:v 4.1 -rc vbr -cbr:v false -channel_layout "stereo" -c:a aac -c:s mov_text "${f/.avi/.mp4}"; done
    

vsync 1 CFR of 24fps (-r 24), cq = 28

for f in *.mkv; do ffmpeg -loglevel verbose -vsync 1 -hwaccel cuda -y -i "$f" -default_mode infer_no_subs -map 0:v:0 -map 0:a -map "0:s?" -dn -map_chapters -1 -r 24 -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -pix_fmt yuv420p -disposition:s:0 0 -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 28 -level:v 4.1 -rc vbr -cbr:v false -c:a copy -c:s copy "${f/.mkv/-modified.mkv}"; done
    

vsync 1 CFR, cq = 28, variable bit rate, keep existing frame rate

for f in *.mkv; do ffmpeg -loglevel info -vsync 1 -hwaccel cuda -y -i "$f" -default_mode infer_no_subs -map 0:v:0 -map 0:a -map "0:s?" -dn -map_chapters -1 -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -pix_fmt yuv420p -disposition:s:0 0 -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 28 -level:v 4.1 -rc vbr -cbr:v false -channel_layout "5.1" -c:a aac -c:s copy "${f/.mkv/-modified.mkv}"; done
    

vsync 1 CFR, cq = 23, variable bit rate, keep existing frame rate

for f in *.mkv; do ffmpeg -loglevel info -vsync 1 -hwaccel cuda -y -i "$f" -default_mode infer_no_subs -map 0:v:0 -map 0:a -map "0:s?" -dn -map_chapters -1 -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -pix_fmt yuv420p -disposition:s:0 0 -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 23 -level:v 4.1 -rc vbr -cbr:v false -channel_layout "5.1" -c:a aac -c:s copy "${f/.mkv/-modified.mkv}"; done
    

stream copy/remux, not changing much except for subtitle deposition and other minor changes

for f in *.mkv; do ffmpeg -y -i "$f" -default_mode infer_no_subs -map 0 -dn -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -disposition:v:0 default -disposition:a:0 default -disposition:s 0 -c:v copy -c:a copy -c:s copy "$f"; done
    

avi > mp4 transcoding, constant frame rate at 24fps, variable bit rate, cq 28

for f in */**.avi; do ffmpeg -loglevel verbose -vsync 1 -hwaccel cuda -y -i "$f" -default_mode infer_no_subs -map 0:v:0 -map 0:a -map "0:s?" -movflags faststart -dn -map_chapters -1 -r 24 -metadata title= -metadata:s:v:0 title= -metadata:s:a:0 title= -metadata:s:a:0 language=eng -metadata creation_time="$(date --iso-8601=seconds)" -metadata:s:v creation_time="$(date --iso-8601=seconds)" -metadata:s:a creation_time="$(date --iso-8601=seconds)" -pix_fmt yuv420p -disposition:s:0 0 -disposition:v:0 default -disposition:a:0 default -c:v h264_nvenc -profile:v high -preset p5 -tune hq -b:v 0 -cq 28 -level:v 4.1 -rc vbr -cbr:v false -channel_layout:a:0 "stereo" -c:a aac -c:s srt "${f/avi/mp4}"; done
    

If you have any questions/comments regarding this article, click here or scroll down below (login isn't required to post comments and there's no waiting period).

FFmpeg + sed: How to insert clean metadata based on input file name
This post shows you how to extract the movie/show name from the file name and insert it into the title metadata field of MKV & MP4 containers.
FFmpeg Map English Subtitles or Audio Stream Only
Dynamic FFmpeg Subtitle Stream Mapping via Language This post shows how you can dynamically map streams based upon the language as the criteria/condition. * Map English subs only by using the parameter -map 0:s:m:language:eng * Map Spanish subs only by using the parameter -map 0:s:m:
Supported MP4 Metadata Keys with FFmpeg
The following table lists the supported MP4 container metadata key names in FFmpeg.
FFmpeg Batch Transcode Audio
Before we dive into how to batch transcode the audio streams using FFmpeg and bash, I feel that I should mention that there is already a free app called Tdarr which takes care of these issues in a nice fancy GUI. Now that my guilt has been removed by way