277 lines
7.8 KiB
Markdown
277 lines
7.8 KiB
Markdown
# 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 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 <lockdata> 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",
|
||
"priority_explanation": "1st: Radarr import history, 2nd: TMDB digital release, 3rd: file mtime",
|
||
"date_sources": {
|
||
"radarr_import": {"date": "2025-07-08T03:30:04+00:00", "source": "radarr:history.import"},
|
||
"digital_release": {"date": "2023-05-15T00:00:00+00:00", "source": "tmdb:digital"}
|
||
},
|
||
"selected_date": "2025-07-08T03:30:04+00:00"
|
||
}
|
||
```
|
||
|
||
## 🐛 Troubleshooting
|
||
|
||
### Manual Imports Show File Dates Instead of Digital Releases
|
||
|
||
**Problem**: Movies manually imported show recent file dates like `2025-08-29T12:14:28+00:00`
|
||
|
||
**Solution**:
|
||
1. Enable TMDB integration:
|
||
```bash
|
||
# Add to your .env file
|
||
TMDB_API_KEY=your_tmdb_api_key
|
||
PREFER_DIGITAL_OVER_FILE_DATES=true
|
||
```
|
||
|
||
2. Test the priority logic:
|
||
```bash
|
||
curl "http://localhost:8080/debug/movie/tt0115963/priority"
|
||
```
|
||
|
||
3. Should show digital date selection when reasonable
|
||
|
||
### "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
|
||
```
|
||
|
||
### Digital Dates Not Found
|
||
|
||
**Problem**: `"external_apis": {"tmdb_enabled": false}`
|
||
|
||
**Solution**:
|
||
1. Add TMDB API key to `.env`
|
||
2. Restart container: `docker-compose restart`
|
||
3. Verify: `curl "http://localhost:8080/debug/movie/tt0115963/priority"`
|
||
|
||
## 📖 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. |