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:
@@ -100,6 +100,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
|
|
||||||
source "$SCRIPT_DIR/../load_config.sh"
|
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 "$@"
|
parse_args "$@"
|
||||||
|
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
@@ -136,6 +144,7 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
|||||||
echo "$ICON_GEAR Anime studios: ${#RADARR_ANIME_STUDIOS[@]} curated"
|
echo "$ICON_GEAR Anime studios: ${#RADARR_ANIME_STUDIOS[@]} curated"
|
||||||
echo "$ICON_GEAR Kids studios: ${#RADARR_KIDS_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 Junk min votes: ${RADARR_JUNK_MIN_VOTES:-15}"
|
||||||
|
echo "$ICON_GEAR Move mode: $MOVE_MODE"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -237,4 +246,112 @@ echo "$ICON_PROTECTED Bad metadata (junk): $JUNK_COUNT (hasFile=false, no imdb
|
|||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
[[ "$ENABLE_LOGGING" != true ]] && echo " (run with --log for the per-title list)"
|
[[ "$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
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user