Review and export 4-camera surround dash-recorder footage on your own machine — six synchronised views, a real 360° ground-plane stitch, and an ffmpeg exporter, with zero npm dependencies.
An 8×-sped tour of Cross → Solo → Command → Drive → Surround 360°. Full 28 s demo (MP4) →
A local web app: one Node file, a vanilla-JS frontend, no build step, no accounts, no telemetry. The server binds 127.0.0.1 only and never writes to your footage folder. Nothing leaves the machine.
Built by ABSG for the WTPN news van, because reviewing four parallel camera streams in an ordinary player is miserable — you scrub four files by hand and guess whether they line up. This treats the whole trip as one timeline: four cameras frame-locked, a real wall clock, mark in/out, hit export. Version 1.0.
You need Node.js 22+ and FFmpeg 6+ (both ffmpeg and ffprobe) on your PATH — details in Requirements.
git clone https://github.com/absginc/GMSurroundVision.git
cd GMSurroundVision
mkdir -p Recordings/SurroundVisionRecorder
cp /Volumes/SURROUNDVISION/*.mp4 Recordings/SurroundVisionRecorder/
node server.jsOpen the URL it prints, pick a trip, and you are in the player.
SurroundVision ready → http://localhost:4171
No install, no build, no watcher, no import step — the app just reads that folder. Stop it with Ctrl+C. Files must be named {CAM}_{YYYY}_{MM}_{DD}_T_{HH}_{MM}_{SS}.mp4; see Getting footage in. To persist settings, copy .env.example to .env and use Node's native env-file support:
cp .env.example .env # then edit .env
node --env-file=.env server.jsThe desktop app is the same player and exporter in a double-clickable shell — nothing to install first, no Node, no FFmpeg, no terminal. Two builds:
| Your Mac | Download |
|---|---|
| Apple Silicon (M-series) | GM-SurroundVision-<version>-arm64.dmg |
| Intel | GM-SurroundVision-<version>-x64.dmg |
- Download the DMG for your Mac from the releases page, open it, and drag GM SurroundVision to Applications.
- First launch: the app is not notarized yet (no Apple Developer account), so macOS will balk. Either right-click the app → Open → Open (needed once), or run:
xattr -cr "/Applications/GM SurroundVision.app"- On first run the app offers to use
~/Movies/GM SurroundVision/Recordingsor lets you choose any folder — a USB drive under/Volumes/…, the folder you copied clips into, wherever the recorder files live. You can change it any time: the empty trips screen has a Choose Recordings Folder… button, and it's in the File menu too. Exports land in~/Movies/GM SurroundVision/Exports(File → Open Exports Folder).
The app bundles its own FFmpeg (ffmpeg 6.0 via ffmpeg-static, ffprobe 8.1.2 from
Martin Riedl's builds — GPL builds invoked as separate
executables), both native arm64, so it runs without Rosetta. To build the DMGs yourself: cd desktop && npm install && npm run dist (Apple Silicon) or
npm run dist:x64 (Intel) — the first build fetches sha256-verified FFmpeg binaries per architecture.
Contents — Download for macOS · Features · Requirements · Where the footage comes from · Getting footage in · Configuration · Using the player · Exporting · Skins · How it works · Roadmap · Troubleshooting · Project layout
| Six views | Quad · Command · Drive · Solo · Cross · Surround 360° |
| Surround 360° | True ground-plane projection (per-camera homography), 3D orbit with presets, three style presets, live calibration drawer |
| Frame-locked playback | All four cameras driven off one virtual trip clock, held in sync across 5-minute segment boundaries |
| Wall-clock timeline | Real recorded time-of-day, segment ticks, hover thumbnails, in/out marking |
| Exporter | single · all · quad · command · drive · cross · surround · pip, speeds ¼×–4×, optional per-camera clips in the same job |
| Six skins | Broadcast · Night Ops · Dash · Daylight · DVR · Cinema |
| Zero dependencies | Node built-ins only. node server.js and you are running. |
| Node.js 22+ | node --version. Nothing to npm install — v22 is the tested baseline, and configuration leans on Node's native --env-file support rather than a dotenv package. |
| FFmpeg 6+ | Both ffmpeg and ffprobe. ffprobe measures clip durations (cached after the first scan); ffmpeg renders thumbnails and every export. Verify with ffmpeg -version && ffprobe -version. |
| A browser | Chromium-based recommended (Chrome, Edge, Brave, Arc): the Surround view leans hard on CSS 3D transforms with matrix3d on live <video> elements. Everything else works fine in any modern browser. |
Installing FFmpeg (it provides both binaries):
| Platform | Command |
|---|---|
| macOS | brew install ffmpeg |
| Debian / Ubuntu | sudo apt install ffmpeg |
| Windows | winget install Gyan.FFmpeg |
Disk space. Recorder clips run roughly 110 MB per 5-minute segment per camera, and exports add to that on top:
| Trip length | Approx. size (4 cameras) |
|---|---|
| 5 min | ~440 MB |
| 20 min | ~1.8 GB |
| 40 min | ~3.5 GB |
The cameras were always on the truck. What changed is the software.
GM software update 663.10 was installed on the build vehicle — a 2025 Chevrolet Tahoe Z71 — on 28 July 2026. Its release notes describe three camera features, each of them qualified with "if your vehicle is equipped":
- Surround Vision Recorder — the vehicle records video from its own cameras, and you can watch those clips back and save them. This is the feature that produces the files this app reads.
- Security Recording — while the vehicle is parked, a triggered alarm starts a recording on its own.
- Cabin Glance — a live look at the seating area on the infotainment screen, for checking on passengers, kids or pets. Live only: it records nothing, so this app has nothing to do with it.
The same update also carried work unrelated to the recorder — responsiveness fixes for the driver display and the rear-view camera, reliability improvements for alerts and chimes, and memory seat buttons that can be linked to user profiles, up to two of them.
So Surround Vision Recorder supplies the footage. What it does not supply is a good way to look at it: review happens on the infotainment screen, in a parked vehicle, a camera at a time. This project is the other half of that — copy the files to a computer and watch all four cameras at once on a real timeline, then cut what you need out of it.
The connection is visible in this repository's own data. The earliest development clips are timestamped 2026-07-28 18:31:45 — a 26-second four-camera test, recorded the same evening the update landed. Everything here was written against that recorder over the days that followed.
Measured with ffprobe on the real files:
| Property | Value |
|---|---|
| Container / codec | MP4, H.264 (avc1), yuv420p |
| Resolution | 1280×768 per camera |
| Frame rate | 30 fps, constant |
| Audio | None at all — one stream per file, video only |
| Segment length | Exactly 300.000 s; the final segment of a trip is shorter |
| Segment size | ~106 MiB (~111 MB) per camera per segment |
| Files per segment | Four — one per camera, sharing an identical timestamp |
| Segment continuity | Gapless — next start == prev start + prev duration |
That table explains a good deal about the app. There is no volume control and exports are silent because there is no audio anywhere in the source. Trips are grouped by a gap rule rather than by metadata because gapless segment arithmetic is the only continuity signal the files carry — nothing in them says "this is the same drive". And the 5-minute chunking is why playback is double-buffered: a trip is not a file, it is a chain of them, and the seam has to be invisible.
Tested. A 2025 Chevrolet Tahoe Z71 running software 663.10, four cameras, writing exactly the output described above. That is the one configuration anything here has actually been run against.
Expected, not verified. Other GM vehicles equipped with Surround Vision Recorder. If the recorder writes the same {CAM}_{YYYY}_{MM}_{DD}_T_{HH}_{MM}_{SS}.mp4 filenames, the app should treat that footage exactly as it treats the Tahoe's — but that follows from the file naming, not from testing, so treat it as an expectation.
Untested. Security Recording clips. This tool has never been pointed at any. If the recorder saves them under the same naming convention, they would land in the folder and show up as ordinary trips like anything else — that is what we would expect to happen, not something anyone here has confirmed.
There is no import step and no database. The app reads a folder — that is the whole ingestion story.
- Copy the files off the recorder. Pull the SD card (or connect the recorder's USB storage) and copy the
.mp4files across. Copy them; do not work off the card. - Drop them in
Recordings/SurroundVisionRecorder/— or leave them where they live and pointRECORDINGS_DIRat that path (see Configuration). - Check the filenames. They must match
{CAM}_{YYYY}_{MM}_{DD}_T_{HH}_{MM}_{SS}.mp4, whereCAMisFRONT,LEFT,REARorRIGHTand the timestamp is the local wall-clock start time of that segment:
FRONT_2026_07_29_T_07_53_06.mp4
LEFT_2026_07_29_T_07_53_06.mp4
REAR_2026_07_29_T_07_53_06.mp4
RIGHT_2026_07_29_T_07_53_06.mp4
Anything that does not match is ignored — dotfiles, .DS_Store, stray exports, subfolders.
- Segment — the four files sharing one timestamp. Recorders chunk continuously, typically 5 minutes (300 s) per file, with the last segment of a trip shorter. Start times and durations are identical across the four cameras.
- Trip — consecutive segments are gapless (
next start == prev start + prev duration). A gap over 45 seconds starts a new trip, so an ignition cycle, a lunch stop or a card swap naturally splits the day. - Missing cameras are fine. A camera with no file for a segment shows
NO SIGNALfor that span, and composite exports substitute black. The trip card flags any trip with fewer than four cameras. - The recordings folder is read-only to this app. It scans and streams from it; it never writes, moves, renames or deletes anything there. Exports go to
Exports/, caches to.cache/. That is deliberate rather than incidental: what you copied off the recorder is the only original that exists, and footage that turns out to matter later should still be byte-identical later. Nothing this app does can put that in question. - New footage needs no import. The folder is rescanned on every visit to the trips screen — copy new files in and hit Refresh. Durations are probed once and cached by name + size + mtime, so rescans are fast.
Different recorder? Any 4-camera recorder should work if the files are h264 MP4 and renamed to the pattern above with a shared per-segment timestamp — expected rather than tested, for the reasons in Compatibility. A batch rename is all it takes — but the timestamp must be the true segment start time, since it drives the wall clock and the trip grouping.
Every setting is an environment variable, and all of them are optional.
| Variable | Default | What it does |
|---|---|---|
PORT |
4171 |
Listen port. If busy, it auto-increments up to PORT+10 (4181 by default). Binds 127.0.0.1 only — never exposed to the network. |
RECORDINGS_DIR |
./Recordings/SurroundVisionRecorder |
Where your recorder files live. Read-only. Relative paths resolve from the project root. |
EXPORTS_DIR |
./Exports |
Where finished exports are written. Created if missing. |
FFMPEG_PATH |
(auto) | Explicit path to the ffmpeg binary. Otherwise resolved from PATH, then from common install directories. |
FFPROBE_PATH |
(auto) | Same, for ffprobe. |
EXPORT_PREFIX |
SV |
Leading token on exported filenames — brand your own output. |
Inline for a one-off:
RECORDINGS_DIR=/Volumes/DASHCAM/clips PORT=8080 node server.jsOr persisted in .env (see .env.example) and loaded with node --env-file=.env server.js:
PORT=4171
RECORDINGS_DIR=/Users/you/Footage/Tahoe
EXPORTS_DIR=/Users/you/Movies/SurroundVision
# FFMPEG_PATH=/opt/homebrew/bin/ffmpeg
# FFPROBE_PATH=/opt/homebrew/bin/ffprobeThe app will never gain an npm dependency. That is a design constraint, not an oversight.
Pick a trip from the grid and you land on the player. One virtual clock drives all four cameras, and the header shows the real recorded time of day at the playhead. That clock — not any one video element — is the source of truth, because four independent decoders chewing through four separate chains of 5-minute files will wander apart on their own; every camera is continuously steered back to a single trip-relative position rather than merely started at the same moment (see How it works). Clicking any small tile promotes it; a camera with no footage for the current span shows NO SIGNAL rather than freezing.
| Key | View | What it is for |
|---|---|---|
Q |
Quad | 2×2 grid — FRONT, RIGHT, LEFT, REAR. The default situational-awareness view. |
W |
Command | One camera large with the other three stacked beside it; click a small tile to swap it into the big slot. Following one thing while watching the rest. |
E |
Drive | FRONT fills the stage with mirrored REAR overhead and mirrored LEFT/RIGHT in the bottom corners — the driver's-seat reconstruction. Best for "what did the driver actually see". |
R |
Solo | One camera full-frame, the others as small thumbnails. Detail work. |
T |
Cross | FRONT over REAR down the centre, LEFT and RIGHT full-height on the flanks (fill-cropped, no black gaps). Wide-lane awareness. |
Y |
Surround 360° | All four cameras projected onto one ground plane around a top-down truck. Parking, clearances, and anything where the relationship between cameras matters more than any single one. |
| Key | Action |
|---|---|
Space |
Play / pause |
← → |
Jump ∓10 s |
Shift+← → |
Jump ∓1 s |
, . |
Frame step (1/30 s) — pauses first |
↑ ↓ |
Speed up / down (0.25× · 0.5× · 1× · 2× · 4× · 8×) |
Q W E R T Y |
Quad · Command · Drive · Solo · Cross · Surround |
1 2 3 4 |
Solo on FRONT / LEFT / REAR / RIGHT, from any view |
I O |
Set the In / Out marker at the playhead |
F |
Fullscreen the stage |
[ ] |
Cycle skin |
? |
Shortcut overlay |
Esc |
Close panel or overlay · clear markers |
Keys are ignored while you are typing in a field — except Esc, which always works and returns focus to the transport.
The timeline. Click or drag anywhere to scrub, hover for a wall-clock and trip-relative tooltip with a thumbnail preview, thin ticks mark segment boundaries, and the In/Out span tints in the skin's accent with draggable handles.
Each camera is horizon-cropped — only the bottom of the frame, the road, is used, so sky and buildings never land on the ground plane. That cropped rectangle is mapped by an exact homography onto a trapezoid on a shared ground canvas: the near edge at the truck, the far edge fanning out wide. Four trapezoids, one per bearing (FRONT 0°, RIGHT 90°, REAR 180°, LEFT 270°), feathered at their far and side edges so the seams blend, composited around a top-down vehicle graphic. The planes wrap the live video elements — nothing is cloned or re-decoded, so the stitch plays at full speed and stays in sync with every other view.
- Orbit — drag to swing yaw/pitch (pitch clamped 0–70°), wheel to zoom, double-click to reset. Presets: TOP-DOWN, 3D FRONT, 3D CHASE, 3D QUARTER.
- Styles — Factory (clean cool-gray ground, parking guide arcs), Tactical (grid, corner brackets, harder edges), Cinematic (heavy vignette, wide feather, chrome hidden). Each skin picks a sensible default; an explicit choice sticks.
- Calibration drawer (gear button) — per-camera sliders for crop, push, depth, near width, far width, feather and lens undistortion, plus global perspective, car size, ground darkness and a guides toggle. Isolate dims the other three cameras while you tune one. Everything applies live and persists across trips and sessions.
- Presets — save a tuned calibration into one of four slots (nameable, loadable, clearable), or snap back to the built-in Default bird's-eye, which can never be overwritten. Loading a preset changes the calibration only; it leaves your current orbit and style alone.
Honest limitation: these are wide-angle cameras with no published lens model, intrinsics or extrinsics. Barrel distortion is corrected by a single radial term fitted by eye against real footage — enough that lane lines and kerbs come out straight and the four sectors agree, but it is a fitted approximation, not a calibrated lens model. The stitch is built on a flat-ground homography — good enough to read curbs, lines and clearances at a glance, and it looks the part. It is not a metric reconstruction; do not measure distances off it. Two minutes in the calibration drawer lines the seams up for your vehicle. The defaults are tuned for the Tahoe Z71.
- Mark the range —
IandOat the playhead, drag the handles, or typemm:ssvalues into the export panel. "Use full trip" grabs everything. - Hit EXPORT and pick a mode:
| Mode | Output |
|---|---|
| Single camera | One camera, full frame |
| Quad 2×2 | 1920×1152 — FRONT, RIGHT, LEFT, REAR |
| Command | 1920×864 — chosen camera large, other three stacked beside |
| Drive | 1920×1152 — main view with mirrored rear and side overlays baked in |
| Cross | 1920×1152 — front over rear centred between full-height sides |
| Surround 360° | 1440×1440 top-down, or 1920×1080 when pitched — rendered with your current orbit angle, style and calibration |
| Picture-in-Picture | Main camera with an inset second camera, top-right |
| All cameras — separate clips | One file per camera, no composite |
- Pick a speed — ¼× · ½× · 1× · 2× · 4×. It applies to every output in the job, and the panel tells you the resulting duration (a 30 s range at ½× renders a 60 s slow-motion clip).
- Optionally tick "Also export each camera as its own clip" — available on any composite mode. The composite and the four raw camera clips render in one job, sequentially, under one progress bar, so a single job produces between 1 and 5 files.
- Start Export. Progress is polled live and aggregated across all sub-renders. Finished files appear in the panel's Exports list with preview, download and delete.
Output is h264 (libx264, CRF 19, veryfast, +faststart), 30 fps, no audio — the source has no audio stream. Files land in Exports/ (or EXPORTS_DIR) named:
{PREFIX}_{tripId}_{MODE}_{h-mm-ss}_{durS}s{_speedX}.mp4
| Part | Meaning |
|---|---|
PREFIX |
EXPORT_PREFIX, default SV. Set it to brand your own output. |
tripId |
YYYY-MM-DD_HH-MM-SS of the trip start |
MODE |
The camera name for single/individual clips, otherwise QUAD, COMMAND-FRONT, DRIVE-FRONT, CROSS, PIP-FRONT-REAR, SURROUND (top-down) or SURROUND-3D (pitched) |
h-mm-ss |
The in-point, relative to the start of the trip |
durS |
Source-range length in seconds |
_speedX |
Only present when speed ≠ 1× (e.g. _0.5x) |
SV_2026-07-29_07-53-06_QUAD_0-04-12_45s.mp4
SV_2026-07-29_07-53-06_FRONT_0-04-12_45s_0.5x.mp4
SV_2026-07-29_07-53-06_SURROUND-3D_0-12-30_20s.mp4
Existing names are never overwritten — a _2, _3, … suffix is appended instead.
Palette button in the header, or [ / ] to cycle. The choice persists, and every view, panel and the trips screen are themed.
| Skin | Look |
|---|---|
| Broadcast (default) | Control room — WTPN red on steel |
| Night Ops | NVG green phosphor, faint scanlines, amber alerts |
| Dash | Vehicle infotainment — navy and ice blue, softer radii, larger type |
| Daylight | Light theme for bright-room review |
| DVR | Retro security recorder — near-black, amber mono labels, cyan hairlines |
| Cinema | Pure black; chrome dims further during playback |
- Trip scanning. Parses filenames, groups identical timestamps into segments, clusters segments into trips at the 45 s gap threshold. Durations come from
ffprobe, cached in.cache/probe.jsonby name + size + mtime, so the first scan costs a moment and every later one is free. - Video streaming.
/video/{filename}serves the source MP4s with full HTTP Range support (206 +Content-Range, 416 on nonsense), which is what makes instant seeking anywhere in a 40-minute trip possible. Filenames are validated against the strict pattern, so path traversal is not expressible. - Thumbnails. Single-frame
ffmpegextractions at snapped widths, cached on disk and served with a longCache-Control, with a cap on concurrent renders so a fast timeline hover cannot fork-bomb the machine. - Export jobs. Each job writes concat-demuxer lists for the segments intersecting the range, seeks on input, and runs its sub-renders sequentially through
ffmpegwith-progress pipe:1parsed for live progress. Output goes to a.part.mp4and is renamed on success, so a half-finished file never appears in the exports list. - Loopback only, by construction. The listener binds
127.0.0.1, which is why there is no account system, no auth layer and no upload path to get wrong — dash footage is full of faces, plates and the inside of your own driveway, and the most reliable way to keep it on your machine is to never open a door off it. A LAN viewer is on the roadmap as an explicit, authenticated opt-in for exactly that reason.
- Double-buffered playback. Every camera owns two
<video>elements: while one plays the current segment, the other quietly preloads the next. At a boundary all four cameras swap buffers in the same tick — which is why playback rolls straight through a 5-minute file boundary instead of stalling. - Master/slave drift correction. One camera drives the virtual trip clock; the other three follow. Every animation frame each slave compares its position against the target: more than 0.3 s out gets a hard
currentTimeset, 0.08–0.3 s out gets a gentle ±6%playbackRatenudge, inside 0.08 s it is left alone. The cameras stay within about a tenth of a second of each other during steady playback. - Surround projection. The destination quad for each camera is computed from the geometry model, then the exact 3×3 homography from the cropped source rectangle to that quad is solved and applied as a CSS
matrix3d— no fakedrotateX. The exporter computes the same quads and feeds them to ffmpeg'sperspectivefilter, so player and export agree. The player uses true CSS 3D and the exporter approximates it: aim for visually close, not pixel-identical.
The vehicle graphic is public/assets/tahoe-top.png — an RGBA top-down render with the nose pointing up. The player draws it as an <img> (falling back to a hand-drawn SVG silhouette if it fails to load) and the exporter reads the same file from disk, so both show the same truck. To use a different vehicle, replace that PNG (nose up, transparent background, tightly trimmed to the vehicle's bounding box including mirrors) and set SV_CAR_RATIO in public/app.js to the new image's width ÷ height — otherwise the render is stretched.
Shipped — v1.0 is this local web app: six views, the 360° surround stitch with calibration, frame-locked playback, the full exporter, six skins.
Planned, roughly in order. Nothing below exists yet.
- Hardware-accelerated export — VideoToolbox on macOS, NVENC on NVIDIA, falling back to libx264. Exports are currently CPU-bound.
- Auto event detection — flag hard braking, sudden motion and scene changes on the timeline so you can jump to the interesting parts instead of scrubbing.
- GPS / telemetry overlay — speed, heading and a map trace, if the recorder logs it.
- Trip notes, bookmarks and search — name a moment, annotate a clip, find it later.
- Desktop app — shipped as a beta for macOS (Apple Silicon and Intel): an Electron shell bundling FFmpeg, with in-app folder selection — see Download for macOS. Still planned: proper signing + notarization (needs an Apple Developer account), Windows, auto-import when a card is inserted, and a background export queue.
- LAN / mobile viewer — opt-in and authenticated, so a phone or tablet on the same network can review trips. Today the server is deliberately loopback-only.
"ffmpeg not found" / thumbnails blank / exports fail immediately. Run which ffmpeg ffprobe. If that prints nothing, install FFmpeg (see Requirements). If it lives somewhere unusual, point at it explicitly:
FFMPEG_PATH=/opt/homebrew/bin/ffmpeg FFPROBE_PATH=/opt/homebrew/bin/ffprobe node server.jsNo trips showing up. Run ls Recordings/SurroundVisionRecorder | head and check, in order:
- Files are directly in the folder, not in a subfolder.
- Filenames match
{CAM}_{YYYY}_{MM}_{DD}_T_{HH}_{MM}_{SS}.mp4exactly —FRONT_2026_07_29_T_07_53_06.mp4. Anything else is silently skipped. RECORDINGS_DIRpoints where you think it does.- Hit Refresh on the trips screen; the folder is rescanned on every load.
Expecting one trip and seeing several? The gaps between your segments exceed 45 s — that is the trip-splitting rule doing its job.
Port already in use. The server tries 4171 and walks up to 4181 on its own, so this is usually handled. To force a port, PORT=8080 node server.js. To find whatever is holding the default:
lsof -nP -iTCP:4171 -sTCP:LISTENSurround view looks wrong / seams misaligned. Open the calibration drawer (gear icon in the Surround view). Raise CROP until the horizon and everything above it is gone, then adjust PUSH and DEPTH until the ground meets the truck cleanly, then NEAR W / FAR W until the seams between cameras agree. Use Isolate to work on one camera at a time. Settings save automatically; Reset restores the Tahoe defaults.
Playback stutters or cameras drift apart. Four simultaneous 1280×768 h264 streams is real work. Use a Chromium-based browser, close other heavy tabs, and prefer Solo or Drive over Quad on modest hardware. Playing from an external drive over USB can starve the decoder — copy the trip locally first.
GMSurroundVision/
├── server.js # entire backend — scanning, streaming, thumbnails, export jobs
├── public/
│ ├── index.html # single-page shell (both screens)
│ ├── app.js # player, sync engine, surround projection, export panel
│ ├── style.css # design system + all six skins
│ └── assets/
│ └── tahoe-top.png # top-down vehicle graphic (nose up, RGBA)
├── docs/ # demo.gif and demo.mp4
├── Recordings/
│ └── SurroundVisionRecorder/ # your footage — READ ONLY, never written to
├── Exports/ # finished exports land here
├── .cache/ # probe cache, thumbnails, temp job files (safe to delete)
├── desktop/ # macOS app (Electron shell); npm run dist builds the DMG
├── .env.example # copy to .env, load with --env-file
├── SPEC.md # full feature + API contract
└── README.md
.cache/ and Exports/ are created on first run. Deleting .cache/ costs nothing but a re-probe on the next scan.
MIT licensed — see LICENSE.
Built by ABSG for the WTPN news van — a 2025 Chevrolet Tahoe Z71 with a 4-camera GM SurroundVision recorder. Not affiliated with, endorsed by, or a product of General Motors; "Chevrolet", "Tahoe" and "SurroundVision" are used descriptively to identify the hardware this tool reads.