SkillsSkill for Claude

Competitor ad messaging matrix

This skill turns a list of competitors into one table of what their recent short ads say. Claude finds each competitor's official YouTube channel, downloads the ads of 15 to 60 seconds with their captions, prices the indexing against your Vivu plan, indexes them in a private Vivu project, searches for price or deal cards, comparison claims in the voiceover, prices said aloud and the closing call to action, checks every hit against a frame or the caption file, and writes ad_matrix.csv. Price cards that nobody reads aloud and jabs at rivals in the voiceover rarely show up in a title, which is why the skill reads the footage.

Maintained by Vivu. Updated 2026-10-13.

Download

competitor-ad-messaging-matrix.zip

9 KB. Unzips to competitor-ad-messaging-matrix/SKILL.md. Upload the zip as it is in the Claude app, or unzip it into your skills folder for Claude Code.

SHA-256 436f04cb7a322ebcbc74d7c65503db4eb7b21a1f0b7a36e7c2b849ce7b3b5e6d

At a glance

What the Competitor ad messaging matrix skill does, where it runs, what it needs, and when it asks
Looks forPrice and deal cards burned into the picture, claims about other carriers or vendors in the voiceover, prices said aloud, and the closing call to action. Titles rarely carry any of them, and many price cards are never read aloud. Every hit is checked against a frame or the caption file before it goes into the matrix.
Runs onClaude Code on your computer (terminal or the Code tab of Claude Desktop): it needs a shell, local files and a residential IP for YouTube downloads. No scheduler; a quarterly refresh is a new run.
Needs
  • Vivu connector with write access, to create a private project, open its upload page and search
  • A shell on your computer with a residential IP, because YouTube blocks downloads from cloud IPs
  • yt-dlp, ffmpeg with ffprobe, and node, to download ads, measure them and extract frames
  • An upload path: the Vivu upload page in your own browser, or a browser tool that can attach local files
  • Web fetch, to check that each channel belongs to the competitor
  • Optional Slack or Notion connector, only if you want the matrix posted
Your Vivu planIndex minutes equal the total length of the ads, plus four precise searches per refresh; the skill measures the ads and shows the cost against your remaining allowance before uploading. In our test run, 12 ads came to 5.43 index minutes.
Asks you firstConfirming that downloading competitors' public YouTube ads is acceptable for your organization, confirming the official channels and the ad list before downloading, the indexing and search cost before uploading, three sample rows and the field mapping before writing the full CSV, and the rendered message or page before any post to Slack or Notion.

The skill does its video work through the Vivu connector. If the connector is not in your Claude yet, add it first; the skill checks that it is connected before it does anything else.

Before you run it

  • Downloading YouTube videos may conflict with YouTube's Terms of Service; use only competitors' official public ads, for internal analysis, without redistribution.
  • The skill does not identify the actors or public figures in ads and does not recognize faces or logos.
  • Posting to Slack or Notion acts as you; nothing is posted without your approval of a full sample.
  • Claude in Chrome uploads at most 10 MB per call; larger files go through your own browser or the Vivu web app.
  • The ads stay in your Vivu project until you delete them; the project is created as private.
  • Vivu returns time windows that can cover most of a short ad; the exact time comes from extracted frames.
  • Small price text on low resolution vertical Shorts can be missed by search; the skill reads a whole ad frame sheet for every ad the search did not return.
  • In our test run every comparison claim was also written on screen, so finding a claim that is only spoken is untested; each one is confirmed in the caption file.
  • Tested on one category of ads; query wording for other categories is untested and its results get the same checks.

Start it

Once the skill is installed, ask for the task in your own words. Naming the skill is the most reliable way to have Claude use it. For example:

Build a competitor ad messaging matrix for our three main competitors from their YouTube ads in the last 90 days.

In Claude Code you can also type /competitor-ad-messaging-matrix. Claude asks for anything the request leaves out, most important first.

