Tutorials

Catch AI Video Slop in 15 Minutes with Pegasus

James Le

Learn how to build an automated QC pipeline for AI video using TwelveLabs' Pegasus model, with step-by-step code for brief-match verdicts, timestamped defect detection, and lip-sync verification, in about 15 minutes.

Learn how to build an automated QC pipeline for AI video using TwelveLabs' Pegasus model, with step-by-step code for brief-match verdicts, timestamped defect detection, and lip-sync verification, in about 15 minutes.

In this article

No headings found on page

Join our newsletter

Receive the latest advancements, tutorials, and industry insights in video understanding

AI로 영상을 검색하고, 분석하고, 탐색하세요.

2026. 7. 10.

6 min

링크 복사하기

Here is an AI-generated clip of a magician shuffling cards. Mid-shuffle, his fingers warp, merge, and melt into the deck. And here is Pegasus catching it: real API output, unedited.

The demo flagging this exact clip, with a clickable defect timeline

Timestamped, typed, machine-readable: the kind of verdict you can wire straight into an automation loop. In the next 15 minutes you'll build the check that produced it, plus a whole-clip pass/fail check, against real slop clips you can download. If you'd rather see it before building it, the live playground runs all five checks in your browser.

Step 0 — Setup (2 minutes)

  1. Create a free TwelveLabs account and copy your API key from the dashboard.

  2. Install the SDK:

from twelvelabs import TwelveLabs

client = TwelveLabs(api_key="YOUR_API_KEY")

That's the whole setup. No index, no cluster, no config file.

Step 1 — Two modes, one decision (1 minute)

Pegasus 1.5 gives you two ways to run QC. Picking the right one is the only architectural decision in this tutorial:


Verdict — prompt-based

analyze

Timeline — Segment /

time_based_metadata

You send

An engineered prompt

A segment schema (no prompt)

You get

One JSON answer for the whole clip

Timestamped segments with typed fields

Best for

"Did I get what I ordered?"

"Where exactly is it broken?"

Rule of thumb: QC over time almost always wants Timeline. A whole-clip FAIL tells you to re-roll; a timestamped FAIL tells you what to trim, seek, or regenerate. We'll build one of each.

Step 2 — Upload a clip as an asset (2 minutes)

Pegasus 1.5 works directly on assets, no indexing step. Upload once, reuse the asset_id for every check:

import time

CLIP_URL = "<https://pegasus-qc-playground.vercel.app/clips/card-shuffle.mp4>"  # our warped-hands clip or bring your own

def upload(url):
    asset = client.assets.create(method="url", url=url)
    while client.assets.retrieve(asset.id).status != "ready":
        time.sleep(3)
    return asset

cards = upload(CLIP_URL) # for the Timeline check
product = upload("<https://pegasus-qc-playground.vercel.app/clips/product-broll.mp4>")      # for the Verdict check

All five sample clips from the demo are free to use and provenance-logged (model, prompt, and seed for each; see the PROVENANCE file in the demo so you know the slop is real, not staged).

Step 3 — Your first Verdict check: brief-match (3 minutes)

The highest-volume QC question in production is the simplest: did the generator produce what was ordered? Send the original brief along with the clip, and demand structured JSON back:

import json
from twelvelabs.types import VideoContext_AssetId

BRIEF = (
    'A red ceramic coffee mug with the word "SUMMIT" printed on it in white capital '
    "letters, steam rising from the coffee, on a rustic wooden desk next to an open "
    "silver laptop and a small white plate holding two croissants, camera slowly pans "
    "left to right, warm morning light through a window"
)

PROMPT = f"""You are a broadcast generation-QA engineer inspecting a SINGLE freshly generated
video clip (one beat) BEFORE it is composited into a final cut. Your only job is
to answer: did the generator produce what was requested?

The clip was ordered with this brief:
"{BRIEF}"

Judge ONLY whether the brief was fulfilled. Do NOT score creative quality or taste.

Respond with ONLY valid JSON:
{{
  "brief_fulfilled": true/false,
  "subject_present": true/false,
  "action_present": true/false,
  "setting_correct": true/false,
  "missing_or_wrong": ["<each element that is missing or wrong>"],
  "confidence": 0.0-1.0,
  "notes": "<one sentence>"
}}"""

res = client.analyze(
    video=VideoContext_AssetId(asset_id=product.id),
    model_name="pegasus1.5",
    prompt=PROMPT,
    temperature=0.2,  # low = deterministic, better for QC
)
verdict = json.loads(res.data)

Run it against our product-shot clip and you get this back, again, real output:

{
  "brief_fulfilled": false,
  "subject_present": true,
  "action_present": false,
  "setting_correct": true,
  "missing_or_wrong": [
    "The laptop is closed, not open as requested.",
    "The plate holds only one croissant, not two.",
    "The camera is static; there is no slow pan left to right."
  ],
  "confidence": 0.95,
  "notes": "The generator failed to render the laptop as open, included only one croissant instead of two, and did not execute the requested camera pan."
}

It counted the croissants. That missing_or_wrong array is your automated re-prompt: feed it back to the generator and order exactly what was missed.

That was your first API call. One round-trip, one structured verdict. Everything else is variations.

Step 4 — Level up to Timeline: the AV-defect audit (5 minutes)

Now the centerpiece. Instead of a prompt, Segment mode takes segment definitions: a schema describing what to find and what to extract for each hit:

from twelvelabs.types import AsyncResponseFormat

SEGMENT_DEFINITION = {
    "id": "av_defects",
    "description": (
        "Segment the clip wherever a technical audio-visual defect is visible or "
        "audible. Include flickering, visual artifacts, warping or morphing objects, "
        "malformed hands or faces, duplicated limbs, sudden resolution or exposure "
        "jumps, audio dropout, distortion, or lip/audio sync drift. Do NOT judge "
        "creative quality (hook, brand fit, aesthetics)."
    ),
    "fields": [
        {"name": "defect_type", "type": "string",
         "description": "The primary defect in this segment",
         "enum": ["flicker", "artifact", "warping", "malformed_anatomy",
                  "duplicated_limb", "exposure_jump", "audio_dropout",
                  "distortion", "sync_drift", "other"]},
        {"name": "severity", "type": "string",
         "description": "How disqualifying the defect is",
         "enum": ["minor", "major"]},
        {"name": "description", "type": "string",
         "description": "One-sentence description of what is wrong in this segment"},
    ],
}

task = client.analyze_async.tasks.create(
    video=VideoContext_AssetId(asset_id=cards.id),
    model_name="pegasus1.5",
    analysis_mode="time_based_metadata",
    temperature=0.2,
    min_segment_duration=2.0,
    response_format=AsyncResponseFormat(
        type="segment_definitions",
        segment_definitions=[SEGMENT_DEFINITION],
    ),
)
while True:
    task = client.analyze_async.tasks.retrieve(task.task_id)
    if task.status in ("ready", "failed"):
        break
    time.sleep(5)

data = json.loads(task.result.data)
for seg in data["av_defects"]:
    print(f'{seg["start_time"]}s–{seg["end_time"]}s', seg["metadata"])

The anatomy, quickly: the definition's id becomes the top-level key in the response; each field needs a type and a description (use enum to lock categorical values); min_segment_duration keeps segments meaningful (2s minimum). A clean clip returns an empty list, so PASS is falsifiable.

Three gotchas that will otherwise cost you a support ticket:

  • No prompt in this mode. Pass one and you get a 400. Your instructions live in the segment and field descriptions.

  • Every field requires a description. It's not decoration — see the box below.

  • finish_reason == "length" means the JSON was truncated: raise max_tokens (up to 98,304) or trim fields.

Your field descriptions ARE the prompt. Engineer them like one.

While building the demo, our first lip-sync check passed a clip where TTS audio played over a completely closed mouth. The field description, "True if lips articulate the words that are heard," let the model answer generously without really looking. Three schema lessons later, the same model on the same clip returns this instead:

