Zum Inhalt springen

Script MediaBox

Aus LHlab Wiki
  __  __          _ _         _               
 |  \/  | ___  __| (_) __ _  | |__   _____  __
 | |\/| |/ _ \/ _` | |/ _` | | '_ \ / _ \ \/ /
 | |  | |  __/ (_| | | (_| | | |_) | (_) >  < 
 |_|  |_|\___|\__,_|_|\__,_| |_.__/ \___/_/\_\

  Bash Media Manager v2025-12-06.17
------------------------------------------------
Current Path: /mnt/work/completed_cleaned/series/Star Trek - Deep Space Nine

1. 🎞️  Show Media Infos
2. 📂  Compare Folders
3. 📺  Check Missing Episodes (TMDB)
4. 📝  Check Subtitles
0. 🚪  Quit

Select option (0-9):
#1 Show Media Infos
📂 Scanning path: /mnt/work/completed_cleaned/series/Star Trek - Deep Space Nine
Searching for media files (recursive)...

📊 Media Analysis Report
TYPE       COUNT      TOTAL SIZE     
----------------------------------------
avi        1          0.00 GB
mkv        170        199.26 GB

Press Enter to return to menu...
#2 Compare Folders
Compare folders
#3 Missing Episodes
Missing episodes

Code

#!/bin/bash

# ==============================================================================
# Header Information
# ==============================================================================
# Author: Manuel Wendel
# Date:   2025-12-06
# Version: 2025-12-06.17
#
#
# Changelog:
# 2025-12-06.01: Initial version with menu structure.
# 2025-12-06.02: Implemented media scanning and folder comparison logic.
# 2025-12-06.03: Fixed bug in compare folders where sub-sub-directories were ignored (removed maxdepth).
# 2025-12-06.04: Fixed ASCII art syntax error. Improved comparison table (full names, color coding).
# 2025-12-06.05: Added "Check Missing Episodes" feature with TMDB integration. Updated version format.
# 2025-12-06.06: Improved episode detection for multi-episode files (e.g., S01E01-E02). Added status column with emojis.
# 2025-12-06.07: Added total file count column to Compare Folders. Expanded video file extensions. Fixed table alignment issues for special characters.
# 2025-12-06.08: Fixed critical bug with spaces in paths (removed eval). Improved emoji alignment in tables.
# 2025-12-06.09: Switched VIDEO_EXTS to Bash Array to fully fix space-in-path issues. Adjusted emoji width calculation for perfect alignment.
# 2025-12-06.10: Fixed persistent find error by cleaning array syntax. Added (Multi) marker for double episodes. Forced name truncation to fix table layout.
# 2025-12-06.11: Season headers now Red if counts mismatch, Yellow if match. '(Multi)' text is Yellow. Updated print_padded to strip colors for alignment.
# 2025-12-06.12: Fixed Javascript SyntaxError by replacing octal escape sequences (\033) with \e. Refactored sed commands to use extended regex (-E).
# 2025-12-06.13: Fixed jq syntax error in TMDB multiple results handling (escaped backslashes for string interpolation).
# 2025-12-06.14: Added Check Subtitles feature (Menu 4) to list available subtitles or flag missing ones.
# 2025-12-06.15: Enhanced Check Missing Episodes with 'mediainfo' integration to show Resolution, Duration, and Audio Tracks.
# 2025-12-06.16: Fixed critical jq syntax error in MediaInfo extraction (escaped backslashes for string interpolation).
# 2025-12-06.17: Replaced JQ interpolation with concatenation to fully solve syntax errors. Fixed table alignment (removed emoji padding hack). Enabled dynamic name column width to prevent stripping.
# ==============================================================================

# Colors
# Using \e instead of octal \033 to avoid JS Strict Mode SyntaxErrors in the template literal generator
RED='\e[0;31m'
GREEN='\e[0;32m'
YELLOW='\e[1;33m'
BLUE='\e[0;34m'
CYAN='\e[0;36m'
NC='\e[0m' # No Color

# Default Variables
WORK_PATH=$(pwd)
VERSION="2025-12-06.17"
TMDB_KEY_FILE="$HOME/tmdb.key"

# Extended List of Video Extensions (Bash Array)
# Note: No outer quotes around the parentheses themselves, only around the glob patterns.
VIDEO_EXTS=(
    "(" 
    -iname "*.mp4" -o 
    -iname "*.mkv" -o 
    -iname "*.avi" -o 
    -iname "*.mov" -o 
    -iname "*.wmv" -o 
    -iname "*.flv" -o 
    -iname "*.webm" -o 
    -iname "*.m4v" -o 
    -iname "*.ts" -o 
    -iname "*.mpg" -o 
    -iname "*.mpeg" -o 
    -iname "*.3gp" -o 
    -iname "*.ogv" -o 
    -iname "*.divx" -o 
    -iname "*.xvid" -o 
    -iname "*.rm" -o 
    -iname "*.iso" -o 
    -iname "*.vob" 
    ")"
)

# ------------------------------------------------------------------------------
# Helper Functions
# ------------------------------------------------------------------------------

print_header() {
    clear
    echo -e "${CYAN}"
    echo "  __  __          _ _         _               "
    echo " |  \/  | ___  __| (_) __ _  | |__   _____  __"
    echo " | |\/| |/ _ \/ _\` | |/ _\` | | '_ \ / _ \ \/ /"
    echo " | |  | |  __/ (_| | | (_| | | |_) | (_) >  < "
    echo " |_|  |_|\___|\__,_|_|\__,_| |_.__/ \___/_/\_\\"
    echo -e "${NC}"
    echo -e "${YELLOW}  Bash Media Manager v${VERSION}${NC}"
    echo "------------------------------------------------"
}

show_help() {
    echo -e "${GREEN}Usage:${NC} $0 [options]"
    echo ""
    echo "Options:"
    echo "  -h, --help       Show this help message and exit"
    echo "  -v, --version    Show version information and exit"
    echo "  -p, --path PATH  Set the working directory (default: current directory)"
    echo ""
    echo "Examples:"
    echo "  $0 -p /home/user/Movies"
    echo "  $0 --version"
}

show_version() {
    echo "Version: ${VERSION}"
}

check_dependencies() {
    local missing=0
    if ! command -v jq &> /dev/null; then
        echo -e "${RED}Error: 'jq' is not installed. Please install it (apt install jq / brew install jq).${NC}"
        missing=1
    fi
    if ! command -v curl &> /dev/null; then
        echo -e "${RED}Error: 'curl' is not installed.${NC}"
        missing=1
    fi
    # mediainfo is optional but recommended for full features
    if ! command -v mediainfo &> /dev/null; then
        echo -e "${YELLOW}Warning: 'mediainfo' is not installed. Detailed media info will be disabled.${NC}"
        echo -e "${YELLOW}Install with: apt install mediainfo / brew install mediainfo${NC}"
        sleep 2
    fi
    
    if [ $missing -eq 1 ]; then return 1; fi
    return 0
}

# Helper to print padded string handling multibyte chars correctly
# Usage: print_padded "String" Width [ColorCode]
print_padded() {
    local str="$1"
    local target_width="$2"
    local color="${3:-}"
    
    # Strip ANSI escape codes for length calculation
    # Using sed to strip ANSI codes. \e matches Escape in bash $'...' string.
    local clean_str=$(echo -e "$str" | sed $'s/\e\[[0-9;]*m//g')
    
    # Calculate visual length
    # FIX: Removed manual adjustment for emojis as it was causing alignment issues (one char too far left)
    # The terminal seems to treat them as standard width or wc -m matches the visual width correctly in this context.
    local visual_len=$(echo -n "$clean_str" | wc -m)
    
    # Calculate padding needed
    local pad=$((target_width - visual_len))
    
    # Prevent negative padding
    if [ $pad -lt 0 ]; then pad=0; fi
    
    # Print with color wrapper, but internal colors in str will override temporarily
    echo -ne "${color}${str}${NC}"
    printf "%*s" $pad ""
}

# ------------------------------------------------------------------------------
# Core Logic
# ------------------------------------------------------------------------------

scan_media_info() {
    local target_path="$1"
    echo -e "${BLUE}📂 Scanning path: ${NC}${target_path}"
    
    if [ ! -d "$target_path" ]; then
        echo -e "${RED}❌ Error: Directory not found.${NC}"
        read -p "Press Enter to return..."
        return
    fi

    echo -e "${CYAN}Searching for media files (recursive)...${NC}"

    tmp_file=$(mktemp)
    
    # Using array expansion "${VIDEO_EXTS[@]}" to safely handle spaces in arguments
    find "$target_path" -type f "${VIDEO_EXTS[@]}" -printf "%f\t%s\n" 2>/dev/null > "$tmp_file"
    
    echo -e "\n${YELLOW}📊 Media Analysis Report${NC}"
    printf "%-10s %-10s %-15s\n" "TYPE" "COUNT" "TOTAL SIZE"
    echo "----------------------------------------"

    awk -F'\t' '
    {
        filename = $1
        size = $2
        n = split(filename, parts, ".")
        if (n > 1) {
            ext = tolower(parts[n])
            count[ext]++
            sum[ext] += size
        }
    }
    END {
        for (ext in count) {
            gb = sum[ext] / 1024 / 1024 / 1024
            printf "%-10s %-10d %.2f GB\n", ext, count[ext], gb
        }
    }' "$tmp_file" | sort

    if [ ! -s "$tmp_file" ]; then
        echo -e "${RED}No video media found.${NC}"
    fi
    rm "$tmp_file"
    echo ""
    read -p "Press Enter to return to menu..."
}

analyze_folder_content() {
    local dir="$1"
    local tmp_analysis=$(mktemp)
    
    # CRITICAL: Quotes around "$dir" are mandatory to handle paths with spaces.
    find "$dir" -type f "${VIDEO_EXTS[@]}" -printf "%f\t%s\n" 2>/dev/null > "$tmp_analysis"
    
    if [ ! -s "$tmp_analysis" ]; then
        echo "0|0|-"
        rm "$tmp_analysis"
        return
    fi

    local result=$(awk -F'\t' '
    BEGIN { total_size=0; total_files=0 }
    {
        filename = $1
        size = $2
        n = split(filename, parts, ".")
        if (n > 1) {
            ext = tolower(parts[n])
            count[ext]++
            total_size += size
            total_files++
        }
    }
    END {
        printf "%d|%d|", total_size, total_files
        for (ext in count) {
            printf "%s(%d) ", ext, count[ext]
        }
    }' "$tmp_analysis")
    rm "$tmp_analysis"
    if [ -z "$result" ]; then echo "0|0|-"; else echo "$result"; fi
}

compare_folders() {
    echo -e "${CYAN}🔄 Compare Folders${NC}"
    echo -n "Enter Path 1 (Enter for current): "
    read input_p1
    p1="${input_p1:-$WORK_PATH}"
    echo -n "Enter Path 2 (Enter for current): "
    read input_p2
    p2="${input_p2:-$WORK_PATH}"

    echo ""
    echo -e "P1: ${BLUE}$p1${NC}"
    echo -e "P2: ${BLUE}$p2${NC}"
    echo ""
    
    if [ ! -d "$p1" ] || [ ! -d "$p2" ]; then
        echo -e "${RED}❌ One or both paths are invalid.${NC}"
        read -p "Press Enter..."
        return
    fi

    echo -e "${YELLOW}Analyzing directories...${NC}"
    # Find immediate subdirectories. Using parenthesis to run in subshell and cd ensures relative paths are correct.
    subdirs=$( (cd "$p1" 2>/dev/null && find . -maxdepth 1 -type d -not -path '.') ; (cd "$p2" 2>/dev/null && find . -maxdepth 1 -type d -not -path '.') )
    # Clean up "./" prefix and get unique list
    unique_subdirs=$(echo "$subdirs" | sed 's|^\./||' | sort | uniq)

    if [ -z "$unique_subdirs" ]; then
        echo "No subdirectories found to compare."
        read -p "Press Enter..."
        return
    fi

    echo ""
    # Adjusted layout for more columns
    # DIR | P1 Size | P1 Cnt | P1 Types | P2 Size | P2 Cnt | P2 Types
    print_padded "DIRECTORY" 25
    echo -n "| "
    print_padded "P1 SIZE" 10
    echo -n "| "
    print_padded "COUNT" 6
    echo -n "| "
    print_padded "P1 TYPES" 20
    echo -n "| "
    print_padded "P2 SIZE" 10
    echo -n "| "
    print_padded "COUNT" 6
    echo -n "| "
    print_padded "P2 TYPES" 20
    echo ""
    echo "-----------------------------------------------------------------------------------------------------------------"

    while IFS= read -r subdir; do
        if [ -z "$subdir" ]; then continue; fi
        
        # P1 Analysis
        if [ -d "$p1/$subdir" ]; then
            res1=$(analyze_folder_content "$p1/$subdir")
            size1=$(echo "$res1" | cut -d'|' -f1)
            cnt1=$(echo "$res1" | cut -d'|' -f2)
            types1=$(echo "$res1" | cut -d'|' -f3)
            
            if [ "$size1" -gt 0 ]; then
                gb1=$(awk -v b="$size1" 'BEGIN { printf "%.2fG", b/1024/1024/1024 }')
                p1_size="$gb1"
                p1_cnt="$cnt1"
                p1_types="${types1:0:18}" # Truncate types for display
            else
                 p1_size="0"
                 p1_cnt="0"
                 p1_types="-"
            fi
        else
            p1_size="N/A"
            p1_cnt="-"
            p1_types="-"
        fi

        # P2 Analysis
        if [ -d "$p2/$subdir" ]; then
            res2=$(analyze_folder_content "$p2/$subdir")
            size2=$(echo "$res2" | cut -d'|' -f1)
            cnt2=$(echo "$res2" | cut -d'|' -f2)
            types2=$(echo "$res2" | cut -d'|' -f3)

            if [ "$size2" -gt 0 ]; then
                gb2=$(awk -v b="$size2" 'BEGIN { printf "%.2fG", b/1024/1024/1024 }')
                p2_size="$gb2"
                p2_cnt="$cnt2"
                p2_types="${types2:0:18}" # Truncate
            else
                 p2_size="0"
                 p2_cnt="0"
                 p2_types="-"
            fi
        else
            p2_size="N/A"
            p2_cnt="-"
            p2_types="-"
        fi
        
        # Color Logic (Green if identical size and count, Red otherwise)
        if [ "$p1_size" == "$p2_size" ] && [ "$p1_cnt" == "$p2_cnt" ]; then 
            row_color="${GREEN}"
        else 
            row_color="${RED}"
        fi

        # Print Row
        # Truncate Dir name if too long
        display_subdir="${subdir:0:24}"
        
        print_padded "$display_subdir" 25 "$row_color"
        echo -ne "${row_color}| ${NC}"
        
        print_padded "$p1_size" 10 "$row_color"
        echo -ne "${row_color}| ${NC}"
        print_padded "$p1_cnt" 6 "$row_color"
        echo -ne "${row_color}| ${NC}"
        print_padded "$p1_types" 20 "$row_color"
        echo -ne "${row_color}| ${NC}"
        
        print_padded "$p2_size" 10 "$row_color"
        echo -ne "${row_color}| ${NC}"
        print_padded "$p2_cnt" 6 "$row_color"
        echo -ne "${row_color}| ${NC}"
        print_padded "$p2_types" 20 "$row_color"
        echo ""

    done <<< "$unique_subdirs"
    
    echo ""
    read -p "Press Enter to return to menu..."
}

check_missing_episodes() {
    check_dependencies || return

    echo -e "${CYAN}📺 Check Missing Episodes (TMDB)${NC}"

    if [ ! -f "$TMDB_KEY_FILE" ]; then
        echo -e "${RED}❌ Error: API Key file not found.${NC}"
        echo "Please create a file at ${BLUE}$TMDB_KEY_FILE${NC} containing your TMDB API key."
        read -p "Press Enter to return..."
        return
    fi
    TMDB_KEY=$(cat "$TMDB_KEY_FILE" | tr -d '[:space:]')

    echo -n "Enter Series Path (Enter for current): "
    read input_path
    target_path="${input_path:-$WORK_PATH}"

    if [ ! -d "$target_path" ]; then
        echo -e "${RED}❌ Error: Invalid directory.${NC}"
        read -p "Press Enter to return..."
        return
    fi
    echo -e "${BLUE}Analyzing: ${target_path}${NC}"

    # Check for mediainfo availability
    local has_mediainfo=0
    if command -v mediainfo &> /dev/null; then has_mediainfo=1; fi

    # --- 1. Identify Series ---
    tmdb_id=""
    series_name=""
    dir_name=$(basename "$target_path")

    # Method 1: ID in folder name
    if [[ "$dir_name" =~ {tmdb-([0-9]+)} ]]; then
        tmdb_id="${BASH_REMATCH[1]}"
        echo -e "${GREEN}✔ Found ID in folder name: $tmdb_id${NC}"
    fi

    # Method 2: ID in tvshow.nfo
    if [ -z "$tmdb_id" ] && [ -f "$target_path/tvshow.nfo" ]; then
        tmdb_id=$(grep "<tmdbid>" "$target_path/tvshow.nfo" | sed -E 's/.*<tmdbid>(.*)</tmdbid>.*/\1/' | head -1)
        if [ ! -z "$tmdb_id" ]; then echo -e "${GREEN}✔ Found ID in tvshow.nfo: $tmdb_id${NC}"; fi
    fi

    # Method 3: Search TMDB
    if [ -z "$tmdb_id" ]; then
        echo -e "${YELLOW}🔍 ID not found locally. Searching TMDB...${NC}"
        
        year=""
        if [ -f "$target_path/tvshow.nfo" ]; then
             year=$(grep "<year>" "$target_path/tvshow.nfo" | sed -E 's/.*<year>(.*)</year>.*/\1/' | head -1)
        fi

        clean_name=$(echo "$dir_name" | sed -E 's/ \([0-9]{4}\)$//')
        encoded_query=$(echo "$clean_name" | jq -sRr @uri)
        api_url="https://api.themoviedb.org/3/search/tv?api_key=$TMDB_KEY&query=$encoded_query"
        if [ ! -z "$year" ]; then
            api_url="${api_url}&first_air_date_year=$year"
        fi

        response=$(curl -s "$api_url")
        count=$(echo "$response" | jq '.total_results')

        if [ "$count" == "0" ]; then
             echo -e "${RED}❌ No series found on TMDB for '$clean_name' ($year).${NC}"
             read -p "Press Enter to return..."
             return
        elif [ "$count" == "1" ]; then
             tmdb_id=$(echo "$response" | jq -r '.results[0].id')
             series_name=$(echo "$response" | jq -r '.results[0].name')
             echo -e "${GREEN}✔ Found: $series_name (ID: $tmdb_id)${NC}"
        else
             echo -e "${YELLOW}⚠ Multiple results found:${NC}"
             # Fix: Escape backslashes for jq string interpolation in the JS string
             echo "$response" | jq -r '.results[] | "\(.id)|\(.name) (\(.first_air_date))"' | head -5 | while IFS='|' read -r id name; do
                echo -e "${CYAN}$id${NC} : $name"
             done
             echo -n "Enter TMDB ID to use: "
             read user_id
             tmdb_id="$user_id"
        fi
    fi

    if [ -z "$tmdb_id" ]; then
        echo -e "${RED}❌ Could not identify series.${NC}"
        read -p "Press Enter to return..."
        return
    fi

    # --- 2. Get Series Details ---
    series_json=$(curl -s "https://api.themoviedb.org/3/tv/$tmdb_id?api_key=$TMDB_KEY&language=de")
    series_name=$(echo "$series_json" | jq -r '.name')
    if [ "$series_name" == "null" ]; then
         echo -e "${RED}❌ Error fetching details for ID $tmdb_id.${NC}"
         read -p "Press Enter..."
         return
    fi
    
    echo ""
    echo -e "📺 Series: ${GREEN}$series_name${NC} (ID: $tmdb_id)"
    echo "----------------------------------------------------------------------------------"

    # --- 3. Process Seasons & Episodes ---
    echo "$series_json" | jq -c '.seasons[]' | while read -r season_obj; do
        season_num=$(echo "$season_obj" | jq '.season_number')
        episode_count=$(echo "$season_obj" | jq '.episode_count')
        
        if [ "$season_num" == "0" ]; then continue; fi 

        index_dir=$(mktemp -d)
        
        # Extended search in find using array
        find "$target_path" -type f "${VIDEO_EXTS[@]}" | grep -iE "S0*${season_num}E[0-9]+" | while read -r filepath; do
            filename=$(basename "$filepath")
            
            # Check for Multi-Episode: SxxEyy-Ezz or SxxEyy-zz
            if [[ "$filename" =~ [Ss]0*${season_num}[Ee]([0-9]+)-[Ee]?([0-9]+) ]]; then
                start_ep=$((10#${BASH_REMATCH[1]}))
                end_ep=$((10#${BASH_REMATCH[2]}))
                if [ $((end_ep - start_ep)) -ge 0 ] && [ $((end_ep - start_ep)) -lt 100 ]; then
                    for ((i=start_ep; i<=end_ep; i++)); do
                        echo "$filepath" > "$index_dir/$i"
                    done
                fi
            # Check for Single Episode: SxxEyy
            elif [[ "$filename" =~ [Ss]0*${season_num}[Ee]([0-9]+) ]]; then
                ep=$((10#${BASH_REMATCH[1]}))
                echo "$filepath" > "$index_dir/$ep"
            fi
        done

        local_files_count=$(ls "$index_dir" 2>/dev/null | wc -l)

        # Dynamic Name Column Width
        season_url="https://api.themoviedb.org/3/tv/$tmdb_id/season/$season_num?api_key=$TMDB_KEY&language=de"
        season_data=$(curl -s "$season_url")
        
        # Calculate max name length to prevent truncation
        max_name_len=$(echo "$season_data" | jq -r '[.episodes[].name | length] | max')
        if [ "$max_name_len" == "null" ] || [ -z "$max_name_len" ]; then max_name_len=20; fi
        
        # Add buffer for suffix (8 chars) and minimum width
        name_width=$((max_name_len + 10))
        if [ $name_width -lt 30 ]; then name_width=30; fi

        echo ""
        # Color coding for Season Header
        if [ "$local_files_count" -eq "$episode_count" ]; then
             season_color="${YELLOW}"
        else
             season_color="${RED}"
        fi
        echo -e "${season_color}Season $season_num ($local_files_count / $episode_count episodes)${NC}"
        
        # Dynamic Header
        print_padded "STAT" 6
        echo -n "| "
        print_padded "EPISODE" 10
        echo -n "| "
        print_padded "NAME" "$name_width"
        echo -n "| "
        print_padded "SIZE" 10
        echo -n "| "
        print_padded "TYPE" 6
        echo -n "| "
        print_padded "MEDIA INFO" 50
        echo ""
        
        # Dynamic Separator Line
        total_table_width=$((6+3+10+3+name_width+3+10+3+6+3+50))
        printf -v sep_line '%*s' "$total_table_width" ''
        echo "${sep_line// /-}"
        
        echo "$season_data" | jq -c '.episodes[]' | while read -r ep_obj; do
            ep_num=$(echo "$ep_obj" | jq '.episode_number')
            ep_name=$(echo "$ep_obj" | jq -r '.name')
            
            display_ep="${season_num}x${ep_num}"
            raw_name="${ep_name}"
            suffix=""
            media_info_str="-"
            
            if [ -f "$index_dir/$ep_num" ]; then
                found_file=$(cat "$index_dir/$ep_num")
                fsize=$(stat -c%s "$found_file")
                gb=$(awk -v z="$fsize" 'BEGIN { printf "%.2f GB", z/1024/1024/1024 }')
                ext="${found_file##*.}"
                
                # Check for Double/Multi Episode in filename again for display
                base_fn=$(basename "$found_file")
                if [[ "$base_fn" =~ [Ss]0*${season_num}[Ee]([0-9]+)-[Ee]?([0-9]+) ]]; then
                    suffix=" (Multi)"
                fi

                # --- MediaInfo Extraction ---
                if [ $has_mediainfo -eq 1 ]; then
                     mi_json=$(mediainfo --OUTPUT=JSON "$found_file" 2>/dev/null)
                     if [ -n "$mi_json" ]; then
                        # FIX: Use string concatenation (+) instead of interpolation \(..) to avoid syntax errors
                        mi_str=$(echo "$mi_json" | jq -r '
                            try (
                                .media.track as $tracks |
                                
                                ($tracks[] | select(."@type"=="General")) as $gen |
                                (($gen.Duration // 0) | tonumber) as $dur_sec |
                                ($dur_sec / 60 | floor) as $mins |
                                
                                ($tracks[] | select(."@type"=="Video") | select(.ID=="1" or .StreamOrder=="0" or true) | .Height) as $h_str |
                                (($h_str // 0) | tonumber) as $height |
                                (
                                    if $height == 0 then ""
                                    elif $height <= 480 then "480p"
                                    elif $height <= 576 then "576p"
                                    elif $height <= 720 then "720p"
                                    elif $height <= 1080 then "1080p"
                                    elif $height <= 2160 then "4K"
                                    else ($height | tostring) + "p"
                                    end
                                ) as $res |
                                
                                [
                                    $tracks[] | select(."@type"=="Audio") |
                                    (.Language // "und" | ascii_upcase) + " (" + (.Format // "unk") + "-" + (.Channels // "?") + "ch)"
                                ] | join(", ") as $audio |
                                
                                $res + " " + ($mins | tostring) + "m | " + $audio
                            ) catch "-"
                        ')
                        media_info_str="$mi_str"
                     fi
                fi
                # -----------------------------

                # Use Dynamic Name Width - No stripping
                display_name="${raw_name}"
                if [ -n "$suffix" ]; then
                    display_name="${raw_name}${YELLOW}${suffix}${GREEN}"
                fi

                print_padded " ✔ " 6 "${GREEN}"
                echo -ne "${GREEN}| ${NC}"
                print_padded "$display_ep" 10 "${GREEN}"
                echo -ne "${GREEN}| ${NC}"
                print_padded "$display_name" "$name_width" "${GREEN}"
                echo -ne "${GREEN}| ${NC}"
                print_padded "$gb" 10 "${GREEN}"
                echo -ne "${GREEN}| ${NC}"
                print_padded "$ext" 6 "${GREEN}"
                echo -ne "${GREEN}| ${NC}"
                print_padded "$media_info_str" 50 "${CYAN}"
                echo ""
            else
                display_name="${raw_name}"
                
                print_padded " ❌ " 6 "${RED}"
                echo -ne "${RED}| ${NC}"
                print_padded "$display_ep" 10 "${RED}"
                echo -ne "${RED}| ${NC}"
                print_padded "$display_name" "$name_width" "${RED}"
                echo -ne "${RED}| ${NC}"
                print_padded "MISSING" 10 "${RED}"
                echo -ne "${RED}| ${NC}"
                print_padded "-" 6 "${RED}"
                echo -ne "${RED}| ${NC}"
                print_padded "-" 50 "${RED}"
                echo ""
            fi
        done
        
        rm -rf "$index_dir"
    done
    
    echo ""
    read -p "Press Enter to return to menu..."
}

check_subtitles() {
    echo -e "${CYAN}📝 Check Subtitles${NC}"
    echo -n "Enter Path (Enter for current): "
    read input_path
    target_path="${input_path:-$WORK_PATH}"

    if [ ! -d "$target_path" ]; then
        echo -e "${RED}❌ Error: Directory not found.${NC}"
        read -p "Press Enter..."
        return
    fi

    echo -e "${BLUE}Scanning for subtitles: ${target_path}${NC}"
    echo ""

    # Header
    print_padded "FILE NAME" 50
    echo -n "| "
    print_padded "LANGUAGES" 30
    echo ""
    echo "----------------------------------------------------------------------------------"

    # Find video files and process them
    find "$target_path" -type f "${VIDEO_EXTS[@]}" | sort | while read -r filepath; do
        filename=$(basename "$filepath")
        dir=$(dirname "$filepath")
        
        # Basename without extension (e.g. Movie.mp4 -> Movie)
        file_base="${filename%.*}"
        
        langs_str=""
        found_langs=()
        
        # 1. Check exact match (Global)
        if [ -f "$dir/$file_base.srt" ] || [ -f "$dir/$file_base.sub" ]; then
            found_langs+=("GLOBAL")
        fi
        
        # 2. Check language specific (base.lang.srt or base.lang.sub)
        # Using simple glob loop to find potential matches
        # We look for files starting with base. and ending in .srt or .sub
        
        # Important: Quote variables to handle spaces, but leave glob unquoted
        for subfile in "$dir/$file_base".*.srt "$dir/$file_base".*.sub; do
            [ -e "$subfile" ] || continue
            
            sub_basename=$(basename "$subfile")
            
            # Remove file_base from the start: Movie.de.srt -> .de.srt
            remainder="${sub_basename#$file_base}"
            
            # Remove extension: .de.srt -> .de
            remainder="${remainder%.*}"
            
            # Remove leading dot: .de -> de
            lang_code="${remainder#.}"
            
            if [ -n "$lang_code" ]; then
                # Convert to Uppercase (Bash 4.0+)
                found_langs+=("${lang_code^^}")
            fi
        done
        
        # Determine status and color
        if [ ${#found_langs[@]} -gt 0 ]; then
            # Deduplicate and format languages using basic shell tools
            # echo array | sort uniq | replace newline with comma space
            unique_sorted_langs=$(printf "%s\n" "${found_langs[@]}" | sort -u | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
            langs_str="$unique_sorted_langs"
            row_color="${GREEN}"
        else
            langs_str="-"
            row_color="${RED}"
        fi
        
        # Truncate filename for display
        display_name="${filename}"
        if [ ${#display_name} -gt 48 ]; then
            display_name="${display_name:0:47}…"
        fi
        
        print_padded "$display_name" 50 "$row_color"
        echo -ne "${row_color}| ${NC}"
        print_padded "$langs_str" 30 "$row_color"
        echo ""
    done
    
    echo ""
    read -p "Press Enter to return..."
}

# ------------------------------------------------------------------------------
# Argument Parsing
# ------------------------------------------------------------------------------

while [[ $# -gt 0 ]]; do
    case $1 in
        -h|--help)
            show_help
            exit 0
            ;;
        -v|--version)
            show_version
            exit 0
            ;;
        -p|--path)
            WORK_PATH="$2"
            shift
            shift
            ;;
        *)
            echo "Unknown option: $1"
            show_help
            exit 1
            ;;
    esac
done

# ------------------------------------------------------------------------------
# Main Menu Loop
# ------------------------------------------------------------------------------

while true; do
    print_header
    echo -e "Current Path: ${BLUE}${WORK_PATH}${NC}"
    echo ""
    echo "1. 🎞️  Show Media Infos"
    echo "2. 📂  Compare Folders"
    echo "3. 📺  Check Missing Episodes (TMDB)"
    echo "4. 📝  Check Subtitles"
    echo "0. 🚪  Quit"
    echo ""
    echo -n "Select option (0-9): "
    read choice

    case $choice in
        1)
            scan_media_info "$WORK_PATH"
            ;;
        2)
            compare_folders
            ;;
        3)
            check_missing_episodes
            ;;
        4)
            check_subtitles
            ;;
        0)
            echo "Bye! 👋"
            exit 0
            ;;
        *)
            echo -e "${RED}Invalid option.${NC}"
            sleep 1
            ;;
    esac
done