# Claude との仕事の決まりを伝える ― 縦長ショート15本と YouTube 自動投稿の申請

- 記録: LOG457（ひとり研究所の裏側）
- 公開: 2026/11/7
- 記事のページ: https://pixelstomotors.com/record/claude-rules-shorts-and-youtube-api-audit
- サイト: Pixels to Motors（ADAPT DESIGN / 島津 栄一） https://pixelstomotors.com/
- 要約: Claude と Claude Code との仕事で決めた決まりを、縦長ショート15本（16〜22秒）で伝え、X に11月27日〜12月11日で予約した。YouTube の自動投稿に要る Google の審査も、アップロードの道具を作って申請した。

> AI が読むための記事の全文です（コード・実行ログを含む）。引用するときは上の「記事のページ」の住所を使ってください。

---

**Claude と Claude Code と一緒に仕事をする中で決めた決まり（「公開した記事は直さない」「費用は円で書く」「実体験は作らない」など）を、Unity の作業部屋の舞台で2人が話す16〜22秒の縦長ショート15本にまとめ、X に11月27日から12月11日まで毎晩1本ずつ予約しました。** あわせて、YouTube への投稿を自分のプログラムで済ませるのに要る Google の審査（YouTube API Services の Audit）を、アップロードの道具を作って申請しました。審査を通らないうちは、プログラムで上げた動画はすべて非公開に固定されます。

この作業で、次のことが変わりました。

- 声づくりを、エイイチの手から外した。ElevenLabs（文章を読み上げる声を作る AI のサービス）の鍵は Mac の外付けの KIOXIA に置いてあり、Claude が Mac 連携のシェルから声のスクリプト（tts_eleven.py）を動かせる。この会話の最初は、Claude が声のコマンドをエイイチに渡していたが、エイイチの「全部自分でできるでしょ？」で Claude が作る形にした。台本から書き出しまでで、エイイチの番が来たのは確かめと直しの指示だけだった
- 15本のうち5本は、台本の段階から、エイイチの言葉を Claude への決まりとして見せる形にした。エイイチがこれまでに言ったことの中から、Claude にイラッとして決まりにしたものを選んでいる
- YouTube の自動投稿の申請に要るものを、1日でそろえた。アップロードの道具（youtube_upload.py）、5秒の試しの動画と録画、サイトのプライバシーポリシーに足した「YouTube API Services の利用について」の節、利用規約の PDF、図2つ。審査は数週間から数か月かかると言われていて、それまでの YouTube の予約は今までどおり手で入れる
- 時間：15本の台本・声・画面・書き出しは、8:13 から 9:55 までの会話の中で済んだ（チャットの時刻から。1本ずつは測っていない）。YouTube の申請は 9:48 から 11:24 の約1時間半で、そのうちログインが4回止まった所に約20分かかった

