A live leaderboard for the Black Ops 7 race to Master Prestige that reads each streamer's rank straight off their Twitch broadcast, so nobody has to type in a score.
A handful of streamers were racing to Master Prestige in Black Ops 7, and the only way to know who was ahead was to check in on every stream. I wanted the standings without the tab-hopping, so I built something to watch them for me.
It was just for fun. Nobody asked for it, which is most of the appeal.
Each channel gets its own capture thread. streamlink turns the channel into an HLS URL, OpenCV reads it, and a two-slot queue keeps only the newest frame, so a slow detector never builds a backlog. Every two seconds the detector looks at whatever is current.
Almost every frame is gameplay. The detector crops the top-middle of the frame, binarizes it three ways, and asks Tesseract for the word PROGRESSION. No word, no detection. When the header is found it becomes the anchor, and the level and prestige boxes are defined as percentages of it.
The level crop gets three binarizations, a 4x upscale and a digits-only Tesseract pass. Only plausible numbers get a vote, and the most common answer wins.
Prestige is stylized text, which is harder. EasyOCR goes first, Tesseract tries sixteen preprocessing and page-mode combinations if that comes up empty, and a fuzzy matcher forgives the usual misreadings: PRESTICE, PREST0E, PREST6E. "Prestige Master" is stored as prestige 20 so it sorts above everything else.
Anything lower than what's already stored is treated as a misread. It only sticks if lower readings keep coming for five detections and ten minutes, so one bad frame can't knock anyone down the board.
The backend is the only writer, so state is a plain streams_data.json, re-uploaded to Firebase Storage on every save. theraceboard.com is one static page on GitHub Pages that fetches the file and ranks streamers client-side, prestige then level. Each row has an info button with the last annotated frame and the raw OCR text, so anyone can check the robot's work.
OCRing the header beat template-matching the UI; the old matcher is still in the repo, unused.
No single preprocessing pass gets trusted on its own.
Part of the detector exists only to forgive a misread G.
With a single writer, a file in a bucket is a perfectly good database.
EasyOCR reads the prestige font better and needs 1–2 GB of RAM to do it, hence the off switch.