Skip to content
jaytrivediSF25Public

About

Transcribe a YouTube video and fact-check what was said, on top of youtube-deepsummary

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

deepcheck

A verification pipeline for spoken claims. Transcribe a video, isolate every checkable assertion, adjudicate each against the live public record.

Python Model tests Platform security License


The problem

Making a claim costs a sentence. Checking one costs a research session. An hour of political speech carries dozens of specific, falsifiable statements and is consumed in full long before any of them are examined.

deepcheck treats a recording as evidence rather than talk. Every judgment is bound to the sources that produced it — the output is not a verdict you are asked to trust, it is a worksheet you are expected to check.

  1 hour of speech  ──►  11,531 words  ──►  20 checkable claims  ──►  31 sources
      65 minutes           transcript          isolated & anchored      retrieved & cited

Transcription is not ours: deepcheck runs on youtube-deepsummary by @nickita-khylkouski, importing its TranscriptExtractor directly. This project is everything downstream of the transcript.


Quick start

git clone https://github.com/jaytrivediSF25/deepcheck.git
cd deepcheck
./scripts/quickstart-claude.sh          # or quickstart-codex.sh

One command: builds the virtualenv, installs deepcheck, vendors the transcriber, checks your credentials, and opens a coding agent in the repo.

macOS / Linux Windows (PowerShell)
Claude Code ./scripts/quickstart-claude.sh .\scripts\quickstart-claude.ps1
Codex ./scripts/quickstart-codex.sh .\scripts\quickstart-codex.ps1

Then:

deepcheck transcribe "https://youtu.be/VIDEO_ID"   # transcript only, no API calls
deepcheck check      "https://youtu.be/VIDEO_ID"   # full verification pass

Prefer Docker? docker build -t deepcheck . && docker run --rm -v "$PWD:/work" -e ANTHROPIC_API_KEY deepcheck check "https://youtu.be/VIDEO_ID"


Pipeline

flowchart LR
    A(["Video URL"]) --> B["youtube-deepsummary<br/><i>TranscriptExtractor</i>"]
    B -. fallback .-> C["yt-dlp subtitles"]
    B --> D["Timestamped<br/>transcript"]
    C --> D
    D --> E["Claim isolation<br/><i>structured output</i>"]
    E --> F["Retrieval<br/><i>web search</i>"]
    F --> G["Adjudication<br/><i>verdict schema</i>"]
    G --> H(["Markdown · HTML · JSON"])
Loading
Stage What it does Why it matters
Acquire Upstream extractor, yt-dlp fallback Provenance starts at ingestion — the report records which route ran
Isolate Decompose into standalone assertions A claim as spoken leans on pronouns and "last year"; rewritten to survive alone
Anchor Match each verbatim quote to a segment A claim you cannot locate in the recording cannot be reviewed
Retrieve Adversarial web search per claim Instructed to find evidence against, not only for
Adjudicate Strict schema, no tools Written from evidence on the page, not from recollection

Why two calls per claim

sequenceDiagram
    participant D as deepcheck
    participant C as Claude
    participant W as Web

    D->>C: Research this claim
    C->>W: search ×N
    W-->>C: results + citations
    C-->>D: evidence brief

    Note over D,C: separate call — citations and<br/>constrained output cannot mix

    D->>C: Adjudicate brief (schema, no tools)
    C-->>D: verdict · confidence · sources
Loading

Partly a technical constraint, partly the more defensible design: the verdict is written from evidence already retrieved rather than from the model's recollection. For any event after the training cutoff, that is the difference between a check and a guess.


Verdicts

Verdict Applies when
🟩 true Accurate as stated
🟩 mostly_true Accurate in substance; minor imprecision
🟨 misleading Real facts arranged to mislead, or right direction and wrong magnitude
🟥 false Contradicted by the evidence
⬜ unverifiable No adequate public evidence either way
⬜ opinion Never a factual assertion to begin with

misleading is the class that earns its keep. Most contested numbers in public life are directionally correct and materially wrong. "Crime fell 88%" when it fell 40% is neither true nor false in any useful sense — a binary scale is forced to pick one, laundering exaggeration into accuracy.

Verdicts are model-generated, carry a confidence level describing the strength of the evidence, and are published with the sources that produced them. The sources are the point.


Example report

C-SPAN's recording of President Trump's remarks at the White House Correspondents' Dinner, July 24 2026 — the rescheduled dinner, held after a shooting outside the Washington Hilton cut the original April 25 event short. Sixty-five minutes covering the Iran strikes, DC crime, TikTok, the ballroom, and a long stretch of political material.

Transcript Claims documented Sources cited Report
11,531 words 20 31 16 pages

📥 Download the report — PDF


Using it