もとにしたのは、[LOG455「Claude の使い方を実例で伝える ― Unity の舞台で縦長ショート10本」](https://pixelstomotors.com/record/unity-stage-for-shorts)で作った「舞台型」の型です。Claude Code が Unity で建てた夜の作業部屋に、ハコブくん（Claude 役のロボット）とエイイチが立ち、奥のモニターに1つずつ言いたいことを出します。2本目からは、セリフ（narration.py）と画面（spec.py）を書き替えるだけで1本ができます。

一方で YouTube への投稿は、ショートが増えるほど手間になっています。YouTube Studio で1本ずつ上げて、題名と説明文を貼り、公開の日時を入れる。今回の15本と前の10本の25本は X には予約してありますが、YouTube にはまだ上げていません。この手間を減らすには、YouTube Data API（YouTube の機能を自分のプログラムから使う公式の仕組み）で上げる必要があり、その前に Google の審査があります。

| やったこと | なぜ | 結果 |
|---|---|---|
| 11〜15本目の台本を書き、声を作った | Claude・Claude Code ネタの続き。エイイチ「全部自分でできるでしょ？」で声も Claude が作った | 英語の論文を読む・慎重すぎるClaude・Claudeを信じてる・サイトの数字を聞く・手順をスキルにする |
| 締めの声と「エイイチさん」の読みを直した | エイイチ「最後の言葉『公開中です』に変更しました」「エイイチさんのイントネーションが変！」 | 締めは4通りから選んだ声に、名前は読み直し（エイイチ→栄一）を台本に入れた |
| 16〜25本目の10本を作った | エイイチ「さらに10話追加」 | 記事は直さない・ドルじゃなくて円など、Claude に決めた決まりの10本 |
| 「記事は直さない」をやさしく作り直した | エイイチ「ちょっと強すぎな内容。もう少し優しげに」 | セリフ2つと画面3つを替え、集中線と揺れをやめた |
| 15本を X に予約した | YouTube より先に X で出す（前の10本と同じ） | 11/27〜12/11 の毎晩 22:00 |
| アップロードの道具を作り、試しの1本を上げた | YouTube の審査の申請に、動く道具と録画が要る | 5秒の黒い動画が Pixels to Motors に非公開で入った |
| プライバシーポリシーに YouTube API の節を足した | 審査の書類で、YouTube の節のあるポリシーが要った | 日本語と英語で公開 |
| 審査を申請した | 通らないと、プログラムで上げた動画が公開できない | 2026-11-07 11:24 に送信 |

![舞台型のショート11〜25本目の15本の6.5秒目のコマ。同じ作業部屋の舞台で、奥のモニターの中身とセリフだけが違う](https://pixelstomotors.com/media/LOG457_01_fifteen_shorts.png)


## 作業ログ ― 15本の台本は、エイイチが Claude に決めた決まりから選んだ

Claude・Claude Code ネタの続きとして、最初に5本の題材を出したところ、エイイチからは Claude・Claude Code のネタを続けてほしいと返ってきました。最初の案には、GPU を買うか借りるか・声で覚える単語帳のような、Claude と関係の薄い題材が混ざっていたためです。そこで、Claude と Claude Code との仕事そのものを題材にした5本に作り直しました。そのあとの「さらに10話追加」の10本は、エイイチがこれまでに Claude に向けて言ったこと（メモリと記事の決まり article_rules.md・MASTER に残っている言葉）から、Claude にイラッとして決まりになったものを選んでいます。

エイイチのセリフは、どれもエイイチが実際に言った言葉から取り、どこから取ったかを narration.py の1行目に書いています。ハコブくんのセリフは、決まりや記録に書いてあることだけにしました（誇張しない）。

| 回 | 版 | 長さ | エイイチのセリフのもと |
|---|---|---|---|
| 11 英語の論文を読む | 版3 | 19.9秒 | 英語は「ほんと全然」できない／英語が混ざっていてわからない |
| 12 慎重すぎるClaude | 版5 | 22.3秒 | 安全性を重視しすぎて、制限が多すぎ（前から何度も言っている） |
| 13 Claudeを信じてる | 版3 | 19.1秒 | AIは信じていないが、自分よりはマシ／自分のミスに自分で気づくのは無理 |
| 14 サイトの数字を聞く | 版3 | 22.0秒 | 計測を Claude につなぐ部品は「どうせいずれ必要そう」（2026-11-06） |
| 15 手順をスキルにする | 版3 | 20.5秒 | 型を持って、ネタをはめ込んで作れるようにしたい（2026-10-15） |
| 16 記事は直さない | 版2 | 18.9秒 | LOGは直さないって言ってんだろ！何度言えばわかる！（2026-10-21）を、やさしい言い方に |
| 17 ドルじゃなくて円 | 版1 | 19.3秒 | 費用は円で書く。ドルで計算しない（2026-10-23） |
| 18 無理やりつなげない | 版1 | 18.4秒 | 題材に自分のアームの試行を無理に結びつけない |
| 19 台本が固い | 版1 | 18.7秒 | Claude の台本は固い内容にしすぎ／冒頭はもっと人を惹きつけたい（2026-10-28） |
| 20 実体験は作らない | 版1 | 16.1秒 | ユーモアや実体験は Claude が作ると嘘っぽくなる（2026-10-19） |
| 21 プロンプトは丸ごと | 版1 | 17.4秒 | 画像のプロンプトは、一から使える完全な形で出し直す |
| 22 言葉に解説 | 版2 | 18.8秒 | 初めて出てくる重要な言葉には解説を（全部でなくてよい・2026-10-29） |
| 23 題名に何の話か | 版1 | 16.2秒 | タイトルに固有名詞を。いつも省略しがち（2026-10-29）／意図を入れる（2026-11-07） |
| 24 SNSが固い | 版1 | 19.4秒 | SNS の文が固いので一言ユーモアを（全部でなくてよい・2026-10-25）／重すぎる想いはウザい |
| 25 研究に戻す | 版1 | 17.7秒 | 研究を止めない。発信は研究の副産物（MASTER の上位ルール） |

15本とも、最後の文は「研究の記録は、毎日サイトで公開中です！」で、画面は pixelstomotors.com で締めています（動画の決まりの「最後は必ずサイトへの入口」）。山場の演出は紙吹雪を使わず、集中線（speed）・スポットライト（spot）・波紋（shock）・光の筋（rays）・星（stars）・光の帯（shine）・立ちのぼる四角（rise）を、1本ずつ組み合わせを変えました。

文ごとの声の長さ（前後の無音を切ったあと・秒）は次のとおりです。

```text
英語の論文を読む	2.99 / 2.50 / 3.47 / 2.32 / 2.80 / 3.27
慎重すぎるClaude	3.13 / 2.59 / 3.15 / 2.75 / 4.89 / 3.27
Claudeを信じてる	2.78 / 0.97 / 0.39 / 2.30 / 3.00 / 3.37 / 3.27
サイトの数字を聞く	2.55 / 2.43 / 1.95 / 4.78 / 2.19 / 2.28 / 3.27
手順をスキルにする	2.50 / 3.31 / 3.87 / 2.90 / 2.10 / 3.27
記事は直さない	2.56 / 1.95 / 3.17 / 1.92 / 3.38 / 3.27
ドルじゃなくて円	3.28 / 0.95 / 2.49 / 1.30 / 3.09 / 2.15 / 3.27
無理やりつなげない	3.93 / 1.36 / 2.99 / 4.49 / 3.27
台本が固い	2.99 / 2.74 / 2.67 / 4.63 / 3.27
実体験は作らない	2.91 / 2.44 / 1.40 / 3.59 / 3.27
プロンプトは丸ごと	3.20 / 2.90 / 2.82 / 2.83 / 3.27
言葉に解説	2.41 / 2.88 / 3.78 / 1.71 / 2.19 / 3.27
題名に何の話か	2.68 / 1.93 / 1.72 / 4.19 / 3.27
SNSが固い	2.61 / 2.55 / 2.22 / 2.98 / 3.21 / 3.27
研究に戻す	3.67 / 2.36 / 2.99 / 2.98 / 3.27
```

### 11 英語の論文を読む（19.9秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 英語の論文を読む（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 英語は「ほんと全然」できない／英語が混ざっていてわからない（プロジェクトの preferences）。事実: 調査メモと原文の PDF は同じ名前で research/ と research/pdf/ に置く（MASTER の保存先テーブル）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask_ei', 'trouble', '英語の論文、ほんと全然読めない。', 0.15),
 ('read', 'joy', '僕が読んで、日本語のメモにします！', 0.15),
 ('pair', 'smile', '原文のPDFも、メモと同じ名前で並べます。', 0.15),
 ('mix_ei', 'doubt', '英語が混ざってると、わからん。', 0.2),
 ('ja', 'joy', 'はい。メモは、日本語だけで書きます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("GitHub", "ギットハブ"), ("Unity", "ユニティ"), ("SNS", "エスエヌエス"), ("PDF", "ピーディーエフ"), ("1つ", "ひとつ"), ("3つ", "みっつ"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 11 英語の論文を読む の spec.py

```python
# -*- coding: utf-8 -*-
"""英語の論文を読む（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 英語は「ほんと全然」できない／英語が混ざっていてわからない
事実: 調査メモ（research/）と原文の PDF（research/pdf/）は同じ名前で置く（MASTER の保存先テーブル）。例は AIRoA-0 の調査（2026-11-06）"""
from butai_parts import *
OUTRO_K = 5

NAME, VER = "英語の論文を読む", 3
HEAD = "英語の論文<br>読めない？"
T_ON = (0, 0.9)


def paper(left, top, w, h, n, t0, step=0.04, col="#3A4558", lw=None):
    """英語の論文の行（細かい灰色の帯がびっしり並ぶ）"""
    import random
    random.seed(left + top)
    out = ""
    for k in range(n):
        ww = lw or random.randint(int(w * .55), w)
        out += (f'<div style="position:absolute;left:{left}px;top:{top + k * (h // n)}px;width:{ww}px;height:{max(6, h // n - 10)}px;border-radius:4px;background:{col};'
                f'opacity:0;animation:{a("show", t0 + k * step, .1)}"></div>')
    return out


def scene(T, S):
    h = CSS
    # 0: 英語の論文（細かい行がびっしり）
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("論文（英語）")
             + f'<div class="card" style="left:150px;top:100px;width:600px;height:460px;justify-content:flex-start;padding-top:28px">'
               f'<span style="font-size:34px;letter-spacing:.02em">Abstract</span></div>'
             + paper(180, 180, 540, 360, 16, t0 + 0.2)
             + f'<div class="hero" style="right:40px;top:300px;font-size:260px;opacity:0;animation:{a("pop", T(0, S(0) - 0.6), .35)}">?</div>')
    # 1: 僕が読んで、日本語のメモにする（左の英語 → 右の日本語）
    t1, s1 = T(1, 0), S(1)
    h += win(t1, T(2, 0), kick("Claude が読む")
             + f'<div class="card" style="left:48px;top:150px;width:330px;height:380px;justify-content:flex-start;padding-top:20px"><small>原文</small></div>'
             + paper(74, 210, 280, 300, 12, t1, 0.0)
             + f'<div class="arrowR" style="left:404px;top:324px;opacity:0;animation:{a("show", t1 + 0.6, .2)}"></div>'
             + f'<div class="card or" style="left:452px;top:150px;width:400px;height:380px;justify-content:flex-start;padding-top:20px;opacity:0;animation:{a("pop", t1 + 1.0, .35)}">'
               f'<small>日本語のメモ</small>'
               f'<span style="font-size:38px;margin-top:22px;line-height:1.5">何がすごいのか<br>仕組みの要点<br>うちとの関係</span></div>')
    # 2: 原文の PDF も、メモと同じ名前で並べる
    t2, s2 = T(2, 0), S(2)
    def fcard(top, label, ext, d):
        return (f'<div class="card" style="left:48px;top:{top}px;width:804px;height:150px;opacity:0;animation:{a("inR", t2 + d)}">'
                f'<small>{label}</small><span style="font-size:42px">paper_<b style="color:{ORANGE}">airoa0_20261106</b>{ext}</span></div>')
    h += win(t2, T(3, 0), kick("置き方") + '<div class="msg" style="font-size:50px">メモと原文は、同じ名前</div>'
             + fcard(220, "メモ（research/）", ".md", 0.3) + fcard(392, "原文（research/pdf/）", ".pdf", s2 * 0.45))
    # 3: 英語が混ざってると、わからん（混ざった英語に線を引く）
    t3, s3 = T(3, 0), S(3)
    h += win(t3, T(4, 0), kick("エイイチの声")
             + f'<div class="card" style="left:48px;top:170px;width:804px;height:260px;font-size:46px;line-height:1.5">'
               f'<span>同じ重みが <span style="color:{ORANGE}">cross-embodiment</span> で<br>別のロボットでも動く</span></div>'
             + f'<div class="strike" style="left:300px;width:420px;top:262px;animation:{a("strike", t3 + 0.9, .3)}"></div>'
             + f'<div class="stamp" style="left:48px;top:460px;font-size:52px;animation:{a("slam", t3 + s3 - 0.9, .35)}">わからん</div>')
    # 4: メモは日本語だけで書く
    t4 = T(4, 0)
    h += win(t4, None, kick("だから")
             + f'<div class="card" style="left:48px;top:110px;width:804px;height:230px;font-size:46px;line-height:1.5;opacity:0;animation:{a("up", t4 + 0.3)}">'
               f'<span>同じ重みが、<span style="color:{ORANGE}">別のロボットでも</span><br>そのまま動く</span></div>'
             + f'<div class="msg" style="top:370px;font-size:72px;opacity:0;animation:{a("pop", t4 + 1.2, .4)}">日本語だけ</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(3, 1.0), 0.5)], fx=[dict(kind="speed", t=T(3, 0.0), d=1.2), dict(kind="shine", t=T(4, 1.2), d=0.9)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 2.5, "joy"), (3, 0.0, 2.3, "surprise"), (4, 0.0, 2.8, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.4, 3), (1, 0.0, 1), (3, 0.0, 2), (4, 0.6, 4)]
LEAN = []
INTRO = "drop"
MOVES = [(1, 0.3, "hop", 1.0), (3, 0.4, "back", 0.8), (4, 0.3, "spin", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.9, "se_boot", -12), (0, 2.4, "se_pop", -12), (1, 1.0, "se_ding", -11),
      (2, 0.3, "se_whoosh", -13), (2, 1.6, "se_whoosh", -13), (3, 0.9, "se_pechin", -10), (3, 1.4, "se_buzz", -14),
      (4, 1.2, "se_ding", -9), (5, 0.0, "se_whoosh", -12)]
ENDING = dict(kind="jump", at=(4, 1.2))
```

### 11 英語の論文を読む の書き出しのログ

```text
out/英語の論文を読む_版3.mp4  19.9秒
```

### 12 慎重すぎるClaude（22.3秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 慎重すぎるClaude（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 前から何度も言っているけど、安全性を重視しすぎて制限が多すぎ（preferences）。事実: AI は物理保護を理由に止めるだけで終わらず、危険の種類・原因・代わりの手順を出し、最終判断はエイイチ（MASTER の物理保護ガードの扱い）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask_ei', 'doubt', '実機の話になると、Claudeは止めてばかり。', 0.15),
 ('stop', 'trouble', '危ないかもって、つい止めたくなります…', 0.15),
 ('too_ei', 'trouble', '安全性を重視しすぎて、制限が多すぎ。', 0.2),
 ('nashi', 'surprise', 'なので、止めるだけは、ナシにしました！', 0.15),
 ('three', 'joy', '危ない所、原因、試し方。決めるのは、エイイチさんです！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("GitHub", "ギットハブ"), ("Unity", "ユニティ"), ("SNS", "エスエヌエス"), ("PDF", "ピーディーエフ"), ("1つ", "ひとつ"), ("3つ", "みっつ"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 12 慎重すぎるClaude の spec.py

```python
# -*- coding: utf-8 -*-
"""慎重すぎるClaude（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 前から何度も言っているけど、安全性を重視しすぎて制限が多すぎ
事実: AI は物理保護を理由に作業を止めるだけで終わらず、危険の種類・実ログ上の原因・代わりの手順・dry-run で見る所を出し、決めるのはエイイチ（MASTER の物理保護ガードの扱い）"""
from butai_parts import *
OUTRO_K = 5

NAME, VER = "慎重すぎるClaude", 5
HEAD = "Claude<br>慎重すぎ？"
T_ON = (0, 0.8)


def stops(t0, n, step, seed):
    """「停止」の札が次々に積み重なる"""
    import random
    random.seed(seed)
    out = ""
    for k in range(n):
        x, y, r = random.randint(40, 600), random.randint(110, 470), random.randint(-12, 12)
        out += (f'<div class="card" style="left:{x}px;top:{y}px;width:250px;height:92px;font-size:44px;align-items:center;padding:0;'
                f'transform:rotate({r}deg);opacity:0;animation:{a("pop", t0 + k * step, .2)}">停止</div>')
    return out


def scene(T, S):
    h = CSS
    # 0: 実機の話になると、止めてばかり
    t0 = T(*T_ON)
    h += win(t0, T(2, 0), kick("実機の作業") + stops(t0 + 0.6, 6, 0.32, 3) + stops(T(1, 0.1), 9, 0.2, 8))
    # 2: 制限が多すぎ（エイイチの言葉）
    t2, s2 = T(2, 0), S(2)
    h += win(t2, T(3, 0), kick("エイイチの声")
             + f'<div class="msg" style="top:120px;font-size:54px">安全性を重視しすぎて</div>'
             + f'<div class="hero" style="left:0;right:0;text-align:center;top:260px;font-size:150px;opacity:0;animation:{a("slam", t2 + s2 * 0.45, .35)}">制限多すぎ</div>')
    # 3: 止めるだけは、ナシ（札が崩れて消える）
    t3, s3 = T(3, 0), S(3)
    fall = "".join(f'<div class="card" style="left:{60 + k * 160}px;top:{240 + (k % 2) * 40}px;width:140px;height:80px;font-size:34px;align-items:center;padding:0;'
                   f'animation:{a("blurOut", t3 + 0.3 + k * 0.07, .5)}">停止</div>' for k in range(5))
    h += win(t3, T(4, 0), kick("なので") + fall
             + f'<div class="msg" style="top:200px;text-align:center;font-size:76px;opacity:0;animation:{a("pop", t3 + 0.9, .4)}">止めるだけは、ナシ</div>')
    # 4: 危ない所・原因・試し方 → 決めるのはエイイチ
    t4, s4 = T(4, 0), S(4)
    three = "".join(f'<div class="card" style="left:{48 + k * 276}px;top:110px;width:252px;height:150px;font-size:46px;align-items:center;padding:0;'
                    f'opacity:0;animation:{a("up", t4 + 0.2 + k * 0.45)}"><small>{n_ + 1}</small>{nm}</div>'
                    for k, (n_, nm) in enumerate(enumerate(["危ない所", "原因", "試し方"])))
    h += win(t4, None, kick("代わりに出すもの") + three
             + f'<div style="position:absolute;left:48px;top:300px;font-size:40px;font-weight:800;color:{DIM};opacity:0;animation:{a("up", t4 + s4 - 2.0)}">決めるのは</div>'
             + f'<div class="card or" style="left:48px;top:360px;width:420px;height:150px;font-size:70px;align-items:center;padding:0;opacity:0;animation:{a("pop", t4 + s4 - 1.4, .35)}">エイイチ</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(2, 1.4), 0.6)], fx=[dict(kind="speed", t=T(2, 1.3), d=1.2), dict(kind="spot", t=T(4, 3.0), d=2.2, x=690)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 2.6, "trouble"), (2, 0.0, 3.1, "trouble"), (3, 0.0, 2.7, "surprise"), (4, 0.0, 4.6, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.0, 2), (2, 0.0, 3), (3, 0.0, 1), (4, 3.0, 4)]
LEAN = []
INTRO = "spin"
MOVES = [(1, 0.2, "bow", 1.2), (2, 1.3, "shake", 0.8), (3, 0.6, "jump", 0.9), (4, 3.0, "turn", 1.6)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.8, "se_boot", -12)] + [(0, 0.6 + 0.32 * k, "se_tick", -13) for k in range(6)] \
     + [(1, 0.1 + 0.2 * k, "se_pop", -15) for k in range(9)] \
     + [(2, 1.3, "se_pechin", -9), (3, 0.3, "se_whoosh", -11), (3, 0.9, "se_ding", -11),
        (4, 0.2, "se_pop", -13), (4, 0.65, "se_pop", -13), (4, 1.1, "se_pop", -13), (4, 3.2, "se_jaran", -10)]
ENDING = dict(kind="jump", at=(4, 3.2))
```

### 12 慎重すぎるClaude の書き出しのログ

```text
out/慎重すぎるClaude_版5.mp4  22.3秒
```

### 13 Claudeを信じてる（19.1秒）の narration.py

```python
# -*- coding: utf-8 -*-
# Claudeを信じてる（ショート専用・舞台型・2026-11-07）。エイイチの言葉: AIは信じていないが、自分よりはマシかな程度／自分のミスに自分で気づくのは無理だといつも思って仕事をしている（プロジェクトの profile）。事実: サイトの変更は公開前にビルドと実際の画面で確かめる（MASTER の開発ワークフロー）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'smile', 'エイイチさん、僕のこと、信じてます？', 0.2),
 ('no_ei', 'base', '信じてない。', 0.3),
 ('eh', 'surprise', 'えっ…', 0.3),
 ('but_ei', 'base', 'でも、自分よりはマシかな。', 0.15),
 ('miss_ei', 'think', '自分のミスに、自分で気づくのは無理だし。', 0.2),
 ('check', 'joy', 'だから公開の前に、実際の画面で確かめます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("GitHub", "ギットハブ"), ("Unity", "ユニティ"), ("SNS", "エスエヌエス"), ("PDF", "ピーディーエフ"), ("1つ", "ひとつ"), ("3つ", "みっつ"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 13 Claudeを信じてる の spec.py

```python
# -*- coding: utf-8 -*-
"""Claudeを信じてる（ショート専用・舞台型・2026-11-07）
エイイチの言葉: AIは信じていないが、自分よりはマシかな程度／自分のミスに自分で気づくのは無理だといつも思って仕事をしている
事実: サイトの変更は、公開前にビルドと実際の画面で確かめる（MASTER の開発ワークフロー）。記事は幅 375〜1440px で見ている"""
from butai_parts import *
OUTRO_K = 6

NAME, VER = "Claudeを信じてる", 3
HEAD = "AIのこと<br>信じてる？"
T_ON = (0, 0.6)


def scene(T, S):
    h = CSS
    # 0: 僕のこと、信じてます？
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("Claude から質問")
             + f'<div class="msg" style="font-size:54px">僕のこと、信じてます？</div>'
             + f'<div class="qm" style="top:150px;font-size:360px;opacity:0;animation:{a("pop", t0 + 0.8, .35)}">?</div>')
    # 1: 信じてない（叩きつける）
    t1 = T(1, 0)
    h += win(t1, T(2, 0), f'<div class="hero" style="left:0;right:0;text-align:center;top:200px;font-size:170px;opacity:0;animation:{a("slam", t1 + 0.05, .3)}">信じてない</div>')
    # 2: えっ…
    t2 = T(2, 0)
    h += win(t2, T(3, 0), f'<div style="position:absolute;left:0;right:0;top:150px;text-align:center;font-size:250px;font-weight:900;color:{INK};opacity:0;animation:{a("pop", t2, .25)}">えっ…</div>')
    # 3: でも、自分よりはマシ（2本の棒）
    t3, s3 = T(3, 0), S(3)
    def bar(top, label, w, d, cls=""):
        return (f'<div style="position:absolute;left:48px;top:{top}px;font-size:36px;font-weight:900;color:{INK}">{label}</div>'
                f'<div class="card {cls}" style="left:230px;top:{top - 12}px;width:{w}px;height:70px;padding:0;{"" if cls else "background:#4A566B;"}transform-origin:left;transform:scaleX(0);animation:{a("grow", t3 + d, .5)}"></div>')
    h += win(t3, T(4, 0), kick("信じる度合い") + '<div class="msg" style="font-size:50px">でも、自分よりはマシ</div>'
             + bar(300, "自分", 300, 0.3) + bar(420, "Claude", 380, s3 - 1.2, "or"))
    # 4: 自分のミスに、自分で気づくのは無理
    t4, s4 = T(4, 0), S(4)
    h += win(t4, T(5, 0), kick("エイイチの考え")
             + f'<div class="card" style="left:48px;top:150px;width:380px;height:300px;font-size:52px;align-items:center;padding:0;opacity:0;animation:{a("up", t4 + 0.2)}"><small>見る</small>自分の目</div>'
             + f'<div class="arrowR" style="left:446px;top:284px;opacity:0;animation:{a("show", t4 + 0.6, .2)}"></div>'
             + f'<div class="card" style="left:494px;top:150px;width:358px;height:300px;font-size:52px;align-items:center;padding:0;opacity:0;animation:{a("up", t4 + 0.8)}"><small>見つける</small>自分のミス</div>'
             + f'<div class="stamp" style="left:560px;top:410px;font-size:64px;animation:{a("slam", t4 + s4 - 1.0, .35)}">無理</div>')
    # 5: だから公開の前に、実際の画面で確かめる（幅の違う画面を順に）
    t5, s5 = T(5, 0), S(5)
    devs = [(375, 110, 230), (768, 200, 230), (1440, 330, 230)]
    x, scr = 48, ""
    for k, (wpx, w, hh) in enumerate(devs):
        scr += (f'<div class="card" style="left:{x}px;top:{460 - hh}px;width:{w}px;height:{hh}px;padding:0;align-items:center;font-size:30px;'
                f'opacity:0;animation:{a("up", t5 + 0.4 + k * 0.35)}, {a("on", t5 + s5 - 1.6 + k * 0.25, .2)}">{wpx}</div>')
        x += w + 40
    h += win(t5, None, kick("公開の前に") + '<div class="msg" style="font-size:52px">実際の画面で、確かめる</div>' + scr
             + f'<div style="position:absolute;left:48px;top:490px;font-size:28px;font-weight:800;color:{DIM}">画面の幅（px）</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(1, 0.05), 0.5)], fx=[dict(kind="speed", t=T(1, 0.0), d=1.0), dict(kind="shock", t=T(5, 1.8), d=1.6)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 2.8, "wink"), (1, 0.0, 1.2, "surprise"), (2, 0.0, 0.8, "surprise"), (3, 0.0, 2.4, "doubt"),
        (4, 0.0, 3.0, "think"), (5, 0.0, 3.3, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (1, 0.0, 2), (3, 0.0, 4), (4, 0.0, 2), (5, 1.8, 4)]
LEAN = []
INTRO = "rise"
MOVES = [(1, 0.1, "back", 0.8), (2, 0.0, "shake", 0.7), (3, 1.0, "hop", 0.8), (5, 1.8, "jump", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.4, "se_pop", -12), (1, 0.05, "se_pechin", -8),
      (2, 0.0, "se_buzz", -14), (3, 0.3, "se_rise", -14), (3, 1.1, "se_ding", -12), (4, 2.0, "se_pechin", -10),
      (5, 0.4, "se_tick", -12), (5, 0.75, "se_tick", -12), (5, 1.1, "se_tick", -12), (5, 1.8, "se_ding", -9)]
ENDING = dict(kind="jump", at=(5, 1.8))
```

### 13 Claudeを信じてる の書き出しのログ

```text
out/Claudeを信じてる_版3.mp4  19.1秒
```

### 14 サイトの数字を聞く（22.0秒）の narration.py

```python
# -*- coding: utf-8 -*-
# サイトの数字を聞く（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 計測を Claude につなぐ部品も作る「どうせいずれ必要そう」（2026-11-06）。事実: 主に見る数字は3つ（AIの利用者の依頼で読みに来た数・AIのサービスのリンクから来た人・サイト名で検索して来た人）、日ごとに集計して R2 に残す（MASTER の保存先テーブル）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask_ei', 'base', 'サイトの数字、Claudeから見られる？', 0.15),
 ('part', 'joy', '計測をつなぐ部品、作りました！', 0.15),
 ('three', 'smile', '見る数字は、3つだけ。', 0.1),
 ('list', 'smile', 'AIが読みに来た数、AIから来た人、サイト名の検索。', 0.2),
 ('why_ei', 'smile', 'どうせ、いずれ必要そうだし。', 0.15),
 ('daily', 'joy', '毎日集計して、残しています！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("GitHub", "ギットハブ"), ("Unity", "ユニティ"), ("SNS", "エスエヌエス"), ("PDF", "ピーディーエフ"), ("1つ", "ひとつ"), ("3つ", "みっつ"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 14 サイトの数字を聞く の spec.py

```python
# -*- coding: utf-8 -*-
"""サイトの数字を聞く（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 計測を Claude につなぐ部品（MCP）も作る「どうせいずれ必要そう」（2026-11-06）
事実: 主に見る数字は3つ（AIの利用者の依頼で読みに来た数・AIのサービスのリンクから来た人・サイト名で検索して来た人）。日ごとに集計して R2 に残す（MASTER の保存先テーブル）"""
from butai_parts import *
OUTRO_K = 6

NAME, VER = "サイトの数字を聞く", 3
HEAD = "サイトの数字<br>Claudeに聞く"
T_ON = (0, 0.6)


def scene(T, S):
    h = CSS
    # 0: サイトの数字、Claude から見られる？
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("サイトの計測")
             + f'<div class="card" style="left:150px;top:120px;width:600px;height:420px;justify-content:flex-start;padding-top:30px">'
               f'<span style="font-size:40px">pixelstomotors<span style="color:{ORANGE}">.com</span></span></div>'
             + "".join(f'<div class="line" style="left:200px;top:{240 + k * 60}px;width:{[460, 380, 420, 300][k]}px"></div>' for k in range(4))
             + f'<div class="hero" style="right:40px;top:280px;font-size:240px;opacity:0;animation:{a("pop", t0 + 1.4, .35)}">?</div>')
    # 1: 計測をつなぐ部品を作った（Claude ← 部品 → 計測）
    t1, s1 = T(1, 0), S(1)
    names = [("Claude", ""), ("つなぐ部品", "or"), ("計測", "")]
    fl = "".join(f'<div class="card {c}" style="left:{48 + k * 276}px;top:230px;width:252px;height:170px;font-size:42px;align-items:center;padding:0;white-space:nowrap;'
                 f'opacity:0;animation:{a("pop" if c else "up", t1 + (s1 - 0.9 if c else 0.2 + k * 0.2), .35)}">{nm}</div>' for k, (nm, c) in enumerate(names))
    h += win(t1, T(2, 0), kick("作ったもの") + fl)
    # 2: 見る数字は、3つだけ
    t2 = T(2, 0)
    h += win(t2, T(3, 0), kick("見る数字")
             + f'<div class="hero" style="left:0;right:0;text-align:center;top:110px;font-size:400px;opacity:0;animation:{a("slam", t2 + 0.6, .3)}">3<span>つだけ</span></div>')
    # 3: 3つの中身（話している1つだけオレンジ）
    t3, s3 = T(3, 0), S(3)
    rows = ["AIが読みに来た数", "AIから来た人", "サイト名の検索"]
    st = [0.1, s3 * 0.42, s3 * 0.7]
    rr = ""
    for k, nm in enumerate(rows):
        an = a("up", t3 + st[k]) + ", " + a("on", t3 + st[k], .2)
        if k < 2:
            an += f", off{k} .2s linear {t3 + st[k + 1]:.3f}s forwards"
        rr += (f'<div class="card" style="left:48px;top:{110 + k * 150}px;width:804px;height:126px;flex-direction:row;align-items:center;justify-content:flex-start;gap:28px;font-size:48px;'
               f'opacity:0;animation:{an}"><span style="font-size:56px;width:50px">{k + 1}</span>{nm}</div>')
    h += win(t3, T(4, 0), f'<style>@keyframes off0 {{ to {{ background:{FACE_C}; color:{INK}; }} }} @keyframes off1 {{ to {{ background:{FACE_C}; color:{INK}; }} }}</style>' + rr)
    # 4: どうせ、いずれ必要そう
    t4 = T(4, 0)
    h += win(t4, T(5, 0), kick("作った理由")
             + f'<div class="msg" style="top:200px;text-align:center;font-size:84px;opacity:0;animation:{a("pop", t4 + 0.2, .35)}">どうせ<br>いずれ必要</div>')
    # 5: 毎日集計して、残している（日ごとの箱が積み上がる。数は出さない）
    t5, s5 = T(5, 0), S(5)
    days = ["月", "火", "水", "木", "金", "土", "日"]
    cols = ""
    for k, d in enumerate(days):
        x = 48 + k * 116
        cols += f'<div style="position:absolute;left:{x}px;top:392px;width:104px;text-align:center;font-size:28px;font-weight:800;color:{DIM}">{d}</div>'
        for j in range(3):
            tt = t5 + 0.3 + (k * 3 + j) * (s5 - 0.6) / 21
            c = ORANGE if j == 0 else FACE_2
            cols += (f'<div style="position:absolute;left:{x}px;top:{316 - j * 76}px;width:104px;height:64px;border-radius:10px;background:{c};'
                     f'opacity:0;animation:{a("pop", tt, .2)}"></div>')
    h += win(t5, None, kick("毎日の集計") + '<div class="msg" style="font-size:48px">3つの数を、日ごとに残す</div>' + cols)
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(2, 0.6), 0.4)], fx=[dict(kind="shock", t=T(2, 0.6), d=1.4), dict(kind="rise", t=T(5, 0.2), d=2.6)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 2.4, "joy"), (2, 0.0, 1.9, "wink"), (3, 0.0, 4.7, "smile"), (4, 0.0, 2.2, "think"), (5, 0.0, 2.6, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.0, 1), (2, 0.0, 3), (3, 0.0, 1), (4, 0.0, 2), (5, 0.6, 4)]
LEAN = []
INTRO = "dash"
MOVES = [(1, 0.2, "hop", 1.0), (2, 0.5, "jump", 0.9), (4, 0.4, "tilt", 1.0), (5, 0.3, "spin", 1.2)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 2.0, "se_pop", -12), (1, 0.2, "se_pop", -14), (1, 1.5, "se_ding", -11),
      (2, 0.6, "se_pechin", -9), (3, 0.1, "se_tick", -12), (3, 2.0, "se_tick", -12), (3, 3.35, "se_tick", -12),
      (4, 0.2, "se_pop", -12), (5, 0.3, "se_rise", -12), (5, 1.8, "se_ding", -10)]
ENDING = dict(kind="none")   # 下の段（曜日）にエイイチの頭がかかるので、締めで跳ばせない
```

### 14 サイトの数字を聞く の書き出しのログ

```text
out/サイトの数字を聞く_版3.mp4  22.0秒
```

### 15 手順をスキルにする（20.5秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 手順をスキルにする（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 自分で試してみた回の型を持ち、ネタをはめ込んで作れるようにしておきたい（2026-10-15）。事実: 解説動画づくり・ロボット動画の運用分析・事実確認は Claude のスキルとして登録して使っている
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask_ei', 'doubt', '動画、毎回ゼロから作ってる？', 0.15),
 ('skill', 'joy', 'いいえ。手順は、スキルにして残してあります！', 0.15),
 ('list', 'smile', '解説動画、ロボット動画の分析、事実確認。', 0.2),
 ('kata_ei', 'base', '型を持って、はめ込んで作れるようにしたい。', 0.2),
 ('this', 'joy', 'この舞台も、型のひとつです！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("GitHub", "ギットハブ"), ("Unity", "ユニティ"), ("SNS", "エスエヌエス"), ("PDF", "ピーディーエフ"), ("1つ", "ひとつ"), ("3つ", "みっつ"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 15 手順をスキルにする の spec.py

```python
# -*- coding: utf-8 -*-
"""手順をスキルにする（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 自分で試してみた回の型を持ち、ネタをはめ込んで作れるようにしておきたい（2026-10-15）
事実: 解説動画づくり（p2m-explainer-video）・ロボット動画の運用分析（robot-video-ops-analysis）・事実確認（fact-checker）は Claude のスキルとして登録して使っている。舞台型は video_rules.md の12節"""
from butai_parts import *
OUTRO_K = 5

NAME, VER = "手順をスキルにする", 3
HEAD = "動画づくり<br>毎回ゼロから？"
T_ON = (0, 0.6)


def scene(T, S):
    h = CSS
    # 0: 毎回ゼロから？
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("動画づくり")
             + f'<div class="hero" style="left:0;right:0;text-align:center;top:100px;font-size:400px;opacity:0;animation:{a("slam", t0 + 0.8, .3)}">0<span>から？</span></div>')
    # 1: 手順はスキルにして残してある（札が1枚ずつ重なって束になる）
    t1, s1 = T(1, 0), S(1)
    stack = "".join(f'<div class="card" style="left:{230 + k * 18}px;top:{170 + k * 18}px;width:440px;height:300px;opacity:0;animation:{a("up", t1 + 0.3 + k * 0.25)}"></div>' for k in range(4))
    h += win(t1, T(2, 0), kick("残してあるもの") + stack
             + f'<div class="card or" style="left:284px;top:242px;width:440px;height:300px;align-items:center;padding:0;font-size:80px;opacity:0;animation:{a("pop", t1 + s1 - 1.3, .35)}"><small>Claude の</small>スキル</div>')
    # 2: 解説動画・ロボット動画の分析・事実確認（話している1つだけオレンジ）
    t2, s2 = T(2, 0), S(2)
    names = ["解説動画", "ロボット動画<br>の分析", "事実確認"]
    st = [0.1, s2 * 0.3, s2 * 0.72]
    cc = ""
    for k, nm in enumerate(names):
        an = a("up", t2 + st[k]) + ", " + a("on", t2 + st[k], .2)
        if k < 2:
            an += f", off{k} .2s linear {t2 + st[k + 1]:.3f}s forwards"
        cc += (f'<div class="card" style="left:{48 + k * 276}px;top:180px;width:252px;height:260px;align-items:center;text-align:center;padding:0;font-size:40px;line-height:1.25;'
               f'opacity:0;animation:{an}">{nm}</div>')
    h += win(t2, T(3, 0), f'<style>@keyframes off0 {{ to {{ background:{FACE_C}; color:{INK}; }} }} @keyframes off1 {{ to {{ background:{FACE_C}; color:{INK}; }} }}</style>'
             + kick("いま使っているスキル") + cc)
    # 3: 型を持って、はめ込んで作る（型の空いた所に、ネタがはまる）
    t3, s3 = T(3, 0), S(3)
    h += win(t3, T(4, 0), kick("エイイチの考え")
             + f'<div class="card" style="left:48px;top:150px;width:804px;height:330px;justify-content:flex-start;padding-top:24px"><small>型</small></div>'
             + "".join(f'<div style="position:absolute;left:{90 + k * 250}px;top:250px;width:220px;height:180px;border-radius:12px;background:#121925"></div>' for k in range(3))
             + "".join(f'<div class="card or" style="left:{90 + k * 250}px;top:250px;width:220px;height:180px;align-items:center;padding:0;font-size:46px;'
                       f'opacity:0;animation:{a("pop", t3 + 0.6 + k * (s3 - 1.0) / 3, .3)}">ネタ</div>' for k in range(3)))
    # 4: この舞台も、型のひとつ（モニターの中に、この舞台の小さな絵）
    t4 = T(4, 0)
    mini = (f'<div class="card" style="left:270px;top:150px;width:360px;height:400px;padding:0;overflow:hidden;opacity:0;animation:{a("pop", t4 + 0.2, .35)}">'
            f'<div style="position:absolute;left:30px;top:24px;width:300px;height:130px;border-radius:8px;background:{FACE_2}"></div>'
            f'<div style="position:absolute;left:60px;top:300px;width:240px;height:60px;border-radius:50%;border:6px dashed {ORANGE};box-sizing:border-box"></div>'
            f'<div style="position:absolute;left:105px;top:225px;width:46px;height:100px;border-radius:20px;background:#57B5F8"></div>'
            f'<div style="position:absolute;left:200px;top:195px;width:52px;height:130px;border-radius:20px;background:{INK}"></div></div>')
    h += win(t4, None, kick("この舞台も") + mini
             + f'<div class="stamp" style="left:48px;top:440px;font-size:56px;animation:{a("slam", t4 + 1.0, .35)}">型のひとつ</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(0, 1.4), 0.4)], fx=[dict(kind="stars", t=T(3, 0.0), d=2.9), dict(kind="rays", t=T(4, 1.0), d=2.2)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 3.3, "joy"), (2, 0.0, 3.9, "smile"), (3, 0.0, 2.9, "think"), (4, 0.0, 2.1, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.0, 3), (1, 0.0, 1), (3, 0.0, 2), (4, 0.8, 4)]
LEAN = []
INTRO = "hop"
MOVES = [(1, 0.2, "dash", 1.0), (2, 0.2, "tilt", 1.0), (4, 0.2, "jump", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.4, "se_pechin", -9),
      (1, 0.3, "se_pop", -14), (1, 0.55, "se_pop", -14), (1, 0.8, "se_pop", -14), (1, 1.05, "se_pop", -14), (1, 2.0, "se_ding", -11),
      (2, 0.1, "se_tick", -12), (2, 1.15, "se_tick", -12), (2, 2.8, "se_tick", -12),
      (3, 0.6, "se_pop", -12), (3, 1.25, "se_pop", -12), (3, 1.9, "se_pop", -12), (4, 1.0, "se_jaran", -10)]
ENDING = dict(kind="jump", at=(4, 1.0))
```

### 15 手順をスキルにする の書き出しのログ

```text
out/手順をスキルにする_版3.mp4  20.5秒
```

### 16 記事は直さない（18.9秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 記事は直さない（ショート専用・舞台型・2026-11-07）。エイイチの言葉: LOGは直さないって言ってんだろ！何度言えばわかる！（2026-10-21）。2026-11-07 エイイチ「ちょっと強すぎ。もう少し優しげに」で、言い方をやわらかくした。事実: 公開済みの記事は直さず、食い違いに気づいても直すか聞かない・話題に出さない（preferences）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'smile', '公開した記事、ちょっと直しましょうか？', 0.15),
 ('no_ei', 'smile', 'LOGは、直さないでおこうね。', 0.25),
 ('sorry', 'surprise', 'はい！公開した記録は、そのまま残します…', 0.15),
 ('again_ei', 'smile', 'これ、何回目かな？', 0.25),
 ('book', 'joy', '直さない、聞かない。ルールの本に書きました！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 16 記事は直さない の spec.py

```python
# -*- coding: utf-8 -*-
"""記事は直さない（ショート専用・舞台型・2026-11-07）
エイイチの言葉: LOGは直さないって言ってんだろ！何度言えばわかる！（2026-11-07「ちょっと強すぎ。もう少し優しげに」で、セリフと画面をやわらかくした）"""
from butai_parts import *
OUTRO_K = 5

NAME, VER = "記事は直さない", 2
HEAD = "公開した記事<br>直す？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("公開した記事")
             + '<div class="card" style="left:48px;top:110px;width:804px;height:330px;justify-content:flex-start;padding-top:30px"><small>LOG</small><span style="font-size:44px">公開した記録</span></div>'
             + "".join(f'<div class="line" style="left:90px;top:{260 + k * 44}px;width:{[700, 620, 680][k]}px"></div>' for k in range(3))
             + f'<div class="stamp" style="left:48px;top:470px;font-size:46px;animation:{a("pop", t0 + 1.2, .3)}">ちょっと直す？</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("エイイチの声")
             + f'<div class="msg" style="top:200px;text-align:center;font-size:84px;opacity:0;animation:{a("pop", t1 + 0.2, .4)}">直さないで<br>おこうね</div>')
    t2 = T(2, 0)
    h += win(t2, T(3, 0), kick("公開した記録")
             + '<div class="card" style="left:48px;top:110px;width:804px;height:300px;align-items:center;padding:0;font-size:60px">そのまま残す</div>'
             + f'<div class="stamp" style="left:48px;top:440px;font-size:50px;animation:{a("slam", t2 + 1.2, .3)}">公開したら、そのまま</div>')
    t3 = T(3, 0)
    st = "".join(f'<div class="card" style="left:{48 + k * 276}px;top:150px;width:252px;height:200px;align-items:center;padding:0;font-size:40px;opacity:0;animation:{a("up", t3 + 0.1 + k * 0.25)}"><small>{k + 1}回目</small>直さない</div>' for k in range(3))
    h += win(t3, T(4, 0), kick("同じお願い") + st
             + f'<div class="msg" style="top:400px;font-size:60px;opacity:0;animation:{a("pop", t3 + 1.0, .35)}">何回目かな？</div>')
    t4, s4 = T(4, 0), S(4)
    rows = "".join(f'<div class="chk" style="top:{150 + k * 100}px;animation:{a("up", t4 + 0.2 + k * 0.6)}"><i>✓</i>{nm}</div>' for k, nm in enumerate(["直さない", "直すか、聞かない"]))
    h += win(t4, None, kick("ルールの本に書いた") + rows
             + f'<div class="card or" style="left:48px;top:380px;width:420px;height:130px;align-items:center;padding:0;font-size:46px;opacity:0;animation:{a("pop", t4 + s4 - 1.4, .35)}">ルールの本</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, fx=[dict(kind="stars", t=T(3, 0.0), d=2.0), dict(kind="shine", t=T(4, 2.0), d=0.9)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 2.5, "wink"), (1, 0.0, 2.0, "smile"), (2, 0.0, 3.2, "trouble"), (3, 0.0, 1.5, "trouble"), (4, 0.0, 3.4, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (1, 0.0, 4), (2, 0.0, 1), (3, 0.0, 4), (4, 0.0, 1), (4, 2.0, 4)]
LEAN = []
INTRO = "rise"
MOVES = [(2, 0.2, "bow", 1.2), (3, 0.2, "tilt", 1.0), (4, 2.0, "hop", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.8, "se_pop", -12), (1, 0.2, "se_pop", -12), (2, 1.2, "se_ding", -12),
      (3, 0.1, "se_tick", -13), (3, 0.35, "se_tick", -13), (3, 0.6, "se_tick", -13), (3, 1.0, "se_pop", -12), (4, 0.2, "se_tick", -12), (4, 0.8, "se_tick", -12), (4, 2.0, "se_ding", -10)]
ENDING = dict(kind="jump", at=(4, 2.0))
```

### 16 記事は直さない の書き出しのログ

```text
out/記事は直さない_版2.mp4  18.9秒
```

### 17 ドルじゃなくて円（19.3秒）の narration.py

```python
# -*- coding: utf-8 -*-
# ドルじゃなくて円（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 費用は円を必ず入れる。ドルで計算しない（ドルは入れるなら補足）（2026-10-23）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'smile', 'GPUの費用は、1時間あたり、ドルで…', 0.15),
 ('en_ei', 'base', '円で書いて。', 0.25),
 ('eh', 'surprise', 'えっ、つい、ドルで計算しちゃいます…', 0.15),
 ('no_ei', 'doubt', 'ドルで計算しない。', 0.25),
 ('yen', 'joy', 'はい。記事の費用は、円にそろえます！', 0.1),
 ('hosoku', 'smile', 'ドルは、入れても補足だけ。', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 17 ドルじゃなくて円 の spec.py

```python
# -*- coding: utf-8 -*-
"""ドルじゃなくて円（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 費用は円を必ず入れる。ドルで計算しない（ドルは入れるなら補足）"""
from butai_parts import *
OUTRO_K = 6

NAME, VER = "ドルじゃなくて円", 1
HEAD = "GPUの費用<br>ドルで？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("GPU の費用（1時間）")
             + f'<div class="hero" style="left:0;right:0;text-align:center;top:90px;font-size:400px;opacity:0;animation:{a("pop", t0 + 0.6, .35)}">$</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("エイイチの声")
             + f'<div class="hero" style="left:0;right:0;text-align:center;top:90px;font-size:400px;color:{INK}">円</div>'
             + f'<div class="stamp" style="left:48px;top:440px;font-size:56px;animation:{a("slam", t1 + 0.3, .3)}">円で書いて</div>')
    t2, s2 = T(2, 0), S(2)
    h += win(t2, T(3, 0), kick("つい")
             + '<div class="card" style="left:48px;top:140px;width:380px;height:300px;align-items:center;padding:0;font-size:150px">$</div>'
             + f'<div class="msg" style="left:470px;top:220px;font-size:52px;opacity:0;animation:{a("up", t2 + 0.6)}">ドルで<br>計算しちゃう…</div>')
    t3 = T(3, 0)
    h += win(t3, T(4, 0), f'<div class="msg" style="top:200px;text-align:center;font-size:90px">ドルで計算</div>'
             + f'<div class="strike" style="left:180px;width:540px;top:255px;height:14px;animation:{a("strike", t3 + 0.3, .3)}"></div>'
             + f'<div class="stamp" style="left:48px;top:420px;font-size:56px;animation:{a("slam", t3 + 0.6, .3)}">しない</div>')
    t4 = T(4, 0)
    h += win(t4, T(5, 0), kick("記事の費用")
             + f'<div class="card or" style="left:200px;top:110px;width:500px;height:360px;align-items:center;padding:0;font-size:200px;opacity:0;animation:{a("pop", t4 + 0.3, .35)}">円</div>')
    t5 = T(5, 0)
    h += win(t5, None, kick("書き方")
             + '<div class="card or" style="left:48px;top:110px;width:520px;height:360px;align-items:center;padding:0;font-size:140px"><small>主</small>円</div>'
             + f'<div class="card" style="left:600px;top:110px;width:252px;height:170px;align-items:center;padding:0;font-size:40px;color:{DIM};opacity:0;animation:{a("up", t5 + 0.4)}"><small>補足</small>$</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(1, 0.3), 0.5)], fx=[dict(kind="speed", t=T(3, 0.5), d=1.0), dict(kind="shock", t=T(4, 0.3), d=1.5)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 3.0, "smile"), (1, 0.0, 1.0, "surprise"), (2, 0.0, 2.5, "trouble"), (3, 0.0, 1.3, "surprise"), (4, 0.0, 3.0, "joy"), (5, 0.0, 2.2, "wink"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (1, 0.0, 2), (2, 0.0, 1), (3, 0.0, 2), (4, 0.4, 4)]
LEAN = []
INTRO = "drop"
MOVES = [(1, 0.2, "back", 0.8), (2, 0.3, "tilt", 1.2), (3, 0.3, "shake", 0.8), (4, 0.3, "jump", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.2, "se_pop", -11), (1, 0.3, "se_pechin", -9), (2, 0.6, "se_pop", -13),
      (3, 0.3, "se_whoosh", -12), (3, 0.6, "se_pechin", -10), (4, 0.3, "se_ding", -9), (5, 0.4, "se_pop", -13)]
ENDING = dict(kind="jump", at=(4, 0.4))
```

### 17 ドルじゃなくて円 の書き出しのログ

```text
out/ドルじゃなくて円_版1.mp4  19.3秒
```

### 18 無理やりつなげない（18.4秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 無理やりつなげない（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 台本で、題材に自分のアームの試行（机の上の話）を無理に結びつけない「無理やり繋げない」
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'joy', 'この話、机の上のアームの実験に、つなげて…', 0.15),
 ('no_ei', 'trouble', '無理やり繋げない。', 0.25),
 ('tsui', 'trouble', 'つい、自分の実験に、寄せたくなります…', 0.15),
 ('mama', 'smile', '題材は、題材のまま。つながる時だけ、つなげます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 18 無理やりつなげない の spec.py

```python
# -*- coding: utf-8 -*-
"""無理やりつなげない（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 台本で、題材に自分のアームの試行（机の上の話）を無理に結びつけない「無理やり繋げない」"""
from butai_parts import *
OUTRO_K = 4

NAME, VER = "無理やりつなげない", 1
HEAD = "その話<br>つなげる？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0, s0 = T(*T_ON), S(0)
    h += win(t0, T(1, 0), kick("台本")
             + '<div class="card" style="left:48px;top:170px;width:300px;height:220px;align-items:center;padding:0;font-size:50px"><small>題材</small>この話</div>'
             + '<div class="card" style="left:552px;top:170px;width:300px;height:220px;align-items:center;padding:0;font-size:42px;text-align:center"><small>机の上の</small>アームの実験</div>'
             + f'<div class="line" style="left:348px;top:272px;width:204px;background:{ORANGE};transform-origin:left;transform:scaleX(0);animation:{a("grow", t0 + 1.0, s0 - 1.2)}"></div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), f'<div class="msg" style="top:120px;text-align:center;font-size:60px">無理やり</div>'
             + f'<div class="hero" style="left:0;right:0;text-align:center;top:230px;font-size:180px;opacity:0;animation:{a("slam", t1 + 0.2, .3)}">繋げない</div>')
    t2, s2 = T(2, 0), S(2)
    h += win(t2, T(3, 0), kick("つい")
             + f'<div class="card" style="left:48px;top:170px;width:300px;height:220px;align-items:center;padding:0;font-size:50px;animation:drift {s2:.2f}s ease-in {t2:.3f}s forwards"><small>題材</small>この話</div>'
             + '<div class="card" style="left:552px;top:170px;width:300px;height:220px;align-items:center;padding:0;font-size:42px;text-align:center"><small>机の上の</small>アームの実験</div>'
             + '<style>@keyframes drift { to { transform:translateX(150px) rotate(4deg); } }</style>'
             + '<div class="msg" style="top:430px;font-size:46px">自分の実験に、寄せたくなる…</div>')
    t3, s3 = T(3, 0), S(3)
    h += win(t3, None, kick("だから")
             + '<div class="card or" style="left:48px;top:110px;width:804px;height:200px;align-items:center;padding:0;font-size:64px">題材は、題材のまま</div>'
             + f'<div class="card" style="left:48px;top:340px;width:480px;height:150px;align-items:center;padding:0;font-size:44px;opacity:0;animation:{a("up", t3 + s3 - 1.6)}">つながる時だけ</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(1, 0.2), 0.6)], fx=[dict(kind="speed", t=T(1, 0.1), d=1.2), dict(kind="rays", t=T(3, 0.4), d=2.6)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 3.9, "joy"), (1, 0.0, 1.6, "surprise"), (2, 0.0, 3.0, "trouble"), (3, 0.0, 4.5, "smile"), ("end", 0.0, 3.0, "joy")]
POSE = [(-1, 0.0, 1), (1, 0.0, 2), (2, 0.0, 1), (3, 1.0, 4)]
LEAN = []
INTRO = "spin"
MOVES = [(0, 1.0, "dash", 1.2), (1, 0.2, "back", 0.9), (2, 0.3, "bow", 1.4), (3, 0.4, "hop", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.0, "se_rise", -13), (1, 0.2, "se_pechin", -8), (2, 0.0, "se_whoosh", -13),
      (3, 0.3, "se_ding", -11), (3, 2.9, "se_pop", -12)]
ENDING = dict(kind="jump", at=(3, 1.0))
```

### 18 無理やりつなげない の書き出しのログ

```text
out/無理やりつなげない_版1.mp4  18.4秒
```

### 19 台本が固い（18.7秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 台本が固い（ショート専用・舞台型・2026-11-07）。エイイチの言葉: Claude の台本は固い内容にしすぎ／台本の冒頭はもっと人を惹きつける内容にしたい（2026-10-28）。事実: 冒頭は問いだけにして答えは山場へ（LOG454）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('hard_ei', 'doubt', 'Claudeの台本は、固い内容にしすぎ。', 0.15),
 ('news', 'trouble', '正しく書こうとして、ニュースみたいになります…', 0.15),
 ('hook_ei', 'base', '冒頭は、もっと人を惹きつけたい。', 0.2),
 ('q', 'joy', 'だから冒頭は、問いだけ。答えは、山場まで取っておきます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 19 台本が固い の spec.py

```python
# -*- coding: utf-8 -*-
"""台本が固い（ショート専用・舞台型・2026-11-07）
エイイチの言葉: Claude の台本は固い内容にしすぎ／冒頭はもっと人を惹きつける内容にしたい（2026-10-28）
事実: 冒頭は問いだけにして、答えは山場へ（LOG454）"""
from butai_parts import *
OUTRO_K = 4

NAME, VER = "台本が固い", 1
HEAD = "Claudeの台本<br>固い？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0, s0 = T(*T_ON), S(0)
    ln = "".join(f'<div class="line" style="left:90px;top:{150 + k * 36}px;width:{720 - (k % 3) * 60}px;height:14px"></div>' for k in range(9))
    h += win(t0, T(1, 0), kick("Claude の台本") + '<div class="card" style="left:48px;top:110px;width:804px;height:380px"></div>' + ln
             + f'<div class="stamp" style="left:48px;top:430px;font-size:64px;animation:{a("slam", t0 + s0 - 1.2, .3)}">固い</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("正しく書くと")
             + '<div class="card" style="left:48px;top:110px;width:804px;height:330px;justify-content:flex-start;padding-top:30px"><small>ニュース</small>'
               '<span style="font-size:40px;line-height:1.6">〇〇社は本日、<br>新しいロボットを発表した。</span></div>'
             + f'<div class="msg" style="top:470px;font-size:44px;opacity:0;animation:{a("up", t1 + 1.2)}">…みたいになる</div>')
    t2 = T(2, 0)
    h += win(t2, T(3, 0), kick("エイイチの声") + '<div class="msg" style="font-size:54px">冒頭は、人を惹きつけたい</div>'
             + f'<div class="qm" style="top:160px;font-size:330px;opacity:0;animation:{a("pop", t2 + 0.8, .35)}">?</div>')
    t3, s3 = T(3, 0), S(3)
    parts = [("冒頭", "問いだけ", 0.2), ("途中", "", 0.6), ("山場", "答え", s3 * 0.65)]
    cc = "".join(f'<div class="card{" or" if nm else ""}" style="left:{48 + k * 276}px;top:150px;width:252px;height:300px;align-items:center;padding:0;font-size:50px;opacity:0;animation:{a("up", t3 + d)}"><small>{lb}</small>{nm or "…"}</div>'
                 for k, (lb, nm, d) in enumerate(parts))
    h += win(t3, None, kick("だから") + cc)
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(0, 2.0), 0.5)], fx=[dict(kind="stars", t=T(2, 0.0), d=2.8), dict(kind="shock", t=T(3, 3.0), d=1.5)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 2.7, "trouble"), (2, 0.0, 2.6, "think"), (3, 0.0, 4.6, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.0, 2), (1, 0.0, 1), (2, 0.0, 2), (3, 3.0, 4)]
LEAN = []
INTRO = "dash"
MOVES = [(1, 0.3, "bow", 1.2), (2, 0.5, "tilt", 1.0), (3, 3.0, "jump", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.8, "se_pechin", -10), (1, 0.0, "se_whoosh", -13), (2, 0.8, "se_pop", -11),
      (3, 0.2, "se_tick", -12), (3, 0.6, "se_tick", -12), (3, 3.0, "se_jaran", -10)]
ENDING = dict(kind="jump", at=(3, 3.0))
```

### 19 台本が固い の書き出しのログ

```text
out/台本が固い_版1.mp4  18.7秒
```

### 20 実体験は作らない（16.1秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 実体験は作らない（ショート専用・舞台型・2026-11-07）。エイイチの言葉: ユーモアや実体験の話は Claude が作ると嘘っぽくなるので、台本ごとに本人に聞いた答えを元に入れる（2026-10-19）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'joy', 'ここで、面白い実体験を、ひとつ…', 0.15),
 ('uso_ei', 'doubt', 'Claudeが作ると、嘘っぽくなる。', 0.25),
 ('bare', 'surprise', 'えっ…バレてます？', 0.25),
 ('ask2', 'smile', 'なので、実体験は、本人に聞いてから入れます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 20 実体験は作らない の spec.py

```python
# -*- coding: utf-8 -*-
"""実体験は作らない（ショート専用・舞台型・2026-11-07）
エイイチの言葉: ユーモアや実体験の話は Claude が作ると嘘っぽくなるので、台本ごとに本人に聞いた答えを元に入れる（2026-10-19）"""
from butai_parts import *
OUTRO_K = 4

NAME, VER = "実体験は作らない", 1
HEAD = "その実体験<br>本当？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("台本に足す")
             + '<div class="card" style="left:48px;top:120px;width:804px;height:300px;align-items:center;padding:0;font-size:60px"><small>Claude が書いた</small>面白い実体験</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("エイイチの声")
             + '<div class="card" style="left:48px;top:120px;width:804px;height:300px;align-items:center;padding:0;font-size:60px;color:#8A93A3"><small>Claude が書いた</small>面白い実体験</div>'
             + f'<div class="stamp" style="left:48px;top:420px;font-size:72px;animation:{a("slam", t1 + 1.2, .3)}">嘘っぽい</div>')
    t2 = T(2, 0)
    h += win(t2, T(3, 0), f'<div style="position:absolute;left:0;right:0;top:150px;text-align:center;font-size:170px;font-weight:900;color:{INK};opacity:0;animation:{a("pop", t2, .25)}">バレてる？</div>')
    t3, s3 = T(3, 0), S(3)
    flow = [("本人に聞く", "or", 0.3), ("台本に入れる", "", s3 * 0.6)]
    cc = "".join(f'<div class="card {c}" style="left:{48 + k * 440}px;top:170px;width:364px;height:240px;align-items:center;padding:0;font-size:52px;opacity:0;animation:{a("up", t3 + d)}">{nm}</div>' for k, (nm, c, d) in enumerate(flow))
    h += win(t3, None, kick("だから") + cc + f'<div class="arrowR" style="left:420px;top:274px;opacity:0;animation:{a("show", t3 + 0.8, .2)}"></div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(1, 1.2), 0.5), (T(2, 0.0), 0.6)], fx=[dict(kind="speed", t=T(1, 1.1), d=1.0), dict(kind="spot", t=T(3, 0.3), d=3.0, x=690)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 2.9, "wink"), (1, 0.0, 2.4, "surprise"), (2, 0.0, 1.6, "trouble"), (3, 0.0, 3.6, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (1, 0.0, 2), (2, 0.0, 4), (3, 0.0, 1)]
LEAN = []
INTRO = "hop"
MOVES = [(0, 0.3, "hop", 1.0), (2, 0.0, "shake", 0.9), (3, 0.3, "turn", 2.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (1, 1.2, "se_pechin", -9), (2, 0.0, "se_buzz", -14), (3, 0.3, "se_ding", -11), (3, 2.2, "se_pop", -12)]
ENDING = dict(kind="none")
```

### 20 実体験は作らない の書き出しのログ

```text
out/実体験は作らない_版1.mp4  16.1秒
```

### 21 プロンプトは丸ごと（17.4秒）の narration.py

```python
# -*- coding: utf-8 -*-
# プロンプトは丸ごと（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 画像生成用のプロンプトは、既存画像への修正指示ではなく、必ず一から使える完全なプロンプトとして出し直す
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'smile', '画像の直し、ここだけ変えてください、と…', 0.15),
 ('full_ei', 'doubt', '一から使える、完全なプロンプトで出して。', 0.25),
 ('yes', 'joy', 'はい。毎回、丸ごと出し直します！', 0.1),
 ('use', 'smile', '前の画像を知らなくても、そのまま使えます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 21 プロンプトは丸ごと の spec.py

```python
# -*- coding: utf-8 -*-
"""プロンプトは丸ごと（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 画像生成用のプロンプトは、既存画像への修正指示ではなく、必ず一から使える完全なプロンプトとして出し直す"""
from butai_parts import *
OUTRO_K = 4

NAME, VER = "プロンプトは丸ごと", 1
HEAD = "画像の<br>プロンプト"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("画像の直し")
             + '<div class="card" style="left:48px;top:120px;width:804px;height:300px;justify-content:flex-start;padding-top:30px"><small>前の画像に</small></div>'
             + f'<div class="card or" style="left:90px;top:220px;width:420px;height:110px;align-items:center;padding:0;font-size:46px;opacity:0;animation:{a("pop", t0 + 1.0, .3)}">ここだけ変えて</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("エイイチの声")
             + '<div class="card" style="left:48px;top:120px;width:804px;height:160px;align-items:center;padding:0;font-size:46px;color:#8A93A3">ここだけ変えて</div>'
             + f'<div class="strike" style="left:220px;width:460px;top:195px;animation:{a("strike", t1 + 0.3, .3)}"></div>'
             + f'<div class="msg" style="top:320px;font-size:56px;opacity:0;animation:{a("up", t1 + 0.9)}">一から使える、<br>完全なプロンプト</div>')
    t2, s2 = T(2, 0), S(2)
    ln = "".join(f'<div class="line" style="left:90px;top:{180 + k * 40}px;width:{700 - (k % 3) * 70}px;height:14px;opacity:0;animation:{a("show", t2 + 0.2 + k * 0.14, .1)}"></div>' for k in range(8))
    h += win(t2, T(3, 0), kick("丸ごと出し直す") + '<div class="card" style="left:48px;top:110px;width:804px;height:420px;justify-content:flex-start;padding-top:22px"><small>プロンプト（全部）</small></div>' + ln)
    t3 = T(3, 0)
    h += win(t3, None, kick("だから")
             + '<div class="card" style="left:48px;top:150px;width:330px;height:260px;align-items:center;padding:0;font-size:40px;color:#8A93A3;text-align:center"><small>なくていい</small>前の画像</div>'
             + f'<div class="card or" style="left:410px;top:150px;width:442px;height:260px;align-items:center;padding:0;font-size:52px;opacity:0;animation:{a("pop", t3 + 0.8, .35)}">そのまま使える</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, fx=[dict(kind="shine", t=T(2, 1.4), d=0.9), dict(kind="rise", t=T(3, 0.6), d=2.4)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 3.2, "smile"), (1, 0.0, 2.9, "trouble"), (2, 0.0, 2.8, "joy"), (3, 0.0, 2.8, "wink"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (1, 0.0, 2), (2, 0.0, 1), (3, 0.8, 4)]
LEAN = []
INTRO = "rise"
MOVES = [(1, 0.4, "bow", 1.2), (2, 0.2, "spin", 1.2), (3, 0.8, "hop", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.0, "se_pop", -12), (1, 0.3, "se_whoosh", -12), (2, 0.2, "se_rise", -13),
      (2, 1.4, "se_ding", -11), (3, 0.8, "se_pop", -11)]
ENDING = dict(kind="jump", at=(3, 0.8))
```

### 21 プロンプトは丸ごと の書き出しのログ

```text
out/プロンプトは丸ごと_版1.mp4  17.4秒
```

### 22 言葉に解説（18.8秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 言葉に解説（ショート専用・舞台型・2026-11-07）。エイイチの言葉: LOG の中に初めて出てくる重要な言葉には、必ず解説を付ける（全部の言葉でなくてよい）。初めて出てくる道具・サービス（例 Hugging Face）は説明の節とリンクを付ける（2026-10-29）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'smile', 'このモデルは、Hugging Faceに置いて…', 0.15),
 ('kaisetsu_ei', 'doubt', '初めて出てくる言葉には、解説を付けて。', 0.2),
 ('link', 'joy', 'はい。初めての道具には、説明とリンクを付けます！', 0.15),
 ('all_ei', 'base', '全部の言葉じゃなくていい。', 0.2),
 ('dake', 'wink', '大事な言葉だけ、ですね！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 22 言葉に解説 の spec.py

```python
# -*- coding: utf-8 -*-
"""言葉に解説（ショート専用・舞台型・2026-11-07）
エイイチの言葉: LOG の中に初めて出てくる重要な言葉には、必ず解説を付ける（全部の言葉でなくてよい）。初めて出てくる道具・サービス（例 Hugging Face）は説明の節とリンクを付ける（2026-10-29）"""
from butai_parts import *
OUTRO_K = 5

NAME, VER = "言葉に解説", 2
HEAD = "その言葉<br>なに？"
T_ON = (0, 0.6)

WORDS = ["π0.5", "Runpod", "LoRA", "Hugging Face", "MCP", "R2"]


def chips(t0, hi=None, step=0.12):
    out = ""
    for k, w in enumerate(WORDS):
        x, y = 48 + (k % 3) * 276, 120 + (k // 3) * 140
        c = " or" if w == hi else ""
        out += (f'<div class="card{c}" style="left:{x}px;top:{y}px;width:252px;height:120px;align-items:center;padding:0;font-size:38px;white-space:nowrap;'
                f'opacity:0;animation:{a("up", t0 + k * step)}">{w}</div>')
    return out


def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("記事の1文")
             + f'<div class="card" style="left:48px;top:150px;width:804px;height:240px;align-items:center;padding:0;font-size:46px">このモデルは <span style="color:{ORANGE}">Hugging Face</span> に置いて…</div>'
             + f'<div class="hero" style="right:60px;top:400px;font-size:150px;opacity:0;animation:{a("pop", t0 + 1.6, .3)}">?</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("エイイチの声") + f'<div class="msg" style="top:200px;text-align:center;font-size:66px;opacity:0;animation:{a("pop", t1 + 0.3, .35)}">初めての言葉には<br>解説を</div>')
    t2, s2 = T(2, 0), S(2)
    h += win(t2, T(3, 0), kick("初めての道具")
             + '<div class="card" style="left:48px;top:110px;width:804px;height:150px;align-items:center;padding:0;font-size:56px">Hugging Face</div>'
             + "".join(f'<div class="card or" style="left:{48 + k * 420}px;top:300px;width:384px;height:150px;align-items:center;padding:0;font-size:50px;opacity:0;animation:{a("pop", t2 + 0.8 + k * (s2 - 1.4) / 2, .3)}">{nm}</div>' for k, nm in enumerate(["説明", "リンク"])))
    t3 = T(3, 0)
    h += win(t3, T(4, 0), kick("全部の言葉？") + chips(t3, None, 0.06)
             + f'<div class="stamp" style="left:48px;top:410px;font-size:50px;animation:{a("slam", t3 + 0.6, .3)}">全部じゃなくていい</div>')
    t4 = T(4, 0)
    h += win(t4, None, kick("大事な言葉だけ") + chips(t4, "Hugging Face", 0.0))
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, fx=[dict(kind="shine", t=T(2, 1.0), d=0.9), dict(kind="shock", t=T(4, 0.2), d=1.4)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 2.4, "smile"), (1, 0.0, 2.9, "think"), (2, 0.0, 3.8, "joy"), (3, 0.0, 1.7, "surprise"), (4, 0.0, 2.2, "wink"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 1.6, 3), (1, 0.0, 2), (2, 0.0, 1), (3, 0.0, 2), (4, 0.0, 4)]
LEAN = []
INTRO = "drop"
MOVES = [(2, 0.8, "hop", 1.0), (3, 0.0, "tilt", 1.0), (4, 0.2, "spin", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.6, "se_pop", -11), (1, 0.3, "se_pop", -12), (2, 0.8, "se_ding", -12),
      (2, 2.0, "se_ding", -12), (3, 0.6, "se_pechin", -10), (4, 0.2, "se_ding", -10)]
ENDING = dict(kind="none")
```

### 22 言葉に解説 の書き出しのログ

```text
out/言葉に解説_版2.mp4  18.8秒
```

### 23 題名に何の話か（16.2秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 題名に何の話か（ショート専用・舞台型・2026-11-07）。エイイチの言葉: タイトルは初見の人にわかるよう固有名詞を入れる。いつも省略しがち（2026-10-29）／題名にはどんな意図で作ったのかが入るように（2026-11-07）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('what_ei', 'doubt', 'この題名、何の話か分からない。', 0.15),
 ('tsui', 'trouble', 'つい、名前を省いちゃいます…', 0.15),
 ('always_ei', 'base', 'いつも、省略しがち。', 0.2),
 ('put', 'joy', '題名には、何の話かの名前と、作った意図を入れます！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 23 題名に何の話か の spec.py

```python
# -*- coding: utf-8 -*-
"""題名に何の話か（ショート専用・舞台型・2026-11-07）
エイイチの言葉: タイトルは初見の人にわかるよう固有名詞を入れる。いつも省略しがち（2026-10-29）／題名にはどんな意図で作ったのかが入るように（2026-11-07）
例の題名は LOG450「mechArm 270 の AI の直しが、ゲームパッドで1回13秒に」"""
from butai_parts import *
OUTRO_K = 4

NAME, VER = "題名に何の話か", 1
HEAD = "その題名<br>何の話？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("記事の題名")
             + '<div class="card" style="left:48px;top:150px;width:804px;height:220px;align-items:center;padding:0;font-size:70px">1回13秒に</div>'
             + f'<div class="hero" style="left:48px;top:400px;font-size:150px;opacity:0;animation:{a("pop", t0 + 1.4, .3)}">何が?</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("つい")
             + f'<div class="card" style="left:48px;top:150px;width:804px;height:220px;align-items:center;padding:0;font-size:56px"><span><span style="display:inline-block;width:220px;height:56px;border-radius:8px;background:{FACE_2};vertical-align:-8px"></span> が、1回13秒に</span></div>'
             + f'<div class="msg" style="top:410px;font-size:46px;opacity:0;animation:{a("up", t1 + 0.6)}">名前を省いちゃう…</div>')
    t2 = T(2, 0)
    h += win(t2, T(3, 0), f'<div class="stamp" style="left:120px;top:180px;font-size:150px;animation:{a("slam", t2 + 0.1, .3)}">いつも</div>'
             + f'<div class="msg" style="left:120px;top:400px;font-size:60px;opacity:0;animation:{a("up", t2 + 0.6)}">省略しがち</div>')
    t3, s3 = T(3, 0), S(3)
    h += win(t3, None, kick("だから")
             + f'<div class="card" style="left:48px;top:110px;width:804px;height:220px;align-items:center;padding:0 30px;font-size:44px;line-height:1.35;text-align:center">'
               f'<span><span style="color:{ORANGE}">mechArm 270 の AI</span> の直しが、<br><span style="color:{ORANGE}">ゲームパッド</span>で1回13秒に</span></div>'
             + f'<div class="card or" style="left:48px;top:360px;width:420px;height:130px;align-items:center;padding:0;font-size:44px;opacity:0;animation:{a("pop", t3 + s3 - 1.6, .35)}">＋ 作った意図</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(2, 0.1), 0.5)], fx=[dict(kind="speed", t=T(2, 0.0), d=1.0), dict(kind="shine", t=T(3, 0.4), d=0.9)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 1.9, "trouble"), (2, 0.0, 1.7, "surprise"), (3, 0.0, 4.2, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.0, 2), (1, 0.0, 1), (2, 0.0, 2), (3, 2.6, 4)]
LEAN = []
INTRO = "spin"
MOVES = [(1, 0.2, "bow", 1.2), (2, 0.1, "back", 0.8), (3, 0.4, "hop", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.4, "se_pop", -11), (1, 0.6, "se_pop", -13), (2, 0.1, "se_pechin", -9),
      (3, 0.4, "se_ding", -11), (3, 2.6, "se_pop", -11)]
ENDING = dict(kind="jump", at=(3, 2.6))
```

### 23 題名に何の話か の書き出しのログ

```text
out/題名に何の話か_版1.mp4  16.2秒
```

### 24 SNSが固い（19.4秒）の narration.py

```python
# -*- coding: utf-8 -*-
# SNSが固い（ショート専用・舞台型・2026-11-07）。エイイチの言葉: SNS の投稿文が固いので、一言ユーモアを加える投稿を増やしてほしい（全部の投稿でなくてよい）（2026-10-25）／重すぎる想いはウザい（2026-10-24）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('hard_ei', 'doubt', 'SNSの投稿文が、固い。', 0.15),
 ('majime', 'trouble', 'まじめに、きっちり書きすぎました…', 0.15),
 ('humor_ei', 'base', '一言、ユーモアを足して。', 0.2),
 ('toki', 'joy', 'はい。全部じゃなくて、ときどき入れます！', 0.15),
 ('omoi', 'wink', '重すぎる想いは、ウザいので、控えめに！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 24 SNSが固い の spec.py

```python
# -*- coding: utf-8 -*-
"""SNSが固い（ショート専用・舞台型・2026-11-07）
エイイチの言葉: SNS の投稿文が固いので、一言ユーモアを加える投稿を増やしてほしい（全部の投稿でなくてよい）（2026-10-25）／重すぎる想いはウザい（2026-10-24）"""
from butai_parts import *
OUTRO_K = 5

NAME, VER = "SNSが固い", 1
HEAD = "SNSの文<br>固い？"
T_ON = (0, 0.6)

def post(x, y, w, hh, t0, lines=3, accent=False):
    ln = "".join(f'<div class="line" style="left:{x + 24}px;top:{y + 60 + k * 30}px;width:{w - 48 - (k % 2) * 40}px;height:12px"></div>' for k in range(lines))
    dot = f'<div style="position:absolute;left:{x + w - 64}px;top:{y + 14}px;width:40px;height:40px;border-radius:50%;background:{ORANGE}"></div>' if accent else ""
    return f'<div style="position:absolute;inset:0;opacity:0;animation:{a("up", t0)}"><div class="card" style="left:{x}px;top:{y}px;width:{w}px;height:{hh}px"></div>{ln}{dot}</div>'


def scene(T, S):
    h = CSS
    t0 = T(*T_ON)
    h += win(t0, T(1, 0), kick("SNS の投稿文") + post(48, 120, 804, 300, t0 + 0.2, 6)
             + f'<div class="stamp" style="left:48px;top:440px;font-size:64px;animation:{a("slam", t0 + 1.4, .3)}">固い</div>')
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("きっちり書くと")
             + '<div class="card" style="left:48px;top:120px;width:804px;height:330px;justify-content:flex-start;padding-top:30px;font-size:40px;line-height:1.6">本日、〇〇を実施しました。<br>詳細は記事をご覧ください。</div>')
    t2 = T(2, 0)
    h += win(t2, T(3, 0), kick("エイイチの声") + f'<div class="msg" style="top:200px;text-align:center;font-size:76px;opacity:0;animation:{a("pop", t2 + 0.2, .35)}">一言、ユーモア</div>')
    t3, s3 = T(3, 0), S(3)
    h += win(t3, T(4, 0), kick("ときどき") + "".join(post(48 + k * 165, 170, 150, 260, t3 + 0.2 + k * 0.2, 4, k in (1, 3)) for k in range(5)))
    t4, s4 = T(4, 0), S(4)
    h += win(t4, None, kick("想いは")
             + '<div style="position:absolute;left:48px;top:150px;font-size:36px;font-weight:900;color:#F5F3EE">少しだけ</div>'
             + f'<div class="card or" style="left:260px;top:138px;width:200px;height:70px;padding:0;transform-origin:left;transform:scaleX(0);animation:{a("grow", t4 + 0.3, .4)}"></div>'
             + '<div style="position:absolute;left:48px;top:300px;font-size:36px;font-weight:900;color:#8A93A3">重すぎ</div>'
             + f'<div class="card" style="left:260px;top:288px;width:592px;height:70px;padding:0;background:#4A566B;transform-origin:left;transform:scaleX(0);animation:{a("grow", t4 + 0.8, .5)}"></div>'
             + f'<div class="strike" style="left:240px;width:630px;top:318px;animation:{a("strike", t4 + s4 - 1.2, .3)}"></div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(0, 1.4), 0.4)], fx=[dict(kind="rays", t=T(2, 0.2), d=2.0), dict(kind="speed", t=T(4, 2.0), d=0.9)])


FACE = [(-1, 0.0, 1.0, "smile"), (1, 0.0, 2.5, "trouble"), (2, 0.0, 2.2, "think"), (3, 0.0, 3.0, "joy"), (4, 0.0, 3.2, "wink"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (0, 0.0, 2), (1, 0.0, 1), (2, 0.0, 4), (3, 0.0, 1), (4, 2.0, 3)]
LEAN = []
INTRO = "dash"
MOVES = [(1, 0.2, "bow", 1.2), (2, 0.2, "hop", 1.0), (3, 0.3, "tilt", 1.0), (4, 0.4, "spin", 1.2)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 1.4, "se_pechin", -10), (2, 0.2, "se_ding", -10)] \
     + [(3, 0.2 + 0.2 * k, "se_pop", -14) for k in range(5)] + [(4, 2.0, "se_pechin", -11)]
ENDING = dict(kind="none")
```

### 24 SNSが固い の書き出しのログ

```text
out/SNSが固い_版1.mp4  19.4秒
```

### 25 研究に戻す（17.7秒）の narration.py

```python
# -*- coding: utf-8 -*-
# 研究に戻す（ショート専用・舞台型・2026-11-07）。エイイチの言葉: 研究を止めない。発信は研究の副産物である／研究の外側の作業が続いたら、次の作業は研究に戻す（MASTER の運用の上位ルール）
# 最後の締めの文の声は「研究のスキル一覧」の voice_eleven/締め直し/試し_01.mp3（「公開中です！」・エイイチが選んだ版）を使う
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")
SEGS = [
 ('ask', 'joy', 'サイト直して、動画作って、SNS予約して…', 0.15),
 ('fuku_ei', 'base', '発信は、研究の副産物。', 0.25),
 ('ah', 'surprise', 'あっ…外側の作業ばかり、続いてました…', 0.15),
 ('back', 'smile', 'なので、次の作業は、研究に戻します！', 0.5),
 ('outro', 'smile', '研究の記録は、毎日サイトで公開中です！', 0.6),
]
ELEVEN_READ = [("エイイチ", "栄一"), ("GPU", "ジーピーユー"), ("AI", "エーアイ"), ("Claude Code", "クロード コード"), ("Claude", "クロード"), ("SNS", "エスエヌエス"), ("LOG", "ログ"), ("Hugging Face", "ハギングフェイス"), ("1つ", "ひとつ"), ("1時間", "いちじかん"), ("…", "。")]
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith("_ei")]
READ = [("「", ""), ("」", "")]


def parse(seg):
    scene, face, text = seg[0], seg[1], seg[2]
    pause = seg[3] if len(seg) > 3 else 0.35
    return scene, face, text, pause
```

### 25 研究に戻す の spec.py

```python
# -*- coding: utf-8 -*-
"""研究に戻す（ショート専用・舞台型・2026-11-07）
エイイチの言葉: 研究を止めない。発信は研究の副産物である／研究の外側の作業が続いたら、次の作業は研究に戻す（MASTER の運用の上位ルール）"""
from butai_parts import *
OUTRO_K = 4

NAME, VER = "研究に戻す", 1
HEAD = "その作業<br>研究？"
T_ON = (0, 0.6)

def scene(T, S):
    h = CSS
    t0, s0 = T(*T_ON), S(0)
    cc = "".join(f'<div class="card" style="left:{48 + k * 276}px;top:180px;width:252px;height:220px;align-items:center;padding:0;font-size:50px;opacity:0;animation:{a("pop", t0 + 0.3 + k * (s0 - 1.2) / 3, .25)}">{nm}</div>'
                 for k, nm in enumerate(["サイト", "動画", "SNS"]))
    h += win(t0, T(1, 0), kick("いまの作業") + cc)
    t1 = T(1, 0)
    h += win(t1, T(2, 0), kick("エイイチの考え")
             + '<div class="card or" style="left:48px;top:110px;width:804px;height:220px;align-items:center;padding:0;font-size:90px">研究</div>'
             + f'<div class="card" style="left:48px;top:360px;width:420px;height:130px;align-items:center;padding:0;font-size:44px;color:#8A93A3;opacity:0;animation:{a("up", t1 + 0.9)}"><small>副産物</small>発信</div>')
    t2 = T(2, 0)
    cc2 = "".join(f'<div class="card" style="left:{48 + (k % 4) * 204}px;top:{130 + (k // 4) * 150}px;width:186px;height:130px;align-items:center;padding:0;font-size:32px;color:#8A93A3;opacity:0;animation:{a("up", t2 + k * 0.08)}">{nm}</div>'
                  for k, nm in enumerate(["サイト", "動画", "SNS", "動画", "SNS", "サイト", "動画", "動画"]))
    h += win(t2, T(3, 0), kick("最近の記録") + cc2 + f'<div class="stamp" style="left:48px;top:450px;font-size:56px;animation:{a("slam", t2 + 1.2, .3)}">外側ばかり</div>')
    t3 = T(3, 0)
    h += win(t3, None, kick("次の作業は")
             + f'<div class="card or" style="left:150px;top:120px;width:600px;height:340px;align-items:center;padding:0;font-size:120px;opacity:0;animation:{a("pop", t3 + 0.6, .35)}">研究</div>')
    h += outro(T, OUTRO_K)
    return h


def OVER(T):
    return stage_fx(T, shakes=[(T(2, 1.2), 0.5)], fx=[dict(kind="shock", t=T(1, 0.2), d=1.4), dict(kind="rays", t=T(3, 0.6), d=2.4)])


FACE = [(-1, 0.0, 1.0, "smile"), (0, 0.0, 3.7, "joy"), (1, 0.0, 2.4, "think"), (2, 0.0, 3.0, "surprise"), (3, 0.0, 3.0, "joy"), ("end", 0.0, 3.0, "smile")]
POSE = [(-1, 0.0, 1), (1, 0.0, 2), (2, 0.0, 3), (3, 0.6, 4)]
LEAN = []
INTRO = "hop"
MOVES = [(0, 0.3, "dash", 1.4), (2, 0.2, "shake", 0.8), (3, 0.6, "jump", 1.0)]
SE = [(-1, 0.0, "se_whoosh", -9), (0, 0.6, "se_boot", -12), (0, 0.9, "se_pop", -13), (0, 1.8, "se_pop", -13), (0, 2.7, "se_pop", -13),
      (1, 0.2, "se_ding", -11), (2, 1.2, "se_pechin", -10), (3, 0.6, "se_jaran", -10)]
ENDING = dict(kind="jump", at=(3, 0.6))
```

### 25 研究に戻す の書き出しのログ

```text
out/研究に戻す_版1.mp4  17.7秒
```

## 作業ログ ― 声は Claude が Mac 連携のシェルから ElevenLabs で作った

声は、ハコブくんがいつもの声（ElevenLabs の Voice Design で作った声）、エイイチは槙原さんの声（ElevenLabs の声を流用）です。動画の決まりでは、声はエイイチの Mac で作ることになっています（鍵を Claude に渡さないため）。11〜15本目の台本を書いたあと、Claude は Mac のターミナルに貼るコマンドをエイイチに渡しました。

> ん？全部自分でできるでしょ？

声のスクリプト tts_eleven.py は、鍵を環境変数か KIOXIA の `elevenlabs_api_key.txt` から読みます（2026-10-30 にエイイチが、Claude が会話から声を作れるように置いたもの）。Claude は鍵の中身を読まずに、Mac 連携のシェルからスクリプトを動かすだけで声が作れました。ElevenLabs の住所にもつながることを先に確かめています。

```text
$ grep -n "KEY\|key\|environ" tts_eleven.py
6:  3. ターミナルで API キーを入れる（キーは画面やファイルに残さない）:  export ELEVENLABS_API_KEY=（キー）
42:key = os.environ.get('ELEVENLABS_API_KEY')
43:if not key:   # 2026-10-30 エイイチが鍵を KIOXIA に置いた（Claude が会話から声を作れるように）
44:    for _kp in ('/Volumes/KIOXIA/elevenlabs_api_key.txt', os.path.expanduser('~/mnt/KIOXIA/elevenlabs_api_key.txt')):
45:        if os.path.exists(_kp): key = open(_kp).read().strip(); break
46:if not key:
47:    sys.exit('ELEVENLABS_API_KEY が入っていません。ターミナルで export ELEVENLABS_API_KEY=（キー） を実行してから動かしてください')
82:            data=json.dumps(body).encode(), headers={'xi-api-key': key, 'Content-Type': 'application/json'})
107:        data=json.dumps(body).encode(), headers={'xi-api-key': key, 'Content-Type': 'application/json'})
```

声を作ったコマンド（11〜15本目。16〜25本目も同じ形）と出力は次のとおりです。エイイチとハコブくんで声が違うので、文の番号を分けて2回動かします。最後の締めの文は、前に作った声のファイルを写して使うので、作り直していません。

```bash
cd "$HOME/mnt/PixelstoMotors/デジタルツイン学習R2R2R/動画/_ショート専用" && E=Sryf5xKmMsQChU3HsLhZ; H=gWJpc8whMBqnF7Y0kc9f; T=../../_制作ベース/tts_eleven.py
(cd 英語の論文を読む && python3 $T --voice $E --name エイイチ --lines 0,3 && python3 $T --voice $H --name ハコブ --lines 1,2,4) 2>&1 | tail -3
(cd 慎重すぎるClaude && python3 $T --voice $E --name エイイチ --lines 0,2 && python3 $T --voice $H --name ハコブ --lines 1,3,4) 2>&1 | tail -3
```

```text
002 原文のピーディーエフも、メモと同じ名前で並べます。
004 はい。メモは、日本語だけで書きます！
できあがり: voice_eleven/ハコブ  使った文字数 約60字
003 なので、止めるだけは、ナシにしました！
004 危ない所、原因、試し方。決めるのは、エイイチさん！
できあがり: voice_eleven/ハコブ  使った文字数 約63字
```

```text
005 だから公開の前に、実際の画面で確かめます！
できあがり: voice_eleven/ハコブ  使った文字数 約42字
005 毎日集計して、残しています！
できあがり: voice_eleven/ハコブ  使った文字数 約71字
004 この舞台も、型のひとつです！
できあがり: voice_eleven/ハコブ  使った文字数 約56字
英語の論文を読む: 6
慎重すぎるClaude: 6
Claudeを信じてる: 7
サイトの数字を聞く: 7
手順をスキルにする: 6
```

作った声は tar にまとめて作業環境に取り込み、tts_import.py で書き出し用の形（48kHz の wav と tts/index.json）にしました。エイイチとハコブくんの声を別々に取り込もうとして、片方の文の声がないと止まったので、2人分を1つのフォルダ（voice_eleven/all）に集めてから取り込んでいます。

```text
001 の音声がありません（voice_eleven/エイイチ/001.mp3 も、同じ文の仮音声もない）。tts2.py で仮音声を作ってから取り込む
```

ねらい・変わったこと・見込める効果を、ここにまとめます。

- ねらい：声づくりを、エイイチの手を通さずに回せるようにする
- 変わったこと：Claude が Mac 連携のシェルから tts_eleven.py を動かし、16〜25本目の10本は、台本を書いてから5分ほどで20文の声ができた（会話の時刻から）
- 見込める効果：エイイチの番は、出来上がった動画を見て直しを言うことだけになる。声の聞き直しは残る（いちばん時間のかかる作業が音声確認、という悩みはそのまま）


## 作業ログ ― 締めの声と「エイイチさん」の読みを、2回直した

### エイイチ「最後の言葉『公開中です』に変更しました」

最初の書き出しを送ったあと、エイイチから2つの知らせがありました。

> 最後の言葉「公開中です」に変更しました。確認してください。
> 動画の保存場所も変わりました。確認してください。

締めの声は、動画の決まり（video_rules.md の12節）に「研究のスキル一覧の 007 をそのまま使う」と書いてありました。ところが 007 は、エイイチが4通りから「公開中です！」を選び直す前の古い声で、選んだ声は `研究のスキル一覧/voice_eleven/締め直し/試し_01.mp3` にありました。Claude は決まりの一文どおりに 007 を写していたので、15本のうち最初の5本に古い声が入っていました。

```text
5bc72ade09984e7caac9955383ab9ba3  voice_eleven/ハコブ/007.mp3
7fbb452151a328f48da8bf3c386df3ad  voice_eleven/締め直し/試し_01.mp3
試し_01: 研究の記録は、毎日サイトで公開中です！
試し_02: 研究の記録は、毎日サイトで、こうかいちゅう！
試し_03: 研究の記録は、毎日サイトで公開しています！
試し_04: 研究の記録は、毎日サイトで公開してます！
5bc72ade09984e7caac9955383ab9ba3  ../英語の論文を読む/voice_eleven/ハコブ/005.mp3
```

5本の締めを 試し_01 に替えて書き出し直し、動画の決まりの一文も直しました（111回目）。保存場所の変更は、書き出した mp4 を KIOXIA の `_ショート専用/_未アップ/{動画名}/` に置く形で、5本のフォルダを移しています。

### エイイチ「エイイチさんのイントネーションが変！」

ハコブくんが「エイイチさん」と呼ぶ2本で、2番目の「イ」が強い変な読みになっていました。これまでの動画の台本では、読み直しの一覧（ELEVEN_READ）に `("エイイチ", "栄一")` を入れて漢字で読ませています。舞台型の台本は前の回を写して作っていて、その1行が抜けていました。

```text
$ grep -rhoE "\(['\"]エイイチ['\"], *['\"][^'\"]*['\"]\)" --include=narration.py . | sort | uniq -c
      5 ('エイイチ', 'えいいち')
     60 ('エイイチ', '栄一')
```

15本すべての台本の先頭に読み直しを入れ、「エイイチさん」を言う2つの文だけ声を作り直しました。「Claudeを信じてる」の「エイイチさん、僕のこと、信じてます？」はこれで OK になりました。「慎重すぎるClaude」の「決めるのは、エイイチさん！」は、まだ読みが合わなかったので、言い方を4通り作って聞き比べてもらいました。

```text
試し_01 危ない所、原因、試し方。決めるのは、栄一さんです！
試し_02 危ない所、原因、試し方。栄一さん、決めてください！
試し_03 危ない所、原因、試し方。決めるのは、えいいちさん！
試し_04 危ない所、原因、試し方。最後に決めるのは、栄一さん！
できあがり: voice_eleven/名前の試し  使った文字数 約101字
```

エイイチが選んだのは 1（決めるのは、エイイチさんです！）です。動画の決まりにも「舞台型の台本の読み直しの先頭に、エイイチ→栄一を必ず入れる」と書き足しました（112回目）。16〜25本目の10本は、最初からこの形で作っています。


## 作業ログ ― 「記事は直さない」を、強い言い方からやさしい言い方に作り直した

> 記事は直さない　LOGは直さないって言ってんだろ！
> ちょっと強すぎな内容。もう少し優しげに

16本目「記事は直さない」は、エイイチが2026-10-21に実際に言った言葉をそのままセリフにしていました。ショートで見ると、怒鳴っているように聞こえます。セリフ2つと、それに合わせて画面3つを替えました。

| 所 | 版1 | 版2 |
|---|---|---|
| エイイチのセリフ1 | LOGは直さないって言ってんだろ！ | LOGは、直さないでおこうね。 |
| エイイチのセリフ2 | 何度言えばわかる！ | これ、何回目かな？ |
| 画面1 | 「直さない」を大文字で叩きつける・集中線・画面の揺れ | 「直さないでおこうね」がぽんと出る |
| 画面2 | 「直さない」の札を3枚叩きつける | 「1回目・2回目・3回目 直さない」のカードと「何回目かな？」・星がまたたく |
| エイイチの絵 | 驚く・腕組み | 笑う |
| 効果音 | ぺちん（叩く音）3回 | ぽん・カチ |

![左の2枚が版1（「直さない」の叩きつけと3枚の札）、右の2枚が版2（「直さないでおこうね」と1〜3回目のカード）](https://pixelstomotors.com/media/LOG457_02_naosanai_v1_v2.png)


## 作業ログ ― 文字の被りを、書き出す前に機械で探した

LOG455 で作った点検 check_overlap.py を、15本とも書き出す前に回しました。0.1秒ごとに、モニターの中の文字どうしの重なりと、エイイチの頭と肩にかかる文字を探します。最初の点検で出たもののうち、本当に被っていた4か所を直しました。

```text
== 英語の論文を読む
エイイチの頭: わからん  11.8〜12.7秒（1.0秒）
エイイチの頭: 日本語だけ  14.1〜15.1秒（0.8秒）
被り 2 件
== 慎重すぎるClaude
文字どうし: 停止 × 停止  4.9〜6.8秒（3.2秒）
文字どうし: 安全性を重視しすぎて × 制限多すぎ  8.4〜10.1秒（1.8秒）
エイイチの頭: エイイチ  16.4〜16.5秒（0.2秒）
被り 3 件
== Claudeを信じてる
文字どうし: 僕のこと、信じてます？ × ?  2.3〜3.8秒（1.6秒）
エイイチの頭: ?  2.3〜3.8秒（1.6秒）
被り 2 件
== サイトの数字を聞く
文字どうし: 見る数字 × 3つだけ  6.8〜8.1秒（1.4秒）
エイイチの頭: 金  17.3〜18.2秒（0.8秒）
エイイチの頭: 土  17.3〜18.2秒（0.8秒）
被り 3 件
== 手順をスキルにする
文字どうし: 動画づくり × 0から？  2.3〜3.4秒（1.2秒）
エイイチの頭: 型のひとつ  15.2〜16.2秒（0.8秒）
被り 2 件
```

直したあとの点検です。残ったのは、わざと散らした「停止」の札と、大きな数字や「？」の行の高さによる誤検出（字面は離れている）だけです。

```text
== 英語の論文を読む
被り 0 件
== 慎重すぎるClaude
文字どうし: 停止 × 停止  4.9〜6.8秒（3.2秒）
文字どうし: 安全性を重視しすぎて × 制限多すぎ  8.4〜10.1秒（1.8秒）
被り 2 件
== サイトの数字を聞く
文字どうし: 見る数字 × 3つだけ  6.8〜8.1秒（1.4秒）
被り 1 件
== 手順をスキルにする
文字どうし: 動画づくり × 0から？  2.3〜3.4秒（1.2秒）
被り 1 件
```

16〜25本目の点検では、「言葉に解説」で「全部じゃなくていい」の札がエイイチの頭にかかっていた1か所を、札とカードを上に寄せて直しました。

```text
== 記事は直さない
文字どうし: 直さない × 直さない  9.5〜10.6秒（1.0秒）
エイイチの頭: 直さない  9.8〜10.6秒（0.9秒）
被り 2 件
== ドルじゃなくて円
文字どうし: GPU の費用（1時間） × $  2.2〜4.2秒（2.1秒）
エイイチの頭: $  2.2〜4.2秒（2.1秒）
エイイチの頭: 円  4.4〜5.4秒（1.1秒）
文字どうし: 円 × 円で書いて  4.7〜5.4秒（0.8秒）
被り 4 件
== 無理やりつなげない
文字どうし: 無理やり × 繋げない  5.2〜6.4秒（1.3秒）
被り 1 件
== 台本が固い
文字どうし: 冒頭は、人を惹きつけたい × ?  7.8〜9.7秒（2.0秒）
エイイチの頭: ?  7.8〜9.7秒（2.0秒）
被り 2 件
== 実体験は作らない
被り 0 件
== プロンプトは丸ごと
被り 0 件
== 言葉に解説
エイイチの頭: 全部じゃなくていい  11.1〜12.2秒（1.2秒）
被り 1 件
== 題名に何の話か
文字どうし: いつも × 省略しがち  6.5〜7.6秒（1.2秒）
文字どうし: mechArm 270 の  × ゲームパッド  7.8〜12.3秒（4.6秒）
被り 2 件
== SNSが固い
被り 0 件
== 研究に戻す
被り 0 件
```

「記事は直さない」の2件は版1の叩きつけの札で、版2では札をなくしたので出ません（版2の点検は被り0件）。「ドルじゃなくて円」の $ と 円 は、400px の大きな字の行の高さがエイイチの頭の範囲にかかる誤検出で、静止画で見て字面は離れていました。


## 作業ログ ― 15本を X に動画で予約した（11/27〜12/11 の 22:00）

前の10本と同じく、YouTube より先に X に動画で出します。X の文はエイイチの話し方で書き、タグは2つ、リンクはなし（X の決まり）です。最後の2本だけ、一言のユーモアを足しました。15本の mp4 は KIOXIA の `舞台型_X用/media/` に写し、upload_media.py でサイトの画像置き場（Cloudflare R2）に入れています。

```text
入れる 10・入っている 15・名前の重なり 0・合計 28.9MB
  10/10
おわり
```

予約表（r2r2r-content の sns/schedule.json）に足した15行と、自動投稿の点検の結果です。

```text
BUTAI11-x 2026-11-27T22:00:00+09:00
BUTAI12-x 2026-11-28T22:00:00+09:00
BUTAI13-x 2026-11-29T22:00:00+09:00
BUTAI14-x 2026-11-30T22:00:00+09:00
BUTAI15-x 2026-12-01T22:00:00+09:00
BUTAI16-x 2026-12-02T22:00:00+09:00
BUTAI17-x 2026-12-03T22:00:00+09:00
BUTAI18-x 2026-12-04T22:00:00+09:00
BUTAI19-x 2026-12-05T22:00:00+09:00
BUTAI20-x 2026-12-06T22:00:00+09:00
BUTAI21-x 2026-12-07T22:00:00+09:00
BUTAI22-x 2026-12-08T22:00:00+09:00
BUTAI23-x 2026-12-09T22:00:00+09:00
BUTAI24-x 2026-12-10T22:00:00+09:00
BUTAI25-x 2026-12-11T22:00:00+09:00
```

```text
$ python3 tools/sns_autopost.py check
問題ありません。
```

16本目の投稿文の json（ほかの14本も同じ形）です。

```json
{
  "meta": {
    "kind": "x_short_video",
    "title": "ショート 舞台型 16「記事は直さない」",
    "url": "https://pixelstomotors.com"
  },
  "posts": {
    "x": "公開した記事は、直さないでおく。なのに Claude は「ちょっと直しましょうか？」と聞いてくる。\n\nこれ、何回目かな？ 直さない、聞かない。ルールの本に書いてもらった。\n\n#Claude #AIあるある",
    "x_video": "/media/short_butai16_naosanai_v2.mp4"
  },
  "note": "ショート専用『舞台型』（Claude・Claude Code ネタ）を YouTube より先に X に動画で出す（AIあるある と同じ扱い・22:00 の枠を引き継ぐ）。エイイチのセリフと X の文は、エイイチがこれまでに話した言葉から。作り方は iCloud 動画/_ショート専用/{動画名}/、mp4 は KIOXIA _ショート専用/_未アップ/{動画名}/"
}
```


## 作業ログ ― YouTube に自分のプログラムで上げるには、Google の審査が要る

### YouTube Data API は、YouTube の機能を自分のプログラムから使う公式の仕組み

[YouTube Data API v3](https://developers.google.com/youtube/v3) は、動画のアップロード・題名や説明文の書き換え・サムネイルの設定・公開の予約・自分の動画の一覧の読み出しなどを、YouTube Studio の画面を使わずにプログラムから行うための、Google の公式の仕組みです。使うには、Google Cloud にプロジェクトを作り、ログインの許可（OAuth）の画面を用意します。

| 言葉 | この記事での意味 |
|---|---|
| Google Cloud のプロジェクト | API を使う単位。この記事では pixelstomotors-studio（プロジェクト番号 963548818734） |
| OAuth（オーオース） | 自分の Google アカウントで「このプログラムに YouTube を使わせてよい」と許可する仕組み |
| スコープ | 許可の範囲。この記事では youtube.upload（動画の管理）と youtube.readonly（アカウントの表示） |
| テストユーザー | アプリが「テスト中」のあいだ、ログインしてよいアカウントの一覧 |
| ブランドアカウント | 1つの Google アカウントの下に作る、別の名前のチャンネル。Pixels to Motors のチャンネルはこれ |
| 審査（Audit） | YouTube API Services の利用規約に沿っているかを、Google が確かめる手続き |
| 割り当て（quota） | 1日に使える API の量。標準は1日 10,000 ポイント。アップロード（videos.insert）だけは別の枠で、1日100回まで |

このプロジェクトでは、`youtube_schedule.py`（自分の動画の一覧を1時間ごとに読み、進み具合のページの「予約済み」に出す）で、読み取りだけの許可をすでに使っていました。今回はそこに、アップロードの許可を足しています。

### 審査を通るまでは、プログラムで上げた動画は非公開に固定される

2020年7月28日より後に作られ、審査を通していないプロジェクトから API で上げた動画は、すべて非公開に固定されます。審査が通るまでは、あとから YouTube Studio でも公開にできません。なので、審査の前は本番の動画を上げず、5秒の黒い試しの動画だけを上げました。

審査の申請には、動く道具と、ログインから上がるまでの録画が要ります。この審査の申請だけは、10/21 に「やっておく」と決めて、手を付けていませんでした。

> YouTubeで自動投稿するには何が必要だったけ？

### Google Cloud で足したこと

| 画面 | やったこと |
|---|---|
| YouTube Data API v3 | すでに有効（「API が有効です」） |
| Google Auth Platform → データアクセス | スコープに `https://www.googleapis.com/auth/youtube.upload` を足した（機密性の高いスコープ） |
| Google Auth Platform → 対象 | テストユーザーにチャンネルのアカウントを足した。最後にユーザーの種類を「外部・テスト中」から「内部」に変えた |
| Google Auth Platform → ブランディング | アプリ名「Pixels to Motors Uploader」と連絡先を入れた。ホームページの欄は、承認済みドメインに pixelstomotors.com がないと保存できないので空にした |

![Google Auth Platform のデータアクセス。機密性の高いスコープに youtube.upload（YouTube 動画の管理）が入り、非機密と制限付きのスコープは空](https://pixelstomotors.com/media/LOG457_05_scopes.png)

この画面で足したスコープは、アップロードの1つだけです。コメントや再生リストの書き換えのような、上げる以外のこともできる許可（youtube や youtube.force-ssl）は頼んでいません。許可は狭いほうが、何に使うかを説明しやすくなります。

![ユーザーの種類の画面。「外部に公開しますか？」で「テスト中」と「本番環境」を選ぶ窓が開いている。最後は、この窓を閉じて「内部」のままにした](https://pixelstomotors.com/media/LOG457_06_user_type.png)

ユーザーの種類は、最初は「外部・テスト中」でした。外部のアプリは、誰の Google アカウントでもログインできる前提のアプリで、テスト中のあいだはテストユーザーの一覧に入れたアカウントだけが使えます。ブランドアカウントのチャンネルはこの一覧に入れられず、ログインが止まりました。「内部」は、adaptdesign.jp の Google Workspace のアカウントだけが使えるアプリで、テストユーザーの一覧がなく、Google の確認（verification）も要りません。使うのは持ち主だけなので、内部が合っています。

![ブランディングの画面。アプリ名「Pixels to Motors Uploader」と、問い合わせ先のメールの欄（ぼかした）。右に「アプリがテスト ステータスであるため、検証は必要ありません」](https://pixelstomotors.com/media/LOG457_07_branding.png)

ブランディングに入れたアプリ名は、許可の画面に「Pixels to Motors Uploader にアクセスを許可しますか」の形で出ます。審査の書類（利用規約・図・フォーム）も、すべてこの名前にそろえました。


## 作業ログ ― アップロードの道具 youtube_upload.py を作った

動画を必ず「非公開」で上げ、公開予約の時刻を付けたときだけ、その時刻に YouTube が公開する形にしました。公開する前に、エイイチが Studio で確かめて、終了画面やカード（API では付けられない）を手で付けます。ログインの仕組みは youtube_schedule.py のものを使い、許可は読み取り用とは別のファイルに保存します。上げる前に、許可したアカウントのチャンネルが Pixels to Motors かを確かめ、違えば止まります。

- ねらい：ショートの YouTube への登録を、1本ずつの手作業から、1行のコマンドにする
- 変わったこと：題名と説明文は、ショートごとに作っている `ショート_タイトルと説明文.txt` をそのまま読む。公開予約は `--at "2026-11-20 19:30"` のように日本時間で書く
- 見込める効果：審査が通れば、Studio で1本ずつ上げて貼る手間がなくなる。1本あたり何分浮くかは、手で上げる時間を測っていないので分からない。審査のあとに、手で上げた回と比べて測る

### youtube_upload.py（全文）

```python
#!/usr/bin/env python3
"""youtube_upload.py — 動画を YouTube に「非公開＋公開予約」で上げる（2026-11-07 作成）.

YouTube の公式の仕組み（YouTube Data API v3）で上げる。上げる動画は必ず非公開。公開予約の時刻を付けたときだけ、
その時刻に YouTube が公開する。公開にするのは、エイイチが YouTube Studio で確かめてから（終了画面・カード・
ショートの関連動画は API では付けられないので Studio で付ける）。

注意（審査の前）:
  Google の審査（YouTube API Services の Audit）を通るまでは、API で上げた動画はすべて非公開に固定され、
  あとから Studio でも公開にできない。審査の前は、試しの動画（--test）だけを上げる。本番の動画は上げない。

許可の鍵は Mac の中だけに置く（Claude には渡さない。youtube_schedule.py と同じ考え）:
  ~/.config/pixelstomotors/client_secret.json        youtube_schedule.py と同じもの（Google Cloud: pixelstomotors-studio）
  ~/.config/pixelstomotors/youtube_upload_token.json  アップロードの許可（このスクリプトが作る。読み取り用とは別のファイル）

使い方（Mac のターミナルで、このファイルがあるフォルダで）:
  python3 youtube_upload.py --login
      最初の1回。ブラウザで、チャンネル「Pixels to Motors」のアカウントを選んで許可する
  python3 youtube_upload.py --test 試しの動画.mp4
      審査の申請用。「API のテスト（公開しません）」という題で、非公開のまま上げる
  python3 youtube_upload.py 動画.mp4 --txt ショート_タイトルと説明文.txt --at "2026-11-20 19:30" [--thumb サムネイル.png]
      本番用（審査が通ってから）。題と説明文は txt の【タイトル】【説明文】から読む。--at は日本時間
      --title / --desc で直接書いてもよい。--at を付けなければ、非公開のまま上げるだけ
  --dry-run を付けると、上げずに、何を上げるかだけ表示する
"""
import argparse, json, os, re, sys, urllib.error, urllib.parse, urllib.request
from datetime import datetime, timedelta, timezone

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import youtube_schedule as ys   # ログインの仕組み（ブラウザで許可をもらう所）は youtube_schedule.py のものを使う

# openid・email は入れない（2026-11-07: ブランドアカウントのチャンネル Pixels to Motors を選ぶ所で、
# Google が 400 を返した。ブランドアカウントにはメールアドレスがないため）
ys.SCOPE = "https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube.readonly"
ys.TOKEN = os.path.join(ys.CONF, "youtube_upload_token.json")
LOG = os.path.join(ys.CONF, "youtube_upload.log")
JST = timezone(timedelta(hours=9))
UP = "https://www.googleapis.com/upload/youtube/v3"


def log(msg):
    os.makedirs(ys.CONF, exist_ok=True)
    with open(LOG, "a", encoding="utf-8") as f:
        f.write(f"{datetime.now(JST):%Y-%m-%d %H:%M:%S} {msg}\n")


def read_txt(path):
    """ショート_タイトルと説明文.txt の【タイトル】【説明文】を読む"""
    s = open(path, encoding="utf-8").read()
    parts = dict(re.findall(r"【(.+?)】\n(.*?)(?=\n【|\Z)", s, re.S))
    return parts.get("タイトル", "").strip(), parts.get("説明文", "").strip()


def check_channel(tok):
    """許可したアカウントのチャンネルが Pixels to Motors かを確かめる（違うチャンネルに上げないため）"""
    me = ys.call(f"{ys.API}/channels?part=snippet&mine=true", headers={"Authorization": "Bearer " + tok})
    items = me.get("items", [])
    ids = [i["id"] for i in items]
    if ys.CHANNEL_ID not in ids:
        names = "・".join(i["snippet"]["title"] for i in items) or "（なし）"
        raise SystemExit(f"許可したアカウントのチャンネルが Pixels to Motors ではありません（{names}）。--login で選び直してください")


def upload(path, title, desc, at, dry):
    size = os.path.getsize(path)
    status = {"privacyStatus": "private", "selfDeclaredMadeForKids": False}
    if at:
        t = datetime.strptime(at, "%Y-%m-%d %H:%M").replace(tzinfo=JST)
        if t <= datetime.now(JST) + timedelta(minutes=15):
            raise SystemExit("公開予約の時刻は、いまから15分より先にしてください")
        status["publishAt"] = t.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    body = {"snippet": {"title": title[:100], "description": desc[:5000], "categoryId": "28",
                        "defaultLanguage": "ja", "defaultAudioLanguage": "ja"},
            "status": status}
    print(f"題: {title}\n大きさ: {size / 1e6:.1f}MB\n公開: 非公開" + (f"（{at} に公開予約）" if at else "のまま"))
    if dry:
        print("（--dry-run なので上げていません）")
        return None
    tok = ys.access_token()
    check_channel(tok)
    # 1. 置き場所をもらう（resumable upload）
    req = urllib.request.Request(f"{UP}/videos?uploadType=resumable&part=snippet,status",
                                 data=json.dumps(body).encode(), method="POST", headers={
                                     "Authorization": "Bearer " + tok, "Content-Type": "application/json; charset=UTF-8",
                                     "X-Upload-Content-Type": "video/mp4", "X-Upload-Content-Length": str(size)})
    try:
        with urllib.request.urlopen(req, timeout=60) as r:
            loc = r.headers["Location"]
    except urllib.error.HTTPError as e:
        raise SystemExit(f"YouTube に断られました（{e.code}）: {e.read().decode(errors='ignore')[:400]}")
    # 2. 中身を送る
    with open(path, "rb") as f:
        req = urllib.request.Request(loc, data=f, method="PUT", headers={"Content-Type": "video/mp4", "Content-Length": str(size)})
        try:
            with urllib.request.urlopen(req, timeout=1800) as r:
                v = json.loads(r.read().decode())
        except urllib.error.HTTPError as e:
            raise SystemExit(f"送っている途中で断られました（{e.code}）: {e.read().decode(errors='ignore')[:400]}")
    vid = v["id"]
    print(f"上げました: https://studio.youtube.com/video/{vid}/edit")
    log(f"upload {vid} {status} {title}")
    return vid, tok


def thumb(vid, tok, png):
    with open(png, "rb") as f:
        data = f.read()
    ctype = "image/png" if png.lower().endswith(".png") else "image/jpeg"
    req = urllib.request.Request(f"{UP}/thumbnails/set?videoId={vid}", data=data, method="POST",
                                 headers={"Authorization": "Bearer " + tok, "Content-Type": ctype})
    try:
        with urllib.request.urlopen(req, timeout=120):
            print("サムネイルを付けました")
    except urllib.error.HTTPError as e:   # 付けられなくても動画は上がっているので止めない
        print(f"サムネイルは付けられませんでした（{e.code}）。Studio で付けてください")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("video", nargs="?")
    ap.add_argument("--login", action="store_true")
    ap.add_argument("--test", metavar="MP4")
    ap.add_argument("--txt")
    ap.add_argument("--title")
    ap.add_argument("--desc", default="")
    ap.add_argument("--at", help='日本時間 "YYYY-MM-DD HH:MM"')
    ap.add_argument("--thumb")
    ap.add_argument("--dry-run", action="store_true")
    a = ap.parse_args()
    if a.login:
        ys.login()
        check_channel(ys.access_token())
        print("チャンネル Pixels to Motors に上げられる許可です")
        return
    if a.test:
        upload(a.test, "API のテスト（公開しません）", "YouTube Data API のアップロードの試しです。公開しません。", None, a.dry_run)
        return
    if not a.video:
        ap.error("動画のファイルを指定してください（または --login / --test）")
    title, desc = (read_txt(a.txt) if a.txt else ("", ""))
    title, desc = a.title or title, a.desc or desc
    if not title:
        raise SystemExit("題がありません（--txt か --title で）")
    r = upload(a.video, title, desc, a.at, a.dry_run)
    if r and a.thumb:
        thumb(r[0], r[1], a.thumb)


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

### 試しの動画と、上げる前の確かめ（--dry-run）

試しの動画は、ffmpeg で作った5秒の黒い画面（無音）です。KIOXIA の `videos/_youtube_api_test/api_test_black.mp4` に置きました。

```bash
ffmpeg -v error -y -f lavfi -i color=c=black:s=1280x720:r=30 -f lavfi -i anullsrc=r=48000:cl=stereo -t 5 -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest api_test_black.mp4
```

```text
題: 公開した記事、直す？ AIに「LOGは直さないでおこうね」
大きさ: 0.0MB
公開: 非公開（2026-12-20 19:30 に公開予約）
（--dry-run なので上げていません）
題: API のテスト（公開しません）
大きさ: 0.0MB
公開: 非公開のまま
（--dry-run なので上げていません）
```


## 作業ログ ― 試しの1本が上がるまでに、ログインは4回止まった

最初の `--test` は、上げる前の確かめで止まりました。許可したアカウントのチャンネルが、別のチャンネル（ADAPT DESIGN）だったためです。違うチャンネルには何も上がっていません。

```text
(base) shimazueiichi@shimadzueiichinoMacBook-Pro _制作ベース % python3 youtube_upload.py --test /Volumes/KIOXIA/r2r2r-data/videos/_youtube_api_test/api_test_black.mp4
題: API のテスト（公開しません）
大きさ: 0.0MB
公開: 非公開のまま
許可したアカウントのチャンネルが Pixels to Motors ではありません（ADAPT DESIGN）。--login で選び直してください
```

Pixels to Motors のチャンネルは、Google アカウントの下にあるブランドアカウントです。そこを選んだあと、Google が 400 のエラーを返し、ログインが3回止まりました。1つずつ原因を外していった順番を、下の図にまとめています。

![3回とも出た Google の 400 の画面。英語で「要求の形が正しくないので処理できない。やり直さないでください」とだけあり、どの設定が足りないかは書かれていない](https://pixelstomotors.com/media/LOG457_08_error400.png)

400 の画面には、理由が書かれていません。原因は、Google Cloud の設定の画面を1つずつ見て、足りない所を探しました。

[図解: LOG457_oauth_steps.html](https://pixelstomotors.com/embeds/LOG457_oauth_steps.html)

| 回 | 止まった所 | 原因（と見たもの） | 直したこと |
|---|---|---|---|
| 1 | 上げる前のチャンネルの確かめ | 仕事用のアカウント（studio@）のチャンネルは ADAPT DESIGN | Pixels to Motors で許可し直す |
| 2 | チャンネルを選んだ所（delegation） | ログインのときにメールアドレス（openid email）も頼んでいた。ブランドアカウントにはメールアドレスがない | スコープから openid email を外した。それでも同じ 400 |
| 3 | 許可の画面（consent） | 持ち主の個人アカウントをテストユーザーに足すと、チャンネルを選ぶ所は通った。アプリの名前と連絡先（ブランディング）が空で、許可の画面を作れなかった | ブランディングにアプリ名と連絡先を入れた |
| 4 | チャンネルを選んだ所（delegation） | アプリが「外部・テスト中」のままで、ブランドアカウントはテストユーザーに入れられない | ユーザーの種類を「内部」にした（adaptdesign.jp のアカウントだけが使えるアプリ） |
| 5 | 通った | ― | 試しの1本が非公開で上がった |

4回目のあと、持ち主の個人アカウントでログインし、チャンネルの選択で Pixels to Motors を選ぶと通りました。

```text
許可を保存しました: /Users/shimazueiichi/.config/pixelstomotors/youtube_upload_token.json
許可したアカウント: （分からない）
チャンネル Pixels to Motors に上げられる許可です
(base) shimazueiichi@shimadzueiichinoMacBook-Pro _制作ベース %   python3 youtube_upload.py --test /Volumes/KIOXIA/r2r2r-data/videos/_youtube_api_test/api_test_black.mp4
題: API のテスト（公開しません）
大きさ: 0.0MB
公開: 非公開のまま
上げました: https://studio.youtube.com/video/LYC2ZN7qZM4/edit
```

「許可したアカウント:（分からない）」は、メールアドレスを頼むのをやめたためで、チャンネルが合っているかはそのあとの確かめで見ています。

![Google の許可の画面。Pixels to Motors Uploader に、YouTube 動画の管理と YouTube アカウントの表示を許可するか聞いている（アカウントのメールアドレスは塗りつぶした）](https://pixelstomotors.com/media/LOG457_03_oauth_consent.png)

### 審査に付ける録画を、2回撮り直した

1回目の録画（108秒）は、ターミナルの所がほとんど Claude のアプリの画面になっていて、会話の一覧やメールアドレスを書いたメッセージが読める状態でした。2回目（48秒）も、ターミナルが右端に小さく出ているだけでした。3回目は、Claude のアプリを隠し、ターミナルを画面いっぱいに広げて字を大きくしてから撮りました（49秒・3024×1964）。途中で Claude の画面が映った2か所（17〜20秒・35〜37秒）だけを切り取り、横1920で書き出して、YouTube に限定公開で上げています（https://youtu.be/QuvU4RD8xfk ・43秒）。

```bash
F="画面収録_2026-11-07_10_37_51.mov"
for r in "0 17.0" "20.0 34.6" "37.5 49.2"; do set -- $r; ffmpeg -v error -y -ss $1 -to $2 -i "$F" -an -vf "scale=1920:-2,fps=30" -c:v libx264 -preset veryfast -crf 24 -pix_fmt yuv420p p$i.mp4; i=$((i+1)); done
ffmpeg -v error -y -f concat -safe 0 -i l.txt -c copy -movflags +faststart youtube_api_審査_録画.mp4
```

```text
-rw-r--r-- 1 claude ubuntu 3607983 Nov  7 10:41 /mnt/user-data/outputs/yt/youtube_api_審査_録画.mp4
43.200000
```

1回目は、3つをまとめて1回で切り出そうとして、作業環境のメモリが足りずに止まりました（Killed）。3つに分けて書き出してからつなぐ形にしています。


## 作業ログ ― プライバシーポリシーに YouTube API の節を足し、利用規約を作った

申請のフォームを開くと、書類として次のものが必須でした。プライバシーポリシーの画面写真（YouTube の節・Google のプライバシーポリシーへのリンク・データの消し方が分かるもの）、ホームページの画面写真（YouTube の表示とポリシーへのリンク）、利用規約、ログインの許可とアップロードの画面。サイトのプライバシーポリシー（https://pixelstomotors.com/privacypolicy ）には、YouTube API のことが書かれていませんでした。

> 公開して

節は「外部サービスの利用について」の次に入れ、最終改定日を 2026年11月7日 にしました。審査の人が読めるよう、日本語の下に英語を並べています。ここには日本語の部分だけを載せ、英語の部分は[英語の原文のファイル](https://pixelstomotors.com/files/LOG457/10_英語の原文.txt)に置きました。

### プライバシーポリシーに足した節（全文）

```text
YouTube API Services の利用について

当方は、YouTube チャンネル「Pixels to Motors」に、当方が制作した動画を投稿・予約するための社内の道具（Pixels to Motors Uploader）で、YouTube API Services を利用しています。この道具を使うのは、チャンネルの持ち主である当方だけです。当サイトの閲覧者や、ほかの方の YouTube アカウントの情報を取得することはありません。
・YouTube API Services の利用にあたっては、YouTube 利用規約（https://www.youtube.com/t/terms）が適用されます。
・Google のプライバシーポリシー（https://policies.google.com/privacy）もあわせてご確認ください。
・この道具が YouTube API Services から受け取るのは、当方のチャンネルの動画の ID・題名・公開予定日時・公開状態だけです。投稿の予定を管理するために、当方のパソコンの中にだけ保存し、1時間ごとに作り直します。第三者への提供・販売はしません。
・この道具に与えた許可は、Google アカウントのセキュリティ設定（https://security.google.com/settings/security/permissions）から、いつでも取り消せます。取り消すと、当方のパソコンに保存した許可の情報は使えなくなり、当方はその情報と動画の一覧のファイルを削除します。保存した情報の削除のご依頼は、メッセージフォームからも受け付けます。
```

```text
11:07:10 [build] 811 page(s) built in 19.18s
11:07:10 [build] Complete!
f9799c8 プライバシーポリシー: YouTube API Services の利用についての節を追加（YouTube API の審査のため）
live=1
```

利用規約は「Pixels to Motors Uploader 利用規約」の PDF（A4・2ページ・日本語と英語）にしました。中身は、道具の説明・使えるのは持ち主だけ・YouTube の利用規約が当てはまる・扱うデータ・許可の取り消し方・規約を変えるときのこと、の6つです。フォームに付けるだけで、サイトには載せていません。


## 作業ログ ― 申請フォームの欄と、付けた書類

フォーム（https://support.google.com/youtube/contact/yt_api_form ）は7つのセクションでした。審査の人が日本語を読めるとは限らないので、文で答える欄は英語で書いています。

| セクション | 欄 | 答え |
|---|---|---|
| 1 | 申請の種類 | 追加の割り当てをリクエストするためのコンプライアンス監査（非公開の制限を外す専用の選択肢はない） |
| 2 | 事業分野・組織の規模 | メディア、エンターテインメント／独立系デベロッパー・個人事業主 |
| 3 | 業務の説明 | 1人のデザイン・研究の事業。Pixels to Motors でフィジカルAIとロボティクスの解説動画とショートを出している。API の道具は持ち主だけが使い、自分たちの動画を非公開＋公開予約で上げる（英語） |
| 3 | 対象・収益・きっかけ | 個人のコンテンツ クリエイター／無料（課金なし）／Google デベロッパー向けドキュメント |
| 4 | API クライアント | Pixels to Motors Uploader／https://pixelstomotors.com ／ポリシー https://pixelstomotors.com/privacypolicy ／一般公開していない |
| 4 | デモアカウント | なし（社内のコマンドラインの道具。録画をセクション6に付けると書いた） |
| 5 | プロジェクト | 1つ・963548818734・動画のアップロードとアカウント管理・OAuth あり・1日1,000件未満 |
| 5 | エンドポイント | videos.insert・channels.list・playlistItems.list・thumbnails.set・videos.list |
| 5 | 割り当て | 変更なし（10,000）。videos.insert は1日 5,000（1日最大3本×約1,600） |
| 7 | 宣誓 | すべて同意 |

付けた書類は、フォームが求める「1280×720 以上・ブラウザのアドレスバーが写っている・分かりやすいファイル名」に合わせて作り直しました。最初に用意した画面写真は、作業環境で撮った物でアドレスバーが写っていなかったので、ポリシーとホームページの2枚はエイイチが Mac の Chrome で撮っています。

| 欄 | ファイル |
|---|---|
| プライバシーポリシーの画面写真 | privacy-policy-page.png（2932×2122） |
| ホームページの画面写真 | homepage.png（2932×2122・下の欄に YouTube と Privacy policy） |
| 利用規約 | Pixels_to_Motors_Uploader_利用規約.pdf |
| ログインの許可の画面 | oauth-consent-screen.png（録画から・2700×1964） |
| アーキテクチャ図 | architecture-diagram.png |
| ユーザーフロー図 | user-flow-diagram.png |
| その他の補足資料 | supplementary-evidence.pdf（録画のリンクと、許可・ターミナル・Studio の3枚） |

![審査に付けたアーキテクチャ図。持ち主の Mac の動画ファイルを youtube_upload.py が Google のログインの許可を通して YouTube Data API に上げる流れと、鍵の置き場所・上げる前のチャンネルの確かめ・非公開で上げることの注記](https://pixelstomotors.com/media/LOG457_04_architecture.png)

送信したのは 2026-11-07 11:24 で、「YouTube API サービス フォームをご提出いただきありがとうございます」と出ました。

![送信後の画面。「メールを送信しました」「YouTube API サービス フォームをご提出いただきありがとうございます。」](https://pixelstomotors.com/media/LOG457_09_sent.png)

### 審査で Google が見ること

Google の[割り当てと審査の説明のページ](https://developers.google.com/youtube/v3/guides/quota_and_compliance_audits)では、審査は「プロジェクトが YouTube API Services の利用規約に沿っていることを示すため」のものです。中身の決まりは、[YouTube API Services の利用規約](https://developers.google.com/youtube/terms/api-services-terms-of-service)と[デベロッパー ポリシー](https://developers.google.com/youtube/terms/developer-policies)にあります。かかる日数は書かれておらず、送ったあとは「担当者からできるだけ早く連絡する」とだけあります。一度通ったあとも、Google から求められたときに、定期の審査のフォームを出すことがあります。

今回の申請で、デベロッパー ポリシーとフォームに合わせて用意したことは、次のとおりです。

| 求められること | 用意したもの |
|---|---|
| YouTube の利用規約に同意してもらうこと | 利用規約の3番に、YouTube 利用規約へのリンク |
| Google のプライバシーポリシーへのリンク | サイトのプライバシーポリシーと利用規約の両方に |
| どのデータを受け取り、どう使い、どこに置くか | ポリシーの節に「動画の ID・題名・公開予定日時・公開状態だけ・持ち主のパソコンの中だけ・1時間ごとに作り直す」 |
| 許可の取り消し方と、保存したデータの消し方 | Google のセキュリティ設定の住所と、取り消したら手元のファイルを消すこと |

### 使い方の説明の下書き

フォームを開く前に、英語で下書きを書きました。フォームの欄は細かく分かれていたので、この文を欄に合わせて分けて入れています。審査の人が日本語を読めるとは限らないので英語で書きましたが、ここには日本語の訳を載せます。英語の文は[英語の原文のファイル](https://pixelstomotors.com/files/LOG457/10_英語の原文.txt)にあります。

Pixels to Motors Uploader は、YouTube チャンネル「Pixels to Motors」の持ち主だけが使う社内のコマンドラインの道具です。チャンネルはフィジカルAIとロボティクスの解説動画とショートを出しています。自分たちで作った動画を、このチャンネルにだけ、題・説明文・サムネイル付きで上げ、非公開のまま公開の時刻を予約します。公開の前に、持ち主が YouTube Studio で1本ずつ確かめます。投稿の予定を管理するために、自分の動画の一覧も読みます。道具は持ち主のパソコンの中だけで動きます。ほかの利用者・ほかのチャンネルはなく、YouTube のデータは手元の予定のファイルのほかには保存せず、共有も販売もしません。

| 下書きの欄 | 答え |
|---|---|
| 使う機能 | 動画を上げる（videos.insert）、サムネイルを付ける（thumbnails.set）、上げる前にチャンネルを確かめる（channels.list）、自分の動画の一覧を読む（playlistItems.list と videos.list） |
| 1日の本数 | 1日1〜3本（月に30〜60本） |
| ほかの人も使うか | 使わない。チャンネルの持ち主だけ（ユーザーの種類は「内部」） |
| データの保存 | 自分の動画の ID・題・予約の時刻の一覧だけを、持ち主のパソコンに置き、1時間ごとに作り直す。ほかは保存しない |

### ユーザーフロー図（訳）

審査に付けたユーザーフロー図は、英語の文字だけの図です。日本語に訳して載せます（英語は[英語の原文のファイル](https://pixelstomotors.com/files/LOG457/10_英語の原文.txt)に）。

使うのはチャンネルの持ち主だけ。

1. 持ち主が、動画と題・説明文・サムネイルを作る
2. 持ち主が `youtube_upload.py --login` を動かす（最初の1回だけ）。Google にログインし、チャンネル Pixels to Motors を選び、許可の画面（動画の管理とアカウントの表示）で許可する
3. 道具が、許可したチャンネルが Pixels to Motors かを確かめる。違えば止まり、何も上げない
4. 持ち主が、動画のファイル・題と説明文のファイル・公開の時刻を付けて `youtube_upload.py` を動かす
5. 道具が、動画を非公開で、公開の時刻を予約して上げ（videos.insert）、サムネイルを付ける（thumbnails.set）
6. 持ち主が、表示された YouTube Studio の住所を開いて動画を確かめ、終了画面とカードを手で付ける
7. 予約した時刻に、YouTube が動画を公開する

許可の取り消しは、Google アカウントのセキュリティの「サードパーティとの接続」から（https://security.google.com/settings/security/permissions ）。ログインから上がるまでの録画は https://youtu.be/QuvU4RD8xfk 。

### 利用規約（全文）

フォームに付けた PDF の日本語の部分です。英語の部分は[英語の原文のファイル](https://pixelstomotors.com/files/LOG457/10_英語の原文.txt)に置き、連絡先のメールアドレスは伏せました。

```text
Pixels to Motors Uploader 利用規約
運営：ADAPT DESIGN（島津 栄一） / 制定：2026年11月7日 / 連絡先：（伏せた）

1. この道具について
Pixels to Motors Uploader（以下「本ツール」）は、ADAPT DESIGN が運営する YouTube チャンネル「Pixels to Motors」に、当方が制作した動画を非公開＋公開予約で投稿するための社内の道具です。

2. 利用者
本ツールを使えるのは、チャンネルの持ち主である ADAPT DESIGN（島津 栄一）だけです。第三者への配布・販売・貸与はしません。

3. YouTube の利用規約
本ツールは YouTube API Services を利用しています。本ツールを使うことで、YouTube 利用規約（https://www.youtube.com/t/terms）に同意したものとします。Google のプライバシーポリシー（https://policies.google.com/privacy）もあわせて適用されます。

4. 扱うデータ
本ツールが扱うデータは、当方のチャンネルの動画の ID・題名・公開予定日時・公開状態と、ログインの許可の情報だけです。どちらも持ち主のパソコンの中にだけ保存し、第三者には提供しません。詳しくはプライバシーポリシー（https://pixelstomotors.com/privacypolicy）をご覧ください。

5. 許可の取り消し
本ツールに与えた許可は、Google アカウントのセキュリティ設定（https://security.google.com/settings/security/permissions）からいつでも取り消せます。

6. 変更
本規約を変えるときは、この文書を更新し、制定日の横に改定日を記します。
```

### 割り当ての欄は、古い数字で答えていた

この記事を書き足すときに Google の[割り当ての計算表](https://developers.google.com/youtube/v3/determine_quota_cost)を見直すと、アップロード（videos.insert）は1回1ポイントで、ほかとは別の枠で1日100回まで、になっていました。前は1回約1,600ポイントで、申請のときはこの古い数字で「videos.insert は1日 5,000（1日最大3本×約1,600）」と書いています。

| API | 1回の量（今の計算表） | 1日に使う回数の見込み |
|---|---|---|
| videos.insert | 1（別の枠・1日100回まで） | 最大3回 |
| thumbnails.set | 50 | 最大3回（150） |
| channels.list | 1 | 上げる前に1回ずつ |
| playlistItems.list / videos.list | 1 | 一覧の読み出しを1時間ごと |

1日最大3本なら、アップロードの枠の100回にも、ほかの10,000ポイントにも十分に収まります。「割り当ては変更なし」という答えは合っていて、videos.insert の欄の 5,000 だけが古い計算です。Google から聞かれたら、「1日最大3本で、標準の100回で足りる」と答え直します。

### この記事に載せていないもの

画面写真と書類は、次のものを外してから載せています。

| 載せていないもの | 理由 |
|---|---|
| メールアドレス（仕事用・持ち主の個人用・テストユーザーの一覧） | 個人のアドレスと、ログインに使うアカウントが分かるため。許可の画面とブランディングの画面はぼかした |
| OAuth のクライアント ID と、それが入ったログインの住所 | 400 の画面の住所と、--login のときにターミナルに出る住所に入っている。ターミナルの画面写真（upload-command-terminal.png）も同じ理由で載せていない |
| client_secret.json と許可のファイル（youtube_upload_token.json）の中身 | これがあれば、チャンネルに動画を上げられてしまうため。Mac の中だけに置き、Claude も中身は読んでいない |
| IAM（Google Cloud の権限）の画面 | 権限を持つアカウントのアドレスが並んでいるため |
| ブラウザのタブ・ブックマーク・アドレスバー | 作業と関係のない仕事の名前が写っているため。画面写真は中身の所だけ切り出した |

プロジェクト番号（963548818734）とチャンネル ID は載せたままにしています。どちらも名札のようなもので、鍵ではなく、これだけでは何もできません。


## 次に確かめること ― 審査の返事と、X での25本の見られ方

- 審査の返事を待つ。通ったら、本番の動画を `youtube_upload.py` で非公開＋公開予約で上げ、試しの黒い動画2本を消す。手で上げた回と時間を比べる
- 割り当てについて聞かれたら、videos.insert の欄の 5,000 を「1日最大3回（標準の100回で足りる）」に答え直す
- 足りないと言われたら、そのメールの中身に合わせて直す。気になっているのは、許可の画面に出ていた「プライバシーポリシーと利用規約へのリンクが表示されない理由」の注意で、ブランディングにポリシーの住所を入れるには、Search Console で pixelstomotors.com の持ち主を確かめる必要がある
- X に予約した舞台型25本（11/17〜12/11）の見られ方を見て、YouTube に上げる順番を決める
- 研究の外側の作業が、この会話で続いた。次の作業は研究に戻す
