CLIP STUDIO PAINTファイルからプレビュー画像を生成する
CLIP STUDIO PAINT ファイル(.clip)のプレビュー画像を node.js で出力したのでメモ。
なお、fs.readFileSync/writeFileSync
を使用しているが、
テスト用に記述してるだけなので、実装する場合はfs.readFile/writeFile
推奨。
コード
とりあえず全体像。TypeScriptで記述している。 外部モジュールとして要sqlite3。
$ npm i sqlite3
import * as fs from "fs" import * as sqlite3 from "sqlite3" // .clipファイルパス const path = "..\\sample_asset\\illust1" const clipPath = path + ".clip" // Bufferの読み込み const clipBuffer = fs.readFileSync(clipPath) // SQLiteのデータは「SQLite format 3」から始まるらしい const searchText = "SQLite format 3" // Uint8Arrayに変換 const uint8Array = new Uint8Array(Buffer.from(searchText)) // clipからSQLiteのデータのスタート位置を取得 const findIndex = clipBuffer.indexOf(uint8Array) // SQLite部分のみ切り出し const resultBuf = clipBuffer.slice(findIndex, clipBuffer.length) // 一時書き出し用のSQLiteファイルパス const dbPath = path + ".sqlite" // いったんsqliteファイルに書き出し fs.writeFileSync(dbPath, resultBuf) // sqlite3モジュールでSQLiteファイルを展開 const db = new sqlite3.Database(dbPath) db.serialize(() => { // CanvasPreviewテーブルからImageData(png)を抽出 db.get("SELECT ImageData FROM CanvasPreview", (err, row) => { if (err) { console.log(err) return } if (row.length > 0) { // png出力パス const binaryPath = path + ".png" // rowのImageDataプロパティからpngを生成 fs.writeFileSync(binaryPath, row.ImageData) } }) })
解説
clipファイルの読み込み
// .clipファイルパス const path = "..\\sample_asset\\illust1" const clipPath = path + ".clip" // Bufferの読み込み const clipBuffer = fs.readFileSync(clipPath)
fs.readFileSync
でclipファイルの読み込みを行う。
fs.readFileSync
はBuffer
型を返す。
SQLiteのデータの位置を探索
clipファイルは何らかのメタデータ+SQLiteで構成されているらしく、 そのままでは下記のとおりSQLiteとして展開できない。
//PowerShellで確認 $ C:\...> sqlite3 illust1.clip SQLite version 3.31.1 2020-01-27 19:55:54 Enter ".help" for usage hints. sqlite> .table Error: file is not a database
そこで、SQLiteのデータだけ切り出してやる必要がある。 噂によるとSQLiteのデータは「SQLite format 3」から始まるらしい。
// SQLiteのデータは「SQLite format 3」から始まるらしい const searchText = "SQLite format 3" // Uint8Arrayに変換 const uint8Array = new Uint8Array(Buffer.from(searchText)) // clipからSQLiteのデータのスタート位置を取得 const findIndex = clipBuffer.indexOf(uint8Array) // SQLite部分のみ切り出し const resultBuf = clipBuffer.slice(findIndex, clipBuffer.length) // 一時書き出し用のSQLiteファイルパス const dbPath = path + ".sqlite" // いったんsqliteファイルに書き出し fs.writeFileSync(dbPath, resultBuf)
Buffer#indexOf
にて、SQLiteのインデックスを取得する。
インデックスが分かったら、そこから終点までslice
してやる。
取得したバッファをfs.writeFileSync
でSQLiteファイルとして書き出し。
SQLiteの展開とpngの書き出し
SQLiteファイルのテーブルは次のようになっており、 CanvasPreviewテーブルにプレビュー画像のデータが保持されている。
// PowerShellで確認 $ C\...> sqlite3 illust1.sqlite //←上で生成したやつ $ sqlite3> .table AnimationCutBank Layer Canvas LayerThumbnail CanvasItem Mipmap CanvasItemBank MipmapInfo CanvasPreview Offscreen ElemScheme ParamScheme ExternalChunk Project ExternalTableAndColumnName RemovedExternal
sqlite3モジュールにより、先ほど生成したSQLiteファイルを展開する。
// sqlite3モジュールでSQLiteファイルを展開 const db = new sqlite3.Database(dbPath) db.serialize(() => { // CanvasPreviewテーブルからImageData(png)を抽出 db.get("select ImageData from CanvasPreview", (err, row) => { if (err) { console.log(err) return } if (row.length > 0) { // png出力パス const binaryPath = path + ".png" // row.ImageDataプロパティからpngを生成 fs.writeFileSync(binaryPath, row.ImageData) } }) })
クエリにてCanvasPreviewからImageDataを取得する。
このとき出力結果のrow
には、次の形でデータが格納されている。
{ ImageData: <Buffer 89 50 4e 47 0d 0a ... >}
つまり、row.ImageData
で目的のプレビュー画像のデータを取得することが可能。
最後に、fs.writeFileSync
によりillust1.png
にrow.ImageData
を書き出してやれば終了である。
感想
4日くらいかかってつらい。Node.jsとか知らんがな(ぇ
参考
CLIP STUDIO PAINTで作成した漫画データのサムネール出力の為の調査 - Qiita
CLIPSTUDIO PAINTのファイルからサムネイル画像を取得 - Qiita
CLIP STUDIO PAINTの.lipファイルをハックして作業動画を書き出すWindowsアプリを作った – PSYENCE:MEDIA
Node.js で TextEncoder や SHA512 を作る
Node.js入門5 色々なループ(for, forEach, while) - Symfoware
Node.js で SQLite を扱う - Corredor
Get list of tables from SQLite in Node.js - Stack Overflow
主にPowerShell関係
Windowsでファイルを16進数テキストに変換したり、16進数テキストをファイルに変換したりする方法 - Eiji James Yoshidaの記録
Windows10でバイナリ<->HEXテキスト変換 - Qiita
PowerShellからデータを出力するパターンまとめ - Qiita