Skip to main content
Yanked

This release has been yanked by its maintainers, and will be ignored by installers, except when explicitly specified.
Consider using release 0.1.2 instead.
Reason given by maintainers: dont want youtube

Golf Swing Analyzer (v1)

Analyzes a golf swing video using pose estimation (MediaPipe) and overlays:

  • Dots on body landmarks (shoulders, elbows, wrists, hips, knees, ankles)
  • Lines connecting them into a skeleton
  • Angle triangles (e.g. spine tilt vs. vertical), color-coded green/orange/red depending on how close to instructional target ranges
  • Wrist trail showing the swing arc/path over recent frames (smoothed — the tracked point is EMA-damped and the drawn line is moving-averaged, so the tracer reads as a clean curve rather than jaggy/shaky segments; tune with TRACER_EMA and TRACER_SMOOTH_WINDOW in src/swing_analyzer/analyze.py)
  • Swing plane line (down-the-line view) extrapolated through shoulders/hands

It also does lightweight swing-phase detection (address / top of backswing / impact / finish) and outputs a metrics report.

Setup

Install as a CLI tool (editable install — picks up code changes immediately):

pip install -e .

This installs a swing command with three subcommands: analyze, compare, full. Run swing --help, or swing <subcommand> --help, for the full flag list at any time.

Note: pin mediapipe==0.10.14 — newer 0.10.3x releases dropped the legacy mediapipe.solutions API in favor of a Tasks API that requires a separately downloaded model file. 0.10.14 keeps the simple bundled-model API this tool uses.

Outputs always land in ./output/ relative to wherever you run swing, not the install location — so cd to your videos first.

Usage

Single view:

swing analyze --face-on my_swing.mp4

Down-the-line view:

swing analyze --dtl my_swing_dtl.mp4

Both angles at once (produces a synced side-by-side video too):

swing analyze --face-on face.mp4 --dtl dtl.mp4

Compare mode

Diff two swings against each other — e.g. same swing with different club/shaft builds, or before/after a change:

swing compare --a old_shaft.mp4 --b new_shaft.mp4 \
    --label-a "Stock shaft" --label-b "Ventus Blue 7X"

Add --view dtl if both clips are down-the-line rather than face-on.

Either --a or --b can be a YouTube (or other yt-dlp-supported) URL instead of a local file, so you can compare your swing against a reference video online — e.g. a tour pro's swing:

swing compare --a my_swing.mp4 \
    --b "https://www.youtube.com/watch?v=XXXXXXXXXXX" \
    --label-b "Rory McIlroy driver"

yt-dlp is installed automatically as a dependency. The clip is cached in ./output/downloads/ by video id, so re-running the same URL won't re-download it. If you omit --label-b (or --label-a) for a URL, it defaults to "YouTube reference". Pick a reference filmed from the same view as your clip (face-on vs. down-the-line) so the metrics line up.

Outputs in ./output/: annotated_<a>.mp4, annotated_<b>.mp4, compare_side_by_side.mp4, and compare_report.md (metrics table + per-swing assessment).

Face blur (anonymize)

Pass --blur-face to pixelate the golfer's face in the output (uses the tracked face landmarks, so the mosaic follows the head through the swing):

# compare mode — blurs the first swing (--a) only; the reference is left as-is
swing compare --a my_swing.mp4 --b "https://youtube.com/..." --blur-face
swing compare --a my_swing.mp4 --b other.mp4 --blur-face --blur-face-b  # blur both

# standalone
swing analyze --face-on my_swing.mp4 --blur-face

In compare mode --blur-face anonymizes only your clip (--a); add --blur-face-b if you also want the reference blurred.

The blur box is temporally smoothed — EMA-averaged frame to frame to reduce jitter, and held through short tracking gaps (up to FACE_BLUR_HOLD_FRAMES, ~0.4s) so it doesn't flicker off on a blurry frame where pose tracking briefly drops. Both FACE_BLUR_SMOOTHING and FACE_BLUR_HOLD_FRAMES are constants in src/swing_analyzer/analyze.py.

Auto-zoom (framing match)

Clips shot from different distances make one golfer look much smaller than the other. Before syncing, each clip's body size is measured (the nose→ankles box as a fraction of frame height, median over the swing) and the clip is zoomed in and panned down to a standard framing:

  • Standard zoom box — the golfer's body is zoomed to fill ~50% of the frame height (STANDARD_BODY_FRACTION). In compare mode the tighter of the two clips raises this so both clips stay matched to each other (e.g. your range clip zooms in to match a tour-pro reference).
  • Standard ground line — the clip is panned down so the feet (and the ball, which sits at the feet) land ~85% down the frame (STANDARD_FEET_Y), so every clip shares the same ground line and the ball/feet are always in view.

