splitsmith 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. splitsmith-0.2.0/.gitignore +77 -0
  2. splitsmith-0.2.0/CHANGELOG.md +19 -0
  3. splitsmith-0.2.0/LICENSE +21 -0
  4. splitsmith-0.2.0/PKG-INFO +301 -0
  5. splitsmith-0.2.0/README.md +272 -0
  6. splitsmith-0.2.0/docs/local-slim/README.md +114 -0
  7. splitsmith-0.2.0/docs/saas-readiness/README.md +124 -0
  8. splitsmith-0.2.0/pyproject.toml +184 -0
  9. splitsmith-0.2.0/site/README.md +101 -0
  10. splitsmith-0.2.0/skills/splitsmith-match/README.md +60 -0
  11. splitsmith-0.2.0/src/splitsmith/__init__.py +3 -0
  12. splitsmith-0.2.0/src/splitsmith/audit.py +87 -0
  13. splitsmith-0.2.0/src/splitsmith/automation.py +238 -0
  14. splitsmith-0.2.0/src/splitsmith/backup.py +298 -0
  15. splitsmith-0.2.0/src/splitsmith/beep_calibration.py +324 -0
  16. splitsmith-0.2.0/src/splitsmith/beep_detect.py +371 -0
  17. splitsmith-0.2.0/src/splitsmith/cleanup.py +327 -0
  18. splitsmith-0.2.0/src/splitsmith/cli.py +1281 -0
  19. splitsmith-0.2.0/src/splitsmith/coach.py +253 -0
  20. splitsmith-0.2.0/src/splitsmith/coach_distributions.py +348 -0
  21. splitsmith-0.2.0/src/splitsmith/compare/__init__.py +7 -0
  22. splitsmith-0.2.0/src/splitsmith/compare/cli.py +153 -0
  23. splitsmith-0.2.0/src/splitsmith/compare/emitter.py +456 -0
  24. splitsmith-0.2.0/src/splitsmith/compare/filler.py +98 -0
  25. splitsmith-0.2.0/src/splitsmith/compare/layout.py +164 -0
  26. splitsmith-0.2.0/src/splitsmith/compare/manifest.py +91 -0
  27. splitsmith-0.2.0/src/splitsmith/compare/project_loader.py +195 -0
  28. splitsmith-0.2.0/src/splitsmith/composition.py +606 -0
  29. splitsmith-0.2.0/src/splitsmith/config.py +442 -0
  30. splitsmith-0.2.0/src/splitsmith/cross_align.py +210 -0
  31. splitsmith-0.2.0/src/splitsmith/csv_gen.py +66 -0
  32. splitsmith-0.2.0/src/splitsmith/data/ensemble_calibration.json +248 -0
  33. splitsmith-0.2.0/src/splitsmith/data/fonts/Antonio-OFL.txt +93 -0
  34. splitsmith-0.2.0/src/splitsmith/data/fonts/Antonio-VariableFont.ttf +0 -0
  35. splitsmith-0.2.0/src/splitsmith/data/fonts/JetBrainsMono-Bold.ttf +0 -0
  36. splitsmith-0.2.0/src/splitsmith/data/fonts/JetBrainsMono-OFL.txt +93 -0
  37. splitsmith-0.2.0/src/splitsmith/data/overlay_theme.json +40 -0
  38. splitsmith-0.2.0/src/splitsmith/data/templates/action-cut.yaml +19 -0
  39. splitsmith-0.2.0/src/splitsmith/data/templates/match-recap.yaml +20 -0
  40. splitsmith-0.2.0/src/splitsmith/data/voter_c_gbdt.joblib +0 -0
  41. splitsmith-0.2.0/src/splitsmith/data/voter_e_visual_probe.joblib +0 -0
  42. splitsmith-0.2.0/src/splitsmith/ensemble/__init__.py +67 -0
  43. splitsmith-0.2.0/src/splitsmith/ensemble/agc_state.py +165 -0
  44. splitsmith-0.2.0/src/splitsmith/ensemble/api.py +419 -0
  45. splitsmith-0.2.0/src/splitsmith/ensemble/backend.py +89 -0
  46. splitsmith-0.2.0/src/splitsmith/ensemble/calibration.py +367 -0
  47. splitsmith-0.2.0/src/splitsmith/ensemble/clap_mel.py +138 -0
  48. splitsmith-0.2.0/src/splitsmith/ensemble/features.py +680 -0
  49. splitsmith-0.2.0/src/splitsmith/ensemble/fixtures.py +222 -0
  50. splitsmith-0.2.0/src/splitsmith/ensemble/tta.py +115 -0
  51. splitsmith-0.2.0/src/splitsmith/ensemble/visual.py +294 -0
  52. splitsmith-0.2.0/src/splitsmith/ensemble/voters.py +202 -0
  53. splitsmith-0.2.0/src/splitsmith/fcp7xml_render.py +558 -0
  54. splitsmith-0.2.0/src/splitsmith/fcpxml_gen.py +1721 -0
  55. splitsmith-0.2.0/src/splitsmith/fixture_schema.py +482 -0
  56. splitsmith-0.2.0/src/splitsmith/lab/__init__.py +79 -0
  57. splitsmith-0.2.0/src/splitsmith/lab/core.py +1118 -0
  58. splitsmith-0.2.0/src/splitsmith/lab/promote.py +555 -0
  59. splitsmith-0.2.0/src/splitsmith/lab/snap_window.py +331 -0
  60. splitsmith-0.2.0/src/splitsmith/lab/sweeps.py +231 -0
  61. splitsmith-0.2.0/src/splitsmith/lab_cli.py +750 -0
  62. splitsmith-0.2.0/src/splitsmith/match_cli.py +315 -0
  63. splitsmith-0.2.0/src/splitsmith/match_model.py +793 -0
  64. splitsmith-0.2.0/src/splitsmith/match_registry.py +131 -0
  65. splitsmith-0.2.0/src/splitsmith/mcp/__init__.py +23 -0
  66. splitsmith-0.2.0/src/splitsmith/mcp/__main__.py +20 -0
  67. splitsmith-0.2.0/src/splitsmith/mcp/detect_tools.py +476 -0
  68. splitsmith-0.2.0/src/splitsmith/mcp/export_tools.py +356 -0
  69. splitsmith-0.2.0/src/splitsmith/mcp/sandbox.py +77 -0
  70. splitsmith-0.2.0/src/splitsmith/mcp/server.py +393 -0
  71. splitsmith-0.2.0/src/splitsmith/mcp/tools.py +207 -0
  72. splitsmith-0.2.0/src/splitsmith/mcp/write_tools.py +268 -0
  73. splitsmith-0.2.0/src/splitsmith/model_cli.py +153 -0
  74. splitsmith-0.2.0/src/splitsmith/models/__init__.py +40 -0
  75. splitsmith-0.2.0/src/splitsmith/models/cache.py +139 -0
  76. splitsmith-0.2.0/src/splitsmith/models/download.py +95 -0
  77. splitsmith-0.2.0/src/splitsmith/models/errors.py +50 -0
  78. splitsmith-0.2.0/src/splitsmith/models/manifest.py +68 -0
  79. splitsmith-0.2.0/src/splitsmith/models/registry.py +256 -0
  80. splitsmith-0.2.0/src/splitsmith/mp4_render.py +513 -0
  81. splitsmith-0.2.0/src/splitsmith/overlay_render.py +817 -0
  82. splitsmith-0.2.0/src/splitsmith/overlay_theme.py +146 -0
  83. splitsmith-0.2.0/src/splitsmith/relink.py +245 -0
  84. splitsmith-0.2.0/src/splitsmith/report.py +258 -0
  85. splitsmith-0.2.0/src/splitsmith/runtime.py +268 -0
  86. splitsmith-0.2.0/src/splitsmith/shot_detect.py +506 -0
  87. splitsmith-0.2.0/src/splitsmith/shot_refine.py +252 -0
  88. splitsmith-0.2.0/src/splitsmith/system_check.py +162 -0
  89. splitsmith-0.2.0/src/splitsmith/templates.py +188 -0
  90. splitsmith-0.2.0/src/splitsmith/thumbnail.py +230 -0
  91. splitsmith-0.2.0/src/splitsmith/trim.py +211 -0
  92. splitsmith-0.2.0/src/splitsmith/ui/__init__.py +10 -0
  93. splitsmith-0.2.0/src/splitsmith/ui/audio.py +536 -0
  94. splitsmith-0.2.0/src/splitsmith/ui/embedded.py +312 -0
  95. splitsmith-0.2.0/src/splitsmith/ui/exports.py +533 -0
  96. splitsmith-0.2.0/src/splitsmith/ui/jobs.py +652 -0
  97. splitsmith-0.2.0/src/splitsmith/ui/logging_setup.py +108 -0
  98. splitsmith-0.2.0/src/splitsmith/ui/match_exports.py +500 -0
  99. splitsmith-0.2.0/src/splitsmith/ui/project.py +1734 -0
  100. splitsmith-0.2.0/src/splitsmith/ui/scoreboard/__init__.py +77 -0
  101. splitsmith-0.2.0/src/splitsmith/ui/scoreboard/cache.py +237 -0
  102. splitsmith-0.2.0/src/splitsmith/ui/scoreboard/http.py +206 -0
  103. splitsmith-0.2.0/src/splitsmith/ui/scoreboard/local.py +377 -0
  104. splitsmith-0.2.0/src/splitsmith/ui/scoreboard/models.py +301 -0
  105. splitsmith-0.2.0/src/splitsmith/ui/scoreboard/protocol.py +51 -0
  106. splitsmith-0.2.0/src/splitsmith/ui/server.py +9178 -0
  107. splitsmith-0.2.0/src/splitsmith/ui_static/.gitignore +4 -0
  108. splitsmith-0.2.0/src/splitsmith/ui_static/index.html +13 -0
  109. splitsmith-0.2.0/src/splitsmith/ui_static/package-lock.json +3062 -0
  110. splitsmith-0.2.0/src/splitsmith/ui_static/package.json +38 -0
  111. splitsmith-0.2.0/src/splitsmith/ui_static/pnpm-lock.yaml +2033 -0
  112. splitsmith-0.2.0/src/splitsmith/ui_static/src/App.tsx +167 -0
  113. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/AddFootageModal.tsx +711 -0
  114. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/AppShell.tsx +228 -0
  115. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/AuditControls.tsx +205 -0
  116. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/BeepSection.tsx +1472 -0
  117. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/CameraModelSelect.tsx +132 -0
  118. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/CleanupDialog.tsx +345 -0
  119. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/DirectoryPickerModal.tsx +45 -0
  120. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/FolderPicker.tsx +920 -0
  121. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/HelpOverlay.tsx +183 -0
  122. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/HitlQueuePanel.tsx +214 -0
  123. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/Jobs.tsx +685 -0
  124. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ListDrawer.tsx +274 -0
  125. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/MarkerGlyph.tsx +107 -0
  126. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/MarkerLayer.tsx +361 -0
  127. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/MountSelect.tsx +94 -0
  128. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/RelinkDialog.tsx +418 -0
  129. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/SettingProvenance.tsx +66 -0
  130. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ShooterScopedRoute.tsx +34 -0
  131. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ShotStepper.tsx +120 -0
  132. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/StageTimeSection.tsx +203 -0
  133. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/SweepsCard.tsx +379 -0
  134. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/VideoPanel.tsx +508 -0
  135. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/Waveform.tsx +331 -0
  136. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/AnomalyChips.tsx +69 -0
  137. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/AnomalyPins.tsx +62 -0
  138. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/BeepStatusChip.tsx +189 -0
  139. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/CamGridModal.tsx +217 -0
  140. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/CamSyncPill.tsx +136 -0
  141. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/MultiCamColumn.tsx +448 -0
  142. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/PrereqGate.tsx +480 -0
  143. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/SessionSummary.tsx +128 -0
  144. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/StageActionBar.tsx +204 -0
  145. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/StageChipRail.tsx +85 -0
  146. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/SyncBanner.tsx +157 -0
  147. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/developer/DeveloperShell.tsx +362 -0
  148. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/match/MatchShell.tsx +449 -0
  149. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/match/MatchSidebar.tsx +452 -0
  150. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/match/ShooterChipStrip.tsx +149 -0
  151. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/AvatarStack.tsx +115 -0
  152. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Brand.tsx +53 -0
  153. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/ContextBar.tsx +72 -0
  154. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/DisplayHeading.tsx +45 -0
  155. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/IconButton.tsx +59 -0
  156. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Kbd.tsx +26 -0
  157. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Kicker.tsx +40 -0
  158. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/ModeSwitch.tsx +80 -0
  159. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Readout.tsx +75 -0
  160. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/ShotTimerShell.tsx +73 -0
  161. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/StageDot.tsx +135 -0
  162. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/StatusPill.tsx +72 -0
  163. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Tick.tsx +77 -0
  164. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/badge.tsx +45 -0
  165. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/button.tsx +55 -0
  166. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/card.tsx +75 -0
  167. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/index.ts +34 -0
  168. splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/skeleton.tsx +12 -0
  169. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/anomalies.ts +173 -0
  170. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/api.ts +2965 -0
  171. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/audit-input.ts +94 -0
  172. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/audit-next-step.ts +73 -0
  173. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/features.ts +48 -0
  174. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/matchHref.ts +56 -0
  175. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/mode.tsx +54 -0
  176. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/slugify.ts +17 -0
  177. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/stageStatus.ts +98 -0
  178. splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/utils.ts +44 -0
  179. splitsmith-0.2.0/src/splitsmith/ui_static/src/main.tsx +11 -0
  180. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Audit.tsx +2869 -0
  181. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/BeepReview.tsx +759 -0
  182. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Coach.tsx +1562 -0
  183. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Compare.tsx +1172 -0
  184. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/CreateMatch.tsx +1567 -0
  185. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Design.tsx +515 -0
  186. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Export.tsx +1158 -0
  187. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Home.tsx +1039 -0
  188. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Ingest.tsx +1131 -0
  189. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Lab.tsx +3356 -0
  190. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/MergeMatches.tsx +636 -0
  191. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Pick.tsx +986 -0
  192. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/PromoteReview.tsx +1006 -0
  193. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Review.tsx +981 -0
  194. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Shooters.tsx +566 -0
  195. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevCorpus.tsx +436 -0
  196. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevRetrain.tsx +610 -0
  197. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevReviewQueue.tsx +460 -0
  198. splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevValidate.tsx +726 -0
  199. splitsmith-0.2.0/src/splitsmith/ui_static/src/styles/index.css +553 -0
  200. splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.app.json +24 -0
  201. splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.app.tsbuildinfo +1 -0
  202. splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.json +7 -0
  203. splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.node.json +18 -0
  204. splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.node.tsbuildinfo +1 -0
  205. splitsmith-0.2.0/src/splitsmith/ui_static/vite.config.ts +29 -0
  206. splitsmith-0.2.0/src/splitsmith/user_config.py +380 -0
  207. splitsmith-0.2.0/src/splitsmith/video_match.py +159 -0
  208. splitsmith-0.2.0/src/splitsmith/video_probe.py +143 -0
  209. splitsmith-0.2.0/src/splitsmith/waveform.py +121 -0
  210. splitsmith-0.2.0/src/splitsmith/youtube_sidecar.py +293 -0
  211. splitsmith-0.2.0/tests/fixtures/beep_calibration/README.md +58 -0
  212. splitsmith-0.2.0/tests/fixtures/schemas/README.md +53 -0
  213. splitsmith-0.2.0/uv.lock +3660 -0
