Initial commit: GPX ride viewer with local-LLM coaching insights
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Download GPX files for your Garmin Connect activities.
|
||||
|
||||
Setup:
|
||||
pip install garminconnect
|
||||
|
||||
Usage:
|
||||
python garmin_gpx_export.py
|
||||
|
||||
You'll be prompted for your Garmin Connect email and password
|
||||
(the same ones you use to log into connect.garmin.com).
|
||||
Credentials are only used locally to authenticate with Garmin's
|
||||
servers and are never stored or sent anywhere else.
|
||||
"""
|
||||
|
||||
import getpass
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from garminconnect import Garmin, GarminConnectAuthenticationError
|
||||
|
||||
|
||||
def get_mfa_code():
|
||||
"""Called automatically by garminconnect if Garmin requests an MFA code."""
|
||||
return input("Enter the MFA code sent to you by Garmin: ").strip()
|
||||
|
||||
|
||||
def main():
|
||||
# --- Login ---
|
||||
email = input("Garmin Connect email: ").strip()
|
||||
password = getpass.getpass("Garmin Connect password: ")
|
||||
|
||||
try:
|
||||
client = Garmin(email=email, password=password, prompt_mfa=get_mfa_code)
|
||||
client.login()
|
||||
except GarminConnectAuthenticationError:
|
||||
print("Login failed. Check your email/password/MFA code.")
|
||||
return
|
||||
|
||||
print(f"Logged in as {client.get_full_name()}")
|
||||
|
||||
# --- Config ---
|
||||
output_dir = "garmin_gpx_exports"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# How many recent activities to check (increase if you want more history)
|
||||
limit = 50
|
||||
|
||||
# Only export activities matching this type, or set to None for all types.
|
||||
# Common values: "cycling", "running", "walking", "hiking", "swimming"
|
||||
activity_type_filter = "cycling"
|
||||
|
||||
# --- Fetch activity list ---
|
||||
activities = client.get_activities(0, limit)
|
||||
print(f"Found {len(activities)} recent activities (checking up to {limit}).")
|
||||
|
||||
exported = 0
|
||||
for act in activities:
|
||||
act_type = act.get("activityType", {}).get("typeKey", "")
|
||||
if activity_type_filter and activity_type_filter not in act_type:
|
||||
continue
|
||||
|
||||
activity_id = act["activityId"]
|
||||
start_time = act.get("startTimeLocal", "unknown_time")
|
||||
name = act.get("activityName", "activity")
|
||||
|
||||
# Sanitize filename
|
||||
safe_time = start_time.replace(":", "-").replace(" ", "_")
|
||||
safe_name = "".join(c for c in name if c.isalnum() or c in (" ", "_", "-")).strip()
|
||||
filename = f"{safe_time}_{safe_name}_{activity_id}.gpx"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
if os.path.exists(filepath):
|
||||
print(f" Skipping (already downloaded): {filename}")
|
||||
continue
|
||||
|
||||
try:
|
||||
gpx_data = client.download_activity(
|
||||
activity_id, dl_fmt=client.ActivityDownloadFormat.GPX
|
||||
)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(gpx_data)
|
||||
print(f" Saved: {filename}")
|
||||
exported += 1
|
||||
except Exception as e:
|
||||
print(f" Failed to export activity {activity_id}: {e}")
|
||||
|
||||
print(f"\nDone. Exported {exported} new GPX file(s) to ./{output_dir}/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user