From f21d49df7fd2cf569b0aed7c3cb8ce6196a44ba6 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:12:57 -0800 Subject: [PATCH] feat: add JXL thumbnail and animated APNG + WEBP support (port #344 and partially port #357) (#549) * feat: add JXL image thumbnail support Co-Authored-By: BPplays <58504799+bpplays@users.noreply.github.com> * feat: add animated previews for webp and apng Co-Authored-By: BPplays <58504799+bpplays@users.noreply.github.com> --------- Co-authored-by: BPplays <58504799+bpplays@users.noreply.github.com> --- requirements.txt | 33 +++++++++---------- tagstudio/src/core/media_types.py | 1 - tagstudio/src/qt/widgets/preview_panel.py | 37 +++++++++++++++------- tagstudio/src/qt/widgets/thumb_renderer.py | 1 + 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/requirements.txt b/requirements.txt index f2b7a90c..e1dff2fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,20 @@ -humanfriendly==10.0 -opencv_python>=4.8.0.74,<=4.9.0.80 -Pillow==10.3.0 -PySide6==6.7.1 -PySide6_Addons==6.7.1 -PySide6_Essentials==6.7.1 -typing_extensions>=3.10.0.0,<=4.11.0 -ujson>=5.8.0,<=5.9.0 -numpy==1.26.4 -rawpy==0.21.0 -pillow-heif==0.16.0 chardet==5.2.0 -structlog==24.4.0 -SQLAlchemy==2.0.34 -pydub==0.25.1 +ffmpeg-python==0.2.0 +humanfriendly==10.0 mutagen==1.47.0 numpy==1.26.4 -ffmpeg-python==0.2.0 -vtf2img==0.1.0 +numpy==1.26.4 +opencv_python>=4.8.0.74,<=4.9.0.80 +pillow-heif==0.16.0 +pillow-jxl-plugin==1.2.6 +Pillow==10.3.0 +pydub==0.25.1 +PySide6_Addons==6.7.1 +PySide6_Essentials==6.7.1 +PySide6==6.7.1 +rawpy==0.21.0 +SQLAlchemy==2.0.34 +structlog==24.4.0 +typing_extensions>=3.10.0.0,<=4.11.0 +ujson>=5.8.0,<=5.9.0 +vtf2img==0.1.0 \ No newline at end of file diff --git a/tagstudio/src/core/media_types.py b/tagstudio/src/core/media_types.py index c07299a7..b88921f3 100644 --- a/tagstudio/src/core/media_types.py +++ b/tagstudio/src/core/media_types.py @@ -193,7 +193,6 @@ class MediaCategories: ".apng", ".gif", ".webp", - ".jxl", } _IMAGE_RAW_SET: set[str] = { ".arw", diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 7d055a3a..90a26265 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -1,6 +1,8 @@ # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio + +import io import os import platform import sys @@ -601,19 +603,32 @@ class PreviewPanel(QWidget): # TODO: Do this all somewhere else, this is just here temporarily. ext: str = filepath.suffix.lower() try: - if filepath.suffix.lower() in [".gif"]: - with open(filepath, mode="rb") as file: - if self.preview_gif.movie(): - self.preview_gif.movie().stop() - self.gif_buffer.close() + if MediaCategories.is_ext_in_category( + ext, MediaCategories.IMAGE_ANIMATED_TYPES, mime_fallback=True + ): + if self.preview_gif.movie(): + self.preview_gif.movie().stop() + self.gif_buffer.close() - ba = file.read() - self.gif_buffer.setData(ba) - movie = QMovie(self.gif_buffer, QByteArray()) - self.preview_gif.setMovie(movie) - movie.start() + image: Image.Image = Image.open(filepath) + anim_image: Image.Image = image + image_bytes_io: io.BytesIO = io.BytesIO() + anim_image.save( + image_bytes_io, + "GIF", + lossless=True, + save_all=True, + loop=0, + disposal=2, + ) + image_bytes_io.seek(0) + ba: bytes = image_bytes_io.read() + + self.gif_buffer.setData(ba) + movie = QMovie(self.gif_buffer, QByteArray()) + self.preview_gif.setMovie(movie) + movie.start() - image = Image.open(str(filepath)) self.resizeEvent( QResizeEvent( QSize(image.width, image.height), diff --git a/tagstudio/src/qt/widgets/thumb_renderer.py b/tagstudio/src/qt/widgets/thumb_renderer.py index 348ab66b..be5d8899 100644 --- a/tagstudio/src/qt/widgets/thumb_renderer.py +++ b/tagstudio/src/qt/widgets/thumb_renderer.py @@ -12,6 +12,7 @@ from pathlib import Path import cv2 import numpy as np +import pillow_jxl # noqa: F401 import rawpy import structlog from mutagen import MutagenError, flac, id3, mp4