mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-23 04:20:12 +02:00
further additions and changes
This commit is contained in:
@@ -13,7 +13,7 @@ import structlog
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class _Contexts(Enum):
|
||||
class Context(Enum):
|
||||
"""An enum representing the use for a media type."""
|
||||
|
||||
SEARCH = auto()
|
||||
@@ -23,13 +23,13 @@ class _Contexts(Enum):
|
||||
class _Type:
|
||||
"""A complete description of a media type and its uses."""
|
||||
|
||||
def __init__(self, exts: str | list[str], contexts: _Contexts | list[_Contexts]) -> None:
|
||||
def __init__(self, exts: str | list[str], contexts: Context | list[Context]) -> None:
|
||||
if isinstance(exts, str):
|
||||
self.exts = [exts]
|
||||
else:
|
||||
self.exts = exts
|
||||
|
||||
if isinstance(contexts, _Contexts):
|
||||
if isinstance(contexts, Context):
|
||||
self.contexts = [contexts]
|
||||
else:
|
||||
self.contexts = contexts
|
||||
@@ -47,23 +47,41 @@ class MediaTypeGroup:
|
||||
searchable_set: set[str] = set()
|
||||
renderable_set: set[str] = set()
|
||||
for type_ in self.types:
|
||||
if _Contexts.SEARCH in type_.contexts:
|
||||
if Context.SEARCH in type_.contexts:
|
||||
for ext in type_.exts:
|
||||
searchable_set.add(ext)
|
||||
|
||||
if _Contexts.RENDER in type_.contexts:
|
||||
if Context.RENDER in type_.contexts:
|
||||
for ext in type_.exts:
|
||||
renderable_set.add(ext)
|
||||
|
||||
self.searchable = frozenset(searchable_set)
|
||||
self.renderable = frozenset(renderable_set)
|
||||
|
||||
def contains(self, ext: str, context: Context) -> bool:
|
||||
# logger.info(f"MIME Guess: {mimetypes.guess_type(Path(f'x{ext}'), strict=False)}")
|
||||
return (
|
||||
context == Context.SEARCH
|
||||
and ext in self.searchable
|
||||
or context == Context.RENDER
|
||||
and ext in self.renderable
|
||||
)
|
||||
# return True
|
||||
|
||||
# if self.is_iana:
|
||||
# mime_type: str | None = mimetypes.guess_type(Path(f"x{ext}"), strict=False)[0]
|
||||
# if mime_type is not None and mime_type.startswith(self.name_key):
|
||||
# logger.info(f"MIME type found for {ext}: {mime_type}")
|
||||
# return True
|
||||
|
||||
# return False
|
||||
|
||||
|
||||
class MediaTypes:
|
||||
SEARCH, RENDER = _Contexts.SEARCH, _Contexts.RENDER
|
||||
SEARCH, RENDER = Context.SEARCH, Context.RENDER
|
||||
|
||||
# Adobe ----------------------------------------------------------------------------------------
|
||||
adobe_photoshop_types = MediaTypeGroup(
|
||||
adobe_photoshop = MediaTypeGroup(
|
||||
"adobe.photoshop",
|
||||
[
|
||||
_Type(".pdd", SEARCH),
|
||||
@@ -71,39 +89,120 @@ class MediaTypes:
|
||||
_Type(".psd", SEARCH),
|
||||
],
|
||||
)
|
||||
|
||||
adobe_illustrator_types = MediaTypeGroup("adobe.illustrator", [_Type(".ai", SEARCH)])
|
||||
|
||||
pdf_types = MediaTypeGroup(
|
||||
adobe_illustrator = MediaTypeGroup(
|
||||
"adobe.illustrator",
|
||||
[
|
||||
_Type(".ai", SEARCH),
|
||||
],
|
||||
)
|
||||
pdf = MediaTypeGroup(
|
||||
"pdf",
|
||||
[
|
||||
_Type(".pdf", [SEARCH, RENDER]),
|
||||
],
|
||||
)
|
||||
|
||||
adobe_types = MediaTypeGroup(
|
||||
"type.adobe", adobe_photoshop_types.types + adobe_illustrator_types.types + pdf_types.types
|
||||
adobe = MediaTypeGroup("adobe", adobe_photoshop.types + adobe_illustrator.types + pdf.types)
|
||||
|
||||
# Affinity -------------------------------------------------------------------------------------
|
||||
affinity_photo = MediaTypeGroup(
|
||||
"affinity.photo",
|
||||
[_Type(".afphoto", [SEARCH, RENDER])],
|
||||
)
|
||||
affinity_designer = MediaTypeGroup(
|
||||
"affinity.designer",
|
||||
[_Type(".afdesign", [SEARCH, RENDER])],
|
||||
)
|
||||
affinity_publisher = MediaTypeGroup(
|
||||
"affinity.publisher",
|
||||
[_Type([".afpublisher", ".afpub"], [SEARCH, RENDER])],
|
||||
)
|
||||
|
||||
# Raster Images --------------------------------------------------------------------------------
|
||||
raster_image_types = MediaTypeGroup(
|
||||
"image.raster",
|
||||
affinity = MediaTypeGroup(
|
||||
"affinity",
|
||||
affinity_photo.types
|
||||
+ affinity_designer.types
|
||||
+ affinity_publisher.types
|
||||
+ [_Type(".af", [SEARCH, RENDER])],
|
||||
)
|
||||
|
||||
# MIDI -----------------------------------------------------------------------------------------
|
||||
midi = MediaTypeGroup("midi", [_Type([".mid", ".midi"], SEARCH)])
|
||||
|
||||
# Audio ----------------------------------------------------------------------------------------
|
||||
audio = MediaTypeGroup(
|
||||
"audio",
|
||||
[
|
||||
_Type(".aac", [RENDER, SEARCH]),
|
||||
_Type(
|
||||
[".jfif", ".jpeg_large", ".jpeg", ".jpg_large", ".jpg"],
|
||||
[SEARCH, RENDER],
|
||||
[".aif", ".aiff", ".aifc"],
|
||||
[RENDER, SEARCH],
|
||||
),
|
||||
_Type(".psd", RENDER),
|
||||
_Type(".caf", [RENDER, SEARCH]),
|
||||
_Type(".flac", [RENDER, SEARCH]),
|
||||
_Type(".m4a", [RENDER, SEARCH]),
|
||||
_Type(".m4p", [RENDER, SEARCH]),
|
||||
_Type(".mp3", [RENDER, SEARCH]),
|
||||
_Type(".ogg", [RENDER, SEARCH]),
|
||||
_Type(".wav", [RENDER, SEARCH]),
|
||||
_Type(".wma", [RENDER, SEARCH]),
|
||||
]
|
||||
+ midi.types,
|
||||
)
|
||||
|
||||
# RAW Images -----------------------------------------------------------------------------------
|
||||
raw_image = MediaTypeGroup(
|
||||
"image.raw",
|
||||
[
|
||||
_Type(".arw", [SEARCH, RENDER]),
|
||||
_Type(".cr2", [SEARCH, RENDER]),
|
||||
_Type(".cr3", [SEARCH, RENDER]),
|
||||
_Type(".crw", [SEARCH, RENDER]),
|
||||
_Type(".dng", [SEARCH, RENDER]),
|
||||
_Type(".nef", [SEARCH, RENDER]),
|
||||
_Type(".nrw", [SEARCH, RENDER]),
|
||||
_Type(".orf", [SEARCH, RENDER]),
|
||||
_Type(".r3d", [SEARCH, RENDER]),
|
||||
_Type(".raf", [SEARCH, RENDER]),
|
||||
_Type(".raw", [SEARCH, RENDER]),
|
||||
_Type(".rw2", [SEARCH, RENDER]),
|
||||
_Type(".srf", [SEARCH, RENDER]),
|
||||
_Type(".srf2", [SEARCH, 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?
|
||||
# Raster Images --------------------------------------------------------------------------------
|
||||
raster_image = MediaTypeGroup(
|
||||
"image.raster",
|
||||
[
|
||||
_Type(".apng", [SEARCH, RENDER]),
|
||||
_Type(".avif", [SEARCH, RENDER]),
|
||||
_Type(".bmp", [SEARCH, RENDER]),
|
||||
_Type(".exr", [SEARCH, RENDER]),
|
||||
_Type(".gif", [SEARCH, RENDER]),
|
||||
_Type(
|
||||
[
|
||||
".jfif",
|
||||
".jpeg_large",
|
||||
".jpeg",
|
||||
".jpg_large",
|
||||
".jpg",
|
||||
],
|
||||
[SEARCH, RENDER],
|
||||
),
|
||||
_Type(".jxl", [SEARCH, RENDER]),
|
||||
_Type(".png", [SEARCH, RENDER]),
|
||||
_Type(".psb", RENDER),
|
||||
_Type(".psd", RENDER),
|
||||
_Type(".webp", [SEARCH, RENDER]),
|
||||
_Type([".heic", ".heif"], [SEARCH, RENDER]),
|
||||
_Type([".j2k", ".jp2", ".jpg2"], [SEARCH, RENDER]),
|
||||
_Type([".tif", ".tiff"], [SEARCH, RENDER]),
|
||||
],
|
||||
)
|
||||
|
||||
vector_image_types = MediaTypeGroup(
|
||||
# Vector Images --------------------------------------------------------------------------------
|
||||
vector = MediaTypeGroup(
|
||||
"image.vector",
|
||||
[
|
||||
_Type(".ai", RENDER),
|
||||
@@ -115,60 +214,112 @@ class MediaTypes:
|
||||
],
|
||||
)
|
||||
|
||||
binary_types = MediaTypeGroup(
|
||||
binary = MediaTypeGroup(
|
||||
"binary",
|
||||
[
|
||||
_Type(".dll", [RENDER, SEARCH]),
|
||||
_Type(".dylib", [RENDER, SEARCH]),
|
||||
_Type(".exe", [RENDER, SEARCH]),
|
||||
_Type(".o", [RENDER, SEARCH]),
|
||||
_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 = MediaTypeGroup(
|
||||
"python",
|
||||
[
|
||||
_Type(".ipynb", [RENDER, SEARCH]),
|
||||
_Type(".py", [RENDER, SEARCH]),
|
||||
_Type(".pyc", [SEARCH]),
|
||||
_Type(".pyd", [SEARCH]),
|
||||
_Type(".pyc", SEARCH),
|
||||
_Type(".pyd", SEARCH),
|
||||
_Type(".pyi", [RENDER, SEARCH]),
|
||||
_Type(".pyo", [SEARCH]),
|
||||
_Type(".pyo", SEARCH),
|
||||
],
|
||||
)
|
||||
|
||||
javascript_types = MediaTypeGroup(
|
||||
javascript = MediaTypeGroup(
|
||||
"javascript",
|
||||
[
|
||||
_Type(".cjs", [SEARCH]),
|
||||
_Type(".js", [SEARCH]),
|
||||
_Type(".jsx", [SEARCH]),
|
||||
_Type(".mjs", [SEARCH]),
|
||||
_Type(".cjs", [SEARCH, RENDER]),
|
||||
_Type(".js", [SEARCH, RENDER]),
|
||||
_Type(".jsx", [SEARCH, RENDER]),
|
||||
_Type(".mjs", [SEARCH, RENDER]),
|
||||
],
|
||||
)
|
||||
|
||||
typescript_types = MediaTypeGroup(
|
||||
typescript = MediaTypeGroup(
|
||||
"typescript",
|
||||
[
|
||||
_Type(".cts", [SEARCH]),
|
||||
_Type(".mts", [SEARCH]),
|
||||
_Type(".ts", [SEARCH]),
|
||||
_Type(".tsx", [SEARCH]),
|
||||
_Type(".cts", [SEARCH, RENDER]),
|
||||
_Type(".mts", [SEARCH, RENDER]),
|
||||
# _Type(".ts", [SEARCH, RENDER]),
|
||||
_Type(".tsx", [SEARCH, RENDER]),
|
||||
],
|
||||
)
|
||||
|
||||
# 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
|
||||
# Shell Script ---------------------------------------------------------------------------------
|
||||
shell = MediaTypeGroup(
|
||||
"shell",
|
||||
[
|
||||
_Type(".bat", [SEARCH, RENDER]),
|
||||
_Type(".csh", [SEARCH, RENDER]),
|
||||
_Type(".fish", [SEARCH, RENDER]),
|
||||
_Type(".ps1", [SEARCH, RENDER]),
|
||||
_Type(".sh", [SEARCH, RENDER]),
|
||||
],
|
||||
)
|
||||
|
||||
# Markdown -------------------------------------------------------------------------------------
|
||||
markdown = MediaTypeGroup(
|
||||
"markdown",
|
||||
[
|
||||
_Type(
|
||||
[
|
||||
".markdown",
|
||||
".md",
|
||||
".mkd",
|
||||
".rmd",
|
||||
],
|
||||
[SEARCH, RENDER],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Plaintext ------------------------------------------------------------------------------------
|
||||
plaintext = MediaTypeGroup(
|
||||
"plaintext",
|
||||
[_Type([".txt", ".text"], [SEARCH, RENDER])] + markdown.types,
|
||||
)
|
||||
|
||||
# Code -----------------------------------------------------------------------------------------
|
||||
code_types = MediaTypeGroup(
|
||||
"type.code",
|
||||
python.types + javascript.types + typescript.types + shell.types,
|
||||
)
|
||||
|
||||
# Video ----------------------------------------------------------------------------------------
|
||||
video = MediaTypeGroup(
|
||||
"video",
|
||||
[
|
||||
_Type(".3gp", [SEARCH, RENDER]),
|
||||
_Type(".avi", [SEARCH, RENDER]),
|
||||
_Type(".flv", [SEARCH, RENDER]),
|
||||
_Type(".gifv", [SEARCH, RENDER]),
|
||||
_Type(".hevc", [SEARCH, RENDER]),
|
||||
_Type(".m4p", [SEARCH, RENDER]),
|
||||
_Type(".m4v", [SEARCH, RENDER]),
|
||||
_Type(".mkv", [SEARCH, RENDER]),
|
||||
_Type(".mov", [SEARCH, RENDER]),
|
||||
_Type(".mp4", [SEARCH, RENDER]),
|
||||
_Type(".webm", [SEARCH, RENDER]),
|
||||
_Type(".wmv", [SEARCH, RENDER]),
|
||||
],
|
||||
)
|
||||
|
||||
# ------------------
|
||||
|
||||
@staticmethod
|
||||
def all_media_types():
|
||||
static_methods = [
|
||||
|
||||
@@ -17,7 +17,13 @@ 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, MediaTypeGroup, MediaTypeOld, MediaTypes
|
||||
from tagstudio.core.media_types import (
|
||||
Context,
|
||||
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 (
|
||||
@@ -27,7 +33,7 @@ from tagstudio.previews.renderers.archive import (
|
||||
open_doc_thumb,
|
||||
powerpoint_thumb,
|
||||
)
|
||||
from tagstudio.previews.renderers.audio import audio_album_thumb, audio_waveform_thumb
|
||||
from tagstudio.previews.renderers.audio import audio_album_thumb, audio_thumb, audio_waveform_thumb
|
||||
from tagstudio.previews.renderers.blender import blender_thumb
|
||||
from tagstudio.previews.renderers.clip_studio import clip_studio_thumb
|
||||
from tagstudio.previews.renderers.ebook import epub_thumb
|
||||
@@ -41,7 +47,7 @@ from tagstudio.previews.renderers.raster_image import (
|
||||
raw_image_thumb,
|
||||
)
|
||||
from tagstudio.previews.renderers.source_engine import vtf_thumb
|
||||
from tagstudio.previews.renderers.text import text_thumb
|
||||
from tagstudio.previews.renderers.text import code_thumb, text_thumb
|
||||
from tagstudio.previews.renderers.vector_image import vector_image_thumb
|
||||
from tagstudio.previews.renderers.video import video_thumb
|
||||
from tagstudio.qt.app_settings import (
|
||||
@@ -761,23 +767,35 @@ class FileRenderer:
|
||||
# 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)),
|
||||
(MediaTypes.raw_image, partial(raw_image_thumb, filepath)),
|
||||
(MediaTypes.binary, partial(raster_image_thumb, filepath)),
|
||||
(MediaTypes.python, partial(text_thumb, filepath)),
|
||||
(MediaTypes.pdf, partial(pdf_thumb, filepath, scaled_size)),
|
||||
(MediaTypes.raster_image, partial(raster_image_thumb, filepath)),
|
||||
(MediaTypes.vector, partial(vector_image_thumb, filepath, scaled_size)),
|
||||
(MediaTypes.code_types, partial(code_thumb, filepath)),
|
||||
(MediaTypes.plaintext, partial(text_thumb, filepath)),
|
||||
(MediaTypes.audio, partial(audio_thumb, filepath, scaled_size, dpi_scale)),
|
||||
(MediaTypes.video, partial(video_thumb, filepath)),
|
||||
]
|
||||
if filepath and filepath.is_file():
|
||||
try:
|
||||
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}")
|
||||
if media_type.contains(ext, Context.RENDER):
|
||||
logger.info(f"{ext} in: {media_type.renderable}")
|
||||
image = renderer()
|
||||
continue
|
||||
|
||||
# TODO: Remove the need for these extra steps
|
||||
if image and ext in MediaTypes.audio.renderable:
|
||||
# TODO: Differentiate between album art and waveform
|
||||
image = self._apply_overlay_color(image, UiColor.GREEN, theme)
|
||||
is_savable_type = False
|
||||
|
||||
break
|
||||
|
||||
if not image:
|
||||
logger.warning(f"No match for {ext}")
|
||||
logger.warning(f"Could not render {ext}")
|
||||
raise NoRendererError
|
||||
|
||||
if image:
|
||||
@@ -836,7 +854,7 @@ class FileRenderer:
|
||||
):
|
||||
image = raw_image_thumb(filepath)
|
||||
# Vector Images ----------------------------------------------------------------
|
||||
elif ext in MediaTypes.vector_image_types.renderable:
|
||||
elif ext in MediaTypes.vector.renderable:
|
||||
image = vector_image_thumb(filepath, scaled_size)
|
||||
# EXR Images -------------------------------------------------------------------
|
||||
elif ext in [".exr"]:
|
||||
|
||||
@@ -20,30 +20,45 @@ from tagstudio.previews.vendored.pydub.audio_segment import (
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def audio_album_thumb(filepath: Path, ext: str) -> Image.Image | None:
|
||||
def audio_thumb(filepath: Path, size: int, dpi_scale: float) -> Image.Image | None:
|
||||
"""Return an album cover preview, or a waveform if cover art does not exist.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
size (int): The size of the thumbnail.
|
||||
dpi_scale (float): The screen pixel ratio.
|
||||
"""
|
||||
image = audio_album_thumb(filepath)
|
||||
if not image:
|
||||
image = audio_waveform_thumb(filepath, size, dpi_scale)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def audio_album_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Return an album cover thumb from an audio file if a cover is present.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
ext (str): The file extension (with leading ".").
|
||||
"""
|
||||
image: Image.Image | None = None
|
||||
ext = filepath.suffix.lower()
|
||||
try:
|
||||
if not filepath.is_file():
|
||||
raise FileNotFoundError
|
||||
|
||||
artwork = None
|
||||
if ext in [".mp3"]:
|
||||
if ext in {".mp3", ".aif", ".aiff"}:
|
||||
id3_tags: id3.ID3 = id3.ID3(filepath)
|
||||
id3_covers: list = id3_tags.getall("APIC") # pyright: ignore[reportUnknownVariableType]
|
||||
if id3_covers:
|
||||
artwork = Image.open(BytesIO(id3_covers[0].data))
|
||||
elif ext in [".flac"]:
|
||||
elif ext in {".flac"}:
|
||||
flac_tags: flac.FLAC = flac.FLAC(filepath)
|
||||
flac_covers: list = flac_tags.pictures # pyright: ignore[reportUnknownVariableType]
|
||||
if flac_covers:
|
||||
artwork = Image.open(BytesIO(flac_covers[0].data))
|
||||
elif ext in [".mp4", ".m4a", ".aac"]:
|
||||
elif ext in {".mp4", ".m4a", ".aac", ".alac"}:
|
||||
mp4_tags: mp4.MP4 = mp4.MP4(filepath)
|
||||
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportUnknownVariableType]
|
||||
if mp4_covers:
|
||||
@@ -61,16 +76,13 @@ def audio_album_thumb(filepath: Path, ext: str) -> Image.Image | None:
|
||||
return image
|
||||
|
||||
|
||||
def audio_waveform_thumb(
|
||||
filepath: Path, ext: str, size: int, pixel_ratio: float
|
||||
) -> Image.Image | None:
|
||||
def audio_waveform_thumb(filepath: Path, size: int, dpi_scale: float) -> Image.Image | None:
|
||||
"""Render a waveform image from an audio file.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
ext (str): The file extension (with leading ".").
|
||||
size (tuple[int,int]): The size of the thumbnail.
|
||||
pixel_ratio (float): The screen pixel ratio.
|
||||
size (int): The size of the thumbnail.
|
||||
dpi_scale (float): The screen pixel ratio.
|
||||
"""
|
||||
# BASE_SCALE used for drawing on a larger image and resampling down
|
||||
# to provide an antialiased effect.
|
||||
@@ -81,8 +93,8 @@ def audio_waveform_thumb(
|
||||
im: Image.Image | None = None
|
||||
|
||||
try:
|
||||
bar_count: int = min(math.floor((size // pixel_ratio) / 5), 64)
|
||||
audio = AudioSegment.from_file(filepath, ext[1:]) # pyright: ignore[reportUnknownVariableType]
|
||||
bar_count: int = min(math.floor((size // dpi_scale) / 5), 64)
|
||||
audio = AudioSegment.from_file(filepath, filepath.suffix.lower()[1:]) # pyright: ignore[reportUnknownVariableType]
|
||||
data = np.frombuffer(buffer=audio._data, dtype=np.int16)
|
||||
data_indices = np.linspace(1, len(data), num=bar_count * samples_per_bar)
|
||||
bar_margin: float = ((size_scaled / (bar_count * 3)) * base_scale) / 2
|
||||
|
||||
@@ -39,13 +39,17 @@ def raster_image_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
if filepath.suffix.lower() == ".exr":
|
||||
return exr_image_thumb(filepath)
|
||||
|
||||
with filepath.open("rb") as file:
|
||||
im = image_from_bytes(BytesIO(file.read()))
|
||||
except (
|
||||
FileNotFoundError,
|
||||
UnidentifiedImageError,
|
||||
DecompressionBombError,
|
||||
FileNotFoundError,
|
||||
NotImplementedError,
|
||||
OSError,
|
||||
UnidentifiedImageError,
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
@@ -100,8 +104,8 @@ def raw_image_thumb(filepath: Path) -> Image.Image | None:
|
||||
)
|
||||
except (
|
||||
DecompressionBombError,
|
||||
LibRawIOError,
|
||||
LibRawFileUnsupportedError,
|
||||
LibRawIOError,
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
|
||||
@@ -53,3 +53,34 @@ def text_thumb(filepath: Path) -> Image.Image | None:
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
|
||||
|
||||
def code_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Render a thumbnail for a plaintext file.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
im: Image.Image | None = None
|
||||
|
||||
bg_color: str = "#000000"
|
||||
fg_color: str = "#00FF00"
|
||||
|
||||
try:
|
||||
encoding = detect_char_encoding(filepath)
|
||||
with open(filepath, encoding=encoding) as text_file:
|
||||
text = text_file.read(256)
|
||||
bg = Image.new("RGB", (256, 256), color=bg_color)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text((16, 16), text, fill=fg_color)
|
||||
im = bg
|
||||
except (
|
||||
UnidentifiedImageError,
|
||||
cv2.error,
|
||||
DecompressionBombError,
|
||||
UnicodeDecodeError,
|
||||
OSError,
|
||||
FileNotFoundError,
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
|
||||
Reference in New Issue
Block a user