Compare commits

...

22 Commits

Author SHA1 Message Date
sbcrumb ade771f661 Merge branch 'dev'
Local Docker Build (Main) / build (push) Successful in 39s
Local Docker Build (Dev) / build-dev (push) Successful in 34s
Local Docker Build (Main) / deploy (push) Successful in 0s
2025-09-25 14:26:31 -04:00
sbcrumb 3e8ea79cbb update emby dll
Local Docker Build (Dev) / build-dev (push) Successful in 23s
Local Docker Build (Main) / build (pull_request) Successful in 24s
Local Docker Build (Main) / deploy (pull_request) Has been skipped
2025-09-25 14:23:28 -04:00
sbcrumb 2b6fd27913 Fix movie webhook comprehensive IMDb detection and batch processing
Local Docker Build (Dev) / build-dev (push) Successful in 28s
Local Docker Build (Main) / build (pull_request) Successful in 24s
Local Docker Build (Main) / deploy (pull_request) Has been skipped
- Fix movie webhook to use comprehensive IMDb detection (dir, filename, movie.nfo) instead of simple string search
- Fix batch processing NameError: use path_obj instead of undefined path variable
- Version bump to 1.8.3

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 13:31:27 -04:00
sbcrumb a8f8e0a7bd Fix movie webhook processing and organize NFOGuard fields
Local Docker Build (Dev) / build-dev (push) Successful in 34s
- Fix WebhookBatcher missing nfo_manager attribute that caused movie processing errors
- Move NFOGuard comment from top to bottom of episode NFOs with other NFOGuard fields
- All NFOGuard additions now grouped together at bottom for better organization

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 13:11:23 -04:00
sbcrumb 5e3d4845b0 fix: Use comprehensive IMDb detection for movie batch validation
Local Docker Build (Dev) / build-dev (push) Successful in 33s
- Replace simple string search with find_movie_imdb_id() method
- Check directory name, filenames, and NFO file content for IMDb ID
- Handle both full IMDb IDs (tt26394837) and number formats
- Add detailed logging for detected vs expected IMDb matching
- Fix validation failures when IMDb ID is in filename/NFO but not directory name

Resolves movie webhook processing failures when directory lacks IMDb ID
but files contain proper identification.
2025-09-25 10:38:27 -04:00
sbcrumb 6a20337181 fix: Improve title detection logic to prevent duplicates
Local Docker Build (Dev) / build-dev (push) Successful in 26s
- Fix flawed title detection that caused duplicates when migrating NFOs
- Add robust title validation (check for non-empty, non-whitespace text)
- Improve debugging output to show title detection process
- Properly handle existing valid titles vs missing/empty titles
- Remove invalid title elements before adding filename-extracted titles

Resolves duplicate <title> elements in NFO files during migration.
2025-09-25 10:33:03 -04:00
sbcrumb f1420cac58 fix: Add title extraction directly to create_episode_nfo method
Local Docker Build (Dev) / build-dev (push) Successful in 19s
- Add _extract_title_from_filename method to NFOManager class
- Integrate title extraction fallback in create_episode_nfo method
- Extract titles when NFO lacks title element or has empty title
- Support both .mkv and .mp4 files with comprehensive logging
- Fix applied to actual NFO creation method being used by episodes

