Skip to content

Commit 7ae3a6b

Browse files
authored
feat: render .clip thumbnails. (#1150)
* feat: render .clip thumbnails. * doc: document .clip support. * fix: add CLIP_STUDIO_PAINT_TYPES to ALL_CATEGORIES. * explicitly close connection.
1 parent f3bcb7c commit 7ae3a6b

File tree

3 files changed

+46
-1
lines changed

3 files changed

+46
-1
lines changed

docs/preview-support.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ Audio thumbnails will default to embedded cover art (if any) and fallback to gen
7979
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.
8080

8181
| Filetype | Extensions | Preview Type |
82-
| ------------------------------------ | --------------------- | -------------------------------------------------------------------------- |
82+
|--------------------------------------| --------------------- | -------------------------------------------------------------------------- |
8383
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
84+
| Clip Studio Paint | `.clip` | Embedded thumbnail |
8485
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
8586
| Krita[^3] | `.kra`, `.krz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
8687
| Mdipack (FireAlpaca, Medibang Paint) | `.mdp` | Embedded thumbnail |

src/tagstudio/core/media_types.py

Lines changed: 9 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"
@@ -177,6 +178,7 @@ class MediaCategories:
177178
".blend31",
178179
".blend32",
179180
}
181+
_CLIP_STUDIO_PAINT_SET: set[str] = {".clip"}
180182
_CODE_SET: set[str] = {
181183
".bat",
182184
".cfg",
@@ -456,6 +458,12 @@ class MediaCategories:
456458
is_iana=False,
457459
name="blender",
458460
)
461+
CLIP_STUDIO_PAINT_TYPES = MediaCategory(
462+
media_type=MediaType.CLIP_STUDIO_PAINT,
463+
extensions=_CLIP_STUDIO_PAINT_SET,
464+
is_iana=False,
465+
name="clip studio paint",
466+
)
459467
CODE_TYPES = MediaCategory(
460468
media_type=MediaType.CODE,
461469
extensions=_CODE_SET,
@@ -644,6 +652,7 @@ class MediaCategories:
644652
AUDIO_MIDI_TYPES,
645653
AUDIO_TYPES,
646654
BLENDER_TYPES,
655+
CLIP_STUDIO_PAINT_TYPES,
647656
DATABASE_TYPES,
648657
DISK_IMAGE_TYPES,
649658
DOCUMENT_TYPES,

src/tagstudio/qt/previews/renderer.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import hashlib
99
import math
1010
import os
11+
import sqlite3
1112
import struct
1213
import tarfile
1314
import xml.etree.ElementTree as ET
@@ -1458,6 +1459,35 @@ def _pdn_thumb(filepath: Path) -> Image.Image | None:
14581459

14591460
return im
14601461

1462+
@staticmethod
1463+
def _clip_thumb(filepath: Path) -> Image.Image | None:
1464+
"""Extract the thumbnail from the SQLite database embedded in a .clip file.
1465+
1466+
Args:
1467+
filepath (Path): The path of the .clip file.
1468+
1469+
Returns:
1470+
Image: The embedded thumbnail, if extractable.
1471+
"""
1472+
im: Image.Image | None = None
1473+
try:
1474+
with open(filepath, "rb") as f:
1475+
blob = f.read()
1476+
sqlite_index = blob.find(b"SQLite format 3")
1477+
if sqlite_index == -1:
1478+
return im
1479+
1480+
with sqlite3.connect(":memory:") as conn:
1481+
conn.deserialize(blob[sqlite_index:])
1482+
thumbnail = conn.execute("SELECT ImageData FROM CanvasPreview").fetchone()
1483+
if thumbnail:
1484+
im = Image.open(BytesIO(thumbnail[0]))
1485+
conn.close()
1486+
except Exception as e:
1487+
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
1488+
1489+
return im
1490+
14611491
def render(
14621492
self,
14631493
timestamp: float,
@@ -1708,6 +1738,11 @@ def _render(
17081738
ext, MediaCategories.KRITA_TYPES, mime_fallback=True
17091739
):
17101740
image = self._krita_thumb(_filepath)
1741+
# Clip Studio Paint ============================================
1742+
elif MediaCategories.is_ext_in_category(
1743+
ext, MediaCategories.CLIP_STUDIO_PAINT_TYPES
1744+
):
1745+
image = self._clip_thumb(_filepath)
17111746
# VTF ==========================================================
17121747
elif MediaCategories.is_ext_in_category(
17131748
ext, MediaCategories.SOURCE_ENGINE_TYPES, mime_fallback=True

0 commit comments

Comments
 (0)