{
  "mouth_visibly_articulating": false,
  "lips_match_words": false,
  "native_lipsync_not_tts_over_silence": false,
  "speech_audible": true,
  "spoken_words": "Our new product launches this Friday, and we could not be more excited about it.",
  "notes": "The woman's mouth remains closed or in a static smile throughout the clip. She does not articulate any words, indicating the audio is a voiceover or TTS layered over the footage."
}

The three lessons, in the order they cost us time: (1) Force visual attention: word booleans as "True ONLY if you can SEE..." rather than descriptions a model can satisfy from audio alone. (2) Gate applicability: the speech_audible field is what keeps a silent product shot from "failing" a lip-sync check; judge lips_match_words only where it's true. (3) Field order matters: when we put the audio gate first, answering "speech is audible" anchored a speaking-person prior and the mouth booleans followed it, consistently.

Visual judgments go before audio anchors, and each field says which modality to judge. When a segment check misses, sharpen the schema before you blame the model. The full evolution is logged in the demo's provenance file.

For a deeper dive, the official Segment quickstart notebook covers scoping with time_ranges, multiple definitions per request, and more.

Step 5 — The full check suite (3 minutes)

You now know both modes, which means you can build all five production checks. The complete prompt pack (every prompt and segment definition below, copy-paste ready) ships with the demo repo.

Check

Mode

Use it when

Brief-match

Verdict

Gating generations against the order (highest volume)

Content verification

Verdict

Scripted dialogue — right character, right line

AV-defect audit

Timeline

Technical artifacts you want timestamped

Lip-sync verification

Timeline

Any talking head — catches TTS-over-still-mouth

Content log

Timeline

Cataloging what you keep, scene by scene

One more trick worth knowing: segment definitions accept up to four reference images via media_sources, referenced in descriptions as <@name> for example "Segment where the on-screen product matches <@intended_product>". That turns brief-match into brand- and character-consistency QC.

Step 6 — Turn it into a pipeline (3 minutes)

Here's the beat that changes this from a demo into infrastructure. Because Timeline verdicts carry timestamps, you don't re-roll failed clips, you re-roll failed windows:

for seg in data["av_defects"]:
    if seg["metadata"]["severity"] == "major":
        # surgical: re-generate only the broken beat, keep the rest
        regenerate(clip, retry_window=(seg["start_time"], seg["end_time"]))

This is the exact pattern running in production today at generative-video studios doing tens of thousands of QC calls: generate -> QC -> auto-route failures back to the generator -> composite only green clips. Zero human review between generation and edit. It's only possible because the verdict says where, not just whether.

Where to go next

  • Scale it: batch your checks, scope long inputs with start_time/end_time or per-definition time_ranges, and pack up to 10 definitions into one request.

  • Wire it to your generator: the QC call doesn't care whether the clip came from Fal, Kling, Runway, or Veo — if it has a URL, it can be checked.

  • Fork the notebook: runnable Colab with all five checks.

  • Play first, build second: the live QC Playground runs every check in this post against every sample clip, and shows the exact API call behind each verdict.

  • Why this layer matters: the companion piece The QC Node: Why Every AI Video Pipeline Needs a Quality-Control Layer

Fifteen minutes, two modes, five checks — and nothing broken gets past your pipeline again.

Here is an AI-generated clip of a magician shuffling cards. Mid-shuffle, his fingers warp, merge, and melt into the deck. And here is Pegasus catching it: real API output, unedited.

The demo flagging this exact clip, with a clickable defect timeline

Timestamped, typed, machine-readable: the kind of verdict you can wire straight into an automation loop. In the next 15 minutes you'll build the check that produced it, plus a whole-clip pass/fail check, against real slop clips you can download. If you'd rather see it before building it, the live playground runs all five checks in your browser.

Step 0 — Setup (2 minutes)

  1. Create a free TwelveLabs account and copy your API key from the dashboard.

  2. Install the SDK:

from twelvelabs import TwelveLabs

client = TwelveLabs(api_key="YOUR_API_KEY")

That's the whole setup. No index, no cluster, no config file.

Step 1 — Two modes, one decision (1 minute)

