Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Usage: `python gcexport.py [how_many] [format] [directory]`

`[how_many]` specifies the number of recent activities you wish to download. You may also specify `all` to download everything. The default is `1`.

`[format]` specifies the desired export format. Valid formats are `gpx`, `tcx` or `original`. The default is `gpx`. When using `original`, a ZIP file is exported that contains the initial input format (e.g., FIT files).
`[format]` specifies the desired export format. Valid formats are `gpx`, `tcx`, `original` or `json`. The default is `gpx`. When using `original`, a ZIP file is exported that contains the initial input format (e.g., FIT files). When using `json`, a JSON file is created with all the metadata.

`[directory]` specifies the output directory for the CSV file and the GPX files. The default is a subdirectory with the format `YYYY-MM-DD_garmin_connect_export`. If the directory does not exist, it will be created. If it does exist, activities with existing GPX files will be skipped and the CSV file will be appended to. This should make it easy to restart failed downloads without repeating work. This also allows you to specify a master directory so that this script can be run regularly (to maintain an up-to-date backup) without re-downloading everything.

Expand Down
48 changes: 28 additions & 20 deletions gcexport.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import urllib2, cookielib, json
from fileinput import filename
import json

if len(argv) > 4:
raise Exception('Too many arguments.')
Expand All @@ -36,8 +37,8 @@

if len(argv) > 2:
data_format = argv[2].lower()
if data_format != 'gpx' and data_format != 'tcx' and data_format != 'original':
raise Exception('Format can only be "gpx," "tcx," or "original."')
if data_format not in ['gpx', 'tcx', 'original', 'json']:
raise Exception('Format can only be "gpx", "tcx", "original", or "json".')
else:
data_format = 'gpx'

Expand Down Expand Up @@ -141,7 +142,7 @@ def http_req(url, post=None, headers={}):
# Query Garmin Connect
result = http_req(url_gc_search + urlencode(search_params))
json_results = json.loads(result) # TODO: Catch possible exceptions here.


search = json_results['results']['search']

Expand Down Expand Up @@ -169,6 +170,9 @@ def http_req(url, post=None, headers={}):
filename = activities_directory + '/activity_' + a['activity']['activityId'] + '.tcx'
download_url = url_gc_tcx_activity + a['activity']['activityId'] + '?full=true'
file_mode = 'w'
elif data_format == 'json':
filename = activities_directory + '/activity_' + a['activity']['activityId'] + '.json'
file_mode = 'w'
else:
filename = activities_directory + '/activity_' + a['activity']['activityId'] + '.zip'
download_url = url_gc_original_activity + a['activity']['activityId']
Expand All @@ -184,23 +188,27 @@ def http_req(url, post=None, headers={}):
# should pick up where it left off.
print '\tDownloading file...',

try:
data = http_req(download_url)
except urllib2.HTTPError as e:
# Handle expected (though unfortunate) error codes; die on unexpected ones.
if e.code == 500 and data_format == 'tcx':
# Garmin will give an internal server error (HTTP 500) when downloading TCX files if the original was a manual GPX upload.
# Writing an empty file prevents this file from being redownloaded, similar to the way GPX files are saved even when there are no tracks.
# One could be generated here, but that's a bit much. Use the GPX format if you want actual data in every file, as I believe Garmin provides a GPX file for every activity.
print 'Writing empty file since Garmin did not generate a TCX file for this activity...',
data = ''
elif e.code == 404 and data_format == 'original':
# For manual activities (i.e., entered in online without a file upload), there is no original file.
# Write an empty file to prevent redownloading it.
print 'Writing empty file since there was no original activity data...',
data = ''
else:
raise Exception('Failed. Got an unexpected HTTP error (' + str(e.code) + ').')
if data_format != 'json':
try:
data = http_req(download_url)
except urllib2.HTTPError as e:
# Handle expected (though unfortunate) error codes; die on unexpected ones.
if e.code == 500 and data_format == 'tcx':
# Garmin will give an internal server error (HTTP 500) when downloading TCX files if the original was a manual GPX upload.
# Writing an empty file prevents this file from being redownloaded, similar to the way GPX files are saved even when there are no tracks.
# One could be generated here, but that's a bit much. Use the GPX format if you want actual data in every file, as I believe Garmin provides a GPX file for every activity.
print 'Writing empty file since Garmin did not generate a TCX file for this activity...',
data = ''
elif e.code == 404 and data_format == 'original':
# For manual activities (i.e., entered in online without a file upload), there is no original file.
# Write an empty file to prevent redownloading it.
print 'Writing empty file since there was no original activity data...',
data = ''
else:
raise Exception('Failed. Got an unexpected HTTP error (' + str(e.code) + ').')

else:
data = json.dumps(a)

save_file = open(filename, file_mode)
save_file.write(data)
Expand Down