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:
+40
-5
@@ -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("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
.replace("'", "'"))
|
||||
|
||||
@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
|
||||
|
||||
Reference in New Issue
Block a user