feat: add OpenDocument thumbnail support (port #366) (#545)

* feat: add OpenDocument thumbnail support

Co-Authored-By: Josh Beatty <joshuatb6@gmail.com>

* tests: add test comparing odt to png snapshot

* tests: add test comparing ods to png snapshot

* test: combine OpenDocument tests

* test: combine compatible preview tests

* test: combine preview render tests

* fix: update test snapshots

---------

Co-authored-by: Josh Beatty <joshuatb6@gmail.com>
This commit is contained in:
Travis Abendshien
2024-11-07 15:00:51 -08:00
committed by GitHub
parent c7171c5455
commit 96026b66bc
10 changed files with 83 additions and 31 deletions

View File

@@ -32,6 +32,7 @@ class MediaType(str, Enum):
INSTALLER: str = "installer"
MATERIAL: str = "material"
MODEL: str = "model"
OPEN_DOCUMENT: str = "open_document"
PACKAGE: str = "package"
PDF: str = "pdf"
PLAINTEXT: str = "plaintext"
@@ -234,6 +235,18 @@ class MediaCategories:
_INSTALLER_SET: set[str] = {".appx", ".msi", ".msix"}
_MATERIAL_SET: set[str] = {".mtl"}
_MODEL_SET: set[str] = {".3ds", ".fbx", ".obj", ".stl"}
_OPEN_DOCUMENT_SET: set[str] = {
".fodg",
".fodp",
".fods",
".fodt",
".mscz",
".odf",
".odg",
".odp",
".ods",
".odt",
}
_PACKAGE_SET: set[str] = {
".aab",
".akp",
@@ -417,6 +430,11 @@ class MediaCategories:
extensions=_MODEL_SET,
is_iana=True,
)
OPEN_DOCUMENT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.OPEN_DOCUMENT,
extensions=_OPEN_DOCUMENT_SET,
is_iana=False,
)
PACKAGE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.PACKAGE,
extensions=_PACKAGE_SET,
@@ -487,6 +505,7 @@ class MediaCategories:
INSTALLER_TYPES,
MATERIAL_TYPES,
MODEL_TYPES,
OPEN_DOCUMENT_TYPES,
PACKAGE_TYPES,
PDF_TYPES,
PLAINTEXT_TYPES,

View File

@@ -617,7 +617,31 @@ class ThumbRenderer(QObject):
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
return im
def _epub_cover(self, filepath: Path) -> Image.Image:
@classmethod
def _open_doc_thumb(cls, filepath: Path) -> Image.Image:
"""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
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
@classmethod
def _epub_cover(cls, filepath: Path) -> Image.Image:
"""Extracts and returns the first image found in the ePub file at the given filepath.
Args:
@@ -780,7 +804,8 @@ class ThumbRenderer(QObject):
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
return im
def _image_vector_thumb(self, filepath: Path, size: int) -> Image.Image:
@classmethod
def _image_vector_thumb(cls, filepath: Path, size: int) -> Image.Image:
"""Render a thumbnail for a vector image, such as SVG.
Args:
@@ -848,7 +873,8 @@ class ThumbRenderer(QObject):
return im
def _pdf_thumb(self, filepath: Path, size: int) -> Image.Image:
@classmethod
def _pdf_thumb(cls, filepath: Path, size: int) -> Image.Image:
"""Render a thumbnail for a PDF file.
filepath (Path): The path of the file.
@@ -1045,6 +1071,11 @@ class ThumbRenderer(QObject):
ext, MediaCategories.VIDEO_TYPES, mime_fallback=True
):
image = self._video_thumb(_filepath)
# OpenDocument/OpenOffice ======================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.OPEN_DOCUMENT_TYPES, mime_fallback=True
):
image = self._open_doc_thumb(_filepath)
# Plain Text ===================================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.PLAINTEXT_TYPES, mime_fallback=True

BIN
tagstudio/tests/fixtures/sample.ods vendored Normal file

Binary file not shown.

BIN
tagstudio/tests/fixtures/sample.odt vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -3,41 +3,43 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import io
from functools import partial
from pathlib import Path
import pytest
from PIL import Image
from src.qt.widgets.thumb_renderer import ThumbRenderer
from syrupy.extensions.image import PNGImageSnapshotExtension
def test_epub_preview(cwd, snapshot):
file_path: Path = cwd / "fixtures" / "sample.epub"
tr = ThumbRenderer()
img: Image.Image = tr._epub_cover(file_path)
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)
def test_pdf_preview(cwd, snapshot):
file_path: Path = cwd / "fixtures" / "sample.pdf"
renderer = ThumbRenderer()
img: Image.Image = renderer._pdf_thumb(file_path, 200)
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)
def test_svg_preview(cwd, snapshot):
file_path: Path = cwd / "fixtures" / "sample.svg"
renderer = ThumbRenderer()
img: Image.Image = renderer._image_vector_thumb(file_path, 200)
@pytest.mark.parametrize(
["fixture_file", "thumbnailer"],
[
(
"sample.odt",
ThumbRenderer._open_doc_thumb,
),
(
"sample.ods",
ThumbRenderer._open_doc_thumb,
),
(
"sample.epub",
ThumbRenderer._epub_cover,
),
(
"sample.pdf",
partial(ThumbRenderer._pdf_thumb, size=200),
),
(
"sample.svg",
partial(ThumbRenderer._image_vector_thumb, size=200),
),
],
)
def test_preview_render(cwd, fixture_file, thumbnailer, snapshot):
file_path: Path = cwd / "fixtures" / fixture_file
img: Image.Image = thumbnailer(file_path)
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")