---
name: in-store-campaign-signage-sheet
description: "Check which campaign signs each store put up, using the managers' walk-through videos and Vivu: find the promo signs, read them from frames, compare with the kit list in a CSV."
---In-store campaign signage sheet
This skill takes the walk-through videos that store managers send back after a promotion kit goes out (one phone video per store, a minute or two each, with the store number in the file name) and the kit list for this campaign (the hanging banners, posters, end display headers and prices head office sent), and returns a store execution sheet: one row per store and kit item, with the minute and second the sign is on screen, the words and price read from a full frame, whether that matches the kit list, a saved frame, and an empty follow up column for the manager. Items Claude could not find in a store go on a "not seen, look by hand" list. Claude measures the videos, prices the indexing against the user's Vivu plan, indexes them in a private Vivu project, runs one precise search for large promotion signs and one for floor and end of aisle displays, checks every returned window on a sheet of frames taken every half second, reads the sign from the sharpest frame, scans every store that has no confirmed sign, and writes signage_sheet.csv.
The value is in what only the picture shows. The store app records one tick ("done"); whether the banner is hanging, whether it is this campaign's banner and whether the price on it is right exists only in the footage, and nobody reads signs aloud while filming. A store has hundreds of shelf price tickets, so a search that confuses them with campaign signs, or a reason text that invents a price, would send the manager after the wrong stores. Vivu narrows each video to a few seconds; the frames, not Vivu, decide what the sign says.
When to use
Use when a regional operations, visual merchandising or field marketing manager says "check which stores put up the new campaign from their walk-through videos", "did every store hang the half price banner", "read the prices on the end displays in these store videos", or "which stores have no campaign signage in their video". For one store, open the video and look, or ask Vivu directly. This skill does not audit shelf price tickets, planograms or stock levels, does not score store managers or staff, and does not message stores; the manager decides the follow up from the sheet.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Vivu's results are candidates. A row is filled only after Claude has looked at the frames of the window; the sign text and price come from a full frame, never from Vivu's reason text, which can describe signs and prices that are not there.
- Stop and tell the user when a required capability or file is missing (no ffmpeg, no write access in Vivu, no store number in a file name, no kit list). Do not guess around it.
- Ask the user before anything that is expensive to redo or that acts on their behalf: the indexing cost before upload, and the sample rows before the full sheet.
- Record signs, not people. Shoppers and staff in a frame never go into the sheet, and the sheet has no column that rates a store manager.
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 videos | measure minutes, extract frames to read the signs | ffmpeg -version and ffprobe -version |
| The walk-through videos as local files, with the store number in each file name | frames come from the local files; Vivu has no export tool. The store column comes from the file name | ls STORE_FOLDER; list any file with no store number and ask the user which store it is |
| The kit list for this campaign | the sheet compares what is on screen with what head office sent | a CSV or a pasted list with one line per item: what it is, where it hangs, the headline words, the price if any |
| 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 |
The skill needs a shell and local files, so it runs in Claude Code on the user's computer (terminal or the Code tab of Claude Desktop). It downloads nothing from YouTube or anywhere else, so no residential IP is needed. Nothing is scheduled and nothing is written outside the working folder.
Inputs to collect
Ask for anything missing, most important first.
- The folder with this campaign's walk-through videos (STORE_FOLDER). Required.
- The kit list (KIT_LIST): item, placement, headline words, price. Required; without it the sheet can say what is up but not whether it is right.
- How the store number appears in the file names (for example STORE-0412 at the start). Default: Claude proposes a pattern from the file names and the user confirms.
- The Vivu project name. Default: "Campaign CAMPAIGN_NAME store walks", private.
Files and state
Keep everything in one working folder next to the videos:
signage-check/
kit_list.csv the campaign's items, one per line, as confirmed by the user
stores.csv file, store, minutes, video id
results/ raw search results, one JSON file per search
frames/ half second sheets, whole video scans, and the full frames each sign was read from
signage_sheet.csv one row per store and kit item seen, with an empty follow_up column
not_seen.txt store and kit item pairs with no confirmed sign, to look at by hand
state.json uploaded files and video ids, searches with job ids, every window with its verdict, rows written
state.json is updated after every step. A rerun reads it first: files already uploaded are not uploaded again, searches already run are not rerun, and windows already checked keep their verdicts, so an interrupted run resumes where it stopped. A store video already in the project from an earlier campaign is matched by video id and only its rows for this kit list are written, so the same sign is not written twice.
Step 1: Check the Vivu connector and the setup
Goal: every row of What you need is confirmed before anything is 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 STORE_FOLDER. Propose the store number pattern from the file names and list any file it does not match.
- Read the kit list back to the user as a table and ask them to confirm it.
- Create the working folders; ffmpeg does not create an output folder:
mkdir -p signage-check/results signage-check/frames
Run every later command from inside signage-check/ (cd signage-check), with STORE_FOLDER given as a full path.
Done when vivu_get_account shows can_create_projects: true, ffmpeg and ffprobe print versions, the user has confirmed the store pattern and the kit list, and the working folders exist.
Step 2: Price the indexing and get approval
Goal: the user sees the cost before anything is uploaded.
- Ask Compliance items 1 and 2 first.
- Measure each video and sum the minutes. ffprobe takes one input file per call, so run it once per file (VIDEO_FILE is a file in STORE_FOLDER):
ffprobe -v error -show_entries format=duration -of csv=p=0 STORE_FOLDER/VIDEO_FILE
- Call vivu_get_usage for the plan and what remains this month.
- Estimate search credits: two precise searches per campaign; a precise search uses 5 credits in total, so about 10 credits, plus 5 for each search rerun after raising maximum_results. Label the total as an estimate.
- Show one table and wait for approval:
| This campaign | 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. As an estimate, 18 stores at a minute and a half each is about 27 index minutes per campaign, which is more than Free and fits Premium about six times a month. If a campaign does not fit, offer these levers in order: index only the stores whose kit compliance is in doubt (for example stores that ticked "done" late), ask stores for a shorter walk-through limited to the campaign areas, split the stores across two months, and only then a larger plan. Never drop a store 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 comparison against a real plan and the approval were not exercised in our test run.
Done when the user has approved the minutes and the credit estimate.
Step 3: Upload and index
Goal: every store video is ready in a private Vivu project.
- Call vivu_list_projects and reuse the project named in the inputs if it exists. Otherwise call vivu_create_project with that name and visibility "private". Store videos show shoppers and staff; 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, so never paste it into a message or file. Give it to the user to open in their own browser and choose the files, or attach them with a browser tool that can upload local files. Claude in Chrome accepts at most 10 MB per upload call, and a phone walk-through is usually larger; the user adds those in the Vivu web app. Never split or recompress a video to fit.
- Poll vivu_list_videos about every 30 seconds until every file shows ready. Match each video back to stores.csv by file name; Vivu turns spaces and punctuation in names into "_", so compare names with those characters replaced. Record the video IDs in state.json and stores.csv.
In our test run the files went up through a script, not through a user's browser or Claude in Chrome; that upload path was not exercised in our test run. From the upload to all 12 videos ready took 8.8 minutes, most of it waiting in the queue.
Done when vivu_list_videos shows every file in stores.csv as ready.
Step 4: Find the campaign sign moments
Goal: a list of windows where a large promotion sign or a floor display is on screen, plus a list of stores that have none.
Two precise searches cover the whole campaign, because one search covers the whole project:
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| promo_signs | a large printed promotion sign in the store: a banner or sign hanging from the ceiling or above the shelves, a wall banner, a poster or standing board, or the header card on a freestanding or end of aisle display, showing a campaign name or a price in large print; small price tickets along the shelf edge, department name signs and the store logo do not count | precise | 40 |
| floor_displays | a freestanding display, pallet stack or end of aisle display of one product set out in the walkway or at the end of an aisle | precise | 30 |
- Run promo_signs with vivu_search_videos (project_id, query, mode "precise", maximum_results 40). 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 it as results/promo_signs.json. Show the result page link in the live reply only; it expires after four hours, so it never goes into the sheet or any file.
- Run floor_displays the same way with maximum_results 30 and save it as results/floor_displays.json. Finding a display by how it looks is a weak capability in general (it can miss, and an empty result proves nothing), so its windows are candidates for the kit items that are displays, checked in Step 5 like the sign windows.
- maximum_results is also the ceiling on what comes back. A store walk usually passes several campaign signs, so set promo_signs to about two per store (40 for 18 stores, up to 100); when a list stops at exactly that number, raise it and rerun.
- Describe signs by kind, size and place, never by the kit list's words. A query that names the exact headline ("a banner that says BACK TO SCHOOL") invites the reason text to repeat those words for whatever sign is in the window, so the kit list is compared in Step 5, from the frames.
- Write each window into state.json as a candidate and list the stores with no window. Those go to Step 6.
Why this chain: the sign search turns a minute and a half of walking into windows of a few seconds, the half second sheets in Step 5 find the sign inside each window and give a frame sharp enough to read, and the scan in Step 6 covers the stores the search returned nothing for, so "not seen" means somebody looked. The walk-through videos usually carry no useful speech (managers film in silence or over store music), so no speech search is run.
Done when results/promo_signs.json and results/floor_displays.json exist and every window is in state.json.
Step 5: Check each window and read the sign
Goal: every window is confirmed or rejected against frames, and every confirmed sign has its words and price read from a full frame and compared with the kit list.
Vivu returns a time range that contains the sign, not the exact seconds, and a handheld walk passes a sign in a second or two. Reading words from signs in real store footage is a weak capability: the reason text can name a sign or a price the frames do not show. So frames decide, never the search alone.
- Make a sheet of one frame every half second across the window (START is start_ms / 1000, DURATION is (end_ms - start_ms) / 1000; STORE is the store number, HIT is h plus the window's number; the 8x6 grid holds 24 seconds, so for a longer window raise the second number):
ffmpeg -v error -ss START -t DURATION -i STORE_FOLDER/VIDEO_FILE -vf "fps=2,scale=320:-2,tile=8x6" -frames:v 1 frames/STORE_HIT_sheet.png
Keep the quotes around the filter. Look at the sheet. Reject the window if no cell shows a large campaign sign or display: shelf edge price tickets (even large yellow ones), price cards in a fresh food counter, department signs and the store logo do not count. 2. On the sheet, pick the cell where the sign is largest and least blurred. Extract that moment at full resolution (SECONDS is the window start plus half the cell's index):
ffmpeg -v error -ss SECONDS -i STORE_FOLDER/VIDEO_FILE -frames:v 1 -q:v 3 frames/STORE_HIT_read.png
- Read the headline words and the price from that frame, exactly as printed. Write a field only when every character is readable. When motion blur, distance or an angle hides a character, try the neighboring cells once; if it is still not certain, write it as partly legible with ? for each unread character, or NOT LEGIBLE. Never fill a price from the kit list or from the reason text.
- Match the sign to a kit list item by placement and headline, and set matches_checklist: yes, no (say what differs: other campaign, other price), or NOT VERIFIED when the words are not legible. A campaign sign that matches no item is still written, as "not on kit list", because an old campaign left up is worth a call.
- Rejected windows stay in results/ and state.json with the reason; they are counted, not dropped silently.
Worked example from our test run
In our test run we used 12 public videos, 17.82 minutes in total: cuts from Creative Commons supermarket walking tours filmed in Australian, German and Swedish stores, renamed like store files (STORE-NUMBER.mp4) with the sound removed. They are handheld walks by a visitor, not videos filmed by store managers for a check, and several files come from the same real store. They carried half price end headers, percent off signs hanging over the aisles, every day price wall banners, a prize promotion display, seasonal displays and price posters. The distractors were the shelf edge itself (big yellow Special and half price strips on every shelf), price cards in seafood and deli counters, price cards on poles above beer pallets, department signs and the store logo, and several files with no campaign sign at all. Before searching we wrote down every large campaign sign moment we could see on a sheet with a frame for every second; that sheet missed two signs (a wall poster and a display poster) that the search later found, so 2 of the 16 real windows are on signs added after the search, and the 8 misses are counted against the moments written down before it. The kit list included an item (a back to school banner) that is in none of the videos.
The promo_signs search, first wording on this corpus, returned 18 windows: 16 real, 2 false and 8 missed sign moments. Both false windows were per-product price cards hung above floor stock (on poles over beer pallets, on a rail over carton stacks), which the reason text called "large printed promotional price signs". The shelf edge strips and counter price cards never came back on their own. The misses were signs the camera passed quickly, including a whole file where the search found nothing and the Step 6 scan found the display header. Windows had a median of 10.5 seconds. The floor_displays search returned 23 windows, all 23 real; we had no ground truth for displays, so its recall is unknown.
The reason text is not a reading. It named the right kind of sign in the real windows, but it got the prize count on a prize promotion sign wrong, and put a poster that stands on a juice stack "hanging from the ceiling". A control search worded with the headline of the absent back to school banner returned 1 window, and its reason described that banner in a store aisle where the frames show only half price, percent off and aisle category signs. That is why the kit list is never put into a query and every word in the sheet comes from a frame. Our own readings were not checked by a second reader; a frame with small or distant text was marked NOT VERIFIED instead of guessed.
Done when every window in state.json has a verdict and every confirmed sign has a read frame in frames/.
Step 6: Scan the stores with no confirmed sign
Goal: every store and kit item without a confirmed sign is either found by a scan or listed in not_seen.txt.
- For each store with no confirmed window for a kit item, make a sheet of the whole video at one frame per second (the 10x9 grid holds 90 seconds; raise the second number for longer videos):
ffmpeg -v error -i STORE_FOLDER/VIDEO_FILE -vf "fps=1,scale=320:-2,tile=10x9" -frames:v 1 frames/STORE_scan.png
- If a cell shows the item, go back to Step 5 with a window around that second. Otherwise add the store and item to not_seen.txt as "not seen, look by hand", with what the video shows instead (for example "video skips the front end displays").
- Tell the user which stores are on the list. An empty search result does not prove a sign is missing in the store; it may be outside what the manager filmed, so the follow up is a question, not a finding.
In our test run the files with no sign window were scanned this way. In one of them the sheet showed a "NEU" (new) display header near the start that the search had left out; the other had no campaign sign, only counter price cards. The same kind of sheet also found an EVERY DAY wall banner in a store where no window covered it, which is why a missing kit item is checked on the sheet even when the store has other windows.
Done when every store and kit item pair has a confirmed row in the sheet or a line in not_seen.txt.
Step 7: Assemble the sheet and confirm the sample
Goal: one table the manager can work through store by store.
- Build one row per store and kit item seen:
store,source_file,checklist_item,mm_ss,sign_text_read,price_read,matches_checklist,frame_path,follow_up
STORE-203,STORE-203.mp4,K1 Half price end display header,00:25,"1/2 PRICE / Great value. Hands down.",,yes,frames/STORE-203_h1_read.png,
STORE-205,STORE-205.mp4,not on kit list,00:07,"SUPER SAVERS / Special","$15 WAS $21 / $12 SAVE $4",not on kit list,frames/STORE-205_h15_read.png,
- Field mapping:
| Field | Source | If unavailable |
|---|---|---|
| store | the store pattern in the file name, confirmed in Step 1 | UNCERTAIN, ask the user |
| source_file | the original file name in stores.csv | none |
| checklist_item | the kit list item the sign matches | "not on kit list" for a campaign sign that matches no item |
| mm_ss | the second of the read frame | none |
| sign_text_read | headline words read from the full frame, as printed | partly legible with ? for unread characters, or NOT LEGIBLE |
| price_read | price read from the full frame | blank when the sign has no price; NOT LEGIBLE when it has one that cannot be read |
| matches_checklist | yes, no (with what differs), or NOT VERIFIED when the text is not legible | NOT VERIFIED |
| frame_path | the read frame | none |
| follow_up | left empty for the manager | blank |
- Show two or three sample rows, the mapping and not_seen.txt, and wait for a yes before writing the full table. Say which parts are Claude's reading: every sign text and price is copied by Claude from a frame, so the manager opens frame_path before calling a store about a wrong price; matches_checklist is Claude's comparison with the kit list.
- Write signage_sheet.csv and not_seen.txt, and give the user the counts: stores checked, windows returned and windows rejected on the frame sheets (with the reason for each), kit items confirmed, items that differ from the kit list, and not seen pairs.
In our test run the sheet and the not seen list were written from the checked windows and the scans; the user's approval of the sample rows was not exercised in our test run.
Done when signage_sheet.csv and not_seen.txt exist and the user has approved the sample rows.
Compliance
- Use only your company's own walk-through videos that stores were asked to film for this check. Ask before Step 2 whether company policy allows uploading them to a third party service.
- Store videos show shoppers and staff. Ask before Step 3 and create the Vivu project as private (the default visibility is the whole organization). Nobody in a frame is named or described in the sheet; only signs are. The videos stay in the user's Vivu project until the user deletes them; the skill never calls vivu_delete_video or vivu_delete_project unless the user asks, and confirms first.
- Shoppers in a store walk can include children. They are never a subject of the check and nothing about them is recorded; do not use this skill on footage whose subject is a child.
- The sheet records what is visible in the video. It does not score store managers or staff, and what to do about a store is the manager's decision.
- No face, voice, logo or identity recognition. Vivu only finds when a large sign or display is on screen; the words and prices are read by Claude from frames extracted by the user's own ffmpeg from their own files.
- The skill sends nothing to stores. The manager follows up from the sheet.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a window shows only price cards on poles above pallets or on a rail above carton stacks | the search counts per-product price cards that are not on the shelf edge as promotion signs | reject the window on the half second sheet; only banners, posters and display headers go into the sheet |
| (observed) the reason text gives a different prize count than the one printed on a prize promotion sign | the reason text paraphrases and can get numbers wrong | copy words and prices from the full frame only |
| (observed) a search for a kit item that is not in the store returns a window whose reason claims "A hanging Back to School campaign banner is visible" | a query with the exact headline invites the reason text to repeat it | search by kind, size and place; compare with the kit list from the frames |
| (observed) a store with a display header gets no window at all | the camera passes the sign quickly | run the Step 6 scan for every store and kit item without a confirmed sign |
| (observed) the reason says a sign is "hanging from the ceiling" when it stands on a product stack | the reason text describes placement loosely | read placement from the frame before matching to a kit item that names a placement |
| a store comes back with no window although the banner is up | an empty search result does not prove the sign is missing; finding a sign by how it looks can miss | run the Step 6 scan before listing the store as not seen |
| a list stops at exactly maximum_results | the cap cut the list short | raise maximum_results and rerun |
| a price in the sheet differs from the sign by one digit | it was filled from the reason text instead of the frame | read every character from the full frame; the reason text can describe prices that are not on screen |
| "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 stores.csv | Vivu replaced spaces or punctuation in the name | compare names with those characters replaced by "_" |