broll について
brollは、動画クリエイター向けにBロール素材をタグ付きで整理・検索する編集スキルです。動画を1本ずつ開かずに、短い編集に合う映像を探せます。
主な機能
- 01クリップのメタデータとタグを整理
- 02確認用コンタクトシートを生成
- 03新しいクリップを追加してタグ付け
- 04場面・ムード・被写体で検索
- 05使用済み・除外素材を記録
活用シーン
- 01夜のデスク映像をショート用に探す
- 02新しいフォルダーをライブラリに追加
- 03文字起こしに合う映像を選ぶ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ melvynx/ video-agent-skills --skill brollインストール後、Claude Code で/brollを入力して読み込まれたか確認します。
詳細
B-roll
B-roll = クリエイターの声に重ねて表示する映像です。本人が撮影した映像(街、夜のデスク、夕日、ジム、手に持ったスマートフォン)、画面録画、写真などです。ライブラリは一度だけ読み込みます。その後は、動画をひとつも開かずに、grep を一度実行するだけで「夜、デスクにいる自分」が見つかります。
<their clips folder>/ the files, never moved or edited
video-agent/broll/
├── library.json one entry per clip: file, length, orientation, date, GPS, tags, used in
├── library.md the same, one line per clip, newest first: what agents grep
├── sheets/<id>.jpg 6 frames of the clip in a 3x2 grid (what the taggers and you look at)
└── tags/batch-NN.json what each Haiku tagger wrote
クリエイターには相手の言語で、専門用語を使わず短く話しかけます(「ta bibliothèque de B-roll」と言い、「JSON」とは言いません)。すべてのコマンドはプロジェクトフォルダー(video-agent/ があるフォルダー)から実行します。事前確認:ffmpeg -version && node -v。不足している場合:/studio-setup tools。
/broll setup
- フォルダー。 クリップがどこにあるか、一行で尋ねます。本人のフォルダーは元の場所に置いたままで構いません(外付けドライブでも使えます)。クリップがあちこちにある場合は、
mkdir -p video-agent/broll/clipsを実行し、そこにコピーします。 - 本人の写真(どのクリップに本人が映っているか判別するため):顔がはっきり写った、明るい写真を
video-agent/broll/me.jpgとして用意し、さらに1〜3枚をme-2.jpg、me-3.jpgとして追加します(顔のアップ、別の帽子や眼鏡を着けた写真、本人が確実に映っているクリップのフレームなど)。写真が1枚だけだと、Haiku は本人のクリップのほとんどを見逃します。写真がない場合、is_selfは false のままです。 - スキャン(クリップ1本あたり約1秒)。新しいファイルだけを読み込むため、同じコマンドを実行すれば後からクリップを追加できます。
node -e '
const fs=require("fs"),path=require("path"),{execFileSync:x}=require("child_process");
const B="video-agent/broll",L=B+"/library.json",ext=/\.(mp4|mov|m4v|mkv|webm|jpe?g|png)$/i;
fs.mkdirSync(B+"/sheets",{recursive:true});
const lib=fs.existsSync(L)?JSON.parse(fs.readFileSync(L,"utf8")):[],known=new Set(lib.map(c=>c.file));
const walk=d=>fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.name.startsWith(".")?[]:e.isDirectory()?walk(path.join(d,e.name)):ext.test(e.name)?[path.resolve(d,e.name)]:[]);
let n=0;
for(const f of process.argv.slice(1).flatMap(a=>fs.statSync(a).isDirectory()?walk(a):[path.resolve(a)])){
if(known.has(f))continue;
try{
const p=JSON.parse(x("ffprobe",["-v","error","-print_format","json","-show_format","-show_streams",f]).toString());
const v=p.streams.find(s=>s.codec_type==="video")||{},t=p.format.tags||{},photo=/\.(jpe?g|png)$/i.test(f);
const turn=Math.abs(+(v.tags?.rotate||(v.side_data_list||[]).find(s=>s.rotation!=null)?.rotation||0))%180===90;
const w=turn?v.height:v.width,h=turn?v.width:v.height,dur=photo?0:+p.format.duration||0;
let id=path.basename(f).replace(/\.[^.]+$/,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"clip";
while(lib.some(c=>c.id===id))id+="-2";
const sheet=B+"/sheets/"+id+".jpg";
x("ffmpeg",["-v","error","-y","-i",f,"-vf",photo?"scale=720:-2":"fps=6/"+Math.max(dur,1)+",scale=360:-2,tile=3x2","-frames:v","1",sheet]);
const g=(t["com.apple.quicktime.location.ISO6709"]||t.location||"").match(/([+-][\d.]+)([+-][\d.]+)/);
lib.push({id,file:f,sheet,duration:Math.round(dur*10)/10,width:w,height:h,orientation:w<h?"portrait":w>h?"landscape":"square",
date:t["com.apple.quicktime.creationdate"]||t.creation_time||null,lat:g?+g[1]:null,lon:g?+g[2]:null,place:null,tags:null,used:[],status:"active"});
if(++n%20===0)fs.writeFileSync(L,JSON.stringify(lib,null,1));
}catch(e){console.log("skipped "+f+": "+e.message.split("\n")[0])}
}
fs.writeFileSync(L,JSON.stringify(lib,null,1));
console.log(n+" new, "+lib.length+" in the library, "+lib.filter(c=>!c.tags&&c.status==="active").length+" to tag");
' "<their clips folder>"
GPS 情報のあるクリップ:場所を特定します。同じ場所ごとに1回リクエストします(小数点以下2桁に丸めます)。1秒ごとに1回実行し、library.json の place に「Lisbon, Portugal」のように設定します:curl -s "https://nominatim.openstreetmap.org/reverse?format=json&zoom=10&lat=<lat>&lon=<lon>" -H "User-Agent: video-agent-skills"。GPS 情報がなければ場所は設定せず、絶対に推測しません。
4. Haiku によるタグ付け(安価で高速)。未タグ付けのクリップを5本ずつまとめます。バッチごとにサブエージェントを1つ用意し、すべてを1つのメッセージで起動して並行処理します。一度に10バッチまで実行します(Claude Code:model: "haiku" を指定した Agent ツール)。このツールでサブエージェントを使えない場合は、同じルールに従って自分でバッチにタグを付けます。各タグ付け担当へのプロンプトを入力したもの:
You tag B-roll clips for a video creator's library. Read each contact sheet below (6 frames of one clip, left to right then top to bottom, spread evenly over the clip) and video-agent/broll/me.jpg, me-2.jpg, me-3.jpg (the creator; skip the missing ones). Then Write ONE file, video-agent/broll/tags/batch-<NN>.json, then answer "done". Nothing else.
Clips: <id> (<duration> s) video-agent/broll/sheets/<id>.jpg ; ... (5 of them)
The file is one JSON object keyed by clip id, every id present, this shape:
{"<id>": {"title": "4 to 8 words", "description": "3 plain sentences: what is on screen, what moves, the light",
"setting": "desk|bedroom|kitchen|street|beach|car|gym|cafe|office|city|nature|screen|studio|...",
"subjects": [], "actions": [], "emotions": [{"name": "serenity", "intensity": 0.6}],
"mood": "calm|joyful|melancholic|tense|energetic|contemplative|neutral",
"time_of_day": "dawn|day|golden hour|sunset|evening|night|unknown",
"shot_type": "close-up|medium|wide|screen|aerial", "keywords": ["8 to 15 words someone would search"],
"has_people": true, "is_self": false, "quality_flags": ["blur|shaky|dark|overexposed|watermark"], "best_start": 0}}
Rules: only what is visible, never a place, brand or name you cannot read on screen. Everything in English. emotions 0 to 2, only when the image clearly carries one. is_self: compare every face with the creator photos (face shape, nose, eyebrows, hair, glasses); same person = true, even with another cap, glasses or clothes, in a mirror selfie or small in the frame; an arm's-length selfie is usually them, look twice; hands or back only = false. best_start = the second where the clean part starts (frame k of 6 is at about k/6 of the length), 0 when clean from the start. Valid JSON, no comment.
その後、すべてのバッチを統合し、library.md を再構築します(library.json を手動で変更した後も同様です)。
node -e '
const fs=require("fs"),B="video-agent/broll",L=B+"/library.json",lib=JSON.parse(fs.readFileSync(L,"utf8"));
fs.mkdirSync(B+"/tags",{recursive:true});
const T=Object.assign({},...fs.readdirSync(B+"/tags").filter(f=>f.endsWith(".json")).map(f=>JSON.parse(fs.readFileSync(B+"/tags/"+f,"utf8"))));
for(const c of lib)if(T[c.id])c.tags={...(c.tags||{}),...T[c.id]};
fs.writeFileSync(L,JSON.stringify(lib,null,1));
const k=c=>c.tags||{},row=c=>[c.id,c.duration+"s",c.orientation,(c.date||"").slice(0,10),c.place||"",k(c).title||"(untagged)",k(c).mood||"",k(c).time_of_day||"",
[...(k(c).keywords||[]),...(k(c).emotions||[]).map(e=>e.name),k(c).setting||""].join(" "),k(c).is_self?"me":"",(k(c).quality_flags||[]).join(" "),c.used.length?"used "+c.used.length:""].join(" | ");
const on=lib.filter(c=>c.status==="active").sort((a,b)=>(b.date||"").localeCompare(a.date||""));
fs.writeFileSync(B+"/library.md","# B-roll library\n\nid | length | orientation | date | place | title | mood | time | keywords | me | flags | used\n"+on.map(row).join("\n")+"\n");
console.log(on.length+" clips, "+on.filter(c=>!c.tags).length+" untagged, "+on.filter(c=>k(c).is_self).length+" with the creator");
'
バッチファイルが有効な JSON でない場合、または ID が不足している場合は、そのタグ付け担当を再実行します。ライブラリが大規模(クリップ300本以上)な場合は、今回は100本にタグを付けると伝え、残りはバックグラウンドで処理するか、動画で B-roll が必要になった最初のタイミングで処理します。
5. クリエイターと一緒に確認:実際のトピックから3つ検索します(下記参照)。ヒットしたクリップの情報を読み、見つかった内容を見せます(「pour « bureau la nuit » j'ai ces 3 plans」)。タグが間違っていたら、library.json を修正して再構築します。二度と使いたくないクリップは除外します。
6. クリエイターに一行で伝えます:「ta bibliothèque est prête : N clips. Dis « ajoute ces vidéos » pour en mettre d'autres, « mets mes B-roll » sur un short.」
/broll add
クリエイターのファイルまたはフォルダーを、クリップ用フォルダーにコピーするか、元の場所でスキャンします。その後、新しいファイルだけについて手順3と4を実行します(スキャンでは既知のファイルをスキップし、タグ付け担当には tags: null のものだけを渡します)。追加された内容を伝えます:「12 clips ajoutés : 4 à Lisbonne en août, 8 au bureau」
/broll search
grep -i -E "sunset|dusk|golden" video-agent/broll/library.md
grep -i "desk" video-agent/broll/library.md | grep -i "night" | grep "| me |"
画像に映っていてほしい内容を英語で入力します。同義語は | でつなぎ、2つ目のフィルターには2つ目の grep を使います。ヒットした項目は自分で順位付けします。検索語に一致する行、未使用のもの、最近のもの、適切な向き(ショート動画には縦向き。横向きのクリップは画面いっぱいに表示するためにクロップします)の順に優先します。その後、候補の上位3本の情報を読みます。タグは候補を探すのに役立ちますが、最終確認は自分の目で行います。
/broll の使用
編集中のショート(video-agent/edits/<slug>/ または名前付き)の言葉に合わせて、Bロールを選びます。
- トランスクリプト(ショートの
files/words.md、ソースの秒数)を読みます。場面や場所を示す行(「リスボンに引っ越しました」)、感情を表す行(「疲れ切っていました」)、クリエイターが何かをしている行(「夜にコードを書いています」)、使っている物についての行に印を付けます。数字、アイデア、証拠はボードのままにします。 - 1行につき1件検索し、シートを読みます。画像が文に合っていて(落ち着いたセリフには落ち着いた画像を合わせます)、
best_startから始まる問題のない部分がその区間全体をカバーし、画面上に問題(ぼやけ、見知らぬ人の顔、プライベートな画面)がない場合にのみ、クリップを採用します。Bロールは8〜15秒ごとに最大1つ、各2〜5秒とし、2つのBロールの間にはクリエイターを5秒表示します。 - 採用した各クリップに、仕様の
media(source/spec.js)で名前を付けます:<id>: { src: '<file>', from: <best_start>, dur: <s>, fill: true }(fillは横長のクリップを9:16にクロップし、focus: [x, y]はその一部を表示します)。 - 同じ仕様の
brollに追加します:{ media: '<id>', from: <source s of the first word>, to: <source s of the last word> }(short-editing のreferences/scene-library.md、「Full-screen B-roll」を参照)。その後、もう一度prepareを実行します(フレームが抽出されます)。 - 各区間の中央で
short.mjs stillsを実行し、内容を確認してから、全体をレンダリングします(short-editing の手順5と6)。 - クリエイターがそのショートを使い続ける場合は、各クリップの
usedにショートのスラッグを追加し、library.mdを再構築します。次のショートでは、まだ使っていない新しいクリップが優先されます。
適切な候補がない場合は、ライブラリにその画像がありません。クリエイターまたはボードを画面に表示し続け、無関係なクリップは使わないでください。
/broll の却下
そのクリップの library.json に "status": "rejected" を設定し("active" にすると再び有効になります)、library.md を再構築します。以後、そのクリップは候補に出てきません。ファイル自体は削除しません。クリエイターの映像だからです。