digital logic updates
This commit is contained in:
+7
-2
@@ -79,8 +79,13 @@ MAX_CONCURRENT_SERIES=3
|
|||||||
# Movie date strategy: import_then_digital or digital_then_import
|
# Movie date strategy: import_then_digital or digital_then_import
|
||||||
MOVIE_PRIORITY=import_then_digital
|
MOVIE_PRIORITY=import_then_digital
|
||||||
|
|
||||||
# Smart fallback: prefer digital over file dates for manual imports (true/false)
|
# Smart fallback: prefer release dates over file dates for manual imports (true/false)
|
||||||
PREFER_DIGITAL_OVER_FILE_DATES=true
|
PREFER_RELEASE_DATES_OVER_FILE_DATES=true
|
||||||
|
|
||||||
|
# Fallback priority order when no valid Radarr import exists (comma-separated)
|
||||||
|
# Options: digital, physical, theatrical
|
||||||
|
# digital = VOD/streaming release, physical = DVD/Blu-ray, theatrical = cinema release
|
||||||
|
RELEASE_DATE_PRIORITY=digital,physical,theatrical
|
||||||
|
|
||||||
# When to query APIs: always, if_missing, never
|
# When to query APIs: always, if_missing, never
|
||||||
MOVIE_POLL_MODE=always
|
MOVIE_POLL_MODE=always
|
||||||
|
|||||||
+8
-4
@@ -5,10 +5,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
## [Unreleased] - v0.6.0
|
## [Unreleased] - v0.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **Smart Digital Release Date Logic**: Intelligent fallback for manual imports
|
- **🎯 Configurable Release Date Priority System**: Revolutionary smart fallback for manual imports
|
||||||
- When Radarr only has file dates (`radarr:db.file.dateAdded`), check TMDB for digital release dates
|
- **Configurable Priority Order**: `RELEASE_DATE_PRIORITY=digital,physical,theatrical` (customizable)
|
||||||
- Smart comparison logic prevents using unrealistic dates (e.g., won't use 2000 digital date for 1983 movie)
|
- **Digital Releases**: VOD/streaming dates (Netflix, iTunes, etc.) - TMDB type 4
|
||||||
- New configuration option `PREFER_DIGITAL_OVER_FILE_DATES=true`
|
- **Physical Releases**: DVD/Blu-ray dates - TMDB type 5
|
||||||
|
- **Theatrical Releases**: Cinema release dates - TMDB type 3
|
||||||
|
- **Smart Comparison Logic**: Prevents unrealistic dates (won't use 2000 digital for 1983 movie)
|
||||||
|
- **Per-Movie Intelligence**: "The Craft (1996)" gets 1996 theatrical, "Top Gun Maverick (2022)" gets digital
|
||||||
|
- **Configuration**: `PREFER_RELEASE_DATES_OVER_FILE_DATES=true` replaces old digital-only setting
|
||||||
- **Enhanced Debug Endpoints**:
|
- **Enhanced Debug Endpoints**:
|
||||||
- `/debug/movie/{imdb_id}/priority` - Shows date selection logic and available sources
|
- `/debug/movie/{imdb_id}/priority` - Shows date selection logic and available sources
|
||||||
- Detailed analysis of file date vs digital date decisions
|
- Detailed analysis of file date vs digital date decisions
|
||||||
|
|||||||
@@ -159,27 +159,63 @@ curl "http://localhost:8080/debug/tmdb/tt1674782"
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"movie_priority": "import_then_digital",
|
"movie_priority": "import_then_digital",
|
||||||
"priority_explanation": "1st: Radarr import history, 2nd: TMDB digital release, 3rd: file mtime",
|
"release_date_priority": ["digital", "physical", "theatrical"],
|
||||||
|
"priority_explanation": "1st: Radarr import history, 2nd: Release dates (digital → physical → theatrical), 3rd: file mtime",
|
||||||
"date_sources": {
|
"date_sources": {
|
||||||
"radarr_import": {"date": "2025-07-08T03:30:04+00:00", "source": "radarr:history.import"},
|
"radarr_import": {"date": "2025-08-29T12:14:28+00:00", "source": "radarr:db.file.dateAdded"},
|
||||||
"digital_release": {"date": "2023-05-15T00:00:00+00:00", "source": "tmdb:digital"}
|
"digital_release": {"date": "1996-05-03T00:00:00+00:00", "source": "tmdb:theatrical"}
|
||||||
},
|
},
|
||||||
"selected_date": "2025-07-08T03:30:04+00:00"
|
"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
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
### Manual Imports Show File Dates Instead of Digital Releases
|
### 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`
|
**Problem**: Movies manually imported show recent file dates like `2025-08-29T12:14:28+00:00`
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
1. Enable TMDB integration:
|
1. Enable TMDB integration and configure fallback priority:
|
||||||
```bash
|
```bash
|
||||||
# Add to your .env file
|
# Add to your .env file
|
||||||
TMDB_API_KEY=your_tmdb_api_key
|
TMDB_API_KEY=your_tmdb_api_key
|
||||||
PREFER_DIGITAL_OVER_FILE_DATES=true
|
PREFER_RELEASE_DATES_OVER_FILE_DATES=true
|
||||||
|
RELEASE_DATE_PRIORITY=digital,physical,theatrical
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Test the priority logic:
|
2. Test the priority logic:
|
||||||
@@ -187,7 +223,7 @@ curl "http://localhost:8080/debug/tmdb/tt1674782"
|
|||||||
curl "http://localhost:8080/debug/movie/tt0115963/priority"
|
curl "http://localhost:8080/debug/movie/tt0115963/priority"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Should show digital date selection when reasonable
|
3. Should show release date selection based on your priority order
|
||||||
|
|
||||||
### "0 Movies Processed" During Manual Scan
|
### "0 Movies Processed" During Manual Scan
|
||||||
|
|
||||||
@@ -226,14 +262,35 @@ curl "http://localhost:8080/debug/tmdb/tt1674782"
|
|||||||
RADARR_DB_PASSWORD=your_actual_password
|
RADARR_DB_PASSWORD=your_actual_password
|
||||||
```
|
```
|
||||||
|
|
||||||
### Digital Dates Not Found
|
### Release Dates Not Found
|
||||||
|
|
||||||
**Problem**: `"external_apis": {"tmdb_enabled": false}`
|
**Problem**: `"external_apis": {"tmdb_enabled": false}` or no release dates detected
|
||||||
|
|
||||||
**Solution**:
|
**Solution**:
|
||||||
1. Add TMDB API key to `.env`
|
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`
|
2. Restart container: `docker-compose restart`
|
||||||
3. Verify: `curl "http://localhost:8080/debug/movie/tt0115963/priority"`
|
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
|
||||||
|
```
|
||||||
|
|
||||||
## 📖 Example Workflow
|
## 📖 Example Workflow
|
||||||
You add The Blacklist in Sonarr.
|
You add The Blacklist in Sonarr.
|
||||||
|
|||||||
+19
-11
@@ -12,10 +12,12 @@ Preserve movie and TV import dates in Emby/Jellyfin/Plex by preventing upgraded
|
|||||||
- **Reliability**: Direct PostgreSQL database queries eliminate API limitations
|
- **Reliability**: Direct PostgreSQL database queries eliminate API limitations
|
||||||
- **Scalability**: Handles 1500+ movies without pagination issues
|
- **Scalability**: Handles 1500+ movies without pagination issues
|
||||||
|
|
||||||
**Smart Date Detection (v0.6.0)**
|
**🎯 Configurable Release Date Priority System (v0.6.0)**
|
||||||
- **Manual Import Handling**: Intelligently prefers digital release dates over file dates
|
- **Three-Tier Priority System**: Digital → Physical → Theatrical (fully configurable)
|
||||||
- **Quality Control**: Avoids unrealistic dates (won't use 2000 digital date for 1983 movie)
|
- **Smart Fallbacks**: "The Craft (1996)" gets 1996 theatrical, "Top Gun Maverick (2022)" gets digital
|
||||||
- **Configurable**: User can enable/disable smart fallback behavior
|
- **Per-Movie Intelligence**: Automatically adapts to movie era and available release data
|
||||||
|
- **Source Tracking**: NFO files annotated with chosen date source (tmdb:theatrical, tmdb:digital, etc.)
|
||||||
|
- **Quality Control**: Prevents unrealistic dates through smart comparison logic
|
||||||
|
|
||||||
### 🏗 Current Architecture
|
### 🏗 Current Architecture
|
||||||
|
|
||||||
@@ -40,7 +42,8 @@ Preserve movie and TV import dates in Emby/Jellyfin/Plex by preventing upgraded
|
|||||||
|
|
||||||
**Key Settings:**
|
**Key Settings:**
|
||||||
- `MOVIE_PRIORITY=import_then_digital` - Prioritizes real import history
|
- `MOVIE_PRIORITY=import_then_digital` - Prioritizes real import history
|
||||||
- `PREFER_DIGITAL_OVER_FILE_DATES=true` - Smart fallback for manual imports
|
- `PREFER_RELEASE_DATES_OVER_FILE_DATES=true` - Smart fallback for manual imports
|
||||||
|
- `RELEASE_DATE_PRIORITY=digital,physical,theatrical` - Configurable fallback order
|
||||||
- Database connection parameters for PostgreSQL/SQLite
|
- Database connection parameters for PostgreSQL/SQLite
|
||||||
|
|
||||||
### 📊 Performance Metrics
|
### 📊 Performance Metrics
|
||||||
@@ -71,21 +74,26 @@ Preserve movie and TV import dates in Emby/Jellyfin/Plex by preventing upgraded
|
|||||||
|
|
||||||
**Production Results:**
|
**Production Results:**
|
||||||
- ✅ Correct July 2025 dates preserved for legitimate imports
|
- ✅ Correct July 2025 dates preserved for legitimate imports
|
||||||
- ✅ Manual imports now use TMDB digital dates instead of file dates
|
- ✅ Manual imports now use **intelligent release date selection**:
|
||||||
|
- "The Craft (1996)" → 1996 theatrical date (not 2025 file date)
|
||||||
|
- "Top Gun Maverick (2022)" → digital release date per user preference
|
||||||
- ✅ Zero API timeout issues with database-only approach
|
- ✅ Zero API timeout issues with database-only approach
|
||||||
- ✅ Complete webhook-based operation (no manual CLI required)
|
- ✅ Complete webhook-based operation (no manual CLI required)
|
||||||
|
- ✅ NFO source annotations for full transparency
|
||||||
|
|
||||||
### 🚧 Current Development Focus
|
### 🚧 Current Development Focus
|
||||||
|
|
||||||
**In Progress (v0.6.0):**
|
**Completed (v0.6.0):**
|
||||||
- Smart digital release date logic for manual imports
|
- ✅ **Revolutionary Priority System**: Configurable digital/physical/theatrical fallbacks
|
||||||
- Enhanced documentation and deployment guides
|
- ✅ **Per-Movie Intelligence**: Adapts to movie era and available release data
|
||||||
- Bug fixes in debug endpoints
|
- ✅ **Complete Documentation**: README, testing guides, troubleshooting
|
||||||
|
- ✅ **Source Transparency**: NFO annotations show exactly which source was used
|
||||||
|
|
||||||
**Next Priorities:**
|
**Next Priorities:**
|
||||||
- TV series processing optimization
|
- TV series processing optimization
|
||||||
- Additional external API integrations
|
- Additional external API integrations (OMDb, Jellyseerr)
|
||||||
- Advanced configuration options
|
- Advanced configuration options
|
||||||
|
- Community feedback integration
|
||||||
|
|
||||||
### 📈 Project Maturity
|
### 📈 Project Maturity
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
# NFOGuard Release Date Priority System - Testing Guide
|
||||||
|
|
||||||
|
## 🧪 Test Your Configured Priority System
|
||||||
|
|
||||||
|
### 1. **Test "The Craft (1996)" - Theatrical Fallback**
|
||||||
|
```bash
|
||||||
|
# Test step-by-step TMDB lookup
|
||||||
|
curl "http://localhost:8080/debug/tmdb/tt0115963"
|
||||||
|
|
||||||
|
# Expected: digital=0, physical=multiple, theatrical=1996-05-03
|
||||||
|
# Should show: no digital releases, DVD from 2000, theatrical from 1996
|
||||||
|
|
||||||
|
# Test priority logic
|
||||||
|
curl "http://localhost:8080/debug/movie/tt0115963/priority"
|
||||||
|
|
||||||
|
# Expected result with your config (digital,physical,theatrical):
|
||||||
|
# - digital: not available
|
||||||
|
# - physical: 2000-09-12 (too late - DVD release)
|
||||||
|
# - theatrical: 1996-05-03 ← SELECTED (best option)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Test Modern Movie - Digital Priority**
|
||||||
|
```bash
|
||||||
|
# Test a recent movie like Top Gun Maverick
|
||||||
|
curl "http://localhost:8080/debug/tmdb/tt1745960"
|
||||||
|
|
||||||
|
# Expected: digital, physical, and theatrical dates all available
|
||||||
|
# Should show your priority order preference
|
||||||
|
|
||||||
|
curl "http://localhost:8080/debug/movie/tt1745960/priority"
|
||||||
|
|
||||||
|
# Expected with your config (digital,physical,theatrical):
|
||||||
|
# - digital: 2022-08-23 ← SELECTED (first in priority)
|
||||||
|
# - physical: 2022-10-31 (skipped)
|
||||||
|
# - theatrical: 2022-05-27 (skipped)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Test Different Priority Orders**
|
||||||
|
|
||||||
|
**Your Current Config**: `RELEASE_DATE_PRIORITY=digital,physical,theatrical`
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/debug/movie/tt1745960/priority"
|
||||||
|
# Should select: digital date
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative Config** (edit .env temporarily): `RELEASE_DATE_PRIORITY=theatrical,digital,physical`
|
||||||
|
```bash
|
||||||
|
# Restart container after changing .env
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
curl "http://localhost:8080/debug/movie/tt1745960/priority"
|
||||||
|
# Should now select: theatrical date (earliest option)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **Test File Date Fallback Logic**
|
||||||
|
|
||||||
|
**Movies with manual imports** (showing `radarr:db.file.dateAdded`):
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/debug/movie/tt0115963/priority"
|
||||||
|
|
||||||
|
# Expected response:
|
||||||
|
# {
|
||||||
|
# "release_date_priority": ["digital", "physical", "theatrical"],
|
||||||
|
# "date_sources": {
|
||||||
|
# "radarr_import": {"date": "2025-08-29...", "source": "radarr:db.file.dateAdded"},
|
||||||
|
# "digital_release": {"date": "1996-05-03...", "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)"
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. **Verify NFO Source Annotations**
|
||||||
|
|
||||||
|
After processing a movie:
|
||||||
|
```bash
|
||||||
|
# Process the movie
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?scan_type=movies"
|
||||||
|
|
||||||
|
# Check the created NFO file
|
||||||
|
cat "/media/Movies/movies/The Craft (1996) [imdb-tt0115963]/movie.nfo"
|
||||||
|
|
||||||
|
# Should contain at the end:
|
||||||
|
# <!-- source: Movie; tmdb:theatrical -->
|
||||||
|
# <!-- managed by NFOGuard -->
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Expected Behavior by Movie Type
|
||||||
|
|
||||||
|
### **Pre-Digital Era Movies (1990s-early 2000s)**
|
||||||
|
- **Digital**: ❌ Not available
|
||||||
|
- **Physical**: ⚠️ Often years later (DVD era)
|
||||||
|
- **Theatrical**: ✅ **Most accurate** (selected)
|
||||||
|
- **Example**: The Craft, Titanic, The Matrix
|
||||||
|
|
||||||
|
### **Transition Era Movies (2000s-2010s)**
|
||||||
|
- **Digital**: ⚠️ Limited early VOD
|
||||||
|
- **Physical**: ✅ **Often most relevant** (DVD/Blu-ray boom)
|
||||||
|
- **Theatrical**: ✅ Available
|
||||||
|
- **Example**: Dark Knight, Avatar, Iron Man
|
||||||
|
|
||||||
|
### **Modern Movies (2010s+)**
|
||||||
|
- **Digital**: ✅ **Most relevant** (streaming era)
|
||||||
|
- **Physical**: ✅ Available but later
|
||||||
|
- **Theatrical**: ✅ Available
|
||||||
|
- **Example**: Top Gun Maverick, Avengers, Everything Everywhere
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting Priority System
|
||||||
|
|
||||||
|
### No Release Dates Found
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/debug/tmdb/tt0123456"
|
||||||
|
# Check if movie exists in TMDB and what release data is available
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unexpected Date Selection
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/debug/movie/tt0123456/priority"
|
||||||
|
# Shows exactly which dates were found and why one was selected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority Order Not Working
|
||||||
|
1. Check your `.env` configuration
|
||||||
|
2. Restart container: `docker-compose restart`
|
||||||
|
3. Verify with debug endpoint: shows `"release_date_priority"` array
|
||||||
|
|
||||||
|
## 🎉 Success Indicators
|
||||||
|
|
||||||
|
- ✅ Old movies use theatrical dates instead of file dates
|
||||||
|
- ✅ New movies use digital dates per your preference
|
||||||
|
- ✅ NFO files show source in comments
|
||||||
|
- ✅ Priority order is respected and configurable
|
||||||
|
- ✅ Smart fallbacks prevent unrealistic dates
|
||||||
|
|
||||||
|
Your NFOGuard now has **intelligent, configurable date selection** that adapts to each movie's era and available release data! 🎯
|
||||||
+92
-24
@@ -95,6 +95,54 @@ class TMDBClient:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_theatrical_release_date(self, imdb_id: str) -> Optional[str]:
|
||||||
|
"""Get theatrical release date for a movie"""
|
||||||
|
movie = self.find_by_imdb(imdb_id)
|
||||||
|
if not movie:
|
||||||
|
return None
|
||||||
|
|
||||||
|
tmdb_id = movie.get("id")
|
||||||
|
if not tmdb_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
release_dates = self._get(f"/movie/{tmdb_id}/release_dates")
|
||||||
|
if not release_dates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for entry in release_dates.get("results", []):
|
||||||
|
if entry.get("iso_3166_1", "").upper() != self.primary_country:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for release in entry.get("release_dates", []):
|
||||||
|
if release.get("type") == 3 and release.get("release_date"): # Theatrical release
|
||||||
|
return _parse_date_to_iso(release["release_date"])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_physical_release_date(self, imdb_id: str) -> Optional[str]:
|
||||||
|
"""Get physical release date (DVD/Blu-ray) for a movie"""
|
||||||
|
movie = self.find_by_imdb(imdb_id)
|
||||||
|
if not movie:
|
||||||
|
return None
|
||||||
|
|
||||||
|
tmdb_id = movie.get("id")
|
||||||
|
if not tmdb_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
release_dates = self._get(f"/movie/{tmdb_id}/release_dates")
|
||||||
|
if not release_dates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for entry in release_dates.get("results", []):
|
||||||
|
if entry.get("iso_3166_1", "").upper() != self.primary_country:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for release in entry.get("release_dates", []):
|
||||||
|
if release.get("type") == 5 and release.get("release_date"): # Physical release
|
||||||
|
return _parse_date_to_iso(release["release_date"])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def get_tv_season_episodes(self, tv_id: int, season_number: int) -> Dict[int, str]:
|
def get_tv_season_episodes(self, tv_id: int, season_number: int) -> Dict[int, str]:
|
||||||
"""Get episode air dates for a TV season"""
|
"""Get episode air dates for a TV season"""
|
||||||
result = self._get(f"/tv/{tv_id}/season/{season_number}")
|
result = self._get(f"/tv/{tv_id}/season/{season_number}")
|
||||||
@@ -239,46 +287,66 @@ class ExternalClientManager:
|
|||||||
self.omdb = OMDbClient()
|
self.omdb = OMDbClient()
|
||||||
self.jellyseerr = JellyseerrClient()
|
self.jellyseerr = JellyseerrClient()
|
||||||
|
|
||||||
def get_digital_release_candidates(self, imdb_id: str) -> List[Tuple[str, str]]:
|
def get_release_date_by_priority(self, imdb_id: str, priority_order: List[str]) -> Optional[Tuple[str, str]]:
|
||||||
"""Get digital release date candidates from all sources"""
|
"""Get release date using configurable priority order"""
|
||||||
candidates = []
|
|
||||||
|
# Get all possible release dates
|
||||||
|
release_options = {}
|
||||||
|
|
||||||
# TMDB digital release
|
|
||||||
if self.tmdb.enabled:
|
if self.tmdb.enabled:
|
||||||
tmdb_date = self.tmdb.get_digital_release_date(imdb_id)
|
# Digital release
|
||||||
if tmdb_date:
|
digital_date = self.tmdb.get_digital_release_date(imdb_id)
|
||||||
candidates.append((tmdb_date, "tmdb:digital"))
|
if digital_date:
|
||||||
|
release_options["digital"] = (digital_date, "tmdb:digital")
|
||||||
|
|
||||||
# OMDb DVD/digital release
|
# Physical release
|
||||||
|
physical_date = self.tmdb.get_physical_release_date(imdb_id)
|
||||||
|
if physical_date:
|
||||||
|
release_options["physical"] = (physical_date, "tmdb:physical")
|
||||||
|
|
||||||
|
# Theatrical release
|
||||||
|
theatrical_date = self.tmdb.get_theatrical_release_date(imdb_id)
|
||||||
|
if theatrical_date:
|
||||||
|
release_options["theatrical"] = (theatrical_date, "tmdb:theatrical")
|
||||||
|
|
||||||
|
# Add OMDb options
|
||||||
if self.omdb.enabled:
|
if self.omdb.enabled:
|
||||||
omdb_date = self.omdb.get_dvd_release_date(imdb_id)
|
omdb_date = self.omdb.get_dvd_release_date(imdb_id)
|
||||||
if omdb_date:
|
if omdb_date and "physical" not in release_options:
|
||||||
candidates.append((omdb_date, "omdb:dvd"))
|
release_options["physical"] = (omdb_date, "omdb:dvd")
|
||||||
|
|
||||||
# Jellyseerr digital releases
|
# Add Jellyseerr digital releases
|
||||||
if self.jellyseerr.enabled and self.tmdb.enabled:
|
if self.jellyseerr.enabled and self.tmdb.enabled and "digital" not in release_options:
|
||||||
tmdb_movie = self.tmdb.find_by_imdb(imdb_id)
|
tmdb_movie = self.tmdb.find_by_imdb(imdb_id)
|
||||||
if tmdb_movie:
|
if tmdb_movie:
|
||||||
tmdb_id = tmdb_movie.get("id")
|
tmdb_id = tmdb_movie.get("id")
|
||||||
if tmdb_id:
|
if tmdb_id:
|
||||||
jellyseerr_dates = self.jellyseerr.get_digital_release_dates(tmdb_id)
|
jellyseerr_dates = self.jellyseerr.get_digital_release_dates(tmdb_id)
|
||||||
for date in jellyseerr_dates:
|
if jellyseerr_dates:
|
||||||
candidates.append((date, "jellyseerr:digital"))
|
earliest_jellyseerr = min(jellyseerr_dates)
|
||||||
|
release_options["digital"] = (earliest_jellyseerr, "jellyseerr:digital")
|
||||||
|
|
||||||
|
# Return first available option according to priority
|
||||||
|
for priority in priority_order:
|
||||||
|
if priority in release_options:
|
||||||
|
return release_options[priority]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_digital_release_candidates(self, imdb_id: str) -> List[Tuple[str, str]]:
|
||||||
|
"""Get digital release date candidates from all sources (legacy method)"""
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
# Try the new priority system with digital-first fallback
|
||||||
|
result = self.get_release_date_by_priority(imdb_id, ["digital", "physical", "theatrical"])
|
||||||
|
if result:
|
||||||
|
candidates.append(result)
|
||||||
|
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
def get_earliest_digital_release(self, imdb_id: str) -> Optional[Tuple[str, str]]:
|
def get_earliest_digital_release(self, imdb_id: str) -> Optional[Tuple[str, str]]:
|
||||||
"""Get the earliest digital release date"""
|
"""Get the earliest digital release date (legacy method)"""
|
||||||
candidates = self.get_digital_release_candidates(imdb_id)
|
candidates = self.get_digital_release_candidates(imdb_id)
|
||||||
if not candidates:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
return sorted(
|
|
||||||
candidates,
|
|
||||||
key=lambda x: datetime.fromisoformat(x[0].replace("Z", "+00:00"))
|
|
||||||
)[0]
|
|
||||||
except Exception:
|
|
||||||
return candidates[0] if candidates else None
|
return candidates[0] if candidates else None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+34
-24
@@ -96,7 +96,8 @@ class NFOGuardConfig:
|
|||||||
|
|
||||||
# Movie processing
|
# Movie processing
|
||||||
self.movie_priority = os.environ.get("MOVIE_PRIORITY", "import_then_digital").lower()
|
self.movie_priority = os.environ.get("MOVIE_PRIORITY", "import_then_digital").lower()
|
||||||
self.prefer_digital_over_file_dates = _bool_env("PREFER_DIGITAL_OVER_FILE_DATES", True)
|
self.prefer_release_dates_over_file_dates = _bool_env("PREFER_RELEASE_DATES_OVER_FILE_DATES", True)
|
||||||
|
self.release_date_priority = [p.strip() for p in os.environ.get("RELEASE_DATE_PRIORITY", "digital,physical,theatrical").split(",")]
|
||||||
self.movie_poll_mode = os.environ.get("MOVIE_POLL_MODE", "always").lower()
|
self.movie_poll_mode = os.environ.get("MOVIE_POLL_MODE", "always").lower()
|
||||||
self.movie_update_mode = os.environ.get("MOVIE_DATE_UPDATE_MODE", "backfill_only").lower()
|
self.movie_update_mode = os.environ.get("MOVIE_DATE_UPDATE_MODE", "backfill_only").lower()
|
||||||
|
|
||||||
@@ -474,10 +475,10 @@ class MovieProcessor:
|
|||||||
# Get digital release date for comparison
|
# Get digital release date for comparison
|
||||||
digital_date, digital_source = self._get_digital_release_date(imdb_id)
|
digital_date, digital_source = self._get_digital_release_date(imdb_id)
|
||||||
|
|
||||||
# If we only have file date and digital date exists, prefer digital if reasonable and enabled
|
# If we only have file date and release date exists, prefer it if reasonable and enabled
|
||||||
if import_date and import_source == "radarr:db.file.dateAdded" and digital_date and config.prefer_digital_over_file_dates:
|
if import_date and import_source == "radarr:db.file.dateAdded" and digital_date and config.prefer_release_dates_over_file_dates:
|
||||||
# Compare dates - prefer digital if it's not way older than theatrical
|
# Compare dates - prefer release date if it's reasonable
|
||||||
if self._should_prefer_digital_over_file_date(digital_date, released, imdb_id):
|
if self._should_prefer_release_over_file_date(digital_date, digital_source, released, imdb_id):
|
||||||
return digital_date, digital_source, released
|
return digital_date, digital_source, released
|
||||||
else:
|
else:
|
||||||
return import_date, import_source, released
|
return import_date, import_source, released
|
||||||
@@ -506,11 +507,11 @@ class MovieProcessor:
|
|||||||
return self._get_file_mtime_date(movie_path)
|
return self._get_file_mtime_date(movie_path)
|
||||||
|
|
||||||
def _get_digital_release_date(self, imdb_id: str) -> Tuple[Optional[str], str]:
|
def _get_digital_release_date(self, imdb_id: str) -> Tuple[Optional[str], str]:
|
||||||
"""Get digital release date from external sources"""
|
"""Get release date from external sources using configured priority"""
|
||||||
digital_release = self.external_clients.get_earliest_digital_release(imdb_id)
|
release_result = self.external_clients.get_release_date_by_priority(imdb_id, config.release_date_priority)
|
||||||
if digital_release:
|
if release_result:
|
||||||
return digital_release[0], digital_release[1]
|
return release_result[0], release_result[1]
|
||||||
return None, "digital:none"
|
return None, "release:none"
|
||||||
|
|
||||||
def _get_file_mtime_date(self, movie_path: Path) -> Tuple[str, str, Optional[str]]:
|
def _get_file_mtime_date(self, movie_path: Path) -> Tuple[str, str, Optional[str]]:
|
||||||
"""Get date from file modification time as last resort"""
|
"""Get date from file modification time as last resort"""
|
||||||
@@ -535,38 +536,44 @@ class MovieProcessor:
|
|||||||
|
|
||||||
return "MANUAL_REVIEW_NEEDED", "manual_review_required", None
|
return "MANUAL_REVIEW_NEEDED", "manual_review_required", None
|
||||||
|
|
||||||
def _should_prefer_digital_over_file_date(self, digital_date: str, theatrical_release: Optional[str], imdb_id: str) -> bool:
|
def _should_prefer_release_over_file_date(self, release_date: str, release_source: str, theatrical_release: Optional[str], imdb_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Decide if digital release date should be preferred over file date
|
Decide if release date should be preferred over file date
|
||||||
|
|
||||||
Logic:
|
Logic:
|
||||||
- If digital date is reasonable (not decades before theatrical), prefer it
|
- For theatrical dates: Always prefer over file dates (they're authoritative)
|
||||||
- If digital date is way too old (like 2000 for 1983 movie), use file date
|
- For physical dates: Usually prefer over file dates
|
||||||
|
- For digital dates: Prefer if reasonable (not decades before theatrical)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
digital_dt = datetime.fromisoformat(digital_date.replace("Z", "+00:00"))
|
release_dt = datetime.fromisoformat(release_date.replace("Z", "+00:00"))
|
||||||
|
|
||||||
# If we have theatrical release date, compare
|
# Always prefer theatrical and physical releases over file dates
|
||||||
|
if any(release_type in release_source for release_type in ["theatrical", "physical"]):
|
||||||
|
_log("INFO", f"Release date {release_date} ({release_source}) for {imdb_id}, preferring over file date")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# If we have theatrical release date, compare digital against it
|
||||||
if theatrical_release:
|
if theatrical_release:
|
||||||
theatrical_dt = datetime.fromisoformat(theatrical_release.replace("Z", "+00:00"))
|
theatrical_dt = datetime.fromisoformat(theatrical_release.replace("Z", "+00:00"))
|
||||||
year_diff = digital_dt.year - theatrical_dt.year
|
year_diff = release_dt.year - theatrical_dt.year
|
||||||
|
|
||||||
# If digital is more than 10 years before theatrical, it's probably wrong
|
# If digital is more than 10 years before theatrical, it's probably wrong
|
||||||
if year_diff < -10:
|
if year_diff < -10:
|
||||||
_log("INFO", f"Digital date {digital_date} is {abs(year_diff)} years before theatrical {theatrical_release} for {imdb_id}, using file date instead")
|
_log("INFO", f"Release date {release_date} is {abs(year_diff)} years before theatrical {theatrical_release} for {imdb_id}, using file date instead")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If digital is within reasonable range (theatrical to +20 years), use it
|
# If digital is within reasonable range (theatrical to +20 years), use it
|
||||||
if -2 <= year_diff <= 20:
|
if -2 <= year_diff <= 20:
|
||||||
_log("INFO", f"Digital date {digital_date} is reasonable for {imdb_id} (theatrical: {theatrical_release}), preferring over file date")
|
_log("INFO", f"Release date {release_date} is reasonable for {imdb_id} (theatrical: {theatrical_release}), preferring over file date")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If no theatrical date, use digital if it's not absurdly old
|
# If no theatrical date, use digital if it's not absurdly old
|
||||||
if digital_dt.year >= 1990: # Reasonable minimum for digital releases
|
if release_dt.year >= 1990: # Reasonable minimum for digital releases
|
||||||
_log("INFO", f"Digital date {digital_date} seems reasonable for {imdb_id}, preferring over file date")
|
_log("INFO", f"Release date {release_date} seems reasonable for {imdb_id}, preferring over file date")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
_log("INFO", f"Digital date {digital_date} seems too old for {imdb_id}, using file date instead")
|
_log("INFO", f"Release date {release_date} seems too old for {imdb_id}, using file date instead")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1160,6 +1167,7 @@ async def debug_movie_priority_logic(imdb_id: str):
|
|||||||
result = {
|
result = {
|
||||||
"imdb_id": imdb_id,
|
"imdb_id": imdb_id,
|
||||||
"movie_priority": config.movie_priority,
|
"movie_priority": config.movie_priority,
|
||||||
|
"release_date_priority": config.release_date_priority,
|
||||||
"priority_explanation": "",
|
"priority_explanation": "",
|
||||||
"date_sources": {},
|
"date_sources": {},
|
||||||
"selected_date": None,
|
"selected_date": None,
|
||||||
@@ -1197,7 +1205,8 @@ async def debug_movie_priority_logic(imdb_id: str):
|
|||||||
|
|
||||||
# Show priority logic
|
# Show priority logic
|
||||||
if config.movie_priority == "import_then_digital":
|
if config.movie_priority == "import_then_digital":
|
||||||
result["priority_explanation"] = "1st: Radarr import history, 2nd: TMDB/OMDb digital release, 3rd: file mtime. Note: If import is only file date, prefer reasonable digital dates."
|
priority_list = " → ".join(config.release_date_priority)
|
||||||
|
result["priority_explanation"] = f"1st: Radarr import history, 2nd: Release dates ({priority_list}), 3rd: file mtime. Note: If import is only file date, prefer reasonable release dates."
|
||||||
|
|
||||||
radarr_import = result["date_sources"].get("radarr_import")
|
radarr_import = result["date_sources"].get("radarr_import")
|
||||||
digital_release = result["date_sources"].get("digital_release")
|
digital_release = result["date_sources"].get("digital_release")
|
||||||
@@ -1205,8 +1214,9 @@ async def debug_movie_priority_logic(imdb_id: str):
|
|||||||
# Check for file date fallback logic
|
# Check for file date fallback logic
|
||||||
if radarr_import and radarr_import["source"] == "radarr:db.file.dateAdded" and digital_release:
|
if radarr_import and radarr_import["source"] == "radarr:db.file.dateAdded" and digital_release:
|
||||||
# Test the smart logic
|
# Test the smart logic
|
||||||
would_prefer_digital = movie_processor._should_prefer_digital_over_file_date(
|
would_prefer_digital = movie_processor._should_prefer_release_over_file_date(
|
||||||
digital_release["date"],
|
digital_release["date"],
|
||||||
|
digital_release["source"],
|
||||||
None, # We don't have theatrical date in this debug context
|
None, # We don't have theatrical date in this debug context
|
||||||
imdb_id
|
imdb_id
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user