671 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			671 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
#!/bin/bash
 | 
						|
set -e
 | 
						|
set -o nounset
 | 
						|
set -o errexit
 | 
						|
 | 
						|
################################################################################
 | 
						|
# burnSubs
 | 
						|
# version 0.10.0
 | 
						|
#################3
 | 
						|
# Wishlist:
 | 
						|
#   queue encodes
 | 
						|
#   finish TODOs
 | 
						|
#	finish help flag
 | 
						|
#	audio recode flag
 | 
						|
#
 | 
						|
# Changes
 | 
						|
#   automatically select JPN audio if more than one audio channel found.
 | 
						|
################################################################################
 | 
						|
 | 
						|
function machineSetup() {
 | 
						|
	# Default setup
 | 
						|
	export FF_ENC="libx264"
 | 
						|
	export FF_HW=""
 | 
						|
	export FILT_PFX=""
 | 
						|
	export FILT_SFX=""
 | 
						|
	CRF=${OPTS_CRF:-23}
 | 
						|
	export FF_STD="-preset myTranscoderPreset -crf $CRF -tune animation \
 | 
						|
		-preset medium -movflags +faststart"
 | 
						|
	export FF_EXT="-profile:v high -level 4.0"
 | 
						|
	# Handle the time limit argument used for testing if it was passed
 | 
						|
	if [[ "${OPTS_TIMELIMIT}" != "null" ]]; then
 | 
						|
		export LIM_TIME="-t ${OPTS_TIMELIMIT}"
 | 
						|
	else
 | 
						|
		export LIM_TIME=""
 | 
						|
	fi
 | 
						|
 | 
						|
	# The default extraProc function. This should be overridden by each encoder
 | 
						|
	# as required. For example, to set bitrate after a video has been parsed.
 | 
						|
	function extraProc() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	# Use the machine hostname to set the default encoding options based upon
 | 
						|
	# my preferences.
 | 
						|
	echo "Machine identification:"
 | 
						|
	if [[ "${OPTS_FORCESOFT}" == "true" ]]; then
 | 
						|
		echo "   > Hostname: $(hostname)"
 | 
						|
		export OPTS_ENC="allsoft"
 | 
						|
	else
 | 
						|
		if [[ "$(hostname)" == "Kusanagi" ]]; then
 | 
						|
			echo "   > Hostname: 草薙素子"
 | 
						|
			export OPTS_ENC="nvidia"
 | 
						|
			echo "---> FIX FFMPEG!"
 | 
						|
			#export FFMPEG="/opt/ffmpeg-nvenc/bin/ffmpeg"
 | 
						|
			export LD_LIBRARY_PATH="/opt/ffmpeg-nvenc/lib"
 | 
						|
			export FF_EXT="${FF_EXT} -pix_fmt yuv420p"
 | 
						|
		elif [[ "$(hostname)" == "grad-heo-lappy" ]]; then
 | 
						|
			echo "   > Hostname: $(hostname)"
 | 
						|
			export OPTS_ENC="vaapi"
 | 
						|
		else
 | 
						|
			echo "   > Hostname: $(hostname)"
 | 
						|
			export OPTS_ENC="allsoft"
 | 
						|
			export FF_EXT="${FF_EXT} -pix_fmt yuv420p"
 | 
						|
		fi
 | 
						|
	fi
 | 
						|
	
 | 
						|
	# Configure audio filtergraph if needed.
 | 
						|
	if [[ "${OPTS_TRANS_AUDIO}" == true ]]; then
 | 
						|
		FILT_AUDIO="-c:a aac"
 | 
						|
		if [[ "${OPTS_LPF_AUDIO}" == true ]]; then
 | 
						|
			FILT_AUDIO="-filter:a highpass=f=7 ${FILT_AUDIO}"
 | 
						|
		fi
 | 
						|
 | 
						|
		if [[ "$(hostname)" == "Ram-the-Red" ]]; then
 | 
						|
			FILT_AUDIO="${FILT_AUDIO} -strict -2"
 | 
						|
		fi
 | 
						|
	else
 | 
						|
		FILT_AUDIO="-c:a copy"
 | 
						|
	fi
 | 
						|
 | 
						|
	# Configure the encoder based upon the specific encoder chain required
 | 
						|
	## NVIDIA GPU Encode/Decode
 | 
						|
	if [[ "$OPTS_ENC" == "nvidia" ]]; then
 | 
						|
		echo "   > Using nVidia Encoder"
 | 
						|
		echo "   >>> full hardware"
 | 
						|
		# NVIDIA GPU encoding options
 | 
						|
		export FF_ENC="h264_nvenc"
 | 
						|
		# Assuming h264 input video
 | 
						|
		# TODO: Handle non h264 input streams
 | 
						|
		#export FF_HW="-hwaccel cuvid -c:v h264_cuvid"
 | 
						|
		#export FF_HW="-hwaccel cuvid"
 | 
						|
		# Frame count lookahead
 | 
						|
		export FF_EXT="${FF_EXT} -rc-lookahead 30"
 | 
						|
 | 
						|
	## Intel CPU Encode/Decode
 | 
						|
	elif [[  "$OPTS_ENC" == "vaapi" ]]; then
 | 
						|
		echo "   > Using VAAPI."
 | 
						|
		# apt install libvdpau-va-gl1
 | 
						|
		#export FF_BIN="/opt/ffmpeg/bin/ffmpeg"
 | 
						|
 | 
						|
		# Full hardware
 | 
						|
		if [[ "${OPTS_FORCEPARTSOFT}" == "false" ]]; then
 | 
						|
			echo "   >>> full hardware"
 | 
						|
			# Full hardware decode/encode
 | 
						|
			export FF_HW="-hwaccel vaapi -hwaccel_device \
 | 
						|
				/dev/dri/renderD128 -hwaccel_output_format vaapi"
 | 
						|
			export FF_ENC="h264_vaapi"
 | 
						|
			export FILT_PFX="scale_vaapi,hwmap=mode=read+write+direct,format=nv12,"
 | 
						|
			export FILT_SFX=",hwmap"
 | 
						|
			# Override the extra options used for this encoder
 | 
						|
			export FF_EXT="-profile:v 100 -level 40"
 | 
						|
		# Mixed software hardware
 | 
						|
		else
 | 
						|
			echo "   >>> hardware decode"
 | 
						|
			echo "     > software encode"
 | 
						|
			# software encode, hardware decode only
 | 
						|
			export FF_HW="-hwaccel vaapi -hwaccel_device /dev/dri/renderD128"
 | 
						|
		fi
 | 
						|
		
 | 
						|
		# Disable this shit
 | 
						|
		# set the bitrate based upon the old video size.
 | 
						|
		if [[ $(false) ]]; then
 | 
						|
			function extraProc() {
 | 
						|
				echo -n "   > determine video size: "
 | 
						|
				LEN_COMPLEX=$("$JQ" '.streams[0].tags.DURATION' \
 | 
						|
					"${STREAMS_ALL}" | tr -d '"')
 | 
						|
				LEN_SECONDS=$("$DATE" -u +'%s' -d "1970-01-01 $LEN_COMPLEX")
 | 
						|
				# shellcheck disable=SC2016
 | 
						|
				VIDEO_SIZE_KBYTES=$("$FFPROBE"  -select_streams v -show_entries \
 | 
						|
					packet=size -of default=nokey=1:noprint_wrappers=1 \
 | 
						|
					-i "${INPUT_VIDEO}" | "$AWK" '{s+=$1} END {print s/1024.0}')
 | 
						|
				KBITRATE=$("$PYTHON3" -c \
 | 
						|
					"print('%.0f' % (1.0*8.0*$VIDEO_SIZE_KBYTES/$LEN_SECONDS))")
 | 
						|
				export FF_EXT="-b:v ${KBITRATE}K "
 | 
						|
			}
 | 
						|
		fi
 | 
						|
	# Full Software encoding/decoding. No hardware assistance.
 | 
						|
	else
 | 
						|
		echo "   > Using default full software chain."
 | 
						|
	fi
 | 
						|
	sleep 1
 | 
						|
	echo "====> Starting <===="
 | 
						|
}
 | 
						|
 | 
						|
