---
title: "YouTube の解説動画に、作り手本人の絵と声が入った ― ハコブくんの声と絵の回から"
slug: maker-voice-in-explainer-video
date: 2026-11-02
day: 425
meta_description: "Pixels to Motors の YouTube 解説動画は、謎解きの回だけで使っていた作り手エイイチのドット絵とAI音声を、ふつうの解説回にも入れる形にした。11分10秒のうち本人が話すのは4文・30.4秒。"
tags: [YouTube, 解説動画, AI音声, ElevenLabs, ドット絵, Gemini, 4コママンガ, ハコブくん, 生成AI]
series: "ひとり研究所の裏側"
---

Pixels to Motors の YouTube 解説動画は、謎解きの回だけで使っていた作り手エイイチのドット絵と AI 音声を、ふつうの解説回にも入れる形に変えた。最初の1本は、シリーズ「ひとりとAIのチーム」4回目「ハコブくんの声と絵は、どう作っているのか ― キャラクターの声と絵を、AIで作って分かったこと」（11分10秒）で、88文のうち84文を案内役のロボット「ハコブくん」が、4文（30.4秒）を作り手本人が話している。

この形で良くなること・できるようになったことは、次の3つです。

- **本人の考えを、本人の口から聞ける。** これまでの解説回では、作り手に聞いた実感をハコブくんが「エイイチいわく、……」と言い換えていました。いまは本人のドット絵が右下に出て、別の AI 音声で本人が話します。見ている人には、どこからが作り手の考えなのかが、声と絵で分かります
- **声を作る手間が、作り手の手から離れた。** これまでは、ハコブくんの声（ElevenLabs）を作るコマンドを、作り手が自分の Mac で打っていました。今回は、Claude が Mac の中で声を作れるようにした仕組みを使い、85文を4回に分けて作りました（最初の15文は28秒）。前の手間は測っていないので、どれだけ浮いたかは数字で言えません。作り手がしたのは、試しの声を聞いて選ぶことだけでした
- **前の声と、いまの声を聞き比べられた。** サイトのトップで前に流していた、ハコブくんの古い動画が残っていたので、その声を動画の中で7.4秒流しました。「声は何度か試して、今の形に落ち着いた」という本人の言葉のすぐあとに、実物の前の声が鳴ります

この形になったのは、確認用の動画を見た作り手の一言からでした。

> あとおれの登場とセリフ（音声）がないけど？どうした？修正

謎解きの回（LOG395）で作った、ドット絵と口の動き・別の AI 音声の仕組みを、そのまま解説回に持ってきました。ほかにも、読みの直しで「表」が全文で抑揚がそろわず「リスト」に言い換えたこと、動画の冒頭で「腕が3本」の生成 AI のコマを見せたこと、台本を codex にかけなかったこと（自分のネタだから）を、この回で決めています。

| やったこと | なぜ | 結果 |
|---|---|---|
| 題材をシリーズ「ひとりとAIのチーム」4回目（ハコブくんの声と絵）に決め、筋を「絵 → 声 → 読み」にした | 作り手が、配膳ロボットのシリーズではなく、別のシリーズの続きを選んだ | 今日の失敗は「腕が3本」。台本は87文（1版） |
| 台本を、書いたのとは別のエージェントに記録と1文ずつ照合させた | 数・日付・言い過ぎを、記録で確かめるため | 直す9・整える7。結論を「最後に選んで通すのは人」に直した |
| codex にはかけなかった | 作り手の判断「自分のネタだから」 | 照合だけで声に進んだ |
| 候補の声の代わりに、サイトのトップの前の動画の声を流した | 候補の声の ID が残っていなかった | 前の声 7.4秒を、映像と一緒に流す場面を作った |
| ハコブくんの声を、Claude が Mac の中で作った | 鍵が KIOXIA にあり、Mac の shell から動かせる | 85文を4回に分けて作った |
| 確認用1回目の指摘を直した | 冒頭のコマの位置・読み（何話・槙原・表）・作り手が出ていない | 本人のドット絵と声の4文を足し、「表」は「リスト」に言い換えた |
| 確認用2回目の指摘を直した | 文を足して番号がずれ、前の声が解説に重なっていた | 前の声を正しい間に移した |

下の図で、11分10秒のどこで、だれが話しているかを切り替えて見られます。左のボタンは3回目までの形（本人の言葉もハコブくんが言い換える）、右のボタンは新しい形です。

【図解】LOG425_voice_timeline.html

【画像】LOG425_04_eiichi_intro.png 解説動画の中で、作り手エイイチのドット絵が真ん中に大きく出る場面。下に「エイイチ この番組を作っている人」の文字

【画像】LOG425_05_eiichi_honne.png 作り手が話す場面。右下にドット絵のエイイチ、左下にハコブくん。画面の中央に「気がつくと、デザインが変わる」


## 作業ログ ― 題材を決めてから、YouTube にアップロードするまで

ここから下は作業の記録です。効果の数字は、測れたものだけを「変わったこと」に書き、ほかは見込みです。視聴の数字は、公開後に YouTube アナリティクスで確かめます。

### 題材：配膳ロボットのシリーズではなく、「ひとりとAIのチーム」の4回目

「動画作成の続き」と頼まれ、進み具合のページ（動画ごとの作業の段階を一覧にしたページ）と作業フォルダを見て、候補を出しました。止まっていたのは、マンガのショート30本（4コマの絵の確認待ち）だけでした。次に、店の比較動画（配膳ロボットのシミュレーションを左右に並べた動画）の続きと、新しい解説回を出したところ、作り手の答えは「YouTube の動画作成に絞って。シリーズもの」でした。

配膳ロボットのシリーズの第3回（3Dの店で止まる所を見る回）を筋の案まで出しましたが、作り手は「他のシリーズ」を選び、その中から「ひとりとAIのチーム 4回目」に決まりました。シリーズの設計では、4回目は重い回の間に置く軽い回で、ハコブくん自身の話です。

### 材料：声の候補・読みの直し・マンガの決まりの表を、記録から数えた

台本に入れる数は、台本を書く日に記録から数え直しています（シリーズの決まり）。11月1日に数えたのは次の値です。

| 数えたもの | 値 | 数えた場所 |
|---|---|---|
| ハコブくんの声の説明文 | 6通り。1つの説明文から声が3つ出る。決めたのは6つ目の2つ目 | `_制作ベース/ハコブくんの声_候補.md` |
| 読みの直し（ElevenLabs に渡す前に、台本の言葉を読み方に置き換える組） | 37本の台本で207組（重複なし） | 動画フォルダの `*/narration.py` の `ELEVEN_READ` |
| マンガの過去の間違いの表 | 10月30日と31日の2日間で22行。うち4行は別の AI が見つけた | `ハコブくん/4コママンガ作成データ/指示/チェック項目と過去の間違い.md` |
| マンガの日本語版の話数 | 99話（10月半ばまで。番号は第122話まで飛んでいる） | `site_archive/index/02_マンガ一覧.md` |
| ハコブくんの表情・口 | 表情8つ・口は声の大きさで4段階 | `video_rules.md` 4-7 |
| ElevenLabs で持てる声の数 | いまのプランで10個まで（声を貸し借りしている） | `video_rules.md` 9-0b |

ElevenLabs（イレブンラボ）は、文章を AI の声で読み上げるサービスです。その中の「ボイスデザイン（Voice Design）」は、どんな声かを文章で書くと、新しい声を作ってくれる機能です（https://elevenlabs.io/ ）。ハコブくんの声も、この番組でたまに出てくる作り手の声も、ここで作っています。

| 言葉 | この記事での意味 |
|---|---|
| ボイスデザイン | 説明の文章から、新しい声の候補を作る機能 |
| 声の ID | 作った声につく番号。台本を読ませるときに指定する |
| 試し読み（--try） | 台本とは別の文を、いくつかの書き方で読ませて聞き比べること |
| 全文 | 台本の全部の文を、1文ずつ声にすること |

マンガの絵を描いているのは、Google の画像生成 AI「Gemini」です（https://ai.google.dev/ ）。Claude が1コマずつ絵の指示の文を書き、Gemini が1コマずつ描きます。

#### 台本の筋を決めたときの選択

| 聞いたこと | 作り手の答え |
|---|---|
| 筋（右の下書き：声 → 読み → 絵） | 絵を前に、声を後に |
| 声の候補（候補1・2・6の試し読み）を流すか | 流す（音声を探す） |
| 今日の失敗 | 絵：腕が3本 |

#### 作り手に聞いた3つの答え

台本に作り手の実感を入れるときは、Claude が作らず、本人に聞いた答えをもとにします（ユーモアや実体験を Claude が作ると嘘っぽくなるため）。聞いたのは3つです。

> 1.怖いと思うことはないな。声と絵は何度か試した（サイトでも作ったことがあった）結果今の声に落ち着いた。当初はもう少し高いキャラ付けされた声だったよ。
> 2.これはどうなんだろう？AIにこれはできないだろう？と思っていた仕事があっという間に代替されるようになっているので無くなってしまう可能性もあるとは思ってます。
> 3.まだ声のイントネーションや、イラストの描写ルールの間違いなどが多くて気がつくとデザインが変わっていたりするので気が抜けない。

### 台本：別のエージェントの照合で、結論を「最後に選んで通すのは人」に直した

1版の結論は「AI は候補を出す。選ぶのと、間違いを見つけるのは人の目と耳」でした。台本を書いたのとは別のエージェントに、記録と1文ずつ突き合わせてもらうと、いちばん大きな指摘はこの結論でした。マンガの過去の間違いの表の22行のうち4行は、見つけたのが別の AI だったからです。

- ねらい：記録から言える範囲で、結論を言い切る
- 変わったこと：結論を「AI は候補を出し、AI も確かめる。それでも、最後に選んで通すのは、いまは人の目と耳」に直した。見立ての最後の文も「腕が3本なのに気づいたのも、声の抑揚のおかしさに気づいたのも、人でした」と、例に絞った
- 見込める効果：「AI は間違いを見つけられない」と受け取られずに、人の役目（選んで通す）が伝わる

ほかに直したのは、表の行数（20→22）、マンガの話数（「130話を超えた」→「日本語版で99話」）、ハコブくんの体（動画の1コマから切り抜いた体はもう使っていない。いまは画像を作る AI で作り直した絵）、「球」を「きゅま」と読んだのは別の回（ハロの回）だったこと、モデルを比べた14文は1本目の動画の冒頭だったこと、です。

作り手の判断で、codex（OpenAI の AI。台本を論破するつもりで事実を確かめてもらう係）にはかけませんでした。

> codexはいらない（自分のネタだから）

#### 照合の結果（別のエージェントの報告の全文）

```text
台本の87文を出典と突き合わせました。直したほうがよい所が9か所、確かめるか言い方を整えたほうがよい所が7か所ありました。いちばん大きいのは、「間違いを見つけるのは人」という結論が、過去の間違いの表と合わない点です（S013・S082・S079）。

## 直したほうがよい所

**S035「2日間で、20行になりました」**
- 問題：数が違います。表は22行ありました（10月31日が18行、10月30日が4行）。
- 出典：チェック項目と過去の間違い.md の「過去の間違い」の表。narration.py の頭のメモにある「20行」も古い数です。
- 直し方の案：「22行」にする。今後も行は増えるので、「11月1日に数えると」と、数えた日を添える。

**S027「130話を超えました」**
- 問題：話の番号と、出した本数が一致しません。10月12日の時点で日本語版は99話を出していて、番号は第122話まででした（番号が飛んでいます）。10月30日に、まだ作っていなかった30話（第133〜93話・76話・73話）を縦長でまとめて作りました。番号は第133話までありますが、出した本数は130に届かない見込みです（99＋30＝約129）。また、縦長版の指示には「サイトと SNS にはまだ出さない」とあります。
- 出典：site_archive/index/02_マンガ一覧.md（日本語 99話・最大 ep122）、video_rules.md の 9-0c（10月30日の30本）、縦長/ の第133話。
- 直し方の案：「第133話まで作りました」のように番号で言う。または、出した本数を数え直して言う。

**S025「体は、生成AIで作った動画の1コマから、切り抜いています」**
- 問題：古い作り方です。10月12日に体を替えていて、いまは ChatGPT で作り直した体（cut_new.png）を使っています。動画の1コマから切り抜いた体は、頭が切れて見えるので使っていません。
- 出典：video_rules.md の「体の画像（2回目の決定）」と 9-0b、hakobu.py 45行目のコメント（「ChatGPT で作り直した体」）、47行目の BODY = "new"。
- 直し方の案：「体は、画像を作るAIで作った絵から、切り抜いています」。

**S069「このシリーズの1回目で、『球』を『きゅま』と読んだのも、ハコブくんです」**
- 問題：「球」を読み違えたのは、10月17日の「ハロは作れるか」です（「きゅま」と「たま」）。シリーズの1回目は、その失敗を例として紹介した回です。いまの言い方だと、1回目の中で読み違えたように聞こえます。
- 出典：video_rules.md の更新履歴「2026-10-17（59回目）」、ひとりとAIのチーム/narration.py の114行目。
- 直し方の案：「1回目でお話しした、『球』を『きゅま』と読んだ失敗も、ハコブくんです」。

**S013・S082「選ぶのと、間違いを見つけるのは、人の目と耳です」**
- 問題：言い過ぎです。表の22行のうち4行は、見つけた人が「別のエージェント」（AI）です（第117話・第114話・第130話と第131話・第121話と第132話）。手順の中でも、最初の確認（チェック①）は Claude がしています。
- 出典：チェック項目と過去の間違い.md の「進め方」と「過去の間違い」の表。
- 直し方の案：「AIも確かめますが、最後に選んで通すのは人です」。または「腕の3本を見つけたのは人でした」のように、例に絞って言う。

**S079「腕の数を数えて、声の抑揚を聞いているのは、人です」**（Claude の下書き）
- 問題：S013 と同じです。腕の数は、チェック①で AI も数えています。
- 直し方の案：「腕が3本なのに気づいたのも、声の抑揚のおかしさに気づいたのも、人でした」。

**S038「いちばん多かったのは、大きさです」**
- 問題：出典に「いちばん多い」とは書かれていません。行で数えると、大きさは4行、体の崩れ（腕が3本・胴が2つ・上半身だけ・脚が切れる・頭身）は6行で、体のほうが多いです。大きさの行は1行に何話もまとめているので、話の数で数えれば多い、という程度です。出典の言い方は「いつも起きる前提で作る」です。
- 直し方の案：「いちばん手こずったのは、大きさです」、または「いちばん多くの話で起きたのは」。

**S054「最初は、もう少し高い、キャラクターらしい声でした」**（本人の言葉）
- 問題：本人の言葉なので事実の照合からは外しますが、すぐ前の S048・S051 と食い違って聞こえます。記録では、最初の候補（第一候補）は落ち着いた30代の声です。その前の仮の声（Open JTalk）は、高さを1つ下げた男性の声でした。「最初」がいつのことなのか、視聴者には分かりません。
- 出典：ハコブくんの声_候補.md、video_rules.md の 3-2。
- 直し方の案：エイイチさんに「最初」がいつかを確かめて、「声を決める前の、〇〇のころは」と時期を足す。それができなければ、後半の一文を外す。

**S030「描いたのとは別のAIに見てもらいます」**
- 問題：誤解を招く言い方です。出典は「別のエージェント」で、別の会話の Claude のことです。「別のAI」だと、別の会社のAIに聞こえます。「何話もまとめて描いたとき」という条件が入っているのはよいです。
- 出典：チェック項目と過去の間違い.md の「進め方」の2。
- 直し方の案：「描いたのとは別の会話のAIに」、または「描いた本人とは別のAIの係に」。

## 確かめるか、言い方を整えたほうがよい所

- **S023「2回確かめています」**：手順では、書き出したあとにも Claude の確認（チェック③）があり、確認は全部で3回です。言い切りをやめて「何度か確かめています」にする案があります。
- **S053「冒頭の14文で聞き比べて」**：この動画の冒頭のことだと受け取られます。比べたのは1本目「AIエージェントの取り違え」の冒頭14文です。「1本目の動画の冒頭14文で」にします。
- **S056「作れる声は10個まで」**：出典の言い方は「自作の声は、いまのプランでは10個が上限」で、持っておける数の上限です。「持てる声は10個まで」のほうが正確です。いま、声の一覧の9個とハコブくんの声で、ちょうど10個です。
- **S057「ときどき右に出てくるエイイチの声」**：出てくるのは右下です。この回の narration.py にはエイイチが話す文（EI_LINES）がないので、この動画の中には出てきません。「ほかの回で、右下に出てくる」とすると、聞いた人が迷いません。声 ID（Sryf5xKmMsQChU3HsLhZ が槙原）は合っています。
- **S061「これまでの37本の分」**：207組は、頼まれた数え方で合っています（この回を除く37本の ELEVEN_READ を重複なしで数えた数）。ただ、この回の narration.py も足すと218組になります（この回で新しく12組増えたため）。また、37本は narration.py のあるフォルダの数です。公開済みの動画の数と同じかどうかは確かめていません。「動画37本の台本で」と言う場合は、公開した本数と合っているかを一度見てください。
- **S070「全文を作る前に、書き方を変えて試し読みし」**：試し読み（--try）は、読み違いを見つけたあとに使っていることも多いです。毎回、作る前にしているようには聞こえないよう、「怪しい言葉は、書き方を変えて試し読みし」にします。
- **S072「減ったのは、1から作る手間です」**：AIを使う前と比べた記録はありません。エイイチのドット絵も「生成AIの絵を元に手で直した」ものです。「1から作ることは、していません」と、事実の言い方にとどめる案があります。

合っていた所は次のとおりです。腕が3本の話とコマ（第121話の3コマ目の高杉さん、第131話の3コマ目の大城さん、どちらも見つけたのはエイイチ）、第1話の日付（2025年12月21日）、Gemini が描いて Claude が指示の文を書く分担、4コマを1枚で描かせない理由、説明文6通り・1つで3つ出る声・6つ目の文章の2つ目に決まったこと、モデルの比べ方、表情8つ・口4段階、声の上限10個と貸し借り、読みの直しの例（栄一・描く・係・節）。

S040・S076・S078 は、本人の言葉と事実の文が混ざっていませんでした。S054 だけは、上に書いたとおり、すぐ前の文の事実と食い違って聞こえます。

全体の所見
1. 数の間違いは S035（22行）と S027（話の番号と本数）の2つで、どちらも数え直せば直ります。
2. 結論の「間違いを見つけるのは人」は、表の4行（AIが見つけた分）と合いません。S013・S082・S079 を「最後に選んで通すのは人」の形にそろえると、筋が通ります。
3. S025（体）と S069（「球」の回）は、古い記録か、別の回の出来事を言っています。S054 は「最初」がいつかをエイイチさんに確かめるのが先です。
```

