update to docker

This commit is contained in:
2025-09-17 15:23:29 -04:00
parent d64228cc82
commit 532ea02a5b
4 changed files with 103 additions and 27 deletions
+36 -7
View File
@@ -1,5 +1,27 @@
# NFOGuard Project Overview
## 🎯 Three-Component System Architecture
**NFOGuard** is a comprehensive media date management ecosystem consisting of three integrated repositories:
### 1. **NFOGuard (Docker Service)** [THIS REPOSITORY]
- **Purpose**: Main webhook service that listens for Sonarr/Radarr events and manages NFO files
- **Version**: v1.6.0
- **Technology**: Python 3, Docker, PostgreSQL integration
- **Role**: Records import dates, creates/updates NFO files, manages filesystem timestamps
### 2. **NFOGuard-Emby-Plugin** (Companion DLL)
- **Purpose**: Emby Server plugin that reads NFO dateadded values and syncs to Emby's DateCreated field
- **Version**: v0.6.1
- **Technology**: C# .NET Framework 4.8, Emby SDK
- **Role**: Real-time date synchronization within Emby, license validation
### 3. **nfoguard-license-server** (License Management)
- **Purpose**: License validation server with admin interface for plugin access control
- **Version**: v1.3.1
- **Technology**: Node.js, Express, SQLite
- **Role**: 7-day trials, license validation, user management dashboard
## Project Purpose
NFOGuard is an automated NFO file management system that integrates with Radarr and Sonarr to maintain accurate metadata and release dates in media library NFO files. It serves as a webhook-driven middleware that preserves existing metadata while adding clean, standardized date information.
@@ -302,7 +324,7 @@ NFOGuard is an automated NFO file management system that integrates with Radarr
### When Starting a New Session
1. **Read Project.md** - This file for full context
2. **Check SUMMARY.md** - Latest changes and current version
3. **Review VERSION** - Current version number
3. **Review VERSION** - Current version number (v1.6.0)
4. **Key Files to Understand**:
- `nfoguard.py` - Main logic, webhook handlers
- `core/nfo_manager.py` - NFO creation, parsing, renaming
@@ -315,17 +337,24 @@ NFOGuard is an automated NFO file management system that integrates with Radarr
- **NFO Renaming**: `core/nfo_manager.py:336-491` (smart episode renaming)
- **Dual Identification**: `core/nfo_manager.py:85-149` (directory + NFO parsing)
### User Context
- **User has**: Radarr, Sonarr, Unmanic integration
- **Environment**: Docker deployment, PostgreSQL databases
- **Focus Areas**: Webhook accuracy, NFO standardization, date integrity
### Project Context (Updated 2025-09-17)
- **Current Status**: Production-ready system with 3-component architecture
- **Version**: v1.6.0 (NFOGuard Docker), v1.1.18 (Emby Plugin), v1.3.2 (License Server)
- **User Environment**: Docker deployment, PostgreSQL databases, Unmanic integration
- **Focus Areas**: Webhook accuracy, NFO standardization, date integrity, lifetime license management
- **Testing Method**: Real webhooks from Radarr/Sonarr, manual scans for validation
- **Recent Updates**: Fixed lifetime license display issue with explicit "never" expires handling
### Three-Repository System Overview
1. **NFOGuard (Docker)** - Main webhook service and NFO management
2. **NFOGuard-Emby-Plugin** - DLL for real-time Emby date synchronization
3. **nfoguard-license-server** - License validation and user management
### Recent Work Pattern
- User reports issues with specific examples (log snippets, webhook behavior)
- We debug by analyzing code at specific line numbers
- Implement fixes with proper error handling and logging
- Update documentation (SUMMARY.md, README.md, Project.md)
- Update documentation (SUMMARY.md, README.md, Project.md) across all repositories
- Version bump and prepare for deployment
This project represents a mature, production-ready system for automated NFO management with comprehensive webhook integration and intelligent metadata handling.
This project represents a mature, production-ready system for automated NFO management with comprehensive webhook integration, intelligent metadata handling, and integrated license management.
+1 -1
View File
@@ -1 +1 @@
1.6.0
1.6.3
+12 -4
View File
@@ -14,13 +14,17 @@ from urllib.error import URLError, HTTPError
from core.logging import _log
def _get_json(url: str, timeout: int = 20, headers: Dict[str, str] = None) -> Optional[Dict[str, Any]]:
def _get_json(url: str, timeout: int = 20, headers: Dict[str, str] = None, suppress_404: bool = False) -> Optional[Dict[str, Any]]:
"""Make GET request and return JSON"""
try:
req = UrlRequest(url, headers=headers or {"Accept": "application/json"})
with urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception as e:
# Handle HTTP 400/404 errors more gracefully for APIs where missing data is normal
if suppress_404 and ("400" in str(e) or "404" in str(e)):
_log("DEBUG", f"GET {url} - item not found (expected): {e}")
else:
_log("WARNING", f"GET {url} failed: {e}")
return None
@@ -83,16 +87,20 @@ class TVDBClient:
url = f"{self.base_url}/search/remoteid?remoteId={imdb_id}&type=series"
headers = {"Authorization": f"Bearer {token}"}
data = _get_json(url, headers=headers)
data = _get_json(url, headers=headers, suppress_404=True)
if data and data.get("status") == "success" and data.get("data"):
series_list = data["data"]
if series_list and len(series_list) > 0:
tvdb_id = series_list[0].get("id")
if tvdb_id:
_log("INFO", f"TVDB: Converted {imdb_id}{tvdb_id}")
_log("INFO", f"TVDB: Found series {imdb_id}{tvdb_id}")
return str(tvdb_id)
# If we get here, the series wasn't found in TVDB (common for newer shows)
_log("DEBUG", f"TVDB: No series found for IMDb {imdb_id} (not available in TVDB)")
except Exception as e:
_log("WARNING", f"TVDB IMDB conversion failed for {imdb_id}: {e}")
_log("WARNING", f"TVDB API error for {imdb_id}: {e}")
return None
+52 -13
View File
@@ -18,20 +18,35 @@ class NFOManager:
self.manager_brand = manager_brand
def parse_imdb_from_path(self, path: Path) -> Optional[str]:
"""Extract IMDb ID from directory path"""
# Look for [imdb-ttXXXXXXX] or [ttXXXXXXX] patterns
"""Extract IMDb ID from directory path or filename"""
# Look for various IMDb patterns in both directory and file names
path_str = str(path).lower()
# Try [imdb-ttXXXXXXX] format first
# Try [imdb-ttXXXXXXX] format first (most explicit)
match = re.search(r'\[imdb-?(tt\d+)\]', path_str)
if match:
return match.group(1)
# Try standalone [ttXXXXXXX] format
# Try standalone [ttXXXXXXX] format in brackets
match = re.search(r'\[(tt\d+)\]', path_str)
if match:
return match.group(1)
# Try {imdb-ttXXXXXXX} format with curly braces
match = re.search(r'\{imdb-?(tt\d+)\}', path_str)
if match:
return match.group(1)
# Try (imdb-ttXXXXXXX) format with parentheses
match = re.search(r'\(imdb-?(tt\d+)\)', path_str)
if match:
return match.group(1)
# Try ttXXXXXXX at end of filename/dirname (common pattern)
match = re.search(r'[-_\s](tt\d+)$', path_str)
if match:
return match.group(1)
return None
def create_movie_nfo(self, movie_dir: Path, imdb_id: str, dateadded: str,
@@ -68,7 +83,8 @@ class NFOManager:
movie.remove(uniqueid)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing NFO {nfo_path}, creating new: {e}")
print(f"⚠️ Corrupted NFO detected: {nfo_path} - {str(e)[:100]}...")
print(f" Creating new clean NFO file to replace corrupted one")
movie = ET.Element("movie")
else:
# Create new NFO structure
@@ -123,8 +139,11 @@ class NFOManager:
with open(nfo_path, 'w', encoding='utf-8') as f:
f.write(full_xml)
print(f"✅ Successfully created/updated movie NFO: {nfo_path}")
print(f" IMDb ID: {imdb_id}, Date Added: {dateadded}, Source: {source}")
except Exception as e:
print(f"Error creating/updating movie NFO {nfo_path}: {e}")
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 or update tvshow.nfo file preserving existing content"""
@@ -153,7 +172,8 @@ class NFOManager:
tvshow.remove(uniqueid)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing tvshow NFO {nfo_path}, creating new: {e}")
print(f"⚠️ Corrupted TV show NFO detected: {nfo_path} - {str(e)[:100]}...")
print(f" Creating new clean tvshow.nfo file to replace corrupted one")
tvshow = ET.Element("tvshow")
else:
# Create new NFO structure
@@ -191,8 +211,11 @@ class NFOManager:
with open(nfo_path, 'w', encoding='utf-8') as f:
f.write(full_xml)
print(f"✅ Successfully created/updated TV show NFO: {nfo_path}")
print(f" IMDb ID: {imdb_id}" + (f", TVDB ID: {tvdb_id}" if tvdb_id else ""))
except Exception as e:
print(f"Error creating/updating 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 or update season.nfo file preserving existing content"""
@@ -218,7 +241,8 @@ class NFOManager:
season.remove(existing)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing season NFO {nfo_path}, creating new: {e}")
print(f"⚠️ Corrupted season NFO detected: {nfo_path} - {str(e)[:100]}...")
print(f" Creating new clean season.nfo file to replace corrupted one")
season = ET.Element("season")
else:
# Create new NFO structure
@@ -249,8 +273,11 @@ class NFOManager:
with open(nfo_path, 'w', encoding='utf-8') as f:
f.write(full_xml)
print(f"✅ Successfully created/updated season NFO: {nfo_path}")
print(f" Season: {season_number}")
except Exception as e:
print(f"Error creating/updating 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,
@@ -283,7 +310,8 @@ class NFOManager:
episode.remove(existing)
except (ET.ParseError, ValueError) as e:
print(f"Warning: Could not parse existing episode NFO {nfo_path}, creating new: {e}")
print(f"⚠️ Corrupted episode NFO detected: {nfo_path} - {str(e)[:100]}...")
print(f" Creating new clean episode NFO file to replace corrupted one")
episode = ET.Element("episodedetails")
else:
# Create new NFO structure
@@ -343,8 +371,11 @@ class NFOManager:
with open(nfo_path, 'w', encoding='utf-8') as f:
f.write(full_xml)
print(f"✅ Successfully created/updated episode NFO: {nfo_path}")
print(f" S{season_num:02d}E{episode_num:02d}, Aired: {aired}, Date Added: {dateadded}")
except Exception as e:
print(f"Error creating/updating 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"""
@@ -363,14 +394,22 @@ class NFOManager:
# Set both access and modification times
os.utime(file_path, (timestamp, timestamp))
print(f"✅ Updated file timestamp: {file_path.name} -> {iso_timestamp}")
except Exception as e:
print(f"Error setting mtime for {file_path}: {e}")
print(f"Error setting mtime for {file_path}: {e}")
def update_movie_files_mtime(self, movie_dir: Path, iso_timestamp: str) -> None:
"""Update modification times for all video files in movie directory"""
video_exts = (".mkv", ".mp4", ".avi", ".mov", ".m4v")
updated_files = []
for file_path in movie_dir.iterdir():
if file_path.is_file() and file_path.suffix.lower() in video_exts:
self.set_file_mtime(file_path, iso_timestamp)
updated_files.append(file_path.name)
if updated_files:
print(f"✅ Updated {len(updated_files)} video file timestamps in {movie_dir.name}")
else:
print(f"⚠️ No video files found to update in {movie_dir.name}")