#!/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=/remote/media/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 /remote/media/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 25 " 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 --user=1000 --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