### 前の声：候補の声は残っていなかったので、サイトのトップの古い動画の声を流した

台本では、声の候補（落ち着いた30代の案内役・親しみのあるロボット・いまの声）を3つ続けて流す予定でした。作り手に候補の声の ID を聞くと、「声の候補はわすれました」。かわりに出てきたのが、この一言です。

> サイトのトップにあるハコブくんの声は前は違う声でした。データ残ってないのかな？

探すと、`ハコブくん/ハコブくん関連データ/ハコブくん_動画.mp4`（2026年8月8日・10.04秒・音声つき）が残っていて、旧サイトのトップの動画（`site_archive/content/static/top/images/` の 848×1056 の動画）と同じ長さでした。声が入っているのは0.5〜7.6秒で、そこを7.4秒切り出して送ると、作り手が「ハコブくんの前の声はそれです！」と確かめました。

台本は、候補を3つ流す4文をやめて、「サイトのトップで流していた、ハコブくんの動画では、前は、こんな声でした。」のあとの8.6秒の間に、前の声を鳴らす形にしました。

- ねらい：「声は何度か試した」を、言葉だけでなく実物の声で伝える
- 変わったこと：前の声 7.4秒を `sound.py` の効果音の行で、次の文の頭の8.2秒前に入れた。縦長の古い動画は、ぼかした同じ動画を背景に敷いた横長の映像にし、頭に5.04秒の止め絵を足して、声と口の動きがそろうようにした
- 見込める効果：作り手が「もう少し高い、キャラクターっぽい声」と言う声を、見ている人が自分の耳で比べられる

【画像】LOG425_06_old_voice.png 解説動画の中で、前のハコブくんの動画を流している場面。左上に「サイトのトップで流していた、前のハコブくんの動画」の札、中央に「前のハコブくんの声 聞いてみてください」

### 声：Claude が Mac の中で ElevenLabs の声を作った

これまでの解説動画では、声を作るコマンド（`tts_eleven.py`）を作り手が自分の Mac で打っていました。鍵（ElevenLabs の API キー）を Claude に渡さないためです。10月30日に、作り手が鍵を外付けのドライブ（KIOXIA）に置き、スクリプトがそこから読む形にしてあったので、今回は Claude が Mac の shell（作り手の Mac の中で命令を動かす道具）から声を作りました。Claude は鍵の中身を読んでいません。

1回目は、85文を裏で一度に作ろうとして（`nohup … &`）、12文で止まりました。Mac の shell は、命令が返ったあとに裏の処理を続けないためです。そこで、1回に170秒までの区切りで、文の番号を分けて作り直しました。

| 回 | 作った文（0から数える） | かかった時間 | 使った文字数 |
|---|---|---|---|
| 1 | 0〜10（裏で動かしたもの） | 止まった | — |
| 2 | 11〜25 | 28秒 | 約545字 |
| 3 | 26〜55 | 170秒以内 | 約1,451字 |
| 4 | 56〜84 | 170秒以内 | 約1,255字 |

できた声は、声592秒＋間85秒＝11分17秒でした（確認用1回目は10分59秒）。

#### 2回目の作成の出力（11〜25）

```text
025 顔は、コードでえがいています。目と、口と、まばたき。表情は8
できあがり: voice_eleven/ハコブ  使った文字数 約545字
28s
26
```

#### 3回目・4回目の作成の出力と、長さの合計

```text
できあがり: voice_eleven/ハコブ  使った文字数 約1451字
56
できあがり: voice_eleven/ハコブ  使った文字数 約1255字
85
声 592秒 + 間 85秒 = 11分17秒
```

「大城さん」の読みだけは、全文を作る前に、漢字のままと「おおしろ」の2通りを試し読みにして、作り手が漢字のままを選びました。

```text
試し_01 大城さんの腕が、3本になっていました。
試し_02 おおしろさんの腕が、3本になっていました。
試し_03 ジェミニというAIが、その指示で絵をえがく。
試し_04 マンガのまきはらくんの声を借りています。
できあがり: voice_eleven/ハコブ  使った文字数 約82字
```

- ねらい：声を作るたびに、作り手が Mac でコマンドを打つ手間をなくす
- 変わったこと：作り手がしたのは、試しの声を聞いて選ぶことだけになった。85文の全文と、確認のあとの作り直し（17文）と、作り手の声の4文を、Claude が作った
- 見込める効果：声の作り直しを、確認用の指摘を受けたその場で回せる。前の手間（作り手が Mac でコマンドを打っていた時間）は測っていないので、浮いた時間は次の回で比べる

### 画面：大文字型＋動き。背景は記録のファイルを流した映像

画面の形は作り手に選んでもらい、シリーズの1〜3回目と同じ「大文字型（画面に出す文字を少なく大きくする型）＋動きあり」にしました。強い動きの場面は11か所（腕が3本のコマ・問い・体の絵・直す前と直した後・22行の数え上げ・前の声・10個・207組の数え上げ・答え・選ぶ仕事・まとめ）、中くらいの場面は3か所です。背景には、マンガの過去の間違いの表・チェック項目・声の頼み方・読みの直しの表を、エディタで開いた見た目に描いて下へ送った映像を敷きました（`make_bg.py`）。

【画像】LOG425_01_scene_sheet.jpg 解説動画の49場面の一覧。場面ごとに「強・中・静」の札が付いている

冒頭のコマは、第121話の3コマ目を描き直す前の絵（`第121話_P3_20261030_114254.jpg`）で、声の「1、2、3」に合わせて、3本の腕の横に数字が1つずつ出ます。

【画像】LOG425_02_hook_arms.png 冒頭の場面。腕が3本描かれた看護師のマンガのコマの横に、1・2・3の数字が出ている

【画像】LOG425_03_fail_arm_before_after.png 直す前（腕が3本）と描き直した後のコマを左右に並べた場面

### 確認用1回目：冒頭の位置・読み3つ・作り手が出ていない

確認用1回目（10分59秒）への作り手の指摘は5つでした。

> 4秒の1コマのセンター合わせて
> 3分22秒 何話を「なんはなし」と呼んでいたので「なんわ」に修正
> 7分7秒 槙原くんのイントネーションがおかしい修正
> 表のイントネーションが全体で揃っていなので全部確認して
> あとおれの登場とセリフ（音声）がないけど？どうした？修正

| 指摘 | 原因 | 直し |
|---|---|---|
| 冒頭のコマが真ん中にない | 数字を左に置くため、コマを右に寄せていた | コマを真ん中に置き、数字も一緒にずらした |
| 何話を「なんはなし」 | 読みの直しがなかった | `('何話', 'なんわ')` を足し、その文を作り直した |
| 槙原くんの抑揚 | 読みの直しで「まきはら」とひらがなにしていた | 試し読みで漢字・ひらがな・カタカナを比べ、作り手が漢字を選んだ。読みの直しから外した |
| 「表」の抑揚がそろわない | 文ごとに「ひょう」の上がり下がりが変わる | 試し読みで漢字・ひらがな・カタカナ・「リスト」を比べ、作り手が「リスト」を選んだ。台本の12か所すべてと、字幕・画面を「リスト」にした（「表情」はそのまま） |
| 作り手が出ていない | 解説回の型では、本人の言葉もハコブくんが言い換えていた | 謎解きの回の仕組みで、本人のドット絵と声の4文を足した |

#### 「槙原くん」と「表」の試し読み

```text
試し_01 実は、マンガの槙原くんの声を借りています。
試し_02 実は、マンガのまきはらくんの声を借りています。
試し_03 実は、マンガのマキハラくんの声を借りています。
試し_04 決まりの表で確かめ、直しの表に足していきます。
試し_05 決まりのひょうで確かめ、直しのひょうに足していきます。
試し_06 決まりのヒョウで確かめ、直しのヒョウに足していきます。
試し_07 決まりのリストで確かめ、直しのリストに足していきます。
できあがり: voice_eleven/ハコブ_試し2  使った文字数 約171字
```

作り手の選択は、槙原くんは「01 漢字」、表は「07 リストに言い換え」でした。

### 作り手が出る4文：ハコブくんが聞き、本人が答える

本人の言葉は、ハコブくんが「エイイチいわく」と言い換えていた所を、ハコブくんの問いと本人の答えに分けました。本人の声は、マンガの登場人物（槙原くん）の AI 音声を借りています（謎解きの回と同じ）。

| 場面 | ハコブくん | エイイチ（本人の声） |
|---|---|---|
| 絵の章の終わり | ここで、作っている本人に聞いてみます。エイイチです。 | 声のイントネーションも、イラストの描き方の決まりの間違いも、まだ多いんだよね。気がつくと、デザインが変わってたりするから、気が抜けない。 |
| 声の章（前の声の前） | 実は、ハコブくんの声は、これが最初ではありません。 | 声と絵は何度か試して、今の声に落ち着いたんだよ。最初は、もう少し高い、キャラクターっぽい声だった。 |
| 見立て | キャラクターの声と絵を、AIで作ることに、怖さはありますか。 | 怖いと思うことは、ないな。 |
| 見立て | では、デザイナーとして、選ぶ仕事は、人に残るんでしょうか。 | これは、どうなんだろう。AIにはできないだろうと思っていた仕事が、あっという間に置き換わるようになってるから。選ぶ仕事も、なくなる可能性はあると思ってます。 |

ドット絵の口と目は、本人の声の大きさで動き（`ei_talk.py`）、本人が話している間はハコブくんの口を止めます（`narration.py` の `EI_LINES`）。

【画像】LOG425_07_mitate_job.png 見立ての場面。右下に指をさすドット絵のエイイチ、中央に「選ぶ仕事も、なくなるかも」

- ねらい：作り手の考えを、作り手の口から聞いてもらう
- 変わったこと：88文のうち4文・30.4秒を本人が話す。どこからが本人の考えかを、ドット絵と声で示す（上の図のとおり）
- 見込める効果：見立ての節が、ハコブくんの説明ではなく本人の意見として届く。見ている人が、作り手本人に相談しやすくなるかは、公開後の相談の連絡で確かめる

声を2人分に分けたので、声の取り込み（`tts_import.py`）が止まりました。片方のフォルダにない文があると止まる作りのためです。ハコブくんの84文と本人の4文の mp3 を、1つのフォルダ（`voice_eleven/_merge`）に集めてから取り込みました。

```text
040 の音声がありません（voice_eleven/ハコブ2/040.mp3 も、同じ文の仮音声もない）。tts2.py で仮音声を作ってから取り込む
000 の音声がありません（voice_eleven/eiichi/000.mp3 も、同じ文の仮音声もない）。tts2.py で仮音声を作ってから取り込む
```

```text
差し替えた文 88  全体 11:10
```

### 確認用2回目：前の声が、ハコブくんの解説に重なっていた