function setupBins() {
 | 
						|
	# The first argument is the "default" binary name, and the second
 | 
						|
	# is the variable where that binary's path is stored. Using *export* to
 | 
						|
	# override the variable before "setupBins" is called will let you specify
 | 
						|
	# a specific instance of the binary to use instead.
 | 
						|
	setAndValidateBin "ffmpeg" "FFMPEG"
 | 
						|
	setAndValidateBin "ffprobe" "FFPROBE"
 | 
						|
	setAndValidateBin "jq" "JQ"
 | 
						|
	setAndValidateBin "python3" "PYTHON3"
 | 
						|
	setAndValidateBin "awk" "AWK"
 | 
						|
	setAndValidateBin "date" "DATE"
 | 
						|
}
 | 
						|
 | 
						|
# Used by the above function to evaluate overrides if they are set, and
 | 
						|
# otherwise to simply return the existing binary as an absolute path.
 | 
						|
function setAndValidateBin() {
 | 
						|
	binaryVariable="$2"
 | 
						|
	# shellcheck disable=SC2086
 | 
						|
	eval binaryCurrentValue="\${$binaryVariable:-}"
 | 
						|
	if [[ "" == "$binaryCurrentValue" ]]; then
 | 
						|
		binaryCurrentValue="$1"
 | 
						|
	fi
 | 
						|
 | 
						|
	# Try to extract the results of the current binary as an absolute path
 | 
						|
	binaryCurrentValue=$(readlink -f "$(which "$binaryCurrentValue")") || /bin/true
 | 
						|
	# And fail if we don't have it
 | 
						|
	if [[ ! -x "$binaryCurrentValue" ]]; then
 | 
						|
		echo "ERROR: Required binary '$1' not found."
 | 
						|
		exit
 | 
						|
	fi
 | 
						|
	
 | 
						|
	# shellcheck disable=SC2086
 | 
						|
	eval export $binaryVariable="$binaryCurrentValue"
 | 
						|
}
 | 
						|
 | 
						|
