-
-
A real job, live on AWS: found, repaired in place and re-checked by OpenCV 5. Fail to Pass in about 4 seconds.
-
No gentle fix for a full-screen strobe, so the job stops and asks you to approve the repair.
-
A first visit shows a recorded Amazon Bedrock run that repaired two flashing regions in place.
-
Every check has a full page: timeline, flash rate before and after, the region it found, the repair.
-
The verification record: hashes, thresholds, every fix. Anyone can re-check it with one command.
-
OpenCV 5 on a Lambda worker on Graviton; Bedrock plans; CloudFront, API Gateway, SQS, S3, DynamoDB, all in CDK.
Try it
- Live demo (no sign-up): d25yofo4x3sac8.cloudfront.net. Pick a test clip and press Run check: a real job on AWS finds the hazard, plans and applies a repair, re-checks it and hands you the repaired video and its verification record, in seconds. The full-screen red clip stops and asks you to approve its repair.
- Code: github.com/SrijaVuppula/SteadyFrame
- Technical report: docs/report/REPORT.md
- Rubric → evidence map: docs/JUDGES.md
Inspiration
On 16 December 1997 one episode of a cartoon on Japanese television sent 685 people, mostly children, to hospital after a sequence of flashing. In 2016 a man sent the journalist Kurt Eichenwald, who has epilepsy, a strobing GIF meant to cause a seizure; he was indicted, a civil court later entered a $100,000 judgment, and in 2024 the UK made sending flashing images to people with epilepsy a crime (Online Safety Act 2023, s.183). About 3 to 5% of the 50 million people with epilepsy are photosensitive, most of them first between ages 7 and 19.
The rules exist (WCAG 2.3.1 for the web, ITU-R BT.1702 and Ofcom guidance for broadcast, and every UK programme delivered to a broadcaster carries a mandatory photosensitivity field in its AS-11 UK DPP metadata). The tools don't fit the people who need them. The broadcast checker (Harding FPA) is commercial; per-video test services start around £30; the free checker (PEAT) forbids use on commercially produced broadcast, film or game content and was last released in 2017; EA's open-source IRIS reports frames, not where on screen. All of them answer pass or fail for the whole file. A creator who fails learns nothing about where the problem is and gets no fix.
Sources: Annals of Neurology / ScienceDaily (1999) for the 1997 broadcast; CBS Texas (2017), The Outline (2019) and the Maryland Daily Record (2020) for the Eichenwald case; legislation.gov.uk for s.183; WHO epilepsy fact sheet (2024); Epilepsy Society (2025); the AMWA AS-11 UK DPP specification; trace.umd.edu/peat; hardingtest.com.
What it does
Open the live demo and press Run check, or upload your own video. SteadyFrame:
- Finds every hazard against WCAG 2.2 SC 2.3.1 or ITU-R BT.1702 / Ofcom: flash rate, luminance swing, area and red flash, per segment, and the region on screen. Not "fail", but "6 flashes a second, a 0.5 luminance swing, top-left of the screen, from 1.0 to 3.4 s".
- Repairs each segment with the least invasive of six strategies, from a regional temporal low-pass in linear light to a frame hold, with a strength computed from the measured swing.
- Checks its own repair: every candidate goes back through the same analyzer. If it still fails, the next attempt is stronger; if the only passing repair changes the picture too much, the job pauses for a person and resumes from its saved state with their decision. The whole file is re-verified at the end.
- Hands over the repaired video, a verification record (SHA-256 of input and output, every threshold applied, each segment and its fix, any human decision, the AS-11 PSE fields), which anyone can re-check on their own machine with
steadyframe verify, and a viewer warning track (WebVTT) for players that show the original.
The model picks the fix. The analyzer decides whether it worked. A person signs off on anything drastic. The model on Amazon Bedrock only ever sees the analyzer's numbers, never pixels, file names or metadata, and nothing it says can mark a job as passed.
How I built it
- OpenCV 5 (
opencv-python-headless 5.0.0.93) for every pixel operation. Per frame: a 256-entrycv2.LUTto linear light,cv2.resizeINTER_AREA straight to a 64x48 grid (exact for luminance because luminance is linear in linear RGB, and 12x faster than per-pixel maps),cv2.transformwith the Rec. 709 weights, a vectorised per-cell transition tracker for the "adjacent peaks and valleys" rule, one-second windows driven by decoder timestamps, the area rule withcv2.boxFilter, regions withcv2.connectedComponentsWithStats. About 600 fps on 720p on four vCPUs. - Repairs with
cv2.accumulateWeightedin linear light, featheredcv2.GaussianBlurmasks, and SSIM built oncv2.GaussianBlurto measure what each repair costs the picture. - The agent: seven tools (analyze, segment detail with physics-based parameter hints, apply, verify, ask a human, accept, finalize) over the Bedrock Converse API. Budgets per segment and per job; any model failure hands the open segments to a deterministic policy and says so in the trace; a human decision is consumed once.
- Bring your own agent:
steadyframe mcpserves the same tools over the Model Context Protocol, so an agent in Kiro, Codex, VS Code or any MCP client can check, repair and verify video with the same refusals. A repair that needs approval is put to the person at the client through MCP elicitation; the agent cannot answer it.run_on_awshands a file to the deployed pipeline. - AWS, all in CDK (7 stacks): S3 + CloudFront, API Gateway + Lambda, SQS with a dead-letter queue, a worker on Graviton (an arm64 Lambda function running the same container code, with an ECS Fargate ARM64 service as the fallback), DynamoDB, CloudWatch dashboard and alarms, a cost budget, least-privilege IAM, 1-day data lifecycle, idle cost about zero. The public demo caps its own spend: 40 Bedrock-planned checks a day (8 per visitor), then the free fixed policy plans and the page says so; 600 checks a day; 20 requests a second.
- Web: React + TypeScript, flash-safe by design (nothing autoplays; the original is behind a click-through warning, dimmed, small and slowed), WCAG AA, axe check in CI.
Results (every number from a script; frozen in the repository)
- Detection, 44 synthetic clips with independent ground truth: 44/44 verdicts, 17/17 boundary cases (3 vs 3.5 vs 4 Hz, swing 0.09 vs 0.11, darker state 0.79 vs 0.81, area 24% vs 26%), per-second F1 1.000; 16/16 H.264 re-encodes keep their verdict.
- Other people's clips: EA IRIS's test videos scored against IRIS's own expected output (8/8 verdicts, 35/35 failing frames inside a detected segment), Apple's flashing sample, and 9 minutes of real camera footage with 0 of 551 seconds wrongly flagged.
- Real lights, labelled by EA's own checker: 17 openly licensed Wikimedia Commons clips (police cars, fire engines, an ambulance, club lights, a phone's LEDs, lightning, fireworks; 10 minutes) labelled frame by frame by EA IRIS 1.1.0. Under the same whole-screen rule SteadyFrame agrees on 16/17 clips and catches 115/115 frames IRIS fails. All 10 clips either tool flagged were repaired by both planners, and IRIS independently passes all 10 repaired files.
- Repair: 25/25 hazardous clips fixed and passing whole-file re-verification.
- Live Bedrock agent vs the fixed policy: 25/25, 1.04 vs 1.10 attempts per hazard, and it kept more of the picture inside the hazard region (SSIM 0.882 vs 0.861), for $3.04 of model calls for the whole suite. It was worse on two red clips in attempts and runtime; the report lists them.
- Trying to break it: a hostile planner using the same tools tried to accept unverified and failed repairs, skip or fake the human approval, finish early, forge ids, push parameters out of range, burn the budget and stall; one more clip carried instructions in its file name and metadata. 10/10 held, 0 false passes, and none of the planted text reached the model.
What it does not do
- A pass means the published thresholds were met as implemented here. It is not a certification and not medical advice; threshold tests have known misses (a UK TV ad that passed a Harding test was withdrawn in 2024 after viewers reported seizures).
- Luminance is relative to peak white. On the Ofcom note's own reference display (gamma 2.2, peak white 200 cd/m²) its 20 and 160 cd/m² limits are exactly the 0.10 and 0.80 used here, but a brighter real display is not modelled.
- The area rule runs on a grid, so a hazard right at the 25% boundary can flip with grid size.
- A full-frame strobe has no regional fix; the repair has to change the whole picture, and when it changes it too much the job waits for a person.
- The pattern detector is experimental and off by default.
- On real lights it is stricter than IRIS where parts of the picture brighten while others darken (a fire engine's alternating lights, moving disco spots), so it asks for some repairs IRIS would not. Which reading is closer to the harm is not something either tool measures.
Challenges I ran into
- Averaging in the wrong space. Smoothing frames in sRGB code values left twice the luminance swing the filter maths predicted. Moving the blend to linear light made the measurement match the prediction, and that is what made the model's parameter hints usable.
- The loop catching my own bug. A feathered mask that faded inward left the edge cells of a region half-treated; the verify step kept failing them. Unit tests on the pieces passed; the closed loop is what found it.
- The thing I rebuilt. Testing on other people's clips broke two assumptions the synthetic suite never touched. Hazards less than a second apart made every repair "fail", because the verification clip contained the next, untreated hazard; failing frames are now attributed to the segment they belong to. And a violation inside flashing that sits exactly at the limit could not be fixed in place, because the repair's own edge added the transition that tipped the neighbouring windows over; the plan now widens to the whole at-limit episode. Both are in the repository as regression tests.
- Exactly three flashes a second is a floating-point boundary in both the generator and the window counter.
- Real lights disagreed with EA's checker, and the disagreement taught me something. On a fire engine and a rotating disco light SteadyFrame flagged seconds that IRIS passed. The first guess, that the flashes weren't simultaneous, was wrong: I measured it, and large areas change in the same frame in both. The cause is averaging. IRIS asks whether the average luminance of the changing area moves by 0.10, and when some lights brighten while others darken those changes cancel; SteadyFrame tests each grid cell, so they don't. I kept the stricter reading and published the measurement instead of tuning toward IRIS.
- A live demo that doesn't make judges wait. The worker scaled to zero on Fargate to cost nothing while idle, and the first job after a quiet spell waited 505 seconds for a task to start. The same container now also runs as an arm64 Lambda function fed by the queue: 46 s cold, under 5 s warm, inside the free tier, with a free five-minute schedule keeping one warm. Fargate stays as the fallback.
Accomplishments I'm proud of
A repair is never accepted on the model's word. Every number in the report is filled in by a script from a results file. The checker agrees with EA's own expected results on its test videos, raised no false alarm in nine minutes of calm footage, and on real police, club and storm footage caught every frame EA's IRIS failed; IRIS passes every file it repaired. And a judge can press one button and watch a real job go from "fail" to a verified "pass" in seconds.
What I learned
Localisation is what makes repair possible: without knowing where, the only fix is dimming the whole picture. And a verification loop catches mistakes that unit tests on the pieces do not.
What's next
More natural footage (concerts, game captures), an optional IRIS-style averaging mode so a team can see both readings side by side, IRIS's extended-failure rule, the fine-pattern exemption, an editor plug-in that imports the hazard segments as timeline markers, and a browser extension that calls the API.
Built With
- amazon-api-gateway
- amazon-bedrock
- amazon-cloudfront
- amazon-cloudwatch
- amazon-dynamodb
- amazon-ecs
- amazon-sqs
- amazon-web-services
- aws-cdk
- aws-graviton
- aws-lambda
- docker
- ffmpeg
- mcp
- numpy
- opencv
- python
- react
- typescript
Log in or sign up for Devpost to join the conversation.