Skip to content

Commit 1f7412b

Browse files
committed
feat: render .clip thumbnails.
1 parent 615978e commit 1f7412b

File tree

2 files changed

+42
-0
lines changed

2 files changed

+42
-0
lines changed

src/tagstudio/core/media_types.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class MediaType(str, Enum):
3333
AUDIO_MIDI = "audio_midi"
3434
AUDIO = "audio"
3535
BLENDER = "blender"
36+
CLIP_STUDIO_PAINT = "clip_studio_paint"
3637
CODE = "code"
3738
DATABASE = "database"
3839
DISK_IMAGE = "disk_image"
@@ -175,6 +176,7 @@ class MediaCategories:
175176
".blend31",
176177
".blend32",
177178
}
179+
_CLIP_STUDIO_PAINT_SET: set[str] = {".clip"}
178180
_CODE_SET: set[str] = {
179181
".bat",
180182
".cfg",
@@ -452,6 +454,12 @@ class MediaCategories:
452454
is_iana=False,
453455
name="blender",
454456
)
457+
CLIP_STUDIO_PAINT_TYPES = MediaCategory(
458+
media_type=MediaType.CLIP_STUDIO_PAINT,
459+
extensions=_CLIP_STUDIO_PAINT_SET,
460+
is_iana=False,
461+
name="clip studio paint",
462+
)
455463
CODE_TYPES = MediaCategory(
456464
media_type=MediaType.CODE,
457465
extensions=_CODE_SET,

src/tagstudio/qt/previews/renderer.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import hashlib
88
import math
99
import os
10+
import sqlite3
1011
import tarfile
1112
import xml.etree.ElementTree as ET
1213
import zipfile
@@ -1378,6 +1379,34 @@ def _video_thumb(filepath: Path) -> Image.Image | None:
13781379
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
13791380
return im
13801381

1382+
@staticmethod
1383+
def _clip_thumb(filepath: Path) -> Image.Image | None:
1384+
"""Extract the thumbnail from the SQLite database embedded in a .clip file.
1385+
1386+
Args:
1387+
filepath (Path): The path of the .clip file.
1388+
1389+
Returns:
1390+
Image: The embedded thumbnail, if extractable.
1391+
"""
1392+
im: Image.Image | None = None
1393+
try:
1394+
with open(filepath, "rb") as f:
1395+
blob = f.read()
1396+
sqlite_index = blob.find(b"SQLite format 3")
1397+
if sqlite_index == -1:
1398+
return im
1399+
1400+
with sqlite3.connect(":memory:") as conn:
1401+
conn.deserialize(blob[sqlite_index:])
1402+
thumbnail = conn.execute("SELECT ImageData FROM CanvasPreview").fetchone()
1403+
if thumbnail:
1404+
im = Image.open(BytesIO(thumbnail[0]))
1405+
except Exception as e:
1406+
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
1407+
1408+
return im
1409+
13811410
def render(
13821411
self,
13831412
timestamp: float,
@@ -1628,6 +1657,11 @@ def _render(
16281657
ext, MediaCategories.KRITA_TYPES, mime_fallback=True
16291658
):
16301659
image = self._krita_thumb(_filepath)
1660+
# Clip Studio Paint ============================================
1661+
elif MediaCategories.is_ext_in_category(
1662+
ext, MediaCategories.CLIP_STUDIO_PAINT_TYPES
1663+
):
1664+
image = self._clip_thumb(_filepath)
16311665
# VTF ==========================================================
16321666
elif MediaCategories.is_ext_in_category(
16331667
ext, MediaCategories.SOURCE_ENGINE_TYPES, mime_fallback=True

0 commit comments

Comments
 (0)