video → frames → LLM.

Turn a video, an animated GIF / APNG / WebP, or a live HLS / DASH / RTSP stream into a timeline of still frames plus the audio track and its transcript, so any LLM can watch + listen. Optional OCR, object gating, embeddings, PII blur, speaker diarisation — all opt-in.

npm i -g peepshow
Install guide View on GitHub →

npm versionNode 22 or later MIT license

drop · process · ask
peepshow ./demo.mp4 scene detection + dedup · 6 frames · 0.8s
Container
Duration
Resolution
Codec
Director
Studio

extracted 6 frames fed to LLM as images ready for analysis_

Who it's for

LLMs can read images. Your footage is a sequence.

Whatever's in your video — a bug, a break-in, a lecture, an exploit — peepshow turns it into still frames an LLM already knows how to reason about.

A QA engineer screen-records a flicker. A designer sends a 12-second Loom. A user uploads a .mov with the frame that breaks everything. peepshow turns that clip into scene-aware stills so the model sees the bug frame-by-frame.

# drop a repro into Claude — the hook does the rest
peepshow ./bug-repro.mov --strategy scene --max 12
  • 12 stills across the scene changes
  • JSON metadata the LLM can cite — timestamps, scores, tags
  • Piped through /peepshow:slides automatically

See all use cases →

one slideshow · any LLM · any video

Why peepshow?

Drag & drop — same UX as images

Drop an .mp4, .mov, or .gif into the Claude prompt. A UserPromptSubmit hook detects the path and auto-invokes /peepshow:slides. No slash command required.

Scene-change detection

Defaults to ffmpeg's select='gt(scene,0.3)' filter — catches visually distinct moments, not fixed-fps noise. Falls back to interval sampling for short clips.

Audio extracted too

Second ffmpeg pass writes a compact mono 16 kHz audio.m4a next to the frames, plus loudness peak + silence ratio. GIF / APNG / animated WebP skip cleanly. Opt out with --no-audio.

Local transcription

If whisper.cpp is on PATH, peepshow auto-transcribes the audio — nothing leaves your machine. Swap to openai / groq / deepgram / assemblyai in one flag. Transcript flows to every sink. Setup guide →

95 built-in sinks

Every run can fan out to databases, object stores, vector DBs, issue trackers, chat, and wiki systems. All tested, all shipped on npm. Full list →

Conditional routing

Sinks fire only when the input matches — --when director=Kubrick, --when path=/Volumes/Work/, --when extension=mp4,mov. Turns peepshow into a smart router.

Hardware-accelerated

VideoToolbox on macOS, VAAPI on Linux, D3D11VA on Windows — auto-detected. Prefers native ffmpeg over the bundled static build for extra speed.

Live statusline badge

[PEEPSHOW:decoding:42%] mid-run, [PEEPSHOW:5frm:scene:system] after, [PEEPSHOW|3s] with three auto-sinks armed.

Works with any LLM CLI

Ships native agent manifests for Claude · Cursor · Windsurf · Cline · Codex · Gemini. Integration snippets for aider · llm · Copilot · Continue · Cody · Zed in the docs.

Container metadata

Title, director, producer, show, genre, creation time — every tag inside the video's container flows into the JSON the LLM sees. Ground answers in what the video says it is, not just what's on screen.

The [PEEPSHOW] statusline badge

A one-line live indicator rendered in the Claude Code statusline — shows whether peepshow is idle, probing ffmpeg, decoding, or done. Also reports which sinks fired, how many frames were extracted, which strategy won.

~/projects $ [PEEPSHOW:idle] rotates every ~1.4s · real peepshow states

Rich states out of the box: [PEEPSHOW:decoding:42%] mid-run, [PEEPSHOW:6frm:scene:system|3s] after (six frames, scene-detection won, used native ffmpeg, three auto-sinks armed), [PEEPSHOW:sink:slack:posted] when a sink succeeds. All readable at a glance without leaving the editor.

More things peepshow does that aren't obvious

Auto-sinks that survive sessions

peepshow sinks add obsidian once — every future run fires it. Config at ~/.peepshow/sinks.json. The statusline shows how many are armed: [PEEPSHOW|3s].

Conditional routing via --when

--when director=Kubrick, --when extension=mp4,mov, --when path=/Volumes/Work/. Sinks only fire when the input matches. ANDed within a sink, ORed across values.

Container metadata flows through

Title, director, producer, show, genre, creation time — every tag inside the video's container reaches both the LLM and each sink's payload. --when key=value can match any of them.

