2025-09-10 08:29:04 -04:00
2025-09-10 08:29:04 -04:00
2025-09-10 08:29:04 -04:00
2025-09-09 17:13:14 -04:00
2025-09-09 08:36:25 -04:00
2025-09-09 10:59:45 -04:00
2025-09-09 16:00:36 -04:00
2025-09-09 14:57:58 -04:00
2025-09-09 08:50:21 -04:00
2025-09-09 08:50:21 -04:00
2025-09-10 08:29:04 -04:00
2025-09-09 15:18:58 -04:00
2025-08-29 16:16:52 -04:00
2025-09-10 08:29:04 -04:00
2025-09-09 11:12:01 -04:00
2025-09-09 10:20:46 -04:00
2025-09-09 17:13:14 -04:00
2025-09-09 08:50:21 -04:00
2025-09-09 08:50:21 -04:00
2025-09-09 08:50:21 -04:00
2025-09-09 08:50:21 -04:00
2025-09-09 16:43:47 -04:00

NFOGuard

*** Alpha in progress things are volatile use at your own risk ***

NFOGuard is a lightweight webhook service that locks in the true import date of your movies and TV episodes, ensuring that upgrades, renames, or reimports never bubble old media up as “new” in Emby, Jellyfin, or Plex.

It integrates with Sonarr and Radarr, listens for import/rename/upgrade events, and automatically manages .nfo files and filesystem timestamps to keep your library chronology consistent.


Features

  • Import Date Locking
    Captures the original date a movie or episode was imported and preserves it across upgrades.

  • NFO Management
    Creates and updates .nfo files for movies and TV episodes with consistent <dateadded> fields.
    Optionally adds <lockdata> to prevent metadata drift from scrapers.

  • Filesystem Time Fixing
    Updates file and directory modification times (mtime) to match the preserved import date.

  • Batching Support
    Groups multiple webhook events (e.g. whole-season downloads) to avoid thrashing.

  • Database Backing
    Uses SQLite to remember previously-seen imports, so dates persist across restarts.

  • Manual Scans
    Exposes endpoints to rescan and reconcile TV or movie libraries on demand.


🏗 How It Works

  1. Webhooks

    • Add NFOGuard as a webhook in Sonarr and Radarr.
    • Supported event types: Download, Upgrade, Rename.
  2. Processing

    • Maps Sonarr/Radarr paths to container paths.
    • Looks up IMDb IDs from folder names or payloads.
    • Writes .nfo files and updates file/dir mtimes.
    • Records everything in media_dates.db.
  3. Playback Apps

    • Emby/Jellyfin/Plex see consistent dateadded values.
    • Upgrades dont bubble to the top of “Recently Added.”

🚀 Quick Start (Docker Compose)

# 1. Copy configuration templates
cp .env.template .env
cp .env.secrets.template .env.secrets

# 2. Edit your configuration files
# .env - paths and preferences (safe to share)
# .env.secrets - API keys and passwords (never commit)

# 3. Start NFOGuard
docker-compose up -d

Docker Compose (Recommended):

version: '3.8'
services:
  nfoguard:
    image: ghcr.io/your-org/nfoguard:latest
    container_name: nfoguard
    ports:
      - "8080:8080"
    volumes:
      - /mnt/unionfs/Media:/media:rw
      - ./data:/app/data
      - ./.env:/app/.env:ro              # Main configuration
      - ./.env.secrets:/app/.env.secrets:ro  # Secure API keys
    restart: unless-stopped

Docker Run (Alternative):

docker run -d \
  --name=NFOGuard \
  -p 8080:8080 \
  -v /mnt/unionfs/Media:/media:rw \
  -v /opt/NFOGuard/data:/app/data \
  -v /opt/NFOGuard/.env:/app/.env:ro \
  -v /opt/NFOGuard/.env.secrets:/app/.env.secrets:ro \
  ghcr.io/your-org/NFOGuard:latest

🔧 Configuration Files

NFOGuard uses a secure two-file configuration system:

.env - Main Configuration (safe to share)

# Media paths
MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
TV_PATHS=/media/TV/tv,/media/TV/tv6

# Release date priority (digital, physical, theatrical)
RELEASE_DATE_PRIORITY=digital,physical,theatrical
PREFER_RELEASE_DATES_OVER_FILE_DATES=true
ALLOW_FILE_DATE_FALLBACK=false

# Smart date validation: prefer theatrical over unreasonably late dates
ENABLE_SMART_DATE_VALIDATION=true
MAX_RELEASE_DATE_GAP_YEARS=10

# Database connection
RADARR_DB_HOST=radarr-postgres
RADARR_DB_PORT=5432
RADARR_DB_NAME=radarr
RADARR_DB_USER=postgres

.env.secrets - Sensitive Data (never commit)

# Database password
RADARR_DB_PASSWORD=your_actual_password

# API keys
TMDB_API_KEY=your_tmdb_api_key
RADARR_API_KEY=your_radarr_api_key
SONARR_API_KEY=your_sonarr_api_key

Security Features:

  • 🔐 API key masking in logs automatically
  • 🚫 Git protection - secrets never committed
  • 🛠️ Easy debugging - main .env safe to share

🔌 API Endpoints

Webhook Endpoints

# Radarr webhook (automatic processing)
POST /webhook/radarr

# Sonarr webhook (automatic processing)  
POST /webhook/sonarr

Manual Processing

# Manual scan all media
curl -X POST "http://localhost:8080/manual/scan?scan_type=both"

# Manual scan TV only
curl -X POST "http://localhost:8080/manual/scan?scan_type=tv"

# Manual scan movies only
curl -X POST "http://localhost:8080/manual/scan?scan_type=movies"

# Manual scan specific path
curl -X POST "http://localhost:8080/manual/scan?path=/media/movies"

# Bulk update all movies from Radarr database
curl -X POST "http://localhost:8080/bulk/update"

Testing & Validation

# Test database connections
curl -X POST "http://localhost:8080/test/bulk-update"

# Test movie directory scanning
curl -X POST "http://localhost:8080/test/movie-scan"

# System health check
curl "http://localhost:8080/health"

# Database statistics
curl "http://localhost:8080/stats"

# Batch processing queue status
curl "http://localhost:8080/batch/status"

Debugging & Analysis

# Debug specific movie import date detection
curl "http://localhost:8080/debug/movie/tt1674782"

# Show complete import history analysis
curl "http://localhost:8080/debug/movie/tt1674782/history"

# Show date priority logic and available sources
curl "http://localhost:8080/debug/movie/tt1674782/priority"

# Debug TMDB API lookup step by step
curl "http://localhost:8080/debug/tmdb/tt1674782"

Response Examples

Health Check:

{
  "status": "healthy",
  "version": "0.5.1", 
  "database_status": "healthy",
  "radarr_database": {"status": "healthy", "movies": 1500}
}

Movie Debug:

{
  "detected_import_date": "2025-07-08T03:30:04+00:00",
  "import_source": "radarr:history.import",
  "movie_title": "Movie Name"
}

Priority Logic Debug:

{
  "movie_priority": "import_then_digital",
  "release_date_priority": ["digital", "physical", "theatrical"],
  "priority_explanation": "1st: Radarr import history, 2nd: Release dates (digital → physical → theatrical), 3rd: file mtime",
  "date_sources": {
    "radarr_import": {"date": "2025-08-29T12:14:28+00:00", "source": "radarr:db.file.dateAdded"},
    "digital_release": {"date": "1996-05-03T00:00:00+00:00", "source": "tmdb:theatrical"}
  },
  "file_date_detected": true,
  "would_prefer_digital": true,
  "selected_date": "1996-05-03T00:00:00+00:00",
  "selected_source": "tmdb:theatrical (preferred over file date)"
}

🎯 Release Date Priority System

NFOGuard uses a smart fallback system when Radarr has no valid import history:

1. Digital Release (streaming/VOD)

  • Netflix, Amazon Prime, iTunes, etc.
  • TMDB Source: Release type 4
  • Typical: 45-90 days after theatrical

2. Physical Release (DVD/Blu-ray)

  • Physical media release dates
  • TMDB Source: Release type 5
  • Typical: 90-120 days after theatrical

3. Theatrical Release (cinema)

  • Original movie theater release
  • TMDB Source: Release type 3
  • Most Authoritative: Always used when no digital/physical exists

🧠 Smart Date Validation

NEW: Automatically detects unreasonable date gaps and chooses the most logical release date.

How It Works

  • Compares release dates: Checks if digital/physical releases are unreasonably far from theatrical
  • Automatic fallback: If gap exceeds threshold, prefers theatrical date instead
  • Configurable threshold: MAX_RELEASE_DATE_GAP_YEARS=10 (default: 10 years)

Real Example: "The Craft (1996)"

# Without smart validation:
Physical Release: 2022-05-17 (26 years after theatrical!)
Selected: 2022# With smart validation:
Theatrical: 1996-05-03
Physical: 2022-05-17 (26 year gap > 10 year limit)
Selected: 1996 theatrical ✅ (smart fallback)

Configuration:

# Enable/disable smart validation
ENABLE_SMART_DATE_VALIDATION=true

# Maximum reasonable gap in years
MAX_RELEASE_DATE_GAP_YEARS=10

Configuration Examples

For "The Craft (1996)" (predates digital):

  • Digital: Not available (movie too old)
  • Physical: DVD from 2000 (too late)
  • Theatrical: 1996-05-03Selected

