0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPTのプランの枠で画像を作る小さなスクリプトを自作しました(Codex CLI の codex exec を Node から呼ぶ)

0
Posted at

個人でWebサイトやアプリを作っています(屋号は sesebox です)。

いま、水槽をモチーフにしたパズルアプリ「WaterFill」を作っています。アプリの中で使う魚やコインの絵が何枚も要るので、ChatGPTに課金しているプランの枠で、コマンド1行で画像を作れるようにしました。そのためのスクリプトを自作したので、仕組みと気をつけたところを書きます。

前提:画像生成は Codex CLI そのものの機能だった

OpenAIの Codex CLI(ターミナルで動くAIのコーディングツール)には、画像を作る機能が最初から入っています(公式ドキュメント:https://learn.chatgpt.com/docs/image-generation )。

ChatGPTのアカウントでログインして使えば、ChatGPTのプランの利用枠の中で画像を作れます。APIのように1枚ごとの料金はかかりません。

npm install -g @openai/codex
codex login              # ブラウザでChatGPTにログイン
codex login status       # → Logged in using ChatGPT ならOK

ネットで調べると、これを便利にする非公式のプラグインもいくつか見つかりました。中を読むと、どれも**codex exec を呼んでいるだけの薄い包み**でした。それなら、知らない人のコードを入れるより、同じことを自分で数十行書くほうが安心だと判断して、自作することにしました。

使い方

node codex-image.mjs --out fish.png "白い背景に、かわいいクマノミを1匹。正方形。"

# 見本の画像を渡して、同じタッチで描かせる
node codex-image.mjs --out new-fish.png --ref fish-01.png --ref fish-02.png "同じタッチでハコフグを1匹"

1枚あたり、だいたい1分でPNGができます。

仕組み

やっていることは3つだけです。

  1. 一時フォルダを作る
  2. その中で codex exec(Codexに1回だけ仕事を頼むコマンド)を走らせて、「画像を1枚作って out.png で保存して」と頼む
  3. できた out.png を、指定の場所にコピーする

中心の部分はこんな感じです。

import { spawnSync } from "node:child_process";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";

const work = mkdtempSync(join(tmpdir(), "codex-image-"));

const env = { ...process.env };
delete env.OPENAI_API_KEY; // ← 大事(後述)

const prompt = [
  "$imagegen",
  "次の説明どおりの画像を1枚だけ生成して、このフォルダ直下に out.png というファイル名で保存してください。",
  "ほかのファイルは作らないでください。",
  "",
  "説明:",
  desc,
].join("\n");

const args = ["exec", "-s", "workspace-write", "--skip-git-repo-check", "--ephemeral", "-C", work];
for (const r of refs) args.push("-i", resolve(r)); // 見本の画像
args.push("-"); // プロンプトは標準入力で渡す

const res = spawnSync(process.execPath, [codexJs, ...args], { env, input: prompt, encoding: "utf8" });

$imagegen は、Codexに「画像生成の機能を使って」と伝える書き方です。-i で画像を添付すると、それを見本にしてくれます。

気をつけたところ

1. OPENAI_API_KEY を子プロセスに渡さない

公式ドキュメントによると、OPENAI_API_KEY が設定されていると、ChatGPTのプランではなくAPIの従量課金で動きます。ほかの開発でAPIキーを環境変数に入れていると、知らないうちにAPI料金になってしまいます。

なので、Codexを呼ぶ直前に delete env.OPENAI_API_KEY で外しています。

2. 一時フォルダの中で走らせる

codex exec は、指定したフォルダの中のファイルを読んだり書いたりできます。リポジトリの中で走らせると、関係ないファイルまで見られてしまいます。

-C で一時フォルダを指定し、-s workspace-write(書き込めるのはそのフォルダだけ)にして、Codexが触れるのは一時フォルダだけにしました。--ephemeral は、この1回の会話を記録に残さないオプションです。

3. codex.cmd をシェル経由で呼ばない

Windowsでは codex の実体が codex.cmd なので、最初は shell: true で呼んでいました。すると Node が次の警告を出しました。

[DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.

シェル経由だと引数がエスケープされず、そのままつながれてしまう、という警告です。説明文に記号や改行が入ると壊れる原因になります。

そこで、npmが入れた本体のJavaScript(npm root -g の下の @openai/codex/bin/codex.js)を、node で直接呼ぶようにしました。説明文も、引数ではなく標準入力で渡しています。

4. 作った画像は、失敗作も全部とっておく

うまくいかなかった絵も、「なぜダメだったか」が次の頼み方のヒントになります。スクリプトが毎回、日付ごとのフォルダに画像のコピーと、頼んだ内容・見本・かかった時間を書いたメモを残すようにしました。

使ってみて分かったこと

  • 見本なしで頼むと、黒い線とベタ塗りの、よくあるイラスト風になりがちでした。アプリの絵と全然タッチが合いません
  • アプリの実際の魚の絵を4枚、見本として渡すと、線の色やヒレの筋、目の描き方までそろった絵が出てきました
  • 逆に、タッチの違う絵を見本にすると、その違いまで忠実に真似されます。使う場所が決まっている絵は、その作品の実物を見本にするのが大事でした
  • 2枚を同時に走らせても大丈夫でした(一時フォルダが別々なのでぶつからない)
  • 画像生成は、ふつうの作業より3〜5倍の速さで利用枠を使うそうです(公式ドキュメント)。試し打ちを連発しないようにしています

まとめ

  • Codex CLIには画像生成が入っていて、ChatGPTのプランの枠で使える
  • codex exec を一時フォルダで呼ぶだけで、「コマンド1行で画像を作る」道具になる
  • OPENAI_API_KEY を外す・一時フォルダに閉じ込める・シェルを通さない、の3つを守れば、自作でも安心して使える

自分で書けば数十行なので、中身の分からないプラグインを入れる前に、一度自作を考えてみるのもおすすめです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?