This repository is no longer maintained.
The active, cleaner and improved version of this project is now here:
https://github.com/fromthelake/mk8dx-video-result-extractor
This old repository is kept available only for historical reference. Please use the new repository for all future development, issues and documentation.
This program analyzes recordings of Mario Kart 8 LAN-play tournament sessions and turns the detected race results into Excel output.
In practice it:
- scans videos for race score screens and total score screens
- extracts player names, points, positions, tracks, and characters
- rebuilds tournament progress race by race
- exports the results into structured workbook files for review and sharing
This project is currently designed for tournament videos with:
- 6 to 12 players visible on score screens
- one single session per video
- all recorded races in that video counted toward final standings (except when explicit validation rules mark races as non-counting, for example late temporary player-drop exclusions)
Unsupported input:
- 5 players or fewer is not supported and is expected to fail or produce invalid standings.
Robustness and known handling:
- handles many real-world issues, including connection resets/errors, OCR name drift/name changes, pauses/low-motion stretches, and character recognition instability
- still depends on video quality and readable score screens; low-quality sources can require review
Current output set for a normal run:
*_Tournament_Results.xlsx*_Tournament_Results.csv*_Final_Standings.csv
Debug outputs can be enabled for a scoped headless run with:
.\.venv\Scripts\mk8-local-play.exe --selection --debug --video <video-name>.\.venv\Scripts\mk8-local-play.exe --selection --subfolders --videos "2026-03-28/VideoA.mp4" "2026-03-28/VideoB.mp4".\.venv\Scripts\mk8-local-play.exe --ocr --selection --subfolders --videos "Mario Kart Toernooien/Level Level/2023-10-12/Toernooi 1 - Ronde 2 - Divisie 1.mp4" --low_res --debug
When --debug is enabled, the run also writes:
Debug/*_Tournament_Results_Debug.xlsxDebug/*_Tournament_Results_Debug.csv
Recent scoring and validation behavior:
- explicit multi-video CLI selection is now available through
--videos, so you can process several exact file paths together in one scoped run --video/--videosalso accept folder paths; folder targets resolve to all supported videos in that folder (recursive when combined with--subfolders)- when
--subfoldersis combined with explicit relative paths in--videos, each requested path now resolves exactly instead of also pulling same-named files from other folders such asbackup/ - score recomputation now resets running tournament totals per video / race class, so repeated player names across separate captures no longer inherit totals from earlier videos
- videos can now contain multiple connection resets; later resets in the same source video are detected and segmented correctly
- reset detection now has a second pass for obvious fresh-session total-score patterns where the displayed totals collapse back to race-points-scale values across most of the field
- temporary player-drop races can stay visible in the workbook while being excluded from tournament totals when a later race recovers to a higher player count
- user exports now include
Counts Toward TotalsandScoring Noteat the end of the table when that late scoring policy applies - first-race scoring recompute now preserves a valid non-zero
OldTotalScorebaseline for the players actually present instead of resetting those totals back to zero - overlap OCR finalization now ignores incomplete race folders that never exported a
2RaceScorebundle, so partially scanned tail races no longer block a whole video's workbook rows from appearing in full multi-video runs - identity standardization now preserves visibly distinct case-only names when they coexist in the same race, so players such as
Florisandflorisare not merged into one identity chain - connection-reset relinking now has a single-swap fallback, so if exactly one player identity changes at reset time it can still relink by elimination even when OCR names are noisy
- one-race low-confidence OCR outlier names are now relinked to the stable adjacent-race identity when continuity proves they are the same player
- headless runs now support experimental
--low_resmode for explicitly selected videos, forcing those race classes through the existing low-res/ultra-low-res identity path without changing default behavior (--ultra_low_resremains as a backward-compatible alias) - recursive runs now skip any videos under a folder named
corruptorexclude - final-race duplicate-name ambiguity notes now only mark the rows that are still truly interchangeable, and the note names the conflicting identity label(s)
- score detection now uses the left-side row-box position signal for the required visible-player prefix instead of relying on a standalone score-strip template match
- initial score confirmation now treats rows
2..6as the required visible-player prefix, so NintendoCapture taken.overlays on row1no longer suppress real score candidates - 12th-place checks now support both the legacy and Dutch templates during score selection
- TotalScore timing now waits for a continuous score-signal drop of
5.0 * fpsand anchors from the start of that drop, so short transition animations no longer trigger early TotalScore exports - points-transition debounce now uses a fixed confirm-hit count (
p5by default) with an FPS-scaled false-gap tolerance, so high-FPS sources keep equivalent gap tolerance without over-delaying transition confirmation - second-pass score selection now uses FPS-adaptive coarse search with rewind (30fps baseline:
+10/ rewind10) before the first hit and again during TotalScore stabilization, reducing wasted frame-by-frame scans - TotalScore stable-signature checks now read total digits only (no race-point OCR in that path) and cache per-frame signatures within each race detail pass to avoid duplicate probe/scan work
- detail-phase fine scanning now uses a 60fps-specific analysis stride (
step=2only for 60fps-class sources); non-60fps sources stay onstep=1 - when that 60fps stride misses transition or stable-total anchors, the same local window is retried once at
step=1as a safety fallback - position-guided player counting now rejects non-finite template scores (
inf/NaN) so malformed row scores cannot create phantom extra players - RaceScore export bundles are now centered on the detected score-transition frame, and the saved
2RaceScoreframes are reused directly by OCR - the OCR position-template matcher now uses the masked
Score_template_white.png/Score_template_black.pngtile path only
Current score-screen support:
- LAN 2 two-player split-screen score layouts
- LAN 1 one-player full-screen score layouts
The score-screen pipeline now auto-detects the supported score layout during extraction.
For 2RaceScore and 3TotalScore, exported frame names and metadata carry the detected
layout tag so OCR can use the matching ROI set directly.
Character OCR also now includes a conservative session-level Mii fallback:
- when one stable player identity repeatedly produces weak, near-tied non-Mii character matches
- and those winning non-Mii matches are unstable across races
- the exported character is relabeled to
Mii - the row receives a short review note:
mii_fallback_unstable_character_match
Character OCR now also includes a roster-family variant refinement pass before that fallback:
- catalog-backed color-variant families such as
Birdo,Yoshi,Shy Guy, andInklingare rescored only against members of the same family - explicit close-cutout families such as
Peach/Pink Gold PeachandMario/Metal Mario/Gold Marioare also compared inside their own family groups - the default/base roster member stays in the family comparison instead of being treated separately
- the refinement uses the same aligned alpha-cutout color scoring as character matching, across the calibrated local alignment offsets, from the saved RaceScore anchor frame
- this is intended to stabilize true family members before the conservative
Miifallback is allowed to relabel them
Family-variant debug probe on saved character crops:
.\.venv\Scripts\python.exe tools\evaluate_character_variant_families.py --crop-dir Output_Results\Debug\character_probe_20260328
Short setup guide for Windows.
Linux or macOS? Read docs/LINUX_MACOS_SETUP.md.
Scan/debug tooling reference: docs/SCAN_DEBUG_TOOLS.md.
GitHub:
For this project itself:
- everything runs from the local
.venvinside this project folder - do not install this app globally with
pip install ... - do not add
mk8-local-playto your system PATH - always run the app from this project folder by using the local
.venvcommand:.\.venv\Scripts\mk8-local-play.exeon Windows.venv/bin/mk8-local-playon Linux/macOS
System-wide installs are only for external tools such as:
- Git
- Python 3.12
- FFmpeg
Choose the folder where you want GitHub to create the project folder.
Example:
- Desktop
- Documents
- a development folder such as
C:\Projects
Open that parent folder in File Explorer.
Then open PowerShell there:
- hold
Shift - right-click in the folder background
- click
Open PowerShell window hereorOpen in Terminal
Important:
- the
git clonecommand in Step 4 will create a new folder namedMarioKart8_lanplay_video_processorinside the folder you opened
Run:
If it works:
- continue to Step 3
If it fails:
- download and install Git for Windows:
- open a new PowerShell window
- run
git --versionagain
Run:
If that does not show Python 3.12, run:
If either command shows Python 3.12:
- continue to Step 4
If Python 3.12 is missing:
- on most Windows 10/11 systems, first try:
- if
wingetis not available or fails, download Python 3.12 manually from: - use Python 3.12 exactly for setup; newer Python versions such as 3.13 or 3.14 are not supported yet
- during install, enable
Add Python to PATHif shown - open a new PowerShell window
- run
py -3.12 --versionagain
Important:
- this installs Python on your system
- the Mario Kart tool itself is still installed only inside this project folder's local
.venv - you do not need a global install of
mk8-local-play
Run:
git clone https://github.com/fromthelake/MarioKart8_lanplay_video_processor cd MarioKart8_lanplay_video_processor
Run:
This setup script:
- creates or reuses the local
.venvin this project folder - uses Python 3.12 specifically and stops if only a newer Python is installed
- installs the app into that local
.venv - installs the Python OCR dependencies, including EasyOCR
- does not require a global install of this app
- does not require adding
mk8-local-playto PATH
If setup succeeds:
- continue to Step 6
If setup fails:
- read the error shown in PowerShell
- fix the missing dependency
- run
./scripts/setup_windows.ps1again
Run:
If the check succeeds:
- continue to Step 7
If the check succeeds, the project is ready to run entirely from:
.\.venv\Scripts\mk8-local-play.exe
Screenshot export format:
- extracted screenshots are controlled by
config/app_config.json->export_image_format - accepted values are
jpg,jpeg, andpng - the current default is
jpgfor smaller exported frame files - use
pngif you want lossless frame exports for troubleshooting or comparison work MK8_EXPORT_IMAGE_FORMATcan still override the config for a single run
Headless debug toggle:
- normal CLI runs can stay lean and skip debug workbook/image output
- use
--debugonmk8-local-play.exeorpython -m mk8_local_play.mainwhen you explicitly want debug CSV, debug workbook, and score-layout images for investigation
Runtime GPU mode defaults:
config/app_config.jsonnow defaultsexecution_modetocpuandeasyocr_gpu_modetoautoexecution_modecontrols OpenCV extraction acceleration and acceptsauto,gpu, orcpueasyocr_gpu_modecontrols EasyOCR and acceptsauto,gpu, orcpu- extraction defaults to
cpubecause that is the fastest verified setting on this machine profile - in
auto, extraction uses CUDA when available and otherwise falls back to CPU - OpenCL extraction remains available through explicit
GPUmode, but is not chosen automatically - when EasyOCR is using GPU, effective OCR workers stay at
1 overlap_ocr_modenow defaults toautooverlap_ocr_consumersnow defaults to2- in overlap
auto, multi-video full runs use the streamed per-race overlap path only when EasyOCR CUDA is available; otherwise runs stay on the existing sequential path - you can still override overlap mode to
videoorrace, and raiseoverlap_ocr_consumerslater for experiments - multi-video initial scan now defaults to
2workers for multi-video runs MK8_PARALLEL_VIDEO_SCAN_WORKERScan still override this manually- higher values such as
3or4oversubscribed the machine in local testing and were slower than2
Recommended performance profile on the current benchmark laptop:
execution_mode=cpueasyocr_gpu_mode=autooverlap_ocr_mode=raceoverlap_ocr_consumers=2MK8_PARALLEL_VIDEO_SCAN_WORKERS=2
Additional extraction defaults now tuned from the full 7-video benchmark:
pass1_scan_workers=4score_analysis_workers=4parallel_video_total_score_workersresolves to2on16+logical CPU threads and1otherwise
This combination is the current best verified throughput profile for the full local tournament benchmark set.
Console output during a run now uses a clearer live format:
- each video gets a stable neon accent color for the whole run
- labels stay neutral while video-owned values are colorized
- workflow ordering is consistent across the input summary, frame-count preflight, scan, and per-video summaries
- scan progress now shows
HH:MM:SS / HH:MM:SSinstead of raw frame counters - live progress uses aligned
Comp/Donefields and includes CPU/RAM/GPU where useful for stall detection - RAM in live progress and phase summaries is reported as percentage
- confirmed scan detections list
Race,Track, andScoreanchors in frame order with source time and frame number - OCR progress uses
Activefor in-flight race bundles and overlap queue labels useQue/AllQue - the final performance summary uses aligned tables for run totals, split phase timings, per-video status, resource peaks, and video-seconds-per-wall-second rate
Time saved by overlapshows the wall-clock time saved through overlap and parallelism
Placeholder identity handling is now tiered:
- normal placeholder rescue still requires repeated multi-race support
- if that fails, a conservative forced-choice fallback can promote a strong top candidate
- forced promotions are marked in the review trail with
placeholder_name_forced_choice
Put your video files in folder:
./Input_Videos/
Optional:
- you can also place videos inside subfolders under
./Input_Videos/ - use
--subfoldersif you want headless runs to include those subfolders
Process everything in Input_Videos:
Process everything in Input_Videos and all subfolders:
Process only the current selected input set, including subfolders:
Process a specific multi-video set by explicit relative file path:
..venv\Scripts\mk8-local-play.exe --selection --subfolders --videos "2026-03-28/Kwalificatie_Groep_1_2026-03-27 20-00-33.mkv" "2026-03-28/Kwalificatie_Groep_2_2026-03-27 20-00-33.mp4" "2026-03-28/Kwalificatie_Groep_3_2026-03-27 20-00-33.mkv"
When --subfolders is used:
- supported videos are discovered recursively under
./Input_Videos/ - exported frame bundles and Excel/CSV
Videonames include a sanitized relative folder path - this avoids naming conflicts when different folders contain files with the same base filename
- with
--videos, explicit relative paths are matched exactly before filename fallback is attempted - with
--videos, folder entries are allowed (for example"2026-03-28"), and will include every supported file in that folder scope
Process only the current selected input set:
Results are written to folder:
./Output_Results/
Extracted race screenshots are written under:
./Output_Results/Frames/
Their file extension follows config/app_config.json -> export_image_format.
Examples:
Output_Results/Frames/Demo_CaptureCard_Race/Race_001/0TrackName.jpgOutput_Results/Frames/Demo_CaptureCard_Race/Race_001/1RaceNumber.jpgOutput_Results/Frames/Demo_CaptureCard_Race/Race_001/2RaceScore/anchor_5869.jpgOutput_Results/Frames/Demo_CaptureCard_Race/Race_001/2RaceScore/consensus_5866.jpgOutput_Results/Frames/Demo_CaptureCard_Race/Race_001/3TotalScore/anchor_5994.jpg
Important:
- score-screen OCR now persists the full frame bundles it uses
- both
--selectionand--ocrread the same saved score bundles anchor_<frame>.jpgis the exported anchor frameconsensus_<frame>.jpgfiles are the neighboring OCR-vote frames used for that score screen
Open the GUI interface:
What it does:
- starts the desktop GUI
- from the GUI you can:
- open the input folder
- merge videos
- run extraction only
- run a scoped selection pass
- toggle subfolder-aware processing
- run OCR/export only
- open the latest Excel output
- clear extracted races or output results
GUI command mapping:
Find Races In Videos- finds and saves the race screens from your videos
Run Selected Videos- does both steps in one go, but only for the selected videos
Also Look In Subfolders- includes videos stored in folders inside
Input_Videos
- includes videos stored in folders inside
Create Excel Results- reads the saved race screens and creates the Excel file
Run everything:
What it does:
- runs extraction on all videos currently present in
Input_Videos - then runs OCR/export on all frames present in
Output_Results/Frames
What it includes:
- the current videos in
Input_Videos - existing extracted frames already present in
Output_Results/Frames
What it does not do:
- it does not limit OCR to only newly extracted frames
Add subfolders to --all:
What it changes:
- extraction also includes supported
.mp4,.mkv,.mkv,.mov,.avi, and.webmfiles found in subfolders underInput_Videos - OCR/export still behaves like
--all, so existing historical frame groups can still be included
Run only the current selected input set:
What it does:
- runs extraction on the currently selected videos in
Input_Videos - then runs OCR/export only for those same video classes
What it includes:
- only the selected/current input videos for this run
- only OCR groups that belong to those selected videos
What it does not do:
- it does not sweep unrelated historical frame groups from older videos
Add subfolders to --selection:
What it changes:
- extraction includes the current selected input set across
Input_Videosand its subfolders - OCR/export stays scoped to only those subfolder-aware video classes
Run extraction only:
What it does:
- scans videos and exports frame bundles into
Output_Results/Frames - does not run OCR or create the final workbook
Run OCR/export only:
What it does:
- runs OCR on the extracted frames currently present in
Output_Results/Frames - writes the workbook output
What it does not do:
- it does not extract frames from videos first
Run OCR/export only, but scoped like --selection:
What it does:
- runs OCR only for the video classes currently selected in
Input_Videos - ignores unrelated historical frame groups from other videos
Run one video only with scoped OCR:
What it does:
- extracts only that one video
- limits OCR/export to that same video class
Recommended use:
- use this when you want a true one-video run
- prefer this over
--all --video ..., because--allcan still include older frame groups during OCR
Run several exact videos together with scoped OCR:
..venv\Scripts\mk8-local-play.exe --selection --subfolders --videos "2026-03-28/Kampioen_2026-03-27 21-50-56.mp4" "2026-03-28/Talent_2026-03-27 21-50-56.mp4" "2026-03-28/Wild_2026-03-27 21-50-56.mp4"
What it does:
- extracts only those explicitly listed files
- limits OCR/export to those same video classes
- keeps multi-video overlap OCR available, so CUDA-backed EasyOCR can still process the selected set together
For the Linux/macOS setup guide, read:
If you want the pipeline, templates, ROIs, and metadata documented for development or reproduction, read: