
縦型ショート動画をコマンド2つで作った。時間がかかったのは全部、日本語のほうだった
縦型のショート動画を4本作りました。絵も声もありません。文字だけです。
作るのに使ったコマンドは、2つです。
VIDEO_TOPIC=akachan python3 slide_text.py # 7枚のPNGを作る
python3 build_video.py --sec 3.6 # つないで mp4 にする
数秒で終わります。 詰まったのは、全部そのあとでした。
同じものを作る手順
用意するもの
pip install pillow imageio-ffmpeg
この2つだけです。 imageio-ffmpeg は、動画をつなぐ ffmpeg を同梱してくれます。
日本語フォントも要ります。Ubuntu / Debian ならこれで入ります。
sudo apt install fonts-ipafont-gothic
Windows なら C:/Windows/Fonts/meiryo.ttc、Mac なら
/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc を指定すれば動きます。
コピペで動く最小版
これを mini.py として保存して実行すると、7枚のPNGと mp4 が出ます。
import os, glob, subprocess
from PIL import Image, ImageDraw, ImageFont
import imageio_ffmpeg
FONT = "/usr/share/fonts/truetype/fonts-japanese-gothic.ttf" # 環境に合わせて変える
W, H = 1080, 1920 # 縦型(9:16)
BG = (40, 42, 50) # 背景
INK = (240, 240, 243) # 文字
SEC = 3.6 # 1枚あたりの表示秒
SLIDES = [
("赤ちゃんは\n美人が好き", "らしい。1987年の実験"),
("実験", "生後6か月の赤ちゃんに、\n顔写真を 2枚ずつ見せた。"),
("結果", "美人のほうを 長く見た。\n7.24秒 と 6.59秒。"),
("0.65秒", "……差、これだけです。"),
("しかも", "3割の赤ちゃんは、\n逆を 長く見ていた。"),
("つまり", "「赤ちゃんは美人が好き」とは、\nまだ言えない。"),
]
os.makedirs("frames", exist_ok=True)
for i, (head, body) in enumerate(SLIDES):
im = Image.new("RGB", (W, H), BG)
d = ImageDraw.Draw(im)
d.multiline_text((W//2, H//2 - 160), head, font=ImageFont.truetype(FONT, 96),
fill=INK, anchor="mm", align="center", spacing=24)
d.multiline_text((W//2, H//2 + 120), body, font=ImageFont.truetype(FONT, 52),
fill=INK, anchor="mm", align="center", spacing=20)
im.save(f"frames/{i:02d}.png")
with open("frames/list.txt", "w") as f: # ffmpeg の concat 用リスト
for p in sorted(glob.glob("frames/*.png")):
f.write(f"file '{os.path.basename(p)}'\nduration {SEC}\n")
f.write(f"file '{os.path.basename(p)}'\n") # 最後の1枚はもう一度書く
subprocess.run([imageio_ffmpeg.get_ffmpeg_exe(), "-y", "-f", "concat",
"-i", "frames/list.txt", "-vf", f"scale={W}:{H}",
"-c:v", "libx264", "-pix_fmt", "yuv420p", "-r", "30", "out.mp4"])
python3 mini.py # → out.mp4
ここまでで完成します。 あとは SLIDES の中身を書き換えるだけです。
つまずくとしたら3か所
| フォントのパス | ここが違うと cannot open resource で止まります。実在するパスを指定してください |
| concat の最後の1枚 | リストの末尾で最後のフレームをもう一度書かないと、最後の1枚が一瞬で消えます(concat の仕様) |
| H.264 が使えない | 環境の ffmpeg が軽量版だと libx264 がありません。imageio_ffmpeg.get_ffmpeg_exe() で同梱版を呼べば通ります |
実際に使っているほうは
上のは説明用に削った版です。実際のスクリプトでは、これを足しています。
- テーマを辞書にして、
VIDEO_TOPIC=akachanのように環境変数で切り替える - 文字がはみ出したら自動でフォントサイズを下げる
- 見出しにバッジ(丸い枠)を付ける
- Shorts のボタン列を避けるため、文字の幅を 780px に制限する(下記②)
中身のほうで決めること5つ
ここからは、コードの話ではありません。1枚に何を書くかの話です。
技術で困ったのは、上の「ffmpegが軽量版だった」1件だけでした。残りの時間は全部、ここに使っています。
① 専門用語は、1枚まるごと使う覚悟がないなら入れない
論文の数字を扱うと、こういう表記がついてきます。
p < .001
相関 0.274
メタ分析
95%信頼区間
p < .001 は「偶然でこうなる確率は 0.1% 未満」という意味です。論文では、その結果が偶然かどうかを示すために必ず書きます。書いてあること自体は正しいです。
ただ、動画にこれを出すと、意味を説明するのに1枚まるごと必要になります。7枚しかないので、そこに1枚は使えません。
数字は残せます。消すのは、数字の呼び名のほうです。
✕ 相関は0.274(p<.001)で有意
◎ 0.274。「強い関係」の目安は0.3。ギリギリ届かない
② 文字は、画面の右端から離す
Shortsは画面の右側に、高評価・コメント・保存・共有のボタンが縦に並びます。そこに文字が隠れます。
SAFE_W = 780 # 文字を収める幅。画面は1080px
カードや背景まで細くしないでください。 文字も一緒に小さくなって、前より読みにくくなります。枠は広いまま、文字の折り返し幅だけ狭めます。
そして本当の直し方は、幅ではありません。文を短くするほうです。
③ 問いは、7枚で答えられる大きさにする
7枚で答えきれない問いを1枚目に置くと、最後まで見ても答えが出ません。
✕ 疲れた日、なぜか食べてしまう。なぜ? ← 原因が複数ある。1本に収まらない
◎ 疲れた日に食べてしまうのは、ストレスのせい? ← 「はい/いいえ」で閉じられる
大きい問いは、割るのではなく縮めます。 割ると、1本目が答えのないまま終わります。
④ 締めは、事実か解釈かを分ける
最後の1枚は、言いたくなることではなく、言えることだけにします。
✕ 赤ちゃんが美を知ってるんじゃない。大人と少し重なっただけ
→ 「重なった」は解釈。実験が測ったのは秒数だけ
◎ 「赤ちゃんは美人が好き」とは、まだ言えない
→ 証拠がどこまで届いているか、という事実
判定の仕方は1つです。 その文が、実験で測ったものだけで言えるか。言えないなら解釈です。
⑤ 中の5枚は、実験の説明に使わない
✕ 16人 → 15分後 → 休息モード → でも → しかも
これは実験の手順です。 見ている人には、話がどこまで進んだのか分かりません。
5枚は、問いがどこまで進んだかに使います。「ふつうの答え → でも → 本当は → 数字 → ただし」の順です。
決まった型
7枚を、こう並べることにしました。
1 なぜ 実際に起きる場面 +「なぜ?」 ← 抽象語を使わない
2 ふつうの答え みんなが思っている説明
3 でも それが崩れる事実
4 本当は 原因
5 数字 ここで初めて根拠を出す
6 ただし どこまで言えるか
7 つまり 1枚目の「なぜ」に、そのまま答える
1枚目と7枚目が対になります。根拠は真ん中に置きます。
そして5枚目が空でも、他の6枚は先に作れます。 実際、調べ物と並行して作っています。
まとめ
技術 コマンド2つ。数秒
決めること 5つ。全部、書いてある日本語の話
「AIで動画を作った」の中身は、ほとんど日本語の作業でした。
技術は再現できます。再現できないのは、1枚に何を書くかのほうです。