dev to main (#14)
Reviewed-on: #14 Co-authored-by: SBCrumb <sbcrumb@kickthetree.com> Co-committed-by: SBCrumb <sbcrumb@kickthetree.com>
This commit is contained in:
+95
-201
@@ -380,28 +380,39 @@ class TVProcessor:
|
||||
# Save to database
|
||||
self.db.upsert_episode_date(imdb_id, season, episode, aired, dateadded, source, True)
|
||||
|
||||
# Create season/tvshow NFOs
|
||||
if config.manage_nfo:
|
||||
seasons_processed = set()
|
||||
for (season, episode) in disk_episodes.keys():
|
||||
if season not in seasons_processed:
|
||||
season_dir = series_path / config.tv_season_dir_format.format(season=season)
|
||||
self.nfo_manager.create_season_nfo(season_dir, season)
|
||||
seasons_processed.add(season)
|
||||
|
||||
# Get TVDB ID for better Emby compatibility
|
||||
tvdb_id = self.external_clients.get_tvdb_series_id(imdb_id)
|
||||
self.nfo_manager.create_tvshow_nfo(series_path, imdb_id, tvdb_id)
|
||||
# Skip season.nfo and tvshow.nfo creation - focus only on episode NFOs
|
||||
pass
|
||||
|
||||
_log("INFO", f"Completed processing TV series: {series_path.name}")
|
||||
|
||||
def _extract_series_title_from_path(self, series_path: Path) -> Optional[str]:
|
||||
"""Extract series title from directory path, removing year and IMDb ID"""
|
||||
name = series_path.name
|
||||
|
||||
# Remove IMDb ID part: [imdb-ttXXXXXX] or [ttXXXXXX]
|
||||
name = re.sub(r'\s*\[imdb-?tt\d+\]', '', name, flags=re.IGNORECASE)
|
||||
name = re.sub(r'\s*\[tt\d+\]', '', name, flags=re.IGNORECASE)
|
||||
|
||||
# Remove year in parentheses: (YYYY)
|
||||
name = re.sub(r'\s*\(\d{4}\)', '', name)
|
||||
|
||||
# Clean up extra spaces
|
||||
name = re.sub(r'\s+', ' ', name).strip()
|
||||
|
||||
return name if name else None
|
||||
|
||||
def _find_disk_episodes(self, series_path: Path) -> Dict[Tuple[int, int], List[Path]]:
|
||||
"""Find all episode video files on disk"""
|
||||
disk_episodes = {}
|
||||
video_exts = (".mkv", ".mp4", ".avi", ".mov", ".m4v")
|
||||
|
||||
_log("DEBUG", f"Scanning for episodes in: {series_path}")
|
||||
_log("DEBUG", f"Looking for season dirs with pattern: '{config.tv_season_dir_pattern}'")
|
||||
|
||||
for season_dir in series_path.iterdir():
|
||||
_log("DEBUG", f"Checking: {season_dir.name} (is_dir: {season_dir.is_dir()})")
|
||||
if not (season_dir.is_dir() and season_dir.name.lower().startswith(config.tv_season_dir_pattern)):
|
||||
_log("DEBUG", f"Skipping {season_dir.name} - doesn't match season pattern")
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -440,29 +451,12 @@ class TVProcessor:
|
||||
if key in disk_episodes: # Only use cached data for episodes we have
|
||||
episode_dates[key] = (ep["aired"], ep["dateadded"], ep["source"])
|
||||
|
||||
# Check for existing NFO files (including long-named ones) for migration
|
||||
nfo_episodes_found = 0
|
||||
for (season_num, episode_num) in disk_episodes.keys():
|
||||
if (season_num, episode_num) not in episode_dates:
|
||||
# Check if this episode has an existing NFO file that needs migration
|
||||
season_dir = series_path / config.tv_season_dir_format.format(season=season_num)
|
||||
existing_nfo = self.nfo_manager.find_existing_episode_nfo(season_dir, season_num, episode_num)
|
||||
|
||||
if existing_nfo:
|
||||
# Force processing of this episode for NFO migration
|
||||
episode_dates[(season_num, episode_num)] = (None, None, "nfo_migration_required")
|
||||
nfo_episodes_found += 1
|
||||
|
||||
if nfo_episodes_found > 0:
|
||||
_log("INFO", f"Found {nfo_episodes_found} episodes with existing NFO files requiring migration")
|
||||
|
||||
# Find missing episodes (not in cache and no existing NFO)
|
||||
# Find missing episodes
|
||||
cached_keys = set(episode_dates.keys())
|
||||
missing_keys = set(disk_episodes.keys()) - cached_keys
|
||||
|
||||
if not missing_keys:
|
||||
if nfo_episodes_found == 0:
|
||||
_log("INFO", "All episodes found in cache")
|
||||
_log("INFO", "All episodes found in cache")
|
||||
return episode_dates
|
||||
|
||||
_log("INFO", f"Querying APIs for {len(missing_keys)} missing episodes")
|
||||
@@ -470,6 +464,15 @@ class TVProcessor:
|
||||
# Query Sonarr for missing episodes
|
||||
if self.sonarr.enabled:
|
||||
series = self.sonarr.series_by_imdb(imdb_id)
|
||||
if not series:
|
||||
_log("DEBUG", f"Trying direct series search for IMDb: {imdb_id}")
|
||||
series = self.sonarr.series_by_imdb_direct(imdb_id)
|
||||
if not series:
|
||||
# Fallback: try searching by series title extracted from path
|
||||
series_title = self._extract_series_title_from_path(series_path)
|
||||
if series_title:
|
||||
_log("DEBUG", f"Trying title search for: {series_title}")
|
||||
series = self.sonarr.series_by_title(series_title)
|
||||
if series:
|
||||
series_id = series.get("id")
|
||||
if series_id:
|
||||
@@ -568,7 +571,7 @@ class TVProcessor:
|
||||
for (season, episode), (aired, dateadded, source) in episode_dates.items():
|
||||
if (season, episode) in disk_episodes:
|
||||
# Get enhanced episode metadata
|
||||
enhanced_metadata = self._get_episode_metadata(series_metadata, season, episode, season_path) if series_metadata else self._get_episode_metadata(None, season, episode, season_path)
|
||||
enhanced_metadata = self._get_episode_metadata(series_metadata, season, episode) if series_metadata else None
|
||||
|
||||
# Create NFO
|
||||
if config.manage_nfo:
|
||||
@@ -587,12 +590,8 @@ class TVProcessor:
|
||||
# Save to database
|
||||
self.db.upsert_episode_date(imdb_id, season, episode, aired, dateadded, source, True)
|
||||
|
||||
# Create season NFO
|
||||
if config.manage_nfo:
|
||||
self.nfo_manager.create_season_nfo(season_path, season_num)
|
||||
# Get TVDB ID for better Emby compatibility
|
||||
tvdb_id = self.external_clients.get_tvdb_series_id(imdb_id)
|
||||
self.nfo_manager.create_tvshow_nfo(series_path, imdb_id, tvdb_id)
|
||||
# Skip season.nfo and tvshow.nfo creation - focus only on episode NFOs
|
||||
pass
|
||||
|
||||
_log("INFO", f"Completed processing season {season_num}")
|
||||
|
||||
@@ -623,21 +622,6 @@ class TVProcessor:
|
||||
source = nfo_data["source"]
|
||||
aired = nfo_data.get("aired")
|
||||
|
||||
# Check if NFO is missing title and try to enhance it
|
||||
if not nfo_data.get("has_title", False):
|
||||
_log("INFO", f"NFO S{season_num:02d}E{episode_num:02d} missing title, attempting to extract from filename")
|
||||
filename_title = self._extract_title_from_filename(season_path, season_num, episode_num)
|
||||
if filename_title:
|
||||
enhanced = self.nfo_manager.enhance_existing_episode_nfo_with_title(
|
||||
season_path, season_num, episode_num, filename_title
|
||||
)
|
||||
if enhanced:
|
||||
_log("INFO", f"✅ Enhanced NFO S{season_num:02d}E{episode_num:02d} with title: '{filename_title}'")
|
||||
else:
|
||||
_log("WARNING", f"Failed to enhance NFO S{season_num:02d}E{episode_num:02d} with title")
|
||||
else:
|
||||
_log("WARNING", f"Could not extract title from filename for S{season_num:02d}E{episode_num:02d}")
|
||||
|
||||
# Update file mtime if enabled (NFO is already correct)
|
||||
if config.fix_dir_mtimes and dateadded:
|
||||
self.nfo_manager.set_file_mtime(episode_file, dateadded)
|
||||
@@ -654,14 +638,12 @@ class TVProcessor:
|
||||
source = existing_episode["source"]
|
||||
aired = existing_episode.get("aired")
|
||||
|
||||
# Create NFO with existing data, but try to add title from filename if missing
|
||||
# Create NFO with existing data (no enhanced metadata needed)
|
||||
if config.manage_nfo and dateadded:
|
||||
# Try to extract title from filename as fallback for Tier 2 processing
|
||||
enhanced_metadata = self._get_episode_metadata(None, season_num, episode_num, season_path)
|
||||
self.nfo_manager.create_episode_nfo(
|
||||
season_path,
|
||||
season_num, episode_num, aired, dateadded, source, config.lock_metadata,
|
||||
enhanced_metadata # Include filename-extracted title if available
|
||||
None # No enhanced metadata for database-cached episodes
|
||||
)
|
||||
|
||||
# Update file mtime if enabled
|
||||
@@ -676,13 +658,13 @@ class TVProcessor:
|
||||
|
||||
# Get enhanced metadata from Sonarr
|
||||
series_metadata = self._get_sonarr_series_metadata(imdb_id)
|
||||
enhanced_metadata = self._get_episode_metadata(series_metadata, season_num, episode_num, season_path) if series_metadata else self._get_episode_metadata(None, season_num, episode_num, season_path)
|
||||
enhanced_metadata = self._get_episode_metadata(series_metadata, season_num, episode_num) if series_metadata else None
|
||||
|
||||
# Get episode date
|
||||
aired, dateadded, source = self._get_single_episode_date(imdb_id, season_num, episode_num, series_metadata)
|
||||
|
||||
# Create NFO
|
||||
if config.manage_nfo and dateadded:
|
||||
# Create NFO (always create if manage_nfo is enabled, even without dateadded)
|
||||
if config.manage_nfo:
|
||||
self.nfo_manager.create_episode_nfo(
|
||||
season_path,
|
||||
season_num, episode_num, aired, dateadded, source, config.lock_metadata,
|
||||
@@ -731,7 +713,6 @@ class TVProcessor:
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_sonarr_series_metadata(self, imdb_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get enhanced series metadata from Sonarr API"""
|
||||
try:
|
||||
@@ -739,6 +720,9 @@ class TVProcessor:
|
||||
return None
|
||||
|
||||
series = self.sonarr.series_by_imdb(imdb_id)
|
||||
if not series:
|
||||
_log("DEBUG", f"Trying direct series search for IMDb: {imdb_id}")
|
||||
series = self.sonarr.series_by_imdb_direct(imdb_id)
|
||||
if not series:
|
||||
_log("DEBUG", f"No Sonarr series found for IMDb {imdb_id}")
|
||||
return None
|
||||
@@ -756,97 +740,20 @@ class TVProcessor:
|
||||
_log("ERROR", f"Error getting Sonarr metadata for {imdb_id}: {e}")
|
||||
return None
|
||||
|
||||
def _get_episode_metadata(self, series_metadata: Dict[str, Any], season_num: int, episode_num: int, season_dir: Optional[Path] = None) -> Optional[Dict[str, Any]]:
|
||||
"""Extract specific episode metadata from series data with filename fallback for titles"""
|
||||
_log("INFO", f"🔍 _get_episode_metadata called for S{season_num:02d}E{episode_num:02d}, season_dir: {season_dir}")
|
||||
metadata = {
|
||||
"title": None,
|
||||
"overview": None,
|
||||
"runtime": None,
|
||||
"ratings": {}
|
||||
}
|
||||
def _get_episode_metadata(self, series_metadata: Dict[str, Any], season_num: int, episode_num: int) -> Optional[Dict[str, Any]]:
|
||||
"""Extract specific episode metadata from series data"""
|
||||
if not series_metadata or "episodes" not in series_metadata:
|
||||
return None
|
||||
|
||||
# Try to get metadata from Sonarr first
|
||||
if series_metadata and "episodes" in series_metadata:
|
||||
for episode in series_metadata["episodes"]:
|
||||
if episode.get("seasonNumber") == season_num and episode.get("episodeNumber") == episode_num:
|
||||
metadata.update({
|
||||
"title": episode.get("title"),
|
||||
"overview": episode.get("overview"),
|
||||
"runtime": episode.get("runtime"),
|
||||
"ratings": episode.get("ratings", {})
|
||||
})
|
||||
break
|
||||
for episode in series_metadata["episodes"]:
|
||||
if episode.get("seasonNumber") == season_num and episode.get("episodeNumber") == episode_num:
|
||||
return {
|
||||
"title": episode.get("title"),
|
||||
"overview": episode.get("overview"),
|
||||
"runtime": episode.get("runtime"),
|
||||
"ratings": episode.get("ratings", {})
|
||||
}
|
||||
|
||||
# If no title from Sonarr, try to extract from filename
|
||||
if not metadata["title"] and season_dir:
|
||||
_log("INFO", f"📁 No title from Sonarr for S{season_num:02d}E{episode_num:02d}, trying filename extraction")
|
||||
filename_title = self._extract_title_from_filename(season_dir, season_num, episode_num)
|
||||
if filename_title:
|
||||
metadata["title"] = filename_title
|
||||
_log("INFO", f"✅ Using filename-extracted title for S{season_num:02d}E{episode_num:02d}: '{filename_title}'")
|
||||
else:
|
||||
_log("DEBUG", f"⚠️ No filename title extracted for S{season_num:02d}E{episode_num:02d}")
|
||||
elif metadata["title"]:
|
||||
_log("DEBUG", f"✅ Using Sonarr title for S{season_num:02d}E{episode_num:02d}: '{metadata['title']}'")
|
||||
|
||||
# Return metadata if we have at least some information
|
||||
if any(metadata.values()):
|
||||
return metadata
|
||||
|
||||
return None
|
||||
|
||||
def _extract_title_from_filename(self, season_dir: Path, season_num: int, episode_num: int) -> Optional[str]:
|
||||
"""Extract episode title from video filename as fallback when Sonarr doesn't provide it"""
|
||||
try:
|
||||
import re
|
||||
# Look for video files matching this episode
|
||||
season_pattern = f"S{season_num:02d}E{episode_num:02d}"
|
||||
_log("INFO", f"🔍 Searching for title in files for {season_pattern} in directory: {season_dir}")
|
||||
|
||||
for video_file in season_dir.glob("*.mkv"):
|
||||
filename = video_file.name
|
||||
_log("DEBUG", f"🔍 Checking file: {filename}")
|
||||
if season_pattern in filename:
|
||||
_log("DEBUG", f"✅ Found matching file: {filename}")
|
||||
# Pattern: SeriesName-S01E01-Episode Title[WEBDL-1080p][AAC2.0][h264].mkv
|
||||
# Extract the part between season/episode and the first bracket
|
||||
pattern = rf'{season_pattern}-(.*?)\['
|
||||
_log("DEBUG", f"🔍 Using regex pattern: {pattern}")
|
||||
match = re.search(pattern, filename)
|
||||
if match:
|
||||
title = match.group(1).strip()
|
||||
_log("DEBUG", f"🔍 Raw extracted title: '{title}'")
|
||||
# Clean up common encoding artifacts and separators
|
||||
title = title.replace('-', ' ').strip()
|
||||
if title:
|
||||
_log("INFO", f"✅ Extracted title from filename: '{title}' for {season_pattern}")
|
||||
return title
|
||||
else:
|
||||
_log("DEBUG", f"⚠️ Title was empty after cleanup")
|
||||
else:
|
||||
_log("DEBUG", f"⚠️ Regex pattern didn't match filename")
|
||||
|
||||
# Also check .mp4 files
|
||||
for video_file in season_dir.glob("*.mp4"):
|
||||
filename = video_file.name
|
||||
_log("DEBUG", f"🔍 Checking .mp4 file: {filename}")
|
||||
if season_pattern in filename:
|
||||
_log("DEBUG", f"✅ Found matching .mp4 file: {filename}")
|
||||
pattern = rf'{season_pattern}-(.*?)\['
|
||||
match = re.search(pattern, filename)
|
||||
if match:
|
||||
title = match.group(1).strip()
|
||||
_log("DEBUG", f"🔍 Raw extracted title from .mp4: '{title}'")
|
||||
title = title.replace('-', ' ').strip()
|
||||
if title:
|
||||
_log("INFO", f"✅ Extracted title from .mp4 filename: '{title}' for {season_pattern}")
|
||||
return title
|
||||
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Error extracting title from filename for S{season_num:02d}E{episode_num:02d}: {e}")
|
||||
|
||||
_log("DEBUG", f"⚠️ No title found in filenames for {season_pattern}")
|
||||
return None
|
||||
|
||||
def _gather_season_episode_dates(self, series_path: Path, imdb_id: str, season_num: int, disk_episodes: Dict[Tuple[int, int], List[Path]], series_metadata: Optional[Dict[str, Any]] = None) -> Dict[Tuple[int, int], Tuple[Optional[str], Optional[str], str]]:
|
||||
@@ -880,6 +787,14 @@ class TVProcessor:
|
||||
if self.sonarr.enabled:
|
||||
try:
|
||||
series = self.sonarr.series_by_imdb(imdb_id)
|
||||
if not series:
|
||||
_log("DEBUG", f"Trying direct series search for IMDb: {imdb_id}")
|
||||
series = self.sonarr.series_by_imdb_direct(imdb_id)
|
||||
if not series:
|
||||
# Try title search as fallback for IMDb mismatches
|
||||
_log("DEBUG", f"Trying title search fallback for IMDb: {imdb_id}")
|
||||
# We don't have series_path here, so just try the imdb_id as a title search
|
||||
# This is less ideal but better than nothing
|
||||
if series:
|
||||
episodes = self.sonarr.episodes_for_series(series["id"])
|
||||
for ep in episodes:
|
||||
@@ -963,7 +878,7 @@ class TVProcessor:
|
||||
# Get episode date information - webhook processing prioritizes existing DB entries
|
||||
_log("DEBUG", f"Processing webhook episode: IMDb={imdb_id}, S{season_num:02d}E{episode_num:02d}")
|
||||
aired, dateadded, source = self._get_webhook_episode_date(imdb_id, season_num, episode_num, series_metadata)
|
||||
enhanced_metadata = self._get_episode_metadata(series_metadata, season_num, episode_num, season_dir) if series_metadata else self._get_episode_metadata(None, season_num, episode_num, season_dir)
|
||||
enhanced_metadata = self._get_episode_metadata(series_metadata, season_num, episode_num) if series_metadata else None
|
||||
|
||||
# Create NFO
|
||||
if config.manage_nfo:
|
||||
@@ -990,20 +905,8 @@ class TVProcessor:
|
||||
|
||||
episodes_processed += 1
|
||||
|
||||
# Create season/tvshow NFOs if any episodes were processed
|
||||
if episodes_processed > 0 and config.manage_nfo:
|
||||
seasons_processed = set()
|
||||
for webhook_episode in webhook_episodes:
|
||||
season_num = webhook_episode.get("seasonNumber")
|
||||
if season_num and season_num not in seasons_processed:
|
||||
season_dir = series_path / config.tv_season_dir_format.format(season=season_num)
|
||||
if season_dir.exists():
|
||||
self.nfo_manager.create_season_nfo(season_dir, season_num)
|
||||
seasons_processed.add(season_num)
|
||||
|
||||
# Get TVDB ID for better Emby compatibility
|
||||
tvdb_id = self.external_clients.get_tvdb_series_id(imdb_id)
|
||||
self.nfo_manager.create_tvshow_nfo(series_path, imdb_id, tvdb_id)
|
||||
# Skip season.nfo and tvshow.nfo creation - focus only on episode NFOs
|
||||
pass
|
||||
|
||||
_log("INFO", f"Completed targeted processing: {episodes_processed}/{len(webhook_episodes)} episodes processed")
|
||||
|
||||
@@ -1047,6 +950,9 @@ class TVProcessor:
|
||||
if not aired and self.sonarr.enabled:
|
||||
try:
|
||||
series = self.sonarr.series_by_imdb(imdb_id)
|
||||
if not series:
|
||||
_log("DEBUG", f"Trying direct series search for IMDb: {imdb_id}")
|
||||
series = self.sonarr.series_by_imdb_direct(imdb_id)
|
||||
if series:
|
||||
episodes = self.sonarr.episodes_for_series(series["id"])
|
||||
for ep in episodes:
|
||||
@@ -1562,13 +1468,12 @@ class MovieProcessor:
|
||||
class WebhookBatcher:
|
||||
"""Batches webhook events to avoid processing storms"""
|
||||
|
||||
def __init__(self, nfo_manager: NFOManager):
|
||||
def __init__(self):
|
||||
self.pending: Dict[str, Dict] = {}
|
||||
self.timers: Dict[str, threading.Timer] = {}
|
||||
self.processing: Set[str] = set()
|
||||
self.lock = threading.Lock()
|
||||
self.executor = ThreadPoolExecutor(max_workers=config.max_concurrent)
|
||||
self.nfo_manager = nfo_manager
|
||||
|
||||
def add_webhook(self, key: str, webhook_data: Dict, media_type: str):
|
||||
"""Add webhook to batch queue"""
|
||||
@@ -1622,23 +1527,11 @@ class WebhookBatcher:
|
||||
# CRITICAL: Validate that the path contains the expected IMDb ID for movies
|
||||
if media_type == 'movie':
|
||||
expected_imdb = key.replace('movie:', '') if key.startswith('movie:') else key
|
||||
# Use comprehensive IMDb detection (directory, filenames, NFO content)
|
||||
detected_imdb = self.nfo_manager.find_movie_imdb_id(path_obj)
|
||||
|
||||
# Check if detected IMDb matches expected (handle both full IMDb IDs and just numbers)
|
||||
imdb_match = False
|
||||
if detected_imdb:
|
||||
if detected_imdb == expected_imdb:
|
||||
imdb_match = True
|
||||
elif detected_imdb.replace('tt', '') == expected_imdb.replace('tt', ''):
|
||||
imdb_match = True
|
||||
|
||||
if not imdb_match:
|
||||
if expected_imdb not in path_str.lower():
|
||||
_log("ERROR", f"BATCH VALIDATION FAILED: Expected IMDb {expected_imdb} not found in path {path_str}")
|
||||
_log("ERROR", f"Detected IMDb: {detected_imdb}, Expected: {expected_imdb}")
|
||||
_log("ERROR", f"This prevents processing wrong movies due to batch corruption")
|
||||
return
|
||||
_log("DEBUG", f"Batch validation passed: Expected IMDb {expected_imdb} matches detected IMDb {detected_imdb}")
|
||||
_log("DEBUG", f"Batch validation passed: IMDb {expected_imdb} found in path")
|
||||
|
||||
# Process based on media type
|
||||
if media_type == 'tv':
|
||||
@@ -1721,7 +1614,7 @@ nfo_manager = NFOManager(config.manager_brand, config.debug)
|
||||
path_mapper = PathMapper(config) # FIXED: Pass config to PathMapper
|
||||
tv_processor = TVProcessor(db, nfo_manager, path_mapper)
|
||||
movie_processor = MovieProcessor(db, nfo_manager, path_mapper)
|
||||
batcher = WebhookBatcher(nfo_manager)
|
||||
batcher = WebhookBatcher()
|
||||
|
||||
# ---------------------------
|
||||
# Webhook Handlers
|
||||
@@ -1834,18 +1727,9 @@ async def radarr_webhook(request: Request, background_tasks: BackgroundTasks):
|
||||
_log("ERROR", f"This prevents processing wrong movies due to path mapping issues")
|
||||
return {"status": "error", "message": f"Mapped movie path does not exist: {container_path}"}
|
||||
|
||||
# Verify the path contains the expected IMDb ID using comprehensive detection
|
||||
detected_imdb = nfo_manager.find_movie_imdb_id(Path(container_path))
|
||||
imdb_match = False
|
||||
if detected_imdb:
|
||||
if detected_imdb == imdb_id or detected_imdb.replace('tt', '') == imdb_id.replace('tt', ''):
|
||||
imdb_match = True
|
||||
|
||||
if not imdb_match:
|
||||
_log("WARNING", f"IMDb ID {imdb_id} not found via comprehensive detection in {container_path}")
|
||||
_log("DEBUG", f"Detected IMDb: {detected_imdb}, Expected: {imdb_id}")
|
||||
else:
|
||||
_log("DEBUG", f"IMDb ID validated: {imdb_id} matches detected {detected_imdb}")
|
||||
# Verify the path contains the expected IMDb ID
|
||||
if imdb_id not in container_path.lower():
|
||||
_log("WARNING", f"IMDb ID {imdb_id} not found in container path {container_path}")
|
||||
|
||||
# Create movie-specific webhook data with proper path validation
|
||||
movie_webhook_data = {
|
||||
@@ -2193,13 +2077,23 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Failed processing episode {scan_path}: {e}")
|
||||
else:
|
||||
# Full series processing
|
||||
for item in scan_path.iterdir():
|
||||
if item.is_dir() and nfo_manager.parse_imdb_from_path(item):
|
||||
try:
|
||||
tv_processor.process_series(item)
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Failed processing TV series {item}: {e}")
|
||||
# Check if this path itself is a series (has IMDb ID in the directory name)
|
||||
if nfo_manager.parse_imdb_from_path(scan_path):
|
||||
try:
|
||||
tv_processor.process_series(scan_path)
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Failed processing TV series {scan_path}: {e}")
|
||||
else:
|
||||
# Full series processing - scan subdirectories
|
||||
for item in scan_path.iterdir():
|
||||
if (item.is_dir() and
|
||||
not item.name.lower().startswith('season') and
|
||||
not re.match(r'^season\s+\d+$', item.name, re.IGNORECASE) and
|
||||
nfo_manager.parse_imdb_from_path(item)):
|
||||
try:
|
||||
tv_processor.process_series(item)
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Failed processing TV series {item}: {e}")
|
||||
|
||||
if scan_type in ["both", "movies"] and scan_path in config.movie_paths:
|
||||
_log("INFO", f"Scanning movies in: {scan_path}")
|
||||
|
||||
Reference in New Issue
Block a user