From 811dfba3213febae4241435790e589d32bc8609e Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Wed, 24 Sep 2025 22:20:06 +0200 Subject: [PATCH 1/4] feat: render .pdn thumbnails. --- src/tagstudio/core/media_types.py | 10 ++++++- src/tagstudio/qt/previews/renderer.py | 43 +++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/tagstudio/core/media_types.py b/src/tagstudio/core/media_types.py index 8659c389d..0cf969e61 100644 --- a/src/tagstudio/core/media_types.py +++ b/src/tagstudio/core/media_types.py @@ -49,6 +49,7 @@ class MediaType(str, Enum): MODEL = "model" OPEN_DOCUMENT = "open_document" PACKAGE = "package" + PAINT_DOT_NET = "paint_dot_net" PDF = "pdf" PLAINTEXT = "plaintext" PRESENTATION = "presentation" @@ -358,6 +359,7 @@ class MediaCategories: ".pkg", ".xapk", } + _PAINT_DOT_NET_SET: set[str] = {".pdn"} _PDF_SET: set[str] = {".pdf"} _PLAINTEXT_SET: set[str] = { ".csv", @@ -554,6 +556,12 @@ class MediaCategories: is_iana=False, name="package", ) + PAINT_DOT_NET_TYPES = MediaCategory( + media_type=MediaType.PAINT_DOT_NET, + extensions=_PAINT_DOT_NET_SET, + is_iana=False, + name="paint.net", + ) PDF_TYPES = MediaCategory( media_type=MediaType.PDF, extensions=_PDF_SET, @@ -679,7 +687,7 @@ def is_ext_in_category(ext: str, media_cat: MediaCategory, mime_fallback: bool = Args: ext (str): File extension with a leading "." and in all lowercase. - media_cat (MediaCategory): The MediaCategory to to check for extension membership. + media_cat (MediaCategory): The MediaCategory to check for extension membership. mime_fallback (bool): Flag to guess MIME type if no set matches are made. """ return media_cat.contains(ext, mime_fallback) diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index f47d534ac..2bc391be1 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -3,10 +3,12 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio +import base64 import contextlib import hashlib import math import os +import struct import tarfile import xml.etree.ElementTree as ET import zipfile @@ -1378,6 +1380,38 @@ def _video_thumb(filepath: Path) -> Image.Image | None: 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. + + Args: + filepath (Path): The path of the .pdn file. + + Returns: + Image: the decoded PNG thumbnail or None by default. + """ + im: Image.Image | None = None + with open(filepath, "rb") as f: + try: + # First 4 bytes are the magic number + if f.read(4) != b"PDN3": + return im + + # Header length is a little-endian 24-bit int + header_size = struct.unpack(" Date: Fri, 26 Sep 2025 16:34:06 +0200 Subject: [PATCH 2/4] fix: add background to .pdn thumbnail --- src/tagstudio/qt/previews/renderer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index 2bc391be1..c07a438df 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -1407,6 +1407,10 @@ def _pdn_thumb(filepath: Path) -> Image.Image | None: if encoded_png: decoded_png = base64.b64decode(encoded_png) im = Image.open(BytesIO(decoded_png)) + if im.mode == "RGBA": + new_bg = Image.new("RGB", im.size, color="#1e1e1e") + new_bg.paste(im, mask=im.getchannel(3)) + im = new_bg except Exception as e: logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__) From a058663ee8355a815a7b49c02eeeaaed52346fb7 Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:36:34 +0200 Subject: [PATCH 3/4] doc: document .pdn support. --- docs/preview-support.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/preview-support.md b/docs/preview-support.md index e7d22a43c..b1fb541f2 100644 --- a/docs/preview-support.md +++ b/docs/preview-support.md @@ -89,6 +89,7 @@ Preview support for office documents or well-known project file formats varies b | 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" } | From b3eb7c7894ae8ba0f2fc7213ca9a03a4b3886696 Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Sun, 28 Sep 2025 14:46:13 +0200 Subject: [PATCH 4/4] fix: add PAINT_DOT_NET_TYPES to ALL_CATEGORIES. --- src/tagstudio/core/media_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tagstudio/core/media_types.py b/src/tagstudio/core/media_types.py index 0cf969e61..e7136500d 100644 --- a/src/tagstudio/core/media_types.py +++ b/src/tagstudio/core/media_types.py @@ -651,6 +651,7 @@ class MediaCategories: MODEL_TYPES, OPEN_DOCUMENT_TYPES, PACKAGE_TYPES, + PAINT_DOT_NET_TYPES, PDF_TYPES, PLAINTEXT_TYPES, PRESENTATION_TYPES,