From f347a367676a09eda9c3e47ecfcd4f482fb8ad9e Mon Sep 17 00:00:00 2001 From: maxDorninger <97409287+maxDorninger@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:28:04 +0100 Subject: [PATCH] create metadataProvider Module and fix different logging formats --- MediaManager/src/config/__init__.py | 37 ++------ MediaManager/src/main.py | 42 +++++++-- MediaManager/src/metadataProvider/__init__.py | 13 +++ .../abstractMetaDataProvider.py | 27 ++++++ MediaManager/src/metadataProvider/tmdb.py | 88 +++++++++++++++++++ 5 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 MediaManager/src/metadataProvider/__init__.py create mode 100644 MediaManager/src/metadataProvider/abstractMetaDataProvider.py create mode 100644 MediaManager/src/metadataProvider/tmdb.py diff --git a/MediaManager/src/config/__init__.py b/MediaManager/src/config/__init__.py index 4364eab..026be66 100644 --- a/MediaManager/src/config/__init__.py +++ b/MediaManager/src/config/__init__.py @@ -1,4 +1,3 @@ -import logging import os from typing import Literal @@ -7,7 +6,7 @@ from pydantic import BaseModel class DbConfig(BaseModel): host: str = os.getenv("DB_HOST") or "localhost" - port: int = int(os.getenv("DB_PORT")) or 5432 + port: int = int(os.getenv("DB_PORT") or 5432) user: str = os.getenv("DB_USERNAME") or "MediaManager" _password: str = os.getenv("DB_PASSWORD") or "MediaManager" dbname: str = os.getenv("DB_NAME") or "MediaManager" @@ -17,17 +16,15 @@ class DbConfig(BaseModel): return self._password -class TvConfig(BaseModel): - api_key: str = os.getenv("TMDB_API_KEY") +class TmdbConfig(BaseModel): + api_key: str = os.getenv("TMDB_API_KEY") or None -class IndexerConfig(BaseModel): - default_indexer: Literal["tmdb"] = os.getenv("INDEXER") or "tmdb" - _default_indexer_api_key: str = os.getenv("INDEXER_API_KEY") - +class BasicConfig(BaseModel): + storage_directory: str = os.getenv("STORAGE_FILE_PATH") or "." class ProwlarrConfig(BaseModel): - enabled: bool = bool(os.getenv("PROWLARR_ENABLED")) or True + enabled: bool = bool(os.getenv("PROWLARR_ENABLED") or True) api_key: str = os.getenv("PROWLARR_API_KEY") url: str = os.getenv("PROWLARR_URL") @@ -37,7 +34,7 @@ class AuthConfig(BaseModel): # openssl rand -hex 32 _jwt_signing_key: str = os.getenv("JWT_SIGNING_KEY") jwt_signing_algorithm: str = "HS256" - jwt_access_token_lifetime: int = int(os.getenv("JWT_ACCESS_TOKEN_LIFETIME")) or 60 * 24 * 30 + jwt_access_token_lifetime: int = int(os.getenv("JWT_ACCESS_TOKEN_LIFETIME") or 60 * 24 * 30) @property def jwt_signing_key(self): @@ -61,23 +58,3 @@ class MachineLearningConfig(BaseModel): def get_db_config() -> DbConfig: return DbConfig() - - -log = logging.getLogger(__name__) - - -def load_config(): - log.info(f"loaded config: DbConfig: {DbConfig().__str__()}") - log.info(f"loaded config: IndexerConfig: {IndexerConfig().__str__()}") - log.info(f"loaded config: AuthConfig: {AuthConfig().__str__()}") - log.info(f"loaded config: TvConfig: {TvConfig().__str__()}") - - -if __name__ == "__main__": - db: DbConfig = DbConfig() - indexer: IndexerConfig = IndexerConfig() - auth: AuthConfig = AuthConfig() - - print(db.__str__()) - print(indexer.__str__()) - print(auth.__str__()) diff --git a/MediaManager/src/main.py b/MediaManager/src/main.py index d919736..7fa9454 100644 --- a/MediaManager/src/main.py +++ b/MediaManager/src/main.py @@ -1,28 +1,52 @@ import logging import sys +from logging.config import dictConfig + +logging.basicConfig(level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(name)s - %(funcName)s(): %(message)s", + stream=sys.stdout, + ) +log = logging.getLogger(__name__) import uvicorn from fastapi import FastAPI -import config import database.users import tv.router from auth import password from routers import users -logging.basicConfig(level=logging.DEBUG, - format="%(asctime)s - %(levelname)s - %(name)s - %(funcName)s(): %(message)s", - stream=sys.stdout) +LOGGING_CONFIG = { + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "default": { + "format": "%(asctime)s - %(levelname)s - %(name)s - %(funcName)s(): %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "default", + "stream": sys.stdout, + }, + }, + "loggers": { + "uvicorn": {"handlers": ["console"], "level": "DEBUG"}, + "uvicorn.access": {"handlers": ["console"], "level": "DEBUG"}, + "fastapi": {"handlers": ["console"], "level": "DEBUG"}, + "__main__": {"handlers": ["console"], "level": "DEBUG"}, + }, +} + +# Apply logging config +dictConfig(LOGGING_CONFIG) -config.load_config() database.init_db() app = FastAPI(root_path="/api/v1") app.include_router(users.router, tags=["users"]) app.include_router(password.router, tags=["authentication"]) app.include_router(tv.router.router, tags=["tv"]) - - - if __name__ == "__main__": - uvicorn.run(app, host="127.0.0.1", port=5049) + uvicorn.run(app, host="127.0.0.1", port=5049, log_config=LOGGING_CONFIG) diff --git a/MediaManager/src/metadataProvider/__init__.py b/MediaManager/src/metadataProvider/__init__.py new file mode 100644 index 0000000..5c1a02e --- /dev/null +++ b/MediaManager/src/metadataProvider/__init__.py @@ -0,0 +1,13 @@ +import logging + +import metadataProvider.tmdb +from database.tv import Show +from metadataProvider.abstractMetaDataProvider import metadata_providers + +log = logging.getLogger(__name__) + + +def get_show_metadata(id: int = None, provider: str = "tmdb") -> Show: + if id is None or provider is None: + raise ValueError("Show Metadata requires id and provider") + return metadata_providers[provider].get_show_metadata(id) diff --git a/MediaManager/src/metadataProvider/abstractMetaDataProvider.py b/MediaManager/src/metadataProvider/abstractMetaDataProvider.py new file mode 100644 index 0000000..ff9d460 --- /dev/null +++ b/MediaManager/src/metadataProvider/abstractMetaDataProvider.py @@ -0,0 +1,27 @@ +import logging +from abc import ABC, abstractmethod + +import config +from database.tv import Show + +log = logging.getLogger(__name__) + + +class MetadataProvider(ABC): + storage_path = config.BasicConfig().storage_directory + @property + @abstractmethod + def name(self) -> str: + pass + + @abstractmethod + def get_show_metadata(self, id: int = None) -> Show: + pass + + +metadata_providers = {} + + +def register_metadata_provider(metadata_provider: MetadataProvider): + log.info("Registering metadata provider:" + metadata_provider.name) + metadata_providers[metadata_provider.name] = metadata_provider diff --git a/MediaManager/src/metadataProvider/tmdb.py b/MediaManager/src/metadataProvider/tmdb.py new file mode 100644 index 0000000..b393d14 --- /dev/null +++ b/MediaManager/src/metadataProvider/tmdb.py @@ -0,0 +1,88 @@ +import logging +import mimetypes + +import requests +import tmdbsimple +from tmdbsimple import TV, TV_Seasons + +import config +from database.tv import Episode, Season, Show +from metadataProvider.abstractMetaDataProvider import MetadataProvider, register_metadata_provider + +config = config.TmdbConfig() +log = logging.getLogger(__name__) + + +class TmdbMetadataProvider(MetadataProvider): + name = "tmdb" + def get_show_metadata(self, id: int = None) -> Show: + """ + + :param id: the external id of the show + :type id: int + :return: returns a ShowMetadata object + :rtype: ShowMetadata + """ + show_metadata = TV(id).info() + season_list = [] + # inserting all the metadata into the objects + for season in show_metadata["seasons"]: + season_metadata = TV_Seasons(tv_id=show_metadata["id"], season_number=season["season_number"]).info() + episode_list = [] + + for episode in season_metadata["episodes"]: + episode_list.append( + Episode( + external_id=int(episode["id"]), + title=episode["name"], + number=int(episode["episode_number"]) + ) + ) + + season_list.append( + Season( + external_id=int(season_metadata["id"]), + name=season_metadata["name"], + overview=season_metadata["overview"], + number=int(season_metadata["season_number"]), + episodes=episode_list + ) + ) + + year: str | None = show_metadata["first_air_date"] + if year: + year: int = int(year.split('-')[0]) + else: + year = None + + show = Show( + external_id=id, + name=show_metadata["name"], + overview=show_metadata["overview"], + year=year, + seasons=season_list, + metadata_provider=self.name, + ) + + # downloading the poster + poster_url = "https://image.tmdb.org/t/p/original" + show_metadata["poster_path"] + res = requests.get(poster_url, stream=True) + content_type = res.headers["content-type"] + file_extension = mimetypes.guess_extension(content_type) + if res.status_code == 200: + with open(f"{self.storage_path}/images/{show.id}{file_extension}", 'wb') as f: + f.write(res.content) + log.info(f"image for show {show.name} successfully downloaded") + + else: + log.warning(f"image for show {show.name} could not be downloaded") + + return show + + def __init__(self, api_key: str = None): + tmdbsimple.API_KEY = api_key + + +if config.api_key is not None: + log.info("Registerng TMDB as metadata provider") + register_metadata_provider(metadata_provider=TmdbMetadataProvider(config.api_key))