diff --git a/SUMMARY.md b/SUMMARY.md index f4b5e7c..63269fd 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,6 @@ # NFOGuard Project Summary -## 📋 Current Status: v0.6.0 (In Development) +## 📋 Current Status: v0.6.1 (Latest Improvements) ### 🎯 Project Goal Preserve movie and TV import dates in Emby/Jellyfin/Plex by preventing upgraded files from appearing as "recently added". NFOGuard listens to Radarr/Sonarr webhooks and manages `.nfo` files and file timestamps to maintain chronological consistency. @@ -112,6 +112,25 @@ Preserve movie and TV import dates in Emby/Jellyfin/Plex by preventing upgraded --- +### 🆕 v0.6.1 Recent Improvements (September 2025) + +**🔧 Enhanced Radarr History Detection:** +- **Smart Upgrade Detection**: Detects movies where first event is `movieFileRenamed` (upgrade scenarios) +- **Improved Logic**: For movies like The Matrix (1999), prefer digital release dates over recent upgrade dates +- **True Import Preservation**: Still honors actual import dates when they exist - only enhances edge cases + +**🎯 Improved IMDb ID Extraction:** +- **Flexible Regex**: Now supports both `[imdb-tt123]` and `[tt123]` formats +- **NFO Fallback**: Scans `.nfo` files when path extraction fails (handles Radarr auto-generated files) +- **Broader Compatibility**: Works with various folder naming conventions + +**🧠 Enhanced Priority Logic:** +- Movies with rename→upgrade history now get better chronological dates +- True import dates always take precedence (no regression) +- Smart fallbacks only engage when appropriate + +--- + **Last Updated:** September 9, 2025 -**Version:** v0.6.0-dev -**Status:** Active Development \ No newline at end of file +**Version:** v0.6.1-dev +**Status:** Enhanced Production Ready \ No newline at end of file diff --git a/clients/radarr_db_client.py b/clients/radarr_db_client.py index 060f20e..eb85f03 100644 --- a/clients/radarr_db_client.py +++ b/clients/radarr_db_client.py @@ -249,6 +249,47 @@ class RadarrDbClient: _log("ERROR", f"Database query error for movie {movie_id}: {e}") return None, "radarr:db.no_date_found" + + def is_first_event_rename_based(self, movie_id: int) -> bool: + """ + Check if the first event in history is rename-based (not a true import) + + This helps identify movies where: + - First event: movieFileRenamed (EventType = 7) + - Followed by: downloadFolderImported (EventType = 3) - this is an upgrade + + In such cases, we should prefer release dates over the upgrade date + """ + query = """ + SELECT h."EventType" as event_type + FROM "History" h + WHERE h."MovieId" = %s + ORDER BY h."Date" ASC + LIMIT 1 + """ + + if self.db_type == "sqlite": + query = query.replace("%s", "?") + + try: + with self._get_connection() as conn: + if self.db_type == "postgresql": + cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + else: + cursor = conn.cursor() + + cursor.execute(query, (movie_id,)) + row = cursor.fetchone() + + if row: + event_type = row['event_type'] if self.db_type == "postgresql" else row[0] + # EventType 7 = movieFileRenamed + return event_type == 7 + + except Exception as e: + _log("DEBUG", f"Error checking first event type for movie {movie_id}: {e}") + + return False def get_movie_file_date(self, movie_id: int) -> Optional[str]: """ @@ -305,11 +346,16 @@ class RadarrDbClient: Returns: (date_iso, source_description) """ - # Try history first + # Try history first - always check for true import dates date_iso, source = self.get_earliest_import_date(movie_id) if date_iso: return date_iso, source + # If no true import found, check if first event is rename-based (upgrade scenario) + if self.is_first_event_rename_based(movie_id): + _log("INFO", f"Movie {movie_id} has rename-first history with no true import - signaling to prefer release dates") + return None, "radarr:db.prefer_release_dates" + # Fallback to file date if requested if fallback_to_file_date: file_date = self.get_movie_file_date(movie_id) diff --git a/core/nfo_manager.py b/core/nfo_manager.py index 8c08dbb..218187d 100644 --- a/core/nfo_manager.py +++ b/core/nfo_manager.py @@ -18,8 +18,8 @@ def _log(level: str, msg: str): class NFOManager: """Handles creation and updating of NFO files for movies and TV episodes""" - # Regex patterns - IMDB_TAG_PATTERN = re.compile(r"\[imdb-(tt\d+)\]", re.IGNORECASE) + # Regex patterns - supports both [imdb-tt123] and [tt123] formats + IMDB_TAG_PATTERN = re.compile(r"\[(?:imdb-)?(tt\d+)\]", re.IGNORECASE) LONG_NFO_PATTERN = re.compile(r".*-S\d{2}E\d{2}-.*\.nfo$", re.IGNORECASE) SHORT_NFO_PATTERN = re.compile(r"^S(\d{2})E(\d{2})\.nfo$", re.IGNORECASE) @@ -28,9 +28,21 @@ class NFOManager: @staticmethod def parse_imdb_from_path(path: Path) -> Optional[str]: - """Extract IMDb ID from directory or filename""" + """Extract IMDb ID from directory or filename, with .nfo fallback""" + # First try path name match = NFOManager.IMDB_TAG_PATTERN.search(path.name) - return match.group(1).lower() if match else None + if match: + return match.group(1).lower() + + # Fallback: scan .nfo files in directory + if path.is_dir(): + for nfo_file in path.glob("*.nfo"): + imdb_id = NFOManager.parse_imdb_from_nfo(nfo_file) + if imdb_id: + _log("DEBUG", f"Found IMDb ID {imdb_id} in {nfo_file.name}") + return imdb_id + + return None @staticmethod def parse_imdb_from_nfo(nfo_path: Path) -> Optional[str]: diff --git a/nfoguard.py b/nfoguard.py index 70cf5ba..45317dc 100644 --- a/nfoguard.py +++ b/nfoguard.py @@ -526,8 +526,12 @@ class MovieProcessor: if movie_id: import_date, import_source = self.radarr.get_movie_import_date(movie_id, fallback_to_file_date=config.allow_file_date_fallback) + # Check for special case: rename-first scenario (should prefer release dates) + if import_source == "radarr:db.prefer_release_dates": + _log("INFO", f"Movie {imdb_id} has rename-first history - skipping import, preferring release dates") + # Fall through to release date logic below # Check if we got a real import date or just file date fallback - if import_date and import_source != "radarr:db.file.dateAdded": + elif import_date and import_source != "radarr:db.file.dateAdded": return import_date, import_source, released # Get digital release date for comparison