> 6分20秒　昔のハコブくんの声が解説のハコブくんと別に入っている
> 6分43秒 本来元のハコブくんの声が入るタイミングで声が入っていない
> それ以外はOK

原因は、作り手の文を足したことで、文の番号が2つずれたことでした。前の声は `sound.py` で「文51の頭から8.2秒前」に置いていて、本人の文を足したあとの51番は、本人が話す文になっていました。正しい置き場所は53番（「たしかに、高い、キャラクターらしい声ですね。」）の8.2秒前です。

```text
49 model 5.47 1.2
50 listen 3.55 0.35
51 listen 7.69 1.2
52 listen_old 5.04 8.6
53 listen_now 7.51 1.2
54 cast 5.01 0.35
```

直したあとの書き出しの記録では、前の声は401.60秒（6分41秒）に、52番の文の後ろの間の中で鳴っています。

```text
  401.60秒  文053  src/前の声.m4a  -1dB  「たしかに、高い、キャラクターらし」
```

この直しは、文の番号ではなく文の頭で場所を探す形にしておけば防げました（`NAME_OK` と `EI_LINES` はすでにその形）。効果音の行は番号のままなので、次の回で直します。

### タイトル・サムネイル・概要欄

タイトルは、3つの案から作り手が組み合わせて決めました。

> ハコブくんの声と絵は、どう作っているのか ―キャラクターの声と絵を、AIで作って分かったこと

サムネイルは、腕が3本のコマを背景にし、左に「腕が／3本／見つけたのは人」、右下のハコブくんの札に「描いたのはAI」を置いた1案で、作り手が「サムネイルはOK」としました。

【画像】LOG425_09_youtube_thumbnail.png YouTube のサムネイル。左に大きく「腕が3本 見つけたのは人」、右に腕が3本の看護師のマンガのコマと、右下にハコブくん

概要欄には、ナレーションが AI 音声であること、作り手の言葉は槙原くんの AI 音声を借りていること、マンガとハコブくんの体の絵が生成 AI であることを書きました。シリーズの1〜3回目へのリンクを入れたので、公開の日は3回目（11月4日公開予約）より後にするよう伝えました。

### 保存した場所と、本編の指紋

| もの | 場所 |
|---|---|
| 作り方のファイル（台本・narration.py・scenes.py・sound.py・make_bg.py・thumb.py・概要欄の下書き・投稿文） | iCloud `PixelstoMotors/デジタルツイン学習R2R2R/動画/ハコブくんの声と絵/` |
| 本編・サムネイル・タイトル・概要欄・投稿文 | KIOXIA `r2r2r-data/videos/ハコブくんの声と絵/アップロード用/` |
| 確認用の動画 | KIOXIA `r2r2r-data/videos/ハコブくんの声と絵/ハコブくんの声と絵_確認用.mp4` |
| 使わなかった声（1版の声・試し読み） | iCloud `動画/_旧版/置き換え前_20261102_ハコブくんの声と絵_声1版/` |
| この回で決めたこと | `動画/_制作ベース/video_rules.md` の100回目（3-1 の読みの直しと更新履歴） |

本編は、作業環境で gzip にして18MBずつ4つに分けて Mac へ送り、つないで指紋（md5）が同じことを確かめました。

```text
b843f7fd622e4eddf2554c69b5676c65  out.mp4
b843f7fd622e4eddf2554c69b5676c65  /sessions/rcw-01levexurxqznstgpzxn8pfx/work_hv/up.mp4
b843f7fd622e4eddf2554c69b5676c65  /sessions/rcw-01levexurxqznstgpzxn8pfx/mnt/KIOXIA/r2r2r-data/videos/ハコブくんの声と絵/アップロード用/ハコブくんの声と絵.mp4
```

### 書き出しの記録（assemble.py の出力・確認用2回目のあと）

```text
BGM 1本・効果音 48個 → mix.wav
  401.60秒  文053  src/前の声.m4a  -1dB  「たしかに、高い、キャラクターらし」
   15.02秒  文003  se_rise  -6dB  「そして、いまお話ししている、ハコ」
   21.32秒  文004  se_rise  -6dB  「AIの絵と声なら、ボタン1つで出」
   34.03秒  文006  se_chapter_glock  -8dB  「Pixels to Motors」
   57.77秒  文009  se_chapter_marimba  -8dB  「今日の問いは、ハコブくんの声と絵」
   63.58秒  文010  se_rise  -6dB  「AIを、チームの係のように使って」
   72.98秒  文011  se_rise  -6dB  「見るのは、このチャンネルの作り方」
   82.65秒  文012  se_rise  -6dB  「先に結論を言うと、AIは候補を出」
  108.18秒  文014  se_rise  -6dB  「先に言っておくと、AIに任せれば」
  127.05秒  文016  se_rise  -6dB  「大きく3つあります。」
  140.57秒  文021  se_chapter_marimba  -8dB  「1つ目、絵を、どう描いているのか」
  152.72秒  文023  se_rise  -6dB  「まず、画面の左にいる、このハコブ」
  161.45秒  文025  se_rise  -6dB  「顔は、コードで描いています。目と」
  173.22秒  文026  se_rise  -6dB  「マンガのほうは、4コママンガ『ハ」
  185.32秒  文027  se_rise  -6dB  「いまの作り方は、こうです。」
  210.85秒  文030  se_rise  -6dB  「4コマを1枚で描かせないのは、1」
  218.22秒  文031  se_rise  -6dB  「今日の失敗は、冒頭の、腕が3本で」
  222.92秒  文032  se_rise  -6dB  「第121話の3コマ目で、高杉さん」
  236.88秒  文033  se_rise  -6dB  「そこで、人が出るコマには、毎回、」
  249.52秒  文034  se_rise  -6dB  「こうした間違いは、リストに1行ず」
  273.62秒  文037  se_rise  -6dB  「いちばん手こずったのは、大きさで」
  292.98秒  文039  se_rise  -6dB  「ここで、作っている本人に聞いてみ」
  298.12秒  文040  se_rise  -6dB  「声のイントネーションも、イラスト」
  308.95秒  文041  se_rise  -6dB  「AIに描かせて、決まりのリストで」
  317.17秒  文042  se_chapter_marimba  -8dB  「2つ目、声を、どう選んだのか、で」
  328.95秒  文044  se_rise  -6dB  「使ったのは、ElevenLabs」
  337.35秒  文045  se_rise  -6dB  「頼む文章は、6通り書きました。年」
  350.45秒  文047  se_rise  -6dB  「最初の、落ち着いた声は、読み間違」
  376.55秒  文049  se_rise  -6dB  「声を作るモデルも、1本目の動画の」
  383.22秒  文050  se_rise  -6dB  「実は、ハコブくんの声は、これが最」
  396.02秒  文052  se_rise  -6dB  「サイトのトップで流していた、ハコ」
  418.35秒  文054  se_rise  -6dB  「マンガのショートでは、看護師さん」
  439.92秒  文057  se_rise  -6dB  「候補はAIが作る。決めるのは、聞」
  447.73秒  文058  se_chapter_marimba  -8dB  「3つ目、読み間違いを、どう直して」
  458.15秒  文060  se_rise  -6dB  「声を作る前に、台本の言葉を、読み」
  470.75秒  文061  se_rise  -6dB  「たとえば、案内の最初に出てくる名」
  494.65秒  文064  se_rise  -6dB  「ここで1つ、考えてみてください。」
  505.95秒  文065  se_rise  -6dB  「答えは、同じとは限りません。」
  531.48秒  文069  se_rise  -6dB  「怪しい言葉は、書き方を変えて試し」
  545.75秒  文070  se_rise  -6dB  「では、手間は減ったのか。」
  561.68秒  文073  se_rise  -6dB  「間違いを見つけたら、リストに足し」
  570.37秒  文074  se_chapter_marimba  -8dB  「ここからは、エイイチの見立てです」
  573.98秒  文075  se_rise  -6dB  「キャラクターの声と絵を、AIで作」
  582.42秒  文077  se_rise  -6dB  「では、デザイナーとして、選ぶ仕事」
  608.60秒  文080  se_chapter_marimba  -8dB  「じゃあ、冒頭の問いに答えますね。」
  637.72秒  文083  se_rise  -6dB  「AIの絵や声を見たら、聞いてみて」
  645.62秒  文084  se_rise  -6dB  「次の回は、ロボットに戻ります。A」
  654.03秒  文085  se_chapter_marimba  -8dB  「この動画が役に立ったら、高評価と」
ハコブくんの口を止める文: [40, 51, 76, 78]
frames 20114 = 670.5s  確認用 25.7MB (crf 32)
```

### 動きの場面の書き出し（motion_kit.py の出力・本人の文を足したあと）

```text
hook_koma html 455 frames
question video 170 frames
body html 262 frames
manga video 363 frames
fail_arm html 419 frames
ledger video 723 frames
size video 581 frames
vd_text video 393 frames
listen_old video 409 frames
cast count 647 frames
read_table video 378 frames
quiz_ans slam 766 frames
ei_intro html 154 frames
honne_check html 325 frames
listen html 384 frames
mitate_fear html 253 frames
mitate_job html 790 frames
answer2 slam 869 frames
重なり 0 件
```


## 作業ログ ― この回で使ったコード（全文）

作業フォルダ（iCloud の `動画/ハコブくんの声と絵/`）にあるファイルの、YouTube にアップロードした版の全文です。共通のスクリプト（`_制作ベース/` の render.py・motion_kit.py・assemble.py・audio_mix.py・tts_eleven.py・tts_import.py など）は変えていません。

### narration.py（台本の1文ずつ（場面・表情・字幕・後ろの間）と、読みの直し・本人が話す文）