# The fall back cleanup function to remove the shit in temp. Set to trap in
 | 
						|
# the setup temp function further on.
 | 
						|
function doCleanup() {
 | 
						|
	# On eject return to where we started, and frag the cleanup directory
 | 
						|
	cd "$WD"
 | 
						|
	if [[ "$OPTS_DEBUG" == "false" ]]; then
 | 
						|
		echo "=> Cleaning up."
 | 
						|
		rm -r "$TMP"
 | 
						|
	else
 | 
						|
		echo "tmp dir: $TMP"
 | 
						|
	fi
 | 
						|
	
 | 
						|
	alertUser
 | 
						|
}
 | 
						|
 | 
						|
# All we do is note that we'll have to move around, and this helps us keep our
 | 
						|
# bearings and avoid polluting the working directory.
 | 
						|
function setupTemp() {
 | 
						|
	echo "=> Performing Setup:"
 | 
						|
	# The FFMPEG calls require all of the files to actually exist on disk. To
 | 
						|
	# accommodate this, we setup a temporary working directory to dump the files
 | 
						|
	# and we will remove it's contents when things are done. This provides a
 | 
						|
	# location for the font files to be placed, as well as a location for the
 | 
						|
	# raw subtitles files.
 | 
						|
	trap doCleanup EXIT
 | 
						|
 | 
						|
	export WD="$PWD"
 | 
						|
	export TMP=$(mktemp -d /tmp/transFont.XXXX)
 | 
						|
}
 | 
						|
 | 
						|
function dumpFonts() {
 | 
						|
	# Dumb font dump
 | 
						|
	cd "$FONTDIR"
 | 
						|
	"$FFMPEG" -dump_attachment:t "" -i "${INPUT_VIDEO}" -vn -an \
 | 
						|
		-f null -y /dev/null 2>/dev/null || /bin/true
 | 
						|
	cd "$WD"
 | 
						|
}
 | 
						|
 | 
						|
function setupFonts() {
 | 
						|
	echo "   > setting up fonts..."
 | 
						|
	# Simply setup an encode specific font configuration setup. This will direct
 | 
						|
	# the system to use any font files that we have provided in $TMP/font_files
 | 
						|
	# then fall back to the system configuration afterwards.
 | 
						|
	export FONTDIR="${TMP}/font_files"
 | 
						|
	FC_DIR="${TMP}/fontconfig"
 | 
						|
	FC_FILE="${FC_DIR}/fonts.conf"
 | 
						|
	mkdir "${FONTDIR}"
 | 
						|
	mkdir "${FC_DIR}"
 | 
						|
	{
 | 
						|
		echo '<?xml version="1.0"?>';
 | 
						|
		echo '<!DOCTYPE fontconfig SYSTEM "fonts.dtd">';
 | 
						|
		echo '<fontconfig>';
 | 
						|
		echo '	<dir>'"${FONTDIR}"'</dir>';
 | 
						|
		echo '	<include>/etc/fonts/fonts.conf</include>';
 | 
						|
		echo '</fontconfig>'
 | 
						|
	} > "${FC_FILE}"
 | 
						|
	# This export line presents the configuration override to FFMPEG later on.
 | 
						|
	export FONTCONFIG_FILE="${FC_FILE}"
 | 
						|
	
 | 
						|
	dumpFonts
 | 
						|
}
 | 
						|
 | 
						|
