From abb44e8dfa69e356a742262c36c5d82a08482663 Mon Sep 17 00:00:00 2001 From: TheCaptain989 Date: Mon, 10 Aug 2026 19:29:51 -0500 Subject: [PATCH 1/2] Release 3.0.2 --- .github/workflows/BuildImage.yml | 2 +- root/usr/local/bin/striptracks.sh | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index 4fc6bd2c3..aa8281e50 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -32,7 +32,7 @@ jobs: echo "MULTI_ARCH=${{ env.MULTI_ARCH }}" >> $GITHUB_OUTPUT if [[ -z "${{ env.MOD_VERSION }}" ]]; then # **** If the mod needs to be versioned, set the versioning logic below. Otherwise leave as is. **** - MOD_VERSION="3.0.1" + MOD_VERSION="3.0.2" else MOD_VERSION=${{ env.MOD_VERSION }} echo "MOD_VERSION_OVERRIDE=true" >> $GITHUB_OUTPUT diff --git a/root/usr/local/bin/striptracks.sh b/root/usr/local/bin/striptracks.sh index 664fb8c34..78685e74d 100755 --- a/root/usr/local/bin/striptracks.sh +++ b/root/usr/local/bin/striptracks.sh @@ -791,8 +791,13 @@ function check_job { local json_test="$(echo "$striptracks_result" | jq -crM '.status?')" case "$json_test" in completed) local return=0; break ;; + queued) + # See issue #125 + [ $striptracks_debug -ge 1 ] && echo "Debug|Job still queued. Waiting 1 second." | log + local return=1 + sleep 1 + ;; 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 From feed1f1c2c522cf2b818ad60f55469bae51497a2 Mon Sep 17 00:00:00 2001 From: TheCaptain989 Date: Sat, 3 Oct 2026 10:26:02 -0500 Subject: [PATCH 2/2] Release 3.1.0 --- .github/workflows/BuildImage.yml | 2 +- README.md | 2 + SECURITY.md | 4 +- .../run | 21 +- root/usr/local/bin/striptracks.sh | 298 +++++++++++++----- 5 files changed, 247 insertions(+), 80 deletions(-) diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index aa8281e50..f1171fead 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -32,7 +32,7 @@ jobs: echo "MULTI_ARCH=${{ env.MULTI_ARCH }}" >> $GITHUB_OUTPUT if [[ -z "${{ env.MOD_VERSION }}" ]]; then # **** If the mod needs to be versioned, set the versioning logic below. Otherwise leave as is. **** - MOD_VERSION="3.0.2" + MOD_VERSION="3.1.0" else MOD_VERSION=${{ env.MOD_VERSION }} echo "MOD_VERSION_OVERRIDE=true" >> $GITHUB_OUTPUT diff --git a/README.md b/README.md index 3ecb79eee..716da6183 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ striptracks.sh [{-a|--audio} [{+|-}modifiers][=name] [{-s|--sub [--set-default-subs [{+|-}modifiers][=name]] [{-l|--log} ] [{-c|--config} ] + [--database ] [{-p|--priority} {idle|low|medium|high}] [{-d|--debug} []] [--no-ansi] @@ -257,6 +258,7 @@ Option|Argument|Description `--set-default-subs`|`[{+\|-}modifiers][=name]`|Set the default subtitles track to the first track the matches, and all other tracks are marked as not default.
Each code may optionally be followed by a plus (`+`) or minus (`-`) and one or more modifiers.
Each code may optionally be followed by an equals (`=`) and a track name matching string. `-l`, `--log`|``|The log filename
Default is `/config/log/striptracks.txt` `-c`, `--config`|``|Radarr/Sonarr XML configuration file
Default is `/config/config.xml` +`--database`|``|Radarr/Sonarr SQLite database file
Default is `/config/radarr.db` or `/config/sonarr.db` `-p`, `--priority`|`idle`, `low`, `medium`, `high`|CPU and I/O process priority for mkvmerge
Default is `medium`
![notes] High priority can consume all system resources. When processing a large video file your system may become unresponsive! `-d`, `--debug`|`[]`|Enables debug logging. Level is optional.
Default is `1` (low)
`2` includes JSON output
`3` contains even more JSON output `--no-ansi`||Force disable ANSI color codes in terminal output diff --git a/SECURITY.md b/SECURITY.md index becef5912..820206791 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,8 @@ Only the latest major and minor version are supported. | Version | Supported | | ------- | ------------------ | -| 3.0.x | :heavy_check_mark: | -| < 3.0 | :x: | +| 3.1.x | :heavy_check_mark: | +| < 3.1 | :x: | ## Reporting a Vulnerability diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-radarr-striptracks-add-package/run b/root/etc/s6-overlay/s6-rc.d/init-mod-radarr-striptracks-add-package/run index 740fae31f..d83d5253f 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-radarr-striptracks-add-package/run +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-radarr-striptracks-add-package/run @@ -12,13 +12,24 @@ Version: {{VERSION}} ---------------- EOF -# Determine if setup is needed -if [ ! -f /usr/bin/mkvmerge ]; then - echo "**** Adding striptracks deps to package install list ****" - echo "mkvtoolnix" >> /mod-repo-packages-to-install.list +# Determine if packages need to be installed +commands=(/usr/bin/mkvmerge /usr/bin/sqlite3) +if [ -f /sbin/apk ]; then + # Alpine + packages=(mkvtoolnix sqlite) else - echo "**** striptracks deps already installed, skipping ****" + # Ubuntu/Debian ('sqlite' is SQLite 2.x, which cannot open SQLite 3 databases) + packages=(mkvtoolnix sqlite3) fi +for index in "${!commands[@]}" +do + if [ ! -f "${commands[$index]}" ]; then + echo "**** Adding striptracks dependency ${packages[$index]} to package install list ****" + echo "${packages[$index]}" >> /mod-repo-packages-to-install.list + else + echo "**** striptracks dependency ${packages[$index]} already installed, skipping ****" + fi +done # Check ownership and attributes on each script file for file in /usr/local/bin/striptracks*.sh diff --git a/root/usr/local/bin/striptracks.sh b/root/usr/local/bin/striptracks.sh index 78685e74d..193057aa5 100755 --- a/root/usr/local/bin/striptracks.sh +++ b/root/usr/local/bin/striptracks.sh @@ -12,7 +12,7 @@ # NOTE: ShellCheck linter directives appear as comments -# Dependencies: # sudo apt install mkvtoolnix jq +# Dependencies: # sudo apt install mkvtoolnix jq sqlite3 # From mkvtoolnix: # mkvmerge # mkvpropedit @@ -60,20 +60,21 @@ function main { process_command_line "$@" initialize_mode_variables check_log - check_required_binaries log_first_debug_messages + check_required_binaries check_wsl check_eventtype log_script_start check_config_file check_arr_config + check_arr_db 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" + process_org_code "striptracks_audiokeep" + process_org_code "striptracks_subskeep" + process_org_code "striptracks_default_audio" + process_org_code "striptracks_default_subtitles" resolve_code_conflict # Read in the output of mkvmerge info extraction get_mediainfo "$striptracks_video" @@ -174,6 +175,10 @@ Options and Arguments: Radarr/Sonarr XML configuration file [default: /config/config.xml] + --database + Radarr/Sonarr SQlite database file + [default: /config/radarr.db or /config/sonarr.db] + -p, --priority idle|low|medium|high CPU and I/O process priority for mkvmerge [default: medium] @@ -276,6 +281,15 @@ function initialize_variables { export striptracks_mode="Custom Script" # Presence of '*_eventtype' variable sets script type when in Custom Script mode: "radarr", "sonarr" export striptracks_type=$(printenv | sed -n 's/_eventtype *=.*$//p') + # Switch to Import mode if *_transfermode variable is found (see issues #52 and #121) + local transfermode=$(printenv | sed -n 's/_transfermode *=.*$//p') + if [ -n "$transfermode" ]; then + export striptracks_mode="Import" + export striptracks_type="$transfermode" # "radarr", "sonarr" + fi + export striptracks_arr_db="/config/${striptracks_type,,}.db" + export striptracks_checked_org_compat=0 + export striptracks_curl_compatible=0 declare -g -x -a striptracks_skip_profile } function parse_arg_string { @@ -351,6 +365,20 @@ function process_command_line { export striptracks_prelogmessagedebug="Debug|Command line arguments are '$*'" fi + # Check for running in Import mode, and skip the command line arguments that Radarr/Sonarr add + # These are not needed as the same data will be scraped from environment variables + if [ "$striptracks_mode" = "Import" ]; then + local sourcepath_var="${striptracks_type}_sourcepath" + local destinationpath_var="${striptracks_type}_destinationpath" + local sourcepath="${!sourcepath_var}" + local destinationpath="${!destinationpath_var}" + + if [[ "$1" == "$sourcepath" ]] && [[ "$2" == "$destinationpath" ]]; then + export striptracks_prelogmessagedebug+=$'\n'"Debug|Import mode detected, skipping command line arguments '$1' and '$2' added by ${striptracks_type^} and using environment variables instead." + shift 2 + fi + fi + # Check for environment variable arguments if [ -n "$STRIPTRACKS_ARGS" ]; then if [ $# -ne 0 ]; then @@ -453,6 +481,17 @@ function process_command_line { export striptracks_arr_config="$2" shift 2 ;; + --database ) + # *arr SQlite database file + if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then + echo_ansi "Error|Invalid option: $1 requires an argument." >&2 + usage + exit 20 + fi + # Overrides default /config/${striptracks_type}.db + export striptracks_arr_db="$2" + shift 2 + ;; -p|--priority ) # Set process priority (see issue #102) if [ -z "$2" ] || [ ${2:0:1} = "-" ]; then @@ -609,14 +648,7 @@ function echo_ansi { fi } function initialize_mode_variables { - # Determines script mode and sets mode specific variables - - # Switch to Import mode if *_transfermode variable is found (see issues #52 and #121) - local transfermode=$(printenv | sed -n 's/_transfermode *=.*$//p') - if [ -n "$transfermode" ]; then - export striptracks_mode="Import" - export striptracks_type="$transfermode" # "radarr", "sonarr" - fi + # Sets script mode specific variables # Mode specific variable assignment if [[ "${striptracks_mode,,}" = "batch" ]]; then @@ -633,6 +665,7 @@ function initialize_mode_variables { export striptracks_video="$radarr_moviefile_path" # shellcheck disable=SC2154 export striptracks_video_folder="$radarr_movie_path" + export striptracks_video_type="movie" export striptracks_video_api="movie" # shellcheck disable=SC2154 export striptracks_video_id="$radarr_movie_id" @@ -645,19 +678,20 @@ function initialize_mode_variables { export striptracks_download_client_type="$radarr_download_client_type" # shellcheck disable=SC2154 export striptracks_rescan_id="$radarr_movie_id" - export striptracks_json_quality_root="movieFile" - export striptracks_video_type="movie" + export striptracks_videofile_json_root="movieFile" 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" + export striptracks_metadata_via_api=("releaseGroup" "indexerFlags" "sceneName" "edition") + export striptracks_metadata_via_db=("originalFilePath") elif [[ "${striptracks_type,,}" = "sonarr" ]]; then # Sonarr # shellcheck disable=SC2154 export striptracks_video="$sonarr_episodefile_path" # shellcheck disable=SC2154 export striptracks_video_folder="$sonarr_series_path" + export striptracks_video_type="series" export striptracks_video_api="episode" # shellcheck disable=SC2154 export striptracks_video_id="$sonarr_episodefile_episodeids" @@ -670,14 +704,15 @@ function initialize_mode_variables { export striptracks_download_client_type="$sonarr_download_client_type" # shellcheck disable=SC2154 export striptracks_rescan_id="$sonarr_series_id" - export striptracks_json_quality_root="episodeFile" - export striptracks_video_type="series" + export striptracks_videofile_json_root="episodeFile" 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 when importing episodes directly # export striptracks_sonarr_json=" \"episodeIds\":[.episodes[].id]," + export striptracks_metadata_via_api=("releaseGroup" "indexerFlags") + export striptracks_metadata_via_db=("sceneName") else # Called in an unexpected way echo_ansi "Error|Unknown or missing *_eventtype or *_transfermode environment variables.\nNot calling from Radarr/Sonarr? Try using Batch Mode option: -f " >&2 @@ -792,7 +827,7 @@ function check_job { case "$json_test" in completed) local return=0; break ;; queued) - # See issue #125 + # Correct return code (see issue #125) [ $striptracks_debug -ge 1 ] && echo "Debug|Job still queued. Waiting 1 second." | log local return=1 sleep 1 @@ -859,7 +894,10 @@ function delete_videofile { 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}]")" + local metadata_field_list + metadata_field_list=$(IFS=,; echo "${striptracks_metadata_via_api[*]}") + # Only send fields that are present and not null, so that nulls don't overwrite existing values + call_api 0 "Updating video metadata." "PUT" "$striptracks_videofile_api/bulk" "$(echo "$striptracks_original_metadata" | jq -crM "[{quality${metadata_field_list:+, $metadata_field_list}} | with_entries(select(.value != null)) | {id:${striptracks_videofile_id}} + .]")" [ "${#striptracks_result}" != 0 ] return } @@ -931,9 +969,10 @@ function check_compat { # 0 - the feature is compatible # 1 - the feature is incompatible - local compat_type="$1" # 'apiv3', 'languageprofile', 'customformat', 'originallanguage', 'qualitylanguage' + local compat_type="$1" # 'apiv3', 'languageprofile', 'customformat', 'originallanguage', 'qualitylanguage', 'curljson', 'sqlitesafe' local return=1 + local compat_with="${striptracks_type^} v${striptracks_arr_version}" case "$compat_type" in apiv3) [ ${striptracks_arr_version/.*/} -ge 3 ] && local return=0 @@ -956,6 +995,16 @@ function check_compat { # Language option in Quality Profile [ "${striptracks_type,,}" = "radarr" ] && [ ${striptracks_arr_version/.*/} -ge 3 ] && local return=0 ;; + curljson) + # curl --json option added in 7.82 + local compat_with="curl v${striptracks_curl_version:-unknown}" + [ -n "$striptracks_curl_version" ] && version_ge "$striptracks_curl_version" "7.82" && local return=0 + ;; + sqlitesafe) + # sqlite3 -safe option added in 3.37 + local compat_with="sqlite3 v${striptracks_sqlite_version:-unknown}" + [ -n "$striptracks_sqlite_version" ] && version_ge "$striptracks_sqlite_version" "3.37" && local return=0 + ;; *) # Unknown feature local message="Error|Unknown feature $compat_type in ${striptracks_type^}" @@ -963,9 +1012,27 @@ function check_compat { echo_ansi "$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 + [ $striptracks_debug -ge 1 ] && echo "Debug|Feature $compat_type is $([ $return -eq 1 ] && echo "not ")compatible with ${compat_with}" | log return $return } +function version_ge { + # Compare two dotted version strings + + # Exit codes: + # 0 - first version is greater than or equal to the second + # 1 - first version is less than the second + + local -a ver1 ver2 + # Strip any non-numeric suffix (e.g. '-DEV') + IFS=. read -ra ver1 <<< "${1%%[^0-9.]*}" + IFS=. read -ra ver2 <<< "${2%%[^0-9.]*}" + local i + for ((i=0; i < ${#ver2[@]}; i++)); do + [ "${ver1[i]:-0}" -gt "${ver2[i]}" ] && return 0 + [ "${ver1[i]:-0}" -lt "${ver2[i]}" ] && return 1 + done + return 0 +} function get_media_config { # Get media management configuration @@ -974,8 +1041,8 @@ function get_media_config { [ "$json_test" != "null" ] && [ "$json_test" != "" ] return } -function set_video_info { - # Update file metadata in Radarr/Sonarr +function set_video_monitored { + # Update video monitored status 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 ] @@ -984,26 +1051,28 @@ function set_video_info { 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' + local var_name="$1" # 'striptracks_audiokeep', 'striptracks_subskeep', 'striptracks_default_audio', or 'striptracks_default_subtitles' - if [[ "${!keep_var}" =~ :org ]]; then - # Check compatibility - if [ "${striptracks_mode,,}" = "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_ansi "$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_ansi "$message" >&2 + if [[ "${!var_name}" =~ :org ]]; then + if [ "$striptracks_checked_org_compat" -eq 0 ]; then + # Check compatibility (first time only) + if [ "${striptracks_mode,,}" = "batch" ]; then + local message="Warn|${var_name} argument contains ':org' code, but this is undefined for Batch mode! Unexpected behavior may result." + echo "$message" | log + echo_ansi "$message" >&2 + elif ! check_compat originallanguage; then + local message="Warn|${var_name} argument contains ':org' code, but this is undefined and not compatible with this mode/version! Unexpected behavior may result." + echo "$message" | log + echo_ansi "$message" >&2 + fi fi + striptracks_checked_org_compat=$((striptracks_checked_org_compat + 1)) # 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 + [ $striptracks_debug -ge 1 ] && echo "Debug|${var_name} argument ':org' specified. Changing '${!var_name}' to '${!var_name//:org/${striptracks_originalLangCode}}'" | log # Replace :org with the original language code - declare -g "$keep_var=${!keep_var//:org/${striptracks_originalLangCode}}" + declare -g "$var_name=${!var_name//:org/${striptracks_originalLangCode}}" fi } function end_script { @@ -1049,7 +1118,7 @@ function check_log { function check_required_binaries { # Check for required binaries - for striptracks_file in "/usr/bin/mkvmerge" "/usr/bin/mkvpropedit" "/usr/bin/jq"; do + for striptracks_file in "/usr/bin/mkvmerge" "/usr/bin/mkvpropedit" "/usr/bin/jq" "/usr/bin/sqlite3"; do if [ ! -f "$striptracks_file" ]; then local message="Error|$striptracks_file is required by this script" echo "$message" | log @@ -1057,6 +1126,14 @@ function check_required_binaries { end_script 4 fi done + + # Detect versions for compatibility checks + export striptracks_curl_version="$(curl --version 2>/dev/null | awk 'NR==1 {print $2}')" + export striptracks_sqlite_version="$(/usr/bin/sqlite3 -version 2>/dev/null | awk '{print $1}')" + [ $striptracks_debug -ge 1 ] && echo "Debug|Detected curl version ${striptracks_curl_version:-unknown} and sqlite3 version ${striptracks_sqlite_version:-unknown}" | log + if ! check_compat curljson; then + export striptracks_curl_compatible=1 + fi } function log_first_debug_messages { # First log messages @@ -1098,6 +1175,7 @@ function check_eventtype { # Handle Test event if [[ "${striptracks_event}" = "Test" ]]; then echo "Info|${striptracks_type^} event: ${striptracks_event}" | log + check_config_file local message="Info|Script was test executed successfully." echo "$message" | log echo_ansi "$message" @@ -1114,6 +1192,11 @@ function check_wsl { 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 + # Adjust database file location to WSL default + if [ ! -f "$striptracks_arr_db" ]; then + export striptracks_arr_db="/mnt/c/ProgramData/${striptracks_type^}/${striptracks_type,,}.db" + [ $striptracks_debug -ge 1 ] && echo "Debug|Will try to use the default WSL database file '$striptracks_arr_db'" | log + fi fi } function log_script_start { @@ -1208,7 +1291,12 @@ function call_api { while (( "$#" )); do case "$1" in "{"*|"["*) - curl_data_args+=(--json "$1") + if [ $striptracks_curl_compatible -eq 0 ]; then + curl_data_args+=(--json "$1") + else + # Content-Type and Accept headers are set explicitly below + curl_data_args+=(--data-raw "$1") + fi ;; *=*) curl_data_args+=(--data-urlencode "$1") @@ -1243,7 +1331,7 @@ function call_api { curl_args+=(--url "$url") [ $striptracks_debug -ge 2 ] && echo "Debug|Executing: curl ${curl_args[*]}" | sed -E 's/(X-Api-Key: )[^ ]+/\1[REDACTED]/' | log unset striptracks_result - # (See issue #104) + # Fix for argument list too long (see issue #104) declare -g striptracks_result # Retry up to five times if database is locked @@ -1462,7 +1550,7 @@ function detect_languages { export striptracks_videoinfo="$striptracks_result" export striptracks_videomonitored="$(echo "$striptracks_videoinfo" | jq -crM ".monitored")" # This is not strictly necessary as the ID 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)" + export striptracks_videofile_id="$(echo "$striptracks_videoinfo" | jq -crM .${striptracks_videofile_json_root}.id)" # Import mode will not have a video file until after import if [ "${striptracks_mode,,}" != "import" ]; then @@ -1477,8 +1565,20 @@ function detect_languages { export striptracks_videofile_info="$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 + local -a metadata_field_array=("${striptracks_metadata_via_api[@]}" "${striptracks_metadata_via_db[@]}") + local metadata_field_list + metadata_field_list=$(IFS=,; echo "${metadata_field_array[*]}") + # Only save fields that are present and not null + export striptracks_original_metadata="$(echo "$striptracks_videofile_info" | jq -crM "{quality${metadata_field_list:+, $metadata_field_list}} | with_entries(select(.value != null))")" + [ $striptracks_debug -ge 1 ] && { + local debug_text="" + for metadata_field in "${metadata_field_array[@]}"; do + local metadata_value="$(echo "$striptracks_original_metadata" | jq -crM --arg field "$metadata_field" '.[$field] | select(. != null)')" + [ -z "$metadata_value" ] && continue + debug_text+="${debug_text:+, }${metadata_field} '${metadata_value}'" + done + echo "Debug|Found video file metadata quality:'$(echo "$striptracks_original_metadata" | jq -crM .quality.quality.name)'${debug_text:+, $debug_text}" | log + } fi # Get quality profile info @@ -1635,6 +1735,24 @@ function check_arr_config { fi fi } +function check_arr_db { + # Test for existence of Radarr/Sonarr database + + # Bypass if using Batch mode + if [ "${striptracks_mode,,}" = "batch" ]; then + [ $striptracks_debug -ge 1 ] && echo "Debug|Cannot check database in Batch mode." | log + return + fi + + if [ -f "$striptracks_arr_db" ]; then + [ $striptracks_debug -ge 1 ] && echo "Debug|Found ${striptracks_type^} database '$striptracks_arr_db'" | log + else + local message="Warn|Unable to locate ${striptracks_type^} database '$striptracks_arr_db'. Unable to update ${striptracks_video_api} metadata." + echo "$message" | log + echo_ansi "$message" >&2 + change_exit_status 20 + fi +} function resolve_code_conflict { # Final assignment of audio and subtitles selection @@ -1982,8 +2100,13 @@ function determine_track_order { ($rule.lang | in({"any":0,($track.language):0})) and ( ($rule.mods | length == 0) or - ( ($rule.mods | map(.forced? // false) | index(true) != null) and $track.forced ) or - ( ($rule.mods | map(.default? // false) | index(true) != null) and $track.default ) + # Negative modifiers (e.g. -f, -d) must match too, so compare each modifier to the + # track flag instead of only looking for true. Multiple modifiers are combined with + # OR, matching the keep logic in process_mkvmerge_json. + any($rule.mods[]; + ( has("forced") and .forced == ($track.forced == true) ) or + ( has("default") and .default == ($track.default == true) ) + ) ) ) | .id as $id | @@ -2137,7 +2260,7 @@ function remux_video { export striptracks_neworder="--track-order $striptracks_neworder" fi - # Execute MKVmerge (remux then rename, see issue #46) + # Execute MKVmerge, remux then rename (see issue #46) local mkvcommand="$striptracks_nice /usr/bin/mkvmerge" execute_mkv_command "remuxing video" "$mkvcommand" -o "$striptracks_tempvideo" -q --title "$(escape_string "$striptracks_title")" $audioarg $subsarg $striptracks_mkvmerge_default_args $striptracks_neworder "$striptracks_video" @@ -2264,8 +2387,8 @@ function rescan_and_cleanup { return fi - ##### 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. + ##### Leaving this here (and all supporting functions and variables) in case the single file import job problem can be resolved (see issue #50). + ##### Importing directly is a much better way than rescanning. # # Scan for files to import into Radarr/Sonarr # if ! get_import_info; then # 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." @@ -2276,11 +2399,14 @@ function rescan_and_cleanup { # fi # # Build JSON data # [ $striptracks_debug -ge 1 ] && echo "Debug|Building JSON data to import" | log + # local -a metadata_field_array=("${striptracks_metadata_via_api[@]}" "${striptracks_metadata_via_db[@]}") + # local metadata_field_list + # metadata_field_list=$(IFS=,; echo "${metadata_field_array[*]}") # 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} - # ) + # map( + # select(.path == \"$striptracks_newvideo\") | + # {path, folderName, \"${striptracks_video_type}Id\":.${striptracks_video_type}.id,${striptracks_sonarr_json}, ${striptracks_language_node}, quality${metadata_field_list:+, $metadata_field_list}} + # ) # ") # # Import new video into Radarr/Sonarr @@ -2345,21 +2471,23 @@ function rescan_and_cleanup { return fi export striptracks_videoinfo="$striptracks_result" - export striptracks_videofile_id="$(echo "$striptracks_videoinfo" | jq -crM .${striptracks_json_quality_root}.id)" + export striptracks_videofile_id="$(echo "$striptracks_videoinfo" | jq -crM .${striptracks_videofile_json_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 [ -n "$striptracks_videomonitored" -a "$(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'" + 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 + set_video_monitored + else + [ $striptracks_debug -ge 1 ] && echo "Debug|Video monitor status unchanged after deleting the original." | log fi - + # Get new video file info if ! get_videofile_info; then # 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." + local message="Warn|The '$striptracks_videofile_api' API with id $striptracks_videofile_id returned no path." echo "$message" | log echo_ansi "$message" >&2 change_exit_status 17 @@ -2367,23 +2495,49 @@ function rescan_and_cleanup { fi export striptracks_videofile_info="$striptracks_result" - # Check that the metadata didn't get lost in the rescan. This is not necessary in Import mode - if [ -n "$striptracks_original_metadata" ] && [ -n "$striptracks_videofile_info" ] && [ "$(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 + # Update the metadata lost in the rescan (see issue #128) + 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 metadata via API." | log + else + local message="Warn|Unable to update ${striptracks_type^} $striptracks_video_api '$striptracks_title' metadata." + echo "$message" | log + echo_ansi "$message" >&2 + change_exit_status 17 + fi + + # Really, *really* bad way to update the Radarr/Sonarr database, but there is no other way to get originalFilePath/sceneName updated (see issue #128) + local debug_text="" + local sqlite_sets="" + for metadata_field in "${striptracks_metadata_via_db[@]}"; do + # Only include fields that are present and not null + local db_col_value=$(echo "$striptracks_original_metadata" | jq -crM --arg field "$metadata_field" '.[$field] | select(. != null)') + if [ -z "$db_col_value" ]; then + [ $striptracks_debug -ge 1 ] && echo "Debug|Skipping null metadata field '${metadata_field}' in database update." | log + continue + fi + debug_text+="${debug_text:+, }${metadata_field} '${db_col_value}'" + sqlite_sets+="${sqlite_sets:+,}${metadata_field}='${db_col_value//\'/\'\'}'" + done + if [ -n "$sqlite_sets" ]; then + local -a sqlite_args=() + check_compat sqlitesafe && sqlite_args+=(-safe) + [ $striptracks_debug -ge 1 ] && echo "Debug|Updating ${striptracks_type^} database directly for ${striptracks_videofile_api} id ${striptracks_videofile_id} with ${debug_text}" | log + [ $striptracks_debug -ge 1 ] && echo "Debug|Executing: /usr/bin/sqlite3 ${sqlite_args[*]}${sqlite_args[*]:+ }\"${striptracks_arr_db}\" \"UPDATE ${striptracks_video_api^}Files SET ${sqlite_sets} WHERE Id=${striptracks_videofile_id};\"" | log + result=$(/usr/bin/sqlite3 "${sqlite_args[@]}" "${striptracks_arr_db}" "UPDATE ${striptracks_video_api^}Files SET ${sqlite_sets} WHERE Id=${striptracks_videofile_id};") + local return=$? + if [ $return -eq 0 ]; then + echo "Info|Successfully updated metadata via database edit." | 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)')'" + local message="Error|[$return] ${striptracks_type^} error when updating ${striptracks_type^} database." echo "$message" | log echo_ansi "$message" >&2 - change_exit_status 17 + change_exit_status 20 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 + [ $striptracks_debug -ge 1 ] && echo "Debug|All metadata fields are null. Skipping database edit." | log fi # Check the languages returned @@ -2461,7 +2615,7 @@ function rescan_and_cleanup { } # 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")" + local renamedvideo="$(echo "$striptracks_result" | jq -crM ".[] | select(.${striptracks_videofile_json_root}Id == $striptracks_videofile_id) | .newPath")" # Rename video if needed if [ -n "$renamedvideo" ]; then rename_videofile "$striptracks_videofile_id" "$renamedvideo"