2021年製MacBookでGemma 4-31Bを使い、1年分の動画をローカルでインデックス化する(スワップ50GB)
(blog.simbastack.com)- 動画アーカイブのボトルネックは編集ツールより検索不能であることにあり、ラベルのないクリップを英語で問い合わせ可能なインデックスに変えることに注力した
- ローカル優先設計として、各クリップの横に
.description.mdのサイドカーファイルを作成し、rating・照明・場所・文字起こし・キーワード・散文的な説明を1回のビジョン呼び出しで抽出した - パイプラインは
ffprobe、exiftool、Nominatim、ffmpeg、WhisperX、insightface、ビジョンモデルを束ね、メタデータ・GPS・フレーム・文字起こし・顔埋め込みを生成する - 2021年製16インチMacBook Pro M1 Max 64GBがLM Studioで Gemma 4 31B Q4 を実行し、大量処理中のスワップは最大50.89GBに達した
- 構造化スキーマと enum 制約 はハルシネーションを減らし、大量インデックスはローカル31Bで処理したうえで、難しい10〜20%だけをクラウドモデルで再評価する構成が可能だった
問題の出発点: 編集より検索
- Maasai Maraに半年近く滞在するあいだ、iPhone、DJI Pocket、ドローン、Nikon Z8、Ray-Ban Metaで撮影した動画が増え続けたが、その大半は見返されないまま残っていた
- Mara Hilltop のソーシャルチャンネルは、コンテンツ不足ではなく 編集時間 の不足によって3か月止まっていた
- Claude CodeとOpus 4.5/4.6によって開発作業は長時間のエージェント実行と並列作業が可能になり、KaribuKitの最初の有料宿泊施設の公開とも重なって、動画編集に使える時間はさらに減った
- 最初に思いついた解決策は、Eddie AI、Higgsfield MCP、Submagic、Bufferを組み合わせた月額 $140 のSaaSスタック だったが、実際のボトルネックには合っていなかった
- 生成AI動画は実際の旅行ブランドにそぐわず、宿泊客が実在する場所を期待している状況では、誤って表示されたAIシーンは信頼を損なう可能性があった
- 現実的な投稿頻度は週3〜5本ではなく週2〜3本に近く、当初計画は2週目から破綻する可能性が高かった
- すでに所有していたDaVinci Resolve StudioとResolve 21のIntelliSearch、Smart Bins、Voice to Subtitleで、Eddieが提供する機能の約70%はカバーできていた
- 残る構成は、Claude CodeがオープンソースのDaVinci Resolve MCPでResolveを操作し、情報系クリップに限ってElevenLabsでボイスオーバーを処理する形で、コストは月額 $22 まで下がった
本当のボトルネック: AIエディタより先に必要なインデックス
- 市販のAI動画エディタは、動画がすでにラベル付けされていることを前提にしているが、実際のアーカイブは
IMG_*.mov、DJI_*.mp4、Mara june 2024 backup final FINALのような名前で散在していた - Eddieは文字起こし検索はできても、ラベルのないアーカイブから「ゴールデンアワーの丘の上の象」のような場面を見つけることはできなかった
- ファイル名、親フォルダ、GPS座標、文字起こしテキストだけでは、「フレーム内にキリンがいる日の出のワイドショット」のような 視覚的内容 は分からない
- 実際のテコはエディタの上ではなくその前段にあり、まずアーカイブを英語で問い合わせ可能な形にする インデックス が必要だった
ローカル優先インデクサの設計
- 全体構造は、SimbaStackでクライアント向けに作っているAIネイティブなビルドに似ていたが、自分自身がクライアントでありエンジニアでもあったため、意思決定は速かった
-
4つの制約
- ローカル優先 である必要があった
- Mara Hilltopのアーカイブは物理SSDにあり、個人動画はノートPCにあり、数千本のマルチGBクリップをクラウドへ上げるのはコスト面でもプライバシー面でも適切ではなかった
- 中央DBより サイドカーファイル を望んだ
- 各クリップの横に
.description.mdを置き、プレーンテキストとして grep できるようにした - インデクサが後で壊れてもファイルは残り、ドライブ間を移動してもデータと一緒に持ち運べる
- 各クリップの横に
- 1回のビジョン呼び出しで必要情報をすべて引き出す必要があった
- 抽出フレームに対するビジョンパスは高コストなので、後で知りたくなる情報まで最初の呼び出しで得られるよう、最初からスキーマを広く設計した
- 含めた項目は rating、technical quality、lighting、time of day、color palette、audio quality、people count、keywords、faces、location、transcript、prose description など
- 3種類のビジョンバックエンドを選べるようにした
- 既定値はClaude MaxサブスクリプションのCLIで、限界費用がなかった
- 速度が必要なときはAnthropic APIを使う
- 大量処理にはLM Studio向けのローカルバックエンドを使い、このローカルバックエンドが中核だった
- ローカル優先 である必要があった
クリップごとの処理パイプライン
ffprobeで メタデータ を読むexiftoolでGPSの緯度・経度・高度を読み取り、iPhone、DJI Pocket、ドローン動画で同様に動作した- Nominatimで逆ジオコーディングを行い、無料で、レート制限があり、APIキーは不要だった
ffmpegで1920pxサイズのフレーム5枚を等間隔で抽出する- WhisperXで単語単位アラインメントとpyannote話者分離を含む文字起こしを行い、Hindi、English、Swahiliなど97言語に対応した
insightfaceで顔を検出し、後でアーカイブ全体から人物検索できるよう、512次元の ArcFace埋め込み を中央のSQLite顔DBに保存した- ビジョンモデルはフレーム、文字起こしの一部、フォルダ文脈を読み、YAML frontmatterと散文説明を返す
- 最終結果は各クリップの横に
.description.mdサイドカーとして記録される - Mara Hilltopの実際のクリップ
IMG_1103.MOVはファイル名だけでは文脈が分からなかったが、Gemmaが生成したサイドカーにはサファリテントの設営、屋内からサバンナへ続くカメラパン、ショットタイプ、マーケティング用リールや旅行VlogのB-rollといった用途が含まれていた - フォルダ単位では、各クリップ横のサイドカーに加え、先頭に
_INDEX.jsonと_INDEX.mdも生成され、高速な grep とLLMへの受け渡しに使われた - 実装全体は約 1,400行のPython からなるClaude Code skillで、Claude Codeが大半を書き、人間の役割はアーキテクチャ、プロンプト、スキーマ設計、バグのトリアージだった
古いMacBookで動かしたローカル31Bモデル
- 2021年に購入した16インチMacBook Pro M1 Max 64GB RAMは、もともとLLM用ではなく、Chromeタブ、DaVinci Resolve、Slack、Discord、Driveを同時に動かすための選択だった
- 5年後、その同じノートPCがLM Studioで Gemma 4 31B Q4 を立ち上げ、1年分の動画アーカイブを処理した
- LM Studioには28.40GBのモデルがメモリに載り、REST APIは
127.0.0.1:1234で動作した - 大量処理中は64GB RAMだけでは足りず、Activity Monitor上のスワップ使用量は最大 50.89GB に達した
- この状態は普段の作業日に維持するには厳しかったが、週末に集中的に回す用途としては許容範囲と見なせた
- ノートPCは熱くなり、ファンも大きく回ったが、別作業をしているあいだもサイドカーを生成し続けた
- 16インチのM1 Maxは、5年落ちのハードウェアでも31Bパラメータのモデルを実用的な速度で回せる余力を示し、ローカルLLMがさらに効率化すれば今後3〜5年は使い続けられると見られた
4つのバグと教訓
-
WhisperX 3.8の話者分離API変更
- WhisperX 3.8では
whisperx.DiarizationPipelineがwhisperx.diarizeサブモジュールへ移動した - コンストラクタ引数
use_auth_tokenは pyannote 3.x に合わせてtokenに変わった - 解決策は シグネチャ introspection だった
- スクリプトはまず
token=を試し、コンストラクタがTypeErrorを出したらuse_auth_token=にフォールバックする - 変化の速いAIライブラリを呼び出すとき、防御的なコンストラクタ呼び出しは安価な保険になる
- WhisperX 3.8では
-
Claude CLIが権限エラーを成功応答のように返す
- CLIバックエンドの最初のテストでは、4つのサイドカーがすべて “I need permission to read the image frames...” という同一テキストで返ってきた
- exit code が0で出力も空ではなかったため、スクリプトの成功判定を通ってしまった
- 非対話モードで
--permission-mode bypassPermissionsがない場合、Claude CLIはプロンプトの代わりに権限拒否テキストを応答本文として返す - 解決策はこのフラグを追加し、「I need permission」を含む短い応答を説明ではなく エラー として扱う防御チェックだった
- AIツールをスクリプトで扱うとき、非対話の権限フローには静かな失敗が潜んでいる
-
Gemmaが
people_count: "many"を返す- ビジョンプロンプトが
integer or the string "many" if >10と指示していたため、Gemmaは指示どおりに振る舞っただけだった - バグはモデルではなく スキーマ設計 にあった
- 修正後は0〜99の整数で推定するよう明記し、既存の
"many"応答はパーサ側で強制変換した - スキーマフィールドは
int または特定 stringのような union にせず、常に整数か常に文字列のどちらかに固定すべきで、そうすれば downstream の利用側が単純になる
- ビジョンプロンプトが
-
手ブレのあるバイククリップが誤って破棄された
- 初期の cull プロンプトは写真ポートフォリオ基準に近く、強いモーションブラー、甘いフォーカス、揺れを
cullと評価していた - スペイン旅行中に撮った夜の手持ちバイククリップも破棄対象になったが、そのブレ自体が記憶の雰囲気だった
- cull 基準を「完璧でない撮影」ではなく「実際の記録ではないもの」へ変更した
- 破棄対象は、レンズキャップ、ポケットの中の映像、2秒のテストクリップ、完全に白飛びした露出のようなクリップへと絞り込んだ
- 写真アーカイブは積極的に cull し、動画の思い出は寛容に cull すべきで、同じスキーマでもモードを明確に分ける必要がある
- 初期の cull プロンプトは写真ポートフォリオ基準に近く、強いモーションブラー、甘いフォーカス、揺れを
構造化スキーマとローカルモデルから得た結論
-
列挙制約はハルシネーションを減らす
- Gemma 4 E4Bは、夜に撮影した coworking-space の写真を “brightly lit, abundant natural light, floor-to-ceiling windows” と描写したが、窓の外は完全な夜だった
- 31Bに構造化スキーマを与え、
golden_hour | bright_daylight | overcast | dim_interior | nighttime | mixed | unclearのどれか1つを選ばせると、thinking-off でも thinking-on でもnighttimeを回復した - モデルは自由記述の散文では誤った説明を作れてしまうが、enum では新しい値を発明できず、誤って選ぶことしかできない
- 指示よりスキーマのほうが安全だった
-
ローカル31Bと構造化プロンプトはクラウドとの差を縮める
- Gemma 4 31B Q4 thinking-off は、構造化スキーマを使うと多くのテストクリップでSonnet 4.6と見分けがつきにくい出力を出した
- クラウドモデルのプレミアムは、難しい 10〜20% のクリップでこそ価値があった
- 何千本ものクリップを一晩かけてインデックス化する大量処理はローカルで回し、ローカルが
reviewと付けたクリップだけをクラウドで再評価する2段構成はスケール可能だった
-
AI動画エディタは競争するレイヤーが高すぎる
- 価値のあるレイヤーはエディタではなく 検索可能なインデックス だった
- 「Maraの手持ち屋内クリップで、ゴールデンアワー、人がいて、8秒より長いもの」のように自然言語で問い合わせできれば、その上のエディタは単純になる
- AIエディタ市場は、存在しないインデックスの上の表層レイヤーを競っており、前提条件であるインデックスを飛ばしている
次のステップと限界
- 次の作業は、Claude Codeをオーケストレータとして使い、DaVinci Resolve MCPでカットを作り、情報系クリップにはElevenLabsのボイスオーバーを付ける エディタ を作ることだ
- ボイスクローンには明確な制限がある
- 道案内、客室説明、多言語版、本人が話しそうな事実情報のようなユーティリティ系コンテンツにのみ使う
- レビューや創業者メッセージには使わない
- 2026年には公開義務に関する法整備が現実味を帯びており、ホスピタリティブランドの信頼は簡単に失われうる
- インデックスがあれば、47GBのDJI Pocket動画から日の出のワイドショットを手でスクラブして探す作業を避けられる
- 現在、5年落ちのノートPC上でMara Hilltopの1年分の動画は英語で問い合わせ可能になっており、代償は週末の時間と50GBのスワップだった
- 古いSSDに残る他の年のデータが次の処理対象になっている
- Mara Hilltopのソーシャルチャンネルは、まだ再始動していない
- インデクサは適切なクリップを見つける問題しか解決しない
- 完成したリールへ仕上げるエディタが残り半分であり、成功すれば続編を書き、失敗すれば失敗理由を書く予定だ
- 正しい答えは人を雇うことかもしれない
- Mara Hilltopに合う、温かく観察的な感覚を持つ編集者を見つけることは、もう1つの skill を書くより難しいかもしれない
- 過剰にカットしたMTV風リールは望む方向ではない
- コードは github.com/Simbastack-hq/framedex で公開されており、PRとissueを受け付けている
1件のコメント
Hacker Newsのコメント
Claudeが記事を書く際、共有するURLを間違えて選んだように思える。ホームフォルダが外部に公開されていない限り、
~/.claude/skills/video-index/にはアクセスできないので、Skillファイルを共有してもらえないか気になる更新: 急いでこのリポジトリを作った - https://github.com/Simbastack-hq/framedex
ライセンスはMITで、汎用化したあとにきちんとテストはまだできていない。近いうちにちゃんと見直して、さらに更新を追加する予定
大きなTODOは2つあって、1) このインデックスとClaudeの助けを使ってDaVinci Resolveでの動画編集をもっと速くすること、2) 今は動画だけを処理しているが、カメラ内の何千枚もの静止画像も理解できるように拡張すること
なぜそこまで多くのスワップが必要なのかよく分からない。必要なメモリ帯域を考えると、SSDの寿命をかなり早く縮めそう
Gemma 4 31Bの4ビット量子化モデルは28.4GiBではなく、およそ19GiB程度のはず [1]。画像を頻繁に入れたことがないので、コンテキストに入れる際にどれだけ余分なメモリが必要かは分からないが、10GiBを超えることはなさそう
アクティビティモニタを見ると、モデルをロードしているように見えるHandyとClaude Code用の仮想マシンの上に、複数のElectronアプリも動いているので、実際の原因はそちらのように見える。ノートPCが激しくディスクアクセスを始めたら、そういうアプリは止まってしまうだろうから役に立たないはず
[1] https://huggingface.co/mlx-community/gemma-4-31b-it-4bit
それでも少しもたつきはしたが、Braveブラウザで大量のタブを開いたままでも別の作業を続けられたのは印象的だった
これがすでに存在していて、しかもかなり良く、50GBスワップも消費しないことは知っていたのだろうか
https://github.com/iliashad/edit-mind
すごい。ローカルモデルを回せるだけのRAMがあればいいのにと思う。ここ数週間でかなり似たものを作ったが、自分はWhisperとffmpegを使うローカルのElectronアプリとして作り、動画と対話するためのセマンティック検索と埋め込みも追加した
視覚分析、タグ付け、動画チャットはClaudeと通信している。このプロジェクトは画像を1枚だけ送る方式なのか気になる。自分はカスタムのシーン検出アルゴリズムで動画ごとに異なる複数の画像を見つけ、字幕と一緒に1回のリクエストでClaudeに送っている。間違いなく一番コストがかかる部分だ。分析にはSonnet 4.6、タグ付けにはHaikuを使うと、1時間分の動画で約1ドルかかり、ローカルでは遅そう
ただ、フレームの選び方が弱点。シーン検出は確実に役立つだろうし、ロードマップの最優先事項。シーン検出でどうフレームを選んでいるのか共有してもらえるとありがたい
ベクトル検索は入れず、より移植性の高い一般的なMarkdownファイルでシンプルに保つ方を選んだ。SSDを移しても知識はファイルと一緒に移動し、同期すべきインデックスもなく、ツールより長生きするプレーンテキストだという利点がある。ただ、あなたが言う別の方向性も探ってみる価値はある
ほかにも良い選択肢がある。Gemini 3.1 Flash Liteはこういう作業にかなり向いている。ただしGemini 3.5 Flashは違う。あれは価格がいまいち
https://openrouter.ai/google/gemma-4-31b-it
質問が2つある
description.mdの例にfaces -> cluster_idのような項目がある。これはDaVinci Resolveの顔インデックスから来たものなのだろうか。写真コレクションでは顔+名前、位置のような情報が本当に重要だが、一般的なLLMはこういうものをうまく扱えない.description.mdサイドカーファイルにすぎない後でClaudeと「ロッジの高級客室の動画を作りたい」のようにブレインストーミングするときに問い合わせられて、Claudeがファイルを見てどの動画が役立つか判断できる
見つけやすいようにテキスト説明を集めたフォルダルートレベルのファイルもある。ブログにサンプル画像を貼っておいた - https://blog.simbastack.com/_media/gvcycx2n.png
顔はinsightface由来。オープンソースの
buffalo_lパックのRetinaFaceで検出し、CPU上でローカル実行している。各クリップのサンプルフレームから顔を検出して埋め込みを取り、~/.framedex/faces.dbに行を書き込む正直、この部分はローカルDBに蓄積されていることは分かっているが、どれだけうまく動くかはまだちゃんと試せていない。近いうちにきちんと確認するつもり
もっと広く言えば、そのためframedexは意図的にLLMに顔や位置の処理を任せていない。顔はinsightface / ArcFace埋め込みで処理しているので、クリップ間比較を決定的に行える。視覚モデルは大まかな人数だけを提供し、誰であるかを識別しようとはしない
位置はexiftool経由のEXIF GPSと、Nominatim/OpenStreetMapの逆ジオコーディングで処理している。推測ではなく堅牢なメタデータ
LLMには得意なことだけをさせている。シーンの説明、雰囲気、ショットの種類、キーワード、保存/レビュー/破棄の評価といったもの。最後の評価の部分には議論の余地があるけれど
2015年製のThinkPadで似たような作業をしようとしてGemmaを動かしてみた。幸いメモリを増設できて、そうでなければかなりつらかったはず
嘘はつけないが、llama.cppを回したらファンが最大速度で回っていた。それでも動いたし、作業は終わった
ときどき「リソースを100%使っている」という比喩として使われている気もするし、ここではたぶんそういう意味なのだろうが、別の文脈では実際の不満として言われることも確かにあった
「生成AI動画には本物の旅行ブランドが入り込む余地はない」という言葉には、Airbnbホストの大半は同意しないのではないかと思う
「TripAdvisorで磔刑にされる」という表現についても、偽の宿を載せるAirbnbホストがどうやって生き残っているのか本当に分からない
一方で、本物の動画は時間がかかり、全体の工程を遅くしてしまう
B2C AIアプリケーションは、個人化されたコンテキストを作るのが難しいという点で構造的な限界があると思う
有能なローカルモデルが大規模にゼロからコンテキスト収集、調査、タグ付けなどをこなせるなら、ここで大きな突破口になる可能性がある
スクリーンショットを何枚か入れると、その中身に基づいて賢く名前を付けようとする。動画やPDFなども同様
ただ、あなたの言う通りAppleがそのまま機能として入れてきそうなので、お金を取ろうとすらしなかった
https://finalfinalreallyfinaluntitleddocumentv3.com/
でも、エージェントが十分に賢くなれば、非技術者の友人たちも「このフォルダの動画を理解できるように整理して」と言うだけでそのままやってくれるようになるのは時間の問題だと思う