Pegasus 1.5 gives you two ways to run QC. Picking the right one is the only architectural decision in this tutorial:


Verdict — prompt-based

analyze

Timeline — Segment /

time_based_metadata

You send

An engineered prompt

A segment schema (no prompt)

You get

One JSON answer for the whole clip

Timestamped segments with typed fields

Best for

"Did I get what I ordered?"

"Where exactly is it broken?"

Rule of thumb: QC over time almost always wants Timeline. A whole-clip FAIL tells you to re-roll; a timestamped FAIL tells you what to trim, seek, or regenerate. We'll build one of each.

Step 2 — Upload a clip as an asset (2 minutes)

Pegasus 1.5 works directly on assets, no indexing step. Upload once, reuse the asset_id for every check:

import time

CLIP_URL = "<https://pegasus-qc-playground.vercel.app/clips/card-shuffle.mp4>"  # our warped-hands clip or bring your own

def upload(url):
    asset = client.assets.create(method="url", url=url)
    while client.assets.retrieve(asset.id).status != "ready":
        time.sleep(3)
    return asset

cards = upload(CLIP_URL) # for the Timeline check
product = upload("<https://pegasus-qc-playground.vercel.app/clips/product-broll.mp4>")      # for the Verdict check

All five sample clips from the demo are free to use and provenance-logged (model, prompt, and seed for each; see the PROVENANCE file in the demo so you know the slop is real, not staged).

Step 3 — Your first Verdict check: brief-match (3 minutes)

The highest-volume QC question in production is the simplest: did the generator produce what was ordered? Send the original brief along with the clip, and demand structured JSON back:

import json
from twelvelabs.types import VideoContext_AssetId

BRIEF = (
    'A red ceramic coffee mug with the word "SUMMIT" printed on it in white capital '
    "letters, steam rising from the coffee, on a rustic wooden desk next to an open "
    "silver laptop and a small white plate holding two croissants, camera slowly pans "
    "left to right, warm morning light through a window"
)

PROMPT = f"""You are a broadcast generation-QA engineer inspecting a SINGLE freshly generated
video clip (one beat) BEFORE it is composited into a final cut. Your only job is
to answer: did the generator produce what was requested?

The clip was ordered with this brief:
"{BRIEF}"

Judge ONLY whether the brief was fulfilled. Do NOT score creative quality or taste.

Respond with ONLY valid JSON:
{{
  "brief_fulfilled": true/false,
  "subject_present": true/false,
  "action_present": true/false,
  "setting_correct": true/false,
  "missing_or_wrong": ["<each element that is missing or wrong>"],
  "confidence": 0.0-1.0,
  "notes": "<one sentence>"
}}"""

res = client.analyze(
    video=VideoContext_AssetId(asset_id=product.id),
    model_name="pegasus1.5",
    prompt=PROMPT,
    temperature=0.2,  # low = deterministic, better for QC
)
verdict = json.loads(res.data)

Run it against our product-shot clip and you get this back, again, real output:

{
  "brief_fulfilled": false,
  "subject_present": true,
  "action_present": false,
  "setting_correct": true,
  "missing_or_wrong": [
    "The laptop is closed, not open as requested.",
    "The plate holds only one croissant, not two.",
    "The camera is static; there is no slow pan left to right."
  ],
  "confidence": 0.95,
  "notes": "The generator failed to render the laptop as open, included only one croissant instead of two, and did not execute the requested camera pan."
}

It counted the croissants. That missing_or_wrong array is your automated re-prompt: feed it back to the generator and order exactly what was missed.

That was your first API call. One round-trip, one structured verdict. Everything else is variations.

Step 4 — Level up to Timeline: the AV-defect audit (5 minutes)

Now the centerpiece. Instead of a prompt, Segment mode takes segment definitions: a schema describing what to find and what to extract for each hit:

from twelvelabs.types import AsyncResponseFormat