Four emit formats

--emit paths (default), --emit json (structured), --emit markdown (human-readable), --emit caveman (token-compressed via caveman). Pair with any agent pipeline.

ffmpeg selection heuristic

PEEPSHOW_FFMPEG env → native ffmpeg on PATH → bundled ffmpeg-static. System build wins because brew/choco/apt ship with VideoToolbox, NVENC, QSV, VAAPI, D3D11VA. Static build is the zero-config safety net.

Written-your-own sinks

A sink is any executable that reads the --emit json payload on stdin. Shell, Node, Python, Go, Rust — doesn't matter. Register persistent ones with peepshow sinks add-cmd 'your-cmd'.

Compressor auto-detect

Install caveman on PATH — peepshow picks it up and auto-compresses output unless you've explicitly set --emit. Opt out with PEEPSHOW_AUTO_COMPRESS=0.

GIF, APNG, animated WebP

Same pipeline, different container. Meme-length loops and multi-frame screenshots all flow through the same scene-detect → prune → sink pipeline.

Install

Two steps. Runtime from npm, plugin from GitHub.

1. Runtime from npm

npm i -g peepshow            # adds peepshow + all peepshow-sink-* bins to PATH
npx peepshow ./video.mp4     # one-shot, no install

Optional native ffmpeg for faster hardware decoding:

brew install ffmpeg          # macOS
choco install ffmpeg-full    # Windows
sudo apt install ffmpeg      # Debian / Ubuntu

Skip entirely — peepshow bundles ffmpeg-static as a fallback.

Optional whisper.cpp for local audio transcription (no cloud, no API keys). When it's on PATH, peepshow auto-enables transcription — no flag needed:

brew install whisper-cpp     # macOS — Homebrew
scoop install whisper-cpp    # Windows — Scoop
# Linux / other: prebuilt releases at github.com/ggml-org/whisper.cpp/releases

Prefer cloud? Skip the binary and pass --transcribe openai / groq / deepgram / assemblyai with the matching *_API_KEY env var. Explicit --no-transcribe turns it off entirely.

2. Plugin for Claude Code

claude plugin marketplace add t0mtaylor/peepshow
claude plugin install peepshow@peepshow-marketplace

Restart claude — the /peepshow:slides skill is live, and the drag-and-drop hook fires on every prompt.

Use anywhere

peepshow ./bug.mov                              # paths + human-readable stats
peepshow ./demo.mp4 --emit json | jq            # structured (includes audio + transcript)
peepshow ./loop.gif --emit caveman              # token-compressed
peepshow ./keynote.mp4 --sink folder:/shared    # fan out
peepshow ./talk.mp4 --transcribe openai         # cloud transcribe
peepshow ./clip.mp4 --no-audio --no-transcribe  # frames only

95 built-in sinks

Every extract can fan out to any number of destinations. A sink reads the JSON payload on stdin, does anything. Conditional matchers (--when) mean each sink only fires when the input fits.

Find the right sink

Pick what you want to do, then where your stack lives. Popular picks re-rank as you choose.

See all 95 sinks →

Popular sinks

Browse by category

See all 95 sinks →

Auto-sinks — configure once, fire forever

peepshow sinks add folder:/Volumes/Shared/peepshow
peepshow sinks add postgres
peepshow sinks add-cmd 'node ~/scripts/obsidian-sync.js'
peepshow sinks list                                # see what's active

peepshow ./any-video.mp4                           # all three fire
peepshow ./one-off.mp4 --no-auto-sinks             # skip for this run

Conditional routing — --when

peepshow sinks add folder:/Volumes/Family --when extension=mp4,mov
peepshow sinks add postgres --when path=/Volumes/Work/
peepshow sinks add folder:/Cinema/Kubrick --when director=Kubrick --when genre=Thriller
peepshow sinks add-cmd 'node x.js' --when filename='*vacation*'

HTML report per run

Every extract writes a self-contained report.html + manifest.json next to the frames. Frames grid · transcript · sink fan-out · LLM analysis. Append-only run history at ~/.peepshow/runs/index.ndjson. The agent that watched the video pipes its synthesis back so the next viewer sees the analysis without re-running the model.

