Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -412,6 +425,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 @@ -482,6 +500,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