Skip to content

Commit

Permalink
Update to version v1.13.5
Browse files Browse the repository at this point in the history
Optionally adjust output card dimensions, various other fixes and changes
  • Loading branch information
CollinHeist authored Apr 10, 2023
2 parents 7b99df9 + e470fc2 commit 4798a2b
Show file tree
Hide file tree
Showing 53 changed files with 861 additions and 595 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ RUN set -eux; \
groupadd -g 314 titlecardmaker; \
useradd -u 314 -g 314 titlecardmaker; \
apt-get update; \
apt-get install -y --no-install-recommends gosu imagemagick; \
apt-get install -y --no-install-recommends gosu imagemagick libmagickcore-6.q16-6-extra; \
rm -rf /var/lib/apt/lists/*; \
cp modules/ref/policy.xml /etc/ImageMagick-6/policy.xml

Expand Down
56 changes: 39 additions & 17 deletions fixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from modules.EmbyInterface import EmbyInterface
from modules.EpisodeInfo import EpisodeInfo
from modules.ImageMaker import ImageMaker
from modules.JellyfinInterface import JellyfinInterface
from modules.PlexInterface import PlexInterface
from modules.PreferenceParser import PreferenceParser
from modules.global_objects import set_preference_parser
Expand Down Expand Up @@ -65,15 +66,15 @@
default=SUPPRESS,
help='Print the last log file')

# Argument group for Plex
# Argument group for the media server
media_server_group = parser.add_argument_group('Media Server')
media_server_group.add_argument(
'--import-cards', '--import-archive', '--load-archive',
type=str,
nargs=2,
default=SUPPRESS,
metavar=('ARCHIVE_DIRECTORY', 'LIBRARY'),
help='Import an archive of Title Cards into Emby/Plex')
help='Import an archive of Title Cards into Emby/Jellyfin/Plex')
media_server_group.add_argument(
'--import-series', '--load-series',
type=str,
Expand Down Expand Up @@ -101,7 +102,15 @@
nargs=3,
default=SUPPRESS,
metavar=('LIBRARY', 'NAME', 'YEAR'),
help='Remove the cards for the given series within Emby/Plex')
help='Remove the cards for the given series within Emby/Jellyfin/Plex')
media_server_group.add_argument(
'--id', '--series-id',
type=str,
nargs=2,
default=[],
action='append',
metavar=('ID_TYPE', 'ID'),
help='Specify database IDs of a series for importing/reloading cards')

# Argument group for Sonarr
sonarr_group = parser.add_argument_group('Sonarr')
Expand Down Expand Up @@ -183,21 +192,23 @@
print(file_handle.read())


# Execute Emby/Plex options
if (hasattr(args, 'import_cards')
or hasattr(args, 'revert_series')) and (pp.use_plex or pp.use_emby):
# Execute Media Server options
if ((hasattr(args, 'import_cards') or hasattr(args, 'revert_series'))
and any((pp.use_emby, pp.use_jellyfin, pp.use_plex))):
# Temporary classes
@dataclass
class Episode:
destination: Path
episode_info: EpisodeInfo
spoil_type: str

# Create Emby/PlexInterface
if args.media_server == 'plex':
media_interface = PlexInterface(**pp.plex_interface_kwargs)
else:
# Create MediaServer Interface
if args.media_server == 'emby':
media_interface = EmbyInterface(**pp.emby_interface_kwargs)
elif args.media_server == 'jellyfin':
media_interface = JellyfinInterface(**pp.jellyfin_interface_kwargs)
else:
media_interface = PlexInterface(**pp.plex_interface_kwargs)

# Get series/name + year from archive directory if unspecified
if hasattr(args, 'import_cards'):
Expand All @@ -217,6 +228,14 @@ class Episode:
archive = pp.source_directory / series_info.full_clean_name
library = args.revert_series[0]

# Get series ID's if provided
if args.id:
for id_type, id_ in args.id:
try:
getattr(series_info, f'set_{id_type}_id')(id_)
except Exception as e:
log.error(f'Unrecognized ID type "{id_type}" - {e}')

# Forget cards associated with this series
media_interface.remove_records(library, series_info)

Expand All @@ -226,7 +245,7 @@ class Episode:
log.warning(f'No images to import')
exit(1)

# For each image, fill out episode map to load into Emby/Plex
# For each image, fill out episode map to load into server
episode_map = {}
for image in all_images:
if (groups := match(r'.*s(\d+).*e(\d+)', image.name, IGNORECASE)):
Expand All @@ -239,18 +258,21 @@ class Episode:
ep = Episode(image, EpisodeInfo('', season, episode), 'spoiled')
episode_map[f'{season}-{episode}'] = ep

# Load images into Emby/Plex
media_interface.set_title_cards(library, series_info,episode_map)
# Load images into server
media_interface.set_title_cards(library, series_info, episode_map)

if hasattr(args, 'forget_cards') and (pp.use_plex or pp.use_emby):
# Create interface and remove records for indicated series+library
# Create interface and remove records for indicated series+library
if (hasattr(args, 'forget_cards')
and any((pp.use_emby, pp.use_jellyfin, pp.use_plex))):
series_info = SeriesInfo(args.forget_cards[1], args.forget_cards[2])

# Create Emby/PlexInterface
if args.media_server == 'emby':
EmbyInterface(**pp.emby_interface_kwargs).remove_records(
args.forget_cards[0], series_info,
)
elif args.media_server == 'jellyfin':
JellyfinInterface(**pp.jellyfin_interface_kwargs).remove_records(
args.forget_cards[0], series_info,
)
else:
PlexInterface(**pp.plex_interface_kwargs).remove_records(
args.forget_cards[0], series_info,
Expand Down
29 changes: 25 additions & 4 deletions modules/BaseCardType.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import abstractmethod
from typing import Any
from typing import Any, Optional

from titlecase import titlecase

Expand Down Expand Up @@ -98,7 +98,10 @@ def USES_SEASON_TITLE(self) -> bool:


@abstractmethod
def __init__(self, blur: bool = False, grayscale: bool = False) -> None:
def __init__(self,
blur: bool = False,
grayscale: bool = False, *,
preferences: Optional['Preferences'] = None) -> None:
"""
Construct a new CardType. Must call super().__init__() to
initialize the parent ImageMaker class (for PreferenceParser and
Expand All @@ -111,7 +114,7 @@ def __init__(self, blur: bool = False, grayscale: bool = False) -> None:
"""

# Initialize parent ImageMaker
super().__init__()
super().__init__(preferences=preferences)

# Object starts as valid
self.valid = True
Expand All @@ -132,7 +135,9 @@ def __repr__(self) -> str:


@staticmethod
def modify_extras(extras: dict[str, Any], custom_font: bool,
def modify_extras(
extras: dict[str, Any],
custom_font: bool,
custom_season_titles: bool) -> None:
"""
Modify the given extras base on whether font or season titles
Expand Down Expand Up @@ -226,6 +231,22 @@ def style(self) -> ImageMagickCommands:
f'-set colorspace sRGB' if self.grayscale else '',
]


@property
def resize_output(self) -> ImageMagickCommands:
"""
ImageMagick commands to resize the card to the global card
dimensions.
Returns:
List of ImageMagick commands.
"""

return [
f'-resize "{self.preferences.card_dimensions}"',
f'-extent "{self.preferences.card_dimensions}"',
]


@abstractmethod
def create(self) -> None:
Expand Down
36 changes: 19 additions & 17 deletions modules/BaseSummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

class BaseSummary(ImageMaker):
"""
This class describes a type of ImageMaker that specializes in creating Show
summaries. These are montage images that display a random selection of
title cards for a given Show object in order to give a quick visual
indicator as to the style of the cards.
This class describes a type of ImageMaker that specializes in
creating Show summaries. These are montage images that display a
random selection of title cards for a given Show object in order to
give a quick visual indicator as to the style of the cards.
This object cannot be instantiated directly, and only provides very few
methods that can/should be used by all Summary subclasses.
This object cannot be instantiated directly, and only provides very
few methods that can/should be used by all Summary subclasses.
"""

"""Directory where all reference files are stored"""
Expand All @@ -41,11 +41,12 @@ def __init__(self, show: 'Show', created_by: str=None) -> None:
Args:
show: The Show object to create the Summary for.
background: Background color or image to use for the summary. Can
also be a "format string" that is "{series_background}" to use
the given Show object's backdrop.
created_by: Optional string to use in custom "Created by .." tag at
the botom of this Summary.
background: Background color or image to use for the
summary. Can also be a "format string" that is
"{series_background}" to use the given Show object's
backdrop.
created_by: Optional string to use in custom "Created by .."
tag at the botom of this Summary.
"""

# Initialize parent ImageMaker
Expand All @@ -66,11 +67,12 @@ def __init__(self, show: 'Show', created_by: str=None) -> None:

def _select_images(self, maximum_images: int=9) -> bool:
"""
Select the images that are to be incorporated into the show summary.
This updates the object's inputs and number_rows attributes.
Select the images that are to be incorporated into the show
summary. This updates the object's inputs and number_rows
attributes.
Args:
maximum_images: maximum number of images to select. Defaults to 9.
maximum_images: maximum number of images to select.
Returns:
Whether the ShowSummary should/can be created.
Expand Down Expand Up @@ -119,9 +121,9 @@ def _select_images(self, maximum_images: int=9) -> bool:

def _create_created_by(self, created_by: str) -> Path:
"""
Create a custom "Created by" tag image. This image is formatted like:
"Created by {input} with {logo} TitleCardMaker". The image is exactly
the correct size (i.e. fit to width of text).
Create a custom "Created by" tag image. This image is formatted
like: "Created by {input} with {logo} TitleCardMaker". The image
is exactly the correct size (i.e. fit to width of text).
Returns:
Path to the created image.
Expand Down
22 changes: 12 additions & 10 deletions modules/CleanPath.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

class CleanPath(_Path_):
"""
Subclass of Path that is more OS-agnostic and implements methods of cleaning
directories and filenames of bad characters. For example:
Subclass of Path that is more OS-agnostic and implements methods of
cleaning directories and filenames of bad characters. For example:
>>> p = CleanPath('./some_file: 123.jpg')
>>> print(p)
Expand Down Expand Up @@ -34,14 +34,15 @@ class CleanPath(_Path_):

def finalize(self) -> 'CleanPath':
"""
Finalize this path by properly resolving if absolute or relative.
Finalize this path by properly resolving if absolute or
relative.
Returns:
This object as a fully resolved path.
Raises:
OSError if the resolution fails (likely due to an unresolvable
filename).
OSError if the resolution fails (likely due to an
unresolvable filename).
"""

return (CleanPath.cwd() / self).resolve()
Expand All @@ -53,7 +54,8 @@ def sanitize_name(filename: str) -> str:
Sanitize the given filename to remove any illegal characters.
Args:
filename: Filename (string) to remove illegal characters from.
filename: Filename (string) to remove illegal characters
from.
Returns:
Sanitized filename.
Expand All @@ -73,8 +75,8 @@ def _sanitize_parts(path: 'CleanPath') -> 'CleanPath':
path: Path to sanitize.
Returns:
Sanitized path. This is a reconstructed CleanPath object with each
folder (or part), except the root/drive, sanitized.
Sanitized path. This is a reconstructed CleanPath object
with each folder (or part), except the root/drive, sanitized.
"""

return CleanPath(
Expand All @@ -88,8 +90,8 @@ def sanitize(self) -> 'CleanPath':
Sanitize all parts (except the root) of this objects path.
Returns:
CleanPath object instantiated with sanitized names of each part of
this object's path.
CleanPath object instantiated with sanitized names of each
part of this object's path.
"""

# Attempt to resolve immediately
Expand Down
14 changes: 10 additions & 4 deletions modules/CollectionPosterMaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ class CollectionPosterMaker(ImageMaker):
__GRADIENT = REF_DIRECTORY.parent / 'genre' / 'genre_gradient.png'


def __init__(self, source: Path, output: Path, title: str, font: Path=FONT,
font_color: str=FONT_COLOR, font_size: float=1.0,
omit_collection: bool=False, borderless: bool=False,
omit_gradient: bool=False) -> None:
def __init__(self,
source: Path,
output: Path,
title: str,
font: Path = FONT,
font_color: str = FONT_COLOR,
font_size: float = 1.0,
omit_collection: bool = False,
borderless: bool = False,
omit_gradient: bool = False) -> None:
"""
Construct a new instance of a CollectionPosterMaker.
Expand Down
15 changes: 8 additions & 7 deletions modules/DatabaseInfoContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

class DatabaseInfoContainer(ABC):
"""
This class describes an abstract base class for all Info objects containing
database ID's. This provides common methods for checking whether an object
has a specific ID, as well as updating an ID within an objct.
This class describes an abstract base class for all Info objects
containing database ID's. This provides common methods for checking
whether an object has a specific ID, as well as updating an ID
within an objct.
"""

def _update_attribute(self, attribute: str, value: Any,
Expand Down Expand Up @@ -38,8 +39,8 @@ def has_id(self, id_: str) -> bool:
id_: ID being checked
Returns:
True if the given ID is defined (i.e. not None) for this object.
False otherwise.
True if the given ID is defined (i.e. not None) for this
object. False otherwise.
"""

return getattr(self, id_) is not None
Expand All @@ -53,8 +54,8 @@ def has_ids(self, *ids: tuple[str]) -> bool:
ids: Any ID's being checked for.
Returns:
True if all the given ID's are defined (i.e. not None) for this
object. False otherwise.
True if all the given ID's are defined (i.e. not None) for
this object. False otherwise.
"""

return all(getattr(self, id_) is not None for id_ in ids)
6 changes: 3 additions & 3 deletions modules/Debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ def format(self, record):

def apply_no_color_formatter() -> None:
"""
Modify the global logger object by replacing the colored Handler with an
instance of the LogFormatterNoColor Handler class. Also set the log level
to that of the removed handler
Modify the global logger object by replacing the colored Handler
with an instance of the LogFormatterNoColor Handler class. Also set
the log level to that of the removed handler.
"""

# Get existing handler's log level, then delete
Expand Down
Loading

0 comments on commit 4798a2b

Please sign in to comment.