zefie icon

ztranscode.sh (primitive x264 to x265 encoder script which grew into a monster)

zefie | PRO | 12/04/20 04:30:53 PM UTC (Edited) | 0 ⭐ | 1312 👁️ | Never ⏰ | []
Bash |

8.91 KB

|

None

|

0 👍

/

0 👎

#!/bin/bash
 
ffmpeg="$(command -v ffmpeg)"
dest="/home/zefie/downloads/manual"
ext=mkv # output ext
fps=24 # video fps for tuning
olddir="old" # where source files go when we are done (relative to $workdir)
outdir="zefie" # where new files go when we are done (relative to $workdir)
baddir="bad" # where files with decode errors end up (relative)
ffmpeg_avg_br=5M
ffmpeg_buf=8M
ffmpeg_max_br=12M
ffmpeg_aud_opts=(-b:a 320k)
ffmpeg_mov_avg_br=10M
ffmpeg_mov_buf=10M
ffmpeg_mov_max_br=20M
ffmpeg_mov_aud_opts=("${ffmpeg_aud_opts[@]}")
 
vcodec=hevc
acodec=aac
ffmpeg_threads=4
preset=hq
twopass=true
errchk=1
delete=1
use_nvidia=1 # comment/unset to disable
use_nvidia_encoder=1 # ignored if use_nvidia unset
use_nvidia_decoder=1 # ignored if use_nvidia unset
 
REPLACE=( \
    "H.264|HEVC" \
    "x264|HEVC" \
    "NTb|zefie" \
    "QOQ|zefie" \
    "DDP5.1|AAC5.1" \
    "DD+2.0|AAC2.0" \
)
 
 
# end config
 
 
ffmpeg_opts=()
processed_count=0
FILES=()
 
if [ "${1}" == "movie" ]; then
    echo " [!!!] Executing in movie mode, using higher bitrates."
    ffmpeg_avg_br="${ffmpeg_mov_avg_br}";
    ffmpeg_buf="${ffmpeg_mov_buf}";
    ffmpeg_max_br="${ffmpeg_mov_max_br}";
    ffmpeg_aud_opts=("${ffmpeg_mov_aud_opts[@]}")
    shift;
fi
 
if [ "${1}" == "noerr" ]; then
    echo " [!!!] Executing in noerr mode, ffmpeg will NOT fail on minor errors!"
    errchk=
    shift;
fi
 
if [ "${1}" == "nomove" ]; then
    echo " [!!!] Executing in nomove mode, will not move failed files to ${baddir} !"
    nomove=1
    shift;
fi
 
if [ "${1}" == "nodel" ]; then
    echo " [!!!] Executing in old mode, will not delete files, will place them in ${olddir}"
    delete=
    shift;
fi
 
if [ -z "${1}" ] || [ ! -d "${1}" ]; then
    if [ ! -z "${1}" ] && [ -f "${1}" ]; then
        workdir="$(realpath "$(dirname "${1}")")"
        singlefile="$(realpath "${1}")"
        echo " [***] Single File Mode: ${singlefile} ..."
    else
        workdir="$(realpath "${PWD}")"
    fi
else
    workdir="$(realpath "${1}")"
fi
 
echo " [***] Working in ${workdir} ..."
 
if [ -z "${delete}" ]; then
    olddir="${workdir}/${olddir}";
fi
outdir="${workdir}/${outdir}"
baddir="${workdir}/${baddir}"
 
 
function detect_hw_encoder() {
    if [ -e "/dev/nvidia0" ] && [ ! -z "${use_nvidia}" ] && [ ! -z "${use_nvidia_encoder}" ]; then
        case "${vcodec}" in
                hevc)
                    echo hevc_nvenc;
                    ;;
                h264)
                    echo h264_nvenc;
                    ;;
                *)
                    echo "${vcodec}";
                    ;;
        esac
    else
        case "${vcodec}" in
                hevc)
                    echo libx265;
                    ;;
                h264)
                    echo libx264;
                    ;;
                *)
                    echo "${vcodec}";
                    ;;
        esac
    fi
}
 
 
function detect_hw_decoder() {
    local codec_data codec_res;
    codec_data="$(mediainfo --Inform="Video;%Format%" "${1}")"
    if [ -e "/dev/nvidia0" ] && [ ! -z "${use_nvidia}" ] && [ ! -z "${use_nvidia_decoder}" ]; then
        case "${codec_data}" in
                    AVC)
                        echo " [***] Using NVidia Accelerated H.264 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v h264_cuvid);
                        ;;
                    HEVC)
                        echo " [***] Using NVidia Accelerated HEVC Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v henc_cuvid);
                        ;;
                    MJPEG)
                        echo " [***] Using NVidia Accelerated MJPEG Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v mjpeg_cuvid);
                        ;;
                    MPEG1)
                        echo " [***] Using NVidia Accelerated MPEG1 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v mpeg1_cuvid);
                        ;;
                    MPEG2)
                        echo " [***] Using NVidia Accelerated MPEG2 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v mpeg2_cuvid);
                        ;;
                    MPEG4)
                        echo " [***] Using NVidia Accelerated MPEG4 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v mpeg4_cuvid);
                        ;;
                    VC1)
                        echo " [***] Using NVidia Accelerated VC1 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v vc1_cuvid);
                        ;;
                    VP8)
                        echo " [***] Using NVidia Accelerated VP8 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v vp8_cuvid);
                        ;;
                    VP9)
                        echo " [***] Using NVidia Accelerated VP9 Decoder" 1>&2
                        codec_res=(-hwaccel cuvid -c:v vp9_cuvid);
                        ;;
                    *)
                        echo " [***] Using Software Video Decoder" 1>&2
                        codec_res=();
                        ;;
        esac
    else
        echo " [***] Using Software Video Decoder" 1>&2
        codec_res=();
    fi
    echo "${codec_res[@]}"
}
 
 
function prepare_ffmpeg_opts() {
    local cuvid_dec ffmpeg_opts_in ffmpeg_opts_qual ffmpeg_opts_out codec ffmpeg_opts_audio_out ffmpeg_opts_in_generic;
 
    ffmpeg_opts_audio_out=(-c:a "${acodec}" "${ffmpeg_aud_opts[@]}")
    ffmpeg_opts_in_generic=(-y -loglevel warning -stats -threads "${ffmpeg_threads}")
    if [ ! -z "${errchk}" ]; then
        ffmpeg_opts_in_generic=("${ffmpeg_opts_in_generic[@]}" -xerror)
    fi
 
 
    codec=$(detect_hw_encoder);
    # shellcheck disable=SC2207
    cuvid_dec=($(detect_hw_decoder "${1}"));
    if [ "${codec}" = "hevc_nvenc" ]; then
        echo " [***] Using NVidia Accelerated HEVC Encoder" 1>&2
        ffmpeg_opts_in=("${ffmpeg_opts_in_generic[@]}" "${cuvid_dec[@]}")
        ffmpeg_opts_qual=(-preset "${preset}" -profile:v main -level:v 5.1 -2pass "${twopass}" -tier high -rc:v vbr_hq -b:v "${ffmpeg_avg_br}" -maxrate:v "${ffmpeg_max_br}" -bufsize:v "${ffmpeg_buf}" -g $((fps * 10)) -map 0 -movflags use_metadata_tags -movflags faststart)
        ffmpeg_opts_out=("${ffmpeg_opts_audio_out[@]}" -c:v "${codec}" "${ffmpeg_opts_qual[@]}" -f matroska)
    fi
 
    if [ "${codec}" = "libx265" ]; then
        echo " [***] Using Software x265 Encoder" 1>&2
        ffmpeg_opts_in=("${ffmpeg_opts_in_generic[@]}" "${cuvid_dec[@]}")
        ffmpeg_opts_qual=(-preset "${preset}" -tune grain -x265-params "'-no-scenecut=1:-2pass=${twopass}:-rc=vbr_hq:-rc-lookahead=${fps}:-surfaces=56'" -b:v "${ffmpeg_avg_br}" -maxrate:v "${ffmpeg_max_br}" -bufsize:v "${ffmpeg_buf}" -g $((fps * 10)) -map 0 -movflags use_metadata_tags -movflags faststart)
        ffmpeg_opts_out=("${ffmpeg_opts_audio_out[@]}" -c:v "${codec}" "${ffmpeg_opts_qual[@]}" -f matroska)
    fi
    ffmpeg_opts=("${ffmpeg_opts_in[@]}" -i "${1}" "${ffmpeg_opts_out[@]}" "${2}")
}
 