SEGMENT_DEFINITION = {
    "id": "av_defects",
    "description": (
        "Segment the clip wherever a technical audio-visual defect is visible or "
        "audible. Include flickering, visual artifacts, warping or morphing objects, "
        "malformed hands or faces, duplicated limbs, sudden resolution or exposure "
        "jumps, audio dropout, distortion, or lip/audio sync drift. Do NOT judge "
        "creative quality (hook, brand fit, aesthetics)."
    ),
    "fields": [
        {"name": "defect_type", "type": "string",
         "description": "The primary defect in this segment",
         "enum": ["flicker", "artifact", "warping", "malformed_anatomy",
                  "duplicated_limb", "exposure_jump", "audio_dropout",
                  "distortion", "sync_drift", "other"]},
        {"name": "severity", "type": "string",
         "description": "How disqualifying the defect is",
         "enum": ["minor", "major"]},
        {"name": "description", "type": "string",
         "description": "One-sentence description of what is wrong in this segment"},
    ],
}

task = client.analyze_async.tasks.create(
    video=VideoContext_AssetId(asset_id=cards.id),
    model_name="pegasus1.5",
    analysis_mode="time_based_metadata",
    temperature=0.2,
    min_segment_duration=2.0,
    response_format=AsyncResponseFormat(
        type="segment_definitions",
        segment_definitions=[SEGMENT_DEFINITION],
    ),
)
while True:
    task = client.analyze_async.tasks.retrieve(task.task_id)
    if task.status in ("ready", "failed"):
        break
    time.sleep(5)

data = json.loads(task.result.data)
for seg in data["av_defects"]:
    print(f'{seg["start_time"]}s–{seg["end_time"]}s', seg["metadata"])

The anatomy, quickly: the definition's id becomes the top-level key in the response; each field needs a type and a description (use enum to lock categorical values); min_segment_duration keeps segments meaningful (2s minimum). A clean clip returns an empty list, so PASS is falsifiable.

Three gotchas that will otherwise cost you a support ticket:

  • No prompt in this mode. Pass one and you get a 400. Your instructions live in the segment and field descriptions.

  • Every field requires a description. It's not decoration — see the box below.

  • finish_reason == "length" means the JSON was truncated: raise max_tokens (up to 98,304) or trim fields.

Your field descriptions ARE the prompt. Engineer them like one.

While building the demo, our first lip-sync check passed a clip where TTS audio played over a completely closed mouth. The field description, "True if lips articulate the words that are heard," let the model answer generously without really looking. Three schema lessons later, the same model on the same clip returns this instead:

{
  "mouth_visibly_articulating": false,
  "lips_match_words": false,
  "native_lipsync_not_tts_over_silence": false,
  "speech_audible": true,
  "spoken_words": "Our new product launches this Friday, and we could not be more excited about it.",
  "notes": "The woman's mouth remains closed or in a static smile throughout the clip. She does not articulate any words, indicating the audio is a voiceover or TTS layered over the footage."
}

The three lessons, in the order they cost us time: (1) Force visual attention: word booleans as "True ONLY if you can SEE..." rather than descriptions a model can satisfy from audio alone. (2) Gate applicability: the speech_audible field is what keeps a silent product shot from "failing" a lip-sync check; judge lips_match_words only where it's true. (3) Field order matters: when we put the audio gate first, answering "speech is audible" anchored a speaking-person prior and the mouth booleans followed it, consistently.

Visual judgments go before audio anchors, and each field says which modality to judge. When a segment check misses, sharpen the schema before you blame the model. The full evolution is logged in the demo's provenance file.

For a deeper dive, the official Segment quickstart notebook covers scoping with time_ranges, multiple definitions per request, and more.

Step 5 — The full check suite (3 minutes)

You now know both modes, which means you can build all five production checks. The complete prompt pack (every prompt and segment definition below, copy-paste ready) ships with the demo repo.

Check

Mode

Use it when

Brief-match

Verdict

Gating generations against the order (highest volume)

Content verification

Verdict

Scripted dialogue — right character, right line

AV-defect audit

Timeline

Technical artifacts you want timestamped

Lip-sync verification

Timeline

Any talking head — catches TTS-over-still-mouth

Content log

Timeline

Cataloging what you keep, scene by scene

One more trick worth knowing: segment definitions accept up to four reference images via media_sources, referenced in descriptions as <@name> for example "Segment where the on-screen product matches <@intended_product>". That turns brief-match into brand- and character-consistency QC.

Step 6 — Turn it into a pipeline (3 minutes)

Here's the beat that changes this from a demo into infrastructure. Because Timeline verdicts carry timestamps, you don't re-roll failed clips, you re-roll failed windows:

for seg in data["av_defects"]:
    if seg["metadata"]["severity"] == "major":
        # surgical: re-generate only the broken beat, keep the rest
        regenerate(clip, retry_window=(seg["start_time"], seg["end_time"]))

This is the exact pattern running in production today at generative-video studios doing tens of thousands of QC calls: generate -> QC -> auto-route failures back to the generator -> composite only green clips. Zero human review between generation and edit. It's only possible because the verdict says where, not just whether.

Where to go next

  • Scale it: batch your checks, scope long inputs with start_time/end_time or per-definition time_ranges, and pack up to 10 definitions into one request.

  • Wire it to your generator: the QC call doesn't care whether the clip came from Fal, Kling, Runway, or Veo — if it has a URL, it can be checked.

  • Fork the notebook: runnable Colab with all five checks.

  • Play first, build second: the live QC Playground runs every check in this post against every sample clip, and shows the exact API call behind each verdict.

  • Why this layer matters: the companion piece The QC Node: Why Every AI Video Pipeline Needs a Quality-Control Layer

Fifteen minutes, two modes, five checks — and nothing broken gets past your pipeline again.

Here is an AI-generated clip of a magician shuffling cards. Mid-shuffle, his fingers warp, merge, and melt into the deck. And here is Pegasus catching it: real API output, unedited.

The demo flagging this exact clip, with a clickable defect timeline

Timestamped, typed, machine-readable: the kind of verdict you can wire straight into an automation loop. In the next 15 minutes you'll build the check that produced it, plus a whole-clip pass/fail check, against real slop clips you can download. If you'd rather see it before building it, the live playground runs all five checks in your browser.

Step 0 — Setup (2 minutes)

  1. Create a free TwelveLabs account and copy your API key from the dashboard.

  2. Install the SDK:

from twelvelabs import TwelveLabs

client = TwelveLabs(api_key="YOUR_API_KEY")

That's the whole setup. No index, no cluster, no config file.

Step 1 — Two modes, one decision (1 minute)

Pegasus 1.5 gives you two ways to run QC. Picking the right one is the only architectural decision in this tutorial:


Verdict — prompt-based

analyze

Timeline — Segment /

time_based_metadata

You send

An engineered prompt

A segment schema (no prompt)

You get

One JSON answer for the whole clip

Timestamped segments with typed fields

Best for

"Did I get what I ordered?"

"Where exactly is it broken?"

Rule of thumb: QC over time almost always wants Timeline. A whole-clip FAIL tells you to re-roll; a timestamped FAIL tells you what to trim, seek, or regenerate. We'll build one of each.

Step 2 — Upload a clip as an asset (2 minutes)

Pegasus 1.5 works directly on assets, no indexing step. Upload once, reuse the asset_id for every check:

import time

CLIP_URL = "<https://pegasus-qc-playground.vercel.app/clips/card-shuffle.mp4>"  # our warped-hands clip or bring your own

def upload(url):
    asset = client.assets.create(method="url", url=url)
    while client.assets.retrieve(asset.id).status != "ready":
        time.sleep(3)
    return asset

cards = upload(CLIP_URL) # for the Timeline check
product = upload("<https://pegasus-qc-playground.vercel.app/clips/product-broll.mp4>")      # for the Verdict check

All five sample clips from the demo are free to use and provenance-logged (model, prompt, and seed for each; see the PROVENANCE file in the demo so you know the slop is real, not staged).

Step 3 — Your first Verdict check: brief-match (3 minutes)

The highest-volume QC question in production is the simplest: did the generator produce what was ordered? Send the original brief along with the clip, and demand structured JSON back:

import json
from twelvelabs.types import VideoContext_AssetId

BRIEF = (
    'A red ceramic coffee mug with the word "SUMMIT" printed on it in white capital '
    "letters, steam rising from the coffee, on a rustic wooden desk next to an open "
    "silver laptop and a small white plate holding two croissants, camera slowly pans "
    "left to right, warm morning light through a window"
)

PROMPT = f"""You are a broadcast generation-QA engineer inspecting a SINGLE freshly generated
video clip (one beat) BEFORE it is composited into a final cut. Your only job is
to answer: did the generator produce what was requested?

The clip was ordered with this brief:
"{BRIEF}"

Judge ONLY whether the brief was fulfilled. Do NOT score creative quality or taste.

Respond with ONLY valid JSON:
{{
  "brief_fulfilled": true/false,
  "subject_present": true/false,
  "action_present": true/false,
  "setting_correct": true/false,
  "missing_or_wrong": ["<each element that is missing or wrong>"],
  "confidence": 0.0-1.0,
  "notes": "<one sentence>"
}}"""

res = client.analyze(
    video=VideoContext_AssetId(asset_id=product.id),
    model_name="pegasus1.5",
    prompt=PROMPT,
    temperature=0.2,  # low = deterministic, better for QC
)
verdict = json.loads(res.data)

Run it against our product-shot clip and you get this back, again, real output:

{
  "brief_fulfilled": false,
  "subject_present": true,
  "action_present": false,
  "setting_correct": true,
  "missing_or_wrong": [
    "The laptop is closed, not open as requested.",
    "The plate holds only one croissant, not two.",
    "The camera is static; there is no slow pan left to right."
  ],
  "confidence": 0.95,
  "notes": "The generator failed to render the laptop as open, included only one croissant instead of two, and did not execute the requested camera pan."
}

It counted the croissants. That missing_or_wrong array is your automated re-prompt: feed it back to the generator and order exactly what was missed.

That was your first API call. One round-trip, one structured verdict. Everything else is variations.

Step 4 — Level up to Timeline: the AV-defect audit (5 minutes)

Now the centerpiece. Instead of a prompt, Segment mode takes segment definitions: a schema describing what to find and what to extract for each hit:

from twelvelabs.types import AsyncResponseFormat

SEGMENT_DEFINITION = {
    "id": "av_defects",
    "description": (
        "Segment the clip wherever a technical audio-visual defect is visible or "
        "audible. Include flickering, visual artifacts, warping or morphing objects, "
        "malformed hands or faces, duplicated limbs, sudden resolution or exposure "
        "jumps, audio dropout, distortion, or lip/audio sync drift. Do NOT judge "
        "creative quality (hook, brand fit, aesthetics)."
    ),
    "fields": [
        {"name": "defect_type", "type": "string",
         "description": "The primary defect in this segment",
         "enum": ["flicker", "artifact", "warping", "malformed_anatomy",
                  "duplicated_limb", "exposure_jump", "audio_dropout",
                  "distortion", "sync_drift", "other"]},
        {"name": "severity", "type": "string",
         "description": "How disqualifying the defect is",
         "enum": ["minor", "major"]},
        {"name": "description", "type": "string",
         "description": "One-sentence description of what is wrong in this segment"},
    ],
}

task = client.analyze_async.tasks.create(
    video=VideoContext_AssetId(asset_id=cards.id),
    model_name="pegasus1.5",
    analysis_mode="time_based_metadata",
    temperature=0.2,
    min_segment_duration=2.0,
    response_format=AsyncResponseFormat(
        type="segment_definitions",
        segment_definitions=[SEGMENT_DEFINITION],
    ),
)
while True:
    task = client.analyze_async.tasks.retrieve(task.task_id)
    if task.status in ("ready", "failed"):
        break
    time.sleep(5)

data = json.loads(task.result.data)
for seg in data["av_defects"]:
    print(f'{seg["start_time"]}s–{seg["end_time"]}s', seg["metadata"])

