Skip to content

Releases: PBNZ/watch-local

v0.7.2 -- documentation corrections

Choose a tag to compare

@PBNZ PBNZ released this 27 Jul 05:21

Documentation-correction release. No behaviour changes — but two
explanations published in 0.7.1 turned out to be wrong, both in the
direction of sounding more certain than the evidence supported.

"The workstation runs the CUDA build forced onto CPU" — refuted

0.7.1 offered this as the leading explanation for a 16-core workstation
trailing a 6-core laptop, and told readers not to treat -Device cpu on
a GPU box as representative of a CPU-only install. A second, genuinely
CPU-only runtime was provisioned alongside the GPU one to test it:

  • There is no separate CPU build. PyPI ships one ctranslate2 wheel
    with CUDA compiled in, and all four native binaries are
    byte-identical across the two installs (sha256).
  • A CPU run never loads the NVIDIA libraries. cuda_paths puts them
    on the DLL search path even for -Device cpu, but after a real int8
    decode cublas64_12, cublasLt64_12, cudnn_ops64_9,
    cudnn_graph64_9 and nvrtc64_120_0 are all unloaded.
  • The two stacks measure the same. Pinned to identical package
    versions so they differed by exactly the three nvidia-* wheels, four
    of five models produced identical segment counts and identical WER to
    the decimal.

-Device cpu on a GPU machine is a faithful CPU-only measurement, so
contributors do not need GPU-free hardware to submit CPU numbers.

large-v3's poor WER had the wrong cause

0.7.1 explained it as the model "transcribing more literally — ~5% more
words than the captions contain". Those surplus words are repeated text
from decodes that got stuck
: the runs that produced them tripped
watch-local's own repetition detector 1–5 times each.

On the reference fixture large-v3 degenerated in 8 of 8 GPU runs and
2 of 3 CPU runs
. No other model has tripped the detector once. The
single clean run emitted 5,713 words against a 5,778-word reference —
fewer than the captions — and scored 5.9% rather than 13–17%.

Root cause is upstream and verified in faster_whisper/transcribe.py:
the default temperature ladder retries a segment judged "too repetitive"
(compression_ratio_threshold=2.4), and above temperature 0 decoding
switches from beam search to stochastic sampling — so identical audio
legitimately yields different transcripts, segment counts and runtimes.

large-v3 figures are now published as ranges with run counts attached.
tiny through medium reproduce to within contention noise and are
unchanged.

Also fixed while re-checking every number against the raw data

  • "the laptop is roughly 2x faster than the i9" was stale — that
    ratio came from the 16-thread sweep 0.7.1 withdrew. The default-thread
    tables give 1.30–1.58x, and the page contradicted itself twice.
  • "large-v3 on GPU varied 16% between two identical runs" predated the
    six-run repeat table added here. Eight GPU runs now span 255–414 s —
    the slowest 62% slower than the fastest.
  • The order-reversed venv pairing now reports all three pairs,
    including the contaminated small pair that moved against the
    conclusion. Omitting it would have been the same failure this release
    is retracting.
  • /watch's runtime CPU warning still carried the old 57–75 min range;
    it now says 49–75 min and attributes the spread to run-to-run decode
    instability rather than to the two machines measured.
  • The large-v3 row of the thread-count table now carries a warning: its
    +28% is two single degenerate draws, and two runs at a fixed thread
    count already span 51%.

Full detail: docs/benchmarks.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01TtKPaytUeHUKADa7UosGzd

watch-local 0.7.1

Choose a tag to compare

@PBNZ PBNZ released this 25 Jul 22:17

watch-local 0.7.1

Correction release. Re-running the benchmarks properly — all five models, CPU and GPU, on the one reference video every published table is supposed to use — overturned a performance claim from 0.7.0 and uncovered a caption-parsing bug that had been quietly skewing /watch's own transcript comparison.

Caption HTML entities were never decoded

parse_vtt stripped cue tags but left entities encoded. Creator captions pad line ends with  , so every word tokenizer downstream read nbsp as a real word — 992 of 6,770 reference words on the reference video, more frequent than "the" (342).

That inflated benchmark WER (small scored 19.7% instead of 6.1%) and, more importantly, depressed /watch's own creator-vs-Whisper length_ratio / word_jaccard / trigram_jaccard — which can push a perfectly good transcript pair into the report's "major divergence" callout. Entities are now unescaped after tag-stripping (so a decoded < can't be re-read as markup), U+00A0 is folded to a space, and whitespace collapsed.

0.7.0's CPU thread auto-sizing made things 15–47% slower — withdrawn

0.7.0 sized unpinned CPU runs to the physical core count and published it as "~24% faster". A five-model A/B on the reference video — same machine, same audio, only the thread count changed — says the opposite:

Model 4 threads (restored default) 16 threads (0.7.0) Cost
tiny 121.2 s 178.5 s +47%
base 203.8 s 280.5 s +38%
small 558.7 s 719.1 s +29%
medium 1,544.2 s 1,781.7 s +15%
large-v3 4,479.5 s 5,739.1 s +28%

Mean cores actually used was ~3 in both columns. Asking for 16 didn't deliver 16 — it delivered the same ~3 plus the overhead of coordinating threads with nothing to do, because Whisper's autoregressive decoder doesn't parallelise. The idle cores #34 reported are idle because they cannot be used, not because of a bad setting.

The original "+24%" sweep has never reproduced — not on the reference video, not on the original clip, not after a 15-minute idle period — and its companion core-count figures predate the process-tree recycled-PID guard. An unpinned CPU run now uses faster-whisper's own setting again. W_CPU_THREADS, setup.ps1 -SetCpuThreads, and benchmark.ps1 -CpuThreads all remain for anyone whose hardware disagrees.

If you want CPU transcription to finish sooner, pick a smaller model. That lever is worth 37×; the thread count is worth nothing reliable.

Published benchmarks now use one video

Every table in docs/benchmarks.md is measured on the same clip, so they can finally be compared: GPU and CPU on an i9-12900K + RTX PRO 5000, alongside the contributed 6-core laptop. The fixture URL is now spelled out in the script help and the docs, not just buried in a parameter default.

A finding worth the read: the 6-core laptop beats the 16-core workstation by ~1.5× on identical audio. Do not size a CPU transcription box by core count.

Added

  • benchmark.ps1 -SkipWarm — skip the untimed warm pass, halving a run, when every model is already cached. A CPU sweep including large-v3 otherwise spends an hour just warming.

Install

/plugin marketplace add PBNZ/watch-local
/plugin install watch-local@watch-local

or use the attached zip for offline/pinned installs.

watch-local 0.7.0

Choose a tag to compare

@PBNZ PBNZ released this 25 Jul 12:20

watch-local 0.7.0

Performance and measurement release: CPU transcription now uses the whole machine, 2× hallucination loops no longer slip through unflagged, and there is a benchmark harness so none of this has to be taken on faith.

Closes #34, #35, #36, #37.

CPU transcription used ~4 threads on any machine (#34)

faster-whisper's cpu_threads=0 default resolves inside ctranslate2 to "OMP_NUM_THREADS or 4" — a constant, not a function of the host — so a 12-thread laptop transcribed on 4 threads and left the rest idle.

Unpinned CPU runs now size themselves to the machine's physical core count, which measurement puts on the plateau. On a 16C/24T box, same clip and model:

cpu_threads Time vs the old default
0 (= the old default, 4) 169.2 s
8 127.0 s +25%
16 (physical cores — the new default) 128.8 s +24%
24 (every logical CPU) 151.7 s +10%

Note the last row: piling onto SMT siblings gives most of the win back, so "use everything" is nearly as bad as using four. Physical cores also lands correctly on non-SMT machines like Apple Silicon. The count is capped by process affinity, so taskset and cgroup limits still hold. Pin it yourself with setup.ps1 -SetCpuThreads N, or -UnsetCpuThreads for auto.

2× repetition loops passed through silently (#35)

The collapser only acted on runs of 3 or more, so a doubled hallucination was neither collapsed nor recorded — the report showed no "unreliable span" caveat.

A doubled segment is now caught when its text could not physically have been spoken in the time it claims. Two details matter: the rate is judged on the fastest occurrence rather than the run average (the usual loop is a normally-paced segment followed by a near-instant copy, which an average hides), and the ceiling sits above the fastest documented human speech, so rapped or chanted repeats survive while a stuck decode overshoots it tenfold. Replayed over 8,477 segments of real transcripts it fires 4 times — every one an unmistakable loop.

A benchmark harness (#37)

scripts/benchmark.ps1 measures per-model wall time, speed vs real-time, mean core usage, peak RSS, and WER against captions, emitting report.md + results.json + results.csv.

benchmark.ps1 -Slug last -Models small,medium          # reuse a watched video
benchmark.ps1 -Slug last -Device cpu -CpuThreads 0,8,16 # thread scaling

Resource sampling is per-process and cross-platform. Two things it gets right that are easy to get wrong: it walks the process tree (on Windows the uv venv's python.exe is a trampoline whose child does all the work, so measuring the launched PID reports ~5 MB and ~0 CPU), and it ignores processes older than the run so a recycled PID cannot fold a stranger's CPU into your numbers.

Every report also states its caption provenance — WER against human-edited creator captions is a quality signal, while WER against the platform's own auto-captions only measures drift between two recognisers.

Published benchmarks and per-model guidance (#36)

docs/benchmarks.md publishes CPU reference numbers (contributed, with hardware and caveats attached) alongside GPU numbers, and docs/benchmarking.md documents the method. The headline for GPU-less machines: large-v3 ran 57 minutes for a 33-minute video and gained nothing over medium on clean English. /watch-setup and /watch's warning now cite those measured times instead of a vague "slow".

Install

/plugin marketplace add PBNZ/watch-local
/plugin install watch-local@watch-local

or use the attached zip for offline/pinned installs.

watch-local 0.6.2

Choose a tag to compare

@PBNZ PBNZ released this 19 Jul 10:18

watch-local 0.6.2

Patch release: the SessionStart hook no longer requires a system Node (#39).

Native Claude Code installs ship as a self-contained binary that bundles its own runtime and never puts node on PATH, so the hook's node check-setup.mjs invocation failed with exit 127 on every session start — silently killing both the "run /watch-setup first" nudge and the Linux/macOS missing-pwsh pre-flight. The hook is now a POSIX sh script invoked with sh, which is present wherever Claude Code can run hooks at all: /bin/sh on Linux/macOS, Git Bash on Windows (a hard requirement of the Windows install). Behavior is unchanged; only the interpreter is. A new .gitattributes pins *.sh to LF so the script cannot check out with CRLF line endings.

See CHANGELOG.md for details.

Install

/plugin marketplace add PBNZ/watch-local
/plugin install watch-local@watch-local

or use the attached zip for offline/pinned installs.

watch-local 0.6.1

Choose a tag to compare

@PBNZ PBNZ released this 17 Jul 03:49

watch-local 0.6.1

Patch release so the distribution zip matches main exactly.

  • Get-PartialSHA256 now disposes its SHA256 hasher (#28) — the project's first community contribution, by @Dodothereal (PR #30). Thank you!
  • LICENSE copyright holder line corrected to PBNZ.

No functional changes beyond the above; see the 0.6.0 notes for the full hardening release this builds on, and CHANGELOG.md for details.

Install

/plugin marketplace add PBNZ/watch-local
/plugin install watch-local@watch-local

or use the attached zip for offline/pinned installs.

watch-local 0.6.0

Choose a tag to compare

@PBNZ PBNZ released this 17 Jul 01:59

watch-local 0.6.0

Pre-launch hardening release: every finding from the July 2026 public review sweep (issues #2#27) is addressed, plus community contributions (#28 via PR #30, thanks @Dodothereal; #25 groundwork nudged by PR #29, thanks @hansi2007).

Security

  • Prompt-injection hardening for the /watch report (#2): video-derived fields (title, uploader, transcripts) are neutralized and explicitly framed as untrusted data; SKILL.md instructs the agent to never follow instructions found in video content. Residual risk documented in SECURITY.md.
  • Scope-guard fixes (#8, #9): case-correct containment on Linux/macOS and working symlink/junction resolution on both PowerShell engines (fail-closed on unresolvable links).
  • Hardened CI: least-privilege workflow permissions + SHA-pinned actions (#23); the skill-safety scanner now scans every markdown file, detects homoglyph evasion, and uses a URL prefix allowlist (#22).
  • sha256-pin trust boundary documented precisely (#26); LICENSE retains the upstream author's copyright notice (#21).

Reliability

  • Staged UNC copies are reclaimed on every exit path; staging free-space check runs before the copy; new setup.ps1 -PurgeStaging (#5, #15).
  • Disk pre-flight measures the correct volume on Linux/macOS (#3).
  • save-here writes source-link.txt on the primary path, reports truthfully, tolerates old schemas, and honors the documented remove-canonical prompt (#4, #12, #20).
  • Merge-failed downloads are a hard error instead of a silent "no audio track" (#10); -Fps and -Screenshots input validation (#13, #14); CUDA probe survives quotes in install paths (#11); Rosetta 2 preflight on Apple Silicon (#19); model-name validation in onboarding (#16, #17); cross-platform build-zip (#18).

Testing

  • Confirmed destructive-purge deletion paths covered end to end (#7); Pester discovery errors now fail CI (#6); download hash-mismatch gate tested (#24); new weekly integration workflow; full suite verified on PowerShell 5.1 + 7, plus integration and a real /watch smoke run.

Full details in CHANGELOG.md.

Install

/plugin marketplace add PBNZ/watch-local
/plugin install watch-local@watch-local

or use the attached zip for offline/pinned installs.

watch-local 0.5.0

Choose a tag to compare

@PBNZ PBNZ released this 16 Jul 11:13

watch-local 0.5.0

First stable release of watch-local — a fully local Claude Code plugin for
/watch-ing videos (URL, local file, or SMB/UNC share). Downloads with yt-dlp,
extracts frames with ffmpeg, and always transcribes locally with
faster-whisper — NVIDIA GPU (NVDEC + CUDA) when detected, CPU otherwise. No
Docker, no admin rights, no cloud API keys.

Promoted from 0.5.0-rc.2 with no functional changes. The 0.5.0 line removed
Docker in favour of a self-provisioned portable runtime, added GPU
auto-detection, and fixed non-interactive onboarding — see
CHANGELOG.md
for the full list.

Install

From GitHub (easiest):

/plugin marketplace add PBNZ/watch-local
/plugin install watch-local@watch-local

From the zip (offline / pinned version): download
watch-local-marketplace-0.5.0.zip below, extract it into a stable folder, then
/plugin marketplace add that folder. Full steps are in the
README.

Then run /watch-setup once to provision the portable runtime and warm the
whisper model.

watch-local 0.5.0-rc.2

Pre-release

Choose a tag to compare

@PBNZ PBNZ released this 15 Jul 02:26

Bug-fix release on 0.5.0-rc.1, remediating a first-run report from a CPU-only work laptop.

Fixed

  • /watch-setup -Yes (non-interactive onboarding) failed at Step 1 on every machine. The wizard's child-spawn helper returned the child's stdout AND its exit code on one pipeline; setup.ps1 -DetectGpu always prints a JSON gpu block, so the "exit code" arrived as an array, the -ne 0 guard element-filtered it to a truthy collection, and the wizard aborted (with a garbled exit { ...json... } 0 message) before writing the setup marker -- even when detection succeeded, on GPU and CPU machines alike. Child spawns now go through _lib.ps1's new Invoke-WLChild, which sends child stdout to the host and returns only a scalar [int] exit code (Pester regression tests added).
  • Runtime provisioning died with "Get-FileHash is not recognized" under powershell.exe on hosts with a polluted PSModulePath (PS7 module dirs listed on the 5.1 path shadow the built-in Utility module with the incompatible PS7 copy). Download verification now hashes via .NET (Get-WLFileSHA256, no module dependency), and Get-WLPSEngine falls back to pwsh for child spawns when 5.1 cannot resolve Get-FileHash / Expand-Archive.
  • Silenced the harmless-but-alarming huggingface_hub cache-symlink warning (Windows without Developer Mode) via HF_HUB_DISABLE_SYMLINKS_WARNING=1 in the whisper worker env.

Changed

  • All documented launcher invocations (SKILL.md, command docs, smoke harness) now pass -NoProfile -- user profiles printed noise and started background tasks inside captured setup output. New SKILL.md troubleshooting entry covers the polluted-PSModulePath failure mode and pwsh -NoProfile as a supported engine on Windows.

Full changelog: v0.5.0-rc.1...v0.5.0-rc.2

watch-local 0.5.0-rc.1

Pre-release

Choose a tag to compare

@PBNZ PBNZ released this 14 Jul 10:22

BREAKING: Docker removed. The plugin now provisions its own fully portable runtime -- nothing to install on the host, and deleting one folder removes every trace. Built for machines that cannot run Docker Desktop (work-laptop licensing), and it turned out simpler AND faster.

Highlights

  • Self-provisioned portable runtime. /watch-setup downloads pinned, sha256-verified standalone tools (yt-dlp, ffmpeg, deno, uv) plus a uv-managed CPython venv with faster-whisper into %LOCALAPPDATA%\watch-local\runtime\ (XDG equivalent on Linux/macOS). No admin rights, nothing on system PATH, no registry entries. Uninstall = remove the plugin + delete that one folder.
  • Native GPU whisper via pip CUDA wheels (nvidia-cublas-cu12 + nvidia-cudnn-cu12). Verified on Blackwell sm_120: ~26% faster than the Docker path on the same audio (73.5s vs 99.2s wall, 97.4% word overlap, large-v3). NVDEC decode unchanged, now through a portable static ffmpeg.
  • Native GPU detection (host nvidia-smi + real NVDEC decode probe) with a new venv-level cuda_whisper signal -- a GPU whose CUDA libraries fail to load degrades to cpu/int8 instead of failing the run. Docker-era configs migrate automatically on their first /watch.
  • Clear PowerShell 7 guidance on Linux/macOS. The SessionStart hook is now a tiny Node script that tells you exactly how to install pwsh when it is missing (it is not preinstalled there) -- the sole non-Windows prerequisite.
  • grab-frames now works on UNC-source jobs (native ffmpeg reads \\server\share directly).
  • Runtime maintenance: setup.ps1 -UpdateYtDlp (self-update for YouTube breakage) and setup.ps1 -UpdateRuntime (re-converge to the pinned manifest).
  • Test matrix hardened: suite runs green under Windows PowerShell 5.1, pwsh-on-Windows, and pwsh-on-Linux; CI covers all three legs; new tests/linux/ container harness performed a real cold-install of the linux_x64 runtime plus integration and a live /watch smoke.

Full details in CHANGELOG.md.

Verified end-to-end on Windows 11 + RTX PRO 5000 Blackwell (GPU whisper + NVDEC + real /watch runs on URL/local/UNC sources) and on Ubuntu in a container (cold-install provisioning, CPU whisper, live smoke). 101 pytest + 62 Pester green on all three engine legs; strict plugin validation + safety scans pass. Still pre-release: macOS and locked-down work laptops have not yet had real-world runs.

watch-local 0.4.0-rc.1

Pre-release

Choose a tag to compare

@PBNZ PBNZ released this 14 Jul 01:33

GPU auto-detection, NVDEC-accelerated decode, and a fully working CPU-only mode (including non-Windows hosts).

Highlights

  • Reliable GPU detection, persisted in config. Setup (and the new setup.ps1 -DetectGpu) probes through docker itself: nvidia-smi for identity (name, VRAM, driver, compute cap) plus a real NVDEC decode test (h264_cuvid on a generated clip). Pre-upgrade installs migrate automatically on their first /watch.
  • NVDEC video decode for frame extraction. With a detected NVDEC-capable GPU, the tools/screenshots/grab-frames containers run with GPU access and ffmpeg decodes on the GPU (-hwaccel cuda, decode-only offload). This removes the multi-core CPU spike during frame extraction on long videos.
  • CPU-only mode -- the plugin now fully works without an NVIDIA GPU. New slim watch-local/whisper:cpu image (~1 GB instead of ~8.7 GB); whisper runs device=cpu / compute_type=int8. Missing GPU is no longer a fatal setup error.
  • Non-Windows hosts (CPU mode). Launcher scripts run under PowerShell 7 on Linux/macOS: XDG-based default dirs, engine-aware child processes, cross-platform hook. Newer and less battle-tested than the Windows path.
  • Report transparency. Every report carries a **Compute:** line; the **Whisper:** line states its device.
  • Repo: adopted the RepoKit living-docs pattern (docs/STATE.json + rendered state blocks + CI check).

Full details in CHANGELOG.md.

Verified end-to-end on Windows 11 + RTX PRO 5000 Blackwell (GPU mode incl. NVDEC probe + real /watch run) and via the CPU whisper container (cpu/int8). 101 pytest + 53 Pester green; strict plugin validation + safety scans pass.