4コママンガ『ハコブくん』は1コマずつ直すと流れが崩れる ― 4コマ通しの確認を足した

この記事の素材(コード・ログ・データ 7ファイル・まとめてダウンロードあり)
AIで読む ChatGPT・Claude などで、この記事に質問できます

画像生成AIに4コママンガを1コマずつ描かせて直していくと、1枚ずつは正しくても、ロボットがだれを向いているか・どれくらいの大きさか・舞台の配膳車がどこにあるかがコマ同士でずれ、話の流れが崩れる。 病院で物を運ぶロボットの4コママンガ『ハコブくん』第143〜147話(pixelstomotors.com/manga)を Google の Gemini で描いたとき、1コマずつの確認では見つからなかったずれを、エイイチが4コマを並べて読んで5回見つけました。

これを受けて、絵を描く手順に「4コマを通して話として読む確認」を足しました。変わったことは次の3つです。

  • 見る順番が決まった:舞台の物の位置、人とロボットの位置と向き、セリフと絵が合っているか、大きさ、描き直したコマの前後、細部、の6つを、4コマを並べて順に見る
  • 直しの頼み方が変わった:ロボットの向きを「画面は相手のほうへ約60°・輪のある側面が見える」と、見える面の名前で書く。高さは「大城さんのみぞおちとへその間」のように体の部位で書く
  • 費用と時間:5話で Gemini に描かせた絵は120枚、見積もりで約1,100円(1話あたり約220円)。前の第138〜142話の約90円より高い。描き直しの80枚のうち66枚は、4コマを並べて見つかったずれの直しだった。描いてからショート5本・サイト公開・SNS の予約までは、約2時間20分だった

『ハコブくん』は、実際の病院でロボットを使ったときに起きたことを、調べた報告をもとに毎話1つ描いている4コママンガです。絵は1コマずつ縦長(縦横比 4:5)で、Gemini の画像生成 API(文章と参考の絵を渡すと絵を返す仕組み)に描かせています。描き直しは1コマ単位でできるので、だめなコマだけ直せば安く済みます。ところが、直したコマが前後のコマとつながっているかは、1コマだけ見ても分かりません。

たとえば第144話「お先にどうぞの作法」では、1コマ目を直したらハコブくんが車いすの目の前に来てしまい、2コマ目の「配膳車の向こうから来る」とつながらなくなりました。1コマ目を直し直すと、今度は配膳車が2コマ目と左右逆になり、大城さんと車いすが横を向きました。さらに、3コマ目ではハコブくんが2人と同じ向きに走り去り、配膳車も消えていました。

やったことの全体は次のとおりです。

やったことなぜ結果
第143〜147話のプロットから、5話の構図とセリフ(spec)を書いた
下調べと検証を済ませたプロット表の続きだったため
20コマの指示ができた
バッチ(まとめて頼む半額の頼み方)で40枚を頼んだ
下書きを安く何話も一度に描くため
2分半後には全部返っていた
1コマずつ確かめて、5コマを描き直した
腕や脚の数、字、ロボットの形を見るため
別のエージェントの確認で2コマ、自分で3コマ
エイイチの指摘で、向き・大きさ・流れを直した
4コマを並べて読むと話がおかしかったため
66枚を描き直した
4コマを通して読む確認を手順に足した
同じずれを次の話で出さないため
チェック項目と過去の間違いの台帳に書いた
ショート・表紙・英語版・サイト・SNS まで出した
5話を公開するため
10ページを公開、SNS を予約
第143〜147話の確定版。上から1話ずつ、左から1〜4コマ目

効果の数字について断っておきます。枚数と時刻は、API の記録とファイルに残った実際の値です。円の額は、1枚約11円(ふつうの頼み方の実績・LOG410)と、バッチはその半額という Google の案内から出した見積もりです。4コマを通して読む確認でどれだけ直しが減るかは、まだ測っていません。次の話で、エイイチの指摘の数と描き直しの枚数で確かめます。

作業ログ ― 第143〜147話のプロットから、構図とセリフの指示を書いた

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(7本)

01_spec.py_spec.py.txt ― spec.py(構図とセリフの指示) / 第143話の spec.py(止まるまで分からない)・11,769字

02_spec.py_spec.py.txt ― spec.py(構図とセリフの指示) / 第144話の spec.py(お先にどうぞの作法)・22,743字

03_spec.py_spec.py.txt ― spec.py(構図とセリフの指示) / 第145話の spec.py(電話中なの)・10,258字

04_spec.py_spec.py.txt ― spec.py(構図とセリフの指示) / 第146話の spec.py(小突かれる場所)・10,296字

05_spec.py_spec.py.txt ― spec.py(構図とセリフの指示) / 第147話の spec.py(気送管との競争)・9,103字

06_sec04.md ― チェック項目と過去の間違い.md に足した手順(チェック①の2) / チェック項目と過去の間違い.md(4コマを通して読む確認を足したあとの全文)・10,633字

07_sec05.py.txt ― 冒頭の動き(motion143x.py)・6,164字

使ったプロット(plots_134_158_直した版.tsv)

プロットは、病院のロボットの報道と研究を調べて作り、codex(別の AI)に疑ってもらって直した表から、続きの5話を使いました(作業フォルダ 記事/ハコブくん_新プロット_20261031/)。

話題登場人物もとにした調べ
143止まるまで分からない岩田、ハコブくん
搬送ロボットが人をよけるよう作られていても、向かってくるように見える(KQED)
144お先にどうぞの作法大城、森田茂、ハコブくん
職員が患者のために脇へ寄ってもロボットは突き進む(病院のロボットの研究を紹介した記事)
145電話中なの
大城、ハコブくん(田中先生は電話の声)
電話中の職員に到着を告げて強い言葉を返された・内科系の病棟は割り込みに厳しい(同上、ウィスコンシン大学の研究)
146小突かれる場所高杉、白石、ハコブくん
いら立った職員が搬送ロボットを蹴った(同上)
147気送管との競争白石、大城、ハコブくん
気送管がある病院で、看護師が看護ロボットは意味がなかったと話した(Proof News・メーカーは反論)

登場人物は、プロット表より少なく絞りました。白石さんが動きを直す場面は、144・145話では絵にせず、4コマ目のハコブくんのセリフで分かるようにしています。145話の4コマ目は、プロットでは「画面に『届キマシタ』と出す」でしたが、ハコブくんの画面には字を出さない決まりがあるので、「電話ガ終ワルマデ、待チマシタ」と声で落としました。

spec.py(構図とセリフの指示)

spec.py は、1話の4コマの構図・カメラ・セリフ・大きさを書くファイルです。vbuild.py(LOG448 までに作った道具)がこれを読み、キャラクターの参考の絵と決まりの文を足して Gemini への頼み文を組みます。下は、描き直しを全部終えた時点の spec.py です。最初の版の PANELS のあとに、直したときの PANELS の上書きを日付とエイイチの指摘を添えて足しているので、どこをどう直したかが残っています。

ハコブくんの向きを書く関数 face は、向きの指摘のあとに作りました(下の「ハコブくんの向きと大きさ」の節)。

_face.py(ハコブくんを相手に向ける言い方)

# 2026-11-08 エイイチ「ハコブくんと患者や看護師の向きや身長などの大きさ変」→ ハコブくんの向きを相手に向ける言い方(向きと見える面で書く)
def face(side, who):
    scr, ring = ("RIGHT", "LEFT") if side == "right" else ("LEFT", "RIGHT")
    return ("HAKOBUKUN FACES %s — NOT THE VIEWER:" % who.upper(), [
        "Hakobukun's front (its screen face) is turned about 60° toward camera-%s, toward %s. It does NOT look at the viewer" % (side, who),
        "in the picture, its screen is on the %s part of its silhouette, seen at an angle, and the flat side panel with the round ring is clearly visible on the %s part" % (scr, ring),
        "its eyes look toward %s" % who])

第143話の spec.py(止まるまで分からない)

01_spec.py_spec.py.txt11,769字・106行・python
EP, TITLE = 143, "止まるまで分からない"
# 2026-11-08 カメラワーク:P1 岩田さんの肩なめ、廊下の奥からまっすぐ来るハコブくん/P2 低い位置、手前に大きなハコブくん(急停止の効果線)と身を固くする岩田さん/P3 岩田さんの顔に寄る・手前にハコブくんの画面の端/P4 高い引き:早めに端へ寄るハコブくんと、歩き出す岩田さん
CRUTCH = "Mr. Iwata walks with TWO underarm crutches, one under each arm, held by his own two hands"
CORRIDOR = ("CORRIDOR", ["a long straight plain hospital corridor suggested ONLY by two thin wall base lines converging toward the far end. NO doors, NO signs, NO text"])
PANELS = [
 dict(chars=["岩田", "ハコブ"], mouth="NORMAL", no_yline=True, costume=[CRUTCH], prop=CORRIDOR,
      hscale_text=["Mr. Iwata is in the near foreground at the RIGHT, seen from his rear three-quarter, knee-up, on his crutches, cut by the right and bottom edges: x 56–100%, his head at about 22% of the image height",
                   "Hakobukun is far down the corridor in the CENTRE, small, three-quarter front view, coming straight toward the camera: its top at about 44% and wheels at about 62% of the image height, x 30–46%"],
別のタブで開く保存

第144話の spec.py(お先にどうぞの作法)

02_spec.py_spec.py.txt22,743字・183行・python
EP, TITLE = 144, "お先にどうぞの作法"
# 2026-11-08 カメラワーク:P1 斜め前の引き、車いすを押す大城さんと奥のハコブくん/P2 大城さんの肩なめ、狭い所をそのまま来るハコブくん/P3 低い位置の真横、車いすの前を横切るハコブくん(スピード線)/P4 ハコブくんの背中なめ(ハコブくん目線)、先に通る2人
CHAIR = "Mr. Morita sits in a plain standard hospital wheelchair with NO text on it; Oshiro pushes it by both handles"
NOBADGE = "Mr. Morita is a patient: he wears NO lanyard, NO name badge, NO ID card"
CART = "a plain hospital meal cart parked against one wall, copied from the attached cart reference, narrowing the corridor. NO text"
PANELS = [
 dict(chars=["大城", "森田茂", "ハコブ"], mouth="NORMAL", no_yline=True, not_standing=["森田茂"], costume=[CHAIR, NOBADGE],
      hscale_text=["Oshiro and Mr. Morita in his wheelchair are in the near foreground at the RIGHT, three-quarter front view, moving toward camera-left: Oshiro full body, her head at about 24% and shoes at about 90% of the image height, x 52–86%; Mr. Morita seated in front of her, his head at about 46%",
別のタブで開く保存

第145話の spec.py(電話中なの)

03_spec.py_spec.py.txt10,258字・93行・python
EP, TITLE = 145, "電話中なの"
# 2026-11-08 カメラワーク:P1 大城さんの腰上(電話を肩にはさむ)・手前に到着したハコブくんの画面の端/P2 真横の引き、大声のハコブくんと振り向く大城さん/P3 大城さんの顔に大きく寄る(目をつぶる)/P4 ハコブくん目線、電話を置いた大城さんと横で待つハコブくん
PHONE = ("HANDSET", ["Oshiro holds one plain cordless internal phone handset — a plain handset with NO text"])
PANELS = [
 dict(chars=["大城", "ハコブ"], mouth="NORMAL", no_yline=True, prop=PHONE,
      hscale_text=["Oshiro is at the RIGHT-CENTRE, waist-up, three-quarter view, standing at a plain low counter (only its top edge visible at about 72% of the image height): x 34–86%, her head at about 26% of the image height",
                   "the top-left part of Hakobukun is in the near foreground at the bottom-LEFT, three-quarter view, just arriving, cut by the left and bottom edges: x 0–30%, its top at about 62% of the image height"],
      camera=["SHOT SIZE: waist-up of Oshiro; the edge of Hakobukun in the foreground", "CAMERA POSITION: front-left of Oshiro", "CAMERA HEIGHT: 140 cm"],
別のタブで開く保存

第146話の spec.py(小突かれる場所)

04_spec.py_spec.py.txt10,296字・94行・python
EP, TITLE = 146, "小突かれる場所"
# 2026-11-08 カメラワーク:P1 高い引き、曲がり角の手前で止まるハコブくんと走る高杉さん/P2 床すれすれ、手前に車輪を小突く足(大きく)/P3 ハコブくんの頭なめ、しゃがんで記録を見る白石さん/P4 斜め前の全身、角の先で待つハコブくんの頭をなでて通る高杉さん
CORNER = "a plain corridor corner suggested ONLY by one thin vertical wall-corner line and a short wall base line. NO doors, NO signs, NO text"
WAGON = "Takasugi pushes a plain small two-shelf nursing wagon with a few plain boxes on it. NO text"
PANELS = [
 dict(chars=["高杉", "ハコブ"], mouth="NORMAL", no_yline=True, prop=("CORRIDOR CORNER", [CORNER]),
      hscale_text=["Hakobukun is in the near foreground at the LEFT-CENTRE, stopped and waiting just before the corner, three-quarter view: x 10–44%, its top at about 52% and wheels at about 88% of the image height",
                   "Takasugi is in the distance beyond the corner at the right, full body, running across with motion lines: her head at about 30% and shoes at about 60% of the image height, x 56–76%"],
別のタブで開く保存

第147話の spec.py(気送管との競争)

05_spec.py_spec.py.txt9,103字・83行・python
EP, TITLE = 147, "気送管との競争"
# 2026-11-08 カメラワーク:P1 白石さんの肩なめ、壁の気送管を指さす大城さん/P2 手元の大写し:カプセルを管に入れる手(手前)と、奥で走り出すハコブくん/P3 床に近い位置、手前に届いたカプセル、奥で固まるハコブくん/P4 斜め前の全身、運ぶ物を書き直した紙を持つ白石さんと、大きな箱を運ぶハコブくん
TUBE = "a pneumatic tube station on the wall: a plain wall-mounted box with a small hinged round door on the front and one round tube going up into the ceiling. NO text, NO buttons with letters"
CAPSULE = "a plain clear plastic pneumatic tube capsule, about 30 cm long, with plain caps at both ends. NO text"
PANELS = [
 dict(chars=["白石", "大城"], no_yline=True, prop=("PNEUMATIC TUBE STATION", [TUBE + ", at the upper right of the picture"]),
      hscale_text=["Shiraishi is in the near foreground at the LEFT, seen from her rear three-quarter, chest-up, cut by the left and bottom edges: x 0–40%, her head at about 30% of the image height",
                   "Oshiro is about 1.5 m beyond at the right-centre, waist-up, three-quarter view, pointing at the tube station on the wall with one hand: her head at about 26% of the image height, x 42–70%",
別のタブで開く保存

バッチで5話40枚を頼み、1コマずつ確かめて5コマを描き直した

40枚のバッチは、2分半後に見たときには全部返っていた

$ python3 vbatch.py submit 143 144 145 146 147 -n 2
第143話 プロンプトを作った
第144話 プロンプトを作った
第145話 プロンプトを作った
第146話 プロンプトを作った
第147話 プロンプトを作った
頼んだ: batches/ij66u4mdf2es9zd5f6gh31f8l3vr44hy9jog 枚数 40 (第143・144・145・146・147話・1コマ2枚)
$ python3 vbatch.py status
20261108_002621 batches/ij66u4mdf2es9zd5f6gh31f8l3vr44hy9jog 第143・144・145・146・147話 40 枚 BATCH_STATE_SUCCEEDED {"requestCount": "40", "successfulRequestCount": "40"}
$ python3 vbatch.py fetch
batches/ij66u4mdf2es9zd5f6gh31f8l3vr44hy9jog 回収 40 枚・画像なし 0 ・入力トークン 141378 ・出力トークン 66839
第143話の下書き。上下の段が同じコマの2枚の候補。2コマ目の下の段はハコブくんの形が違う

2枚の候補から選び、5コマを描き直した

2枚の候補から、形・字・大きさがよいほうを選びました。両方ともだめだったのは次のコマです。

コマ見つけた人何がだめだったか直し方
145話 3コマ目Claude
「受ケ取リヲ」のリがひらがなの「り」
描き直すと受話器が2つになったので、最初の候補(縦書き・字は正しい)を使った
146話 2コマ目Claude
手前に、だれのものでもない大きな脚が出た(2枚とも)
足を手前に大きく切る構図をやめ、高杉さんの全身の中で足を見せた
146話 3コマ目Claude
手前のハコブくんの頭が別の形/ハコブくんが2台
手前の頭なめをやめ、1台だけ全身で描いた
146話 2コマ目(2回目)別のエージェント
ハコブくんの目が傾いた困り目になった
「目は縦長の楕円のまま、口だけで困りを出す」を足した
147話 2コマ目別のエージェント
大城さんの体が下で水平に切れ、下が白い空き
人は右と下の端でだけ切れ、体は下の端まで続く、と書いた

何話もまとめて描いたときは、描いた本人とは別のエージェントに1コマずつ見てもらう決まりです(同じ目で見ると見落とすため)。20枚を見てもらい、2コマが要修正、5コマが軽微でした。この時点で、4コマを並べた確認用の絵をエイイチに見せました。

最初にエイイチに見せた第143〜147話。ハコブくんがどのコマでも読者のほうを向き、手前のハコブくんが人より大きく見える

エイイチが4コマを並べて見つけた、5つのずれ

ハコブくんの向きと大きさがずれていた(16コマ)

ハコブくんと患者や看護師の向きや身長などの大きさ変です。修正してください。

原因は2つありました。1つは、ハコブくんがどのコマでも読者のほうを向いていたこと。Gemini は、向きを言わないと顔を読者に向けます。相手と話しているのに、ハコブくんだけがこちらを見ていました。もう1つは、構図に動きを出すため、ハコブくんを手前に大きく入れたコマが多かったこと。手前にあるものは大きく写るので、身長115cm のハコブくんが、181cm の岩田さんの胸や肩まで届いて見えました。

直し方は3つです。

  1. 人とハコブくんの関係を見せるコマは、手前と奥に重ねず、同じ床に並べる。並び図どおりの高さの枠を描いた下地を添える(vbuild の yline)
  2. 向きは「画面は相手のほうへ約60°・輪のある側面が見える」と、見える面の名前で書く(_face.py)
  3. 高さは体の部位でも書く(「岩田さんのベルトの高さ」「大城さんのみぞおちとへその間」)。それでも Gemini は大きめに描くので、候補を測って選ぶ
  • ねらい:ハコブくんが話している相手を向き、並び図(ハコブくん115cm、人は152〜181cm)どおりの大きさに見えるようにする
  • 変わったこと:描き直した16コマで、ハコブくんの画面が相手の側に来た。測った高さの比は、たとえば143話2コマ目が0.66(並び図では0.64)、144話3コマ目が0.63(0.70)
  • 見込める効果:人とロボットの関係(だれに話し、どちらが大きいか)が1コマで読める。次の話から、最初の下書きで向きと大きさがそろうコマが増えるかを、描き直しの枚数で確かめる
向きと大きさを直した13コマの2枚ずつの候補。同じ床に並べても、ハコブくんが人の胸まで届く候補や、別の形のロボットが混ざる

候補の大きさは vbuild.py measure で測りました。絵の中の人とロボットの塊を見つけ、上端と下端から高さの比を出します。カウンターや吹き出しが人とつながると塊が大きくなり、比がずれるので、そのコマは目でも測りました。

$ for a in "143 2" "145 1" "145 2" "145 4" "146 2" "146 4" "144 1" "144 3" "143 4"; do set -- $a; python3 vbuild.py $1 measure $2 8 2>&1 | grep "_01" ; done
第143話_P2_20261108_010101.jpg [(31, 41, 90, 49), (66, 18, 90, 72)] 高さの比 0.68
第143話_P2_20261108_010115.jpg [(29, 28, 93, 65), (75, 19, 95, 76)] 高さの比 0.86
第143話_P2_20261108_010924.jpg [(31, 41, 94, 53), (70, 15, 95, 80)] 高さの比 0.66
第143話_P2_20261108_010929.jpg [(29, 37, 95, 57), (71, 11, 96, 86)] 高さの比 0.66
第145話_P1_20261108_010727.jpg [(45, 19, 99, 79)] 
第145話_P1_20261108_010729.jpg [(44, 5, 99, 94)] 
第145話_P1_20261108_010853.jpg [(28, 31, 88, 57), (79, 17, 100, 83)] 高さの比 0.69
第145話_P1_20261108_010854.jpg [(26, 43, 95, 52), (75, 19, 100, 81)] 高さの比 0.64
第145話_P2_20261108_010722.jpg [(31, 39, 95, 56), (76, 19, 95, 76)] 高さの比 0.74
第145話_P2_20261108_010726.jpg [(29, 34, 96, 62), (69, 20, 97, 77)] 高さの比 0.81
第145話_P2_20261108_010853.jpg [(27, 34, 97, 63), (59, 15, 97, 82)] 高さの比 0.77
第145話_P2_20261108_010901.jpg [(29, 36, 97, 61), (78, 2, 97, 95)] 高さの比 0.64
第145話_P4_20261108_010723.jpg [(46, 13, 97, 83)] 
第145話_P4_20261108_010738.jpg [(50, 15, 91, 75)] 
第145話_P4_20261108_010858.jpg [(46, 11, 95, 85)] 
第145話_P4_20261108_010907.jpg [(55, 9, 97, 88)] 
第146話_P2_20261108_010629.jpg [(52, 22, 88, 66)] 
第146話_P2_20261108_010637.jpg [(51, 19, 86, 68)] 
第146話_P2_20261108_010944.jpg [(52, 14, 95, 81)] 
第146話_P2_20261108_010946.jpg [(52, 17, 96, 79)] 
第146話_P4_20261108_010631.jpg [(54, 17, 93, 76)] 
第146話_P4_20261108_010645.jpg [(56, 11, 89, 78)] 
第146話_P4_20261108_010943.jpg [(53, 20, 91, 71)] 
第146話_P4_20261108_010945.jpg [(53, 14, 96, 82)] 
第144話_P1_20261108_010238.jpg [(17, 45, 84, 39), (66, 22, 85, 63)] 高さの比 0.62
第144話_P1_20261108_010311.jpg [(48, 18, 95, 77)] 
第144話_P3_20261108_010310.jpg [(52, 25, 92, 67)] 
第144話_P3_20261108_010312.jpg [(20, 43, 82, 39), (66, 26, 88, 62)] 高さの比 0.63
第143話_P4_20261108_010103.jpg [(29, 38, 92, 54), (75, 10, 93, 83)] 高さの比 0.65
第143話_P4_20261108_010125.jpg [(31, 42, 96, 54), (74, 11, 96, 85)] 高さの比 0.64

塊が1つしか出ていない行は、人とハコブくんが重なって1つの塊になったコマです。145話1・2コマ目の塊は、カウンターや吹き出しとつながって下端・上端が100や2になっているので、目で測った比(0.67と0.74)で選びました。並べて見直したとき、143話3コマ目・144話4コマ目・147話3コマ目は、手前に入れたハコブくんがまだ大きく見えたので、同じ床で向き合う構図に替えて、もう一度描きました。

第144話の1コマ目と2コマ目がつながっていなかった

144話の1コマ目がこれだと2コマ目と繋がりがおかしい

大きさを直すために1コマ目を同じ床に並べたところ、ハコブくんが車いすの目の前に来ていました。2コマ目では、ハコブくんは配膳車の向こうから来ます。1コマ目を、廊下の奥・配膳車のさらに向こうに小さく見える形に描き直しました。

第147話の2コマ目でドロワーに窓ができていた

147コマ目のハコブくんのドロワーの描写がおかしい

検体を運ぶハコブくんの体に窓ができ、中のカプセルが見えていました。「ドロワーは閉じる・体に窓や透明な所はない・中は見えない」を足して描き直しました。

第144話の配膳車が左右逆で、2人が横を向いていた

1コマ目の台車の置き場所が左右逆(大きさもおかしい) 大城さんと車椅子の向きが変

1コマ目を前から撮っていたため、2コマ目(大城さんの後ろから撮った絵)と配膳車の左右が逆になり、大城さんと車いすは廊下の奥ではなく横を向いていました。2コマ目と同じく2人の後ろから撮り、配膳車は右の壁ぎわ、2人は奥へ進む、と書きました。

第144話は全体の流れと、2コマ目の狭さがおかしかった

全体的に流れが変じゃない? いや2コマ目も変だよ。通れそうに見えるのに、通れるかな(もっと圧迫感を表現しないと) 1コマ目ももう少し描き方修正して、通路は広そうに見えてるよ

並べて読み直すと、3コマ目ではハコブくんが2人と同じ左向きに走り去り、前を横切っていませんでした。配膳車も3・4コマ目で消え、どこで先を譲ったのかが分かりません。2コマ目は廊下が広く見え、「通れるかな…」と言うほど狭くありませんでした。

直し方は、舞台と向きを4コマで固定することです。

コマカメラ2人の向きハコブくん配膳車
12人の後ろ廊下の奥へ奥のすき間から来る(小さく)右の壁ぎわ、2人のすぐ先
2大城さんの肩ごし奥へ(止まって確かめる)すき間に入ってくる
右の壁ぎわ、廊下の大半をふさぐ
3真横画面の左へ左から右へすり抜ける奥の壁ぎわの真ん中
43コマ目と同じ真横画面の左へ(先に通る)左で止まって待つ奥の壁ぎわの真ん中

2コマ目は、両側の壁を近くに描き、配膳車で幅の大半をふさいで、すき間は車いすがぎりぎりにしました。1コマ目も同じ狭い廊下にしました。

直す前の第144話。3コマ目でハコブくんが2人と同じ向きに走り、3・4コマ目に配膳車がない
直したあとの第144話。配膳車が4コマとも同じ場所にあり、2コマ目のすき間は車いすがぎりぎり

4コマを通して読む確認を、絵を描く手順に足した

チェック項目と過去の間違い.md に足した手順(チェック①の2)

1コマずつの確認(チェック①)と、エイイチの確認(チェック②)の間に、Claude が4コマを並べて話として読む段を足しました。

  • ねらい:エイイチがチェック②で見つけていたずれ(向き・大きさ・舞台の物の位置・流れ)を、見せる前に Claude が見つける
  • 変わったこと:4コマを並べて、舞台・位置と向き・セリフと絵・大きさ・描き直したコマの前後・細部の6つを順に見る。チェック項目にも4つ足した
  • 見込める効果:エイイチの指摘と描き直しの枚数が減る(見込み)。次の話で、指摘の数と描き直しの枚数を今回(指摘5回・描き直し66枚)と比べて確かめる

チェック項目と過去の間違い.md(4コマを通して読む確認を足したあとの全文)

06_sec04.md10,633字・151行・markdown
# 縦長4コマ・マンガショートの チェック項目と過去の間違い

このファイルは、4コママンガを描くとき・ショートにするときに**毎回最初に読む**。
間違いが見つかったら、下の「過去の間違い」に1行足し、直し方がプロンプトの決まりになったら「チェック項目」にも足す。
置き場所: iCloud `ハコブくん/4コママンガ作成データ/指示/チェック項目と過去の間違い.md`(正)。vbuild.py の `check` がこのファイルを読んで確認の手順を出す。

---
別のタブで開く保存

YouTube ショート5本を作った

冒頭の動き(motion143x.py)

ショートは、縦長のコマを順に見せ、声と効果音を付けた30秒以内の動画です。冒頭の約3.6秒は、話ごとに違う動きをするハコブくんのアニメにしています(同じ動きを並べない決まり)。5つとも新しく作りました。

話冒頭の動き
143
突っ込んできてピタッと急停止、画面に「止マレマス」
144
通り過ぎかけてバックで戻り、「ドウゾ」とおじぎ
145
大声でぴょんぴょん「オ薬デス!」のあと、縮こまって「…」
146
待っているところを小突かれて×目、「衝撃検知」と出て脇へ移る
147
力をためて猛ダッシュ、戻ってきて「先ヲ越サレ…」
07_sec05.py.txt6,164字・128行・python
"""第143〜147話の冒頭の動き(2026-11-08)。話ごとに違う動き(138〜142・134 とも重ねない)
143 右からまっすぐ突っ込んできて、ピタッと急停止(前のめりに揺れる)→「止マレ/マス」→ 得意げ
144 右から来て、左へスーッと通り過ぎかけ → 気づいてバックで戻る → 小さくおじぎ「ドウゾ」
145 来て止まり、大声でぴょんぴょん「オ薬/デス!」→ 縮こまって小さくなり、目が点で静かに待つ「…」
146 待っているところを左から小突かれ(ガクン・画面がゆれる)→ ×目 →「衝撃/検知」→ 右の空いた所へ移る
147 しゃがんで力をためる → 左へ猛ダッシュ(スピード線)→ 戻ってきて、しょんぼり「先ヲ/越サレ…」"""
import sys
import numpy as np
別のタブで開く保存

声(声.sh)

声は ElevenLabs(文章を人の声で読み上げるサービス)で作りました。鍵は KIOXIA に置いたファイルを tts_eleven.py が読みます。146話の「302号室」は「さんまるに」と読ませています。

第143話の声.sh

#!/bin/bash
# 第143話_止まるまで分からない のショートの声(2026-11-08)。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/岩田/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice Qs0BJHky1uZDKyp7LL2L --name 岩田 --try "ぶつかる…!|止まれるのは知ってるよ。でも、止まるまで分からないのが怖いんだ" || echo "  !岩田 で止まりました"
[ -f "voice_eleven/ハコブ/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "止まれます。安全です|早めに、よける向きを見せます" || echo "  !ハコブ で止まりました"
echo "第143話_止まるまで分からない: $(ls voice_eleven/*/試し_*.mp3 2>/dev/null | wc -l | tr -d " ") 本(4本でそろう)"

第144話の声.sh

#!/bin/bash
# 第144話_お先にどうぞの作法 のショートの声(2026-11-08)。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/大城/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice PrFsT9a2iZebpuiWVeug --name 大城 --try "あ、ハコブくん|通れるかな…|待ってたのに、先に行くの!?" || echo "  !大城 で止まりました"
[ -f "voice_eleven/ハコブ/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "道が、空きましたので|森田さん、お先にどうぞ" || echo "  !ハコブ で止まりました"
[ -f "voice_eleven/森田茂/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice ydyMD3g4SbEeNpF0wahP --name 森田茂 --try "よう分かっとる" || echo "  !森田茂 で止まりました"
echo "第144話_お先にどうぞの作法: $(ls voice_eleven/*/試し_*.mp3 2>/dev/null | wc -l | tr -d " ") 本(6本でそろう)"

第145話の声.sh

#!/bin/bash
# 第145話_電話中なの のショートの声(2026-11-08)。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/大城/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice PrFsT9a2iZebpuiWVeug --name 大城 --try "はい、田中先生。点滴の時間ですね|今、電話中なの見えない?|待っててくれて、ありがと" || echo "  !大城 で止まりました"
[ -f "voice_eleven/ハコブ/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "お薬が、届きました!受け取りを、お願いします!|受け取りを、お願いします|電話が終わるまで、待ちました" || echo "  !ハコブ で止まりました"
[ -f "voice_eleven/田中/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice ydyMD3g4SbEeNpF0wahP --name 田中 --try "え、何?" || echo "  !田中 で止まりました"
echo "第145話_電話中なの: $(ls voice_eleven/*/試し_*.mp3 2>/dev/null | wc -l | tr -d " ") 本(7本でそろう)"

第146話の声.sh

#!/bin/bash
# 第146話_小突かれる場所 のショートの声(2026-11-08)。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/高杉/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice 2zABg81whjrIKypr7XTd --name 高杉 --try "次は、さんまるに号室…!|なんでいつもここに!|…昨日はごめんね" || echo "  !高杉 で止まりました"
[ -f "voice_eleven/ハコブ/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "衝撃を、また検知しました|ここなら、邪魔になりません" || echo "  !ハコブ で止まりました"
[ -f "voice_eleven/白石/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice orQ2eWONBvtrTIvdS8RS --name 白石 --try "故障はない…。壊れにくくする…じゃないな" || echo "  !白石 で止まりました"
echo "第146話_小突かれる場所: $(ls voice_eleven/*/試し_*.mp3 2>/dev/null | wc -l | tr -d " ") 本(6本でそろう)"

第147話の声.sh

#!/bin/bash
# 第147話_気送管との競争 のショートの声(2026-11-08)。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/白石/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice orQ2eWONBvtrTIvdS8RS --name 白石 --try "次は、ハコブくんで検体を検査室へ運びます|勝負する相手を、間違えてた" || echo "  !白石 で止まりました"
[ -f "voice_eleven/大城/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice PrFsT9a2iZebpuiWVeug --name 大城 --try "…検体なら、もうあれがあるけど|よーい、どん!" || echo "  !大城 で止まりました"
[ -f "voice_eleven/ハコブ/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "…先を、越されました|管に入らない物を、運びます" || echo "  !ハコブ で止まりました"
echo "第147話_気送管との競争: $(ls voice_eleven/*/試し_*.mp3 2>/dev/null | wc -l | tr -d " ") 本(6本でそろう)"

ショートの台本(spec_ep143〜147.py)

spec_ep143.py

import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24; common24.BGMAP[143] = "第63話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第143話_止まるまで分からない", 143
HEAD = ['止まれるのに', 'なぜ怖い?']
COMIC = C + "ep143.jpg"                    # 縦長のコマ(1080×1350)を P1〜P4 の順に積んだ画像
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep143", EP)                  # 冒頭の動き(第143話のフォルダの motion143x.py)
SCENES = [
  dict(kind="op", video=anim(EP, "ep143"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_dash.mp3", db=-12), dict(at=0.7, f="se/se_skid.wav", db=-10)]),
  dict(kind="panel", crop=P1, fit=False, min=1.8, words=[W_("まっすぐ来る…", 0.1, 540, 1500, 96, "orange", 0)]),
  dict(kind="panel", crop=P2, fit=False, lines=[ns("岩田", 1, "【ぶつかる】…!")], se=[dict(at=0.0, f="se/se_skid.wav", db=-12)]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(1, "止マレマス。|【安全デス】"), ns("岩田", 2, "止まれるのは知ってるよ。||でも、【止まるまで分からない】|のが怖いんだ")]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(2, "早メニ、|【ヨケル向キ】ヲ見セマス")]),
  end(("止まれるだけでは", 74, "ink", 0.0), ("安心されない", 74, "ink", 0.25), ("早めに", 120, "orange", 0.9), ("よける", 120, "orange", 0.9), ("(遠くで向きを変えて、よける気持ちを見せる)", 34, "gray", 1.8)),
]

spec_ep144.py

import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24; common24.BGMAP[144] = "第67話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第144話_お先にどうぞの作法", 144
HEAD = ['患者さんより', '先に行く?']
COMIC = C + "ep144.jpg"                    # 縦長のコマ(1080×1350)を P1〜P4 の順に積んだ画像
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep144", EP)                  # 冒頭の動き(第143話のフォルダの motion143x.py)
SCENES = [
  dict(kind="op", video=anim(EP, "ep144"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_dash.mp3", db=-12), dict(at=1.1, f="se/se_pikon.mp3", db=-10)]),
  dict(kind="panel", crop=P1, fit=False, lines=[ns("大城", 1, "あ、【ハコブくん】")]),
  dict(kind="panel", crop=P2, fit=False, lines=[ns("大城", 2, "【通れるかな】…")]),
  dict(kind="panel", crop=P3, fit=False, se=[dict(at=0.0, f="se/se_dash.mp3", db=-12)], lines=[ns("大城", 3, "待ってたのに、|【先に行くの】!?"), hk(1, "道ガ、|【空キマシタ】ノデ")]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(2, "森田サン、|【オ先ニドウゾ】"), ns("森田茂", 1, "【よう分かっとる】")]),
  end(("狭い所では", 74, "ink", 0.0), ("患者さんが先", 74, "ink", 0.25), ("譲り合い", 120, "orange", 0.9), ("に加わる", 120, "orange", 0.9), ("(手前で止まって、先に通す)", 34, "gray", 1.8)),
]

spec_ep145.py

import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24; common24.BGMAP[145] = "第90話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第145話_電話中なの", 145
HEAD = ['知らせたのに', 'なぜ怒られる?']
COMIC = C + "ep145.jpg"                    # 縦長のコマ(1080×1350)を P1〜P4 の順に積んだ画像
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep145", EP)                  # 冒頭の動き(第143話のフォルダの motion143x.py)
SCENES = [
  dict(kind="op", video=anim(EP, "ep145"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_dash.mp3", db=-12), dict(at=0.7, f="se/se_pikon.mp3", db=-8)]),
  dict(kind="panel", crop=P1, fit=False, lines=[ns("大城", 1, "はい、田中先生。|【点滴の時間】ですね")]),
  dict(kind="panel", crop=P2, fit=False, lines=[hk(1, "オ薬ガ、届キマシタ!|【受ケ取リヲ、オ願イシマス】!"), ns("大城", 2, "今、【電話中】なの|見えない?")]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(2, "受ケ取リヲ、|【オ願イシマス】"), ns("田中", 1, "え、【何】?", phone=True)]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(3, "電話ガ終ワルマデ、|【待チマシタ】"), ns("大城", 3, "待っててくれて、|【ありがと】")]),
  end(("知らせる時は", 74, "ink", 0.0), ("相手の手を見る", 74, "ink", 0.25), ("待つ", 120, "orange", 0.9), ("のも仕事", 120, "orange", 0.9), ("(急ぎだけ、担当の端末を鳴らす)", 34, "gray", 1.8)),
]

spec_ep146.py

import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24; common24.BGMAP[146] = "第52話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第146話_小突かれる場所", 146
HEAD = ['なぜいつも', '小突かれる?']
COMIC = C + "ep146.jpg"                    # 縦長のコマ(1080×1350)を P1〜P4 の順に積んだ画像
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep146", EP)                  # 冒頭の動き(第143話のフォルダの motion143x.py)
SCENES = [
  dict(kind="op", video=anim(EP, "ep146"), dur=DUR, lines=[], se=[dict(at=0.9, f="se/se_don.mp3", db=-8), dict(at=1.7, f="se/se_pikon.mp3", db=-10)]),
  dict(kind="panel", crop=P1, fit=False, se=[dict(at=0.0, f="se/se_pikon.mp3", db=-10)], lines=[ns("高杉", 1, "次は【302号室】…!")]),
  dict(kind="panel", crop=P2, fit=False, se=[dict(at=0.0, f="se/se_don.mp3", db=-10)], lines=[ns("高杉", 2, "なんで|【いつもここに】!")]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(1, "衝撃ヲ、|【マタ検知】シマシタ"), ns("白石", 1, "故障はない…。||【壊れにくくする】…|じゃないな")]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(2, "ココナラ、|【邪魔ニナリマセン】"), ns("高杉", 3, "…昨日は|【ごめんね】")]),
  end(("小突かれた記録は", 74, "ink", 0.0), ("邪魔の手がかり", 74, "ink", 0.25), ("待つ場所", 120, "orange", 0.9), ("を変える", 120, "orange", 0.9), ("(壊れにくくする前に、原因を探す)", 34, "gray", 1.8)),
]

spec_ep147.py

import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24; common24.BGMAP[147] = "第85話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第147話_気送管との競争", 147
HEAD = ['ロボット対', '気送管']
COMIC = C + "ep147.jpg"                    # 縦長のコマ(1080×1350)を P1〜P4 の順に積んだ画像
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep147", EP)                  # 冒頭の動き(第143話のフォルダの motion143x.py)
SCENES = [
  dict(kind="op", video=anim(EP, "ep147"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_rev.mp3", db=-14), dict(at=0.8, f="se/se_dash.mp3", db=-10)]),
  dict(kind="panel", crop=P1, fit=False, lines=[ns("白石", 1, "次は、ハコブくんで|【検体を検査室へ】運びます"), ns("大城", 1, "…検体なら、|【もうあれがある】けど")]),
  dict(kind="panel", crop=P2, fit=False, se=[dict(at=1.0, f="se/se_dash.mp3", db=-10)], lines=[ns("大城", 2, "【よーい、どん】!")]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(1, "…先ヲ、|【越サレマシタ】"), ns("白石", 2, "勝負する相手を、|【間違えてた】")]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(2, "【管ニ入ラナイ物】ヲ、|運ビマス")]),
  end(("比べる相手は", 74, "ink", 0.0), ("人だけじゃない", 74, "ink", 0.25), ("今ある設備", 120, "orange", 0.9), ("と分ける", 120, "orange", 0.9), ("(管に入らない物を、ロボットで運ぶ)", 34, "gray", 1.8)),
]

書き出したショートの長さと、冒頭3秒の音量

できあがり: out_第144話_お先にどうぞの作法.mp4 20.8 秒
できあがり: out_第143話_止まるまで分からない.mp4 22.4 秒
できあがり: out_第147話_気送管との競争.mp4 26.2 秒
できあがり: out_第146話_小突かれる場所.mp4 25.1 秒
できあがり: out_第145話_電話中なの.mp4 27.2 秒

$ for e in 143 144 145 146 147; do ... ffmpeg -t 3 -i ... -af volumedetect ...; done
143 mean_volume: -20.5 dB max_volume: -1.2 dB 
144 mean_volume: -20.9 dB max_volume: -1.3 dB 
145 mean_volume: -21.0 dB max_volume: -1.3 dB 
146 mean_volume: -18.7 dB max_volume: -1.5 dB 
147 mean_volume: -20.5 dB max_volume: -1.4 dB 

前に、作業環境に効果音のリンクがなく冒頭が無音で書き出されたことがあったので(第134〜137話)、冒頭3秒の音量を測っています。5本とも鳴っています。

第143話のショートを1.4秒ごとに並べたコマ。冒頭の動き、4コマ、締めの言葉の順

表紙・英語版を作り、サイトに10ページを公開した

表紙(cover_edit_143.py と make_covers.py)

表紙は、見せ場のコマから吹き出しを Gemini の編集で消し、上に「第N話 題」を重ねて作ります(LOG413)。

cover_edit_143.py(吹き出しを消す)

"""縦長4コマの表紙用:選んだコマから吹き出しと字を消す(Gemini の API・2026-10-31)"""
import sys, os, concurrent.futures
sys.path.insert(0, os.path.expanduser('~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/道具'))
import vbuild as V
PICK = {143:2,144:3,145:2,146:2,147:2}
NOEDIT = {133,128,116,111,101,99,76}
PROMPT = """Edit this manga panel. Remove EVERY speech bubble and thought bubble completely, including their tails and all the text inside them (ALSO the speech bubble inside the small square inset window in the corner: erase that bubble entirely so the inset shows only the person in the room). Where a bubble was, redraw what would be behind it: plain white background, or the continuation of the wall, hair, body or object that the bubble covered. Keep sound-effect lettering drawn directly on the art (like バチン, カラン, ジリリリ) as it is. Keep EVERYTHING else exactly the same: same characters, faces, eyes, poses, sizes, positions, clothes, robots, screentone, line style, black-and-white manga look, white background, 4:5 framing. Do not add any new text, bubble, border, inset window, portrait or object. Where the bubble was, leave plain white background (and continue the thin vertical line if it passed behind the bubble)."""
here = os.path.dirname(os.path.abspath(__file__))
open(here + '/cover_edit/_prompt.txt', 'w', encoding='utf-8').write(PROMPT)
eps = [int(a) for a in sys.argv[1:]] or [e for e in PICK if e not in NOEDIT]
def job(e):
    src = os.path.expanduser(f'~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/第{e}話/仕上げ/第{e}話_P{PICK[e]}_1080x1350.png')
    return V.gen_one(here + '/cover_edit/_prompt.txt', here + f'/cover_edit/ep{e}_P{PICK[e]}', [("the manga panel to edit", src)], "4:5")
with concurrent.futures.ThreadPoolExecutor(6) as ex:
    for r in ex.map(job, eps): print(r)

make_covers.py(題を重ねて、表紙とコマの webp を書き出す)

"""第143〜147話の表紙(2026-11-08):上に「第N話 タイトル」、下に吹き出しを消したコマ(covers_v2/make_covers.py と同じ組み方)"""
from PIL import Image, ImageDraw, ImageFont
import numpy as np, glob
FB='/usr/share/fonts/opentype/noto/NotoSansCJK-Black.ttc'
T={143:'止まるまで分からない',144:'お先にどうぞの作法',145:'電話中なの',146:'小突かれる場所',147:'気送管との競争'}
PICK={143:2,144:3,145:2,146:2,147:2}
W,H=1080,1350; X0=56; MAXW=W-2*X0
font=ImageFont.truetype(FB,96,index=0)
for e,k in PICK.items():
    src=glob.glob(f'_stage143/ep{e}_P{k}_*.jpg')[0]
    p=Image.open(src).convert('RGB').resize((W,H),Image.LANCZOS)
    a=np.asarray(p).astype(np.int16); a[a.min(2)>232]=255; p=Image.fromarray(a.astype(np.uint8))
    head=f'第{e}話'; t=T[e]; one=f'{head} {t}'
    lines=[one] if font.getlength(one)<=MAXW else [head,t]
    c=Image.new('RGB',(W,H),'white'); d=ImageDraw.Draw(c); y=62
    for ln in lines:
        f=font
        while f.getlength(ln)>MAXW: f=ImageFont.truetype(FB,f.size-4,index=0)
        d.text((X0,y),ln,font=f,fill=(36,36,36)); y+=f.size+18
    top=y+40; A=H-top
    g=np.asarray(p.convert('L'))<235; ys=np.where(g.any(1))[0]; xs=np.where(g.any(0))[0]
    cy0,cy1,cx0,cx1=ys.min(),ys.max(),xs.min(),xs.max(); ch=cy1-cy0
    s=min(A*0.94/ch,1.45) if ch<A else A/ch
    q=p.resize((round(W*s),round(H*s)),Image.LANCZOS)
    mx=(cx0+cx1)/2*s; my=(cy0+cy1)/2*s
    if s>=1:
        L=int(min(max(mx-W/2,0),q.width-W)); Tp=int(min(max(my-A/2,0),q.height-A))
        b=Image.new('RGB',(W,A),'white'); b.paste(q.crop((L,Tp,min(q.width,L+W),min(q.height,Tp+A))),(0,0)); c.paste(b,(0,top))
    else:
        t0=int(cy0*s); b=Image.new('RGB',(q.width,A),'white'); b.paste(q.crop((0,t0,q.width,min(q.height,t0+A))),(0,0)); c.paste(b,((W-q.width)//2,top))
    c.save(f'media/manga_ep{e}_cover.webp','WEBP',quality=88,method=6)
    for i in range(1,5):
        Image.open(f'_stage143/第{e}話_P{i}_1080x1350.png').convert('RGB').save(f'media/manga_ep{e}_p{i}.webp','WEBP',quality=88,method=6)
S=Image.new('RGB',(5*370,460),'#888')
for i,e in enumerate(PICK): S.paste(Image.open(f'media/manga_ep{e}_cover.webp').resize((360,450)),(i*370,0))
S.save('covers.jpg',quality=85)
第143〜147話の表紙

英語版(en_manga.py の指定)

英語版は描き直さず、吹き出しの中の日本語を消して英語を組みます(LOG416 の en_manga.py)。seed は各吹き出しの白い内側の1点で、白い閉じた領域を探すプログラムで座標を出してから、どの吹き出しかを見て当てました。

英語版の指定 ep143.json

{"ep": 143, "cover_line": "STOPPING ISN'T THE SCARY PART.", "panels": [{"bubbles": [], "signs": []}, {"bubbles": [{"seed": [928, 215], "text": "It's going\nto hit me…!"}], "signs": []}, {"bubbles": [{"seed": [383, 409], "text": "I can stop.\nI am safe."}, {"seed": [759, 127], "text": "I know you can stop.\nWhat scares me is not knowing\nuntil you actually do."}], "signs": []}, {"bubbles": [{"seed": [431, 267], "text": "I will turn early,\nso you can see\nI'm moving aside."}], "signs": []}]}

英語版の指定 ep144.json

{"ep": 144, "cover_line": "I WAS WAITING, AND YOU WENT FIRST?!", "panels": [{"bubbles": [{"seed": [481, 196], "text": "Oh, Hakobukun."}], "signs": []}, {"bubbles": [{"seed": [624, 165], "text": "Can we fit\nthrough there…?"}], "signs": []}, {"bubbles": [{"seed": [121, 183], "text": "The way\nwas clear."}, {"seed": [731, 185], "text": "I was waiting,\nand you went first?!"}], "signs": []}, {"bubbles": [{"seed": [147, 223], "text": "Mr. Morita,\nafter you."}, {"seed": [744, 199], "text": "Now you've\ngot it."}], "signs": []}]}

英語版の指定 ep145.json

{"ep": 145, "cover_line": "CAN'T YOU SEE I'M ON THE PHONE?", "panels": [{"bubbles": [{"seed": [626, 147], "text": "Yes, Dr. Tanaka.\nTime for the IV, right?"}], "signs": []}, {"bubbles": [{"seed": [564, 180], "text": "Your medicine\nhas arrived!\nPlease sign for it!"}, {"seed": [723, 138], "text": "Can't you see\nI'm on the phone?"}], "signs": []}, {"bubbles": [{"seed": [282, 332], "text": "Please sign\nfor it."}, {"seed": [925, 250], "text": "Huh?\nWhat?"}], "signs": []}, {"bubbles": [{"seed": [447, 152], "text": "I waited until\nyour call ended."}, {"seed": [614, 342], "text": "Thanks for\nwaiting."}], "signs": []}]}

英語版の指定 ep146.json

{"ep": 146, "cover_line": "WHY ARE YOU ALWAYS RIGHT HERE?!", "panels": [{"bubbles": [{"seed": [903, 172], "text": "Room 302\nnext…!"}], "signs": []}, {"bubbles": [{"seed": [989, 246], "text": "Why are you\nalways\nright here?!"}], "signs": []}, {"bubbles": [{"seed": [163, 209], "text": "Impact detected\nagain."}, {"seed": [894, 228], "text": "Nothing's broken…\nMaking it tougher…\nisn't the answer."}], "signs": []}, {"bubbles": [{"seed": [400, 153], "text": "…Sorry about\nyesterday."}, {"seed": [897, 240], "text": "Here, I will not\nbe in the way."}], "signs": []}]}

英語版の指定 ep147.json

{"ep": 147, "cover_line": "…I WAS BEATEN.", "panels": [{"bubbles": [{"seed": [110, 177], "text": "Next, Hakobukun will\ncarry specimens\nto the lab."}, {"seed": [809, 213], "text": "…We already have\nsomething for\nspecimens."}], "signs": []}, {"bubbles": [{"seed": [683, 123], "text": "Ready… go!"}], "signs": []}, {"bubbles": [{"seed": [143, 258], "text": "…I was\nbeaten."}, {"seed": [920, 259], "text": "We picked\nthe wrong\nopponent."}], "signs": []}, {"bubbles": [{"seed": [584, 223], "text": "I will carry\nwhat doesn't fit\nin the tube."}], "signs": []}]}
第143〜147話の英語版。各段の左4つが英語にしたコマ、右端が英語版の表紙

R2 に画像を入れ、ページを公開した

$ python3 upload_media.py media_ep143_147 media_ep143_147_en
入れる 50・入っている 0・名前の重なり 0・合計 8.0MB
  50/50
おわり
$ for e in 143 144 145 146 147; do echo "$e $(curl ... /manga/ep$e) $(curl ... /english-4-koma/ep$e) $(curl ... /media/manga_ep${e}_cover.webp) $(curl ... /media/manga_ep${e}en_p4.webp)"; done
143 200 200 200 200
144 200 200 200 200
145 200 200 200 200
146 200 200 200 200
147 200 200 200 200
話日本語のページの題英語のページの題
143
止まれることと、止まると分かることは違う
Stopping Is Not the Same as Showing You’ll Stop
144
廊下の譲り合いに、ロボットも加わる
Robots Join the Hallway Etiquette Too
145
知らせる時機は、相手の手のふさがり具合で選ぶ
Choosing When to Speak Up
146
小突かれた記録は、邪魔の元を探す手がかり
When a Robot Gets Kicked, Look for the Cause
147
置き換える相手は、人だけではない
Robots Don’t Only Replace People

ページは pixelstomotors.com/manga/ep143 〜 ep147 と pixelstomotors.com/english-4-koma/ep143 〜 ep147 です。p2m-site への commit は 18a6f4b です。

SNS の予約

話
日本語の X・LinkedIn(日本時間)
英語の X(米国東部時間)
14312/7 20:002/8 11:00
14412/8 20:002/9 11:00
14512/9 20:002/10 11:00
14612/12 19:302/11 11:00
14712/13 19:302/12 11:00

マンガの投稿文は r2r2r-content の sns/MANGA_ep143〜147_sns_posts.json、ショートの X の型は sns/SHORT_ep143〜147_sns_posts.json です(commit 59cc07f)。進捗管理表(Google スプレッドシート)の No.143〜147 は「完了」にしました。

Gemini に描かせた枚数は120枚、見積もりで約1,100円

頼んだもの頼み方枚数見積もり
5話の下書き(1コマ2枚)バッチ40約220円
1コマずつの確認で描き直しふつう9約100円
向きと大きさの描き直しふつう47約520円
第144話の流れと147話のドロワーの描き直し
ふつう19約210円
表紙の吹き出しを消す編集ふつう5約55円
合計120約1,100円

前の第138〜142話は5話で67枚・約460円(LOG448)でした。今回は、4コマを並べて見つかったずれの直しが66枚(向きと大きさ47枚・流れとドロワー19枚)あり、2倍以上かかりました。向きと大きさの47枚には、途中で止まった頼みの3枚を含みます。

次の話も、描いたら4コマを並べて話として読んでから見せる

次は第148話からです。描いたら、1コマずつの確認のあとに、4コマを並べて話として読む確認を通してから、エイイチに見せます。エイイチの指摘の数と、描き直しの枚数を今回と比べます。