---
title: "4コママンガ『ハコブくん』を Gemini の API で描く ― 次は話を選ぶだけでショートまで"
slug: gemini-api-4koma
date: 2026-10-30
day: 410
meta_description: "4コママンガ『ハコブくん』の縦長のコマは、Gemini API（Nano Banana 2）で1コマ約11円で描けた。参照画像に名前の札を付けるとキャラの取り違えが直り、第61話は絵から声・ショートまで会話の中で作れた。"
tags: [4コママンガ, ハコブくん, 画像生成AI, Gemini, Nano Banana 2, ElevenLabs, YouTubeショート, LinkedIn, Claude, AI活用]
ai_consult: true
cover: "/media/LOG410_cover.jpg"
series: "ひとり研究所の裏側"
---

**4コママンガ『ハコブくん』の縦長のコマは、Google の Gemini API（画像を描くモデル Nano Banana 2）で、1コマ約11円・20秒前後で描けました。参照画像の前に「この絵は高杉さん」のような名前の札を付けて送ると、キャラの取り違えも直りました。** 第61話「押していいのか問題」は、4コマの絵から、声、22.9秒の YouTube ショート、X の予約まで、Claude との1つの会話の中で作れています。

『ハコブくん』は、病院で薬を運ぶロボット・ハコブくんと看護師さんたちの4コママンガです。これまで絵は、Gemini のアプリ（ブラウザやスマホで使うもの）に、1コマずつ手で頼んでいました。Claude が書いたプロンプト（絵を頼む文）をコピーし、キャラの三面図（正面・後ろ・横から描いた設定画）を添えて送り、できた絵を保存する。4コマで4回、描き直しがあればその分だけ、この手の作業が増えます。

API（プログラムから AI に頼むための窓口）を使うと、この「コピーして送って保存する」を、プログラムに任せられます。ただ、アプリと同じ絵柄で描けるのか、1枚いくらかかるのかは、試すまで分かりませんでした。

試して分かったことを先に並べます。

| 確かめたこと | 結果 |
|---|---|
| 縦長（4:5）で返ってくるか | 928×1152 で返る。比は 0.806 で、4:5（0.8）とほぼ同じ |
| 1コマの費用 | 約11円（入力 約3,000〜4,800 トークン・出力の画像 1,120 トークン） |
| 1コマの時間 | 12〜35秒 |
| 絵柄 | サイトに出しているマンガの見え方に近い。ハコブくんの形・トーン・字の書き方もそろう |
| うまくいかなかったこと | 看護師さんの髪型の取り違え（ボブとお団子）、セリフの字の重なり・抜け、ボタンの形の違い |
| 効いた直し方 | 参照画像に名前の札を付けて送る・最初に描けたコマを部品の見本にして添える |
| 第61話の4枚にかかった費用 | 描き直しを含めて13枚・約144円 |

絵のほかにも、同じ流れで手作業を減らしました。

- 声：ショートの声は、ElevenLabs（文章を人の声で読み上げるサービス）の鍵を毎回ターミナルに入れて作っていました。鍵をファイルに置き、会話の中から作れるようにしました
- LinkedIn：解説動画を LinkedIn に手で上げていたのをやめ、YouTube の公開と同じ時刻に、YouTube のリンク付きで自動で出すようにしました
- データの置き場所：キャラの図・指示・話ごとのデータが1つのフォルダに混ざっていたので、分けました

【画像】LOG410_01_ep61_4panels.jpg Gemini の API で描き、背景を白にして仕上げた第61話の4コマ。袋を引っかけたハコブくん、停止ボタンの前で指が止まる高杉さん、無言で押す大城さん、3人の会話のオチ

この記事の効果の書き方について断っておきます。費用・時間・トークン数は、API の返事に入っていた実際の値です。「手作業が減る」は見込みで、次の話を作るときに、どれだけ人の手が要ったかで確かめます。


## 作業ログ ― Gemini の API で4コマを描く準備

### Gemini のアプリではなく API を使うと決めた理由

縦長の4コマを作る会話（LOG408 の続き）で、まず「アプリの代わりに何が使えるか」を比べました。

| 方法 | できること | このマンガに向かない理由 |
|---|---|---|
| Gemini の API | プロンプトとキャラの画像と縦長の下地を、スクリプトから送れる | 費用がかかる（アプリの契約とは別） |
| Figma の連携の画像生成 | Gemini の画像モデルを選べる | 文章しか渡せず、キャラの画像を添えられない。顔がそろわない |
| ブラウザの自動操作 | アプリの Gemini をそのまま使える | 遅く、画面が変わると止まりやすい |

キャラの顔をそろえるには、三面図を毎回添える必要があります。それができるのは API だけでした。

### Gemini API と Google AI Studio は何か