@@ -0,0 +1,77 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ *.egg-info/
13
+ dist/
14
+ build/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # uv
19
+ .python-version
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+ *.swo
26
+ .DS_Store
27
+
28
+ # Local env files (contain bearer tokens for scoreboard.urdr.dev etc).
29
+ .env
30
+ .env.local
31
+ .env.*.local
32
+
33
+ # Project-specific
34
+ analysis/
35
+ match_raw/
36
+ *.mp4
37
+ *.mov
38
+ *.wav
39
+ # Whitelist small audio fixtures only. Video fixtures (e.g. stage_sample.mp4 at
40
+ # ~580 MB) live on disk for integration tests but are kept out of git history;
41
+ # track them via git-lfs or an external store if/when they need to be shared.
42
+ !tests/fixtures/*.wav
43
+ # But keep CLI-cached extractions (named identically to their source video)
44
+ # out of git -- they're derivable from the video on demand.
45
+ tests/fixtures/stage_sample.wav
46
+ config.yaml
47
+
48
+ # Review-server save backups (auto-created on every Cmd+S in the audit UI).
49
+ *.json.bak
50
+
51
+ # Audit safety backups created before destructive fixture migrations.
52
+ *.json.before-*
53
+
54
+ # Cached PANN embeddings (regenerable via scripts/extract_audio_embeddings.py).
55
+ tests/fixtures/.cache/
56
+
57
+ # Wide-window fixture audio + sidecars produced by
58
+ # scripts/extract_full_fixture_audio.py (issue #87 negative mining).
59
+ # Regenerable from the source videos listed in _sources.yaml.
60
+ tests/fixtures/full/*
61
+ !tests/fixtures/full/_sources.yaml
62
+ !tests/fixtures/full/.gitkeep
63
+
64
+ # Waveform peaks caches written by splitsmith.waveform.ensure_peaks (#15).
65
+ # Regenerable from the WAV; sidecar JSON next to the audio file.
66
+ *.peaks-*.json
67
+ # And audit-mode trim params sidecars from splitsmith.ui.audio.
68
+ *_trimmed.params.json
69
+ .claude/
70
+
71
+ # Wrangler local state for Cloudflare Pages deploys.
72
+ .wrangler/
73
+
74
+ # Node tooling for the marketing site (wrangler dev dep). The pnpm
75
+ # lockfile *is* committed (under pnpm-lock.yaml) so wrangler version
76
+ # stays reproducible across CI / contributors -- only ignore node_modules.
77
+ node_modules/
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (2026-05-24)
4
+
5
+ First public release.
6
+
7
+ Extract IPSC shot splits from head-mounted camera footage. Detect shots
8
+ via a 3-voter ensemble (envelope onset / CLAP / GBDT-with-PANN), produce
9
+ a CSV of splits, and emit an FCPXML timeline with per-shot markers and
10
+ optional overlay clips for Final Cut Pro.
11
+
12
+ Install:
13
+
14
+ ```
15
+ uv tool install splitsmith
16
+ ```
17
+
18
+ After install, run `splitsmith fetch-models` to pre-download the ~440 MB
19
+ of ONNX detection artifacts (otherwise they download on first detection).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mathias Axell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: splitsmith
3
+ Version: 0.2.0
4
+ Summary: Extract IPSC shot splits from head-mounted camera footage
5
+ Author: Mathias Axell
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: fastapi>=0.115.0
10
+ Requires-Dist: httpx>=0.28.0
11
+ Requires-Dist: huggingface-hub>=0.26.0
12
+ Requires-Dist: joblib>=1.4.0
13
+ Requires-Dist: librosa>=0.10.2
14
+ Requires-Dist: mcp>=1.0.0
15
+ Requires-Dist: numpy>=1.26.0
16
+ Requires-Dist: onnxruntime>=1.20.0
17
+ Requires-Dist: pillow>=10.0.0
18
+ Requires-Dist: pydantic-settings>=2.6.0
19
+ Requires-Dist: pydantic>=2.7.0
20
+ Requires-Dist: python-dotenv>=1.0.0
21
+ Requires-Dist: pyyaml>=6.0.1
22
+ Requires-Dist: rich>=13.7.0
23
+ Requires-Dist: scikit-learn>=1.8.0
24
+ Requires-Dist: scipy>=1.13.0
25
+ Requires-Dist: soundfile>=0.12.1
26
+ Requires-Dist: typer>=0.12.0
27
+ Requires-Dist: uvicorn[standard]>=0.30.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # splitsmith
31
+
32
+ Extract per-shot split times from head-mounted camera footage of IPSC matches and generate Final Cut Pro timelines with per-shot markers.
33
+
34
+ ![audit view](docs/screenshots/audit.png)
35
+
36
+ Built to do two things from a single stage video: get per-shot splits for analysis and coaching, and prepare frame-marked clips for match-footage review. Your head-mounted cam (Insta360 Go 3S in this case) already captures audio of every shot; the RO's timer only records your total stage time, so the splits live in the video and nowhere else. Splitsmith extracts them and turns them into a CSV plus an FCPXML timeline with per-shot markers you can step through in Final Cut Pro.
37
+
38
+ **Inputs:** raw MP4s from a head-mounted cam, stage time data from SSI Scoreboard.
39
+ **Outputs (per stage):** lossless trim around the start beep, splits CSV, FCPXML with frame-aligned markers, anomaly report.
40
+
41
+ ## What it looks like
42
+
43
+ | | |
44
+ |---|---|
45
+ | ![ingest](docs/screenshots/ingest.png) | **Ingest.** Drop a folder of GoPro clips; the engine auto-matches them to stages by file timestamp. |
46
+ | ![beep review](docs/screenshots/beep-review.png) | **Beep review.** Auto-snap to the start beep on each stage; low-confidence detections land in a HITL queue. |
47
+ | ![audit](docs/screenshots/audit.png) | **Audit.** Waveform + per-shot markers from the 3-voter ensemble. Click a marker to inspect votes; drag to fine-tune. |
48
+ | ![compare](docs/screenshots/compare.png) | **Compare.** Multi-shooter grid, all beep-aligned to t=0. Audio from one shooter, video tiles for everyone else. |
49
+ | ![export](docs/screenshots/export.png) | **Export.** Per-stage or whole-match FCPXML. Open in Final Cut Pro, M / Shift+M to navigate markers. |
50
+
51
+ > Screenshots regenerate from a live `splitsmith ui` via `scripts/capture_screenshots.py`. See [issue tracking the local run](#regenerating-screenshots) below.
52
+
53
+ ## Quickstart
54
+
55
+ > The slim wheel install (`uv tool install splitsmith`) is the target end-user path once the wheel is published to PyPI -- the engine work is done (#377), publishing is the next step. Until then, install from source as described below.
56
+
57
+ You need: `uv`, `ffmpeg`/`ffprobe` on PATH, and (for source checkouts) Node 20 + `pnpm` to build the SPA. See [Install](#install) for OS-specific package commands.
58
+
59
+ ```bash
60
+ # Install from source (today's recommended path)
61
+ git clone https://github.com/mandakan/splitsmith.git
62
+ cd splitsmith
63
+ uv sync # slim runtime deps only (~100 MB)
64
+ (cd src/splitsmith/ui_static && pnpm install && pnpm build)
65
+ uv run splitsmith fetch-models # ~440 MB ONNX artifacts; one-time
66
+
67
+ # Bring your own head-cam clip and stage time (or use any IPSC video at hand)
68
+ uv run splitsmith single \
69
+ --video path/to/your_stage.mp4 \
70
+ --time 14.74 \
71
+ --output ./demo_analysis \
72
+ --stage-name "Per told me to do it" \
73
+ --stage-number 3
74
+
75
+ ls -la ./demo_analysis/
76
+ cat ./demo_analysis/stage3_per-told-me-to-do-it_report.txt
77
+ ```
78
+
79
+ For the full ingest -> audit -> export workflow with persistent project state, use the UI:
80
+
81
+ ```bash
82
+ uv run splitsmith ui --project ~/matches/your-match
83
+ ```
84
+
85
+ The repo ships a real Stage 3 audio sample at `tests/fixtures/stage-shots-tallmilan-2026-stage3-s97dcec94.wav` (Tallmilan 2026, 14.74s, 14 audited shots) plus its sibling JSON with ground-truth shot times. The companion source MP4 is gitignored -- bring your own video to exercise the full ingest pipeline.
86
+
87
+ ## The workflow
88
+
89
+ 1. **Ingest** -- point at a folder of raw cam files; `splitsmith ui` auto-matches them to scoreboard stages by file timestamp.
90
+ 2. **Beep review** -- the detector finds the start beep on each stage; anything below the auto-trust threshold lands in a HITL queue.
91
+ 3. **Audit** -- the 3-voter ensemble (envelope onsets + CLAP prompts + GBDT over hand-crafted + PANN features) emits shot times; review the waveform and drag / drop markers to fix outliers.
92
+ 4. **Export** -- generate a per-stage FCPXML (markers per shot) or a multi-shooter Compare FCPXML (beep-aligned grid). Splits CSV ships alongside for the cull workflow.
93
+
94
+ ## Install
95
+
96
+ ### System prerequisites
97
+
98
+ - **OS.** macOS (primary target), Linux, or Windows. FCPXML is generated everywhere but Final Cut Pro itself is macOS-only -- on Linux/Windows you'll need to copy the `.fcpxml` to a Mac to open it (or just use the splits CSV directly).
99
+ - **Python 3.11+** via `uv`
100
+ - macOS: `brew install uv`
101
+ - Linux: `curl -LsSf https://astral.sh/uv/install.sh | sh`
102
+ - Windows: `winget install --id=astral-sh.uv -e`
103
+ - **`ffmpeg` and `ffprobe`** on `PATH`
104
+ - macOS: `brew install ffmpeg`  ·  Linux: `apt install ffmpeg`  ·  Windows: `winget install --id=Gyan.FFmpeg -e`
105
+
106
+ `splitsmith ui` checks for both on first launch and prints a copy-pasteable install hint if they're missing.
107
+
108
+ ### Option 1: slim wheel (end users, ~100 MB) -- pending first PyPI release
109
+
110
+ The shipped install will be `uv tool install splitsmith` once the first release-please release lands on `main` (see [Releases](#releases) below). Detection models (~440 MB total) download from `models.splitsmith.app` on first detection -- pre-fetch with `splitsmith fetch-models`. No torch, transformers, or panns_inference in the install.
111
+
112
+ ```bash
113
+ uv tool install splitsmith # available after the first 0.x.y release; see Option 2 below until then
114
+ splitsmith fetch-models # pre-fetch ONNX artifacts (optional)
115
+ splitsmith ui --project ~/matches/your-match
116
+ ```
117
+
118
+ ### Option 2: from source (today's recommended path; required for contributors)
119
+
120
+ ```bash
121
+ git clone https://github.com/mandakan/splitsmith.git
122
+ cd splitsmith
123
+ uv sync # slim runtime deps only (~100 MB)
124
+ (cd src/splitsmith/ui_static && pnpm install && pnpm build)
125
+ uv run splitsmith fetch-models # ~440 MB ONNX artifacts; one-time
126
+ uv run splitsmith --help
127
+ ```
128
+
129
+ Source checkouts also need **Node.js 20+ and `pnpm`** to build the SPA:
130
+ - macOS: `brew install node && npm install -g pnpm`
131
+ - Linux: `apt install nodejs npm && sudo npm install -g pnpm`
132
+ - Windows: `winget install -e --id OpenJS.NodeJS.LTS`, open a new shell, then `npm install -g pnpm`
133
+
134
+ ### Option 3: contributor install (adds torch + tests + export tooling)
135
+
136
+ For running the test suite, rebuilding the ONNX artifacts, or enabling the optional Voter E visual probe:
137
+
138
+ ```bash
139
+ uv sync --all-groups # adds torch / transformers / panns / pytest / ruff / onnx export tools
140
+ uv run pytest -q # unit tests
141
+ uv run pytest -q -m integration # ffmpeg/ffprobe-backed tests
142
+ ```
143
+
144
+ ## Runtime backends (slim ONNX vs dev torch)
145
+
146
+ The shipped runtime uses [ONNX Runtime](https://onnxruntime.ai/) for Voter B (CLAP) and Voter D (PANN gunshot probability, folded into Voter C). The slim install carries no torch, no transformers, no panns_inference. The first detection downloads ~440 MB of ONNX artifacts from `models.splitsmith.app` into `~/.splitsmith/models/` (pre-fetch with `splitsmith fetch-models`).
147
+
148
+ The dev backend (torch + transformers + panns_inference) lives in the `[dev]` group and is required for: re-running `scripts/build_ensemble_artifacts.py`, the ONNX export scripts (`scripts/export_pann_onnx.py`, `scripts/export_clap_onnx.py`), and enabling Voter E (CLIP visual probe -- off by default; turn on with `SPLITSMITH_ENABLE_VOTER_E=1` after `uv sync --all-groups`).
149
+
150
+ `select_backend()` resolves to torch when both backends are importable (dev path with HF caches), and to onnxruntime by elimination otherwise (slim install). Override per-process with `SPLITSMITH_BACKEND=torch|onnx`. See [`docs/local-slim/`](docs/local-slim/) for the full design.
151
+
152
+ ## Subcommands
153
+
154
+ All commands run as `uv run splitsmith <subcommand>`. Pass `--help` for the full option list. See [`docs/COMMANDS.md`](docs/COMMANDS.md) for usage examples per command.
155
+
156
+ | command | purpose |
157
+ |---|---|
158
+ | `ui` | Production SPA. Persistent project state on disk; the main entry point. |
159
+ | `detect` | One-shot beep + shot detection preview. No files written. |
160
+ | `single` | Full pipeline on one video with an explicit stage time. |
161
+ | `process` | Batch over an SSI Scoreboard stage JSON, matching videos to stages by timestamp. |
162
+ | `compare` | Render a multi-shooter beep-aligned grid FCPXML across N projects. |
163
+ | `lab` | Algorithm Lab -- fixture eval, parameter sweeps, live tuning. |
164
+ | `review` | Single-page UI for auditing detected shots against a fixture WAV. |
165
+ | `audit-apply` | Merge an audited candidates CSV back into a fixture JSON. |
166
+ | `fcpxml` | Regenerate a timeline from a hand-edited splits CSV. |
167
+ | `mcp` | Model Context Protocol server; pairs with the `/splitsmith-match` Claude Code skill. |
168
+
169
+ ## Configuration
170
+
171
+ Defaults live in `src/splitsmith/config.py`. Override via `--config path/to/config.yaml`:
172
+
173
+ ```yaml
174
+ beep_detect:
175
+ freq_min_hz: 2000
176
+ freq_max_hz: 5000
177
+ min_duration_ms: 150
178
+ # Cutoff = max(min_amplitude * peak, noise_floor_factor * noise_floor, min_abs_peak).
179
+ min_amplitude: 0.05
180
+ min_abs_peak: 0.005
181
+ noise_floor_factor: 5.0
182
+ envelope_smoothing_ms: 40.0
183
+ tonal_band_lo_hz: 2200
184
+ tonal_band_hi_hz: 3500
185
+ tonal_weight: 0.7
186
+ dur_match_min_ms: 150.0
187
+ dur_match_full_ms: 300.0
188
+ dur_match_weight: 1.0
189
+ silence_window_s: 1.5
190
+ silence_pre_skip_s: 0.2
191
+ min_pre_window_s: 0.2
192
+ search_window_s: 30.0
193
+ top_n_candidates: 5
194
+
195
+ shot_detect:
196
+ min_gap_ms: 80
197
+ onset_delta: 0.07
198
+ pre_max_ms: 30
199
+ post_max_ms: 30
200
+
201
+ video_match:
202
+ tolerance_minutes: 15
203
+ prefer_ctime: true
204
+
205
+ output:
206
+ trim_buffer_seconds: 5.0
207
+ trim_mode: lossless # "lossless" (CLI default) or "audit"
208
+ trim_gop_frames: 15 # audit mode: keyframe every N frames (0.5s @ 30fps)
209
+ trim_audit_crf: 20 # audit mode: x264 CRF (lower = better quality, larger files)
210
+ trim_audit_preset: fast # audit mode: x264 preset
211
+ fcpxml_version: "1.10"
212
+ split_color_thresholds:
213
+ green_max: 0.25
214
+ yellow_max: 0.35
215
+ transition_min: 1.0
216
+ ```
217
+
218
+ `trim_mode` controls how `trim.py` cuts videos:
219
+
220
+ - `lossless` (default): `ffmpeg -c copy`. Instant; archival quality; inherits the source GOP. Insta360 head-cam typically has keyframes every 1-4 seconds.
221
+ - `audit`: re-encodes with a short GOP (default 0.5s) so browser `<video>` scrubbing in the production UI's audit screen lands within ~1 frame of the pointer. Encoding cost is roughly 1-2x realtime on Apple Silicon. Audio is stream-copied either way so the detector's input is bit-exact across modes.
222
+
223
+ Override per command via `--trim-mode lossless|audit` on `splitsmith single` and `splitsmith process`.
224
+
225
+ Lower `shot_detect.onset_delta` if you're under-detecting shots from a heavily-comped open gun. Tighten `beep_detect.min_amplitude` if a louder ambient noise is being mistaken for the beep.
226
+
227
+ ## Output file layout
228
+
229
+ ```
230
+ analysis/
231
+ stage3_per-told-me-to-do-it_trimmed.mp4 # lossless cut around the beep
232
+ stage3_per-told-me-to-do-it_splits.csv # editable; the source of truth for fcpxml regen
233
+ stage3_per-told-me-to-do-it.fcpxml # open in Final Cut Pro; M / Shift+M to navigate markers
234
+ stage3_per-told-me-to-do-it_report.txt # human-readable summary + anomalies
235
+ ```
236
+
237
+ A `.wav` cache file is written next to each source video on first run -- this is intentional (re-running `detect` on the same video skips the audio extraction). Add `*.wav` to your match-videos directory's `.gitignore` if it's tracked.
238
+
239
+ ## What's in git
240
+
241
+ Source MP4/MOV recordings are gitignored (multi-GB; not worth git-LFS for a personal tool). The committed inputs are:
242
+
243
+ - `tests/fixtures/*.wav` -- pre-trimmed audio slices (~1-2 MB each), the canonical input for every detection / classification / eval script.
244
+ - `tests/fixtures/*.json` -- audited shot times (ground truth) plus `_candidates_pending_audit` (raw detector output) and a `source` / `fixture_window_in_source` provenance pair naming the original video and time window.
245
+
246
+ Anyone with the repo can run the full pipeline (beep / shot detection, PANN + CLAP feature extraction, ensemble eval) against the WAVs without touching the source videos. What you can't reproduce without the original MP4 is the `audit-prep` step itself -- if you want a different trim window, padding, or beep override, you need the source video. That step is "input data preparation" rather than "pipeline reproduction"; the WAV is the canonical artefact downstream.
247
+
248
+ ## Detection methodology
249
+
250
+ The detector is built around half-rise leading-edge timing (consistent across recordings; insensitive to AGC and gain) plus a 3-voter ensemble for shot classification. Full write-up in [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md): beep detection, shot detection, the half-rise rationale, confidence ranking, and the ensemble performance dashboard.
251
+
252
+ ## The audited corpus
253
+
254
+ Shot detection is driven by a 3-voter ensemble (envelope onsets, CLAP audio embeddings, and a per-camera-class GBDT) calibrated against the audited fixtures under `tests/fixtures/`. Each fixture is a real beep-to-last-shot audio slice with hand-labeled shot times plus a `camera` / `shooter` / `stage_rounds` provenance block.
255
+
256
+ The corpus grows over time -- right now I curate it by hand from my own matches and a few squadmates'. After dropping new audited fixtures into `tests/fixtures/`, rebuild the shipped calibration artifacts (`src/splitsmith/data/ensemble_calibration.json` and `src/splitsmith/data/voter_c_gbdt.joblib`) with:
257
+
258
+ ```bash
259
+ uv run python scripts/build_ensemble_artifacts.py
260
+ ```
261
+
262
+ ### Contributing audited stages (planned)
263
+
264
+ Soon I want to open the corpus to opt-in contributions from anyone running Splitsmith -- a per-stage "share this audited stage to improve detection" prompt at promotion time, so contributors grow the corpus without ever sharing a stage they didn't actively audit. Default will be off; nothing leaves your machine without explicit consent.
265
+
266
+ ## Regenerating screenshots
267
+
268
+ The README gallery comes from a live `splitsmith ui` driven through Playwright. To refresh after a UI redesign:
269
+
270
+ ```bash
271
+ uv pip install --with playwright playwright
272
+ playwright install chromium
273
+
274
+ uv run python scripts/capture_screenshots.py \
275
+ --project ~/matches/your-real-match \
276
+ --stage 3 \
277
+ --output docs/screenshots/
278
+ ```
279
+
280
+ Commit the resulting PNGs. The script needs a project that's already been through the workflow (videos ingested, beeps reviewed, shots detected); Compare requires multiple shooters' trims on disk and is skipped with a warning otherwise.
281
+
282
+ ## Troubleshooting
283
+
284
+ - **"ffmpeg binary not found" / "ffprobe binary not found"** -- install via your platform's package manager (macOS `brew install ffmpeg`, Linux `apt install ffmpeg`, Windows `winget install --id=Gyan.FFmpeg`), or set `--ffmpeg-binary` if installed elsewhere.
285
+ - **`process` aborts with "Ambiguous stages"** -- two videos fall in the same stage's window, or one video matches multiple stages. Either narrow the input directory or use `single` for each stage.
286
+ - **Report shows ">> 32 shots, possible false positives"** -- expected on busy ranges. Cull in the CSV and regenerate the FCPXML.
287
+ - **Last shot is detected after the official stage time** -- usually a neighbouring-bay shot fired during your last shot's echo window. Drop it in the CSV.
288
+ - **No FCPXML markers visible in FCP** -- ensure FCP 11.x or later (FCPXML 1.10 is required). Older versions need an export of the timeline into 1.9.
289
+
290
+ ## Releases
291
+
292
+ Versioning is driven by [release-please](https://github.com/googleapis/release-please) on `main`:
293
+
294
+ - Conventional commits (`feat:`, `fix:`, `perf:`, `refactor:`, ...) accumulate into an always-open `chore: release X.Y.Z` PR that bumps `pyproject.toml` and `CHANGELOG.md`. Merging that PR cuts the tag + GitHub Release.
295
+ - The GitHub Release triggers `.github/workflows/release-please.yml`'s `publish-pypi` job, which builds the slim wheel (SPA included via the same SPA-build step as the smoke job) and runs `uv publish --trusted-publishing automatic`.
296
+ - No PyPI API token in repo secrets -- authentication uses [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC). One-time setup: at <https://pypi.org/manage/account/publishing/>, add a "pending publisher" for project name `splitsmith`, owner `mandakan`, repo `splitsmith`, workflow `release-please.yml`, environment `pypi`.
297
+ - Model artifacts on R2 are SHA-pinned in `src/splitsmith/data/ensemble_calibration.json` and decoupled from the app version. Re-uploads go through `scripts/upload_model_artifacts.py`; the calibration file is the source of truth that the wheel ships with.
298
+
299
+ ## Project status
300
+
301
+ Personal tool, actively developed for the maintainer's own match-recap workflow. The pipeline structure is intentionally narrow (single-shooter or multi-shooter compare, IPSC-style start beep + shot timing, FCPXML output) and not aimed at being a general-purpose audio analysis library. PRs welcome where they fit the use case; see `SPEC.md` and `CLAUDE.md` for the design priorities.