#!/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