function setupPreset() {
 | 
						|
	echo "   > setting up preset..."
 | 
						|
	# The preset system provides a way to specify specific encoding options.
 | 
						|
	# This can easily be removed as desired.
 | 
						|
	presetName="$1"
 | 
						|
	# And include the ffmpeg preset
 | 
						|
	export FFMPEG_DATADIR="${TMP}/ffmpeg"
 | 
						|
	FFMPEG_PRESET_FILE="${FFMPEG_DATADIR}/${FF_ENC}-${presetName}.preset"
 | 
						|
 | 
						|
	mkdir "${FFMPEG_DATADIR}"
 | 
						|
	{	
 | 
						|
		# This preset is for the roku
 | 
						|
		echo 'coder=1'
 | 
						|
		echo 'flags=+loop'
 | 
						|
		echo 'cmp=+chroma'
 | 
						|
		echo 'partitions=+parti8x8+parti4x4+partp8x8+partb8x8'
 | 
						|
		echo 'me_method=umh'
 | 
						|
		echo 'subq=8'
 | 
						|
		echo 'me_range=16'
 | 
						|
		echo 'g=250'
 | 
						|
		echo 'keyint_min=25'
 | 
						|
		echo 'sc_threshold=40'
 | 
						|
		echo 'i_qfactor=0.71'
 | 
						|
		echo 'b_strategy=2'
 | 
						|
		echo 'qcomp=0.6'
 | 
						|
		echo 'qmin=10'
 | 
						|
		echo 'qmax=51'
 | 
						|
		echo 'qdiff=4'
 | 
						|
		echo 'bf=4'
 | 
						|
		echo 'refs=4'
 | 
						|
		echo 'directpred=3'
 | 
						|
		echo 'trellis=1'
 | 
						|
		echo 'flags2=+wpred+mixed_refs+dct8x8+fastpskip'
 | 
						|
	} > "${FFMPEG_PRESET_FILE}"
 | 
						|
}
 | 
						|
 | 
						|
function parseStreams() {
 | 
						|
	# Most MKV files will only have a single subtitle file. In the case we have
 | 
						|
	# multiple subtitles we wish to handle conversion gracefully. To do so, we
 | 
						|
	# extract all of the track information (the first ffprobe call), then use
 | 
						|
	# the json parsing tool jq to extract subtitle data for styled subtitles.
 | 
						|
	#
 | 
						|
	# 
 | 
						|
	STREAMS_ALL="${TMP}/streams.json"
 | 
						|
	export STREAMS_SUB="${TMP}/subs.json"
 | 
						|
	export STREAMS_AUDIO="${TMP}/audio.json"
 | 
						|
	"$FFPROBE" -v error -of json -show_streams "${INPUT_VIDEO}" 2>/dev/null > "${STREAMS_ALL}"
 | 
						|
	
 | 
						|
	# Extract subtitles
 | 
						|
	#$item.codec_type == "subtitle" \&\& 
 | 
						|
	# shellcheck disable=SC2016
 | 
						|
	"$JQ" 'reduce .streams[] as $item ([]; if ($item.codec_name == "ass") then [.[],$item] else . end) | reduce .[] as $item ([]; [.[],{t:($item.tags.title),i:($item.index),lang:$item.tags.language, disposition:$item.disposition}]) | reduce .[] as $item ([]; [.[],{t:($item.t // ($item.lang + "-" + ($item.i | tostring))),i:$item.i,lang:$item.lang,disposition:$item.disposition}])' "${STREAMS_ALL}" > "${STREAMS_SUB}"
 | 
						|
	export SUB_COUNT=$("$JQ" 'length' "${STREAMS_SUB}")
 | 
						|
	# shellcheck disable=SC2016
 | 
						|
	"$JQ" 'reduce .streams[] as $item ([]; if ($item.codec_type == "audio") then [.[],$item] else . end) | reduce .[] as $item ([]; [.[],{t:($item.tags.title),i:($item.index),lang:$item.tags.language, disposition:$item.disposition}]) | reduce .[] as $item ([]; [.[],{t:($item.t // ($item.lang + "-" + ($item.i | tostring))),i:$item.i,lang:$item.lang,disposition:$item.disposition}])' "${STREAMS_ALL}" > "${STREAMS_AUDIO}"
 | 
						|
	export AUDIO_COUNT=$("$JQ" 'length' "${STREAMS_AUDIO}")
 | 
						|
	
 | 
						|
	
 | 
						|
}
 | 
						|
 | 
						|
