Skip to content

Commit 4798a2b

Browse files
authored
Update to version v1.13.5
Optionally adjust output card dimensions, various other fixes and changes
2 parents 7b99df9 + e470fc2 commit 4798a2b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+861
-595
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ RUN set -eux; \
4444
groupadd -g 314 titlecardmaker; \
4545
useradd -u 314 -g 314 titlecardmaker; \
4646
apt-get update; \
47-
apt-get install -y --no-install-recommends gosu imagemagick; \
47+
apt-get install -y --no-install-recommends gosu imagemagick libmagickcore-6.q16-6-extra; \
4848
rm -rf /var/lib/apt/lists/*; \
4949
cp modules/ref/policy.xml /etc/ImageMagick-6/policy.xml
5050

fixer.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from modules.EmbyInterface import EmbyInterface
1111
from modules.EpisodeInfo import EpisodeInfo
1212
from modules.ImageMaker import ImageMaker
13+
from modules.JellyfinInterface import JellyfinInterface
1314
from modules.PlexInterface import PlexInterface
1415
from modules.PreferenceParser import PreferenceParser
1516
from modules.global_objects import set_preference_parser
@@ -65,15 +66,15 @@
6566
default=SUPPRESS,
6667
help='Print the last log file')
6768

68-
# Argument group for Plex
69+
# Argument group for the media server
6970
media_server_group = parser.add_argument_group('Media Server')
7071
media_server_group.add_argument(
7172
'--import-cards', '--import-archive', '--load-archive',
7273
type=str,
7374
nargs=2,
7475
default=SUPPRESS,
7576
metavar=('ARCHIVE_DIRECTORY', 'LIBRARY'),
76-
help='Import an archive of Title Cards into Emby/Plex')
77+
help='Import an archive of Title Cards into Emby/Jellyfin/Plex')
7778
media_server_group.add_argument(
7879
'--import-series', '--load-series',
7980
type=str,
@@ -101,7 +102,15 @@
101102
nargs=3,
102103
default=SUPPRESS,
103104
metavar=('LIBRARY', 'NAME', 'YEAR'),
104-
help='Remove the cards for the given series within Emby/Plex')
105+
help='Remove the cards for the given series within Emby/Jellyfin/Plex')
106+
media_server_group.add_argument(
107+
'--id', '--series-id',
108+
type=str,
109+
nargs=2,
110+
default=[],
111+
action='append',
112+
metavar=('ID_TYPE', 'ID'),
113+
help='Specify database IDs of a series for importing/reloading cards')
105114

106115
# Argument group for Sonarr
107116
sonarr_group = parser.add_argument_group('Sonarr')
@@ -183,21 +192,23 @@
183192
print(file_handle.read())
184193

185194

186-
# Execute Emby/Plex options
187-
if (hasattr(args, 'import_cards')
188-
or hasattr(args, 'revert_series')) and (pp.use_plex or pp.use_emby):
195+
# Execute Media Server options
196+
if ((hasattr(args, 'import_cards') or hasattr(args, 'revert_series'))
197+
and any((pp.use_emby, pp.use_jellyfin, pp.use_plex))):
189198
# Temporary classes
190199
@dataclass
191200
class Episode:
192201
destination: Path
193202
episode_info: EpisodeInfo
194203
spoil_type: str
195204

196-
# Create Emby/PlexInterface
197-
if args.media_server == 'plex':
198-
media_interface = PlexInterface(**pp.plex_interface_kwargs)
199-
else:
205+
# Create MediaServer Interface
206+
if args.media_server == 'emby':
200207
media_interface = EmbyInterface(**pp.emby_interface_kwargs)
208+
elif args.media_server == 'jellyfin':
209+
media_interface = JellyfinInterface(**pp.jellyfin_interface_kwargs)
210+
else:
211+
media_interface = PlexInterface(**pp.plex_interface_kwargs)
201212

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

231+
# Get series ID's if provided
232+
if args.id:
233+
for id_type, id_ in args.id:
234+
try:
235+
getattr(series_info, f'set_{id_type}_id')(id_)
236+
except Exception as e:
237+
log.error(f'Unrecognized ID type "{id_type}" - {e}')
238+
220239
# Forget cards associated with this series
221240
media_interface.remove_records(library, series_info)
222241

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

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

242-
# Load images into Emby/Plex
243-
media_interface.set_title_cards(library, series_info,episode_map)
261+
# Load images into server
262+
media_interface.set_title_cards(library, series_info, episode_map)
244263

245-
if hasattr(args, 'forget_cards') and (pp.use_plex or pp.use_emby):
246-
# Create interface and remove records for indicated series+library
264+
# Create interface and remove records for indicated series+library
265+
if (hasattr(args, 'forget_cards')
266+
and any((pp.use_emby, pp.use_jellyfin, pp.use_plex))):
247267
series_info = SeriesInfo(args.forget_cards[1], args.forget_cards[2])
248-
249-
# Create Emby/PlexInterface
250268
if args.media_server == 'emby':
251269
EmbyInterface(**pp.emby_interface_kwargs).remove_records(
252270
args.forget_cards[0], series_info,
253271
)
272+
elif args.media_server == 'jellyfin':
273+
JellyfinInterface(**pp.jellyfin_interface_kwargs).remove_records(
274+
args.forget_cards[0], series_info,
275+
)
254276
else:
255277
PlexInterface(**pp.plex_interface_kwargs).remove_records(
256278
args.forget_cards[0], series_info,

modules/BaseCardType.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import abstractmethod
2-
from typing import Any
2+
from typing import Any, Optional
33

44
from titlecase import titlecase
55

@@ -98,7 +98,10 @@ def USES_SEASON_TITLE(self) -> bool:
9898

9999

100100
@abstractmethod
101-
def __init__(self, blur: bool = False, grayscale: bool = False) -> None:
101+
def __init__(self,
102+
blur: bool = False,
103+
grayscale: bool = False, *,
104+
preferences: Optional['Preferences'] = None) -> None:
102105
"""
103106
Construct a new CardType. Must call super().__init__() to
104107
initialize the parent ImageMaker class (for PreferenceParser and
@@ -111,7 +114,7 @@ def __init__(self, blur: bool = False, grayscale: bool = False) -> None:
111114
"""
112115

113116
# Initialize parent ImageMaker
114-
super().__init__()
117+
super().__init__(preferences=preferences)
115118

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

133136

134137
@staticmethod
135-
def modify_extras(extras: dict[str, Any], custom_font: bool,
138+
def modify_extras(
139+
extras: dict[str, Any],
140+
custom_font: bool,
136141
custom_season_titles: bool) -> None:
137142
"""
138143
Modify the given extras base on whether font or season titles
@@ -226,6 +231,22 @@ def style(self) -> ImageMagickCommands:
226231
f'-set colorspace sRGB' if self.grayscale else '',
227232
]
228233

234+
235+
@property
236+
def resize_output(self) -> ImageMagickCommands:
237+
"""
238+
ImageMagick commands to resize the card to the global card
239+
dimensions.
240+
241+
Returns:
242+
List of ImageMagick commands.
243+
"""
244+
245+
return [
246+
f'-resize "{self.preferences.card_dimensions}"',
247+
f'-extent "{self.preferences.card_dimensions}"',
248+
]
249+
229250

230251
@abstractmethod
231252
def create(self) -> None:

modules/BaseSummary.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88

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

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

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

6768
def _select_images(self, maximum_images: int=9) -> bool:
6869
"""
69-
Select the images that are to be incorporated into the show summary.
70-
This updates the object's inputs and number_rows attributes.
70+
Select the images that are to be incorporated into the show
71+
summary. This updates the object's inputs and number_rows
72+
attributes.
7173
7274
Args:
73-
maximum_images: maximum number of images to select. Defaults to 9.
75+
maximum_images: maximum number of images to select.
7476
7577
Returns:
7678
Whether the ShowSummary should/can be created.
@@ -119,9 +121,9 @@ def _select_images(self, maximum_images: int=9) -> bool:
119121

120122
def _create_created_by(self, created_by: str) -> Path:
121123
"""
122-
Create a custom "Created by" tag image. This image is formatted like:
123-
"Created by {input} with {logo} TitleCardMaker". The image is exactly
124-
the correct size (i.e. fit to width of text).
124+
Create a custom "Created by" tag image. This image is formatted
125+
like: "Created by {input} with {logo} TitleCardMaker". The image
126+
is exactly the correct size (i.e. fit to width of text).
125127
126128
Returns:
127129
Path to the created image.

modules/CleanPath.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
class CleanPath(_Path_):
77
"""
8-
Subclass of Path that is more OS-agnostic and implements methods of cleaning
9-
directories and filenames of bad characters. For example:
8+
Subclass of Path that is more OS-agnostic and implements methods of
9+
cleaning directories and filenames of bad characters. For example:
1010
1111
>>> p = CleanPath('./some_file: 123.jpg')
1212
>>> print(p)
@@ -34,14 +34,15 @@ class CleanPath(_Path_):
3434

3535
def finalize(self) -> 'CleanPath':
3636
"""
37-
Finalize this path by properly resolving if absolute or relative.
37+
Finalize this path by properly resolving if absolute or
38+
relative.
3839
3940
Returns:
4041
This object as a fully resolved path.
4142
4243
Raises:
43-
OSError if the resolution fails (likely due to an unresolvable
44-
filename).
44+
OSError if the resolution fails (likely due to an
45+
unresolvable filename).
4546
"""
4647

4748
return (CleanPath.cwd() / self).resolve()
@@ -53,7 +54,8 @@ def sanitize_name(filename: str) -> str:
5354
Sanitize the given filename to remove any illegal characters.
5455
5556
Args:
56-
filename: Filename (string) to remove illegal characters from.
57+
filename: Filename (string) to remove illegal characters
58+
from.
5759
5860
Returns:
5961
Sanitized filename.
@@ -73,8 +75,8 @@ def _sanitize_parts(path: 'CleanPath') -> 'CleanPath':
7375
path: Path to sanitize.
7476
7577
Returns:
76-
Sanitized path. This is a reconstructed CleanPath object with each
77-
folder (or part), except the root/drive, sanitized.
78+
Sanitized path. This is a reconstructed CleanPath object
79+
with each folder (or part), except the root/drive, sanitized.
7880
"""
7981

8082
return CleanPath(
@@ -88,8 +90,8 @@ def sanitize(self) -> 'CleanPath':
8890
Sanitize all parts (except the root) of this objects path.
8991
9092
Returns:
91-
CleanPath object instantiated with sanitized names of each part of
92-
this object's path.
93+
CleanPath object instantiated with sanitized names of each
94+
part of this object's path.
9395
"""
9496

9597
# Attempt to resolve immediately

modules/CollectionPosterMaker.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ class CollectionPosterMaker(ImageMaker):
2020
__GRADIENT = REF_DIRECTORY.parent / 'genre' / 'genre_gradient.png'
2121

2222

23-
def __init__(self, source: Path, output: Path, title: str, font: Path=FONT,
24-
font_color: str=FONT_COLOR, font_size: float=1.0,
25-
omit_collection: bool=False, borderless: bool=False,
26-
omit_gradient: bool=False) -> None:
23+
def __init__(self,
24+
source: Path,
25+
output: Path,
26+
title: str,
27+
font: Path = FONT,
28+
font_color: str = FONT_COLOR,
29+
font_size: float = 1.0,
30+
omit_collection: bool = False,
31+
borderless: bool = False,
32+
omit_gradient: bool = False) -> None:
2733
"""
2834
Construct a new instance of a CollectionPosterMaker.
2935

modules/DatabaseInfoContainer.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
class DatabaseInfoContainer(ABC):
77
"""
8-
This class describes an abstract base class for all Info objects containing
9-
database ID's. This provides common methods for checking whether an object
10-
has a specific ID, as well as updating an ID within an objct.
8+
This class describes an abstract base class for all Info objects
9+
containing database ID's. This provides common methods for checking
10+
whether an object has a specific ID, as well as updating an ID
11+
within an objct.
1112
"""
1213

1314
def _update_attribute(self, attribute: str, value: Any,
@@ -38,8 +39,8 @@ def has_id(self, id_: str) -> bool:
3839
id_: ID being checked
3940
4041
Returns:
41-
True if the given ID is defined (i.e. not None) for this object.
42-
False otherwise.
42+
True if the given ID is defined (i.e. not None) for this
43+
object. False otherwise.
4344
"""
4445

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

6061
return all(getattr(self, id_) is not None for id_ in ids)

modules/Debug.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ def format(self, record):
112112

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

120120
# Get existing handler's log level, then delete

0 commit comments

Comments
 (0)