Install
$ agentstack add skill-nikhilbhansali-youtube-data-skills-youtube-title-tag-optimizer ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
YouTube Title & Tag Optimizer
Optimize your video title, tags, and description before publishing by analyzing what works for top-ranking videos.
Usage
/youtube-title-tag-optimizer "air fryer recipes"
/youtube-title-tag-optimizer "Python tutorial" --my-title "Learn Python in 10 Minutes"
/youtube-title-tag-optimizer --keyword "home workout" --my-title "Best Home Workout for Beginners 2024"
Instructions
When the user invokes this skill:
Step 1: Parse Arguments
Extract from the user's input:
- Keyword (required): The topic/keyword to optimize for
- --my-title "..." (optional): User's working title to score and improve
Step 2: Get API Key
Check the user's Claude memory for a YouTube Data API v3 key. If not found, ask: > "I need a YouTube Data API v3 key to analyze ranking videos. You can get one from the Google Cloud Console. Please paste your key."
Step 3: Write the Script
Write the following Python script to /tmp/_yt_title_optimizer_XXXX.py (where XXXX is a random suffix, e.g. $(openssl rand -hex 4)):
#!/usr/bin/env python3
"""
YouTube Title & Tag Optimizer
Analyzes top-ranking videos for a keyword to extract winning title patterns,
effective tags, and generate optimization recommendations.
Usage:
YT_API_KEY=KEY python3 /tmp/_yt_title_optimizer_XXXX.py "keyword"
YT_API_KEY=KEY python3 /tmp/_yt_title_optimizer_XXXX.py "keyword" --my-title "My Title"
"""
import argparse
import json
import os
import re
import sys
from datetime import datetime, timezone
from collections import Counter
try:
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
except ImportError:
print("ERROR: google-api-python-client not installed. Run: pip3 install google-api-python-client")
sys.exit(1)
def parse_duration(iso_duration: str) -> int:
m = re.match(r"PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?", iso_duration or "")
if not m:
return 0
return int(m.group(1) or 0) * 3600 + int(m.group(2) or 0) * 60 + int(m.group(3) or 0)
def format_number(n: int) -> str:
if n >= 1_000_000: return f"{n/1_000_000:.1f}M"
if n >= 1_000: return f"{n/1_000:.1f}K"
return str(n)
def analyze_title(title: str) -> dict:
"""Analyze a single title for patterns."""
analysis = {
"length_chars": len(title),
"length_words": len(title.split()),
"has_number": bool(re.search(r'\d', title)),
"has_question": title.rstrip().endswith("?"),
"has_how_to": bool(re.search(r'how\s+to', title, re.I)),
"has_brackets": bool(re.search(r'[\[\(]', title)),
"has_parentheses": bool(re.search(r'\(', title)),
"has_caps_word": bool(re.search(r'\b[A-Z]{2,}\b', title)),
"has_emoji": bool(re.search(r'[^\w\s,.\-!?\'\"()\[\]:;/\\@#$%^&*+=~`|<>]', title)),
"has_year": bool(re.search(r'20[12]\d', title)),
"has_list_format": bool(re.search(r'^\d+\s', title)),
"has_vs": bool(re.search(r'\bvs\.?\b', title, re.I)),
"has_colon": ":" in title,
"has_pipe": "|" in title,
"has_dash_separator": " - " in title or " — " in title,
"has_exclamation": "!" in title,
"starts_with_how": title.lower().startswith("how"),
"starts_with_why": title.lower().startswith("why"),
"starts_with_what": title.lower().startswith("what"),
"starts_with_number": bool(re.match(r'^\d', title)),
}
# Detect power words
power_words = {
"urgency": ["now", "today", "immediately", "urgent", "hurry", "fast", "quick", "instantly", "stop"],
"curiosity": ["secret", "hidden", "surprising", "shocking", "unexpected", "weird", "strange", "mystery", "truth", "reveal"],
"value": ["free", "best", "top", "ultimate", "complete", "guide", "hack", "tips", "tricks", "easy", "simple"],
"emotional": ["amazing", "incredible", "insane", "mind-blowing", "life-changing", "game-changer", "beautiful", "perfect", "love", "hate"],
"fear": ["mistake", "wrong", "avoid", "never", "worst", "dangerous", "warning", "risk", "fail", "scam", "don't"],
}
title_lower = title.lower()
found_power_words = {}
for category, words in power_words.items():
matches = [w for w in words if w in title_lower]
if matches:
found_power_words[category] = matches
analysis["power_words"] = found_power_words
# Detect title structure/formula
structures = []
if re.match(r'^\d+\s', title):
structures.append("listicle")
if re.search(r'how\s+to', title, re.I):
structures.append("how-to")
if title.rstrip().endswith("?"):
structures.append("question")
if re.search(r'\bvs\.?\b', title, re.I):
structures.append("comparison")
if re.search(r'\breview\b', title, re.I):
structures.append("review")
if re.search(r'in\s+\d+\s+(minute|min|second|sec|hour|day|step)', title, re.I):
structures.append("time-bound")
if re.search(r'for\s+(beginners|newbies|noobs|starters)', title, re.I):
structures.append("beginner-targeted")
if re.search(r'(complete|ultimate|full|definitive)\s+guide', title, re.I):
structures.append("comprehensive-guide")
if re.search(r'\b(do|don\'t|never|stop|avoid)\b', title, re.I):
structures.append("imperative")
analysis["structures"] = structures
return analysis
def main():
parser = argparse.ArgumentParser(description="Optimize YouTube titles and tags")
parser.add_argument("keyword", help="Keyword to optimize for")
parser.add_argument("--my-title", default=None, help="Your working title to score")
parser.add_argument("--output-dir", default=None, help="Output directory")
args = parser.parse_args()
api_key = os.environ.get("YT_API_KEY")
if not api_key:
print("ERROR: YT_API_KEY environment variable not set.")
sys.exit(1)
youtube = build("youtube", "v3", developerKey=api_key)
quota_used = 0
print(f"Analyzing keyword: {args.keyword}")
if args.my_title:
print(f"Your title: {args.my_title}\n")
# --- Search by relevance ---
print("Fetching top-ranking videos (by relevance)...")
try:
relevance_resp = youtube.search().list(
part="snippet", q=args.keyword, type="video",
order="relevance", maxResults=50,
).execute()
relevance_ids = [item["id"]["videoId"] for item in relevance_resp.get("items", [])]
quota_used += 100
except HttpError as e:
print(f"Search error: {e}")
sys.exit(1)
# --- Search by view count ---
print("Fetching most-viewed videos...")
try:
viewcount_resp = youtube.search().list(
part="snippet", q=args.keyword, type="video",
order="viewCount", maxResults=50,
).execute()
viewcount_ids = [item["id"]["videoId"] for item in viewcount_resp.get("items", [])]
quota_used += 100
except HttpError as e:
viewcount_ids = []
print(f"View count search error: {e}")
# Combine and deduplicate
all_ids = list(dict.fromkeys(relevance_ids + viewcount_ids))
print(f"Total unique videos: {len(all_ids)}")
# --- Fetch video details ---
print("Fetching video details...")
videos = []
for i in range(0, len(all_ids), 50):
batch = all_ids[i:i+50]
try:
resp = youtube.videos().list(
part="snippet,statistics,contentDetails",
id=",".join(batch),
).execute()
videos.extend(resp.get("items", []))
quota_used += 1
except HttpError as e:
print(f"Error: {e}")
print(f" Got details for {len(videos)} videos")
# --- Process videos ---
processed = []
all_tags = Counter()
title_analyses = []
for v in videos:
views = int(v.get("statistics", {}).get("viewCount", 0))
likes = int(v.get("statistics", {}).get("likeCount", 0))
comments = int(v.get("statistics", {}).get("commentCount", 0))
duration = parse_duration(v.get("contentDetails", {}).get("duration", ""))
title = v["snippet"]["title"]
tags = v.get("snippet", {}).get("tags", [])
for tag in tags:
all_tags[tag.lower()] += 1
t_analysis = analyze_title(title)
title_analyses.append(t_analysis)
processed.append({
"video_id": v["id"],
"title": title,
"title_analysis": t_analysis,
"channel": v["snippet"]["channelTitle"],
"views": views,
"likes": likes,
"comments": comments,
"engagement_rate": round((likes + comments) / max(views, 1) * 100, 2),
"duration_sec": duration,
"tags": tags,
"tag_count": len(tags),
"description_first_line": v["snippet"].get("description", "").split("\n")[0][:200],
"published_at": v["snippet"]["publishedAt"],
})
processed.sort(key=lambda x: x["views"], reverse=True)
# --- Aggregate title analysis ---
total = len(title_analyses) or 1
aggregate_title = {
"avg_length_chars": round(sum(t["length_chars"] for t in title_analyses) / total, 1),
"avg_length_words": round(sum(t["length_words"] for t in title_analyses) / total, 1),
"pct_has_number": round(sum(1 for t in title_analyses if t["has_number"]) / total * 100, 1),
"pct_question": round(sum(1 for t in title_analyses if t["has_question"]) / total * 100, 1),
"pct_how_to": round(sum(1 for t in title_analyses if t["has_how_to"]) / total * 100, 1),
"pct_brackets": round(sum(1 for t in title_analyses if t["has_brackets"]) / total * 100, 1),
"pct_caps_word": round(sum(1 for t in title_analyses if t["has_caps_word"]) / total * 100, 1),
"pct_emoji": round(sum(1 for t in title_analyses if t["has_emoji"]) / total * 100, 1),
"pct_year": round(sum(1 for t in title_analyses if t["has_year"]) / total * 100, 1),
"pct_list_format": round(sum(1 for t in title_analyses if t["has_list_format"]) / total * 100, 1),
"pct_colon": round(sum(1 for t in title_analyses if t["has_colon"]) / total * 100, 1),
"pct_pipe": round(sum(1 for t in title_analyses if t["has_pipe"]) / total * 100, 1),
"pct_exclamation": round(sum(1 for t in title_analyses if t["has_exclamation"]) / total * 100, 1),
}
structure_counter = Counter()
for t in title_analyses:
for s in t["structures"]:
structure_counter[s] += 1
aggregate_title["structures"] = {k: round(v/total*100, 1) for k, v in structure_counter.most_common()}
power_category_counter = Counter()
for t in title_analyses:
for cat in t["power_words"]:
power_category_counter[cat] += 1
aggregate_title["power_word_categories"] = {k: round(v/total*100, 1) for k, v in power_category_counter.most_common()}
word_counter = Counter()
stop_words = {"the", "a", "an", "is", "it", "in", "on", "at", "to", "for", "of", "and", "or", "but", "with", "you", "your", "my", "this", "that", "i", "me", "we", "how", "what", "why", "do", "does", "can", "will", "be", "are", "was", "not", "no", "so", "if"}
for v in processed:
words = re.findall(r'[a-zA-Z]{3,}', v["title"].lower())
for w in words:
if w not in stop_words:
word_counter[w] += 1
aggregate_title["common_title_words"] = word_counter.most_common(30)
# --- Score user's title if provided ---
my_title_score = None
if args.my_title:
my_analysis = analyze_title(args.my_title)
score = 0
feedback = []
if 40 80:
feedback.append("Title may be too long -- could get truncated in search results")
else:
score += 10
feedback.append("Title length is acceptable but could be optimized")
keyword_lower = args.keyword.lower()
title_lower = args.my_title.lower()
if keyword_lower in title_lower:
score += 20
if title_lower.startswith(keyword_lower) or title_lower[:20].find(keyword_lower) >= 0:
score += 5
feedback.append("Keyword appears early in title -- great for SEO")
else:
feedback.append("Keyword is present in title")
else:
kw_words = keyword_lower.split()
matches = sum(1 for w in kw_words if w in title_lower)
if matches > 0:
score += 10
feedback.append(f"Partial keyword match ({matches}/{len(kw_words)} words)")
else:
feedback.append("WARNING: Keyword not found in title -- critical for SEO")
if my_analysis["has_number"]:
score += 10
feedback.append("Contains a number -- increases click-through rate")
if my_analysis["power_words"]:
score += 10
cats = list(my_analysis["power_words"].keys())
feedback.append(f"Uses power words ({', '.join(cats)})")
else:
feedback.append("Consider adding power words for emotional impact")
if my_analysis["structures"]:
score += 10
feedback.append(f"Uses proven structure: {', '.join(my_analysis['structures'])}")
else:
feedback.append("Consider using a proven structure (how-to, listicle, question)")
if my_analysis["has_brackets"]:
score += 5
feedback.append("Uses brackets -- can boost CTR by 33%")
if my_analysis["has_caps_word"]:
score += 5
feedback.append("Strategic use of CAPS for emphasis")
top_10_titles = [v["title"] for v in processed[:10]]
common_patterns_in_top = set()
for t in top_10_titles:
ta = analyze_title(t)
for s in ta["structures"]:
common_patterns_in_top.add(s)
matching_patterns = set(my_analysis["structures"]) & common_patterns_in_top
if matching_patterns:
score += 10
feedback.append(f"Matches top-performer patterns: {', '.join(matching_patterns)}")
my_title_score = {
"title": args.my_title,
"score": min(score, 100),
"analysis": my_analysis,
"feedback": feedback,
}
# --- Build output ---
output = {
"keyword": args.keyword,
"analyzed_at": datetime.now(timezone.utc).isoformat(),
"total_videos_analyzed": len(processed),
"aggregate_title_analysis": aggregate_title,
"top_tags": all_tags.most_common(50),
"tag_stats": {
"unique_tags": len(all_tags),
"avg_tags_per_video": round(sum(v["tag_count"] for v in processed) / len(processed), 1) if processed else 0,
},
"top_videos": [
{
"title": v["title"],
"video_id": v["video_id"],
"views": v["views"],
"engagement_rate": v["engagement_rate"],
"channel": v["channel"],
"tag_count": v["tag_count"],
"title_analysis": v["title_analysis"],
"description_first_line": v["description_first_line"],
}
for v in processed[:20]
],
"all_videos": processed,
"my_title_score": my_title_score,
"quota_used": {
"total_estimated": quota_used,
},
}
# --- Save ---
safe_kw = re.sub(r'[^a-zA-Z0-9_-]', '_', args.keyword)[:100]
date_str = datetime.now().strftime("%Y%m%d")
output_dir = args.output_dir or f"yt_optimize_{safe_kw}_{date_str}"
os.makedirs(output_dir, exist_ok=True)
output_file = os.path.join(output_dir, "title_tag_data.json")
try:
with open(output_file, "w", encoding="utf-8") as f:
json.dump(output, f, indent=2, ensure_ascii=False)
except (IOErr
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [nikhilbhansali](https://github.com/nikhilbhansali)
- **Source:** [nikhilbhansali/youtube-data-skills](https://github.com/nikhilbhansali/youtube-data-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.