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.
- splitsmith-0.2.0/.gitignore +77 -0
- splitsmith-0.2.0/CHANGELOG.md +19 -0
- splitsmith-0.2.0/LICENSE +21 -0
- splitsmith-0.2.0/PKG-INFO +301 -0
- splitsmith-0.2.0/README.md +272 -0
- splitsmith-0.2.0/docs/local-slim/README.md +114 -0
- splitsmith-0.2.0/docs/saas-readiness/README.md +124 -0
- splitsmith-0.2.0/pyproject.toml +184 -0
- splitsmith-0.2.0/site/README.md +101 -0
- splitsmith-0.2.0/skills/splitsmith-match/README.md +60 -0
- splitsmith-0.2.0/src/splitsmith/__init__.py +3 -0
- splitsmith-0.2.0/src/splitsmith/audit.py +87 -0
- splitsmith-0.2.0/src/splitsmith/automation.py +238 -0
- splitsmith-0.2.0/src/splitsmith/backup.py +298 -0
- splitsmith-0.2.0/src/splitsmith/beep_calibration.py +324 -0
- splitsmith-0.2.0/src/splitsmith/beep_detect.py +371 -0
- splitsmith-0.2.0/src/splitsmith/cleanup.py +327 -0
- splitsmith-0.2.0/src/splitsmith/cli.py +1281 -0
- splitsmith-0.2.0/src/splitsmith/coach.py +253 -0
- splitsmith-0.2.0/src/splitsmith/coach_distributions.py +348 -0
- splitsmith-0.2.0/src/splitsmith/compare/__init__.py +7 -0
- splitsmith-0.2.0/src/splitsmith/compare/cli.py +153 -0
- splitsmith-0.2.0/src/splitsmith/compare/emitter.py +456 -0
- splitsmith-0.2.0/src/splitsmith/compare/filler.py +98 -0
- splitsmith-0.2.0/src/splitsmith/compare/layout.py +164 -0
- splitsmith-0.2.0/src/splitsmith/compare/manifest.py +91 -0
- splitsmith-0.2.0/src/splitsmith/compare/project_loader.py +195 -0
- splitsmith-0.2.0/src/splitsmith/composition.py +606 -0
- splitsmith-0.2.0/src/splitsmith/config.py +442 -0
- splitsmith-0.2.0/src/splitsmith/cross_align.py +210 -0
- splitsmith-0.2.0/src/splitsmith/csv_gen.py +66 -0
- splitsmith-0.2.0/src/splitsmith/data/ensemble_calibration.json +248 -0
- splitsmith-0.2.0/src/splitsmith/data/fonts/Antonio-OFL.txt +93 -0
- splitsmith-0.2.0/src/splitsmith/data/fonts/Antonio-VariableFont.ttf +0 -0
- splitsmith-0.2.0/src/splitsmith/data/fonts/JetBrainsMono-Bold.ttf +0 -0
- splitsmith-0.2.0/src/splitsmith/data/fonts/JetBrainsMono-OFL.txt +93 -0
- splitsmith-0.2.0/src/splitsmith/data/overlay_theme.json +40 -0
- splitsmith-0.2.0/src/splitsmith/data/templates/action-cut.yaml +19 -0
- splitsmith-0.2.0/src/splitsmith/data/templates/match-recap.yaml +20 -0
- splitsmith-0.2.0/src/splitsmith/data/voter_c_gbdt.joblib +0 -0
- splitsmith-0.2.0/src/splitsmith/data/voter_e_visual_probe.joblib +0 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/__init__.py +67 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/agc_state.py +165 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/api.py +419 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/backend.py +89 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/calibration.py +367 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/clap_mel.py +138 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/features.py +680 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/fixtures.py +222 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/tta.py +115 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/visual.py +294 -0
- splitsmith-0.2.0/src/splitsmith/ensemble/voters.py +202 -0
- splitsmith-0.2.0/src/splitsmith/fcp7xml_render.py +558 -0
- splitsmith-0.2.0/src/splitsmith/fcpxml_gen.py +1721 -0
- splitsmith-0.2.0/src/splitsmith/fixture_schema.py +482 -0
- splitsmith-0.2.0/src/splitsmith/lab/__init__.py +79 -0
- splitsmith-0.2.0/src/splitsmith/lab/core.py +1118 -0
- splitsmith-0.2.0/src/splitsmith/lab/promote.py +555 -0
- splitsmith-0.2.0/src/splitsmith/lab/snap_window.py +331 -0
- splitsmith-0.2.0/src/splitsmith/lab/sweeps.py +231 -0
- splitsmith-0.2.0/src/splitsmith/lab_cli.py +750 -0
- splitsmith-0.2.0/src/splitsmith/match_cli.py +315 -0
- splitsmith-0.2.0/src/splitsmith/match_model.py +793 -0
- splitsmith-0.2.0/src/splitsmith/match_registry.py +131 -0
- splitsmith-0.2.0/src/splitsmith/mcp/__init__.py +23 -0
- splitsmith-0.2.0/src/splitsmith/mcp/__main__.py +20 -0
- splitsmith-0.2.0/src/splitsmith/mcp/detect_tools.py +476 -0
- splitsmith-0.2.0/src/splitsmith/mcp/export_tools.py +356 -0
- splitsmith-0.2.0/src/splitsmith/mcp/sandbox.py +77 -0
- splitsmith-0.2.0/src/splitsmith/mcp/server.py +393 -0
- splitsmith-0.2.0/src/splitsmith/mcp/tools.py +207 -0
- splitsmith-0.2.0/src/splitsmith/mcp/write_tools.py +268 -0
- splitsmith-0.2.0/src/splitsmith/model_cli.py +153 -0
- splitsmith-0.2.0/src/splitsmith/models/__init__.py +40 -0
- splitsmith-0.2.0/src/splitsmith/models/cache.py +139 -0
- splitsmith-0.2.0/src/splitsmith/models/download.py +95 -0
- splitsmith-0.2.0/src/splitsmith/models/errors.py +50 -0
- splitsmith-0.2.0/src/splitsmith/models/manifest.py +68 -0
- splitsmith-0.2.0/src/splitsmith/models/registry.py +256 -0
- splitsmith-0.2.0/src/splitsmith/mp4_render.py +513 -0
- splitsmith-0.2.0/src/splitsmith/overlay_render.py +817 -0
- splitsmith-0.2.0/src/splitsmith/overlay_theme.py +146 -0
- splitsmith-0.2.0/src/splitsmith/relink.py +245 -0
- splitsmith-0.2.0/src/splitsmith/report.py +258 -0
- splitsmith-0.2.0/src/splitsmith/runtime.py +268 -0
- splitsmith-0.2.0/src/splitsmith/shot_detect.py +506 -0
- splitsmith-0.2.0/src/splitsmith/shot_refine.py +252 -0
- splitsmith-0.2.0/src/splitsmith/system_check.py +162 -0
- splitsmith-0.2.0/src/splitsmith/templates.py +188 -0
- splitsmith-0.2.0/src/splitsmith/thumbnail.py +230 -0
- splitsmith-0.2.0/src/splitsmith/trim.py +211 -0
- splitsmith-0.2.0/src/splitsmith/ui/__init__.py +10 -0
- splitsmith-0.2.0/src/splitsmith/ui/audio.py +536 -0
- splitsmith-0.2.0/src/splitsmith/ui/embedded.py +312 -0
- splitsmith-0.2.0/src/splitsmith/ui/exports.py +533 -0
- splitsmith-0.2.0/src/splitsmith/ui/jobs.py +652 -0
- splitsmith-0.2.0/src/splitsmith/ui/logging_setup.py +108 -0
- splitsmith-0.2.0/src/splitsmith/ui/match_exports.py +500 -0
- splitsmith-0.2.0/src/splitsmith/ui/project.py +1734 -0
- splitsmith-0.2.0/src/splitsmith/ui/scoreboard/__init__.py +77 -0
- splitsmith-0.2.0/src/splitsmith/ui/scoreboard/cache.py +237 -0
- splitsmith-0.2.0/src/splitsmith/ui/scoreboard/http.py +206 -0
- splitsmith-0.2.0/src/splitsmith/ui/scoreboard/local.py +377 -0
- splitsmith-0.2.0/src/splitsmith/ui/scoreboard/models.py +301 -0
- splitsmith-0.2.0/src/splitsmith/ui/scoreboard/protocol.py +51 -0
- splitsmith-0.2.0/src/splitsmith/ui/server.py +9178 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/.gitignore +4 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/index.html +13 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/package-lock.json +3062 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/package.json +38 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/pnpm-lock.yaml +2033 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/App.tsx +167 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/AddFootageModal.tsx +711 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/AppShell.tsx +228 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/AuditControls.tsx +205 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/BeepSection.tsx +1472 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/CameraModelSelect.tsx +132 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/CleanupDialog.tsx +345 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/DirectoryPickerModal.tsx +45 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/FolderPicker.tsx +920 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/HelpOverlay.tsx +183 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/HitlQueuePanel.tsx +214 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/Jobs.tsx +685 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ListDrawer.tsx +274 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/MarkerGlyph.tsx +107 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/MarkerLayer.tsx +361 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/MountSelect.tsx +94 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/RelinkDialog.tsx +418 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/SettingProvenance.tsx +66 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ShooterScopedRoute.tsx +34 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ShotStepper.tsx +120 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/StageTimeSection.tsx +203 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/SweepsCard.tsx +379 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/VideoPanel.tsx +508 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/Waveform.tsx +331 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/AnomalyChips.tsx +69 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/AnomalyPins.tsx +62 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/BeepStatusChip.tsx +189 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/CamGridModal.tsx +217 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/CamSyncPill.tsx +136 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/MultiCamColumn.tsx +448 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/PrereqGate.tsx +480 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/SessionSummary.tsx +128 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/StageActionBar.tsx +204 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/StageChipRail.tsx +85 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/audit/SyncBanner.tsx +157 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/developer/DeveloperShell.tsx +362 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/match/MatchShell.tsx +449 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/match/MatchSidebar.tsx +452 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/match/ShooterChipStrip.tsx +149 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/AvatarStack.tsx +115 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Brand.tsx +53 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/ContextBar.tsx +72 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/DisplayHeading.tsx +45 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/IconButton.tsx +59 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Kbd.tsx +26 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Kicker.tsx +40 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/ModeSwitch.tsx +80 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Readout.tsx +75 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/ShotTimerShell.tsx +73 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/StageDot.tsx +135 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/StatusPill.tsx +72 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/Tick.tsx +77 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/badge.tsx +45 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/button.tsx +55 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/card.tsx +75 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/index.ts +34 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/components/ui/skeleton.tsx +12 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/anomalies.ts +173 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/api.ts +2965 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/audit-input.ts +94 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/audit-next-step.ts +73 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/features.ts +48 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/matchHref.ts +56 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/mode.tsx +54 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/slugify.ts +17 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/stageStatus.ts +98 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/lib/utils.ts +44 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/main.tsx +11 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Audit.tsx +2869 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/BeepReview.tsx +759 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Coach.tsx +1562 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Compare.tsx +1172 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/CreateMatch.tsx +1567 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Design.tsx +515 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Export.tsx +1158 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Home.tsx +1039 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Ingest.tsx +1131 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Lab.tsx +3356 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/MergeMatches.tsx +636 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Pick.tsx +986 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/PromoteReview.tsx +1006 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Review.tsx +981 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/Shooters.tsx +566 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevCorpus.tsx +436 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevRetrain.tsx +610 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevReviewQueue.tsx +460 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/pages/dev/DevValidate.tsx +726 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/src/styles/index.css +553 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.app.json +24 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.app.tsbuildinfo +1 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.json +7 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.node.json +18 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/tsconfig.node.tsbuildinfo +1 -0
- splitsmith-0.2.0/src/splitsmith/ui_static/vite.config.ts +29 -0
- splitsmith-0.2.0/src/splitsmith/user_config.py +380 -0
- splitsmith-0.2.0/src/splitsmith/video_match.py +159 -0
- splitsmith-0.2.0/src/splitsmith/video_probe.py +143 -0
- splitsmith-0.2.0/src/splitsmith/waveform.py +121 -0
- splitsmith-0.2.0/src/splitsmith/youtube_sidecar.py +293 -0
- splitsmith-0.2.0/tests/fixtures/beep_calibration/README.md +58 -0
- splitsmith-0.2.0/tests/fixtures/schemas/README.md +53 -0
- 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).
|
splitsmith-0.2.0/LICENSE
ADDED
|
@@ -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
|
+

|
|
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.** Drop a folder of GoPro clips; the engine auto-matches them to stages by file timestamp. |
|
|
46
|
+
|  | **Beep review.** Auto-snap to the start beep on each stage; low-confidence detections land in a HITL queue. |
|
|
47
|
+
|  | **Audit.** Waveform + per-shot markers from the 3-voter ensemble. Click a marker to inspect votes; drag to fine-tune. |
|
|
48
|
+
|  | **Compare.** Multi-shooter grid, all beep-aligned to t=0. Audio from one shooter, video tiles for everyone else. |
|
|
49
|
+
|  | **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.
|