For "Top Gun Maverick (2022)" (modern movie):

  • Digital: 2022-08-23Selected (your priority)
  • ⏭️ Physical: 2022-10-31 (skipped due to priority)
  • ⏭️ Theatrical: 2022-05-27 (skipped due to priority)

🐛 Troubleshooting

Manual Imports Show File Dates Instead of Release Dates

Problem: Movies manually imported show recent file dates like 2025-08-29T12:14:28+00:00

Solution:

  1. Enable TMDB integration and configure fallback priority:

    # Add to your .env file
    TMDB_API_KEY=your_tmdb_api_key
    PREFER_RELEASE_DATES_OVER_FILE_DATES=true
    RELEASE_DATE_PRIORITY=digital,physical,theatrical
    
  2. Test the priority logic:

    curl "http://localhost:8080/debug/movie/tt0115963/priority"
    
  3. Should show release date selection based on your priority order

"0 Movies Processed" During Manual Scan

Problem: Manual scan reports processing 0 movies

Solution:

  1. Test directory scanning:

    curl -X POST "http://localhost:8080/test/movie-scan"
    
  2. Verify paths in your .env file match your actual structure:

    # Your structure: /media/Movies/movies/
    MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
    
  3. Ensure movie directories have IMDb tags:

    Movie Name [imdb-tt1234567] (2023)/
    

Database Connection Issues

Problem: "Radarr database connection failed"

Solution:

  1. Test database connectivity:

    curl -X POST "http://localhost:8080/test/bulk-update"
    
  2. Verify database credentials in .env:

    RADARR_DB_HOST=radarr-postgres
    RADARR_DB_PASSWORD=your_actual_password
    

Release Dates Not Found

Problem: "external_apis": {"tmdb_enabled": false} or no release dates detected

Solution:

  1. Add TMDB API key and configure fallback order:
    # Add to your .env
    TMDB_API_KEY=your_api_key
    RELEASE_DATE_PRIORITY=digital,physical,theatrical
    
  2. Restart container: docker-compose restart
  3. Test all release types: curl "http://localhost:8080/debug/tmdb/tt0115963"
  4. Verify priority logic: curl "http://localhost:8080/debug/movie/tt0115963/priority"

Customizing Release Date Priority

Default Order: digital,physical,theatrical (your preference) Alternative Orders:

# Prefer theatrical dates for older movies
RELEASE_DATE_PRIORITY=theatrical,digital,physical

# Physical media collector preference  
RELEASE_DATE_PRIORITY=physical,digital,theatrical

# Digital-first modern preference
RELEASE_DATE_PRIORITY=digital,theatrical,physical

Disabling File Date Fallbacks

Problem: Don't want file modification dates used at all

Solution: Disable file date fallbacks completely

# Add to your .env
ALLOW_FILE_DATE_FALLBACK=false

Result:

  • Movies with release dates get processed normally
  • ⚠️ Movies with NO release dates get skipped (no NFO created)
  • 🔇 No more "Using file dateAdded as fallback" warnings

📖 Example Workflow

You add The Blacklist in Sonarr.

Sonarr downloads S01E01 → NFOGuard logs the import date in DB and .nfo.

Months later, a better-quality upgrade replaces the file.

NFOGuard updates the .nfo and mtime, but keeps the original import date.

Emby/Jellyfin/Plex see the file as unchanged in chronology.

Result: no more "old shows" showing up in "Recently Added."

🔍 Logging & Debugging

Enable verbose logging

DEBUG=true

Check container logs

docker logs sonarr-nfo-cache

Debug specific movie import detection

curl http://localhost:8080/debug/movie/tt1674782

Manual scan with verbose output

curl -X POST "http://localhost:8080/manual/scan?scan_type=movies"

File logs (inside container)

/app/data/logs/nfoguard.log

⚙️ Environment Variables (v0.2.1+) Variable Default Description RADARR_ROOT_FOLDERS (required) What Radarr sees: /mnt/unionfs/Media/Movies/movies SONARR_ROOT_FOLDERS (required) What Sonarr sees: /mnt/unionfs/Media/TV/tv
DOWNLOAD_PATH_INDICATORS (see example) Paths that indicate downloads vs existing files MOVIE_PRIORITY import_then_digital Strategy: import_then_digital or digital_then_import MOVIE_POLL_MODE always When to query APIs: always, if_missing, never MOVIE_DATE_UPDATE_MODE backfill_only Update behavior: backfill_only or overwrite

📜 License MIT License do what you want, just dont blame us if your timestamps go timey-wimey.

S
Description
No description provided
Readme MIT 19 MiB
Languages
Python 81%
JavaScript 11.3%
HTML 4.7%
CSS 2.8%
Dockerfile 0.2%