add-broll
Get AI suggestions for B-roll footage to fill visual gaps in a video timeline using Pixabay stock media.
SKILL.mdAutoAnimation/.claude/skills/add-broll/SKILL.md
GitHubPurpose
Automatically detect gaps between dialogue lines where B-roll footage should be inserted, extract visual concepts from surrounding dialogue context using Gemini AI, search Pixabay for matching stock imagery, and place the selected media on the timeline with appropriate transitions.
Steps
Detect dialogue gaps. Call
findDialogueGaps(fps, totalFrames)frombrollIntelligence.ts. This:- Reads all dialogue lines from
useMultiCharacterStore - Sorts by
startFrameand finds gaps >= 1 second between consecutive lines - Also detects trailing gaps after the last line (capped at 3 seconds)
- Returns
BrollGap[]withstartFrame,endFrame,contextBefore,contextAfter(clean dialogue text without emotion cues)
- Reads all dialogue lines from
Extract visual concepts. Call
extractVisualConcepts(dialogueTexts)frombrollIntelligence.ts. This:- Sends gap context strings (combined before/after dialogue) to Gemini
- Asks for 2-4 word stock footage search queries per gap
- Returns
(string | null)[]-- null for gaps with no clear visual concept - Falls back to
nullfor all entries if Gemini API key is missing
Search Pixabay. The
useBrollStore.analyzeTranscript()action orchestrates the full pipeline:- Calls
findDialogueGaps()andextractVisualConcepts() - Creates
BrollSuggestion[]with queries, gap frame ranges, and status - Searches Pixabay for each suggestion (rate-limited: 500ms between requests, max 2 concurrent)
- Results are sorted by score (likes + views/100) and limited to top 5 per suggestion
- Calls
Review and customize suggestions. Each
BrollSuggestionhas:query-- the search query (editable)results--BrollSearchResult[]with thumbnail, preview, download URLsselectedIndex-- which result is currently selectedstatus-- 'pending', 'accepted', or 'rejected'transition-- media transition type (default: 'ken-burns')- Actions:
swapSuggestion(id, newIndex),refreshSuggestion(id, newQuery),setSuggestionTransition(id, transition)
Accept or reject suggestions.
acceptSuggestion(id): Downloads the selected image, adds it touseMediaStoreas an asset, places it on the canvas at the gap's frame range with the chosen transitionrejectSuggestion(id): Marks as rejected, skips placement
Analyze topics (optional). Call
analyzeSpeakerTopics(dialogueLines)frombrollIntelligence.tsfor deeper analysis. Groups consecutive lines by topic using Gemini, returningTopicGroup[]withtopic,lineIndices,startFrame,endFrame.Rank B-roll results. Use
rankBrollSuggestions(hits, query, role)frombrollIntelligence.tsto score Pixabay results by landscape orientation (30%), resolution up to 1920px (40%), and popularity (30%).
Key Files
src/services/brollIntelligence.ts-- findDialogueGaps(), extractVisualConcepts(), analyzeSpeakerTopics(), rankBrollSuggestions(), BrollGap, TopicGroup typessrc/stores/useBrollStore.ts-- analyzeTranscript(), acceptSuggestion(), rejectSuggestion(), refreshSuggestion(), BrollSuggestion, BrollSearchResult typessrc/services/pixabay.ts-- Pixabay stock media API, searchImages()src/stores/useMediaStore.ts-- Media asset storage, addAsset(), addToCanvas()src/components/panels/BrollSuggestionPanel.tsx-- B-roll suggestion UI
Common Issues
- Gemini API key missing: Visual concept extraction returns null for all entries. Suggestions fall back to generic "stock footage" queries.
- Pixabay API key missing: Search will fail. Set
VITE_PIXABAY_API_KEYin environment. - Rate limiting: Pixabay requests are rate-limited to 500ms intervals with max 2 concurrent. Large numbers of gaps will take time to populate.
- No gaps detected: If dialogue is continuous with no gaps >= 1 second, no suggestions are generated. Consider the
SilenceRemovalServiceto create gaps by removing silences. - Image download CORS: The
acceptSuggestion()action downloads fromlargeImageURLdirectly. If CORS blocks, the download may fail. - Transition types: Default is 'ken-burns'. Available transitions include: 'fade', 'zoom', 'slide', 'ken-burns' from
useMediaStore.
Examples
// Full pipeline via store
const store = useBrollStore.getState()
await store.analyzeTranscript()
// Review suggestions
const suggestions = store.suggestions // BrollSuggestion[]
// Each has: query, results[], status, transition
// Swap to a different result
store.swapSuggestion(suggestions[0].id, 2) // select 3rd result
// Change search query and refresh
await store.refreshSuggestion(suggestions[0].id, 'sunset beach ocean')
// Accept a suggestion (downloads and places on timeline)
await store.acceptSuggestion(suggestions[0].id)
// Manual gap detection
import { findDialogueGaps, extractVisualConcepts } from '@/services/brollIntelligence'
const gaps = findDialogueGaps(30, totalFrames)
const queries = await extractVisualConcepts(gaps.map(g => g.contextBefore || ''))