Add comprehensive TV show enhancements and URL-safe endpoints

 Features Added:
• Single season/episode processing capability via new endpoints
• Enhanced NFO generation with full Sonarr API metadata (titles, plots, ratings)
• NFOGuard timestamp tracking in all NFO source comments
• URL-safe /tv/scan-season and /tv/scan-episode endpoints

🔧 Technical Improvements:
• Fixed URL encoding issues for paths with spaces and special characters
• Enhanced TVProcessor with process_season() and process_episode_file() methods
• Rich metadata extraction from Sonarr API (episode titles, plots, runtime, ratings)
• XML escaping for special characters in metadata
• Comprehensive episode parsing (SxxExx and numeric formats)

📚 Documentation:
• Updated README with new TV processing endpoints
• Added enhanced NFO generation examples showing before/after
• Documented URL-safe alternatives to manual scan paths

🎯 User Benefits:
• Can now process single seasons: Chicago Fire Season 13
• Can now process single episodes: S13E21 files
• Enhanced NFO files with full episode metadata for better Emby/Plex experience
• Timestamps show when NFOGuard processed each file
This commit is contained in:
2025-09-11 09:39:42 -04:00
parent c7c783570c
commit c705fb4d6d
3 changed files with 426 additions and 12 deletions
+40 -5
View File
@@ -73,6 +73,18 @@ class NFOManager:
return None
@staticmethod
def _escape_xml(text: str) -> str:
"""Escape XML special characters"""
if not text:
return ""
return (str(text)
.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&apos;"))
@staticmethod
def _ensure_child_text(parent: ET.Element, tag: str, text: str, overwrite: bool = True) -> ET.Element:
"""Ensure child element exists with specified text"""
@@ -183,10 +195,11 @@ class NFOManager:
if lock_metadata:
self._ensure_child_text(root, "lockdata", "true", overwrite=True)
# Add footer comments
# Add footer comments with timestamp
self._strip_existing_footer_comments(root)
current_time = datetime.now(timezone.utc).isoformat(timespec="seconds")
root.append(ET.Comment(f" source: Movie; {source_detail} "))
root.append(ET.Comment(f" managed by {self.manager_brand} "))
root.append(ET.Comment(f" managed by {self.manager_brand} at {current_time} "))
# Pretty-print and save
self._indent_xml(root)
@@ -208,7 +221,8 @@ class NFOManager:
aired: Optional[str] = None,
dateadded: Optional[str] = None,
source_detail: str = "unknown",
lock_metadata: bool = True
lock_metadata: bool = True,
enhanced_metadata: Optional[Dict[str, Any]] = None
):
"""Create episode NFO file (S01E01.nfo format)"""
try:
@@ -227,6 +241,26 @@ class NFOManager:
f" <episode>{episode_num}</episode>",
]
# Add enhanced metadata if available
if enhanced_metadata:
if enhanced_metadata.get("title"):
xml_lines.append(f" <title>{self._escape_xml(enhanced_metadata['title'])}</title>")
if enhanced_metadata.get("overview"):
xml_lines.append(f" <plot>{self._escape_xml(enhanced_metadata['overview'])}</plot>")
if enhanced_metadata.get("runtime"):
xml_lines.append(f" <runtime>{enhanced_metadata['runtime']}</runtime>")
# Add ratings if available
if enhanced_metadata.get("ratings"):
ratings = enhanced_metadata["ratings"]
if ratings.get("imdb", {}).get("value"):
imdb_rating = ratings["imdb"]["value"]
imdb_votes = ratings["imdb"].get("votes", 0)
xml_lines.append(f" <rating>{imdb_rating}</rating>")
xml_lines.append(f" <votes>{imdb_votes}</votes>")
if aired:
# Extract date part from ISO string
air_date = aired.split('T')[0]
@@ -241,10 +275,11 @@ class NFOManager:
if lock_metadata:
xml_lines.append(" <lockdata>true</lockdata>")
# Add source comments
# Add source comments with timestamp
if source_detail:
current_time = datetime.now(timezone.utc).isoformat(timespec="seconds")
xml_lines.append(f" <!-- source: TV Episode; {source_detail} -->")
xml_lines.append(f" <!-- managed by {self.manager_brand} -->")
xml_lines.append(f" <!-- managed by {self.manager_brand} at {current_time} -->")
xml_lines.append("</episodedetails>")
xml_lines.append("") # Final newline