Skip to content

Commit cee42df

Browse files
committed
feat: render .mdp thumbnails.
1 parent 1981b13 commit cee42df

File tree

3 files changed

+71
-14
lines changed

3 files changed

+71
-14
lines changed

docs/library/index.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,21 @@ Audio thumbnails will default to embedded cover art (if any) andfallback to gene
8686

8787
Preview support for office documents or well-known project file formats varies by the format and whether or not embedded thumbnails are available to be read from. OpenDocument-based files are typically supported.
8888

89-
| Filetype | Extensions | Preview Type |
90-
| ----------------------------- | --------------------- | -------------------------------------------------------------------------- |
91-
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
92-
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
93-
| Krita[^3] | `.kra`, `.krz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
94-
| MuseScore | `.mscz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
95-
| Numbers (Apple iWork) | `.numbers` | Embedded thumbnail |
96-
| OpenDocument Presentation | `.odp`, `.fodp` | Embedded thumbnail |
97-
| OpenDocument Spreadsheet | `.ods`, `.fods` | Embedded thumbnail |
98-
| OpenDocument Text | `.odt`, `.fodt` | Embedded thumbnail |
99-
| Pages (Apple iWork) | `.pages` | Embedded thumbnail |
100-
| PDF | `.pdf` | First page render |
101-
| Photoshop | `.psd` | Flattened image render |
102-
| PowerPoint (Microsoft Office) | `.pptx`, `.ppt` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
89+
| Filetype | Extensions | Preview Type |
90+
| ------------------------------------ | --------------------- | -------------------------------------------------------------------------- |
91+
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
92+
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
93+
| Krita[^3] | `.kra`, `.krz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
94+
| Mdipack (FireAlpaca, Medibang Paint) | `.mdp` | Embedded thumbnail |
95+
| MuseScore | `.mscz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
96+
| Numbers (Apple iWork) | `.numbers` | Embedded thumbnail |
97+
| OpenDocument Presentation | `.odp`, `.fodp` | Embedded thumbnail |
98+
| OpenDocument Spreadsheet | `.ods`, `.fods` | Embedded thumbnail |
99+
| OpenDocument Text | `.odt`, `.fodt` | Embedded thumbnail |
100+
| Pages (Apple iWork) | `.pages` | Embedded thumbnail |
101+
| PDF | `.pdf` | First page render |
102+
| Photoshop | `.psd` | Flattened image render |
103+
| PowerPoint (Microsoft Office) | `.pptx`, `.ppt` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
103104

104105
### :material-book: eBooks
105106

src/tagstudio/core/media_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class MediaType(str, Enum):
4646
INSTALLER = "installer"
4747
IWORK = "iwork"
4848
MATERIAL = "material"
49+
MDIPACK = "mdipack"
4950
MODEL = "model"
5051
OPEN_DOCUMENT = "open_document"
5152
PACKAGE = "package"
@@ -335,6 +336,7 @@ class MediaCategories:
335336
_INSTALLER_SET: set[str] = {".appx", ".msi", ".msix"}
336337
_IWORK_SET: set[str] = {".key", ".pages", ".numbers"}
337338
_MATERIAL_SET: set[str] = {".mtl"}
339+
_MDIPACK_SET: set[str] = {".mdp"}
338340
_MODEL_SET: set[str] = {".3ds", ".fbx", ".obj", ".stl"}
339341
_OPEN_DOCUMENT_SET: set[str] = {
340342
".fodg",
@@ -536,6 +538,12 @@ class MediaCategories:
536538
is_iana=False,
537539
name="material",
538540
)
541+
MDIPACK_TYPES = MediaCategory(
542+
media_type=MediaType.MDIPACK,
543+
extensions=_MDIPACK_SET,
544+
is_iana=False,
545+
name="mdipack",
546+
)
539547
MODEL_TYPES = MediaCategory(
540548
media_type=MediaType.MODEL,
541549
extensions=_MODEL_SET,
@@ -640,6 +648,7 @@ class MediaCategories:
640648
INSTALLER_TYPES,
641649
IWORK_TYPES,
642650
MATERIAL_TYPES,
651+
MDIPACK_TYPES,
643652
MODEL_TYPES,
644653
OPEN_DOCUMENT_TYPES,
645654
PACKAGE_TYPES,

src/tagstudio/qt/previews/renderer.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import hashlib
88
import math
99
import os
10+
import struct
1011
import tarfile
1112
import xml.etree.ElementTree as ET
1213
import zipfile
14+
import zlib
1315
from copy import deepcopy
1416
from io import BytesIO
1517
from pathlib import Path
@@ -1378,6 +1380,48 @@ def _video_thumb(filepath: Path) -> Image.Image | None:
13781380
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
13791381
return im
13801382

1383+
@staticmethod
1384+
def _mdp_thumb(filepath: Path) -> Image.Image | None:
1385+
"""Extract the thumbnail from a .mdp file.
1386+
1387+
Args:
1388+
filepath (Path): The path of the .mdp file.
1389+
1390+
Returns:
1391+
Image: The embedded thumbnail.
1392+
"""
1393+
im: Image.Image | None = None
1394+
try:
1395+
with open(filepath, "rb") as f:
1396+
magic = struct.unpack("<7sx", f.read(8))[0]
1397+
if magic != b"mdipack":
1398+
return im
1399+
1400+
bin_header = struct.unpack("<LLL", f.read(12))
1401+
xml_header = ET.fromstring(f.read(bin_header[1]))
1402+
mdibin_count = len(xml_header.findall("./*Layer")) + 1
1403+
for _ in range(mdibin_count):
1404+
pac_header = struct.unpack("<3sxLLLL48s64s", f.read(132))
1405+
if not pac_header[6].startswith(b"thumb"):
1406+
f.seek(pac_header[3], os.SEEK_CUR)
1407+
continue
1408+
1409+
thumb_element = unwrap(xml_header.find("Thumb"))
1410+
dimensions = (
1411+
int(unwrap(thumb_element.get("width"))),
1412+
int(unwrap(thumb_element.get("height"))),
1413+
)
1414+
thumb_blob = f.read(pac_header[3])
1415+
if pac_header[2] == 1:
1416+
thumb_blob = zlib.decompress(thumb_blob, bufsize=pac_header[4])
1417+
1418+
im = Image.frombytes("RGBA", dimensions, thumb_blob, "raw", "BGRA")
1419+
break
1420+
except Exception as e:
1421+
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
1422+
1423+
return im
1424+
13811425
def render(
13821426
self,
13831427
timestamp: float,
@@ -1704,6 +1748,9 @@ def _render(
17041748
ext, MediaCategories.PDF_TYPES, mime_fallback=True
17051749
):
17061750
image = self._pdf_thumb(_filepath, adj_size)
1751+
# MDIPACK ======================================================
1752+
elif MediaCategories.is_ext_in_category(ext, MediaCategories.MDIPACK_TYPES):
1753+
image = self._mdp_thumb(_filepath)
17071754
# No Rendered Thumbnail ========================================
17081755
if not image:
17091756
raise NoRendererError

0 commit comments

Comments
 (0)