Add --move mode to radarr_classification_scan.sh

Acts only on forward misplacements (clear-cut anime/kids classified
content sitting outside its dedicated root) — reverse leaks and junk
entries stay report-only, since those need human judgment or a
different action (removal, not a move). One movie at a time with a
real re-fetch-and-verify after each, matching the race condition found
doing this by hand for Sonarr earlier in the session.
This commit is contained in:
Gmer4Lfe
2026-07-17 11:58:28 -04:00
parent 10d6f372b3
commit 46ddc55345
+117
View File
@@ -100,6 +100,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"
# --move is a script-local flag, not one parse_args recognizes — check the raw args before
# they get filtered into PARSED_ARGS.
MOVE_MODE=false
for _arg in "$@"; do
[[ "$_arg" == "--move" ]] && MOVE_MODE=true
done
unset _arg
parse_args "$@"
# ==============================================================================================
@@ -136,6 +144,7 @@ if [[ "$SHOW_STATUS" == true ]]; then
echo "$ICON_GEAR Anime studios: ${#RADARR_ANIME_STUDIOS[@]} curated"
echo "$ICON_GEAR Kids studios: ${#RADARR_KIDS_STUDIOS[@]} curated"
echo "$ICON_GEAR Junk min votes: ${RADARR_JUNK_MIN_VOTES:-15}"
echo "$ICON_GEAR Move mode: $MOVE_MODE"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
@@ -237,4 +246,112 @@ echo "$ICON_PROTECTED Bad metadata (junk): $JUNK_COUNT (hasFile=false, no imdb
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
[[ "$ENABLE_LOGGING" != true ]] && echo " (run with --log for the per-title list)"
# ==============================================================================================
# ━━━ Move Mode ━━━
# ==============================================================================================
# Acts only on FORWARD misplacements (clear-cut: classified anime/kids, sitting in the wrong
# root) and never on REVERSE leaks (those are judgment calls — deliberate style placements
# like Castlevania/Legend of Korra live there, so they're report-only) or JUNK (those need
# removal from Radarr, not a file move — nothing to move for a hasFile=false entry).
#
# One movie at a time, verified after each, matching the lesson learned doing this by hand
# for Sonarr earlier: a rapid-fire batch of moves raced Radarr's own file-move worker and
# left two series' files at an intermediate path while the API still reported success. A
# short sleep plus a real re-fetch-and-check after every single move catches that here
# before it can compound across dozens of movies.
if [[ "$MOVE_MODE" == true ]]; then
echo ""
echo "━━━ $ICON_SYNC Move Mode — Forward Misplacements ━━━"
acquire_lock "wait"
trap "_release_all_locks" EXIT
build_arr_path_map "RADARR"
MOVE_TARGETS=$(echo "$RESULTS" | jq -c '[.[] | select((.forward_anime_miss or .forward_kids_miss) and (.is_junk | not))]')
MOVE_COUNT=$(echo "$MOVE_TARGETS" | jq 'length')
if [[ "$MOVE_COUNT" -eq 0 ]]; then
info "Nothing to move"
exit 0
fi
warn "About to move $MOVE_COUNT movies — one at a time, verifying after each"
MOVED=0
FAILED=0
while IFS= read -r item; do
id=$(echo "$item" | jq -r '.id')
title=$(echo "$item" | jq -r '.title')
is_anime_flag=$(echo "$item" | jq -r '.is_anime')
if [[ "$is_anime_flag" == "true" ]]; then
target_root="$RADARR_ANIME_ROOT"
else
target_root="$RADARR_KIDS_ROOT"
fi
# RESULTS only carries the reduced report fields — Radarr's PUT expects the complete
# resource representation, so fetch a fresh full movie record to modify and send back.
full_movie=$(arr_api "$RADARR_URL" "$RADARR_API_KEY" "v3" "movie/$id" "Radarr")
if [[ -z "$full_movie" ]]; then
error "$title — could not fetch full movie record, skipping"
(( FAILED++ ))
continue
fi
old_path=$(echo "$full_movie" | jq -r '.path')
folder_name="${old_path##*/}"
# A literal "/" in the folder name would build a broken nested directory instead of
# moving to one clean folder — bit us once already doing this by hand for Sonarr.
if [[ "$folder_name" == *"/"* ]]; then
error "$title — folder name contains '/', skipping (needs manual handling)"
(( FAILED++ ))
continue
fi
new_path="${target_root}/${folder_name}"
info "$title: $old_path$new_path"
updated_movie=$(echo "$full_movie" | jq --arg root "$target_root" --arg path "$new_path" \
'.rootFolderPath = $root | .path = $path')
http_code=$(curl -sf -o /dev/null -w "%{http_code}" -X PUT \
--max-time 30 \
-H "X-Api-Key: $RADARR_API_KEY" \
-H "Content-Type: application/json" \
-d "$updated_movie" \
"${RADARR_URL}/api/v3/movie/${id}?moveFiles=true" 2>/dev/null)
if [[ "$http_code" != "200" && "$http_code" != "202" ]]; then
error "$title — API returned HTTP $http_code — stopping (review before re-running)"
(( FAILED++ ))
break
fi
sleep 3
# Never trust the PUT response alone — re-fetch and confirm the change actually landed.
verify_movie=$(arr_api "$RADARR_URL" "$RADARR_API_KEY" "v3" "movie/$id" "Radarr")
verify_root=$(echo "$verify_movie" | jq -r '.rootFolderPath')
verify_hasfile=$(echo "$verify_movie" | jq -r '.hasFile')
if [[ "$verify_root" == "$target_root" ]] && [[ "$verify_hasfile" == "true" ]]; then
echo " $ICON_SUCCESS $title — moved and verified"
(( MOVED++ ))
else
error "$title — verification failed (root: $verify_root, hasFile: $verify_hasfile) — stopping"
(( FAILED++ ))
break
fi
done < <(echo "$MOVE_TARGETS" | jq -c '.[]')
echo ""
echo "━━━━━ $ICON_SUMMARY MOVE SUMMARY ━━━━━"
echo "$ICON_SUCCESS Moved: $MOVED"
echo "$ICON_ERROR Failed: $FAILED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
fi
exit 0