diff --git a/modules/cards/CutoutTitleCard.py b/modules/cards/CutoutTitleCard.py index 60e6accd..bdf199aa 100755 --- a/modules/cards/CutoutTitleCard.py +++ b/modules/cards/CutoutTitleCard.py @@ -79,7 +79,7 @@ class CutoutTitleCard(BaseCardType): identifier='cutout_vertical_shift', description='Additional vertical shift to apply to the cutout', tooltip=( - 'How much to offset the cutout text by. Default is ' + 'Number between -1800 and 1800. Default is ' '0. Unit is pixels.' ), default=0, @@ -129,9 +129,10 @@ class CutoutTitleCard(BaseCardType): 'font_interword_spacing', 'font_kerning', 'font_size', 'font_vertical_shift', 'overlay_color', 'blur_edges', 'number_blur_profile', 'overlay_transparency', 'cutout_vertical_shift', + '__text_mask', ) - def __init__(self, + def __init__(self, *, source_file: Path, card_file: Path, title_text: str, @@ -184,6 +185,9 @@ def __init__(self, self.overlay_color = overlay_color self.overlay_transparency = overlay_transparency + # Implementation details + self.__text_mask: Optional[Path] = None + def _format_episode_text(self, episode_text: str) -> str: """ @@ -234,6 +238,67 @@ def title_text_commands(self) -> ImageMagickCommands: ] + @property + def episode_text_mask(self) -> ImageMagickCommands: + """ + Subcommands for adding the episode text mask used in the cutout. + """ + + # Base commands for all text generation + blur = f'-blur "{self.number_blur_profile}"' if self.blur_edges else '' + text_commands = [ + f'-set colorspace sRGB', + f'-background transparent', + f'-density 200', + f'-pointsize 500', + f'-gravity center', + f'-interline-spacing -300', + f'-font "{self.EPISODE_TEXT_FONT.resolve()}"', + f'-fill white', + f'+size', + f'label:"{self.episode_text}"', + # Resize with 100px margin on all sides + f'-resize 3100x1700', + f'-extent "{self.TITLE_CARD_SIZE}"', + ] + + # A non-zero cutout shift requires an intermediate image + if self.cutout_vertical_shift != 0: + # Determine which direction to add padding to in order to + # generate indicated shift + gravity = 'north' if self.cutout_vertical_shift > 0 else 'south' + + # Create temporary image + self.__text_mask = self.image_magick.get_random_filename( + self.source_file + ) + self.image_magick.run([ + f'convert', + *text_commands, + # Adjust bounds to effectively shift image up/down + # this is required since -page and -geometry offsets + # do not affect mask images used in image mask + # composition + f'-gravity {gravity}', + f'-extent 3200x{1800 + abs(self.cutout_vertical_shift)}', + # Re-center to final mask dimensions + f'-gravity center -extent 3200x1800', + blur, + f'"{self.__text_mask.resolve()}"', + ]) + + # Add to primary image as a file composition + return [f'"{self.__text_mask.resolve()}"'] + + # No cutout shift, can generate mask on-the-fly + return [ + f'\(', + *text_commands, + blur, + f'\)' + ] + + @property def transparency_overlay_commands(self) -> ImageMagickCommands: """Subcommand to turn the overlay semi-transparent""" @@ -308,7 +373,7 @@ def create(self) -> None: # Masked Alpha Composition layers must be ordered as: # [Replace Black Parts of Mask] | [Replace White Parts of Mask] | [Mask] - command = ' '.join([ + self.image_magick.run([ f'convert', f'-set colorspace sRGB', # Create solid-color overlay @@ -318,27 +383,14 @@ def create(self) -> None: f'\( "{self.source_file.resolve()}"', *self.resize_and_style, f'\)', - # Create cutout of episode text - f'\( -set colorspace sRGB', - f'-background transparent', - f'-density 200', - f'-pointsize 500', - f'-gravity center', - f'-geometry +0{self.cutout_vertical_shift:+}', - f'-interline-spacing -300', - f'-font "{self.EPISODE_TEXT_FONT.resolve()}"', - f'-fill white', - f'+size', - f'label:"{self.episode_text}"', - # Resize with 100px margin on all sides - f'-resize 3100x1700', - f'-extent "{self.TITLE_CARD_SIZE}"', - f'-blur "{self.number_blur_profile}" \)' if self.blur_edges else '\)', + # Create/add cutout of episode text + *self.episode_text_mask, # Use masked alpha composition to combine images f'-gravity center', f'-composite', *self.transparency_overlay_commands, # Add title text + f'-density 200', *self.title_text_commands, # Attempt to overlay mask *self.add_overlay_mask(self.source_file), @@ -347,4 +399,4 @@ def create(self) -> None: f'"{self.output_file.resolve()}"', ]) - self.image_magick.run(command) + self.image_magick.delete_intermediate_images(self.__text_mask) diff --git a/modules/ref/version_webui b/modules/ref/version_webui index 08277359..76cd05d2 100755 --- a/modules/ref/version_webui +++ b/modules/ref/version_webui @@ -1 +1 @@ -v2.0-alpha.12.1-webui55 \ No newline at end of file +v2.0-alpha.12.1-webui56 \ No newline at end of file