What is inside

  1. When to use
  2. Working principles
  3. What you need before starting
  4. Inputs to collect
  5. Files and state
  6. Step 1: Check the Vivu connector and the setup
  7. Step 2: Confirm the official channels and list the ads
  8. Step 3: Download the ads and captions
  9. Step 4: Price, upload and index
  10. Step 5: Run the standing searches
  11. Step 6: Check every hit against the ad
  12. Step 7: Assemble the matrix and share it
  13. Compliance
  14. Known failure modes

The full skill

This is competitor-ad-messaging-matrix/SKILL.md from the download, as Claude reads it: the frontmatter first, then the instructions.

---
name: competitor-ad-messaging-matrix
description: "Build a CSV matrix of rivals' short ads with Vivu: price cards, comparison claims and CTAs, each checked against frames and captions. Use for a quarterly competitive ad review."
---

Competitor ad messaging matrix with Vivu

This skill turns a list of competitors into one comparison table of what their recent short ads say. Claude finds each competitor's official YouTube channel, lists the ads of 15 to 60 seconds from the chosen quarter, downloads them with captions, prices the indexing against the user's Vivu plan, uploads and indexes them in one private Vivu project, runs four standing searches (price or deal text on screen, comparison claims in the voiceover, prices said out loud, the closing call to action), checks every hit against a frame or the caption file, reads each ad's full transcript for its main pitch, and writes ad_matrix.csv with one row per competitor, ad and dimension.

The value is in what the ads show and say but never put in a title. A carrier spot titled "Eau De New Phone" carries no price or claim in its title; the price card "$1000 OFF, no trade in required" in another spot is on screen for a few seconds and nobody reads it aloud; jabs at rivals such as "it's on the best network" or "hidden fees, higher bills, mystery charges" come in the voiceover and at most a passing caption. A caption search misses the written only prices, and a title scan misses nearly everything. Every row in the matrix is checked against the source before it goes in, and the false positives are counted and shown to the user.

When to use

Use when someone asks to compare competitors' ads, build or refresh a competitor messaging matrix, see what rivals are claiming or charging in their recent ads, or prepare the ad section of a quarterly competitive review. For a daily watch that alerts on new competitor videos, use the youtube-competitor-watch skill instead. For long webinars and product demos, a battlecard built from pricing pages and settings screens fits better than this skill. For a one off question about a single ad, search Vivu directly.

Working principles

  1. Report measured numbers, not estimates. When a number is an estimate, say so.
  2. Nothing is "verified" until it has been checked against the source video. Search results are candidates until a frame or the caption file confirms them.
  3. Stop and tell the user when a required capability or tool is missing. Do not guess around it.
  4. Ask the user before anything that is expensive to redo or that acts on their behalf: the channel list before downloading, the indexing cost before uploading, and any post to Slack or Notion.
  5. Quote on screen text from the frame you extracted and spoken words from the caption file, never from Vivu's reason text, which paraphrases.
  6. Describe what an ad claims. Do not judge which ad works better, and do not identify the people on screen.

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; ask the user to reconnect Vivu with write access
A shell on the user's computer with a residential IP, and a working folder YouTube blocks downloads from cloud and datacenter IPs the one ad test download in Step 3
yt-dlp, ffmpeg with ffprobe, and node download, measure length, extract frames yt-dlp --version, ffmpeg -version, node --version
An upload path moving the ads into Vivu vivu_open_upload_page plus the user's own browser, or a browser tool that can attach local files
Web fetch verifying that each channel belongs to the competitor fetch one competitor homepage
Slack or Notion connector (optional) posting the finished matrix read the target channel or page once; skip when the user only wants the CSV

This skill runs in Claude Code on the user's computer (terminal or the Code tab of Claude Desktop), because it downloads from YouTube and reads local files. It does not schedule anything; a quarterly refresh is a new run.

Inputs to collect

Ask for anything missing, most important first.

  1. The competitors: two to four company names or websites. Required.
  2. The quarter or date window. Default: the last 90 days.
  3. Dimensions. Default: price or deal on screen, comparison claim, price said out loud, closing call to action, plus the main pitch read from each transcript.
  4. Ads per competitor. Default: up to 8, newest first, 15 to 60 seconds long, both the /videos and /shorts tabs.
  5. Where the finished matrix goes. Default: ad_matrix.csv in the working folder only; Slack or Notion only after approval.

Files and state

Keep everything in one working folder:

ad-matrix/
  config.json        competitors, channel IDs, window, dimensions, vivu project id, destination
  channels.csv       verified official channel per competitor
  ads.tsv            one row per downloaded ad: competitor, youtube_id, published, duration_s, width, height, file
  ads/               flat folder, only .mp4 files (upload candidates)
  captions/          one .vtt per ad (yt-dlp subtitles)
  results/           raw search results, one JSON per query
  frames/            frames extracted inside result windows, and one 1 fps sheet per ad
  ad_matrix.csv      the matrix (Step 7)
  state.json         completed steps, uploaded video IDs, query job IDs, rows already verified

state.json records each finished step, the Vivu video ID of every uploaded ad, the job ID of each search, and the key (youtube_id, dimension, mm_ss) of every verified row. A rerun after an interruption reads it, skips finished steps and does not search or upload twice.

Step 1: Check the Vivu connector and the setup

Goal: confirm every requirement before any download or upload.

  1. 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. Continue only when it shows can_create_projects: true. If a later write call fails with "has not granted vivu.write", ask the user to reconnect Vivu with write access.
  2. Run yt-dlp --version, ffmpeg -version and node --version. Name anything missing and stop.
  3. If the user wants the matrix posted, read the target Slack channel or Notion page once to confirm access.

Done when vivu_get_account returned can_create_projects: true and the three tools print versions.

Step 2: Confirm the official channels and list the ads

Goal: one verified official channel per competitor and a list of ads in the window.

  1. Find each competitor's channel and verify it the way youtube-competitor-watch describes (the company's site links to the channel, or the channel description names the company; otherwise mark it UNCERTAIN and ask the user). The youtube-competitor-watch skill (https://vivu.ai/skills/youtube-competitor-watch) covers channel verification and download troubleshooting in more depth.
  2. List both tabs; Shorts hold the vertical cuts of the same campaigns:
yt-dlp --flat-playlist --playlist-end 40 --print "%(id)s|%(duration)s|%(title)s" "https://www.youtube.com/@HANDLE/videos"
yt-dlp --flat-playlist --playlist-end 40 --print "%(id)s|%(duration)s|%(title)s" "https://www.youtube.com/@HANDLE/shorts"

HANDLE is the channel handle from step 1. Shorts print NA for duration in this listing; the download filter in Step 3 enforces the length. 3. Drop how to videos, tutorials and episodes, keep ads, and mark foreign language cuts of the same spot so the user can include or skip them. 4. Show the user the ad list per competitor (title, ID, tab) and wait for approval.

Handles are often not the brand name: some guessed handles failed with HTTP 404 before the right ones were found. Checking ownership against the company's own website was not exercised in our test run; there we matched each channel by its name and channel ID in the download metadata.

Done when channels.csv has one verified channel per competitor and the user approved the ad list.

Step 3: Download the ads and captions

Goal: every approved ad in ads/ with its caption file in captions/.

Run on the user's computer, never in a cloud sandbox. Test one ad first.

yt-dlp --js-runtimes node --restrict-filenames \
  -f "bv*[height<=720]+ba/b[height<=720]" --merge-output-format mp4 \
  --match-filter "duration<=65 & upload_date>=YYYYMMDD" \
  --download-archive ad-matrix/archive.txt \
  -P "temp:SCRATCH_DIR" -P "home:ad-matrix/ads" \
  -o "%(id)s_%(title).60B.%(ext)s" --retries 5 \
  "https://www.youtube.com/watch?v=VIDEO_ID"
yt-dlp --js-runtimes node --restrict-filenames --skip-download \
  --write-subs --write-auto-subs --sub-langs "en,en-US,en-orig" --sub-format vtt \
  --sleep-requests 2 --sleep-subtitles 15 \
  -P "home:ad-matrix/captions" -o "%(id)s_%(title).60B.%(ext)s" \
  "https://www.youtube.com/watch?v=VIDEO_ID"

YYYYMMDD is the first day of the window, SCRATCH_DIR is a folder outside ad-matrix/ads, VIDEO_ID comes from the approved list. 720p keeps price cards readable. Captions come in a separate call because a caption download that fails with "HTTP Error 429: Too Many Requests" stops the video download in the same call.

If downloads fail with "HTTP Error 403: Forbidden", add --extractor-args "youtube:player_client=web_embedded" to both commands; our test run used that client for every download.

The slower sleep settings above fetched the captions that had failed in our test run. YouTube sometimes serves only a lower resolution for Shorts; keep what you get and say so in ads.tsv. An ad with no caption file usually has no voice; record that in ads.tsv.

Measure every file and record duration and size in ads.tsv:

ffprobe -v error -show_entries format=duration:stream=width,height -of csv=p=0 FILE

Done when ads/ holds only the approved .mp4 files and ads.tsv lists each with duration, width and height.

Step 4: Price, upload and index

Goal: every ad indexed in one private Vivu project, after the user approved the cost.

  1. Sum the durations in ads.tsv. Call vivu_get_usage and show a table: index minutes needed against remaining index minutes, and search credits for four precise searches (a precise search uses 5 credits in total) plus any rewordings, labeled as an estimate. Plans for reference: Free is 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. If the job does not fit, offer to index fewer ads per competitor (newest first) before suggesting a larger plan. Wait for approval.
  2. Call vivu_list_projects and reuse a project named "Ad matrix QUARTER" if one exists; otherwise call vivu_create_project with that name and visibility "private". The default visibility is the whole organization, and a competitive review usually should not be.
  3. Call vivu_open_upload_page with the project ID. The link expires in 180 seconds, so request it right before use and never post or store it. Give it to the user to open in their own browser and select the files from ads/, or attach them with a browser tool that can attach local files. Claude in Chrome accepts at most 10 MB per upload call; send larger files through the user's browser or the Vivu web app. Do not split or recompress ads to fit.
  4. Poll vivu_list_videos about every 30 seconds until every ad shows ready, and compare the count with ads.tsv. Vivu replaces spaces and some punctuation in file names with underscores, so match results back by the 11 character YouTube ID at the start of each name.

In our test run 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. The account used in our test run returns no allowance figures, so the comparison with a real plan and the user's approval were not exercised in our test run either.

Done when vivu_list_videos shows every ad in ads.tsv as ready and state.json holds their video IDs.

Step 5: Run the standing searches

Goal: four result files in results/, one per dimension.

Each query maps to one column of the matrix. The wording names the kind of thing, not the exact words, because a query that spells out a specific caption tends to pull in windows where that caption is only spoken.

Field Query Mode
price_on_screen text burned into the ad picture that states a price or a deal: a dollar price per month, an amount off, free months or a free phone, or a price guarantee; a logo, a slogan or a network claim without a number does not count precise
comparison_spoken a voice in the ad says this carrier is better than other carriers or than the viewer's current carrier, for example claiming the best network, or criticizing what other carriers do such as hidden fees or price hikes precise
price_spoken a voice in the ad says a price, an amount you save, free months, or that the price stays locked precise
cta_card closing text on screen telling the viewer where to go next, such as a web address or Learn more precise

The wording above is for wireless carriers. For another category, keep the structure and swap the examples (for software: "a price per seat", "free trial", "better than spreadsheets"); a reworded query has not been tested and its first results need the same checks.

The chain: price_on_screen finds the price cards; price_spoken then shows which of those prices are also said aloud, so the matrix can flag written only offers; comparison_spoken finds the claims about rivals, which the voiceover carries; cta_card closes each ad's row with where it sends the viewer.

Run each with vivu_search_videos (project_id, query, mode "precise", maximum_results set to twice the number of ads in ads.tsv, at most 100). maximum_results is also the ceiling on what a search can find, and a short ad can hold two or three matching moments, so a fixed small value would cap recall once there are more ads; with the default of 8 ads for each of 4 competitors that is 64.

Our test run used maximum_results 30 with 12 ads.

fast mode returns whole files with no reason text, so it is not used here: every row needs a timestamp. 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 Vivu result page link in the reply if the user wants to look; it expires after four hours, so it never goes into the CSV, Slack or Notion.

Done when results/ holds four completed result files and state.json records their job IDs.

Step 6: Check every hit against the ad

Goal: each search result judged real or false, with the exact words.

Vivu returns a time range that contains the moment, not an exact frame, and in short ads that range often covers most of the ad. So:

Run these commands from the same folder as Step 3 (the one that contains ad-matrix/).

  1. On screen dimensions (price_on_screen, cta_card): extract a frame every 0.5 seconds inside the window and read the card from the full frame:
mkdir -p ad-matrix/frames
ffmpeg -nostdin -v error -ss START_S -to END_S -i FILE -vf fps=2 ad-matrix/frames/FIELD_YTID_%03d.png

START_S and END_S come from start_ms and end_ms divided by 1000; FILE is the ad's path under ad-matrix/ads/; FIELD and YTID name the output. Copy the card text as written into on_screen_text, with its MM:SS from the first frame that shows it. Background store posters and tiny legal lines do not count as the ad's price card. 2. Spoken dimensions (comparison_spoken, price_spoken): open the ad's caption file and copy the exact words inside the window into spoken_words. If there is no caption file, call vivu_get_video_summary (project_id, video_id, include_segments true, start_ms and end_ms around the window) and mark the row NOT VERIFIED, because the summary paraphrases too. 3. A window with no matching card or words is a false positive. Count false positives per dimension and show the counts to the user. 4. Search misses happen most on small vertical cuts. Make one 1 fps sheet per ad and read every ad that no on screen search returned:

ffmpeg -nostdin -v error -i FILE -vf "fps=1,scale=-2:240,tile=8x9" -frames:v 1 ad-matrix/frames/YTID_sheet.png

An 8x9 sheet holds 72 one second frames, enough for every ad that passes the 65 second download filter, so end cards are not cut off. Rows found this way get status found_in_sheet, so the user can tell them apart from search results. 5. Main pitch: read each ad's full caption file (a short ad is a few lines, cheaper to read whole than to search) and write one line on what it leads with. Main pitch is not a Vivu search; in our test run those rows came from reading captions.

Worked example from our test run

In our test run on 12 official ads from the YouTube channels of three competing US prepaid wireless carriers, published over several years rather than within one quarter (public footage, 5.43 minutes in total, horizontal and vertical cuts, the smallest a low resolution vertical Short), every ad was ready about 6.3 minutes after the upload started. Ground truth was written from whole ad frame sheets and caption files before any search. Filtering the ad list by date window was not exercised in our test run.

The price_on_screen query returned 10 windows: 10 real, 0 false, 0 missed. That includes the price cards that no voice reads and the low resolution vertical cut. The comparison_spoken query returned 4 windows: 4 real, 0 false, 0 missed. The price_spoken query returned 7 windows: 7 real, 0 false, and it did not return the written only price cards. The cta_card query returned 6 windows: 6 real, 0 false. None of the queries was reworded.

The run also showed limits. The windows are wide for short ads: the median price window was 8.1 seconds, and one window on a vertical ad ran 23.3 seconds, almost the whole ad, so the frames extracted in the first check above are what give the MM:SS. And every comparison claim in this test corpus was also on screen as a text card or a burned in caption, so how well the spoken search finds a claim that is only said, never written, is untested; the caption file check above is what confirms a claim was spoken. The sample was small and from one category; expect some false positives on other footage and keep counting them.

Done when every result in results/ is marked verified or false in state.json, every ad not returned by an on screen search has a whole ad frame sheet that was read, and each ad has a main pitch line.

Step 7: Assemble the matrix and share it

Goal: ad_matrix.csv, approved by the user, and an optional post.

  1. Write one row per verified finding:
competitor,video_file,youtube_id,published,dimension,mm_ss,on_screen_text,spoken_words,frame_path,source_link,status,our_response

competitor comes from the verified channel, published from the download metadata, source_link is the user's own YouTube link with &t=SECONDS (never the Vivu result page), status is verified, found_in_sheet, written_only (a price on screen that no voice says) or NOT VERIFIED, and our_response stays empty for the user.

  1. Show the user three sample rows and the field mapping before writing the full file:
Field Source If unavailable
competitor, youtube_id, published channels.csv and ads.tsv UNCERTAIN
mm_ss start of the frame or caption line that shows the finding the window start, marked approximate
on_screen_text full frame read in Step 6 blank
spoken_words caption file NOT VERIFIED from the summary
status Step 6 NOT VERIFIED
  1. Posting to Slack or Notion acts as the user. Name the destination, show the full rendered message or page, and wait for a yes. Slack gets a short summary by competitor with the CSV attached or linked; Notion gets the same columns as a table. Posting was not exercised in our test run; the message and page were rendered to files only.
Competitor ad matrix, DATE: N ads from M competitors, R checked rows (CSV attached: ad_matrix.csv)

COMPETITOR (K ads)
- Price: "CARD TEXT" YOUTUBE_LINK&t=SECONDS
- Price, written only: "CARD TEXT" YOUTUBE_LINK&t=SECONDS
- Comparison: "SPOKEN WORDS" YOUTUBE_LINK&t=SECONDS
- No comparison claims found.

Our response column is empty for the team to fill in.

DATE, N, M, R and K come from the finished CSV; CARD TEXT and SPOKEN WORDS are copied from it; YOUTUBE_LINK is the ad's own YouTube address. Write "No ... found" lines instead of leaving a competitor out, so the team sees the gap.

Done when ad_matrix.csv has one row per verified finding and the user approved it, plus the post if one was requested.

Compliance

  1. Downloading YouTube videos may conflict with YouTube's Terms of Service. Use only the competitors' official public ads, keep them for internal analysis, do not redistribute them, and have the user confirm this is acceptable for their organization before Step 3.
  2. Ads show actors and sometimes well known people. This skill does not identify anyone and does not recognize faces or logos; the competitor comes from the verified channel.
  3. The ads stay in the user's Vivu project until the user deletes them. Create the project as private. Never delete a project or video unless the user asks, and confirm first.
  4. Quoting a competitor's claim internally is fine; using their footage or logos in anything public needs its own review.
  5. The matrix describes claims; it does not judge which ad is better or whether a claim is true.

Known failure modes

Symptom Cause Fix
(observed) caption download fails with "HTTP Error 429: Too Many Requests" and the ad in the same call is not saved YouTube rate limits caption requests download videos first, then captions in a separate --skip-download call with --sleep-requests and --sleep-subtitles
(observed) channel listing fails with "HTTP Error 404: Not Found" the guessed @handle is not the brand's handle take the channel link from the company's website, then list it
(observed) yt-dlp prints "There are no subtitles for the requested languages" the ad has no voice, or captions are off read the ad from frames; leave spoken_words empty, or NOT VERIFIED if someone does speak
(observed) one window covers almost a whole vertical ad Vivu returns a range that contains the moment, and short ads are short extract frames across the window as in Step 6 and take MM:SS from the first frame that shows the card
(observed) a price window starts on a store poster in the background the scene has prices on set dressing before the real end card count only the ad's own card; posters and tiny legal lines do not go in the matrix
(observed) a comparison claim is returned while the same words are on screen in our test corpus every comparison claim was both said and written, so the spoken search was never tested alone confirm in the caption file that the words were spoken before marking the row spoken
a price card on a small vertical cut is missing from the results small text on low resolution Shorts is found less reliably (capabilities record) read the whole ad frame sheet of every ad that no on screen search returned
a quote in the matrix does not match the ad the reason text was copied; it paraphrases copy on screen text from the frame and spoken words from the caption file
"Sign in to confirm you're not a bot" download attempted from a cloud IP run Step 3 on the user's computer
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
"has not granted vivu.write" Vivu connected read only the user reconnects Vivu with write access
a file is rejected by the browser upload tool Claude in Chrome takes at most 10 MB per call the user adds that file in their own browser or the Vivu web app
a result's file name does not match ads.tsv Vivu normalized the file name match on the YouTube ID at the start of the name

Connect Vivu, then add the skill.

The skill runs through the Vivu connector. Add it to Claude from the connector directory, then install the skill.