From fa519daffa7397b851c04ee0c442197aec097cd5 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:56:11 +0300 Subject: [PATCH] Import emberz CV parser core into app/features/cv Wholesale copy of core/worker/capture/store/node from the emberz repo with relative imports stripped of .ts extensions. Atlases and template sets now load from the assets repo CDN (assets/cv/v1): the worker receives the base URL via its init message, Node tests/tools read the local assets checkout (CV_ASSETS_DIR override). --- app/features/cv/capture/sampler.ts | 71 + app/features/cv/capture/vod-frames.ts | 110 + app/features/cv/core/ability-harvest.ts | 80 + app/features/cv/core/batches.ts | 116 + app/features/cv/core/canonical.ts | 14 + app/features/cv/core/cv.ts | 66 + .../cv/core/detectors/death/abilities.ts | 85 + app/features/cv/core/detectors/death/index.ts | 714 ++ .../detectors/death/localized-messages.ts | 9914 +++++++++++++++++ app/features/cv/core/detectors/death/rois.ts | 163 + .../cv/core/detectors/death/weapon-names.ts | 242 + .../cv/core/detectors/map-start/index.ts | 334 + .../cv/core/detectors/map-start/rois.ts | 115 + .../cv/core/detectors/minimap/abilities.ts | 20 + .../cv/core/detectors/minimap/index.ts | 591 + .../cv/core/detectors/minimap/rois.ts | 287 + .../cv/core/detectors/minimap/stage.ts | 217 + app/features/cv/core/detectors/registry.ts | 40 + .../detectors/scoreboard-own/abilities.ts | 32 + .../cv/core/detectors/scoreboard-own/index.ts | 246 + .../cv/core/detectors/scoreboard-own/rois.ts | 96 + .../core/detectors/scoreboard-replay/code.ts | 122 + .../detectors/scoreboard-replay/header.ts | 132 + .../core/detectors/scoreboard-replay/index.ts | 385 + .../core/detectors/scoreboard-replay/rois.ts | 172 + .../cv/core/detectors/scoreboard/digits.ts | 51 + .../detectors/scoreboard/header-entries.ts | 47 + .../cv/core/detectors/scoreboard/header.ts | 137 + .../cv/core/detectors/scoreboard/index.ts | 269 + .../cv/core/detectors/scoreboard/kits.ts | 190 + .../cv/core/detectors/scoreboard/names.ts | 220 + .../cv/core/detectors/scoreboard/pov.ts | 67 + .../cv/core/detectors/scoreboard/rois.ts | 111 + .../cv/core/detectors/scoreboard/row.ts | 142 + .../cv/core/detectors/scoreboard/specials.ts | 252 + .../cv/core/detectors/scoreboard/weapons.ts | 352 + app/features/cv/core/detectors/suppressor.ts | 72 + app/features/cv/core/detectors/types.ts | 37 + app/features/cv/core/glyphs.ts | 583 + app/features/cv/core/image.ts | 150 + app/features/cv/core/localized-entries.ts | 2160 ++++ app/features/cv/core/localized.ts | 94 + app/features/cv/core/replay-time.ts | 128 + app/features/cv/core/resources.ts | 194 + app/features/cv/core/text.ts | 137 + app/features/cv/core/timeline/index.ts | 87 + .../cv/core/timeline/same-scoreboard.ts | 88 + app/features/cv/core/vod-matches.ts | 220 + app/features/cv/node/assets-dir.ts | 9 + app/features/cv/node/fixtures.ts | 147 + app/features/cv/node/image-io.ts | 29 + app/features/cv/node/resources.ts | 55 + app/features/cv/store/db.ts | 110 + app/features/cv/store/events.ts | 142 + app/features/cv/store/vods.ts | 129 + app/features/cv/worker/analyzer.worker.ts | 80 + app/features/cv/worker/client.ts | 93 + app/features/cv/worker/pool.ts | 76 + app/features/cv/worker/protocol.ts | 41 + app/features/cv/worker/resources.ts | 77 + app/utils/urls.ts | 4 + 61 files changed, 21144 insertions(+) create mode 100644 app/features/cv/capture/sampler.ts create mode 100644 app/features/cv/capture/vod-frames.ts create mode 100644 app/features/cv/core/ability-harvest.ts create mode 100644 app/features/cv/core/batches.ts create mode 100644 app/features/cv/core/canonical.ts create mode 100644 app/features/cv/core/cv.ts create mode 100644 app/features/cv/core/detectors/death/abilities.ts create mode 100644 app/features/cv/core/detectors/death/index.ts create mode 100644 app/features/cv/core/detectors/death/localized-messages.ts create mode 100644 app/features/cv/core/detectors/death/rois.ts create mode 100644 app/features/cv/core/detectors/death/weapon-names.ts create mode 100644 app/features/cv/core/detectors/map-start/index.ts create mode 100644 app/features/cv/core/detectors/map-start/rois.ts create mode 100644 app/features/cv/core/detectors/minimap/abilities.ts create mode 100644 app/features/cv/core/detectors/minimap/index.ts create mode 100644 app/features/cv/core/detectors/minimap/rois.ts create mode 100644 app/features/cv/core/detectors/minimap/stage.ts create mode 100644 app/features/cv/core/detectors/registry.ts create mode 100644 app/features/cv/core/detectors/scoreboard-own/abilities.ts create mode 100644 app/features/cv/core/detectors/scoreboard-own/index.ts create mode 100644 app/features/cv/core/detectors/scoreboard-own/rois.ts create mode 100644 app/features/cv/core/detectors/scoreboard-replay/code.ts create mode 100644 app/features/cv/core/detectors/scoreboard-replay/header.ts create mode 100644 app/features/cv/core/detectors/scoreboard-replay/index.ts create mode 100644 app/features/cv/core/detectors/scoreboard-replay/rois.ts create mode 100644 app/features/cv/core/detectors/scoreboard/digits.ts create mode 100644 app/features/cv/core/detectors/scoreboard/header-entries.ts create mode 100644 app/features/cv/core/detectors/scoreboard/header.ts create mode 100644 app/features/cv/core/detectors/scoreboard/index.ts create mode 100644 app/features/cv/core/detectors/scoreboard/kits.ts create mode 100644 app/features/cv/core/detectors/scoreboard/names.ts create mode 100644 app/features/cv/core/detectors/scoreboard/pov.ts create mode 100644 app/features/cv/core/detectors/scoreboard/rois.ts create mode 100644 app/features/cv/core/detectors/scoreboard/row.ts create mode 100644 app/features/cv/core/detectors/scoreboard/specials.ts create mode 100644 app/features/cv/core/detectors/scoreboard/weapons.ts create mode 100644 app/features/cv/core/detectors/suppressor.ts create mode 100644 app/features/cv/core/detectors/types.ts create mode 100644 app/features/cv/core/glyphs.ts create mode 100644 app/features/cv/core/image.ts create mode 100644 app/features/cv/core/localized-entries.ts create mode 100644 app/features/cv/core/localized.ts create mode 100644 app/features/cv/core/replay-time.ts create mode 100644 app/features/cv/core/resources.ts create mode 100644 app/features/cv/core/text.ts create mode 100644 app/features/cv/core/timeline/index.ts create mode 100644 app/features/cv/core/timeline/same-scoreboard.ts create mode 100644 app/features/cv/core/vod-matches.ts create mode 100644 app/features/cv/node/assets-dir.ts create mode 100644 app/features/cv/node/fixtures.ts create mode 100644 app/features/cv/node/image-io.ts create mode 100644 app/features/cv/node/resources.ts create mode 100644 app/features/cv/store/db.ts create mode 100644 app/features/cv/store/events.ts create mode 100644 app/features/cv/store/vods.ts create mode 100644 app/features/cv/worker/analyzer.worker.ts create mode 100644 app/features/cv/worker/client.ts create mode 100644 app/features/cv/worker/pool.ts create mode 100644 app/features/cv/worker/protocol.ts create mode 100644 app/features/cv/worker/resources.ts diff --git a/app/features/cv/capture/sampler.ts b/app/features/cv/capture/sampler.ts new file mode 100644 index 000000000..8875f4cde --- /dev/null +++ b/app/features/cv/capture/sampler.ts @@ -0,0 +1,71 @@ +/** + * Capture layer: OBS Virtual Camera in via getUserMedia, frames out as + * ImageBitmaps at a low sample rate. The interface downstream is just + * (bitmap, t) — a WHIP/MediaMTX transport can replace this file later. + */ + +export async function openVirtualCamera(deviceId?: string): Promise { + return navigator.mediaDevices.getUserMedia({ + video: { + deviceId: deviceId ? { exact: deviceId } : undefined, + width: { ideal: 1920 }, + height: { ideal: 1080 }, + }, + audio: false, + }); +} + +export async function listVideoInputs(): Promise { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.filter((d) => d.kind === "videoinput"); +} + +export type FrameHandler = (bitmap: ImageBitmap, t: number) => void; + +/** + * Sample frames from a playing video element at ~fps using + * requestVideoFrameCallback. Returns a stop function. + */ +export function startSampler( + video: HTMLVideoElement, + fps: number, + onFrame: FrameHandler, +): () => void { + const intervalMs = 1000 / fps; + let lastSample = -Infinity; + let lastMediaTime = -Infinity; + let stopped = false; + let handle = 0; + + const tick = async (now: number, metadata: VideoFrameCallbackMetadata) => { + if (stopped) return; + // Throttle on the callback clock, not metadata.mediaTime: Firefox never + // advances mediaTime for MediaStream-backed videos, which would freeze + // sampling after the first frame. + if (now - lastSample >= intervalMs) { + lastSample = now; + try { + const bitmap = await createImageBitmap(video); + if (stopped) { + bitmap.close(); + return; + } + // Same Firefox quirk for the frame timestamp: fall back to the clock + // when mediaTime isn't advancing so timestamps stay monotonic (the + // timeline's merge windows compare them). + const t = metadata.mediaTime > lastMediaTime ? metadata.mediaTime : now / 1000; + lastMediaTime = Math.max(lastMediaTime, metadata.mediaTime); + onFrame(bitmap, t); + } catch { + // video not ready / tab hidden — skip this frame + } + } + if (!stopped) handle = video.requestVideoFrameCallback(tick); + }; + handle = video.requestVideoFrameCallback(tick); + + return () => { + stopped = true; + video.cancelVideoFrameCallback(handle); + }; +} diff --git a/app/features/cv/capture/vod-frames.ts b/app/features/cv/capture/vod-frames.ts new file mode 100644 index 000000000..cbe6e13d9 --- /dev/null +++ b/app/features/cv/capture/vod-frames.ts @@ -0,0 +1,110 @@ +/** + * VoD frame extraction: step through a video file yielding (frame, t) as + * fast as decoding allows — no real-time playback. The primary path demuxes + * the file and decodes **every frame** sequentially with WebCodecs (via + * mediabunny), yielding the VideoFrames themselves (transferable to the + * analyzer workers with no main-thread conversion); when the container/codec + * can't be read that way, it falls back to seek-stepping a