Video Intelligence Extraction
Overview
Extract actionable intelligence from video content across YouTube, Vimeo, and company websites. Video reveals what text sources cannot: facility scale, team composition, product demonstrations, signage, event attendance, and operational reality.
This skill focuses on metadata + caption extraction (fast, free, no storage cost) and only downloads actual video files when visual analysis is required.
When to Use
- Target company has a YouTube channel (even a small one — Shorts count)
- Founder or leadership appears in podcast/interview videos
- Product demos or walkthroughs exist on any platform
- Event recordings show the company's booth, presentations, or attendance
- You need to verify claims (facility size, team count, product range) with visual evidence
- Company website has embedded video content
Prerequisites
| Tool |
Install |
Purpose |
| yt-dlp |
pip install yt-dlp or brew install yt-dlp |
Video metadata/caption download |
| Python 3.10+ |
System |
VTT parsing, automation |
| Whisper (optional) |
pip install openai-whisper |
Transcription when no captions exist |
| ffmpeg (optional) |
brew install ffmpeg |
Audio extraction for Whisper |
Phase 1: Discovery
YouTube Channel Enumeration
# List ALL videos on a channel (returns video IDs + titles, no download)
yt-dlp --flat-playlist --print "%(id)s %(title)s" "https://www.youtube.com/@CHANNEL_HANDLE"
# Include upload dates for timeline analysis
yt-dlp --flat-playlist --print "%(upload_date)s %(id)s %(title)s" "https://www.youtube.com/@CHANNEL_HANDLE"
YouTube Search
# Search YouTube (top 10 results)
yt-dlp --flat-playlist --print "%(id)s %(title)s" "ytsearch10:COMPANY_NAME"
# Broader search (top 30)
yt-dlp --flat-playlist --print "%(id)s %(title)s" "ytsearch30:COMPANY_NAME founder interview"
Other Discovery Methods
| Source |
Method |
| Google Video search |
site:youtube.com "Company Name" |
| Company website |
Inspect source for <iframe src="youtube.com/embed/..."> |
| Vimeo |
yt-dlp --flat-playlist "https://vimeo.com/USER" |
| LinkedIn |
Manual browse — videos cannot be enumerated programmatically |
| Event platforms |
Check Sched/Emamo session pages for embedded video links |
Discovery Checklist
Phase 2: Capture
Metadata + Captions (Default — No Video Download)
# Entire channel — metadata + auto-captions, no video files
yt-dlp --write-info-json --write-auto-sub --sub-lang en --skip-download \
--sleep-interval 2 "https://www.youtube.com/@CHANNEL"
# Single video
yt-dlp --write-info-json --write-auto-sub --sub-lang en --skip-download \
"https://www.youtube.com/watch?v=VIDEO_ID"
# Multiple specific videos from a list
yt-dlp --write-info-json --write-auto-sub --sub-lang en --skip-download \
--sleep-interval 2 -a video_urls.txt
Key yt-dlp Flags
| Flag |
Purpose |
--write-info-json |
Saves full metadata (title, date, duration, description, tags, view count) |
--write-auto-sub |
Downloads auto-generated captions |
--sub-lang en |
English captions only |
--skip-download |
No video/audio file — metadata only |
--sleep-interval 2 |
Rate limiting (critical for channels with 30+ videos) |
--flat-playlist |
List mode — enumerate without downloading anything |
-f "bestvideo[height<=720]+bestaudio" |
Download video at reasonable quality |
-x --audio-format mp3 |
Extract audio only (for Whisper) |
Download Video Files (When Visual Analysis Needed)
# 720p max (good balance of quality vs. file size)
yt-dlp -f "bestvideo[height<=720]+bestaudio" --merge-output-format mp4 VIDEO_URL
# Audio only for Whisper transcription
yt-dlp -x --audio-format mp3 VIDEO_URL
Output File Structure
project/
├── raw/video/
│ ├── Title of Video [VIDEO_ID].info.json # Metadata
│ ├── Title of Video [VIDEO_ID].en.vtt # Auto-captions (VTT format)
│ └── Title of Video [VIDEO_ID].mp4 # Video file (only if downloaded)
├── transcripts/
│ ├── VIDEO_ID_transcript.txt # Parsed plain text
│ └── VIDEO_ID_transcript.txt
└── analysis/
└── video_intelligence_summary.md
Phase 3: VTT Parsing
Convert VTT Captions to Plain Text
import re
import glob
def vtt_to_text(vtt_path):
"""Parse VTT subtitle file to clean plain text."""
with open(vtt_path) as f:
content = f.read()
# Remove WEBVTT header and metadata
content = re.sub(r'WEBVTT.*?\n', '', content, flags=re.DOTALL)
# Remove timestamp lines
content = re.sub(r'\d{2}:\d{2}:\d{2}\.\d+ --> .*', '', content)
# Remove HTML-style formatting tags
content = re.sub(r'<[^>]+>', '', content)
# Deduplicate consecutive repeated lines and join
lines = []
for l in content.split('\n'):
l = l.strip()
if l and not l.isdigit() and (not lines or l != lines[-1]):
lines.append(l)
return ' '.join(lines)
def batch_parse_vtts(vtt_dir, output_dir):
"""Parse all VTT files in a directory to plain text."""
import os
os.makedirs(output_dir, exist_ok=True)
for vtt_file in glob.glob(f"{vtt_dir}/*.vtt"):
text = vtt_to_text(vtt_file)
# Extract video ID from filename pattern "Title [VIDEO_ID].en.vtt"
video_id = re.search(r'\[([^\]]+)\]', vtt_file)
if video_id:
out_name = f"{video_id.group(1)}_transcript.txt"
else:
out_name = os.path.basename(vtt_file).replace('.en.vtt', '_transcript.txt')
with open(os.path.join(output_dir, out_name), 'w') as f:
f.write(text)
print(f"Parsed: {out_name} ({len(text.split())} words)")
Phase 4: Whisper Fallback (No Captions Available)
When auto-captions don't exist (common for Shorts, older videos, non-English content):
# Download audio only
yt-dlp -x --audio-format mp3 -o "%(id)s.%(ext)s" VIDEO_URL
# Transcribe with Whisper
whisper VIDEO_ID.mp3 --model base --language en --output_format txt
# For better proper noun accuracy (3-5x slower):
whisper VIDEO_ID.mp3 --model medium --language en --output_format txt
When Captions Typically Don't Exist
- YouTube Shorts (< 60 seconds)
- Videos uploaded before ~2015 (pre-auto-caption era)
- Unlisted/private videos
- Music-heavy content with minimal speech
- Non-English content without specified language
Phase 5: Intelligence Extraction
Metadata Intelligence (from info.json)
Every .info.json file contains:
| Field |
Intelligence value |
upload_date |
Timeline of company activity and content strategy |
duration |
Short = promo; Long = substantive interview |
description |
Often contains links, names, timestamps, partner mentions |
tags |
SEO strategy, self-identified categories |
view_count |
Which content resonates with their audience |
like_count |
Engagement quality signal |
channel |
Who published it (their channel vs. guest appearance) |
Transcript Intelligence Checklist
Visual Intelligence Checklist (Requires Video Download)
Edge Cases and Gotchas
YouTube rate limiting — Use --sleep-interval 2 between downloads. DNS errors appear after 30+ rapid requests. For large channels, break into batches of 20.
Channel download filenames — yt-dlp names files as "Title [VIDEO_ID].ext" when downloading from channels. Spaces and special characters in titles break shell scripts. Use -o "%(id)s.%(ext)s" for clean filenames.
Duplicate uploads — Same interview uploaded under different channels or titles. Cross-reference by duration + upload_date to catch duplicates before spending time analyzing both.
LinkedIn native videos — CANNOT be downloaded programmatically. LinkedIn CDN URLs are authenticated and expire within hours. Flag as manual task: "Save as Web Complete" in browser, or screenshot key frames.
Shorts vs. full videos — Shorts URL format is youtube.com/shorts/ID but yt-dlp handles both formats transparently. Don't skip Shorts — they often show products, facilities, and team culture.
Unlisted videos — Not in channel listings but accessible via direct URL. Search engines may have indexed them. Check Google cache and Wayback Machine for unlisted URLs that were once linked publicly.
Auto-caption quality — No punctuation, sometimes garbled proper nouns (Celina → Selena, BROGAV → ProGraph). 95%+ word accuracy for standard English speech. Always verify company/person names against known-good sources.
Upload date vs. content date — Videos may be uploaded weeks or months after recording. Check description and audio cues for actual recording date. Event videos especially lag.
info.json is gold — Always capture with --write-info-json. The description field often contains timestamps, guest names, links to resources, and partner mentions that aren't in the video itself.
VTT timestamps are valuable — The plain-text conversion loses temporal information. Keep original VTT files as source of truth. Timestamps let you find specific moments for visual verification.
Anti-Patterns
| Don't |
Do instead |
| Ignore Shorts because they're short |
Review Shorts for visual intel (products, facility, team) |
| Assume all videos have captions |
Check; fall back to Whisper for uncaptioned content |
| Download video files by default |
Start with --skip-download; only get video for visual analysis |
| Try to automate LinkedIn video download |
Flag as manual task; CDN URLs are authenticated |
| Trust upload_date as content date |
Cross-reference with description, audio cues, event dates |
| Download entire channel at once without rate limiting |
Use --sleep-interval 2 and batch into groups of 20 |
| Parse only transcripts, ignore info.json |
Metadata contains names, links, tags not in the audio |
| Treat video as inferior to text sources |
Video uniquely confirms physical reality (facility, team, products) |
Decision Tree
Content is on YouTube?
├── YES → yt-dlp --write-info-json --write-auto-sub --skip-download
│ ├── Has captions? → Parse VTT to text → Extract intelligence
│ └── No captions?
│ ├── Video > 60s with speech? → yt-dlp -x → Whisper → Analyze
│ └── Short/visual only? → Download video → Visual analysis
└── NO →
├── Vimeo → yt-dlp supports it (same workflow as YouTube)
├── LinkedIn video → Manual "Save as Web Complete" (flag as manual task)
├── Company website embed → Extract iframe URL → usually YouTube/Vimeo
└── Other platform → Check yt-dlp supported sites list
Real-World Results (BROGAV Case Study)
| Metric |
Value |
| Total videos discovered |
45 (9 full-length, 36 Shorts) |
| VTT caption files captured |
10 |
| Transcripts parsed to text |
10 |
| Substantive interviews analyzed |
2 (5,000+ words each) |
| Podcast via RSS/Whisper fallback |
1 (6,489 words) |
| Duplicate videos caught |
1 (same interview, two channels) |
| Unique intelligence extracted |
Founder origin story, revenue signals, growth plans, supplier relationships, facility details, team culture |
| Visual intelligence from Shorts |
Product range, warehouse layout, branded cabinet line, seasonal inventory |
Integration with Other Skills
| Skill |
Integration point |
deep-research |
Video is Phase 3/4 source alongside web search |
intelligence-dossier |
Video transcripts feed People, Products, Financial Signals sections |
client-discovery-osint |
Customer testimonial videos name clients directly |
supplier-verification |
Product demo videos show supplier logos and branded products |
era-validated-linkedin-analysis |
Video upload dates cross-reference LinkedIn activity timeline |
contact-sheet-image-analysis |
Video screenshots can be batch-analyzed via thumbnail grids |
1---2name: video-intelligence3description: Video Intelligence Extraction4---5# Video Intelligence Extraction67## Overview89Extract actionable intelligence from video content across YouTube, Vimeo, and company websites. Video reveals what text sources cannot: facility scale, team composition, product demonstrations, signage, event attendance, and operational reality.1011This skill focuses on metadata + caption extraction (fast, free, no storage cost) and only downloads actual video files when visual analysis is required.1213---1415## When to Use1617- Target company has a YouTube channel (even a small one — Shorts count)18- Founder or leadership appears in podcast/interview videos19- Product demos or walkthroughs exist on any platform20- Event recordings show the company's booth, presentations, or attendance21- You need to verify claims (facility size, team count, product range) with visual evidence22- Company website has embedded video content2324---2526## Prerequisites2728| Tool | Install | Purpose |29|------|---------|---------|30| yt-dlp | `pip install yt-dlp` or `brew install yt-dlp` | Video metadata/caption download |31| Python 3.10+ | System | VTT parsing, automation |32| Whisper (optional) | `pip install openai-whisper` | Transcription when no captions exist |33| ffmpeg (optional) | `brew install ffmpeg` | Audio extraction for Whisper |3435---3637## Phase 1: Discovery3839### YouTube Channel Enumeration4041```bash42# List ALL videos on a channel (returns video IDs + titles, no download)43yt-dlp --flat-playlist --print "%(id)s %(title)s" "https://www.youtube.com/@CHANNEL_HANDLE"4445# Include upload dates for timeline analysis46yt-dlp --flat-playlist --print "%(upload_date)s %(id)s %(title)s" "https://www.youtube.com/@CHANNEL_HANDLE"47```4849### YouTube Search5051```bash52# Search YouTube (top 10 results)53yt-dlp --flat-playlist --print "%(id)s %(title)s" "ytsearch10:COMPANY_NAME"5455# Broader search (top 30)56yt-dlp --flat-playlist --print "%(id)s %(title)s" "ytsearch30:COMPANY_NAME founder interview"57```5859### Other Discovery Methods6061| Source | Method |62|--------|--------|63| Google Video search | `site:youtube.com "Company Name"` |64| Company website | Inspect source for `<iframe src="youtube.com/embed/...">` |65| Vimeo | `yt-dlp --flat-playlist "https://vimeo.com/USER"` |66| LinkedIn | Manual browse — videos cannot be enumerated programmatically |67| Event platforms | Check Sched/Emamo session pages for embedded video links |6869### Discovery Checklist7071- [ ] Target company's own YouTube channel72- [ ] Founder/CEO personal channel or guest appearances73- [ ] Industry podcast channels where target was interviewed74- [ ] Event/conference channels with target's presentations75- [ ] Partner/supplier channels mentioning target76- [ ] Local news/media channels covering target7778---7980## Phase 2: Capture8182### Metadata + Captions (Default — No Video Download)8384```bash85# Entire channel — metadata + auto-captions, no video files86yt-dlp --write-info-json --write-auto-sub --sub-lang en --skip-download \87 --sleep-interval 2 "https://www.youtube.com/@CHANNEL"8889# Single video90yt-dlp --write-info-json --write-auto-sub --sub-lang en --skip-download \91 "https://www.youtube.com/watch?v=VIDEO_ID"9293# Multiple specific videos from a list94yt-dlp --write-info-json --write-auto-sub --sub-lang en --skip-download \95 --sleep-interval 2 -a video_urls.txt96```9798### Key yt-dlp Flags99100| Flag | Purpose |101|------|---------|102| `--write-info-json` | Saves full metadata (title, date, duration, description, tags, view count) |103| `--write-auto-sub` | Downloads auto-generated captions |104| `--sub-lang en` | English captions only |105| `--skip-download` | No video/audio file — metadata only |106| `--sleep-interval 2` | Rate limiting (critical for channels with 30+ videos) |107| `--flat-playlist` | List mode — enumerate without downloading anything |108| `-f "bestvideo[height<=720]+bestaudio"` | Download video at reasonable quality |109| `-x --audio-format mp3` | Extract audio only (for Whisper) |110111### Download Video Files (When Visual Analysis Needed)112113```bash114# 720p max (good balance of quality vs. file size)115yt-dlp -f "bestvideo[height<=720]+bestaudio" --merge-output-format mp4 VIDEO_URL116117# Audio only for Whisper transcription118yt-dlp -x --audio-format mp3 VIDEO_URL119```120121### Output File Structure122123```124project/125├── raw/video/126│ ├── Title of Video [VIDEO_ID].info.json # Metadata127│ ├── Title of Video [VIDEO_ID].en.vtt # Auto-captions (VTT format)128│ └── Title of Video [VIDEO_ID].mp4 # Video file (only if downloaded)129├── transcripts/130│ ├── VIDEO_ID_transcript.txt # Parsed plain text131│ └── VIDEO_ID_transcript.txt132└── analysis/133 └── video_intelligence_summary.md134```135136---137138## Phase 3: VTT Parsing139140### Convert VTT Captions to Plain Text141142```python143import re144import glob145146def vtt_to_text(vtt_path):147 """Parse VTT subtitle file to clean plain text."""148 with open(vtt_path) as f:149 content = f.read()150 # Remove WEBVTT header and metadata151 content = re.sub(r'WEBVTT.*?\n', '', content, flags=re.DOTALL)152 # Remove timestamp lines153 content = re.sub(r'\d{2}:\d{2}:\d{2}\.\d+ --> .*', '', content)154 # Remove HTML-style formatting tags155 content = re.sub(r'<[^>]+>', '', content)156 # Deduplicate consecutive repeated lines and join157 lines = []158 for l in content.split('\n'):159 l = l.strip()160 if l and not l.isdigit() and (not lines or l != lines[-1]):161 lines.append(l)162 return ' '.join(lines)163164165def batch_parse_vtts(vtt_dir, output_dir):166 """Parse all VTT files in a directory to plain text."""167 import os168 os.makedirs(output_dir, exist_ok=True)169 for vtt_file in glob.glob(f"{vtt_dir}/*.vtt"):170 text = vtt_to_text(vtt_file)171 # Extract video ID from filename pattern "Title [VIDEO_ID].en.vtt"172 video_id = re.search(r'\[([^\]]+)\]', vtt_file)173 if video_id:174 out_name = f"{video_id.group(1)}_transcript.txt"175 else:176 out_name = os.path.basename(vtt_file).replace('.en.vtt', '_transcript.txt')177 with open(os.path.join(output_dir, out_name), 'w') as f:178 f.write(text)179 print(f"Parsed: {out_name} ({len(text.split())} words)")180```181182---183184## Phase 4: Whisper Fallback (No Captions Available)185186When auto-captions don't exist (common for Shorts, older videos, non-English content):187188```bash189# Download audio only190yt-dlp -x --audio-format mp3 -o "%(id)s.%(ext)s" VIDEO_URL191192# Transcribe with Whisper193whisper VIDEO_ID.mp3 --model base --language en --output_format txt194195# For better proper noun accuracy (3-5x slower):196whisper VIDEO_ID.mp3 --model medium --language en --output_format txt197```198199### When Captions Typically Don't Exist200201- YouTube Shorts (< 60 seconds)202- Videos uploaded before ~2015 (pre-auto-caption era)203- Unlisted/private videos204- Music-heavy content with minimal speech205- Non-English content without specified language206207---208209## Phase 5: Intelligence Extraction210211### Metadata Intelligence (from info.json)212213Every `.info.json` file contains:214215| Field | Intelligence value |216|-------|-------------------|217| `upload_date` | Timeline of company activity and content strategy |218| `duration` | Short = promo; Long = substantive interview |219| `description` | Often contains links, names, timestamps, partner mentions |220| `tags` | SEO strategy, self-identified categories |221| `view_count` | Which content resonates with their audience |222| `like_count` | Engagement quality signal |223| `channel` | Who published it (their channel vs. guest appearance) |224225### Transcript Intelligence Checklist226227- [ ] Named people (employees, partners, clients)228- [ ] Revenue/growth figures mentioned casually in interviews229- [ ] Future plans or strategy discussed230- [ ] Pain points or challenges admitted231- [ ] Competitor mentions (positive or negative)232- [ ] Hiring plans or team size references233- [ ] Product roadmap or feature announcements234- [ ] Customer names or case studies mentioned verbally235236### Visual Intelligence Checklist (Requires Video Download)237238- [ ] Facility/warehouse size and condition239- [ ] Number of employees visible (team size proxy)240- [ ] Products on shelves or in demos (inventory breadth)241- [ ] Equipment and machinery (capital investment signals)242- [ ] Signage, branding, and logo usage243- [ ] Event booth size and design quality244- [ ] Vehicle fleet (delivery capability)245- [ ] Customer interactions caught on camera246- [ ] Geographic/location identifiers in background247248---249250## Edge Cases and Gotchas2512521. **YouTube rate limiting** — Use `--sleep-interval 2` between downloads. DNS errors appear after 30+ rapid requests. For large channels, break into batches of 20.2532542. **Channel download filenames** — yt-dlp names files as "Title [VIDEO_ID].ext" when downloading from channels. Spaces and special characters in titles break shell scripts. Use `-o "%(id)s.%(ext)s"` for clean filenames.2552563. **Duplicate uploads** — Same interview uploaded under different channels or titles. Cross-reference by duration + upload_date to catch duplicates before spending time analyzing both.2572584. **LinkedIn native videos** — CANNOT be downloaded programmatically. LinkedIn CDN URLs are authenticated and expire within hours. Flag as manual task: "Save as Web Complete" in browser, or screenshot key frames.2592605. **Shorts vs. full videos** — Shorts URL format is `youtube.com/shorts/ID` but yt-dlp handles both formats transparently. Don't skip Shorts — they often show products, facilities, and team culture.2612626. **Unlisted videos** — Not in channel listings but accessible via direct URL. Search engines may have indexed them. Check Google cache and Wayback Machine for unlisted URLs that were once linked publicly.2632647. **Auto-caption quality** — No punctuation, sometimes garbled proper nouns (Celina → Selena, BROGAV → ProGraph). 95%+ word accuracy for standard English speech. Always verify company/person names against known-good sources.2652668. **Upload date vs. content date** — Videos may be uploaded weeks or months after recording. Check description and audio cues for actual recording date. Event videos especially lag.2672689. **info.json is gold** — Always capture with `--write-info-json`. The description field often contains timestamps, guest names, links to resources, and partner mentions that aren't in the video itself.26927010. **VTT timestamps are valuable** — The plain-text conversion loses temporal information. Keep original VTT files as source of truth. Timestamps let you find specific moments for visual verification.271272---273274## Anti-Patterns275276| Don't | Do instead |277|-------|-----------|278| Ignore Shorts because they're short | Review Shorts for visual intel (products, facility, team) |279| Assume all videos have captions | Check; fall back to Whisper for uncaptioned content |280| Download video files by default | Start with `--skip-download`; only get video for visual analysis |281| Try to automate LinkedIn video download | Flag as manual task; CDN URLs are authenticated |282| Trust upload_date as content date | Cross-reference with description, audio cues, event dates |283| Download entire channel at once without rate limiting | Use `--sleep-interval 2` and batch into groups of 20 |284| Parse only transcripts, ignore info.json | Metadata contains names, links, tags not in the audio |285| Treat video as inferior to text sources | Video uniquely confirms physical reality (facility, team, products) |286287---288289## Decision Tree290291```292Content is on YouTube?293├── YES → yt-dlp --write-info-json --write-auto-sub --skip-download294│ ├── Has captions? → Parse VTT to text → Extract intelligence295│ └── No captions?296│ ├── Video > 60s with speech? → yt-dlp -x → Whisper → Analyze297│ └── Short/visual only? → Download video → Visual analysis298└── NO →299 ├── Vimeo → yt-dlp supports it (same workflow as YouTube)300 ├── LinkedIn video → Manual "Save as Web Complete" (flag as manual task)301 ├── Company website embed → Extract iframe URL → usually YouTube/Vimeo302 └── Other platform → Check yt-dlp supported sites list303```304305---306307## Real-World Results (BROGAV Case Study)308309| Metric | Value |310|--------|-------|311| Total videos discovered | 45 (9 full-length, 36 Shorts) |312| VTT caption files captured | 10 |313| Transcripts parsed to text | 10 |314| Substantive interviews analyzed | 2 (5,000+ words each) |315| Podcast via RSS/Whisper fallback | 1 (6,489 words) |316| Duplicate videos caught | 1 (same interview, two channels) |317| Unique intelligence extracted | Founder origin story, revenue signals, growth plans, supplier relationships, facility details, team culture |318| Visual intelligence from Shorts | Product range, warehouse layout, branded cabinet line, seasonal inventory |319320---321322## Integration with Other Skills323324| Skill | Integration point |325|-------|------------------|326| `deep-research` | Video is Phase 3/4 source alongside web search |327| `intelligence-dossier` | Video transcripts feed People, Products, Financial Signals sections |328| `client-discovery-osint` | Customer testimonial videos name clients directly |329| `supplier-verification` | Product demo videos show supplier logos and branded products |330| `era-validated-linkedin-analysis` | Video upload dates cross-reference LinkedIn activity timeline |331| `contact-sheet-image-analysis` | Video screenshots can be batch-analyzed via thumbnail grids |