mirror of
https://github.com/linuxserver/docker-mods.git
synced 2026-02-04 14:44:38 +08:00
2041 lines
96 KiB
Bash
Executable File
2041 lines
96 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Video remuxing script designed for use with Radarr and Sonarr
|
|
# Automatically strips out unwanted audio and subtitles tracks, keeping only the desired languages.
|
|
# Prod: https://github.com/linuxserver/docker-mods/tree/radarr-striptracks
|
|
# Dev/test: https://github.com/TheCaptain989/radarr-striptracks
|
|
#
|
|
# Inspired by Endoro's post 1/5/2014:
|
|
# https://forum.videohelp.com/threads/343271-BULK-remove-non-English-tracks-from-MKV-container#post2292889
|
|
#
|
|
# Put a colon `:` in front of every language code. Expects ISO639-2 codes
|
|
|
|
# NOTE: ShellCheck linter directives appear as comments
|
|
|
|
# Dependencies: # sudo apt install mkvtoolnix jq
|
|
# From mkvtoolnix:
|
|
# mkvmerge
|
|
# mkvpropedit
|
|
# From jq:
|
|
# jq
|
|
# Generally always available:
|
|
# sed
|
|
# awk
|
|
# curl
|
|
# numfmt
|
|
# stat
|
|
# nice
|
|
# basename
|
|
# dirname
|
|
# mktemp
|
|
|
|
# Exit codes:
|
|
# 0 - success; or test
|
|
# 1 - no video file specified on command line
|
|
# 2 - no audio language specified on command line
|
|
# 3 - no subtitles language specified on command line
|
|
# 4 - mkvmerge, mkvpropedit, or jq not found
|
|
# 5 - input video file not found
|
|
# 6 - unable to rename temp video to MKV
|
|
# 7 - unknown eventtype environment variable
|
|
# 8 - unsupported Radarr/Sonarr version (v2)
|
|
# 9 - mkvmerge returned an unsupported container format
|
|
# 10 - remuxing completed, but no output file found
|
|
# 11 - source video had no audio tracks
|
|
# 12 - log file is not writable
|
|
# 13 - mkvmerge or mkvpropedit exited with an error
|
|
# 15 - could not set permissions and/or owner on new file
|
|
# 16 - could not delete the original file
|
|
# 17 - Radarr/Sonarr API error
|
|
# 18 - Radarr/Sonarr job timeout
|
|
# 20 - general error
|
|
|
|
### Global variables
|
|
function initialize_variables {
|
|
# Initialize variables
|
|
|
|
export striptracks_script=$(basename "$0")
|
|
export striptracks_ver="{{VERSION}}"
|
|
export striptracks_pid=$$
|
|
export striptracks_arr_config=/config/config.xml
|
|
export striptracks_log=/config/logs/striptracks.txt
|
|
export striptracks_maxlogsize=512000
|
|
export striptracks_maxlog=4
|
|
export striptracks_debug=0
|
|
export striptracks_nice="nice"
|
|
# If this were defined directly in Radarr or Sonarr this would not be needed here
|
|
# shellcheck disable=SC2089
|
|
export striptracks_isocodemap='{"languages":[{"language":{"name":"Afrikaans","iso639-2":["afr"]}},{"language":{"name":"Albanian","iso639-2":["sqi","alb"]}},{"language":{"name":"Any","iso639-2":["any"]}},{"language":{"name":"Arabic","iso639-2":["ara"]}},{"language":{"name":"Bengali","iso639-2":["ben"]}},{"language":{"name":"Bosnian","iso639-2":["bos"]}},{"language":{"name":"Bulgarian","iso639-2":["bul"]}},{"language":{"name":"Catalan","iso639-2":["cat"]}},{"language":{"name":"Chinese","iso639-2":["zho","chi"]}},{"language":{"name":"Croatian","iso639-2":["hrv"]}},{"language":{"name":"Czech","iso639-2":["ces","cze"]}},{"language":{"name":"Danish","iso639-2":["dan"]}},{"language":{"name":"Dutch","iso639-2":["nld","dut"]}},{"language":{"name":"English","iso639-2":["eng"]}},{"language":{"name":"Estonian","iso639-2":["est"]}},{"language":{"name":"Finnish","iso639-2":["fin"]}},{"language":{"name":"Flemish","iso639-2":["nld","dut"]}},{"language":{"name":"French","iso639-2":["fra","fre"]}},{"language":{"name":"Georgian","iso639-2":["kat","geo"]}},{"language":{"name":"German","iso639-2":["deu","ger"]}},{"language":{"name":"Greek","iso639-2":["ell","gre"]}},{"language":{"name":"Hebrew","iso639-2":["heb"]}},{"language":{"name":"Hindi","iso639-2":["hin"]}},{"language":{"name":"Hungarian","iso639-2":["hun"]}},{"language":{"name":"Icelandic","iso639-2":["isl","ice"]}},{"language":{"name":"Indonesian","iso639-2":["ind"]}},{"language":{"name":"Italian","iso639-2":["ita"]}},{"language":{"name":"Japanese","iso639-2":["jpn"]}},{"language":{"name":"Kannada","iso639-2":["kan"]}},{"language":{"name":"Korean","iso639-2":["kor"]}},{"language":{"name":"Latvian","iso639-2":["lav"]}},{"language":{"name":"Lithuanian","iso639-2":["lit"]}},{"language":{"name":"Macedonian","iso639-2":["mac","mkd"]}},{"language":{"name":"Malayalam","iso639-2":["mal"]}},{"language":{"name":"Marathi","iso639-2":["mar"]}},{"language":{"name":"Mongolian","iso639-2":["mon"]}},{"language":{"name":"Norwegian","iso639-2":["nno","nob","nor"]}},{"language":{"name":"Persian","iso639-2":["fas","per"]}},{"language":{"name":"Polish","iso639-2":["pol"]}},{"language":{"name":"Portuguese","iso639-2":["por"]}},{"language":{"name":"Portuguese (Brazil)","iso639-2":["por"]}},{"language":{"name":"Romanian","iso639-2":["rum","ron"]}},{"language":{"name":"Romansh","iso639-2":["roh"]}},{"language":{"name":"Russian","iso639-2":["rus"]}},{"language":{"name":"Serbian","iso639-2":["srp"]}},{"language":{"name":"Slovak","iso639-2":["slk","slo"]}},{"language":{"name":"Slovenian","iso639-2":["slv"]}},{"language":{"name":"Spanish","iso639-2":["spa"]}},{"language":{"name":"Spanish (Latino)","iso639-2":["spa"]}},{"language":{"name":"Swedish","iso639-2":["swe"]}},{"language":{"name":"Tagalog","iso639-2":["tgl"]}},{"language":{"name":"Tamil","iso639-2":["tam"]}},{"language":{"name":"Telugu","iso639-2":["tel"]}},{"language":{"name":"Thai","iso639-2":["tha"]}},{"language":{"name":"Turkish","iso639-2":["tur"]}},{"language":{"name":"Ukrainian","iso639-2":["ukr"]}},{"language":{"name":"Unknown","iso639-2":["und"]}},{"language":{"name":"Urdu","iso639-2":["urd"]}},{"language":{"name":"Vietnamese","iso639-2":["vie"]}}]}'
|
|
# Presence of '*_eventtype' variable sets script mode
|
|
export striptracks_type=$(printenv | sed -n 's/_eventtype *=.*$//p')
|
|
declare -g -x -a striptracks_skip_profile
|
|
}
|
|
|
|
### Functions
|
|
function main {
|
|
# Main script execution
|
|
### MAIN
|
|
|
|
initialize_variables
|
|
process_command_line "$@"
|
|
initialize_mode_variables
|
|
check_log
|
|
check_required_binaries
|
|
log_first_debug_messages
|
|
check_wsl
|
|
check_eventtype
|
|
log_script_start
|
|
check_config
|
|
check_video
|
|
detect_languages
|
|
# Special handling for ':org' code from command line.
|
|
process_org_code "audio" "striptracks_audiokeep"
|
|
process_org_code "subtitles" "striptracks_subskeep"
|
|
process_org_code "audio" "striptracks_default_audio"
|
|
process_org_code "subtitles" "striptracks_default_subtitles"
|
|
resolve_code_conflict
|
|
# Read in the output of mkvmerge info extraction
|
|
get_mediainfo "$striptracks_video"
|
|
process_mkvmerge_json
|
|
determine_track_order
|
|
set_default_tracks
|
|
set_title_and_exit_if_nothing_removed
|
|
remux_video
|
|
set_perms_and_owner
|
|
replace_original_video
|
|
rescan_and_cleanup
|
|
}
|
|
function usage {
|
|
# Short usage
|
|
|
|
usage="Try '$striptracks_script --help' for more information."
|
|
echo "$usage" >&2
|
|
}
|
|
function long_usage {
|
|
# Full usage
|
|
|
|
usage="$striptracks_script Version: $striptracks_ver
|
|
Video remuxing script that only keeps tracks with the specified languages.
|
|
Designed for use with Radarr and Sonarr, but may be used standalone in batch
|
|
mode.
|
|
|
|
Source: https://github.com/TheCaptain989/radarr-striptracks
|
|
|
|
Usage:
|
|
$0 [{-a|--audio} <audio_languages> [{-s|--subs} <subtitle_languages>] [{-f|--file} <video_file>]] [--reorder] [--disable-recycle] [--skip-profile <profile_name>]... [--set-default-audio <language_code[=name][-f]>] [--set-default-subs <language_code[=name][-f]>] [{-l|--log} <log_file>] [{-c|--config} <config_file>] [{-p|--priority} {idle|low|medium|high}] [{-d|--debug} [<level>]]
|
|
|
|
Options can also be set via the STRIPTRACKS_ARGS environment variable.
|
|
Command-line arguments override the environment variable.
|
|
|
|
Options and Arguments:
|
|
-a, --audio <audio_languages[+modifier]>
|
|
Audio languages to keep
|
|
ISO639-2 code(s) prefixed with a colon \`:\`
|
|
multiple codes may be concatenated.
|
|
Each code may optionally be followed by a
|
|
plus \`+\` and one or more modifiers.
|
|
-s, --subs <subtitle_languages[+modifier]>
|
|
Subtitles languages to keep
|
|
ISO639-2 code(s) prefixed with a colon \`:\`
|
|
multiple codes may be concatenated.
|
|
Each code may optionally be followed by a
|
|
plus \`+\` and one or more modifiers.
|
|
-f, --file <video_file> If included, the script enters batch mode
|
|
and converts the specified video file.
|
|
WARNING: Do not use this argument when
|
|
calling from Radarr or Sonarr!
|
|
--reorder Reorder audio and subtitles tracks to match
|
|
the language code order specified in the
|
|
<audio_languages> and <subtitle_languages>
|
|
arguments.
|
|
--disable-recycle Disable recycle bin use, even if configured
|
|
in Radarr/Sonarr
|
|
--skip-profile <profile_name>
|
|
Skip processing if the video was downloaded
|
|
using the specified quality profile name.
|
|
May be specified multiple times to skip
|
|
multiple profiles.
|
|
--set-default-audio <language_code[=name][-f]>
|
|
Set the default audio track to the first
|
|
track of the specified language.
|
|
The code may optionally be followed by an
|
|
equals \`=\` and a track name.
|
|
The code may optionally be followed by a
|
|
minus \`-f\` to indicate skipping Forced
|
|
tracks.
|
|
--set-default-subs <language_code[=name][-f]>
|
|
Set the default subtitles track to the first
|
|
track of the specified language.
|
|
The code may optionally be followed by an
|
|
equals \`=\` and a track name.
|
|
The code may optionally be followed by a
|
|
minus \`-f\` to indicate skipping Forced
|
|
tracks.
|
|
-l, --log <log_file> Log filename
|
|
[default: /config/log/striptracks.txt]
|
|
-c, --config <config_file> Radarr/Sonarr XML configuration file
|
|
[default: ./config/config.xml]
|
|
-p, --priority idle|low|medium|high
|
|
CPU and I/O process priority for mkvmerge
|
|
[default: medium]
|
|
-d, --debug [<level>] Enable debug logging
|
|
level is optional, between 1-3
|
|
1 is lowest, 3 is highest
|
|
[default: 1]
|
|
--help Display this help and exit
|
|
--version Display script version and exit
|
|
|
|
When audio_languages and subtitle_languages are omitted the script detects the
|
|
audio or subtitle languages configured in the Radarr or Sonarr profile. When
|
|
used on the command line, they override the detected codes. They are also
|
|
accepted as positional parameters for backwards compatibility.
|
|
|
|
Language modifiers may be \`f\` or \`d\` which select Forced or Default tracks
|
|
respectively, or a number which specifies the maximum tracks to keep.
|
|
|
|
Track name modifiers are a string. The string is used to match against the
|
|
track name, with the first track that matches the specified language and name
|
|
being set as default.
|
|
|
|
Batch Mode:
|
|
In batch mode the script acts as if it were not called from within Radarr
|
|
or Sonarr. It converts the file specified on the command line and ignores
|
|
any environment variables that are normally expected. The MKV embedded title
|
|
attribute is set to the basename of the file minus the extension.
|
|
|
|
Examples:
|
|
$striptracks_script -d 2 # Enable debugging level 2, audio and
|
|
# subtitles languages detected from
|
|
# Radarr/Sonarr
|
|
$striptracks_script -a :eng:und -s :eng # Keep English and Unknown audio and
|
|
# English subtitles
|
|
$striptracks_script -a :eng:org -s :any+f:eng # Keep English and Original audio,
|
|
# and forced or English subtitles
|
|
$striptracks_script -a :eng -s \"\" # Keep English audio and no subtitles
|
|
$striptracks_script -d :eng:kor:jpn :eng:spa # Enable debugging level 1, keeping
|
|
# English, Korean, and Japanese
|
|
# audio, and English and Spanish
|
|
# subtitles
|
|
$striptracks_script -f \"/movies/path/Finding Nemo (2003).mkv\" -a :eng:und -s :eng
|
|
# Batch Mode
|
|
# Keep English and Unknown audio and
|
|
# English subtitles, converting video
|
|
# specified
|
|
$striptracks_script -a :any -s \"\" # Keep all audio and no subtitles
|
|
$striptracks_script -a :org:any+d1 -s :eng+1:any+f2
|
|
# Keep all Original and one default
|
|
# audio in any language, and one
|
|
# English and two forced subtitles
|
|
# in any language
|
|
"
|
|
echo "$usage"
|
|
}
|
|
function process_command_line {
|
|
# Process arguments, either from the command line or from the environment variable
|
|
|
|
# Log command-line arguments
|
|
if [ $# -ne 0 ]; then
|
|
export striptracks_prelogmessagedebug="Debug|Command line arguments are '$*'"
|
|
fi
|
|
|
|
# Check for environment variable arguments
|
|
if [ -n "$STRIPTRACKS_ARGS" ]; then
|
|
if [ $# -ne 0 ]; then
|
|
export striptracks_prelogmessage="Warning|STRIPTRACKS_ARGS environment variable set but will be ignored because command line arguments were also specified."
|
|
else
|
|
# Move the environment variable arguments to the command line for processing
|
|
export striptracks_prelogmessage="Info|Using settings from environment variable."
|
|
eval set -- "$STRIPTRACKS_ARGS"
|
|
fi
|
|
fi
|
|
|
|
# Process arguments
|
|
# Taken from Drew Stokes post 3/24/2015:
|
|
# https://medium.com/@Drew_Stokes/bash-argument-parsing-54f3b81a6a8f
|
|
unset pos_params
|
|
while (( "$#" )); do
|
|
case "$1" in
|
|
-d|--debug )
|
|
# Enable debugging, with optional level
|
|
if [ -n "$2" ] && [ ${2:0:1} != "-" ] && [[ "$2" =~ ^[0-9]+$ ]]; then
|
|
export striptracks_debug=$2
|
|
shift 2
|
|
else
|
|
export striptracks_debug=1
|
|
shift
|
|
fi
|
|
;;
|
|
-l|--log )
|
|
# Log file
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 20
|
|
fi
|
|
export striptracks_log="$2"
|
|
shift 2
|
|
;;
|
|
--help )
|
|
# Display full usage
|
|
long_usage
|
|
exit 0
|
|
;;
|
|
--version )
|
|
# Display version
|
|
echo "${striptracks_script} ${striptracks_ver/{{VERSION\}\}/unknown}"
|
|
exit 0
|
|
;;
|
|
-f|--file )
|
|
# Batch Mode
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 1
|
|
fi
|
|
# Overrides detected *_eventtype
|
|
export striptracks_type="batch"
|
|
export striptracks_video="$2"
|
|
shift 2
|
|
;;
|
|
-a|--audio )
|
|
# Audio languages to keep
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 2
|
|
elif [[ "$2" != :* ]]; then
|
|
echo "Error|Invalid option: $1 argument requires a colon." >&2
|
|
usage
|
|
exit 2
|
|
fi
|
|
export striptracks_audiokeep="$2"
|
|
shift 2
|
|
;;
|
|
-s|--subs|--subtitles )
|
|
# Subtitles languages to keep
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 3
|
|
elif [[ "$2" != :* ]]; then
|
|
echo "Error|Invalid option: $1 argument requires a colon." >&2
|
|
usage
|
|
exit 3
|
|
fi
|
|
export striptracks_subskeep="$2"
|
|
shift 2
|
|
;;
|
|
-c|--config )
|
|
# *arr XML configuration file
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 20
|
|
fi
|
|
# Overrides default /config/config.xml
|
|
export striptracks_arr_config="$2"
|
|
shift 2
|
|
;;
|
|
-p|--priority )
|
|
# Set process priority (see issue #102)
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 20
|
|
elif [[ ! "$2" =~ ^(idle|low|medium|high)$ ]]; then
|
|
echo "Error|Invalid option: $1 argument must be idle, low, medium, or high." >&2
|
|
usage
|
|
exit 20
|
|
fi
|
|
case "$2" in
|
|
idle) export striptracks_nice="ionice -c 3 nice -n 19" ;; # Idle priority
|
|
low) export striptracks_nice="ionice -c 2 -n 7 nice -n 19" ;; # Low priority
|
|
medium) export striptracks_nice="ionice -c 2 -n 4 nice -n 10" ;; # Normal priority
|
|
high) export striptracks_nice="ionice -c 2 -n 0 nice -n 0" ;; # High priority
|
|
esac
|
|
shift 2
|
|
;;
|
|
--reorder )
|
|
# Reorder audio and subtitles tracks (see issue #92)
|
|
export striptracks_reorder="true"
|
|
shift
|
|
;;
|
|
--disable-recycle )
|
|
# Disable recycle bin use (see issue #99)
|
|
export striptracks_recycle="false"
|
|
shift
|
|
;;
|
|
--skip-profile )
|
|
# Skip processing if the video was downloaded using the specified quality profile name. (see issue #108)
|
|
# May be specified multiple times to skip multiple profiles.
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 20
|
|
fi
|
|
striptracks_skip_profile+=("$2")
|
|
shift 2
|
|
;;
|
|
--set-default-audio )
|
|
# Set the default audio track to specified language, and optional type (see issue #111)
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 20
|
|
elif [[ "$2" != :* ]]; then
|
|
echo "Error|Invalid option: $1 argument requires a colon." >&2
|
|
usage
|
|
exit 20
|
|
fi
|
|
export striptracks_default_audio="$2"
|
|
shift 2
|
|
;;
|
|
--set-default-subs|--set-default-subtitles )
|
|
# Set the default subtitles track to specified language, and optional type (see issue #111)
|
|
if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then
|
|
echo "Error|Invalid option: $1 requires an argument." >&2
|
|
usage
|
|
exit 20
|
|
elif [[ "$2" != :* ]]; then
|
|
echo "Error|Invalid option: $1 argument requires a colon." >&2
|
|
usage
|
|
exit 20
|
|
fi
|
|
export striptracks_default_subtitles="$2"
|
|
shift 2
|
|
;;
|
|
-*)
|
|
# Unknown option
|
|
echo "Error|Unknown option: $1" >&2
|
|
usage
|
|
exit 20
|
|
;;
|
|
*)
|
|
# preserve positional arguments
|
|
local pos_params="$pos_params $1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
# Set positional arguments in their proper place
|
|
eval set -- "$pos_params"
|
|
|
|
# Check for and assign positional arguments. Named override positional.
|
|
if [ -n "$1" ]; then
|
|
if [ -n "$striptracks_audiokeep" ]; then
|
|
echo "Warning|Both positional and named arguments set for audio. Using $striptracks_audiokeep" >&2
|
|
else
|
|
export striptracks_audiokeep="$1"
|
|
fi
|
|
fi
|
|
if [ -n "$2" ]; then
|
|
if [ -n "$striptracks_subskeep" ]; then
|
|
echo "Warning|Both positional and named arguments set for subtitles. Using $striptracks_subskeep" >&2
|
|
else
|
|
export striptracks_subskeep="$2"
|
|
fi
|
|
fi
|
|
}
|
|
function initialize_mode_variables {
|
|
# Sets mode specific variables
|
|
|
|
if [[ "${striptracks_type,,}" = "batch" ]]; then
|
|
# Batch mode
|
|
export batch_eventtype="Convert"
|
|
export striptracks_title="$(basename "$striptracks_video" ".${striptracks_video##*.}")"
|
|
elif [[ "${striptracks_type,,}" = "radarr" ]]; then
|
|
# Radarr mode
|
|
# shellcheck disable=SC2154
|
|
export striptracks_video="$radarr_moviefile_path"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_video_folder="$radarr_movie_path"
|
|
export striptracks_video_api="movie"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_video_id="${radarr_movie_id}"
|
|
export striptracks_videofile_api="moviefile"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_videofile_id="${radarr_moviefile_id}"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_rescan_id="${radarr_movie_id}"
|
|
export striptracks_json_quality_root="movieFile"
|
|
export striptracks_video_type="movie"
|
|
export striptracks_video_rootNode=""
|
|
# shellcheck disable=SC2154
|
|
export striptracks_title="${radarr_movie_title:-UNKNOWN} (${radarr_movie_year:-UNKNOWN})"
|
|
export striptracks_language_jq=".language"
|
|
# export striptracks_language_node="languages"
|
|
elif [[ "${striptracks_type,,}" = "sonarr" ]]; then
|
|
# Sonarr mode
|
|
# shellcheck disable=SC2154
|
|
export striptracks_video="$sonarr_episodefile_path"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_video_folder="$sonarr_series_path"
|
|
export striptracks_video_api="episode"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_video_id="${sonarr_episodefile_episodeids}"
|
|
export striptracks_videofile_api="episodefile"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_videofile_id="${sonarr_episodefile_id}"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_rescan_id="${sonarr_series_id}"
|
|
export striptracks_json_quality_root="episodeFile"
|
|
export striptracks_video_type="series"
|
|
export striptracks_video_rootNode=".series"
|
|
# shellcheck disable=SC2154
|
|
export striptracks_title="${sonarr_series_title:-UNKNOWN} $(numfmt --format "%02f" ${sonarr_episodefile_seasonnumber:-0})x$(numfmt --format "%02f" ${sonarr_episodefile_episodenumbers:-0}) - ${sonarr_episodefile_episodetitles:-UNKNOWN}"
|
|
# export striptracks_language_node="language"
|
|
# # Sonarr requires the episodeIds array
|
|
# export striptracks_sonarr_json=" \"episodeIds\":[.episodes[].id],"
|
|
else
|
|
# Called in an unexpected way
|
|
echo -e "Error|Unknown or missing '*_eventtype' environment variable: ${striptracks_type}\nNot calling from Radarr/Sonarr? Try using Batch Mode option: -f <file>" >&2
|
|
usage
|
|
exit 7
|
|
fi
|
|
export striptracks_rescan_api="Rescan${striptracks_video_type^}"
|
|
export striptracks_eventtype="${striptracks_type,,}_eventtype"
|
|
export striptracks_newvideo="${striptracks_video%.*}.mkv"
|
|
}
|
|
function log {(
|
|
# Write piped message to log file
|
|
# Can still go over striptracks_maxlog if read line is too long
|
|
# Must include whole function in subshell for read to work!
|
|
|
|
while read -r
|
|
do
|
|
# shellcheck disable=2046
|
|
echo $(date +"%Y-%m-%d %H:%M:%S.%1N")"|[$striptracks_pid]$REPLY" >>"$striptracks_log"
|
|
local filesize=$(stat -c %s "$striptracks_log")
|
|
if [ $filesize -gt $striptracks_maxlogsize ]
|
|
then
|
|
for i in $(seq $((striptracks_maxlog-1)) -1 0); do
|
|
[ -f "${striptracks_log::-4}.$i.txt" ] && mv "${striptracks_log::-4}."{$i,$((i+1))}".txt"
|
|
done
|
|
[ -f "${striptracks_log::-4}.txt" ] && mv "${striptracks_log::-4}.txt" "${striptracks_log::-4}.0.txt"
|
|
touch "$striptracks_log"
|
|
fi
|
|
done
|
|
)}
|
|
function read_xml {
|
|
# Read XML file and parse it
|
|
# Inspired by https://stackoverflow.com/questions/893585/how-to-parse-xml-in-bash
|
|
|
|
local IFS=\>
|
|
read -r -d \< striptracks_xml_entity striptracks_xml_content
|
|
}
|
|
function get_version {
|
|
# Get Radarr/Sonarr version
|
|
|
|
call_api 0 "Getting ${striptracks_type^} version." "GET" "system/status"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.version?')"
|
|
[ "$json_test" != "null" ] && [ "$json_test" != "" ]
|
|
return
|
|
}
|
|
function get_video_info {
|
|
# Get video information
|
|
|
|
call_api 0 "Getting video information for $striptracks_video_api '$striptracks_video_id'." "GET" "$striptracks_video_api/$striptracks_video_id"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.hasFile?')"
|
|
[ "$json_test" = "true" ]
|
|
return
|
|
}
|
|
function get_videofile_info {
|
|
# Get video file information
|
|
|
|
call_api 0 "Getting video file information for $striptracks_videofile_api '$striptracks_videofile_id'." "GET" "$striptracks_videofile_api/$striptracks_videofile_id"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.path?')"
|
|
[ "$json_test" != "null" ] && [ "$json_test" != "" ]
|
|
return
|
|
}
|
|
function rescan {
|
|
# Initiate Rescan request
|
|
|
|
echo "Info|Calling ${striptracks_type^} API to rescan ${striptracks_video_type}" | log
|
|
call_api 0 "Forcing rescan of $striptracks_video_type '$striptracks_rescan_id'." "POST" "command" "{\"name\":\"$striptracks_rescan_api\",\"${striptracks_video_type}Id\":$striptracks_rescan_id}"
|
|
export striptracks_jobid="$(echo $striptracks_result | jq -crM '.id?')"
|
|
[ "$striptracks_jobid" != "null" ] && [ "$striptracks_jobid" != "" ]
|
|
return
|
|
}
|
|
function check_job {
|
|
# Check result of command job
|
|
|
|
# Exit codes:
|
|
# 0 - success
|
|
# 1 - queued
|
|
# 2 - failed
|
|
# 3 - loop timed out
|
|
# 10 - curl error
|
|
|
|
local jobid="$1" # Job ID to check
|
|
|
|
local i=0
|
|
for ((i=1; i <= 15; i++)); do
|
|
call_api 0 "Checking job $jobid completion." "GET" "command/$jobid"
|
|
local api_return=$?; [ $api_return -ne 0 ] && {
|
|
local return=10
|
|
break
|
|
}
|
|
|
|
# Job status checks
|
|
local json_test="$(echo $striptracks_result | jq -crM '.status?')"
|
|
case "$json_test" in
|
|
completed) local return=0; break ;;
|
|
failed) local return=2; break ;;
|
|
queued) local return=3; break ;;
|
|
*)
|
|
# It may have timed out, so let's wait a second
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Job not done. Waiting 1 second." | log
|
|
local return=3
|
|
sleep 1
|
|
;;
|
|
esac
|
|
done
|
|
return $return
|
|
}
|
|
function get_profiles {
|
|
# Get profiles
|
|
|
|
local profile_type="$1" # 'quality' or 'language'
|
|
|
|
call_api 1 "Getting list of $profile_type profiles." "GET" "${profile_type}profile"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.message?')"
|
|
[ "$json_test" != "NotFound" ]
|
|
return
|
|
}
|
|
function get_language_codes {
|
|
# Get language codes
|
|
|
|
local endpoint="language"
|
|
if check_compat languageprofile; then
|
|
local endpoint="languageprofile"
|
|
fi
|
|
call_api 1 "Getting list of language codes." "GET" "$endpoint"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.[] | .name')"
|
|
[ "$json_test" != "null" ] && [ "$json_test" != "" ]
|
|
return
|
|
}
|
|
function get_custom_formats {
|
|
# Get custom formats
|
|
|
|
call_api 1 "Getting list of custom formats." "GET" "customformat"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.[] | .name')"
|
|
[ "$json_test" != "null" ] && [ "$json_test" != "" ]
|
|
return
|
|
}
|
|
function delete_videofile {
|
|
# Delete video file
|
|
|
|
local videofile_id="$1" # ID of video file to inspect
|
|
|
|
call_api 0 "Deleting or recycling \"$striptracks_video\"." "DELETE" "$striptracks_videofile_api/$videofile_id"
|
|
return
|
|
}
|
|
# function get_import_info {
|
|
# # Get file details on possible files to import into Radarr/Sonarr
|
|
#
|
|
# if [[ "${striptracks_type,,}" = "radarr" ]]; then
|
|
# local temp_id="${striptracks_video_type}Id=$striptracks_rescan_id"
|
|
# fi
|
|
# call_api 1 "Getting list of files that can be imported." "GET" "manualimport" "folder=$striptracks_video_folder" "filterExistingFiles=false" "${temp_id:+$temp_id}"
|
|
# [ "${#striptracks_result}" != 0 ]
|
|
# return
|
|
# }
|
|
function set_metadata {
|
|
# Update file metadata in Radarr/Sonarr (see issue #97)
|
|
|
|
call_api 0 "Updating from quality '$(echo $striptracks_videofile_info | jq -crM .quality.quality.name)' to '$(echo $striptracks_original_metadata | jq -crM .quality.quality.name)' and release group '$(echo $striptracks_videofile_info | jq -crM '.releaseGroup | select(. != null)')' to '$(echo $striptracks_original_metadata | jq -crM '.releaseGroup | select(. != null)')'." "PUT" "$striptracks_videofile_api/bulk" "$(echo $striptracks_original_metadata | jq -crM "[{id:${striptracks_videofile_id}, quality, releaseGroup}]")"
|
|
[ "${#striptracks_result}" != 0 ]
|
|
return
|
|
}
|
|
function get_mediainfo {
|
|
# Read in the output of mkvmerge info extraction (see issue #87)
|
|
|
|
local videofile="$1" # Video file to inspect
|
|
|
|
local mkvcommand="/usr/bin/mkvmerge -J \"$(escape_string "$videofile")\""
|
|
execute_mkv_command "$mkvcommand" "inspecting video"
|
|
local return=$?
|
|
|
|
unset striptracks_json
|
|
# This must be a declare statement to avoid the 'Argument list too long' error with some large returned JSON (see issue #104)
|
|
declare -g striptracks_json
|
|
striptracks_json="$striptracks_mkvresult"
|
|
return $return
|
|
}
|
|
# function import_video {
|
|
# # Import new video into Radarr/Sonarr
|
|
#
|
|
# call_api 0 "Importing new file into ${striptracks_type^}." "POST" "command" "{\"name\":\"ManualImport\",\"files\":$striptracks_json,\"importMode\":\"auto\"}"
|
|
# local json_test="$(echo $striptracks_result | jq -crM '.id?')"
|
|
# [ "$json_test" != "null" ] && [ "$json_test" != "" ]
|
|
# return
|
|
# }
|
|
function get_rename {
|
|
# Get a list of video files from Radarr/Sonarr that need to be renamed
|
|
|
|
call_api 0 "Getting list of videos that could be renamed." "GET" "rename" "${striptracks_video_type}Id=$striptracks_rescan_id"
|
|
[ "$striptracks_result" != "null" ] && [ "$striptracks_result" != "" ]
|
|
return
|
|
}
|
|
function rename_videofile {
|
|
# Rename video file according to Radarr/Sonarr naming rules
|
|
|
|
local file_id="$1" # ID of the video file to rename
|
|
local newname="$2" # New name of the video file
|
|
|
|
echo "Info|Renaming new video file per ${striptracks_type^}'s rules to \"$(basename "$newname")\"" | log
|
|
call_api 0 "Renaming \"$striptracks_newvideo\"." "POST" "command" "{\"name\":\"RenameFiles\",\"${striptracks_video_type}Id\":$striptracks_rescan_id,\"files\":[$file_id]}"
|
|
[ "$striptracks_result" != "null" ] && [ "$striptracks_result" != "" ]
|
|
return
|
|
}
|
|
function set_language {
|
|
# Set videofile language (see issue #97)
|
|
|
|
local json_languages="$1" # JSON array of languages
|
|
local videofile_id="$2" # ID of the video file to update
|
|
|
|
call_api 0 "Updating from language(s) '$(echo $striptracks_videofile_info | jq -crM "[.languages[].name] | join(\",\")")' to '$(echo $json_languages | jq -crM "[.[].name] | join(\",\")")'." "PUT" "$striptracks_videofile_api/bulk" "[{\"id\":${videofile_id},\"languages\":${json_languages}}]"
|
|
[ "$striptracks_result" != "null" ] && [ "$striptracks_result" != "" ]
|
|
return
|
|
}
|
|
function set_legacy_sonarr_language {
|
|
# Set video language in Sonarr v3
|
|
|
|
local json_languages="$1" # JSON array of languages
|
|
local videofile_id="$2" # ID of the video file to update
|
|
|
|
call_api 0 "Updating from language '$(echo $striptracks_videofile_info | jq -crM ".language.name")' to '$(echo $json_languages | jq -crM ".[0].name")'." "PUT" "$striptracks_videofile_api/editor" "{\"${striptracks_videofile_api}Ids\":[${videofile_id}],\"language\":$(echo $json_languages | jq -crM ".[0]")}"
|
|
[ "$striptracks_result" != "null" ] && [ "$striptracks_result" != "" ]
|
|
return
|
|
}
|
|
function check_compat {
|
|
# Compatibility checker
|
|
|
|
# Exit codes:
|
|
# 0 - the feature is compatible
|
|
# 1 - the feature is incompatible
|
|
|
|
local compat_type="$1" # 'apiv3', 'languageprofile', 'customformat', 'originallanguage', 'qualitylanguage'
|
|
|
|
local return=1
|
|
case "$compat_type" in
|
|
apiv3)
|
|
[ ${striptracks_arr_version/.*/} -ge 3 ] && local return=0
|
|
;;
|
|
languageprofile)
|
|
# Langauge Profiles
|
|
[ "${striptracks_type,,}" = "sonarr" ] && [ ${striptracks_arr_version/.*/} -eq 3 ] && local return=0
|
|
;;
|
|
customformat)
|
|
# Language option in Custom Formats
|
|
[ "${striptracks_type,,}" = "radarr" ] && [ ${striptracks_arr_version/.*/} -ge 3 ] && local return=0
|
|
[ "${striptracks_type,,}" = "sonarr" ] && [ ${striptracks_arr_version/.*/} -ge 4 ] && local return=0
|
|
;;
|
|
originallanguage)
|
|
# Original language selection
|
|
[ "${striptracks_type,,}" = "radarr" ] && [ ${striptracks_arr_version/.*/} -ge 3 ] && local return=0
|
|
[ "${striptracks_type,,}" = "sonarr" ] && [ ${striptracks_arr_version/.*/} -ge 4 ] && local return=0
|
|
;;
|
|
qualitylanguage)
|
|
# Language option in Quality Profile
|
|
[ "${striptracks_type,,}" = "radarr" ] && [ ${striptracks_arr_version/.*/} -ge 3 ] && local return=0
|
|
;;
|
|
*)
|
|
# Unknown feature
|
|
local message="Error|Unknown feature $compat_type in ${striptracks_type^}"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
;;
|
|
esac
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Feature $compat_type is $([ $return -eq 1 ] && echo "not ")compatible with ${striptracks_type^} v${striptracks_arr_version}" | log
|
|
return $return
|
|
}
|
|
function get_media_config {
|
|
# Get media management configuration
|
|
|
|
call_api 0 "Getting ${striptracks_type^} configuration." "GET" "config/mediamanagement"
|
|
local json_test="$(echo $striptracks_result | jq -crM '.id?')"
|
|
[ "$json_test" != "null" ] && [ "$json_test" != "" ]
|
|
return
|
|
}
|
|
function set_video_info {
|
|
# Update file metadata in Radarr/Sonarr
|
|
|
|
call_api 1 "Updating monitored to '$striptracks_videomonitored'." "PUT" "$striptracks_video_api/$striptracks_video_id" "$(echo $striptracks_videoinfo | jq -crM .monitored="$striptracks_videomonitored")"
|
|
[ "${#striptracks_result}" != 0 ]
|
|
return
|
|
}
|
|
function process_org_code {
|
|
# Handle :org language code
|
|
|
|
local track_type="$1" # 'audio' or 'subtitles'
|
|
local keep_var="$2" # 'striptracks_audiokeep', 'striptracks_subskeep', 'striptracks_default_audio', or 'striptracks_default_subtitles'
|
|
|
|
if [[ "${!keep_var}" =~ :org ]]; then
|
|
# Check compatibility
|
|
if [ "${striptracks_type,,}" = "batch" ]; then
|
|
local message="Warn|${track_type^} argument contains ':org' code, but this is undefined for Batch mode! Unexpected behavior may result."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
elif ! check_compat originallanguage; then
|
|
local message="Warn|${track_type^} argument contains ':org' code, but this is undefined and not compatible with this mode/version! Unexpected behavior may result."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
fi
|
|
|
|
# Log debug message if applicable
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|${track_type^} argument ':org' specified. Changing '${!keep_var}' to '${!keep_var//:org/${striptracks_originalLangCode}}'" | log
|
|
|
|
# Replace :org with the original language code
|
|
declare -g "$keep_var=${!keep_var//:org/${striptracks_originalLangCode}}"
|
|
fi
|
|
}
|
|
function end_script {
|
|
# Exit program
|
|
|
|
# Cool bash feature
|
|
local message="Info|Completed in $((SECONDS/60))m $((SECONDS%60))s"
|
|
echo "$message" | log
|
|
[ "$1" != "" ] && export striptracks_exitstatus=$1
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Exit code ${striptracks_exitstatus:-0}" | log
|
|
exit ${striptracks_exitstatus:-0}
|
|
}
|
|
function change_exit_status {
|
|
# Set exit status code, but only if it is not already set
|
|
|
|
local exit_status="$1" # Exit status code to set
|
|
if [ -z "$striptracks_exitstatus" ]; then
|
|
export striptracks_exitstatus="$exit_status"
|
|
fi
|
|
}
|
|
function check_log {
|
|
# Log file checks
|
|
|
|
# Check that log path exists
|
|
if [ ! -d "$(dirname "$striptracks_log")" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Log file path does not exist: '$(dirname "$striptracks_log")'. Using log file in current directory."
|
|
export striptracks_log=./striptracks.txt
|
|
fi
|
|
|
|
# Check that the log file exists
|
|
if [ ! -f "$striptracks_log" ]; then
|
|
echo "Info|Creating a new log file: $striptracks_log"
|
|
touch "$striptracks_log"
|
|
fi
|
|
|
|
# Check that the log file is writable
|
|
if [ ! -w "$striptracks_log" ]; then
|
|
echo "Error|Log file '$striptracks_log' is not writable or does not exist." >&2
|
|
export striptracks_log=/dev/null
|
|
change_exit_status 12
|
|
fi
|
|
}
|
|
function check_required_binaries {
|
|
# Check for required binaries
|
|
|
|
for striptracks_file in "/usr/bin/mkvmerge" "/usr/bin/mkvpropedit" "/usr/bin/jq"; do
|
|
if [ ! -f "$striptracks_file" ]; then
|
|
local message="Error|$striptracks_file is required by this script"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 4
|
|
fi
|
|
done
|
|
}
|
|
function log_first_debug_messages {
|
|
# First log messages
|
|
|
|
# Log Debug state
|
|
if [ $striptracks_debug -ge 1 ]; then
|
|
local message="Debug|Running ${striptracks_script} version ${striptracks_ver/{{VERSION\}\}/unknown} with debug logging level ${striptracks_debug}. Video: $striptracks_title"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
fi
|
|
|
|
# Log command line parameters
|
|
if [ -n "$striptracks_prelogmessagedebug" ]; then
|
|
# striptracks_prelogmessagedebug is set above, before argument processing
|
|
[ $striptracks_debug -ge 1 ] && echo "$striptracks_prelogmessagedebug" | log
|
|
fi
|
|
|
|
# Log STRIPTRACKS_ARGS usage
|
|
if [ -n "$striptracks_prelogmessage" ]; then
|
|
# striptracks_prelogmessage is set above, before argument processing
|
|
echo "$striptracks_prelogmessage" | log
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|STRIPTRACKS_ARGS: ${STRIPTRACKS_ARGS}" | log
|
|
fi
|
|
|
|
# Log environment
|
|
[ $striptracks_debug -ge 2 ] && printenv | sort | sed 's/^/Debug|/' | log
|
|
}
|
|
function check_eventtype {
|
|
# Check for invalid _eventtypes and handle test event
|
|
|
|
if [[ "${!striptracks_eventtype}" =~ Grab|Rename|MovieAdded|MovieDelete|MovieFileDelete|SeriesAdd|SeriesDelete|EpisodeFileDelete|HealthIssue|ApplicationUpdate ]]; then
|
|
local message="Error|${striptracks_type^} event ${!striptracks_eventtype} is not supported. Exiting."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 20
|
|
fi
|
|
|
|
# Handle Test event
|
|
if [[ "${!striptracks_eventtype}" = "Test" ]]; then
|
|
echo "Info|${striptracks_type^} event: ${!striptracks_eventtype}" | log
|
|
local message="Info|Script was test executed successfully."
|
|
echo "$message" | log
|
|
echo "$message"
|
|
end_script 0
|
|
fi
|
|
}
|
|
function check_wsl {
|
|
# Check for WSL environment
|
|
|
|
if [ -n "$WSL_DISTRO_NAME" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Running in virtual WSL $WSL_DISTRO_NAME distribution." | log
|
|
# Adjust config file location to WSL default
|
|
if [ ! -f "$striptracks_arr_config" ]; then
|
|
export striptracks_arr_config="/mnt/c/ProgramData/${striptracks_type^}/config.xml"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Will try to use the default WSL configuration file '$striptracks_arr_config'" | log
|
|
fi
|
|
fi
|
|
}
|
|
function log_script_start {
|
|
# First normal log entry (when there are no errors) (see issue #61)
|
|
|
|
# shellcheck disable=SC2046
|
|
local filesize=$(stat -c %s "${striptracks_video}" | numfmt --to iec --format "%.3f")
|
|
local message="Info|${striptracks_type^} event: ${!striptracks_eventtype}, Video: $striptracks_video, Size: $filesize"
|
|
echo "$message" | log
|
|
}
|
|
function check_config {
|
|
# Check for config file
|
|
|
|
if [ "$striptracks_type" = "batch" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Not using config file in batch mode." | log
|
|
elif [ -f "$striptracks_arr_config" ]; then
|
|
# Read *arr config.xml
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Reading from ${striptracks_type^} config file '$striptracks_arr_config'" | log
|
|
while read_xml; do
|
|
[[ $striptracks_xml_entity = "Port" ]] && local port=$striptracks_xml_content
|
|
[[ $striptracks_xml_entity = "UrlBase" ]] && local urlbase=$striptracks_xml_content
|
|
[[ $striptracks_xml_entity = "BindAddress" ]] && local bindaddress=$striptracks_xml_content
|
|
[[ $striptracks_xml_entity = "ApiKey" ]] && export striptracks_apikey=$striptracks_xml_content
|
|
done < "$striptracks_arr_config"
|
|
|
|
# Allow use of environment variables from https://github.com/Sonarr/Sonarr/pull/6746
|
|
local port_var="${striptracks_type^^}__SERVER__PORT"
|
|
[ -n "${!port_var}" ] && local port="${!port_var}"
|
|
local urlbase_var="${striptracks_type^^}__SERVER__URLBASE"
|
|
[ -n "${!urlbase_var}" ] && local urlbase="${!urlbase_var}"
|
|
local bindaddress_var="${striptracks_type^^}__SERVER__BINDADDRESS"
|
|
[ -n "${!bindaddress_var}" ] && local bindaddress="${!bindaddress_var}"
|
|
local apikey_var="${striptracks_type^^}__AUTH__APIKEY"
|
|
[ -n "${!apikey_var}" ] && export striptracks_apikey="${!apikey_var}"
|
|
|
|
# Check for WSL environment and adjust bindaddress if not otherwise specified
|
|
if [ -n "$WSL_DISTRO_NAME" -a "$bindaddress" = "*" ]; then
|
|
local bindaddress=$(ip route show | grep -i default | awk '{ print $3}')
|
|
fi
|
|
|
|
# Check for localhost
|
|
[[ $bindaddress = "*" ]] && local bindaddress=localhost
|
|
|
|
# Strip leading and trailing forward slashes from URL base (see issue #66)
|
|
local urlbase="$(echo "$urlbase" | sed -re 's/^\/+//; s/\/+$//')"
|
|
|
|
# Build URL to Radarr/Sonarr API (see issue #57)
|
|
export striptracks_api_url="http://$bindaddress:$port${urlbase:+/$urlbase}/api/v3"
|
|
|
|
# Check Radarr/Sonarr version
|
|
get_version
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
# curl errored out. API calls are really broken at this point.
|
|
local message="Error|[$return] Unable to get ${striptracks_type^} version information. It is not safe to continue."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 17
|
|
}
|
|
export striptracks_arr_version="$(echo $striptracks_result | jq -crM .version)"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Detected ${striptracks_type^} version $striptracks_arr_version" | log
|
|
|
|
# Requires API v3
|
|
if ! check_compat apiv3; then
|
|
# Radarr/Sonarr version 3 required
|
|
local message="Error|This script does not support ${striptracks_type^} version ${striptracks_arr_version}. Please upgrade."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 8
|
|
fi
|
|
else
|
|
# No config file means we can't call the API. Best effort at this point.
|
|
local message="Warn|Unable to locate ${striptracks_type^} config file: '$striptracks_arr_config'"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
fi
|
|
}
|
|
function call_api {
|
|
# Call the Radarr/Sonarr API
|
|
|
|
local debug_add=$1 # Value added to debug level when evaluating for JSON debug output
|
|
local message="$2" # Message to log
|
|
local method="$3" # HTTP method to use (GET, POST, PUT, DELETE)
|
|
local endpoint="$4" # API endpoint to call
|
|
local data # Data to send with the request. All subsequent arguments are treated as data.
|
|
|
|
# Process remaining data values
|
|
shift 4
|
|
while (( "$#" )); do
|
|
# Escape double quotes in data parameter
|
|
local param="${1//\"/\\\"}"
|
|
case "$param" in
|
|
"{"*|"["*)
|
|
data+=" --json \"$param\""
|
|
shift
|
|
;;
|
|
*=*)
|
|
data+=" --data-urlencode \"$param\""
|
|
shift
|
|
;;
|
|
*)
|
|
data+=" --data-raw \"$param\""
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
local url="$striptracks_api_url/$endpoint"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|$message Calling ${striptracks_type^} API using $method and URL '$url'${data:+ with$data}" | log
|
|
if [ "$method" = "GET" ]; then
|
|
method="-G"
|
|
else
|
|
method="-X $method"
|
|
fi
|
|
local curl_cmd="curl -s --fail-with-body -H \"X-Api-Key: $striptracks_apikey\" -H \"Content-Type: application/json\" -H \"Accept: application/json\" ${data:+$data} $method \"$url\""
|
|
[ $striptracks_debug -ge 2 ] && echo "Debug|Executing: $curl_cmd" | sed -E 's/(X-Api-Key: )[^"]+/\1[REDACTED]/' | log
|
|
unset striptracks_result
|
|
# (See issue #104)
|
|
declare -g striptracks_result
|
|
|
|
# Retry up to five times if database is locked
|
|
local i=0
|
|
for ((i=1; i <= 5; i++)); do
|
|
striptracks_result=$(eval "$curl_cmd")
|
|
local curl_return=$?
|
|
if [ $curl_return -ne 0 ]; then
|
|
local message=$(echo -e "[$curl_return] curl error when calling: \"$url\"${data:+ with$data}\nWeb server returned: $(echo $striptracks_result | jq -jcM 'if type=="array" then map(.errorMessage) | join(", ") else (if has("title") then "[HTTP \(.status?)] \(.title?) \(.errors?)" elif has("message") then .message else "Unknown JSON format." end) end')" | awk '{print "Error|"$0}')
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
break
|
|
fi
|
|
# Exit loop if database is not locked, else wait
|
|
if wait_if_locked; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
# APIs can return A LOT of data, and it is not always needed for debugging
|
|
[ $striptracks_debug -ge 2 ] && echo "Debug|API returned ${#striptracks_result} bytes." | log
|
|
[ $striptracks_debug -ge $((2 + debug_add)) -a ${#striptracks_result} -gt 0 ] && echo "API returned: $striptracks_result" | awk '{print "Debug|"$0}' | log
|
|
return $curl_return
|
|
}
|
|
function wait_if_locked {
|
|
# Wait 1 minute if database is locked
|
|
|
|
# Exit codes:
|
|
# 0 - Database is not locked
|
|
# 1 - Database is locked
|
|
|
|
if [[ "$(echo $striptracks_result | jq -jcM '.message?')" =~ database\ is\ locked ]]; then
|
|
local return=1
|
|
echo "Warn|Database is locked; system is likely overloaded. Sleeping 1 minute." | log
|
|
sleep 60
|
|
else
|
|
local return=0
|
|
fi
|
|
return $return
|
|
}
|
|
function escape_string {
|
|
# Escape special characters in string for use in mkvmerge/mkvpropedit commands
|
|
|
|
local input="$1" # Input string to escape
|
|
|
|
# Escape backslashes, double quotes, and dollar signs
|
|
# shellcheck disable=SC2001
|
|
local output="$(echo "$input" | sed -e 's/[`"\\$]/\\&/g')"
|
|
echo "$output"
|
|
}
|
|
function execute_mkv_command {
|
|
# Execute mkvmerge or mkvpropedit command
|
|
|
|
local command="$1" # Full mkvmerge or mkvpropedit command to execute
|
|
local action="$2" # Action being performed (for logging purposes)
|
|
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Executing: $command" | log
|
|
local shortcommand="$(echo $command | sed -E 's/(.+ )?(\/[^ ]+) .*$/\2/')"
|
|
shortcommand=$(basename "$shortcommand")
|
|
unset striptracks_mkvresult
|
|
# This must be a declare statement to avoid the 'Argument list too long' error with some large returned JSON (see issue #104)
|
|
declare -g striptracks_mkvresult
|
|
striptracks_mkvresult=$(eval "$command")
|
|
local return=$?
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|$shortcommand returned ${#striptracks_mkvresult} bytes" | log
|
|
[ $striptracks_debug -ge 2 ] && [ ${#striptracks_mkvresult} -ne 0 ] && echo "$shortcommand returned: $striptracks_mkvresult" | awk '{print "Debug|"$0}' | log
|
|
case $return in
|
|
1)
|
|
local message=$(echo -e "[$return] Warning when $action.\n$shortcommand returned: $(echo "$striptracks_mkvresult" | jq -crM '.warnings[]')" | awk '{print "Warn|"$0}')
|
|
echo "$message" | log
|
|
;;
|
|
2)
|
|
local message=$(echo -e "[$return] Error when $action.\n$shortcommand returned: $(echo "$striptracks_mkvresult" | jq -crM '.errors[]')" | awk '{print "Error|"$0}')
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 13
|
|
;;
|
|
esac
|
|
# Check for unsupported container
|
|
if [ "$(echo "$striptracks_mkvresult" | jq -crM '.container.supported')" = "false" ]; then
|
|
local message="Error|Video format for '$videofile' is unsupported. Unable to continue. $shortcommand returned container info: $(echo $striptracks_mkvresult | jq -crM .container)"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 9
|
|
fi
|
|
return $return
|
|
}
|
|
function check_video {
|
|
# Video file checks
|
|
|
|
# Check if video file variable is blank
|
|
if [ -z "$striptracks_video" ]; then
|
|
local message="Error|No video file found! radarr_moviefile_path or sonarr_episodefile_path environment variable missing and -f option not specified on command line."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
usage
|
|
end_script 1
|
|
fi
|
|
|
|
# Check if source video exists
|
|
if [ ! -f "$striptracks_video" ]; then
|
|
local message="Error|Input video file not found: \"$striptracks_video\""
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 5
|
|
fi
|
|
|
|
# Test for hardlinked file (see issue #85)
|
|
local refcount=$(stat -c %h "$striptracks_video")
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Input file has a hard link count of $refcount" | log
|
|
if [ "$refcount" != "1" ]; then
|
|
local message="Warn|Input video file is a hardlink and this will be broken by remuxing."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
fi
|
|
|
|
# Create temporary filename
|
|
local basename="$(basename -- "${striptracks_video}")"
|
|
local fileroot="${basename%.*}"
|
|
export striptracks_tempvideo="$(dirname -- "${striptracks_video}")/$(mktemp -u -- "${fileroot:0:5}.tmp.XXXXXX")"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Using temporary file \"$striptracks_tempvideo\"" | log
|
|
}
|
|
function detect_languages {
|
|
# Detect languages configured in Radarr/Sonarr, quality of video, etc.
|
|
|
|
# Bypass if using batch mode
|
|
if [ "$striptracks_type" = "batch" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Cannot detect languages in batch mode." | log
|
|
# Check for URL
|
|
elif [ -n "$striptracks_api_url" ]; then
|
|
# Get list of all language IDs
|
|
if get_language_codes; then
|
|
export striptracks_lang_codes="$striptracks_result"
|
|
|
|
# Get video profile
|
|
if get_video_info; then
|
|
export striptracks_videoinfo="$striptracks_result"
|
|
export striptracks_videomonitored="$(echo "$striptracks_videoinfo" | jq -crM ".monitored")"
|
|
# This is not strictly necessary as this is normally set in the environment. However, this is needed for testing scripts and it doesn't hurt to use the data returned by the API call.
|
|
export striptracks_videofile_id="$(echo $striptracks_videoinfo | jq -crM .${striptracks_json_quality_root}.id)"
|
|
|
|
# Get video file info. Needed to save the original quality, release group, and custom formats
|
|
if get_videofile_info; then
|
|
export striptracks_videofile_info="$striptracks_result"
|
|
|
|
# Get quality profile info
|
|
if get_profiles quality; then
|
|
local qualityProfiles="$striptracks_result"
|
|
|
|
# Save original metadata
|
|
export striptracks_original_metadata="$(echo $striptracks_videofile_info | jq -crM '{quality, releaseGroup}')"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Found video file quality '$(echo $striptracks_original_metadata | jq -crM .quality.quality.name)' and release group '$(echo $striptracks_original_metadata | jq -crM '.releaseGroup | select(. != null)')'" | log
|
|
|
|
# Get language name(s) from quality profile used by video
|
|
local profileId="$(echo $striptracks_videoinfo | jq -crM ${striptracks_video_rootNode}.qualityProfileId)"
|
|
local profileName="$(echo $qualityProfiles | jq -crM ".[] | select(.id == $profileId).name")"
|
|
local profileLanguages="$(echo $qualityProfiles | jq -cM "[.[] | select(.id == $profileId) | .language]")"
|
|
local languageSource="quality profile"
|
|
check_compat qualitylanguage && local qualityLanguage=" with language '$(echo $profileLanguages | jq -crM '[.[] | "\(.name) (\(.id | tostring))"] | join(",")')'"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Found quality profile '${profileName} (${profileId})'$qualityLanguage" | log
|
|
|
|
# Skip processing if profile name matches any --skip-profile entries (see issue #108)
|
|
if [ ${#striptracks_skip_profile[@]} -gt 0 ]; then
|
|
for skip_profile in "${striptracks_skip_profile[@]}"; do
|
|
if [ "$skip_profile" = "$profileName" ]; then
|
|
local message="Info|Skipping processing because quality profile '$profileName' is configured to be skipped."
|
|
echo "$message" | log
|
|
echo "$message"
|
|
end_script 0
|
|
fi
|
|
done
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Quality profile '$profileName' does not match any configured to skip: '$(printf "%s," "${striptracks_skip_profile[@]}" | sed -e 's/,$//')'" | log
|
|
fi
|
|
|
|
# Query custom formats if returned language from quality profile is null or -1 (Any)
|
|
if [ -z "$profileLanguages" -o "$profileLanguages" = "[null]" -o "$(echo $profileLanguages | jq -crM '.[].id')" = "-1" ] && check_compat customformat; then
|
|
[ $striptracks_debug -ge 1 ] && [ "$(echo $profileLanguages | jq -crM '.[].id')" = "-1" ] && echo "Debug|Language selection of 'Any' in quality profile. Deferring to Custom Format language selection if it exists." | log
|
|
# Get list of Custom Formats, and hopefully languages
|
|
get_custom_formats
|
|
local customFormats="$striptracks_result"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Processing custom format(s) '$(echo "$customFormats" | jq -crM '[.[] | select(.specifications[].implementation == "LanguageSpecification") | .name] | unique | join(",")')'" | log
|
|
|
|
# Pick our languages by combining data from quality profile and custom format configuration.
|
|
# I'm open to suggestions if there's a better way to get this list or selected languages.
|
|
# Did I mention that JQ is crazy hard?
|
|
local qcf_langcodes=$(echo "$qualityProfiles $customFormats" | jq -s -crM --argjson ProfileId $profileId '
|
|
[
|
|
# This combines the custom formats [1] with the quality profiles [0], iterating over custom formats that
|
|
# specify languages and evaluating the scoring from the selected quality profile.
|
|
(
|
|
.[1] | .[] |
|
|
{id, specs: [.specifications[] | select(.implementation == "LanguageSpecification") | {langCode: .fields[] | select(.name == "value").value, negate, except: ((.fields[] | select(.name == "exceptLanguage").value) // false)}]}
|
|
) as $CustomFormat |
|
|
.[0] | .[] |
|
|
select(.id == $ProfileId) | .formatItems[] | select(.format == $CustomFormat.id) |
|
|
{format, name, score, specs: $CustomFormat.specs}
|
|
] |
|
|
[
|
|
# Only count languages with positive scores plus languages with negative scores that are negated, and
|
|
# languages with negative scores that use Except
|
|
.[] |
|
|
(select(.score > 0) | .specs[] | select(.negate == false and .except == false)),
|
|
(select(.score < 0) | .specs[] | select(.negate == true and .except == false)),
|
|
(select(.score < 0) | .specs[] | select(.negate == false and .except == true)) |
|
|
.langCode
|
|
] |
|
|
unique | join(",")
|
|
')
|
|
[ $striptracks_debug -ge 2 ] && echo "Debug|Custom format language code(s) '$qcf_langcodes' were selected based on quality profile scores." | log
|
|
|
|
if [ -n "$qcf_langcodes" ]; then
|
|
# Convert the language codes into language code/name pairs
|
|
local profileLanguages="$(echo $striptracks_lang_codes | jq -crM "map(select(.id | inside($qcf_langcodes)) | {id, name})")"
|
|
local languageSource="custom format"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Found custom format language(s) '$(echo $profileLanguages | jq -crM '[.[] | "\(.name) (\(.id | tostring))"] | join(",")')'" | log
|
|
else
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|None of the applied custom formats have language conditions with usable scores." | log
|
|
fi
|
|
fi
|
|
|
|
# Check if the languageprofile API is supported (only in legacy Sonarr; but it was *way* better than Custom Formats <sigh>)
|
|
if [ -z "$profileLanguages" -o "$profileLanguages" = "[null]" ] && check_compat languageprofile; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|No language found in quality profile or in custom formats. This is normal in legacy versions of Sonarr." | log
|
|
if get_profiles language; then
|
|
local languageProfiles="$striptracks_result"
|
|
|
|
# Get language name(s) from language profile used by video
|
|
local profileId="$(echo $striptracks_videoinfo | jq -crM .series.languageProfileId)"
|
|
local profileName="$(echo $languageProfiles | jq -crM ".[] | select(.id == $profileId).name")"
|
|
local profileLanguages="$(echo $languageProfiles | jq -cM "[.[] | select(.id == $profileId) | .languages[] | select(.allowed).language]")"
|
|
local languageSource="language profile"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Found language profile '(${profileId}) ${profileName}' with language(s) '$(echo $profileLanguages | jq -crM '[.[].name] | join(",")')'" | log
|
|
else
|
|
# languageProfile API failed
|
|
local message="Warn|The 'languageprofile' API returned an error."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
fi
|
|
|
|
# Check if after all of the above we still couldn't get any languages
|
|
if [ -z "$profileLanguages" -o "$profileLanguages" = "[null]" ]; then
|
|
local message="Warn|No languages found in any profile or custom format. Unable to use automatic language detection."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
else
|
|
# Final determination of configured languages in profiles or custom formats
|
|
local profileLangNames="$(echo $profileLanguages | jq -crM '[.[].name]')"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Determined ${striptracks_type^} configured language(s) of '$(echo $profileLanguages | jq -crM '[.[] | "\(.name) (\(.id | tostring))"] | join(",")')' from $languageSource" | log
|
|
fi
|
|
|
|
# Get originalLanguage of video
|
|
if check_compat originallanguage; then
|
|
local originalLangName="$(echo $striptracks_videoinfo | jq -crM ${striptracks_video_rootNode}.originalLanguage.name)"
|
|
|
|
# shellcheck disable=SC2090
|
|
export striptracks_originalLangCode="$(echo $striptracks_isocodemap | jq -jcM ".languages[] | select(.language.name == \"$originalLangName\") | .language | \":\(.\"iso639-2\"[])\"")"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Found original video language of '$originalLangName ($striptracks_originalLangCode)' from $striptracks_video_type '$striptracks_rescan_id'" | log
|
|
fi
|
|
|
|
# Map language names to ISO code(s) used by mkvmerge
|
|
unset striptracks_profileLangCodes
|
|
for templang in $(echo $profileLangNames | jq -crM '.[]'); do
|
|
# Convert 'Original' language selection to specific video language
|
|
if [ "$templang" = "Original" ]; then
|
|
local templang="$originalLangName"
|
|
fi
|
|
# shellcheck disable=SC2090
|
|
export striptracks_profileLangCodes+="$(echo $striptracks_isocodemap | jq -jcM ".languages[] | select(.language.name == \"$templang\") | .language | \":\(.\"iso639-2\"[])\"")"
|
|
done
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Mapped $languageSource language(s) '$(echo $profileLangNames | jq -crM "join(\",\")")' to ISO639-2 code list '$striptracks_profileLangCodes'" | log
|
|
else
|
|
# Get qualityprofile API failed
|
|
local message="Warn|Unable to retrieve quality profiles from ${striptracks_type^} API"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
else
|
|
# No '.path' in returned JSON
|
|
local message="Warn|The '$striptracks_videofile_api' API with id $striptracks_videofile_id returned no path."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
fi
|
|
else
|
|
# 'hasFile' is False in returned JSON.
|
|
local message="Warn|Could not find a video file for $striptracks_video_api id '$striptracks_video_id'"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
else
|
|
# Get language codes API failed
|
|
local message="Warn|Unable to retrieve language codes from 'language' API (curl error or returned a null name)."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
# Check if Radarr/Sonarr are configured to unmonitor deleted videos
|
|
get_media_config
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
# No '.id' in returned JSON
|
|
local message="Warn|The Media Management Config API returned no id."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
}
|
|
if [ "$(echo "$striptracks_result" | jq -crM ".autoUnmonitorPreviouslyDownloaded${striptracks_video_api^}s")" = "true" ]; then
|
|
local message="Warn|Will compensate for ${striptracks_type^} configuration to unmonitor deleted ${striptracks_video_api}s."
|
|
echo "$message" | log
|
|
fi
|
|
else
|
|
# No URL means we can't call the API
|
|
local message="Warn|Unable to determine ${striptracks_type^} API URL."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
fi
|
|
}
|
|
function resolve_code_conflict {
|
|
# Final assignment of audio and subtitles selection
|
|
|
|
# Guard clause
|
|
if [ -z "$striptracks_audiokeep" -a -z "$striptracks_profileLangCodes" ]; then
|
|
local message="Error|No audio languages specified or detected!"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
usage
|
|
end_script 2
|
|
fi
|
|
# Allows command line argument to override detected languages
|
|
if [ -z "$striptracks_audiokeep" -a -n "$striptracks_profileLangCodes" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|No command line audio languages specified. Using code list '$striptracks_profileLangCodes'" | log
|
|
export striptracks_audiokeep="$striptracks_profileLangCodes"
|
|
else
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Using command line audio languages '$striptracks_audiokeep'" | log
|
|
fi
|
|
|
|
# Log configuration that removes all subtitles
|
|
if [ -z "$striptracks_subskeep" -a -z "$striptracks_profileLangCodes" ]; then
|
|
local message="Info|No subtitles languages specified or detected. Removing all subtitles found."
|
|
echo "$message" | log
|
|
export striptracks_subskeep="null"
|
|
fi
|
|
# Allows command line argument to override detected languages
|
|
if [ -z "$striptracks_subskeep" -a -n "$striptracks_profileLangCodes" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|No command line subtitle languages specified. Using code list '$striptracks_profileLangCodes'" | log
|
|
export striptracks_subskeep="$striptracks_profileLangCodes"
|
|
else
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Using command line subtitle languages '$striptracks_subskeep'" | log
|
|
fi
|
|
# Display what we're doing
|
|
local message="Info|Keeping audio tracks with codes '$(echo $striptracks_audiokeep | sed -e 's/^://; s/:/,/g')' and subtitle tracks with codes '$(echo $striptracks_subskeep | sed -e 's/^://; s/:/,/g')'"
|
|
echo "$message" | log
|
|
}
|
|
function process_mkvmerge_json {
|
|
# Process JSON data from MKVmerge; track selection logic
|
|
|
|
export striptracks_json_processed=$(echo "$striptracks_json" | jq -jcM --arg AudioKeep "$striptracks_audiokeep" \
|
|
--arg SubsKeep "$striptracks_subskeep" '
|
|
# Parse input string into JSON language rules function
|
|
def parse_language_codes(codes):
|
|
# Supports f, d, and number modifiers (see issues #82 and #86)
|
|
# -1 default value in language key means to keep unlimited tracks
|
|
# NOTE: Logic can result in duplicate keys, but jq just uses the last defined key
|
|
codes | split(":")[1:] | map(split("+") | {lang: .[0], mods: .[1]}) |
|
|
{languages: map(
|
|
# Select tracks with no modifiers or only numeric modifiers
|
|
(select(.mods == null) | {(.lang): -1}),
|
|
(select(.mods | test("^[0-9]+$")?) | {(.lang): .mods | tonumber})
|
|
) | add,
|
|
forced_languages: map(
|
|
# Select tracks with f modifier
|
|
select(.mods | contains("f")?) | {(.lang): ((.mods | scan("[0-9]+") | tonumber) // -1)}
|
|
) | add,
|
|
default_languages: map(
|
|
# Select tracks with d modifier
|
|
select(.mods | contains("d")?) | {(.lang): ((.mods | scan("[0-9]+") | tonumber) // -1)}
|
|
) | add
|
|
};
|
|
|
|
# Language rules for audio and subtitles, adding required audio tracks (see issue #54)
|
|
(parse_language_codes($AudioKeep) | .languages += {"mis":-1,"zxx":-1}) as $AudioRules |
|
|
parse_language_codes($SubsKeep) as $SubsRules |
|
|
|
|
# Log chapter information
|
|
if (.chapters[0].num_entries) then
|
|
.striptracks_log = "Info|Chapters: \(.chapters[].num_entries)"
|
|
else . end |
|
|
|
|
# Process tracks
|
|
reduce .tracks[] as $track (
|
|
# Create object to hold tracks and counters for each reduce iteration
|
|
# This is what will be output at the end of the reduce loop
|
|
{"tracks": [], "counters": {"audio": {"normal": {}, "forced": {}, "default": {}}, "subtitles": {"normal": {}, "forced": {}, "default": {}}}};
|
|
|
|
# Set track language to "und" if null or empty
|
|
# NOTE: The // operator cannot be used here because it checks for null or empty values, not blank strings
|
|
(if ($track.properties.language == "" or $track.properties.language == null) then "und" else $track.properties.language end) as $track_lang |
|
|
|
|
# Initialize counters for each track type and language
|
|
(.counters[$track.type].normal[$track_lang] //= 0) |
|
|
if $track.properties.forced_track then (.counters[$track.type].forced[$track_lang] //= 0) else . end |
|
|
if $track.properties.default_track then (.counters[$track.type].default[$track_lang] //= 0) else . end |
|
|
.counters[$track.type] as $track_counters |
|
|
|
|
# Add tracks one at a time to output object above
|
|
.tracks += [
|
|
$track |
|
|
.striptracks_debug_log = "Debug|Parsing track ID:\(.id) Type:\(.type) Name:\(.properties.track_name) Lang:\($track_lang) Codec:\(.codec) Default:\(.properties.default_track) Forced:\(.properties.forced_track)" |
|
|
# Use track language evaluation above
|
|
.properties.language = $track_lang |
|
|
|
|
# Determine keep logic based on type and rules
|
|
if .type == "video" then
|
|
.striptracks_keep = true
|
|
elif .type == "audio" or .type == "subtitles" then
|
|
.striptracks_log = "\(.id): \($track_lang) (\(.codec))\(if .properties.track_name then " \"" + .properties.track_name + "\"" else "" end)" |
|
|
# Same logic for both audio and subtitles
|
|
(if .type == "audio" then $AudioRules else $SubsRules end) as $currentRules |
|
|
if ($currentRules.languages["any"] == -1 or ($track_counters.normal | add) < $currentRules.languages["any"] or
|
|
$currentRules.languages[$track_lang] == -1 or $track_counters.normal[$track_lang] < $currentRules.languages[$track_lang]) then
|
|
.striptracks_keep = true
|
|
elif (.properties.forced_track and
|
|
($currentRules.forced_languages["any"] == -1 or ($track_counters.forced | add) < $currentRules.forced_languages["any"] or
|
|
$currentRules.forced_languages[$track_lang] == -1 or $track_counters.forced[$track_lang] < $currentRules.forced_languages[$track_lang])) then
|
|
.striptracks_keep = true |
|
|
.striptracks_rule = "forced"
|
|
elif (.properties.default_track and
|
|
($currentRules.default_languages["any"] == -1 or ($track_counters.default | add) < $currentRules.default_languages["any"] or
|
|
$currentRules.default_languages[$track_lang] == -1 or $track_counters.default[$track_lang] < $currentRules.default_languages[$track_lang])) then
|
|
.striptracks_keep = true |
|
|
.striptracks_rule = "default"
|
|
else . end |
|
|
if .striptracks_keep then
|
|
.striptracks_log = "Info|Keeping \(if .striptracks_rule then .striptracks_rule + " " else "" end)\(.type) track " + .striptracks_log
|
|
else
|
|
.striptracks_keep = false
|
|
end
|
|
else . end
|
|
] |
|
|
|
|
# Increment counters for each track type and language
|
|
.counters[$track.type].normal[$track_lang] +=
|
|
if .tracks[-1].striptracks_keep then
|
|
1
|
|
else 0 end |
|
|
.counters[$track.type].forced[$track_lang] +=
|
|
if ($track.properties.forced_track and .tracks[-1].striptracks_keep) then
|
|
1
|
|
else 0 end |
|
|
.counters[$track.type].default[$track_lang] +=
|
|
if ($track.properties.default_track and .tracks[-1].striptracks_keep) then
|
|
1
|
|
else 0 end
|
|
) |
|
|
|
|
# Ensure at least one audio track is kept
|
|
if ((.tracks | map(select(.type == "audio")) | length == 1) and (.tracks | map(select(.type == "audio" and .striptracks_keep)) | length == 0)) then
|
|
# If there is only one audio track and none are kept, keep the only audio track
|
|
.tracks |= map(if .type == "audio" then
|
|
.striptracks_log = "Warn|No audio tracks matched! Keeping only audio track " + .striptracks_log |
|
|
.striptracks_keep = true
|
|
else . end)
|
|
elif (.tracks | map(select(.type == "audio" and .striptracks_keep)) | length == 0) then
|
|
# If no audio tracks are kept, first try to keep the default audio track
|
|
.tracks |= map(if .type == "audio" and .properties.default_track then
|
|
.striptracks_log = "Warn|No audio tracks matched! Keeping default audio track " + .striptracks_log |
|
|
.striptracks_keep = true
|
|
else . end) |
|
|
# If still no audio tracks are kept, keep the first audio track
|
|
if (.tracks | map(select(.type == "audio" and .striptracks_keep)) | length == 0) then
|
|
(first(.tracks[] | select(.type == "audio"))) |= . +
|
|
{striptracks_log: ("Warn|No audio tracks matched! Keeping first audio track " + .striptracks_log),
|
|
striptracks_keep: true}
|
|
else . end
|
|
else . end |
|
|
|
|
# Output simplified dataset
|
|
{ striptracks_log, tracks: .tracks | map({ id, type, language: .properties.language, name: .properties.track_name, forced: .properties.forced_track, default: .properties.default_track, striptracks_debug_log, striptracks_log, striptracks_keep }) }
|
|
')
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Track processing returned ${#striptracks_json_processed} bytes." | log
|
|
[ $striptracks_debug -ge 2 ] && echo "Track processing returned: $(echo "$striptracks_json_processed" | jq)" | awk '{print "Debug|"$0}' | log
|
|
|
|
# Write messages to log
|
|
echo "$striptracks_json_processed" | jq -crM --argjson Debug $striptracks_debug '
|
|
# Join log messages into one line function
|
|
def log_removed_tracks($type):
|
|
if (.tracks | map(select(.type == $type and .striptracks_keep == false)) | length > 0) then
|
|
"Info|Removing \($type) tracks: " +
|
|
(.tracks | map(select(.type == $type and .striptracks_keep == false) | .striptracks_log) | join(", "))
|
|
else empty end;
|
|
|
|
# Log the chapters, if any
|
|
.striptracks_log // empty,
|
|
|
|
# Log debug messages
|
|
( .tracks[] | (if $Debug >= 1 then .striptracks_debug_log else empty end),
|
|
|
|
# Log messages for kept tracks
|
|
(select(.striptracks_keep) | .striptracks_log // empty)
|
|
),
|
|
|
|
# Log removed tracks
|
|
log_removed_tracks("audio"),
|
|
log_removed_tracks("subtitles"),
|
|
|
|
# Summary of kept tracks
|
|
"Info|Kept tracks: \(.tracks | map(select(.striptracks_keep)) | length) " +
|
|
"(audio: \(.tracks | map(select(.type == "audio" and .striptracks_keep)) | length), " +
|
|
"subtitles: \(.tracks | map(select(.type == "subtitles" and .striptracks_keep)) | length))"
|
|
' | log
|
|
|
|
# Check for no audio tracks
|
|
if [ "$(echo "$striptracks_json_processed" | jq -crM '.tracks|map(select(.type=="audio" and .striptracks_keep))')" = "[]" ]; then
|
|
local message="Error|Unable to determine any audio tracks to keep. Exiting."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 11
|
|
fi
|
|
}
|
|
function determine_track_order {
|
|
# Determine current and new track order for mkvmerge
|
|
|
|
# Map current track order
|
|
export striptracks_order=$(echo "$striptracks_json_processed" | jq -jcM '.tracks | map(select(.striptracks_keep) | .id | "0:" + tostring) | join(",")')
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Current mkvmerge track order: $striptracks_order" | log
|
|
|
|
# Prepare to reorder tracks if option is enabled (see issue #92)
|
|
if [ "$striptracks_reorder" = "true" ]; then
|
|
export striptracks_neworder=$(echo "$striptracks_json_processed" | jq -jcM --arg AudioKeep "$striptracks_audiokeep" \
|
|
--arg SubsKeep "$striptracks_subskeep" '
|
|
# Reorder tracks function
|
|
def order_tracks(tracks; rules; tracktype):
|
|
rules | split(":")[1:] | map(split("+") | {lang: .[0], mods: .[1]}) |
|
|
reduce .[] as $rule (
|
|
[];
|
|
. as $orderedTracks |
|
|
. += [tracks |
|
|
map(. as $track |
|
|
select(.type == tracktype and .striptracks_keep and
|
|
($rule.lang | in({"any":0,($track.language):0})) and
|
|
($rule.mods == null or
|
|
($rule.mods | test("[fd]") | not) or
|
|
($rule.mods | contains("f") and $track.forced) or
|
|
($rule.mods | contains("d") and $track.default)
|
|
)
|
|
) |
|
|
.id as $id |
|
|
# Remove track id from orderedTracks if it already exists
|
|
if ([$id] | flatten | inside($orderedTracks | flatten)) then empty else $id end
|
|
)]
|
|
) | flatten;
|
|
|
|
# Reorder audio and subtitles according to language code order
|
|
.tracks as $tracks |
|
|
order_tracks($tracks; $AudioKeep; "audio") as $audioOrder |
|
|
order_tracks($tracks; $SubsKeep; "subtitles") as $subsOrder |
|
|
|
|
# Output ordered track string compatible with the mkvmerge --track-order option
|
|
# Video tracks are always first, followed by audio tracks, then subtitles
|
|
# NOTE: If there is only one audio track and it does not match a code in AudioKeep, it will not appear in the new track order string
|
|
# NOTE: Other track types are still preserved as mkvmerge will automatically place any missing tracks after those listed per https://mkvtoolnix.download/doc/mkvmerge.html#mkvmerge.description.track_order
|
|
$tracks | map(select(.type == "video") | .id) + $audioOrder + $subsOrder | map("0:" + tostring) | join(",")
|
|
')
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|New mkvmerge track order: $striptracks_neworder" | log
|
|
local message="Info|Reordering tracks using language code order."
|
|
echo "$message" | log
|
|
fi
|
|
}
|
|
function set_default_tracks {
|
|
# Build mkvpropedit parameters to set default flags on audio and subtitle tracks.
|
|
|
|
# Process audio and subtitle --set-default track settings
|
|
for tracktype in audio subtitles; do
|
|
local cfgvar="striptracks_default_${tracktype}"
|
|
local currentcfg="${!cfgvar}"
|
|
|
|
if [ -z "$currentcfg" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|No default ${tracktype} track setting specified." | log
|
|
continue
|
|
fi
|
|
|
|
# Use jq to find the track ID using case-insensitive substring match on track name
|
|
local track_id=$(echo "$striptracks_json_processed" | jq -crM --arg type "$tracktype" --arg currentcfg "$currentcfg" '
|
|
def parse_cfg(cfg):
|
|
# Remove leading ":" then split on "=" (if present)
|
|
# Supports f as a modifier (see issue #113)
|
|
(cfg | ltrimstr(":") | split("=")) as $eq |
|
|
($eq[0]) as $left |
|
|
(if ($eq | length > 1) then $eq[1] else "" end) as $right |
|
|
|
|
# Detect trailing "-f" on left or right and strip it; only "f" is a valid modifier
|
|
(if ($left | test("-f$")) then {lang: ($left | sub("-f$"; "")), skip: true} else {lang: $left, skip: false} end) as $leftinfo |
|
|
|
|
(if $right == "" then
|
|
$leftinfo + {name: ""}
|
|
else
|
|
(if ($right | test("-f$")) then
|
|
$leftinfo + {name: ($right | sub("-f$"; "")), skip: true}
|
|
else
|
|
$leftinfo + {name: $right}
|
|
end)
|
|
end);
|
|
|
|
parse_cfg($currentcfg) as $rule |
|
|
.tracks |
|
|
map(. as $track |
|
|
(($rule.lang == "any" or $rule.lang == $track.language) as $lang_match |
|
|
($rule.name == "" or (($track.name // "") | ascii_downcase | contains(($rule.name // "") | ascii_downcase))) as $name_match |
|
|
($rule.skip and $track.forced) as $skipped |
|
|
select($track.type == $type and $lang_match and $name_match and ($skipped | not) and .striptracks_keep)
|
|
)
|
|
) |
|
|
.[0].id // ""
|
|
')
|
|
|
|
if [ -n "$track_id" ]; then
|
|
# The track IDs must be converted to 1-based for mkvpropedit (add 1)
|
|
# Set variable to set default only on selected track (unset others of same type)
|
|
export striptracks_default_flags
|
|
striptracks_default_flags+=" --edit track:$((track_id + 1)) --set flag-default=1"
|
|
# Find other kept tracks of same type to unset default flag
|
|
local unset_ids=$(echo "$striptracks_json_processed" | jq -crM --arg type "$tracktype" --argjson track_id "$track_id" '.tracks | map(select(.type == $type and .striptracks_keep and .id != $track_id) | .id) | join(",")')
|
|
striptracks_default_flags+="$(echo $unset_ids | awk 'BEGIN {RS=","}; /[0-9]+/ {print " --edit track:" ($0 += 1) " --set flag-default=0"}' | tr -d '\n')"
|
|
local message="Info|Setting ${tracktype} track ${track_id} as default$([ -n "$unset_ids" ] && echo " and removing default from track(s) '$unset_ids'")."
|
|
echo "$message" | log
|
|
# Remove leading space
|
|
striptracks_default_flags="${striptracks_default_flags# }"
|
|
else
|
|
local message="Warn|No ${tracktype} track matched default specification '${currentcfg}'. No changes made to default ${tracktype} tracks."
|
|
echo "$message" | log
|
|
fi
|
|
done
|
|
|
|
if [ -n "$striptracks_default_flags" ]; then
|
|
# Execute mkvpropedit to set default flags on tracks
|
|
local mkvcommand="/usr/bin/mkvpropedit -q $striptracks_default_flags \"$(escape_string "$striptracks_video")\""
|
|
execute_mkv_command "$mkvcommand" "setting default track flags"
|
|
fi
|
|
}
|
|
function set_title_and_exit_if_nothing_removed {
|
|
# If no tracks are removed, we can skip remuxing, set the tile, and exit early
|
|
|
|
# All tracks matched/no tracks removed (see issues #49 and #89)
|
|
if [ "$(echo "$striptracks_json" | jq -crM '.tracks|map(select(.type=="audio" or .type=="subtitles"))|length')" = "$(echo "$striptracks_json_processed" | jq -crM '.tracks|map(select((.type=="audio" or .type=="subtitles") and .striptracks_keep))|length')" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|No tracks will be removed from video \"$striptracks_video\"" | log
|
|
# Check if already MKV
|
|
if [[ $striptracks_video == *.mkv ]]; then
|
|
# Check if reorder option is unset or if the order wouldn't change (see issue #92)
|
|
if [ "$striptracks_reorder" != "true" -o "$striptracks_order" = "$striptracks_neworder" ]; then
|
|
# Remuxing not performed
|
|
local message="Info|No tracks would be removed from video$( [ "$striptracks_reorder" = "true" ] && echo " or reordered"). Setting Title only and exiting."
|
|
echo "$message" | log
|
|
local mkvcommand="/usr/bin/mkvpropedit -q --edit info --set \"title=$(escape_string "$striptracks_title")\" \"$(escape_string "$striptracks_video")\""
|
|
execute_mkv_command "$mkvcommand" "setting video title"
|
|
end_script
|
|
else
|
|
# Reorder tracks anyway
|
|
local message="Info|No tracks will be removed from video, but they can be reordered. Remuxing anyway."
|
|
echo "$message" | log
|
|
fi
|
|
else
|
|
# Not MKV
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Source video is not MKV. Remuxing anyway." | log
|
|
fi
|
|
fi
|
|
}
|
|
function remux_video {
|
|
# Execute MKVmerge to remux video
|
|
|
|
# Build argument with kept audio tracks for MKVmerge
|
|
local audioarg=$(echo "$striptracks_json_processed" | jq -crM '.tracks | map(select(.type == "audio" and .striptracks_keep) | .id) | join(",")')
|
|
local audioarg="-a $audioarg"
|
|
|
|
# Build argument with kept subtitles tracks for MKVmerge, or remove all subtitles
|
|
local subsarg=$(echo "$striptracks_json_processed" | jq -crM '.tracks | map(select(.type == "subtitles" and .striptracks_keep) | .id) | join(",")')
|
|
if [ ${#subsarg} -ne 0 ]; then
|
|
local subsarg="-s $subsarg"
|
|
else
|
|
local subsarg="-S"
|
|
fi
|
|
|
|
# Build argument for track reorder option for MKVmerge
|
|
if [ ${#striptracks_neworder} -ne 0 ]; then
|
|
export striptracks_neworder="--track-order $striptracks_neworder"
|
|
fi
|
|
|
|
# Execute MKVmerge (remux then rename, see issue #46)
|
|
local mkvcommand="$striptracks_nice /usr/bin/mkvmerge --title \"$(escape_string "$striptracks_title")\" -q -o \"$(escape_string "$striptracks_tempvideo")\" $audioarg $subsarg $striptracks_neworder \"$(escape_string "$striptracks_video")\""
|
|
execute_mkv_command "$mkvcommand" "remuxing video"
|
|
|
|
# Check for non-empty file
|
|
if [ ! -s "$striptracks_tempvideo" ]; then
|
|
local message="Error|Unable to locate or invalid remuxed file: \"$striptracks_tempvideo\". Halting."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 10
|
|
fi
|
|
}
|
|
function set_perms_and_owner {
|
|
# Set permissions and owner of the remuxed video
|
|
|
|
# Checking that we're running as root
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
# Set owner
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Changing owner of file \"$striptracks_tempvideo\"" | log
|
|
local result
|
|
result=$(chown --reference="$striptracks_video" "$striptracks_tempvideo")
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message=$(echo -e "[$return] Error when changing owner of file: \"$striptracks_tempvideo\"\nchown returned: $result" | awk '{print "Error|"$0}')
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 15
|
|
}
|
|
else
|
|
# Unable to change owner when not running as root
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Unable to change owner of file when running as user '$(id -un)'" | log
|
|
fi
|
|
# Set permissions
|
|
local result
|
|
result=$(chmod --reference="$striptracks_video" "$striptracks_tempvideo")
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message=$(echo -e "[$return] Error when changing permissions of file: \"$striptracks_tempvideo\"\nchmod returned: $result" | awk '{print "Error|"$0}')
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 15
|
|
}
|
|
}
|
|
function replace_original_video {
|
|
# Replace original video with remuxed video
|
|
|
|
# Just delete the original video if running in batch mode or if configured to do so (see issue #99)
|
|
if [ "$striptracks_type" = "batch" -o "$striptracks_recycle" = "false" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Deleting: \"$striptracks_video\"" | log
|
|
local result
|
|
result=$(rm "$striptracks_video")
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message=$(echo -e "[$return] Error when deleting video: \"$striptracks_video\"\nrm returned: $result" | awk '{print "Error|"$0}')
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 16
|
|
}
|
|
else
|
|
# Call Radarr/Sonarr to delete the original video, or recycle if configured.
|
|
delete_videofile $striptracks_videofile_id
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message="Error|[$return] ${striptracks_type^} error when deleting the original video: \"$striptracks_video\""
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
}
|
|
fi
|
|
|
|
# Another check for the temporary file, to make sure it wasn't deleted (see issue #65)
|
|
if [ ! -f "$striptracks_tempvideo" ]; then
|
|
local message="Error|${striptracks_type^} deleted the temporary remuxed file: \"$striptracks_tempvideo\". Halting."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 10
|
|
fi
|
|
|
|
# Rename the temporary video file to MKV
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Renaming \"$striptracks_tempvideo\" to \"$striptracks_newvideo\"" | log
|
|
local result
|
|
result=$(mv -f "$striptracks_tempvideo" "$striptracks_newvideo")
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message=$(echo -e "[$return] Unable to rename temp video: \"$striptracks_tempvideo\" to: \"$striptracks_newvideo\". Halting.\nmv returned: $result" | awk '{print "Error|"$0}')
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script 6
|
|
}
|
|
|
|
# Log new file size (see issue #61)
|
|
# shellcheck disable=SC2046
|
|
local filesize=$(stat -c %s "${striptracks_newvideo}" | numfmt --to iec --format "%.3f")
|
|
local message="Info|New size: $filesize"
|
|
echo "$message" | log
|
|
}
|
|
function rescan_and_cleanup {
|
|
# Call Radarr/Sonarr API to RescanMovie/RescanSeries
|
|
# Fix various database issues that occur after a rescan, such as wrong metadata, monitoring status, listed languages, needing to be renamed, etc.
|
|
|
|
# Check for URL
|
|
if [ "$striptracks_type" = "batch" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Not calling API while in batch mode." | log
|
|
elif [ -n "$striptracks_api_url" ]; then
|
|
# Check for video IDs
|
|
if [ "$striptracks_video_id" -a "$striptracks_videofile_id" ]; then
|
|
##### Leaving this here (and all supporting functions and variables) in case the single file import job problem can be resolved.
|
|
##### See GitHub Issue #50. Importing directly is a much better way than rescanning.
|
|
# Scan for files to import into Radarr/Sonarr
|
|
# if get_import_info; then
|
|
# # Build JSON data
|
|
# [ $striptracks_debug -ge 1 ] && echo "Debug|Building JSON data to import" | log
|
|
# striptracks_json=$(echo $striptracks_result | jq -jcM "
|
|
# map(
|
|
# select(.path == \"$striptracks_newvideo\") |
|
|
# {path, folderName, \"${striptracks_video_type}Id\":.${striptracks_video_type}.id,${striptracks_sonarr_json} quality, $striptracks_language_node}
|
|
# )
|
|
# ")
|
|
|
|
# # Import new video into Radarr/Sonarr
|
|
# import_video
|
|
# return=$?; [ $return -ne 0 ] && {
|
|
# message="Error|[$return] ${striptracks_type^} error when importing new video!"
|
|
# echo "$message" | log
|
|
# echo "$message" >&2
|
|
# change_exit_status 17
|
|
# }
|
|
# striptracks_jobid="$(echo $striptracks_result | jq -crM .id)"
|
|
# Check status of job
|
|
# Rescan if recycle bin use is disabled to remove the original video from the database
|
|
if [ "$striptracks_recycle" = "false" ]; then
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Recycle Bin use is disabled and original video has been deleted. Rescaning to remove the original video from the ${striptracks_type^} database." | log
|
|
rescan
|
|
sleep 1
|
|
fi
|
|
# Scan the disk for the new movie file
|
|
if rescan; then
|
|
# Give it a beat
|
|
sleep 1
|
|
# Check that the Rescan completed
|
|
check_job $striptracks_jobid
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
case $return in
|
|
1)
|
|
local message="Info|${striptracks_type^} job ID $striptracks_jobid is queued. Trusting this will complete and exiting."
|
|
;;
|
|
2) local message="Warn|${striptracks_type^} job ID $striptracks_jobid failed."
|
|
change_exit_status 17
|
|
;;
|
|
3) local message="Warn|Script timed out waiting on ${striptracks_type^} job ID $striptracks_jobid. Last status was: $(echo $striptracks_result | jq -crM .status)"
|
|
change_exit_status 18
|
|
;;
|
|
10) local message="Error|${striptracks_type^} job ID $striptracks_jobid returned a curl error."
|
|
change_exit_status 17
|
|
;;
|
|
esac
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
end_script
|
|
}
|
|
|
|
# Get new video file id
|
|
if get_video_info; then
|
|
export striptracks_videoinfo="$striptracks_result"
|
|
export striptracks_videofile_id="$(echo $striptracks_videoinfo | jq -crM .${striptracks_json_quality_root}.id)"
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Using new video file id '$striptracks_videofile_id'" | log
|
|
|
|
# Check if video monitored status changed after the delete/import (see issues #87 and #90)
|
|
if [ "$(echo "$striptracks_videoinfo" | jq -crM ".monitored")" != "$striptracks_videomonitored" ]; then
|
|
local message="Warn|Video monitor status changed after deleting the original. Setting it back to '$striptracks_videomonitored'"
|
|
echo "$message" | log
|
|
# Set video monitor state
|
|
set_video_info
|
|
fi
|
|
|
|
# Get new video file info
|
|
if get_videofile_info; then
|
|
export striptracks_videofile_info="$striptracks_result"
|
|
# Check that the metadata didn't get lost in the rescan.
|
|
if [ "$(echo $striptracks_videofile_info | jq -crM .quality.quality.name)" != "$(echo $striptracks_original_metadata | jq -crM .quality.quality.name)" -o "$(echo $striptracks_videofile_info | jq -crM '.releaseGroup | select(. != null)')" != "$(echo $striptracks_original_metadata | jq -crM '.releaseGroup | select(. != null)')" ]; then
|
|
# Put back the missing metadata
|
|
set_metadata
|
|
# Check that the returned result shows the updates
|
|
if [ "$(echo $striptracks_result | jq -crM .[].quality.quality.name)" = "$(echo $striptracks_original_metadata | jq -crM .quality.quality.name)" ]; then
|
|
# Updated successfully
|
|
echo "Info|Successfully updated quality to '$(echo $striptracks_result | jq -crM .[].quality.quality.name)' and release group to '$(echo $striptracks_result | jq -crM '.[].releaseGroup | select(. != null)')'" | log
|
|
else
|
|
local message="Warn|Unable to update ${striptracks_type^} $striptracks_video_api '$striptracks_title' to quality '$(echo $striptracks_original_metadata | jq -crM .quality.quality.name)' or release group to '$(echo $striptracks_original_metadata | jq -crM '.releaseGroup | select(. != null)')'"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
else
|
|
# The metadata was already set correctly
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Metadata quality '$(echo $striptracks_videofile_info | jq -crM .quality.quality.name)' and release group '$(echo $striptracks_videofile_info | jq -crM '.releaseGroup | select(. != null)')' remained unchanged." | log
|
|
fi
|
|
|
|
# Check the languages returned
|
|
# If we stripped out other language tracks, remove them from Radarr/Sonarr
|
|
# Only works in Radarr and Sonarr v4 (no per-episode edit function in Sonarr v3)
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Getting languages in new video file \"$striptracks_newvideo\"" | log
|
|
get_mediainfo "$striptracks_newvideo"
|
|
|
|
# Build array of full name languages
|
|
local newvideo_langcodes="$(echo $striptracks_json | jq -crM '.tracks[] | select(.type == "audio") | .properties.language')"
|
|
unset newvideo_languages
|
|
for i in $newvideo_langcodes; do
|
|
# shellcheck disable=SC2090
|
|
# Exclude Any, Original, and Unknown
|
|
local newvideo_languages+="$(echo $striptracks_isocodemap | jq -crM ".languages[] | .language | select((.\"iso639-2\"[]) == \"$i\") | select(.name != \"Any\" and .name != \"Original\" and .name != \"Unknown\").name")"
|
|
done
|
|
if [ -n "$newvideo_languages" ]; then
|
|
# Covert to standard JSON
|
|
local json_languages="$(echo $striptracks_lang_codes | jq -crM "map(select(.name | inside(\"$newvideo_languages\")) | {id, name})")"
|
|
|
|
# Check languages for Radarr and Sonarr v4
|
|
# Sooooo glad I did it this way
|
|
if [ "$(echo $striptracks_videofile_info | jq -crM .languages)" != "null" ]; then
|
|
if [ "$(echo $striptracks_videofile_info | jq -crM .languages)" != "$json_languages" ]; then
|
|
set_language "$json_languages" $striptracks_videofile_id
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message="Error|${striptracks_type^} error when updating video language(s)."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
}
|
|
else
|
|
# The languages are already correct
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Language(s) '$(echo $json_languages | jq -crM "[.[].name] | join(\",\")")' remained unchanged." | log
|
|
fi
|
|
# Check languages for Sonarr v3 and earlier
|
|
elif [ "$(echo $striptracks_videofile_info | jq -crM .language)" != "null" ]; then
|
|
if [ "$(echo $striptracks_videofile_info | jq -crM .language)" != "$(echo $json_languages | jq -crM '.[0]')" ]; then
|
|
set_legacy_sonarr_language "$json_languages" $striptracks_videofile_id
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message="Error|${striptracks_type^} error when updating video language(s)."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
}
|
|
else
|
|
# The languages are already correct
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|Language '$(echo $json_languages | jq -crM ".[0].name")' remained unchanged." | log
|
|
fi
|
|
else
|
|
# Some unknown JSON formatting
|
|
local message="Warn|The '$striptracks_videofile_api' API returned unknown JSON language node."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
fi
|
|
elif [ "$newvideo_langcodes" = "und" ]; then
|
|
# Only language detected is Unknown
|
|
echo "Warn|The only audio language in the video file was 'Unknown (und)'. Not updating ${striptracks_type^} database." | log
|
|
else
|
|
# Video language not in striptracks_isocodemap
|
|
local message="Warn|Video language code(s) '${newvideo_langcodes//$'\n'/,}' not found in the ISO Codemap. Cannot evaluate."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
fi
|
|
|
|
# Get list of videos that could be renamed (see issue #50)
|
|
get_rename
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message="Warn|[$return] ${striptracks_type^} error when getting list of videos to rename."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
}
|
|
# Check if new video is in list of files that can be renamed
|
|
if [ -n "$striptracks_result" -a "$striptracks_result" != "[]" ]; then
|
|
local renamedvideo="$(echo "$striptracks_result" | jq -crM ".[] | select(.${striptracks_json_quality_root}Id == $striptracks_videofile_id) | .newPath")"
|
|
# Rename video if needed
|
|
if [ -n "$renamedvideo" ]; then
|
|
rename_videofile "$striptracks_videofile_id" "$renamedvideo"
|
|
local return=$?; [ $return -ne 0 ] && {
|
|
local message="Error|[$return] ${striptracks_type^} error when renaming \"$(basename "$striptracks_newvideo")\" to \"$(basename "$renamedvideo")\""
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
}
|
|
else
|
|
# The file doesn't need to be renamed
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|This video file doesn't need to be renamed." | log
|
|
fi
|
|
else
|
|
# Nothing to rename
|
|
[ $striptracks_debug -ge 1 ] && echo "Debug|No video files need to be renamed." | log
|
|
fi
|
|
else
|
|
# No '.path' in returned JSON
|
|
local message="Warn|The '$striptracks_videofile_api' API with ${striptracks_video_api}File id $striptracks_videofile_id returned no path."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
else
|
|
# 'hasFile' is False in returned JSON
|
|
local message="Warn|Could not find a video file for $striptracks_video_api id '$striptracks_video_id'"
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
# else
|
|
# local message="Error|${striptracks_type^} error getting import file list in \"$striptracks_video_folder\" for $striptracks_video_type ID $striptracks_rescan_id. Cannot import remuxed video."
|
|
# echo "$message" | log
|
|
# echo "$message" >&2
|
|
# change_exit_status 17
|
|
# fi
|
|
else
|
|
# Error from rescan API
|
|
local message="Error|The '$striptracks_rescan_api' API with ${striptracks_video_type}Id $striptracks_rescan_id failed."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 17
|
|
fi
|
|
else
|
|
# No video ID means we can't call the API
|
|
local message="Warn|Missing or empty environment variable: striptracks_video_id='$striptracks_video_id' or striptracks_videofile_id='$striptracks_videofile_id'. Cannot rescan for remuxed video."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
fi
|
|
else
|
|
# No URL means we can't call the API
|
|
local message="Warn|Unable to determine ${striptracks_type^} API URL."
|
|
echo "$message" | log
|
|
echo "$message" >&2
|
|
change_exit_status 20
|
|
fi
|
|
}
|
|
|
|
# Do not execute if this script is being sourced from a test script
|
|
if [[ ! "${BASH_SOURCE[1]}" =~ test_.*\.sh$ ]]; then
|
|
main "$@"
|
|
end_script
|
|
fi
|