From a7bf45faaf7a9ce90310f6c55667f6feb9f8f2ea Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sat, 18 Jul 2026 21:41:39 -0400 Subject: [PATCH] =?UTF-8?q?Add=20trailer=5Ffolder=5Fmigration.sh=20?= =?UTF-8?q?=E2=80=94=20one-off=20tool=20to=20migrate=20TV=20trailers=20int?= =?UTF-8?q?o=20trailers/=20convention?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tools/trailer_folder_migration.sh | 199 ++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100755 Tools/trailer_folder_migration.sh diff --git a/Tools/trailer_folder_migration.sh b/Tools/trailer_folder_migration.sh new file mode 100755 index 0000000..7629091 --- /dev/null +++ b/Tools/trailer_folder_migration.sh @@ -0,0 +1,199 @@ +#!/bin/bash +# ============================================================================================== +# ============================= Trailer Folder Migration ======================================= +# ============================================================================================== +# +# PURPOSE +# ───────────────────────────────────────────────────────────────────────────── +# One-off migration for TV show trailers downloaded before Trailarr's "Series +# Trailers" profile was corrected. Emby ingests TV trailers sitting at the +# series root using the movie-style " (Year)-trailer.ext" convention as +# fake episodes instead of recognizing them as trailers — confirmed via Emby's +# server log. The fix is a "trailers/" subfolder (lowercase) under the series +# folder containing a plain "trailer.ext" file — same convention Trailarr now +# uses going forward (customfilter_id=2, "Series Trailers" profile). +# +# This script only moves/renames existing misplaced files. It does not touch +# Trailarr or Emby — a normal periodic Emby library scan picks up the moved +# files on its own. +# +# ============================================================================================== +# OPERATIONAL MODEL +# ============================================================================================== +# +# Discovery +# Scans SONARR_TV_ROOT one level deep for "*-trailer.*" files sitting +# directly in a series folder (the movie-style convention). +# +# Single Trailer Per Show +# / (Year)-trailer.mkv → /trailers/trailer.mkv +# +# Multiple Trailers Per Show +# Sorted alphabetically for determinism. First file becomes trailer.ext, +# subsequent files become trailer-2.ext, trailer-3.ext, etc. Emby's TV +# trailer convention only requires the file to live in trailers/ — exact +# filename beyond the first doesn't matter, but distinct names avoid +# collisions and keep behavior deterministic on re-run. +# +# Collision Safety +# If the destination filename already exists, the file is skipped and +# logged as a conflict rather than overwritten. Lets the script be re-run +# safely after a partial run or a manually resolved conflict. +# +# ============================================================================================== +# CONFIGURATION +# ============================================================================================== +# +# host*.conf +# +# HOST*_SONARR_TV_ROOT +# Host filesystem path to the TV library. Aliased by detect_hosts() → +# SONARR_TV_ROOT. +# +# ============================================================================================== +# RUNTIME MODES +# ============================================================================================== +# +# trailer_folder_migration.sh +# Move every misplaced trailer file into /trailers/. +# +# trailer_folder_migration.sh --dry-run +# Show what would move, with no filesystem changes. +# +# trailer_folder_migration.sh --log +# Verbose output — per-file source/destination. +# +# ============================================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/../load_config.sh" + +parse_args "$@" + +# ============================================================================================== +# ━━━ Setup ━━━ +# ============================================================================================== +acquire_lock + +# detect_hosts() sets MY_ID and aliases HOST*_SONARR_TV_ROOT → SONARR_TV_ROOT +detect_hosts + +if [[ -z "${SONARR_TV_ROOT:-}" ]]; then + error "SONARR_TV_ROOT not set for $MY_ID — check HOST*_SONARR_TV_ROOT in host*.conf" + exit 1 +fi + +if [[ ! -d "$SONARR_TV_ROOT" ]]; then + error "SONARR_TV_ROOT not found: $SONARR_TV_ROOT" + exit 1 +fi + +log "Identity: $MY_ID ($LOCAL_SERVER_NAME)" +log "TV root: $SONARR_TV_ROOT" +[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be moved" + +# ============================================================================================== +# ━━━ Discover misplaced trailer files, grouped by show ━━━ +# ============================================================================================== +echo "" +echo "━━━ $ICON_CLEAN Trailer Folder Migration — $MY_ID ━━━" + +START=$(date +%s) + +declare -A SHOW_FILES=() # show dir → newline-separated list of trailer file paths +while IFS= read -r -d '' f; do + show_dir="$(dirname "$f")" + SHOW_FILES["$show_dir"]+="$f"$'\n' +done < <(find "$SONARR_TV_ROOT" -mindepth 2 -maxdepth 2 -iname "*-trailer.*" -print0) + +TOTAL_SHOWS=${#SHOW_FILES[@]} +if [[ "$TOTAL_SHOWS" -eq 0 ]]; then + echo "$ICON_DONE No misplaced trailer files found — nothing to migrate" + exit 0 +fi + +warn "$TOTAL_SHOWS show(s) with misplaced trailer file(s)" + +MOVED=0 +SKIPPED=0 +FAILED=0 +FAIL_LIST=() + +for show_dir in "${!SHOW_FILES[@]}"; do + mapfile -t files < <(printf '%s' "${SHOW_FILES[$show_dir]}" | sort) + trailers_dir="$show_dir/trailers" + show_name="$(basename "$show_dir")" + + if [[ "$DRY_RUN" != true ]]; then + mkdir -p "$trailers_dir" || { + error "$show_name — could not create trailers/ dir" + FAILED=$(( FAILED + ${#files[@]} )) + FAIL_LIST+=("$show_name") + continue + } + fi + + idx=0 + for src in "${files[@]}"; do + [[ -z "$src" ]] && continue + idx=$(( idx + 1 )) + ext="${src##*.}" + if [[ "$idx" -eq 1 ]]; then + dest="$trailers_dir/trailer.$ext" + else + dest="$trailers_dir/trailer-$idx.$ext" + fi + + if [[ "$DRY_RUN" == true ]]; then + log "$show_name — would move: $(basename "$src") → trailers/$(basename "$dest")" + MOVED=$(( MOVED + 1 )) + continue + fi + + if [[ -e "$dest" ]]; then + warn "$show_name — $(basename "$dest") already exists, skipping $(basename "$src")" + SKIPPED=$(( SKIPPED + 1 )) + continue + fi + + if mv "$src" "$dest"; then + log "$show_name — $(basename "$src") → trailers/$(basename "$dest")" + MOVED=$(( MOVED + 1 )) + else + error "$show_name — failed to move $(basename "$src")" + FAILED=$(( FAILED + 1 )) + FAIL_LIST+=("$show_name") + fi + done +done + +END=$(date +%s) + +# ============================================================================================== +# ━━━ Summary ━━━ +# ============================================================================================== +echo "" +echo "━━━━━ $ICON_SUMMARY TRAILER MIGRATION SUMMARY ━━━━━" +echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" +echo "$ICON_CLEAN Shows: $TOTAL_SHOWS" +echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" +echo "" +echo "$ICON_DONE Moved: $MOVED" +[[ "$SKIPPED" -gt 0 ]] && echo "$ICON_SKIP Skipped: $SKIPPED (destination already existed)" +[[ "$FAILED" -gt 0 ]] && echo "$ICON_ERROR Failed: $FAILED" +echo "" + +if [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — no changes made" +elif [[ "$FAILED" -gt 0 ]]; then + echo "$ICON_ERROR Status: SOME MOVES FAILED — ${FAIL_LIST[*]}" + notify "Trailer folder migration failed for: ${FAIL_LIST[*]}" \ + "Trailer Migration" "warning" +else + echo "$ICON_DONE Status: done — $MOVED file(s) migrated across $TOTAL_SHOWS show(s)" +fi +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +[[ "$FAILED" -gt 0 ]] && exit 1 +exit 0