Skip to content

Commit d3c3e63

Browse files
CyanVoxelJorgeRuiyedpodtrzitko
authored
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>
1 parent 3d7629b commit d3c3e63

File tree

5 files changed

+69
-1
lines changed

5 files changed

+69
-1
lines changed

tagstudio/src/core/media_types.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class MediaType(str, Enum):
2323
DATABASE: str = "database"
2424
DISK_IMAGE: str = "disk_image"
2525
DOCUMENT: str = "document"
26+
EBOOK: str = "ebook"
2627
FONT: str = "font"
2728
IMAGE_ANIMATED: str = "image_animated"
2829
IMAGE_RAW: str = "image_raw"
@@ -160,6 +161,25 @@ class MediaCategories:
160161
".wpd",
161162
".wps",
162163
}
164+
_EBOOK_SET: set[str] = {
165+
".epub",
166+
# ".azw",
167+
# ".azw3",
168+
# ".cb7",
169+
# ".cba",
170+
# ".cbr",
171+
# ".cbt",
172+
# ".cbz",
173+
# ".djvu",
174+
# ".fb2",
175+
# ".ibook",
176+
# ".inf",
177+
# ".kfx",
178+
# ".lit",
179+
# ".mobi",
180+
# ".pdb"
181+
# ".prc",
182+
}
163183
_FONT_SET: set[str] = {
164184
".fon",
165185
".otf",
@@ -347,6 +367,11 @@ class MediaCategories:
347367
extensions=_DOCUMENT_SET,
348368
is_iana=False,
349369
)
370+
EBOOK_TYPES: MediaCategory = MediaCategory(
371+
media_type=MediaType.EBOOK,
372+
extensions=_EBOOK_SET,
373+
is_iana=False,
374+
)
350375
FONT_TYPES: MediaCategory = MediaCategory(
351376
media_type=MediaType.FONT,
352377
extensions=_FONT_SET,
@@ -448,6 +473,7 @@ class MediaCategories:
448473
DATABASE_TYPES,
449474
DISK_IMAGE_TYPES,
450475
DOCUMENT_TYPES,
476+
EBOOK_TYPES,
451477
FONT_TYPES,
452478
IMAGE_ANIMATED_TYPES,
453479
IMAGE_RAW_TYPES,

tagstudio/src/qt/widgets/thumb_renderer.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import math
77
import struct
8+
import zipfile
89
from copy import deepcopy
910
from io import BytesIO
1011
from pathlib import Path
@@ -616,6 +617,29 @@ def _source_engine(self, filepath: Path) -> Image.Image:
616617
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
617618
return im
618619

620+
def _epub_cover(self, filepath: Path) -> Image.Image:
621+
"""Extracts and returns the first image found in the ePub file at the given filepath.
622+
623+
Args:
624+
filepath (Path): The path to the ePub file.
625+
626+
Returns:
627+
Image: The first image found in the ePub file, or None by default.
628+
"""
629+
im: Image.Image = None
630+
try:
631+
with zipfile.ZipFile(filepath, "r") as zip_file:
632+
for file_name in zip_file.namelist():
633+
if file_name.lower().endswith(
634+
(".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg")
635+
):
636+
image_data = zip_file.read(file_name)
637+
im = Image.open(BytesIO(image_data))
638+
except Exception as e:
639+
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
640+
641+
return im
642+
619643
def _font_short_thumb(self, filepath: Path, size: int) -> Image.Image:
620644
"""Render a small font preview ("Aa") thumbnail from a font file.
621645
@@ -1045,6 +1069,11 @@ def render(
10451069
image = self._audio_waveform_thumb(_filepath, ext, adj_size, pixel_ratio)
10461070
if image is not None:
10471071
image = self._apply_overlay_color(image, UiColor.GREEN)
1072+
# Ebooks =======================================================
1073+
elif MediaCategories.is_ext_in_category(
1074+
ext, MediaCategories.EBOOK_TYPES, mime_fallback=True
1075+
):
1076+
image = self._epub_cover(_filepath)
10481077
# Blender ======================================================
10491078
elif MediaCategories.is_ext_in_category(
10501079
ext, MediaCategories.BLENDER_TYPES, mime_fallback=True
@@ -1060,7 +1089,6 @@ def render(
10601089
ext, MediaCategories.SOURCE_ENGINE_TYPES, mime_fallback=True
10611090
):
10621091
image = self._source_engine(_filepath)
1063-
10641092
# No Rendered Thumbnail ========================================
10651093
if not _filepath.exists():
10661094
raise FileNotFoundError

tagstudio/tests/fixtures/sample.epub

4.31 KB
Binary file not shown.
Loading

tagstudio/tests/qt/test_thumb_renderer.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@
1010
from syrupy.extensions.image import PNGImageSnapshotExtension
1111

1212

13+
def test_epub_preview(cwd, snapshot):
14+
file_path: Path = cwd / "fixtures" / "sample.epub"
15+
tr = ThumbRenderer()
16+
img: Image.Image = tr._epub_cover(file_path)
17+
18+
img_bytes = io.BytesIO()
19+
img.save(img_bytes, format="PNG")
20+
img_bytes.seek(0)
21+
22+
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)
23+
24+
1325
def test_pdf_preview(cwd, snapshot):
1426
file_path: Path = cwd / "fixtures" / "sample.pdf"
1527
renderer = ThumbRenderer()
@@ -18,6 +30,7 @@ def test_pdf_preview(cwd, snapshot):
1830
img_bytes = io.BytesIO()
1931
img.save(img_bytes, format="PNG")
2032
img_bytes.seek(0)
33+
2134
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)
2235

2336

@@ -29,4 +42,5 @@ def test_svg_preview(cwd, snapshot):
2942
img_bytes = io.BytesIO()
3043
img.save(img_bytes, format="PNG")
3144
img_bytes.seek(0)
45+
3246
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)

0 commit comments

Comments
 (0)