mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-25 14:54:17 +02:00
refactor: consolidate embedded archive thumbnail functions
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
|
||||
|
||||
@@ -36,17 +36,19 @@ from tagstudio.qt.global_settings import (
|
||||
from tagstudio.qt.helpers.gradients import four_corner_gradient
|
||||
from tagstudio.qt.models.palette import UI_COLORS, ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.renderers.apple import apple_embedded_thumb
|
||||
from tagstudio.renderers.archive import archive_thumb
|
||||
from tagstudio.renderers.archive import (
|
||||
apple_embedded_thumb,
|
||||
archive_thumb,
|
||||
krita_thumb,
|
||||
open_doc_thumb,
|
||||
powerpoint_thumb,
|
||||
)
|
||||
from tagstudio.renderers.audio import audio_album_thumb, audio_waveform_thumb
|
||||
from tagstudio.renderers.blender import blender
|
||||
from tagstudio.renderers.clip_studio import clip_thumb, pdn_thumb
|
||||
from tagstudio.renderers.ebook import epub_cover
|
||||
from tagstudio.renderers.font import font_long_thumb, font_short_thumb
|
||||
from tagstudio.renderers.krita import krita_thumb
|
||||
from tagstudio.renderers.medibang_paint import mdp_thumb
|
||||
from tagstudio.renderers.microsoft_office import powerpoint_thumb
|
||||
from tagstudio.renderers.open_document import open_doc_thumb
|
||||
from tagstudio.renderers.pdf import pdf_thumb
|
||||
from tagstudio.renderers.raster_image import image_exr_thumb, image_raw_thumb, image_thumb
|
||||
from tagstudio.renderers.source_engine import vtf_thumb
|
||||
@@ -58,10 +60,10 @@ if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import Image
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def apple_embedded_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render an apple embedded thumbnail (iWork, Apple Creative Studio).
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
thumb_files: list[str] = [
|
||||
"preview.jpg",
|
||||
"QuickLook/Preview.heic",
|
||||
"QuickLook/Thumbnail.jpg",
|
||||
"QuickLook/Thumbnail.heic",
|
||||
"QuickLook/Thumbnail.webp",
|
||||
"QuickLook/Icon.webp",
|
||||
]
|
||||
im: Image.Image | None = None
|
||||
|
||||
def get_image(path: str) -> Image.Image | None:
|
||||
thumb_im: Image.Image | None = None
|
||||
# Read the specific file into memory
|
||||
file_data = zip_file.read(path)
|
||||
thumb_im = Image.open(BytesIO(file_data))
|
||||
return thumb_im
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(filepath, "r") as zip_file:
|
||||
thumb: Image.Image | None = None
|
||||
|
||||
# Check if the file exists in the zip
|
||||
for thumb_file in thumb_files:
|
||||
if thumb_file in zip_file.namelist():
|
||||
thumb = get_image(thumb_file)
|
||||
break
|
||||
else:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath)
|
||||
|
||||
if thumb:
|
||||
im = Image.new("RGB", thumb.size, color="#1e1e1e")
|
||||
im.paste(thumb)
|
||||
except zipfile.BadZipFile as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
|
||||
|
||||
return im
|
||||
@@ -16,7 +16,7 @@ from PIL import Image
|
||||
|
||||
from tagstudio.core.media_types import MediaCategories
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.renderers.raster_image import load_raster_image
|
||||
from tagstudio.renderers.raster_image import image_from_bytes
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -60,27 +60,7 @@ class TarFile:
|
||||
self.tar.__exit__(*args)
|
||||
|
||||
|
||||
def archive_thumb(filepath: Path, ext: str) -> Image.Image | None:
|
||||
"""Extract the first image found in the archive.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path to the archive.
|
||||
ext (str): The file extension.
|
||||
|
||||
Returns:
|
||||
Image: The first image found in the archive.
|
||||
"""
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
with open_archive(filepath, ext) as archive:
|
||||
im = first_image(archive)
|
||||
except Exception as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
|
||||
return im
|
||||
|
||||
|
||||
def open_archive(filepath: Path, ext: str) -> Archive:
|
||||
def open_archive(filepath: Path, ext: str = "") -> Archive:
|
||||
"""Open an archive with its corresponding archiver.
|
||||
|
||||
Args:
|
||||
@@ -100,7 +80,7 @@ def open_archive(filepath: Path, ext: str) -> Archive:
|
||||
return archiver(filepath, "r")
|
||||
|
||||
|
||||
def first_image(archive: Archive) -> Image.Image | None:
|
||||
def first_image_in_archive(archive: Archive) -> Image.Image | None:
|
||||
"""Find and extract the first renderable image in the archive.
|
||||
|
||||
Args:
|
||||
@@ -113,6 +93,66 @@ def first_image(archive: Archive) -> Image.Image | None:
|
||||
ext = Path(file_name).suffix
|
||||
if MediaCategories.IMAGE_RASTER_TYPES.contains(ext):
|
||||
image_data = archive.read(file_name) # pyright: ignore[reportUnknownVariableType]
|
||||
return load_raster_image(BytesIO(image_data))
|
||||
return image_from_bytes(BytesIO(image_data))
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def archive_thumb(
|
||||
filepath: Path, ext: str = "", image_names: list[Path] | list[str] | None = None
|
||||
) -> Image.Image | None:
|
||||
"""Extract an embedded preview image from an archive.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path to the archive.
|
||||
ext (str): The file extension. Used to help determine more specific archive type.
|
||||
image_names: (list[Path] | list[str] | None): List of embedded image names to search for.
|
||||
|
||||
Returns:
|
||||
Image: The first image found in the archive.
|
||||
"""
|
||||
try:
|
||||
with open_archive(filepath, ext) as archive:
|
||||
# If no list of image names to search for was provided, default to the first image.
|
||||
if not image_names:
|
||||
return first_image_in_archive(archive)
|
||||
|
||||
for image_name in image_names:
|
||||
if image_name in archive.namelist():
|
||||
file_data = archive.read(str(image_name)) # pyright: ignore[reportUnknownVariableType]
|
||||
return image_from_bytes(BytesIO(file_data))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return None
|
||||
|
||||
|
||||
def apple_embedded_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render an apple embedded thumbnail (iWork, Apple Creative Studio)."""
|
||||
image_names: list[str] = [
|
||||
"preview.jpg",
|
||||
"QuickLook/Preview.heic",
|
||||
"QuickLook/Thumbnail.jpg",
|
||||
"QuickLook/Thumbnail.heic",
|
||||
"QuickLook/Thumbnail.webp",
|
||||
"QuickLook/Icon.webp",
|
||||
]
|
||||
return archive_thumb(filepath, image_names=image_names)
|
||||
|
||||
|
||||
def krita_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for a Krita file."""
|
||||
image_names = ["preview.png"]
|
||||
return archive_thumb(filepath, image_names=image_names)
|
||||
|
||||
|
||||
def open_doc_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for an OpenDocument file."""
|
||||
image_names = ["Thumbnails/thumbnail.png"]
|
||||
return archive_thumb(filepath, image_names=image_names)
|
||||
|
||||
|
||||
def powerpoint_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for a Microsoft PowerPoint file."""
|
||||
image_names = ["docProps/thumbnail.jpeg"]
|
||||
return archive_thumb(filepath, image_names=image_names)
|
||||
|
||||
@@ -12,8 +12,8 @@ from PIL import Image
|
||||
|
||||
from tagstudio.core.media_types import MediaCategories
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.renderers.archive import Archive, first_image, open_archive
|
||||
from tagstudio.renderers.raster_image import load_raster_image
|
||||
from tagstudio.renderers.archive import Archive, first_image_in_archive, open_archive
|
||||
from tagstudio.renderers.raster_image import image_from_bytes
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -39,7 +39,7 @@ def epub_cover(filepath: Path, ext: str) -> Image.Image | None:
|
||||
im = cover_from_comic_info(archive, comic_info, "InnerCover")
|
||||
|
||||
if not im:
|
||||
im = first_image(archive)
|
||||
im = first_image_in_archive(archive)
|
||||
except Exception as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
|
||||
@@ -68,6 +68,6 @@ def cover_from_comic_info(
|
||||
ext = Path(page_name).suffix
|
||||
if MediaCategories.IMAGE_RASTER_TYPES.contains(ext):
|
||||
image_data = archive.read(page_name) # pyright: ignore[reportUnknownVariableType]
|
||||
im = load_raster_image(BytesIO(image_data))
|
||||
im = image_from_bytes(BytesIO(image_data))
|
||||
|
||||
return im
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import Image
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def krita_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for a Krita file.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
file_path_within_zip = "preview.png"
|
||||
im: Image.Image | None = None
|
||||
with zipfile.ZipFile(filepath, "r") as zip_file:
|
||||
# Check if the file exists in the zip
|
||||
if file_path_within_zip in zip_file.namelist():
|
||||
# Read the specific file into memory
|
||||
file_data = zip_file.read(file_path_within_zip)
|
||||
thumb_im = Image.open(BytesIO(file_data))
|
||||
if thumb_im:
|
||||
im = Image.new("RGB", thumb_im.size, color="#1e1e1e")
|
||||
im.paste(thumb_im)
|
||||
else:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath)
|
||||
|
||||
return im
|
||||
@@ -1,38 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import Image
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def powerpoint_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for a Microsoft PowerPoint file.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
file_path_within_zip = "docProps/thumbnail.jpeg"
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
with zipfile.ZipFile(filepath, "r") as zip_file:
|
||||
# Check if the file exists in the zip
|
||||
if file_path_within_zip in zip_file.namelist():
|
||||
# Read the specific file into memory
|
||||
file_data = zip_file.read(file_path_within_zip)
|
||||
thumb_im = Image.open(BytesIO(file_data))
|
||||
if thumb_im:
|
||||
im = Image.new("RGB", thumb_im.size, color="#1e1e1e")
|
||||
im.paste(thumb_im)
|
||||
else:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath)
|
||||
except zipfile.BadZipFile as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
|
||||
|
||||
return im
|
||||
@@ -1,35 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import Image
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def open_doc_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for an OpenDocument file.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
file_path_within_zip = "Thumbnails/thumbnail.png"
|
||||
im: Image.Image | None = None
|
||||
with zipfile.ZipFile(filepath, "r") as zip_file:
|
||||
# Check if the file exists in the zip
|
||||
if file_path_within_zip in zip_file.namelist():
|
||||
# Read the specific file into memory
|
||||
file_data = zip_file.read(file_path_within_zip)
|
||||
thumb_im = Image.open(BytesIO(file_data))
|
||||
if thumb_im:
|
||||
im = Image.new("RGB", thumb_im.size, color="#1e1e1e")
|
||||
im.paste(thumb_im)
|
||||
else:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath)
|
||||
|
||||
return im
|
||||
@@ -1,14 +1,15 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# NOTE: This file contains Qt imports because Qt is used as the vector renderer in this case.
|
||||
# This is NOT considered part of the Qt frontend, but is technically tangled with the Qt import.
|
||||
|
||||
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import (
|
||||
Image,
|
||||
)
|
||||
from PIL import Image
|
||||
from PySide6.QtCore import QBuffer, QFile, QFileDevice, QIODeviceBase, QSizeF
|
||||
from PySide6.QtGui import QImage
|
||||
from PySide6.QtPdf import QPdfDocument, QPdfDocumentRenderOptions
|
||||
|
||||
@@ -35,7 +35,7 @@ def image_thumb(filepath: Path) -> Image.Image | None:
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
with filepath.open("rb") as file:
|
||||
im = load_raster_image(BytesIO(file.read()))
|
||||
im = image_from_bytes(BytesIO(file.read()))
|
||||
except (
|
||||
FileNotFoundError,
|
||||
UnidentifiedImageError,
|
||||
@@ -102,7 +102,7 @@ def image_raw_thumb(filepath: Path) -> Image.Image | None:
|
||||
return im
|
||||
|
||||
|
||||
def load_raster_image(image_data: BytesIO) -> Image.Image:
|
||||
def image_from_bytes(image_data: BytesIO) -> Image.Image:
|
||||
"""Load a raster image and add a background if it's transparent.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# NOTE: This file contains Qt imports because Qt is used as the vector renderer in this case.
|
||||
# This is NOT considered part of the Qt frontend, but is technically tangled with the Qt import.
|
||||
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
Reference in New Issue
Block a user