diff --git a/docs/preview-support.md b/docs/preview-support.md index 7565fbc21..dbda26220 100644 --- a/docs/preview-support.md +++ b/docs/preview-support.md @@ -83,7 +83,7 @@ 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" } | | Clip Studio Paint | `.clip` | Embedded thumbnail | | Keynote (Apple iWork) | `.key` | Embedded thumbnail | @@ -98,6 +98,7 @@ Preview support for office documents or well-known project file formats varies b | Paint.NET | `.pdn` | Embedded thumbnail | | PDF | `.pdf` | First page render | | Photoshop | `.psd` | Flattened image render | +| Pixelmator Pro (Apple iWork) | `.pxd` | Embedded thumbnail | | PowerPoint (Microsoft Office) | `.pptx`, `.ppt` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } | ### :material-archive: Archives diff --git a/src/tagstudio/core/media_types.py b/src/tagstudio/core/media_types.py index db62d7807..12f6ae8aa 100644 --- a/src/tagstudio/core/media_types.py +++ b/src/tagstudio/core/media_types.py @@ -12,14 +12,14 @@ logger = structlog.get_logger(__name__) FILETYPE_EQUIVALENTS = [ - set(["aif", "aiff", "aifc"]), - set(["html", "htm", "xhtml", "shtml", "dhtml"]), - set(["jfif", "jpeg_large", "jpeg", "jpg_large", "jpg"]), - set(["json", "jsonc", "json5"]), - set(["md", "markdown", "mkd", "rmd"]), - set(["tar.gz", "tgz"]), - set(["xml", "xul"]), - set(["yaml", "yml"]), + {"aif", "aiff", "aifc"}, + {"html", "htm", "xhtml", "shtml", "dhtml"}, + {"jfif", "jpeg_large", "jpeg", "jpg_large", "jpg"}, + {"json", "jsonc", "json5"}, + {"md", "markdown", "mkd", "rmd"}, + {"tar.gz", "tgz"}, + {"xml", "xul"}, + {"yaml", "yml"}, ] @@ -75,7 +75,7 @@ class MediaCategory: extensions (set[str]): The set of file extensions associated with this category. Includes leading ".", all lowercase, and does not need to be unique to this category. - is_iana (bool): Represents whether or not this is an IANA registered category. + is_iana (bool): Represents whether this is an IANA registered category. """ media_type: MediaType @@ -257,6 +257,7 @@ class MediaCategories: ".odt", ".pages", ".pdf", + ".pxd", ".rtf", ".tex", ".wpd", @@ -336,7 +337,7 @@ class MediaCategories: ".webp", } _INSTALLER_SET: set[str] = {".appx", ".msi", ".msix"} - _IWORK_SET: set[str] = {".key", ".pages", ".numbers"} + _IWORK_SET: set[str] = {".key", ".numbers", ".pages"} _MATERIAL_SET: set[str] = {".mtl"} _MDIPACK_SET: set[str] = {".mdp"} _MODEL_SET: set[str] = {".3ds", ".fbx", ".obj", ".stl"} diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index f3471e5f1..13ae74756 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -623,7 +623,7 @@ def _apply_edge( image (Image.Image): The image to apply the edge to. edge (tuple[Image.Image, Image.Image]): The edge images to apply. Item 0 is the inner highlight, and item 1 is the outer shadow. - faded (bool): Whether or not to apply a faded version of the edge. + faded (bool): Whether to apply a faded version of the edge. Used for light themes. """ opacity: float = 1.0 if not faded else 0.8 @@ -847,7 +847,7 @@ def _open_doc_thumb(filepath: Path) -> Image.Image | None: @staticmethod def _krita_thumb(filepath: Path) -> Image.Image | None: - """Extract and render a thumbnail for an Krita file. + """Extract and render a thumbnail for a Krita file. Args: filepath (Path): The path of the file. @@ -1218,14 +1218,18 @@ def _image_vector_thumb(filepath: Path, size: int) -> Image.Image: return im @staticmethod - def _iwork_thumb(filepath: Path) -> Image.Image | None: - """Extract and render a thumbnail for an Apple iWork (Pages, Numbers, Keynote) file. + def _apple_embedded_thumb(filepath: Path) -> Image.Image | None: + """Extract and render an apple embedded thumbnail (iWork, Apple Creative Studio). Args: filepath (Path): The path of the file. """ - preview_thumb_dir = "preview.jpg" - quicklook_thumb_dir = "QuickLook/Thumbnail.jpg" + thumb_files: list[str] = [ + "preview.jpg", + "QuickLook/Preview.heic", + "QuickLook/Thumbnail.jpg", + "QuickLook/Thumbnail.heic", + ] im: Image.Image | None = None def get_image(path: str) -> Image.Image | None: @@ -1240,10 +1244,10 @@ def get_image(path: str) -> Image.Image | None: thumb: Image.Image | None = None # Check if the file exists in the zip - if preview_thumb_dir in zip_file.namelist(): - thumb = get_image(preview_thumb_dir) - elif quicklook_thumb_dir in zip_file.namelist(): - thumb = get_image(quicklook_thumb_dir) + for thumb_file in thumb_files: + if thumb_file in zip_file.namelist(): + thumb = get_image(thumb_file) + break else: logger.error("Couldn't render thumbnail", filepath=filepath) @@ -1314,7 +1318,7 @@ def _pdf_thumb(filepath: Path, size: int) -> Image.Image | None: page_size *= size / page_size.height() else: page_size *= size / page_size.width() - # Enlarge image for antialiasing + # Enlarge image for anti-aliasing scale_factor = 2.5 page_size *= scale_factor # Render image with no anti-aliasing for speed @@ -1865,8 +1869,11 @@ def _render( ): image = self._open_doc_thumb(_filepath) # Apple iWork Suite ============================================ - elif MediaCategories.is_ext_in_category(ext, MediaCategories.IWORK_TYPES): - image = self._iwork_thumb(_filepath) + elif ( + MediaCategories.is_ext_in_category(ext, MediaCategories.IWORK_TYPES) + or ext == ".pxd" + ): + image = self._apple_embedded_thumb(_filepath) # Plain Text =================================================== elif MediaCategories.is_ext_in_category( ext, MediaCategories.PLAINTEXT_TYPES, mime_fallback=True