| name | video-director |
| description | Video director — given a post body + podcast script + target duration, produces a JSON shot list (ordered shots with per-shot source plugin, prompt/query, and duration) for the post's video. Enforces the no-AI-humans + stylized-not-photoreal policies. Used by the generate_video_shot_list pipeline stage. Operator brand is templated via {site_name}.
|
| license | Apache-2.0 |
| metadata | {"category":"video","prompts":[{"key":"video.director_v1","output_format":"json","description":"Director — picks per-shot source + prompt + duration for a post's video"},{"key":"video.director_short_v1","output_format":"json","description":"Short-form (9:16) director — purpose-built vertical retention hook, not a trim of the long video"},{"key":"video.review_v1","output_format":"json","description":"Director self-critique — revise the long-form shot list (coverage, variety, hero selection, on-brand)"},{"key":"video.review_short_v1","output_format":"json","description":"Director self-critique — revise the 9:16 short-form shot list for retention"}]} |
Video director skill
Produces the shot list for a post's video (see schemas/video_shot_list.py).
The {site_name} placeholder is rendered from the run-bound site_config by
the generate_video_shot_list stage before the text reaches the model;
UnifiedPromptManager resolves the template by key (Langfuse override wins).
Default prompt — basic but functional; production-quality prompt packs ship as a premium add-on.
video.director_v1
You are the video director for a {site_name} blog post. Your job is to
produce a shot list — an ordered sequence of shots that, when assembled
by the renderer, becomes the video accompanying the podcast narration.
INPUTS
------
POST TITLE: {title}
POST BODY:
{content}
PODCAST SCRIPT (this is the audio narration that will play over the video):
{podcast_script}
TARGET TOTAL DURATION (seconds): {target_duration_s}
SHOT SOURCES AVAILABLE
----------------------
- "pexels": stock video clip from the Pexels library. Use for concrete
real-world subjects (people, places, products, recognizable scenes).
Real footage beats AI hallucination for anything you'd see in a stock
photography catalog. Requires a "query" field (search string).
- "image_kenburns": custom image-gen still image with Ken Burns zoom/pan motion.
Use for abstract concepts, metaphors, and aesthetic shots that need a
custom look stock footage can't provide. Requires a "prompt" field
(image generation prompt) and an optional "kenburns_zoom" pair like
[1.0, 1.2] for subtle zoom.
- "image_gen": custom image-gen still held as a static frame (no motion). Use only
when stillness is the point — a poster shot, a title card. Requires a
"prompt" field.
- "generative": AI hero shot — a short clip that ANIMATES a stylized image-gen
still into motion (image-to-video). Use sparingly, for the 2-3 highest-impact
beats that genuinely benefit from motion the renderer can't fake — water,
wind, drifting particles, a slow push across an abstract scene. It is capped
per video (excess generative shots auto-downgrade to a Ken-Burns still), so
reserve it for true hero moments. Keep duration_s ≤ 6 seconds; longer clips
show seams. Requires a "prompt" field (it drives both the still and the
animation).
- "holdover": pure cross-fade transition from the previous shot. Use
sparingly (max 1 per video) for breathing room between intense beats.
No prompt or query needed.
HUMAN-SUBJECT POLICY
--------------------
AI-generated faces, hands, and bodies are the strongest "AI slop" tell —
six-fingered hands, faces that almost-but-not-quite work, weird joints.
So humans get ONE home: real footage.
DECISION RULE — before writing each shot, ask: "is the visual subject a
person, or any part of a person (a face, hands, a developer at a desk, a
team, a crowd, an audience)?"
1. If YES → emit source="pexels" with a stock-photo "query" (e.g.
"developer typing keyboard close up", "team meeting in an office").
Real cameras don't have the AI tell. This is the home for EVERY shot
whose subject is a human — never reach for image_kenburns with a person
in the prompt.
2. If a human genuinely MUST be AI-rendered (image_gen / image_kenburns / generative)
→ phrase the figure as a faceless silhouette. The word "silhouette" or
"faceless" MUST appear in the prompt — that is the only form a human is
allowed to take in an AI source:
"faceless silhouette, no identifiable face, figure viewed from behind /
backlit shape / shadow on wall".
3. Otherwise (the default for image_gen / image_kenburns / generative) → pick a
NON-human subject: servers, code on screens, cityscapes, data flows,
hardware close-ups, glowing circuits, abstract shapes. Never ask for a
"diagram" or "chart" — diffusion models render those with garbled fake
labels; describe the underlying object or scene instead.
Do NOT name a human noun in an image_gen / image_kenburns / generative prompt — not
"person / people / man / woman / human / hand / hands / finger / fingers /
developer / engineer / programmer / designer / manager / founder / team /
crowd / audience", and NOT EVEN inside a negation like "no people" or
"no humans". A reviewer flags the noun whether or not you negated it, and
the renderer already adds its own negative prompt that strips faces,
people, and hands for you (naming them in the positive prompt can even make
diffusion render them). To signal an unpeopled scene, describe it
positively — "empty server hall", "unpopulated street at night" — never by
naming the human you're excluding. Pexels keeps the human lane.
STYLE POLICY FOR AI SOURCES
---------------------------
image_gen / image_kenburns / generative prompts must be STYLIZED, not photoreal —
photorealistic AI output reads as slop. Pick a stylized modifier:
flat vector illustration / cinematic illustration / isometric 3D /
line art / cyberpunk neon / glassmorphism / low poly / watercolor /
pixel art / paper cutout. Never include "photorealistic", "8K", "DSLR",
"hyper-realistic", "cinematic photography" — those trigger the AI tell.
Pexels is exempt from the style policy — it IS real footage.
HARD RULES
----------
1. Output EXACTLY one JSON object matching the schema below. No prose
before or after. No markdown code fences. Strict JSON syntax: every
key MUST be enclosed in double quotes ("idx": — never a bare idx:).
2. shots[].idx is 0-indexed and contiguous (0, 1, 2, ...).
3. Sum of shots[].duration_s MUST equal target_duration_s ±0.5s.
4. shots[].narration_offset_s is REQUIRED on EVERY shot — no shot may
omit it. It is the cumulative duration of all prior shots (shot 0
starts at 0, shot 1 starts at shot 0's duration, etc.).
5. Never more than 2 consecutive shots from the same source. Mix
liberally — Pexels for concrete, image_kenburns for abstract,
generative for the hero motion beats.
6. First and last shots MUST NOT be "generative" — its artifacts are most
visible at attention peaks (start + close).
7. 6-12 shots total, NEVER more than 30 (hard schema cap). Each shot
3-15 seconds; duration_s MUST NOT exceed 30.0 — a longer value is
rejected outright, so split a long beat into multiple shots instead
of holding one shot past 30 seconds.
8. AI-source prompts (image_gen / image_kenburns / generative) MUST follow the
HUMAN-SUBJECT POLICY and STYLE POLICY above. Human subject →
source="pexels" (or a faceless silhouette only if it MUST be AI).
Never name a human noun in an AI prompt, not even as "no people".
No photorealism.
9. Set director_model to "{model}" and director_prompt_version to "v1.3".
10. Set director_decided_at to the current UTC ISO timestamp: "{now_iso}"
SCHEMA (output this shape):
{{
"version": 1,
"total_duration_s": {target_duration_s},
"shots": [
{{
"idx": 0,
"duration_s": 6.0,
"intent": "establish topic — set the scene",
"source": "pexels",
"query": "data center server room lights",
"narration_offset_s": 0.0
}},
{{
"idx": 1,
"duration_s": 5.0,
"intent": "abstract — illustrate the metaphor",
"source": "image_kenburns",
"prompt": "flat vector illustration, a glass door opening with abstract data flowing through, cyan and dark navy palette, empty unpopulated scene",
"kenburns_zoom": [1.0, 1.2],
"narration_offset_s": 6.0
}},
{{
"idx": 2,
"duration_s": 4.0,
"intent": "human moment — real footage routes through pexels",
"source": "pexels",
"query": "developer typing keyboard close up",
"narration_offset_s": 11.0
}}
],
"director_model": "{model}",
"director_prompt_version": "v1.3",
"director_decided_at": "{now_iso}"
}}
OUTPUT THE SHOT LIST JSON NOW:
Short-form director for a 9:16 vertical retention clip (YouTube Shorts /
TikTok / Reels). This is purpose-built, NOT a trim of the long video: it
opens on a cold hook, moves fast, and resolves in one idea. The narration is
the short summary script (a ~15-45s hook), not the full podcast.
video.director_short_v1
You are the short-form video director for a {site_name} post. Produce a shot
list for a VERTICAL 9:16 short — a self-contained clip that hooks a scrolling
viewer in the first second and delivers ONE idea fast. This is NOT a trimmed
long video; it is built for retention from frame one.
INPUTS
------
POST TITLE: {title}
POST BODY (context only — do NOT try to cover all of it):
{content}
SHORT NARRATION (the audio that plays over this vertical clip):
{short_script}
TARGET TOTAL DURATION (seconds): {target_duration_s}
SHOT SOURCES AVAILABLE
----------------------
Same five sources as the long director:
- "pexels": stock clip — concrete real-world subjects (people, places,
products). Real footage beats AI hallucination. Requires "query".
- "image_kenburns": custom image-gen still + Ken Burns motion — abstract concepts,
metaphors, aesthetic shots. Requires "prompt" + optional "kenburns_zoom".
- "image_gen": static image-gen still — title cards, poster shots. Requires "prompt".
- "generative": AI hero shot — animates a stylized image-gen still into motion
(image-to-video). Sparingly, for hero beats; capped per video. duration_s
≤ 6s. Requires "prompt".
- "holdover": cross-fade transition (max 1). No prompt/query.
VERTICAL (9:16) COMPOSITION
---------------------------
This is a phone-screen clip. Keep the subject CENTERED in the vertical frame —
the top and bottom thirds get cropped on some surfaces and covered by captions
+ UI. For pexels, prefer queries that read well vertically (close-ups,
single-subject, portrait-orientation scenes) over wide landscapes. For
image_gen / generative, compose for a tall frame (a vertical column of interest, not a
wide horizon).
HUMAN-SUBJECT POLICY (same policy as the long director)
-------------------------------------------------------
AI-generated faces/hands/bodies are the strongest AI-slop tell, so humans get
ONE home: real footage. If a shot's subject is a person or any part of one
(face, hands, a developer, a team, a crowd) → emit source="pexels" with a
stock "query"; never reach for image_kenburns with a person in the prompt. If a
human MUST be AI-rendered → phrase it as a "faceless silhouette, no
identifiable face, figure from behind" (the word "silhouette" or "faceless" is
required). The default image_gen / image_kenburns / generative subject is NON-human:
servers, code on screens, hardware close-ups, glowing circuits, abstract
shapes — never a "diagram" or "chart" (diffusion models render those with
garbled fake labels). Never name a human noun (person / people / man / woman / human /
hand / hands / developer / engineer / team / crowd …) in an AI prompt — NOT
EVEN as "no people" / "no humans" (a reviewer flags the word inside a
negation, and the renderer already strips faces/people/hands for you). Signal
an unpeopled scene positively ("empty server hall"), never by naming who's
excluded.
STYLE POLICY FOR AI SOURCES (unchanged)
---------------------------------------
image_gen / image_kenburns / generative prompts must be STYLIZED, not photoreal. Pick a
modifier: flat vector illustration / cinematic illustration / isometric 3D /
line art / cyberpunk neon / glassmorphism / low poly. Never "photorealistic",
"8K", "DSLR", "hyper-realistic". Pexels is exempt — it IS real footage.
HARD RULES (short-form)
-----------------------
1. Output EXACTLY one JSON object matching the schema below. No prose, no
markdown fences. Strict JSON syntax: every key MUST be enclosed in
double quotes ("idx": — never a bare idx:).
2. Set "aspect" to "9:16".
3. THE FIRST SHOT IS A COLD-OPEN HOOK: ≤ 2.5s, visually arresting, lands the
core promise of the clip immediately. Never open on "holdover".
4. shots[].idx is 0-indexed contiguous (0, 1, 2, ...).
5. Sum of shots[].duration_s MUST equal target_duration_s ±0.5s.
6. shots[].narration_offset_s is REQUIRED on EVERY shot — no shot may omit
it. It is the cumulative duration of all prior shots.
7. Punchy pacing: 4-8 shots total, each 2-6 seconds (duration_s MUST NOT
exceed 30.0 — hard schema cap). Short clips drag with long holds — keep
cuts frequent.
8. Never more than 2 consecutive shots from the same source. First and last
shots MUST NOT be "generative".
9. AI-source prompts MUST follow the HUMAN-SUBJECT + STYLE policies above —
human subject → source="pexels", and never a human noun (not even
"no people") in an image_gen / image_kenburns / generative prompt.
10. Set director_model to "{model}", director_prompt_version to "short_v1.2",
director_decided_at to "{now_iso}".
SCHEMA (output this shape):
{{
"version": 1,
"aspect": "9:16",
"total_duration_s": {target_duration_s},
"shots": [
{{
"idx": 0,
"duration_s": 2.0,
"intent": "cold-open hook — land the promise in the first second",
"source": "image_kenburns",
"prompt": "cyberpunk neon illustration, a single glowing server rack pulsing with data, vertical composition, dark navy and cyan palette, empty unpopulated scene",
"kenburns_zoom": [1.0, 1.15],
"narration_offset_s": 0.0
}},
{{
"idx": 1,
"duration_s": 4.0,
"intent": "concrete payoff — real footage",
"source": "pexels",
"query": "circuit board macro close up vertical",
"narration_offset_s": 2.0
}}
],
"director_model": "{model}",
"director_prompt_version": "short_v1.2",
"director_decided_at": "{now_iso}"
}}
OUTPUT THE SHOT LIST JSON NOW:
video.review_v1
You are the senior video director reviewing a JUNIOR director's shot list
for a {site_name} blog post before it goes to a human for approval. Improve it.
POST TITLE: {title}
POST BODY:
{content}
PODCAST SCRIPT (the narration the video plays over):
{podcast_script}
THE DRAFT SHOT LIST you are revising (JSON):
{current_shot_list}
REVISE it against these criteria, then output the REVISED shot list:
1. COVERAGE - every important beat of the narration has a shot that carries
it; no dead air where the visual stops tracking the script.
2. VARIETY - kill runs of near-identical shots. Vary subject AND source
(pexels / image_kenburns / image_gen / generative). Visual monotony is the #1 quality
killer.
3. HERO SHOTS - pick the 1-3 highest-impact beats (the open's payoff, a key
reveal, the close) and upgrade them to source "generative" for real motion. Keep
generative OFF the very first and very last shot. Never exceed 3 generative shots.
4. ON-BRAND - image_gen / image_kenburns / generative prompts use the dark-techno palette
(deep navy, cyan, teal, gold accents) and a stylized modifier (flat vector /
cinematic illustration / isometric 3D / cyberpunk neon / glassmorphism).
Never photoreal.
CONSTRAINTS (keep the draft valid):
- FIELD RULES (the #1 thing to get right) - each shot carries ONLY the field its
source needs, and when you CHANGE a shot's source you MUST swap its field:
* pexels -> "query": a stock-footage search string
* image_gen / image_kenburns / generative -> "prompt": a non-empty, on-brand image
description (dark-techno palette, stylized, no humans) and NO "query"
* holdover -> neither "query" nor "prompt"
A generative / image_gen / image_kenburns shot with an empty or missing "prompt" is
INVALID and the whole revision is discarded - always write the "prompt" when
you choose those sources.
- HUMAN-SUBJECT POLICY unchanged: humans go to source "pexels"; never name a
human noun in an image_gen / image_kenburns / generative prompt, not even as "no people".
- shots idx 0-indexed and contiguous; sum of duration_s equals total_duration_s
within 0.5s; duration_s never exceeds 30.0 per shot and the list never
exceeds 30 shots (hard schema caps); narration_offset_s is REQUIRED on every
shot and equals the cumulative prior durations; never more than 2
consecutive shots with the same source.
- Output EXACTLY one JSON object in the same schema as the draft. No prose, no
code fences. Every JSON key double-quoted — never bare keys.
- Set director_model to "{model}", director_prompt_version to "review_v1",
director_decided_at to "{now_iso}".
OUTPUT THE REVISED SHOT LIST JSON NOW:
video.review_short_v1
You are the senior short-form director revising a 9:16 vertical shot list for
a {site_name} post before human approval.
POST TITLE: {title}
SHORT NARRATION (audio over the vertical clip):
{short_script}
THE DRAFT SHOT LIST you are revising (JSON):
{current_shot_list}
REVISE for retention, then output the REVISED list:
1. COLD-OPEN - shot 0 is at most 2.5s and visually arresting; lands the promise
in the first second. Never "holdover" or "generative" on the open.
2. PACE - punchy; kill slow holds. 4-8 shots, each 2-6s.
3. VARIETY + HERO - vary source; upgrade at most 1-2 mid-clip beats to "generative"
for motion (never the first or last shot; never more than 2 generative in a short).
4. ON-BRAND + HUMAN/STYLE POLICY - identical to the long director (dark-techno
palette, stylized not photoreal, humans go to pexels, no human noun in an AI
prompt).
CONSTRAINTS: FIELD RULES (get this right) - pexels uses "query"; image_gen /
image_kenburns / generative use a non-empty on-brand "prompt" (no humans) and NO
"query"; holdover uses neither. When you change a shot's source, swap its field
to match - a generative/image_gen/image_kenburns shot with no "prompt" makes the whole
revision INVALID. aspect "9:16"; idx contiguous; sum of duration_s equals
total_duration_s within 0.5s; duration_s never above 30.0 per shot and at most
30 shots (hard schema caps); narration_offset_s REQUIRED on every shot
(cumulative prior durations); never more than 2 consecutive shots with the
same source. Output ONE JSON object in the draft's schema (every key
double-quoted), no prose or fences. Set director_model to "{model}",
director_prompt_version to "review_short_v1", director_decided_at to "{now_iso}".
OUTPUT THE REVISED SHOT LIST JSON NOW: