#!/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/ log_file="" fail() { echo "$*" >&2 if [[ -e ${log_file} ]]; then rm ${log_file} fi exit -1 } VIDEO=$(getent group video | sed -E 's,^video:[^:]*:([^:]*):.*$,\1,') RENDER=$(getent group render | sed -E 's,^render:[^:]*:([^:]*):.*$,\1,') [[ "${VIDEO}" != "" ]] || fail "No video group found." ADD_GROUPS="--group-add ${VIDEO}" [[ "${RENDER}" != "" ]] && ADD_GROUPS+=" --group-add ${RENDER}" 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}")" if [ ! -d "${base}" ]; then continue fi base=/multimedia/backup"${base}" if [ ! -d "${base}" ]; then mkdir -p "${base}" || fail "Unable to mkdir '${base}'" fi if [ -d "${base}" ]; then mv "${IN}" "${base}"/ || fail "Unable to move '$IN'" fi } function convert { IN="$1" SRC_CODEC="$2" FORMAT="$3" OUT="${IN/%.???/.mkv}" IN="$(realpath "${IN}")" OUT="$(realpath "${OUT}")" 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=" " output_flags=" -c copy " 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=" -c copy -vf vpp_qsv=format=nv12 -c:v h264_qsv -global_quality 20 " 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}") || fail "Unable to count frames" echo "${FRAMES} frames." # Use stdbuf to flush stdout/stderr every line STARTTIME=$(date +%s) LASTTIME=${STARTTIME} LASTPOS=0 DIN="$(dirname "${IN}")" DOUT="$(dirname "${OUT}")" quiet=" -v quiet -loglevel error " # quiet="" log_file=$(mktemp) { docker run \ --device=/dev/dri \ --user=$(id -u) \ --rm \ ${ADD_GROUPS} \ -v "${DIN}":"${DIN}" \ -v "${DOUT}":"${DOUT}" \ intel-media-ffmpeg \ ffmpeg \ ${quiet} \ -nostdin \ ${input_flags} \ -i "${IN}" \ -progress /dev/stdout \ -metadata title="${TITLE/.\//}" \ ${output_flags} \ -movflags +faststart \ "${OUT}" \ -y || echo "FFMPEG failed" | tee ${log_file} } | while read line; do if [[ "${line}" == "ffmpeg failed" ]]; then cat ${log_file} fail "Terminating" false exit -1 break fi POS=$(echo $line | sed -n 's/^frame=*\(.*\)/\1/p') if [[ "${POS}" == "" ]]; then true continue fi NOW=$(date +%s) ELAPSEDTIME=$(( NOW - LASTTIME )) if (( ELAPSEDTIME <= 5 )); then true continue fi 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} 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}" || fail "Unable to mv '${OUT}' -> '${IN}'" fi } rm ${log_file} NOW=$(date +%s) echo -e "\nCompleted in $(mstodate $(( NOW - STARTTIME ))000)" } 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 . -name '* *' -type d | while read file; do mv "${file}" "${file// /_}" || fail "Unable to rename dir ${file}"; done find . -name '* *' -type f | while read file; do mv "${file}" "${file// /_}" || fail "Unable to rename file ${file}"; done 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 cat << EOF To transfer the updated files: rsync -avprlP --remove-source-files /multimedia/Downloads/ azurite:/multimedia/ find /multimedia/Downloads/ -type d -empty -delete EOF