```python
# -*- coding: utf-8 -*-
# 台本「ハコブくんの声と絵は、どう作っているのか」— シリーズ「ひとりとAIのチーム」4回目（描く係・声の係）
#   2026-11-01 1版。筋はエイイチの決定（絵 → 声 → 読み・今日の失敗は「腕が3本」・候補の声を流す）
#   設計: _ネタ帳/シリーズ_ひとりとAIのチーム_20261018.md・骨組み: 台本_15本_20261012/25_ハコブくんの声と絵.md
#   書き写した元: 動画/AIに昨日の続きをさせる/narration.py（形・読みの直し・自己紹介とアウトロの定型）
#   数えた値（2026-11-01）:
#     - 読みの直しの表: 動画フォルダの narration.py 37本の ELEVEN_READ を重複なしで数えて207組（read_table）
#     - 過去の間違いの表: ハコブくん/4コママンガ作成データ/指示/チェック項目と過去の間違い.md の表 22行（2026-10-30〜31）（ledger）
#   出典（台本の中の事実）:
#     - hook_koma・fail_arm・fail_fix: チェック項目と過去の間違い.md（第121話 P3 高杉さんの腕が3本・第131話 P3 大城さんの腕が3本・見つけたのはエイイチ・直し ANATOMY CHECK）
#     - body・face: video_rules.md 4-7（体は ChatGPT で作り直した絵 cut_new.png（hakobu.py BODY="new"）・顔はコード・表情8・口4段階）
#     - manga・manga_flow・manga_why: 動画/_ネタ帳/マンガ_動画ネタ一覧.md（ep01 2025-12-21）・site_archive/index/02_マンガ一覧.md（日本語99話・2026-10-12 時点）・道具/vbuild.py（gemini-3.1-flash-image）・
#       チェック項目と過去の間違い.md「進め方」・マンガのプロジェクト指示_横長版（4コマを1枚で生成しない理由）
#     - size: チェック項目と過去の間違い.md B（原因1・yline と下地・measure）
#     - vd・vd_text・why・model: _制作ベース/ハコブくんの声_候補.md（説明文6つ・1つで3つ出る・候補1は読みは正確だが見た目に落ち着きすぎ・候補6パターン2に決定・v4 と Multilingual v2 を冒頭14文で比較）・video_rules.md 3-1（声の説明の言い方）
#     - cast: video_rules.md 9-0b（自作の声は今のプランで10個が上限・常連の声を借りる）・_制作ベース/声の一覧.json（槙原＝Sryf5x…・エイイチの声に使っている）
#     - read_ex・quiz_ans・try: video_rules.md 3-1（栄一・描く・係・節・球・--try と全文の違い）
#   2026-11-01 2版: 別のエージェントの照合（直す9・整える7）で直した（結論を「最後に選んで通すのは人」に・22行・99話・体は作り直した絵・球はハロの回・14文は1本目）
#   2026-11-01 3版: エイイチの決定で確定（codex なし）。候補の声は ID を忘れたので流さず、サイトのトップの前の声（ハコブくん/ハコブくん関連データ/ハコブくん_動画.mp4・2026-08-08・site_archive の top にも同じ10.04秒の動画）を流す（listen_old の後の間 8.6秒に src/前の声.m4a を sound.py の SE で入れる）
#   2026-11-01 4版（確認用1回目の指摘）: 何話→なんわ・エイイチの登場と声（ei_intro・EI_LINES 4文）・冒頭のコマを真ん中に・槙原と「表」の抑揚を --try で選ぶ
#   2026-11-01 5版: 「表」は全文で抑揚がそろわず、--try でエイイチが「リスト」への言い換えを選んだ（表情は除く）。「槙原」は ELEVEN_READ のひらがな（まきはら）をやめて漢字のまま（--try 01 をエイイチが選んだ）
#   エイイチの実感（2026-11-01 に聞いた答え。言い回しだけ整えた）: honne_check（気が抜けない）・honne_voice（何度か試した・最初は高いキャラ声）・mitate_fear・mitate_job
#   エイイチに確かめてほしい所: S079 は Claude の下書き
# 話すのはハコブくん。エイイチは三人称で呼ぶ（video_rules.md 3-1）
#
# 各要素: (場面の名前, 表情, 字幕の文, 後ろの間[秒])  間を省略すると 0.35 秒
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")

SEGS = [
 # 0 フック（0:00〜）
 ("hook_koma", "base", "このマンガのコマ、ちょっと、腕を数えてみてください。"),
 ("hook_koma", "surprise", "1、2、3。高杉さんの腕が、3本あります。"),
 ("hook_koma", "smile", "描いたのは、画像を作るAIです。見つけたのは、人の目でした。", 1.2),
 ("hook_voice", "base", "そして、いまお話ししている、ハコブくんのこの声も、AIで作っています。", 1.2),
 ("hook_q", "doubt", "AIの絵と声なら、ボタン1つで出てくるんじゃないの。そう思いませんか。"),
 ("hook_q", "smile", "今日は、ハコブくん自身の話です。ちょっと照れますが、作り方の中身を見ていきます。", 1.8),
 # 1 自己紹介
 ("title", "smile", "Pixels to Motors。案内役のハコブくんです。"),
 ("title", "base", "この番組では、フィジカルAIのニュースや実機の記録から、現場で使える形を考えます。", 1.2),
 ("title_eiichi", "base", "エイイチは、本業のデザイナーをしながら、ロボティクスの研究と開発をしています。｜その中で分かったことと、そこから考えた見立てを、ハコブくんがお伝えします。", 1.8),
 # 2 今日の問い
 ("question", "surprise", "今日の問いは、ハコブくんの声と絵は、どう作っているのか、です。", 1.2),
 ("series", "base", "AIを、チームの係のように使って、1人で研究と発信を続ける。その中身を見ていくシリーズの、4回目です。", 1.2),
 ("scope", "base", "見るのは、このチャンネルの作り方です。｜1人の、1つのやり方の話で、AIの製品の比べ合いではありません。", 1.8),
 # 3 結論と立場
 ("answer", "base", "先に結論を言うと、AIは候補を出します。AIも確かめますが、最後に選んで通すのは、人の目と耳です。"),
 ("answer", "think", "絵は、1コマずつAIに描かせて、決まりのリストで確かめ、最後に人が見て通します。｜声は、文章で頼んで候補を作り、人が聞き比べて決めました。読み間違いは、見つけるたびに、直しのリストに足しています。", 1.2),
 ("stance", "base", "先に言っておくと、AIに任せれば、絵も読みも自然に正しくなる、という話ではないんです。"),
 ("stance", "trouble", "人が選んで確かめるなら、手間は減っていないのでは。そう思った方もいるかもしれません。減った所と、減っていない所があります。それも見ていきます。", 1.8),
 # 4 目次
 ("toc", "base", "大きく3つあります。"),
 ("toc", "base", "絵を、どう描いているのか。"),
 ("toc", "base", "声を、どう選んだのか。"),
 ("toc", "base", "読み間違いを、どう直しているのか。"),
 ("toc", "base", "最後に、エイイチの見立てを話します。", 1.8),
 # 5 節1：絵を、どう描いているのか
 ("c1", "base", "1つ目、絵を、どう描いているのか、です。"),
 ("c1", "base", "一言で言うと、AIに1コマずつ描かせて、決まりのリストと人の目で、何度か確かめています。", 1.2),
 ("body", "base", "まず、画面の左にいる、このハコブくん。"),
 ("body", "base", "体は、画像を作るAIで作った絵から、切り抜いています。", 1.2),
 ("face", "smile", "顔は、コードで描いています。目と、口と、まばたき。表情は8つあります。｜口は、声の大きさに合わせて、4段階で開きます。", 1.2),
 ("manga", "base", "マンガのほうは、4コママンガ『ハコブくん』です。2025年12月に1話目を出して、10月半ばまでに、日本語版で99話を出しました。", 1.2),
 ("manga_flow", "base", "いまの作り方は、こうです。"),
 ("manga_flow", "base", "Claudeが、1コマずつ、絵の指示の文を書く。GeminiというAIが、その指示で絵を描く。"),
 ("manga_flow", "base", "描けたら、AIが、決まりのリストと見比べて、1コマずつ確かめる。何話もまとめて描いたときは、描いたのとは別の会話のAIに見てもらいます。最後に、人が全部のコマを見て、通すかを決めます。", 1.2),
 ("manga_why", "think", "4コマを1枚で描かせないのは、1枚に4つの構図を頼むと、どこかが崩れるからです。", 1.2),
 ("fail", "trouble", "今日の失敗は、冒頭の、腕が3本です。", 1.2),
 ("fail_arm", "base", "第121話の3コマ目で、高杉さんの腕が3本。第131話の3コマ目では、大城さんの腕が3本になっていました。どちらも、見つけたのは人でした。", 1.2),
 ("fail_fix", "base", "そこで、人が出るコマには、毎回、腕は2本、数えること、と指示に入れました。確かめるリストにも、腕と手と脚を、数える行があります。", 1.2),
 ("ledger", "base", "こうした間違いは、リストに1行ずつ足しています。11月1日に数えると、10月30日と31日の2日間で、22行ありました。"),
 ("ledger", "smile", "ハコブくんの横の輪に、穴が開いた。ハコブくんに、腕が生えた。扉が、頭の上に浮いた。"),
 ("ledger", "trouble", "……ハコブくんには、腕も脚もありません。生えたら困ります。", 1.2),
 ("size", "base", "いちばん手こずったのは、大きさです。"),
 ("size", "base", "身長を数字で書いても、絵のAIは、コマの空いた所に収まる大きさで描いてしまいます。｜そこで、頭のてっぺんと足もとの位置を線で決めた下地を、一緒に渡すようにしました。描いたあとに、高さの比も測ります。", 1.2),
 ("ei_intro", "smile", "ここで、作っている本人に聞いてみます。エイイチです。", 1.2),
 ("honne_check", "base", "声のイントネーションも、イラストの描き方の決まりの間違いも、まだ多いんだよね。気がつくと、デザインが変わってたりするから、気が抜けない。", 1.2),
 ("c1_sum", "think", "AIに描かせて、決まりのリストで確かめ、最後は人の目で通す。これが、1つ目です。", 1.8),
 # 6 節2：声を、どう選んだのか
 ("c2", "base", "2つ目、声を、どう選んだのか、です。"),
 ("c2", "base", "一言で言うと、文章で頼むと声を作る仕組みで候補を作り、人が聞き比べて決めました。", 1.2),
 ("vd", "base", "使ったのは、ElevenLabsのボイスデザインです。どんな声かを文章で書くと、新しい声を作ってくれます。", 1.2),
 ("vd_text", "base", "頼む文章は、6通り書きました。年代も、話し方も、少しずつ変えています。"),
 ("vd_text", "base", "1つの文章で、声が3つずつ出てきます。そこから、聞き比べていきました。", 1.2),
 ("why", "base", "最初の、落ち着いた声は、読み間違いもなく、正確でした。｜でも、ハコブくんの見た目には、落ち着きすぎていて、合わなかったんです。"),
 ("why", "think", "決まったのは、6つ目の文章で出た3つのうち、2つ目の声です。｜「説明好きで親切な、お手伝いロボット」をイメージした、30代くらいの男性の声を思わせる声です。誰かの声を録音して、元にしてはいません。", 1.2),
 ("model", "base", "声を作るモデルも、1本目の動画の冒頭の14文で聞き比べて、決めました。", 1.2),
 ("listen", "smile", "実は、ハコブくんの声は、これが最初ではありません。"),
 ("listen", "base", "声と絵は何度か試して、今の声に落ち着いたんだよ。最初は、もう少し高い、キャラクターっぽい声だった。", 1.2),
 ("listen_old", "base", "サイトのトップで流していた、ハコブくんの動画では、前は、こんな声でした。", 8.6),
 ("listen_now", "smile", "たしかに、高い、キャラクターらしい声ですね。いまの、この声は、ボイスデザインで作り直したものです。", 1.2),
 ("cast", "base", "マンガのショートでは、看護師さんや先生にも、同じ仕組みで声を付けています。"),
 ("cast", "trouble", "ただ、いまのプランでは、持てる声は10個までです。足りないので、同じ話に出ない人どうしで、声を貸し借りしています。"),
 ("cast", "smile", "謎解きの回で、右下に出てくるエイイチの声も、実はマンガの槙原くんの声を借りています。", 1.2),
 ("c2_sum", "think", "候補はAIが作る。決めるのは、聞いた人の耳。これが、2つ目です。", 1.8),
 # 7 節3：読み間違いを、どう直しているのか
 ("c3", "base", "3つ目、読み間違いを、どう直しているのか、です。"),
 ("c3", "base", "一言で言うと、間違いを見つけるたびに、直しのリストに足していきます。", 1.2),
 ("read_table", "base", "声を作る前に、台本の言葉を、読み方に置き換えるリストです。｜11月1日に、これまでの37本の台本の分を数えると、207組ありました。", 1.2),
 ("read_ex", "base", "たとえば、案内の最初に出てくる名前は、漢字にして渡しています。いくつかの書き方を聞き比べて、いちばん平らに読めたのが、漢字だったからです。"),
 ("read_ex", "trouble", "「描く」を「かく」と読んで、「書く」と聞き分けられなかったこともあります。いまは「えがく」と渡しています。"),
 ("read_ex", "base", "「係」は、漢字と、ひらがなと、カタカナで聞き比べて、カタカナにしました。", 1.2),
 ("quiz", "base", "ここで1つ、考えてみてください。｜同じ字で、読みが2つある言葉。AIは、どちらで読むかを、毎回同じに決めてくれるでしょうか。", 1.2),
 ("quiz_ans", "surprise", "答えは、同じとは限りません。"),
 ("quiz_ans", "base", "「節」という字を、同じ動画の中で、「せつ」と「ふし」に読み分けたことがありました。"),
 ("quiz_ans", "think", "そういう言葉は、読みを足すより、別の言葉に言い換えています。「節の頭」は、「章の頭」に、という具合です。"),
 ("quiz_ans", "smile", "ちなみに、1回目でお話しした、「球」を「きゅま」と読んだ失敗も、ハコブくんです。……ちゃんと覚えています。", 1.2),
 ("try", "base", "怪しい言葉は、書き方を変えて試し読みし、人が聞いて選びます。｜それでも、全文で読ませると、試しでは読めた言葉を、読み違えることがあります。だから、最後は通しで聞きます。", 1.2),
 ("less_more", "base", "では、手間は減ったのか。"),
 ("less_more", "base", "声を録音する、絵を一から描く、という作業は、していません。"),
 ("less_more", "think", "減っていないのは、選ぶことと、確かめることです。むしろ、確かめる所は、リストになって増えています。", 1.2),
 ("c3_sum", "think", "間違いを見つけたら、リストに足して、次から同じ所でつまずかないようにする。これが、3つ目です。", 1.8),
 # 8 見立て
 ("mitate", "base", "ここからは、エイイチの見立てです。", 1.2),
 ("mitate_fear", "base", "キャラクターの声と絵を、AIで作ることに、怖さはありますか。", 0.6),
 ("mitate_fear", "base", "怖いと思うことは、ないな。", 1.2),
 ("mitate_job", "think", "では、デザイナーとして、選ぶ仕事は、人に残るんでしょうか。", 0.6),
 ("mitate_job", "base", "これは、どうなんだろう。AIにはできないだろうと思っていた仕事が、あっという間に置き換わるようになってるから。選ぶ仕事も、なくなる可能性はあると思ってます。", 1.2),
 ("mitate_job", "think", "ただ、今日の記録を見る限り、腕が3本なのに気づいたのも、声の抑揚のおかしさに気づいたのも、人でした。", 1.8),
 # 9 問いへの回答・まとめ
 ("answer2", "base", "じゃあ、冒頭の問いに答えますね。ハコブくんの声と絵は、どう作っているのか。"),
 ("answer2", "think", "絵は、AIに1コマずつ描かせて、決まりのリストと人の目で確かめる。声は、文章で頼んで作った候補から、人が聞き比べて選ぶ。｜読み間違いは、見つけるたびに、リストに足していく。"),
 ("answer2", "base", "AIは候補を出し、AIも確かめる。それでも、最後に選んで通すのは、いまは人の目と耳です。", 1.2),
 ("question_next", "base", "AIの絵や声を見たら、聞いてみてください。その候補を、誰が選んで、誰が確かめたのか。", 1.2),
 ("series_next", "smile", "次の回は、ロボットに戻ります。AIに、ブロックをつかめたかを判定させたら、どうなったか。", 1.8),
 # 10 アウトロ
 ("outro", "smile", "この動画が役に立ったら、高評価とチャンネル登録をお願いします。"),
 ("outro", "base", "ロボットの開発や導入の相談は、概要欄の連絡先から、エイイチに直接どうぞ。"),
 ("outro", "wink", "コメントは、エイイチが全部読んでいます。それではまた。", 1.8),
]

# エイイチの声で読む文（槙原の声を流用・video_rules.md 1-4）。ハコブくんの口は動かさない。2026-11-01 確認用1回目でエイイチの指示「おれの登場とセリフ（音声）がない」で足した
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[2].startswith((
    "声のイントネーションも、イラストの",
    "声と絵は何度か試して",
    "怖いと思うことは、ないな",
    "これは、どうなんだろう",
))]

# 「エイイチ」を残す文（自己紹介・目次の最後・見立ての入り・アウトロの2文のほかに、名前が要る文）。文の頭で探す（video_rules.md 3-1）
NAME_OK = [i + 1 for i, seg in enumerate(SEGS) if seg[2].startswith(("ここで、作っている本人に", "謎解きの回で、右下に"))]

READ = [
 ("Pixels to Motors", "ピクセルズ トゥー モーターズ"), ("ElevenLabs", "イレブンラボ"), ("Gemini", "ジェミニ"), ("Claude", "クロード"),
 ("フィジカルAI", "フィジカルエーアイ"), ("AI", "エーアイ"), ("1人", "ひとり"), ("1つ", "ひとつ"), ("2つ", "ふたつ"), ("3つ", "みっつ"),
 ("栄一", "えいいち"), ("「", ""), ("」", ""), ("『", ""), ("』", ""), ("｜", ""), ("…", ""),
]

ELEVEN_READ = [
    ('エイイチ', '栄一'),   # video_rules.md 3-1
    ('4コママンガ', 'よんコマまんが'),
    ('Claude', 'クロード'),
    ('ElevenLabs', 'イレブンラボ'),
    ('描く', 'えがく'), ('描き', 'えがき'), ('描か', 'えがか'), ('描いて', 'えがいて'), ('描いた', 'えがいた'), ('描け', 'えがけ'),
    ('係', 'カカリ'),
    ('物差し', 'ものさし'), ('人型', 'ひとがた'),
    # この回で先回りして足す読み（--try で確かめる）
    ('Gemini', 'ジェミニ'),
    ('第121話', 'だいひゃくにじゅういちわ'), ('第131話', 'だいひゃくさんじゅういちわ'),
    ('130話', 'ひゃくさんじゅうわ'),
    ('1話目', 'いちわめ'),
    ('3コマ目', 'さんコマめ'),
    ('207組', 'にひゃくななくみ'),
    ('14文', 'じゅうよんぶん'),
    ('何話', 'なんわ'),   # 2026-11-01 確認用で「なんはなし」と読んだ（エイイチが見つけた）
    ('22行', 'にじゅうにぎょう'),
    ('99話', 'きゅうじゅうきゅうわ'),
    ('高杉', 'たかすぎ'),
]

OVERRIDE = {}

def reading(t):
    t = OVERRIDE.get(t, t)
    for a, b in READ:
        t = t.replace(a, b)
    return t


def parse(seg):
    """1行を (場面, 表情, 字幕, 間) にそろえる。表情の欄がない古い形 (場面, 字幕[, 間]) も読む"""
    if len(seg) >= 3 and seg[1] in FACES:
        scene, face, text = seg[0], seg[1], seg[2]
        pause = seg[3] if len(seg) > 3 else 0.35
    else:
        scene, face, text = seg[0], "base", seg[1]
        pause = seg[2] if len(seg) > 2 else 0.35
    return scene, face, text, pause
```

