Skip to content

Commit

Permalink
Create SQLAlchemy ORM models
Browse files Browse the repository at this point in the history
SQL portion of #311
  • Loading branch information
CollinHeist committed Mar 20, 2023
1 parent 340b197 commit 195af95
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 14 deletions.
Empty file modified app/database/__init__.py
100644 → 100755
Empty file.
76 changes: 74 additions & 2 deletions app/database/session.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,8 +1,80 @@
from pathlib import Path

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

from app.models.preferences import Preferences
from modules.EmbyInterface import EmbyInterface
from modules.JellyfinInterface import JellyfinInterface
from modules.PlexInterface2 import PlexInterface
from modules.SonarrInterface2 import SonarrInterface
from modules.TMDbInterface2 import TMDbInterface

SQLALCHEMY_DATABASE_URL = 'sqlite:///./db.sqlite'
engine = create_engine(
# TODO https://github.com/ChristopherGS/ultimate-fastapi-tutorial/blob/main/part-08-structure-and-versioning/app/db/session.py
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

if (file := Path('/mnt/user/Media/TitleCardMaker/app/prefs.json')).exists():
from pickle import load
with file.open('rb') as fh:
PreferencesLocal = load(fh)
else:
PreferencesLocal = Preferences()

EmbyInterfaceLocal = None
if PreferencesLocal.use_emby:
try:
EmbyInterfaceLocal = EmbyInterface(
PreferencesLocal.emby_url,
PreferencesLocal.emby_api_key,
PreferencesLocal.emby_username,
PreferencesLocal.emby_use_ssl,
PreferencesLocal.emby_filesize_limit,
)
except Exception as e:
...

JellyfinInterfaceLocal = None
if PreferencesLocal.use_jellyfin:
try:
JellyfinInterfaceLocal = JellyfinInterface(
PreferencesLocal.jellyfin_url,
PreferencesLocal.jellyfin_api_key,
PreferencesLocal.jellyfin_username,
PreferencesLocal.jellyfin_use_ssl,
PreferencesLocal.jellyfin_filesize_limit,
)
except Exception as e:
...

PlexInterfaceLocal = None
if PreferencesLocal.use_plex:
try:
PlexInterfaceLocal = PlexInterface(
PreferencesLocal.plex_url,
PreferencesLocal.plex_token,
PreferencesLocal.plex_use_ssl,
PreferencesLocal.plex_integrate_with_pmm,
PreferencesLocal.plex_filesize_limit,
)
except Exception:
...

SonarrInterfaceLocal = None
if PreferencesLocal.use_sonarr:
SonarrInterfaceLocal = SonarrInterface(
PreferencesLocal.sonarr_url,
PreferencesLocal.sonarr_api_key,
PreferencesLocal.plex_use_ssl,
)

TMDbInterfaceLocal = None
if PreferencesLocal.use_tmdb:
TMDbInterfaceLocal = TMDbInterface(
PreferencesLocal.tmdb_api_key,
)
Empty file modified app/models/__init__.py
100644 → 100755
Empty file.
25 changes: 25 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from sqlalchemy import Boolean, Column, Integer, String, ForeignKey

from app.database.session import Base

class Card(Base):
__tablename__ = 'card'

id = Column(Integer, primary_key=True, index=True)
series_id = Column(Integer, ForeignKey('series.id'))
episode_id = Column(Integer, ForeignKey('episode.id'))
font_id = Column(Integer, ForeignKey('font.id'))

source = Column(String)
output = Column(String)
card_type = Column(String)

blur = Column(Boolean)
grayscale = Column(Boolean)

title = Column(String)
season_text = Column(String)
episode_text = Column(String)
hide_season = Column(Boolean)

extras = Column(String, nullable=True)
32 changes: 26 additions & 6 deletions app/models/episode.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
from pydantic import BaseModel
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy import Boolean, Column, Integer, String, ForeignKey

class Episode(BaseModel):
from app.database.session import Base

def default_source_file(context) -> str:
params = context.get_current_parameters()
return f's{params["season_number"]}e{params["episode_number"]}.jpg'

def default_destination(context) -> str:
params = context.get_current_parameters()
return f'card-s{params["season_number"]}e{params["episode_number"]}.jpg'

class Episode(Base):
__tablename__ = 'episode'

id = Column(Integer, primary_key=True, index=True)
series_id = Column(Integer, ForeignKey('series.id'))

season_number = Column(Integer)
episode_number = Column(Integer)
absolute_number = Column(Integer, nullable=True)
absolute_number = Column(Integer, default=None)

title = Column(String)
match_title = Column(Boolean, default=False)

source_file = Column(String, default=default_source_file)
destination = Column(String, default=default_destination)

source_file = Column(String)
destination = Column(String)
emby_id = Column(Integer, default=None)
imdb_id = Column(String, default=None)
jellyfin_id = Column(String, default=None)
sonarr_id = Column(String, default=None)
tmdb_id = Column(Integer, default=None)
tvdb_id = Column(Integer, default=None)
tvrage_id = Column(Integer, default=None)
25 changes: 25 additions & 0 deletions app/models/font.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from sqlalchemy import Boolean, Column, Integer, Float, String, ForeignKey

from json import dumps, loads
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.ext.mutable import MutableDict, MutableList
from sqlalchemy import PickleType

from app.database.session import Base

class Font(Base):
__tablename__ = 'font'

id = Column(Integer, primary_key=True, index=True)
name = Column(String)
file_path = Column(String, default=None)
color = Column(String, default=None)
title_case = Column(String, default=None)
size = Column(Float, default=1.0)
kerning = Column(Float, default=1.0)
stroke_width = Column(Float, default=1.0)
interline_spacing = Column(Integer, default=0)
vertical_shift = Column(Integer, default=0)
validate_characters = Column(Boolean, default=None)
delete_missing = Column(Boolean, default=True)
replacements = Column(MutableDict.as_mutable(PickleType), default={})
166 changes: 166 additions & 0 deletions app/models/preferences.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from collections import namedtuple
from os import environ
from pathlib import Path

from pickle import dump

from modules.Debug import log
from modules.ImageMagickInterface import ImageMagickInterface
from modules.StyleSet import StyleSet

EpisodeDataSource = namedtuple('EpisodeDataSource', ('value', 'label'))
Emby = EpisodeDataSource('emby', 'Emby')
Jellyfin = EpisodeDataSource('jellyfin', 'Jellyfin')
Plex = EpisodeDataSource('plex', 'Plex')
Sonarr = EpisodeDataSource('sonarr', 'Sonarr')
TMDb = EpisodeDataSource('tmdb', 'TMDb')

class Preferences:

DEFAULT_CARD_FILENAME_FORMAT = ('{full_name} S{season_number:02}'
'E{episode_number:02}')
DEFAULT_CARD_EXTENSION = '.jpg'
DEFAULT_IMAGE_SOURCE_PRIORITY = ['TMDb', 'Plex', 'Jellyfin', 'Emby']
DEFAULT_EPISODE_DATA_SOURCE = 'Sonarr'
VALID_IMAGE_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.tiff', '.gif', '.webp')


def __init__(self) -> None:
self.asset_directory = Path(__file__).parent.parent / 'assets'
self.card_directory = Path(__file__).parent / 'cards'
self.source_directory = Path(__file__).parent / 'source'

self.card_filename_format = self.DEFAULT_CARD_FILENAME_FORMAT
self.card_extension = self.DEFAULT_CARD_EXTENSION
self.image_source_priority = self.DEFAULT_IMAGE_SOURCE_PRIORITY
self.episode_data_source = self.DEFAULT_EPISODE_DATA_SOURCE
self.valid_image_extensions = self.VALID_IMAGE_EXTENSIONS

self.validate_fonts = True
self.specials_folder_format = 'Specials'
self.season_folder_format = 'Season {season}'
self.sync_specials = True
self.supported_language_codes = []

self.default_card_type = 'Standard'
self.default_watched_style = 'Unique'
self.default_unwatched_style = 'Unique'

self.use_emby = False
self.emby_url = 'http://192.168.0.11:8096/emby' #''
self.emby_api_key = 'e25b06a1aee34fc0949c35d74f379d03' #''
self.emby_username = 'CollinHeist' #''
self.emby_use_ssl = False
self.emby_filesize_limit = None

self.use_jellyfin = False
self.jellyfin_url = ''
self.jellyfin_api_key = ''
self.jellyfin_username = ''
self.jellyfin_use_ssl = False
self.jellyfin_filesize_limit = None

self.use_plex = True #False
self.plex_url = 'http://192.168.0.29:32400/' #''
self.plex_token = 'pzfzWxW-ygxzJJc-t_Pw' #''
self.plex_use_ssl = False
self.plex_integrate_with_pmm = False
self.plex_filesize_limit = None

self.use_sonarr = True #False
self.sonarr_url = 'http://192.168.0.29:8989/api/v3/' #''
self.sonarr_api_key = 'd43fdeb9c3744f58b1ffbaf5dc21e55a' #''
self.sonarr_use_ssl = False
self.sonarr_libraries = {}

self.use_tmdb = True #False
self.tmdb_api_key = 'b1dbf02a14b523a94401e3e6ee521353' #''
self.tmdb_minimum_width = 800 #0
self.tmdb_minimum_height = 400 #0
self.tmdb_skip_localized = False
self.supported_language_codes = []

self.is_docker = environ.get('TCM_IS_DOCKER', 'false').lower() == 'true'
self.use_magick_prefix = False
self.imagemagick_container = 'ImageMagick' #None
self.imagemagick_timeout = 60
self.__determine_imagemagick_prefix()


def __setattr__(self, name, value) -> None:
self.__dict__[name] = value
self._rewrite_preferences()


def _rewrite_preferences(self) -> None:
with Path('/mnt/user/Media/TitleCardMaker/app/prefs.json').open('wb') as fh:
dump(self, fh)


def __determine_imagemagick_prefix(self) -> None:
"""
Determine whether to use the "magick " prefix for ImageMagick commands.
If a prefix cannot be determined, a critical message is logged and the
program exits with an error.
"""

# Try variations of the font list command with/out the "magick " prefix
for prefix, use_magick in zip(('', 'magick '), (False, True)):
# Create ImageMagickInterface and verify validity
interface = ImageMagickInterface(
self.imagemagick_container,
use_magick,
self.imagemagick_timeout
)
if interface.validate_interface():
self.use_magick_prefix = use_magick
log.debug(f'Using "{prefix}" ImageMagick command prefix')
return None

# If none of the font commands worked, IM might not be installed
log.critical(f"ImageMagick doesn't appear to be installed")
self.valid = False


def update_values(self, **update_kwargs) -> None:
for name, value in update_kwargs.items():
self.__dict__[name] = value
self._rewrite_preferences()


@property
def valid_image_sources(self) -> list[str]:
return set(
(['Emby'] if self.use_emby else [])
+ (['Plex'] if self.use_plex else [])
+ (['TMDb'] if self.use_tmdb else [])
)


@property
def valid_episode_data_sources(self) -> list[str]:
return (
(['Emby'] if self.use_emby else [])
+ (['Plex'] if self.use_plex else [])
+ (['TMDb'] if self.use_tmdb else [])
+ (['Sonarr'] if self.use_sonarr else [])
)


@property
def enabled_media_servers(self) -> list[str]:
return (
(['Emby'] if self.use_emby else [])
+ (['Plex'] if self.use_plex else [])
)


@staticmethod
def get_filesize(value: int, unit: str) -> int:
return value * {
'B': 1, 'Bytes': 1,
'KB': 2**10, 'Kilobytes': 2**10,
'MB': 2**20, 'Megabytes': 2**20,
'GB': 2**30, 'Gigabytes': 2**30,
'TB': 2**40, 'Terabytes': 2**40,
}[unit]
Loading

0 comments on commit 195af95

Please sign in to comment.