mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-23 04:20:12 +02:00
feat!: establish new MediaType system
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user