The anatomy, quickly: the definition's id becomes the top-level key in the response; each field needs a type and a description (use enum to lock categorical values); min_segment_duration keeps segments meaningful (2s minimum). A clean clip returns an empty list, so PASS is falsifiable.

Three gotchas that will otherwise cost you a support ticket:

  • No prompt in this mode. Pass one and you get a 400. Your instructions live in the segment and field descriptions.

  • Every field requires a description. It's not decoration — see the box below.

  • finish_reason == "length" means the JSON was truncated: raise max_tokens (up to 98,304) or trim fields.

Your field descriptions ARE the prompt. Engineer them like one.

While building the demo, our first lip-sync check passed a clip where TTS audio played over a completely closed mouth. The field description, "True if lips articulate the words that are heard," let the model answer generously without really looking. Three schema lessons later, the same model on the same clip returns this instead:

{
  "mouth_visibly_articulating": false,
  "lips_match_words": false,
  "native_lipsync_not_tts_over_silence": false,
  "speech_audible": true,
  "spoken_words": "Our new product launches this Friday, and we could not be more excited about it.",
  "notes": "The woman's mouth remains closed or in a static smile throughout the clip. She does not articulate any words, indicating the audio is a voiceover or TTS layered over the footage."
}

The three lessons, in the order they cost us time: (1) Force visual attention: word booleans as "True ONLY if you can SEE..." rather than descriptions a model can satisfy from audio alone. (2) Gate applicability: the speech_audible field is what keeps a silent product shot from "failing" a lip-sync check; judge lips_match_words only where it's true. (3) Field order matters: when we put the audio gate first, answering "speech is audible" anchored a speaking-person prior and the mouth booleans followed it, consistently.

Visual judgments go before audio anchors, and each field says which modality to judge. When a segment check misses, sharpen the schema before you blame the model. The full evolution is logged in the demo's provenance file.

For a deeper dive, the official Segment quickstart notebook covers scoping with time_ranges, multiple definitions per request, and more.

Step 5 — The full check suite (3 minutes)

You now know both modes, which means you can build all five production checks. The complete prompt pack (every prompt and segment definition below, copy-paste ready) ships with the demo repo.

Check

Mode

Use it when

Brief-match

Verdict

Gating generations against the order (highest volume)

Content verification

Verdict

Scripted dialogue — right character, right line

AV-defect audit

Timeline

Technical artifacts you want timestamped

Lip-sync verification

Timeline

Any talking head — catches TTS-over-still-mouth

Content log

Timeline

Cataloging what you keep, scene by scene

One more trick worth knowing: segment definitions accept up to four reference images via media_sources, referenced in descriptions as <@name> for example "Segment where the on-screen product matches <@intended_product>". That turns brief-match into brand- and character-consistency QC.

Step 6 — Turn it into a pipeline (3 minutes)

Here's the beat that changes this from a demo into infrastructure. Because Timeline verdicts carry timestamps, you don't re-roll failed clips, you re-roll failed windows:

for seg in data["av_defects"]:
    if seg["metadata"]["severity"] == "major":
        # surgical: re-generate only the broken beat, keep the rest
        regenerate(clip, retry_window=(seg["start_time"], seg["end_time"]))

This is the exact pattern running in production today at generative-video studios doing tens of thousands of QC calls: generate -> QC -> auto-route failures back to the generator -> composite only green clips. Zero human review between generation and edit. It's only possible because the verdict says where, not just whether.

Where to go next

  • Scale it: batch your checks, scope long inputs with start_time/end_time or per-definition time_ranges, and pack up to 10 definitions into one request.

  • Wire it to your generator: the QC call doesn't care whether the clip came from Fal, Kling, Runway, or Veo — if it has a URL, it can be checked.

  • Fork the notebook: runnable Colab with all five checks.

  • Play first, build second: the live QC Playground runs every check in this post against every sample clip, and shows the exact API call behind each verdict.

  • Why this layer matters: the companion piece The QC Node: Why Every AI Video Pipeline Needs a Quality-Control Layer

Fifteen minutes, two modes, five checks — and nothing broken gets past your pipeline again.