---
name: press-briefing-fact-check-sheet
description: "Build a fact-check sheet from an agency news conference video with Vivu: per topic, the minute the official states the figure, the quote, and the slide read from a frame. Use on release day."
---Press briefing fact-check sheet with slide frames
This skill takes the official recording of a government news conference (one to three briefings, downloaded from the agency's website or official channel), the agency's caption file or transcript, and the four to six topics a reporter needs to check, and returns a fact-check sheet: for each topic and briefing, the start and end time where an official states the figure, the sentence quoted from the captions, the slide title and figure read from a full frame at that moment, whether the spoken and the on screen figures agree, the speaker as labeled in the captions or on a name bar, the frame file, a link to the official video at that second, and an empty column for the reporter's own check with the agency. Claude downloads the presentation part of each briefing, prices it against the user's Vivu plan, uploads it to a private Vivu project, runs one precise search per topic, reads the captions inside every returned window to keep only the windows where the topic's figure is actually said, reads the slide from a frame, and writes the sheet.
The value is in what the recording shows and the transcript does not. A transcript, when the agency posts one at all, has the spoken numbers but not the charts; the slide carries the definition that decides what a figure means ("real" versus nominal income, the official poverty rate versus the Supplemental Poverty Measure) and sometimes a number that is rounded differently from what was said. The hard part is that a briefing on a data release states many figures in a row, and neighbouring topics bleed into each other: the official poverty rate and the SPM rate are presented back to back and compared, and the overview at the start repeats every headline. So every window Vivu returns stays a candidate until the captions inside it show an official saying that topic's figure, and no number in the sheet comes from Vivu's reason text.
When to use
Use when a reporter or fact checker says "pull the poverty and income numbers from today's Census briefing with timestamps", "where in the press conference did the director give the budget figure, and what was on the slide?", "make me a check sheet for these five figures from the briefing video", or "did the number on the chart match what the official said?".
Not for these:
- A one off question about one figure in one video ("when does she mention the uninsured rate?"): search Vivu directly and read the captions around the hit.
- The question and answer part of a briefing. This skill indexes the presentation; reporters' questions and officials' off script answers need their own pass (index the Q&A part and add topics for it).
- Deciding whether a figure is right or newsworthy. The sheet puts the spoken figure next to the slide; the reporter checks with the agency and writes the story.
- Identifying people by face or voice. Speakers come from the caption labels, a name bar or the agency's transcript only.
- Recordings the newsroom has no right to use, such as a paywalled feed.
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. Every Vivu window is a candidate until the captions inside it show an official saying the topic's figure. Figures come from the captions and the frame, never from Vivu's reason text, which paraphrases and can invent numbers.
- 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 briefing list and its index minutes, before upload) and before writing the full sheet (one sample row first). The skill sends nothing to anyone and contacts no agency.
- Never decide for the reporter. When the spoken and the on screen figures differ or one cannot be read, write that in the row; the agency_verified column stays empty for the reporter.
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 on the user's computer with yt-dlp, ffmpeg and ffprobe, and node for yt-dlp | download the briefing, measure it, cut frames | yt-dlp --version, ffmpeg -version, ffprobe -version, node --version |
| A residential IP, when the briefing is on YouTube | YouTube blocks downloads from cloud and datacenter IPs | the one video download in Step 3 |
| The agency's captions or transcript for each briefing | the captions inside each window decide whether the figure is said, and the quote is copied from them | Step 3 downloads the caption file; if there is none, ask for the agency's transcript or a local speech to text tool such as whisper.cpp |
| An upload path | move the files into Vivu | vivu_open_upload_page plus the user's own browser, or a browser tool that can attach local files |
| The briefing links and the topic list | topics drive the searches; the links are the source of the video and of the sheet's link column | the user pastes them |
This skill needs Claude Code on the user's computer (the terminal or the Code tab of Claude Desktop), because it downloads and reads local files and runs ffmpeg. Claude on the web and cloud sessions cannot reach the files, and YouTube blocks their IPs. Nothing recurs, so no scheduler is involved; run it again for the next briefing.
Inputs to collect
Ask for anything missing, most important first.
- TOPICS: the four to six figures to check, in the reporter's words (for example "median household income", "official poverty rate", "share without health insurance"). Required.
- BRIEFING_URLS: the official recording of each briefing, one to three. Required.
- The presentation part of each briefing as start and end times. Default: read the chapter list in the video description, or the first and last lines of the presentation in the captions (the moderator usually says "we'll take your questions"); include the closing recap.
- Optional control topic: a figure the reporter expects was not given, to see whether the search invents one. Default: none.
- WORK_NAME: a short name for the working folder and the Vivu project. Default: the agency and release date, such as census-income-2023-09-12.
Files and state
Keep everything in one working folder:
briefing-WORK_NAME/
config.json topics with their query, briefing URLs, cut times, Vivu project id
briefings.csv file, listed_name, source_url, cut_start_s, duration_s, video_id, caption_file
video/ the downloaded presentation parts (.mp4)
subs/ the caption files (.vtt)
results/ one JSON per topic: search results without the result page link
sheets/ contact sheets for kept windows
frames/ full frames the slide was read from
candidates.csv every window Vivu returned, with the caption verdict and the reason
fact_check_sheet.csv one row per topic per briefing
state.json steps done, files uploaded, topics searched with job ids, windows checked
Run the commands from inside briefing-WORK_NAME/ so the relative paths resolve.
A rerun reads state.json first and skips finished steps: a downloaded file is not downloaded again (the yt-dlp archive file), a file marked uploaded is not uploaded again, a topic with a saved result is not searched again, and a window with a recorded verdict is not checked again. A topic added later is searched against the same project.
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
yt-dlp --version,ffmpeg -version,ffprobe -versionandnode --version. - Ask for the topics and the briefing links if they are not in the request.
In our test run vivu_get_account returned can_create_projects: true and the four tools were installed. In Step 2, the queries were written by us from the release's own reports; the reporter's approval of the queries and the rights confirmation were not exercised in our test run (the footage is a federal agency's public domain work).
Done when vivu_get_account shows can_create_projects: true, the four tools print their versions, and the user has given the topics and the briefing links.
Step 2: Turn topics into queries and confirm the rights
Goal: one search query per topic, and the user's confirmation that the recordings can be used.
- Rewrite each topic as a description of an official saying that figure, in the agency's own terms: "an official states FIGURE_NAME for the year and HOW_IT_CHANGED". Name the measure exactly as the agency does (the Supplemental Poverty Measure is not the official poverty rate), because the search matches what is said, and two measures with similar names are presented side by side. Keep the reporter's wording in config.json next to it; the sheet shows the reporter's wording.
- Do not put the figure you expect into the query. A number in the query invites a reason text that repeats it.
- Confirm the Compliance items with the user (rights to the recording, YouTube's terms when downloading from YouTube).
Done when the user approves the query list and confirms the Compliance items, and both are saved in config.json.
Step 3: Download the presentation and the captions
Goal: the presentation part of each briefing and its caption file in the working folder.
Run on the user's computer, never in a cloud sandbox. For a briefing on YouTube, get the chapters first; agencies often list the presentation and the Q&A times in the description:
yt-dlp --js-runtimes node --skip-download --write-description --write-subs --write-auto-subs --sub-langs "en,en-US" --sub-format vtt -P "home:subs" -o "%(id)s.%(ext)s" "https://www.youtube.com/watch?v=VIDEO_ID"
Prefer the human captions (.en.vtt with speaker labels) when the agency uploaded them; YouTube's automatic captions mishear names and numbers ("genie index"). Then download only the presentation, at 720p, which keeps chart labels readable:
yt-dlp --js-runtimes node --restrict-filenames -f "bv*[height<=720]+ba/b[height<=720]" --merge-output-format mp4 --download-sections "*START-END" --download-archive archive.txt -P "home:video" -o "BRIEFING_%(id)s.%(ext)s" --retries 5 "https://www.youtube.com/watch?v=VIDEO_ID"
VIDEO_ID is the 11 character ID in the link, START and END the presentation times as HH:MM:SS, and BRIEFING a short label such as cb2023. --restrict-filenames keeps the names to letters, digits and underscores, so they come back from Vivu unchanged. A file cut at START has its own clock: file time plus START is the time in the official video, so record cut_start_s in briefings.csv. For an agency website that offers an mp4, download it directly and cut nothing. If yt-dlp prints "HTTP Error 403: Forbidden", retry once with --extractor-args "youtube:player_client=web_embedded,default".
The youtube-competitor-watch skill (https://vivu.ai/skills/youtube-competitor-watch) covers channel verification and download troubleshooting in more depth.
In our test run both presentations downloaded at 720p with the web_embedded client named above, along with their caption files. One briefing had human captions with speaker labels; the other had only automatic captions, which wrote "Gini" as "genie" and garbled one spoken figure.
With --download-sections yt-dlp re-cuts the section through ffmpeg, which is slower than a plain download of the same length.
Done when video/ holds one mp4 per briefing, subs/ holds a caption file for each, and briefings.csv lists file, source_url and cut_start_s.
Step 4: Price, upload and index
Goal: every presentation indexed in a private Vivu project, with the user's approval of the cost.
- Measure each file:
ffprobe -v error -show_entries format=duration -of csv=p=0 video/FILE, where FILE is the presentation file in video/ (for example cb2023_VIDEO_ID.mp4). Write duration_s. - Call vivu_get_usage and show one table:
| These briefings | Plan allowance | Remaining this month | |
|---|---|---|---|
| Index minutes | sum of duration_s / 60 | from vivu_get_usage | from vivu_get_usage |
| Search credits | 5 per topic, 25 for five topics (estimate), plus 5 for each rewording | 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. Two briefings with a 30 minute presentation each are about 60 index minutes (estimate): Premium covers that, Free covers part of one.
- If the briefings do not fit, offer levers in this order: index only the presentation (not the Q&A), index one briefing first, and only then move to a larger plan. Never drop a briefing the user asked for without saying which one, and never trim or recompress a file to save minutes.
- Call vivu_list_projects and reuse a project named "Briefings 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; unpublished story work belongs in a private project.
- 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 the files, or open it in a browser tool that can attach local files. Claude in Chrome accepts at most 10 MB per upload call and a briefing is larger, so briefings normally go through the user's own browser or the Vivu web app. Never split or recompress a file to fit.
- Poll vivu_list_videos until every file shows ready. Vivu replaces spaces and punctuation in file names with underscores; with --restrict-filenames the names already match. Record each video_id in briefings.csv and state.json.
In our test run the two presentations measured 57.75 minutes and were both ready about 15 minutes after the upload started; each duration_ms equaled the ffprobe length and the names matched. 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. 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 the user has approved the index minutes, and every file in briefings.csv shows ready and has a video_id.
Step 5: Search each topic
Goal: a saved list of candidate windows for every topic.
One precise search per topic, because each topic is its own row. Every search is precise: only precise returns a time window, and fast returns whole files with an empty reason, which tells you nothing the briefing list does not. The chain behind each row: the precise search suggests windows where an official says the figure, the captions inside each window decide whether that topic's figure is really said there (Step 6), and a frame from the same window gives the slide (Step 7). That last step is the flip from what is said to what is shown: the slide title carries the definition and the figure as published.
Run vivu_search_videos (project_id, query, mode "precise", maximum_results 10) for each topic. Ten covers the usual places a headline figure is said in one presentation (the opening overview, the presenter's section, the closing recap) across two or three briefings, plus the subgroup figures that Step 6 has to set aside; it is also the recall ceiling, so for more than three briefings raise it. vivu_search_videos 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 the results to results/TOPIC_KEY.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.
The queries from our test run, on two Census Bureau income, poverty and health insurance briefings, show the pattern:
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| median_income | an official says how much real median household income changed from the year before and gives the new median household income figure | precise | 10 |
| income_inequality | an official reports the change in income inequality measured by the Gini index | precise | 10 |
| official_poverty | an official states the official poverty rate for the year and how many people were in poverty | precise | 10 |
| spm_poverty | an official states the Supplemental Poverty Measure (SPM) rate for the year and how it changed | precise | 10 |
| uninsured | an official gives the headline uninsured rate for the whole population this year: the percentage and the number of people who had no health insurance at any point in the year; not a rate for one age, income or work group, and not a chart of earlier years | precise | 10 |
| control (optional) | an official states the homeownership rate for the year | precise | 10 |
In our test run the uninsured wording above came after 1 rewording on the same corpus. The first wording ("an official states the share of people without health insurance for the year") returned 10 windows, 5 of them false: uninsured rates for children in poverty, for workers and for age groups, a trend chart, and a sentence saying the rate had not changed. Saying "the whole population" and naming what does not count removed all five. The control topic, never mentioned in either briefing, returned 0 windows. In our test run the seven precise searches (five topics, one rewording and the control) came to 35 credits (estimate: 5 per precise search).
When a topic's first page has subgroup windows, reword it the same way. A rerun is one more precise search.
Done when every topic has a saved result file and its job id is in state.json.
Step 6: Check every window against the captions
Goal: each window labeled "figure said" or "other", before anything reaches the sheet.
- For each window, convert start_ms and end_ms to seconds and read the caption lines whose times fall inside it. Caption times are in the official video's clock: add cut_start_s to the window first. In a .vtt file the times are on the line above the text, so
grep -n "^00:MM:" subs/VIDEO_ID.en.vttfinds the lines for minute MM. VIDEO_ID is the briefing's YouTube ID from briefings.csv; use .en-US.vtt or the auto-caption file if that is what Step 3 saved. - Label the window:
- figure said: an official says this topic's figure for the whole population and the year (in the presenter's section, the opening overview or the closing recap);
- other: a subgroup figure (children, workers, a region), a different measure, a trend with no figure for the year, or a sentence that the figure did not change.
- Write every window with its label and the caption line that decided it into candidates.csv, and count the "other" windows as false positives for the topic. Vivu's reason text never decides the label.
- For each topic and briefing keep one "figure said" window, preferring the presenter's section over the overview and recap, because that is where the chart for the figure is shown.
- A topic with no "figure said" window in a briefing gets a row saying "not found in the indexed presentation" and goes back to the user.
Worked example from our test run
The test corpus was two public news conferences from the U.S. Census Bureau's official channel on its income, poverty and health insurance release (two different years), cut to the presentation and recap: 57.75 minutes in all, slides with charts and highlight bullets, one briefing with human captions and one with automatic captions only. Before searching we marked in the captions, for five topics, every place an official says the topic's figure: the presenter's section (the main statement, one per topic per briefing) and the overview and recap repeats.
On the first wordings the five topic searches returned 37 windows: 31 real and 6 false. After 1 rewording of the uninsured query on the same corpus, the final wordings returned 29 windows: 28 real and 1 false, and 0 main statements missed. The official poverty rate and the SPM rate, presented back to back and compared with each other, never crossed. The one false window on the final wordings is the trap this step exists for: the official says how far the SPM child poverty rate rose, and the child rate is the same number as the headline SPM rate that year; the slide on screen is "SPM Poverty Rates by Age". Only the captions and the slide title tell the two apart; the reason text called it the SPM rate.
Every number in the reason texts in our test run matched the captions or the slide, and one reason gave a count with the slide's decimal where the official had said "about". The figures in the sheet still come only from the captions and the frame. Per topic, the median window was 12 to 23 seconds wide, often starting or ending on the neighbouring slide.
Done when every window in results/ has a label and a caption line in candidates.csv, and each topic has a kept window or a "not found" note for each briefing.
Step 7: Read the slide from a full frame
Goal: for each kept window, the slide title and figure on screen, read from the picture.
- Cut a contact sheet across the kept window, one frame every half second, because windows often start on the previous slide and end on the next:
ffmpeg -v error -y -ss WIN_START -to WIN_END -i video/FILE -vf "fps=2,scale=240:-1,tile=8x6" -frames:v 1 sheets/TOPIC_KEY_BRIEFING.png
WIN_START and WIN_END are the kept window's start_ms and end_ms divided by 1000, in seconds in the file (not the official video's clock). One sheet holds 24 seconds; a longer window needs a second sheet from WIN_START plus 24. Tile k is at WIN_START plus k/2 seconds, counting from 0 at the top left. Pick a tile where the slide for this figure is fully shown. 2. Extract that moment as a full frame and read it:
ffmpeg -v error -y -ss SECONDS -i video/FILE -frames:v 1 -q:v 3 frames/TOPIC_KEY_BRIEFING.png
FILE is the presentation file in video/ (for example cb2023_VIDEO_ID.mp4), SECONDS is WIN_START plus k/2 for the tile you picked, TOPIC_KEY is the field name from the query table (for example official_poverty), and BRIEFING is the label from Step 3.
- Write the slide title and the figure exactly as shown ("11.4 percent", "$67,500", "0.489"). When the slide has no figure for this topic (a chart with no label for the year, a title slide), write "not on slide". When the text cannot be read, write UNCERTAIN; never complete it from the captions or the reason.
In our test run the slide title and figure were readable in all ten kept windows at 720p, including the briefing where the slide is shrunk next to a presenter tile. Some slides carried the level but not the change or the count that the official said; the sheet says so instead of marking a mismatch.
Done when every kept window has a frame in frames/ and a slide_title and slide_figure value (or "not on slide" or UNCERTAIN).
Step 8: Write the fact-check sheet
Goal: a sheet the reporter can check against and quote from.
- Show the user one sample row and the field mapping, and wait for a yes before writing the rest:
topic,briefing,file,start,end,spoken_quote,spoken_figure,slide_title,slide_figure,match,speaker,frame,source_link,transcript_checked,agency_verified
official poverty rate,2020 data (2021-09-14),cb2021_o6t84kGR20c.mp4,12:57,13:28,"The official poverty rate in 2020 was 11.4% -- up 1 percentage point from 10.5 percent in 2019.",11.4%; 37.2 million,"Highlights: Official Poverty",11.4 percent; up 1.0 point; 37.2 million,same,caption label (income and poverty presenter),frames/official_poverty_cb2021.png,https://www.youtube.com/watch?v=o6t84kGR20c&t=777,yes,
| Field | Source | If unavailable |
|---|---|---|
| topic | the reporter's wording from config.json | none |
| briefing, file | briefings.csv | none |
| start, end | the kept window in the file, as MM:SS | none |
| spoken_quote, spoken_figure | copied from the captions inside the window | NOT VERIFIED when there are no captions |
| slide_title, slide_figure | read from the full frame (Step 7) | "not on slide" or UNCERTAIN |
| match | same, differs (say how), or partial (say what is only spoken) | UNCERTAIN |
| speaker | the caption's speaker label, a name bar in the frame, or the agency's transcript | UNCERTAIN |
| frame | path of that frame | blank |
| source_link | the official video link with t equal to cut_start_s plus start, in seconds | file name and time |
| transcript_checked | yes when Step 6 labeled the window from the captions | no |
| agency_verified | left empty for the reporter | stays empty |
- Write fact_check_sheet.csv, one row per topic per briefing.
- Tell the user the counts: rows written, rows where spoken and slide differ or one is missing, topics not found, windows set aside in candidates.csv.
In our test run the sheet has 10 rows. All ten matched; some of them say that a change or a count was spoken but not printed on the slide. The five rows from the briefing with automatic captions have speaker UNCERTAIN, because those captions carry no speaker labels and no name bar was on screen. The user's approval of the sample row was not exercised in our test run.
Done when fact_check_sheet.csv has one row per topic per briefing (kept or "not found"), the user has approved the sample row, and the counts are reported.
Compliance
- Use only recordings the agency published. Work by U.S. federal government employees in their official duties is in the public domain; state, city and other agencies' recordings can carry their own terms, so check the terms or ask the press office before Step 3. Keep the downloads for the newsroom's checking work and do not redistribute the video or clips.
- Downloading from YouTube may conflict with YouTube's Terms of Service. Have the user confirm this is acceptable for their organization before Step 3, or use the agency's own mp4 when it offers one.
- Download to the user's computer and upload from there. The skill does not hand video links to a third party importer.
- Speakers come from the caption labels, a name bar on screen or the agency's transcript. The skill does no face or voice recognition and does not identify reporters heard in the room.
- The videos stay in the user's Vivu project until the user deletes them. Delete a project or video only when the user asks, and confirm first.
- The sheet puts the spoken and the on screen figures side by side. It does not judge whether a figure is right, and it contacts no one; the reporter checks with the agency and fills agency_verified.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a topic's first page has windows for children, workers or an age group | the subgroup figures follow the headline in the same section and use the same words | label them "other" in Step 6; reword the query to ask for the whole population and name what does not count, as the uninsured query did (5 of 10 false before, 0 of 2 after) |
| (observed) a window has the same number as the headline but for a different group | the SPM child poverty rate equaled the headline SPM rate that year; the reason text called it the SPM rate | read the captions and the slide title ("SPM Poverty Rates by Age"); never accept a window because the number matches |
| (observed) a window has no figure at all | the official says "no statistically significant change in the uninsured rate" or introduces a trend chart | label it "other"; the sheet needs the figure for the year |
| (observed) automatic captions turn a figure into words that are not a number | YouTube's automatic captions garbled the spoken figure as "Cantu was" | read the slide from the frame, mark spoken_figure UNCERTAIN, and listen to that second of the video before quoting |
| (observed) the window starts or ends on the neighbouring slide | precise windows are wider than the sentence and cross slide changes | pick the frame from the half second contact sheet, not the window's first or middle second |
| (observed) speaker is UNCERTAIN for a whole briefing | automatic captions carry no speaker labels and no name bar was on screen | use the agency's transcript or the speaker list in the video description, and say which |
| a reason text gives a figure that is not in the captions or on the slide | the reason paraphrases and can invent numbers | never copy a figure from the reason; quote the captions and read the frame |
| a topic gets no "figure said" window | the briefing did not give that figure, or the query uses a different name for the measure than the agency | write "not found in the indexed presentation"; reword once with the agency's term; an empty result does not prove the figure was never said |
| a figure given only in the Q&A is missing | the skill indexes the presentation only | index the Q&A part as its own file and add the topic |
| yt-dlp prints "HTTP Error 403: Forbidden" | YouTube refused the default player client | retry with --extractor-args "youtube:player_client=web_embedded,default" |
| "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 |
| a briefing 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 |