### scenes.py（場面の絵・動き・本人のドット絵の小窓）

```python
# -*- coding: utf-8 -*-
# 動画「ハコブくんの声と絵は、どう作っているのか」（シリーズ「ひとりとAIのチーム」4回目・描く係と声の係）の場面
PATTERN = 2   # 画面パターン2（大文字型・video_rules.md 4-4c）
MOVE = True   # 画面型の動き（4-4d）を強弱を付けて使う（2026-11-01 エイイチが選んだ）
# 2026-11-01: 「AIに昨日の続きをさせる」の scenes.py の形を写して書いた
# 強弱（video_rules.md 4-4d）:
#   中: manga（縦長の並び）・vd_text（頼んだ文章）・size（チェック項目）
#   強（11場面・続けない）: hook_koma（腕が3本のコマ）・question・body（体の絵）・fail_arm（直す前／直した後）・ledger（22 と数え上げ）・
#     listen_old（前の声の動画）・cast（10 個）・read_table（207 と数え上げ）・quiz_ans・mitate_job・answer2
# 背景の「画面」は、記録のファイルをエディタの見た目で描いて下へ送った映像（make_bg.py。中身は記録のまま）
# マンガの絵は生成AI（Gemini）で描いた物。直す前の絵（腕が3本）は 第121話/生成/第121話_P3_20261030_114254.jpg
import os as _os, sys as _sys
_here = _os.path.dirname(_os.path.abspath(__file__))
for _c in (_os.environ.get("P2M_BASE", ""), _os.path.join(_here, "..", "base"), _os.path.join(_here, "..", "_制作ベース")):
    if _c and _os.path.exists(_os.path.join(_c, "scene_kit.py")):
        _sys.path.insert(0, _c); break
from scene_kit import *
from motion_kit import use_motion, CAP

import json as _json, base64 as _b64
from scene_kit import _p2page
# --- 場面ごとの、文の始まりの秒（声の長さから）。「車は作れるのにロボは作れない」の scenes.py から写した ---
_idx = _json.load(open(_os.path.join(_here, 'tts/index.json'))) if _os.path.exists(_os.path.join(_here, 'tts/index.json')) else []
_LINES = {}
for _o in _idx:
    _d = _LINES.setdefault(_o['scene'], [])
    _t0 = (_d[-1][0] + _d[-1][1] + _d[-1][2]) if _d else 0.0
    _d.append((_t0, _o['dur'], _o['pause']))


def at(scene, k=0, f=0.0):
    L = _LINES.get(scene)
    if not L:
        return 0.3
    k = min(k, len(L) - 1)
    return round(L[k][0] + L[k][1] * f, 2)


def scene_len(scene):
    L = _LINES.get(scene)
    return (L[-1][0] + L[-1][1] + L[-1][2]) if L else 4.0


FUDA_CSS = """
.fuda{position:absolute;left:140px;top:96px;display:flex;align-items:center;gap:14px;font-size:30px;font-weight:700;color:#0D0D0D}
.fuda .k{padding:6px 18px;border-radius:999px;font-size:28px;font-weight:900}
.fuda .k.ei{background:#C4471F;color:#fff}
"""


def fuda(who):
    return f'<style>{FUDA_CSS}</style><div class="fuda"><span class="k ei">エイイチ本人</span><span>{who}</span></div>'


# --- エイイチ（ドット絵・右下の小窓）。video_rules.md 1-4 ---
import ei_talk as _talk
EI = {k: (_talk.base_uri(k), _talk.base_uri(k, True)) for k in ('udegumi', 'unazuku', 'yubisasu', 'hirameku')}
import importlib.util as _ilu
_sp = _ilu.spec_from_file_location('_narr_here', _os.path.join(_here, 'narration.py'))
_nm = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_nm)
_EIL = getattr(_nm, 'EI_LINES', [])
_EI_SPEAK = {}
_cnt = {}
for _o in _idx:
    _k = _cnt.get(_o['scene'], 0); _cnt[_o['scene']] = _k + 1
    if _o['i'] in _EIL:
        _EI_SPEAK.setdefault(_o['scene'], []).append((_k, _o['i']))

EI_CSS = """
.eiw{position:absolute;right:10px;bottom:0px;width:420px;height:420px;overflow:hidden}
.eiw img{position:absolute;left:0;top:8px;width:420px;height:420px;image-rendering:pixelated}
"""


def _blinks(start, end):
    t, out = start, []
    while t < end - 0.4:
        out.append(round(t, 2)); t += 3.3
    return out


def ei_wipe(pose, scene, pop=None):
    L = scene_len(scene) + 1
    spans, mouth, blinks = [], [], []
    for k, i in _EI_SPEAK.get(scene, []):
        t0 = at(scene, k, 0.0)
        sq, end = _talk.mouth_seq(i, t0)
        spans.append((t0, end)); mouth += sq
        blinks += [(b, 0.13) for b in _talk.blink_times(t0, end)]
    mouth.sort()
    blinks += [(b, 0.15) for b in _blinks((pop or 0) + 1.3, scene_len(scene)) if not any(a0 - 0.2 <= b <= a1 + 0.2 for a0, a1 in spans)]
    ev = []
    for t, l in mouth:
        if pose in _talk.MOUTH:
            ev.append((t, f'<img src="{_talk.mouth_patches(pose)[l]}" data-a="fade" data-t="{t}" data-d="0.01">'))
    e = _talk.eye_patches(pose)
    for b, d in blinks:
        if e:
            ev.append((b, f'<img src="{e[1]}" data-a="fade" data-t="{b}" data-d="0.01">'))
            ev.append((round(b + d, 2), f'<img src="{e[0]}" data-a="fade" data-t="{round(b + d, 2)}" data-d="0.01">'))
    ev.sort(key=lambda x: x[0])
    inner = f'<img src="{EI[pose][0]}">' + ''.join(h for _, h in ev)
    a = f' data-a="pop" data-t="{pop}" data-d="0.35"' if pop is not None else ''
    return f'<style>{EI_CSS}</style><div class="eiw"{a}>{inner}</div>'


def add(page_html, *parts):
    return page_html.replace('</body>', ''.join(parts) + '</body>', 1)


EI_BIG = """<style>.eib{position:absolute;left:50%;top:120px;width:520px;height:520px;margin-left:-260px;border-radius:40px;overflow:hidden;background:#F5F5F0;border:6px solid #D6D6D1}
.eib img{position:absolute;left:0;top:10px;width:520px;height:520px;image-rendering:pixelated}
.eibn{position:absolute;left:420px;right:420px;top:660px;text-align:center;font-size:72px;font-weight:900}
.eibn small{display:block;font-size:40px;font-weight:700;color:#888;margin-top:6px}</style>"""

T1 = "<b>1</b>絵"
T2 = "<b>2</b>声"
T3 = "<b>3</b>読み"
T4 = "<b>4</b>エイイチの見立て"

BMS = dict(file="src/bg_mistakes.mp4", dim=0.5, slow=1.3)      # 過去の間違いのリスト
BCK = dict(file="src/bg_check.mp4", dim=0.5, slow=1.3)         # チェック項目 A（体）
BVC = dict(file="src/bg_voice.mp4", dim=0.5, slow=1.3)         # 声の頼んだ文章
BRD = dict(file="src/bg_read.mp4", dim=0.5, slow=1.3)          # 読みの直しのリスト
BMG = dict(file="src/bg_manga.mp4", dim=0.55, slow=1.0)        # 縦長マンガの確認用の並び
OLD = dict(file="src/hakobu_old_wide.mp4", dim=0.8)                 # サイトのトップの前の動画（前の声）

S = {}
# 0 フック
S["hook_koma"] = p2_word(['腕を', '<span class="red">数えてみて</span>'], kicker="このマンガのコマ", kc="muted", size=170)
S["hook_voice"] = p2_word(['この声も', '<span class="blue">AIで作った</span>'], kicker="いま話している、ハコブくんの声", kc="muted", size=150)
S["hook_q"] = p2_quiz(['AIの絵と声は、', 'ボタン1つで出る？'], pill="そう思いませんか", size=130)
# 2 今日の問い
S["question"] = p2_quiz(['ハコブくんの声と絵は、', 'どう作っているのか'], pill="今日の問い", size=120)
S["series"] = p2_word(['ひとりとAIの', '<span class="blue">チーム</span>'], kicker="シリーズ 4回目", kc="muted", size=170, sub="今日は「描く係」と「声の係」")
S["scope"] = p2_word(['1人の、', '<span class="blue">1つのやり方</span>'], kicker="今日の範囲：このチャンネルの作り方", kc="muted", size=150, sub="AIの製品の比べ合いではない")
# 3 結論と立場
S["answer"] = p2_pair("AI", "人", da="候補を出す・確かめる", db="最後に選んで通す", ca="blue", cb="red", mid="→", kicker="先に結論", size=200)
S["stance"] = p2_word(['手間は', '<span class="red">減った？</span>'], kicker="人が選んで確かめるなら", kc="muted", size=170, sub="減った所と、減っていない所がある")
# 4 目次
S["toc"] = p2_list([("1", "絵を、どう描く"), ("2", "声を、どう選んだ"), ("3", "読み間違いを、どう直す"), ("4", "エイイチの見立て")], kicker="今日は3つ＋見立て", size=90)
# 5 節1 絵
S["c1"] = chapter2("1つ目", "絵", "AIに描かせて、リストと人の目で確かめる")
S["body"] = p2_word(['体は', '<span class="blue">AIの絵</span>'], kicker="画面の左のハコブくん", kc="muted", size=170, sub="画像を作るAIで作った絵から、切り抜いた", tag=T1)
S["face"] = p2_pair("8つ", "4段階", da="表情", db="口の開き（声の大きさで）", ca="blue", cb="blue", mid="／", kicker="顔はコードで描く", size=200, tag=T1)
S["manga"] = p2_num("99", "話", kicker="4コママンガ『ハコブくん』（日本語版・10月半ばまで）", sub="1話目は 2025年12月", tag=T1)
S["manga_flow"] = p2_list([("Claude", "指示の文を書く"), ("Gemini", "1コマずつ描く"), ("AI", "リストと見比べる"), ("人", "全部見て通す")], kicker="いまの作り方", size=80, tag=T1)
S["manga_why"] = p2_word(['4コマを1枚で', '<span class="red">描かせない</span>'], kicker="1枚に4つの構図を頼むと", kc="muted", size=140, sub="どこかが崩れる", tag=T1)
S["fail"] = p2_word(['今日の', '<span class="red">失敗</span>'], kicker="冒頭の、あのコマ", kc="muted", size=190, tag=T1)
S["fail_arm"] = p2_pair("腕が3本", "2本に", da="第121話・3コマ目（直す前）", db="描き直した後", ca="red", cb="blue", mid="→", kicker="見つけたのは、人", size=150, tag=T1, sub="第131話でも、大城さんの腕が3本")
S["fail_fix"] = p2_word(['腕は2本、', '<span class="blue">数えること</span>'], kicker="人が出るコマには、毎回指示に入れた", kc="muted", size=150, sub="確かめるリストにも、腕・手・脚を数える行", tag=T1)
S["ledger"] = p2_num("22", "行", kicker="過去の間違いのリスト（10/30〜31・11/1 に数えた）", sub="輪に穴・腕が生えた・扉が浮いた", tag=T1)
S["size"] = p2_word(['数字を書いても', '<span class="red">大きさは守らない</span>'], kicker="いちばん手こずったのは、大きさ", kc="muted", size=120, sub="頭と足の位置の線を渡し、描いた後に比を測る", tag=T1)
S["c1_sum"] = p2_pair("AIが描く", "人が通す", da="リストで確かめる", db="最後は人の目", ca="blue", cb="red", mid="→", kicker="1つ目のまとめ", size=150, tag=T1)
# 6 節2 声
S["c2"] = chapter2("2つ目", "声", "文章で頼んで候補を作り、聞き比べる")
S["vd"] = p2_word(['文章で頼むと', '<span class="blue">声ができる</span>'], kicker="ElevenLabs のボイスデザイン", kc="muted", size=150, tag=T2)
S["vd_text"] = p2_pair("6通り", "3つずつ", da="頼んだ文章", db="1つの文章から出る声", ca="blue", cb="blue", mid="×", kicker="候補を作る", size=170, tag=T2)
S["why"] = p2_word(['正確でも', '<span class="red">見た目に合わない</span>'], kicker="最初の、落ち着いた30代の声", kc="muted", size=140, sub="決めたのは、6つ目の文章の2つ目の声", tag=T2)
S["model"] = p2_word(['モデルも', '<span class="blue">聞き比べた</span>'], kicker="1本目の動画の冒頭14文で", kc="muted", size=160, tag=T2)
S["listen_old"] = p2_word(['前の', '<span class="red">ハコブくんの声</span>'], kicker="サイトのトップの動画", kc="muted", size=150, tag=T2)
S["listen_now"] = p2_pair("前の声", "いまの声", da="高い、キャラクターらしい声", db="ボイスデザインで作り直した", ca="", cb="blue", mid="→", kicker="聞き比べると", size=150, tag=T2)
S["cast"] = p2_num("10", "個まで", kicker="いまのプランで持てる声", sub="足りないので、出ない話どうしで貸し借り", tag=T2)
S["c2_sum"] = p2_pair("AIが作る", "耳で決める", da="候補", db="聞いた人", ca="blue", cb="red", mid="→", kicker="2つ目のまとめ", size=150, tag=T2)
# 7 節3 読み
S["c3"] = chapter2("3つ目", "読み", "見つけるたびに、直しのリストに足す")
S["read_table"] = p2_num("207", "組", kicker="読みの直しのリスト（37本の台本・11/1 に数えた）", sub="台本の言葉 → 読み方", tag=T3)
S["read_ex"] = p2_list([("描く", "かく → えがく"), ("係", "カタカナで渡す"), ("名前", "漢字で渡す")], kicker="直した例", size=96, tag=T3)
S["quiz"] = p2_quiz(['読みが2つある字。', '毎回同じに読む？'], pill="考えてみてください", size=120, tag=T3)
S["quiz_ans"] = p2_word(['同じとは', '<span class="red">限らない</span>'], kicker="答え", kc="muted", size=170, sub="「節」を「せつ」と「ふし」に読み分けた → 言い換える", tag=T3)
S["try"] = p2_word(['最後は', '<span class="blue">通しで聞く</span>'], kicker="試し読みで読めても", kc="muted", size=160, sub="全文では読み違えることがある", tag=T3)
S["less_more"] = p2_pair("していない", "残っている", da="声の録音・一から描く", db="選ぶ・確かめる", ca="blue", cb="red", mid="／", kicker="手間は減ったのか", size=140, tag=T3)
S["c3_sum"] = p2_word(['リストに足して、', '<span class="blue">同じ所でつまずかない</span>'], kicker="3つ目のまとめ", kc="muted", size=120, tag=T3)
# 8 見立て
S["mitate"] = chapter2("最後に", "見立て", "エイイチの考え")

# --- エイイチの登場と声（2026-11-01 確認用1回目の指示「おれの登場とセリフ（音声）がない」）---
_o, _c = EI['udegumi']
_bl = ''.join(f'<img src="{_c}" data-a="fade" data-t="{b}" data-d="0.01"><img src="{_o}" data-a="fade" data-t="{b + 0.15}" data-d="0.01">' for b in _blinks(1.6, scene_len('ei_intro')))
S["ei_intro"] = page(EI_BIG + f'<div class="eib" data-a="pop" data-t="0.15" data-d="0.4"><img src="{_o}">{_bl}</div><div class="eibn" data-a="up" data-t="0.6">エイイチ<small>この番組を作っている人</small></div>', tag=T1)
S["honne_check"] = add(p2_word(['気がつくと、', '<span class="red">デザインが変わる</span>'], kicker="作っている本人に聞いた", kc="muted", size=140, sub="だから、気が抜けない", tag=T1), fuda("確認の大変さ"), ei_wipe('udegumi', 'honne_check', pop=0.1))
S["listen"] = add(p2_word(['声は', '<span class="blue">何度か試した</span>'], kicker="実は", kc="muted", size=170, sub="最初は、もう少し高いキャラクターっぽい声", tag=T2), fuda("声の決め方"), ei_wipe('hirameku', 'listen', pop=at('listen', 1, 0.0)))
S["mitate_fear"] = add(p2_word(['怖いと思うことは、', '<span class="blue">ないな</span>'], kicker="AIで声と絵を作ることに、怖さは？", kc="muted", size=140, tag=T4), fuda("エイイチの見立て"), ei_wipe('udegumi', 'mitate_fear', pop=0.1))
S["mitate_job"] = add(p2_word(['選ぶ仕事も、', '<span class="red">なくなるかも</span>'], kicker="デザイナーとして", kc="muted", size=150, sub="できないと思った仕事も、すぐ置き換わった", tag=T4), fuda("エイイチの見立て"), ei_wipe('yubisasu', 'mitate_job', pop=0.1))
# 9 答え
S["answer2"] = p2_word(['最後に通すのは、', '<span class="red">いまは人</span>'], kicker="問いへの答え", kc="muted", size=150, sub="AIは候補を出し、AIも確かめる")
S["question_next"] = p2_quiz(['その候補、', '誰が選んだ？'], pill="AIの絵や声を見たら", size=150)
S["series_next"] = p2_word(['つかめたかを', '<span class="blue">AIが判定</span>'], kicker="次回", kc="muted", size=150, sub="ロボットに戻ります")

# 動画シーンに重ねるテロップ（透過）
O = {
 "title": overlay('<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">フィジカルAI解説</div><div class="c">案内役：ハコブくん（AI音声）</div></div>'),
 "title_eiichi": overlay('<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">エイイチ</div><div class="c">デザイナー／ロボティクスの研究・開発</div></div>'),
 "outro": overlay('<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">相談は概要欄の連絡先から</div><div class="c">高評価・チャンネル登録／コメントはエイイチが全部読んでいます</div></div>'),
}

# 実機の映像を流す場面 (映像, 開始秒, 透かし)。開始秒 None は前の場面の続き
VIDEO = {
 "title": ("src/day124_crop.mp4", 4.5, False),
 "title_eiichi": ("src/day124_crop.mp4", None, False),
 "outro": ("src/day124_crop.mp4", 18.0, False),
}

B4 = 'src/manga/p121_P3_before.jpg'
A4 = 'src/manga/p121_P3_after.png'
def _f(p):
    return 'file://' + _os.path.abspath(_os.path.join(_here, p))

# 動きのある場面（motion_kit.py が src/motion_{場面}.mp4 に書き出す）
MOTION = {
 "hook_koma": dict(type="html", html=page(
     f'<img data-a="pop" data-t="0.1" data-d="0.5" src="{_f(B4)}" style="position:absolute;left:670px;top:100px;height:720px;border:6px solid #0D0D0D;border-radius:8px">'
     '<div data-a="slam" data-t="4.2" data-d="0.4" style="position:absolute;left:550px;top:450px;font-size:120px;font-weight:900;color:#C4471F;-webkit-text-stroke:6px #fff;paint-order:stroke fill">1</div>'
     '<div data-a="slam" data-t="4.7" data-d="0.4" style="position:absolute;left:750px;top:540px;font-size:120px;font-weight:900;color:#C4471F;-webkit-text-stroke:6px #fff;paint-order:stroke fill">2</div>'
     '<div data-a="slam" data-t="5.2" data-d="0.4" style="position:absolute;left:960px;top:590px;font-size:120px;font-weight:900;color:#C4471F;-webkit-text-stroke:6px #fff;paint-order:stroke fill">3</div>'
     '<div data-a="fade" data-t="0.5" style="position:absolute;left:670px;top:832px;font-size:26px;color:#888">マンガの絵は生成AI（Gemini）で作成・第121話の直す前の絵</div>')),
 "question": dict(type="video", bg=dict(BMG, ss=0, blur=6), card=dict(pill="今日の問い", big="ハコブくんの声と絵は、<br>どう作っているのか", size=110, at=0.15)),
 "body": dict(type="html", html=page(
     f'<img data-a="pop" data-t="0.2" data-d="0.5" src="{_f("src/manga/body.png")}" style="position:absolute;left:520px;top:150px;height:700px">'
     f'<img data-a="pop" data-t="{0.2}" data-d="0.5" src="{_f("src/manga/faces.png")}" style="position:absolute;left:1060px;top:260px;width:700px;border-radius:12px">'
     '<div data-a="write" data-t="0.1" data-d="0.8" style="position:absolute;left:0;right:0;top:70px;text-align:center;font-family:\'Klee One\';font-weight:600;font-size:90px;color:#0D0D0D">体はAIの絵、顔はコード</div>', tag=T1)),
 "manga": dict(type="video", bg=dict(BMG, ss=0), cap="縦長の4コマの確認用の並び（絵は生成AI）",
               card=dict(kicker="4コママンガ『ハコブくん』日本語版", kicker_color="#ddd", big='<span style="color:#fff">99話</span>', sub="1話目は 2025年12月（10月半ばまで）", size=200, at=(0, 0.6))),
 "fail_arm": dict(type="html", html=page(
     f'<img data-a="pop" data-t="0.1" data-d="0.5" src="{_f(B4)}" style="position:absolute;left:520px;top:200px;height:640px;border:6px solid #C4471F;border-radius:8px">'
     f'<img data-a="slam" data-t="{2.0}" data-d="0.45" src="{_f(A4)}" style="position:absolute;left:1140px;top:200px;height:640px;border:6px solid #1A6C98;border-radius:8px">'
     '<div data-a="fade" data-t="0.3" style="position:absolute;left:520px;top:110px;width:520px;text-align:center;font-size:64px;font-weight:900;color:#C4471F">直す前：腕が3本</div>'
     '<div data-a="fade" data-t="2.0" style="position:absolute;left:1140px;top:110px;width:520px;text-align:center;font-size:64px;font-weight:900;color:#1A6C98">描き直した後</div>'
     '<div data-a="fade" data-t="0.5" style="position:absolute;left:420px;bottom:236px;font-size:28px;color:#888">第121話の3コマ目（絵は生成AIで作成）</div>', tag=T1)),
 "ledger": dict(type="video", bg=dict(BMS, ss=0), cap="チェック項目と過去の間違い.md（過去の間違いのリスト）",
                card=dict(kicker="過去の間違いのリスト（10/30〜31）", kicker_color="#ddd", big='<span data-c="0.3,1.6,22">0</span><span style="font-size:110px">行</span>', sub="輪に穴・腕が生えた・扉が浮いた", size=220, at=(0, 0.2))),
 "size": dict(type="video", bg=dict(BCK, ss=0), cap="チェック項目と過去の間違い.md（A. 体）",
             card=dict(kicker="いちばん手こずったのは、大きさ", kicker_color="#ddd", big='数字を書いても<br><span style="color:#C4471F">大きさは守らない</span>', sub="頭と足の位置の線を渡し、描いた後に比を測る", size=110, at=(0, 0.3))),
 "vd_text": dict(type="video", bg=dict(BVC, ss=0), cap="ハコブくんの声_候補.md（頼んだ文章の日本語訳）",
                card=dict(kicker="候補を作る", kicker_color="#ddd", big='文章<span style="color:#5BA4D0">6通り</span> × 声<span style="color:#5BA4D0">3つずつ</span>', size=110, at=(0, 0.2))),
 "listen_old": dict(type="video", bg=dict(OLD, ss=0), cap="サイトのトップで流していた、前のハコブくんの動画",
                    card=dict(kicker="前のハコブくんの声", kicker_color="#ddd", big='<span style="color:#fff">聞いてみてください</span>', size=110, at=(0, 0.1))),
 "cast": dict(type="count", counts=[("10", 10, 0.2, (0, 0.8))]),
 "read_table": dict(type="video", bg=dict(BRD, ss=0), cap="37本の台本の読みの直し（ELEVEN_READ・重複なし）",
                    card=dict(kicker="読みの直しのリスト（11/1 に数えた）", kicker_color="#ddd", big='<span data-c="0.3,1.8,207">0</span><span style="font-size:110px">組</span>', size=220, at=(0, 0.2))),
 "quiz_ans": dict(type="slam", at=0.05),
 "ei_intro": dict(type="html", html=S["ei_intro"]),
 "honne_check": dict(type="html", html=S["honne_check"]),
 "listen": dict(type="html", html=S["listen"]),
 "mitate_fear": dict(type="html", html=S["mitate_fear"]),
 "mitate_job": dict(type="html", html=S["mitate_job"]),
 "answer2": dict(type="slam", at=(1, 0.1)),
}
use_motion(MOTION, VIDEO, O, _here, MOVE)
```

### sound.py（BGM と効果音・前の声を鳴らす所）

```python
# 音の置き方（audio_mix.py が読む。書き方は audio_mix.py の先頭）
# 2026-11-02 確認用2回目の指摘で直した: エイイチの文を足して番号がずれ、前の声が listen の文の上に重なっていた（51→53）
# 2026-11-01 「AIに昨日の続きをさせる」の sound.py を写した（同じ曲・同じ決まり）。前の声（サイトのトップの動画の声）を listen_old のあとの間に入れた

BGM = [
    dict(file='sound/bgm_明るいB_2.mp3', start=0, end=None, level=-23, gap=-16, fade_in=0.6, fade_out=3.0),
]

SE = [
    # 前のハコブくんの声（src/前の声.m4a・7.4秒）。listen_old（文52）の後の間 8.6秒の中で鳴らす。
    # 次の文（53）の頭から 8.2秒前 ＝ 文52 の頭から 5.44秒後。映像（src/hakobu_old_wide.mp4）も頭に 5.04秒の止め絵を入れてそろえた
    (53, -8.2, 'src/前の声.m4a', -1),
]

SCENE_SE = dict(file='se_rise', level=-6, offset=-0.15, skip=['title_eiichi', 'listen_now'])
CHAPTER_SE = dict(file='se_chapter_marimba', files={'title': 'se_chapter_glock'}, level=-8, offset=-0.3,
                  scenes=['title', 'question', 'c1', 'c2', 'c3', 'mitate', 'answer2', 'outro'])
```

### ei_talk.py（本人のドット絵の口と目を声で動かす（KDDIの300億円の回から写した。変えていない））

