diff --git a/main.py b/main.py index aa1881a3..6323bd5e 100755 --- a/main.py +++ b/main.py @@ -41,12 +41,13 @@ def runtime(arg: str) -> dict: def frequency(arg: str) -> dict: try: - interval, unit = match(r'(\d+)(m|h|d|w)', arg).groups() + interval, unit = match(r'(\d+)(s|m|h|d|w)', arg).groups() interval, unit = int(interval), unit.lower() - assert interval > 0 and unit in ('m', 'h', 'd', 'w') + assert interval > 0 and unit in ('s', 'm', 'h', 'd', 'w') return { 'interval': interval, - 'unit': {'m':'minutes', 'h':'hours', 'd':'days', 'w':'weeks'}[unit] + 'unit': {'s': 'seconds', 'm':'minutes', 'h':'hours', 'd':'days', + 'w':'weeks'}[unit], } except Exception: raise ArgumentTypeError(f'Invalid frequency, specify as FREQUENCY[unit]' @@ -78,9 +79,9 @@ def frequency(arg: str) -> dict: type=frequency, default=environ.get(ENV_FREQUENCY, DEFAULT_FREQUENCY), metavar='FREQUENCY[unit]', - help=f'How often to run the TitleCardMaker. Units can be m/h/d/w for ' - f'minutes/hours/days/weeks. Environment variable {ENV_FREQUENCY}. ' - f'Defaults to "{DEFAULT_FREQUENCY}"') + help=f'How often to run the TitleCardMaker. Units can be s/m/h/d/w for ' + f'seconds/minutes/hours/days/weeks. Environment variable ' + f'{ENV_FREQUENCY}. Defaults to "{DEFAULT_FREQUENCY}"') parser.add_argument( '-m', '--missing', '--missing-file', type=Path, diff --git a/mini_maker.py b/mini_maker.py index 6d34849d..1c577a66 100755 --- a/mini_maker.py +++ b/mini_maker.py @@ -297,8 +297,8 @@ class Show: season_text=args.season_text, font=args.season_font, font_color=args.season_font_color, - font_size=args.season_font_size, - font_kerning=args.season_font_kerning, + font_size=float(args.season_font_size[:-1])/100.0, + font_kerning=float(args.season_font_kerning[:-1])/100.0, ).create() \ No newline at end of file diff --git a/modules/AnimeTitleCard.py b/modules/AnimeTitleCard.py index 0c1282e8..d099e3ee 100755 --- a/modules/AnimeTitleCard.py +++ b/modules/AnimeTitleCard.py @@ -17,24 +17,17 @@ class AnimeTitleCard(CardType): """Characteristics for title splitting by this class""" TITLE_CHARACTERISTICS = { 'max_line_width': 25, # Character count to begin splitting titles - 'max_line_count': 3, # Maximum number of lines a title can take up + 'max_line_count': 4, # Maximum number of lines a title can take up 'top_heavy': False, # This class uses bottom heavy titling } """How to name archive directories for this type of card""" ARCHIVE_NAME = 'Anime Style' - """Path to the font to use for the episode title""" + """Characteristics of the default title font""" TITLE_FONT = str((REF_DIRECTORY / 'Flanker Griffo.otf').resolve()) DEFAULT_FONT_CASE = 'source' - - """Color to use for the episode title""" TITLE_COLOR = 'white' - - """Color of the episode/episode number text""" - EPISODE_TEXT_COLOR = 'white' - - """Default characters to replace in the generic font""" FONT_REPLACEMENTS = {} """Whether this class uses season titles for the purpose of archives""" @@ -46,12 +39,8 @@ class AnimeTitleCard(CardType): """Path to the font to use for the kanji font""" KANJI_FONT = REF_DIRECTORY / 'hiragino-mincho-w3.ttc' - """Path to the font to use for the series count text""" + """Font characteristics for the series count text""" SERIES_COUNT_FONT = REF_DIRECTORY / 'Avenir.ttc' - - """Character used to join season and episode text (with spacing)""" - SERIES_COUNT_JOIN_CHARACTER = '·' - SERIES_COUNT_TEXT_COLOR = '#CFCFCF' """Paths to intermediate files that are deleted after the card is created""" @@ -59,11 +48,14 @@ class AnimeTitleCard(CardType): __SOURCE_WITH_GRADIENT = CardType.TEMP_DIR / 'source_with_gradient.png' __GRADIENT_WITH_TITLE = CardType.TEMP_DIR / 'gradient_with_title.png' + __slots__ = ('source_file', 'output_file', 'title', 'kanji', 'use_kanji', + 'require_kanji', 'season_text', 'episode_text', 'hide_season', + 'separator', 'blur', 'font_size') def __init__(self, source: Path, output_file: Path, title: str, season_text: str, episode_text: str, hide_season: bool, - kanji: str=None, require_kanji: bool=False, blur: bool=False, - font_size: float=1.0, *args, **kwargs) -> None: + kanji: str=None, require_kanji: bool=False, separator: str='·', + blur: bool=False, font_size: float=1.0, *args, **kwargs)->None: """ Constructs a new instance. @@ -77,6 +69,8 @@ def __init__(self, source: Path, output_file: Path, title: str, :param kanji: Kanji text to place above the episode title on this card. :param require_kanji: Whether to require kanji for this card. + :param separator: Character to use to separate season and + episode text. :param blur: Whether to blur the source image. :param font_size: Scalar to apply to the title font size. :param args and kwargs: Unused arguments to permit generalized @@ -102,6 +96,7 @@ def __init__(self, source: Path, output_file: Path, title: str, self.season_text = self.image_magick.escape_chars(season_text.upper()) self.episode_text = self.image_magick.escape_chars(episode_text.upper()) self.hide_season = hide_season + self.separator = separator self.blur = blur # Font customizations @@ -323,7 +318,7 @@ def __add_series_count_text(self, titled_image: Path) -> Path: """ # Construct season text - season_text = f'{self.season_text} {self.SERIES_COUNT_JOIN_CHARACTER} ' + season_text = f'{self.season_text} {self.separator} ' # Command list used by both the metric and season text command season_text_command_list = [ diff --git a/modules/LogoTitleCard.py b/modules/LogoTitleCard.py index 35d6b970..e92ff0a3 100755 --- a/modules/LogoTitleCard.py +++ b/modules/LogoTitleCard.py @@ -20,11 +20,9 @@ class LogoTitleCard(CardType): 'top_heavy': False, # This class uses bottom heavy titling } - """Default font and text color for episode title text""" + """Characteristics of the default title font""" TITLE_FONT = str((REF_DIRECTORY / 'Sequel-Neue.otf').resolve()) TITLE_COLOR = '#EBEBEB' - - """Default characters to replace in the generic font""" FONT_REPLACEMENTS = {'[': '(', ']': ')', '(': '[', ')': ']', '―': '-', '…': '...'} @@ -50,17 +48,17 @@ class LogoTitleCard(CardType): __slots__ = ('source_file', 'output_file', 'title', 'season_text', 'episode_text', 'font', 'font_size', 'title_color', - 'hide_season', 'blur', 'vertical_shift', 'interline_spacing', - 'kerning', 'stroke_width') + 'hide_season', 'separator', 'blur', 'vertical_shift', + 'interline_spacing', 'kerning', 'stroke_width') def __init__(self, output_file: Path, title: str, season_text: str, episode_text: str, font: str, font_size: float, - title_color: str, hide_season: bool, blur: bool=False, - vertical_shift: int=0, interline_spacing: int=0, - kerning: float=1.0, stroke_width: float=1.0, - logo: str=None, background: str='#000000', - *args, **kwargs) -> None: + title_color: str, hide_season: bool, separator: str='•', + blur: bool=False, vertical_shift: int=0, + interline_spacing: int=0, kerning: float=1.0, + stroke_width: float=1.0, logo: str=None, + background: str='#000000', *args, **kwargs) -> None: """ Initialize the TitleCardMaker object. This primarily just stores instance variables for later use in `create()`. If the provided font @@ -76,6 +74,8 @@ def __init__(self, output_file: Path, title: str, season_text: str, :param title_color: Color to use for the episode title. :param hide_season: Whether to omit the season text (and joining character) from the title card completely. + :param separator: Character to use to separate season and + episode text. :param blur: Whether to blur the source image. :param vertical_shift: Pixels to adjust title vertical shift by. :param interline_spacing: Pixels to adjust title interline spacing by. @@ -99,16 +99,20 @@ def __init__(self, output_file: Path, title: str, season_text: str, self.season_text = self.image_magick.escape_chars(season_text.upper()) self.episode_text = self.image_magick.escape_chars(episode_text.upper()) + # Font attributes self.font = font self.font_size = font_size self.title_color = title_color self.hide_season = hide_season - self.blur = blur self.vertical_shift = vertical_shift self.interline_spacing = interline_spacing self.kerning = kerning self.stroke_width = stroke_width + + # Miscellaneous attributes + self.blur = blur self.background = background + self.separator = separator def __title_text_global_effects(self) -> list: @@ -318,7 +322,7 @@ def _get_series_count_text_dimensions(self) -> dict: f'-font "{self.EPISODE_COUNT_FONT.resolve()}"', f'-gravity center', *self.__series_count_text_effects(), - f'-annotate +0+689.5 "• "', + f'-annotate +0+689.5 "{self.separator} "', f'-gravity west', *self.__series_count_text_effects(), f'-annotate +1640+697.2 "{self.episode_text}"', @@ -367,9 +371,9 @@ def _create_series_count_text_image(self, width: float, width1: float, f'-annotate +0+{height-25} "{self.season_text} "', f'-font "{self.EPISODE_COUNT_FONT.resolve()}"', *self.__series_count_text_black_stroke(), - f'-annotate +{width1}+{height-25-6.5} "•"', + f'-annotate +{width1}+{height-25-6.5} "{self.separator}"', *self.__series_count_text_effects(), - f'-annotate +{width1}+{height-25-6.5} "•"', + f'-annotate +{width1}+{height-25-6.5} "{self.separator}"', *self.__series_count_text_black_stroke(), f'-annotate +{width1+width2}+{height-25} "{self.episode_text}"', *self.__series_count_text_effects(), diff --git a/modules/Manager.py b/modules/Manager.py index d541b487..5eee5a1c 100755 --- a/modules/Manager.py +++ b/modules/Manager.py @@ -26,10 +26,6 @@ def __init__(self) -> None: # Get the global preferences self.preferences = global_objects.pp - # Establish directory bases - self.source_base = self.preferences.source_directory - self.archive_base = self.preferences.archive_directory - # Optionally assign PlexInterface self.plex_interface = None if global_objects.pp.use_plex: diff --git a/modules/PlexInterface.py b/modules/PlexInterface.py index 751880cd..22327671 100755 --- a/modules/PlexInterface.py +++ b/modules/PlexInterface.py @@ -269,7 +269,7 @@ def update_watched_statuses(self, library_name: str, ep_key = f'{plex_episode.parentIndex}-{plex_episode.index}' if not (episode := episode_map.get(ep_key)): continue - + # Set Episode watched/spoil statuses episode.update_statuses(plex_episode.isWatched, watched_style, unwatched_style) diff --git a/modules/Profile.py b/modules/Profile.py index d86c45dc..35700eca 100755 --- a/modules/Profile.py +++ b/modules/Profile.py @@ -151,7 +151,10 @@ def get_episode_text(self, episode: 'Episode') -> str: if (self.__use_custom_seasons and '{abs_' in self.episode_text_format and episode.episode_info.abs_number is None): log.warning(f'Episode text formatting uses absolute episode ' - f'number, but {episode} has no absolute number') + f'number, but {episode} has no absolute number - ' + f'using episode number instead') + new_fmt = self.episode_text_format.replace('{abs_', '{episode_') + self.episode_text_format = new_fmt # Format MultiEpisode episode text if isinstance(episode, MultiEpisode): @@ -291,7 +294,8 @@ def convert_title(self, title_text: str, """ # Modify the title if it contains the episode text format - if self.__use_custom_seasons: + if (self.__use_custom_seasons and + self.episode_text_format not in ('{abs_number}','{episode_number}')): # Attempt to remove text that matches the episode text format string title_text = self.__remove_episode_text_format(title_text) diff --git a/modules/RomanNumeralTitleCard.py b/modules/RomanNumeralTitleCard.py new file mode 100755 index 00000000..54188b5d --- /dev/null +++ b/modules/RomanNumeralTitleCard.py @@ -0,0 +1,217 @@ +from pathlib import Path + +from modules.CardType import CardType +from modules.Debug import log + +class RomanNumeralTitleCard(CardType): + """ + This class defines a type of CardType that produces un-imaged title cards + with roman numeral text behind the central title. The style is inspired + from the official Devilman Crybaby title cards. + """ + + """Directory where all reference files used by this card are stored""" + REF_DIRECTORY = Path(__file__).parent / 'ref' / 'roman' + + """Characteristics for title splitting by this class""" + TITLE_CHARACTERISTICS = { + 'max_line_width': 26, # Character count to begin splitting titles + 'max_line_count': 5, # Maximum number of lines a title can take up + 'top_heavy': True, # This class uses bottom heavy titling + } + + """Default font and text color for episode title text""" + TITLE_FONT = str((REF_DIRECTORY / 'flanker-griffo.otf').resolve()) + TITLE_COLOR = 'white' + + """Default characters to replace in the generic font""" + FONT_REPLACEMENTS = {} + + """Default episode text format for this class""" + EPISODE_TEXT_FORMAT = '{episode_number}' + + """Whether this CardType uses season titles for archival purposes""" + USES_SEASON_TITLE = False + + """Whether this CardType uses unique source images""" + USES_UNIQUE_SOURCES = False + + """Standard class has standard archive name""" + ARCHIVE_NAME = 'Roman Numeral Style' + + """Blur profile for this card is 1/3 the radius of the standard blur""" + BLUR_PROFILE = '0x30' + + """Default fonts and color for series count text""" + ROMAN_NUMERAL_FONT = REF_DIRECTORY / 'sinete-regular.otf' + ROMAN_NUMERAL_TEXT_COLOR = '#AE2317' + + __slots__ = ('output_file', 'title', 'title_color', 'background', 'blur', + 'roman_numeral_color', 'roman_numeral', '__roman_text_scalar') + + def __init__(self, output_file: Path, title: str, episode_text: str, + title_color: str, episode_number: int=1, blur: bool=False, + background: str='black', + roman_numeral_color: str=ROMAN_NUMERAL_TEXT_COLOR, + *args, **kwargs) -> None: + """ + Constructs a new instance. + + :param output_file: Output file. + :param title: Episode title. + :param episode_text: The episode text to parse the roman + numeral from. + :param episode_number: Episode number for the roman + numerals. + :param title_color: Color to use for the episode title. + :param background: Color for the background. + :param roman_numeral_color: Color for the roman numerals. + :param blur: Whether to blur the source image. + :param args and kwargs: Unused arguments. + """ + + # Initialize the parent class - this sets up an ImageMagickInterface + super().__init__() + + # Store object attributes + self.output_file = output_file + self.title = self.image_magick.escape_chars(title) + self.title_color = title_color + self.background = background + self.roman_numeral_color = roman_numeral_color + self.blur = blur + + # Try and parse roman digit from the episode text, if cannot be done, + # just use actual episode number + digit = int(episode_text) if episode_text.isdigit() else episode_number + self.__assign_roman_numeral(digit) + + + def __assign_roman_numeral(self, number: int) -> None: + """ + Convert the given number to a roman numeral, update the scalar and text + attributes of this object. + + :param number: The number to become the roman numeral. + """ + + # Index-sorted places -> roman numerals + m_text = ['', 'M', 'MM', 'MMM'] + c_text = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'] + x_text = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'] + i_text = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'] + + # Get each places' roman numeral + thousands = m_text[number // 1000] + hundreds = c_text[(number % 1000) // 100] + tens = x_text[(number % 100) // 10] + ones = i_text[number % 10] + + numeral = (thousands + hundreds + tens + ones).strip() + + # Split roman numerals that are longer than 6 chars into two lines + if len(numeral) >= 5: + roman_text = [numeral[:len(numeral)//2], numeral[len(numeral)//2:]] + else: + roman_text = [numeral] + + # Update scalar for this text + self.__assign_roman_scalar(roman_text) + + # Assign combined roman numeral text + self.roman_numeral = '\n'.join(roman_text) + + + def __assign_roman_scalar(self, roman_text: list[str]) -> None: + """ + Assign the roman text scalar for this text based on the widest line of + the given roman numeral text. + + :param roman_text: List of strings, where each entry is a new line + in the roman numeral string. + """ + + # Width of each roman numeral + widths = { + 'I': 364, 'V': 782, 'X': 727, 'L': 599, + 'C': 779, 'D': 856, 'M': 1004 + } + + # Get max width of all lines + max_width = max(sum(widths[ch] for ch in line) for line in roman_text) + + # Get width of output title card for comparison + card_width = int(self.TITLE_CARD_SIZE.split('x')[0]) + + # Scale roman numeral text if line width is larger than card (+margin) + if max_width > (card_width - 100): + self.__roman_text_scalar = (card_width - 100) / max_width + else: + self.__roman_text_scalar = 1.0 + + + @staticmethod + def is_custom_font(font: 'Font') -> bool: + """ + Determine whether the given font characteristics constitute a default + or custom font. + + :param font: The Font being evaluated. + + :returns: False, as custom fonts aren't used. + """ + + return False + + + @staticmethod + def is_custom_season_titles(custom_episode_map: bool, + episode_text_format: str) -> bool: + """ + Determine whether the given attributes constitute custom or generic + season titles. + + :param custom_episode_map: Whether the EpisodeMap was + customized. + :param episode_text_format: The episode text format in use. + + :returns: False, as season titles aren't used. + """ + + return False + + + def create(self): + """ + Make the necessary ImageMagick and system calls to create this object's + defined title card. + """ + + # Scale font size and interline spacing of roman text + font_size = int(1250 * self.__roman_text_scalar) + interline_spacing = int(-700 * self.__roman_text_scalar) + + # Generate command to create card + command = ' '.join([ + f'convert', + f'-size "{self.TITLE_CARD_SIZE}"', + f'xc:"{self.background}"', + f'-font "{self.ROMAN_NUMERAL_FONT.resolve()}"', + f'-fill "{self.roman_numeral_color}"', + f'-pointsize {font_size}', + f'-gravity center', + f'-interline-spacing {interline_spacing}', + f'-annotate +0-175 "{self.roman_numeral}"', # Should be +0+0.. + f'-blur {self.BLUR_PROFILE}' if self.blur else '', + f'-font "{self.TITLE_FONT}"', + f'-pointsize 150', + f'-interword-spacing 40', + f'-interline-spacing 0', + f'-fill "{self.title_color}"', + f'-annotate +0+0 "{self.title}"', + f'"{self.output_file.resolve()}"', + ]) + + # Create the card + self.image_magick.run(command) + diff --git a/modules/SeasonPoster.py b/modules/SeasonPoster.py index ddc2e9d8..acad523b 100755 --- a/modules/SeasonPoster.py +++ b/modules/SeasonPoster.py @@ -9,6 +9,9 @@ class SeasonPoster(ImageMaker): Season posters take images, add a logo and season title. """ + """Default size of all season posters""" + SEASON_POSTER_SIZE = '2000x3000' + """Directory where all reference files used by this card are stored""" REF_DIRECTORY = Path(__file__).parent / 'ref' /'season_poster' @@ -78,8 +81,8 @@ def create(self) -> None: f'-density 300', f'"{self.source.resolve()}"', # Resize input image f'-gravity center', - f'-resize "2000x3000^"', # Force into 2000x3000 - f'-extent "2000x3000"', + f'-resize "{self.SEASON_POSTER_SIZE}^"', # Force into 2000x3000 + f'-extent "{self.SEASON_POSTER_SIZE}"', f'"{self.GRADIENT_OVERLAY.resolve()}"', # Apply gradient f'-compose Darken', # Darken mode f'-composite', # Merge images diff --git a/modules/Show.py b/modules/Show.py index a965c63e..46b41d21 100755 --- a/modules/Show.py +++ b/modules/Show.py @@ -679,9 +679,9 @@ def select_source_images(self, plex_interface: PlexInterface=None, if image_url is not None: WebInterface.download_image(image_url, episode.source) log.debug(f'Downloaded {episode.source.name} for {self} ' - f'from {source_interface.title()}') + f'from {source_interface}') break - + # Query TMDb for the backdrop if one does not exist and is needed if (download_backdrop and tmdb_interface and self.tmdb_sync and not self.backdrop.exists()): diff --git a/modules/StandardTitleCard.py b/modules/StandardTitleCard.py index ba778b95..861cbdef 100755 --- a/modules/StandardTitleCard.py +++ b/modules/StandardTitleCard.py @@ -22,11 +22,9 @@ class StandardTitleCard(CardType): 'top_heavy': False, # This class uses bottom heavy titling } - """Default font and text color for episode title text""" + """Characteristics of the default title font""" TITLE_FONT = str((REF_DIRECTORY / 'Sequel-Neue.otf').resolve()) TITLE_COLOR = '#EBEBEB' - - """Default characters to replace in the generic font""" FONT_REPLACEMENTS = {'[': '(', ']': ')', '(': '[', ')': ']', '―': '-', '…': '...'} @@ -51,14 +49,14 @@ class StandardTitleCard(CardType): __slots__ = ('source_file', 'output_file', 'title', 'season_text', 'episode_text', 'font', 'font_size', 'title_color', - 'hide_season', 'blur', 'vertical_shift', 'interline_spacing', - 'kerning', 'stroke_width') + 'hide_season', 'separator', 'blur', 'vertical_shift', + 'interline_spacing', 'kerning', 'stroke_width') def __init__(self, source: Path, output_file: Path, title: str, season_text: str, episode_text: str, font: str, font_size: float, title_color: str, hide_season: bool, - blur: bool=False, vertical_shift: int=0, + separator: str='•', blur: bool=False, vertical_shift: int=0, interline_spacing: int=0, kerning: float=1.0, stroke_width: float=1.0, *args, **kwargs) -> None: """ @@ -77,6 +75,8 @@ def __init__(self, source: Path, output_file: Path, title: str, :param title_color: Color to use for the episode title. :param hide_season: Whether to omit the season text (and joining character) from the title card completely. + :param separator: Character to use to separate season and + episode text. :param blur: Whether to blur the source image. :param vertical_shift: Pixels to adjust title vertical shift by. :param interline_spacing: Pixels to adjust title interline spacing by. @@ -102,6 +102,7 @@ def __init__(self, source: Path, output_file: Path, title: str, self.font_size = font_size self.title_color = title_color self.hide_season = hide_season + self.separator = separator self.blur = blur self.vertical_shift = vertical_shift self.interline_spacing = interline_spacing @@ -286,7 +287,7 @@ def _get_series_count_text_dimensions(self) -> dict: f'-font "{self.EPISODE_COUNT_FONT.resolve()}"', f'-gravity center', *self.__series_count_text_effects(), - f'-annotate +0+689.5 "• "', + f'-annotate +0+689.5 "{self.separator} "', f'-gravity west', *self.__series_count_text_effects(), f'-annotate +1640+697.2 "{self.episode_text}"', @@ -335,9 +336,9 @@ def _create_series_count_text_image(self, width: float, width1: float, f'-annotate +0+{height-25} "{self.season_text} "', f'-font "{self.EPISODE_COUNT_FONT.resolve()}"', *self.__series_count_text_black_stroke(), - f'-annotate +{width1}+{height-25-6.5} "•"', + f'-annotate +{width1}+{height-25-6.5} "{self.separator}"', *self.__series_count_text_effects(), - f'-annotate +{width1}+{height-25-6.5} "•"', + f'-annotate +{width1}+{height-25-6.5} "{self.separator}"', *self.__series_count_text_black_stroke(), f'-annotate +{width1+width2}+{height-25} "{self.episode_text}"', *self.__series_count_text_effects(), @@ -392,7 +393,6 @@ def is_custom_font(font: 'Font') -> bool: return ((font.file != StandardTitleCard.TITLE_FONT) or (font.size != 1.0) or (font.color != StandardTitleCard.TITLE_COLOR) - or (font.replacements != StandardTitleCard.FONT_REPLACEMENTS) or (font.vertical_shift != 0) or (font.interline_spacing != 0) or (font.kerning != 1.0) diff --git a/modules/Template.py b/modules/Template.py index a649cf38..a191a478 100755 --- a/modules/Template.py +++ b/modules/Template.py @@ -1,3 +1,4 @@ +from copy import deepcopy from re import findall from modules.Debug import log @@ -5,8 +6,8 @@ class Template: """ This class describes a template. A Template is a fallback YAML object that - can be "filled in" with values, or just outright contain them. Variable - data is encoded in the form <<{key}>>. When applied to some series YAML + can be "filled in" with values, or just outright contain them. Variable data + is encoded in the form <<{key}>>. When applied to some series YAML dictionary, the template'd YAML is applied to the series, unless both have instances of the data, in which the series data takes priority. """ @@ -139,7 +140,7 @@ def apply_to_series(self, series_name: str, series_yaml: dict) -> bool: return False # Take given template values, fill in template object - modified_template = self.__template.copy() + modified_template = deepcopy(self.__template) for key, value in series_yaml['template'].items(): self.__apply_value_to_key(modified_template, key, value) @@ -151,4 +152,3 @@ def apply_to_series(self, series_name: str, series_yaml: dict) -> bool: return True - diff --git a/modules/Title.py b/modules/Title.py index d0d78190..009bfcf2 100755 --- a/modules/Title.py +++ b/modules/Title.py @@ -25,8 +25,10 @@ class Title: PARTLESS_REGEX = ( # Match for "title" (digit) or "title" (Part (digit)) re_compile(r'^(.*?)\s*\((?:Part\s*)?\d+\)', IGNORECASE), - # Match for "title" (digit) or "title" (Part (digit)) - re_compile(r'^(.*?)(?::|\s*-|,)\s+Part\s*\d*', IGNORECASE), + # Match for "title" (optional separator) Part (word) + re_compile(r'^(.*?)(?::|\s*-|,)?\s+Part\s+[a-zA-Z0-9]+', IGNORECASE), + # Match for "title" (Part (word)) + re_compile(r'^(.*?)\s*\(Part\s*[a-zA-Z0-9]+\)', IGNORECASE), ) __slots__ = ('full_title', '__title_lines', '__manually_specified', diff --git a/modules/TitleCard.py b/modules/TitleCard.py index 8c8ed003..92567252 100755 --- a/modules/TitleCard.py +++ b/modules/TitleCard.py @@ -6,6 +6,7 @@ # Default CardType classes from modules.AnimeTitleCard import AnimeTitleCard from modules.LogoTitleCard import LogoTitleCard +from modules.RomanNumeralTitleCard import RomanNumeralTitleCard from modules.StandardTitleCard import StandardTitleCard from modules.StarWarsTitleCard import StarWarsTitleCard from modules.TextlessTitleCard import TextlessTitleCard @@ -38,6 +39,8 @@ class TitleCard: 'textless': TextlessTitleCard, 'logo': LogoTitleCard, 'reality tv': LogoTitleCard, + 'roman': RomanNumeralTitleCard, + 'roman numeral': RomanNumeralTitleCard, } """Mapping of illegal filename characters and their replacements""" diff --git a/modules/ref/roman/flanker-griffo.otf b/modules/ref/roman/flanker-griffo.otf new file mode 100755 index 00000000..f75f5052 Binary files /dev/null and b/modules/ref/roman/flanker-griffo.otf differ diff --git a/modules/ref/roman/sinete-regular.otf b/modules/ref/roman/sinete-regular.otf new file mode 100755 index 00000000..139b37a8 Binary files /dev/null and b/modules/ref/roman/sinete-regular.otf differ