Skip to content

Commit

Permalink
Release v1.14.4
Browse files Browse the repository at this point in the history
Create Overline and Marvel card, add masks to Tinted Frame card, other changes and fixes
  • Loading branch information
CollinHeist authored Sep 18, 2023
2 parents 89cfc92 + 6f188d3 commit 09be5be
Show file tree
Hide file tree
Showing 24 changed files with 1,063 additions and 62 deletions.
11 changes: 8 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
ARG PYVERSION=3.11

# Create pipenv image to convert Pipfile to requirements.txt
FROM python:3.11-slim as pipenv
FROM python:${PYVERSION}-slim as pipenv

# Copy Pipfile and Pipfile.lock
COPY Pipfile Pipfile.lock ./
Expand All @@ -8,7 +10,7 @@ COPY Pipfile Pipfile.lock ./
RUN pip3 install --no-cache-dir --upgrade pipenv; \
pipenv requirements > requirements.txt

FROM python:3.11-slim as python-reqs
FROM python:${PYVERSION}-slim as python-reqs

# Copy requirements.txt from pipenv stage
COPY --from=pipenv /requirements.txt requirements.txt
Expand All @@ -19,7 +21,7 @@ RUN apt-get update; \
pip3 install --no-cache-dir -r requirements.txt

# Set base image for running TCM
FROM python:3.11-slim
FROM python:${PYVERSION}-slim
LABEL maintainer="CollinHeist" \
description="Automated title card maker for Plex"

Expand All @@ -28,6 +30,7 @@ WORKDIR /maker
COPY . /maker

# Copy python packages from python-reqs
# update with python version
COPY --from=python-reqs /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages

# Script environment variables
Expand All @@ -48,6 +51,8 @@ RUN set -eux; \
rm -rf /var/lib/apt/lists/*; \
cp modules/ref/policy.xml /etc/ImageMagick-6/policy.xml

VOLUME [ "/config" ]

# Entrypoint
CMD ["python3", "main.py", "--run", "--no-color"]
ENTRYPOINT ["bash", "./start.sh"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ For invocation and configuration details, read [here](https://github.com/CollinH
Below are examples of almost all the types of title card that can be created automatically by TitleCardMaker:

### Built-in Card Types
<img alt="Anime" src="https://user-images.githubusercontent.com/17693271/185820454-4e3dca1c-c0df-4fa0-a7a7-81e070aa9e69.jpg" height="150"/> <img alt="comic book" src="https://github.com/CollinHeist/TitleCardMaker/assets/17693271/c5f34e46-ec3b-44a9-a563-b9a24db8cd1a" height="150"/> <img alt="Cutout" src="https://user-images.githubusercontent.com/17693271/212500535-e88daff6-ecc0-4cc8-8627-82069114c7e0.jpg" height="150"/> <img alt="Divider" src="https://user-images.githubusercontent.com/17693271/232378485-a9a737dc-9faf-47c2-b639-7df3d3ffb194.jpg" height="150"> <img alt="Fade" src="https://user-images.githubusercontent.com/17693271/214648223-b4f68553-e982-4efa-a16b-9662018b5d40.jpg" height="150"/> <img alt="Frame" src="https://user-images.githubusercontent.com/17693271/202352614-155a176a-fdb0-4476-9f11-6a3a20533a54.jpg" height="150"/> <img alt="Landscape" src="https://user-images.githubusercontent.com/17693271/202352137-b411da21-65ce-4bed-991b-90428c71ec34.jpg" height="150"/> <img alt="Logo" src="https://user-images.githubusercontent.com/17693271/172227163-0ee4990a-b0a8-4dbd-91b3-3f57dfe6e732.jpg" height="150"/> <img alt="Olivier" src="https://user-images.githubusercontent.com/17693271/212500009-067f14ff-4f48-4f75-bacd-7311a9aba716.jpg" height="150"/> <img alt="Poster" src="https://user-images.githubusercontent.com/17693271/180627387-f72bb58e-e001-4608-b4be-82a26263c628.jpg" height="150"/> <img alt="Roman" src="https://user-images.githubusercontent.com/17693271/203910966-4dde1466-6c7e-4422-923b-1f9222ad49e9.jpg" height="150"/> <img alt="Standard" src="https://user-images.githubusercontent.com/17693271/212500240-ae946f2c-a5c8-4881-85f2-83ccb45bf46e.jpg" height="150"/> <img alt="Star Wars" src="https://user-images.githubusercontent.com/17693271/170836059-136fa6eb-40ef-4cd7-9aca-8ad8e0537239.jpg" height="150"> <img alt="tinted Frame" src="https://user-images.githubusercontent.com/17693271/233257029-8b17ce2e-01ea-4ae3-bc73-54e152be4d31.jpg" height="150"> <img alt="Tinted Glass" src="https://user-images.githubusercontent.com/17693271/213939482-6018b2be-28c5-42dd-988d-d7b9733fe0e8.jpg" height="150"> <img alt="White Border" height="150" src="https://github.com/CollinHeist/TitleCardMaker/assets/17693271/14f25d6a-4be7-4078-97c2-7730ed070508"/>
<img alt="Anime" src="https://user-images.githubusercontent.com/17693271/185820454-4e3dca1c-c0df-4fa0-a7a7-81e070aa9e69.jpg" height="150"/> <img alt="comic book" src="https://github.com/CollinHeist/TitleCardMaker/assets/17693271/c5f34e46-ec3b-44a9-a563-b9a24db8cd1a" height="150"/> <img alt="Cutout" src="https://user-images.githubusercontent.com/17693271/212500535-e88daff6-ecc0-4cc8-8627-82069114c7e0.jpg" height="150"/> <img alt="Divider" src="https://user-images.githubusercontent.com/17693271/232378485-a9a737dc-9faf-47c2-b639-7df3d3ffb194.jpg" height="150"> <img alt="Fade" src="https://user-images.githubusercontent.com/17693271/214648223-b4f68553-e982-4efa-a16b-9662018b5d40.jpg" height="150"/> <img alt="Frame" src="https://user-images.githubusercontent.com/17693271/202352614-155a176a-fdb0-4476-9f11-6a3a20533a54.jpg" height="150"/> <img alt="Landscape" src="https://user-images.githubusercontent.com/17693271/202352137-b411da21-65ce-4bed-991b-90428c71ec34.jpg" height="150"/> <img alt="Logo" src="https://user-images.githubusercontent.com/17693271/172227163-0ee4990a-b0a8-4dbd-91b3-3f57dfe6e732.jpg" height="150"/> <img alt="Marvel" src="https://github.com/CollinHeist/TitleCardMaker/assets/17693271/8cd0c2b5-bd48-4c58-9e5b-bf1e0ebf322d" height="150"> <img alt="Olivier" src="https://user-images.githubusercontent.com/17693271/212500009-067f14ff-4f48-4f75-bacd-7311a9aba716.jpg" height="150"/> <img alt="Overline" src="https://github.com/TitleCardMaker/TitleCardMaker-WebUI/assets/17693271/64d88162-08e2-4745-aed7-06e136c21b2e" height="150"> <img alt="Poster" src="https://user-images.githubusercontent.com/17693271/180627387-f72bb58e-e001-4608-b4be-82a26263c628.jpg" height="150"/> <img alt="Roman" src="https://user-images.githubusercontent.com/17693271/203910966-4dde1466-6c7e-4422-923b-1f9222ad49e9.jpg" height="150"/> <img alt="Standard" src="https://user-images.githubusercontent.com/17693271/212500240-ae946f2c-a5c8-4881-85f2-83ccb45bf46e.jpg" height="150"/> <img alt="Star Wars" src="https://user-images.githubusercontent.com/17693271/170836059-136fa6eb-40ef-4cd7-9aca-8ad8e0537239.jpg" height="150"> <img alt="tinted Frame" src="https://user-images.githubusercontent.com/17693271/233257029-8b17ce2e-01ea-4ae3-bc73-54e152be4d31.jpg" height="150"> <img alt="Tinted Glass" src="https://user-images.githubusercontent.com/17693271/213939482-6018b2be-28c5-42dd-988d-d7b9733fe0e8.jpg" height="150"> <img alt="White Border" height="150" src="https://github.com/CollinHeist/TitleCardMaker/assets/17693271/14f25d6a-4be7-4078-97c2-7730ed070508"/>

> The above cards are, in order, the [anime](https://github.com/CollinHeist/TitleCardMaker/wiki/AnimeTitleCard), [comic book](https://github.com/CollinHeist/TitleCardMaker/wiki/ComicBookTitleCard), [cutout](https://github.com/CollinHeist/TitleCardMaker/wiki/CutoutTitleCard), [divider](https://github.com/CollinHeist/TitleCardMaker/wiki/DividerTitleCard), [fade](https://github.com/CollinHeist/TitleCardMaker/wiki/FadeTitleCard), [frame](https://github.com/CollinHeist/TitleCardMaker/wiki/FrameTitleCard), [landscape](https://github.com/CollinHeist/TitleCardMaker/wiki/LandscapeTitleCard), [logo](https://github.com/CollinHeist/TitleCardMaker/wiki/LogoTitleCard), [olivier](https://github.com/CollinHeist/TitleCardMaker/wiki/OlivierTitleCard), [poster](https://github.com/CollinHeist/TitleCardMaker/wiki/PosterTitleCard), [roman](https://github.com/CollinHeist/TitleCardMaker/wiki/RomanNumeralTitleCard), [standard](https://github.com/CollinHeist/TitleCardMaker/wiki/StandardTitleCard), [star wars](https://github.com/CollinHeist/TitleCardMaker/wiki/StarWarsTitleCard), [tinted frame](https://github.com/CollinHeist/TitleCardMaker/wiki/TintedFrameTitleCard), [tinted glass](https://github.com/CollinHeist/TitleCardMaker/wiki/TintedGlassTitleCard), and the [white border](https://github.com/CollinHeist/TitleCardMaker/wiki/WhiteBorderTitleCard) title cards - the [textless](https://github.com/CollinHeist/TitleCardMaker/wiki/TitleCard) card is not shown.
> The above cards are, in order, the [anime](https://github.com/CollinHeist/TitleCardMaker/wiki/AnimeTitleCard), [comic book](https://github.com/CollinHeist/TitleCardMaker/wiki/ComicBookTitleCard), [cutout](https://github.com/CollinHeist/TitleCardMaker/wiki/CutoutTitleCard), [divider](https://github.com/CollinHeist/TitleCardMaker/wiki/DividerTitleCard), [fade](https://github.com/CollinHeist/TitleCardMaker/wiki/FadeTitleCard), [frame](https://github.com/CollinHeist/TitleCardMaker/wiki/FrameTitleCard), [landscape](https://github.com/CollinHeist/TitleCardMaker/wiki/LandscapeTitleCard), [logo](https://github.com/CollinHeist/TitleCardMaker/wiki/LogoTitleCard), [marvel](https://github.com/CollinHeist/TitleCardMaker/wiki/MarvelTitleCard), [olivier](https://github.com/CollinHeist/TitleCardMaker/wiki/OlivierTitleCard), [overline](https://github.com/CollinHeist/TitleCardMaker/wiki/OverlineTitleCard), [poster](https://github.com/CollinHeist/TitleCardMaker/wiki/PosterTitleCard), [roman](https://github.com/CollinHeist/TitleCardMaker/wiki/RomanNumeralTitleCard), [standard](https://github.com/CollinHeist/TitleCardMaker/wiki/StandardTitleCard), [star wars](https://github.com/CollinHeist/TitleCardMaker/wiki/StarWarsTitleCard), [tinted frame](https://github.com/CollinHeist/TitleCardMaker/wiki/TintedFrameTitleCard), [tinted glass](https://github.com/CollinHeist/TitleCardMaker/wiki/TintedGlassTitleCard), and the [white border](https://github.com/CollinHeist/TitleCardMaker/wiki/WhiteBorderTitleCard) title cards - the [textless](https://github.com/CollinHeist/TitleCardMaker/wiki/TitleCard) card is not shown.

<details><summary><h3>User-Created Card Types</h3></summary>

Expand Down
17 changes: 6 additions & 11 deletions modules/BaseCardType.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class BaseCardType(ImageMaker):
"""Default case string for all title text"""
DEFAULT_FONT_CASE = 'upper'

"""Default font replacements"""
FONT_REPLACEMENTS = {}

"""Mapping of 'case' strings to format functions"""
CASE_FUNCTIONS = {
'blank': lambda _: '',
Expand Down Expand Up @@ -85,13 +88,6 @@ def TITLE_COLOR(self) -> str:
raise NotImplementedError(f'All CardType objects must implement this')


@property
@abstractmethod
def FONT_REPLACEMENTS(self) -> dict:
"""Standard font replacements for the episode title font"""
raise NotImplementedError(f'All CardType objects must implement this')


@property
@abstractmethod
def USES_SEASON_TITLE(self) -> bool:
Expand Down Expand Up @@ -136,10 +132,9 @@ def __init__(self,
def __repr__(self) -> str:
"""Returns an unambiguous string representation of the object."""

attributes = ', '.join(
f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__
if not attr.startswith('__')
)
attributes = ', '.join(f'{attr}={getattr(self, attr)!r}'
for attr in self.__slots__
if not attr.startswith('__'))

return f'<{self.__class__.__name__} {attributes}>'

Expand Down
2 changes: 1 addition & 1 deletion modules/DatabaseInfoContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __eq__(self, other: 'DatabaseInfoContainer') -> bool:

# Verify class comparison
if not isinstance(other, self.__class__):
raise TypeError(f'Can only compare like DatabaseInfoContainer objects')
raise TypeError(f'Can only compare like DatabaseInfoContainers')

# Compare each ID attribute in slots
for attr in self.__slots__:
Expand Down
6 changes: 3 additions & 3 deletions modules/Font.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class Font(YamlReader):

"""Valid YAML attributes to customize a font"""
VALID_ATTRIBUTES = (
'validate', 'color', 'size', 'file', 'case', 'case_name',
'replacements', 'vertical_shift', 'interline_spacing',
'interword_spacing', 'kerning', 'stroke_width',
'validate', 'color', 'size', 'file', 'case', 'replacements',
'vertical_shift', 'interline_spacing', 'interword_spacing', 'kerning',
'stroke_width',
)

"""Compiled regex to identify percentage values for scalars"""
Expand Down
12 changes: 10 additions & 2 deletions modules/JellyfinInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ class JellyfinInterface(EpisodeDataSource, MediaServer, SyncInterface):
AIRDATE_FORMAT = '%Y-%m-%dT%H:%M:%S.%f000000Z'


def __init__(self, url: str, api_key: str,
def __init__(self,
url: str,
api_key: str,
username: Optional[str] = None,
verify_ssl: bool = True,
filesize_limit: Optional[int] = None) -> None:
filesize_limit: Optional[int] = None,
) -> None:
"""
Construct a new instance of an interface to a Jellyfin server.
Expand Down Expand Up @@ -367,6 +370,11 @@ def get_all_episodes(self,
params={'Fields': 'ProviderIds'} | self.__params
)

if not isinstance(response, dict) or 'Items' not in response:
log.error(f'Jellyfin returned bad Episode data for {series_info}')
log.debug(f'{response=}')
return []

# Parse each returned episode into EpisodeInfo object
all_episodes = []
for episode in response['Items']:
Expand Down
5 changes: 3 additions & 2 deletions modules/Manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from modules.Debug import log, TQDM_KWARGS
from modules.JellyfinInterface import JellyfinInterface
from modules.PlexInterface import PlexInterface
from modules.Show import Show
from modules.ShowArchive import ShowArchive
from modules.SonarrInterface import SonarrInterface
from modules.TautulliInterface import TautulliInterface
Expand Down Expand Up @@ -108,8 +109,8 @@ def __init__(self, check_tautulli: bool = True) -> None:
)

# Setup blank show and archive lists
self.shows = []
self.archives = []
self.shows: list[Show] = []
self.archives: list[ShowArchive] = []


def sync_series_files(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion modules/MediaInfoSet.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def get_series_info(self,
self.series_info_db.insert({
'full_name': full_name, 'emby_id': emby_id, 'imdb_id': imdb_id,
'jellyfin_id': jellyfin_id, 'sonarr_id': sonarr_id,
'tmdb_id': tmdb_id, 'tvdb_id': tvdb_id, 'tvrage_id': tvrage_id,
'tmdb_id': tmdb_id, 'tvdb_id': tvdb_id, 'tvrage_id': tvrage_id,
})

return series_info
Expand Down
17 changes: 11 additions & 6 deletions modules/PlexInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,11 @@ def __get_series(self,

# Try by name
try:
for series in library.search(title=series_info.name,
year=series_info.year, libtype='show'):
if series.title in (series_info.name, series_info.full_name):
results = library.search(
title=series_info.name, year=series_info.year, libtype='show'
)
for series in results:
if series_info.matches(series.title):
return series
except NotFound:
pass
Expand All @@ -217,7 +219,8 @@ def __get_series(self,

@catch_and_log('Error getting library paths', default={})
def get_library_paths(self,
filter_libraries: list[str] = []) -> dict[str, list[str]]:
filter_libraries: list[str] = [],
) -> dict[str, list[str]]:
"""
Get all libraries and their associated base directories.
Expand Down Expand Up @@ -653,7 +656,8 @@ def get_libraries(self) -> list[str]:
reraise=True)
def __retry_upload(self,
plex_object: Union[PlexEpisode, PlexSeason],
filepath: Path) -> None:
filepath: Path,
) -> None:
"""
Upload the given poster to the given Episode, retrying if it fails.
Expand Down Expand Up @@ -828,7 +832,8 @@ def set_season_posters(self,

@catch_and_log('Error getting episode details')
def get_episode_details(self,
rating_key: int) -> list[tuple[SeriesInfo, EpisodeInfo, str]]:
rating_key: int,
) -> list[tuple[SeriesInfo, EpisodeInfo, str]]:
"""
Get all details for all episodes indicated by the given Plex rating key.
Expand Down
18 changes: 11 additions & 7 deletions modules/Profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

from modules.Debug import log
from modules.EpisodeInfo import EpisodeInfo
from modules.EpisodeMap import EpisodeMap
from modules.Font import Font
from modules.MultiEpisode import MultiEpisode
from modules.SeriesInfo import SeriesInfo


class Profile:
"""
This class describes a profile. A profile defines whether to use
Expand All @@ -27,8 +29,9 @@ def __init__(self,
series_info: SeriesInfo,
font: Font,
hide_seasons: bool,
episode_map: 'EpisodeMap',
episode_text_format: str) -> None:
episode_map: EpisodeMap,
episode_text_format: str,
) -> None:
"""
Construct a new instance of a Profile. All given arguments will
be applied through this Profile (and whether it's generic or
Expand Down Expand Up @@ -140,9 +143,9 @@ def convert_profile(self, seasons: str, font: str) -> None:
"""

# Update this object's data
self.__use_custom_seasons = (seasons in ('custom', 'hidden'))
self.hide_season_title = (seasons == 'hidden')
self.__use_custom_font = (font == 'custom')
self.__use_custom_seasons = seasons in ('custom', 'hidden')
self.hide_season_title = seasons == 'hidden'
self.__use_custom_font = font == 'custom'

# If the new profile has a generic font, reset font attributes
if not self.__use_custom_font:
Expand Down Expand Up @@ -220,8 +223,9 @@ def get_episode_text(self, episode: 'Episode') -> str:
log.warning(f'Episode text formatting uses absolute episode number,'
f' but {episode} has no absolute number - using episode'
f' number instead')
format_string = self.episode_text_format.replace('{abs_',
'{episode_')
format_string = self.episode_text_format.replace(
'{abs_', '{episode_'
)

# Format MultiEpisode episode text
if isinstance(episode, MultiEpisode):
Expand Down
9 changes: 6 additions & 3 deletions modules/RemoteFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from modules.Debug import log
from modules.PersistentDatabase import PersistentDatabase


class RemoteFile:
"""
This class describes a RemoteFile. A RemoteFile is a file that is
Expand Down Expand Up @@ -92,8 +93,10 @@ def __str__(self) -> str:
def __repr__(self) -> str:
"""Returns an unambiguous string representation of the object."""

return (f'<RemoteFile remote_source={self.remote_source}, local_file='
f'{self.local_file}, valid={self.valid}>')
return (
f'<RemoteFile remote_source={self.remote_source}, local_file='
f'{self.local_file}, valid={self.valid}>'
)


def resolve(self) -> Path:
Expand All @@ -117,7 +120,7 @@ def __get_remote_content(self) -> Response:
Response object from this object's remote source.
"""

return get(self.remote_source, timeout=30)
return get(self.remote_source, timeout=10)


def download(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion modules/SeriesYamlWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def yaml_contains(yaml: dict, key: str) -> tuple[bool, str]:
return True, key
# If present in lowercase
for yaml_key in yaml.get('series', {}).keys():
if key.lower() == yaml_key.lower():
if str(key).lower() == yaml_key.lower():
return True, yaml_key
# Not present at all
return False, key
Expand Down
8 changes: 5 additions & 3 deletions modules/StandardSummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _create_montage(self) -> Path:
f'-tile 3x3',
f'-geometry +80+80',
f'-shadow',
f'"'+'" "'.join(self.inputs)+'"', # Wrap each filename in ""
f'"'+'" "'.join(self.inputs)+'"', # Wrap each filename in ""
f'"{self.__MONTAGE_PATH.resolve()}"',
])

Expand Down Expand Up @@ -201,7 +201,8 @@ def _add_logo(self, montage: Path, logo: Path) -> Path:

def _add_created_by(self,
montage_and_logo: Path,
created_by: Path) -> Path:
created_by: Path,
) -> Path:
"""
Add the 'created by' image to the bottom of the montage.
Expand Down Expand Up @@ -232,7 +233,8 @@ def _add_created_by(self,

def __add_background_image(self,
montage_and_logo: Path,
created_by: Path) -> Path:
created_by: Path,
) -> Path:
"""
Add the two images on top of the background image.
Expand Down
Loading

0 comments on commit 09be5be

Please sign in to comment.