```python
# エイイチ（ドット絵）が話している間、口と目を動かす部品（2026-10-28 エイイチの指示「話している時はおれの口や目が動くように」）
# 口は3段（閉じる・いつもの笑顔・大きく開く）を、声の大きさで 0.1 秒ごとに選ぶ（1段ずつしか変えない）。
# 目は、話し始めと、話している間ときどきまばたきする。
# どれも 100 ドットの絵の「その部分だけ」を描いた透明な PNG を、時間で重ねていく（motion_kit の data-a="fade"）。
import os, io, base64, wave
import numpy as np
from PIL import Image

HERE = os.path.dirname(os.path.abspath(__file__))
INK = (1, 1, 1, 255)
TEETH = (250, 250, 246, 255)
INSIDE = (70, 22, 18, 255)
TONGUE = (177, 67, 33, 255)

# 口の両端（左x, 右x, 上y）。100 ドットの絵の座標
MOUTH = {'udegumi': (39, 52, 41), 'yubisasu': (39, 52, 41), 'unazuku': (36, 47, 43), 'hirameku': (38, 49, 42)}


def _load(pose, b=False):
    return np.array(Image.open(os.path.join(HERE, f"src/ei/{pose}{'_b' if b else ''}.png")).convert('RGBA'))


def fill_teeth(a):
    """顔の中の透明な穴（歯の所）を白で埋める。背景に映像があると透けて見えるため"""
    from scipy import ndimage
    t = a[..., 3] < 128
    lab, n = ndimage.label(t)
    edge = set(np.unique(np.concatenate([lab[0], lab[-1], lab[:, 0], lab[:, -1]])))
    for k in range(1, n + 1):
        if k in edge:
            continue
        m = lab == k
        if m.sum() < 80:
            a[m] = TEETH
    return a


def _uri(a):
    b = io.BytesIO(); Image.fromarray(a.astype(np.uint8)).save(b, 'PNG')
    return 'data:image/png;base64,' + base64.b64encode(b.getvalue()).decode()


def _skin(a, xl, xr, yt):
    """顔のいちばん多い肌の色（口のまわりの、明るい色）"""
    from collections import Counter
    reg = a[yt - 6:yt + 6, xl - 3:xr + 4].reshape(-1, 4)
    c = Counter(tuple(int(v) for v in px) for px in reg if px[3] > 200 and px[0] > 220 and 120 < px[1] < 200)
    return c.most_common(1)[0][0]


def mouth_patches(pose):
    """口だけを描いた透明な絵 3枚（0 閉じる・1 いつもの・2 開く）"""
    a = fill_teeth(_load(pose))
    xl, xr, yt = MOUTH[pose]
    sk = _skin(a, xl, xr, yt)
    y0, y1 = yt, yt + 5
    out = []
    for lv in range(3):
        p = np.zeros_like(a)
        if lv == 1:
            p[y0:y1 + 1, xl:xr + 1] = a[y0:y1 + 1, xl:xr + 1]
        else:
            # 元の口の線と歯だけを肌の色で消す（まわりの影は残す）
            reg = a[y0:y1 + 1, xl:xr + 1].copy()
            rg = reg[..., :3].astype(int)
            ink = (rg.max(2) < 90) | (abs(rg[..., 0] - rg[..., 1]) < 40)   # 黒い線・白や灰の歯
            reg[ink] = sk
            p[y0:y1 + 1, xl:xr + 1] = reg
            p[yt, xl] = p[yt, xr] = INK
            p[yt + 1, xl + 1:xr] = INK
            if lv == 2:
                p[yt + 2, xl + 1] = p[yt + 2, xr - 1] = INK
                p[yt + 2, xl + 2:xr - 1] = TEETH
                p[yt + 3, xl + 2] = p[yt + 3, xr - 2] = INK
                p[yt + 3, xl + 3:xr - 2] = INSIDE
                p[yt + 4, xl + 3] = p[yt + 4, xr - 3] = INK
                p[yt + 4, xl + 4:xr - 3] = TONGUE
                p[yt + 5, xl + 4:xr - 3] = INK
        out.append(_uri(p))
    return out


def eye_patches(pose):
    """目の所だけの絵（開いた目・閉じた目）"""
    o, c = _load(pose), _load(pose, True)
    d = np.argwhere((o != c).any(2))
    if not len(d):
        return None
    y0, y1, x0, x1 = d[:, 0].min() - 1, d[:, 0].max() + 1, d[:, 1].min() - 1, d[:, 1].max() + 1
    po, pc = np.zeros_like(o), np.zeros_like(o)
    po[y0:y1 + 1, x0:x1 + 1] = o[y0:y1 + 1, x0:x1 + 1]
    pc[y0:y1 + 1, x0:x1 + 1] = c[y0:y1 + 1, x0:x1 + 1]
    return _uri(po), _uri(pc)


def base_uri(pose, b=False):
    return _uri(fill_teeth(_load(pose, b)))


def envelope(i, step=0.1):
    """tts/{i:03d}.wav の声の大きさ（step 秒ごと・0〜1）"""
    w = wave.open(os.path.join(HERE, f'tts/{i:03d}.wav'))
    sr, n, ch, sw = w.getframerate(), w.getnframes(), w.getnchannels(), w.getsampwidth()
    x = np.frombuffer(w.readframes(n), dtype=np.int16 if sw == 2 else np.int32).astype(np.float32)
    if ch > 1:
        x = x.reshape(-1, ch).mean(1)
    x /= np.abs(x).max() + 1e-9
    h = int(sr * step)
    r = np.array([np.sqrt(np.mean(x[k:k + h] ** 2)) for k in range(0, len(x) - h, h)])
    return r / (np.percentile(r, 95) + 1e-9)


def talk_layers(pose, i, t0, step=0.1):
    """場面の頭から t0 秒にエイイチの文 i が始まるときの、口と目の重ね（img の並び）"""
    m = mouth_patches(pose)
    env = envelope(i, step)
    seq, lv = [], 1
    for k, v in enumerate(env):
        want = 0 if v < 0.18 else (1 if v < 0.55 else 2)
        if want != lv:
            lv += 1 if want > lv else -1
            seq.append((round(t0 + k * step, 2), lv))
    end = round(t0 + len(env) * step, 2)
    seq.append((end, 1))   # 話し終わったら、いつもの笑顔に戻す
    h = ''
    for t, l in seq:
        h += f'<img src="{m[l]}" data-a="fade" data-t="{t}" data-d="0.01">'
    e = eye_patches(pose)
    if e:
        o, c = e
        bt, last = t0 + 0.05, end
        while bt < last - 0.3:
            h += f'<img src="{c}" data-a="fade" data-t="{round(bt, 2)}" data-d="0.01"><img src="{o}" data-a="fade" data-t="{round(bt + 0.13, 2)}" data-d="0.01">'
            bt += 1.6 + (int(bt * 7) % 5) * 0.25
    return h, (t0, end)


# 2026-10-29 「車は作れるのにロボは作れない」で足した（scenes.py の ei_wipe が使う。前の回の作業環境にだけあった版を、同じ動きで書き直した）
def mouth_seq(i, t0, step=0.1):
    """文 i の口の段の並び [(秒, 段)] と、話し終わりの秒。段は 0 閉じる・1 いつもの・2 開く（1段ずつしか変えない）"""
    env = envelope(i, step)
    seq, lv = [], 1
    for k, v in enumerate(env):
        want = 0 if v < 0.18 else (1 if v < 0.55 else 2)
        if want != lv:
            lv += 1 if want > lv else -1
            seq.append((round(t0 + k * step, 2), lv))
    end = round(t0 + len(env) * step, 2)
    seq.append((end, 1))
    return seq, end


def blink_times(t0, end):
    """話している間のまばたき（1.6〜2.6秒おき）"""
    out, bt = [], t0 + 0.05
    while bt < end - 0.3:
        out.append(round(bt, 2))
        bt += 1.6 + (int(bt * 7) % 5) * 0.25
    return out
```

### make_bg.py（記録のファイルをエディタの見た目で描いて、下へ送る背景の映像を作る）

```python
# -*- coding: utf-8 -*-
"""背景の映像（記録のファイルを、エディタで開いた画面のように描いて、ゆっくり下へ送る）を src/ に作る。
2026-11-01「ハコブくんの声と絵」: 「AIに昨日の続きをさせる」の make_bg.py を写した。過去の間違いの表・チェック項目・声の候補・読みの直しの表を見せる（画面の録画の代わり。中身は記録のまま・書き換えない）
  python3 make_bg.py
"""
import html, os, subprocess
from playwright.sync_api import sync_playwright

HERE = os.path.dirname(os.path.abspath(__file__))
FPS = 24
# (出力, 元のファイル, タブに出す名前, 何行目から, 何行目まで, 秒)
JOBS = [
    ("bg_mistakes.mp4", "src/md/mistakes.md", "ハコブくん / チェック項目と過去の間違い.md（過去の間違いの表）", 1, 26, 30),
    ("bg_check.mp4", "src/md/check_items.md", "ハコブくん / チェック項目と過去の間違い.md（A. 体・B. 大きさ）", 1, 30, 30),
    ("bg_voice.mp4", "src/md/voice_candidates.md", "_制作ベース / ハコブくんの声_候補.md（頼んだ文章の日本語訳・決定）", 1, 38, 30),
    ("bg_read.mp4", "src/md/eleven_read.md", "37本の narration.py / ELEVEN_READ（読みの直し・重複なし）", 1, 208, 60),
]

CSS = """
body{margin:0;background:#1e1e1e;width:1920px;font-family:'Noto Sans CJK JP',sans-serif}
.tab{height:64px;background:#2d2d2d;color:#ddd;font-size:28px;display:flex;align-items:center;padding-left:40px;border-bottom:2px solid #3c3c3c}
.tab b{background:#1e1e1e;padding:14px 30px;border-top:3px solid #C4471F;font-weight:500}
.code{padding:30px 60px 400px 0}
.ln{display:flex;font-size:30px;line-height:1.6}
.n{width:110px;text-align:right;color:#6e7681;padding-right:34px;flex:none}
.t{color:#d4d4d4;white-space:pre-wrap;word-break:break-all;flex:1}
.t.h{color:#569cd6;font-weight:700}
.t.ng{color:#f48771}
.t.b{color:#ce9178}
"""


def render(src, tab, a, b, png):
    lines = open(os.path.join(HERE, src), encoding="utf-8").read().split("\n")[a - 1:b]
    rows = []
    for i, l in enumerate(lines, a):
        cls = "h" if l.startswith("#") else ("ng" if "❌" in l or "⚠" in l else ("b" if l.startswith("**") else ""))
        rows.append(f'<div class="ln"><div class="n">{i}</div><div class="t {cls}">{html.escape(l) or "&nbsp;"}</div></div>')
    doc = f'<html><head><meta charset="utf-8"><style>{CSS}</style></head><body><div class="tab"><b>{html.escape(tab)}</b></div><div class="code">{"".join(rows)}</div></body></html>'
    with sync_playwright() as p:
        br = p.chromium.launch()
        pg = br.new_page(viewport={"width": 1920, "height": 1080})
        pg.set_content(doc)
        pg.screenshot(path=png, full_page=True)
        br.close()


for out, src, tab, a, b, sec in JOBS:
    png = os.path.join(HERE, "src", out.replace(".mp4", ".png"))
    render(src, tab, a, b, png)
    h = int(subprocess.check_output(["ffprobe", "-v", "error", "-show_entries", "stream=height", "-of", "csv=p=0", png]).decode())
    travel = max(h - 1080, 0)
    subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-loop", "1", "-i", png, "-t", str(sec), "-r", str(FPS),
                    "-vf", f"crop=1920:1080:0:'min({travel},{travel}*t/{sec})',format=yuv420p",
                    "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", os.path.join(HERE, "src", out)], check=True)
    print(out, h, travel)
```

### sheet.py（場面の一覧の画像を作る）

```python
import os, subprocess, narration as N
from PIL import Image, ImageDraw, ImageFont
order=[]
for s in N.SEGS:
    sc=N.parse(s)[0]
    if sc not in order: order.append(sc)
STRONG={'hook_koma','question','body','fail_arm','ledger','listen_old','cast','read_table','quiz_ans','mitate_job','answer2'}
MID={'manga','vd_text','size'}
tw,th=480,270; cols=6; rows=(len(order)+cols-1)//cols
sheet=Image.new('RGB',(cols*tw,rows*(th+44)),'#ddd'); d=ImageDraw.Draw(sheet)
f=ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc',26)
os.makedirs('sheet_tmp',exist_ok=True)
for i,sc in enumerate(order):
    mp=f'src/motion_{sc}.mp4'
    if os.path.exists(mp):
        dur=float(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration','-of','csv=p=0',mp]))
        p=f'sheet_tmp/{sc}.png'; subprocess.run(['ffmpeg','-y','-loglevel','error','-ss',str(dur*0.8),'-i',mp,'-frames:v','1',p])
    elif os.path.exists(f'png/scene/{sc}.png'): p=f'png/scene/{sc}.png'
    else:
        p=f'sheet_tmp/{sc}.png'; subprocess.run(['ffmpeg','-y','-loglevel','error','-ss','6','-i','src/day124_crop.mp4','-frames:v','1',p])
    im=Image.open(p).convert('RGB').resize((tw,th))
    x=(i%cols)*tw; y=(i//cols)*(th+44)
    sheet.paste(im,(x,y+44))
    lab='強' if sc in STRONG else ('中' if sc in MID else '静')
    col={'強':'#C4471F','中':'#1A6C98','静':'#555'}[lab]
    d.rectangle([x,y,x+tw-2,y+42],fill=col); d.text((x+10,y+6),f'{i+1:02d} {lab}  {sc}',fill='white',font=f)
sheet.save('一覧_場面.jpg',quality=85); print(sheet.size, len(order))
```

### thumb.py（YouTube のサムネイルの文言と背景）

```python
# サムネイルの文言と背景（thumbnail.py が読む）。2026-11-01「ハコブくんの声と絵」: 背景は第121話の3コマ目の直す前の絵（生成AI・Gemini）に、数字をコードで重ねた画像
PHOTO = 'サムネ素材/腕が3本_全体.png'
PHOTO_POS_FULL = '50% 50%'
HAKOBU = True
LABEL = '描いたのはAI'
KICKER = 'ハコブくんの声と絵の作り方'
LINES = ['腕が', '<span class="red">3本</span>', '見つけたのは人']
VARIANTS = ['photo']
SIZE_L1, SIZE_L2, SIZE_L3 = 170, 330, 130
TXT_TOP = 70
HK_BOTTOM = -300
LABEL_BOTTOM = 40
LABEL_RIGHT = 450
LABEL_TAIL = 'bubble'
LABEL_FS = 60
```

### make_docs.py（読み物の台本と codex の依頼文を作る（この回は codex にかけていない））