This runs in both compare and standalone (swing analyze) modes. It only ever zooms in (never adds borders) and crops at the original aspect ratio, so the body is never stretched. The two standards are constants at the top of src/swing_analyzer/analyze.py — override them there (or pass target_fraction to match_framing()); there is deliberately no command-line flag.

Slow-motion & tempo sync

Every clip is checked on load: swing tempo is measured from the downswing (top-of-backswing → impact, the fastest and most consistent part of a swing). A real-time downswing is ~0.2–0.35s; anything that plays back faster than ~0.8s is treated as real-time and slowed down so the downswing spans ~1.5s, while a clip that's already slow-mo is left as-is. In compare mode both clips are slowed to that same tempo so they play at matched speed.

The compare_side_by_side.mp4 then does a piecewise time-warp — backswing, downswing, and follow-through are each resampled to a shared length — so top-of-backswing and impact line up across the two clips, not just the first frame.

Guardrails keep pathological clips sane: slowdown is capped at 8×, a retimed clip never plays longer than 20s, and the side-by-side trims excess backswing/follow-through context. Tunables live at the top of src/swing_analyzer/analyze.py (SLOWMO_*). This all leans on the heuristic phase detector, so it works best on a clean, tightly-trimmed single-swing clip — feed it a 20s highlight reel and top/impact detection (and therefore the tempo/sync) gets unreliable.

Output

All in ./output/:

  • annotated_<name>.mp4 — video with overlays
  • side_by_side.mp4 — if both views were provided
  • report.md — phase frame numbers, key metrics, and flagged issues

Tips for good input video

  • Frame the whole swing (address to finish) in view, camera roughly waist-to-head height
  • Face-on: camera directly facing you, perpendicular to target line
  • Down-the-line: camera behind you, aligned with the target line
  • Plain background helps pose detection; avoid other people/objects in frame
  • 30fps+ is fine; higher fps (60/120) gives smoother phase detection around impact

Known limitations (v1)

  • 2D single-camera pose estimation — no true 3D depth, so some angles (especially swing plane in face-on view) are approximate.
  • Phase detection is a simple heuristic based on wrist height/velocity, not a robust event detector — works best with a clear address position held briefly and a normal tempo swing.
  • No clubhead or clubshaft tracking (MediaPipe tracks body only) — swing plane line is a proxy through hands, not the actual shaft.
  • No ball flight / clubhead speed data — that needs radar/launch-monitor input.
  • Target ranges in TARGETS (in src/swing_analyzer/analyze.py) are rough instructional benchmarks, not personalized — worth tuning to your own baseline over time rather than treating as absolute truth.

Extending

  • Compare mode (src/swing_analyzer/compare.py) is scaffolded — it's a thin wrapper that calls analyze_video() on both clips and diffs the summary dicts. Natural next steps: frame-aligned overlay (warp swing B's timeline onto A's phase markers so both hit "top of backswing" at the same frame), a ghost/overlay video (draw both skeletons on one frame, semi-transparent), or exporting the comparison table as CSV for tracking across many sessions.
  • To track club-specific data (shaft lean, clubface angle) you'd need either a marker on the club visible to a second detector, or a dedicated launch monitor/radar unit — video-only pose estimation can't see the club reliably.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

swing_analyzer-0.1.0.tar.gz (57.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swing_analyzer-0.1.0-py3-none-any.whl (59.7 kB view details)

Uploaded Python 3

File details

Details for the file swing_analyzer-0.1.0.tar.gz.

File metadata

  • Download URL: swing_analyzer-0.1.0.tar.gz
  • Upload date:
  • Size: 57.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for swing_analyzer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f63d6270616ffbbfcc7f6d11e6a5585ee2a4350c9eff70176bd924bc63f7956f
MD5 51edcc3ab619683b2d8a5ea9397bb00e
BLAKE2b-256 25500ed5d3fe617c88f6d5d331fe36d72acdf1cd5e7dc290d71a2c662d2bd9d6

See more details on using hashes here.

File details

Details for the file swing_analyzer-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: swing_analyzer-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 59.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for swing_analyzer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7623a47bfcc81bd75306479d597e3471da8559b50bb1df024a4f9f78930e5ff2
MD5 4240592c8efa71e58156b66a32f8488c
BLAKE2b-256 7a72119f6e789088c16089a0f46ea67d458e8044e163c62ee28189a51129edcc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page