Created
March 16, 2024 16:19
-
-
Save xarblu/b6beccad6bdb96b181633284f00770b3 to your computer and use it in GitHub Desktop.
FFMPEG Movie Encode Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Usage: | |
| # movenc.sh <infile> [<outfile>] | |
| # | |
| # User variables/args: | |
| # CROP - ffmpeg crop filter of format xx:xx:xx:xx | |
| # ASTREAMS - ffmpeg audio stream | |
| # SSTREAMS - ffmpeg subtitle stream | |
| # TUNE - x264 tune | |
| # | |
| # Dependencies: | |
| # ffmpeg with libfdk_aac and x264 | |
| # jq | |
| for arg in "${@}"; do | |
| case "${arg}" in | |
| --astreams=*) | |
| ASTREAMS="${arg#*=}" | |
| shift | |
| ;; | |
| --sstreams=*) | |
| SSTREAMS="${arg#*=}" | |
| shift | |
| ;; | |
| --crop=*) | |
| CROP="${arg#*=}" | |
| shift | |
| ;; | |
| --tune=*) | |
| TUNE="${arg#*=}" | |
| shift | |
| ;; | |
| esac | |
| done | |
| # needs 1 arg, a file that exists | |
| [[ ! -f "${1}" ]] && echo "File \"${1}\" doesn't exist." && exit 1 | |
| # arg $1 is input file | |
| infile="${1}" | |
| # arg $2 is output file (defaults to ${infile%.*}+done.mkv) | |
| outfile="${2:-${infile%.*}+done.mkv}" | |
| # get ffprobe attribute for v:0 | |
| get_vattr() { | |
| ffprobe -i "${infile}" -v quiet -print_format json \ | |
| -show_streams -select_streams v:0 \ | |
| | jq --raw-output ".streams[0].${1}" | |
| } | |
| # get sample_aspect_ratio via ffprobe | |
| get_sar() { | |
| get_vattr 'sample_aspect_ratio' | sed -e 's|:|/|' | |
| } | |
| # get resolution via ffprobe if not cropped, else use crop resolution | |
| get_width() { | |
| if [[ -z "${CROP}" ]]; then | |
| echo -n "$(get_vattr 'width')" | |
| else | |
| echo -n "${CROP}" | awk -F ':' '{ print $1 }' | |
| fi | |
| } | |
| get_height() { | |
| if [[ -z "${CROP}" ]]; then | |
| echo -n "$(get_vattr 'height')" | |
| else | |
| echo -n "${CROP}" | awk -F ':' '{ print $2 }' | |
| fi | |
| } | |
| # get crop filter string | |
| get_crop() { | |
| [[ -n "${CROP}" ]] && echo -n ",crop=${CROP}" | |
| } | |
| # $1: stream_spec $2: tag | |
| get_tag_for() { | |
| ffprobe -i "${infile}" -v quiet -print_format json \ | |
| -show_streams -select_streams "${1#0:}" \ | |
| | jq --raw-output ".streams[0].tags.${2}" | |
| } | |
| add_vflags() { | |
| local fps gop | |
| fps="$(get_vattr 'r_frame_rate' | bc)" | |
| _FFMPEG_ARGS+=( -c:v libx264 ) | |
| # set preset based on res (576 is PAL DVD) | |
| if (( $(get_height) > 576 )); then | |
| _FFMPEG_ARGS+=( -preset slower ) | |
| else | |
| _FFMPEG_ARGS+=( -preset veryslow ) | |
| fi | |
| # considered "visually lossless" | |
| _FFMPEG_ARGS+=( -crf 17 ) | |
| # optionally add a tune | |
| if [[ -n "${TUNE}" ]]; then | |
| _FFMPEG_ARGS+=( -tune "${TUNE}" ) | |
| fi | |
| # GOP is 10*fps capped at 300 | |
| gop="$(( "${fps}" * 10 ))" | |
| if (( ${gop} >= 300 )); then | |
| _FFMPEG_ARGS+=( -g 300 ) | |
| else | |
| _FFMPEG_ARGS+=( -g "${gop}" ) | |
| fi | |
| # always apply sar filter to get correct aspect ratio | |
| # conditionally add crop filter | |
| _FFMPEG_ARGS+=( -vf "setsar=$(get_sar)$(get_crop)" ) | |
| } | |
| # unconditional libfdk_aac with -vbr 5 | |
| add_aflags() { | |
| _FFMPEG_ARGS+=( | |
| -c:a libfdk_aac | |
| -vbr 5 | |
| ) | |
| } | |
| # for now nothing fancy, just copy | |
| add_sflags() { | |
| _FFMPEG_ARGS+=( | |
| -c:s copy | |
| ) | |
| } | |
| # map streams and some metadata | |
| add_mappings() { | |
| local i val | |
| # map global metadata | |
| _FFMPEG_ARGS+=( -map_metadata 0 ) | |
| # map video | |
| _FFMPEG_ARGS+=( -map 0:v:0 ) | |
| # no default metadata mappings | |
| _FFMPEG_ARGS+=( -map_metadata:s:v -1 ) | |
| _FFMPEG_ARGS+=( -map_metadata:s:a -1 ) | |
| _FFMPEG_ARGS+=( -map_metadata:s:s -1 ) | |
| # map audio and subtitles only keeping language and title metadata | |
| # audio | |
| i=0 | |
| for s in ${ASTREAMS}; do | |
| _FFMPEG_ARGS+=( -map "${s}" ) | |
| # make first track the default | |
| _FFMPEG_ARGS+=( "-disposition:a:${i}" "$([[ "${i}" -eq 0 ]] && echo -n default || echo -n 0)" ) | |
| for tag in language title; do | |
| val="$(get_tag_for "${s}" "${tag}")" | |
| [[ "${val}" != "null" ]] && _FFMPEG_ARGS+=( "-metadata:s:a:${i}" "${tag}=${val}" ) | |
| done | |
| i=$(( ${i} + 1 )) | |
| done | |
| # subtitles | |
| i=0 | |
| for s in ${SSTREAMS}; do | |
| _FFMPEG_ARGS+=( -map "${s}" ) | |
| for tag in language title; do | |
| val="$(get_tag_for "${s}" "${tag}")" | |
| [[ "${val}" != "null" ]] && _FFMPEG_ARGS+=( "-metadata:s:s:${i}" "${tag}=${val}" ) | |
| done | |
| i=$(( ${i} + 1 )) | |
| done | |
| } | |
| # build ffmpeg args | |
| declare -a _FFMPEG_ARGS | |
| add_vflags | |
| add_aflags | |
| add_sflags | |
| add_mappings | |
| # form ffmpeg command for encode, PRETEND only echos command | |
| $([[ -n "${PRETEND}" ]] && echo echo) \ | |
| ffmpeg -i "${infile}" "${_FFMPEG_ARGS[@]}" "${outfile}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment