日々是好日

プログラミングについてのあれこれ、ムダ知識など

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.readFileSyncBuffer型を返す。

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.writeFileSyncSQLiteファイルとして書き出し。

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.pngrow.ImageDataを書き出してやれば終了である。

感想

4日くらいかかってつらい。Node.jsとか知らんがな(ぇ

参考

CLIP STUDIO PAINTで作成した漫画データのサムネール出力の為の調査 - Qiita

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

CLIP STUDIO PAINTの.lipファイルをハックして作業動画を書き出すWindowsアプリを作った – PSYENCE:MEDIA

Node.js で TextEncoder や SHA512 を作る

オブジェクトでの作業 - JavaScript | MDN

Node.js入門5 色々なループ(for, forEach, while) - Symfoware

Node.js で SQLite を扱う - Corredor

Writing to Files in Node.js

Get list of tables from SQLite in Node.js - Stack Overflow

主にPowerShell関係

Windowsでファイルを16進数テキストに変換したり、16進数テキストをファイルに変換したりする方法 - Eiji James Yoshidaの記録

Windows10でバイナリ<->HEXテキスト変換 - Qiita

PowerShellからデータを出力するパターンまとめ - Qiita

cutコマンドについてまとめました 【Linuxコマンド集】

Engineer のメモ帳