Stable Diffusionを使用して大量の画像を効率的に生成する際、手動での作業は時間がかかり、非効率的です。今回は、WebUI APIを活用して画像生成を自動化し、リアルタイムのプログレスバーやカラフルなターミナル出力を備えた高機能なコマンドラインツールの実装方法について詳しく解説いたします。
デモンストレーション
CLI実行の様子
ターミナルでの実際の実行画面。カラフルなプログレスバーとリアルタイム進捗表示が確認できます。
プロジェクトリポジトリ
完全なソースコード、ドキュメント、設定例、コントリビューションガイドラインが含まれています。
開発背景と目的
Stable Diffusion WebUIは強力な画像生成ツールですが、複数の設定で大量の画像を生成する場合、一つ一つ手動で設定するのは非常に煩雑です。特に、異なるプロンプトやパラメータで複数のバリエーションを生成したい場合、効率的な自動化ツールが必要となります。本記事で紹介するツールは、設定ファイルからバッチ処理を行い、生成過程をリアルタイムで監視できる機能を提供します。
プロジェクト構成と技術選択
本ツールは以下の技術スタックを採用しています。
TypeScriptをメイン言語として選択した理由は、型安全性によってAPI レスポンスの構造を明確に定義でき、開発時のエラーを早期に発見できるためです。Commander.jsは直感的なCLIインターフェースの構築を可能にし、chalkライブラリによって視覚的に分かりやすいカラー出力を実現しています。dotenvを使用することで、環境に応じた設定の管理が容易になります。
プロジェクトの構造は非常にシンプルで、メインロジックは単一のTypeScriptファイルに集約されています。これにより、保守性を保ちながら理解しやすいコードベースを実現しています。
核心機能の実装解説
設定ファイルの読み込みシステム
設定ファイルはJSONL(JSON Lines)形式を採用しており、各行が独立したJSON設定オブジェクトとなっています。この形式の利点は、設定の追加や削除が容易で、大きなファイルでも行単位での処理が可能なことです。
async function loadConfigs(): Promise<PayloadConfig[]> {
const configContent = await readFile(config.configsFile, "utf-8");
const configs: PayloadConfig[] = [];
for (const line of configContent.trim().split("\n")) {
if (line.trim()) {
configs.push(JSON.parse(line) as PayloadConfig);
}
}
return configs;
}
この実装では、ファイルを行ごとに分割し、空行をスキップしながら各行をJSONとしてパースしています。TypeScriptの型システムを活用することで、設定オブジェクトの構造を事前に定義し、型安全性を確保しています。
プログレスバーの視覚的実装
最も特徴的な機能の一つが、リアルタイムで更新されるプログレスバーです。この実装では、ユニコード文字とANSIエスケープシーケンスを組み合わせて、美しい視覚効果を実現しています。
function createProgressBar(progress: number, width: number = 30): string {
const filled = Math.round(progress * width);
const empty = width - filled;
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
return `[${bar}] ${chalk.cyan((progress * 100).toFixed(1) + '%')}`;
}
この関数は進捗率を受け取り、指定された幅に合わせてプログレスバーを生成します。塗りつぶし部分は緑色の実線文字で、未完了部分は灰色の薄い文字で表現されます。パーセンテージは青色で表示され、視覚的な区別を明確にしています。
非同期処理による効率的な監視システム
画像生成の監視は、生成リクエストと並行して実行される非同期処理として実装されています。これにより、メインスレッドをブロックすることなく、リアルタイムでの進捗更新が可能になります。
async function waitForGenerationComplete(generationPromise: Promise<any>, expectedIterations: number = 1): Promise<void> {
let isComplete = false;
generationPromise.then(() => {
isComplete = true;
}).catch(() => {
isComplete = true;
});
while (!isComplete) {
try {
const res = await fetch(progressUrl);
const progress = await res.json() as ProgressResponse;
const currentJob = progress.state.job_no || 0;
const totalJobs = progress.state.job_count || expectedIterations;
const currentIter = Math.min(currentJob + 1, totalJobs);
const iterProgress = createProgressBar(progress.progress);
const overallProgress = totalJobs > 1
? (currentJob + progress.progress) / totalJobs
: progress.progress;
const overallBar = createProgressBar(overallProgress);
const stepInfo = progress.state.sampling_steps > 0
? ` Step ${progress.state.sampling_step}/${progress.state.sampling_steps}`
: '';
const eta = progress.eta_relative > 0 ? ` ETA: ${progress.eta_relative.toFixed(1)}s` : '';
let progressLine;
if (totalJobs > 1) {
progressLine = `\r🔄 ${chalk.blue(`Iter ${currentIter}/${totalJobs}`)}: ${iterProgress}${chalk.yellow(stepInfo)} | ${chalk.magenta('Overall')}: ${overallBar}${chalk.cyan(eta)}`;
} else {
progressLine = `\r🔄 ${iterProgress}${chalk.yellow(stepInfo)}${chalk.cyan(eta)}`;
}
process.stdout.write('\x1b[2K' + progressLine);
await new Promise(resolve => setTimeout(resolve, 500));
} catch (err) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
process.stdout.write('\x1b[2K\r');
console.log("✅ Generation complete");
}
この実装の特徴は、複数の反復処理に対応していることです。単一の画像生成だけでなく、複数のバッチ処理や反復処理の場合には、現在の反復と全体の進捗を同時に表示します。ANSIエスケープシーケンス(\x1b[2K
)を使用して行をクリアし、カーソルを行の先頭に戻すことで、プログレスバーの滑らかな更新を実現しています。
キーボード入力による制御機能
ユーザーエクスペリエンスを向上させるため、キーボード入力による制御機能を実装しています。特に、'z'キーによる優雅な終了機能は、現在の処理を完了してから安全に終了することを可能にします。
function setupKeyboardHandler() {
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', (key) => {
const keyStr = key.toString();
if (keyStr === 'z' || keyStr === 'Z') {
console.log(chalk.yellow('\n🛑 Graceful exit requested. Will finish current config and exit...'));
config.shouldExit = true;
} else if (keyStr === '\u0003') { // Ctrl+C
console.log(chalk.red('\n❌ Force exit'));
process.exit(1);
}
});
}
}
この実装では、標準入力をrawモードに設定することで、バッファリングなしで即座にキーボード入力を捕捉できます。TTY環境でのみ動作するようにチェックを行い、異なる実行環境での互換性を確保しています。
ファイル管理とメタデータ保存
生成された画像は日付ベースのディレクトリ構造で整理され、各画像には実際に使用されたシード値を含むファイル名が付けられます。オプションでメタデータファイルを保存する機能も提供されており、後の分析や再現性の確保に役立ちます。
for (let i = 0; i < data.images.length; i++) {
const imgBuffer = Buffer.from(data.images[i], "base64");
const timestamp = Date.now();
const actualSeed = infoData.seed ? infoData.seed + i : Math.floor(Math.random() * 1000000000);
const filename = `${timestamp}-${actualSeed}.png`;
const filePath = join(outputDir, filename);
await writeFile(filePath, imgBuffer);
if (config.saveMeta) {
const metaFilename = `${timestamp}-${actualSeed}.meta.json`;
const metaFilePath = join(outputDir, metaFilename);
const metadata = {
config: currentConfig,
response: {
info: infoData,
timestamp: new Date().toISOString(),
filename: filename,
seed: actualSeed
}
};
await writeFile(metaFilePath, JSON.stringify(metadata, null, 2));
}
}
ファイル名にタイムスタンプとシード値を組み合わせることで、一意性を確保しながら、生成条件の識別を容易にしています。メタデータファイルには、使用された設定とAPI レスポンスの詳細が含まれ、生成過程の完全な記録を提供します。
実際の使用方法と活用シナリオ
このツールは様々なシナリオで活用できます。まず、設定ファイルを作成します。例えば、異なるキャラクターや背景で複数のバリエーションを生成したい場合、以下のような設定を用意します。
{"prompt": "1girl, solo, green tracksuit, black hair, brown eyes, mischievous smile", "negative_prompt": "worst quality, bad quality", "width": 720, "height": 1280, "sampler_name": "Euler a", "steps": 28, "cfg_scale": 7, "seed": -1, "batch_size": 1, "n_iter": 2, "hires_fix": false}
{"prompt": "1girl, solo, red dress, blonde hair, blue eyes, gentle smile", "negative_prompt": "worst quality, bad quality", "width": 720, "height": 1280, "sampler_name": "Euler a", "steps": 28, "cfg_scale": 7, "seed": -1, "batch_size": 1, "n_iter": 1, "hires_fix": false}
実行は非常に簡単で、基本的な使用ではnpm start
コマンドだけで開始できます。詳細なログが必要な場合はnpm start -- -v
を使用し、メタデータの保存が必要な場合はnpm start -- --save-meta
オプションを追加します。
カスタム設定ファイルや出力ディレクトリを指定する場合は、npm start -- --configs-file custom.jsonl --output-dir ./my-images
のようにオプションを組み合わせて使用できます。
環境設定とサーバー連携
ツールの柔軟性を確保するため、環境変数による設定管理を採用しています。.env
ファイルでWebUI APIのURLや默认のファイルパスを設定できます。
SD_WEBUI_URL=http://192.168.100.105:7860
CONFIGS_FILE=prompts/configs.jsonl
OUTPUT_DIR=out
設定の優先順位は、コマンドライン引数、環境変数、デフォルト値の順序で適用されます。これにより、一般的な設定は環境変数で管理し、特定の実行時のみコマンドライン引数で上書きすることが可能です。
Stable Diffusion WebUIとの連携では、--listen --api
フラグを指定してWebUIを起動する必要があります。--listen
フラグは外部からの接続を許可し、--api
フラグはREST APIエンドポイントを有効にします。これらの設定により、ローカルネットワーク内の別のマシンからでもAPIアクセスが可能になります。
エラーハンドリングと堅牢性
実際の運用では、ネットワークエラーやサーバーの一時的な過負荷などの問題が発生する可能性があります。本ツールでは、これらの状況に対応するため、適切なエラーハンドリングとリトライメカニズムを実装しています。
プログレス監視では、APIエンドポイントへの接続エラーを捕捉し、警告メッセージを表示しながら継続的に監視を行います。サーバーの準備状態を確認する機能も実装されており、前の処理が完全に終了してから次の処理を開始するようになっています。
パフォーマンス最適化と最適化戦略
大量の画像生成を効率的に処理するため、いくつかの最適化戦略を採用しています。プログレス更新の頻度は500ミリ秒間隔に設定されており、ネットワーク負荷と応答性のバランスを取っています。
メモリ使用量の最適化では、画像データをBase64形式で受け取った後、即座にBufferに変換してファイルに書き込み、メモリに長時間保持することを避けています。大きな設定ファイルに対しても、ストリーミング処理により効率的に処理できるよう設計されています。
拡張可能性と今後の発展方向
現在の実装は基本的な機能に集中していますが、さまざまな拡張の可能性があります。例えば、複数のWebUIサーバーに対する負荷分散、画像品質の自動評価機能、生成結果の統計分析機能などが考えられます。
また、WebSocketを使用したリアルタイム通信により、さらに応答性の高い監視システムを構築することも可能です。設定ファイルの形式についても、YAMLやTOMLなどの他の形式への対応や、より高度なテンプレート機能の追加も検討できます。
まとめ
本記事では、TypeScriptを使用してStable Diffusion WebUI APIと連携する自動化ツールの実装について詳しく解説いたしました。リアルタイムプログレスバー、カラフルなターミナル出力、優雅な終了機能などの実装により、単なる自動化ツールを超えた、使いやすく信頼性の高いソフトウェアを構築することができました。
このツールは、Stable Diffusionを活用した画像生成作業の効率化に大きく貢献し、創作活動やビジネス用途での活用において、時間とリソースの大幅な節約を実現します。ソースコードは読みやすく設計されており、個々のニーズに合わせてカスタマイズや機能拡張も容易に行えます。
自動化は単なる時間短縮だけでなく、一貫性の確保、エラーの削減、再現性の向上といった品質面でのメリットも提供します。本記事で紹介した実装手法は、他のAPIベースのツール開発にも応用可能であり、より広範な自動化プロジェクトの参考となることでしょう。
プロジェクトリポジトリ
本記事で紹介したツールの完全なソースコード、詳細なドキュメント、実用的な設定例、コントリビューションガイドラインをご確認いただけます。