Help us understand the problem. What is going on with this article?

CLIP STUDIO PAINTのファイルからNode.jsでサムネイル画像を出力する

CLIP STUDIO PAINTファイル(.clip)のサムネイル画像をNode.js上で出力してみました。

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

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

こちらの記事のように、Pythonやシェル、Goで実装されたライブラリ等はあったものの、JavaScript(TypeScript)で実装されたものが無かったので調べてみました。

概要

  • clipファイルからSQLite部分のデータを切り出し
  • 切り出したSQLiteファイルからサムネイル画像のデータを抽出

次節から実装になりますが、ファイルの読み書きに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 dbBuf = clipBuffer.slice(findIndex, clipBuffer.length)

// 一時書き出し用のSQLiteファイルパス
const dbPath = path + ".sqlite"
// いったんsqliteファイルに書き出し
fs.writeFileSync(dbPath, dbBuf)

// sqlite3モジュールでSQLiteファイルを展開
const db = new sqlite3.Database(dbPath)
db.serialize(() => {
  // CanvasPreviewテーブルからImageData(png)を抽出
  db.all("SELECT ImageData FROM CanvasPreview", (err, row) => {
    if (err) {
      console.log(err)
      return
    }

    if (row[0].length > 0) {
      // png出力パス
      const binaryPath = path + ".png"
      // rowのImageDataプロパティからpngを生成
      fs.writeFileSync(binaryPath, row[0].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 unit8Array = new Uint8Array(Buffer.from(searchText))

// clipからSQLiteのデータのスタート位置を取得
const findIndex = clipBuffer.indexOf(uint8Array)

// SQLite部分のみ切り出し
const dbBuf = clipBuffer.slice(findIndex, clipBuffer.length)

// 一時書き出し用のSQLiteファイルパス
const dbPath = path + ".sqlite"
// いったんsqliteファイルに書き出し
fs.writeFileSync(dbPath, dbBuf)

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.all("select ImageData from CanvasPreview", (err, row) => {
    if (err) {
      console.log(err)
      return
    }

    if (row.length > 0) {
      // png出力パス
      const binaryPath = path + ".png"
      // row[0].ImageDataプロパティからpngを生成
      fs.writeFileSync(binaryPath, row[0].ImageData)
    }
  })
})

クエリにてCanvasPreviewからImageDataを取得する。
このとき出力結果のrowには、次の形でデータが格納されている。

{ ImageData:
   <Buffer 89 50 4e 47 0d 0a ... >}

つまり、row[0].ImageDataで目的のサムネイル画像のデータを取得することが可能。
最後に、fs.writeFileSyncによりillust1.pngrow.ImageDataを書き出してやれば終了である。

参考

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

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

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

Node.js で TextEncoder や SHA512 を作る

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

Node.js で SQLite を扱う

Writing to Files in Node.js

Get list of tables from SQLite in Node.js

主にPowerShell関係

MS - certutil

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

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

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

Windows PowerShell : UNIXの cut コマンド相当の作業をする。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした