From 382e2390e9cbd0ca00a1f9b8ede66453660cc21b Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 26 Sep 2026 13:21:59 +0300 Subject: [PATCH] Dont save frames unless debugging --- app/features/scanner/README.md | 7 ++++--- app/features/scanner/components/LandingView.tsx | 6 ++++-- app/features/scanner/components/VodView.tsx | 5 ++++- app/features/scanner/components/live-session.ts | 13 +++++++++++-- app/features/scanner/components/vod-scan.ts | 14 +++++++++++--- app/features/scanner/store/events.ts | 4 ++-- app/features/scanner/worker/analyzer.worker.ts | 5 ++++- app/features/scanner/worker/client.ts | 3 +++ app/features/scanner/worker/protocol.ts | 2 ++ 9 files changed, 45 insertions(+), 14 deletions(-) diff --git a/app/features/scanner/README.md b/app/features/scanner/README.md index 0112ba4c7..464db2959 100644 --- a/app/features/scanner/README.md +++ b/app/features/scanner/README.md @@ -48,9 +48,10 @@ opens it, for anyone, through the same handoff Inspect uses. so events from different page loads share one timeline; the ring buffer stamps footage the same way. - **Retention** (`store/events.ts`, on a throttled pass at every save): - whole sessions older than 30 days or beyond the newest 20 go; full-res - frames are kept for everyone — a misread is only reportable while the - frame exists — bounded by 72 h and `MAX_FRAMES`, the event staying with + whole sessions older than 30 days or beyond the newest 20 go. Full-res + frames (and the thumbnails made from them) are only captured in debug + mode — the worker skips the PNG encode otherwise (`attachFrames`) — and + are bounded by 72 h and `MAX_FRAMES`, the event staying with `hasFrame: false`. Clips have their own cap and outlive session deletion. - **Upload** is on by default when logged in (settings toggle, persisted). Live: a scoreboard closes its match and sends it, a 15 s tick retries diff --git a/app/features/scanner/components/LandingView.tsx b/app/features/scanner/components/LandingView.tsx index d94917952..926124527 100644 --- a/app/features/scanner/components/LandingView.tsx +++ b/app/features/scanner/components/LandingView.tsx @@ -28,6 +28,7 @@ import styles from "./LandingView.module.css"; import { getLiveSession, startCapture, useLiveSession } from "./live-session"; import { SettingsPopover } from "./SettingsPopover"; import { SourceSelect } from "./SourceSelect"; +import { useDebug } from "./use-debug"; import { startVodScan } from "./vod-scan"; import { useVods } from "./vods-feed"; @@ -55,6 +56,7 @@ export function LandingView() { const vods = useVods(); const clips = useClips(); const [telemetry] = useSearchParam(scannerSearchParams, "telemetry"); + const debug = useDebug(); const [, setParams] = useSearchParamsTyped(scannerSearchParams); const [over, setOver] = useState(false); const [playing, setPlaying] = useState(null); @@ -68,7 +70,7 @@ export function LandingView() { setParams({ view: "live" }); return; } - await startCapture(); + await startCapture({ saveFrames: debug }); if (getLiveSession().status === "running") setParams({ view: "live" }); }; @@ -77,7 +79,7 @@ export function LandingView() { void inspectScreenshot(file); return; } - void startVodScan(file, { telemetry }); + void startVodScan(file, { telemetry, saveFrames: debug }); setParams({ view: "vod", name: file.name }); }; diff --git a/app/features/scanner/components/VodView.tsx b/app/features/scanner/components/VodView.tsx index 571b4195e..6590bd793 100644 --- a/app/features/scanner/components/VodView.tsx +++ b/app/features/scanner/components/VodView.tsx @@ -89,7 +89,10 @@ function ScanVodView({ name }: { name: string }) { const file = e.target.files?.[0]; e.target.value = ""; if (file) { - void startVodScan(file, { telemetry: telemetryOn }); + void startVodScan(file, { + telemetry: telemetryOn, + saveFrames: debug, + }); } }} /> diff --git a/app/features/scanner/components/live-session.ts b/app/features/scanner/components/live-session.ts index 1202e22a8..d1a6db9f4 100644 --- a/app/features/scanner/components/live-session.ts +++ b/app/features/scanner/components/live-session.ts @@ -179,8 +179,16 @@ function set(patch: Partial): void { for (const listener of listeners) listener(); } -/** Opens the source, brings the worker up, then starts sampling; a second call while running is ignored. */ -export async function startCapture(): Promise { +/** + * Opens the source, brings the worker up, then starts sampling; a second call + * while running is ignored. `saveFrames` (debug mode) keeps each event's + * analyzed frame and thumbnail for the raw detections. + */ +export async function startCapture({ + saveFrames, +}: { + saveFrames: boolean; +}): Promise { if (snapshot.status === "starting" || snapshot.status === "running") return; releaseCaptureLock = await acquireCaptureLock(); if (!releaseCaptureLock) { @@ -232,6 +240,7 @@ export async function startCapture(): Promise { client = new AnalyzerClient(onResult, onWorkerError, undefined, { frameQueueLimit: FRAME_QUEUE_LIMIT, webgpu: settings.webgpu, + attachFrames: saveFrames, }); try { await client.whenReady(); diff --git a/app/features/scanner/components/vod-scan.ts b/app/features/scanner/components/vod-scan.ts index 9a0f16172..5fbc17816 100644 --- a/app/features/scanner/components/vod-scan.ts +++ b/app/features/scanner/components/vod-scan.ts @@ -207,10 +207,14 @@ export async function uploadVodScan( } } -/** Scans `file` as fast as decoding allows; a finished scan replaces any saved one of the same name. */ +/** + * Scans `file` as fast as decoding allows; a finished scan replaces any saved + * one of the same name. `saveFrames` (debug mode) keeps each event's analyzed + * frame and thumbnail for the raw detections. + */ export async function startVodScan( file: File, - { telemetry }: { telemetry: boolean }, + { telemetry, saveFrames }: { telemetry: boolean; saveFrames: boolean }, ): Promise { cancelVodScan(); const abort = { aborted: false }; @@ -296,7 +300,11 @@ export async function startVodScan( seek.frameDone?.(); seek.frameDone = null; }, - { collectTelemetry: telemetry, webgpu: readSettings().webgpu }, + { + collectTelemetry: telemetry, + webgpu: readSettings().webgpu, + attachFrames: saveFrames, + }, ), ); await Promise.all(clients.map((c) => c.whenReady())); diff --git a/app/features/scanner/store/events.ts b/app/features/scanner/store/events.ts index 815305d90..30a964f7e 100644 --- a/app/features/scanner/store/events.ts +++ b/app/features/scanner/store/events.ts @@ -21,8 +21,8 @@ import { EVENTS_STORE, FRAMES_STORE, readwrite, tx } from "./db"; const MAX_EVENTS = 10_000; /** - * Full-res frame PNGs (~1-2MB each) are what makes a misread reportable, so - * they are kept for everyone — bounded by age and count, whichever bites first. + * Full-res frame PNGs (~1-2MB each) are what makes a misread reportable; only + * debug-mode captures save them, bounded by age and count, whichever bites first. */ const MAX_FRAMES = 200; const FRAME_MAX_AGE_MS = 72 * 60 * 60 * 1000; diff --git a/app/features/scanner/worker/analyzer.worker.ts b/app/features/scanner/worker/analyzer.worker.ts index 604d5f7ed..ab47cafca 100644 --- a/app/features/scanner/worker/analyzer.worker.ts +++ b/app/features/scanner/worker/analyzer.worker.ts @@ -81,6 +81,7 @@ let scheduler: DetectorScheduler | null = null; /** null unless the init message asked for telemetry */ let telemetry: ScanTelemetry | null = null; let collectTelemetry = false; +let attachFrames = true; let chunkAborted = false; /** last per-frame t, to reset telemetry when a new session rewinds the clock */ let lastFrameT = Number.NEGATIVE_INFINITY; @@ -110,6 +111,7 @@ async function init({ suppressSteadyFrames = true, collectTelemetry: collect = false, webgpu = false, + attachFrames: attach = true, }: InitRequest): Promise { // VoD scans need the helper workers: start them booting alongside init if (typeof VideoDecoder !== "undefined") { @@ -138,6 +140,7 @@ async function init({ matchClosingTypes: SCOREBOARD_EVENT_TYPES, }); collectTelemetry = collect; + attachFrames = attach; telemetry = freshTelemetry(); post({ kind: "ready" }); } catch (error) { @@ -216,7 +219,7 @@ async function analyzePrepared( if (action === "added" || action === "replaced") listed = true; } const blob = - listed && detector.attachFrame !== false + attachFrames && listed && detector.attachFrame !== false ? await frameBlob() : undefined; post({ diff --git a/app/features/scanner/worker/client.ts b/app/features/scanner/worker/client.ts index c15b01aa6..da74ae508 100644 --- a/app/features/scanner/worker/client.ts +++ b/app/features/scanner/worker/client.ts @@ -68,6 +68,8 @@ export class AnalyzerClient { frameQueueLimit?: number; /** match templates on WebGPU when the browser has an adapter (settings `webgpu`) */ webgpu?: boolean; + /** ship the analyzed frame's PNG on results (default true) */ + attachFrames?: boolean; } = {}, ) { this.#frameQueueLimit = options.frameQueueLimit ?? 0; @@ -123,6 +125,7 @@ export class AnalyzerClient { suppressSteadyFrames: options.suppressSteadyFrames ?? true, collectTelemetry: options.collectTelemetry ?? false, webgpu: options.webgpu ?? false, + attachFrames: options.attachFrames ?? true, }); } diff --git a/app/features/scanner/worker/protocol.ts b/app/features/scanner/worker/protocol.ts index 995169fc6..344756161 100644 --- a/app/features/scanner/worker/protocol.ts +++ b/app/features/scanner/worker/protocol.ts @@ -15,6 +15,8 @@ export interface InitRequest { collectTelemetry?: boolean; /** match templates (and upscale sub-1080p frames) on WebGPU when an adapter exists; default false */ webgpu?: boolean; + /** PNG-encode the analyzed frame onto results whose events fired; default true (live and VoD scans only want it in debug mode) */ + attachFrames?: boolean; } export interface AnalyzeRequest {