# 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 `` fields. Optionally adds `` 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 don’t bubble to the top of β€œRecently Added.” --- ## πŸš€ Quick Start (Docker) ```bash docker run -d \ --name=NFOGuard \ -p 8080:8080 \ -v /mnt/unionfs/Media:/media:rw \ -v /opt/NFOGuard/data:/app/data \ -e TV_PATHS="/media/TV/tv,/media/TV/tv6" \ -e MOVIE_PATHS="/media/Movies/movies,/media/Movies/movies6" \ ghcr.io/your-org/NFOGuard:latest βš™οΈ Environment Variables Variable Default Description TV_PATHS /media/tv,/media/tv6 Comma-separated list of TV root paths MOVIE_PATHS /media/movies,/media/movies6 Comma-separated list of Movie root paths DB_PATH /app/data/media_dates.db Path to SQLite database MANAGE_NFO true Write/update NFO files FIX_DIR_MTIMES true Sync directory/file mtimes to import date LOCK_METADATA true Add in NFOs BATCH_DELAY 5.0 Delay before processing batched events DEBUG false Enable verbose logging πŸ”Œ API Endpoints ### Webhook Endpoints ```bash # Radarr webhook (automatic processing) POST /webhook/radarr # Sonarr webhook (automatic processing) POST /webhook/sonarr ``` ### Manual Processing ```bash # 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 ```bash # 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 ```bash # 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**: ```json { "status": "healthy", "version": "0.5.1", "database_status": "healthy", "radarr_database": {"status": "healthy", "movies": 1500} } ``` **Movie Debug**: ```json { "detected_import_date": "2025-07-08T03:30:04+00:00", "import_source": "radarr:history.import", "movie_title": "Movie Name" } ``` **Priority Logic Debug**: ```json { "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 ### Configuration Examples **For "The Craft (1996)"** (predates digital): - ❌ Digital: Not available (movie too old) - ❌ Physical: DVD from 2000 (too late) - βœ… **Theatrical: 1996-05-03** ← **Selected** **For "Top Gun Maverick (2022)"** (modern movie): - βœ… **Digital: 2022-08-23** ← **Selected** (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: ```bash # 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: ```bash 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: ```bash curl -X POST "http://localhost:8080/test/movie-scan" ``` 2. Verify paths in your `.env` file match your actual structure: ```bash # 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: ```bash curl -X POST "http://localhost:8080/test/bulk-update" ``` 2. Verify database credentials in `.env`: ```bash 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: ```bash # 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**: ```bash # 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 ```bash # 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 don’t blame us if your timestamps go timey-wimey.