Skip to content

Commit

Permalink
feat: add OpenDocument thumbnail support (port TagStudioDev#366) (Tag…
Browse files Browse the repository at this point in the history
…StudioDev#545)

* feat: add OpenDocument thumbnail support

Co-Authored-By: Josh Beatty <[email protected]>

* 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 <[email protected]>
  • Loading branch information
CyanVoxel and Joshua-Beatty authored Nov 7, 2024
1 parent c7171c5 commit 96026b6
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 31 deletions.
19 changes: 19 additions & 0 deletions tagstudio/src/core/media_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -487,6 +505,7 @@ class MediaCategories:
INSTALLER_TYPES,
MATERIAL_TYPES,
MODEL_TYPES,
OPEN_DOCUMENT_TYPES,
PACKAGE_TYPES,
PDF_TYPES,
PLAINTEXT_TYPES,
Expand Down
37 changes: 34 additions & 3 deletions tagstudio/src/qt/widgets/thumb_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,31 @@ def _source_engine(self, filepath: Path) -> Image.Image:
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:
Expand Down Expand Up @@ -780,7 +804,8 @@ def _image_thumb(self, filepath: Path) -> Image.Image:
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:
Expand Down Expand Up @@ -848,7 +873,8 @@ def _model_stl_thumb(self, filepath: Path, size: int) -> Image.Image:

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.
Expand Down Expand Up @@ -1045,6 +1071,11 @@ def render(
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
Expand Down
Binary file added tagstudio/tests/fixtures/sample.ods
Binary file not shown.
Binary file added tagstudio/tests/fixtures/sample.odt
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 30 additions & 28 deletions tagstudio/tests/qt/test_thumb_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 96026b6

Please sign in to comment.