You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
apps/stream turns an uploaded video into the smallest set of frames that still contains every safety-relevant event, with accurate timestamps — and exposes that capability to the AI agent as a set of callable tools rather than a fire-and-forget pipeline.
Scope correction (şartname + mentor e-mail)
The competition input model is file upload, not live capture: "Operasyon sahasında bir video sisteme yüklenir." The final is scored on short, previously-unseen test videos given to every team under identical conditions. RTSP / continuous capture is therefore out of scope for the Grand Final and tracked as a post-final stretch goal. This removes back-pressure, frame-drop and buffering concerns from this issue entirely.
The core constraint
VLM context is the bottleneck, and it is the reason this service exists:
2 min video @ 30 fps
3600 frames
Naive 1 fps sampling
120 frames
~800 tokens/frame (to be measured, resolution-dependent)
~96k tokens
Realistic budget for one pass
16–64 frames
So ~120 candidate frames must become ~32 — and the accident will be in exactly the frame we drop. Two failure modes, both fatal:
Too few frames → the 00:15 rollover is missed → event detection fails → the %35 functionality score is gone.
Too many frames → context overflows, latency explodes → the performance criteria are gone.
The correct balance differs per video. A fixed frame count or a fixed motion threshold fails on one side or the other, and şartname §4 explicitly states: "Statik, yalnızca kural tabanlı çözümler düşük puanlanacaktır." Adaptivity here is a real engineering requirement, not just a scoring one.
🔍 Design: coarse-to-fine temporal zoom
Treat video analysis like zooming on a map, not like looking at a photo.
┌──────────────────────────────────────────┐
uploaded video ──►│ PASS 1 — motion profile (CPU, ~sec) │
│ decode @160x90 gray, frame differencing │
│ ⇒ 1-D "where something happens" curve │
└────────────────┬─────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ PASS 2 — coarse look (~16 frames) │
│ adaptive sampling weighted by motion │
│ ⇒ "what is going on in this video?" │
└────────────────┬─────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ PASS 3 — zoom (agent-triggered) │
│ agent: "something at ~00:14, zoom in" │
│ ⇒ 12 dense frames from 00:12–00:17 │
│ ⇒ "what exactly happened, and at when?" │
└──────────────────────────────────────────┘
Total frames sent to the VLM: ~28 instead of 120, with the critical moment localized precisely.
Pass 1 — motion profile
Decode the whole video downscaled to 160x90 grayscale and compute per-frame SAD (sum of absolute differences) against the previous frame. No GPU, no OpenCV, thousands of frames/sec. Output: [(timestamp, motion_score)] plus scene-cut positions.
Pass 2 — adaptive selection
Not "1 frame per second" and not "threshold > X" — both are static. Instead, sample uniformly in motion-space rather than time-space:
Build the cumulative motion curve M(t) = Σ m(t).
Place N points equally spaced along the M axis and map them back to t.
Dense motion regions automatically receive dense sampling; static regions receive sparse sampling. No threshold is ever chosen by hand.
Guard rails:
Scene-cut boundaries are always included.
max_gap guarantees at least one frame per quiet stretch (a static frame is still evidence — e.g. a motionless person on the ground).
Pass 3 is deliberately not automated. It is exposed to the agent, which decides when and where to zoom.
This is a scoring decision as much as an architectural one:
şartname §7 — "Mock fonksiyonların ajanın araçları olarak başarıyla kullanılması" (part of %35) — satisfied directly.
şartname §7 — "Otonomi ve Zeka" (%20): dinamik araç seçimi, çok adımlı karar zincirleri — satisfied directly.
It gives the jury a one-line story: our agent does not watch the video, it investigates it.
Timestamp grounding
VLMs are weak at "when" — given 16 frames they know the order, not the clock. But the şartname scores precisely on timestamps. Two cheap mitigations, both to be A/B measured:
Burn the timestamp into the frame (rendered overlay, corner).
Pass a parallel text index: "Frame 7 → t=00:15.2".
📊 KPIs (feeds the mandatory measurement report, şartname §4/§6)
Metric
Definition
Target
Event coverage recall
% of Golden Dataset ground-truth events with ≥1 selected frame within ±1.0 s
≥ 95%
Frame reduction ratio
selected frames ÷ total frames
≥ 100×
Timestamp error
|reported − ground truth| for detected events
< 1.0 s
Pass-1 throughput
analysis frames/sec, and × realtime
measure & report
Preprocessing latency
wall-clock seconds per minute of video
measure & report
Peak RSS
resident memory during pass 1
must stay bounded and independent of video length
The first three are properties of the algorithm and hold on any machine, so they carry hard targets. The last three depend on the final hardware, which is not yet fixed — they are measured and reported as şartname §4 requires. Crucially, the adaptive-vs-uniform baseline comparison is run on identical hardware, so the delta stays meaningful regardless of which machine we end up with.
Event coverage recall is the metric that matters most: it isolates "did sampling lose the event?" from "did the VLM understand it?" — a stream-side failure we can measure and fix without touching the model. It requires #5.
✅ Definition of Done
A video file in → stream.frame.extracted out, end to end, over NATS
All six tools callable by the orchestrator and covered by the shared contract
KPI table above filled with real numbers on the Golden Dataset
Adaptive sampling beats the uniform-1fps baseline on event coverage recall at equal frame budget
ffmpeg dependency + run steps documented in documents/features/stream-service.md
🚫 Out of Scope
Live RTSP / continuous capture (post-final stretch goal)
🎯 Task Description
apps/streamturns an uploaded video into the smallest set of frames that still contains every safety-relevant event, with accurate timestamps — and exposes that capability to the AI agent as a set of callable tools rather than a fire-and-forget pipeline.Scope correction (şartname + mentor e-mail)
The competition input model is file upload, not live capture: "Operasyon sahasında bir video sisteme yüklenir." The final is scored on short, previously-unseen test videos given to every team under identical conditions. RTSP / continuous capture is therefore out of scope for the Grand Final and tracked as a post-final stretch goal. This removes back-pressure, frame-drop and buffering concerns from this issue entirely.
The core constraint
VLM context is the bottleneck, and it is the reason this service exists:
So ~120 candidate frames must become ~32 — and the accident will be in exactly the frame we drop. Two failure modes, both fatal:
The correct balance differs per video. A fixed frame count or a fixed motion threshold fails on one side or the other, and şartname §4 explicitly states: "Statik, yalnızca kural tabanlı çözümler düşük puanlanacaktır." Adaptivity here is a real engineering requirement, not just a scoring one.
🔍 Design: coarse-to-fine temporal zoom
Treat video analysis like zooming on a map, not like looking at a photo.
Total frames sent to the VLM: ~28 instead of 120, with the critical moment localized precisely.
Pass 1 — motion profile
Decode the whole video downscaled to 160x90 grayscale and compute per-frame SAD (sum of absolute differences) against the previous frame. No GPU, no OpenCV, thousands of frames/sec. Output:
[(timestamp, motion_score)]plus scene-cut positions.Pass 2 — adaptive selection
Not "1 frame per second" and not "threshold > X" — both are static. Instead, sample uniformly in motion-space rather than time-space:
M(t) = Σ m(t).Npoints equally spaced along the M axis and map them back tot.Guard rails:
max_gapguarantees at least one frame per quiet stretch (a static frame is still evidence — e.g. a motionless person on the ground).min_gap+ pHash dedup drops near-identical frames.Pass 3 — zoom, as a tool
Pass 3 is deliberately not automated. It is exposed to the agent, which decides when and where to zoom.
This is a scoring decision as much as an architectural one:
Timestamp grounding
VLMs are weak at "when" — given 16 frames they know the order, not the clock. But the şartname scores precisely on timestamps. Two cheap mitigations, both to be A/B measured:
"Frame 7 → t=00:15.2".🛠️ Affected Domain / Package
apps/dashboard(Next.js Live Monitoring Panel)apps/identity(Ory Kratos/Keto Identity & Authorization)apps/gateway(Rust Axum API Gateway & Realtime SSE)apps/stream(Video Ingestion & Dynamic Frame Sampling)apps/ai(Agentic Reasoning & Decision Logic)packages/optics(Image & Media Processing Toolkit)packages/database(SurrealDB, Qdrant)platform/ortools/(Docker, Infrastructure & Benchmarks)packages/event-sdk(shared message contracts)🏗️ Technical Decisions
ffmpeg/ffprobeas subprocesses, raw frames over a pipe🔌 Tool API (agent-facing contract)
The surface the orchestrator may call. This is the contract other issues depend on — changes here need a heads-up in #6.
All frames carry their true source timestamp; the agent never has to infer time from ordering.
📡 NATS Contracts (draft — to be finalised in
packages/event-sdk)stream.video.ingested{ "video_id": "...", "object_key": "raw/<id>.mp4", "duration_ms": 122000, "fps": 30.0, "width": 1920, "height": 1080, "ingested_at": "..." }stream.frame.extracted{ "video_id": "...", "batch_id": "...", "pass": "overview|zoom", "frames": [ { "t_ms": 15200, "object_key": "frames/<id>/015200.jpg", "motion_score": 0.87, "is_scene_cut": false } ] }📋 Task Checklist
Ingestion
ffprobe(duration, fps, resolution, rotation)stream.video.ingestedPass 1 — motion profile (
packages/optics)Pass 2 — adaptive selection
Nbudgetmin_gap/max_gapguard rails + scene-cut forcingstream.frame.extractedPass 3 — zoom + tool surface
zoom_range,get_frame,crop_region,motion_profile,video_infoValidation
📊 KPIs (feeds the mandatory measurement report, şartname §4/§6)
The first three are properties of the algorithm and hold on any machine, so they carry hard targets. The last three depend on the final hardware, which is not yet fixed — they are measured and reported as şartname §4 requires. Crucially, the adaptive-vs-uniform baseline comparison is run on identical hardware, so the delta stays meaningful regardless of which machine we end up with.
Event coverage recall is the metric that matters most: it isolates "did sampling lose the event?" from "did the VLM understand it?" — a stream-side failure we can measure and fix without touching the model. It requires #5.
✅ Definition of Done
stream.frame.extractedout, end to end, over NATSdocuments/features/stream-service.md🚫 Out of Scope
🔗 Related Resources / Docs
event.risk.detecteddownstream of this)documents/features/stream-service.md