In every report

  • Frames grid w/ lightbox + keyboard nav (//J/K/Esc)
  • Summary line + full transcript text
  • LLM analysis section w/ provider + model badges
  • Sink fan-out w/ status filter (✅/❌/⏭)
  • Stats grid + raw JSON tree (collapsible)

Opt-out flags

--no-report          # skip HTML
--no-manifest        # skip both
--no-index           # skip ndjson
--report-dir <path>  # override location
--report-open        # open in browser

Inspect runs

peepshow runs list
peepshow runs show <id>
peepshow runs prune
peepshow report <dir>

Closing the loop — LLM annotates the report

When peepshow runs inside an LLM workflow (Claude Code · Cursor · Windsurf · Cline · Codex · Gemini), the LLM consuming the frames pipes its understanding back. Every supported agent ships with the annotate instruction wired in.

echo '{"summary":"<2-4 sentences>","provider":"claude-code","model":"claude-opus-4-7"}' \
  | peepshow report annotate "<outputDir>"

Open the Report walkthrough → docs/REPORT.md

Agent support

Native manifests for every major LLM CLI. Install peepshow once; every agent picks it up.

AgentManifestInstall
Claude Code.claude-plugin/ + skills/ + hooks/claude plugin marketplace add t0mtaylor/peepshow
Cursor.cursor/rules/peepshow.mdcpicked up when peepshow is installed into the project
Windsurf.windsurf/rules/peepshow.mdsame
Cline.clinerules/peepshow.mdsame
Codex CLI.codex/hooks.json + .codex/config.tomlSessionStart hook announces peepshow; invoke via Bash
Gemini CLIgemini-extension.json + GEMINI.mdpoint Gemini at the repo as a custom command
Codex agents / Zed AIAGENTS.mdconvention-based pickup
Generic agents registry.agents/plugins/marketplace.jsonnpx skills add t0mtaylor/peepshow (once supported)

Deep-dive each agent integration →

Plus documented integration snippets for Copilot CLI · aider · llm · Continue · Cody · Zed AI · Perplexity · Ollama in docs/INTEGRATIONS.md.

FAQ

Does it work without ffmpeg installed?
Yes — npm i peepshow pulls in ffmpeg-static as a fallback. Native ffmpeg (via brew / choco / apt) is preferred for hardware decoding but optional.
What happens when I drop a video into Claude Code?
A UserPromptSubmit hook spots the video path, injects a reminder into Claude's context, and Claude auto-invokes /peepshow:slides <path>. Frames are extracted, read as images, and Claude answers — without you typing the slash command.
Are static images handled too?
No — static JPG / PNG / WebP are already readable by every LLM. peepshow only runs for things with multiple frames across time: video and animated images.
Does peepshow extract audio too, or just frames?
Audio too, as of v0.4.0. When the input carries an audio stream (MP4 / MOV / WebM / MKV), a second ffmpeg pass emits a compact mono 16 kHz AAC audio.m4a next to the frames and probes loudness peak + silence ratio. Animated GIF, APNG, and animated WebP skip cleanly — those formats can't carry audio. Opt out per-run with --no-audio or globally via PEEPSHOW_AUDIO_ENCODER=off.
Is the spoken audio transcribed?
If whisper.cpp is already on your PATH, yes — transcription runs automatically with the base.en model and the transcript lands in the JSON payload under audio.transcript. No whisper.cpp binary? peepshow skips silently; frames + audio still emit. Prefer a cloud provider or your own setup? Switch via --transcribe openai|groq|deepgram|assemblyai|custom, or force off with --no-transcribe.
Where do API keys live for the cloud transcribers + sinks?
On your machine, in your own environment variables. peepshow never forwards credentials to peepshow.dev, the author, or anywhere else — the CLI reads OPENAI_API_KEY, DEEPGRAM_API_KEY, SLACK_WEBHOOK_URL, etc. locally and talks to those services directly from your terminal. The static site at peepshow.dev is pure documentation; there is no backend to phone home to.
What do all 95 sinks actually do?
Each sink pipes a peepshow run into a specific downstream system. Browse by category on the sinks hub, or use the use-case finder — pick what you want to do (search / alert / archive / memory / whiteboard / analytics / compliance / LLM pipeline / review / workflow) and where your stack lives (cloud · self-hosted · LLM · local) and it ranks the matching sinks and hands you the CLI command.
Where does the runtime code come from?
npm. The public GitHub repo ships only manifests, hooks, and docs — no compiled code. That keeps the source trusted (versioned on npm with integrity hashes) and the GitHub surface clean.
Can I write my own sink?
Any executable that reads the --emit json payload on stdin is a valid sink — bash, Node, Python, Go, Rust, whatever. Register persistent ones via peepshow sinks add-cmd 'your command'. See the sink spec.
What is peepshow serve?
A local HTTP dashboard for the run history. Run peepshow serve and visit http://127.0.0.1:7331/ — the /runs page lists every extract with thumbnails, filter chips (status / sinks / callers / tags / auto-tags), search, and a row/card view toggle. Each run links to a per-run report with the original video preview, frames + per-frame captions, and inline tag editor. Auto-tags (has-analysis, partial-captions, portrait, 1080p, etc.) drive the filter chips for free. Loopback-bound by default; remote bind needs --token. Full reference: docs/SERVE.md.
Does peepshow phone home? How do I turn it off?
By default, yes — an anonymous beacon (just version + OS family + outcome, no paths or payload) goes to Matomo + GA4 after each run, and peepshow serve pages load the same trackers consent-gated. Anonymous uuid lives in ~/.peepshow/anon-id. Four ways to disable:
  • peepshow config set telemetry off — persistent
  • PEEPSHOW_TELEMETRY=0 — per-invocation env
  • DO_NOT_TRACK=1 — honoured globally
  • "Reject" on the peepshow serve consent banner — disables page analytics
Sink interaction logs (~/.peepshow/sink-log.ndjson) and the run history are local only — never beaconed. Full reference + every switch in docs/PRIVACY.md.
What can I do via the peepshow runs subcommands?
peepshow runs list prints recent runs; show <id> dumps a manifest; prune [--keep N] removes dead-outputDir entries (--keep caps the index to N newest); repair emits a worklist of runs missing LLM analysis (and --apply takes annotations back via stdin); dedup [--all|--runId X] [--dry-run] re-runs the perceptual-hash pass on existing runs that were extracted with --no-dedup. The repair + dedup flows pair with the peepshow runs first-run nudge from peepshow serve.
How does it pair with caveman mode?
peepshow ... --emit caveman prints an ultra-terse one-line summary + paths, designed for token-compressed LLM setups like JuliusBrussee/caveman. Saves ~70% of peepshow's preamble tokens.
Where can I see what's in each release?
peepshow.dev/releases mirrors CHANGELOG.md from the repo. User-facing changes only — new sinks, new features, opt-in behaviour — so it's easy to skim before upgrading. The site also shows the version + git short SHA in the footer and in <meta name="peepshow:version"> on every page; when a new build lands while you have a tab open, a small banner above the cookie bar offers Reload.
Which LLMs does peepshow work with?
All multimodal ones. Gemini reads video natively — peepshow caps the token cost and handles animated GIF / APNG / WebP. Claude (Opus 4.7 / Sonnet 4.6 / Haiku 4.5), GPT-4o / GPT-5, and Grok have image-only vision — peepshow is the bridge to video. Pixtral, Qwen2.5-VL, DeepSeek-VL2, and local models (Llama 3.2 Vision via Ollama / LM Studio / llama.cpp) all read the same frame bundle. Per-model guides with token math, install snippets, and frame-strategy presets: peepshow + every LLM →.
How do I do X with peepshow? (GIF, YouTube, Loom, CCTV, Notion, Slack…)
The how-to hub has copy-paste workflows for the common tasks: GIF → LLM, YouTube → LLM (yt-dlp + peepshow), Loom → LLM, transcribe locally with whisper.cpp, CCTV analysis, dashcam review, video → Notion, video → Slack, → Obsidian, → SQLite, screen-recording bug repros, plus APNG + animated WebP handling. Each page is a HowTo-schema'd step list with running commands.
How does peepshow compare to native video on Gemini / hand-rolled ffmpeg?
Honest side-by-side at peepshow vs. Four comparison pages: vs Gemini native video (token-cost ceiling, animated formats, audit trail), vs hand-rolled ffmpeg (yes you could write it yourself — peepshow already did, plus 71 sinks), vs OpenAI video (OpenAI has no native video — peepshow is the bridge), vs whisper.cpp standalone (whisper alone is fine if you only need a transcript). Each page has a comparison table, "pick peepshow when…" / "pick the alternative when…" bullets, and a verdict.
Wait — is this connected to the Channel 4 TV show Peep Show?
No. peepshow LLM is a developer CLI for extracting video frames for large language models. It has no affiliation with the British sitcom Peep Show (© Objective Productions / Channel 4 Television Corporation, created by Sam Bain & Jesse Armstrong) or its cast. Full disclaimer on the Terms page. Looking for the show? Head to channel4.com.