Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/library/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Preview support for office documents or well-known project file formats varies b
| 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 |
| 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" } |
Expand Down
9 changes: 9 additions & 0 deletions src/tagstudio/core/media_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class MediaType(str, Enum):
AUDIO_MIDI = "audio_midi"
AUDIO = "audio"
BLENDER = "blender"
CLIP_STUDIO_PAINT = "clip_studio_paint"
CODE = "code"
DATABASE = "database"
DISK_IMAGE = "disk_image"
Expand Down Expand Up @@ -175,6 +176,7 @@ class MediaCategories:
".blend31",
".blend32",
}
_CLIP_STUDIO_PAINT_SET: set[str] = {".clip"}
_CODE_SET: set[str] = {
".bat",
".cfg",
Expand Down Expand Up @@ -452,6 +454,12 @@ class MediaCategories:
is_iana=False,
name="blender",
)
CLIP_STUDIO_PAINT_TYPES = MediaCategory(
media_type=MediaType.CLIP_STUDIO_PAINT,
extensions=_CLIP_STUDIO_PAINT_SET,
is_iana=False,
name="clip studio paint",
)
CODE_TYPES = MediaCategory(
media_type=MediaType.CODE,
extensions=_CODE_SET,
Expand Down Expand Up @@ -628,6 +636,7 @@ class MediaCategories:
AUDIO_MIDI_TYPES,
AUDIO_TYPES,
BLENDER_TYPES,
CLIP_STUDIO_PAINT_TYPES,
DATABASE_TYPES,
DISK_IMAGE_TYPES,
DOCUMENT_TYPES,
Expand Down
34 changes: 34 additions & 0 deletions src/tagstudio/qt/previews/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import hashlib
import math
import os
import sqlite3
import tarfile
import xml.etree.ElementTree as ET
import zipfile
Expand Down Expand Up @@ -1378,6 +1379,34 @@ def _video_thumb(filepath: Path) -> Image.Image | None:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
return im

@staticmethod
def _clip_thumb(filepath: Path) -> Image.Image | None:
"""Extract the thumbnail from the SQLite database embedded in a .clip file.

Args:
filepath (Path): The path of the .clip file.

Returns:
Image: The embedded thumbnail, if extractable.
"""
im: Image.Image | None = None
try:
with open(filepath, "rb") as f:
blob = f.read()
sqlite_index = blob.find(b"SQLite format 3")
if sqlite_index == -1:
return im

with sqlite3.connect(":memory:") as conn:
conn.deserialize(blob[sqlite_index:])
thumbnail = conn.execute("SELECT ImageData FROM CanvasPreview").fetchone()
if thumbnail:
im = Image.open(BytesIO(thumbnail[0]))
except Exception as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)

return im

def render(
self,
timestamp: float,
Expand Down Expand Up @@ -1628,6 +1657,11 @@ def _render(
ext, MediaCategories.KRITA_TYPES, mime_fallback=True
):
image = self._krita_thumb(_filepath)
# Clip Studio Paint ============================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.CLIP_STUDIO_PAINT_TYPES
):
image = self._clip_thumb(_filepath)
# VTF ==========================================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.SOURCE_ENGINE_TYPES, mime_fallback=True
Expand Down