Etappe 3: Metadaten-Lookup + Pre-Scan
- SQLite-Cache für API-Rate-Limits (LRU, 10k Einträge) - TMDB/MusicBrainz/TheTVDB Clients - Pre-Scan-Modul für TOC-Lesung ohne Ripping - Metadaten-Preview UI - Jellyfin-Formatierung (NFO + Images) - API Endpoints für Lookup, Confirm, Format
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
"""NFO-Generator für Jellyfin (Kodi/NFO-Schema)."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from xml.dom.minidom import getDOMImplementation
|
||||
|
||||
|
||||
class NFOGenerator:
|
||||
def __init__(self):
|
||||
self.dom_impl = getDOMImplementation()
|
||||
|
||||
def _create_element(self, doc, name: str, text: str = None) -> None:
|
||||
"""Hilfsfunktion für Element-Erstellung."""
|
||||
element = doc.createElement(name)
|
||||
if text:
|
||||
element.appendChild(doc.createTextNode(str(text)))
|
||||
return element
|
||||
|
||||
def generate_movie_nfo(self, title: str, year: int,
|
||||
overview: str = "", rating: float = 0.0,
|
||||
runtime: int = 0, genres: List[str] = None,
|
||||
director: str = "", writer: str = "",
|
||||
actors: List[str] = None, studio: str = "",
|
||||
premiered: str = "", mpaa: str = "") -> str:
|
||||
"""Generiere movie.nfo für Filme."""
|
||||
doc = self.dom_impl.createDocument(None, "movie", None)
|
||||
root = doc.documentElement
|
||||
|
||||
root.appendChild(self._create_element(doc, "title", title))
|
||||
root.appendChild(self._create_element(doc, "year", year))
|
||||
root.appendChild(self._create_element(doc, "plot", overview))
|
||||
root.appendChild(self._create_element(doc, "rating", rating))
|
||||
root.appendChild(self._create_element(doc, "runtime", runtime))
|
||||
|
||||
if genres:
|
||||
for genre in genres:
|
||||
root.appendChild(self._create_element(doc, "genre", genre))
|
||||
|
||||
if director:
|
||||
root.appendChild(self._create_element(doc, "director", director))
|
||||
|
||||
if writer:
|
||||
root.appendChild(self._create_element(doc, "writer", writer))
|
||||
|
||||
if actors:
|
||||
for actor in actors:
|
||||
actor_node = doc.createElement("actor")
|
||||
actor_node.appendChild(self._create_element(doc, "name", actor))
|
||||
root.appendChild(actor_node)
|
||||
|
||||
if studio:
|
||||
root.appendChild(self._create_element(doc, "studio", studio))
|
||||
|
||||
if premiered:
|
||||
root.appendChild(self._create_element(doc, "premiered", premiered))
|
||||
|
||||
if mpaa:
|
||||
root.appendChild(self._create_element(doc, "mpaa", mpaa))
|
||||
|
||||
# Attribution
|
||||
root.appendChild(self._create_element(doc, "details", "Source: TMDB"))
|
||||
|
||||
return doc.toprettyxml(indent=" ")
|
||||
|
||||
def generate_series_nfo(self, title: str, year: int,
|
||||
overview: str = "", rating: float = 0.0,
|
||||
genres: List[str] = None, studio: str = "",
|
||||
premiered: str = "") -> str:
|
||||
"""Generiere series.nfo für Serien."""
|
||||
doc = self.dom_impl.createDocument(None, "tvshow", None)
|
||||
root = doc.documentElement
|
||||
|
||||
root.appendChild(self._create_element(doc, "title", title))
|
||||
root.appendChild(self._create_element(doc, "year", year))
|
||||
root.appendChild(self._create_element(doc, "plot", overview))
|
||||
root.appendChild(self._create_element(doc, "rating", rating))
|
||||
|
||||
if genres:
|
||||
for genre in genres:
|
||||
root.appendChild(self._create_element(doc, "genre", genre))
|
||||
|
||||
if studio:
|
||||
root.appendChild(self._create_element(doc, "studio", studio))
|
||||
|
||||
if premiered:
|
||||
root.appendChild(self._create_element(doc, "premiered", premiered))
|
||||
|
||||
# Attribution
|
||||
root.appendChild(self._create_element(doc, "details", "Source: TMDB"))
|
||||
|
||||
return doc.toprettyxml(indent=" ")
|
||||
|
||||
def generate_episode_nfo(self, title: str, season: int, episode: int,
|
||||
overview: str = "", rating: float = 0.0,
|
||||
director: str = "", premiered: str = "",
|
||||
writers: List[str] = None) -> str:
|
||||
"""Generiere episode.nfo für Episoden."""
|
||||
doc = self.dom_impl.createDocument(None, "episodedetails", None)
|
||||
root = doc.documentElement
|
||||
|
||||
root.appendChild(self._create_element(doc, "title", title))
|
||||
root.appendChild(self._create_element(doc, "season", season))
|
||||
root.appendChild(self._create_element(doc, "episode", episode))
|
||||
root.appendChild(self._create_element(doc, "plot", overview))
|
||||
root.appendChild(self._create_element(doc, "rating", rating))
|
||||
|
||||
if director:
|
||||
root.appendChild(self._create_element(doc, "director", director))
|
||||
|
||||
if premiered:
|
||||
root.appendChild(self._create_element(doc, "premiered", premiered))
|
||||
|
||||
if writers:
|
||||
for writer in writers:
|
||||
root.appendChild(self._create_element(doc, "credits", writer))
|
||||
|
||||
return doc.toprettyxml(indent=" ")
|
||||
|
||||
def generate_album_nfo(self, title: str, artist: str, year: int,
|
||||
genres: List[str] = None, rating: float = 0.0,
|
||||
review: str = "") -> str:
|
||||
"""Generiere album.nfo für Musikalben."""
|
||||
doc = self.dom_impl.createDocument(None, "musicalbum", None)
|
||||
root = doc.documentElement
|
||||
|
||||
root.appendChild(self._create_element(doc, "title", title))
|
||||
root.appendChild(self._create_element(doc, "artist", artist))
|
||||
root.appendChild(self._create_element(doc, "year", year))
|
||||
root.appendChild(self._create_element(doc, "rating", rating))
|
||||
root.appendChild(self._create_element(doc, "review", review))
|
||||
|
||||
if genres:
|
||||
for genre in genres:
|
||||
root.appendChild(self._create_element(doc, "genre", genre))
|
||||
|
||||
# Attribution
|
||||
root.appendChild(self._create_element(doc, "details", "Source: MusicBrainz"))
|
||||
|
||||
return doc.toprettyxml(indent=" ")
|
||||
|
||||
def save_nfo(self, content: str, output_path: Path) -> bool:
|
||||
"""Speichere NFO-Datei."""
|
||||
try:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"NFO save error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# Beispieldaten
|
||||
if __name__ == "__main__":
|
||||
nfo_gen = NFOGenerator()
|
||||
|
||||
# movie.nfo
|
||||
movie_nfo = nfo_gen.generate_movie_nfo(
|
||||
title="Inception",
|
||||
year=2010,
|
||||
overview="A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O.",
|
||||
rating=8.8,
|
||||
runtime=148,
|
||||
genres=["Action", "Sci-Fi", "Thriller"],
|
||||
director="Christopher Nolan",
|
||||
actors=["Leonardo DiCaprio", "Joseph Gordon-Levitt", "Ellen Page"]
|
||||
)
|
||||
|
||||
print(movie_nfo)
|
||||
Reference in New Issue
Block a user