ブラウザだけで AI 動画生成→OpenCV 加工する
AI で動画を作れるようにはなりましたが、生成した動画をちょっといじる となると、途端に面倒になります。Wan や Sora で作った mp4 を一度ローカルに保存して、Premiere や CapCut、あるいは ffmpeg で編集する……というのが今の一般的な流れでしょうか。
ただ、実は生成から OpenCV 加工、動画書き出しまで ブラウザのタブ 1 枚で完結 させる方法があります。OpenRouter の動画生成 API と、HTML 1 枚で動く Python 実行環境 fudebako (私が開発しています) を組み合わせて、その一連の流れを通してみます。
完成イメージはこんな感じです。左が AI で生成した素の 5 秒動画、右が fudebako の中で OpenCV 加工とテロップを乗せた Shorts 風の動画。インストール不要、外部ツールもなし。ブラウザだけで完結しています。
全体像
流れは 3 ステップです。
- OpenRouter Wan 2.6 で動画を生成(API を叩く)
- fudebako で frame 抽出 + OpenCV 加工(cv2.bilateralFilter など)
-
cv2.VideoWriterで WebM 書き出し(標準 OpenCV API、ffmpeg 不要)
順番に解説します。
ステップ 1: OpenRouter Wan 2.6 で動画生成
OpenRouter は LLM のイメージが強いですが、2026 年 4 月から動画生成 API も追加されています。alibaba/wan-2.6 や google/veo-3.1-fast といったモデルが、共通のスキーマで叩けるようになっています。
fudebako のセルから普通に POST します。OpenRouter の動画 API は非同期ジョブ方式なので、投げたあとに polling して、完了したら mp4 の URL を取って保存する、という流れになります。
import os, json, asyncio
from pyodide.http import pyfetch
API_KEY = os.environ["OPENROUTER_API_KEY"]
API_BASE = "https://openrouter.ai/api/v1/videos"
PROMPT = (
"A black Japanese pencil case (筆箱) sitting on a wooden desk softly opens. "
"Pastel-colored Python code snippets float out like holographic ribbons. "
"Warm soft daylight, shallow depth of field, slow cinematic dolly-in."
)
# Submit
r = await pyfetch(API_BASE, method="POST",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
body=json.dumps({
"model": "alibaba/wan-2.6",
"prompt": PROMPT,
"aspect_ratio": "9:16",
"resolution": "1080p",
"duration": 5,
"generate_audio": False,
}))
job = await r.json()
poll_url = job["polling_url"]
# Poll until completed
for _ in range(60):
await asyncio.sleep(10)
pr = await pyfetch(poll_url, headers={"Authorization": f"Bearer {API_KEY}"})
status = await pr.json()
if status["status"] == "completed":
urls = status["unsigned_urls"]
break
# Download (Bearer 認証必須 — unsigned URL でも実は要)
dl = await pyfetch(urls[0], headers={"Authorization": f"Bearer {API_KEY}"})
data = await dl.bytes()
with open("/drive/wan26.mp4", "wb") as f:
f.write(data)
5 秒・1080p・9:16・音声なしで、実測 $0.60 / 約 70 秒 でした。1080p や縦長指定で倍率がかかるらしく、公開されている「\$0.04 per second」よりは少し高めに出る印象です(5s で \$0.20 想定が $0.60 だった)。
ハマりどころ:
- API キーは fudebako の設定(歯車アイコン)→ 環境変数に
OPENROUTER_API_KEYを入れておけば、Python 側からos.environで読めます。 -
unsigned_urlsと書いてありますが、ダウンロード時にも Authorization ヘッダが必要です(最初これに気づかず、エラーメッセージの JSON を動画だと思って保存してしまいました)。 -
/drive/に保存すると Drive タブからダウンロードできます。v0.4.16 からはプレビュー再生も可能です。
ステップ 2: 動画 → frame 抽出
cv2.VideoCapture は ブラウザ環境では動きません。代わりに、HTML5 の <video> 要素と Canvas を使って frame を抜き出し、/drive/frames_in/ に PNG で保存します。fudebakoにはjsを実行する run_js があるのでこれを使います。
from pyodide.code import run_js
# JS で /drive/wan26.mp4 を <video> に流し込み、各 currentTime で
# canvas に描画 → PNG として /drive/frames_in/ に書き戻し
js = """
(async () => {
const bytes = await fudebako.drive.readBytes("/drive/wan26.mp4");
const blob = new Blob([bytes], {type: "video/mp4"});
const url = URL.createObjectURL(blob);
const video = document.createElement("video");
video.src = url; video.muted = true;
await new Promise(r => video.addEventListener("loadedmetadata", r, {once: true}));
const FPS = 30, W = 540, H = 960;
const N = Math.round(video.duration * FPS);
const canvas = new OffscreenCanvas(W, H);
const ctx = canvas.getContext("2d");
for (let i = 0; i < N; i++) {
video.currentTime = i / FPS;
await new Promise(r => video.addEventListener("seeked", r, {once: true}));
ctx.drawImage(video, 0, 0, W, H);
const pngBlob = await canvas.convertToBlob({type: "image/png"});
const ab = await pngBlob.arrayBuffer();
const name = `/drive/frames_in/frame_${String(i).padStart(3, "0")}.png`;
await fudebako.drive.write(name, new Uint8Array(ab));
}
URL.revokeObjectURL(url);
return N;
})()
"""
n = await run_js(js)
print(f"extracted {n} frames")
5 秒 / 30fps の動画を 540×960 に落としながら 150 枚抜き出すのに 約 8 秒。1080×1920 のままだと PNG が重すぎるので、SNS 用ならこのくらいのサイズで十分でしょう。
ステップ 3: OpenCV で per-frame 加工 + cv2.VideoWriter で書き出し
ここからは普通の OpenCV のコードです。fudebako の v0.4.16 から cv2.VideoWriter がそのまま動く ようになったので、out.write(bgr) で 1 フレームずつ流し込めば、/drive/shorts.webm が出来上がります。中身は WebCodecs API + WebM コンテナですが、書く側のコードはローカル Python で書く OpenCV と同じです。
日本語テロップには fudebako.text.render を使います。v0.4.16 で追加した API で、ブラウザの Canvas 経由で文字を焼いてくれます。Pyodide 同梱のフォントは日本語が化けますが、fudebakoはしっかり日本語対応しています。
import cv2, glob
import numpy as np
import fudebako
JP_FONT = "Hiragino Sans, Yu Gothic, Noto Sans CJK JP, sans-serif"
H, W, FPS = 960, 540, 30
# テロップ PNG を 30 ms で生成(マルチライン対応、stroke 付き)
ov = fudebako.text.render(
"fudebako で\nOpenCV 加工",
width=540, height=200, font_size=50, font_family=JP_FONT,
fill="#FFFFFF", stroke="#000000", stroke_width=7,
align="center", baseline="top",
)
ov_bgra = cv2.imdecode(np.frombuffer(ov, dtype=np.uint8), cv2.IMREAD_UNCHANGED)
# 標準 OpenCV API で WebM 書き出し
out = cv2.VideoWriter("/drive/shorts.webm", cv2.VideoWriter_fourcc(*"VP80"), FPS, (W, H))
paths = sorted(glob.glob("/drive/frames_in/frame_*.png"))
for p in paths:
bgr = cv2.imread(p)
# HSV boost で彩度アップ
hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV).astype(np.float32)
hsv[:, :, 1] = np.clip(hsv[:, :, 1] * 1.25, 0, 255)
bgr = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
# 下部に日本語テロップ合成
a = ov_bgra[:, :, 3:4].astype(np.float32) / 255.0
sub = bgr[H-240:H-40, :].astype(np.float32)
bgr[H-240:H-40, :] = (sub * (1 - a) + ov_bgra[:, :, :3].astype(np.float32) * a).astype(np.uint8)
out.write(bgr)
out.release()
150 フレーム分の処理+エンコードで 約 6 秒。出力は 1.3 MB ほど。ホスト側の ffmpegなど 一切使うことなく編集できました。
ステップ 4: Drive タブでそのままプレビュー
書き出した shorts.webm は Drive タブからクリックするだけでプレビュー再生できます。ホストにダウンロードする前に、ブラウザの中で内容を確認できるのは、何回も加工パラメタを変えながら回すときに地味に効きます。
満足するまでブラウザの中だけでイテレーションを回して、最終版だけを ↓ ボタンでホストへダウンロードする、という使い方ができます。
OpenCVを使った応用:漫画化(cartoonize)
加工部分を書き換えるだけで、色々な表現が試せます。たとえば cv2.bilateralFilter と cv2.adaptiveThreshold を組み合わせれば漫画風になります。
def cartoonize(img):
color = img.copy()
for _ in range(2):
color = cv2.bilateralFilter(color, d=9, sigmaColor=200, sigmaSpace=80)
g = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
g = cv2.medianBlur(g, 7)
edges = cv2.adaptiveThreshold(
g, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 2)
em = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
return cv2.bitwise_and(color, em)
bilateral filter が重いので 150 フレームで 26 秒ほどかかりましたが、ブラウザを閉じずにそのまま webm として取り出せるのはやはり便利です。
制約・注意点
正直なところ、まだ不便な点もあります。
-
cv2.VideoCaptureが未実装:動画の読み込みに JS を介する必要があるのは今後の課題です。 - コーデックは WebM が基本:H.264 mp4 の書き出しはブラウザ側のサポート状況(特に Safari)に左右されます。SNS に上げるなら、Chrome で WebM を作るのが一番安定します。
- Wan 2.6 のコスト:前述の通り、高解像度だと意外と料金がかかります。OpenRouter のダッシュボードで予算制限をかけておくのが無難です。
- API キーの扱い:localStorage に保存されるので、共有 PC などでは注意してください。
まとめ
OpenRouter で動画を生成して、fudebako の中で OpenCV 加工、そして動画保存までをブラウザ 1 枚で通してみました。
-
生成: OpenRouter の API を
pyfetchで叩く -
抽出:
<video>+ Canvas でフレームを抜き出す -
加工 + 出力: 標準 OpenCV API(
cv2.VideoWriter)で WebM 保存 - 確認: Drive タブのプレビューでそのまま再生
「AI で生成して、自分でちょっと加工して、そのまま SNS へ」というサイクルを 1 タブの中で完結させたい場合には、なかなか面白い組み合わせではないでしょうか。
関連リンク
- fudebako: https://github.com/jugoya-ai/fudebako
- OpenRouter Video API: https://openrouter.ai/docs/api/api-reference/video-generation/create-videos
- 関連記事: Photoshop 不要|1 枚の HTML で画像の背景除去を行う方法