function listSubtitles() {
 | 
						|
	# TODO: 
 | 
						|
	echo ""
 | 
						|
	echo "available subtitles:"
 | 
						|
	printf "Num: %4s %s\n" "LANG" "Subtitle Title String"
 | 
						|
	echo "---------------------------------"
 | 
						|
	for iSUB in $(seq 0 $(($SUB_COUNT-1))); do
 | 
						|
		X_TITLE=$($JQ '.['$iSUB'].t' $STREAMS_SUB | tr -d '"')
 | 
						|
		X_INDEX=$($JQ '.['$iSUB'].i' $STREAMS_SUB | tr -d '"')
 | 
						|
		X_LANG=$($JQ '.['$iSUB'].lang' $STREAMS_SUB)
 | 
						|
		printf " %2d: %4s %s\n" $X_INDEX $X_LANG "$X_TITLE" 
 | 
						|
	done
 | 
						|
	echo ""
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
function listAudioTracks() {
 | 
						|
	# TODO: 
 | 
						|
	echo ""
 | 
						|
	echo "available audio tracks:"
 | 
						|
	printf "Num: %4s %s\n" "LANG" "Title String"
 | 
						|
	echo "---------------------------------"
 | 
						|
	for iAUDIO in $(seq 0 $(($AUDIO_COUNT-1))); do
 | 
						|
		X_TITLE=$($JQ '.['$iAUDIO'].t' $STREAMS_AUDIO | tr -d '"')
 | 
						|
		X_INDEX=$($JQ '.['$iAUDIO'].i' $STREAMS_AUDIO | tr -d '"')
 | 
						|
		X_LANG=$($JQ '.['$iAUDIO'].lang' $STREAMS_AUDIO)
 | 
						|
		printf " %2d: %4s %s\n" $X_INDEX $X_LANG "$X_TITLE" 
 | 
						|
	done
 | 
						|
	echo ""
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
function selectSubs() {
 | 
						|
	# TODO: handle multiple subtitle files
 | 
						|
	# TODO: verify the the subtitle index is legal
 | 
						|
	if [[ $SUB_COUNT -eq 0 ]]; then
 | 
						|
		echo "   > ERROR: No subtitles!"
 | 
						|
		echo "      > Reverting to a dry run."
 | 
						|
		export OPTS_DRYRUN=true
 | 
						|
		export SUBTITLE_INDEX=-1
 | 
						|
	else
 | 
						|
		if [[ $OPTS_SELSUB -lt 0 && $SUB_COUNT -gt 1 ]]; then
 | 
						|
			LANG_TEST=$($JQ '[.[].lang] | index("eng")' "$STREAMS_SUB")
 | 
						|
			echo "   > WARNING: Multiple subtitles!"
 | 
						|
			printf "       Using default selection rules... "
 | 
						|
			if [[ "$LANG_TEST" == "null" ]]; then
 | 
						|
				echo "English not found!"
 | 
						|
				echo "    ==> Reverting to first subtitle file."
 | 
						|
				export SUBTITLE_INDEX=$($JQ '.[0].i' "$STREAMS_SUB")
 | 
						|
			else
 | 
						|
				# we found english
 | 
						|
				echo "English found"
 | 
						|
				export SUBTITLE_INDEX=$($JQ '.['$LANG_TEST'].i' "$STREAMS_SUB")
 | 
						|
			fi
 | 
						|
		else
 | 
						|
			if [[ $SUB_COUNT -eq 1 ]]; then
 | 
						|
				export SUBTITLE_INDEX=$($JQ '.[].i' "$STREAMS_SUB")
 | 
						|
			else
 | 
						|
				export SUBTITLE_INDEX=$OPTS_SELSUB
 | 
						|
			fi
 | 
						|
		fi
 | 
						|
		SUBTITLE_ARRAY_INDEX=$($JQ '[.[].i] | index('$SUBTITLE_INDEX')' "$STREAMS_SUB")
 | 
						|
		SUBTITLE_NAME=$($JQ '.['$SUBTITLE_ARRAY_INDEX'].t' "$STREAMS_SUB")
 | 
						|
		echo "   > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}"
 | 
						|
	fi
 | 
						|
	
 | 
						|
	if [[ $AUDIO_COUNT -gt 1 ]]; then
 | 
						|
		if [[ $OPTS_SELAUDIO -lt 0 ]]; then
 | 
						|
			LANG_TEST=$($JQ '[.[].lang] | index("jpn")' "$STREAMS_AUDIO")
 | 
						|
			echo "   > WARNING: Multiple audio streams!"
 | 
						|
			printf "       Using default selection rules... "
 | 
						|
			if [[ "$LANG_TEST" == "null" ]]; then
 | 
						|
				echo "Japanese audio not found!"
 | 
						|
				echo "    ==> Reverting to first subtitle file."
 | 
						|
				export AUDIO_INDEX=$($JQ '.[0].i' "$STREAMS_AUDIO")
 | 
						|
			else
 | 
						|
				# we found english
 | 
						|
				echo "Japanese audio found"
 | 
						|
				export AUDIO_INDEX=$($JQ '.['$LANG_TEST'].i' "$STREAMS_AUDIO")
 | 
						|
			fi
 | 
						|
		else
 | 
						|
			AUDIO_INDEX=$OPTS_SELAUDIO
 | 
						|
		fi
 | 
						|
		export FF_AUDIO="-map 0:$AUDIO_INDEX -map 0:v:0"
 | 
						|
	else
 | 
						|
		export FF_AUDIO=""
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function extractSubs() {
 | 
						|
	export SUBTITLE_FILE="${TMP}/ripped.ass"
 | 
						|
	if [[ $SUBTITLE_INDEX -lt 0 ]]; then
 | 
						|
		echo "   > skipping subtitles (TODO: tidy cleanup)"
 | 
						|
		return
 | 
						|
	else
 | 
						|
		echo "   > extracting subtitles"
 | 
						|
	fi
 | 
						|
	extractIndex="$1"
 | 
						|
	"$FFMPEG" -i "${INPUT_VIDEO}" -map 0:"${extractIndex}" -vn -an -c:s copy -c:a copy \
 | 
						|
		"$SUBTITLE_FILE" 2>/dev/null
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
function doTranscode() {
 | 
						|
	echo "=> Starting transcode:"
 | 
						|
	# shellcheck disable=SC2086
 | 
						|
	echo "$FFMPEG" ${FF_HW} -i "${INPUT_VIDEO}" -sn ${LIM_TIME} \
 | 
						|
		-filter:v "${FILT_PFX}ass=${SUBTITLE_FILE}${FILT_SFX}" \
 | 
						|
		${FILT_AUDIO} -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO}	\
 | 
						|
		"${OUTPUT_VIDEO}"
 | 
						|
	if [[ "$OPTS_DRYRUN" == true ]]; then
 | 
						|
		return
 | 
						|
	fi
 | 
						|
	# shellcheck disable=SC2086
 | 
						|
	"$FFMPEG" ${FF_HW} -i "${INPUT_VIDEO}" -sn ${LIM_TIME} \
 | 
						|
		-filter:v "${FILT_PFX}ass=${SUBTITLE_FILE}${FILT_SFX}" \
 | 
						|
		${FILT_AUDIO} -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO}	\
 | 
						|
		"${OUTPUT_VIDEO}"
 | 
						|
	export FINAL_STATUS=$?
 | 
						|
}
 | 
						|
 | 
						|
function runExtraProc() {
 | 
						|
	if [[ "$FF_ENC" == "$1" ]]; then
 | 
						|
		extraProc
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function klobberCheck() {
 | 
						|
	if [[ -e "${OUTPUT_VIDEO}" ]]; then
 | 
						|
		if [[ "$OPTS_KLOBBER" == "true" ]]; then
 | 
						|
			echo "   > klobbering old file '${OUTPUT_VIDEO}'"
 | 
						|
			rm -v "${OUTPUT_VIDEO}"
 | 
						|
		else
 | 
						|
			echo "   > refusing to klobber old file '${OUTPUT_VIDEO}'"
 | 
						|
			exit 1
 | 
						|
		fi
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
function alertUser() {
 | 
						|
	# if the function isn't available, then define an empty function to
 | 
						|
	# reject the message into the void
 | 
						|
	if ! which notify-send >/dev/null; then
 | 
						|
		function notify-send() { return; }
 | 
						|
	fi
 | 
						|
	
 | 
						|
	if [[ $FINAL_STATUS -ne 0 ]]; then
 | 
						|
		notify-send -u normal --icon error "🗙 Transcode failed!" \
 | 
						|
			"$(basename "${INPUT_VIDEO}")\n🞩🞩🞩🞩🞩\n$(basename "${OUTPUT_VIDEO}")"
 | 
						|
	else
 | 
						|
		notify-send -u normal --icon ${NOTIFY_ICON} "✔ Transcode finished" \
 | 
						|
			"$(basename "${INPUT_VIDEO}")\n↓↓↓↓↓\n$(basename "${OUTPUT_VIDEO}")"
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# And verify arguments
 | 
						|
######
 | 
						|
# Defaults
 | 
						|
OPTS_KLOBBER=false
 | 
						|
OPTS_LISTSUBS=false
 | 
						|
OPTS_SELSUB=-1
 | 
						|
OPTS_SELAUDIO=-1
 | 
						|
OPTS_LISTSUBS=false
 | 
						|
OPTS_FORCESOFT=false
 | 
						|
OPTS_FORCEPARTSOFT=false
 | 
						|
OPTS_ENC="allsoft"
 | 
						|
OPTS_TIMELIMIT=null
 | 
						|
OPTS_DRYRUN=false
 | 
						|
OPTS_DEBUG=false
 | 
						|
OPTS_LPF_AUDIO=false
 | 
						|
OPTS_TRANS_AUDIO=false
 | 
						|
OPTS_derived_NO_OUTPUT=false
 | 
						|
unset OPT_CRF
 | 
						|
# this is the --icon flag passed to notify-send at the end of the transcode
 | 
						|
NOTIFY_ICON="face-tired"
 | 
						|
# preinitalized final ffmpeg status to assumed error
 | 
						|
FINAL_STATUS=1
 | 
						|
 | 
						|
### Argument Parsing
 | 
						|
#	Input/Output videos (non-flagged arguments)
 | 
						|
#	optional:
 | 
						|
#		-k		do clobber output
 | 
						|
#		-s #	subtitle track
 | 
						|
#		-l		list subtitle tracks of input files		
 | 
						|
#		--soft	force software encoder
 | 
						|
#		-d		debug, don't cleanup
 | 
						|
###
 | 
						|
 | 
						|
 | 
						|
######
 | 
						|
# Reformat and organize the input strings
 | 
						|
OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix' -- "$@")
 | 
						|
# reassign them as positional arguments
 | 
						|
eval set -- "$OPT_STRING"
 | 
						|
 | 
						|
while true; do
 | 
						|
	case "$1" in
 | 
						|
		"-t")
 | 
						|
			OPTS_TIMELIMIT="$2"
 | 
						|
			echo ">> !! limiting encode to time '$OPTS_TIMELIMIT'"
 | 
						|
			shift 2
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"-k")
 | 
						|
			OPTS_KLOBBER=true
 | 
						|
			echo ">> !! klobber when encoding time comes."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"-l")
 | 
						|
			OPTS_LISTSUBS=true
 | 
						|
			OPTS_derived_NO_OUTPUT=true
 | 
						|
			echo ">> !! list subtitles and exit"
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"-s")
 | 
						|
			OPTS_SELSUB="$2"
 | 
						|
			#TODO: verify legal subtitle track number convention"
 | 
						|
			echo "TODO: verify legal subtitle track number convention" 
 | 
						|
			echo ">> !! Selecting subtitle track #${OPTS_SELSUB}"
 | 
						|
			shift 2
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"-a")
 | 
						|
			OPTS_SELAUDIO="$2"
 | 
						|
			#TODO: verify legal subtitle track number convention"
 | 
						|
			echo "TODO: verify legal audio track number convention" 
 | 
						|
			echo ">> !! Selecting audio track #${OPTS_SELAUDIO}"
 | 
						|
			shift 2
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--psoft")
 | 
						|
			OPTS_FORCEPARTSOFT=true
 | 
						|
			echo ">> !! forcing software encoding."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--lpf")
 | 
						|
			OPTS_LPF_AUDIO=true
 | 
						|
			OPTS_TRANS_AUDIO=true
 | 
						|
			echo ">> !! low pass filter audio to AAC."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--audiofix")
 | 
						|
			OPTS_TRANS_AUDIO=true
 | 
						|
			echo ">> !! no filter audio to AAC."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--soft")
 | 
						|
			OPTS_FORCESOFT=true
 | 
						|
			echo ">> !! forcing software decoding/encoding."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--crf")
 | 
						|
			OPTS_CRF="$2"
 | 
						|
			echo ">> !! CRF Override CRF='$OPTS_CRF'"
 | 
						|
			shift 2
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"-d")
 | 
						|
			OPTS_DEBUG=true
 | 
						|
			echo ">> !! debug enabled."
 | 
						|
			echo ">>      temp files will not be cleaned up."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--dry")
 | 
						|
			OPTS_DRYRUN=true
 | 
						|
			echo ">> !! dry run. No encode."
 | 
						|
			shift
 | 
						|
			continue
 | 
						|
		;;
 | 
						|
		"--")
 | 
						|
			shift # all arguments parsed
 | 
						|
			break
 | 
						|
		;;
 | 
						|
		"-h" | "--help")
 | 
						|
			# TODO: Display help
 | 
						|
			shift # all arguments parsed
 | 
						|
			echo "$(basename $0) [args] <input> <output>"
 | 
						|
			#echo "TODO: HELP!" # Display HELP
 | 
						|
			cat << _EOT
 | 
						|
  ----------------------------------------------------------------------------
 | 
						|
    -k          auto-klobber when ffmpeg asks
 | 
						|
 | 
						|
    -t <t>      ffmpeg encoding time limit
 | 
						|
    --crf <#>   override CRF setting
 | 
						|
    --soft      force software decode and encode
 | 
						|
    --psoft     use software encoding (allow hardware decode when available)
 | 
						|
    --audiofix  transcode audio
 | 
						|
    --lpf     transcode audio, and low-pass filter as well
 | 
						|
 | 
						|
    -l          list subtitles and audio tracks (no encoding)
 | 
						|
    -s <#>      select specific subtitle track number
 | 
						|
                #TODO: verify legal subtitle track number convention
 | 
						|
    -a <#>      select specific subtitle track number
 | 
						|
                #TODO: verify legal subtitle track number convention
 | 
						|
 | 
						|
    -d          debug (no cleanup)
 | 
						|
    --dry       dry run (no encoding)
 | 
						|
