feat: add ePub thumbnail support (port #387) (#539)

* feat: add ePub thumbnail support

Co-Authored-By: Jorge Rui Da Silva Barrios <29062316+jorgerui@users.noreply.github.com>

* tests: compare epub cover against png snapshot

Co-Authored-By: yed <yedpodtrzitko@users.noreply.github.com>

* test: optimize epub test file

---------

Co-authored-by: Jorge Rui Da Silva Barrios <29062316+jorgerui@users.noreply.github.com>
Co-authored-by: yed <yedpodtrzitko@users.noreply.github.com>
This commit is contained in:
Travis Abendshien
2024-10-17 15:15:51 -07:00
committed by GitHub
parent 3d7629bc73
commit d3c3e634b9
5 changed files with 69 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ class MediaType(str, Enum):
DATABASE: str = "database"
DISK_IMAGE: str = "disk_image"
DOCUMENT: str = "document"
EBOOK: str = "ebook"
FONT: str = "font"
IMAGE_ANIMATED: str = "image_animated"
IMAGE_RAW: str = "image_raw"
@@ -160,6 +161,25 @@ class MediaCategories:
".wpd",
".wps",
}
_EBOOK_SET: set[str] = {
".epub",
# ".azw",
# ".azw3",
# ".cb7",
# ".cba",
# ".cbr",
# ".cbt",
# ".cbz",
# ".djvu",
# ".fb2",
# ".ibook",
# ".inf",
# ".kfx",
# ".lit",
# ".mobi",
# ".pdb"
# ".prc",
}
_FONT_SET: set[str] = {
".fon",
".otf",
@@ -347,6 +367,11 @@ class MediaCategories:
extensions=_DOCUMENT_SET,
is_iana=False,
)
EBOOK_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.EBOOK,
extensions=_EBOOK_SET,
is_iana=False,
)
FONT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.FONT,
extensions=_FONT_SET,
@@ -448,6 +473,7 @@ class MediaCategories:
DATABASE_TYPES,
DISK_IMAGE_TYPES,
DOCUMENT_TYPES,
EBOOK_TYPES,
FONT_TYPES,
IMAGE_ANIMATED_TYPES,
IMAGE_RAW_TYPES,

View File

@@ -5,6 +5,7 @@
import math
import struct
import zipfile
from copy import deepcopy
from io import BytesIO
from pathlib import Path
@@ -616,6 +617,29 @@ class ThumbRenderer(QObject):
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
return im
def _epub_cover(self, filepath: Path) -> Image.Image:
"""Extracts and returns the first image found in the ePub file at the given filepath.
Args:
filepath (Path): The path to the ePub file.
Returns:
Image: The first image found in the ePub file, or None by default.
"""
im: Image.Image = None
try:
with zipfile.ZipFile(filepath, "r") as zip_file:
for file_name in zip_file.namelist():
if file_name.lower().endswith(
(".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg")
):
image_data = zip_file.read(file_name)
im = Image.open(BytesIO(image_data))
except Exception as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
return im
def _font_short_thumb(self, filepath: Path, size: int) -> Image.Image:
"""Render a small font preview ("Aa") thumbnail from a font file.
@@ -1045,6 +1069,11 @@ class ThumbRenderer(QObject):
image = self._audio_waveform_thumb(_filepath, ext, adj_size, pixel_ratio)
if image is not None:
image = self._apply_overlay_color(image, UiColor.GREEN)
# Ebooks =======================================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.EBOOK_TYPES, mime_fallback=True
):
image = self._epub_cover(_filepath)
# Blender ======================================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.BLENDER_TYPES, mime_fallback=True
@@ -1060,7 +1089,6 @@ class ThumbRenderer(QObject):
ext, MediaCategories.SOURCE_ENGINE_TYPES, mime_fallback=True
):
image = self._source_engine(_filepath)
# No Rendered Thumbnail ========================================
if not _filepath.exists():
raise FileNotFoundError

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -10,6 +10,18 @@ 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()
@@ -18,6 +30,7 @@ def test_pdf_preview(cwd, snapshot):
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)
@@ -29,4 +42,5 @@ def test_svg_preview(cwd, snapshot):
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)