Scenario
The first 5 seconds of a YouTube pre-roll ad determine everything — viewers either watch or hit Skip. This job takes your pre-roll ad candidates, runs Video Analysis focused on the opening 5 seconds (hook score, visual complexity, text legibility, emotional trigger), then puts the results through a Focus Group asking the core question: “Would you skip this ad?” The result is a skip-or-watch prediction with specific improvement recommendations from each persona before you commit media budget.Architecture
Code
import os, requests, time
MV = os.environ["MAVERA_API_KEY"]
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
PRE_ROLL_CANDIDATES = [
{"path": "ads/preroll_a_product_demo.mp4", "label": "Product Demo — Fast Cuts"},
{"path": "ads/preroll_b_testimonial.mp4", "label": "Customer Testimonial — Face First"},
{"path": "ads/preroll_c_problem_hook.mp4", "label": "Problem Hook — Text Overlay"},
{"path": "ads/preroll_d_humor.mp4", "label": "Humor — Unexpected Opening"},
]
# 1. Upload and analyze each candidate
analyses = []
for candidate in PRE_ROLL_CANDIDATES:
upload = requests.post(f"{MV_BASE}/assets",
headers={"Authorization": f"Bearer {MV}"},
files={"file": (
candidate["label"] + ".mp4",
open(candidate["path"], "rb"),
"video/mp4",
)},
).json()
analysis = requests.post(f"{MV_BASE}/video-analysis", headers=MV_H, json={
"asset_id": upload["id"],
"analysis_types": [
"hook_score", "visual_complexity", "text_legibility",
"emotional_arc", "cognitive_load", "pacing",
],
"options": {"focus_window": "0-5s"},
"metadata": {"label": candidate["label"], "format": "pre-roll"},
}).json()
analyses.append({"id": analysis["id"], "label": candidate["label"]})
time.sleep(0.5)
# 2. Poll all analyses
results = []
for a in analyses:
for _ in range(30):
time.sleep(3)
status = requests.get(
f"{MV_BASE}/video-analysis/{a['id']}", headers=MV_H
).json()
if status.get("status") == "completed":
break
r = status.get("results", {})
results.append({
"label": a["label"],
"hook_score": r.get("hook_score", {}).get("score", 0),
"hook_assessment": r.get("hook_score", {}).get("assessment", ""),
"visual_complexity": r.get("visual_complexity", {}).get("score", 0),
"text_legibility": r.get("text_legibility", {}).get("score", 0),
"emotional_trigger": r.get("emotional_arc", {}).get("peak_emotion", "none"),
"cognitive_load": r.get("cognitive_load", {}).get("average", 0),
})
# 3. Create Focus Group personas
persona_ids = []
for name, desc in [
("Impatient Scroller", "25-year-old who skips every ad. Watches YouTube on mobile during commute. Only stops for humor or shock value. Hates sales pitches."),
("Intentional Viewer", "35-year-old who watches ads if they're relevant. Desktop viewer, often in research mode. Will watch 15s if the topic matches their search."),
("Background Listener", "28-year-old who plays YouTube in the background while working. Audio matters more than visual. Will skip if the first sound is a jingle."),
]:
p = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
"name": name, "description": desc,
}).json()
persona_ids.append(p["id"])
time.sleep(0.3)
# 4. Focus Group — "Would you skip?"
result_block = "\n\n".join(
f"AD {i+1}: \"{r['label']}\"\n"
f" Hook Score: {r['hook_score']}/100 — {r['hook_assessment']}\n"
f" Visual Complexity: {r['visual_complexity']}/10 | Text Legibility: {r['text_legibility']}/10\n"
f" Emotional Trigger: {r['emotional_trigger']} | Cognitive Load: {r['cognitive_load']}/10"
for i, r in enumerate(results)
)
fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
"name": "Pre-Roll Skip Test",
"persona_ids": persona_ids,
"questions": [
f"You're watching a YouTube video and these pre-roll ads play. For each, answer: Would you SKIP or WATCH? Why?\n\n{result_block}",
"Which ad's first 2 seconds grabbed you? What specifically hooked you?",
"Rewrite the opening line of the weakest ad to make you NOT skip.",
"Rate each ad 1-10 on 'Would I click through to the website?'",
],
"responses_per_persona": 2,
}).json()
for _ in range(20):
time.sleep(5)
data = requests.get(f"{MV_BASE}/focus-groups/{fg['id']}", headers=MV_H).json()
if data.get("status") == "completed":
break
print("PRE-ROLL AD SKIP TEST")
print("=" * 60)
for r in results:
skip_predict = "WATCH" if r["hook_score"] > 70 else "LIKELY SKIP" if r["hook_score"] > 45 else "SKIP"
print(f" {r['label']:<45} Hook:{r['hook_score']:>3}/100 → {skip_predict}")
print("\nFOCUS GROUP RESPONSES:")
for resp in data.get("responses", []):
print(f"\n[{resp.get('persona_id', '?')[:20]}] {resp.get('question', '')[:60]}...")
print(f" → {resp.get('answer', '')[:300]}")
const MV = process.env.MAVERA_API_KEY;
const MV_BASE = "https://app.mavera.io/api/v1";
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const fs = await import("fs");
const PRE_ROLL_CANDIDATES = [
{ path: "ads/preroll_a_product_demo.mp4", label: "Product Demo — Fast Cuts" },
{ path: "ads/preroll_b_testimonial.mp4", label: "Customer Testimonial — Face First" },
{ path: "ads/preroll_c_problem_hook.mp4", label: "Problem Hook — Text Overlay" },
{ path: "ads/preroll_d_humor.mp4", label: "Humor — Unexpected Opening" },
];
// 1. Upload and analyze
const analyses = [];
for (const c of PRE_ROLL_CANDIDATES) {
const formData = new FormData();
formData.append("file", new Blob([fs.readFileSync(c.path)], { type: "video/mp4" }), `${c.label}.mp4`);
const upload = await fetch(`${MV_BASE}/assets`, {
method: "POST", headers: { Authorization: `Bearer ${MV}` }, body: formData,
}).then(r => r.json());
const analysis = await fetch(`${MV_BASE}/video-analysis`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
asset_id: upload.id,
analysis_types: ["hook_score", "visual_complexity", "text_legibility", "emotional_arc", "cognitive_load", "pacing"],
options: { focus_window: "0-5s" },
metadata: { label: c.label, format: "pre-roll" },
}),
}).then(r => r.json());
analyses.push({ id: analysis.id, label: c.label });
await new Promise(r => setTimeout(r, 500));
}
// 2. Poll
const results = [];
for (const a of analyses) {
let status;
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 3000));
status = await fetch(`${MV_BASE}/video-analysis/${a.id}`, { headers: MV_H }).then(r => r.json());
if (status.status === "completed") break;
}
const r = status.results || {};
results.push({
label: a.label, hookScore: r.hook_score?.score || 0,
hookAssessment: r.hook_score?.assessment || "",
visualComplexity: r.visual_complexity?.score || 0,
textLegibility: r.text_legibility?.score || 0,
emotionalTrigger: r.emotional_arc?.peak_emotion || "none",
cognitiveLoad: r.cognitive_load?.average || 0,
});
}
// 3. Personas
const personaIds = [];
for (const [name, desc] of [
["Impatient Scroller", "25yo, skips every ad, mobile, commute. Only humor/shock stops scroll."],
["Intentional Viewer", "35yo, watches relevant ads, desktop, research mode. Will watch 15s if relevant."],
["Background Listener", "28yo, background YouTube while working. Audio > visual. Skips jingles."],
]) {
const p = await fetch(`${MV_BASE}/personas`, { method: "POST", headers: MV_H,
body: JSON.stringify({ name, description: desc }) }).then(r => r.json());
personaIds.push(p.id);
await new Promise(r => setTimeout(r, 300));
}
// 4. Focus Group
const resultBlock = results.map((r, i) =>
`AD ${i + 1}: "${r.label}"\n Hook: ${r.hookScore}/100 — ${r.hookAssessment}\n Visual: ${r.visualComplexity}/10 | Text: ${r.textLegibility}/10\n Emotion: ${r.emotionalTrigger} | Cog Load: ${r.cognitiveLoad}/10`
).join("\n\n");
const fg = await fetch(`${MV_BASE}/focus-groups`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
name: "Pre-Roll Skip Test", persona_ids: personaIds,
questions: [
`Pre-roll ads below. SKIP or WATCH each? Why?\n\n${resultBlock}`,
"Which first 2 seconds grabbed you? What hooked you?",
"Rewrite the weakest ad's opener to make you NOT skip.",
"Rate each 1-10: 'Would I click through?'",
],
responses_per_persona: 2,
}),
}).then(r => r.json());
let data;
for (let i = 0; i < 20; i++) {
await new Promise(r => setTimeout(r, 5000));
data = await fetch(`${MV_BASE}/focus-groups/${fg.id}`, { headers: MV_H }).then(r => r.json());
if (data.status === "completed") break;
}
console.log("PRE-ROLL AD SKIP TEST");
console.log("=".repeat(60));
for (const r of results) {
const verdict = r.hookScore > 70 ? "WATCH" : r.hookScore > 45 ? "LIKELY SKIP" : "SKIP";
console.log(` ${r.label.padEnd(45)} Hook:${String(r.hookScore).padStart(3)}/100 → ${verdict}`);
}
console.log("\nFOCUS GROUP RESPONSES:");
for (const resp of data.responses || []) {
console.log(`\n[${(resp.persona_id || "?").slice(0, 20)}] ${(resp.question || "").slice(0, 60)}...`);
console.log(` → ${(resp.answer || "").slice(0, 300)}`);
}
Example Output
PRE-ROLL AD SKIP TEST
============================================================
Product Demo — Fast Cuts Hook: 52/100 → LIKELY SKIP
Customer Testimonial — Face First Hook: 78/100 → WATCH
Problem Hook — Text Overlay Hook: 85/100 → WATCH
Humor — Unexpected Opening Hook: 91/100 → WATCH
FOCUS GROUP RESPONSES:
[Impatient Scroller] Pre-roll ads below. SKIP or WATCH each?...
→ AD 1: SKIP — opens with a product shot, my finger is already on Skip.
AD 2: WATCH — face in first frame creates social obligation to listen.
AD 3: WATCH — text overlay "You're wasting $400/month" hits a nerve.
AD 4: WATCH — unexpected visual makes me curious. I'll give it 5 more seconds.
[Intentional Viewer] Rate each 1-10: 'Would I click through?'...
→ AD 1: 4/10 — generic product demo. AD 2: 7/10 — real person builds trust.
AD 3: 9/10 — problem framing matches my search intent. AD 4: 6/10 — funny
but unclear what the product does.
[Background Listener] Rewrite the weakest ad's opener...
→ AD 1 original: [product spinning on white background, corporate music]
Rewrite: Open with a voice saying "Stop. You're about to skip this, but
you're also about to waste $400 this month." — audio-first grabs background
listeners like me.
Error Handling
Focus window option
Focus window option
The
focus_window: "0-5s" option restricts analysis to the opening seconds. If unsupported, the full video is analyzed — results are still valid but include post-skip content.Pre-roll format requirements
Pre-roll format requirements
YouTube pre-roll ads are 6s (bumper), 15s, or 30s. Ensure uploaded files match these lengths. Longer files are analyzed in full, diluting the skip-window focus.
Focus Group persona calibration
Focus Group persona calibration
The “Impatient Scroller” persona skews negative — expected. Weight their feedback for skip prediction, but weight “Intentional Viewer” for click-through prediction.