DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Destinhue /

Destinhue/dsh-video-vision

Verified

Look at a video instead of being told about one: a contact-sheet storyboard tool for DeepSeek Harness (video_probe / video_storyboard / video_zoom).

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@d846b4e3

dsh-video-vision

Look at a video instead of being told about one: one addressable storyboard grid per view, tuned for deepseek-v4.1-flash vision. Drop-in replacement for dsh-video-storyboard / dsh-video-understand — same three tool names, same argument shapes, a different engine underneath.

video_probe       metadata + a reading plan, no frames decoded
video_storyboard  one contact sheet + a text index binding every cell to a timecode
video_zoom        1-9 large cells over a short window

Design notes with the measurements behind every decision: docs/DESIGN.md. Measured behaviour, per-clip tables and error margins: docs/EVIDENCE.md.

Six text cards, each a cell of one contact sheet
Clip D: six slides that differ only in their text block. Six cells, 359 vision tokens, every bullet readable.
Storyboard with an audio envelope band along the bottom
Clip G: nine cells plus an audio band — teal envelope, orange speech spans, yellow cut ticks. One image, 542 tokens.
Three large cells across a one-second crossfade
video_zoom over a 1 s crossfade: before, mid-transition, after — three cells at full width.

Why it is better

It sees cuts a luma-only detector cannot. Every frame is reduced to a 12x7 grid of luma and chroma block means in a single ffmpeg pass, so a cut between two shots of identical brightness — a day scene to a differently graded day scene — is still a cut. Jumps are then classified five ways: cut, partial (a slide changing only its text block), overlay (a caption appearing), light (a lamp switching on) and noise; only the first two become cells.

Flashes and fades are not shots. A one-frame white flash restores the picture within three frames and is rejected; a dissolve is recognised as a transient — a plateau of change that stops afterwards — so it yields one boundary instead of a burst of tiny shots, while a slow pan yields none.

The sheet is sized per cell, not per sheet. The host bills an image by area (tokens = gridH*(gridW+1)+2, 1024 cap). Ten cells in a 1.25 Mpx sheet cost exactly what four cost, so the budget buys more moments at smaller cells until cells stop being legible — six cells get ~350 tokens, twenty get ~1000, and a cheap clip stays cheap.

Cells are earned, not evenly spaced. One cell per shot is the floor; extra cells go only to shots that are long enough and have a frame 2.5x further from every chosen frame than the "same picture" threshold. A static talking head never earns a second cell; a 30 s pan spreads over four.

Decoding is batched. Six cell extractions share one ffmpeg process (several inputs, one output each), three processes in flight — a 20-cell sheet costs 2-4 process launches instead of 60.

No prose in results. Reading instructions live in the tool description, which sits in the cached prompt prefix and is paid once; the result itself is only the index and the image. textOnly + cellRange pages a long film at zero image cost, and repeat calls are cached.


Numbers

Nine clips, frame-exact ground truth, one machine, driven directly as a module (bench/final.mjs). Protocol, per-clip tables and threats to validity: docs/EVIDENCE.md.

value
wall clock, cold 634 ms (581-648 ms over runs)
wall clock, cached 411 ms (370-411 ms)
moments shown 67 cells
vision tokens 3 648
tokens per shown moment 54.5
cut F1 (micro, 0.5 s) 0.872
cut recall / precision 1.000 / 0.774
shot coverage 0.963
text in the result 477 chars

On a 13-minute film one call returns one image: 28 cells at 331x186, 1 024 tokens, 6.2 s, ~1 353 tokens of context including the index. Full table in EVIDENCE.md.

Where this plugin is weak is stated there rather than rounded away: a 1 s crossfade is reported as one dissolve boundary and scored as a false positive against a ground truth that calls it zero cuts, and two clips in the corpus change their whole frame between sections, so they read as cuts. Real-world spot checks with screenshots of what was actually readable: docs/REAL-WORLD.md.


Install

The plugin has no runtime dependencies. It needs ffmpeg, and uses ffprobe when present (without it, metadata is parsed from the ffmpeg -i banner).

cd /path/to/dsh-video-vision
node bin/install.mjs                 # patch ~/.dsh/profiles/desktop, then restart DSH
node bin/install.mjs --dry-run       # show what it would change, touch nothing
node bin/install.mjs --remove        # uninstall

The script does the three things by hand that a pnpm install would do — add "dsh-video-vision": "link:<abs path>" to the profile's dependencies, append the package to dsh.profile.bundles, and junction the package into the profile's node_modules. It is idempotent; --copy vendors a snapshot instead of linking. Either way the app must be restarted afterwards: cordis.patch.yml inserts the plugin into the harness tree at boot, which is what makes the tools appear.

ffmpeg

Resolution order (first hit wins): $DSH_FFMPEG_PATH, $DSH_VIDEO_FFMPEG, $FFMPEG_PATH, the package's own vendor/bin, ffmpeg-static / @ffmpeg-installer in node_modules, WinGet Links, C:\ffmpeg\bin, scoop shims, the npm roaming prefix, /usr/local/bin, an imageio-ffmpeg wheel inside ~/.dsh/dsh-runtimes/*/, then plain ffmpeg on PATH. $DSH_FFPROBE_PATH / $FFPROBE_PATH do the same for ffprobe.

Verify a machine end to end (no video needed — it builds one):

node bin/selfcheck.mjs --smoke          # 16 checks (17 with ffprobe), builds its own clip
node bin/selfcheck.mjs --smoke --host   # + validates all three tools against the
                                        # installed Harness's schema engine (29 checks)
node --test test/                       # 159 tests, no network, hermetic temp dirs

--host runs the exact assertions ctx.tools.register runs, then calls every tool once and validates the result the way the runtime does. Run it with Electron's node (ELECTRON_RUN_AS_NODE=1) — plain node cannot read inside app.asar. This is not ceremony: a tool that returns { text: {…} } instead of { text: "…" } is rejected by the host with "value.text" must be a string, and nothing local notices until a user calls it.


Use

video_storyboard input:"C:\clips\pv.mp4"
pv.mp4 · 0:00:40.00 · 1280x720 · 30fps · 9 cuts · median shot 4.00s
sheet 1 · 2x5 grid · 942x1328 · 10 cells · window 00:00.00-00:40.00
cols: cell · pos · span · (cuts) · frame@
1 r1c1 00:00.00-00:04.00 @00:01.98
2 r1c2 00:04.00-00:07.68 @00:05.85
...
audio: speech 42% · -18.2 dBFS · loudest 0:41.20 · 3 silences · 12 onsets
transcript: subtitle-track · 18 segments

The image arrives in the same tool result. r2c1 is row 2, column 1 from the top-left; the number burned into each cell is the cell index.

argument meaning
input file path, or a direct http(s) URL that ffmpeg can fetch
start / end / duration read one window instead of the whole file
detail low (≤6 cells) · auto (≤20, default) · high (≤28)
cells force an exact count (1–24)
pick sharp (default) or middle
textOnly text index only: no image, no decode
cellRange with textOnly, [first, last] to list

video_zoom takes the same window arguments plus cells (1–9) and at; a single cell is returned at full resolution.


What it cannot do

  • No OCR and no ASR. Reading text is the model's job, done from pixels; speech is not transcribed unless the file carries a subtitle track of its own. There is no model download, no API key, no network call.
  • No downloader. Site pages (YouTube, Bilibili) are not resolved: that needs yt-dlp, which this plugin neither ships nor shells out to. Download the file and pass the path. Direct http(s) URLs to a media file do work, through ffmpeg itself.
  • Cuts are detected, not understood. A whip pan and a cut can look alike in a 96x54 grid; the classifier prefers "not a cut" when the change is local.
  • It is not a player. One call = one still image of up to 28 moments.

Layout

src/index.js     plugin entry: registers the three tools, resolves binaries
src/tools.js     tool specs (descriptions are prompt-cached, so they are short)
src/pipeline.js  orchestration + analysis cache
src/scan.js      one-pass decode, cut/overlay/light/flash/dissolve detection
src/layout.js    shot building, cell budget, grid geometry, token math
src/frames.js    batched frame extraction
src/sheet.js     contact-sheet compositor (no image library, raw RGB)
src/png.js       PNG encoder (zlib)
src/manifest.js  the text index
src/timeline.js  audio envelope, speech spans, subtitle extraction
src/media.js     ffmpeg/ffprobe discovery, probing, process helpers

docs/EVIDENCE.md     measured behaviour, with the scripts that produced it
docs/DESIGN.md       why each threshold exists; mechanisms that were rejected
docs/REAL-WORLD.md   three untouched screen captures, inspected by eye
docs/TOKENS.md       how DeepSeek prices an image, and what the grid size costs
bench/               the corpus, its generator, and every script that made a number
test/                159 unit tests, no network, hermetic temp dirs

Thanks

The idea this plugin starts from — one contact sheet with burnt-in cell indices, plus a text index binding cells to timecodes, put together without an image library — comes from SAOS-3624/dsh-video-storyboard, whose documentation is the clearest statement of why it works. Thank you.

This is an independent implementation: no code is copied, and detection, cell budgeting, decoding and composition differ. The three tool names, their argument shapes and the manifest grammar are kept compatible on purpose, so it can stand in where that plugin is already installed. See NOTICE.

MIT licensed. ffmpeg is GPL/LGPL depending on the build you point it at; this package ships none.

—/ 5

No ratings yet

Verified DSH bundle

Commit d846b4e38fe6

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout