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