Skip to content

Commit

Permalink
Implement Series YAML importing
Browse files Browse the repository at this point in the history
- Major part of #311
- Create /api/import/series to import Series YAML and create Series objects with matching config
- Also remove erroneous source_directory from BaseSeries Pydantic model
  • Loading branch information
CollinHeist committed May 12, 2023
1 parent 4e83844 commit f6a5124
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 38 deletions.
214 changes: 213 additions & 1 deletion app/internal/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from modules.Debug import log
from modules.EpisodeMap import EpisodeMap
from modules.SeriesInfo import SeriesInfo


Percentage = lambda s: float(str(s).split('%')[0]) / 100.0
Expand Down Expand Up @@ -204,6 +205,13 @@ def parse_fonts(
# Create NewNamedFont objects for all listed fonts
fonts = []
for font_name, font_dict in all_fonts.items():
# Skip if not a dictionary
if not isinstance(font_dict, dict):
raise HTTPException(
status_code=422,
detail=f'Invalid Font "{font_name}"',
)

# Get replacements
replacements = _get(font_dict, 'replacements', default={})
if not isinstance(replacements, dict):
Expand Down Expand Up @@ -248,6 +256,8 @@ def parse_templates(
List of NewTemplates that match any defined YAML templates.
Raises:
HTTPException (404) if an indicated Font name cannot be found in
the database.
HTTPException (422) if there are any YAML formatting errors.
Pydantic ValidationError if a NewTemplate object cannot be
created from the given YAML.
Expand All @@ -266,6 +276,13 @@ def parse_templates(
# Create NewTemplate objects for all listed templates
templates = []
for template_name, template_dict in all_templates.items():
# Skip if not a dictionary
if not isinstance(template_dict, dict):
raise HTTPException(
status_code=422,
detail=f'Invalid Template "{template_name}"',
)

# Parse custom Font
template_font = _get(template_dict, 'font')
font_id = None
Expand Down Expand Up @@ -340,4 +357,199 @@ def parse_templates(
extra_values=list(extras.values()),
))

return templates
return templates


def parse_series(
db: 'Database',
preferences: Preferences,
yaml_dict: dict[str, Any],
default_library: Optional[str] = None) -> list[NewSeries]:
"""
Create NewSeries objects for any defined series in the given YAML.
Args:
db: Database to query for custom Fonts and Templates if
indicated by any series.
preferences: Preferences to standardize styles and query for
the default media server.
yaml_dict: Dictionary of YAML attributes to parse.
default_library: Optional default Library name to apply to the
Series if one is not manually specified within YAML.
Returns:
List of NewSeries that match any defined YAML series.
Raises:
HTTPException (404) if an indicated Font or Template name cannot
be found in the database.
HTTPException (422) if there are any YAML formatting errors.
Pydantic ValidationError if a NewSeries object cannot be
created from the given YAML.
"""

# If series header was included, get those
if 'series' in yaml_dict:
all_series = yaml_dict['series']
else:
all_series = yaml_dict

# If not a dictionary of series, return empty list
if not isinstance(all_series, dict):
return []

# Determine which media server to assume libraries are for
if preferences.use_emby:
library_type = 'emby_library_name'
elif preferences.use_jellyfin:
library_type = 'jellyfin_library_name'
else:
library_type = 'plex_library_name'

# Create NewSeries objects for all listed templates
series = []
for series_name, series_dict in all_series.items():
# Skip if not a dictionary
if not isinstance(series_dict, dict):
raise HTTPException(
status_code=422,
detail=f'Invalid Series "{series_name}"',
)

# Create SeriesInfo for this series - parsing name/year/ID's
series_info = SeriesInfo(
_get(series_dict, 'name', type_=str, default=series_name),
_get(series_dict, 'year', type_=int, default=None),
emby_id=_get(series_dict, 'emby_id', default=None),
imdb_id=_get(series_dict, 'imdb_id', default=None),
jellyfin_id=_get(series_dict, 'jellyfin_id', default=None),
sonarr_id=_get(series_dict, 'sonarr_id', default=None),
tmdb_id=_get(series_dict, 'tmdb_id', default=None),
tvdb_id=_get(series_dict, 'tvdb_id', default=None),
tvrage_id=_get(series_dict, 'tvrage_id', default=None),
)

# Parse custom Font
series_font = _get(series_dict, 'font', default={})
font_id = None
if not isinstance(series_font, (str, dict)):
raise HTTPException(
status_code=422,
detail=f'Unrecognized Font in Series "{series_info}"',
)
elif isinstance(series_font, str):
# Get Font ID of this Font (if indicated)
font = db.query(models.font.Font)\
.filter_by(name=series_font).first()
if font is None:
raise HTTPException(
status_code=404,
detail=f'Font "{series_font}" not found',
)
font_id = font.id

# Parse template
template_id = None
if 'template' in series_dict:
# Get template name for querying
series_template = series_dict['template']
if isinstance(series_template, str):
template_name = series_template
elif isinstance(series_template, dict) and 'name' in series_template:
template_name = series_template['name']
else:
raise HTTPException(
status_code=422,
detail=f'Unrecognized Template in Series "{series_info}"',
)

# Get Template ID
template = db.query(models.template.Template)\
.filter_by(name=template_name).first()
if template is None:
raise HTTPException(
status_code=404,
detail=f'Template "{template_name}" not found',
)
template_id = template.id

# Get season titles via episode_ranges or seasons
episode_map = EpisodeMap(
_get(series_dict, 'seasons', default={}),
_get(series_dict, 'episode_ranges', default={}),
)
if not episode_map.valid:
raise HTTPException(
status_code=422,
detail=f'Invalid season titles in Series "{series_info}"',
)
season_titles = episode_map.raw

# Get extras
extras = _get(series_dict, 'extras', default={})
if not isinstance(extras, dict):
raise HTTPException(
status_code=422,
detail=f'Invalid extras in Series "{series_info}"',
)

# Use default library if a manual one was not specified
if (library := _get(series_dict, 'library')) is None:
library = default_library

# Create NewTemplate with all indicated customization
series.append(NewSeries(
name=series_info.name,
year=series_info.year,
template_id=template_id,
sync_specials=_get(series_dict, 'sync_specials', type_=bool),
card_filename_format=_get(series_dict, 'filename_format'),
episode_data_source=_parse_episode_data_source(series_dict),
match_titles=_get(series_dict, 'refresh_titles', default=True),
card_type=_get(series_dict, 'card_type'),
unwatched_style=_get(
series_dict,
'unwatched_style',
type_=preferences.standardize_style
), watched_style=_get(
series_dict,
'watched_style',
type_=preferences.standardize_style
),
translations=_parse_translations(series_dict),
font_id=font_id,
font_color=_get(series_dict, 'font', 'color'),
font_title_case=_get(series_dict, 'font', 'case'),
size=_get(
series_dict,
'font', 'size',
default=1.0, type_=Percentage
), kerning=_get(
series_dict,
'font', 'kerning',
default=1.0, type_=Percentage
), stroke_width=_get(
series_dict,
'font', 'stroke_width',
default=1.0, type_=Percentage
), interline_spacing=_get(
series_dict,
'font', 'interline_spacing',
default=0, type_=int
), vertical_shift=_get(
series_dict,
'font', 'vertical_shift',
default=0, type_=int
), directory=_get(series_dict, 'media_directory'),
**{library_type: library},
**series_info.ids,
season_title_ranges=list(season_titles.keys()),
season_title_values=list(season_titles.values()),
hide_season_text=_get(series_dict, 'seasons', 'hide', type_=bool),
episode_text_format=_get(series_dict, 'episode_text_format'),
hide_episode_text=_get(series_dict, 'hide_episode_text', type_=bool),
extra_keys=list(extras.keys()),
extra_values=list(extras.values()),
))

return series
8 changes: 3 additions & 5 deletions app/internal/series.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
from pathlib import Path
from requests import get
from typing import Any, Optional
from typing import Optional

from fastapi import HTTPException

from modules.Debug import log

from app.dependencies import (
get_database, get_preferences, get_emby_interface,
get_imagemagick_interface, get_jellyfin_interface, get_plex_interface,
get_sonarr_interface, get_tmdb_interface
get_database, get_emby_interface, get_jellyfin_interface,
get_plex_interface,
)
import app.models as models
from app.database.query import get_font, get_template
from app.schemas.preferences import MediaServer
from app.schemas.series import Series

Expand Down
2 changes: 1 addition & 1 deletion app/internal/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)
from app.internal.series import download_series_poster, set_series_database_ids
from app.internal.sources import download_series_logo
from app.schemas.sync import Sync, UpdateSync
from app.schemas.sync import Sync
from app.schemas.series import Series
import app.models as models

Expand Down
Loading

0 comments on commit f6a5124

Please sign in to comment.