Resolves title extraction by working in the correct code path
rather than relying on metadata processing that wasn't being called.
2025-09-25 09:40:26 -04:00
sbcrumb 39c1e54922 debug: Add INFO log to track if _get_episode_metadata is called
Local Docker Build (Dev) / build-dev (push) Successful in 23s
2025-09-25 09:36:08 -04:00
sbcrumb e870474f07 debug: Change DEBUG logs to INFO to trace title extraction
Local Docker Build (Dev) / build-dev (push) Successful in 18s
2025-09-25 09:32:58 -04:00
sbcrumb b4fe0cb7fe bump: Version 1.8.2 with title extraction functionality
Local Docker Build (Dev) / build-dev (push) Successful in 28s
2025-09-25 09:26:09 -04:00
sbcrumb fef99b9bfd Merge pull request 'titlename-fix' (#10) from titlename-fix into dev
Local Docker Build (Dev) / build-dev (push) Successful in 25s
Reviewed-on: #10
2025-09-25 09:15:07 -04:00
sbcrumb c9567ce70b update: update version number 2025-09-25 09:15:07 -04:00
sbcrumb ba488cdbea Merge pull request 'titlename-fix' (#9) from titlename-fix into dev
Local Docker Build (Dev) / build-dev (push) Successful in 33s
Reviewed-on: #9
2025-09-25 09:08:50 -04:00
sbcrumb c33a26460d debug: Add comprehensive debugging for title extraction
- Add detailed debug logging to _extract_title_from_filename() method
- Log file search, regex pattern matching, and title cleanup process
- Fix Tier 2 processing to call _get_episode_metadata() for title extraction
- Add debug logging to _get_episode_metadata() to trace execution flow
- Include both .mkv and .mp4 file checking with detailed logs

This should help identify why title extraction isn't working for
recreated NFO files and provide visibility into the extraction process.
2025-09-25 09:08:50 -04:00
sbcrumb 91031ba321 Merge pull request 'fix: Add title enhancement for existing NFO files' (#8) from titlename-fix into dev
Local Docker Build (Dev) / build-dev (push) Successful in 33s
Reviewed-on: #8
2025-09-25 09:01:04 -04:00
sbcrumb a1921936f7 fix: Add title enhancement for existing NFO files
- Modify extract_nfoguard_dates_from_episode_nfo() to detect missing titles
- Add enhance_existing_episode_nfo_with_title() method to update NFOs with titles
- Enhance Tier 1 processing to check for missing titles in existing NFO files
- Extract titles from filenames and update existing NFOs that lack title elements
- Fix NFO filename case to match S01E01.nfo format
- Preserves all existing NFO content while adding missing title elements

Addresses issue where existing NFO files with NFOGuard metadata were
skipping title extraction due to Tier 1 optimization caching.
2025-09-25 09:01:04 -04:00
sbcrumb 7131aa211c Merge pull request 'fix: Add filename-based title extraction for TV episodes' (#7) from titlename-fix into dev
Local Docker Build (Dev) / build-dev (push) Successful in 22s
Reviewed-on: #7
2025-09-25 08:54:51 -04:00
sbcrumb 16d9956202 fix: Add filename-based title extraction for TV episodes
Local Docker Build (Dev) / build-dev (pull_request) Successful in 18s
- Add _extract_title_from_filename() method to extract episode titles from video filenames
- Enhance _get_episode_metadata() to use filename fallback when Sonarr API doesn't provide titles
- Update all _get_episode_metadata() calls to pass season directory for filename extraction
- Extract titles from patterns like: Series-S01E01-Episode Title[WEBDL-1080p][AAC2.0][h264].mkv
- Preserves existing Sonarr API behavior while providing fallback for missing titles
- Fixes episodes with missing <title> elements in NFO files when Sonarr data is incomplete

Resolves issue where episodes processed via sonarr:history.import had empty NFO titles
despite clear title information being available in the video filenames.
2025-09-25 08:53:56 -04:00
sbcrumb c21a0df9c1 Merge pull request 'fix: nfo' (#5) from nfofix into dev
Local Docker Build (Dev) / build-dev (push) Successful in 28s
Local Docker Build (Main) / build (pull_request) Successful in 23s
Local Docker Build (Main) / deploy (pull_request) Has been skipped
Reviewed-on: #5
2025-09-24 19:36:18 -04:00
sbcrumb 27430a7ead fix: nfo
Local Docker Build (Dev) / build-dev (pull_request) Successful in 23s
2025-09-24 19:35:39 -04:00
sbcrumb 210af8ac4b tv-nfo (#4)
Local Docker Build (Dev) / build-dev (push) Successful in 23s
Reviewed-on: #4
Co-authored-by: SBCrumb <sbcrumb@kickthetree.com>
Co-committed-by: SBCrumb <sbcrumb@kickthetree.com>
2025-09-24 18:53:48 -04:00
sbcrumb ca2e77f4c7 remove .local
Local Docker Build (Dev) / build-dev (push) Successful in 17s
2025-09-24 15:14:40 -04:00
4 changed files with 273 additions and 38 deletions
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
1.8.0
1.8.3
+132 -13
View File
@@ -163,7 +163,7 @@ class NFOManager:
def extract_nfoguard_dates_from_episode_nfo(self, season_path: Path, season_num: int, episode_num: int) -> Optional[Dict[str, str]]:
"""Extract NFOGuard-managed dates from existing episode NFO file"""
nfo_filename = f"s{season_num:02d}e{episode_num:02d}.nfo"
nfo_filename = f"S{season_num:02d}E{episode_num:02d}.nfo"
nfo_path = season_path / nfo_filename
if not nfo_path.exists():
@@ -190,7 +190,11 @@ class NFOManager:
if aired_elem is not None and aired_elem.text:
result["aired"] = aired_elem.text.strip()
print(f"✅ Found NFOGuard data in episode NFO S{season_num:02d}E{episode_num:02d}: dateadded={result['dateadded']}, aired={result.get('aired', 'None')}")
# Check if title is missing
title_elem = root.find('.//title')
result["has_title"] = title_elem is not None and title_elem.text and title_elem.text.strip()
print(f"✅ Found NFOGuard data in episode NFO S{season_num:02d}E{episode_num:02d}: dateadded={result['dateadded']}, aired={result.get('aired', 'None')}, has_title={result['has_title']}")
return result
except (ET.ParseError, Exception) as e:
@@ -199,6 +203,104 @@ class NFOManager:
return None
def enhance_existing_episode_nfo_with_title(self, season_path: Path, season_num: int, episode_num: int, title: str) -> bool:
"""Add title to existing episode NFO file that's missing one"""
nfo_filename = f"S{season_num:02d}E{episode_num:02d}.nfo"
nfo_path = season_path / nfo_filename
if not nfo_path.exists():
return False
try:
tree = ET.parse(nfo_path)
root = tree.getroot()
# Check if title already exists
existing_title = root.find('.//title')
if existing_title is not None and existing_title.text and existing_title.text.strip():
return False # Title already exists
# Add title element (insert near the top, after any existing plot element)
title_elem = ET.Element("title")
title_elem.text = title
# Find the best position to insert title (after plot if it exists, otherwise at the beginning)
plot_elem = root.find('.//plot')
if plot_elem is not None:
# Insert after plot
plot_index = list(root).index(plot_elem)
root.insert(plot_index + 1, title_elem)
else:
# Insert at the beginning (after any existing title that might be empty)
if existing_title is not None:
title_index = list(root).index(existing_title)
root.remove(existing_title)
root.insert(title_index, title_elem)
else:
root.insert(0, title_elem)
# Write the updated NFO
tree.write(nfo_path, encoding='utf-8', xml_declaration=True)
print(f"📝 Enhanced existing NFO with title: S{season_num:02d}E{episode_num:02d} - '{title}'")
return True
except Exception as e:
print(f"⚠️ Error enhancing NFO with title: {e}")
return False
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 metadata 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}"
print(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
print(f"🔍 Checking file: {filename}")
if season_pattern in filename:
print(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}-(.*?)\['
print(f"🔍 Using regex pattern: {pattern}")
match = re.search(pattern, filename)
if match:
title = match.group(1).strip()
print(f"🔍 Raw extracted title: '{title}'")
# Clean up common encoding artifacts and separators
title = title.replace('-', ' ').strip()
if title:
print(f"✅ Extracted title from filename: '{title}' for {season_pattern}")
return title
else:
print(f"⚠️ Title was empty after cleanup")
else:
print(f"⚠️ Regex pattern didn't match filename")
# Also check .mp4 files
for video_file in season_dir.glob("*.mp4"):
filename = video_file.name
print(f"🔍 Checking .mp4 file: {filename}")
if season_pattern in filename:
print(f"✅ Found matching .mp4 file: {filename}")
pattern = rf'{season_pattern}-(.*?)\['
match = re.search(pattern, filename)
if match:
title = match.group(1).strip()
print(f"🔍 Raw extracted title from .mp4: '{title}'")
title = title.replace('-', ' ').strip()
if title:
print(f"✅ Extracted title from .mp4 filename: '{title}' for {season_pattern}")
return title
except Exception as e:
print(f"⚠️ Error extracting title from filename for S{season_num:02d}E{episode_num:02d}: {e}")
print(f"⚠️ No title found in filenames for {season_pattern}")
return None
def _parse_nfo_with_tolerance(self, nfo_path: Path):
"""Parse NFO file with tolerance for URLs appended after XML"""
try:
@@ -594,6 +696,29 @@ class NFOManager:
runtime_elem = ET.SubElement(episode, "runtime")
runtime_elem.text = str(enhanced_metadata["runtime"])
# Fallback: Extract title from filename if no valid title present
title_elem = episode.find("title")
has_valid_title = title_elem is not None and title_elem.text and title_elem.text.strip()
print(f"🔍 Title check for S{season_num:02d}E{episode_num:02d}: has_element={title_elem is not None}, has_text={title_elem.text if title_elem is not None else 'N/A'}, has_valid_title={has_valid_title}")
if not has_valid_title:
print(f"🔍 No valid title found in NFO for S{season_num:02d}E{episode_num:02d}, attempting filename extraction")
filename_title = self._extract_title_from_filename(season_dir, season_num, episode_num)
if filename_title:
# Remove existing empty/invalid title element if present
if title_elem is not None:
episode.remove(title_elem)
# Add new title element
title_elem = ET.SubElement(episode, "title")
title_elem.text = filename_title
print(f"✅ Added filename-extracted title to NFO: S{season_num:02d}E{episode_num:02d} - '{filename_title}'")
else:
print(f"⚠️ Could not extract title from filename for S{season_num:02d}E{episode_num:02d}")
else:
print(f"✅ Valid title already exists in NFO for S{season_num:02d}E{episode_num:02d}: '{title_elem.text.strip()}')")
# Add NFOGuard fields at the bottom
# Basic episode info at the end
@@ -617,22 +742,16 @@ class NFOManager:
lockdata = ET.SubElement(episode, "lockdata")
lockdata.text = "true"
# Add NFOGuard comment at the beginning
comment_text = f" Created by {self.manager_brand} - Source: {source} "
# Add NFOGuard comment at the bottom with other NFOGuard fields
nfoguard_comment = ET.Comment(f" Created by {self.manager_brand} - Source: {source} ")
episode.append(nfoguard_comment)
# Write file with proper formatting
tree = ET.ElementTree(episode)
ET.indent(tree, space=" ", level=0)
# Write to string first to add comment
xml_str = ET.tostring(episode, encoding='unicode')
# Add XML declaration and comment
full_xml = f'<?xml version="1.0" encoding="utf-8"?>\n<!--{comment_text}-->\n{xml_str}'
# Write to file
with open(nfo_path, 'w', encoding='utf-8') as f:
f.write(full_xml)
# Write to file normally (ET will handle the comment properly)
tree.write(nfo_path, encoding='utf-8', xml_declaration=True)
print(f"✅ Successfully created/updated episode NFO: {nfo_path}")
print(f" S{season_num:02d}E{episode_num:02d}, Aired: {aired}, Date Added: {dateadded}")
+134 -18
View File
@@ -568,7 +568,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) if series_metadata else None
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)
# Create NFO
if config.manage_nfo:
@@ -623,6 +623,21 @@ 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)
@@ -639,12 +654,14 @@ class TVProcessor:
source = existing_episode["source"]
aired = existing_episode.get("aired")
# Create NFO with existing data (no enhanced metadata needed)
# Create NFO with existing data, but try to add title from filename if missing
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,
None # No enhanced metadata for database-cached episodes
enhanced_metadata # Include filename-extracted title if available
)
# Update file mtime if enabled
@@ -659,7 +676,7 @@ 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) if series_metadata else None
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)
# Get episode date
aired, dateadded, source = self._get_single_episode_date(imdb_id, season_num, episode_num, series_metadata)
@@ -739,20 +756,97 @@ 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) -> Optional[Dict[str, Any]]:
"""Extract specific episode metadata from series data"""
if not series_metadata or "episodes" not in series_metadata:
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": {}
}
# 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:
return {
metadata.update({
"title": episode.get("title"),
"overview": episode.get("overview"),
"runtime": episode.get("runtime"),
"ratings": episode.get("ratings", {})
}
})
break
# 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]]:
@@ -869,7 +963,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) if series_metadata else None
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)
# Create NFO
if config.manage_nfo:
@@ -1468,12 +1562,13 @@ class MovieProcessor:
class WebhookBatcher:
"""Batches webhook events to avoid processing storms"""
def __init__(self):
def __init__(self, nfo_manager: NFOManager):
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"""
@@ -1527,11 +1622,23 @@ 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
if expected_imdb not in path_str.lower():
# 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:
_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: IMDb {expected_imdb} found in path")
_log("DEBUG", f"Batch validation passed: Expected IMDb {expected_imdb} matches detected IMDb {detected_imdb}")
# Process based on media type
if media_type == 'tv':
@@ -1614,7 +1721,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()
batcher = WebhookBatcher(nfo_manager)
# ---------------------------
# Webhook Handlers
@@ -1727,9 +1834,18 @@ 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
if imdb_id not in container_path.lower():
_log("WARNING", f"IMDb ID {imdb_id} not found in container path {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}")
# Create movie-specific webhook data with proper path validation
movie_webhook_data = {