_EOT
 | 
						|
			exit
 | 
						|
		;;
 | 
						|
		*)
 | 
						|
			echo "Arg: $1"
 | 
						|
			echo "Internal Error!" >&2
 | 
						|
			exit
 | 
						|
		;;
 | 
						|
	esac
 | 
						|
done
 | 
						|
 | 
						|
# Now parse POSITIONAL ARGUMENTS
 | 
						|
if [[ $# -eq 2 || $OPTS_derived_NO_OUTPUT == true ]]; then
 | 
						|
	INPUT_VIDEO="$(readlink -f "$1")"
 | 
						|
	if [[ $# -eq 2 ]]; then
 | 
						|
		OUTPUT_VIDEO="$2"
 | 
						|
	else
 | 
						|
		OUTPUT_VIDEO="/dev/null"
 | 
						|
	fi
 | 
						|
else
 | 
						|
	echo "ERROR: Incorrect number of positional arguments. Expected 2, got $#"
 | 
						|
	echo "       $(basename $0) [args] <input> <output>"
 | 
						|
	exit
 | 
						|
fi
 | 
						|
 | 
						|
###############
 | 
						|
# Configure the encoder based upon the hostname
 | 
						|
machineSetup
 | 
						|
 | 
						|
###############
 | 
						|
# Check and validate the binaries we use to parse and setup the encoding chain
 | 
						|
setupBins
 | 
						|
# Setup the temp space for working files
 | 
						|
setupTemp
 | 
						|
# Extract embedded fonts and configure fontconfig
 | 
						|
setupFonts
 | 
						|
# Export the preset file used for the Roku
 | 
						|
setupPreset myTranscoderPreset
 | 
						|
 | 
						|
###############
 | 
						|
# Parse stream data to identify subtitles
 | 
						|
parseStreams
 | 
						|
# Now! If OPTS_LISTSUBS is defined, then we branch to list subs and exit.
 | 
						|
if [[ "$OPTS_LISTSUBS" == "true" ]]; then
 | 
						|
	listSubtitles
 | 
						|
	listAudioTracks
 | 
						|
	exit	
 | 
						|
fi
 | 
						|
# ask the user for the subtitle file if more than one is available
 | 
						|
# Also selects audio stream.
 | 
						|
selectSubs
 | 
						|
# extract the selected subtitle file
 | 
						|
extractSubs $SUBTITLE_INDEX
 | 
						|
# Set the bitrate if that function wasn't disabled
 | 
						|
runExtraProc "h264_vaapi"
 | 
						|
 | 
						|
# If we're clobbering, now is the time to do the clobbering
 | 
						|
klobberCheck
 | 
						|
# Kickoff the transcode
 | 
						|
doTranscode
 | 
						|
 | 
						|
# cleanup automatically executes after the done message is cleared
 | 
						|
echo "done."
 |