feat!: establish new MediaType system

This commit is contained in:
Travis Abendshien
2026-08-19 15:30:27 -07:00
parent 33199f5b26
commit 5ada5c91f5
5 changed files with 303 additions and 90 deletions
+208 -42
View File
@@ -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):
+78 -33
View File
@@ -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
+2 -2
View File
@@ -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()
+3 -3
View File
@@ -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)
+12 -10
View File
@@ -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: