diff --git a/README.md b/README.md index a8d06d1..d218f44 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,21 @@ Once started, the program will prompt you for your account password and then log in to your Garmin Connect account to download all activities to a destination directory on your machine. -For each activity, three files are stored: an activity summary (JSON), -activity details (JSON) and the activity GPX file. All files are written -to the same directory (``activities//`` by default). -Each activity file is prefixed by its upload timestamp and its activity id. +For each activity, these files are stored: + + - an activity summary file (JSON) + + - an activity details file (JSON) + + - an activity GPX file (XML) + + - an activity TCX file (XML) + + - an activity FIT file (binary) + +All files are written to the same directory (``activities//`` +by default). Each activity file is prefixed by its upload timestamp and its +activity id. Library import diff --git a/garminexport.py b/garminexport.py index 9098933..6b80921 100755 --- a/garminexport.py +++ b/garminexport.py @@ -67,7 +67,9 @@ if __name__ == "__main__": activity_summary = client.get_activity_summary(id) activity_details = client.get_activity_details(id) activity_gpx = client.get_activity_gpx(id) - + activity_tcx = client.get_activity_tcx(id) + activity_fit = client.get_activity_fit(id) + # for each activitity save the summary, details and GPX file. creation_millis = activity_summary["activity"]["uploadDate"]["millis"] timestamp = datetime.fromtimestamp(int(creation_millis)/1000.0) @@ -78,6 +80,8 @@ if __name__ == "__main__": summary_file = path_prefix + "_summary.json" details_file = path_prefix + "_details.json" gpx_file = path_prefix + ".gpx" + tcx_file = path_prefix + ".tcx" + fit_file = path_prefix + ".fit" with codecs.open(summary_file, encoding="utf-8", mode="w") as f: f.write(json.dumps( activity_summary, ensure_ascii=False, indent=4)) @@ -85,7 +89,11 @@ if __name__ == "__main__": f.write(json.dumps( activity_details, ensure_ascii=False, indent=4)) with codecs.open(gpx_file, encoding="utf-8", mode="w") as f: - f.write(activity_gpx) + f.write(activity_gpx) + with codecs.open(tcx_file, encoding="utf-8", mode="w") as f: + f.write(activity_tcx) + with open(fit_file, mode="wb") as f: + f.write(activity_fit) except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() log.error(u"failed with exception: %s", e) diff --git a/garminexport/garminclient.py b/garminexport/garminclient.py index ef23279..91b14a5 100755 --- a/garminexport/garminclient.py +++ b/garminexport/garminclient.py @@ -7,7 +7,9 @@ import json import logging import re import requests +from StringIO import StringIO import sys +import zipfile # # Note: For more detailed information about the API services @@ -250,3 +252,37 @@ class GarminClient(object): activity_id, response.status_code, response.text)) return response.text + + def get_activity_tcx(self, activity_id): + """Return a TCX (Training Center XML) representation of a + given activity. + + :param activity_id: Activity identifier. + :type activity_id: int + :returns: The TCX representation of the activity as an XML string. + :rtype: str + """ + response = self.session.get("https://connect.garmin.com/proxy/activity-service-1.3/tcx/course/{}".format(activity_id)) + if response.status_code != 200: + raise Exception(u"failed to fetch TCX for activity {}: {}\n{}".format( + activity_id, response.status_code, response.text)) + return response.text + + def get_activity_fit(self, activity_id): + """Return a FIT representation for a given activity. + + :param activity_id: Activity identifier. + :type activity_id: int + :returns: A string with a FIT file for the activity. + :rtype: str + """ + + response = self.session.get("https://connect.garmin.com/proxy/download-service/files/activity/{}".format(activity_id)) + if response.status_code != 200: + raise Exception(u"failed to fetch FIT for activity {}: {}\n{}".format( + activity_id, response.status_code, response.text)) + # fit file returned from server is in a zip archive + zipped_fit_file = response.content + zip = zipfile.ZipFile(StringIO(zipped_fit_file), mode="r") + # return the ".fit" entry from the zip archive + return zip.open(str(activity_id) + ".fit").read()