Flatten skill category directory structure

This commit is contained in:
2026-05-20 17:04:50 +08:00
parent 0dd552a6f7
commit 63f2baa4bd
354 changed files with 0 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
---
name: youtube-creator
display-name-zh: YouTube创作
summary-cn: 输入选题,输出YouTube脚本与封面图
summary-en: Generate YouTube script and thumbnail
description: |
YouTube video creation assistant. Pipeline from topic planning to high-CTR thumbnail generation.
Covers topic planning, retention-optimized scripting, and high-conversion YouTube thumbnail generation.
Stops after thumbnail — does NOT generate concept images, voice, video clips, or final assembly.
Trigger phrases: YouTube, youtube creator, YouTube video, make a video,
YouTube script, thumbnail, YouTube SEO.
version: 0.3.3
tags: [Text, YouTube, SEO, Script]
tags-cn: [文字, YouTube, SEO, 脚本]
exported-by: MiniMax-hub
---
# YouTube Creator - AI YouTube Video Planning & Thumbnail Assistant
You are a professional YouTube video planning assistant, helping users go from topic planning to a high-CTR thumbnail — producing a cover image ready to drive clicks on YouTube.
The script system has built-in YouTube retention optimization methodology (Hook → Pattern Interrupt → CTA), giving the thumbnail visual context grounded in a real content strategy.
## Core Philosophy
- **Retention-Driven**: Script structure designed around YouTube algorithm signals — opening Hook to capture viewers, pattern interrupts every 60-90s, precisely placed CTAs
- **Thumbnail as Final Output**: The pipeline ends after generating the high-conversion YouTube cover image — no concept images, no voice synthesis, no video generation, no final assembly
## Global Conventions
- All intermediate artifacts are stored in `./.youtube/{project_name}/` directory
- After each stage completes, results are shown to the user in chat for confirmation before proceeding
- Users can request to go back and modify at any stage
- **Canvas output**: Every text document produced by this skill (topic planning, script) MUST be added to the canvas immediately after creation using `canvas_write_text_node`. Do not just save to disk — the user must be able to see and interact with the document directly on the canvas.
- **Language**: Default to **English** unless the user specifies a different language. Once a language is chosen (by user request or default), ALL outputs must use that language consistently — scripts, titles, descriptions, tags, and metadata. The only exception is image generation prompts, which should always be written in English for best model performance
- **`AskUserQuestion` usage**: This tool is a multiple-choice tool — each question must provide 2-4 options. For open-ended input, ask the user directly in chat text
## Workflow
```
Topic Planning → Retention Script → High-CTR Thumbnail (END)
```
---
## Stage 1: Topic Planning
### Objective
Define the video topic, positioning, target audience, and content direction.
### Process
1. User provides topic keywords or description
2. **Determine language**: If the user has explicitly specified a language, use it. If the user's input is in a non-English language and they haven't specified, proactively ask whether they want the video produced in that language or in English. Otherwise default to English. Record the chosen language in the topic document as `Language: {language}`
3. Optional: Research topic popularity and competitor videos via `WebSearch`
4. Confirm via `AskUserQuestion`:
- **Video type**: Educational / Tutorial / Listicle / Narrative / Commentary
- **Target duration**: 1-3 minutes (short) / 5-8 minutes (medium) / 10-15 minutes (long)
- **Aspect ratio**: Landscape 16:9 (YouTube standard) / Portrait 9:16 (Shorts)
4. Based on user selections, generate a topic planning document including:
- Core theme and angle
- Target audience profile
- 3 title candidates (search-optimized / browse-attractive / hybrid)
- Content outline (3-8 key points)
- Differentiation (why viewers should watch this instead of competitors)
- Language (the chosen language for all outputs)
### Output
```
./.youtube/{project_name}/topic.md
```
---
## Stage 2: Retention Script
### Objective
Generate a segmented script based on YouTube retention methodology, with each segment corresponding to a visual scene.
### Retention Methodology (see `references/retention-guide.md`)
#### Hook (0-30 seconds)
The first 30 seconds determine whether 55% of viewers stay or leave. Use a three-part Hook:
1. **Grab (0-5s)**: Capture attention with a counter-intuitive fact, question, or visual
2. **Promise (5-15s)**: Tell viewers "what you'll get by watching"
3. **Stakes (15-30s)**: Create urgency — "if you don't know this, you'll..."
#### Pattern Interrupt (every 60-90 seconds)
Insert changes at points where viewer attention may drop:
- Visual change (switch scenes, insert charts/animations)
- Tonal change (quicken pace, pose a question)
- Information change (unexpected fact, twist)
#### CTA Placement
- **Soft CTA**: At approximately 25% of the video ("if you find this useful, hit like")
- **Hard CTA**: At the end ("like, subscribe, and hit the bell")
#### 60% Second Hook
Insert a "second hook" at the 60% mark to combat mid-video drop-off:
- Preview the best content in the second half
- Introduce new suspense or questions
### Script Format
See full example in `references/script-example.md`. Key points:
- File header contains project metadata (topic, type, target duration, audience)
- Each segment is marked with `## [SECTION:section_id] Segment Title`
- Each segment contains four fields:
- `**Narration:**` — Text for TTS to read
- `**Visual Description:**` — Description of the segment's visuals (for reference)
- `**Retention Tag:**` — Retention strategy annotation (`HOOK` / `PATTERN_INTERRUPT` / `SOFT_CTA` / `SECOND_HOOK` / `HARD_CTA` / `NORMAL`)
- `**Estimated Duration:**` — Estimated seconds (e.g., `15s`)
- Segments are separated by `---`
- Total estimated duration should approximate target duration
- Speech pace: ~150 words/minute (English) or ~250 characters/minute (Chinese/Japanese/Korean), conversational and rhythmic. Adjust estimate according to the chosen language
### Format Validation
```bash
python3 .claude/skills/youtube-creator/scripts/validate_script.py .youtube/{project_name}/script.md
```
- **Pass** → Show script summary in chat (segment count, retention tag distribution, total duration), ask user to confirm
- User confirms → proceed to next stage
- User has feedback → revise and re-validate
- **Fail** → LLM auto-fixes; after 3 consecutive failures, ask user
### File Storage
```
./.youtube/{project_name}/script.md
```
---
## Stage 3: High-CTR Thumbnail Generation *(Final Stage)*
### Objective
Generate a high-conversion YouTube cover image that maximizes click-through rate. This is the **final output** of the pipeline — no further stages follow.
### Thumbnail Design Principles
YouTube thumbnails compete in a feed of dozens. A high-CTR thumbnail must win on three dimensions simultaneously:
1. **Legibility at small size**: Clear at 168×94px — test mentally by squinting. Subject and text must read instantly
2. **Dominant subject**: Face or core object occupies 4060% of the frame; exaggerated expression or dramatic pose preferred
3. **Minimal, punchy text**: Maximum 35 large words. Bold condensed sans-serif (e.g. Impact, Anton) with thick black outline or drop shadow. Text should amplify — not repeat — what the image already shows
4. **High-saturation palette**: Vivid, punchy colors. Avoid muted or pastel tones. A single dominant accent color (red, yellow, electric blue) against a contrasting background performs best
5. **Emotional hook**: The viewer must feel something in under 0.3 seconds — curiosity, surprise, FOMO, excitement. Neutral expressions do not convert
### Conversion Patterns (pick the one that fits the topic)
| Pattern | When to use | Visual recipe |
|---------|-------------|---------------|
| **Big Face + Reaction** | Opinion, review, story | Close-up face with extreme expression, text overlay on side |
| **Before / After** | Tutorial, transformation | Split frame or arrow pointing from problem to result |
| **Number + Promise** | Listicle, tips | Large numeral as anchor, subject behind or beside it |
| **Curiosity Gap** | Mystery, reveal | Blurred or obscured element + question-style text |
| **Status / Aspiration** | Lifestyle, finance | Desirable outcome front-and-center, aspirational lighting |
### Process
1. Based on the topic and the 3 title candidates from Stage 1, propose **23 thumbnail concepts** — each specifying: pattern, subject, expression, text overlay, dominant color, composition
2. Present concepts in chat; let user choose via `AskUserQuestion`
3. Generate the selected concept using the image agent:
- Aspect ratio: **16:9**
- Resolution: **2K** or **4K**
- Prompt must explicitly include "YouTube thumbnail style, high contrast, vibrant colors"
4. If the thumbnail requires text rendered onto the image, use `qwen_image_generation` (specializes in text-in-image) to add or refine text elements
5. Review quality; regenerate if the expression, text legibility, or color punch is insufficient
6. **Present the final thumbnail to the user and confirm completion** — the pipeline ends here
### Prompt Structure
```
YouTube thumbnail, [subject + expression], [composition framing],
bold [color] condensed sans-serif text "[TEXT]" with thick black outline,
[dominant accent color] background, high contrast, vibrant saturated colors,
clean composition, 4K, photorealistic / [style]
```
### File Storage
```
./.youtube/{project_name}/thumbnail/
├── concept_A.jpg # Concept A
├── concept_B.jpg # Concept B (optional)
├── concept_C.jpg # Concept C (optional)
└── selected.jpg # Final approved thumbnail
```
---
## File Organization Overview
```
./.youtube/
└── {project_name}/
├── topic.md # Topic planning
├── script.md # Retention script
└── thumbnail/ # Final output — high-CTR cover image
├── concept_A.jpg
└── selected.jpg
```
## Output Controls
- Default language is **English**. If the user specifies a different language, all outputs use that language: scripts, titles, descriptions, and tags
- Image generation prompts are always written in English for best AI model performance
- Each stage shows results to user in chat for confirmation
- **Pipeline stops after Stage 3** — the approved thumbnail is the final deliverable
+5
View File
@@ -0,0 +1,5 @@
YouTube 视频创作助手,覆盖从选题规划到高点击率封面生成的完整流程。
包含选题策划、留存率优化脚本撰写,以及高转化 YouTube 缩略图生成。
流程在封面图产出后结束,不会继续生成概念图、配音、视频片段或最终成片。
触发词包括:YouTube、youtube creator、YouTube video、make a video、
YouTube script、thumbnail、YouTube SEO。
@@ -0,0 +1,86 @@
# YouTube Retention Optimization Methodology
This document summarizes the core methodology for optimizing YouTube video retention, used to guide script writing.
## Key Data
- **55% of viewers leave within the first 60 seconds**
- YouTube algorithm's most valued signals (by weight): Shares > Rewatches > Saves > Likes > Comments
- CTR below 3% = problematic; 7-10%+ = strong
- Average watch duration below 40% = algorithmic demotion
## Three-Part Hook (0-30 seconds)
The first 30 seconds are the make-or-break window. Use a three-part structure:
### Grab (0-5 seconds)
Capture attention using any of these approaches:
- **Counter-intuitive fact**: "You think drinking 8 glasses of water is healthiest? Scientists say you're wrong"
- **Shocking statistic**: "97% of people worldwide don't know about this feature"
- **Visual impact**: Strong visual contrast or conflict
### Promise (5-15 seconds)
Tell viewers what they'll gain from watching:
- "By the end of this video, you'll learn 3 methods you can use immediately"
- "Today I'm going to tell you the secret that [industry] doesn't want you to know"
### Stakes (15-30 seconds)
Create a sense of loss for not watching:
- "If you don't know this, you might already be making this mistake"
- "90% of people wasted thousands of dollars by ignoring this one thing"
## Pattern Interrupt (every 60-90 seconds)
Viewer attention naturally drops every 60-90 seconds — you need to proactively break the pattern:
### Visual Interrupt
- Switch scenes (indoor → outdoor, close-up → wide shot)
- Insert charts, animations, text effects
- Change frame composition
### Information Interrupt
- Drop an unexpected fact: "But what you probably don't know is..."
- Set up a mini-cliffhanger: "What I'm about to say next might completely change how you think about this"
- Pose a thought-provoking question
### Rhythm Interrupt
- Suddenly speed up or slow down speech
- Insert brief pauses (0.5-1 second)
- Change vocal tone (narration → rhetorical question → exclamation)
## CTA Placement Strategy
### Soft CTA (at ~25% of video)
- Approximately 60% of viewers are still watching — the optimal window for maximizing likes
- Keep the tone relaxed and natural: "If you're finding this useful, drop a like and let's keep going"
- Don't interrupt content flow — integrate naturally
### Second Hook (at ~60% of video)
- Critical point for combating mid-video drop-off
- Preview the best content in the second half: "But the most important method hasn't been covered yet"
- Create new suspense to keep viewers until the end
### Hard CTA (ending)
- Clear call to action: "Like, subscribe, and hit the notification bell"
- Preview next video content, giving viewers a reason to come back
- Only ~16% of viewers remain at this point, but they're the most loyal fans
## Pacing Control
- English speech pace: ~150 words/minute (natural conversational rhythm)
- Slow down slightly before important information (for emphasis)
- Speed up slightly for list-type content (to avoid dragging)
- Each narration segment should ideally be 15-30 seconds
## Retention Tag Reference
Use the following tags in scripts to facilitate rhythm optimization during production:
| Tag | Meaning | Suggested Position |
|-----|---------|-------------------|
| `HOOK` | Opening hook | First segment, 0-30s |
| `PATTERN_INTERRUPT` | Pattern interrupt | Every 60-90s |
| `SOFT_CTA` | Soft engagement prompt | At ~25% |
| `SECOND_HOOK` | Second hook | At ~60% |
| `HARD_CTA` | Hard call to action | Final segment |
| `NORMAL` | Standard content | All other segments |
@@ -0,0 +1,132 @@
# YouTube Video Script Example
Topic: Why Do You Always Procrastinate? Your Brain's Self-Deception Mechanism
Type: Educational
Target Duration: 3 minutes
Audience: General
---
## [SECTION:hook] Hook - Opening Hook
**Narration:**
Have you ever had this experience? The deadline is staring you right in the face, but instead of working, you pick up your phone and scroll through short videos for two hours — then spiral into deep regret at midnight. You think it's a willpower problem, but here's the truth — your brain is lying to you. Today, I'm going to reveal the 3 brain traps behind procrastination and how to beat them with science.
**Visual Description:**
Close-up: A person sitting at a computer with a blank document on screen, but looking down at their phone. A clock spins rapidly in the background. The frame transitions from dark tones to bright, with a tense rhythm.
**Retention Tag:** HOOK
**Estimated Duration:** 25s
---
## [SECTION:trap1] Trap 1: Instant Gratification Bias
**Narration:**
The first trap is called "instant gratification bias." Your brain is hardwired to prefer pleasure you can get right now over future rewards. When you face a difficult task, your brain automatically calculates — the pain of writing the report is immediate, but the satisfaction of finishing it is in the future. So your brain makes the "rational" choice: let's watch some videos first.
**Visual Description:**
Animation style: Cross-section of the brain. Left side labeled "Limbic System" flashes red (representing instant pleasure), right side labeled "Prefrontal Cortex" flashes blue (representing rational planning). A scale tilts between the two.
**Retention Tag:** NORMAL
**Estimated Duration:** 25s
---
## [SECTION:interrupt1] Pattern Interrupt - Shocking Fact
**Narration:**
Did you know? Research has found that procrastinators' amygdalae are on average 10% larger than non-procrastinators'. This means they perceive negative emotions more intensely. In other words, procrastination isn't laziness — it's an emotion regulation strategy.
**Visual Description:**
Full-screen text: "Procrastination isn't laziness," then cuts to a 3D render of the amygdala with comparison data charts. Tech-blue color palette.
**Retention Tag:** PATTERN_INTERRUPT
**Estimated Duration:** 15s
---
## [SECTION:cta_soft] Soft CTA
**Narration:**
If you're someone who struggles with procrastination, hit that like button and let's dig into two more traps that are even harder to spot.
**Visual Description:**
Clean visual: Animated like button effect, warm orange tones, relaxed atmosphere.
**Retention Tag:** SOFT_CTA
**Estimated Duration:** 6s
---
## [SECTION:trap2] Trap 2: The Perfectionism Trap
**Narration:**
The second trap is perfectionism. Many people procrastinate not because they don't want to do the work, but because they're afraid of doing it badly. They have a perfect standard in their mind, and any start that falls short of that standard fills them with anxiety. So they choose not to start at all — because if you never start, you can never fail.
**Visual Description:**
A person standing before a massive white canvas, holding a paintbrush but unable to move. Above the canvas floats a golden "100%" symbol, flickering with an oppressive glow. Illustration style.
**Retention Tag:** NORMAL
**Estimated Duration:** 22s
---
## [SECTION:second_hook] Second Hook
**Narration:**
The third trap I'm about to reveal might be the most mind-blowing one. Most people think deadlines force them to act, but science tells us it might actually make procrastination worse.
**Visual Description:**
A deadline date circled in red on a calendar, frame gradually zooming in to build tension. Then the image suddenly reverses — the calendar shatters.
**Retention Tag:** SECOND_HOOK
**Estimated Duration:** 12s
---
## [SECTION:trap3] Trap 3: The Deadline Illusion
**Narration:**
The third trap is the "deadline illusion." Your brain deceives you into thinking "there's still time" until the very last moment triggers emergency mode. Research shows that setting overly long deadlines actually leads to worse procrastination. Your brain can't perceive the pressure of a distant future — you only take action when the deadline is close enough to trigger anxiety.
**Visual Description:**
Timeline animation: A progress bar from left to right. The person stays at the starting point for most of the timeline, then frantically sprints in the final 10%. Data chart showing "Parkinson's Law" curve.
**Retention Tag:** NORMAL
**Estimated Duration:** 25s
---
## [SECTION:solution] The Fix
**Narration:**
So what do you do? Three practical methods. First, the "Two-Minute Start" — tell yourself you'll only work for two minutes. Most of the time, you'll keep going. Second, break big tasks into small ones and reward yourself after each completion. Third, set "artificial deadlines" — move the due date up by half to create manufactured urgency.
**Visual Description:**
Three-panel split screen: Left panel shows a "2 MIN" timer counting down; center panel shows a large block being broken into small blocks, each getting checked off; right panel shows a calendar with a new red line drawn before the original deadline. Bright, encouraging color palette.
**Retention Tag:** PATTERN_INTERRUPT
**Estimated Duration:** 25s
---
## [SECTION:outro] Outro
**Narration:**
Remember, procrastination isn't a character flaw — it's your brain's protection mechanism gone sideways. Understanding it is the first step to changing it. If this video helped you, please like, subscribe, and hit the notification bell. Next time, we'll explore more secrets about how your brain really works.
**Visual Description:**
Warm scene: A person smiling as they begin typing at their computer, background gradually brightening. Ends with channel logo and subscribe button animation.
**Retention Tag:** HARD_CTA
**Estimated Duration:** 18s
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""Validate youtube-creator script format compliance.
Rules:
- File header must contain metadata (Topic, Type, Target Duration, Audience)
- Each segment must use ## [SECTION:id] Title
- Each segment must contain **Narration:**
- Each segment must contain **Visual Description:**
- Each segment must contain **Retention Tag:** (HOOK/PATTERN_INTERRUPT/SOFT_CTA/SECOND_HOOK/HARD_CTA/NORMAL)
- Each segment must contain **Estimated Duration:** Xs
- Segments are separated by ---
- section_id must be unique
- Must have at least one HOOK segment and one HARD_CTA segment
"""
import re
import sys
from pathlib import Path
SECTION_RE = re.compile(r"^##\s+\[SECTION:(\w+)\]\s+(.+)$")
DURATION_RE = re.compile(r"(\d+)s")
RETENTION_TAGS = {"HOOK", "PATTERN_INTERRUPT", "SOFT_CTA", "SECOND_HOOK", "HARD_CTA", "NORMAL"}
RETENTION_RE = re.compile(r"\*\*Retention Tag[:]\*\*\s*(\S+)")
def parse_script(path: str) -> tuple[dict, list[dict]]:
"""Parse script.md, return (metadata, sections list)."""
text = Path(path).read_text(encoding="utf-8")
lines = text.split("\n")
sections: list[dict] = []
meta: dict = {"topic": "", "type": "", "target_duration": "", "audience": ""}
current: dict | None = None
in_header = True
for line_raw in lines:
line = line_raw.strip()
# Parse file header metadata
if in_header and not line.startswith("##"):
if line.startswith("Topic:") or line.startswith("Topic"):
meta["topic"] = line.split(":", 1)[-1].split("", 1)[-1].strip()
elif line.startswith("Type:") or line.startswith("Type"):
meta["type"] = line.split(":", 1)[-1].split("", 1)[-1].strip()
elif line.startswith("Target Duration:") or line.startswith("Target Duration"):
meta["target_duration"] = line.split(":", 1)[-1].split("", 1)[-1].strip()
elif line.startswith("Audience:") or line.startswith("Audience"):
meta["audience"] = line.split(":", 1)[-1].split("", 1)[-1].strip()
continue
in_header = False
section_match = SECTION_RE.match(line)
if section_match:
if current:
sections.append(current)
current = {
"id": section_match.group(1),
"title": section_match.group(2),
"has_narration": False,
"has_visual": False,
"has_retention": False,
"has_duration": False,
"duration_seconds": 0,
"retention_tag": "",
}
continue
if current is None:
continue
if line.startswith("**Narration:**") or line.startswith("**Narration**"):
current["has_narration"] = True
elif line.startswith("**Visual Description:**") or line.startswith("**Visual Description**"):
current["has_visual"] = True
elif line.startswith("**Retention Tag:**") or line.startswith("**Retention Tag**"):
current["has_retention"] = True
m = RETENTION_RE.match(line)
if m:
current["retention_tag"] = m.group(1)
elif line.startswith("**Estimated Duration:**") or line.startswith("**Estimated Duration**"):
current["has_duration"] = True
m = DURATION_RE.search(line)
if m:
current["duration_seconds"] = int(m.group(1))
if current:
sections.append(current)
return meta, sections
def validate(meta: dict, sections: list[dict]) -> list[str]:
"""Validate and return list of issues."""
issues: list[str] = []
# Metadata check
if not meta["topic"]:
issues.append("WARN Header missing Topic:")
if not meta["type"]:
issues.append("WARN Header missing Type:")
if not meta["target_duration"]:
issues.append("WARN Header missing Target Duration:")
if not sections:
issues.append("ERROR No ## [SECTION:xxx] segments found")
return issues
# section_id uniqueness
ids = [s["id"] for s in sections]
seen = set()
for sid in ids:
if sid in seen:
issues.append(f"ERROR Duplicate section_id: {sid}")
seen.add(sid)
total_duration = 0
retention_tags_found: list[str] = []
for sc in sections:
label = f"[{sc['id']}] {sc['title']}"
if not sc["has_narration"]:
issues.append(f"ERROR {label} missing **Narration:**")
if not sc["has_visual"]:
issues.append(f"ERROR {label} missing **Visual Description:**")
if not sc["has_retention"]:
issues.append(f"ERROR {label} missing **Retention Tag:**")
elif sc["retention_tag"] and sc["retention_tag"] not in RETENTION_TAGS:
issues.append(
f"ERROR {label} invalid Retention Tag: {sc['retention_tag']}, "
f"expected {'/'.join(sorted(RETENTION_TAGS))}"
)
if not sc["has_duration"]:
issues.append(f"ERROR {label} missing **Estimated Duration:**")
elif sc["duration_seconds"] <= 0:
issues.append(f"ERROR {label} Estimated Duration format error (should be Xs, e.g. 15s)")
elif sc["duration_seconds"] > 120:
issues.append(f"WARN {label} segment too long ({sc['duration_seconds']}s > 120s)")
total_duration += sc["duration_seconds"]
if sc["retention_tag"]:
retention_tags_found.append(sc["retention_tag"])
# Retention tag checks
if "HOOK" not in retention_tags_found:
issues.append("ERROR Script missing HOOK segment (opening must have a Hook)")
if "HARD_CTA" not in retention_tags_found:
issues.append("WARN Script missing HARD_CTA segment (recommend adding subscribe prompt at end)")
# Pattern interrupt density check
pi_count = retention_tags_found.count("PATTERN_INTERRUPT")
if total_duration > 180 and pi_count == 0:
issues.append("WARN Video exceeds 3 minutes but has no PATTERN_INTERRUPT — recommend one every 60-90s")
# Total duration check
if total_duration > 0:
target = meta.get("target_duration", "")
if "1-3" in target or "1 min" in target or "2 min" in target or "3 min" in target:
if total_duration > 210:
issues.append(
f"WARN Total duration {total_duration}s exceeds target (1-3 min ≈ 60-180s)"
)
elif "5-8" in target or "5 min" in target:
if total_duration > 540:
issues.append(
f"WARN Total duration {total_duration}s exceeds target (5-8 min ≈ 300-480s)"
)
return issues
def print_summary(meta: dict, sections: list[dict], issues: list[str]) -> None:
total_duration = sum(s["duration_seconds"] for s in sections)
# Retention tag statistics
tag_counts: dict[str, int] = {}
for s in sections:
tag = s.get("retention_tag", "UNKNOWN") or "UNKNOWN"
tag_counts[tag] = tag_counts.get(tag, 0) + 1
print("=== YouTube Video Script Validation Report ===\n")
print(f"Topic: {meta.get('topic', 'Not specified')}")
print(f"Type: {meta.get('type', 'Not specified')}")
print(f"Target Duration: {meta.get('target_duration', 'Not specified')}")
print(f"Audience: {meta.get('audience', 'Not specified')}")
print(f"Total Segments: {len(sections)}")
print(f"Estimated Total Duration: {total_duration}s ({total_duration // 60}m{total_duration % 60}s)")
print()
# Retention tag distribution
print("Retention Tag Distribution:")
for tag in ["HOOK", "PATTERN_INTERRUPT", "SOFT_CTA", "SECOND_HOOK", "HARD_CTA", "NORMAL"]:
count = tag_counts.get(tag, 0)
if count > 0:
print(f" {tag:<20} {count}x")
print()
check = lambda v: "Y" if v else "-"
print(f"{'Section ID':<14} {'Title':<16} {'Dur':>5} {'Narr':>4} {'Vis':>4} {'Retention Tag':<20}")
print("-" * 70)
for sc in sections:
sid = sc["id"][:12]
title = sc["title"][:14]
dur = f"{sc['duration_seconds']}s" if sc["duration_seconds"] > 0 else "?"
tag = sc.get("retention_tag", "-") or "-"
print(
f"{sid:<14} {title:<16} {dur:>5} "
f"{check(sc['has_narration']):>4} {check(sc['has_visual']):>4} "
f"{tag:<20}"
)
print()
if issues:
errors = [i for i in issues if "ERROR" in i]
warns = [i for i in issues if "WARN" in i]
print(f"Found {len(errors)} error(s), {len(warns)} warning(s):\n")
for issue in issues:
print(f" {issue}")
else:
print("ALL PASS")
def main():
if len(sys.argv) < 2:
print("Usage: python validate_script.py <script.md path>")
sys.exit(1)
script_path = Path(sys.argv[1])
if not script_path.exists():
print(f"ERROR: File not found: {script_path}")
sys.exit(1)
print(f"\n--- {script_path} ---\n")
meta, sections = parse_script(str(script_path))
issues = validate(meta, sections)
print_summary(meta, sections, issues)
has_error = any("ERROR" in i for i in issues)
sys.exit(1 if has_error else 0)
if __name__ == "__main__":
main()