From 5ada5c91f5fc3539d74cffc653e9ec504f50374d Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:30:27 -0700 Subject: [PATCH] feat!: establish new MediaType system --- src/tagstudio/core/media_types.py | 250 +++++++++++++++---- src/tagstudio/previews/file_renderer.py | 111 +++++--- src/tagstudio/previews/renderers/pdf.py | 4 +- src/tagstudio/qt/mixed/item_thumb.py | 6 +- src/tagstudio/qt/views/preview_thumb_view.py | 22 +- 5 files changed, 303 insertions(+), 90 deletions(-) diff --git a/src/tagstudio/core/media_types.py b/src/tagstudio/core/media_types.py index 5f286419..b06e20bb 100644 --- a/src/tagstudio/core/media_types.py +++ b/src/tagstudio/core/media_types.py @@ -5,17 +5,183 @@ import enum import mimetypes from dataclasses import dataclass +from enum import Enum, auto from pathlib import Path import structlog logger = structlog.get_logger(__name__) + +class _Contexts(Enum): + """An enum representing the use for a media type.""" + + SEARCH = auto() + RENDER = auto() + + +class _Type: + """A complete description of a media type and its uses.""" + + def __init__(self, exts: str | list[str], contexts: _Contexts | list[_Contexts]) -> None: + if isinstance(exts, str): + self.exts = [exts] + else: + self.exts = exts + + if isinstance(contexts, _Contexts): + self.contexts = [contexts] + else: + self.contexts = contexts + + +class MediaTypeGroup: + def __init__(self, name_key: str, types: list[_Type]) -> None: + self.searchable: frozenset[str] + self.renderable: frozenset[str] + self.name_key = name_key + self.types = types + # NOTE: Does self.types need to exist? + # TODO: Handle equivalencies + + searchable_set: set[str] = set() + renderable_set: set[str] = set() + for type_ in self.types: + if _Contexts.SEARCH in type_.contexts: + for ext in type_.exts: + searchable_set.add(ext) + + if _Contexts.RENDER in type_.contexts: + for ext in type_.exts: + renderable_set.add(ext) + + self.searchable = frozenset(searchable_set) + self.renderable = frozenset(renderable_set) + + +class MediaTypes: + SEARCH, RENDER = _Contexts.SEARCH, _Contexts.RENDER + + # Adobe ---------------------------------------------------------------------------------------- + adobe_photoshop_types = MediaTypeGroup( + "adobe.photoshop", + [ + _Type(".pdd", SEARCH), + _Type(".psb", SEARCH), + _Type(".psd", SEARCH), + ], + ) + + adobe_illustrator_types = MediaTypeGroup("adobe.illustrator", [_Type(".ai", SEARCH)]) + + pdf_types = MediaTypeGroup( + "pdf", + [ + _Type(".pdf", [SEARCH, RENDER]), + ], + ) + + adobe_types = MediaTypeGroup( + "type.adobe", adobe_photoshop_types.types + adobe_illustrator_types.types + pdf_types.types + ) + + # Raster Images -------------------------------------------------------------------------------- + raster_image_types = MediaTypeGroup( + "image.raster", + [ + _Type( + [".jfif", ".jpeg_large", ".jpeg", ".jpg_large", ".jpg"], + [SEARCH, RENDER], + ), + _Type(".psd", RENDER), + ], + ) + + # FIXME: Should the file renderer fallback to the search context if no render context is found, + # to use as a default preview? + # Because some files like .eps ot .pyc are never going to be rendered, but still should have + # default icons for the categories that they're in. + # OR should there be a new context? + + vector_image_types = MediaTypeGroup( + "image.vector", + [ + _Type(".ai", RENDER), + _Type(".eps", SEARCH), + _Type(".epsf", SEARCH), + _Type(".epsi", SEARCH), + _Type(".svg", [SEARCH, RENDER]), + _Type(".svgz", SEARCH), + ], + ) + + binary_types = MediaTypeGroup( + "binary", + [ + _Type(".pyc", [RENDER, SEARCH]), + _Type(".pyd", [RENDER, SEARCH]), + _Type(".pyo", [RENDER, SEARCH]), + _Type(".dll", [RENDER, SEARCH]), + _Type(".o", [RENDER, SEARCH]), + _Type(".dylib", [RENDER, SEARCH]), + _Type(".exe", [RENDER, SEARCH]), + ], + ) + + python_types = MediaTypeGroup( + "python", + [ + _Type(".ipynb", [RENDER, SEARCH]), + _Type(".py", [RENDER, SEARCH]), + _Type(".pyc", [SEARCH]), + _Type(".pyd", [SEARCH]), + _Type(".pyi", [RENDER, SEARCH]), + _Type(".pyo", [SEARCH]), + ], + ) + + javascript_types = MediaTypeGroup( + "javascript", + [ + _Type(".cjs", [SEARCH]), + _Type(".js", [SEARCH]), + _Type(".jsx", [SEARCH]), + _Type(".mjs", [SEARCH]), + ], + ) + + typescript_types = MediaTypeGroup( + "typescript", + [ + _Type(".cts", [SEARCH]), + _Type(".mts", [SEARCH]), + _Type(".ts", [SEARCH]), + _Type(".tsx", [SEARCH]), + ], + ) + + # TODO: Move to FileRenderer + unrenderable_types = binary_types.types # Eventually exclude .exe and stuff + + # NOTE: This is a subjective group used for grouping files together for searches + # and for creating color-on-black syntax highlighted previews. + code_types = MediaTypeGroup( + "type.code", python_types.types + javascript_types.types + typescript_types.types + ) + + @staticmethod + def all_media_types(): + static_methods = [ + name for name, attr in MediaTypes.__dict__.items() if isinstance(attr, MediaTypeGroup) + ] + return static_methods + + FILETYPE_EQUIVALENTS = [ {"aif", "aiff", "aifc"}, {"html", "htm", "xhtml", "shtml", "dhtml"}, {"jfif", "jpeg_large", "jpeg", "jpg_large", "jpg"}, - {"json", "jsonc", "json5"}, + {"json", "jsonc", "json5", "jsonl"}, {"md", "markdown", "mkd", "rmd"}, {"tar.gz", "tgz"}, {"xml", "xul"}, @@ -23,7 +189,7 @@ FILETYPE_EQUIVALENTS = [ ] -class MediaType(enum.StrEnum): +class MediaTypeOld(enum.StrEnum): """Names of media types.""" ADOBE_PHOTOSHOP = "adobe_photoshop" @@ -78,7 +244,7 @@ class MediaCategory: is_iana (bool): Represents whether this is an IANA registered category. """ - media_type: MediaType + media_type: MediaTypeOld extensions: set[str] name: str is_iana: bool = False @@ -425,223 +591,223 @@ class MediaCategories: } ADOBE_PHOTOSHOP_TYPES = MediaCategory( - media_type=MediaType.ADOBE_PHOTOSHOP, + media_type=MediaTypeOld.ADOBE_PHOTOSHOP, extensions=_ADOBE_PHOTOSHOP_SET, is_iana=False, name="photoshop", ) AFFINITY_PHOTO_TYPES = MediaCategory( - media_type=MediaType.AFFINITY_PHOTO, + media_type=MediaTypeOld.AFFINITY_PHOTO, extensions=_AFFINITY_PHOTO_SET, is_iana=False, name="affinity photo", ) ARCHIVE_TYPES = MediaCategory( - media_type=MediaType.ARCHIVE, + media_type=MediaTypeOld.ARCHIVE, extensions=_ARCHIVE_SET, is_iana=False, name="archive", ) AUDIO_MIDI_TYPES = MediaCategory( - media_type=MediaType.AUDIO_MIDI, + media_type=MediaTypeOld.AUDIO_MIDI, extensions=_AUDIO_MIDI_SET, is_iana=False, name="audio midi", ) AUDIO_TYPES = MediaCategory( - media_type=MediaType.AUDIO, + media_type=MediaTypeOld.AUDIO, extensions=_AUDIO_SET | _AUDIO_MIDI_SET, is_iana=True, name="audio", ) BLENDER_TYPES = MediaCategory( - media_type=MediaType.BLENDER, + media_type=MediaTypeOld.BLENDER, extensions=_BLENDER_SET, is_iana=False, name="blender", ) CLIP_STUDIO_PAINT_TYPES = MediaCategory( - media_type=MediaType.CLIP_STUDIO_PAINT, + media_type=MediaTypeOld.CLIP_STUDIO_PAINT, extensions=_CLIP_STUDIO_PAINT_SET, is_iana=False, name="clip studio paint", ) CODE_TYPES = MediaCategory( - media_type=MediaType.CODE, + media_type=MediaTypeOld.CODE, extensions=_CODE_SET, is_iana=False, name="code", ) DATABASE_TYPES = MediaCategory( - media_type=MediaType.DATABASE, + media_type=MediaTypeOld.DATABASE, extensions=_DATABASE_SET, is_iana=False, name="database", ) DISK_IMAGE_TYPES = MediaCategory( - media_type=MediaType.DISK_IMAGE, + media_type=MediaTypeOld.DISK_IMAGE, extensions=_DISK_IMAGE_SET, is_iana=False, name="disk image", ) DOCUMENT_TYPES = MediaCategory( - media_type=MediaType.DOCUMENT, + media_type=MediaTypeOld.DOCUMENT, extensions=_DOCUMENT_SET, is_iana=False, name="document", ) EBOOK_TYPES = MediaCategory( - media_type=MediaType.EBOOK, + media_type=MediaTypeOld.EBOOK, extensions=_EBOOK_SET, is_iana=False, name="ebook", ) FONT_TYPES = MediaCategory( - media_type=MediaType.FONT, + media_type=MediaTypeOld.FONT, extensions=_FONT_SET, is_iana=True, name="font", ) IMAGE_ANIMATED_TYPES = MediaCategory( - media_type=MediaType.IMAGE_ANIMATED, + media_type=MediaTypeOld.IMAGE_ANIMATED, extensions=_IMAGE_ANIMATED_SET, is_iana=False, name="animated image", ) IMAGE_RAW_TYPES = MediaCategory( - media_type=MediaType.IMAGE_RAW, + media_type=MediaTypeOld.IMAGE_RAW, extensions=_IMAGE_RAW_SET, is_iana=False, name="raw image", ) IMAGE_VECTOR_TYPES = MediaCategory( - media_type=MediaType.IMAGE_VECTOR, + media_type=MediaTypeOld.IMAGE_VECTOR, extensions=_IMAGE_VECTOR_SET, is_iana=False, name="vector image", ) IMAGE_RASTER_TYPES = MediaCategory( - media_type=MediaType.IMAGE, + media_type=MediaTypeOld.IMAGE, extensions=_IMAGE_RASTER_SET, is_iana=False, name="raster image", ) IMAGE_TYPES = MediaCategory( - media_type=MediaType.IMAGE, + media_type=MediaTypeOld.IMAGE, extensions=_IMAGE_RASTER_SET | _IMAGE_RAW_SET | _IMAGE_VECTOR_SET, is_iana=True, name="image", ) INSTALLER_TYPES = MediaCategory( - media_type=MediaType.INSTALLER, + media_type=MediaTypeOld.INSTALLER, extensions=_INSTALLER_SET, is_iana=False, name="installer", ) IWORK_TYPES = MediaCategory( - media_type=MediaType.IWORK, + media_type=MediaTypeOld.IWORK, extensions=_IWORK_SET, is_iana=False, name="iwork", ) MATERIAL_TYPES = MediaCategory( - media_type=MediaType.MATERIAL, + media_type=MediaTypeOld.MATERIAL, extensions=_MATERIAL_SET, is_iana=False, name="material", ) MDIPACK_TYPES = MediaCategory( - media_type=MediaType.MDIPACK, + media_type=MediaTypeOld.MDIPACK, extensions=_MDIPACK_SET, is_iana=False, name="mdipack", ) MODEL_TYPES = MediaCategory( - media_type=MediaType.MODEL, + media_type=MediaTypeOld.MODEL, extensions=_MODEL_SET, is_iana=True, name="model", ) OPEN_DOCUMENT_TYPES = MediaCategory( - media_type=MediaType.OPEN_DOCUMENT, + media_type=MediaTypeOld.OPEN_DOCUMENT, extensions=_OPEN_DOCUMENT_SET, is_iana=False, name="open document", ) PACKAGE_TYPES = MediaCategory( - media_type=MediaType.PACKAGE, + media_type=MediaTypeOld.PACKAGE, extensions=_PACKAGE_SET, is_iana=False, name="package", ) PAINT_DOT_NET_TYPES = MediaCategory( - media_type=MediaType.PAINT_DOT_NET, + media_type=MediaTypeOld.PAINT_DOT_NET, extensions=_PAINT_DOT_NET_SET, is_iana=False, name="paint.net", ) PDF_TYPES = MediaCategory( - media_type=MediaType.PDF, + media_type=MediaTypeOld.PDF, extensions=_PDF_SET | _ADOBE_ILLUSTRATOR_SET, is_iana=False, name="pdf", ) PLAINTEXT_TYPES = MediaCategory( - media_type=MediaType.PLAINTEXT, + media_type=MediaTypeOld.PLAINTEXT, extensions=_PLAINTEXT_SET | _CODE_SET, is_iana=False, name="plaintext", ) PRESENTATION_TYPES = MediaCategory( - media_type=MediaType.PRESENTATION, + media_type=MediaTypeOld.PRESENTATION, extensions=_PRESENTATION_SET, is_iana=False, name="presentation", ) PROGRAM_TYPES = MediaCategory( - media_type=MediaType.PROGRAM, + media_type=MediaTypeOld.PROGRAM, extensions=_PROGRAM_SET, is_iana=False, name="program", ) SHADER_TYPES = MediaCategory( - media_type=MediaType.SHADER, + media_type=MediaTypeOld.SHADER, extensions=_SHADER_SET, is_iana=False, name="shader", ) SHORTCUT_TYPES = MediaCategory( - media_type=MediaType.SHORTCUT, + media_type=MediaTypeOld.SHORTCUT, extensions=_SHORTCUT_SET, is_iana=False, name="shortcut", ) SOURCE_ENGINE_TYPES = MediaCategory( - media_type=MediaType.SOURCE_ENGINE, + media_type=MediaTypeOld.SOURCE_ENGINE, extensions=_SOURCE_ENGINE_SET, is_iana=False, name="source engine", ) SPREADSHEET_TYPES = MediaCategory( - media_type=MediaType.SPREADSHEET, + media_type=MediaTypeOld.SPREADSHEET, extensions=_SPREADSHEET_SET, is_iana=False, name="spreadsheet", ) TEXT_TYPES = MediaCategory( - media_type=MediaType.TEXT, + media_type=MediaTypeOld.TEXT, extensions=_DOCUMENT_SET | _PLAINTEXT_SET, is_iana=True, name="text", ) VIDEO_TYPES = MediaCategory( - media_type=MediaType.VIDEO, + media_type=MediaTypeOld.VIDEO, extensions=_VIDEO_SET, is_iana=True, name="video", ) KRITA_TYPES = MediaCategory( - media_type=MediaType.IMAGE, + media_type=MediaTypeOld.IMAGE, extensions=_KRITA_SET, is_iana=False, name="krita", @@ -687,14 +853,14 @@ class MediaCategories: ] @staticmethod - def get_types(ext: str, mime_fallback: bool = False) -> set[MediaType]: + def get_types(ext: str, mime_fallback: bool = False) -> set[MediaTypeOld]: """Return a set of MediaTypes given a file extension. Args: ext (str): File extension with a leading "." and in all lowercase. mime_fallback (bool): Flag to guess MIME type if no set matches are made. """ - media_types: set[MediaType] = set() + media_types: set[MediaTypeOld] = set() for cat in MediaCategories.ALL_CATEGORIES: if cat.contains(ext, mime_fallback): diff --git a/src/tagstudio/previews/file_renderer.py b/src/tagstudio/previews/file_renderer.py index 07df276b..810a1388 100644 --- a/src/tagstudio/previews/file_renderer.py +++ b/src/tagstudio/previews/file_renderer.py @@ -5,7 +5,9 @@ import contextlib import hashlib import math +from collections.abc import Callable from copy import deepcopy +from functools import partial from pathlib import Path import structlog @@ -15,7 +17,7 @@ from PIL.Image import DecompressionBombError from tagstudio.core.exceptions import NoRendererError from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.ignore import Ignore -from tagstudio.core.media_types import MediaCategories, MediaType +from tagstudio.core.media_types import MediaCategories, MediaTypeGroup, MediaTypeOld, MediaTypes from tagstudio.core.utils.types import unwrap from tagstudio.previews.gradients import four_corner_gradient from tagstudio.previews.renderers.archive import ( @@ -89,15 +91,15 @@ class FileRenderer: url (Path): The file url to assess. "$LOADING" will return the loading graphic. """ ext = url.suffix.lower() - types: set[MediaType] = MediaCategories.get_types(ext, mime_fallback=True) + types: set[MediaTypeOld] = MediaCategories.get_types(ext, mime_fallback=True) # Manual icon overrides. if ext in {".gif", ".vtf"}: - return MediaType.IMAGE + return MediaTypeOld.IMAGE elif ext in {".dll", ".pyc", ".o", ".dylib"}: - return MediaType.PROGRAM + return MediaTypeOld.PROGRAM elif ext in {".mscz"}: # noqa: SIM114 - return MediaType.TEXT + return MediaTypeOld.TEXT # Loop though the specific (non-IANA) categories and return the string # name of the first matching category found. @@ -751,34 +753,79 @@ class FileRenderer: cache_filename (Path | None): An optional filename to use to save to the cache. """ + filepath = Path(filepath) if isinstance(filepath, str) else filepath scaled_size = math.ceil(max(size[0], size[1]) * dpi_scale) image: Image.Image | None = None - filepath_: Path = Path(filepath) is_savable_type: bool = True - if filepath_ and filepath_.is_file(): + # Ordered groups of file renderers. + # A file extension is rendered with the first group it's found in. + render_groups: list[tuple[MediaTypeGroup, Callable[..., Image.Image | None]]] = [ + (MediaTypes.raster_image_types, partial(raster_image_thumb, filepath)), + (MediaTypes.vector_image_types, partial(vector_image_thumb, filepath, scaled_size)), + (MediaTypes.binary_types, partial(raster_image_thumb, filepath)), + (MediaTypes.python_types, partial(text_thumb, filepath)), + (MediaTypes.pdf_types, partial(pdf_thumb, filepath, scaled_size)), + ] + if filepath and filepath.is_file(): try: - ext: str = filepath_.suffix.lower() if filepath_.suffix else filepath_.stem.lower() + ext = filepath.suffix.lower() if filepath.suffix else filepath.stem.lower() + for media_type, renderer in render_groups: + if ext in media_type.renderable: + logger.warning(f"{ext}: {media_type.renderable}") + image = renderer() + continue + + if not image: + logger.warning(f"No match for {ext}") + raise NoRendererError + + if image: + image = self._resize_image(image, (scaled_size, scaled_size)) + + if cache_filename and is_savable_type and image and cache: + cache.save_image(image, cache_filename, mode="RGBA") + except ( + AssertionError, + ChildProcessError, + DecompressionBombError, + UnidentifiedImageError, + ValueError, + ) as e: + logger.error( + "[FileRenderer] Couldn't render thumbnail", + filepath=filepath, + error=type(e).__name__, + ) + image = None + except NoRendererError: + image = None + + return image + + if filepath and filepath.is_file(): + try: + ext: str = filepath.suffix.lower() if filepath.suffix else filepath.stem.lower() # eBooks =========================================================================== if MediaCategories.is_ext_in_category( ext, MediaCategories.EBOOK_TYPES, mime_fallback=True ): - image = epub_thumb(filepath_, ext) + image = epub_thumb(filepath, ext) # Krita ============================================================================ elif MediaCategories.is_ext_in_category( ext, MediaCategories.KRITA_TYPES, mime_fallback=True ): - image = krita_thumb(filepath_) + image = krita_thumb(filepath) # Clip Studio Paint ================================================================ elif MediaCategories.is_ext_in_category( ext, MediaCategories.CLIP_STUDIO_PAINT_TYPES ): - image = clip_studio_thumb(filepath_) + image = clip_studio_thumb(filepath) # VTF ============================================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.SOURCE_ENGINE_TYPES, mime_fallback=True ): - image = vtf_thumb(filepath_) + image = vtf_thumb(filepath) # Images =========================================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.IMAGE_TYPES, mime_fallback=True @@ -787,61 +834,59 @@ class FileRenderer: if MediaCategories.is_ext_in_category( ext, MediaCategories.IMAGE_RAW_TYPES, mime_fallback=True ): - image = raw_image_thumb(filepath_) + image = raw_image_thumb(filepath) # Vector Images ---------------------------------------------------------------- - elif MediaCategories.is_ext_in_category( - ext, MediaCategories.IMAGE_VECTOR_TYPES, mime_fallback=True - ): - image = vector_image_thumb(filepath_, scaled_size) + elif ext in MediaTypes.vector_image_types.renderable: + image = vector_image_thumb(filepath, scaled_size) # EXR Images ------------------------------------------------------------------- elif ext in [".exr"]: - image = exr_image_thumb(filepath_) + image = exr_image_thumb(filepath) # Normal Images ---------------------------------------------------------------- else: - image = raster_image_thumb(filepath_) + image = raster_image_thumb(filepath) # Videos =========================================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.VIDEO_TYPES, mime_fallback=True ): - image = video_thumb(filepath_) + image = video_thumb(filepath) # PowerPoint ======================================================================= elif ext in {".pptx"}: - image = powerpoint_thumb(filepath_) + image = powerpoint_thumb(filepath) # OpenDocument/OpenOffice ========================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.OPEN_DOCUMENT_TYPES, mime_fallback=True ): - image = open_doc_thumb(filepath_) + image = open_doc_thumb(filepath) # Apple iWork + Creator Studio ===================================================== elif ( MediaCategories.is_ext_in_category(ext, MediaCategories.IWORK_TYPES) or ext == ".pxd" ): - image = apple_embedded_thumb(filepath_) + image = apple_embedded_thumb(filepath) # Plain Text ======================================================================= elif MediaCategories.is_ext_in_category( ext, MediaCategories.PLAINTEXT_TYPES, mime_fallback=True ): - image = text_thumb(filepath_) + image = text_thumb(filepath) # Fonts ============================================================================ elif MediaCategories.is_ext_in_category( ext, MediaCategories.FONT_TYPES, mime_fallback=True ): if is_thumb: # Short (Aa) Preview - image = font_small_thumb(filepath_, scaled_size) + image = font_small_thumb(filepath, scaled_size) if image is not None: image = self._apply_overlay_color(image, UiColor.BLUE, theme) else: # Large (Full Alphabet) Preview - image = font_full_preview(filepath_, scaled_size) + image = font_full_preview(filepath, scaled_size) # Audio ======================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.AUDIO_TYPES, mime_fallback=True ): - image = audio_album_thumb(filepath_, ext) + image = audio_album_thumb(filepath, ext) if image is None: - image = audio_waveform_thumb(filepath_, ext, scaled_size, dpi_scale) + image = audio_waveform_thumb(filepath, ext, scaled_size, dpi_scale) is_savable_type = False if image is not None: image = self._apply_overlay_color(image, UiColor.GREEN, theme) @@ -849,21 +894,21 @@ class FileRenderer: elif MediaCategories.is_ext_in_category( ext, MediaCategories.BLENDER_TYPES, mime_fallback=True ): - image = blender_thumb(filepath_) + image = blender_thumb(filepath) # PDF ========================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.PDF_TYPES, mime_fallback=True ): - image = pdf_thumb(filepath_, scaled_size, ext) + image = pdf_thumb(filepath, scaled_size, ext) # Archives ===================================================== elif MediaCategories.is_ext_in_category(ext, MediaCategories.ARCHIVE_TYPES): - image = archive_thumb(filepath_, ext=ext) + image = archive_thumb(filepath, ext=ext) # MDIPACK ====================================================== elif MediaCategories.is_ext_in_category(ext, MediaCategories.MDIPACK_TYPES): - image = medibang_paint_thumb(filepath_) + image = medibang_paint_thumb(filepath) # Paint.NET ==================================================== elif MediaCategories.is_ext_in_category(ext, MediaCategories.PAINT_DOT_NET_TYPES): - image = paint_dot_net_thumb(filepath_) + image = paint_dot_net_thumb(filepath) # No Rendered Thumbnail ======================================== if not image: raise NoRendererError diff --git a/src/tagstudio/previews/renderers/pdf.py b/src/tagstudio/previews/renderers/pdf.py index 65b67646..f5fb14e0 100644 --- a/src/tagstudio/previews/renderers/pdf.py +++ b/src/tagstudio/previews/renderers/pdf.py @@ -19,7 +19,7 @@ from tagstudio.qt.views.styles.image_effects import replace_transparent_pixels logger = structlog.get_logger(__name__) -def pdf_thumb(filepath: Path, size: int, ext: str) -> Image.Image | None: +def pdf_thumb(filepath: Path, size: int) -> Image.Image | None: """Render a thumbnail for a PDF or Adobe Illustrator file. filepath (Path): The path of the file. @@ -44,7 +44,7 @@ def pdf_thumb(filepath: Path, size: int, ext: str) -> Image.Image | None: else: page_size *= size / page_size.width() # Enlarge image for anti-aliasing - scale_factor = 2.5 if ext in {".pdf"} else 1 + scale_factor = 2.5 page_size *= scale_factor # Render image with no anti-aliasing for speed render_options: QPdfDocumentRenderOptions = QPdfDocumentRenderOptions() diff --git a/src/tagstudio/qt/mixed/item_thumb.py b/src/tagstudio/qt/mixed/item_thumb.py index fd0031e0..0ea21628 100644 --- a/src/tagstudio/qt/mixed/item_thumb.py +++ b/src/tagstudio/qt/mixed/item_thumb.py @@ -15,7 +15,7 @@ from PySide6.QtWidgets import QBoxLayout, QCheckBox, QHBoxLayout, QLabel, QVBoxL from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE from tagstudio.core.library.alchemy.enums import ItemType from tagstudio.core.library.alchemy.library import Library -from tagstudio.core.media_types import MediaCategories, MediaType +from tagstudio.core.media_types import MediaCategories, MediaTypeOld from tagstudio.core.utils.types import unwrap from tagstudio.i18n.platform_strings import open_file_str, trash_term from tagstudio.i18n.translations import Translations @@ -361,7 +361,7 @@ class ItemThumb(FlowWidget): ext = filename.suffix.lower() if ext and ext.startswith(".") is False: ext = "." + ext - media_types: set[MediaType] = MediaCategories.get_types(ext) + media_types: set[MediaTypeOld] = MediaCategories.get_types(ext) if ( not MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_TYPES) or MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_RAW_TYPES) @@ -380,7 +380,7 @@ class ItemThumb(FlowWidget): if ext or filename.stem: self.ext_badge.setText(ext.upper()[1:] or filename.stem.upper()) show_ext_badge = True - if MediaType.VIDEO in media_types or MediaType.AUDIO in media_types: + if MediaTypeOld.VIDEO in media_types or MediaTypeOld.AUDIO in media_types: show_count_badge = True self.ext_badge.setHidden(not show_ext_badge) diff --git a/src/tagstudio/qt/views/preview_thumb_view.py b/src/tagstudio/qt/views/preview_thumb_view.py index 5fcb922f..cd61363b 100644 --- a/src/tagstudio/qt/views/preview_thumb_view.py +++ b/src/tagstudio/qt/views/preview_thumb_view.py @@ -13,7 +13,7 @@ from PySide6.QtGui import QAction, QMovie, QPixmap, QResizeEvent from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QStackedLayout, QWidget from tagstudio.core.library.alchemy.library import Library -from tagstudio.core.media_types import MediaType +from tagstudio.core.media_types import MediaTypeOld from tagstudio.i18n.platform_strings import open_file_str, trash_term from tagstudio.i18n.translations import Translations from tagstudio.qt.mixed.file_attributes import FileAttributeData @@ -200,8 +200,8 @@ class PreviewThumbView(QWidget): if m: m.setScaledSize(adj_size) - def __switch_preview(self, preview: MediaType | None) -> None: - if preview in [MediaType.AUDIO, MediaType.VIDEO]: + def __switch_preview(self, preview: MediaTypeOld | None) -> None: + if preview in [MediaTypeOld.AUDIO, MediaTypeOld.VIDEO]: self.__media_player.show() self.__image_layout.setCurrentWidget(self.__media_player_page) self.check_ffmpeg.emit(True) # noqa: FBT003 @@ -210,15 +210,17 @@ class PreviewThumbView(QWidget): self.__media_player.hide() self.check_ffmpeg.emit(False) # noqa: FBT003 - if preview in [MediaType.IMAGE, MediaType.AUDIO]: + if preview in [MediaTypeOld.IMAGE, MediaTypeOld.AUDIO]: self.__button_wrapper.show() self.__image_layout.setCurrentWidget( - self.__preview_img_page if preview == MediaType.IMAGE else self.__media_player_page + self.__preview_img_page + if preview == MediaTypeOld.IMAGE + else self.__media_player_page ) else: self.__button_wrapper.hide() - if preview == MediaType.IMAGE_ANIMATED: + if preview == MediaTypeOld.IMAGE_ANIMATED: self.__preview_gif.show() self.__image_layout.setCurrentWidget(self.__preview_gif_page) else: @@ -251,7 +253,7 @@ class PreviewThumbView(QWidget): def _display_video(self, filepath: Path, size: QSize | None) -> FileAttributeData: self.__should_render_on_resize = False - self.__switch_preview(MediaType.VIDEO) + self.__switch_preview(MediaTypeOld.VIDEO) self.__update_media_player(filepath) stats = FileAttributeData() @@ -270,7 +272,7 @@ class PreviewThumbView(QWidget): return stats def _display_audio(self, filepath: Path) -> FileAttributeData: - self.__switch_preview(MediaType.AUDIO) + self.__switch_preview(MediaTypeOld.AUDIO) self.__render_thumb(filepath) self.__update_media_player(filepath) return FileAttributeData() @@ -300,7 +302,7 @@ class PreviewThumbView(QWidget): return None # The animation has more than 1 frame, continue displaying it as an animation - self.__switch_preview(MediaType.IMAGE_ANIMATED) + self.__switch_preview(MediaTypeOld.IMAGE_ANIMATED) self.resizeEvent( QResizeEvent( QSize(stats.width, stats.height), @@ -315,7 +317,7 @@ class PreviewThumbView(QWidget): def _display_image(self, filepath: Path): """Renders the given file as an image, no matter its media type.""" - self.__switch_preview(MediaType.IMAGE) + self.__switch_preview(MediaTypeOld.IMAGE) self.__render_thumb(filepath) def hide_preview(self) -> None: