---
name: shot-list-reuse-coverage
description: "Check a product line's past shoot footage against your next shot list with Vivu: find each shot, confirm it on frames, note colour and packaging, and write a coverage table before you book."
---Pre-shoot reuse check against your shot list
This skill takes the shot list for a product line's next shoot and the folder of that line's past footage (raw takes and published product videos), and tells the producer which shots already exist. Claude measures and prices the footage against the user's Vivu plan, indexes it in a private Vivu project, runs one search per shot list row, looks at frames inside every returned window, notes the product colour and packaging it sees, and writes coverage.csv: one row per found clip with the shot, a status (have, candidate, not found), the source file, start and end as MM:SS, a frame, the colour and packaging seen, and an empty "reuse or reshoot" column for the producer.
The value is in what the files do not say. Camera files are named C0012.MP4 and the DAM tags stop at the SKU; whether the bottle is being carried by its handle, sitting on a kitchen counter or being drunk from, which colour it is, and whether it is in the old packaging only shows in the picture. Vivu finds the moments by what happens in them. It is also easily fooled by look-alike products and by colour words, so every row on the sheet has been checked on frames by Claude, and colour and packaging come from the frames, never from Vivu's description.
When to use
Use when a producer or creative lead says "before we book the shoot, check what we already have for this shot list", "which of these shots do we have in old footage", "pull reusable takes for the next product shoot", or "make a coverage sheet for the shot list". It works on one product line's folder at a time; it does not replace or sync the DAM.
Not for these: a one off question about one clip (search Vivu directly), choosing which take is best (the skill does not judge shot quality), cutting or versioning the clips (Vivu only finds them; editing happens in the user's editor), or recognizing people, faces or logos.
Working principles
- Report measured numbers, not estimates. When a number is an estimate (index minutes before measuring, credits), say so.
- Nothing goes on the sheet as "have" until Claude has looked at frames from the window and seen the product doing the shot. Everything uncertain stays "candidate".
- Colour and packaging version come from the frames. Never put a colour in the search query, and never copy a colour or a version from Vivu's reason text.
- Stop and tell the user when something required is missing (no ffmpeg, no write access in Vivu, no shot list). Do not guess around it.
- Ask before anything expensive to redo or done on the user's behalf: the indexing cost before upload, and one sample row before the full sheet.
- "Not found" means the search returned nothing usable. It does not prove the footage has no such shot; say that on the sheet.
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing.
| 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 connection is read only; the user reconnects Vivu and allows write access |
| A shell with ffmpeg and ffprobe on the machine that holds the footage | measure minutes, extract frames and contact sheets | ffmpeg -version and ffprobe -version |
| The product line's footage on local disk in one folder | frames come from the local files; Vivu has no export tool | ls FOOTAGE_FOLDER shows the video files |
| The shot list for the next shoot | one search per row | the user pastes it or points to the file |
| A browser that can open the Vivu upload page | the connector has no direct upload tool | the user opens the link, or Claude in Chrome is connected |
Nothing is downloaded from YouTube, so no residential IP is needed. Nothing is scheduled and nothing is posted: the output is a CSV in the working folder. It runs in Claude Code on the user's computer (the terminal or the Code tab of Claude Desktop), because it needs a shell and the local files.
Inputs to collect
Ask for anything missing, most important first.
- The shot list: one line per shot, in the producer's words (for example "bottle carried by the handle", "someone drinking", "bottle on the kitchen counter", "outdoor use", "flat lay with the box"). Required.
- The footage folder for this one product line. Required. If the DAM holds several product lines, download only this line's folder.
- A one line description of the product's look, without colour (for example "a straight-sided bottle with a stiff round ring handle on the lid"). Default: Claude drafts it from the first frames and asks the user to confirm.
- Colours and packaging versions that matter (for example "the old box has a white band"). Default: record whatever colour and packaging is visible.
- Whether the sheet should flag shots with people in them. Default: yes, a people_in_shot column, so the producer can check model release terms before reuse.
- The Vivu project name. Default: "PRODUCT_LINE past footage", private.
Files and state
Keep everything in one working folder next to the footage:
reuse-check/
config.json product line, product look line, shot list, footage folder, vivu project id
files.csv one row per footage file: file name, duration seconds, vivu video id
results/ raw search results, one JSON file per shot row
frames/ contact strips and picked frames
coverage.csv the sheet
state.json uploaded file names and video ids, shot rows searched with job ids, windows checked with verdicts, rows written
state.json is updated after every step. A rerun reads it first: files already uploaded are not uploaded again, shot rows already searched are not searched again, and windows already checked keep their verdicts, so an interrupted run picks up where it stopped and no clip is written twice.
Step 1: Check the Vivu connector and the setup
Goal: every row of What you need is confirmed before anything is measured or uploaded.
- Call vivu_get_account. If the tool is missing, tell the user to add the Vivu connector in Claude (https://mcp.vivu.ai/mcp) and stop. If can_create_projects is not true, or a write call later fails with "has not granted vivu.write", ask the user to reconnect Vivu with write access and stop until they have.
- Run ffmpeg -version and ffprobe -version. List the footage folder.
- Ask for the shot list if it is missing, and write config.json.
Done when vivu_get_account shows can_create_projects: true, ffmpeg and ffprobe print versions, and config.json holds the shot list and the footage folder.
Step 2: Turn the shot list into searches
Goal: one search per shot row, written so that a look-alike product is less likely to match.
- Write each shot row as what happens in the picture plus the product's look line, with no colour and no brand name. Examples:
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| loop_carry | someone lifts or carries the water bottle by the round loop handle on its lid, or keys or a phone hang from the loop | precise | 30 |
| drinking | a person drinks from a straight-sided bottle in one flat matte colour whose lid carries a stiff round ring handle about as wide as the bottle itself, not a thin fabric strap | precise | 30 |
| kitchen_counter | the water bottle standing on a kitchen counter, with kitchen cabinets, tiles or a stove behind it | precise | 30 |
| outdoor_use | someone using the slim water bottle outdoors, on a walk, in a park or sitting on grass | precise | 30 |
| packaging_flatlay | a flat lay seen from above of the water bottle next to its box or packaging | precise | 30 |
Every row is a picture search. Product footage is mostly music or a voiceover that does not describe the shot, so there is no spoken or on screen text version of these rows to search instead. The chain is the same for each row: the precise search narrows the folder to windows, the frames in Step 6 decide whether the product is really doing the shot, and only checked clips reach the sheet. 2. Keep colour out of every query. A colour word makes Vivu report other colours of the same product as the colour asked for (see the worked example). Colour is read from frames in Step 6. 3. Show the user the list of searches and the credit estimate for them (Step 3) before running anything.
Why precise and why 30: the sheet needs start and end times, and only precise returns a time range; fast returns whole files. In a folder of 30 to 50 clips the same shot can appear a dozen times, and maximum_results is also the ceiling on what comes back, so 30 leaves room. If a row returns exactly 30, raise maximum_results and run that row again.
In our test run the drinking wording in the table is the one we settled on after 2 rewordings on the same corpus; the other four rows ran as first written.
Done when config.json holds one query per shot row and the user has seen them.
Step 3: Price the indexing and get approval
Goal: the user sees what indexing and searching will use before anything is uploaded.
- Measure every file: ffprobe -v error -show_entries format=duration -of csv=p=0 FILE, write the seconds to files.csv, and sum the minutes.
- Call vivu_get_usage for the plan and what remains this month.
- Estimate search credits: one precise search per shot row. A precise search uses 5 credits in total, so a six row shot list is an estimate of 30 credits, plus 5 for each row the user asks to reword and rerun.
- Show one table and wait for approval:
| This product line | Remaining on the plan | |
|---|---|---|
| Index minutes | measured sum | from vivu_get_usage |
| Search credits | estimate | from vivu_get_usage |
For reference, the Free plan has 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 folder of 40 one minute clips is an estimate of 40 index minutes, which is more than the Free plan holds. If the folder does not fit, offer these levers in order: drop the clips the user knows are unrelated (other products, behind the scenes), index only the most recent shoots for this line, and only then a larger plan. Never drop material the user asked for without saying so.
In our test run the account was an admin account whose vivu_get_usage shows no remaining allowance, so the plan comparison and the approval were not exercised in our test run.
Done when the user has approved the index minutes and the credit estimate.
Step 4: Upload and index
Goal: every approved file is ready in a private Vivu project.
- Call vivu_list_projects and reuse the project named in config.json if it exists. Otherwise call vivu_create_project with that name and visibility "private". Unreleased product footage belongs in a private project; the default visibility is the whole organization.
- Call vivu_open_upload_page with the project ID right before the upload. The link expires in 180 seconds and is a sign in link: do not paste it into any message or file. Give it to the user to open in their own browser and choose the files, or attach the files with a browser tool that can upload local files. Claude in Chrome accepts at most 10 MB per upload call; camera files are usually larger, and the user adds those in the Vivu web app. Never split or recompress a file to fit a tool limit.
- Poll vivu_list_videos about every 30 seconds until every file shows ready. Match each video back to files.csv by file name (Vivu turns spaces and punctuation into underscores) and, when two names collide, by duration_ms against the ffprobe seconds. Record the video IDs in state.json.
This step was not exercised in our test run: the test footage was already indexed from an earlier test, so no upload or indexing time was measured.
Done when vivu_list_videos shows every file in files.csv as ready.
Step 5: Search each shot row
Goal: one saved result per shot row.
Run each query with vivu_search_videos (project_id, query, mode "precise", maximum_results 30). 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 completed result as results/FIELD.json. Show the result page link in the live reply only; it expires after four hours, so it never goes into coverage.csv or any file. The sheet points at the user's own files and times.
Done when every shot row has a saved result file and a job ID in state.json.
Step 6: Check every window on frames
Goal: every returned window is judged have, candidate or rejected, with the colour and packaging read from the picture.
- For each result, make a contact strip of the window at two frames a second:
ffmpeg -v error -ss START -to END -i FOOTAGE_FOLDER/FILE -vf "fps=2,scale=180:-2,tile=8x2" -frames:v 1 frames/FIELD_hN_strip.png
START and END are start_ms / 1000 and end_ms / 1000; N is the result number; widen the tile (10x2, 12x2) for windows longer than eight seconds so every frame fits. Keep the quotes around the filter; some shells treat the comma list as a pattern otherwise. 2. Look at the strip. The window is "have" only if the frames show this product line (check the look line from config.json: shape, handle, lid) doing the shot. Vivu's window is wider than the shot and often starts on a neighbouring close-up, so write down the seconds where the shot actually is. 3. Reject windows that show a different product doing the shot. This is the most common false hit: a look-alike bottle from another brand being carried or drunk from. The reason text describes it as the product in the query; the frames settle it. 4. Mark "candidate" when the product or the action cannot be told apart at strip size, and extract a full size frame to decide. SECONDS is the second in the source file where the unclear frame sits on the strip (START plus the frame's position divided by 2):
ffmpeg -v error -ss SECONDS -i FOOTAGE_FOLDER/FILE -frames:v 1 -q:v 3 frames/FIELD_hN_pick.png
- Read the colour and the packaging version from the frames and write them down. If two versions of the packaging are close, compare with the reference the user gave in Inputs; if the frame does not show the packaging, write "none in frame", not a guess.
- Note whether people are in the shot (hands only, model on camera). The skill does not identify anyone; the column is there so the producer checks release terms before reuse.
- Record every verdict in state.json. Rejected windows stay in results/ and are counted, not written to the sheet.
Worked example from our test run
In our test run we used 12 public short videos (5.37 minutes) published by brands: 8 of one slim bottle product line in several colours, some of them cutdowns of the same creative, and 4 other brand bottles and tumblers as look-alikes. We wrote down where each shot is before searching, from per second contact sheets. The test footage was already indexed, so upload and indexing time were not measured.
The loop_carry row returned 12 windows: 10 real and 2 false, 0 missed. Both false ones were another brand's slim bottle carried by a fabric strap.
The drinking row's first wording returned 7 windows, 5 real and 2 false (the other brand's bottle being drunk from outdoors). After 2 rewordings on the same corpus, describing the stiff ring handle and ruling out a fabric strap, it returned 6 windows: 5 real, 1 false, 0 missed.
The kitchen_counter row returned 5 windows: 4 real and 1 false (another brand's tumblers on an office table), 0 missed. One of the 4 real was a close-up of the bottle standing in the kitchen set with the counter edge out of frame, which was not on our list written before searching; we counted it as real after looking at the frames, and with the original list the row would have had 3 real and 2 false.
The outdoor_use row returned 2 windows, both false: the product line is never outdoors in this footage, and both windows were other brands' bottles outdoors. The row went on the sheet as "not found". The packaging_flatlay row returned 0 windows; the footage has no packaging, so old versus new packaging was not tested.
The colour test ran the loop_carry query with "slate blue" added. It returned 6 windows: 2 real and 4 false, a 67% false share. The 4 false windows showed teal bottles and another brand's bottles, and every reason called them slate blue. This is why colour stays out of the queries and is read from frames.
Done when every window of every shot row has a verdict in state.json and the user has seen the real and rejected counts per row.
Step 7: Write coverage.csv and confirm the sample
Goal: the producer gets one sheet in shot list order, with every shot row present.
- Build one row per checked clip, and one "not found" row for each shot row with no checked clip:
shot_id,shot,status,source_file,start_mmss,end_mmss,frame,colour_seen,packaging_seen,people_in_shot,note,reuse_or_reshoot
S1,bottle lifted or carried by the lid loop,have,C0012.MP4,00:04,00:09,frames/loop_carry_h0_strip.png,slate blue,none in frame,hands only,keys and phone hanging from the loop,
S4,bottle used outdoors,not found,,,,,,,,2 windows were other brands (rejected); not proof the footage has none,
- Field mapping:
| Field | Source | If unavailable |
|---|---|---|
| shot_id, shot | the user's shot list | none |
| status | have (checked on frames), candidate (not settled on frames), not found (no checked clip) | candidate |
| source_file | the user's file name from files.csv | none |
| start_mmss, end_mmss | the seconds where Claude saw the shot on the strip, as MM:SS in the source file | the Vivu window, labeled UNCERTAIN in note |
| frame | path of the checked strip or picked frame | none |
| colour_seen, packaging_seen | read by Claude from the frames | "none in frame" or UNCERTAIN |
| people_in_shot | seen on the frames (hands only, model on camera, none) | UNCERTAIN |
| note | what else is in the window (captions, a cutdown of another file) | blank |
| reuse_or_reshoot | left empty for the producer | blank |
- Show the user one sample "have" row and one "not found" row with the mapping, and wait for a yes before writing the full sheet. Say which fields are Claude's reading of a frame (colour_seen, packaging_seen, people_in_shot) and what changes if the reading is wrong: a clip in the wrong colour or old packaging stays on the list, and the producer finds out only when they open it.
- Write coverage.csv. Under it, write the counts per shot row: clips marked have, windows rejected, candidates. For every "not found" row, repeat that the search returned nothing usable, which does not prove the footage has no such shot. Where cutdowns of the same creative both show up, say so in note, so the producer does not count one shot twice.
In our test run coverage.csv was built from the checked windows, including a "not found" row for each empty shot row; the user approval of the sample row was not exercised in our test run.
Done when coverage.csv exists with a row for every shot list line and the user has approved the sample rows.
Compliance
- Use only footage the brand owns or has licensed for reuse. Confirm this before Step 4.
- People on camera: reusing a shot with a model depends on the model release (term, media, territory). The sheet flags people_in_shot; the producer checks the release before a clip moves to "reuse". Ask the user before Step 7 whether any talent asked not to be reused, and leave their shots out.
- Minors: if any footage shows minors, reuse needs guardian consent covering the new use; otherwise leave those rows out.
- The skill does no face, logo or identity recognition, and does not judge which take looks best.
- The footage stays in the user's Vivu project until the user deletes it. The skill never calls vivu_delete_video or vivu_delete_project unless the user asks, and confirms first.
- Nothing is posted or sent. coverage.csv stays in the working folder.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a different brand's bottle comes back as the shot (carried by a loop, being drunk from); in our test run 2 false of 12 for loop_carry | same category products do the same actions, and the reason describes them as the product in the query | check the product's shape, handle and lid on the strip and reject other products; describe the product's look in the query without colour or brand |
| (observed) a colour in the query returns other colours; in our test run 4 false of 6 | the reason calls every returned bottle the colour asked for, for example "A person holds and carries the slate blue water bottle by its round loop handle alongside other items." | keep colour out of the query and read colour from the frames |
| (observed) the first drinking wording returned 2 false of 7 | look-alike bottles being drunk from outdoors | describe the handle and rule out a fabric strap; check every window on frames anyway |
| (observed) a shot the product line never has returns other products; in our test run 2 false for outdoor_use | Vivu returns the closest scene it has | reject on frames and write "not found" for that row |
| (observed) a table in an office with a kitchen behind comes back as a kitchen counter; in our test run 1 false of 5 | the reason says "Water bottles/tumblers are standing on a kitchen counter with kitchen cabinets and kitchen fixtures visible in the background." | check the frames for the product and the counter |
| (observed) the window starts on a close-up before the shot | the window is wider than the shot | write the seconds seen on the strip, not the window's start and end |
| a packaging or colour version is mislabeled on the sheet | it was copied from the reason text | read colour and packaging from the frames; write UNCERTAIN when the frame does not show it |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu with write access |
| 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 takes at most 10 MB per upload call | the user adds that file in the Vivu web app; never split or recompress it |
| a result's file name does not match files.csv | Vivu replaced spaces or punctuation in the name | match on the rest of the name and on duration_ms against the ffprobe seconds |
| a row returns exactly maximum_results | the cap cut the list short | raise maximum_results and rerun that row |
| the same shot is counted twice | a cutdown or square version of the same creative is in the folder | note cutdowns in the note column and count them as one shot |