
Video Content Analyzer
- 679 installs
- 194 repo stars
- Updated January 23, 2026
- bradautomates/head-of-content
video-content-analyzer is a Python Claude Code skill that analyzes Instagram Reels, TikTok, and YouTube Shorts with Gemini to extract hooks, structure, and replicable short-form patterns.
About
video-content-analyzer is a Python script skill that sends short-form videos from Instagram Reels, TikTok, and YouTube Shorts to Google's Gemini API for structured content analysis. It uses gemini-2.5-flash to identify hooks, narrative structure, pacing, and replicable creative patterns developers can feed into content pipelines. The script accepts platform-specific metadata fields, loads credentials from environment variables via dotenv, and outputs JSON-friendly analysis for content strategists automating competitive research. Developers reach for video-content-analyzer when batch-analyzing competitor shorts or building AI-assisted content briefs from social video URLs.
- Analyzes short-form video via Gemini (default model gemini-2.5-flash) for hooks and structure
- Supports Instagram Reels, TikTok, and YouTube Shorts with platform-specific field mappings
- Python CLI with argparse, optional requests and google-genai imports
- Focuses on replicable content patterns rather than vanity metrics alone
- Loads credentials via dotenv when available for API access
Video Content Analyzer by the numbers
- 679 all-time installs (skills.sh)
- Ranked #662 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bradautomates/head-of-content --skill video-content-analyzerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 679 |
|---|---|
| repo stars | ★ 194 |
| Security audit | 2 / 3 scanners passed |
| Last updated | January 23, 2026 |
| Repository | bradautomates/head-of-content ↗ |
How do you analyze short-form video hooks with AI?
Analyze Instagram Reels, TikTok, and YouTube Shorts with Gemini to extract hooks, structure, and replicable short-form patterns for content strategy.
Who is it for?
Content engineers automating competitive short-form video research across Instagram, TikTok, and YouTube Shorts.
Skip if: Long-form video production pipelines or teams without Gemini API credentials and platform video URLs.
When should I use this skill?
The developer wants to analyze Reels, TikToks, or YouTube Shorts for hooks, structure, or replicable content patterns.
What you get
JSON analysis reports with hooks, narrative structure, and replicable short-form content patterns per platform.
- JSON content analysis
- Hook and structure breakdowns
By the numbers
- Supports 3 short-form platforms: Instagram Reels, TikTok, and YouTube Shorts
- Uses Gemini model gemini-2.5-flash
Files
Video Content Analyzer
Analyze short-form videos with Gemini AI to extract hooks, content structure, delivery style, and CTA strategies.
Prerequisites
GEMINI_API_KEYenvironment variablegoogle-genaiandrequestsPython packages
Usage
python3 .claude/skills/video-content-analyzer/scripts/analyze_videos.py \
--input outliers.json \
--output video-analysis.json \
--platform instagram \
--max-videos 5Parameters
| Arg | Description |
|---|---|
--input, -i | Input JSON file with outlier posts (required) |
--output, -o | Output JSON file for results (required) |
--platform, -p | Platform: instagram, tiktok, or youtube (default: instagram) |
--max-videos, -n | Max videos to analyze (default: 5) |
Input Format
Accepts outlier JSON from platform-specific research skills. Handles both formats:
- Direct list:
[{post1}, {post2}, ...] - Wrapped:
{"outliers": [{post1}, {post2}, ...]}
The script automatically maps platform-specific fields:
| Platform | Video URL Fields | Caption | Username |
|---|---|---|---|
videoUrl | caption | ownerUsername | |
| TikTok | videoUrl, video_url, webVideoUrl | text, desc | authorUsername |
| YouTube | videoUrl, url | title | channelTitle |
TikTok Note: The Apify TikTok Scraper returns webVideoUrl (the TikTok page URL) rather than a direct video download URL. Gemini will attempt to analyze from this page URL.
Output
Returns JSON array with analysis for each video:
[
{
"post_id": "ABC123",
"username": "creator",
"url": "https://...",
"platform": "instagram",
"likes": 50000,
"comments": 1200,
"views": 500000,
"analysis": {
"hook": {
"technique": "pattern-interrupt",
"opening_line": "Stop scrolling if you...",
"attention_grab": "Creates urgency and targets specific audience",
"replicable_formula": "Stop scrolling if you [pain point]"
},
"content_structure": {
"format": "problem-solution",
"sections": [...],
"pacing": "fast",
"retention_techniques": ["pattern interrupts", "text overlays"]
},
"delivery_style": {
"speaking": "direct-to-camera",
"energy": "high-energy",
"text_overlays": true,
"visual_style": "quick cuts with b-roll"
},
"cta_strategy": {
"type": "follow",
"cta_text": "Follow for more tips",
"placement": "end"
},
"why_it_works": "..."
}
}
]Hook Techniques
The analyzer identifies these hook types:
pattern-interrupt- Breaks expected patternsquestion- Opens with engaging questionbold-claim- Makes surprising statementstory-tease- Hints at compelling narrativevisual-shock- Striking visual openingcuriosity-gap- Creates information gapdirect-address- Speaks to specific audiencecontroversial-take- Polarizing opinionrelatable-pain- Targets common struggletransformation-preview- Shows before/after
Content Formats
problem-solution- Present problem, offer fixlisticle- Numbered tips/itemsstory- Narrative arctutorial- Step-by-step how-tobefore-after- Transformation revealday-in-life- Lifestyle contentreaction- Response to other contenthot-take- Opinion piecetool-demo- Product/tool showcase
#!/usr/bin/env python3
"""
Analyze short-form videos with Gemini AI.
Supports Instagram Reels, TikTok, and YouTube Shorts.
Focuses on content structure, hooks, and replicable patterns.
"""
import json
import argparse
import os
import io
import time
import re
from pathlib import Path
# Conditional imports
try:
import requests
REQUESTS_AVAILABLE = True
except ImportError:
REQUESTS_AVAILABLE = False
try:
from google import genai
from google.genai import types
GEMINI_AVAILABLE = True
except ImportError:
GEMINI_AVAILABLE = False
# Load environment variables
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
GEMINI_MODEL = "gemini-2.5-flash"
# Platform-specific field mappings
PLATFORM_MAPPINGS = {
"instagram": {
"video_url": ["videoUrl"],
"post_id": ["shortCode", "id"],
"caption": ["caption"],
"username": ["ownerUsername"],
"url": ["url"],
"likes": ["likesCount"],
"comments": ["commentsCount"],
"views": ["videoViewCount", "videoPlayCount"],
"is_video": lambda p: (
p.get('videoUrl') and (
p.get('type', '').lower() == 'video' or
p.get('productType', '').lower() == 'clips' or
p.get('isVideo')
)
),
},
"tiktok": {
"video_url": ["videoUrl", "video_url", "webVideoUrl"],
"post_id": ["id", "video_id"],
"caption": ["text", "desc", "caption", "description"],
"username": ["authorUsername", "author", "authorMeta.name", "username"],
"url": ["webVideoUrl", "url"],
"likes": ["diggCount", "likes", "likesCount"],
"comments": ["commentCount", "comments", "commentsCount"],
"views": ["playCount", "plays", "viewCount", "views"],
"is_video": lambda p: bool(get_field(p, ["videoUrl", "video_url", "webVideoUrl"])),
},
"youtube": {
"video_url": ["videoUrl", "url"],
"post_id": ["videoId", "id"],
"caption": ["title", "description"],
"username": ["channelTitle", "channel", "author"],
"url": ["url", "videoUrl"],
"likes": ["likeCount", "likes"],
"comments": ["commentCount", "comments"],
"views": ["viewCount", "views"],
"is_video": lambda p: True, # YouTube results are always videos
},
}
VIDEO_ANALYSIS_PROMPT = '''Analyze this short-form video focusing on CONTENT STRUCTURE and HOOK TECHNIQUE.
CAPTION/TITLE CONTEXT:
{caption}
Analyze the video and return a JSON object with this exact structure:
{{
"hook": {{
"technique": "<one of: pattern-interrupt, question, bold-claim, story-tease, visual-shock, curiosity-gap, direct-address, controversial-take, relatable-pain, transformation-preview>",
"opening_line": "<exact words or description of what's said/shown in first 3 seconds>",
"attention_grab": "<why this hook works - be specific about the psychological trigger>",
"replicable_formula": "<template version of this hook that could be adapted, e.g. 'If you [action], you're [consequence]'>"
}},
"content_structure": {{
"format": "<one of: problem-solution, listicle, story, tutorial, before-after, day-in-life, reaction, transformation, hot-take, tool-demo>",
"sections": [
{{
"name": "<section name like 'Hook', 'Problem', 'Solution', 'CTA'>",
"duration_pct": <percentage of video>,
"description": "<what happens in this section>"
}}
],
"pacing": "<one of: rapid-fire, fast, moderate, slow>",
"retention_techniques": ["<list techniques used to keep viewers watching>"]
}},
"delivery_style": {{
"speaking": "<one of: direct-to-camera, voiceover, text-only, mixed, no-speech>",
"energy": "<one of: high-energy, conversational, calm-authority, urgent>",
"text_overlays": <true/false>,
"visual_style": "<description of editing style, transitions, b-roll usage>"
}},
"cta_strategy": {{
"type": "<one of: comment-keyword, link-bio, follow, save, share, dm, none>",
"cta_text": "<exact CTA if present>",
"placement": "<where in video the CTA appears>"
}},
"why_it_works": "<2-3 sentence analysis of why this content performs well>"
}}
Focus on ACTIONABLE insights that could be replicated. Be specific about techniques.
Return ONLY valid JSON, no other text.'''
def get_field(post: dict, field_names: list, default=None):
"""Get field value trying multiple possible field names."""
for name in field_names:
# Handle nested fields like "authorMeta.name"
if '.' in name:
parts = name.split('.')
value = post
for part in parts:
if isinstance(value, dict):
value = value.get(part)
else:
value = None
break
if value is not None:
return value
elif name in post and post[name] is not None:
return post[name]
return default
def extract_post_data(post: dict, platform: str) -> dict:
"""Extract normalized data from a post using platform mapping."""
mapping = PLATFORM_MAPPINGS.get(platform, PLATFORM_MAPPINGS["instagram"])
return {
"video_url": get_field(post, mapping["video_url"]),
"post_id": get_field(post, mapping["post_id"], "unknown"),
"caption": get_field(post, mapping["caption"], ""),
"username": get_field(post, mapping["username"], "unknown"),
"url": get_field(post, mapping["url"], ""),
"likes": get_field(post, mapping["likes"], 0) or 0,
"comments": get_field(post, mapping["comments"], 0) or 0,
"views": get_field(post, mapping["views"], 0) or 0,
"engagement_score": post.get('_engagement_score', 0),
"engagement_rate": post.get('_engagement_rate', 0),
}
def is_video_post(post: dict, platform: str) -> bool:
"""Check if post is a video using platform-specific logic."""
mapping = PLATFORM_MAPPINGS.get(platform, PLATFORM_MAPPINGS["instagram"])
is_video_fn = mapping.get("is_video", lambda p: bool(get_field(p, mapping["video_url"])))
return is_video_fn(post)
def parse_response(text: str) -> dict:
"""Parse Gemini response, handling markdown code blocks."""
json_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text)
if json_match:
text = json_match.group(1)
try:
return json.loads(text.strip())
except json.JSONDecodeError:
return {"raw_analysis": text}
def download_video(video_url: str, timeout: int = 60) -> bytes:
"""Download video from URL."""
response = requests.get(video_url, timeout=timeout)
response.raise_for_status()
return response.content
def upload_video_to_gemini(client, video_bytes: bytes, display_name: str):
"""Upload video bytes to Gemini File API."""
buffer = io.BytesIO(video_bytes)
file = client.files.upload(
file=buffer,
config=types.UploadFileConfig(
mime_type='video/mp4',
display_name=display_name
)
)
return file
def wait_for_processing(client, file, timeout: int = 300):
"""Wait for Gemini to process uploaded file."""
start = time.time()
while time.time() - start < timeout:
file = client.files.get(name=file.name)
if file.state.name == "ACTIVE":
return file
elif file.state.name == "FAILED":
raise RuntimeError(f"File processing failed: {file.name}")
time.sleep(5)
raise TimeoutError(f"File processing timeout: {file.name}")
def analyze_video(client, video_source, caption: str) -> dict:
"""Analyze video with Gemini."""
prompt = VIDEO_ANALYSIS_PROMPT.format(caption=caption[:1000] if caption else "No caption")
response = client.models.generate_content(
model=GEMINI_MODEL,
contents=[video_source, prompt],
config=types.GenerateContentConfig(
response_modalities=["TEXT"],
),
)
if response.candidates and response.candidates[0].content:
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
return parse_response(part.text)
return {"error": "No response from Gemini"}
def analyze_videos(outliers: list[dict], platform: str = "instagram", max_videos: int = 5) -> list[dict]:
"""
Analyze top outlier videos with Gemini AI.
Args:
outliers: List of outlier posts from any platform
platform: Platform name for field mapping (instagram, tiktok, youtube)
max_videos: Maximum number of videos to analyze
Returns:
List of analysis results for each video.
"""
if not GEMINI_AVAILABLE:
print("Error: google-genai package not installed")
print(" Install with: pip install google-genai")
return []
if not REQUESTS_AVAILABLE:
print("Error: requests package not installed")
return []
api_key = os.environ.get('GEMINI_API_KEY')
if not api_key:
print("Error: GEMINI_API_KEY not set")
return []
client = genai.Client(api_key=api_key)
# Filter to videos only using platform-specific logic
video_posts = [p for p in outliers if is_video_post(p, platform)][:max_videos]
if not video_posts:
print("No video content found in outliers")
return []
print(f"Analyzing {len(video_posts)} {platform} videos with Gemini AI...")
results = []
for i, post in enumerate(video_posts, 1):
data = extract_post_data(post, platform)
video_url = data["video_url"]
if not video_url:
print(f" [{i}/{len(video_posts)}] Skipping - no video URL")
continue
print(f" [{i}/{len(video_posts)}] Analyzing @{data['username']} - {data['post_id']}...")
result = {
"post_id": data["post_id"],
"username": data["username"],
"url": data["url"],
"platform": platform,
"engagement_score": data["engagement_score"],
"engagement_rate": data["engagement_rate"],
"likes": data["likes"],
"comments": data["comments"],
"views": data["views"],
}
try:
# Try direct URL first
try:
analysis = analyze_video(client, video_url, data["caption"])
if 'error' not in analysis and 'raw_analysis' not in analysis:
result['analysis'] = analysis
hook = analysis.get('hook', {}).get('technique', 'analyzed')
print(f" Done: {hook}")
results.append(result)
time.sleep(2)
continue
except Exception:
print(f" Direct URL failed, trying upload...")
# Fallback: download and upload
video_bytes = download_video(video_url)
print(f" Downloaded {len(video_bytes) / 1024 / 1024:.1f} MB")
file = upload_video_to_gemini(client, video_bytes, f"{platform}_{data['post_id']}")
print(f" Uploaded, processing...")
file = wait_for_processing(client, file)
analysis = analyze_video(client, file, data["caption"])
result['analysis'] = analysis
hook = analysis.get('hook', {}).get('technique', 'analyzed')
print(f" Done: {hook}")
# Cleanup
try:
client.files.delete(name=file.name)
except:
pass
results.append(result)
time.sleep(2)
except Exception as e:
print(f" Error: {e}")
result['error'] = str(e)
results.append(result)
successful = sum(1 for r in results if 'analysis' in r)
print(f"Successfully analyzed {successful}/{len(video_posts)} videos")
return results
def main():
parser = argparse.ArgumentParser(description='Analyze short-form videos with Gemini AI')
parser.add_argument('--input', '-i', required=True,
help='Input JSON file with outlier posts')
parser.add_argument('--output', '-o', required=True,
help='Output JSON file for video analysis results')
parser.add_argument('--platform', '-p', default='instagram',
choices=['instagram', 'tiktok', 'youtube'],
help='Platform for field mapping (default: instagram)')
parser.add_argument('--max-videos', '-n', type=int, default=5,
help='Maximum videos to analyze (default: 5)')
args = parser.parse_args()
print(f"Loading outliers from: {args.input}")
with open(args.input, 'r') as f:
data = json.load(f)
# Handle both list format and dict with 'outliers' key
if isinstance(data, dict) and 'outliers' in data:
outliers = data['outliers']
else:
outliers = data
print(f"Loaded {len(outliers)} outlier posts")
results = analyze_videos(outliers, args.platform, args.max_videos)
# Save results
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
with open(args.output, 'w') as f:
json.dump(results, f, indent=2, default=str)
print(f"\nVideo analysis saved to: {args.output}")
if __name__ == '__main__':
main()
Related skills
How it compares
Pick video-content-analyzer for automated short-form video deconstruction rather than manual transcript-only summarization skills.
FAQ
Which platforms does video-content-analyzer support?
video-content-analyzer supports Instagram Reels, TikTok, and YouTube Shorts, mapping platform-specific metadata fields like videoUrl, shortCode, and captions before Gemini analysis.
Which Gemini model does video-content-analyzer use?
video-content-analyzer calls Google's gemini-2.5-flash model through the google-genai Python SDK to extract hooks, structure, and replicable short-form content patterns.
Is Video Content Analyzer safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.