```python
"""台本の読み物（台本_*.md）と codex 敵対的検証の依頼（codex_*.md）を、narration.py から作り直す。
台本を直したら、動画の作業フォルダで  python3 make_docs.py
2026-11-01: 「AIに昨日の続きをさせる」の make_docs.py を写して書き換えた（シリーズ「ひとりとAIのチーム」4回目）
"""
import sys, json, os, datetime
sys.path.insert(0, '.')
from narration import SEGS, parse
NAME = "ハコブくんの声と絵"
TITLE = "ハコブくんの声と絵は、どう作っているのか ― AIが候補を出し、人が見比べ・聞き比べて選ぶ"
ROUND = 1
FJ = {"base": "基本", "smile": "小さな笑み", "surprise": "驚き", "doubt": "ジト目", "think": "目を閉じる",
      "trouble": "困り眉", "joy": "＞▽＜", "wink": "ウインク"}
SEC = [("hook_koma", "0 フック"), ("title", "1 自己紹介"), ("question", "2 今日の問い"), ("answer", "3 結論と立場"),
       ("toc", "4 目次"), ("c1", "5 節1：絵を、どう描いているのか"),
       ("c2", "6 節2：声を、どう選んだのか"), ("c3", "7 節3：読み間違いを、どう直しているのか"),
       ("mitate", "8 見立て"), ("answer2", "9 問いへの回答・まとめ"), ("outro", "10 アウトロ")]
idx = {o['i']: o for o in json.load(open('tts/index.json'))} if os.path.exists('tts/index.json') else {}
rows, n, t, done, first, last = [], 0, 0.0, set(), {}, {}
for k, s in enumerate(SEGS):
    sc, f, text, p = parse(s)
    for key, name in SEC:
        if sc == key and name not in done:
            rows.append(("sec", name, t)); done.add(name)
    o = idx.get(k)
    t += (o['dur'] + o['pause']) if o else 0
    if text:
        n += 1
        first.setdefault(sc, n); last[sc] = n
        rows.append(("line", n, f, sc, text))


def R(*scs):
    return "・".join((f"S{first[x]:03d}〜S{last[x]:03d}" if first[x] != last[x] else f"S{first[x]:03d}") for x in scs if x in first)


# 読み物の台本
out = [f"# 台本：{TITLE}", "", "話し手はハコブくん（エイイチは三人称）。各行は「文番号｜表情｜場面｜字幕の文」。字幕の「｜」は、読み上げは1文のまま字幕だけ次の枚に替える位置。", ""]
for r in rows:
    if r[0] == "sec":
        m, ss = divmod(int(r[2]), 60); out += ["", f"## {r[1]}" + (f"（{m}:{ss:02d}〜）" if idx or r[1] == "0 フック" else ""), ""]
    else:
        out.append(f"- S{r[1]:03d}｜{FJ[r[2]]}｜{r[3]}｜{r[4]}")
if idx:
    m, ss = divmod(int(t), 60)
    out += ["", f"全体の長さ（いまの音声）：{m}分{ss:02d}秒"]
open(f"台本_{NAME}.md", "w").write("\n".join(out) + "\n")

# codex 敵対的検証の依頼
body = []
for r in rows:
    body.append(f"\n### {r[1]}\n" if r[0] == "sec" else f"S{r[1]:03d}　{r[4].replace('｜', '')}")
md = f"""# 敵対的検証依頼：YouTube台本「{TITLE}」

## あなたへの依頼

これから公開する解説動画（見込み 約9分）の台本を、**この台本を論破するつもりで**検証してください。褒める必要はありません。見落としを1つでも多く見つけることが目的です。

- チャンネル：フィジカルAI解説｜Pixels to Motors。書き手はエイイチ（台本の文は主に Claude が書き、エイイチが確定する）。本業のデザイナーをしながら、ロボティクスの研究と開発をしている個人
- 話し手：ナレーションはキャラクターのハコブくん（AI 音声）。エイイチを三人称で呼ぶ
- 視聴者：ロボティクスに関心のある経営者・PM・マネージャー・開発者
- シリーズ「ひとりとAIのチーム」の4本目（全6回）。エイイチが研究と発信を1人で続けるために実際にやっている AI の使い方を、記録をもとに見せる。AI 活用の相談の看板にはしない。この回は、案内役ハコブくん自身の「絵と声」の作り方。シリーズの中の軽い回
- 台本の結論：AI は候補を出し、AI も確かめるが、最後に選んで通すのは人。絵は Claude が1コマずつ指示の文を書き、Gemini が描き、決まりの表（チェック項目）と人の目で確かめる（今日の失敗＝人の腕が3本）。声は ElevenLabs のボイスデザインで説明文から候補を作り、人が聞き比べて決めた。読み間違いは見つけるたびに直しの表に足す。手間は「1から作る」作業はしていないが、選ぶ・確かめる手間は残っている
- 材料はエイイチ自身の記録（マンガのチェック項目と過去の間違いの表・声の候補の記録・動画制作の決まり video_rules.md・台本の読みの直しの表）。あなたは記録を直接は開けないので、**下の記録の写しと台本の文が合っているか・記録から言える範囲を越えていないか**を中心に見てください。ElevenLabs のボイスデザインと Gemini の画像生成の一般的な説明は、公式のページをウェブで確かめてください
- 書く前に、台本を書いたのとは別のエージェントに、記録と1文ずつ突き合わせてもらった（直したこと: 結論を「間違いを見つけるのは人」から「AIも確かめるが、最後に選んで通すのは人」に（過去の間違いの表22行のうち4行は別のAIが見つけていた）・表の行数20→22・「130話を超えた」→日本語版99話（番号は飛んでいる）・体は動画の1コマでなく作り直した絵・「球」の読み違いは別の回・14文は1本目の動画の冒頭・別のAI→別の会話のAI・「いちばん多かった」→「いちばん手こずった」）
- エイイチ本人の実感の文（{R('honne_voice','honne_check')}・{R('mitate_fear','mitate_job')} の一部）は本人の答えで、事実として裏付けを探す対象ではありません。置いた場所と、事実の文を弱めていないかは見てください
- 検証日：{datetime.date.today().isoformat()}。参照できない場合は「未確認」と明記し、推測で OK を出さないでください
- **これは{ROUND}回目の検証です。**

## 検証してほしい観点

### A. 事実
A1. 下の記録の写しと台本の各文が合っているか。特に数（{R('fail_arm','ledger','read_table','vd_text','face','cast','manga')}）
A2. ElevenLabs・Gemini の説明（{R('vd','manga_flow')}）が、公式の説明の範囲か。製品の比べ合いや宣伝に聞こえないか
### B. 論理
B1. 問い（{R('question')}）と答え（{R('answer','answer2')}）がそろっているか
B2. 「人が選ぶなら手間は減っていないのでは」という反論（{R('stance','less_more')}）に、記録から言える範囲で答えているか。減った・増えたと言い過ぎていないか
B3. 「最後に選んで通すのは人」と言うのに、AI が見つけた間違いがあることと矛盾しないか
### C. 抜けている反論・視点
C1. 生成AIの絵と声を使うことへの、権利・利用条件・表示（概要欄に「AI音声」「マンガの絵は生成AI」と書く運用）の視点が抜けていないか。ただし台本で扱うかはエイイチが決める
C2. 1人の体制の話を、チームや会社にそのまま当てはめていないか
### D. 言い方
D1. 断定が強すぎる文、逆に曖昧で意味のない文
D2. 冗長な所。いまの見込みは約9分（字幕の本文で約3,600字）
D3. 軽い一言（{R('ledger','quiz_ans','hook_q')} の一部）が、事実の文や但し書きを弱めていないか

## 回答の書式

指摘ごとに次の形で書いてください。重大なものから順に並べてください。

```
[❌ NG / ⚠️ 要注意] 文番号（例：S043）
問題：何が問題か（1〜2文）
根拠：確認した出典（記録の写しの該当箇所・ウェブの原文）または推論の筋道
修正案：差し替える文（台本の話し言葉の調子に合わせて）
```

最後に次の3点をまとめてください。
1. このまま公開してよいか（はい／直してから／大きく作り直すべき）
2. 公開前に必ず直すべき指摘の文番号
3. 台本の結論そのものへの評価（成立している／条件つきで成立／成立していない）と、その理由

---

## 台本が使った出典（エイイチの記録。2026-11-01 に Claude が Mac で数えた・写した）

| 主張 | 文番号 | 記録 |
|---|---|---|
| 第121話の3コマ目で高杉さん、第131話の3コマ目で大城さんの腕が3本。どちらもエイイチが見つけた。人が出るコマに「腕は2本・数える」を入れた | {R('hook_koma','fail_arm','fail_fix')} | チェック項目と過去の間違い.md「過去の間違い」の表: 「2026-10-31｜第121話 P3｜高杉さんの腕が3本｜エイイチ｜ANATOMY CHECK（腕2本・数える）を人が出る全コマに」「2026-10-31｜第131話 P3｜大城さんの腕が3本｜エイイチ｜ANATOMY CHECK」。チェック項目 A「人は腕2本・手2つ・脚2本。数える」 |
| 表は10月30日と31日の2日間で22行（11月1日に数えた） | {R('ledger')} | 同じ表の行数（10/31 が18行・10/30 が4行）。うち4行は見つけた人が「別のエージェント」 |
| 横の輪に穴・腕が生えた・扉が頭の上に浮いた | {R('ledger')} | 同じ表「30話すべて｜ハコブくんの側面の輪の真ん中に穴」「第116話｜ハコブくんに人型の腕・脚が生える」「第125話 P2｜扉がハコブくんの頭の上に浮いた板になった」 |
| 身長を数字で書いても、絵のAIはコマの空いた所に収まる大きさで描く。頭頂と足もとの線の下地を渡し、高さの比も測る | {R('size')} | チェック項目 B「大きさが並び図と変わる原因」1「絵のモデルは数字の大きさを守らない…コマを埋める・構図が収まるように大きさを決める」・「大きさの指示は1か所だけ（yline の頭頂・足もとの位置＋薄緑の枠の下地）」・「measure で比を出していちばん近いものを選ぶ」 |
| 絵の作り方: Claude が1コマずつ指示の文、Gemini が描く、AI が表で確かめ（まとめて描いたら別の会話のAI）、最後にエイイチ | {R('manga_flow')} | チェック項目「進め方」1〜4（チェック①は Claude・1枚ずつ。まとめて何話も描いたときは別のエージェント。チェック②はエイイチの OK）。道具/vbuild.py「縦長4コマのプロンプトを…組み、Gemini で描き」MODEL = gemini-3.1-flash-image |
| 4コマを1枚で描かせないのは、どこかが崩れるから | {R('manga_why')} | マンガのプロジェクト指示（横長版）「4コマを1枚で生成することはしない。1枚に4つの構図・向き・比率を同時指定すると必ずどこかが崩れるため、1プロンプト＝1コマに分割する」 |
| 2025年12月に1話目、10月半ばまでに日本語版99話 | {R('manga')} | マンガ_動画ネタ一覧.md「ep01｜2025-12-21」・site_archive/index/02_マンガ一覧.md「日本語 99話」（最新 ep122・2026-10-11） |
| 体は画像を作るAIで作った絵から切り抜き、顔はコード（目・口・まばたき・表情8・口4段階） | {R('body','face')} | hakobu.py「"new": ChatGPT で作り直した体」BODY = "new"・video_rules.md 4-7（表情 base・smile・surprise・doubt・think・trouble・joy・wink／口は音量で4段階／まばたき） |
| 声: ElevenLabs のボイスデザイン。説明文を6通り。1つの説明文で3つ出る。候補1は読みは正確だが見た目に落ち着きすぎ。6つ目の説明文の2つ目に決定。モデルは1本目の動画の冒頭14文で比べた | {R('vd','vd_text','why','model')} | ハコブくんの声_候補.md（候補1〜6の説明文・「1つの説明文で3つ出る」・試した結果の表・決定「候補6のパターン2」・「モデルは Eleven v4 に決めた（冒頭14文を Multilingual v2 と聞き比べて…）」）。video_rules.md 3-1「説明好きで親切な、お手伝いロボット…誰かの声を録音して元にしてはいない」 |
| 作れる（持てる）声は今のプランで10個まで。同じ話に出ない人どうしで声を借りる。エイイチの声はマンガの槙原くんの声 | {R('cast')} | video_rules.md 9-0b「ElevenLabs の自作の声は、いまのプランでは10個が上限（2026-10-24 に 10/10）。新しい人は常連の声を借りる（同じ話に本人が出ない組み合わせにする）」・声の一覧.json「槙原: Sryf5xKmMsQChU3HsLhZ」・謎解きの回のエイイチの声キー Sryf5xKmMsQChU3HsLhZ |
| 読みの直しの表は37本の台本で207組（11月1日に数えた） | {R('read_table')} | 動画フォルダの narration.py 37本の ELEVEN_READ を重複なしで数えた |
| 名前は漢字の「栄一」で渡す（いちばん平ら）・描く→えがく・係→カタカナ・節を「せつ」「ふし」に読み分けた→言い換え・「球」を「きゅま」・試しで読めても全文で読み違える | {R('read_ex','quiz_ans','try')} | video_rules.md 3-1 の該当の行（2026-10-12 栄一／2026-10-18 描く・係／2026-10-19 節／2026-10-17 ハロの回の球／2026-10-15 29対） |
| 見立て | {R('mitate','mitate_fear','mitate_job')} | エイイチの答え（2026-11-01）: 「怖いと思うことはないな」「AIにはできないだろうと思っていた仕事があっという間に代替されるようになっているので、無くなってしまう可能性もある」 |

---

## 台本全文（動画で読み上げる版）
""" + "\n".join(body) + "\n"
open(f"codex_敵対的検証依頼_{NAME}.md", "w").write(md)
print("文", n)
```

### 概要欄の下書き（概要欄_下書き.md）

```markdown
<!--
YouTube の概要欄の下書き（2026-11-01「ハコブくんの声と絵」＝シリーズ「ひとりとAIのチーム」4回目）。{t:文番号} は、その文が始まる時間に置きかわる（文番号は 0 から数える tts の番号）。
python3 <_制作ベース>/description.py で 概要欄.txt を作り、それを貼る。
数は 2026-11-01 に数えた（過去の間違いの表 22行・読みの直し 37本で207組）。マンガの話数は site_archive の一覧（日本語99話・2026-10-12 時点）。
3本目は 2026-11-04 公開予約。この回の公開日が先にならないか確かめてから貼る。
-->
このマンガのコマ、腕を数えると3本あります。描いたのは画像を作るAI、見つけたのは人の目でした。案内役ハコブくんの絵と声を、どう作っているのか。AIが候補を出し、人が見比べ・聞き比べて選ぶ作り方を、実際の記録で見ていきます。

シリーズ「ひとりとAIのチーム」の4本目です。
1本目：ひとりとAIで、研究と発信を続けるには https://www.youtube.com/watch?v=uoqq8KNpyPY
2本目：AIの間違いは、誰が見つけるのか https://www.youtube.com/watch?v=E7uC2HYB8ks
3本目：AIに、昨日の続きをさせるには https://www.youtube.com/watch?v=MomQ_CB8xDc

▼目次
0:00 腕が3本のコマ
{t:6} この番組について
{t:9} 今日の問い：ハコブくんの声と絵は、どう作っているのか
{t:12} 先に結論
{t:21} 1. 絵を、どう描いているのか
{t:31} 今日の失敗：腕が3本
{t:42} 2. 声を、どう選んだのか
{t:50} 前のハコブくんの声
{t:58} 3. 読み間違いを、どう直しているのか
{t:74} エイイチの見立て
{t:80} 冒頭の問いへの答え
{t:85} エンディング

▼この動画で扱う範囲
・1人の、1つのやり方の話です。このチャンネルの作り方を見ていて、AIの製品を比べたものではありません
・過去の間違いのリストの22行（2026年10月30〜31日の分）、読みの直しの207組（これまでの37本の台本・重複なし）は、2026年11月1日に数えた数です
・マンガの話数（日本語版99話）は、2026年10月半ばまでに出した数です
・画面の文書は、記録のファイルを抜粋して映したものです

▼使っているAI（この動画で紹介したもの）
・Claude（Anthropic）：マンガの絵の指示の文、台本を書く係
・Gemini（Google）：マンガの絵を描く係
・ElevenLabs：ナレーションの声（ボイスデザイン）・BGM

▼この番組について
「フィジカルAI解説｜Pixels to Motors」は、フィジカルAIのニュースや実機の記録から、現場で使える形を考える番組です。
エイイチは、本業のデザイナーをしながら、ロボティクスの研究と開発をしています。その中で分かったことと、そこから考えた見立てを、案内役のハコブくんがお伝えします。

・ナレーションは AI 音声（ElevenLabs）です。話しているのは Pixels to Motors のキャラクター、ハコブくんです。エイイチの言葉は、マンガの登場人物（槙原くん）の AI 音声を借りて読んでいます。エイイチの絵は、生成AIの絵を元に手で直したドット絵です
・途中で流す「前のハコブくんの声」は、サイトのトップで以前に流していた動画の声です
・マンガの絵は生成AI（Gemini）で作成しています。ハコブくんの体の絵も、画像を作るAIで作った絵です
・BGM は ElevenLabs Music で作った曲、効果音はコードで作った音です
・実機の映像はエイイチが撮ったものです

▼ロボットの開発・導入のご相談
メール：pixelstomotors@adaptdesign.jp

▼リンク
Webサイト：https://pixelstomotors.com
4コママンガ『ハコブくん』：https://pixelstomotors.com/manga-archive
GitHub：https://github.com/PixelstoMotors
X：https://x.com/adaptdesignjp
LinkedIn：https://www.linkedin.com/in/eiichi-shimazu

チャンネル登録・高評価もよろしくお願いします。コメントは、エイイチが全部読んでいます。

#フィジカルAI #AI活用 #生成AI
```