[Gemini API](https://ai.google.dev/gemini-api/docs) は、Google の AI の Gemini を、プログラムから呼び出すための窓口です。画像を描くモデルもここから使えます。鍵（API キー）を作るのと支払いの設定は、[Google AI Studio](https://aistudio.google.com/) という Google のページで行います。

| この記事で使う言葉 | 意味 |
|---|---|
| API キー（鍵） | プログラムが「誰が頼んでいるか」を Google に示すための文字列。人に見せない。KIOXIA（外付けのドライブ）のファイルに置き、スクリプトは読むだけにした |
| Nano Banana 2 | Gemini の画像を描くモデルの呼び名。API では `gemini-3.1-flash-image` という名前 |
| トークン | AI が文や画像を数える単位。費用はトークンの数で決まる |
| 無料枠 | 支払いの設定をしなくても使える範囲。画像のモデルは無料枠では使えなかった |
| 前払い（クレジット） | Google AI Studio の支払いは、先にお金を入れて、使った分が引かれる形だった |

このプロジェクトでは、Claude（この会話の AI）がプロンプトを書き、Mac の上のスクリプトが Gemini API に送り、返ってきた絵を Claude が見て直す、という分担にしました。

### API の鍵を作って KIOXIA に置いた手順

鍵は Google AI Studio の「API キー」の画面で作りました。初めてなので、プロジェクト（鍵や支払いをまとめる単位）も、その場で「p2m-manga」として作りました。

鍵は、ターミナルで次の1行を動かして、コピーした鍵をそのままファイルに書き込む方法で置きます。テキストエディットで保存すると書式や拡張子が変わりやすいためです。

```bash
pbpaste > /Volumes/KIOXIA/gemini_api_key.txt
```

`pbpaste` は「いまコピーしてあるもの」を出す命令です。ここで2回つまずきました。

1. この1行をチャットからコピーしたとき、先にコピーしてあった鍵が消え、ファイルの中身がコマンドの文字そのものになった
2. やり直しで、↑キーで出したコマンドの後ろに鍵が続けて貼られ、鍵の文字が入った名前のファイルができた

2回目は、ファイルの中身（鍵そのもの）を正しい名前のファイルに写し、間違ってできたファイルは消してもらいました。確かめるときは鍵の中身を表示せず、長さと文字の種類だけを見ます。

```bash
f="$HOME/mnt/KIOXIA/gemini_api_key.txt"; ls -la "$f" | awk '{print $5, $6, $7, $8}'; wc -c < "$f"; head -c 4 "$f" | grep -q '^AIza' && echo "先頭はAIza" || echo "先頭がAIzaではない"; tr -d '\n' < "$f" | grep -qE '^[A-Za-z0-9_-]+$' && echo "余計な文字なし" || echo "余計な文字あり"; wc -l < "$f"; P="$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長試作_20261030/第61話_P1_プロンプト.txt"; wc -c < "$P"
```

```text
44 Oct 30 05:03
44
先頭がAIzaではない
余計な文字あり
0
0
```

```bash
f="$HOME/mnt/KIOXIA/gemini_api_key.txt"; sed -E 's/[A-Za-z]/a/g; s/[0-9]/9/g' "$f"; echo; grep -c "pbpaste\|Volumes" "$f"
```

```text
aaaaaaa > /aaaaaaa/aaaaaa/aaaaaa_aaa_aaa.aaa
1
```

英字を a、数字を 9 に置き換えて形だけを見ると、中身がコマンドの文字だと分かります。もう1つ、確かめ方も間違っていました。今の Google の鍵は `AIza` ではなく `AQ.` で始まる形でした。

2回目の直しで使ったスクリプトです。鍵の文字は表示しません。

```bash
cd "$HOME/mnt/KIOXIA" && python3 - <<'EOF'
import os
base="gemini_api_key.txt"
odd=[n for n in os.listdir('.') if n.startswith(base) and n!=base]
assert len(odd)==1, len(odd)
odd=odd[0]
suffix=odd[len(base):]
content=open(odd,encoding='utf-8').read().strip()
print("中身と名前の後ろが同じ:", content==suffix, "長さ:", len(content), "先頭3文字の形:", "AQ." if content.startswith("AQ.") else "ちがう")
open(base,'w',encoding='utf-8').write(content)
os.makedirs("_to_delete",exist_ok=True)
os.rename(odd,"_to_delete/gemini_key_mistake.txt")
print("書き直した長さ:", os.path.getsize(base))
print("残り:", [n for n in os.listdir('.') if 'gemini' in n])
EOF
```

```text
中身と名前の後ろが同じ: True 長さ: 53 先頭3文字の形: AQ.
書き直した長さ: 53
残り: ['gemini_api_key.txt']
```

途中でフォルダの一覧を出したとき、名前に入っていた鍵の文字が一度画面に出ました。この記事には載せていません。

- ねらい：鍵を人に見せずに、スクリプトから使える場所に置く
- 変わったこと：鍵は KIOXIA の決まった名前のファイルに1つだけ。スクリプトは読むだけで、記事・ログ・JSON には出さない
- 見込める効果：鍵を貼る手作業が、最初の1回で終わる。ElevenLabs の鍵も同じ形にした（後の節）


## 作業ログ ― Claude Code で第61話の1コマ目を試した

### Claude Code に渡した依頼

最初の試しは、Mac の Claude Code（Mac の上でコードを書いて動かす Claude。LOG390 の依頼箱で依頼を受け渡している）に頼みました。依頼箱では別の長い仕事（12時間まで）が動いていたので、依頼は手で Claude Code に渡しています。

#### 20261030_33_Geminiの画像APIで第61話P1を1枚試す.md

```markdown
---
作業フォルダ: /Users/shimazueiichi/r2r2r-scripts
上限時間: 45
---
# 依頼：Gemini の画像の API で、ハコブくん縦長4コマ 第61話の Panel 1 を1枚だけ生成して確かめる（2026-10-30）

## ねらい
今はアプリの Gemini に1コマずつ手で頼んでいる。API で同じ頼み方（プロンプト＋キャラの画像＋縦長の下地）ができるかを、1枚で確かめる。

## 置き場所（作業フォルダの外は、下の2か所の読み書きだけ）
- 作成データ：`~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/ハコブくん/4コママンガ作成データ/`（以下「作成データ」）
- API の鍵：`/Volumes/KIOXIA/gemini_api_key.txt`（読むだけ。鍵の文字をログ・報告・コード・git に出さない。環境変数にも書き残さない）

## やること
1. 最初に、KIOXIA がつながっているか、鍵のファイルがあるかを確かめる。どちらかがなければ、何もせずに止めて報告する
2. スクリプト `~/r2r2r-scripts/manga/gemini_panel.py` を作る
   - 鍵はファイルから読むだけ
   - 引数：プロンプトのファイル・添付する画像（複数）・出力先・モデル名
   - Google の公式の Python の部品（google-genai）を使う。なければ入れる
   - 縦横比は API の指定で 4:5、大きさは 1K を指定する（指定の項目名は Google の公式の資料で確かめる）
   - 返ってきた画像と、返事の中身（使ったモデル名・トークン数などの使用量。画像の中身は除く）を JSON で保存する
3. モデルを決める：鍵で使えるモデルの一覧を取り、画像を作れるモデルを並べる。アプリの Gemini が今使っている画像のモデルと同じものを、Google の公式の資料で確かめて選ぶ（軽い版 Lite は選ばない）。決めた理由を報告に書く
4. 生成は **1回だけ**。プロンプトは `作成データ/縦長試作_20261030/第61話_P1_プロンプト.txt` の全文をそのまま渡す（書き換えない・足さない）。空なら止めて報告する
5. 添付する画像（この順）：
   - `作成データ/canvas_4x5_1024x1280.png`（縦横比の下地）
   - `作成データ/ハコブくん.png`（リファレンス台帳の「ハコブくんが出る全コマ」）
   - 高杉さんは画面に入らない（声だけ）ので添付しない
   - プロンプトの冒頭の「USE ATTACHED REFERENCES」の一覧（ハコブくん.png と、縦長の下地）と食い違ったら、生成せずに止めて報告する
6. 出力：`作成データ/縦長試作_20261030/API試し/` に `第61話_P1_api_{モデル名}_{日時}.png` と同じ名前の `.json`
7. 失敗したとき（無料枠では使えない・支払いの設定がない、など）は、やり直さずに、Google からの返事の文をそのまま報告に書く
8. スクリプトだけを git に個別に add して commit する（鍵と画像は入れない）

## 確かめて報告すること
1. **大きさ**：返ってきた画像の縦横の画素数と、比（4:5 にどれだけ近いか）
2. **絵柄**：画像は人が見るので、出力先のパスを書くだけでよい。気づいた崩れ（脚や靴の残像・口が線になる・停止ボタン以外の部品が足されている など）があれば一言
3. **費用**：返事の使用量（入力・出力のトークン数）と、Google の公式の料金のページの単価から、1枚の費用を出す。**円で書く**（ドルは補足。換算に使ったレートと料金ページの URL も書く）
4. 使ったモデル名・かかった時間

## 決めてほしいこと（ここで止める）
- 2枚目以降の生成・Panel 2〜4 の生成はしない。続けるかはエイイチが決める
```

### 1回目は「無料枠の上限が0」で断られた

1回目は、絵が返ってきませんでした。Google の返事は次のとおりです。

```text
429 RESOURCE_EXHAUSTED … Quota exceeded for metric: generate_content_free_tier_requests,
limit: 0, model: gemini-3.1-flash-image
```

**画像のモデルは、無料枠では1枚も使えません。** 料金のページでも、このモデルの無料枠は「Not available」でした。Google AI Studio の「課金」から支払いを設定し、前払いで 3,200円を入れました。残りが減っても自動では足されない設定（オートチャージ オフ）にしています。

### Claude Code の報告と gemini_panel.py

支払いの設定のあと、同じコマンドでもう一度描かせました。Claude Code の報告の全文です（1回目の報告に、Panel 2〜4 の追記が続きます）。

#### 20261030_33_Gemini画像API試し_報告.md

````markdown
# 報告：Gemini の画像 API で第61話 P1 を1枚試す（2026-10-30）

## 結論
**生成できなかった。** API への依頼は1回だけ送ったが、Google から「無料枠ではこのモデルの上限が 0」という返事が来て断られた（429 RESOURCE_EXHAUSTED）。依頼のとおり、やり直していない。
画像の API は**支払いの設定（有料の枠）がないと使えない**。料金のページにも、このモデルの無料枠は「Not available」と書いてある。
スクリプトはできていて、依頼の中身（プロンプト・添付・4:5・1K の指定）も確かめた。支払いの設定をすれば、同じコマンドをもう一度動かすだけで試せる。

## やったこと
1. KIOXIA はつながっていて、鍵のファイル `/Volumes/KIOXIA/gemini_api_key.txt` もあった
2. スクリプト `~/r2r2r-scripts/manga/gemini_panel.py` を作った（commit `6542019`。git に入れたのはこのファイルだけ）
   - 鍵はファイルから読むだけ。表示も保存もしない（JSON とスクリプトに鍵の文字がないことを確かめた）
   - 引数：`--prompt`、`--image`（何枚でも。渡した順に添付）、`--out-dir`、`--model`、`--name`
   - google-genai 2.34.0 を使う
   - 縦横比と大きさは `generationConfig.imageConfig` の `aspectRatio="4:5"` と `imageSize="1K"` で指定する。公式の API の資料で確かめた。4:5 と 1K はどちらも使える値に入っている。Python では `types.ImageConfig(aspect_ratio=..., image_size=...)`
   - 出てくるもの：PNG と、同じ名前の JSON（モデル名・使用量・かかった時間・返事の中身。画像のデータは入れない）。失敗したときは、Google からの返事を JSON に残す
3. 添付が合っているか：プロンプトの冒頭「USE ATTACHED REFERENCES」には「ハコブくん.png」と「blank 4:5 canvas」の2つが書いてあり、添付する2枚（canvas_4x5_1024x1280.png → ハコブくん.png の順）と食い違いはなかった。プロンプトは全文をそのまま渡した（9,534 バイト。空ではない）
4. 生成は1回だけ → 断られた（下にある返事の文）

## モデルを選んだ理由
鍵で使える画像のモデル（一覧から取ったもの）：
- gemini-2.5-flash-image（Nano Banana）
- gemini-3-pro-image ／ gemini-3-pro-image-preview ／ nano-banana-pro-preview（Nano Banana Pro）
- **gemini-3.1-flash-image** ／ gemini-3.1-flash-image-preview（Nano Banana 2）
- gemini-3.1-flash-lite-image（Nano Banana 2 Lite。軽い版なので外す）
- gemini-nano-banana-2.1（Nano Banana 2.1）

**選んだモデル：`gemini-3.1-flash-image`（Nano Banana 2。preview ではない正式版）**
- Google の公式の発表（Workspace Updates、2026年2月）に、アプリの Gemini は Nano Banana 2（Gemini 3.1 Flash Image）を使い、Fast・Thinking・Pro のどれでも Nano Banana Pro に代わる、と書いてある
  https://workspaceupdates.googleblog.com/2026/02/introducing-nano-banana-2-in-gemini-app.html
- Nano Banana 2.1（2026年10月に出た）はもっと新しいが、アプリの Gemini で使われている、と書いた公式の資料は見つからなかった。API の資料（https://ai.google.dev/gemini-api/docs/models/gemini-nano-banana-2.1 ）にも、アプリのリリースノート（https://gemini.google/release-notes/ ）にも書いていない。なので、「アプリと同じもの」という条件で 2 を選んだ

## Google からの返事（そのまま。長いので主なところ）
```
429 RESOURCE_EXHAUSTED
You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image, description: Generate content requests per day limit exceeded on Free Tier.
* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image, description: Generate content requests per minute limit exceeded on Free Tier.
* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-3.1-flash-image, description: Generate content input tokens per minute limit exceeded on Free Tier.
Please retry in 1h46m9.026243828s.
```
「limit: 0」なので、待っても無料枠のままでは使えない（「1時間46分後にやり直して」と書いてあるが、上限が 0 なので意味がない）。返事の全文は下の JSON に入っている：
`作成データ/縦長試作_20261030/API試し/第61話_P1_api_gemini-3.1-flash-image_20261030_141350.json`

## 確かめて報告すること
1. **大きさ**：画像が返ってこなかったので測れない
2. **絵柄**：画像がないので、なし
3. **費用**：生成していないので、使用量は返ってきていない。かかった費用は 0 円。
   代わりに、入力のトークン数を数える API（count_tokens。画像は作らない）で数えた入力の量と、料金のページの単価から、1枚あたりの見込みを出した：
   - 入力：2,855 トークン（プロンプト＋添付2枚）× $0.50／100万トークン ＝ $0.0014
   - 出力：1K の画像1枚 ＝ $0.067（1,120 トークン × $60／100万トークン）
   - 合計 **約 10.7 円／枚**（$0.068）
   - レート：1ドル ＝ 156.23 円（Bloomberg Linea、2026-09-17 の値。今日の値は見つからなかったので、見つかった中で一番新しいものを使った）
   - 料金のページ：https://ai.google.dev/gemini-api/docs/pricing （Gemini 3.1 Flash Image：有料の枠、無料枠は Not available）
   - モデルが考える分のトークンも数えられると、少し高くなるかもしれない。実際の額は、1枚作ったときの使用量で確かめたい
4. **モデル名・時間**：gemini-3.1-flash-image。依頼を送ってから断られるまで 0.85 秒

## 決めてほしいこと
- Google AI Studio で、この鍵のプロジェクトに支払いの設定（有料の枠）をするかどうか。設定したら、次のコマンドで同じ1枚をもう一度試せる：
  ```
  cd ~/r2r2r-scripts
  D="$HOME/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/ハコブくん/4コママンガ作成データ"
  python3 manga/gemini_panel.py --prompt "$D/縦長試作_20261030/第61話_P1_プロンプト.txt" \
    --image "$D/canvas_4x5_1024x1280.png" --image "$D/ハコブくん.png" \
    --out-dir "$D/縦長試作_20261030/API試し" --model gemini-3.1-flash-image --name 第61話_P1
  ```
- 2枚目以降と、Panel 2〜4 は作っていない

---

# 追記：支払いの設定のあとにもう一度生成した結果（2026-10-30 14:23）

生成は1回だけ。前と同じコマンド・同じプロンプト・同じ添付（下地 → ハコブくん.png）で動かした。**今回は成功した。**

- 画像：`作成データ/縦長試作_20261030/API試し/第61話_P1_api_gemini-3.1-flash-image_20261030_142359.png`
- 返事の記録：同じ名前の `.json`

## 1. 大きさ
- **928 × 1152 画素**（横 × 縦）
- 比は 0.8056。4:5（0.8）とは 0.7% しか違わず、ほぼ 4:5。1K を指定すると、このモデルは 1024 × 1280 ではなく 928 × 1152 で返してくる
- 気づいたこと：返ってきた画像は **JPEG** だった（返事の `image/jpeg`）。スクリプトは拡張子を .png 固定で保存するので、中身が JPEG なのに名前は .png になっている。見るだけなら困らないが、直すかどうか決めてほしい

## 2. 絵柄（気づいたことだけ）
- 脚や靴の残像はない。人の手や体も描かれていない
- セリフ「ちょっと、引っかかってる！」は1文字も違わない。吹き出しの尾は右上に向いていて、端には付いていない
- 下地はむらのない緑。ハコブくんの体の部品（横の丸窓・後ろのパネル）はリファレンスと合っている。足されているのは上の停止ボタンだけ
- 少しずれているところ：
  - 吹き出しの上の端が、画像の高さの約5%にある（指示は 8〜20%）
  - ハコブくんの天面が少し見えていて、停止ボタンが全部見えている（指示は「上から見せない。ドームの頭が少しのぞくだけ」）
  - 袋の後ろの床に、スピード線が描かれている（指示は「床の線を描かない」）

## 3. 費用
返事の使用量：
- 入力：2,855 トークン（文字 2,339・画像 516）
- 出力：1,644 トークン（画像 1,120・残り 524）。残りの 524 は種類が書かれていないが、モデルが考える分（thinking）と思われる

料金のページの単価（Gemini 3.1 Flash Image、有料の枠）で計算した：
- 入力：2,855 × $0.50／100万 ＝ $0.0014
- 出力の画像：1,120 × $60／100万 ＝ $0.0672
- 出力の残り：524 × $3／100万（文字と thinking の単価）＝ $0.0016
- **合計 約 11.0 円／枚**（$0.0702）
- 残りの 524 も画像の単価で数えられたとすると、多くて約 15.6 円（$0.100）
- レート：1ドル ＝ 156.23 円（Bloomberg Linea、2026-09-17 の値。今日の値は見つからなかった）
- 料金のページ：https://ai.google.dev/gemini-api/docs/pricing

## 4. モデル・時間
- モデル：gemini-3.1-flash-image（返事の model_version も同じ）
- 時間：13.1 秒

## 決めてほしいこと
- 2枚目以降と Panel 2〜4 を作るかどうか（今回は作っていない）
- スクリプトの拡張子を、返ってきた形式（JPEG なら .jpg）に合わせるかどうか


---

# 追記：第61話 Panel 2〜4 を生成した（2026-10-30 14:40）

各コマ1回だけ。やり直しはしていない。スクリプトを直して、返ってきた形式に合った拡張子で保存するようにした（commit 00025f2）。3枚とも JPEG で返ってきたので .jpg で保存している。
モデルは gemini-3.1-flash-image。費用は 1ドル＝156.23円（2026-09-17 の値）で計算した。単価は、入力 $0.50、出力の画像 $60、出力の残り（thinking）$3（どれも100万トークンあたり）。

| コマ | 画素数（比） | 入力トークン | 出力トークン（画像＋残り） | 1枚の費用 | 時間 |
|---|---|---|---|---|---|
| P2 | 928×1152（0.8056） | 3,443 | 1,681（1,120＋561） | 約 11.0 円（$0.0706） | 18.2 秒 |
| P3 | 928×1152（0.8056） | 3,478 | 1,572（1,120＋452） | 約 11.0 円（$0.0703） | 19.4 秒 |
| P4 | 928×1152（0.8056） | 3,867 | 1,865（1,120＋745） | 約 11.1 円（$0.0714） | 13.6 秒 |

**3枚の合計：約 33.2 円（$0.212）**

出力先：`作成データ/縦長試作_20261030/API試し/第61話_P{2,3,4}_api_gemini-3.1-flash-image_20261030_1440xx.jpg`（同じ名前の .json がある）
メモ：P2〜P4 のプロンプトの「USE ATTACHED REFERENCES」には、下地（4:5 の白紙）の行がない。下地は指示どおり最初に添付した。
````

#### gemini_panel.py（Claude Code が作ったスクリプト）

```python
#!/usr/bin/env python3
"""Gemini の画像 API でマンガの1コマを1枚だけ生成する。

使い方:
  python3 manga/gemini_panel.py --prompt P.txt --image canvas.png --image hakobu.png \
      --out-dir OUT --model gemini-3.1-flash-image --name 第61話_P1

- 鍵は /Volumes/KIOXIA/gemini_api_key.txt から読むだけ (表示・保存しない)
- 縦横比 4:5 / 大きさ 1K を generationConfig.imageConfig で指定
- 生成は1回だけ。失敗してもやり直さない
- 出力: {name}_api_{model}_{日時}.{png|jpg|webp} (返ってきた形式に合わせる) と同じ名前の .json
  (使用量など。画像データは含めない)
"""
import argparse
import json
import mimetypes
import sys
import time
from datetime import datetime
from pathlib import Path

from google import genai
from google.genai import types

KEY_PATH = Path("/Volumes/KIOXIA/gemini_api_key.txt")
EXT_BY_MIME = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}


def load_key():
    if not Path("/Volumes/KIOXIA").is_dir():
        sys.exit("ABORT: KIOXIA がつながっていない")
    if not KEY_PATH.is_file():
        sys.exit(f"ABORT: 鍵のファイルがない: {KEY_PATH}")
    key = KEY_PATH.read_text().strip()
    if not key:
        sys.exit("ABORT: 鍵のファイルが空")
    return key


def strip_inline_data(obj):
    """返事の dict から画像のバイト列を取り除く。"""
    if isinstance(obj, dict):
        out = {}
        for k, v in obj.items():
            if k in ("inline_data", "inlineData") and isinstance(v, dict):
                out[k] = {kk: vv for kk, vv in v.items() if kk != "data"}
                out[k]["data"] = "<omitted>"
            elif k in ("thought_signature", "thoughtSignature"):
                out[k] = "<omitted>"
            else:
                out[k] = strip_inline_data(v)
        return out
    if isinstance(obj, list):
        return [strip_inline_data(v) for v in obj]
    return obj


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--prompt", required=True, type=Path)
    ap.add_argument("--image", action="append", default=[], type=Path,
                    help="添付する画像 (この順で渡す。複数可)")
    ap.add_argument("--out-dir", required=True, type=Path)
    ap.add_argument("--model", required=True)
    ap.add_argument("--name", default="panel", help="出力ファイル名の頭")
    ap.add_argument("--aspect-ratio", default="4:5")
    ap.add_argument("--image-size", default="1K")
    args = ap.parse_args()

    prompt = args.prompt.read_text(encoding="utf-8")
    if not prompt.strip():
        sys.exit(f"ABORT: プロンプトが空: {args.prompt}")
    for p in args.image:
        if not p.is_file():
            sys.exit(f"ABORT: 画像がない: {p}")

    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    stem = f"{args.name}_api_{args.model}_{stamp}"
    args.out_dir.mkdir(parents=True, exist_ok=True)
    json_path = args.out_dir / f"{stem}.json"
    if any(args.out_dir.glob(f"{stem}.*")):
        sys.exit(f"ABORT: 同名ファイルがある: {stem}")

    contents = [prompt]
    for p in args.image:
        mime = mimetypes.guess_type(p.name)[0] or "image/png"
        contents.append(types.Part.from_bytes(data=p.read_bytes(), mime_type=mime))

    config = types.GenerateContentConfig(
        response_modalities=["TEXT", "IMAGE"],
        image_config=types.ImageConfig(
            aspect_ratio=args.aspect_ratio,
            image_size=args.image_size,
        ),
    )

    client = genai.Client(api_key=load_key())
    record = {
        "requested_model": args.model,
        "prompt_file": str(args.prompt),
        "images": [str(p) for p in args.image],
        "aspect_ratio": args.aspect_ratio,
        "image_size": args.image_size,
        "started_at": datetime.now().isoformat(timespec="seconds"),
    }
    t0 = time.time()
    try:
        resp = client.models.generate_content(
            model=args.model, contents=contents, config=config)
    except Exception as e:
        record["elapsed_sec"] = round(time.time() - t0, 2)
        record["error"] = f"{type(e).__name__}: {e}"
        json_path.write_text(json.dumps(record, ensure_ascii=False, indent=2),
                             encoding="utf-8")
        print(f"ERROR: {record['error']}")
        print(f"json: {json_path}")
        sys.exit(1)
    record["elapsed_sec"] = round(time.time() - t0, 2)

    image_saved = False
    texts = []
    for cand in resp.candidates or []:
        for part in (cand.content.parts if cand.content else None) or []:
            if part.inline_data and part.inline_data.data and not image_saved:
                mime = part.inline_data.mime_type or "image/png"
                img_path = args.out_dir / f"{stem}{EXT_BY_MIME.get(mime, '.bin')}"
                img_path.write_bytes(part.inline_data.data)
                record["image_mime_type"] = mime
                image_saved = True
            elif part.text and not part.thought:
                texts.append(part.text)

    record["model_version"] = resp.model_version
    record["response_id"] = resp.response_id
    record["usage_metadata"] = (resp.usage_metadata.model_dump(mode="json", exclude_none=True)
                                if resp.usage_metadata else None)
    record["finish_reasons"] = [str(c.finish_reason) for c in resp.candidates or []]
    record["texts"] = texts
    record["image_file"] = str(img_path) if image_saved else None
    record["response"] = strip_inline_data(resp.model_dump(mode="json", exclude_none=True))
    json_path.write_text(json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")

    print(f"elapsed: {record['elapsed_sec']} s / model_version: {resp.model_version}")
    print(f"usage: {json.dumps(record['usage_metadata'], ensure_ascii=False)}")
    print(f"image: {img_path if image_saved else '(画像なし)'}")
    print(f"json: {json_path}")
    if not image_saved:
        sys.exit(1)


if __name__ == "__main__":
    main()
```

### 1コマ目の絵とサイトのマンガを見比べた

返ってきた1コマ目は 928×1152 でした。頼んだ 1024×1280 より1割ほど小さく、比はほぼ 4:5 です。中身は JPEG で、名前だけ .png になっていたので、2枚目からは拡張子を中身に合わせました。

絵を見て、最初は「カメラが高い」と指摘しました。プロンプトでは「カメラは約50cm・上面を見せない」と頼んでいたのに、ハコブくんの上面が見えていたからです。ところが、サイトに出しているマンガ（第61話・第50話・第24話の公開版）を見直すと、ハコブくんは上面が少し見える高さで描くのがふつうでした。指摘は取り下げ、縦長の指示とカメラワーク規定のほうを直しました。

```bash
cd "$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ"; python3 - <<'EOF'
p1="プロジェクトのファイル_md_20261030/カメラワーク規定_縦長版.md"
p2="マンガのプロジェクト指示_縦長版_20261030.md"
p3="縦長試作_20261030/縦長の差分指示.md"
s=open(p1,encoding='utf-8').read()
old="- ハコブくんを上から見下ろすショットは禁止（既存ルール）。見下ろす高さにするのは、人物だけを撮るコマに限る\n"
new=old+"- ハコブくん（1150mm）を人の目線（約150cm）から撮ると、上面が少し見える。これがサイトのマンガのふつうの見え方で、見下ろすショットには当たらない。ハコブくんが出るコマの高さは、まず人の目線を基本にする\n- 低い位置（約50cm）は、コマの中身で選ぶ（床・足元・引きずる物を大きく見せる、など）。上面を見せたくない理由がないときは使わない\n"
assert s.count(old)==1; open(p1,'w',encoding='utf-8').write(s.replace(old,new))
s=open(p2,encoding='utf-8').read()
old="- ハコブくんの「あおり・俯瞰なし」は変えない。高さの差は人物の仕草（見上げる・見下ろす）で出す\n"
new=old+"- ハコブくんが出るコマのカメラは、人の目線（約150cm・上面が少し見える）を基本にする。これはサイトのマンガのふつうの見え方で、俯瞰には当たらない。低い位置（約50cm）は、床・足元を見せるなどコマの中身で選ぶときだけにする\n"
assert s.count(old)==1; open(p2,'w',encoding='utf-8').write(s.replace(old,new))
s=open(p3,encoding='utf-8').read().rstrip('\n')+"\n"
s+="""
## 試作で分かったこと（第61話・Gemini の API・2026-10-30）

- **カメラの高さ**：Panel 1 を「低め（約50cm）・上面を見せない」で頼んだが、上面が少し見える高さで描かれた。サイトのマンガもこの見え方がふつう（第61話・第50話・第24話の公開版で確認）。ハコブくんが出るコマは人の目線（約150cm）を基本にし、低い位置はコマの中身で選ぶときだけにする（カメラワーク規定とプロジェクト指示に書き足した）
"""
open(p3,'w',encoding='utf-8').write(s)
print("ok")
EOF
```

### プロンプトを作る場所を、この会話に移すと決めた

ここまでプロンプトは、claude.ai の別のプロジェクト（縦長のマンガのプロンプトを作るプロジェクト）で作り、テキストに貼って保存していました。プロンプトの決まり（縦長の指示・カメラワーク規定・リファレンス台帳）は、どれも Mac のファイルにあります。この会話で読めば、プロンプト作りから生成・確かめまで1つの会話でつながります。第61話の Panel 2〜4 だけは前のプロジェクトで作り、次の話からはこの会話（開発ログのプロジェクト）で作ると決めました。

- ねらい：プロンプトを貼って保存する手間と、決まりの写しが2か所でずれることをなくす
- 変わったこと：決まりの正は Mac のファイル1つ。プロンプト・生成・確かめを同じ会話で行う
- 見込める効果：別のプロジェクトにファイルを入れ直す作業がなくなる（第61話では、カメラの決まりを直したあとに1回入れ直した）


## 作業ログ ― 参照画像に名前の札を付けて、取り違えを直した

### Claude Code が描いた Panel 2〜4 は、髪型を取り違えた

Panel 2〜4 も Claude Code に1回ずつ描かせました。三面図と見比べると、合っていないところがはっきりありました。

- P2・P4 の高杉さんが長い髪で描かれた。三面図はあご丈の黒いボブ
- P3 で押す人がボブで描かれた。押すのはお団子の大城さん
- P4 のハコブくんが、画面が横に付いた箱の形に崩れた。セリフも「どこにも書／書いてないのよ」と「書」が2回出た

原因の1つは、プロンプトの見分けの言葉でした。縦長の指示では、高杉さんの見分けの言葉を `hair worn down, no glasses`（髪をおろす・メガネなし）と決めていました。アプリでは添付の三面図が勝ってボブになっていたようですが、API では「おろす」を長い髪と読みました。もう1つは、画像を並べて送るだけで、どの画像が誰の三面図かを伝えていなかったことです。

### gemini_gen_labeled.py：画像の前に名前の札を付けて送る

API では、画像の前に文を挟めます。そこで、画像ごとに「Reference image — Nurse Takasugi, short chin-length black bob…」のような札を付けて送るスクリプトを、こちらで書きました。Mac の作業用の窓（Linux）から Google の API に直接届いたので、Claude Code を通さずに動かしています。

#### gemini_gen_labeled.py

```python
#!/usr/bin/env python3
"""Gemini の画像の API で、名前の札を付けた参照画像と一緒に1コマを生成する（第61話の描き直し用・2026-10-30）
使い方: python3 gemini_gen_labeled.py <プロンプト.txt> <出力の頭> <札=画像のパス> ...
鍵は KIOXIA/gemini_api_key.txt から読むだけ（表示しない）"""
import sys, json, base64, time, urllib.request, mimetypes, os, datetime
MODEL = "gemini-3.1-flash-image"
KEY_PATHS = [os.path.expanduser("~/mnt/KIOXIA/gemini_api_key.txt"), "/Volumes/KIOXIA/gemini_api_key.txt"]
key = next(open(p).read().strip() for p in KEY_PATHS if os.path.exists(p))
prompt_path, out_head, refs = sys.argv[1], sys.argv[2], sys.argv[3:]
parts = []
for r in refs:
    label, path = r.split("=", 1)
    mime = mimetypes.guess_type(path)[0] or "image/png"
    parts.append({"text": f"Reference image — {label}:"})
    parts.append({"inlineData": {"mimeType": mime, "data": base64.b64encode(open(path, "rb").read()).decode()}})
parts.append({"text": open(prompt_path, encoding="utf-8").read()})
body = {"contents": [{"role": "user", "parts": parts}],
        "generationConfig": {"responseModalities": ["IMAGE"], "imageConfig": {"aspectRatio": "4:5", "imageSize": "1K"}}}
req = urllib.request.Request(f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent",
        data=json.dumps(body).encode(), headers={"Content-Type": "application/json", "x-goog-api-key": key})
t = time.time()
try:
    res = json.load(urllib.request.urlopen(req, timeout=170))
except urllib.error.HTTPError as e:
    print("失敗", e.code, e.read().decode()[:800]); sys.exit(1)
el = time.time() - t
stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
img = None
for c in res.get("candidates", []):
    for p in c.get("content", {}).get("parts", []):
        if "inlineData" in p:
            img = p["inlineData"]
ext = ".jpg" if img and "jpeg" in img["mimeType"] else ".png"
base = f"{out_head}_{MODEL}_{stamp}"
if img:
    open(base + ext, "wb").write(base64.b64decode(img["data"]))
for c in res.get("candidates", []):
    for p in c.get("content", {}).get("parts", []):
        p.pop("inlineData", None)
meta = {"model": MODEL, "elapsed_s": round(el, 2), "prompt": prompt_path, "refs": refs, "response": res}
json.dump(meta, open(base + ".json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
u = res.get("usageMetadata", {})
print(os.path.basename(base + ext) if img else "画像なし", f"{el:.1f}秒", "入力", u.get("promptTokenCount"), "出力", u.get("candidatesTokenCount"), res.get("candidates", [{}])[0].get("finishReason"))
```

プロンプトは、見分けの言葉と、髪の違いだけを書いた独立の節（WHO IS WHO）を足して作り直しました。

```bash
cd "$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長試作_20261030"; python3 - <<'EOF'
T_LIST="- Nurse Takasugi — SHORT chin-length straight black BOB with bangs, no glasses (her labelled reference sheet)"
O_LIST="- Nurse Oshiro — black hair pulled back tight into a BUN at the back of the head, NOT a bob (her labelled reference sheet)"
HAIR_BLOCK="""WHO IS WHO — HAIR IS THE KEY, NEVER SWAP:
- every reference image is labelled with the character's name. Match each character ONLY to her own labelled sheet
- Takasugi: SHORT chin-length black BOB, the hair ends at the jaw line. Her hair NEVER reaches her shoulders. NO long hair, NO ponytail, NO bun
- Oshiro: hair pulled back into a BUN at the back of her head, forehead and ears visible. NEVER a bob, NEVER hair hanging down
"""
TAKA_ONLY_BLOCK="""TAKASUGI'S HAIR — MATCH HER LABELLED SHEET:
- Takasugi has SHORT chin-length straight black BOB hair with bangs, the hair ends at the jaw line. Her hair NEVER reaches her shoulders. NO long hair, NO ponytail, NO bun
"""
HAKO_SHAPE="""HAKOBUKUN SHAPE — AS ON ITS LABELLED SHEET:
- a NARROWER head unit carrying the screen sits on TOP of a WIDER body. The screen faces the SAME direction as the front of the body
- the circular ring panel is on its SIDE, never on the face side. Do NOT draw it as a box with the screen on its side, NOT like a TV or microwave
- the body is complete from the head unit down to the black band and the dotted base; it is NOT squashed short
"""
def fix(n, s):
    s=s.replace("- Nurse Takasugi (hair worn down, no glasses)\n", T_LIST+"\n")
    s=s.replace("- Nurse Takasugi\n", T_LIST+"\n")
    s=s.replace("- Nurse Oshiro (bun)\n", O_LIST+"\n")
    s=s.replace("- Hakobukun\n", "- Hakobukun (its labelled orthographic sheet: front, side, rear)\n",1)
    s=s.replace("Her hair is worn down, as dark as on the sheet","Her hair is a SHORT chin-length black BOB exactly as on her sheet, as dark as on the sheet")
    s=s.replace("- her hair is worn down at the length shown on her reference sheet, as dark as on the sheet","- her hair is a SHORT chin-length black BOB exactly as on her reference sheet, as dark as on the sheet")
    i=s.index("COSTUME CONSISTENCY") if "COSTUME CONSISTENCY" in s else s.index("HAKOBUKUN — TEXT-FREE")
    s=s[:i]+(HAIR_BLOCK if n in (3,4) else TAKA_ONLY_BLOCK)+"\n"+s[i:]
    if n==4:
        old="- Hakobukun's body is rotated about 45° to face camera-LEFT, toward Takasugi — the viewer sees its screen in three-quarter view and its LEFT side with the circular ring panel\n"
        assert old in s
        s=s.replace(old, old+"- in the picture the screen is on the LEFT part of the robot, angled toward Takasugi; its side with the circular ring panel shows to the RIGHT of the screen\n")
        j=s.index("HAKOBUKUN ORIENTATION")
        s=s[:j]+HAKO_SHAPE+"\n"+s[j:]
        old="- draw each line ONCE."
        s=s.replace(old,"- Takasugi's bubble holds exactly three short lines: 押していいって、 / どこにも / 書いてないのよ — the kanji 書 appears ONLY ONCE\n"+old,1)
    return s
for n in (2,3,4):
    s=fix(n, open(f"第61話_P{n}_プロンプト.txt",encoding='utf-8').read())
    assert "worn down" not in s, n
    open(f"第61話_P{n}_プロンプト_v2.txt",'w',encoding='utf-8').write(s)
    print(n, len(s.encode()))
EOF
```

```text
2 10208
3 9412
4 11855
```

3枚を同時に描かせたときのコマンドと出力です。札の文は、三面図の要点（髪型・メガネ）と、並び図の身長だけにしました。

```bash
D="$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ"; V="$D/縦長試作_20261030"; cd "$V/API試し"
C="blank 4:5 canvas, it only sets the shape of the picture, draw nothing from it=$D/canvas_4x5_1024x1280.png"
T="Nurse Takasugi, short chin-length black bob, no glasses (front, back, side)=$D/高杉綾-女性看護師.png"
O="Nurse Oshiro, hair in a bun at the back (front, back, side)=$D/大城愛名-女性看護師.png"
H="Hakobukun robot orthographic sheet (front, side, rear)=$D/ハコブくん.png"
L="lineup sheet of relative heights, labels under each figure: ハコブくん 115cm, 高杉 155cm, 大城 165cm=$D/並び図_主要キャラ.png"
python3 gemini_gen_labeled.py "$V/第61話_P2_プロンプト_v2.txt" "第61話_P2_v2" "$C" "$T" "$H" "$L" &
python3 gemini_gen_labeled.py "$V/第61話_P3_プロンプト_v2.txt" "第61話_P3_v2" "$C" "$T" "$O" "$H" "$L" &
python3 gemini_gen_labeled.py "$V/第61話_P4_プロンプト_v2.txt" "第61話_P4_v2" "$C" "$T" "$O" "$H" "$L" &
wait
```

```text
第61話_P4_v2_gemini-3.1-flash-image_20261030_055115.jpg 21.8秒 入力 4341 出力 1763 STOP
第61話_P2_v2_gemini-3.1-flash-image_20261030_055120.jpg 26.3秒 入力 3639 出力 1702 STOP
第61話_P3_v2_gemini-3.1-flash-image_20261030_055129.jpg 35.1秒 入力 3765 出力 1587 STOP
```

3枚とも、三面図どおりの髪型になりました。 P4 のハコブくんの形と、セリフの字の重なりも直りました。

【画像】LOG410_02_before_after.jpg 名前の札を付ける前と後の比べ。P2 は長い髪からボブに、P3 は押す人がボブからお団子の大城さんに、P4 は箱の形に崩れたハコブくんと重なった字が直った

- ねらい：どの参照画像が誰の三面図かを、Gemini に取り違えさせない
- 変わったこと：画像の前に、名前と見分けの要点を書いた札を付けて送る。プロンプトには髪の違いだけの節を置く
- 見込める効果：P2〜P4 の3枚で、髪型の取り違えが3枚から0枚になった（直す前は Claude Code のスクリプトで札なし、直した後は札あり。どちらも1回ずつ描いた結果）

### 三面図にない停止ボタンは、最初に描けたコマを見本にした

第61話には、ハコブくんの三面図にない部品（非常停止のボタン）が出てきます。ところが、コマごとに形が変わりました。P2・P3 は「白い台に黒い平たいふた」、P1 は灰色のキノコ形、P4 はキノコ形やボタンなし。

そこで、P2 で描けたボタンを切り抜いて「停止ボタンの見本」と札を付け、ほかのコマにも添えました。これで P1・P4 もほかのコマと同じ形になりました。**三面図にない部品は、最初に描けたコマを見本にして、残りのコマに添えるとそろいます。**

```bash
cd "$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長試作_20261030"; python3 - <<'EOF'
from PIL import Image
im=Image.open("API試し/第61話_P2_v2_gemini-3.1-flash-image_20261030_055120.jpg")
c=im.crop((200,500,420,640)); c=c.resize((c.width*3,c.height*3),Image.LANCZOS); c.save("API試し/停止ボタンの見本_P2v2より.png")
EOF
```

### 1コマ目は4回描いた（しっぽ・背中の小窓・袋の位置・ボタン）

1コマ目は、直すたびに別のところがずれて、4回描きました。

| 回 | 直したこと | 次に見つかったこと |
|---|---|---|
| 1回目（Claude Code） | — | 吹き出しのしっぽがハコブくんを向き、ハコブくんがしゃべっているように見える。吹き出しが横いっぱい |
| 2回目 | しっぽは吹き出しの右上から、右上の角の外へ。幅は画像の約44% | 背中の小窓に顔（目と口）が出た |
| 3回目 | 背中の小窓は何も映らない暗い窓 | 袋がハコブくんの横（車輪の脇）に付いた。ボタンが灰色のキノコ形 |
| 4回目 | 袋は進む向きの真後ろ。背面の通気口の下の真ん中に結び目が挟まる。ボタンの見本を添えた | 使った絵 |

袋の位置は「真後ろ」と書くだけでは伝わらず、「背面（小窓・扉・黒い通気口の面）の下の真ん中」と、面の名前で書くと直りました。

【画像】LOG410_03_p1_tries.jpg 1コマ目を4回描いた絵。しっぽが下を向いた1回目、背中の小窓に顔が出た2回目、袋が横に付いてボタンが灰色の3回目、袋が真後ろでボタンがそろった4回目

4枚目もボタンの形がほかと違ったので2回描き直し、1回目は「押セマス」が「押セス」と1字抜けました。行の分け方と「セとスの間にマ」を書くと直りました。セリフは毎回1字ずつ見比べています。

### 生成した13枚の時間と費用

費用は、返事の使用量（入力・出力のトークン数）と、Google の[料金のページ](https://ai.google.dev/gemini-api/docs/pricing)の単価（入力 $0.50・出力の画像 $60・出力の残り $3。どれも100万トークンあたり）で計算し、1ドル＝156.23円で円にしました。

| ファイル | 描いた人 | 時間 | 入力 | 出力 | 費用 |
|---|---|---|---|---|---|
| P1_api（1回目） | Claude Code | — | — | — | 0円（無料枠の上限0で断られた） |
| P1_api（2回目） | Claude Code | 13.1秒 | 2,855 | 1,644 | 11.0円 |
| P2_api | Claude Code | 18.2秒 | 3,443 | 1,681 | 11.0円 |
| P3_api | Claude Code | 19.4秒 | 3,478 | 1,572 | 11.0円 |
| P4_api | Claude Code | 13.6秒 | 3,867 | 1,865 | 11.1円 |
| P2_v2 | Claude（札あり） | 26.3秒 | 3,639 | 1,702 | 11.1円 |
| P3_v2 | Claude（札あり） | 35.1秒 | 3,765 | 1,587 | 11.0円 |
| P4_v2 | Claude（札あり） | 21.8秒 | 4,341 | 1,763 | 11.1円 |
| P1_v2 | Claude（札あり） | 12.0秒 | 3,080 | 1,539 | 10.9円 |
| P4_v3 | Claude（札あり） | 20.5秒 | 4,396 | 1,979 | 11.2円 |
| P1_v3 | Claude（札あり） | 19.9秒 | 3,127 | 1,634 | 11.0円 |
| P1_v4 | Claude（札あり・ボタンの見本） | 16.3秒 | 3,508 | 1,609 | 11.0円 |
| P4_v4 | Claude（札あり・ボタンの見本） | 23.7秒 | 4,697 | 1,580 | 11.1円 |
| P4_v5 | Claude（札あり・ボタンの見本） | 21.1秒 | 4,756 | 1,839 | 11.2円 |
| 合計 | | | | | 13枚・143.7円 |

出力のうち画像の分は、どの回も 1,120 トークンでした。添付の画像を増やすと入力のトークンが増えますが、費用の大半は出力の画像の分（1枚 $0.0672）なので、1枚の費用はほとんど変わりません。

### 使った4枚のプロンプト

#### 第61話_P1_プロンプト_v4.txt

```text
black and white manga panel artwork on a flat chroma-key green background, single panel, no panel border, no frame line
the artwork bleeds to all four edges of the image

USE ATTACHED REFERENCES — match face, hair, body proportions, robot shape, line weight, and screentone density EXACTLY. Do not stylize beyond reference.
- Hakobukun — ハコブくん.png
- the blank 4:5 canvas image only sets the shape of the picture — draw nothing from it

HAKOBUKUN — TEXT-FREE MACHINE, HIGHEST PRIORITY:
- Hakobukun's body carries NO writing of any kind. Every character of dialogue belongs inside a speech bubble
- the tablet screen at the top of Hakobukun is its FACE: a dark screentone rectangle carrying exactly three simple white shapes — two small vertical rounded-oval eyes and one small mouth between and below them. The eyes are FLAT SOLID WHITE ovals with NO pupil, NO iris, NO highlight, NO outline, NO eyelash — simple pictogram shapes, not anime eyes. Keep them small relative to the screen; the dark screen area around them stays wide and empty. The screen shows an EXPRESSION, never words
- the mouth is a small white OPEN SMILE — a half-circle with a flat top edge and a rounded bottom edge, exactly as on the reference sheet. NOT a thin curved line, NOT a wide grin, NO teeth
- the EYES DO NOT CHANGE: they stay the same two flat solid white ovals, fully round-ended, NOT narrowed, NOT half-closed, NOT curved, NOT tilted. Only the mouth carries the expression
- every surface of Hakobukun carries only the details shown on the reference sheet — nothing is added, and NO writing appears anywhere on the body
- Hakobukun has NO arms, NO hands, NO attachments of any kind

STOP BUTTON — THE ONLY ADDED PART:
- Hakobukun carries ONE extra part that is not on its reference sheet: a single round emergency-stop push button. This is the ONLY exception to "nothing is added"
- it sits on the TOP surface of Hakobukun, near the REAR edge, behind the screen housing
- SAME DESIGN AS IN THE OTHER PANELS — copy the labelled stop-button sample image: a LOW, FLAT, round cap filled almost SOLID BLACK, sitting directly on a low WHITE round collar. About as wide as a palm. NOT a tall mushroom, NO stem, NOT light grey, NOT dotted grey
- the button carries NO text, NO symbol, NO label, NO arrow. There is exactly ONE button — NO other buttons, switches, or lamps are added
- the button is clearly visible on the top surface

HAKOBUKUN PROPORTIONS — DO NOT REDRAW:
- copy Hakobukun's silhouette directly from the attached orthographic reference sheet — same body, same outline, same ratio
- height-to-width ratio is FIXED at 1.7 : 1 (height = 1.7 × width)
- do NOT stretch it taller; do NOT squash it wider; do NOT slim it beside a human
- Hakobukun is a WIDE, STOUT, boxy machine — when in doubt, err WIDER, never taller
- draw Hakobukun upright, in front, three-quarter, or rear view only — NO tilted, NO foreshortened, NO low-angle or high-angle views

HAKOBUKUN SCALE — FIXED IN PIXELS:
- the image is 1280px tall. Hakobukun, standing upright: the TOP of its body lands at about 24% of the image height and the BOTTOM OF ITS WHEELS lands at about 74%
- it is about 640px tall; its rear face is about 376px wide (640 ÷ 1.7). In three-quarter view its side also shows, so the whole silhouette is somewhat wider — never narrower
- do NOT enlarge it to fill the frame, and do NOT shrink it

HAKOBUKUN SEEN FROM BEHIND — FACE NOT VISIBLE:
- Hakobukun is driving AWAY from the viewer, toward the upper-left, its body turned 45° to camera-left — the viewer sees its REAR panel and its RIGHT flank with the circular ring panel, on the viewer's right side of the robot
- its face screen points away from the viewer and is NOT visible. Do NOT turn it around to show the face
- the small dark window on its rear panel is a PLAIN dark window: NO eyes, NO mouth, NO face, NO symbols. The only face Hakobukun has is the front screen, which is hidden in this picture
- its body stays upright; only the speed lines show the motion

LINEN BAG — DRAGGED, NOT CARRIED:
- one white cloth linen bag, stuffed soft and round with sheets, its mouth tied shut in a knot. Plain cloth with NO text, NO logo, NO pattern
- the bag is DIRECTLY BEHIND Hakobukun, on its line of travel: it lies on the floor right in front of the REAR panel (the panel with the small dark window, the door and the dark vent), between the rear panel and the viewer
- its knotted neck is caught under the CENTRE of the bottom edge of the rear panel, below the dark vent. It is NOT beside the side with the circular ring panel, NOT next to a side wheel, NOT off to one side
- the bag is NOT part of the robot, NOT tied on deliberately, NOT inside a drawer — it is snagged by accident
- the bag lies on its side and is dragged along behind the robot, its cloth bunched and creased by the pull
- short broken speed lines trail behind the bag, pointing back toward the viewer, opposite to the robot's direction of travel, to show it scraping along. They are short dashes, NOT a continuous floor line
- the bag and Hakobukun are the ONLY objects in the picture

OFF-SCREEN VOICE — NO PERSON IN THE PICTURE:
- the speaker of the bubble is NOT drawn. NO person, NO hand, NO arm, NO foot, NO shadow of a person anywhere in the picture
- only the bubble's tail shows where the voice comes from: from outside the picture, beyond the upper-right corner

OFF-SCREEN VOICE BUBBLE — TAIL POINTS UP AND OUT, STRICT:
- the bubble is a compact rounded oval, about 44% of the image width — NOT a wide band across the picture. Its left edge is at about 46% and its right edge at about 90% of the image width
- the text sits on two lines inside it: ちょっと、 / 引っかかってる！
- the tail grows out of the TOP-RIGHT of the bubble's outline and points UP and to the RIGHT, toward the upper-right corner of the image, where the off-screen speaker is
- the tail NEVER points down, NEVER points toward Hakobukun, NEVER points toward the bag. Hakobukun is NOT the speaker
- the tail is short and ends inside the image, a little before the corner — it does NOT touch the edge

STYLE — match references, avoid drift:
- flat clean line art, minimal shading
- light screentone only for clothes/hair, NO heavy hatching
- ALL shading, including on the robots, is done with dot-pattern screentone with visible halftone dots — NO smooth gradient, NO airbrush, NO 3D-render shading
- NO ink-heavy seinen/gekiga style
- NO photorealistic face shading, NO sweat-drop overdraw
- character faces stay simple and consistent with reference
- line weight: thin and even, not dramatic

BACKGROUNDS — FLAT CHROMA KEY GREEN:
- the entire background is filled with one completely flat, uniform chroma-key green (#00B140) — a strong, saturated green, NOT pale green, NOT mint — a solid single-tone field with NO gradient, NO shading, NO texture, NO vignette, NO lighting falloff
- the green reaches every edge of the image and fills all empty space between and around the figures
- NO corridor perspective, NO windows, NO ceiling lights, NO door frames, NO wall paneling
- NO tables, NO chairs, NO furniture
- do NOT use any green anywhere on the characters, the robots, the props, the clothing, the line art, or the speech bubbles — green appears ONLY in the background
- the boundary between the figures and the green is a clean, hard edge — NO soft blur, NO glow, NO anti-aliased halo, NO drop shadow
- the figures keep their original black-and-white manga line art with white fills and screentone
- speech bubbles stay filled with solid pure white, NOT green
- NO floor line, NO ground line, NO horizon line anywhere — the figures stand directly against the flat green
- focus is on the characters and robots, NEVER the environment

DIALOGUE LANGUAGE:
- ALL dialogue text must be rendered in Japanese exactly as written below
- DO NOT translate any line into English

SPEECH BUBBLE TEXT — STRICT:
- the bracketed speaker labels are instructions for YOU only — they are NOT part of the artwork
- render ONLY the text inside 「」 as visible characters; draw NOTHING else
- that text goes inside ordinary rounded speech bubbles — NO jagged bubbles, NO burst shapes
- NEVER draw the speaker label, the brackets, a name, or a colon inside or beside any speech bubble. NEVER draw a bubble that contains only a name
- the 「」 quotation marks themselves are not drawn
- the text in every bubble runs HORIZONTALLY, left to right
- each bubble's tail points clearly to its own speaker, as stated in COMPOSITION. If two speakers say the same words, draw two separate bubbles
- draw each line ONCE. Do NOT repeat, duplicate, or echo any line inside a bubble
- reproduce each 「」 line CHARACTER BY CHARACTER, exactly as many characters as written — do NOT insert, repeat, or duplicate any single character inside a word
- letter the dialogue in Japanese manga typesetting style: kanji in a gothic face, kana in a mincho face (the "antique" style used in printed manga)

CAMERA:
- SHOT SIZE: full body of Hakobukun, with the dragged bag large in the foreground
- CAMERA POSITION: diagonally behind Hakobukun on its right side, rear three-quarter, about 1.5 m from the robot
- CAMERA HEIGHT: 50 cm, lens held LEVEL — NOT tilted up. Hakobukun's vertical edges stay vertical
- FOREGROUND: the linen bag, nearer to the camera than the robot, so it is drawn large
- FRAME CROP: nothing is cropped — the robot and the bag are both complete
- NOT a front-facing lineup. NOBODY faces the viewer directly.

COMPOSITION — 4:5, generate at 1024 × 1280:
- PLACEMENT, STRICT: Hakobukun is centred at about 46% of the image width, its top at 24% and its wheels at 74% of the image height
- the bag lies in the lower foreground DIRECTLY in front of the robot's rear panel, between 74% and 94% of the image height, its knotted neck leading straight up to the centre of the rear bottom edge. The robot, the bag and the speed lines form one straight line of travel
- the speech bubble occupies the band between 8% and 20% of the image height, between 46% and 90% of the image width, above the robot. It is drawn COMPLETE, with its full rounded outline and every character visible. NO bubble touches or crosses any edge of the image. If the bubble does not fit, make the bubble SMALLER — never crop it
- the tail of the bubble 「ちょっと、引っかかってる！」 points toward the upper-right corner, toward the off-screen speaker Takasugi. The tail ends inside the image and does NOT touch the edge
- keep at least 8% of the image width empty on the far left and far right

Urgent, comic moment. Hakobukun keeps driving forward, completely unaware that a linen bag is snagged under its base and scraping along the floor behind it. A voice calls out from off-screen.
[Takasugi]「ちょっと、引っかかってる！」
```

#### 第61話_P2_プロンプト_v2.txt

```text
black and white manga panel artwork on a flat chroma-key green background, single panel, no panel border, no frame line
the artwork bleeds to all four edges of the image

USE ATTACHED REFERENCES — match face, hair, body proportions, robot shape, line weight, and screentone density EXACTLY. Do not stylize beyond reference.
- Nurse Takasugi — SHORT chin-length straight black BOB with bangs, no glasses (her labelled reference sheet)
- Hakobukun (its labelled orthographic sheet: front, side, rear)
- the lineup sheet shows their relative heights

TAKASUGI'S HAIR — MATCH HER LABELLED SHEET:
- Takasugi has SHORT chin-length straight black BOB hair with bangs, the hair ends at the jaw line. Her hair NEVER reaches her shoulders. NO long hair, NO ponytail, NO bun

COSTUME CONSISTENCY — match the attached reference sheets exactly:
- Nurse Takasugi wears the same short-sleeved V-neck scrub top and scrub trousers as on her reference sheet, with the same fabric tone
- her hair is a SHORT chin-length black BOB exactly as on her reference sheet, as dark as on the sheet — do NOT lighten it to brown. NO glasses
- she carries nothing in her hands

HAKOBUKUN — TEXT-FREE MACHINE, HIGHEST PRIORITY:
- Hakobukun's body carries NO writing of any kind. Every character of dialogue belongs inside a speech bubble
- the tablet screen at the top of Hakobukun is its FACE: a dark screentone rectangle carrying exactly three simple white shapes — two small vertical rounded-oval eyes and one small mouth between and below them. The eyes are FLAT SOLID WHITE ovals with NO pupil, NO iris, NO highlight, NO outline, NO eyelash — simple pictogram shapes, not anime eyes. Keep them small relative to the screen; the dark screen area around them stays wide and empty. The screen shows an EXPRESSION, never words
- NORMAL: the mouth is a small white OPEN SMILE — a half-circle with a flat top edge and a rounded bottom edge, exactly as on the reference sheet. NOT a thin curved line, NOT a wide grin, NO teeth
- the EYES DO NOT CHANGE: they stay the same two flat solid white ovals, fully round-ended, NOT narrowed, NOT half-closed, NOT curved, NOT tilted. Only the mouth carries the expression
- every surface of Hakobukun carries only the details shown on the reference sheet — nothing is added, and NO writing appears anywhere on the body
- Hakobukun has NO arms, NO hands, NO attachments of any kind

HAKOBUKUN MOUTH — A FILLED SHAPE, NOT A LINE:
- the mouth is a SOLID FILLED white shape: a half-circle, flat edge on top, rounded edge below, white all the way through
- it is NOT a curved stroke, NOT a smile line, NOT an outline with dark inside

STOP BUTTON — THE ONLY ADDED PART:
- in this picture Hakobukun carries ONE part that is NOT on the reference sheet: a single round emergency-stop push button. It is the ONLY exception to "nothing is added"
- it sits on the TOP surface of Hakobukun's head unit, toward the REAR edge, behind the screen
- shape: a short round base with a slightly domed, mushroom-shaped cap, about the width of a palm. The cap is filled with dark screentone so it stands out against the white body; the base is white with a thin outline
- the button carries NO text, NO symbol, NO arrow, NO ring of lettering
- there is exactly ONE button. Do NOT add any other button, switch, lamp, or panel

HAKOBUKUN PROPORTIONS — DO NOT REDRAW:
- copy Hakobukun's silhouette directly from the attached orthographic reference sheet — same body, same outline, same ratio
- height-to-width ratio is FIXED at 1.7 : 1 (height = 1.7 × width)
- do NOT stretch it taller; do NOT squash it wider; do NOT slim it beside a human
- Hakobukun is a WIDE, STOUT, boxy machine — when in doubt, err WIDER, never taller
- draw Hakobukun upright, in front, three-quarter, or rear view only — NO tilted, NO foreshortened, NO low-angle or high-angle views

HAKOBUKUN SCALE — MEASURED AGAINST TAKASUGI'S BODY, STRICT:
- Hakobukun is about 74% of Takasugi's total height
- the TOP EDGE of Hakobukun's screen is LEVEL WITH her chest
- her shoulders, chin, and face are all ABOVE the robot. If the screen reaches her shoulders, it is drawn too large. If the robot only reaches the waist, it is drawn too small
- its width is its height divided by 1.7
- both stand on the same invisible ground: the bottom of its wheels and the soles of her shoes would be level, below the bottom edge of the image
- do NOT enlarge Hakobukun to balance the composition, and do NOT shrink it to waist height

HAKOBUKUN ORIENTATION:
- Hakobukun's body is rotated about 45° to face camera-LEFT — the viewer sees its screen in three-quarter view and its LEFT side with the circular ring panel. Its rear is toward the right of the image, next to Takasugi
- the top surface of its head unit is slightly visible from above, so the stop button near the rear edge is clearly seen

FINGER NOT TOUCHING THE BUTTON:
- Takasugi reaches toward the stop button with one hand, index finger extended
- her fingertip STOPS about one finger-width ABOVE the cap — there is a clear gap of green between fingertip and button. She is NOT touching it, NOT pressing it
- her whole arm and hand are inside the frame

STYLE — match references, avoid drift:
- flat clean line art, minimal shading
- light screentone only for clothes/hair, NO heavy hatching
- ALL shading, including on the robots, is done with dot-pattern screentone with visible halftone dots — NO smooth gradient, NO airbrush, NO 3D-render shading
- NO ink-heavy seinen/gekiga style
- NO photorealistic face shading, NO sweat-drop overdraw
- character faces stay simple and consistent with reference
- line weight: thin and even, not dramatic

BACKGROUNDS — FLAT CHROMA KEY GREEN:
- the entire background is filled with one completely flat, uniform chroma-key green (#00B140) — a strong, saturated green, NOT pale green, NOT mint — a solid single-tone field with NO gradient, NO shading, NO texture, NO vignette, NO lighting falloff
- the green reaches every edge of the image and fills all empty space between and around the figures
- NO corridor perspective, NO windows, NO ceiling lights, NO door frames, NO wall paneling
- NO tables, NO chairs, NO furniture
- do NOT use any green anywhere on the characters, the robots, the props, the clothing, the line art, or the speech bubbles — green appears ONLY in the background
- the boundary between the figures and the green is a clean, hard edge — NO soft blur, NO glow, NO anti-aliased halo, NO drop shadow
- the figures keep their original black-and-white manga line art with white fills and screentone
- speech bubbles stay filled with solid pure white, NOT green
- NO floor line, NO ground line, NO horizon line anywhere — the figures stand directly against the flat green
- focus is on the characters and robots, NEVER the environment

DIALOGUE LANGUAGE:
- ALL dialogue text must be rendered in Japanese exactly as written below
- DO NOT translate any line into English

SPEECH BUBBLE TEXT — STRICT:
- the bracketed speaker labels are instructions for YOU only — they are NOT part of the artwork
- render ONLY the text inside 「」 as visible characters; draw NOTHING else
- that text goes inside ordinary rounded speech bubbles — NO jagged bubbles, NO burst shapes
- NEVER draw the speaker label, the brackets, a name, or a colon inside or beside any speech bubble. NEVER draw a bubble that contains only a name
- the 「」 quotation marks themselves are not drawn
- the text in every bubble runs HORIZONTALLY, left to right
- each bubble's tail points clearly to its own speaker, as stated in COMPOSITION. If two speakers say the same words, draw two separate bubbles
- draw each line ONCE. Do NOT repeat, duplicate, or echo any line inside a bubble
- reproduce each 「」 line CHARACTER BY CHARACTER, exactly as many characters as written — do NOT insert, repeat, or duplicate any single character inside a word
- letter the dialogue in Japanese manga typesetting style: kanji in a gothic face, kana in a mincho face (the "antique" style used in printed manga)

CAMERA:
- SHOT SIZE: two-shot, Takasugi from the hips up, Hakobukun's upper body
- CAMERA POSITION: front-left of Takasugi, about 40° off her front, 1.5 m from the pair
- CAMERA HEIGHT: 150 cm (human eye level), level with Takasugi's face, looking slightly down onto the top of Hakobukun
- FRAME CROP: the bottom edge of the image cuts Takasugi at her hips and cuts Hakobukun just BELOW the bottom of its screen — the whole screen and the stop button stay inside the frame
- NOT a front-facing lineup. NOBODY faces the viewer directly.

COMPOSITION — 4:5 portrait, generate at 1024x1280:
- PLACEMENT, STRICT: the two figures form ONE close group. Hakobukun is centred at about 36% of the image width, in the lower-left part of the image; Takasugi is centred at about 66%, standing just behind Hakobukun's rear corner — Hakobukun's right edge overlaps the silhouette of her lower body. They are NOT pushed to opposite edges
- keep at least 8% of the image width empty on the far left and far right
- the top of Takasugi's head is at about 28% of the image height; her face sits between about 28% and 42%
- the speech bubble occupies the band between 4% and 24% of the image height, above the figures. It is drawn COMPLETE, with its full rounded outline and every character visible. NO bubble touches or crosses any edge of the image. If the bubble does not fit, make the bubble SMALLER — never crop it
- the tail of the bubble 「これ、私が押していいやつ…？」 points to Takasugi
- the …？ at the end of the line is drawn as written
- nothing important — no face, no bubble, no button — sits in the bottom-right area (the right 15% of the width and the bottom 40% of the height)

Takasugi, body turned about 45° to camera-left, leans slightly toward Hakobukun and reaches for the stop button on top of it, but her finger freezes just above it. Her face is troubled: brows drawn together, eyes looking down at the button, mouth slightly open, exactly ONE small sweat drop at her temple. She is NOT smiling, NOT pressing the button.
[Takasugi]「これ、私が押していいやつ…？」
```

#### 第61話_P3_プロンプト_v2.txt

```text
black and white manga panel artwork on a flat chroma-key green background, single panel, no panel border, no frame line
the artwork bleeds to all four edges of the image

USE ATTACHED REFERENCES — match face, hair, body proportions, robot shape, line weight, and screentone density EXACTLY. Do not stylize beyond reference.
- Nurse Takasugi — SHORT chin-length straight black BOB with bangs, no glasses (her labelled reference sheet)
- Nurse Oshiro — black hair pulled back tight into a BUN at the back of the head, NOT a bob (her labelled reference sheet)
- Hakobukun (its labelled orthographic sheet: front, side, rear)
- the lineup sheet shows their relative heights

WHO IS WHO — HAIR IS THE KEY, NEVER SWAP:
- every reference image is labelled with the character's name. Match each character ONLY to her own labelled sheet
- Takasugi: SHORT chin-length black BOB, the hair ends at the jaw line. Her hair NEVER reaches her shoulders. NO long hair, NO ponytail, NO bun
- Oshiro: hair pulled back into a BUN at the back of her head, forehead and ears visible. NEVER a bob, NEVER hair hanging down

COSTUME CONSISTENCY — match the attached reference sheets exactly:
- Nurse Takasugi wears the same short-sleeved V-neck scrub top as on her reference sheet, with the same fabric tone. Her hair is a SHORT chin-length black BOB exactly as on her sheet, as dark as on the sheet — do NOT lighten it to brown
- Nurse Oshiro wears the same short-sleeved V-neck scrub top and scrub trousers as on her reference sheet, with the same fabric tone
- Oshiro's hair is tied up in a bun. Fill the hair as SOLID BLACK, a single flat dark mass — do NOT render as a mid-tone, do NOT build the tone out of many thin parallel strands
- neither nurse carries anything in her hands

HAKOBUKUN — TEXT-FREE MACHINE, HIGHEST PRIORITY:
- Hakobukun's body carries NO writing of any kind. Every character of dialogue belongs inside a speech bubble
- the tablet screen at the top of Hakobukun is its FACE: a dark screentone rectangle carrying exactly three simple white shapes — two small vertical rounded-oval eyes and one small mouth between and below them. The eyes are FLAT SOLID WHITE ovals with NO pupil, NO iris, NO highlight, NO outline, NO eyelash — simple pictogram shapes, not anime eyes. Keep them small relative to the screen; the dark screen area around them stays wide and empty. The screen shows an EXPRESSION, never words
- STOPPED / SILENT: the mouth is a small FLAT HORIZONTAL LINE. NOT curved, NOT open
- the EYES DO NOT CHANGE: they stay the same two flat solid white ovals, fully round-ended, NOT narrowed, NOT half-closed, NOT curved, NOT tilted. Only the mouth carries the expression
- every surface of Hakobukun carries only the details shown on the reference sheet — nothing is added, and NO writing appears anywhere on the body
- Hakobukun has NO arms, NO hands, NO attachments of any kind

STOP BUTTON — THE ONLY ADDED PART:
- in this picture Hakobukun carries ONE part that is NOT on the reference sheet: a single round emergency-stop push button. It is the ONLY exception to "nothing is added"
- it sits on the TOP surface of Hakobukun's head unit, toward the REAR edge, behind the screen
- shape: a short round base with a slightly domed, mushroom-shaped cap, about the width of a palm. The cap is filled with dark screentone so it stands out against the white body; the base is white with a thin outline
- the button carries NO text, NO symbol, NO arrow, NO ring of lettering
- there is exactly ONE button. Do NOT add any other button, switch, lamp, or panel

HAKOBUKUN PROPORTIONS — DO NOT REDRAW:
- copy Hakobukun's silhouette directly from the attached orthographic reference sheet — same body, same outline, same ratio
- height-to-width ratio is FIXED at 1.7 : 1 (height = 1.7 × width)
- do NOT stretch it taller; do NOT squash it wider; do NOT slim it beside a human
- Hakobukun is a WIDE, STOUT, boxy machine — when in doubt, err WIDER, never taller
- draw Hakobukun upright, in front, three-quarter, or rear view only — NO tilted, NO foreshortened, NO low-angle or high-angle views

HAKOBUKUN SCALE — MEASURED AGAINST OSHIRO'S BODY, STRICT:
- Hakobukun is about 70% of Oshiro's total height
- the TOP EDGE of Hakobukun's screen is LEVEL WITH the pit of her stomach — the bottom of her breastbone
- her chest, shoulders, and face are all ABOVE the robot. If the screen reaches her chest, it is drawn too large. If the robot only reaches the waist, it is drawn too small
- its width is its height divided by 1.7
- both stand on the same invisible ground: the bottom of its wheels and the soles of her shoes would be level, below the bottom edge of the image
- do NOT enlarge Hakobukun to balance the composition, and do NOT shrink it to waist height
- Takasugi is much closer to the camera and is drawn larger by perspective — do NOT size Hakobukun against Takasugi

HAKOBUKUN ORIENTATION:
- Hakobukun's body is rotated about 45° to face camera-LEFT — the viewer sees its screen in three-quarter view and its LEFT side with the circular ring panel. Its rear is toward the right of the image, next to Oshiro
- the top surface of its head unit is slightly visible from above, so the stop button near the rear edge is clearly seen

PALM ON THE BUTTON:
- Oshiro presses the stop button DOWN with the FLAT PALM of one hand, arm straight, in one firm motion. Her palm covers the cap
- her other arm hangs relaxed at her side. Her whole upper body and both arms are inside the frame

SOUND EFFECT — 「バチン」 ONLY:
- the sound effect バチン is hand-lettered in bold Japanese katakana, exactly these three characters, NOT translated
- it is drawn directly on the green, OUTSIDE any speech bubble — there are NO speech bubbles in this picture
- it sits just above Oshiro's hand and the button, between Oshiro's face and Takasugi's head, at about 35–50% of the image height and 45–70% of the image width

CONCENTRATION LINES — FEW AND SHORT:
- a few thin, short black concentration lines radiate from the button and Oshiro's palm only
- they do NOT cover any face, and they do NOT fill the whole frame

STYLE — match references, avoid drift:
- flat clean line art, minimal shading
- light screentone only for clothes/hair, NO heavy hatching
- ALL shading, including on the robots, is done with dot-pattern screentone with visible halftone dots — NO smooth gradient, NO airbrush, NO 3D-render shading
- NO ink-heavy seinen/gekiga style
- NO photorealistic face shading, NO sweat-drop overdraw
- character faces stay simple and consistent with reference
- line weight: thin and even, not dramatic

BACKGROUNDS — FLAT CHROMA KEY GREEN:
- the entire background is filled with one completely flat, uniform chroma-key green (#00B140) — a strong, saturated green, NOT pale green, NOT mint — a solid single-tone field with NO gradient, NO shading, NO texture, NO vignette, NO lighting falloff
- the green reaches every edge of the image and fills all empty space between and around the figures
- NO corridor perspective, NO windows, NO ceiling lights, NO door frames, NO wall paneling
- NO tables, NO chairs, NO furniture
- do NOT use any green anywhere on the characters, the robots, the props, the clothing, the line art, or the speech bubbles — green appears ONLY in the background
- the boundary between the figures and the green is a clean, hard edge — NO soft blur, NO glow, NO anti-aliased halo, NO drop shadow
- the figures keep their original black-and-white manga line art with white fills and screentone
- speech bubbles stay filled with solid pure white, NOT green
- NO floor line, NO ground line, NO horizon line anywhere — the figures stand directly against the flat green
- focus is on the characters and robots, NEVER the environment

CAMERA:
- SHOT SIZE: over-the-shoulder; Oshiro from the hips up, Hakobukun's upper body
- CAMERA POSITION: behind Takasugi's right shoulder, about 1.5 m from Hakobukun
- CAMERA HEIGHT: 150 cm (human eye level), looking slightly down onto the top of Hakobukun
- FOREGROUND: the back of Takasugi's head and her right shoulder and upper back, seen from behind, cropped by the left edge and the bottom edge of the frame, occupying x 0–30%. Her face is NOT visible
- FRAME CROP: Oshiro cut at the hips by the bottom edge; Hakobukun cut just below its front handle slot
- NOT a front-facing lineup. NOBODY faces the viewer directly.

COMPOSITION — 4:5 portrait, generate at 1024x1280:
- PLACEMENT, STRICT: Takasugi fills the left foreground (x 0–30%). Hakobukun is centred at about 48% of the image width; Oshiro is centred at about 72%, standing right beside Hakobukun's rear corner. Hakobukun and Oshiro form one close group — the gap between them is narrower than Hakobukun's own body
- keep at least 8% of the image width empty on the far right
- Oshiro's face sits at about 25–38% of the image height, at least 8% in from the right edge
- nothing important — no face, no sound effect, no button — sits in the bottom-right area (the right 15% of the width and the bottom 40% of the height)

Oshiro walks up and, without a word, slaps the stop button down. Her face is calm and matter-of-fact: eyes on the button, brows relaxed, mouth closed. She is NOT angry, NOT shouting. Hakobukun has stopped at that instant. Takasugi, seen from behind in the foreground, stands still watching.
```

#### 第61話_P4_プロンプト_v5.txt

```text
black and white manga panel artwork on a flat chroma-key green background, single panel, no panel border, no frame line
the artwork bleeds to all four edges of the image

USE ATTACHED REFERENCES — match face, hair, body proportions, robot shape, line weight, and screentone density EXACTLY. Do not stylize beyond reference.
- Nurse Takasugi — SHORT chin-length straight black BOB with bangs, no glasses (her labelled reference sheet)
- Nurse Oshiro — black hair pulled back tight into a BUN at the back of the head, NOT a bob (her labelled reference sheet)
- Hakobukun (its labelled orthographic sheet: front, side, rear)
- the lineup sheet shows their relative heights

WHO IS WHO — HAIR IS THE KEY, NEVER SWAP:
- every reference image is labelled with the character's name. Match each character ONLY to her own labelled sheet
- Takasugi: SHORT chin-length black BOB, the hair ends at the jaw line. Her hair NEVER reaches her shoulders. NO long hair, NO ponytail, NO bun
- Oshiro: hair pulled back into a BUN at the back of her head, forehead and ears visible. NEVER a bob, NEVER hair hanging down

COSTUME CONSISTENCY — match the attached reference sheets exactly:
- Nurse Takasugi wears the same short-sleeved V-neck scrub top as on her reference sheet, with the same fabric tone. Her hair is a SHORT chin-length black BOB exactly as on her sheet, as dark as on the sheet — do NOT lighten it to brown
- Nurse Oshiro wears the same short-sleeved V-neck scrub top and scrub trousers as on her reference sheet, with the same fabric tone
- Oshiro's hair is tied up in a bun. Fill the hair as SOLID BLACK, a single flat dark mass — do NOT render as a mid-tone, do NOT build the tone out of many thin parallel strands
- neither nurse carries anything in her hands

HAKOBUKUN — TEXT-FREE MACHINE, HIGHEST PRIORITY:
- Hakobukun's body carries NO writing of any kind. Every character of dialogue belongs inside a speech bubble
- the tablet screen at the top of Hakobukun is its FACE: a dark screentone rectangle carrying exactly three simple white shapes — two small vertical rounded-oval eyes and one small mouth between and below them. The eyes are FLAT SOLID WHITE ovals with NO pupil, NO iris, NO highlight, NO outline, NO eyelash — simple pictogram shapes, not anime eyes. Keep them small relative to the screen; the dark screen area around them stays wide and empty. The screen shows an EXPRESSION, never words
- TROUBLED / UNSURE: the mouth is a small WAVY LINE — a gentle horizontal squiggle like a tilde. NOT a frown, NOT open
- the EYES DO NOT CHANGE: they stay the same two flat solid white ovals, fully round-ended, NOT narrowed, NOT half-closed, NOT curved, NOT tilted. Only the mouth carries the expression
- every surface of Hakobukun carries only the details shown on the reference sheet — nothing is added, and NO writing appears anywhere on the body
- Hakobukun has NO arms, NO hands, NO attachments of any kind

STOP BUTTON — THE ONLY ADDED PART:
- in this picture Hakobukun carries ONE part that is NOT on the reference sheet: a single round emergency-stop push button. It is the ONLY exception to "nothing is added"
- it sits on the TOP surface of Hakobukun's head unit, toward the REAR edge, behind the screen
- SAME DESIGN AS IN THE OTHER PANELS — copy the labelled stop-button sample image: a LOW, FLAT, round cap filled almost SOLID BLACK, a short straight-sided cylinder, sitting directly on a low WHITE round collar. About as wide as a palm. NOT a domed mushroom, NO stem, NO neck between cap and collar
- the button carries NO text, NO symbol, NO arrow, NO ring of lettering
- there is exactly ONE button. Do NOT add any other button, switch, lamp, or panel
- the button MUST be VISIBLE in this picture: the camera looks slightly down onto the top of Hakobukun, so the dome of the button stands up clearly on top of the head unit. Do NOT leave it out — it is the button Oshiro just pressed

HAKOBUKUN PROPORTIONS — DO NOT REDRAW:
- copy Hakobukun's silhouette directly from the attached orthographic reference sheet — same body, same outline, same ratio
- height-to-width ratio is FIXED at 1.7 : 1 (height = 1.7 × width)
- do NOT stretch it taller; do NOT squash it wider; do NOT slim it beside a human
- Hakobukun is a WIDE, STOUT, boxy machine — when in doubt, err WIDER, never taller
- draw Hakobukun upright, in front, three-quarter, or rear view only — NO tilted, NO foreshortened, NO low-angle or high-angle views

HAKOBUKUN SCALE — MEASURED AGAINST OSHIRO'S BODY, STRICT:
- Hakobukun is about 70% of Oshiro's total height
- the TOP EDGE of Hakobukun's screen is LEVEL WITH the pit of her stomach — the bottom of her breastbone, just below her folded arms
- her folded arms, shoulders, and face are all ABOVE the robot. If the screen reaches her chest, it is drawn too large. If the robot only reaches the waist, it is drawn too small
- its width is its height divided by 1.7
- both stand on the same invisible ground: the bottom of its wheels and the soles of her shoes would be level, below the bottom edge of the image
- do NOT enlarge Hakobukun to balance the composition, and do NOT shrink it to waist height
- Takasugi is much closer to the camera and is drawn larger by perspective — do NOT size Hakobukun against Takasugi

HAKOBUKUN SHAPE — AS ON ITS LABELLED SHEET:
- a NARROWER head unit carrying the screen sits on TOP of a WIDER body. The screen faces the SAME direction as the front of the body
- the circular ring panel is on its SIDE, never on the face side. Do NOT draw it as a box with the screen on its side, NOT like a TV or microwave
- the body is complete from the head unit down to the black band and the dotted base; it is NOT squashed short

HAKOBUKUN ORIENTATION:
- Hakobukun's body is rotated about 45° to face camera-LEFT, toward Takasugi — the viewer sees its screen in three-quarter view and its LEFT side with the circular ring panel
- in the picture the screen is on the LEFT part of the robot, angled toward Takasugi; its side with the circular ring panel shows to the RIGHT of the screen

BOTH HANDS IN FRAME:
- Takasugi raises both forearms in front of her chest, elbows bent, palms turned UP and open — an exasperated "see?" gesture
- BOTH hands and all fingers are fully inside the frame. Do NOT crop a hand at the edge

STYLE — match references, avoid drift:
- flat clean line art, minimal shading
- light screentone only for clothes/hair, NO heavy hatching
- ALL shading, including on the robots, is done with dot-pattern screentone with visible halftone dots — NO smooth gradient, NO airbrush, NO 3D-render shading
- NO ink-heavy seinen/gekiga style
- NO photorealistic face shading, NO sweat-drop overdraw
- character faces stay simple and consistent with reference
- line weight: thin and even, not dramatic

BACKGROUNDS — FLAT CHROMA KEY GREEN:
- the entire background is filled with one completely flat, uniform chroma-key green (#00B140) — a strong, saturated green, NOT pale green, NOT mint — a solid single-tone field with NO gradient, NO shading, NO texture, NO vignette, NO lighting falloff
- the green reaches every edge of the image and fills all empty space between and around the figures
- NO corridor perspective, NO windows, NO ceiling lights, NO door frames, NO wall paneling
- NO tables, NO chairs, NO furniture
- do NOT use any green anywhere on the characters, the robots, the props, the clothing, the line art, or the speech bubbles — green appears ONLY in the background
- the boundary between the figures and the green is a clean, hard edge — NO soft blur, NO glow, NO anti-aliased halo, NO drop shadow
- the figures keep their original black-and-white manga line art with white fills and screentone
- speech bubbles stay filled with solid pure white, NOT green
- NO floor line, NO ground line, NO horizon line anywhere — the figures stand directly against the flat green
- focus is on the characters and robots, NEVER the environment

DIALOGUE LANGUAGE:
- ALL dialogue text must be rendered in Japanese exactly as written below
- DO NOT translate any line into English

SPEECH BUBBLE TEXT — STRICT:
- the bracketed speaker labels are instructions for YOU only — they are NOT part of the artwork
- render ONLY the text inside 「」 as visible characters; draw NOTHING else
- that text goes inside ordinary rounded speech bubbles — NO jagged bubbles, NO burst shapes
- NEVER draw the speaker label, the brackets, a name, or a colon inside or beside any speech bubble. NEVER draw a bubble that contains only a name
- the 「」 quotation marks themselves are not drawn
- the text in every bubble runs HORIZONTALLY, left to right
- each bubble's tail points clearly to its own speaker, as stated in COMPOSITION. If two speakers say the same words, draw two separate bubbles
- Takasugi's bubble holds exactly three short lines: 押していいって、 / どこにも / 書いてないのよ — the kanji 書 appears ONLY ONCE
- Hakobukun's bubble holds exactly three short lines: 非常停止、 / 誰デモ押セマス！ / 以後明記シマス — check every katakana: 押セマス has マ between セ and ス, and 明記シマス ends with シマス
- draw each line ONCE. Do NOT repeat, duplicate, or echo any line inside a bubble
- reproduce each 「」 line CHARACTER BY CHARACTER, exactly as many characters as written — do NOT insert, repeat, or duplicate any single character inside a word
- letter the dialogue in Japanese manga typesetting style: kanji in a gothic face, kana in a mincho face (the "antique" style used in printed manga)

CAMERA:
- SHOT SIZE: three-person depth shot; Takasugi chest-up in the foreground, Hakobukun's upper half and Oshiro from mid-thigh up behind
- CAMERA POSITION: front-left of Takasugi, about 45° off her front, close to her; Hakobukun and Oshiro about 2 m further back
- CAMERA HEIGHT: 150 cm (human eye level), looking slightly down onto the top of Hakobukun
- FOREGROUND: Takasugi's left shoulder cropped by the left frame edge; her figure occupies x 0–42%
- FRAME CROP: Takasugi cut at mid-chest by the bottom edge; Oshiro cut at mid-thigh; Hakobukun cut just below its front handle slot
- NOT a front-facing lineup. NOBODY faces the viewer directly.

COMPOSITION — 4:5 portrait, generate at 1024x1280:
- PLACEMENT, STRICT: Takasugi is in the left foreground, her face centred at about 24% of the image width and between about 30% and 52% of the image height. Hakobukun stands behind her, centred at about 52% of the image width; Takasugi's right shoulder overlaps Hakobukun's left edge. Oshiro stands right beside Hakobukun, centred at about 74%; her face sits between about 32% and 44% of the image height, at least 8% in from the right edge
- the speech bubbles occupy the band between 4% and 26% of the image height, above the figures. They are drawn COMPLETE, each with its full rounded outline and every character visible. NO bubble touches or crosses any edge of the image. If a bubble does not fit, make the bubble SMALLER — never crop it. Each bubble may wrap its text onto two or three short lines
- the three bubbles sit side by side from left to right in this order: 「押していいって、どこにも書いてないのよ」 at the left, 「非常停止、誰デモ押セマス！以後明記シマス」 in the centre, 「うんね」 at the right
- the tail of the bubble 「押していいって、どこにも書いてないのよ」 points to Takasugi
- the tail of the bubble 「非常停止、誰デモ押セマス！以後明記シマス」 points to Hakobukun
- the tail of the bubble 「うんね」 points to Oshiro
- keep at least 8% of the image width empty on the far left and far right of the bubble band
- nothing important — no face, no bubble — sits in the bottom-right area (the right 15% of the width and the bottom 40% of the height)

Takasugi, body turned about 45° to camera-right toward Hakobukun, complains with both palms up: brows raised, mouth open mid-sentence. Hakobukun, stopped, answers with a troubled wavy mouth. Oshiro, behind, arms folded across her chest, eyes closed, head tipped slightly forward in a small nod, mouth closed in a calm line.
[Takasugi]「押していいって、どこにも書いてないのよ」
[Hakobukun]「非常停止、誰デモ押セマス！以後明記シマス」
[Oshiro]「うんね」
```

### 縦長の差分指示に足した学び

直し方は、縦長の差分指示（縦長の4コマを作るときの決まりの追記）に1つずつ書き足しています。全文です。

#### 縦長の差分指示.md

```markdown
# 縦長4コマ（ショート用の試作）— 差分の指示

この指示は、プロジェクト指示に対する**今回だけの上書き**です。ここに書いていない決まり（キャラ・ハコブくん・ピカリくん・背景の緑・吹き出し・文字・禁止事項）は、プロジェクト指示と v4.0 のままです。

## 目的

1コマ＝縦長1枚（4:5）の4枚で、YouTube ショートでめくって見せる試作。サイトと SNS には出さない。

## 出力するもの（ここが一番大事）

1. サイズ設計表（下の縦長版）
2. Panel 1〜4 のプロンプト（小窓があればその直後に）

**次は出さない・行わない**：英語タイトル／デザインノート（日英）／メタディスクリプション／SNS文案／自動投稿への登録（`sns/` への追加と push もしない）。

## サイズ設計（縦長版。プロジェクト指示の表の代わり）

| コマ | Figma配置枠 | 生成比率 | 生成解像度 |
|---|---|---|---|
| Panel 1〜4 | 1080 × 1350（1コマ1ページ） | 4 : 5 | 1024 × 1280 |
| Panel N 小窓 | Figma で決める | 1 : 1 | 1024 × 1024 |

- 縦横比の下地は `canvas_4x5_1024x1280.png` を添付する
- Panel 1 の「2:1 で生成して上下を切る」の決まりは使わない
- 枠線・余白・コマの並びは今までどおり書かない（Figma の担当）

## 構図の強弱（縦長版）

- Panel 1 ＝つかみ（めくらせる1枚。型は各話の「縦長の構図」に書く）
- Panel 2 ＝状況が動く
- Panel 3 ＝めくる前の引き（「次どうなる？」を残して終える）
- Panel 4 ＝めくった瞬間のオチ

各コマのカメラ（寄り・引き・肩越しなど）は、各話の「縦長の構図」に従う。

## 縦長での置き方

- **吹き出しの帯**：画像の高さの 4%〜26% を基本にする。人物とロボットはその下
- **安全な範囲**：顔と吹き出しは、上下左右の端から 8% 以上内側。**右下（幅の右 15%・高さの下 40%）に顔・吹き出し・小道具の要所を置かない**（ショートの右のボタンの列がかかる）
- **1人のコマ**：中央（幅の 50%）に置く
- **2体が並ぶコマ**：中心を幅の 38% と 62% にし、左右の空きは各 6% 以上（横長版の 42%／58%・12% の代わり）
- **手前と奥に重ねるのは肩越しの構図だけ**。肩越しでは身長比を当てはめない（今の決まりどおり）
- 全身が入るコマは、足元を画像の高さの 92% 前後にそろえる（床線は描かない）
- ハコブくんの「あおり・俯瞰なし」は変えない。高さの差は人物の仕草（見上げる・見下ろす）で出す

## 試作で分かったこと（第13話・2026-10-30）

- **脚と靴の残像**：全身のコマ（P1・P4）で、半透明の緑がかった脚と靴がもう一組出た。全身のコマには、残像を禁じる独立セクションを置く（脚・靴は一組だけ、半透明の重なり・ぶれ・残像を描かない）
- **ハコブくんの口**：線の笑顔になった。口は「塗りつぶした形であって線ではない」を独立セクションで書くと直った
- **ドロワーの中を見せない構図**：中が「空っぽ」に見えた。開き幅を手の幅ほどにし、上面が見えない高さ（目線より下・約100cm）から描くと、中が見えない形になった
- **背景の緑**：Figma を使わず、緑の強さから背景を白に置き換えられた（にじみなし）
- **ショートで使ったのは P3・P4 だけ**：P1・P2 の役割は冒頭の動くハコブくんが受け持った
- **4枚は毎回すべて作る**：サイトに載せるときに4枚とも要るため。ショートでどのコマを使うかは話ごとに決める（冒頭の動くハコブくんと役割が重なるコマは省く）

## 試作で分かったこと（第61話・Gemini の API・2026-10-30）

- **カメラの高さ**：Panel 1 を「低め（約50cm）・上面を見せない」で頼んだが、上面が少し見える高さで描かれた。サイトのマンガもこの見え方がふつう（第61話・第50話・第24話の公開版で確認）。ハコブくんが出るコマは人の目線（約150cm）を基本にし、低い位置はコマの中身で選ぶときだけにする（カメラワーク規定とプロジェクト指示に書き足した）
- **キャラの取り違え**：API で画像を並べて送っただけだと、高杉さんが長い髪になり（P2・P4）、大城さんがボブになった（P3）。画像の前に「Nurse Takasugi, short chin-length black bob…」のような名前の札を付けて送り、プロンプトに髪の違いだけの独立セクション（WHO IS WHO）を置くと、3枚とも三面図どおりになった。高杉さんの見分けの言葉は `short chin-length black bob, no glasses` に変えた（プロジェクト指示も直した）
- **ハコブくんの形の崩れ（P4）**：「画面の頭が胴の上に乗る。画面は胴の正面と同じ向き。丸い輪は横の面だけ」を独立セクションで書くと、三面図の形に戻った
- **セリフの字の重なり（P4）**：「書」が2回出た。吹き出しの行の分け方（押していいって、／どこにも／書いてないのよ）と「書は1回だけ」を書くと直った
- **画面の外からの声の吹き出し（P1）**：しっぽがハコブくんのほうを向き、ハコブくんがしゃべっているように見えた。「しっぽは吹き出しの右上から出て、右上の角の外へ向く。下やハコブくんへは向けない」と吹き出しの幅（画像の幅の約44%）を独立セクションで書くと直った
- **背面の小窓（P1）**：背中から見たコマで、背面の小窓に顔（目と口）が出た。「背面の小窓は何も映らない暗い窓。顔は正面の画面だけ」と書くと直った
- **引きずる物の位置（P1）**：袋がハコブくんの横（丸い輪の面の車輪の脇）に付いた。「袋は進む向きの真後ろ。背面（小窓・扉・黒い通気口の面）の下の真ん中に結び目が挟まる。横の面や車輪の脇ではない」と面の名前で書くと、真後ろになった
- **停止ボタンのデザインのそろえ方**：P1 だけ灰色のキノコ形になった。先に描けたコマ（P2）のボタンを切り抜き、「停止ボタンの見本」と札を付けて添えると、ほかのコマと同じ「白い台に黒い平たいふた」になった。三面図にない部品は、最初に描けたコマを見本にして残りのコマに添える
- **ボタンの見本はどのコマにも添える**：P4 に見本を添えなかったら、ボタンがキノコ形に戻った。見本を添え、形を「低く平たい黒いふた・白い台・首なし」と書くと、ほかのコマとそろった
- **カタカナの抜け（P4）**：「押セマス」が「押セス」になった。吹き出しの行の分け方と「セとスの間にマ」を書くと直った。セリフは毎回1字ずつ見比べる

## 置き場所（2026-10-30 に整理）

`4コママンガ作成データ/` の中を次のように分けた。

| フォルダ | 中身 |
|---|---|
| キャラ/ | 三面図・並び図・ロボットの図（リファレンス台帳の37枚） |
| 下地/ | 縦横比の下地 canvas_ |
| 指示/ | 縦長版・横長版の指示、カメラワーク規定（縦長版）、リファレンス台帳、この差分指示。古いものは 指示/控え/ |
| 道具/ | gemini_gen_labeled.py（名前の札を付けて Gemini に頼む）、仕上げ_緑を白に_1080x1350.py |
| 縦長/第NN話/ | その話の TSV・プロンプト・生成/（API が返した絵と記録）・仕上げ/（1080×1350） |
| 横長_完成版_2026-10-19_20/ | 横長の完成版 |
| プロット/ | まとめて作ったプロット |
```

縦長の指示の、高杉さんの見分けの言葉も変えました。

```bash
cd "$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ"; python3 - <<'EOF'
p="マンガのプロジェクト指示_縦長版_20261030.md"
s=open(p,encoding='utf-8').read()
old="/ 高杉=`hair worn down, no glasses` /"
assert s.count(old)==1
s=s.replace(old,"/ 高杉=`short chin-length black bob, no glasses`（三面図はあご丈のボブ。`hair worn down` と書くと API では長い髪になった）/")
open(p,'w',encoding='utf-8').write(s)
EOF
```


## 作業ログ ― 背景を白にして 1080×1350 に仕上げた

Gemini には、背景を一色の緑（クロマキー）で描かせています。緑の強さから「どれだけ背景か」を出して白に置き換え、1080×1350 に書き出すスクリプトです。JPEG のむらで背景にうっすら灰色が残ったので、ほぼ背景の画素は真っ白にそろえる1行を足しています。

#### 仕上げ_緑を白に_1080x1350.py

```python
"""縦長4コマの仕上げ：緑の背景を白に置き換え（緑の強さから背景の割合を出す・にじみなし）、1080×1350 に書き出す"""
import sys, numpy as np
from PIL import Image
def finish(src, dst):
    a = np.asarray(Image.open(src).convert("RGB")).astype(np.float64)
    h, w, _ = a.shape
    k = 24
    corners = np.concatenate([a[:k,:k].reshape(-1,3), a[:k,-k:].reshape(-1,3), a[-k:,:k].reshape(-1,3), a[-k:,-k:].reshape(-1,3)])
    bg = np.median(corners, axis=0)
    m = (a[...,0] + a[...,2]) / 2; mg = (bg[0] + bg[2]) / 2
    alpha = np.clip((a[...,1] - m) / (bg[1] - mg), 0, 1)
    v = np.where(alpha < 0.999, (m - alpha * mg) / np.maximum(1 - alpha, 1e-3), 255)
    v = np.clip(v, 0, 255)
    out = alpha * 255 + (1 - alpha) * v
    out = np.where(alpha > 0.85, 255, out)  # ほぼ背景の画素は真っ白にそろえる（JPEG のむら消し）
    img = Image.fromarray(np.repeat(out[..., None], 3, axis=2).round().astype(np.uint8))
    s = max(1080 / w, 1350 / h)
    img = img.resize((round(w * s), round(h * s)), Image.LANCZOS)
    l = (img.width - 1080) // 2; t = (img.height - 1350) // 2
    img = img.crop((l, t, l + 1080, t + 1350))
    img.save(dst)
    r = np.asarray(img).astype(int)
    green = int(((r[...,1] - np.maximum(r[...,0], r[...,2])) > 12).sum())
    print(dst, img.size, "背景の色", bg.round().astype(int).tolist(), "緑の残り画素", green)
for src, dst in zip(sys.argv[1::2], sys.argv[2::2]):
    finish(src, dst)
```

```text
仕上げ/第61話_P1_1080x1350.png (1080, 1350) 背景の色 [8, 167, 75] 緑の残り画素 0
仕上げ/第61話_P2_1080x1350.png (1080, 1350) 背景の色 [8, 170, 73] 緑の残り画素 0
仕上げ/第61話_P3_1080x1350.png (1080, 1350) 背景の色 [9, 181, 55] 緑の残り画素 0
仕上げ/第61話_P4_1080x1350.png (1080, 1350) 背景の色 [13, 163, 77] 緑の残り画素 0
```


## 作業ログ ― 4コマのデータの置き場所を分けた

キャラの三面図37枚・縦横比の下地・指示・プロット・試作が、`4コママンガ作成データ/` の直下に混ざっていました。放っておくと、どこに何があるか分からなくなるので、種類ごとに分けました。一番上のフォルダの名前は、過去の記事や台帳が書いているので変えていません。

| フォルダ | 中身 |
|---|---|
| キャラ/ | 三面図・並び図・ロボットの図（リファレンス台帳の37枚） |
| 下地/ | 縦横比の下地 canvas_ |
| 指示/ | 縦長版・横長版の指示、カメラワーク規定（縦長版）、リファレンス台帳、縦長の差分指示。古いものは 指示/控え/ |
| 道具/ | gemini_gen_labeled.py、仕上げ_緑を白に_1080x1350.py |
| 縦長/第NN話/ | その話の TSV・プロンプト・生成/（API が返した絵と記録）・仕上げ/（1080×1350） |
| 横長_完成版_2026-10-19_20/ | 横長の完成版 |
| プロット/ | まとめて作ったプロット |

```bash
set -e; cd "$HOME/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ"
mkdir -p キャラ 下地 指示 道具 縦長 プロット
mv 縦長試作_20261030 縦長/第61話
mkdir -p 縦長/第13話; mv 縦長/第61話/第13話_縦長.tsv 縦長/第13話/
mv 縦長/第61話/縦長の差分指示.md 指示/
mv 縦長/第61話/仕上げ/仕上げ_緑を白に_1080x1350.py 道具/
mv 縦長/第61話/API試し/gemini_gen_labeled.py 道具/
mv 縦長/第61話/API試し 縦長/第61話/生成
mv 完成版_2026-10-19_20 横長_完成版_2026-10-19_20
mv canvas_*.png 下地/
mv プロット_124-133話_法律と運用.* プロット/
mv マンガのプロジェクト指示_縦長版_20261030.md マンガのプロジェクト指示_横長版_20261030.md 指示/
mv プロジェクトのファイル_md_20261030 指示/控え
mv 指示/控え/カメラワーク規定_縦長版.md 指示/控え/ハコブくん_リファレンス台帳.md 指示/
for f in *.png *.jpeg; do mv "$f" キャラ/; done
mv "$HOME/mnt/PixelstoMotors/依頼箱/確認待ち/20261030_33_Geminiの画像APIで第61話P1を1枚試す.md" "$HOME/mnt/PixelstoMotors/依頼箱/済み/"
```

- ねらい：キャラの図・決まり・話ごとのデータを、探さずに見つけられるようにする
- 変わったこと：直下のファイル48個を、7つのフォルダに分けた。新しい話は `縦長/第NN話/` に作る
- 見込める効果：プロンプトを作るときに読むファイル（指示/）と添える画像（キャラ/・下地/）の場所が決まり、スクリプトに書く道のりも短くなる


## 作業ログ ― 第61話の縦長ショートを作った

### ショートの流れ：冒頭2.4秒のあと、4枚ともめくる

YouTube ショートは、マンガのショートの型（冒頭の動くハコブくん → コマ → 叩きつけ → まとめ）で組みました。第13話（LOG408）は冒頭が1・2コマ目の中身を受け持ち、コマは2枚だけ使いました。第61話は冒頭を2.4秒に縮め、縦長の4枚をすべてめくります。

| 場面 | 長さ | 中身 |
|---|---|---|
| 冒頭 | 2.4秒 | 廊下で、袋を引っかけたままのハコブくんが「お届け中です」とご機嫌に進む |
| P1 | 2.8秒 | 高杉さん「ちょっと、引っかかってる！」。「ズルズル…」の擬音 |
| P2 | 2.8秒 | 高杉さん「これ、私が押していいやつ…？」。オレンジの「？」 |
| P3 | 1.7秒 | 「バチン！」の叩きつけ・ドーン・画面が揺れる |
| P4 | 8.8秒 | 高杉さん・ハコブくん・大城さんの3つのセリフ |
| まとめ | 4.4秒 | ボタンに手が届く／でも、／押していいかは／別の話 |

【画像】LOG410_04_short_frames.jpg 第61話のショートの5つの場面。冒頭の袋を引きずるハコブくん、1コマ目と「ズルズル…」、3コマ目の「バチン！」、4コマ目のハコブくんの水色の字幕、まとめの画面

### 冒頭の動き「引きずり」を足した

冒頭は、コードでハコブくんを動かす仕組み（hakobu_motion.py）に、新しい動き「引きずり」を足しました。袋は絵ではなく、その場でコードで描いています（布らしいでこぼこの輪郭・床の影・結び目・スピード線）。

#### hakobu_motion.py に足した Drag61

```python
class Drag61:           # 第61話: 袋を引っかけたまま、気づかずにご機嫌で進む（「お届け中です」）
    dur = 2.6
    empty = True
    sub = 3
    X0, X1 = 0.0, -240.0          # 右から入って、止まらずに左へ進み続ける（袋が画面に入る位置）
    H0, H1 = 0.55, 1.6             # 「お届け中です」を言う間（声に合わせて上書き）
    def _x(self, t): return self.X0 + (self.X1 - self.X0) * (t / self.dur)
    def motion(self, t):
        return dict(dx=self._x(t), dy=-5 * abs(np.sin(2 * np.pi * 3.2 * t)), rot=-1.5 + 1.0 * np.sin(2 * np.pi * 1.6 * t), sx=1.02, sy=0.99)
    def face(self, t):
        if self.H0 <= t < self.H1:
            return ("open", "open"), None, ("mid" if int(t * 9) % 2 else "small")
        return ("open", "open"), None, "closed"
    def ring(self, t): return 0.75
    def blur(self, t): return True
    def dim(self, t): return 1.0
    def fx(self, t, frame, mo):
        x = RIGHT + mo.get("dx", 0)                  # 体の右下の角（進む向きの後ろ）
        yb = 905 + mo.get("dy", 0) * 0.3
        img = np.ascontiguousarray(frame.astype(np.uint8))
        wob = 4 * np.sin(2 * np.pi * 5.5 * t)
        cx, cy = int(x + 130), int(yb - 74 + wob * 0.4)    # 袋の真ん中
        # 引きずった跡のスピード線（袋の後ろ＝右へ）
        for k, (oy, ln) in enumerate([(-8, 70), (8, 95), (22, 60), (34, 80)]):
            ph = (t * 600 + k * 37) % 40
            x0 = int(cx + 85 + ph); cv2.line(img, (x0, cy + 30 + oy), (x0 + ln, cy + 30 + oy), (40, 40, 40), 3, cv2.LINE_AA)
        # 結び目からロボットの下のふちへ続くひも
        kx, ky = int(x + 30), int(yb - 58)
        cv2.line(img, (int(x - 6), int(yb - 6)), (kx, ky), (20, 20, 20), 5, cv2.LINE_AA)
        # 袋（白い布・ふくらんで横倒し）
        axes = (94, 60)
        cv2.ellipse(img, (cx + 6, cy + 56), (100, 14), 0, 0, 360, (150, 140, 130), -1, cv2.LINE_AA)      # 床の影
        th = np.linspace(0, 2 * np.pi, 48, endpoint=False)          # 布らしいでこぼこの輪郭
        rr = 1 + 0.06 * np.sin(3 * th + 1.3) + 0.04 * np.sin(5 * th + t * 6)
        body = np.stack([cx + axes[0] * rr * np.cos(th), cy + axes[1] * rr * np.sin(th)], 1).astype(np.int32)
        cv2.fillPoly(img, [body], (246, 246, 242), cv2.LINE_AA)
        cv2.ellipse(img, (cx + 6, cy + 14), (int(axes[0] * 0.85), int(axes[1] * 0.6)), wob, 0, 180, (214, 212, 206), -1, cv2.LINE_AA)   # 下の陰
        cv2.polylines(img, [body], True, (15, 15, 15), 4, cv2.LINE_AA)
        for a0, a1, r in [(200, 250, 0.55), (280, 330, 0.6), (120, 160, 0.5)]:     # 布のしわ
            cv2.ellipse(img, (cx + 8, cy + 4), (int(axes[0] * r), int(axes[1] * r)), wob, a0, a1, (60, 60, 60), 3, cv2.LINE_AA)
        # 口を縛った結び目（左＝ロボットの側）
        pts = np.array([[cx - 90, cy - 16], [kx + 4, ky - 18], [kx - 6, ky + 2], [kx + 6, ky + 20], [cx - 90, cy + 18]], np.int32)
        cv2.fillPoly(img, [pts], (246, 246, 242), cv2.LINE_AA); cv2.polylines(img, [pts], True, (15, 15, 15), 4, cv2.LINE_AA)
        cv2.circle(img, (int(cx - 86), cy), 10, (15, 15, 15), -1, cv2.LINE_AA)
        return img.astype(np.float32)

PATTERNS.update({"引きずり": Drag61})
```

### 声を作る .sh と、組み立ての台本

#### 第61話の声.sh

```bash
#!/bin/bash
# 第61話（縦長4コマを4枚ともめくるショート・2026-10-30）の声を作る。先に read -s ELEVENLABS_API_KEY && export ELEVENLABS_API_KEY
# できている声はとばすので、何度動かしてもよい
cd "$(dirname "$0")"
B=../_制作ベース
HK="gWJpc8whMBqnF7Y0kc9f"
id() { python3 -c "import json;print(json.load(open('../$B/声の一覧.json')).get('$1',''))"; }
mk() { # mk 人 本数 文
  local who="$1" n="$2" txt="$3" v
  if [ "$who" = "ハコブ" ]; then v=$HK; else v="$(id "$who")"; fi
  [ -f "voice_eleven/$who/試し_$(printf %02d $n).mp3" ] && return
  python3 ../$B/tts_eleven.py --voice "$v" --name "$who" --try "$txt" || echo "  ！ $who で止まりました"
}
echo "== 第61話_押していい"; cd "第61話_押していい"
[ -f narration.py ] || printf "# ショート専用。声は --try で作る（_ショート専用/第61話の声.sh）\nSEGS = []\n" > narration.py
# --try は1回ごとに 試し_01 から番号を振り直すので、1人ぶんを「｜」でつないで1回で作る（本数＝その人の文の数）
mk "高杉" 3 "ちょっと、引っかかってる！｜これ、私が押していいやつ…？｜押していいって、どこにも書いてないのよ"
mk "ハコブ" 2 "お届け中です。｜非常停止、誰でも押せます！以後、明記します。"
mk "大城" 1 "うんね。"
echo "できあがり: $(ls voice_eleven/*/試し_*.mp3 2>/dev/null | wc -l | tr -d ' ') 本（6本そろえば完成）"
```

#### spec_ep61.py（第61話のショートの台本）

```python
import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24; common24.BGMAP[61] = "第86話.png"          # 病棟の廊下（第86話の背景を借りる）
HERE = os.path.dirname(os.path.abspath(__file__)); d = lambda l, e=2.0: vlen(HERE, l["v"], e)
NAME, EP = "第61話_押していい", 61
HEAD = ["非常停止ボタン、", "押していいのは誰？"]
COMIC = C + "ep61.png"                      # 縦長のコマ（1080×1350）を上から P1〜P4 の順に積んだ画像
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]

# 冒頭：袋を引っかけたまま、ご機嫌で進むハコブくん「お届け中です」
l0 = hk(1, "お届け中デス", at=0.35)
H0 = l0["at"]; H1 = H0 + d(l0, 1.2)
DUR = max(2.4, H1 + 0.7)
MOTION = mot("引きずり", EP, H0=H0, H1=H1, dur=DUR)

SCENES = [
  dict(kind="op", video=anim(EP, "引きずり"), dur=DUR, lines=[l0],
       se=[dict(at=0.0, f="se/候補_A_ズザーッ.m4a", db=-16)]),
  dict(kind="panel", crop=P1, fit=False, min=2.0, lines=[ns("高杉", 1, "ちょっと、|【引っかかってる】！")],
       words=[W_("ズルズル…", 0.2, 300, 845, 84, "ink", -8)]),
  dict(kind="panel", crop=P2, fit=False, min=2.4, lines=[ns("高杉", 2, "これ、【私が】|【押していい】やつ…？")],
       words=[W_("？", 1.3, 990, 470, 180, "orange", 12)]),
  dict(kind="panel", crop=P3, fit=False, min=1.7,
       slam=dict(text="バチン！", at=0.35, y=190, size=170, color="orange"),
       se=[dict(at=0.35, f="se/se_don.mp3", db=-6)]),
  dict(kind="panel", crop=P4, fit=False, min=4.0,
       lines=[ns("高杉", 3, "【押していい】って、|どこにも【書いてない】のよ"),
              hk(2, "非常停止、|【誰デモ押セマス】！||以後、【明記シマス】"),
              ns("大城", 1, "【うんね】", gap=0.1)]),
  end(("ボタンに手が届く", 74, "ink", 0.0), ("でも、", 74, "ink", 0.25),
      ("押していいかは", 120, "orange", 0.9), ("別の話", 120, "orange", 0.9),
      ("（「誰でも押せる」と書いておく）", 34, "gray", 1.8)),
]
```

```text
できあがり: out_第61話_押していい.mp4 22.9 秒
```

### 字幕の色は「ハコブくんは水色と白、ほかの人は白とオレンジ」にそろえた

字幕の強める語をオレンジにする工夫を入れたあと、色の決まりを1つ足しました。

| 誰の字幕 | 地の字 | 強める語 |
|---|---|---|
| ハコブくん | 水色 | 白 |
| ほかの人 | 白 | オレンジ |

途中で、色を解説動画と同じ暗いオレンジ・水色1色ずつにそろえ、黒いふちを細くしてみましたが、見た目がいまいちで、前の明るい色に戻しました。明るい色は、黒いふちの上で目立たせるためのものでした。残したのは、誰の字幕かで色を分ける決まりだけです。ショートを組む仕組み（マンガショート_mkshort.py）の変更です。

#### マンガショート_mkshort.py の変更（差分）

```diff
--- "kit/_\345\210\266\344\275\234\343\203\231\343\203\274\343\202\271/\343\203\236\343\203\263\343\202\254\343\202\267\343\203\247\343\203\274\343\203\210_mkshort.py"	2026-10-30 11:17:54.000000000 +0900
+++ w/mkshort.py	2026-10-30 15:29:41.220011576 +0900
@@ -17,6 +17,7 @@
 
 W, H, FPS, SR = 1080, 1920, 30, 48000
 PAPER = (245, 245, 240); INK = (13, 13, 13); GRAY = (136, 136, 136); ORANGE = (196, 71, 31); BLUE = (120, 200, 255)
+SUB_OR = (255, 140, 90)   # 字幕の強める語のオレンジ（黒いふちの上で目立つ明るさ）
 COL = {"ink": INK, "orange": ORANGE, "gray": GRAY}
 FONT = "/usr/share/fonts/opentype/noto/NotoSansCJK-Black.ttc"
 F = lambda size: ImageFont.truetype(FONT, size, index=0)
@@ -224,15 +225,17 @@
 import functools
 @functools.lru_cache(maxsize=None)
 def subtitle(txt, who, small):
+    # 字幕の色: ハコブくんは水色の字で強める語は白、ほかの人は白い字で強める語はオレンジ（2026-10-30 エイイチの指示。色を増やさない）
+    hl = (255, 255, 255) if who == "hk" else SUB_OR
     if small:
-        return text_img(txt, 30, fill=BLUE if who == "hk" else (255, 255, 255), stroke=5, stroke_fill=INK)
+        return text_img(txt, 30, fill=BLUE if who == "hk" else (255, 255, 255), stroke=5, stroke_fill=INK, hl=hl)
     rows = txt.split("|")                 # 「|」で2行に分ける（1行は13字まで）
     plain = lambda r: len(r.replace("【", "").replace("】", ""))
     if len(rows) == 1 and plain(txt) > 13:     # 長いときは真ん中に近い「、」「。」で自動で折る
         cands = [i + 1 for i, ch in enumerate(txt[:-1]) if ch in "、。！？"]
         if cands:
             k = min(cands, key=lambda i: abs(plain(txt[:i]) - plain(txt) / 2)); rows = [txt[:k], txt[k:]]
-    ims = [text_img(r, 66, fill=BLUE if who == "hk" else (255, 255, 255), stroke=11, stroke_fill=INK, hl=(255, 140, 90)) for r in rows]
+    ims = [text_img(r, 66, fill=BLUE if who == "hk" else (255, 255, 255), stroke=11, stroke_fill=INK, hl=hl) for r in rows]
     if len(ims) == 1: return ims[0]
     out = Image.new("RGBA", (max(i.width for i in ims), sum(i.height - 14 for i in ims) + 14), (0, 0, 0, 0))
     y = 0
```


## 作業ログ ― ElevenLabs の声と LinkedIn の投稿を自動にした

### ElevenLabs は何か・鍵をファイルに置いた

[ElevenLabs](https://elevenlabs.io/) は、文章を人の声で読み上げるサービスです。看護師さんたちとハコブくんの声は、ElevenLabs で作った声（Voice ID で呼び分ける）で読み上げています。これまでは、声を作るたびに鍵をターミナルに入れてもらっていました。Gemini と同じく鍵を KIOXIA の `elevenlabs_api_key.txt` に置き、読み上げのスクリプトが、環境変数がなければこのファイルを読むようにしました。

ここでも、鍵がファイル名の後ろに付いたファイルができました。鍵の文字を表示せずに、中身を正しい名前のファイルへ写しています。

```bash
cd "$HOME/mnt/KIOXIA" && python3 - <<'EOF'
import os
base="elevenlabs_api_key.txt"
c=[n for n in os.listdir('.') if n.startswith(base)]
print("見つかった数:", len(c), "正しい名前のファイル:", base in c)
odd=[n for n in c if n!=base]
for n in odd:
    suf=n[len(base):]; body=open(n,encoding='utf-8',errors='replace').read().strip()
    print("名前の後ろの長さ:", len(suf), "中身の長さ:", len(body), "中身と名前の後ろが同じ:", body==suf, "中身が英数字だけ:", body.replace('_','').replace('-','').isalnum())
EOF
```

```text
見つかった数: 1 正しい名前のファイル: False
名前の後ろの長さ: 51 中身の長さ: 51 中身と名前の後ろが同じ: True 中身が英数字だけ: True
```

鍵で読み上げを1回だけ試すと、声の一覧や使った量は「権限がない」で断られ、読み上げはできました。読み上げだけの権限の鍵です。

```text
voices?page_size=1 ng 401 {"detail":{"type":"authentication_error","code":"unauthorized","message":"The API key you used is missing the permission voices_read to execute this operation.","status":"missing_permissions","request
models ng 401 {"detail":{"type":"authentication_error","code":"unauthorized","message":"The API key you used is missing the permission models_read to execute this operation.","status":"missing_permissions","request
ok 11327 バイト
```

tts_eleven.py と sound_eleven.py に足した行です。

```python
key = os.environ.get('ELEVENLABS_API_KEY')
if not key:   # 2026-10-30 エイイチが鍵を KIOXIA に置いた（Claude が会話から声を作れるように）
    for _kp in ('/Volumes/KIOXIA/elevenlabs_api_key.txt', os.path.expanduser('~/mnt/KIOXIA/elevenlabs_api_key.txt')):
        if os.path.exists(_kp): key = open(_kp).read().strip(); break
```

- ねらい：ショートの声を作るたびに、鍵を入れてもらう手間をなくす
- 変わったこと：鍵は KIOXIA のファイル。声は会話の中から作る。新しい声をデザインするときだけ、今までどおりターミナルで作業する
- 見込める効果：1話あたり1回のターミナルの作業がなくなる（第61話では声の6本を作るのに1回あった）

### LinkedIn の本編の動画を、YouTube のリンクで自動で出すようにした

解説動画（本編）は、YouTube の公開予約に合わせて X に自動で出し、LinkedIn には動画を直接アップロードして手で出していました。この手作業が面倒なので、LinkedIn も YouTube のリンクのカード（YouTube のサムネイル付き）で自動で出すようにしました。決めたのは次の2つです。

| 決めたこと | 中身 |
|---|---|
| 対象 | 本編だけ。ショートは LinkedIn に出さない |
| 時刻 | YouTube の公開と同じ時刻。研究の記録の 17:00 の枠とは別 |

ただ、LinkedIn では、外のサイトへのリンクの投稿は、動画を直接上げるより広がりにくいと言われています（公式の発表ではありません）。そこで、ここぞという回は、これまでどおり LinkedIn に動画を直接上げることにしました。その回は json に `"linkedin_manual": true` を書くと、自動の予約から外れます。

公開前の本編14本の json に、作業フォルダの投稿文（投稿文_X_LinkedIn.txt）から日本語の LinkedIn の本文とコメントを入れました。

```bash
cd /home/claude/r2r2r-content && python3 - <<'EOF'
import json, glob, os, re
from datetime import datetime, timezone
now=datetime.now(timezone.utc)
CJK=re.compile(r'[぀-ヿ一-鿿]')
def parse(path):
    L=open(path,encoding='utf-8').read().split("\n")
    heads=[i for i,l in enumerate(L) if l.startswith("■")]
    def sec(pred):
        for k,i in enumerate(heads):
            if pred(L[i]):
                j=heads[k+1] if k+1<len(heads) else len(L)
                return [l for l in L[i+1:j] if not re.fullmatch(r"[─━]+",l.strip())]
        return None
    body=sec(lambda h: h.startswith("■ LinkedIn") and "コメント" not in h)
    com=sec(lambda h: h.startswith("■ LinkedIn") and "コメント" in h)
    b="\n".join(body)
    if "――――" in b: b=b.split("――――",1)[1]
    b=b.strip().replace("【URL】","{url}")
    c=""
    if com:
        paras=[p.strip() for p in "\n".join(com).split("\n\n") if p.strip()]
        c="\n\n".join(p for p in paras if CJK.search(p)).replace("【URL】","{url}")
    return b,c
files={os.path.basename(f)[:-4]:f for f in glob.glob('/home/claude/li/*.txt')}
for p in sorted(glob.glob('sns/VIDEO_*_sns_posts.json')):
    d=json.load(open(p,encoding='utf-8')); m=d['meta']
    pa=m.get('publish_at')
    if pa and datetime.fromisoformat(pa)<now: continue
    key=next((k for k in files if m['title'].startswith(k)),None)
    if not key: print("文なし",p); continue
    b,c=parse(files[key])
    d['posts']['linkedin_template']=b
    if c: d['posts']['linkedin_comment_template']=c
    d['note']=re.sub(r"LinkedIn は動画を直接アップロードする決まりなので入れない","LinkedIn も YouTube のリンクのカードで自動で出す（2026-10-30 から。linkedin_template の {url} が動画の URL に替わる）",d.get('note',''))
    json.dump(d,open(p,'w',encoding='utf-8'),ensure_ascii=False,indent=2); open(p,'a').write("\n")
    print(os.path.basename(p), pa, len(b), "字 / コメント", len(c), "字", "| 本文に{url}" if "{url}" in b else "")
EOF
```

```text
VIDEO_ai-reviews-ai_sns_posts.json 2026-11-01T19:30:00+09:00 663 字 / コメント 120 字 
VIDEO_ai-yesterday_sns_posts.json 2026-11-04T19:30:00+09:00 700 字 / コメント 125 字 
VIDEO_ghost-cyborg-body_sns_posts.json 2026-11-12T19:15:00+09:00 767 字 / コメント 216 字 
VIDEO_ghost-remote-body_sns_posts.json 2026-11-10T19:00:00+09:00 857 字 / コメント 168 字 
VIDEO_haro_sns_posts.json 2026-10-30T19:30:00+09:00 702 字 / コメント 166 字 
VIDEO_kddi-elyza-robotics_sns_posts.json None 863 字 / コメント 161 字 
VIDEO_patlabor_sns_posts.json 2026-11-11T19:30:00+09:00 733 字 / コメント 145 字 
VIDEO_serving-robots-how-many_sns_posts.json 2026-11-09T19:30:00+09:00 1020 字 / コメント 155 字 
VIDEO_serving-robots-one-less_sns_posts.json 2026-11-16T19:30:00+09:00 1117 字 / コメント 198 字 
VIDEO_softbank-pudu-no1_sns_posts.json None 783 字 / コメント 110 字 
VIDEO_solo-ai-team_sns_posts.json 2026-10-31T19:30:00+09:00 638 字 / コメント 99 字 
VIDEO_tesla-optimus-ramp_sns_posts.json 2026-11-02T19:30:00+09:00 668 字 / コメント 85 字 
VIDEO_votoms-at_sns_posts.json 2026-11-06T19:15:00+09:00 796 字 / コメント 140 字 
VIDEO_world-record-and-laundry_sns_posts.json 2026-11-23T19:30:00+09:00 614 字 / コメント 98 字
```

同期のスクリプト（sync_youtube_shorts.py）・投稿のスクリプト（sns_autopost.py）・カレンダーのスクリプト（calendar_plan.py）の変更です。

#### LinkedIn を YouTube のリンクにした変更（差分）

```diff
diff --git a/tools/calendar_plan.py b/tools/calendar_plan.py
index 66b6e2a..d79753a 100644
--- a/tools/calendar_plan.py
+++ b/tools/calendar_plan.py
@@ -17,7 +17,7 @@
 予定の見分け方（key）。予定の説明の最後の行に「p2m-key: {key}」を入れる
   x:{id} / linkedin:{id}            予定表の1行（id は schedule.json の id）
   yt:{youtube_id}                   YouTube の公開
-  li-video:{youtube_id}             LinkedIn に動画を手で上げる（動画は LinkedIn に直接アップロードする決まりのため）
+  li-video:{youtube_id}             （2026-10-30 まで）LinkedIn に動画を手で上げる。今は本編も自動のリンク投稿なので作らない
 """
 import argparse, glob, json, os, re
 from datetime import datetime, timedelta, timezone
@@ -98,10 +98,7 @@ def main():
         watch = f"https://www.youtube.com/watch?v={yid}"
         title = m.get("youtube_title") or m.get("title", "")
         out.append(ev(f"yt:{yid}", f"YouTube公開 {name}", t, f"{title}\n{watch}"))
-        note = "YouTube の公開と同じ時刻。LinkedIn には動画を直接アップロードする決まり（自動では出ない）。"
-        if kind == "video":
-            note += f"文は iCloud の 動画/{m.get('title', '')}/投稿文_X_LinkedIn.txt"
-        out.append(ev(f"li-video:{yid}", f"LinkedIn 動画を上げる（手で）{name}", t, f"{note}\nYouTube: {watch}"))
+        # 本編の LinkedIn は 2026-10-30 から YouTube のリンクのカードで自動（予定表の linkedin:{id} で出る）。手で上げる予定は入れない
     out.sort(key=lambda x: (x["startTime"], x["key"]))
     print(json.dumps(out, ensure_ascii=False, indent=1))
 
diff --git a/tools/sns_autopost.py b/tools/sns_autopost.py
index 65ac978..8d31b59 100644
--- a/tools/sns_autopost.py
+++ b/tools/sns_autopost.py
@@ -94,7 +94,9 @@ def texts_for(entry, src):
         "text": posts.get("linkedin" + sfx, "").strip(),
         "comment": posts.get("linkedin_comment" + sfx, "").strip(),
         "url": meta.get("url" + sfx) or meta.get("url", ""),
-        "title": meta.get("title" + sfx) or meta.get("title", ""),
+        # YouTube の本編（2026-10-30 から LinkedIn にもリンクで出す）はカードの題を YouTube のタイトルにする
+        "title": (meta.get("youtube_title") if meta.get("kind") == "youtube_video" else None)
+                 or meta.get("title" + sfx) or meta.get("title", ""),
         "description": meta.get("meta_description" + sfx) or meta.get("meta_description", ""),
     }
 
@@ -258,7 +260,17 @@ def og_image(page_url):
 def li_thumbnail(token, person, page_url):
     """記事の表紙画像を LinkedIn に上げて image の URN を返す。失敗したら None（表紙なしで投稿する）。"""
     try:
-        img = og_image(page_url)
+        m = re.search(r"(?:youtu\.be/|youtube\.com/(?:watch\?v=|shorts/))([\w-]{6,})", page_url)
+        if m:   # YouTube の動画は、ページを読まずにサムネイルを直接取る（同意の画面などで og:image が取れないことがあるため）
+            img = f"https://i.ytimg.com/vi/{m.group(1)}/hqdefault.jpg"
+            for size in ("maxresdefault", "hqdefault"):
+                try:
+                    http("GET", f"https://i.ytimg.com/vi/{m.group(1)}/{size}.jpg", where="YouTube のサムネイル")
+                    img = f"https://i.ytimg.com/vi/{m.group(1)}/{size}.jpg"; break
+                except Exception:
+                    continue
+        else:
+            img = og_image(page_url)
         if not img:
             return None
         _, _, data = http("GET", img, where="表紙画像")
diff --git a/tools/sync_youtube_shorts.py b/tools/sync_youtube_shorts.py
index ae536f9..79d98aa 100644
--- a/tools/sync_youtube_shorts.py
+++ b/tools/sync_youtube_shorts.py
@@ -17,7 +17,9 @@
      12:30 の枠（2026-10-25 に足した）は「それ以外」に入り、13:00 になる
   3. まだ投稿していない予約は、YouTube 側で時刻を変えていたら合わせて直す
   4. 公開の時刻を過ぎていて、まだ予定表にないものは入れない（古いものを今さら出さない）
-LinkedIn には入れない（動画は LinkedIn に直接アップロードする決まり。video_rules.md の 0節の22）。
+本編は LinkedIn にも入れる（2026-10-30 エイイチの指示「LinkedIn は YouTube の投稿に統一」）。posts.linkedin_template があれば、
+YouTube の公開の時刻に LinkedIn の予約（id は VIDEO_{名前}-linkedin）を入れ、{url} を動画の URL にして posts.linkedin と
+posts.linkedin_comment を作る。LinkedIn では動画の URL のリンクのカード（YouTube のサムネイルつき）で出る。ショートは LinkedIn に出さない。
 
 使い方: python3 tools/sync_youtube_shorts.py --videos youtube_videos.json [--dry-run]
 """
@@ -100,6 +102,31 @@ def main():
         elif entry["post_at"] != post_at:
             changes.append(f"時刻を直した {sid} {entry['post_at']} → {post_at}")
             entry["post_at"] = post_at
+        # 本編の LinkedIn（2026-10-30 から）: YouTube の公開と同じ時刻。同じ時刻の LinkedIn の予約があれば30分ずつ後ろへ
+        if is_main and posts.get("linkedin_template"):
+            lid = sid[:-len("-x")] + "-linkedin"
+            lentry = next((e for e in sched["posts"] if e["id"] == lid), None)
+            if not (lentry and lid in done_ids) and (lentry or when >= now):
+                ltext = posts["linkedin_template"].replace("{url}", url)
+                lcom = posts.get("linkedin_comment_template", "").replace("{url}", url)
+                if posts.get("linkedin") != ltext or posts.get("linkedin_comment", "") != lcom:
+                    posts["linkedin"] = ltext
+                    if lcom:
+                        posts["linkedin_comment"] = lcom
+                    if not a.dry_run:
+                        with open(path, "w", encoding="utf-8") as f:
+                            json.dump(src, f, ensure_ascii=False, indent=2); f.write("\n")
+                ltaken = {e["post_at"] for e in sched["posts"] if e["platform"] == "linkedin" and e["id"] != lid}
+                lt = when.astimezone(JST)
+                while lt.isoformat() in ltaken:
+                    lt = lt + timedelta(minutes=30)
+                lpost = lt.isoformat()
+                if lentry is None:
+                    sched["posts"].append({"id": lid, "source": os.path.relpath(path, ROOT), "platform": "linkedin", "post_at": lpost})
+                    changes.append(f"足した {lid} {lpost} {v['title'][:30]}")
+                elif lentry["post_at"] != lpost:
+                    changes.append(f"時刻を直した {lid} {lentry['post_at']} → {lpost}")
+                    lentry["post_at"] = lpost
     sched["posts"].sort(key=lambda e: (e["post_at"], e["id"]))
     if changes and not a.dry_run:
         with open(SCHEDULE, "w", encoding="utf-8") as f:
```

#### 直接上げる回を自動の予約から外す変更（差分）

```diff
diff --git a/tools/sync_youtube_shorts.py b/tools/sync_youtube_shorts.py
index 79d98aa..fa59e92 100644
--- a/tools/sync_youtube_shorts.py
+++ b/tools/sync_youtube_shorts.py
@@ -103,7 +103,13 @@ def main():
             changes.append(f"時刻を直した {sid} {entry['post_at']} → {post_at}")
             entry["post_at"] = post_at
         # 本編の LinkedIn（2026-10-30 から）: YouTube の公開と同じ時刻。同じ時刻の LinkedIn の予約があれば30分ずつ後ろへ
-        if is_main and posts.get("linkedin_template"):
+        if is_main and meta.get("linkedin_manual"):
+            # エイイチが LinkedIn に動画を直接上げる回（2026-10-30「ポイントポイントで直接あげる」）。自動の予約はまだ出ていなければ外す
+            lid = sid[:-len("-x")] + "-linkedin"
+            if lid not in done_ids and any(e["id"] == lid for e in sched["posts"]):
+                sched["posts"] = [e for e in sched["posts"] if e["id"] != lid]
+                changes.append(f"外した {lid}（LinkedIn は直接上げる回）")
+        elif is_main and posts.get("linkedin_template"):
             lid = sid[:-len("-x")] + "-linkedin"
             lentry = next((e for e in sched["posts"] if e["id"] == lid), None)
             if not (lentry and lid in done_ids) and (lentry or when >= now):
```

同期を動かした結果です。

```text
足した VIDEO_ai-reviews-ai-linkedin 2026-11-01T19:30:00+09:00 AIの間違いは、誰が見つけるのか ― 別のAIに疑わせたら、
足した VIDEO_ai-yesterday-linkedin 2026-11-04T19:30:00+09:00 AIに、昨日の続きをさせるには ― 新しい会話のAIのために
足した VIDEO_ghost-cyborg-body-linkedin 2026-11-12T19:15:00+09:00 攻殻機動隊の義体は、いまの技術でどこまで作れるのか ― 体は
足した VIDEO_ghost-remote-body-linkedin 2026-11-10T19:00:00+09:00 攻殻機動隊を思わせる遠隔ボディは、もう作れるのか ― 中身に
足した VIDEO_haro-linkedin 2026-10-30T19:30:00+09:00 ハロは、もう作れるのか ― 話す・転がる・段差を上る。1台に
足した VIDEO_kddi-elyza-robotics-x 2026-11-06T20:30:00+09:00 KDDIとソフトバンクはフィジカルAIでもライバルか　300
足した VIDEO_kddi-elyza-robotics-linkedin 2026-11-06T19:30:00+09:00 KDDIとソフトバンクはフィジカルAIでもライバルか　300
足した VIDEO_patlabor-linkedin 2026-11-11T19:30:00+09:00 パトレイバーは、もう作れるのか ― 人型の重機は線路で働いて
足した VIDEO_serving-robots-how-many-linkedin 2026-11-09T19:30:00+09:00 配膳ロボット1台だと、料理が遅くなる？ ファミレスで0〜6台
足した VIDEO_serving-robots-one-less-linkedin 2026-11-16T19:30:00+09:00 配膳ロボットは「0.5人分」？ ファミレスで人件費とレンタル
足した VIDEO_softbank-pudu-no1-x 2026-11-05T20:30:00+09:00 猫の配膳ロボット、売上世界一はソフトバンクかPuduか
足した VIDEO_softbank-pudu-no1-linkedin 2026-11-05T19:30:00+09:00 猫の配膳ロボット、売上世界一はソフトバンクかPuduか
足した VIDEO_solo-ai-team-linkedin 2026-10-31T19:30:00+09:00 ひとりとAIで、研究と発信を続けるには ― AIに任せる仕事
足した VIDEO_tesla-optimus-ramp-linkedin 2026-11-02T19:30:00+09:00 テスラは人型ロボットをなぜすぐ作れないのか。決算説明会から3
足した VIDEO_world-record-and-laundry-linkedin 2026-11-23T19:30:00+09:00 走るのは人間超え、たたむのはまだ。人型ロボットの謎を3つの手
```

- ねらい：LinkedIn に動画を手で上げる作業をなくす
- 変わったこと：本編は、YouTube で公開予約するだけで、X と LinkedIn の両方に、公開と同じ時刻で出る。ここぞという回だけ、手で直接上げる
- 見込める効果：本編1本ごとの LinkedIn の手作業がなくなる（予約済みの13本と、まだ予約していない1本の分）。リンクの投稿で反応が落ちるかは、LinkedIn の分析の書き出しで、直接上げていた回と比べて確かめる


## 次は、話を選ぶだけで4コマからショートまで

【図解】LOG410_manual_steps.html

第61話で、4コマの絵・仕上げ・声・ショート・X の予約まで、どれも会話の中から動かせるようになりました。人の手が残っているのは、話を選ぶこと、できた絵とショートを見て直しを言うこと、YouTube に上げて公開予約することです。次の話は、この会話のプロジェクトで TSV（話のプロット）を受け取り、プロンプト作りから始めます。どれだけ人の手が要ったかを、次の話で数えます。
