dates for TV shows

This commit is contained in:
2025-09-14 13:56:31 -04:00
parent d7db924510
commit 2657729975
3 changed files with 207 additions and 67 deletions
+51 -21
View File
@@ -1,26 +1,24 @@
# NFOGuard Development Summary
## Current Status: v1.4.0 - Complete Date Field Management
## Current Status: v1.4.1 - Complete TV & Movie NFO Management
### Latest Updates (v1.4.0)
- **🎯 ALL DATE FIELDS**: Now moves premiered, year, dateadded, and lockdata to bottom
- **📋 COMPREHENSIVE**: Removes existing premiered/year fields and re-adds at bottom
- **✅ PRESERVATION**: Preserves existing premiered date if no new release date provided
- **🧹 CLEAN STRUCTURE**: All date/metadata fields grouped together at bottom
### Latest Updates (v1.4.1)
- **📺 TV SHOW SUPPORT**: Extended preservation logic to tvshow.nfo, season.nfo, and episode.nfo
- **🎯 CONSISTENT BEHAVIOR**: All NFO types now preserve existing content and append NFOGuard fields
- **✅ COMPREHENSIVE**: Movies, TV shows, seasons, and episodes all handle field placement correctly
- **🧹 UNIFIED APPROACH**: Same preservation and bottom-placement logic across all media types
### Complete NFO Structure (Fixed)
### Complete NFO Management Coverage
#### Movie NFO Structure
```xml
<movie>
<!-- All existing Radarr/Jellyfin content preserved -->
<title>Annabelle Comes Home</title>
<plot>Plot content...</plot>
<uniqueid type="tmdb" default="true">521029</uniqueid> <!-- preserved -->
<genre>Horror</genre>
<director>Gary Dauberman</director>
<!-- All existing Radarr content preserved -->
<title>Movie Title</title>
<plot>Plot...</plot>
<actor>...</actor>
<!-- ... all existing fields including all actors ... -->
<!-- ALL date/metadata fields at absolute bottom -->
<!-- NFOGuard fields at bottom -->
<uniqueid type="imdb" default="true">tt8350360</uniqueid>
<premiered>2019-06-26</premiered>
<year>2019</year>
@@ -29,12 +27,44 @@
</movie>
```
### NFOGuard Managed Fields
-`uniqueid[type="imdb"]` - IMDb ID for identification
-`premiered` - Release date (moved to bottom)
-`year` - Extracted from premiered date
-`dateadded` - Import timestamp
-`lockdata` - Prevents metadata overwrites
#### TV Show NFO Structure
```xml
<tvshow>
<!-- All existing Sonarr content preserved -->
<title>Show Title</title>
<plot>Show plot...</plot>
<actor>...</actor>
<!-- NFOGuard fields at bottom -->
<uniqueid type="imdb" default="true">tt1234567</uniqueid>
<uniqueid type="tvdb">12345</uniqueid>
<lockdata>true</lockdata>
</tvshow>
```
#### Episode NFO Structure
```xml
<episodedetails>
<!-- All existing Sonarr content preserved -->
<title>Episode Title</title>
<plot>Episode plot...</plot>
<director>...</director>
<!-- NFOGuard fields at bottom -->
<season>1</season>
<episode>5</episode>
<aired>2019-06-26</aired>
<dateadded>2025-09-14T13:50:34-04:00</dateadded>
<lockdata>true</lockdata>
</episodedetails>
```
### Preservation Features
-**Movies**: Preserves title, plot, actors, ratings, all existing metadata
-**TV Shows**: Preserves show metadata, existing uniqueid[tvdb], actor info
-**Episodes**: Preserves episode title, plot, director, existing aired dates
-**Seasons**: Preserves any existing season metadata
-**All Types**: Existing content stays in original order, NFOGuard fields at bottom
### Complete Path Processing Pipeline
-**Path Mapping**: Fixed substring matching (movies/movies6, tv/tv6)
+1 -1
View File
@@ -1 +1 @@
1.4.0
1.4.1
+155 -45
View File
@@ -127,76 +127,192 @@ class NFOManager:
print(f"Error creating/updating movie NFO {nfo_path}: {e}")
def create_tvshow_nfo(self, series_dir: Path, imdb_id: str, tvdb_id: Optional[str] = None) -> None:
"""Create tvshow.nfo file"""
"""Create or update tvshow.nfo file preserving existing content"""
nfo_path = series_dir / "tvshow.nfo"
try:
tvshow = ET.Element("tvshow")
# Try to load existing NFO file
if nfo_path.exists():
try:
tree = ET.parse(nfo_path)
tvshow = tree.getroot()
# Ensure root element is <tvshow>
if tvshow.tag != "tvshow":
raise ValueError("Root element is not <tvshow>")
# Remove existing NFOGuard-managed elements to avoid duplicates
# These will be re-added at the bottom
for tag in ["lockdata"]:
existing = tvshow.find(tag)
if existing is not None:
tvshow.remove(existing)
# Remove ALL existing uniqueid with type="imdb" regardless of attributes
for uniqueid in tvshow.findall("uniqueid[@type='imdb']"):
tvshow.remove(uniqueid)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing tvshow NFO {nfo_path}, creating new: {e}")
tvshow = ET.Element("tvshow")
else:
# Create new NFO structure
tvshow = ET.Element("tvshow")
# Add IMDb ID
# Add NFOGuard fields at the bottom
# Add IMDb uniqueid at the end
imdb_uniqueid = ET.SubElement(tvshow, "uniqueid", type="imdb", default="true")
imdb_uniqueid.text = imdb_id
# Add TVDB ID if available
if tvdb_id:
# Add TVDB ID if available (preserve existing or add new)
if tvdb_id and not tvshow.find("uniqueid[@type='tvdb']"):
tvdb_uniqueid = ET.SubElement(tvshow, "uniqueid", type="tvdb")
tvdb_uniqueid.text = tvdb_id
# Add source comment
comment = ET.Comment(f" Created by {self.manager_brand} ")
tvshow.insert(0, comment)
# Add lockdata at the very end
lockdata = ET.SubElement(tvshow, "lockdata")
lockdata.text = "true"
# Write file
# Add NFOGuard comment at the beginning
comment_text = f" Created by {self.manager_brand} "
# Write file with proper formatting
tree = ET.ElementTree(tvshow)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
# Write to string first to add comment
xml_str = ET.tostring(tvshow, 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)
except Exception as e:
print(f"Error creating tvshow NFO {nfo_path}: {e}")
print(f"Error creating/updating tvshow NFO {nfo_path}: {e}")
def create_season_nfo(self, season_dir: Path, season_number: int) -> None:
"""Create season.nfo file"""
"""Create or update season.nfo file preserving existing content"""
nfo_path = season_dir / "season.nfo"
try:
season_dir.mkdir(exist_ok=True)
season = ET.Element("season")
# Try to load existing NFO file
if nfo_path.exists():
try:
tree = ET.parse(nfo_path)
season = tree.getroot()
# Ensure root element is <season>
if season.tag != "season":
raise ValueError("Root element is not <season>")
# Remove existing NFOGuard-managed elements
for tag in ["seasonnumber", "lockdata"]:
existing = season.find(tag)
if existing is not None:
season.remove(existing)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing season NFO {nfo_path}, creating new: {e}")
season = ET.Element("season")
else:
# Create new NFO structure
season = ET.Element("season")
# Add NFOGuard fields at the bottom
seasonnumber = ET.SubElement(season, "seasonnumber")
seasonnumber.text = str(season_number)
# Add source comment
comment = ET.Comment(f" Created by {self.manager_brand} ")
season.insert(0, comment)
# Add lockdata at the end
lockdata = ET.SubElement(season, "lockdata")
lockdata.text = "true"
# Write file
# Add NFOGuard comment at the beginning
comment_text = f" Created by {self.manager_brand} "
# Write file with proper formatting
tree = ET.ElementTree(season)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
# Write to string first to add comment
xml_str = ET.tostring(season, 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)
except Exception as e:
print(f"Error creating season NFO {nfo_path}: {e}")
print(f"Error creating/updating season NFO {nfo_path}: {e}")
def create_episode_nfo(self, season_dir: Path, season_num: int, episode_num: int,
aired: Optional[str], dateadded: Optional[str], source: str,
lock_metadata: bool = True, enhanced_metadata: Optional[Dict[str, Any]] = None) -> None:
"""Create episode NFO file"""
"""Create or update episode NFO file preserving existing content"""
# Generate episode filename pattern
episode_filename = f"S{season_num:02d}E{episode_num:02d}.nfo"
nfo_path = season_dir / episode_filename
try:
episode = ET.Element("episodedetails")
# Try to load existing NFO file
if nfo_path.exists():
try:
tree = ET.parse(nfo_path)
episode = tree.getroot()
# Ensure root element is <episodedetails>
if episode.tag != "episodedetails":
raise ValueError("Root element is not <episodedetails>")
# Remove existing NFOGuard-managed elements to avoid duplicates
# These will be re-added at the bottom
nfoguard_fields = ["aired", "dateadded", "lockdata", "season", "episode"]
for tag in nfoguard_fields:
existing = episode.find(tag)
if existing is not None:
# Store the aired value before removing
if tag == "aired" and not aired:
aired = existing.text # Preserve existing aired date
episode.remove(existing)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing episode NFO {nfo_path}, creating new: {e}")
episode = ET.Element("episodedetails")
else:
# Create new NFO structure
episode = ET.Element("episodedetails")
# Basic episode info
# Enhanced metadata should be preserved - only add if not already present
if enhanced_metadata:
if enhanced_metadata.get("title") and not episode.find("title"):
title_elem = ET.SubElement(episode, "title")
title_elem.text = enhanced_metadata["title"]
if enhanced_metadata.get("overview") and not episode.find("plot"):
plot_elem = ET.SubElement(episode, "plot")
plot_elem.text = enhanced_metadata["overview"]
if enhanced_metadata.get("runtime") and not episode.find("runtime"):
runtime_elem = ET.SubElement(episode, "runtime")
runtime_elem.text = str(enhanced_metadata["runtime"])
# Add NFOGuard fields at the bottom
# Basic episode info at the end
season_elem = ET.SubElement(episode, "season")
season_elem.text = str(season_num)
episode_elem = ET.SubElement(episode, "episode")
episode_elem.text = str(episode_num)
# Dates
# Dates at the end
if aired:
aired_elem = ET.SubElement(episode, "aired")
aired_elem.text = aired[:10] if len(aired) >= 10 else aired
@@ -205,36 +321,30 @@ class NFOManager:
dateadded_elem = ET.SubElement(episode, "dateadded")
dateadded_elem.text = dateadded
# Enhanced metadata if available
if enhanced_metadata:
if enhanced_metadata.get("title"):
title_elem = ET.SubElement(episode, "title")
title_elem.text = enhanced_metadata["title"]
if enhanced_metadata.get("overview"):
plot_elem = ET.SubElement(episode, "plot")
plot_elem.text = enhanced_metadata["overview"]
if enhanced_metadata.get("runtime"):
runtime_elem = ET.SubElement(episode, "runtime")
runtime_elem.text = str(enhanced_metadata["runtime"])
# Add source comment
comment = ET.Comment(f" Created by {self.manager_brand} - Source: {source} ")
episode.insert(0, comment)
# Add lockdata if requested
# Add lockdata at the very end
if lock_metadata:
lockdata = ET.SubElement(episode, "lockdata")
lockdata.text = "true"
# Write file
# Add NFOGuard comment at the beginning
comment_text = f" Created by {self.manager_brand} - Source: {source} "
# Write file with proper formatting
tree = ET.ElementTree(episode)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
# 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)
except Exception as e:
print(f"Error creating episode NFO {nfo_path}: {e}")
print(f"Error creating/updating episode NFO {nfo_path}: {e}")
def set_file_mtime(self, file_path: Path, iso_timestamp: str) -> None:
"""Set file modification time to match import date"""