Files
nfoguard/core/nfo_manager.py
T
2025-09-14 13:13:13 -04:00

213 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""
NFO Manager for creating and managing metadata files
Handles NFO creation for movies, TV shows, seasons, and episodes
"""
import os
import xml.etree.ElementTree as ET
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Any
import re
class NFOManager:
"""Manages NFO file creation and updates"""
def __init__(self, manager_brand: str = "NFOGuard"):
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
path_str = str(path).lower()
# Try [imdb-ttXXXXXXX] format first
match = re.search(r'\[imdb-?(tt\d+)\]', path_str)
if match:
return match.group(1)
# Try standalone [ttXXXXXXX] format
match = re.search(r'\[(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,
released: Optional[str] = None, source: str = "unknown",
lock_metadata: bool = True) -> None:
"""Create movie.nfo file with proper metadata"""
nfo_path = movie_dir / "movie.nfo"
try:
movie = ET.Element("movie")
# Add unique ID
uniqueid = ET.SubElement(movie, "uniqueid", type="imdb", default="true")
uniqueid.text = imdb_id
# Add dates
if dateadded:
dateadded_elem = ET.SubElement(movie, "dateadded")
dateadded_elem.text = dateadded
if released:
premiered_elem = ET.SubElement(movie, "premiered")
premiered_elem.text = released[:10] if len(released) >= 10 else released
# Add source comment
comment = ET.Comment(f" Created by {self.manager_brand} - Source: {source} ")
movie.insert(0, comment)
# Add lockdata if requested
if lock_metadata:
lockdata = ET.SubElement(movie, "lockdata")
lockdata.text = "true"
# Write file
tree = ET.ElementTree(movie)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
except Exception as e:
print(f"Error creating 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"""
nfo_path = series_dir / "tvshow.nfo"
try:
tvshow = ET.Element("tvshow")
# Add IMDb ID
imdb_uniqueid = ET.SubElement(tvshow, "uniqueid", type="imdb", default="true")
imdb_uniqueid.text = imdb_id
# Add TVDB ID if available
if tvdb_id:
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)
# Write file
tree = ET.ElementTree(tvshow)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
except Exception as e:
print(f"Error creating tvshow NFO {nfo_path}: {e}")
def create_season_nfo(self, season_dir: Path, season_number: int) -> None:
"""Create season.nfo file"""
nfo_path = season_dir / "season.nfo"
try:
season_dir.mkdir(exist_ok=True)
season = ET.Element("season")
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)
# Write file
tree = ET.ElementTree(season)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
except Exception as e:
print(f"Error creating 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"""
# 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")
# Basic episode info
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
if aired:
aired_elem = ET.SubElement(episode, "aired")
aired_elem.text = aired[:10] if len(aired) >= 10 else aired
if dateadded:
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
if lock_metadata:
lockdata = ET.SubElement(episode, "lockdata")
lockdata.text = "true"
# Write file
tree = ET.ElementTree(episode)
ET.indent(tree, space=" ", level=0)
tree.write(nfo_path, encoding="utf-8", xml_declaration=True)
except Exception as e:
print(f"Error creating 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"""
try:
# Parse ISO timestamp
if iso_timestamp.endswith('Z'):
dt = datetime.fromisoformat(iso_timestamp.replace('Z', '+00:00'))
elif '+' in iso_timestamp or 'T' in iso_timestamp:
dt = datetime.fromisoformat(iso_timestamp)
else:
# Assume it's already a simple date
dt = datetime.fromisoformat(iso_timestamp + 'T00:00:00')
# Convert to timestamp
timestamp = dt.timestamp()
# Set both access and modification times
os.utime(file_path, (timestamp, timestamp))
except Exception as 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")
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)