From 6d2435a037c1b3267859f83987d2836600903554 Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Tue, 28 Oct 2025 17:21:29 -0400 Subject: [PATCH] update scan --- api/routes.py | 2 +- processors/movie_processor.py | 147 +++++++++++++++++++++++++++++++++- processors/tv_processor.py | 3 +- 3 files changed, 147 insertions(+), 5 deletions(-) diff --git a/api/routes.py b/api/routes.py index 2753d88..2b5b65e 100644 --- a/api/routes.py +++ b/api/routes.py @@ -1001,7 +1001,7 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N # Determine force_scan based on scan mode force_scan = (scan_mode == "full") shutdown_event = dependencies.get("shutdown_event") - result = movie_processor.process_movie(item, webhook_mode=False, force_scan=force_scan, shutdown_event=shutdown_event) + result = movie_processor.process_movie(item, webhook_mode=False, force_scan=force_scan, scan_mode=scan_mode, shutdown_event=shutdown_event) movie_total += 1 if result == "skipped": movie_skipped += 1 diff --git a/processors/movie_processor.py b/processors/movie_processor.py index c7a5007..af18f13 100644 --- a/processors/movie_processor.py +++ b/processors/movie_processor.py @@ -151,7 +151,7 @@ class MovieProcessor: _log("ERROR", f"Error checking movie completion for {imdb_id}: {e}") return False, f"Error checking completion: {e}" - def process_movie(self, movie_path: Path, webhook_mode: bool = False, force_scan: bool = False, shutdown_event=None) -> str: + def process_movie(self, movie_path: Path, webhook_mode: bool = False, force_scan: bool = False, scan_mode: str = "smart", shutdown_event=None) -> str: """Process a movie directory""" imdb_id = self.nfo_manager.find_movie_imdb_id(movie_path) if not imdb_id: @@ -165,8 +165,9 @@ class MovieProcessor: else: _log("INFO", f"Processing movie: {movie_path.name} (IMDb: {imdb_id})") - # Check if we should skip this movie (unless forced or webhook mode) - if not force_scan and not webhook_mode: + # Check if we should skip this movie (unless forced, webhook mode, or incomplete mode) + # Skip database optimization for incomplete mode since we need to check NFO files first + if not force_scan and not webhook_mode and scan_mode != "incomplete": should_skip, reason = self.should_skip_movie(imdb_id, movie_path.name) if should_skip: _log("INFO", f"⏭️ SKIPPING MOVIE: {movie_path.name} [{imdb_id}] - {reason}") @@ -196,6 +197,11 @@ class MovieProcessor: _log("WARNING", f"No video files found in: {movie_path} - skipping database entry") return "no_video_files" + # For incomplete mode: Start with NFO check to find missing dateadded elements + if scan_mode == "incomplete": + return self._process_movie_nfo_first(movie_path, imdb_id, shutdown_event) + + # For smart/full modes: Use database-first optimization # TIER 1: Check database first (fastest - local lookup) existing = self.db.get_movie_dates(imdb_id) _log("DEBUG", f"Database lookup for {imdb_id}: {existing}") @@ -400,6 +406,141 @@ class MovieProcessor: _log("INFO", f"Completed processing movie: {movie_path.name} (source: {source})") return "processed" + def _process_movie_nfo_first(self, movie_path: Path, imdb_id: str, shutdown_event=None) -> str: + """Process movie for incomplete mode: Check NFO files first for missing dateadded elements""" + _log("INFO", f"🔍 NFO-FIRST MODE: Checking movie for missing dateadded in NFO file: {movie_path.name}") + + # Check for shutdown signal + if shutdown_event and shutdown_event.is_set(): + _log("INFO", f"⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping movie NFO-first processing: {movie_path.name}") + return "shutdown" + + nfo_path = movie_path / "movie.nfo" + + # STEP 1: Check if NFO file exists and has dateadded + _log("DEBUG", f"STEP 1 - Checking NFO file for missing dateadded: {nfo_path}") + _log("INFO", f"🔍 NFO exists: {nfo_path.exists()}") + + if nfo_path.exists(): + nfo_data = self.nfo_manager.extract_nfoguard_dates_from_nfo(nfo_path) + _log("INFO", f"🔍 NFOGuard data extracted: {nfo_data}") + + if nfo_data and nfo_data.get('dateadded'): + # NFO has dateadded - this movie is complete + _log("INFO", f"✅ NFO has dateadded={nfo_data['dateadded']}, movie marked as complete") + dateadded = nfo_data["dateadded"] + source = nfo_data["source"] + released = nfo_data.get("released") + + # Cache NFO data in database for future lookups + self.db.upsert_movie_dates(imdb_id, released, dateadded, source, True) + + # Update file mtimes if needed + if config.fix_dir_mtimes and dateadded: + self.nfo_manager.update_movie_files_mtime(movie_path, dateadded) + + _log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [nfo-complete]") + return "processed" + else: + # NFO exists but missing dateadded + _log("INFO", f"🔍 NFO exists but missing dateadded - needs DB/API lookup") + else: + # No NFO file found + _log("INFO", f"🔍 No NFO file found - needs DB/API lookup") + + # STEP 2: For movies missing dateadded in NFO, check database + _log("DEBUG", f"STEP 2 - Checking database for missing dateadded") + existing = self.db.get_movie_dates(imdb_id) + + if existing and existing.get("dateadded") and existing.get("source") != "no_valid_date_source": + # Found in database - use cached data and will add to NFO + _log("INFO", f"✅ Database has dateadded={existing['dateadded']} - will add to NFO") + dateadded, source, released = existing["dateadded"], existing["source"], existing.get("released") + + # Convert datetime objects to strings for NFO manager + if hasattr(dateadded, 'isoformat'): + dateadded = dateadded.isoformat() + if released and hasattr(released, 'isoformat'): + released = released.isoformat() + + # Create NFO with database data + if config.manage_nfo: + self.nfo_manager.create_movie_nfo( + movie_path, imdb_id, dateadded, released, source, config.lock_metadata + ) + + if config.fix_dir_mtimes and dateadded: + self.nfo_manager.update_movie_files_mtime(movie_path, dateadded) + + _log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [database-to-nfo]") + return "processed" + + # STEP 3: For movies still missing dateadded, query APIs + _log("DEBUG", f"STEP 3 - Querying APIs for missing dateadded") + + # Check for shutdown signal before API calls + if shutdown_event and shutdown_event.is_set(): + _log("INFO", f"⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping before API calls: {movie_path.name}") + return "shutdown" + + # Handle TMDB ID fallback case + is_tmdb_fallback = imdb_id.startswith("tmdb-") + + if is_tmdb_fallback: + # TMDB fallback processing + _log("INFO", f"🔍 TMDB fallback processing for {imdb_id}") + + # Check for existing TMDB NFO date data + tmdb_data = self._extract_dates_from_tmdb_nfo(nfo_path) + if tmdb_data and tmdb_data.get('dateadded'): + dateadded = tmdb_data['dateadded'] + released = tmdb_data.get('released') + source = "tmdb_nfo" + else: + # Use file modification time as fallback for TMDB + dateadded, source, released = self._get_file_mtime_date(movie_path) + _log("INFO", f"Using file mtime as fallback for TMDB movie: {dateadded}") + else: + # Standard IMDb processing + # Try to get digital release date from external APIs + digital_date, digital_source = self._get_digital_release_date(imdb_id) + + if digital_date: + dateadded = digital_date + source = digital_source + released = digital_date # For movies, digital release is often the key date + _log("INFO", f"Got digital release date from APIs: {dateadded} (source: {source})") + else: + # Check Radarr NFO for premiered date + radarr_premiered = self._get_radarr_nfo_premiered_date(movie_path) + if radarr_premiered: + dateadded = radarr_premiered + source = "radarr_nfo" + released = radarr_premiered + _log("INFO", f"Got premiered date from Radarr NFO: {dateadded}") + else: + # Last resort: file modification time + dateadded, source, released = self._get_file_mtime_date(movie_path) + _log("INFO", f"Using file mtime as last resort: {dateadded}") + + # Save to database and create NFO + if dateadded: + self.db.upsert_movie_dates(imdb_id, released, dateadded, source, True) + + if config.manage_nfo: + self.nfo_manager.create_movie_nfo( + movie_path, imdb_id, dateadded, released, source, config.lock_metadata + ) + + if config.fix_dir_mtimes and dateadded: + self.nfo_manager.update_movie_files_mtime(movie_path, dateadded) + + _log("INFO", f"🔍 NFO-FIRST COMPLETE: {movie_path.name} (source: {source})") + return "processed" + else: + _log("WARNING", f"Could not determine dateadded for movie: {movie_path.name}") + return "error" + def _extract_dates_from_tmdb_nfo(self, nfo_path: Path) -> Optional[Dict[str, str]]: """Extract date information from TMDB-based NFO file""" if not nfo_path.exists(): diff --git a/processors/tv_processor.py b/processors/tv_processor.py index 47d8fc9..c6e81e6 100644 --- a/processors/tv_processor.py +++ b/processors/tv_processor.py @@ -155,7 +155,8 @@ class TVProcessor: _log("INFO", f"Processing TV series: {series_path.name}") # Fast check first - avoid expensive filesystem scan if possible - if not force_scan: + # Skip fast optimization for incomplete mode since we need to check NFO files first + if not force_scan and scan_mode != "incomplete": should_skip_fast, reason_fast, episodes_in_db = self.should_skip_series_fast(imdb_id, series_path.name) if should_skip_fast: _log("INFO", f"⚡ FAST SKIP: {series_path.name} [{imdb_id}] - {reason_fast}")