# transcript only — no API calls, no cost
deepcheck transcribe "https://www.youtube.com/watch?v=VIDEO_ID" -o transcript.txt

# full verification pass
deepcheck check "https://www.youtube.com/watch?v=VIDEO_ID" -f md,html,json -o report

# `check` is the default
deepcheck "https://www.youtube.com/watch?v=VIDEO_ID"

Three artifacts per run:

report.md     reading and diffing
report.json   machine-readable — claim, verdict, confidence, source URLs
report.html   single self-contained file; covers embedded as data URIs,
              opens offline, archivable as-is
Flag Default Purpose
-o, --out report Output path, without extension
-f, --format md Any of md, html, json, comma-separated
--max-claims 40 Cap on claims verified. 0 disables the cap
--concurrency 4 Parallel verifications
--max-searches 6 Web searches allowed per claim
--effort high low … max; reasoning depth
--model claude-opus-5 Model override
--no-fallbacks off Disable the server-side refusal fallback

Every flag has a DEEPCHECK_* environment equivalent — see .env.example. Cost scales with claims, not video length: one call per transcript chunk, then two per claim.


Architecture

deepcheck/
├── transcript.py   upstream integration + yt-dlp fallback
├── compat.py       shim for youtube-transcript-api 1.2+
├── claims.py       chunking, isolation, timestamp anchoring
├── verify.py       retrieval → adjudication
├── report.py       Markdown / HTML / JSON renderers
├── llm.py          pause_turn, refusals, JSON extraction
└── cli.py          argument routing, error messaging

A compatibility shim for upstream. youtube-deepsummary calls YouTubeTranscriptApi.get_transcript() and .list_transcripts() — classmethods that youtube-transcript-api removed in 1.2. Upstream pins ==1.1.0, which will not install on every Python version, and the last release retaining the classmethods no longer works against YouTube's current endpoints.

flowchart LR
    U["upstream calls<br/><code>get_transcript()</code>"] --> S["compat.py<br/><i>re-attaches classmethods</i>"]
    S --> L["youtube-transcript-api 1.2+<br/><code>.fetch() / .list()</code>"]
Loading

No fork, no frozen interpreter, and a no-op when the methods already exist.

Failure isolation. Per-claim errors and refusals are recorded as unverifiable with the reason attached. One uncheckable claim does not take down the run, and the report says so rather than silently dropping it.


Security

deepcheck reads adversarial input by design: anyone can upload a video, caption tracks are text a third party wrote, and the research brief contains material pulled from arbitrary web pages. Three controls are enforced in code, not asked for in a prompt.

Citations must have been retrieved. A source is admissible only if the search tool actually returned that URL. A URL the model produces from anywhere else — recollection, or text embedded in a hostile page — is dropped. A verification tool that can be talked into citing a URL is worse than no tool, because the citation is the part the reader trusts.

URLs are validated, not merely escaped. html.escape does not make a URL safe: <a href="javascript:alert(1)"> survives escaping untouched, and the HTML report is a self-contained file opened from disk. Only http and https reach an href; anything else renders as inert text marked link withheld. URLs with embedded credentials (https://[email protected]) and bidirectional override characters are rejected too.

Untrusted text is fenced. The transcript and the research brief are wrapped in labelled blocks behind an explicit boundary notice. Prompting is mitigation rather than guarantee — which is exactly why the two controls above exist.

Subprocess calls pass argument lists, never a shell string. The only user-controlled component of any command is the video ID, validated against [A-Za-z0-9_-]{11} with fullmatch after control characters are stripped.

CI runs pip-audit, CodeQL, and Bandit on every push, and re-runs the dependency audit weekly. Full threat model and reporting process: SECURITY.md.


Installation

If you would rather not use the quick start:

git clone https://github.com/jaytrivediSF25/deepcheck.git
cd deepcheck
pip install -e .

./scripts/install_upstream.sh          # macOS / Linux
.\scripts\install_upstream.ps1         # Windows

Credentials resolve in the SDK's own order — ANTHROPIC_API_KEY, then ANTHROPIC_AUTH_TOKEN, then an ant auth login profile. Transcription requires none of them.

Make targets: make dev · make upstream · make test · make check URL=…


Development

pip install -e ".[dev]"
pytest        # 98 passed — no network required

URL parsing · VTT parsing and rolling-window collapse · chunking · timestamp anchoring · claim prioritization · the compatibility shim · report rendering in all three formats · HTML escaping · CLI argument routing · API error messaging · and the trust boundaries above.


Licence

MIT — see LICENSE. youtube-deepsummary is a separate project under its own licence; vendored at install time rather than redistributed.

About

Transcribe a YouTube video and fact-check what was said, on top of youtube-deepsummary

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages