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

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


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


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


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


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


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

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


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


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


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


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

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

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

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

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

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

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

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

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