180 lines
5.4 KiB
Bash
Executable File
180 lines
5.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# Look into HW video encoding to vp9 instead of h264:
|
|
# https://www.reddit.com/r/VP9/comments/g9uzzv/hardware_encoding_vp9_on_intel/
|
|
|
|
video_detect() {
|
|
for file in "${*}"; do
|
|
INFO=($(ffprobe -v error \
|
|
-select_streams v:0 \
|
|
-show_entries stream=codec_name:format=format_name \
|
|
-of default=noprint_wrappers=1:nokey=1 "${file}"))
|
|
if (( ${#INFO[*]} != 2 )); then
|
|
echo "${file}|none|none"
|
|
else
|
|
echo "${file}|${INFO[0]}|${INFO[1]}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# mstodate MILLISECONDS
|
|
#
|
|
# Given MILLISECONDS, convert to DAYS:HOURS:MINUTES:SECONDS.MS
|
|
mstodate() {
|
|
scales=( "86400-d:" "3600-h:-02" "60-m:-02" "1-.-02" )
|
|
echo $1 | sed -E 's/([[:digit:]]{3})$/ \1/' | while read sec msec; do
|
|
for scale in ${scales[@]}; do
|
|
parts=(${scale//-/ })
|
|
divisor=${parts[0]}
|
|
suffix=${parts[1]}
|
|
min=${parts[2]}
|
|
num_scale=$((sec / divisor))
|
|
sec=$((sec - (num_scale * divisor)))
|
|
printf "%${min}d%s" ${num_scale} ${suffix}
|
|
done
|
|
echo "${msec}s"
|
|
done
|
|
}
|
|
|
|
function move {
|
|
base="$(dirname "${IN}")"
|
|
[ ! -d "${base}" ] && continue
|
|
base=/multimedia/backup/"${base}"
|
|
[ ! -d "${base}" ] && mkdir -p "${base}"
|
|
[ -e "${base}" ] && mv "${IN}" "${base}"/
|
|
}
|
|
|
|
function convert {
|
|
IN="$1"
|
|
SRC_CODEC="$2"
|
|
FORMAT="$3"
|
|
OUT="${IN/%.???/.mkv}"
|
|
ORIG=${OUT}
|
|
if [[ "${OUT}" == "${IN}" ]]; then
|
|
OUT=${OUT/.mkv/.transcoded.mkv}
|
|
[ -e "${OUT}" ] && rm "${OUT}"
|
|
else
|
|
[ -e "${OUT}" ] && move && return 0
|
|
[ -e /multimedia/backup/"${OUT}" ] && return 0
|
|
fi
|
|
|
|
TITLE="${IN%.*}"
|
|
TITLE="${TITLE##*/}"
|
|
if [[ "${SRC_CODEC}" == "h264" ]]; then
|
|
echo "Content will have format changed from ${FORMAT}/h264 to mkv/h264."
|
|
# Just change container to matroska
|
|
input_flags="
|
|
-c:v copy
|
|
-c:a copy
|
|
"
|
|
output_flags=""
|
|
else
|
|
echo "Content will be transcoded from ${FORMAT}/${SRC_CODEC} to mkv/h264."
|
|
# Convert all video streams to h264. Copy all other streams unchanged (audio, subtitle, etc.)
|
|
# Move headers to start of the file for fast start
|
|
# Use "reasonable" quality level of 20, slow encode
|
|
#
|
|
input_flags="
|
|
-hwaccel qsv
|
|
-c:v hevc_qsv
|
|
"
|
|
output_flags="
|
|
-vf vpp_qsv=format=nv12
|
|
-c:v h264_qsv
|
|
-preset slow
|
|
"
|
|
fi
|
|
|
|
echo -n "Counting frames in ${IN}: "
|
|
FRAMES=$(ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 "${IN}")
|
|
echo "${FRAMES} frames."
|
|
|
|
# Use stdbuf to flush stdout/stderr every line
|
|
STARTTIME=$(date +%s)
|
|
LASTTIME=${STARTTIME}
|
|
LASTPOS=0
|
|
IN=$(realpath "${IN}")
|
|
OUT=$(realpath "${OUT}")
|
|
DIN=$(dirname "${IN}")
|
|
DOUT=$(dirname "${OUT}")
|
|
NO_COLOR=1 \
|
|
docker run --device=/dev/dri \
|
|
--rm \
|
|
-v "${DIN}:${DIN}" \
|
|
-v "${DOUT}:${DOUT}" \
|
|
intel-media-ffmpeg \
|
|
ffmpeg \
|
|
-nostdin \
|
|
-v quiet \
|
|
-loglevel fatal \
|
|
${input_flags} \
|
|
-i "${IN}" \
|
|
-progress /dev/stdout \
|
|
-metadata title="${TITLE/.\//}" \
|
|
${output_flags} \
|
|
-movflags +faststart \
|
|
"${OUT}" \
|
|
-y | while read line; do
|
|
POS=$(echo $line | sed -n 's/^frame=*\(.*\)/\1/p')
|
|
if [[ "${POS}" != "" ]]; then
|
|
NOW=$(date +%s)
|
|
ELAPSEDTIME=$(( NOW - LASTTIME ))
|
|
if (( ELAPSEDTIME > 5 )); then
|
|
ELAPSEDFRAMES=$(( POS - LASTPOS ))
|
|
REMAININGFRAMES=$(( FRAMES - POS ))
|
|
REMAININGFRAMES=$(( REMAININGFRAMES * 1000 )) # convert to ms
|
|
FRAMERATE=$(( ELAPSEDFRAMES / ELAPSEDTIME ))
|
|
if (( FRAMERATE != 0 )); then
|
|
REMAININGMS=$(( REMAININGFRAMES / FRAMERATE ))
|
|
printf "\r%*s\r%s" $(tput cols) " " "Processing for $(mstodate $(( NOW - STARTTIME ))000). Processing at ($((ELAPSEDFRAMES / ELAPSEDTIME ))fps). Now at frame $POS of $FRAMES $(( $(( 100 * POS)) / FRAMES ))%. Estimating $(mstodate ${REMAININGMS}) remaining."
|
|
else
|
|
printf "\r%*s\r%s" $(tput cols) " " "Processing for $(mstodate $(( NOW - STARTTIME ))000). Processing at ($((ELAPSEDFRAMES / ELAPSEDTIME ))fps). Now at frame $POS of $FRAMES $(( $(( 100 * POS)) / FRAMES ))%. No frames processed in last ${ELAPSEDTIME} seconds."
|
|
fi
|
|
LASTTIME=${NOW}
|
|
LASTPOS=${POS}
|
|
fi
|
|
fi
|
|
done && move && {
|
|
if [[ "${ORIG}" == "${IN}" ]]; then
|
|
# If the original file was an '.mkv' then OUT was '.transcoded.mkv'
|
|
# during transcode. Now that the transcode is complete, set the
|
|
# file back to the original name.
|
|
mv "${OUT}" "${IN}"
|
|
fi
|
|
}
|
|
NOW=$(date +%s)
|
|
echo -e "\nCompleted in $(mstodate $(( NOW - STARTTIME ))000)"
|
|
# -vcodec copy \
|
|
# -acodec aac \
|
|
}
|
|
|
|
function check_and_convert {
|
|
video_detect "$1" | while read entry; do
|
|
# # file:codec:format
|
|
file="${entry%%|*}" # file
|
|
entry="${entry#*|}" # codec:format
|
|
codec="${entry%%|*}" # codec
|
|
format="${entry##*|}" # format
|
|
codec="${codec,,}"
|
|
if [[ "${codec}" == "none" ]]; then
|
|
continue
|
|
fi
|
|
if [[ "${codec}" == "h264" ]] && [[ "${format}" =~ mkv|matroska ]]; then
|
|
# If container is not mkv, convert to mkv container
|
|
echo "Skipping ${file} as it is already mkv/h264."
|
|
else
|
|
convert "${file}" "${codec}" "${format}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
if [ -z "$1" ]; then
|
|
find . -type f \
|
|
-and -not -path "./backup/*" | sort | while read file; do
|
|
check_and_convert "${file}"
|
|
done
|
|
else
|
|
for file in "$*"; do
|
|
check_and_convert "${file}"
|
|
done
|
|
fi
|