From 8d865f377170c3ce01040c80e8d36d3749da8cd4 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:42:43 -0800 Subject: [PATCH 01/27] v0.9 --- burnSubs | 553 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100755 burnSubs diff --git a/burnSubs b/burnSubs new file mode 100755 index 0000000..589f85e --- /dev/null +++ b/burnSubs @@ -0,0 +1,553 @@ +#!/bin/bash +set -e +set -o nounset +set -o errexit + +################################################################################ +# burnSubs +# version 0.9.1 +#################3 +# Wishlist: +# queue encodes +# finish TODOs +# list subtitles flag +# finish help flag +# audio recode flag +################################################################################ + +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 /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 ''; + echo ''; + echo ''; + echo ' '"${FONTDIR}"''; + echo ' /etc/fonts/fonts.conf'; + echo '' + } > "${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" + "$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}") + +} + +function listSubtitles() { + # TODO: + 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! Todo!" + export SUBTITLE_INDEX=-1 + elif [[ $SUB_COUNT -eq 1 ]]; then + export SUBTITLE_INDEX=$($JQ '.[].i' "$STREAMS_SUB") + SUBTITLE_NAME=$($JQ '.[].t' "$STREAMS_SUB") + echo " > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}" + else + echo " > ERROR: Multiple subtitles! Todo!" + export SUBTITLE_INDEX=-1 + fi +} + + +function extractSubs() { + echo " > extracting subtitles" + export SUBTITLE_FILE="${TMP}/ripped.ass" + 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} \ + "${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} \ + "${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_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 +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 + 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 + ;; + "--psoft") + OPTS_FORCEPARTSOFT=true + echo ">> !! forcing software encoding." + shift + continue + ;; + "--audio") + 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 "TODO: HELP!" # Display HELP + exit + ;; + *) + echo "Arg: $1" + echo "Internal Error!" >&2 + exit + ;; + esac +done + +# Now parse POSITIONAL ARGUMENTS +if [[ $# -ne 2 ]]; then + echo "ERROR: Incorrect number of positional arguments. Expected 2, got $#" + echo " $0 [args] " + exit +else + INPUT_VIDEO="$(readlink -f "$1")" + OUTPUT_VIDEO="$2" +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 + exit +fi +# ask the user for the subtitle file if more than one is available +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." From bd87bacacbfaa7986cb184e52f4439a7783fec94 Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:42:43 -0800 Subject: [PATCH 02/27] v0.9 --- burnSubs | 553 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100755 burnSubs diff --git a/burnSubs b/burnSubs new file mode 100755 index 0000000..589f85e --- /dev/null +++ b/burnSubs @@ -0,0 +1,553 @@ +#!/bin/bash +set -e +set -o nounset +set -o errexit + +################################################################################ +# burnSubs +# version 0.9.1 +#################3 +# Wishlist: +# queue encodes +# finish TODOs +# list subtitles flag +# finish help flag +# audio recode flag +################################################################################ + +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 /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 ''; + echo ''; + echo ''; + echo ' '"${FONTDIR}"''; + echo ' /etc/fonts/fonts.conf'; + echo '' + } > "${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" + "$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}") + +} + +function listSubtitles() { + # TODO: + 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! Todo!" + export SUBTITLE_INDEX=-1 + elif [[ $SUB_COUNT -eq 1 ]]; then + export SUBTITLE_INDEX=$($JQ '.[].i' "$STREAMS_SUB") + SUBTITLE_NAME=$($JQ '.[].t' "$STREAMS_SUB") + echo " > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}" + else + echo " > ERROR: Multiple subtitles! Todo!" + export SUBTITLE_INDEX=-1 + fi +} + + +function extractSubs() { + echo " > extracting subtitles" + export SUBTITLE_FILE="${TMP}/ripped.ass" + 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} \ + "${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} \ + "${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_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 +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 + 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 + ;; + "--psoft") + OPTS_FORCEPARTSOFT=true + echo ">> !! forcing software encoding." + shift + continue + ;; + "--audio") + 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 "TODO: HELP!" # Display HELP + exit + ;; + *) + echo "Arg: $1" + echo "Internal Error!" >&2 + exit + ;; + esac +done + +# Now parse POSITIONAL ARGUMENTS +if [[ $# -ne 2 ]]; then + echo "ERROR: Incorrect number of positional arguments. Expected 2, got $#" + echo " $0 [args] " + exit +else + INPUT_VIDEO="$(readlink -f "$1")" + OUTPUT_VIDEO="$2" +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 + exit +fi +# ask the user for the subtitle file if more than one is available +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." From 1823bcab3f1f457a4f91cbeac3f373f01c487b31 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:42:58 -0800 Subject: [PATCH 03/27] v0.9.1 --- burnSubs | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) mode change 100755 => 100644 burnSubs diff --git a/burnSubs b/burnSubs old mode 100755 new mode 100644 index 589f85e..6b44de0 --- a/burnSubs +++ b/burnSubs @@ -294,6 +294,17 @@ function parseStreams() { 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) + X_INDEX=$($JQ '.['$iSUB'].i' $STREAMS_SUB) + X_LANG=$($JQ '.['$iSUB'].lang' $STREAMS_SUB) + printf " %2d: %4s %s\n" $X_INDEX $X_LANG "$X_TITLE" + done + echo "" return } @@ -303,13 +314,30 @@ function selectSubs() { if [[ $SUB_COUNT -eq 0 ]]; then echo " > ERROR: No subtitles! Todo!" export SUBTITLE_INDEX=-1 - elif [[ $SUB_COUNT -eq 1 ]]; then - export SUBTITLE_INDEX=$($JQ '.[].i' "$STREAMS_SUB") - SUBTITLE_NAME=$($JQ '.[].t' "$STREAMS_SUB") - echo " > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}" else - echo " > ERROR: Multiple subtitles! Todo!" - export SUBTITLE_INDEX=-1 + 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 } From 59eb1dad5ad0a9dd5cdcb57e5391da06b1d04c8e Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:42:58 -0800 Subject: [PATCH 04/27] v0.9.1 --- burnSubs | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) mode change 100755 => 100644 burnSubs diff --git a/burnSubs b/burnSubs old mode 100755 new mode 100644 index 589f85e..6b44de0 --- a/burnSubs +++ b/burnSubs @@ -294,6 +294,17 @@ function parseStreams() { 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) + X_INDEX=$($JQ '.['$iSUB'].i' $STREAMS_SUB) + X_LANG=$($JQ '.['$iSUB'].lang' $STREAMS_SUB) + printf " %2d: %4s %s\n" $X_INDEX $X_LANG "$X_TITLE" + done + echo "" return } @@ -303,13 +314,30 @@ function selectSubs() { if [[ $SUB_COUNT -eq 0 ]]; then echo " > ERROR: No subtitles! Todo!" export SUBTITLE_INDEX=-1 - elif [[ $SUB_COUNT -eq 1 ]]; then - export SUBTITLE_INDEX=$($JQ '.[].i' "$STREAMS_SUB") - SUBTITLE_NAME=$($JQ '.[].t' "$STREAMS_SUB") - echo " > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}" else - echo " > ERROR: Multiple subtitles! Todo!" - export SUBTITLE_INDEX=-1 + 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 } From e5b5ea6f808acea18e05a3b3f0c2fe7568b7e9d4 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:43:25 -0800 Subject: [PATCH 05/27] v0.9.2 --- burnSubs | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) mode change 100644 => 100755 burnSubs diff --git a/burnSubs b/burnSubs old mode 100644 new mode 100755 index 6b44de0..f019935 --- a/burnSubs +++ b/burnSubs @@ -5,12 +5,11 @@ set -o errexit ################################################################################ # burnSubs -# version 0.9.1 +# version 0.9.2 #################3 # Wishlist: # queue encodes # finish TODOs -# list subtitles flag # finish help flag # audio recode flag ################################################################################ @@ -48,7 +47,7 @@ function machineSetup() { if [[ "$(hostname)" == "Kusanagi" ]]; then echo " > Hostname: č‰č–™ē“ å­" export OPTS_ENC="nvidia" - echo "FIX FFMPEG!" + 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" @@ -312,7 +311,9 @@ 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! Todo!" + 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 @@ -343,8 +344,13 @@ function selectSubs() { function extractSubs() { - echo " > extracting subtitles" 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 @@ -522,7 +528,26 @@ while true; do "-h" | "--help") # TODO: Display help shift # all arguments parsed - echo "TODO: HELP!" # Display HELP + echo "$(basename $0) [args] " + #echo "TODO: HELP!" # Display HELP + cat << _EOT + ---------------------------------------------------------------------------- + -k auto-klobber when ffmpeg asks + + -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 + --audio transcode audio, and low-pass filter as well + + -l list subtitles (no encoding) + -s <#> select specific subtitle track number + #TODO: verify legal subtitle track number convention + + -d debug (no cleanup) + --dry dry run (no encoding) +_EOT exit ;; *) @@ -536,7 +561,7 @@ done # Now parse POSITIONAL ARGUMENTS if [[ $# -ne 2 ]]; then echo "ERROR: Incorrect number of positional arguments. Expected 2, got $#" - echo " $0 [args] " + echo " $(basename $0) [args] " exit else INPUT_VIDEO="$(readlink -f "$1")" From 91e6fa045139eb9cc8c9158f4c9346e3f1f882f0 Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:43:25 -0800 Subject: [PATCH 06/27] v0.9.2 --- burnSubs | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) mode change 100644 => 100755 burnSubs diff --git a/burnSubs b/burnSubs old mode 100644 new mode 100755 index 6b44de0..f019935 --- a/burnSubs +++ b/burnSubs @@ -5,12 +5,11 @@ set -o errexit ################################################################################ # burnSubs -# version 0.9.1 +# version 0.9.2 #################3 # Wishlist: # queue encodes # finish TODOs -# list subtitles flag # finish help flag # audio recode flag ################################################################################ @@ -48,7 +47,7 @@ function machineSetup() { if [[ "$(hostname)" == "Kusanagi" ]]; then echo " > Hostname: č‰č–™ē“ å­" export OPTS_ENC="nvidia" - echo "FIX FFMPEG!" + 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" @@ -312,7 +311,9 @@ 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! Todo!" + 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 @@ -343,8 +344,13 @@ function selectSubs() { function extractSubs() { - echo " > extracting subtitles" 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 @@ -522,7 +528,26 @@ while true; do "-h" | "--help") # TODO: Display help shift # all arguments parsed - echo "TODO: HELP!" # Display HELP + echo "$(basename $0) [args] " + #echo "TODO: HELP!" # Display HELP + cat << _EOT + ---------------------------------------------------------------------------- + -k auto-klobber when ffmpeg asks + + -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 + --audio transcode audio, and low-pass filter as well + + -l list subtitles (no encoding) + -s <#> select specific subtitle track number + #TODO: verify legal subtitle track number convention + + -d debug (no cleanup) + --dry dry run (no encoding) +_EOT exit ;; *) @@ -536,7 +561,7 @@ done # Now parse POSITIONAL ARGUMENTS if [[ $# -ne 2 ]]; then echo "ERROR: Incorrect number of positional arguments. Expected 2, got $#" - echo " $0 [args] " + echo " $(basename $0) [args] " exit else INPUT_VIDEO="$(readlink -f "$1")" From 89fdfc7e7885b685683d3a77cd6e65c1c41ffd77 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:44:32 -0800 Subject: [PATCH 07/27] v0.10.0 --- burnSubs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/burnSubs b/burnSubs index f019935..561b8bb 100755 --- a/burnSubs +++ b/burnSubs @@ -5,13 +5,16 @@ set -o errexit ################################################################################ # burnSubs -# version 0.9.2 +# 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() { @@ -281,6 +284,7 @@ function parseStreams() { # 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 @@ -288,6 +292,10 @@ function parseStreams() { # 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_SUB}") + } @@ -340,6 +348,24 @@ function selectSubs() { SUBTITLE_NAME=$($JQ '.['$SUBTITLE_ARRAY_INDEX'].t' "$STREAMS_SUB") echo " > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}" fi + + if [[ $AUDIO_COUNT -gt 1 ]]; 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 + export FF_AUDIO="-map 0:$AUDIO_INDEX -map 0:v:0" + else + export FF_AUDIO="" + fi } @@ -362,7 +388,7 @@ function doTranscode() { # 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} \ + ${FILT_AUDIO} -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO} \ "${OUTPUT_VIDEO}" if [[ "$OPTS_DRYRUN" == true ]]; then return @@ -370,7 +396,7 @@ function doTranscode() { # 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} \ + ${FILT_AUDIO} -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO} \ "${OUTPUT_VIDEO}" export FINAL_STATUS=$? } @@ -426,6 +452,7 @@ 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" @@ -465,6 +492,7 @@ while true; do ;; "-l") OPTS_LISTSUBS=true + OPTS_derived_NO_OUTPUT=true echo ">> !! list subtitles and exit" shift continue @@ -591,6 +619,7 @@ if [[ "$OPTS_LISTSUBS" == "true" ]]; then 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 From 0b604be35a7165949a47be1966be581f697a2e75 Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:44:32 -0800 Subject: [PATCH 08/27] v0.10.0 --- burnSubs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/burnSubs b/burnSubs index f019935..561b8bb 100755 --- a/burnSubs +++ b/burnSubs @@ -5,13 +5,16 @@ set -o errexit ################################################################################ # burnSubs -# version 0.9.2 +# 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() { @@ -281,6 +284,7 @@ function parseStreams() { # 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 @@ -288,6 +292,10 @@ function parseStreams() { # 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_SUB}") + } @@ -340,6 +348,24 @@ function selectSubs() { SUBTITLE_NAME=$($JQ '.['$SUBTITLE_ARRAY_INDEX'].t' "$STREAMS_SUB") echo " > subtitles: [${SUBTITLE_INDEX}] ${SUBTITLE_NAME}" fi + + if [[ $AUDIO_COUNT -gt 1 ]]; 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 + export FF_AUDIO="-map 0:$AUDIO_INDEX -map 0:v:0" + else + export FF_AUDIO="" + fi } @@ -362,7 +388,7 @@ function doTranscode() { # 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} \ + ${FILT_AUDIO} -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO} \ "${OUTPUT_VIDEO}" if [[ "$OPTS_DRYRUN" == true ]]; then return @@ -370,7 +396,7 @@ function doTranscode() { # 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} \ + ${FILT_AUDIO} -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO} \ "${OUTPUT_VIDEO}" export FINAL_STATUS=$? } @@ -426,6 +452,7 @@ 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" @@ -465,6 +492,7 @@ while true; do ;; "-l") OPTS_LISTSUBS=true + OPTS_derived_NO_OUTPUT=true echo ">> !! list subtitles and exit" shift continue @@ -591,6 +619,7 @@ if [[ "$OPTS_LISTSUBS" == "true" ]]; then 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 From 2ec1960ebe5df72f061dcb7e4a78499025c9466e Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:44:50 -0800 Subject: [PATCH 09/27] v0.10.1 --- burnSubs | 76 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/burnSubs b/burnSubs index 561b8bb..2835f6c 100755 --- a/burnSubs +++ b/burnSubs @@ -294,7 +294,7 @@ function parseStreams() { 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_SUB}") + export AUDIO_COUNT=$("$JQ" 'length' "${STREAMS_AUDIO}") } @@ -306,8 +306,8 @@ function listSubtitles() { 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) - X_INDEX=$($JQ '.['$iSUB'].i' $STREAMS_SUB) + 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 @@ -315,6 +315,22 @@ function listSubtitles() { 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 @@ -350,17 +366,21 @@ function selectSubs() { fi if [[ $AUDIO_COUNT -gt 1 ]]; 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") + 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 - # we found english - echo "Japanese audio found" - export AUDIO_INDEX=$($JQ '.['$LANG_TEST'].i' "$STREAMS_AUDIO") + AUDIO_INDEX=$OPTS_SELAUDIO fi export FF_AUDIO="-map 0:$AUDIO_INDEX -map 0:v:0" else @@ -443,6 +463,7 @@ function alertUser() { OPTS_KLOBBER=false OPTS_LISTSUBS=false OPTS_SELSUB=-1 +OPTS_SELAUDIO=-1 OPTS_LISTSUBS=false OPTS_FORCESOFT=false OPTS_FORCEPARTSOFT=false @@ -505,13 +526,21 @@ while true; do 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 ;; - "--audio") + "--lpf") OPTS_LPF_AUDIO=true OPTS_TRANS_AUDIO=true echo ">> !! low pass filter audio to AAC." @@ -567,11 +596,13 @@ while true; do --soft force software decode and encode --psoft use software encoding (allow hardware decode when available) --audiofix transcode audio - --audio transcode audio, and low-pass filter as well + --lpf transcode audio, and low-pass filter as well - -l list subtitles (no encoding) + -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) @@ -587,13 +618,17 @@ _EOT done # Now parse POSITIONAL ARGUMENTS -if [[ $# -ne 2 ]]; then +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] " exit -else - INPUT_VIDEO="$(readlink -f "$1")" - OUTPUT_VIDEO="$2" fi ############### @@ -616,6 +651,7 @@ 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 From cb96ff8b3cd81fe66a6b015db66159993665840c Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:44:50 -0800 Subject: [PATCH 10/27] v0.10.1 --- burnSubs | 76 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/burnSubs b/burnSubs index 561b8bb..2835f6c 100755 --- a/burnSubs +++ b/burnSubs @@ -294,7 +294,7 @@ function parseStreams() { 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_SUB}") + export AUDIO_COUNT=$("$JQ" 'length' "${STREAMS_AUDIO}") } @@ -306,8 +306,8 @@ function listSubtitles() { 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) - X_INDEX=$($JQ '.['$iSUB'].i' $STREAMS_SUB) + 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 @@ -315,6 +315,22 @@ function listSubtitles() { 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 @@ -350,17 +366,21 @@ function selectSubs() { fi if [[ $AUDIO_COUNT -gt 1 ]]; 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") + 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 - # we found english - echo "Japanese audio found" - export AUDIO_INDEX=$($JQ '.['$LANG_TEST'].i' "$STREAMS_AUDIO") + AUDIO_INDEX=$OPTS_SELAUDIO fi export FF_AUDIO="-map 0:$AUDIO_INDEX -map 0:v:0" else @@ -443,6 +463,7 @@ function alertUser() { OPTS_KLOBBER=false OPTS_LISTSUBS=false OPTS_SELSUB=-1 +OPTS_SELAUDIO=-1 OPTS_LISTSUBS=false OPTS_FORCESOFT=false OPTS_FORCEPARTSOFT=false @@ -505,13 +526,21 @@ while true; do 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 ;; - "--audio") + "--lpf") OPTS_LPF_AUDIO=true OPTS_TRANS_AUDIO=true echo ">> !! low pass filter audio to AAC." @@ -567,11 +596,13 @@ while true; do --soft force software decode and encode --psoft use software encoding (allow hardware decode when available) --audiofix transcode audio - --audio transcode audio, and low-pass filter as well + --lpf transcode audio, and low-pass filter as well - -l list subtitles (no encoding) + -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) @@ -587,13 +618,17 @@ _EOT done # Now parse POSITIONAL ARGUMENTS -if [[ $# -ne 2 ]]; then +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] " exit -else - INPUT_VIDEO="$(readlink -f "$1")" - OUTPUT_VIDEO="$2" fi ############### @@ -616,6 +651,7 @@ 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 From 28c2c7fddd2792d87c5c49519be755d95e3c8b12 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:45:04 -0800 Subject: [PATCH 11/27] v0.10.2 --- burnSubs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/burnSubs b/burnSubs index 2835f6c..f0c0f85 100755 --- a/burnSubs +++ b/burnSubs @@ -211,7 +211,7 @@ function dumpFonts() { # Dumb font dump cd "$FONTDIR" "$FFMPEG" -dump_attachment:t "" -i "${INPUT_VIDEO}" -vn -an \ - -f null /dev/null 2>/dev/null || /bin/true + -f null -y /dev/null 2>/dev/null || /bin/true cd "$WD" } From 82e028bc14a8aa6b14353324dc2c79c023583dc3 Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:45:04 -0800 Subject: [PATCH 12/27] v0.10.2 --- burnSubs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/burnSubs b/burnSubs index 2835f6c..f0c0f85 100755 --- a/burnSubs +++ b/burnSubs @@ -211,7 +211,7 @@ function dumpFonts() { # Dumb font dump cd "$FONTDIR" "$FFMPEG" -dump_attachment:t "" -i "${INPUT_VIDEO}" -vn -an \ - -f null /dev/null 2>/dev/null || /bin/true + -f null -y /dev/null 2>/dev/null || /bin/true cd "$WD" } From be2c3c111e0bd732da3a2aa8d9978d45aefb49f3 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 7 Nov 2019 07:46:13 -0800 Subject: [PATCH 13/27] v0.10.4, formal git setup. --- .gitignore | 2 ++ burnSubs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ef5c6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.directory + diff --git a/burnSubs b/burnSubs index f0c0f85..2f2cbf7 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.10.0 +# version 0.10.4 #################3 # Wishlist: # queue encodes From d40f7b8b63042569b527c3112f196f187fbbb46a Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Thu, 7 Nov 2019 07:46:13 -0800 Subject: [PATCH 14/27] v0.10.4, formal git setup. --- .gitignore | 2 ++ burnSubs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ef5c6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.directory + diff --git a/burnSubs b/burnSubs index f0c0f85..2f2cbf7 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.10.0 +# version 0.10.4 #################3 # Wishlist: # queue encodes From 85aa98c6200109748d0dd909ba9153425c3b2f9c Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 12 Apr 2020 18:06:45 -0700 Subject: [PATCH 15/27] Added flags for strings to capture specific languages. --- burnSubs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/burnSubs b/burnSubs index 2f2cbf7..4b504ca 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.10.4 +# version 0.11.0 #################3 # Wishlist: # queue encodes @@ -23,6 +23,7 @@ function machineSetup() { 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" @@ -341,7 +342,8 @@ function selectSubs() { export SUBTITLE_INDEX=-1 else if [[ $OPTS_SELSUB -lt 0 && $SUB_COUNT -gt 1 ]]; then - LANG_TEST=$($JQ '[.[].lang] | index("eng")' "$STREAMS_SUB") + OPTS_SELSUB_LANG="${OPTS_SELSUB_LANG:-eng}" + LANG_TEST=$($JQ '[.[].lang] | index("'${OPTS_SELSUB_LANG}'")' "$STREAMS_SUB") echo " > WARNING: Multiple subtitles!" printf " Using default selection rules... " if [[ "$LANG_TEST" == "null" ]]; then @@ -367,7 +369,8 @@ function selectSubs() { if [[ $AUDIO_COUNT -gt 1 ]]; then if [[ $OPTS_SELAUDIO -lt 0 ]]; then - LANG_TEST=$($JQ '[.[].lang] | index("jpn")' "$STREAMS_AUDIO") + OPTS_SELAUDIO_LANG="${OPTS_SELAUDIO_LANG:-jpn}" + LANG_TEST=$($JQ '[.[].lang] | index("'${OPTS_SELAUDIO_LANG}'")' "$STREAMS_AUDIO") echo " > WARNING: Multiple audio streams!" printf " Using default selection rules... " if [[ "$LANG_TEST" == "null" ]]; then @@ -493,7 +496,7 @@ FINAL_STATUS=1 ###### # Reformat and organize the input strings -OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix' -- "$@") +OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") # reassign them as positional arguments eval set -- "$OPT_STRING" @@ -526,6 +529,14 @@ while true; do shift 2 continue ;; + "--slocale") + OPTS_SELSUB_LANG="$2" + #TODO: verify legal subtitle track number convention" + echo "TODO: verify legal subtitle track number convention" + echo ">> !! Selecting subtitle track #${OPTS_SELSUB_LANG}" + shift 2 + continue + ;; "-a") OPTS_SELAUDIO="$2" #TODO: verify legal subtitle track number convention" @@ -534,6 +545,14 @@ while true; do shift 2 continue ;; + "--alocale") + OPTS_SELAUDIO_LANG="$2" + #TODO: verify legal subtitle track number convention" + echo "TODO: verify legal audio track number convention" + echo ">> !! Selecting audio track with locale #${OPTS_SELAUDIO_LANG}" + shift 2 + continue + ;; "--psoft") OPTS_FORCEPARTSOFT=true echo ">> !! forcing software encoding." @@ -601,8 +620,12 @@ while true; do -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 + -a <#> select specific audio track number + #TODO: verify legal audio track number convention + --slocale 'eng' select specific subtitle track number + #TODO: verify legal subtitle track number convention + --alocale 'jpn' select specific audio track number + #TODO: verify legal audio track number convention -d debug (no cleanup) --dry dry run (no encoding) From 84cbd0bc744cea86fcd72e97ff27edfc7b185069 Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Sun, 12 Apr 2020 18:06:45 -0700 Subject: [PATCH 16/27] Added flags for strings to capture specific languages. --- burnSubs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/burnSubs b/burnSubs index 2f2cbf7..4b504ca 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.10.4 +# version 0.11.0 #################3 # Wishlist: # queue encodes @@ -23,6 +23,7 @@ function machineSetup() { 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" @@ -341,7 +342,8 @@ function selectSubs() { export SUBTITLE_INDEX=-1 else if [[ $OPTS_SELSUB -lt 0 && $SUB_COUNT -gt 1 ]]; then - LANG_TEST=$($JQ '[.[].lang] | index("eng")' "$STREAMS_SUB") + OPTS_SELSUB_LANG="${OPTS_SELSUB_LANG:-eng}" + LANG_TEST=$($JQ '[.[].lang] | index("'${OPTS_SELSUB_LANG}'")' "$STREAMS_SUB") echo " > WARNING: Multiple subtitles!" printf " Using default selection rules... " if [[ "$LANG_TEST" == "null" ]]; then @@ -367,7 +369,8 @@ function selectSubs() { if [[ $AUDIO_COUNT -gt 1 ]]; then if [[ $OPTS_SELAUDIO -lt 0 ]]; then - LANG_TEST=$($JQ '[.[].lang] | index("jpn")' "$STREAMS_AUDIO") + OPTS_SELAUDIO_LANG="${OPTS_SELAUDIO_LANG:-jpn}" + LANG_TEST=$($JQ '[.[].lang] | index("'${OPTS_SELAUDIO_LANG}'")' "$STREAMS_AUDIO") echo " > WARNING: Multiple audio streams!" printf " Using default selection rules... " if [[ "$LANG_TEST" == "null" ]]; then @@ -493,7 +496,7 @@ FINAL_STATUS=1 ###### # Reformat and organize the input strings -OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix' -- "$@") +OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") # reassign them as positional arguments eval set -- "$OPT_STRING" @@ -526,6 +529,14 @@ while true; do shift 2 continue ;; + "--slocale") + OPTS_SELSUB_LANG="$2" + #TODO: verify legal subtitle track number convention" + echo "TODO: verify legal subtitle track number convention" + echo ">> !! Selecting subtitle track #${OPTS_SELSUB_LANG}" + shift 2 + continue + ;; "-a") OPTS_SELAUDIO="$2" #TODO: verify legal subtitle track number convention" @@ -534,6 +545,14 @@ while true; do shift 2 continue ;; + "--alocale") + OPTS_SELAUDIO_LANG="$2" + #TODO: verify legal subtitle track number convention" + echo "TODO: verify legal audio track number convention" + echo ">> !! Selecting audio track with locale #${OPTS_SELAUDIO_LANG}" + shift 2 + continue + ;; "--psoft") OPTS_FORCEPARTSOFT=true echo ">> !! forcing software encoding." @@ -601,8 +620,12 @@ while true; do -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 + -a <#> select specific audio track number + #TODO: verify legal audio track number convention + --slocale 'eng' select specific subtitle track number + #TODO: verify legal subtitle track number convention + --alocale 'jpn' select specific audio track number + #TODO: verify legal audio track number convention -d debug (no cleanup) --dry dry run (no encoding) From 1eea92094b7a899b04557678851cf1cc705cfb76 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 8 May 2020 19:39:06 -0700 Subject: [PATCH 17/27] Fixed short -a flag bug --- burnSubs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/burnSubs b/burnSubs index 4b504ca..40a27c2 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.11.0 +# version 0.11.1 #################3 # Wishlist: # queue encodes @@ -291,7 +291,7 @@ function parseStreams() { # 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}" + "$JQ" 'reduce .streams[] as $item ([]; if ($item.codec_name == "ass" or $item.codec_name == "dvd_subtitle") then [.[],$item] else . end) | reduce .[] as $item ([]; [.[],{t:($item.tags.title),i:($item.index),lang:$item.tags.language, disposition:$item.disposition, codec:($item.codec_name)}]) | reduce .[] as $item ([]; [.[],{t:($item.t // ($item.lang + "-" + ($item.i | tostring))),i:$item.i,lang:$item.lang,disposition:$item.disposition,codec:$item.codec}])' "${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}" @@ -310,7 +310,11 @@ function listSubtitles() { 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) + X_CODEC=$($JQ '.['$iSUB'].codec' $STREAMS_SUB) printf " %2d: %4s %s\n" $X_INDEX $X_LANG "$X_TITLE" + if [[ "$X_CODEC" != "ass" ]]; then + printf " format: %s\n" $X_CODEC + fi done echo "" return @@ -496,7 +500,7 @@ FINAL_STATUS=1 ###### # Reformat and organize the input strings -OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") +OPT_STRING=$(getopt -o 'hkls:a:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") # reassign them as positional arguments eval set -- "$OPT_STRING" From 328fcb23185dae3f3d9606d72520819b5b55c17f Mon Sep 17 00:00:00 2001 From: Luke Renaud Date: Fri, 8 May 2020 19:39:06 -0700 Subject: [PATCH 18/27] Fixed short -a flag bug --- burnSubs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/burnSubs b/burnSubs index 4b504ca..40a27c2 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.11.0 +# version 0.11.1 #################3 # Wishlist: # queue encodes @@ -291,7 +291,7 @@ function parseStreams() { # 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}" + "$JQ" 'reduce .streams[] as $item ([]; if ($item.codec_name == "ass" or $item.codec_name == "dvd_subtitle") then [.[],$item] else . end) | reduce .[] as $item ([]; [.[],{t:($item.tags.title),i:($item.index),lang:$item.tags.language, disposition:$item.disposition, codec:($item.codec_name)}]) | reduce .[] as $item ([]; [.[],{t:($item.t // ($item.lang + "-" + ($item.i | tostring))),i:$item.i,lang:$item.lang,disposition:$item.disposition,codec:$item.codec}])' "${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}" @@ -310,7 +310,11 @@ function listSubtitles() { 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) + X_CODEC=$($JQ '.['$iSUB'].codec' $STREAMS_SUB) printf " %2d: %4s %s\n" $X_INDEX $X_LANG "$X_TITLE" + if [[ "$X_CODEC" != "ass" ]]; then + printf " format: %s\n" $X_CODEC + fi done echo "" return @@ -496,7 +500,7 @@ FINAL_STATUS=1 ###### # Reformat and organize the input strings -OPT_STRING=$(getopt -o 'hkls:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") +OPT_STRING=$(getopt -o 'hkls:a:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") # reassign them as positional arguments eval set -- "$OPT_STRING" From 5a1809437770ec619b3711d3fecbd3cfe8ff42fc Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 15 May 2020 17:59:56 -0700 Subject: [PATCH 19/27] Improved default filter selection rules. --- burnSubs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/burnSubs b/burnSubs index 40a27c2..39f9a20 100755 --- a/burnSubs +++ b/burnSubs @@ -5,8 +5,8 @@ set -o errexit ################################################################################ # burnSubs -# version 0.11.1 -#################3 +# version 0.11.2 +################# # Wishlist: # queue encodes # finish TODOs @@ -346,18 +346,29 @@ function selectSubs() { export SUBTITLE_INDEX=-1 else if [[ $OPTS_SELSUB -lt 0 && $SUB_COUNT -gt 1 ]]; then - OPTS_SELSUB_LANG="${OPTS_SELSUB_LANG:-eng}" - LANG_TEST=$($JQ '[.[].lang] | index("'${OPTS_SELSUB_LANG}'")' "$STREAMS_SUB") echo " > WARNING: Multiple subtitles!" printf " Using default selection rules... " - if [[ "$LANG_TEST" == "null" ]]; then + OPTS_SELSUB_LANG="${OPTS_SELSUB_LANG:-eng}" + LANG_TEST=$($JQ '[.[].lang | match("'${OPTS_SELSUB_LANG}'")] | length' "$STREAMS_SUB") + if [[ "$LANG_TEST" == "0" ]]; then echo "English not found!" echo " ==> Reverting to first subtitle file." export SUBTITLE_INDEX=$($JQ '.[0].i' "$STREAMS_SUB") - else + elif [[ "$LANG_TEST" == "1" ]]; then + LANG_SINGLE_SELECT=$($JQ '[.[].lang] | index("'${OPTS_SELSUB_LANG}'")' "$STREAMS_SUB") # we found english echo "English found" - export SUBTITLE_INDEX=$($JQ '.['$LANG_TEST'].i' "$STREAMS_SUB") + export SUBTITLE_INDEX=$($JQ '.['$LANG_SINGLE_SELECT'].i' "$STREAMS_SUB") + else + # try to avoid a signs and lyrics track + LANG_SINGLE_SELECT=$($JQ '[.[].lang] | index("'${OPTS_SELSUB_LANG}'")' "$STREAMS_SUB") + echo "Multiple english tracks found." + export SUBTITLE_INDEX=$($JQ 'reduce .[] as $trk ([]; if ($trk.lang == "'${OPTS_SELSUB_LANG}'" and ( ($trk.t | test("lyrics";"i") or ($trk.t | test("signs";"i")) ) | not ) ) then [.[],{t:$trk.t,i:$trk.i}] else . end) | .[].i' "$STREAMS_SUB") + # And display rejected subtitles too. + SUBTITLE_REJECT_LIST=($($JQ 'reduce .[] as $trk ([]; if ($trk.lang == "'${OPTS_SELSUB_LANG}'" and ( ($trk.t | test("lyrics";"i") or ($trk.t | test("signs";"i")) ) ) ) then [.[],{t:$trk.t,i:$trk.i}] else . end) | .[].t' "$STREAMS_SUB")) + for REJECT_SUB in ${SUBTITLE_REJECT_LIST[@]}; do + echo " > rejecting ${REJECT_SUB}" + done fi else if [[ $SUB_COUNT -eq 1 ]]; then @@ -379,12 +390,14 @@ function selectSubs() { printf " Using default selection rules... " if [[ "$LANG_TEST" == "null" ]]; then echo "Japanese audio not found!" - echo " ==> Reverting to first subtitle file." + echo " ==> Reverting to first audio stream." 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") + AUDIO_STREAM_TITLE=$($JQ '.['$LANG_TEST'].t' "$STREAMS_AUDIO") + echo " ==> stream has title ${AUDIO_STREAM_TITLE}" fi else AUDIO_INDEX=$OPTS_SELAUDIO From a3817c90fb20eaa989c57f07d82beb4f415153d9 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 11 Jul 2020 11:49:19 -0700 Subject: [PATCH 20/27] added option to display default CRF in help. --- burnSubs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/burnSubs b/burnSubs index 39f9a20..7807ecc 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.11.2 +# version 0.11.3 ################# # Wishlist: # queue encodes @@ -17,6 +17,8 @@ set -o errexit # automatically select JPN audio if more than one audio channel found. ################################################################################ +DEFAULTS_OPTS_CRF=20 + function machineSetup() { # Default setup export FF_ENC="libx264" @@ -24,7 +26,7 @@ function machineSetup() { export FILT_PFX="" export FILT_SFX="" - CRF=${OPTS_CRF:-23} + CRF=${OPTS_CRF:-$DEFAULTS_OPTS_CRF} export FF_STD="-preset myTranscoderPreset -crf $CRF -tune animation \ -preset medium -movflags +faststart" export FF_EXT="-profile:v high -level 4.0" @@ -629,6 +631,7 @@ while true; do -t ffmpeg encoding time limit --crf <#> override CRF setting + default: $DEFAULTS_OPTS_CRF --soft force software decode and encode --psoft use software encoding (allow hardware decode when available) --audiofix transcode audio From e7d6ce8de372c3f898faf5b01451351f95b500d0 Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 19 Jul 2020 12:25:26 -0700 Subject: [PATCH 21/27] Changed FFMPEG default verbosity. Only shows progress now. --- burnSubs | 82 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/burnSubs b/burnSubs index 7807ecc..9448490 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.11.3 +# version 0.12.0 ################# # Wishlist: # queue encodes @@ -428,17 +428,21 @@ function extractSubs() { function doTranscode() { echo "=> Starting transcode:" # shellcheck disable=SC2086 - echo "$FFMPEG" ${FF_HW} -i "${INPUT_VIDEO}" -sn ${LIM_TIME} \ + echo "$FFMPEG" ${FF_VERBOSITY} ${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} \ + ${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} \ + "$FFMPEG" ${FF_VERBOSITY} ${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} \ + ${FILT_AUDIO} \ + -c:v "${FF_ENC}" ${FF_STD} ${FF_EXT} ${FF_AUDIO} \ "${OUTPUT_VIDEO}" export FINAL_STATUS=$? } @@ -496,6 +500,7 @@ OPTS_DEBUG=false OPTS_LPF_AUDIO=false OPTS_TRANS_AUDIO=false OPTS_derived_NO_OUTPUT=false +OPTS_VERBOSITY=1 unset OPT_CRF # this is the --icon flag passed to notify-send at the end of the transcode NOTIFY_ICON="face-tired" @@ -515,7 +520,8 @@ FINAL_STATUS=1 ###### # Reformat and organize the input strings -OPT_STRING=$(getopt -o 'hkls:a:dt:' --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:' -- "$@") +OPT_STRING=$(getopt -o 'hkls:a:dt:vq' \ + --long 'help,psoft,soft,dry,crf:,audio,audiofix,alocale:,slocale:,verbose,quiet' -- "$@") # reassign them as positional arguments eval set -- "$OPT_STRING" @@ -616,6 +622,22 @@ while true; do shift continue ;; + "--verbose"|"-v") + if [[ ${OPTS_VERBOSITY} -ne 0 ]]; then + OPTS_VERBOSITY=2 + echo ">> !! print ffmpeg header." + else + echo ">> !! Ignoring verbosity flag. Quiet takes precidence." + fi + shift + continue + ;; + "--quiet"|"-q") + OPTS_VERBOSITY=0 + echo ">> !! print ffmpeg header." + shift + continue + ;; "--") shift # all arguments parsed break @@ -627,28 +649,30 @@ while true; do #echo "TODO: HELP!" # Display HELP cat << _EOT ---------------------------------------------------------------------------- - -k auto-klobber when ffmpeg asks + -k auto-klobber when ffmpeg asks - -t ffmpeg encoding time limit - --crf <#> override CRF setting - default: $DEFAULTS_OPTS_CRF - --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 + -t ffmpeg encoding time limit + --crf <#> override CRF setting + default: $DEFAULTS_OPTS_CRF + --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 audio track number - #TODO: verify legal audio track number convention - --slocale 'eng' select specific subtitle track number - #TODO: verify legal subtitle track number convention - --alocale 'jpn' select specific audio track number - #TODO: verify legal audio track number convention + -l list subtitles and audio tracks (no encoding) + -s <#> select specific subtitle track number + #TODO: verify legal subtitle track number convention + -a <#> select specific audio track number + #TODO: verify legal audio track number convention + --slocale 'eng' select specific subtitle track number + #TODO: verify legal subtitle track number convention + --alocale 'jpn' select specific audio track number + #TODO: verify legal audio track number convention - -d debug (no cleanup) - --dry dry run (no encoding) + -d debug (no cleanup) + --dry dry run (no encoding) + -q, --quiet make ffmpeg shutup + -v, --verbose show all FFMPEG details (except that ruddy header) _EOT exit ;; @@ -674,6 +698,14 @@ else exit fi +# Actual verbosity parsing +if [[ ${OPTS_VERBOSITY} -le 0 ]]; then + FF_VERBOSITY="-hide_banner -loglevel error" +elif [[ ${OPTS_VERBOSITY} -eq 1 ]]; then + FF_VERBOSITY="-hide_banner -loglevel error -stats" +else # ie [[ ${OPTS_VERBOSITY} -ge 2 ]]; then + FF_VERBOSITY="-hide_banner" +fi ############### # Configure the encoder based upon the hostname machineSetup From c1e70ce736c80ca87ee84405a2d4cca7aa30a711 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 19 Sep 2020 16:11:11 -0700 Subject: [PATCH 22/27] added surround sound mixdown default. --- burnSubs | 68 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/burnSubs b/burnSubs index 9448490..9838b67 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.12.0 +# version 0.13.0 ################# # Wishlist: # queue encodes @@ -66,20 +66,6 @@ function machineSetup() { 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 @@ -296,10 +282,45 @@ function parseStreams() { "$JQ" 'reduce .streams[] as $item ([]; if ($item.codec_name == "ass" or $item.codec_name == "dvd_subtitle") then [.[],$item] else . end) | reduce .[] as $item ([]; [.[],{t:($item.tags.title),i:($item.index),lang:$item.tags.language, disposition:$item.disposition, codec:($item.codec_name)}]) | reduce .[] as $item ([]; [.[],{t:($item.t // ($item.lang + "-" + ($item.i | tostring))),i:$item.i,lang:$item.lang,disposition:$item.disposition,codec:$item.codec}])' "${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}" + "$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, chan:{channels:$item.channels, channel_layout:$item.channel_layout}}]) | reduce .[] as $item ([]; [.[],{t:($item.t // ($item.lang + "-" + ($item.i | tostring))),i:$item.i,lang:$item.lang,disposition:$item.disposition, chan:$item.chan}])' "${STREAMS_ALL}" > "${STREAMS_AUDIO}" export AUDIO_COUNT=$("$JQ" 'length' "${STREAMS_AUDIO}") +} + +function setupAudioTranscode() { + if [[ $AUDIO_COUNT -ne 1 ]]; then + CHANNEL_COUNT=$($JQ '.[] | select(.i == '$AUDIO_INDEX') | .chan.channels' "$STREAMS_AUDIO") + CHANNEL_LAYOUT=$($JQ '.[] | select(.i == '$AUDIO_INDEX') | .chan.channel_layout' "$STREAMS_AUDIO") + else + CHANNEL_COUNT=$($JQ '.[] | .chan.channels' "$STREAMS_AUDIO") + CHANNEL_LAYOUT=$($JQ '.[] | .chan.channel_layout' "$STREAMS_AUDIO") + fi + + if [[ "${CHANNEL_COUNT}" == "6" && "${CHANNEL_LAYOUT}" == '"5.1"' ]]; then + # check if we're 5.1 and if so flag transcode. + export OPTS_TRANS_AUDIO=true + elif [[ "${CHANNEL_COUNT}" != "2" && "${CHANNEL_LAYOUT}" != '"5.1"' ]]; then + echo "ERROR: Trying to enocde non 5.1 and non-stereo audio stream." + exit 1 + 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 [[ "${OPTS_SURROUND_PRESERVE}" == false ]]; then + # From https://superuser.com/questions/852400/properly-downmix-5-1-to-stereo-using-ffmpeg + # Nightmode Formula + FILT_AUDIO="-filter:a pan=stereo|FL> !! preserving 5.1/7.1 surround sound if available." + shift + continue + ;; "--soft") OPTS_FORCESOFT=true echo ">> !! forcing software decoding/encoding." @@ -657,6 +686,7 @@ while true; do --soft force software decode and encode --psoft use software encoding (allow hardware decode when available) --audiofix transcode audio + --keep-surround try to preserve surround sound rather than downmixing to stereo. --lpf transcode audio, and low-pass filter as well -l list subtitles and audio tracks (no encoding) @@ -734,6 +764,10 @@ fi selectSubs # extract the selected subtitle file extractSubs $SUBTITLE_INDEX + +# Configure the audio straem +setupAudioTranscode + # Set the bitrate if that function wasn't disabled runExtraProc "h264_vaapi" From 3547c0eaebe9b1161eb4a88d1ba68c9dad8bc562 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 19 Sep 2020 16:13:13 -0700 Subject: [PATCH 23/27] machine name tweak. --- burnSubs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/burnSubs b/burnSubs index 9838b67..7aee242 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.13.0 +# version 0.13.1 ################# # Wishlist: # queue encodes @@ -57,7 +57,7 @@ function machineSetup() { #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 + elif [[ "$(hostname)" == "random-vaapi-intel-laptop" ]]; then echo " > Hostname: $(hostname)" export OPTS_ENC="vaapi" else From 49fead9c6cdbd9758cbb6ee60cf47db061f922cd Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 19 Sep 2020 16:32:51 -0700 Subject: [PATCH 24/27] added readme, removed defunct dependancies. --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ burnSubs | 8 ++++---- 2 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..64907eb --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# burnSubs +You found my burnSubs tool. The goal for this tool is to ease the +conversion of arbitrary video files with soft-subtitles in the SSA/ASS +Substation Alpha format into simple stereo hard-subtitled video files. + +## Features +* softsub to hardsub conversion +* embedded font files +* selecting specific audio streams +* selecting specific video streams +* automatic selection of language preferences +* default surround sound to stereo down-mixing + * opt out CLI flag available +* anti-clobbering default behavior +* auto-cleanup on error + +## Prerequisite Tools +Firstly, the tool will yell at you if the tools it needs don't exist. +Feel free to just run the tool, and it will let you know what you're +missing. + +* [`ffmpeg`](https://ffmpeg.org/) - the one and only + * `ffprobe` - usually comes with ffmpeg +* [`jq`](https://stedolan.github.io/jq/) - file/pipe based JSON processor + + +## How does it work? +`burnSubs` takes an input video file and tries to figure out what +audio streams and subtitle streams exist within the file. It stores +metadata in `/tmp` while it runs. When running it will pull the +streams within the input file, and try to select Japanese language +audio streams, and a non-signs subtitle stream (i.e. a full language +subtitle stream) to add to the output video. + +Before transcoding it will then extract the subtitle file to pass into +`ffmpeg`'s subtitle burn in filter, and will try to down-mix any +surround sound input streams to stereo. Downmixing, track selection, +clobbering behavior, and verbosity can all be controlled to a limited +extent by CLI flags. + +## Can you make it do *XYZ*. +Give me an enhancement request in github and I'll take a look. \ No newline at end of file diff --git a/burnSubs b/burnSubs index 7aee242..3c29365 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.13.1 +# version 0.13.2 ################# # Wishlist: # queue encodes @@ -139,9 +139,9 @@ function setupBins() { setAndValidateBin "ffmpeg" "FFMPEG" setAndValidateBin "ffprobe" "FFPROBE" setAndValidateBin "jq" "JQ" - setAndValidateBin "python3" "PYTHON3" - setAndValidateBin "awk" "AWK" - setAndValidateBin "date" "DATE" + #setAndValidateBin "python3" "PYTHON3" + #setAndValidateBin "awk" "AWK" + #setAndValidateBin "date" "DATE" } # Used by the above function to evaluate overrides if they are set, and From 6945d8dba947706eb081633bfb6495ba9b9b12b6 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 18 Jun 2022 14:28:20 -0700 Subject: [PATCH 25/27] Bugfix for rejecting dubs and logical parenth error in multi-english sub configuration. --- burnSubs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/burnSubs b/burnSubs index 3c29365..045b828 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.13.2 +# version 0.13.3 ################# # Wishlist: # queue encodes @@ -386,9 +386,9 @@ function selectSubs() { # try to avoid a signs and lyrics track LANG_SINGLE_SELECT=$($JQ '[.[].lang] | index("'${OPTS_SELSUB_LANG}'")' "$STREAMS_SUB") echo "Multiple english tracks found." - export SUBTITLE_INDEX=$($JQ 'reduce .[] as $trk ([]; if ($trk.lang == "'${OPTS_SELSUB_LANG}'" and ( ($trk.t | test("lyrics";"i") or ($trk.t | test("signs";"i")) ) | not ) ) then [.[],{t:$trk.t,i:$trk.i}] else . end) | .[].i' "$STREAMS_SUB") + export SUBTITLE_INDEX=$($JQ 'reduce .[] as $trk ([]; if ($trk.lang == "'${OPTS_SELSUB_LANG}'" and (( ($trk.t | test("lyrics";"i")) or ($trk.t | test("signs";"i")) or ($trk.t | test("dub";"i")) )|not) ) then [.[],{t:$trk.t,i:$trk.i}] else . end) | .[].i' "$STREAMS_SUB") # And display rejected subtitles too. - SUBTITLE_REJECT_LIST=($($JQ 'reduce .[] as $trk ([]; if ($trk.lang == "'${OPTS_SELSUB_LANG}'" and ( ($trk.t | test("lyrics";"i") or ($trk.t | test("signs";"i")) ) ) ) then [.[],{t:$trk.t,i:$trk.i}] else . end) | .[].t' "$STREAMS_SUB")) + SUBTITLE_REJECT_LIST=($($JQ 'reduce .[] as $trk ([]; if ($trk.lang == "'${OPTS_SELSUB_LANG}'" and (( ($trk.t | test("lyrics";"i")) or ($trk.t | test("signs";"i")) or ($trk.t | test("dub";"i")) )) ) techo hen [.[],{t:$trk.t,i:$trk.i}] else . end) | .[].t' "$STREAMS_SUB")) for REJECT_SUB in ${SUBTITLE_REJECT_LIST[@]}; do echo " > rejecting ${REJECT_SUB}" done From 2d05add839d5a1df8a5af147594c1208c9273714 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 19 Sep 2020 16:49:02 -0700 Subject: [PATCH 26/27] Added deubg making the output verbose. --- burnSubs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/burnSubs b/burnSubs index 3c29365..0713272 100755 --- a/burnSubs +++ b/burnSubs @@ -5,7 +5,7 @@ set -o errexit ################################################################################ # burnSubs -# version 0.13.2 +# version 0.13.3 ################# # Wishlist: # queue encodes @@ -13,8 +13,6 @@ set -o errexit # finish help flag # audio recode flag # -# Changes -# automatically select JPN audio if more than one audio channel found. ################################################################################ DEFAULTS_OPTS_CRF=20 @@ -175,6 +173,7 @@ function doCleanup() { echo "=> Cleaning up." rm -r "$TMP" else + set +x echo "tmp dir: $TMP" fi @@ -314,7 +313,7 @@ function setupAudioTranscode() { if [[ "${OPTS_SURROUND_PRESERVE}" == false ]]; then # From https://superuser.com/questions/852400/properly-downmix-5-1-to-stereo-using-ffmpeg # Nightmode Formula - FILT_AUDIO="-filter:a pan=stereo|FL Date: Tue, 18 Apr 2023 17:01:54 -0700 Subject: [PATCH 27/27] default CRF to 18 --- burnSubs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/burnSubs b/burnSubs index 7738dd3..0feca62 100755 --- a/burnSubs +++ b/burnSubs @@ -15,7 +15,7 @@ set -o errexit # ################################################################################ -DEFAULTS_OPTS_CRF=20 +DEFAULTS_OPTS_CRF=18 function machineSetup() { # Default setup