CLIPSTUDIO PAINTのファイルからサムネイル画像を取得

.clipファイルからサムネを取得して、自作の管理ソフトに使うために試行錯誤したので私的メモ。

# 使用ツール

 - Python3(3.8.0 64bit)
 - VSCode(1.40.1)


# 参考サイト

 - [CLIP STUDIO PAINTの.lipファイルをハックして作業動画を書き出すWindowsアプリを作った](https://tech.recruit-mp.co.jp/etc/building-lip-to-animated-gif-converter-named-lip2gif/)
 - [イラストを Git で管理したかったのでツールをつくった](https://syfm.hatenablog.com/entry/2016/10/25/183044)
 - [CLIP STUDIO PAINTで作成した漫画データのサムネール出力の為の調査](https://qiita.com/ariela/items/71986b1bf65a566d2e75)

# 概要

## 書いたコード

```Python:GetThumbnail.py
import sqlite3

# .clipファイルからSQLite部分を探して抜き出す
with open('./test.clip', mode='rb') as f:
    clip_str = f.read()

    search_text = "SQLite format 3"
    search_text_bytes = search_text.encode("utf-8")

    find_index =  clip_str.find(search_text_bytes)

    if 0 <= find_index:
        result_str = clip_str[find_index:len(clip_str)]

# 一旦別ファイルに保存
with open("./test.sqlite", mode="wb") as f:
    f.write(result_str)

# サムネ取得
with sqlite3.connect(result_str) as connect:
    cursor = connect.cursor()
    cursor.execute("select ImageData from CanvasPreview")
    png_data = cursor.fetchone()
    with open("./test.png", mode="wb") as f:
        f.write(png_data[0])
    cursor.close()

print("Success!")
```

Pythonにまだ不慣れのため成功前提で記述しており、
失敗したときの例外処理が皆無なのは許してください・・・

## 解説

.clipファイルには大きく2つのデータに分けられる。
前半はおそらくクリスタ独自のデータ（詳しく調べてないので不明。）
後半はSQLiteで記述されたデータベースだ。

今回はサムネだけ欲しかったので、
「[CLIP STUDIO PAINTの.lipファイルをハックして作業動画を書き出すWindowsアプリを作った](https://tech.recruit-mp.co.jp/etc/building-lip-to-animated-gif-converter-named-lip2gif/)」で

>単刀直入に言うと、クリスタの.lipファイルはSQLiteデータベースです。

と書いてある通り、SQLite部分からサムネを取得するようにした。
<br>
が、解説されていたのは.lipファイルの情報でそのまま読み込んだだけではSQLite部分は後ろにあって、読み込めないデータとして認識されてしまう。

そこで「[イラストを Git で管理したかったのでツールをつくった](https://syfm.hatenablog.com/entry/2016/10/25/183044)」で

>clip ファイルは何かしらのメタデータ(ちゃんとは確認していない)と SQLite データベースがくっついているので、まず SQLite データベースのマジックナンバーを探して、そこから末端までを取り出しています。

とあるようにまずSQLite部分を抜き出すようにした。

<br>

```Python:
# .clipファイルからSQLite部分を探して抜き出す
with open('./test.clip', mode='rb') as f:
    clip_str = f.read()

    search_text = "SQLite format 3"
    search_text_bytes = search_text.encode("utf-8")

    find_index =  clip_str.find(search_text_bytes)

    if 0 <= find_index:
        result_str = clip_str[find_index:len(clip_str)]
```

SQLiteファイルのヘッダーにはバイナリで「SQLite format 3」と記述してある。
（正確には"SQLite format 3 \ 000"）
.clipファイルの頭からこの文字列があるインデックスまでを調べ、
見つけたインデックス以下がSQLiteファイルとなっている。

ちなみに.clipファイルはバイナリデータなので、
変な文字変換などが起きないようにバイナリデータとして読み書きを行っている。
<br>

```Python:
# 一旦別ファイルに保存
with open("./test.sqlite", mode="wb") as f:
    f.write(result_str)
```
ここは正直、他のやり方があると思っているがわからなかったので妥協・・・
取得したSQLiteデータをわざわざ別ファイルに書き込んでいる。

<br>

```Python:
# サムネ取得
with sqlite3.connect(result_str) as connect:
    cursor = connect.cursor()
    cursor.execute("select ImageData from CanvasPreview")
    png_data = cursor.fetchone()
    with open("./test.png", mode="wb") as f:
        f.write(png_data[0])
    cursor.close()
```
サムネ情報がどこにあるのかは「[CLIP STUDIO PAINTの.lipファイルをハックして作業動画を書き出すWindowsアプリを作った](https://tech.recruit-mp.co.jp/etc/building-lip-to-animated-gif-converter-named-lip2gif/)」で紹介されている通り、CanvasPreviewのImageDataの中にあることがわかるのでそこからデータ取得。
どうせデータ一個しか入ってないことがわかっているのでcursor.fetchone()にしている。


取ってきたデータはタプル型だがpngデータ一つしか入っていないのでインデックス0番を直接取得し、それをバイナリとしてファイルに書き込んでいる。
取得したpng_data[0]にはバイナリでpngが記述されていた。
おそらくクリスタで.clipファイルを保存するときに一緒にサムネをここに書き込んでいるのだと思われる。

# 終わりに

最初はバイナリデータなのにVSCodeで無理やりテキストとして.clipファイルを開き、"SQLite format 3"より前を削除して保存、SQLiteで読み込もうとしても対応していないファイルとして読み込めなかった。
が、プログラムでバイナリとして読み込むことでうまくいった。（当たり前・・・）

Pythonでうまくいったので今度は自作アプリ用にC#で読み込めるようにするが、おそらくほぼ同じようなやり方だと思っている。
自作アプリでは複数の.clipからサムネを表示するので、すべて読み込んでアプリが固まらないように非同期で読み込み、読み込みが終わるまでは代わりの画像を出すようにする予定。

<br>

初めてまとめたのでつたない文章ですが、
ご覧いただきありがとうございました。m(_ _)m