From f4c56f6cbebeb7b8efd96c9584d10f9cb9ef9411 Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Sat, 13 Sep 2025 19:52:43 -0400 Subject: [PATCH] feat: smart NFO management and correct episode date field mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💾 Smart NFO File Management: - Add intelligent content comparison to prevent unnecessary file overwrites - Only update NFO files when content actually changes (ignoring timestamp differences) - Dramatically reduce file system writes and improve scan performance - Applied to both TV episodes and movies for comprehensive efficiency - New _nfo_content_matches() method for smart content comparison 🗓️ Fix Episode Date Field Mapping: - BREAKING: Correct date field usage in episode NFOs - and : Now use historical air dates (e.g., 1951-10-15) - : Correctly shows actual download/import date (e.g., 2025-09-13) - Resolves confusion between when show aired vs when user downloaded it - Media servers now show historically accurate metadata with working "Recently Added" 🎯 Technical Changes: - Enhanced create_episode_nfo() with content comparison logic - Enhanced create_movie_nfo() with content comparison logic - Separated historical date logic from import date logic - Added comprehensive debug logging for file skip/update decisions 🏆 User Experience: - Faster scans with fewer unnecessary file operations - Correct historical context in media servers - "Recently Added" functionality preserved and working - No more metadata confusion between air dates and import dates Example NFO output: - 1951-10-15 (historical) - 2025-09-13T21:39:28+00:00 (when downloaded) - 1951-10-15 (historical) --- SUMMARY.md | 45 ++++++++++++++++++++++++++- core/nfo_manager.py | 75 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index 55b9f3d..620f82c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -167,6 +167,49 @@ Preserve movie and TV import dates in Emby/Jellyfin/Plex by preventing upgraded --- +### 🆕 v0.6.3 NFO Management & Date Accuracy Improvements (September 2025) + +**💾 Smart NFO File Management:** +- **Problem**: NFOguard was rewriting NFO files for every episode on every scan, even untouched ones +- **Solution**: Intelligent content comparison - only updates NFOs when content actually changes +- **Performance**: Dramatically reduces unnecessary file system writes and processing time +- **Both Media Types**: Applied to both TV episodes and movies for comprehensive efficiency + +**🗓️ Correct Date Field Mapping:** +- **Problem**: Episode NFOs showing import dates (2025) for historical fields `` and `` +- **Root Cause**: Logic incorrectly used `dateadded` for all date fields instead of proper separation +- **Fix**: Clear separation of date meanings: + - ``: Historical air date (e.g., 1951-10-15 for I Love Lucy) + - ``: Historical premiere date (same as aired) + - ``: Actual download/import date (e.g., 2025-09-13T21:39:28+00:00) + +**🎯 Technical Implementation:** +- **New Method**: `_nfo_content_matches()` - smart content comparison ignoring timestamps +- **Enhanced Logic**: Episodes now correctly separate historical vs import dates +- **Skip Logic**: Files marked "already up-to-date" when no meaningful changes detected +- **Logging**: Clear debug output showing when files are skipped vs updated + +**🏆 User Experience Improvements:** +- **Correct Metadata**: Shows historically accurate air dates in media servers +- **Recently Added**: Still works properly using `dateadded` field +- **Performance**: Faster scans with fewer unnecessary file writes +- **Accuracy**: No more confusion between when show aired vs when you downloaded it + +**📋 Example Before/After:** +```xml + +2025-09-13 +2025-09-13T21:39:28+00:00 +2025-09-13 + + +1951-10-15 +2025-09-13T21:39:28+00:00 +1951-10-15 +``` + +--- + **Last Updated:** September 13, 2025 -**Version:** v0.6.2-dev +**Version:** v0.6.3-dev **Status:** Enhanced Production Ready \ No newline at end of file diff --git a/core/nfo_manager.py b/core/nfo_manager.py index 20c7d29..3feba9d 100644 --- a/core/nfo_manager.py +++ b/core/nfo_manager.py @@ -204,12 +204,28 @@ class NFOManager: # Pretty-print and save self._indent_xml(root) + # Generate new content + import io + new_content_buffer = io.StringIO() + ET.ElementTree(root).write(new_content_buffer, encoding="unicode", xml_declaration=True) + new_content = new_content_buffer.getvalue() + # Write to both canonical path and alt path if it exists for target in [canonical_path] + ([alt_path] if alt_path.exists() else []): + # Check if content has meaningfully changed + if target.exists(): + try: + existing_content = target.read_text(encoding="utf-8") + if self._nfo_content_matches(existing_content, new_content): + _log("DEBUG", f"Movie NFO already up-to-date: {target}") + continue + except Exception as e: + _log("DEBUG", f"Could not read existing NFO {target}: {e}") + try: target.parent.mkdir(parents=True, exist_ok=True) - ET.ElementTree(root).write(str(target), encoding="utf-8", xml_declaration=True) - _log("DEBUG", f"Updated NFO: {target}") + target.write_text(new_content, encoding="utf-8") + _log("DEBUG", f"Created/updated movie NFO: {target}") except Exception as e: _log("ERROR", f"Failed writing {target}: {e}") @@ -261,17 +277,15 @@ class NFOManager: xml_lines.append(f" {imdb_rating}") xml_lines.append(f" {imdb_votes}") - if dateadded: - # For Emby TV episodes: Use dateadded (import date) as aired date - # This ensures Emby displays the import date instead of scan date - import_date = dateadded.split('T')[0] - xml_lines.append(f" {import_date}") - xml_lines.append(f" {dateadded}") - xml_lines.append(f" {import_date}") - elif aired: - # Fallback: Use actual air date if no import date available + # Add historical air date and premiere date (should be the same) + if aired: air_date = aired.split('T')[0] xml_lines.append(f" {air_date}") + xml_lines.append(f" {air_date}") + + # Add import/download date (when user actually got the episode) + if dateadded: + xml_lines.append(f" {dateadded}") if lock_metadata: xml_lines.append(" true") @@ -287,12 +301,49 @@ class NFOManager: content = "\n".join(xml_lines) + # Check if NFO already exists and has correct content + if nfo_path.exists(): + try: + existing_content = nfo_path.read_text(encoding="utf-8") + if self._nfo_content_matches(existing_content, content): + _log("DEBUG", f"Episode NFO already up-to-date: {nfo_path}") + return + except Exception as e: + _log("DEBUG", f"Could not read existing NFO {nfo_path}: {e}") + try: nfo_path.write_text(content, encoding="utf-8") - _log("DEBUG", f"Created episode NFO: {nfo_path}") + _log("DEBUG", f"Created/updated episode NFO: {nfo_path}") except Exception as e: _log("ERROR", f"Failed writing {nfo_path}: {e}") + def _nfo_content_matches(self, existing_content: str, new_content: str) -> bool: + """ + Compare NFO content intelligently, ignoring timestamp differences + but checking for meaningful changes in episode data. + """ + try: + # Remove timestamp comments as they will always differ + def clean_content(content: str) -> str: + lines = content.split('\n') + cleaned_lines = [] + for line in lines: + # Skip lines with timestamps (managed by NFOGuard at ...) + if 'managed by' in line and 'at ' in line: + continue + cleaned_lines.append(line.strip()) + return '\n'.join(cleaned_lines) + + cleaned_existing = clean_content(existing_content) + cleaned_new = clean_content(new_content) + + # Compare the essential content (ignoring whitespace differences) + return cleaned_existing == cleaned_new + + except Exception as e: + _log("DEBUG", f"Error comparing NFO content: {e}") + return False + def create_season_nfo(self, season_dir: Path, season_num: int): """Create season.nfo if it doesn't exist""" season_nfo = season_dir / "season.nfo"