featuer: smart scan to skip ones with dates
Local Docker Build (Dev) / build-dev (push) Successful in 4s
Local Docker Build (Dev) / build-dev (push) Successful in 4s
This commit is contained in:
+30
-3
@@ -573,6 +573,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
tv_series_total = 0
|
tv_series_total = 0
|
||||||
tv_series_skipped = 0
|
tv_series_skipped = 0
|
||||||
tv_series_processed = 0
|
tv_series_processed = 0
|
||||||
|
movie_total = 0
|
||||||
|
movie_skipped = 0
|
||||||
|
movie_processed = 0
|
||||||
|
|
||||||
paths_to_scan = []
|
paths_to_scan = []
|
||||||
if path:
|
if path:
|
||||||
@@ -659,9 +662,17 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
movie_count += 1
|
movie_count += 1
|
||||||
print(f"INFO: Processing movie: {item.name}")
|
print(f"INFO: Processing movie: {item.name}")
|
||||||
try:
|
try:
|
||||||
movie_processor.process_movie(item)
|
# Determine force_scan based on scan mode
|
||||||
|
force_scan = (scan_mode == "full")
|
||||||
|
result = movie_processor.process_movie(item, webhook_mode=False, force_scan=force_scan)
|
||||||
|
movie_total += 1
|
||||||
|
if result == "skipped":
|
||||||
|
movie_skipped += 1
|
||||||
|
elif result == "processed":
|
||||||
|
movie_processed += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed processing movie {item}: {e}")
|
print(f"ERROR: Failed processing movie {item}: {e}")
|
||||||
|
movie_total += 1
|
||||||
|
|
||||||
# Yield control every 2 movies to allow other requests (webhooks, web interface)
|
# Yield control every 2 movies to allow other requests (webhooks, web interface)
|
||||||
if movie_count % 2 == 0:
|
if movie_count % 2 == 0:
|
||||||
@@ -697,10 +708,26 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
|
|
||||||
# Print optimization statistics for TV scans
|
# Print optimization statistics for TV scans
|
||||||
if scan_type in ["both", "tv"] and tv_series_total > 0:
|
if scan_type in ["both", "tv"] and tv_series_total > 0:
|
||||||
print(f"📊 SCAN OPTIMIZATION STATS: Total: {tv_series_total}, Processed: {tv_series_processed}, Skipped: {tv_series_skipped}")
|
print(f"📊 TV SCAN OPTIMIZATION: Total: {tv_series_total}, Processed: {tv_series_processed}, Skipped: {tv_series_skipped}")
|
||||||
if tv_series_skipped > 0:
|
if tv_series_skipped > 0:
|
||||||
skip_percentage = (tv_series_skipped / tv_series_total) * 100
|
skip_percentage = (tv_series_skipped / tv_series_total) * 100
|
||||||
print(f"⚡ PERFORMANCE BOOST: {tv_series_skipped}/{tv_series_total} series skipped ({skip_percentage:.1f}% time saved!)")
|
print(f"⚡ TV PERFORMANCE BOOST: {tv_series_skipped}/{tv_series_total} series skipped ({skip_percentage:.1f}% time saved!)")
|
||||||
|
|
||||||
|
# Print optimization statistics for movie scans
|
||||||
|
if scan_type in ["both", "movies"] and movie_total > 0:
|
||||||
|
print(f"📊 MOVIE SCAN OPTIMIZATION: Total: {movie_total}, Processed: {movie_processed}, Skipped: {movie_skipped}")
|
||||||
|
if movie_skipped > 0:
|
||||||
|
skip_percentage = (movie_skipped / movie_total) * 100
|
||||||
|
print(f"⚡ MOVIE PERFORMANCE BOOST: {movie_skipped}/{movie_total} movies skipped ({skip_percentage:.1f}% time saved!)")
|
||||||
|
|
||||||
|
# Print combined optimization statistics for "both" scans
|
||||||
|
if scan_type == "both" and (tv_series_total > 0 or movie_total > 0):
|
||||||
|
total_items = tv_series_total + movie_total
|
||||||
|
total_skipped = tv_series_skipped + movie_skipped
|
||||||
|
total_processed = tv_series_processed + movie_processed
|
||||||
|
if total_skipped > 0:
|
||||||
|
overall_skip_percentage = (total_skipped / total_items) * 100
|
||||||
|
print(f"🎯 OVERALL OPTIMIZATION: {total_skipped}/{total_items} items skipped ({overall_skip_percentage:.1f}% total time saved!)")
|
||||||
|
|
||||||
background_tasks.add_task(run_scan)
|
background_tasks.add_task(run_scan)
|
||||||
return {"status": "started", "message": f"Manual {scan_type} scan started (mode: {scan_mode})"}
|
return {"status": "started", "message": f"Manual {scan_type} scan started (mode: {scan_mode})"}
|
||||||
|
|||||||
@@ -88,12 +88,75 @@ class MovieProcessor:
|
|||||||
path_mapper=self.path_mapper
|
path_mapper=self.path_mapper
|
||||||
)
|
)
|
||||||
|
|
||||||
def process_movie(self, movie_path: Path, webhook_mode: bool = False) -> None:
|
def should_skip_movie(self, imdb_id: str, movie_name: str = "") -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Determine if we should skip processing this movie based on completion status
|
||||||
|
|
||||||
|
Args:
|
||||||
|
imdb_id: Movie IMDb ID
|
||||||
|
movie_name: Movie name for logging
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(should_skip: bool, reason: str)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.db.get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
if self.db.db_type == "postgresql":
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT dateadded, source, has_video_file
|
||||||
|
FROM movies
|
||||||
|
WHERE imdb_id = %s
|
||||||
|
""", (imdb_id,))
|
||||||
|
else:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT dateadded, source, has_video_file
|
||||||
|
FROM movies
|
||||||
|
WHERE imdb_id = ?
|
||||||
|
""", (imdb_id,))
|
||||||
|
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if not result:
|
||||||
|
return False, "No database record found"
|
||||||
|
|
||||||
|
if self.db.db_type == "postgresql":
|
||||||
|
dateadded = result['dateadded']
|
||||||
|
source = result['source']
|
||||||
|
has_video_file = result['has_video_file']
|
||||||
|
else:
|
||||||
|
dateadded = result[0] if result[0] else None
|
||||||
|
source = result[1] if result[1] else None
|
||||||
|
has_video_file = result[2] if result[2] else False
|
||||||
|
|
||||||
|
# Skip if:
|
||||||
|
# 1. Movie has a valid dateadded timestamp
|
||||||
|
# 2. Source is valid (not 'unknown' or 'no_valid_date_source')
|
||||||
|
# 3. Has video file on disk
|
||||||
|
if (dateadded and
|
||||||
|
source and
|
||||||
|
source not in ['unknown', 'no_valid_date_source'] and
|
||||||
|
has_video_file):
|
||||||
|
return True, f"Complete: Has valid date '{dateadded}' from source '{source}'"
|
||||||
|
elif not dateadded:
|
||||||
|
return False, "Missing dateadded"
|
||||||
|
elif not source or source in ['unknown', 'no_valid_date_source']:
|
||||||
|
return False, f"Invalid source: '{source}'"
|
||||||
|
elif not has_video_file:
|
||||||
|
return False, "No video file detected"
|
||||||
|
else:
|
||||||
|
return False, "Incomplete movie data"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_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) -> str:
|
||||||
"""Process a movie directory"""
|
"""Process a movie directory"""
|
||||||
imdb_id = self.nfo_manager.find_movie_imdb_id(movie_path)
|
imdb_id = self.nfo_manager.find_movie_imdb_id(movie_path)
|
||||||
if not imdb_id:
|
if not imdb_id:
|
||||||
_log("ERROR", f"No IMDb ID found in movie directory, filenames, or NFO file: {movie_path}")
|
_log("ERROR", f"No IMDb ID found in movie directory, filenames, or NFO file: {movie_path}")
|
||||||
return
|
return "error"
|
||||||
|
|
||||||
# Handle TMDB ID fallback case
|
# Handle TMDB ID fallback case
|
||||||
is_tmdb_fallback = imdb_id.startswith("tmdb-")
|
is_tmdb_fallback = imdb_id.startswith("tmdb-")
|
||||||
@@ -102,6 +165,21 @@ class MovieProcessor:
|
|||||||
else:
|
else:
|
||||||
_log("INFO", f"Processing movie: {movie_path.name} (IMDb: {imdb_id})")
|
_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:
|
||||||
|
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}")
|
||||||
|
# Still update the movie record to track that we've seen it
|
||||||
|
self.db.upsert_movie(imdb_id, str(movie_path))
|
||||||
|
return "skipped"
|
||||||
|
else:
|
||||||
|
_log("INFO", f"🎬 PROCESSING MOVIE: {movie_path.name} [{imdb_id}] - {reason}")
|
||||||
|
elif force_scan:
|
||||||
|
_log("INFO", f"🔄 FORCE PROCESSING MOVIE: {movie_path.name} [{imdb_id}] - Force scan enabled")
|
||||||
|
else:
|
||||||
|
_log("INFO", f"📥 WEBHOOK PROCESSING MOVIE: {movie_path.name} [{imdb_id}] - Webhook mode")
|
||||||
|
|
||||||
# Update database
|
# Update database
|
||||||
self.db.upsert_movie(imdb_id, str(movie_path))
|
self.db.upsert_movie(imdb_id, str(movie_path))
|
||||||
|
|
||||||
@@ -141,7 +219,7 @@ class MovieProcessor:
|
|||||||
self.nfo_manager.update_movie_files_mtime(movie_path, dateadded)
|
self.nfo_manager.update_movie_files_mtime(movie_path, dateadded)
|
||||||
|
|
||||||
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [database-cached]")
|
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [database-cached]")
|
||||||
return
|
return "processed"
|
||||||
else:
|
else:
|
||||||
_log("INFO", f"🔍 TIER 1 SKIP - {imdb_id}: Database incomplete, proceeding to Tier 2")
|
_log("INFO", f"🔍 TIER 1 SKIP - {imdb_id}: Database incomplete, proceeding to Tier 2")
|
||||||
|
|
||||||
@@ -169,7 +247,7 @@ class MovieProcessor:
|
|||||||
self.nfo_manager.update_movie_files_mtime(movie_path, dateadded)
|
self.nfo_manager.update_movie_files_mtime(movie_path, dateadded)
|
||||||
|
|
||||||
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [nfo-cached]")
|
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [nfo-cached]")
|
||||||
return
|
return "processed"
|
||||||
|
|
||||||
# TIER 2.5: Check for any existing valid date data in NFO (even without lockdata marker)
|
# TIER 2.5: Check for any existing valid date data in NFO (even without lockdata marker)
|
||||||
# Only use NFO dates if prioritize_nfo is enabled, otherwise check external APIs first
|
# Only use NFO dates if prioritize_nfo is enabled, otherwise check external APIs first
|
||||||
@@ -198,7 +276,7 @@ class MovieProcessor:
|
|||||||
self.nfo_manager.update_movie_files_mtime(movie_path, dateadded)
|
self.nfo_manager.update_movie_files_mtime(movie_path, dateadded)
|
||||||
|
|
||||||
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [existing-nfo-enhanced]")
|
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [existing-nfo-enhanced]")
|
||||||
return
|
return "processed"
|
||||||
elif existing_nfo_data:
|
elif existing_nfo_data:
|
||||||
_log("INFO", f"🔍 TIER 2.5 - Found existing date data in NFO (no lockdata): {existing_nfo_data['dateadded']} (source: {existing_nfo_data['source']})")
|
_log("INFO", f"🔍 TIER 2.5 - Found existing date data in NFO (no lockdata): {existing_nfo_data['dateadded']} (source: {existing_nfo_data['source']})")
|
||||||
_log("INFO", f"🎯 MANUAL_SCAN_PRIORITIZE_NFO=False - Will verify against external APIs first")
|
_log("INFO", f"🎯 MANUAL_SCAN_PRIORITIZE_NFO=False - Will verify against external APIs first")
|
||||||
@@ -228,7 +306,7 @@ class MovieProcessor:
|
|||||||
self.db.upsert_movie_dates(imdb_id, released, dateadded, source, True)
|
self.db.upsert_movie_dates(imdb_id, released, dateadded, source, True)
|
||||||
|
|
||||||
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [tmdb-nfo]")
|
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source}) [tmdb-nfo]")
|
||||||
return
|
return "processed"
|
||||||
|
|
||||||
# TIER 3: No cached data found - proceed with API lookups and verification
|
# TIER 3: No cached data found - proceed with API lookups and verification
|
||||||
|
|
||||||
@@ -277,7 +355,7 @@ class MovieProcessor:
|
|||||||
if final_dateadded is None:
|
if final_dateadded is None:
|
||||||
_log("WARNING", f"Movie {movie_path.name} - no valid date source available, but NFO was still processed")
|
_log("WARNING", f"Movie {movie_path.name} - no valid date source available, but NFO was still processed")
|
||||||
self.db.upsert_movie_dates(imdb_id, released, None, source, True)
|
self.db.upsert_movie_dates(imdb_id, released, None, source, True)
|
||||||
return
|
return "processed"
|
||||||
|
|
||||||
# Update dateadded and source for the rest of processing
|
# Update dateadded and source for the rest of processing
|
||||||
dateadded = final_dateadded
|
dateadded = final_dateadded
|
||||||
@@ -305,6 +383,7 @@ class MovieProcessor:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source})")
|
_log("INFO", f"Completed processing movie: {movie_path.name} (source: {source})")
|
||||||
|
return "processed"
|
||||||
|
|
||||||
def _extract_dates_from_tmdb_nfo(self, nfo_path: Path) -> Optional[Dict[str, str]]:
|
def _extract_dates_from_tmdb_nfo(self, nfo_path: Path) -> Optional[Dict[str, str]]:
|
||||||
"""Extract date information from TMDB-based NFO file"""
|
"""Extract date information from TMDB-based NFO file"""
|
||||||
|
|||||||
Reference in New Issue
Block a user