zcmd() {
    while [ ! -z "${1}" ]; do
        case "${1}" in
                silent)
                    local SILENT=1;
                    shift
                    ;;
                errchk)
                    local ERRCHK=1;
                    shift
                    ;;
                *)
                    break;
                    ;;
        esac
    done
 
    if [ -z ${SILENT} ]; then
        echo " [***] Executing: ${*}"
    fi
 
    "${@}";
    local RES=$?
    if [ ${RES} != 0 ] && [ ! -z ${ERRCHK} ]; then
            echo "exiting with error ${RES}"
            exit "${RES}"
    fi
    return ${RES}
}
 
 
function process_file() {
    local fname res;
    if [ -z "${ext}" ]; then
        ext="$(echo "${f}" | rev | cut -d'.' -f1 | rev)"
    fi
    fname="$(basename "${f}" | rev | cut -d'.' -f2- | rev)"
 
 
    # renaming for output
    for rep in "${REPLACE[@]}"; do
        rep1=$(echo "${rep}" | cut -d'|' -f1)
        rep2=$(echo "${rep}" | cut -d'|' -f2)
        # shellcheck disable=SC2001
        fname="$(echo "${fname}" | sed "s|${rep1}|${rep2}|")"
        unset rep1 rep2;
    done
 
    if [ ! -d "${outdir}" ]; then
        zcmd errchk mkdir "${outdir}"
    fi
 
    # shellcheck disable=SC2207
    prepare_ffmpeg_opts "${1}" "${outdir}/${fname}.${ext}"
    zcmd "${ffmpeg}" "${ffmpeg_opts[@]}"
    res=$?
    if [ ${res} -eq 0 ] && [ -f "${outdir}/${fname}.${ext}" ]; then
        if [ -z "${delete}" ]; then
            if [ ! -d "${olddir}" ]; then
                zcmd errchk mkdir "${olddir}"
            fi
            zcmd errchk mv -v "${f}" "${olddir}/$(basename "${f}")"
        else
            zcmd errchk rm -v "${f}"
        fi
        if [ ! -z "${dest}" ] && [ ! -z "${errchk}" ]; then
            # move to dest, if defined, and if not running in noerr mode
            mv -v "${outdir}/${fname}.${ext}" "$(realpath "${dest}")/${fname}.${ext}"
        fi
    else
        if [ "${res}" -ne 255 ]; then
            echo " [!!!] errors while encoding ${f}!"
            if [ -z "${nomove}" ]; then
                if [ ! -d "${baddir}" ]; then
                    zcmd errchk mkdir "${baddir}"
                fi
                echo "      Moving ${f} to ${baddir} ..."
                zcmd silent errchk mv "${f}" "${baddir}/$(basename "${f}")"
            fi
            echo "      Removing incomplete encoding at ${outdir}/${fname}.${ext} ..."
            zcmd silent rm -f "${outdir}/${fname}.${ext}"
        else
            echo "exiting with error ${res}"
            exit ${res}
        fi
    fi
    processed_count=$((processed_count+1))
}
 
 
if [ -z "${singlefile}" ]; then
    while IFS= read -r -d '' file; do
        if [ -f "${file}" ]; then
            if [ "$(echo "${file}" | grep 'zefie\.' -c)" -eq 0 ]; then
                FILES=("${FILES[@]}" "${file}")
            fi
        fi
    done < <(find "${workdir}" -maxdepth 1 -regextype posix-extended -regex '.*\.(mp4|mkv)' -print0);
else
    FILES=("${singlefile}")
fi
 
if [ "${#FILES[@]}" -gt 0 ]; then
    if [ -z "${singlefile}" ]; then
        # shellcheck disable=SC2207
        IFS=$'\n' sorted=($(sort <<<"${FILES[*]}")); unset IFS;
        FILES=("${sorted[@]}")
        unset sorted;
        echo -n " [***] Found ${#FILES[@]} file"
        if [ "${#FILES[@]}" -ne 1 ]; then
            echo -n "s";
        fi
        echo " ... processing now."
    fi
 
    for f in "${FILES[@]}"; do
            echo " [***] Processing ${f} (file $((processed_count+1)) of ${#FILES[@]}, $((${#FILES[@]}-processed_count-1)) remaining) ..."
            process_file "${f}"
    done
else
    echo " [!!!] Did not find any files :("
    exit 1
fi
 

Comments