update: scan to check .nfo files first
Local Docker Build (Dev) / build-dev (push) Successful in 7s

This commit is contained in:
2025-10-28 17:16:15 -04:00
parent b709a377c8
commit b1598114d9
2 changed files with 137 additions and 7 deletions
+3 -3
View File
@@ -862,8 +862,8 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
try: try:
# Determine force_scan based on scan mode # Determine force_scan based on scan mode
force_scan = (scan_mode == "full") force_scan = (scan_mode == "full")
print(f"DEBUG: Processing series {scan_path} with force_scan={force_scan}") print(f"DEBUG: Processing series {scan_path} with force_scan={force_scan}, scan_mode={scan_mode}")
result = tv_processor.process_series(scan_path, force_scan=force_scan) result = tv_processor.process_series(scan_path, force_scan=force_scan, scan_mode=scan_mode)
tv_series_total += 1 tv_series_total += 1
if result == "skipped": if result == "skipped":
tv_series_skipped += 1 tv_series_skipped += 1
@@ -928,7 +928,7 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
try: try:
# Determine force_scan based on scan mode # Determine force_scan based on scan mode
force_scan = (scan_mode == "full") force_scan = (scan_mode == "full")
result = tv_processor.process_series(item, force_scan=force_scan) result = tv_processor.process_series(item, force_scan=force_scan, scan_mode=scan_mode)
tv_series_total += 1 tv_series_total += 1
if result == "skipped": if result == "skipped":
tv_series_skipped += 1 tv_series_skipped += 1
+134 -4
View File
@@ -145,7 +145,7 @@ class TVProcessor:
_log("ERROR", f"Error checking series completion for {imdb_id}: {e}") _log("ERROR", f"Error checking series completion for {imdb_id}: {e}")
return False, f"Error checking completion: {e}" return False, f"Error checking completion: {e}"
def process_series(self, series_path: Path, force_scan: bool = False) -> str: def process_series(self, series_path: Path, force_scan: bool = False, scan_mode: str = "smart") -> str:
"""Process a TV series directory""" """Process a TV series directory"""
imdb_id = self.nfo_manager.parse_imdb_from_path(series_path) imdb_id = self.nfo_manager.parse_imdb_from_path(series_path)
if not imdb_id: if not imdb_id:
@@ -226,12 +226,17 @@ class TVProcessor:
return extract_title_from_directory_name(series_path.name) return extract_title_from_directory_name(series_path.name)
def _gather_episode_dates(self, series_path: Path, imdb_id: str, disk_episodes: Dict[Tuple[int, int], List[Path]]) -> Dict[Tuple[int, int], Tuple[Optional[str], Optional[str], str]]: def _gather_episode_dates(self, series_path: Path, imdb_id: str, disk_episodes: Dict[Tuple[int, int], List[Path]], scan_mode: str = "smart") -> Dict[Tuple[int, int], Tuple[Optional[str], Optional[str], str]]:
"""Gather episode air dates and date added information with database-first optimization""" """Gather episode air dates and date added information with optimization based on scan mode"""
_log("INFO", f"🎯 GATHERING EPISODE DATES for {imdb_id}: {len(disk_episodes)} episodes on disk") _log("INFO", f"🎯 GATHERING EPISODE DATES for {imdb_id}: {len(disk_episodes)} episodes on disk (mode: {scan_mode})")
episode_dates = {} episode_dates = {}
episodes_needing_lookup = [] episodes_needing_lookup = []
# For incomplete mode: Start with NFO check to find missing dateadded elements
if scan_mode == "incomplete":
return self._gather_episode_dates_nfo_first(series_path, imdb_id, disk_episodes)
# For smart/full modes: Use database-first optimization
# TIER 1: Check database first for existing dates (fastest) # TIER 1: Check database first for existing dates (fastest)
_log("DEBUG", f"TIER 1 - Checking database for existing episode dates for {len(disk_episodes)} episodes") _log("DEBUG", f"TIER 1 - Checking database for existing episode dates for {len(disk_episodes)} episodes")
db_cache_hits = 0 db_cache_hits = 0
@@ -378,6 +383,131 @@ class TVProcessor:
return episode_dates return episode_dates
def _gather_episode_dates_nfo_first(self, series_path: Path, imdb_id: str, disk_episodes: Dict[Tuple[int, int], List[Path]]) -> Dict[Tuple[int, int], Tuple[Optional[str], Optional[str], str]]:
"""Gather episode dates for incomplete mode: Check NFO files first for missing dateadded elements"""
_log("INFO", f"🔍 NFO-FIRST MODE: Checking {len(disk_episodes)} episodes for missing dateadded in NFO files")
episode_dates = {}
episodes_needing_db_check = []
episodes_needing_api_lookup = []
# STEP 1: Check each NFO file to determine if dateadded is missing
_log("DEBUG", f"STEP 1 - Checking NFO files for missing dateadded elements")
nfo_complete_count = 0
for (season, episode) in disk_episodes:
season_dir = series_path / config.get_season_dir_name(season)
episode_files = disk_episodes[(season, episode)]
nfo_has_dateadded = False
nfo_data = None
# Look for NFO file for this episode
for episode_file in episode_files:
nfo_path = episode_file.with_suffix('.nfo')
if nfo_path.exists():
# Extract NFOGuard data from episode NFO
nfo_data = self.nfo_manager.extract_nfoguard_dates_from_nfo(nfo_path)
if nfo_data and nfo_data.get('dateadded'):
# NFO has dateadded - this episode is complete
aired = nfo_data.get('aired')
dateadded = nfo_data.get('dateadded')
source = nfo_data.get('source', 'nfo_cache')
episode_dates[(season, episode)] = (aired, dateadded, source)
nfo_complete_count += 1
nfo_has_dateadded = True
_log("DEBUG", f"S{season:02d}E{episode:02d}: NFO has dateadded={dateadded}, marked as complete")
break
if not nfo_has_dateadded:
# NFO missing or missing dateadded - needs further processing
if nfo_data:
# NFO exists but missing dateadded
_log("DEBUG", f"S{season:02d}E{episode:02d}: NFO exists but missing dateadded - needs DB/API lookup")
else:
# No NFO file found
_log("DEBUG", f"S{season:02d}E{episode:02d}: No NFO file found - needs DB/API lookup")
episodes_needing_db_check.append((season, episode))
_log("INFO", f"NFO files with dateadded: {nfo_complete_count}/{len(disk_episodes)} episodes. Need DB check: {len(episodes_needing_db_check)}")
# STEP 2: For episodes missing dateadded in NFO, check database
if episodes_needing_db_check:
_log("DEBUG", f"STEP 2 - Checking database for {len(episodes_needing_db_check)} episodes missing dateadded")
db_hits = 0
for (season, episode) in episodes_needing_db_check:
db_result = self.db.get_episode_date(imdb_id, season, episode)
if db_result and db_result.get('dateadded'):
# Found in database - use cached data and will add to NFO later
aired = db_result.get('aired')
dateadded = db_result.get('dateadded')
source = db_result.get('source', 'database_cache')
episode_dates[(season, episode)] = (aired, dateadded, source)
db_hits += 1
_log("DEBUG", f"S{season:02d}E{episode:02d}: Database has dateadded={dateadded} - will add to NFO")
else:
# Not in database or incomplete - needs API lookup
episodes_needing_api_lookup.append((season, episode))
_log("INFO", f"Database hits: {db_hits}/{len(episodes_needing_db_check)} episodes. Need API lookup: {len(episodes_needing_api_lookup)}")
# STEP 3: For episodes still missing dateadded, query Sonarr
if episodes_needing_api_lookup:
_log("DEBUG", f"STEP 3 - Querying Sonarr for {len(episodes_needing_api_lookup)} episodes missing from NFO and database")
sonarr_episodes = self._get_sonarr_episodes(imdb_id, episodes_needing_api_lookup)
for (season, episode) in episodes_needing_api_lookup:
aired = None
dateadded = None
source = "unknown"
# Try Sonarr first
if (season, episode) in sonarr_episodes:
sonarr_data = sonarr_episodes[(season, episode)]
aired = sonarr_data.get('airDate')
dateadded = sonarr_data.get('dateAdded')
if dateadded:
source = "sonarr:history.import"
_log("DEBUG", f"S{season:02d}E{episode:02d}: Got Sonarr import date: {dateadded}")
else:
source = "sonarr:no_import_date"
_log("DEBUG", f"S{season:02d}E{episode:02d}: Sonarr has data but no dateAdded (aired: {aired})")
# Fallback to external sources if needed
if not aired:
_log("DEBUG", f"S{season:02d}E{episode:02d}: No aired date from Sonarr, trying external APIs")
external_aired = self.external_clients.get_episode_air_date(imdb_id, season, episode)
if external_aired:
aired = external_aired
if not dateadded:
source = "external"
_log("INFO", f"S{season:02d}E{episode:02d}: Found aired date from external APIs: {aired}")
# Use air date as fallback for dateadded if no import date found
if not dateadded and aired:
dateadded = aired
if source == "sonarr:no_import_date":
source = "sonarr:aired_fallback"
elif source == "external":
source = "external:aired_fallback"
else:
source = f"{source}_fallback" if source != "unknown" else "aired_fallback"
_log("DEBUG", f"S{season:02d}E{episode:02d}: Using aired date as dateadded fallback: {dateadded}")
# Save to database for future lookups
if dateadded or aired:
self.db.upsert_episode_date(imdb_id, season, episode, aired, dateadded, source, True)
episode_dates[(season, episode)] = (aired, dateadded, source)
_log("INFO", f"🔍 NFO-FIRST COMPLETE: {len(episode_dates)} episodes processed")
for (s, e), (aired, dateadded, source) in episode_dates.items():
_log("INFO", f" S{s:02d}E{e:02d}: aired={aired}, dateadded={dateadded}, source={source}")
return episode_dates
def _get_sonarr_episodes(self, imdb_id: str, episodes_filter: List[Tuple[int, int]] = None) -> Dict[Tuple[int, int], Dict[str, Any]]: def _get_sonarr_episodes(self, imdb_id: str, episodes_filter: List[Tuple[int, int]] = None) -> Dict[Tuple[int, int], Dict[str, Any]]:
"""Get episode information from Sonarr including import history - optimized to only fetch needed episodes""" """Get episode information from Sonarr including import history - optimized to only fetch needed episodes"""
try: try: