feat: render .mdp thumbnails. (#1153)

This commit is contained in:
Sola-ris
2025-12-15 18:37:07 +01:00
committed by GitHub
parent dcd48ebb12
commit 04744b224c
3 changed files with 71 additions and 15 deletions

View File

@@ -78,21 +78,22 @@ Audio thumbnails will default to embedded cover art (if any) and fallback to gen
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.
| Filetype | Extensions | Preview Type |
| ----------------------------- | --------------------- | -------------------------------------------------------------------------- |
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
| Krita[^3] | `.kra`, `.krz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| MuseScore | `.mscz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Numbers (Apple iWork) | `.numbers` | Embedded thumbnail |
| OpenDocument Presentation | `.odp`, `.fodp` | Embedded thumbnail |
| OpenDocument Spreadsheet | `.ods`, `.fods` | Embedded thumbnail |
| OpenDocument Text | `.odt`, `.fodt` | Embedded thumbnail |
| Pages (Apple iWork) | `.pages` | Embedded thumbnail |
| Paint.NET | `.pdn` | Embedded thumbnail |
| PDF | `.pdf` | First page render |
| Photoshop | `.psd` | Flattened image render |
| PowerPoint (Microsoft Office) | `.pptx`, `.ppt` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Filetype | Extensions | Preview Type |
| ------------------------------------ | --------------------- | -------------------------------------------------------------------------- |
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
| Krita[^3] | `.kra`, `.krz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Mdipack (FireAlpaca, Medibang Paint) | `.mdp` | Embedded thumbnail |
| MuseScore | `.mscz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Numbers (Apple iWork) | `.numbers` | Embedded thumbnail |
| OpenDocument Presentation | `.odp`, `.fodp` | Embedded thumbnail |
| OpenDocument Spreadsheet | `.ods`, `.fods` | Embedded thumbnail |
| OpenDocument Text | `.odt`, `.fodt` | Embedded thumbnail |
| Pages (Apple iWork) | `.pages` | Embedded thumbnail |
| Paint.NET | `.pdn` | Embedded thumbnail |
| PDF | `.pdf` | First page render |
| Photoshop | `.psd` | Flattened image render |
| PowerPoint (Microsoft Office) | `.pptx`, `.ppt` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
### :material-book: eBooks

View File

@@ -46,6 +46,7 @@ class MediaType(str, Enum):
INSTALLER = "installer"
IWORK = "iwork"
MATERIAL = "material"
MDIPACK = "mdipack"
MODEL = "model"
OPEN_DOCUMENT = "open_document"
PACKAGE = "package"
@@ -336,6 +337,7 @@ class MediaCategories:
_INSTALLER_SET: set[str] = {".appx", ".msi", ".msix"}
_IWORK_SET: set[str] = {".key", ".pages", ".numbers"}
_MATERIAL_SET: set[str] = {".mtl"}
_MDIPACK_SET: set[str] = {".mdp"}
_MODEL_SET: set[str] = {".3ds", ".fbx", ".obj", ".stl"}
_OPEN_DOCUMENT_SET: set[str] = {
".fodg",
@@ -538,6 +540,12 @@ class MediaCategories:
is_iana=False,
name="material",
)
MDIPACK_TYPES = MediaCategory(
media_type=MediaType.MDIPACK,
extensions=_MDIPACK_SET,
is_iana=False,
name="mdipack",
)
MODEL_TYPES = MediaCategory(
media_type=MediaType.MODEL,
extensions=_MODEL_SET,
@@ -648,6 +656,7 @@ class MediaCategories:
INSTALLER_TYPES,
IWORK_TYPES,
MATERIAL_TYPES,
MDIPACK_TYPES,
MODEL_TYPES,
OPEN_DOCUMENT_TYPES,
PACKAGE_TYPES,

View File

@@ -12,6 +12,7 @@ import struct
import tarfile
import xml.etree.ElementTree as ET
import zipfile
import zlib
from copy import deepcopy
from io import BytesIO
from pathlib import Path
@@ -1379,6 +1380,48 @@ class ThumbRenderer(QObject):
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
return im
@staticmethod
def _mdp_thumb(filepath: Path) -> Image.Image | None:
"""Extract the thumbnail from a .mdp file.
Args:
filepath (Path): The path of the .mdp file.
Returns:
Image: The embedded thumbnail.
"""
im: Image.Image | None = None
try:
with open(filepath, "rb") as f:
magic = struct.unpack("<7sx", f.read(8))[0]
if magic != b"mdipack":
return im
bin_header = struct.unpack("<LLL", f.read(12))
xml_header = ET.fromstring(f.read(bin_header[1]))
mdibin_count = len(xml_header.findall("./*Layer")) + 1
for _ in range(mdibin_count):
pac_header = struct.unpack("<3sxLLLL48s64s", f.read(132))
if not pac_header[6].startswith(b"thumb"):
f.seek(pac_header[3], os.SEEK_CUR)
continue
thumb_element = unwrap(xml_header.find("Thumb"))
dimensions = (
int(unwrap(thumb_element.get("width"))),
int(unwrap(thumb_element.get("height"))),
)
thumb_blob = f.read(pac_header[3])
if pac_header[2] == 1:
thumb_blob = zlib.decompress(thumb_blob, bufsize=pac_header[4])
im = Image.frombytes("RGBA", dimensions, thumb_blob, "raw", "BGRA")
break
except Exception as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
return im
@staticmethod
def _pdn_thumb(filepath: Path) -> Image.Image | None:
"""Extract the base64-encoded thumbnail from a .pdn file header.
@@ -1741,6 +1784,9 @@ class ThumbRenderer(QObject):
ext, MediaCategories.PDF_TYPES, mime_fallback=True
):
image = self._pdf_thumb(_filepath, adj_size)
# MDIPACK ======================================================
elif MediaCategories.is_ext_in_category(ext, MediaCategories.MDIPACK_TYPES):
image = self._mdp_thumb(_filepath)
# Paint.NET ====================================================
elif MediaCategories.is_ext_in_category(ext, MediaCategories.PAINT_DOT_NET_TYPES):
image = self._pdn_thumb(_filepath)