---
name: promo-clip-reuse-check
description: "Check old product videos and promo cuts for prices, percent off, promo codes and sale dates with Vivu, and get a reuse check sheet. Use before reusing clips in a new campaign or product page."
---Promo clip reuse check for outdated offers
This skill takes the product videos and promo cuts a store team plans to reuse on a new campaign or product page and returns a reuse check sheet: one row for each price, percent off, promo code or sale date the skill finds and checks in a clip, with the file name, the time as MM:SS, the words read from a full frame, the words said in the voiceover copied from a transcript, the frame it was read from, and an empty decision column ("use as is", "cut this part", "do not use"). Clips with no offer get a row that says so. Claude helps the user pick the 30 to 50 clips this campaign needs by folder and file name, prices the job against the user's Vivu plan, uploads the clips to a private Vivu project, runs two precise searches, checks every window against the frames or the transcript, sweeps every clip the searches did not return with a cheap local contact sheet, and writes the sheet.
The value is in what the clips carry that no file name or folder records. A "$9.99" or "20% OFF" burned into a product shot can sit on screen for two seconds, a sale date can run as a small badge through the whole spot, and a voiceover can say "just $9.99" with nothing written on screen. Text tools see none of this, and a transcript sees only the spoken half. The hard part is that promo cuts are full of text that is not an offer (product names, slogans, "best deal ever" with no number, quantities like "2 medium pizzas"), and Vivu's reason text has described on-screen words that were only spoken. So every window Vivu returns stays a candidate until a full frame shows the offer text or the transcript shows the spoken words, and the words in the sheet come from that frame or transcript, never from the reason text.
When to use
Use when someone asks "before we reuse these product videos, check which ones still show old prices or discount codes", "go through the summer promo folder and flag anything with a sale price or end date", "which of our old ads say 20% off?", or "make me a list of clips that mention a price so I can cut them before the relaunch".
Not for these:
- One clip, or one question about one video ("does the intro of this ad show the price?"): open the clip or search Vivu directly.
- Deciding whether a price claim or an ad is legal, compliant or still valid. The sheet lists where offers appear; the user and their legal or pricing owner decide.
- Finding shots for a new edit by shot list (product on a counter, someone carrying it). The shot-list-reuse-coverage skill (https://vivu.ai/skills/shot-list-reuse-coverage) is built for that.
- Footage the team does not own or has no license to reuse, such as a creator's video outside the license term.
- A whole video library of hundreds of clips in one run. Pick the clips this campaign will use first (Step 2); a full library costs more index minutes than any plan holds.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Nothing is verified until it has been checked against the source clip. An on-screen window is a candidate until a full frame shows the offer text, and the words in the sheet are copied from that frame. A spoken window is a candidate until the transcript shows the words. Vivu's reason text paraphrases and has put spoken prices on screen, so it never fills a cell.
- Stop and tell the user when a required capability or tool is missing. Do not guess around it.
- Ask the user before anything that is expensive to redo (the indexed minutes, before upload) and before anything that acts on their behalf (putting the sheet in a shared folder or posting a note). The skill writes local files only until the user approves a destination.
- Every clip gets a row. A clip the searches did not return is swept with a local contact sheet before it is marked "no offer found"; an empty search result does not prove a clip is clean.
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing before doing anything else.
| Requirement | Why | How to check |
|---|---|---|
| Vivu connector with write access | create a private project, open its upload page, search | vivu_get_account shows can_create_projects: true (tool names may carry a server prefix). A write call failing with "has not granted vivu.write" means the user reconnects Vivu and allows write access |
| A shell with ffmpeg and ffprobe | measure clip lengths, extract frames and contact sheets, cut a clip without the offer | ffmpeg -version, ffprobe -version |
| The clips as local files in one folder | Vivu indexes uploaded files, and frames are read from the user's own copy | ls CLIP_DIR shows the video files |
| A transcript for clips with a voiceover: a local speech to text tool such as whisper.cpp, or caption files the team already has | spoken prices are copied from a transcript, never from the reason text | whisper-cli --help (or the tool's own version command), or an .srt / .vtt next to the clip |
| An upload path | move the clips into Vivu | vivu_open_upload_page plus the user's own browser, or a browser tool that can attach local files |
| Which product and campaign the clips are for | picks the 30 to 50 clips worth indexing | the user names them, or points at the folders |
This skill needs Claude Code on the user's computer (the terminal or the Code tab of Claude Desktop), because it reads local files and runs ffmpeg. It does not download anything from YouTube or other sites, so no residential IP is needed. Nothing recurs, so no scheduler is involved; run it once per campaign. The only connector it needs is Vivu; putting the sheet anywhere else uses whichever connector the user names, after approval.
Inputs to collect
Ask for anything missing, most important first.
- CLIP_DIR: the folder (or folders) with the clips. Required.
- The product and campaign the clips are for. Required; Step 2 uses it to pick files by name and folder.
- Which offers count. Default: any price, percent off, promo code, free shipping or gift threshold with a number, and any sale date. The user may add the current offer so the sheet can mark rows that still match it.
- WORK_NAME: a short name for the working folder and the Vivu project. Default: the campaign name, for example fall-relaunch.
- Where the sheet goes. Default: a local CSV only; anything else waits for approval in Step 8.
Files and state
Keep everything in one working folder:
reuse-WORK_NAME/
config.json campaign, product, Vivu project id, query wording, destination
clips.csv file, listed_name, duration_s, video_id, transcript
upload/ the selected clips (copies, or links to the originals)
transcripts/ one .srt or .vtt per clip with a voiceover
results/ one JSON per search, without the result page link
frames/ contact sheets and the full frames the offer text was read from
candidates.csv every window Vivu returned, with the verdict and the reason
reuse_check.csv the sheet: one row per offer found, plus one row per clean clip
state.json steps done, clips uploaded, searches run with job ids, windows checked
Run every command from the folder that contains reuse-WORK_NAME/. A rerun reads state.json first: a clip marked uploaded is not uploaded again, a search with a saved result is not run again, and a window with a verdict in candidates.csv is not checked again.
Step 1: Check the Vivu connector and the setup
Goal: confirm every requirement before spending anything.
- Call vivu_get_account. If the tool does not exist, tell the user to add the Vivu connector in Claude (
https://mcp.vivu.ai/mcp) and stop. If the account does not showcan_create_projects: true, or a later write call fails with "has not granted vivu.write", ask the user to reconnect Vivu and allow write access, then stop until they have. - Run
ffmpeg -versionandffprobe -version, and check the transcript tool from the table above. - Confirm the Compliance items with the user, including whether any clip shows children and, if so, that guardian consent covers this use.
Done when vivu_get_account shows can_create_projects: true, both ffmpeg commands print a version, and the user has confirmed the clips are theirs to reuse, and any clip with children has guardian consent or is left out.
Step 2: Pick the clips and price the job
Goal: the clips this campaign will use, measured and priced, with the user's approval before anything is uploaded.
- List the video files under CLIP_DIR with their folders. Pick the ones whose folder or file name matches the product and campaign from the inputs, show the list, and let the user add or drop files. Indexing a whole library to find a few promo cuts wastes minutes; a campaign usually reuses 30 to 50 clips.
- Copy the chosen files into reuse-WORK_NAME/upload/ with names that keep only letters, digits and hyphens (CLIPNAME below), so Vivu keeps each name as it is. Vivu rewrites spaces and punctuation in file names; a percent sign came back as a different character in our test run.
- Measure each file and write duration_s into clips.csv:
ffprobe -v error -show_entries format=duration -of csv=p=0 reuse-WORK_NAME/upload/CLIPNAME.mp4
- Call vivu_get_usage and show one table:
| This check | Plan allowance | Remaining this month | |
|---|---|---|---|
| Index minutes | sum of duration_s / 60 | from vivu_get_usage | from vivu_get_usage |
| Search credits | two precise searches, 10 credits, plus 5 for each rewording (estimate) | from vivu_get_usage | from vivu_get_usage |
Plan facts: Free is $0 a month with 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. A precise search uses 5 credits in total. For example, 40 clips of 30 seconds are 20 index minutes (estimate), which fills a Free month.
- If the job does not fit, offer levers in this order: index only the clips this campaign will run first, split the check across two months, and only then move to a larger plan. Never drop a clip without naming it.
In our test run the 12 clips measured 3.71 minutes. The account used in our test run returns no allowance figures, so the plan table and the user's approval were not exercised in our test run.
Done when every chosen clip is in upload/ with a duration_s in clips.csv and the user has approved the minutes and credits.
Step 3: Upload and index
Goal: every chosen clip indexed in a private Vivu project.
- Call vivu_list_projects and reuse a project named "Reuse check WORK_NAME" if one exists. Otherwise call vivu_create_project with that name and visibility "private". The default visibility is organization, which shows the project to everyone in the user's Vivu organization; unreleased prices and campaign cuts usually should not be.
- Call vivu_open_upload_page with the project ID immediately before uploading. The link expires in 180 seconds and works once, so never post or store it. Give it to the user to open in their own browser and select every file in upload/, or open it in a browser tool that can attach local files. Claude in Chrome accepts at most 10 MB per upload call; larger files go through the user's own browser or the Vivu web app. Never split or recompress a clip to fit.
- Poll vivu_list_videos about every 30 seconds until every clip shows ready. Match each listed file_name to clips.csv and record the video_id; when a name does not match, match by length (duration_ms against duration_s).
In our test run all 12 clips were ready about 4 minutes after the upload started. The files went through the upload page with an automated browser, so opening the link in the user's own browser was not exercised in our test run.
Done when every row of clips.csv has a video_id and every clip shows ready.
Step 4: Search
Goal: saved candidate windows for offers shown on screen and offers said out loud.
Two precise searches cover the whole batch. fast is not used: it returns whole files with an empty reason, and the file list is already known. The chain: the on-screen search finds windows where price or deal text is burned into the picture, Step 5 reads the words from frames, the spoken search flips to what the voiceover says (offers that are never written on screen), and Step 6 copies the exact words from the transcript.
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| on_screen_offer | text burned into the video picture that states a price or a deal: a dollar price such as $9.99, a percent off, a promo or discount code, or the dates a sale runs; a product name, a slogan without a number, or a quantity like "2 pizzas" does not count | precise | 40 |
| spoken_offer | a voice in the video says a price, a discount or a deal out loud, such as how much something costs, a percent off, a promo code or until when a sale runs | precise | 40 |
The on-screen query names the kind of text, never the exact words. On other footage, a query that asked for one exact caption made Vivu claim that caption on clips where it was only spoken or where other text was on screen. maximum_results 40 suits a batch of up to about 40 clips, since most clips carry one or two offer moments; it is also the recall ceiling, so raise it for a larger batch (the limit is 100).
Run vivu_search_videos (project_id, query, mode "precise", maximum_results) for each. It returns a job ID; call vivu_get_search_results until complete is true. Each status call can wait up to 45 seconds, so a pending search is not a stalled one. Save each result to results/FIELD.json without the result page link, and show the result page link only in the live reply: it expires after four hours, so it never goes into the sheet or candidates.csv. Both searches used the wording above unchanged (0 rewordings).
Done when both result files are saved and their job ids are in state.json.
Step 5: Read the on-screen offers from frames
Goal: each on-screen window labeled kept or rejected, with the offer text copied from a full frame.
- Make a contact sheet across the window, one frame every half second. Windows are wider than the text: a price graphic on screen for two seconds often sits inside a window that also holds the product shots and the end logo.
ffmpeg -v error -y -ss START -to END -i reuse-WORK_NAME/upload/CLIPNAME.mp4 -vf "fps=2,scale=320:-1,tile=8x4" -frames:v 1 reuse-WORK_NAME/frames/CLIPNAME_START_sheet.png
START and END are the window in seconds (start_ms and end_ms divided by 1000). Tile k is at START plus k/2 seconds, counting from 0 at the top left; one sheet covers 16 seconds, so a longer window needs a second sheet from START plus 16. 2. Pick the tile where the offer text is fully shown and extract it as a full frame:
ffmpeg -v error -y -ss SECONDS -i reuse-WORK_NAME/upload/CLIPNAME.mp4 -frames:v 1 -q:v 3 reuse-WORK_NAME/frames/CLIPNAME_SECONDS.png
- Label the window. Kept: a price, a percent off, a code, a free shipping or gift threshold with a number, or sale dates, readable in the frame. Rejected: a product name, a slogan or lockup without a number ("best deal ever"), a quantity ("2 medium pizzas"), or no text at all. Count rejected windows as false positives.
- Copy the words exactly as written, including the small print line when it states offer terms ("offer only applies to ..."). Text too small to read at the clip's resolution is marked UNCERTAIN; never complete it from the reason text.
- Write every window into candidates.csv with its verdict and a one line reason.
Worked example from our test run
The test corpus was 12 official brand ads from public channels (3.71 minutes), standing in for a store team's own promo cuts: short spots with burned-in prices, a sale date badge, brand spots with only product names on screen, and a talking head video that announces a percent off offer with its dates and shows no text. Before searching we went through every clip on a per second contact sheet and in the captions and wrote down where each offer is shown and where it is said. A clip we had listed as showing its price without saying it does say the price in the voiceover; we found the caption line after the search and counted it as spoken for both searches, which changed no verdict on a returned window. Most clips came from one brand's channel, so the graphics share a style.
The on-screen search returned 7 windows: 7 real, 0 false, and 0 missed. The spoken search returned 8 windows: 8 real, 0 false, and 0 missed. Neither clip where the offer is only spoken was returned by the on-screen search, and the clip where the sale dates are only on screen was not returned by the spoken search, so in this run the two searches did not leak into each other. Brand spots with product names on screen, and "best deal ever" lockups without a number, were not returned. The median on-screen window was 8 seconds wide while the price graphic in it was on screen for only part of that, and several windows covered nearly the whole clip, so the half second contact sheet is what places the offer. These ads are clean, large broadcast graphics; small badges on a phone video, busy backgrounds and a creator's handwritten text are untested, and on other footage Vivu's reason text has claimed captions that were only spoken. Treat every window as a candidate and read the frame.
Done when every on-screen window has a verdict in candidates.csv and each kept window has a frame and its text.
Step 6: Check the spoken offers in the transcript
Goal: each spoken window labeled kept or rejected, with the words copied from a transcript.
- Make a transcript for each clip with a voiceover, or use the caption file the team already has. With whisper.cpp, for example (MODEL is the path of a downloaded model file):
ffmpeg -v error -y -i reuse-WORK_NAME/upload/CLIPNAME.mp4 -ar 16000 -ac 1 reuse-WORK_NAME/transcripts/CLIPNAME.wav
whisper-cli -m MODEL -f reuse-WORK_NAME/transcripts/CLIPNAME.wav -osrt -of reuse-WORK_NAME/transcripts/CLIPNAME
- Read the transcript lines between start_ms and end_ms. Kept: someone in the clip says a price, a discount, a code or sale dates. Rejected: the window has no such words. A guess or a comparison price said aloud ("$30") is still a spoken price; keep it and say what it is.
- Copy the sentence into spoken_words. Speech to text can mishear numbers ("1999" for "$19.99"); when the frame shows the same offer, keep both and let the user compare. Mark a quote NOT VERIFIED when there is no transcript.
- For a second signal, vivu_get_video_summary with include_segments, start_ms and end_ms around the window gives a short summary of that part of the clip. It paraphrases, so it never fills a cell.
In our test run every spoken window held the offer sentence in the platform captions; the captions wrote one price without its dollar sign and point, and one window was 27 seconds wide because it held two sentences about the same percent off offer. Our test run used the captions YouTube made for these ads; a local speech to text tool was not exercised in our test run.
Done when every spoken window has a verdict in candidates.csv and each kept window has the words from the transcript.
Step 7: Sweep the clips the searches did not return
Goal: every clip checked, including the ones neither search returned.
An empty result does not prove a clip is clean, so check each clip with no kept window on a contact sheet of the whole clip, one tile per second. This is local and costs no credits:
ffmpeg -v error -y -i reuse-WORK_NAME/upload/CLIPNAME.mp4 -vf "fps=1,scale=320:-1,tile=8x8" -frames:v 1 reuse-WORK_NAME/frames/CLIPNAME_sweep.png
Tile k is at k seconds, and one sheet covers 64 seconds. Look for price or deal text, then grep its transcript for numbers and offer words:
grep -n -i -E "[$][0-9]|percent|% off|code|sale|deal|free shipping|until|ends" reuse-WORK_NAME/transcripts/CLIPNAME.srt
Write "no offer found" only after both checks. Mark rows found here as found_by "contact sheet", so the user knows the search did not find them.
In our test run the sweep sheets of the clips that neither search returned showed product names only, and their captions held no price, so they went into the sheet as no offer found.
Done when every clip in clips.csv has at least one row: an offer with a frame or transcript line, or "no offer found".
Step 8: Write the sheet and get approval
Goal: a reuse check sheet the team can act on, approved before it goes anywhere.
- Show the user one sample row and the field mapping, and wait for a yes before writing the rest:
file,mm_ss,kind,on_screen_text,spoken_words,frame,transcript_checked,found_by,decision
PROMO_FALL_15s.mp4,00:05,both,"MIX & MATCH SPECIALTY PIZZAS $9.99 EACH","you could mix and match medium specialty pizzas for $9.99 each.",frames/PROMO_FALL_15s_6.png,yes,search,
| Field | Source | If unavailable |
|---|---|---|
| file | clips.csv | none |
| mm_ss | the kept frame or transcript line, as MM:SS in the clip | blank for "no offer found" |
| kind | on_screen, spoken, both, or none | none |
| on_screen_text | copied from the full frame | UNCERTAIN for illegible text |
| spoken_words | copied from the transcript | NOT VERIFIED without a transcript |
| frame | path of the frame the text was read from | blank |
| transcript_checked | yes when the words were read in a transcript | no |
| found_by | search, or contact sheet (Step 7) | none |
| decision | left empty for the user: use as is, cut this part, do not use | stays empty |
- Write reuse_check.csv with one row per offer moment and one row per clean clip. Tell the user the counts: clips with an offer on screen, clips with an offer only in speech, clean clips, windows rejected, and rows found only by the sweep.
- When the user marks a row "cut this part", offer a trimmed copy that ends before the offer appears (or starts after it), re-encoded so the cut is exact, and check its last frame:
ffmpeg -v error -y -ss 0 -to CUT_AT -i reuse-WORK_NAME/upload/CLIPNAME.mp4 -c:v libx264 -c:a aac reuse-WORK_NAME/CLIPNAME_trimmed.mp4
ffmpeg -v error -y -sseof -0.2 -i reuse-WORK_NAME/CLIPNAME_trimmed.mp4 -frames:v 1 -q:v 3 reuse-WORK_NAME/frames/CLIPNAME_trimmed_last.png
CUT_AT is the second where the offer starts. Look at the last frame before saying the copy is clean: in our test run a copy cut before the price graphic still carried the offer's small print line at the bottom of the frame, because the small print ran before and after the price. The trimmed copy is a new file; the original is never changed. 4. Before putting the sheet anywhere outside the working folder (a shared drive, a Google Sheet, a Notion page, a Slack channel), name the destination, show the full rendered sheet or message, and wait for explicit approval. Email goes to drafts unless the user asks to send.
In our test run the sheet has a row for every clip, the trimmed copy was made and checked, and a short team note was rendered. The user's approval of the sample row and posting anything were not exercised in our test run, and nothing was sent.
Done when reuse_check.csv has at least one row for every clip in clips.csv, and the user has approved the sample row and any destination.
Compliance
- Use only clips the team shot itself or footage whose license covers reuse in the new campaign (agency, supplier and creator footage often has a term or channel limit). Confirm this with the user in Step 1.
- People on camera in the team's own ads have usually signed releases for the original campaign; whether those releases cover the reuse is the user's call. The skill reads text and speech only and does not identify anyone by face or voice.
- Ask in Step 1 whether any chosen clip shows children. Upload such a clip only when the user confirms the guardian consent or release on file covers storing it in Vivu and reusing it; otherwise leave it out of upload/, name it to the user, and give it a "do not use" row in the sheet. The skill reads text and speech only and does not identify anyone.
- The sheet lists where offers appear. It does not judge whether a price, a discount claim or an ad is legal or still valid; the user, or their legal or pricing owner, decides.
- The clips stay in the user's Vivu project until the user deletes them. Create the project as private. Delete a project or video only when the user asks, and confirm first.
- Putting the sheet in a shared place or posting a note acts as the user; Step 8 asks first.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) an on-screen window covers nearly the whole clip while the price is shown for a moment | Vivu returns a time range that contains the moment, not an exact frame | read the half second contact sheet and record the tile where the text is shown |
| (observed) a trimmed copy cut before the price still shows "OFFER ONLY APPLIES TO MEDIUM PIZZAS" | the offer's small print runs longer than the price graphic | check the last frame of every trimmed copy; move the cut or mark the clip as not to be used |
| (observed) the transcript writes a price as "1999" | speech to text drops the dollar sign and the point | compare with the frame when the offer is also on screen; keep both |
| (observed) one spoken window spans two sentences and 27 seconds | the clip repeats the same offer, and Vivu returns one window around both | read every transcript line in the window and write one row per sentence |
| (observed) a file named with a percent sign is listed under a changed name | Vivu rewrites spaces and punctuation in file names | name files with letters, digits and hyphens before upload, or match by length |
| the reason text claims a caption that is only spoken | on other footage the reason text has put spoken words on screen and invented caption text | copy on-screen text only from a full frame |
| an empty result for a clip that does show a price | recall is limited by wording and by maximum_results, and small text was untested | sweep every clip without a kept window (Step 7) |
| small badges or text over a busy background are missed or misread | small or low contrast text was not tested; on other footage small text was read wrong | read the frame at full size; mark UNCERTAIN when it is not legible |
| upload page asks to sign in or shows an error | the one time upload link expires after 180 seconds | request a new link right before opening it |
| a file is rejected by the browser upload tool | Claude in Chrome accepts at most 10 MB per upload call | the user opens the upload link in their own browser or adds the file in the Vivu web app |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu with write access |