This is a little tool that will pull workouts from a Strava account and push them to a FitTrackee instance. The tool was written to help automatically backup workout tracks from the commercial service onto a self-hosted instance for safe keeping.
You'll need the following:
- An installation of FitTrackee
- An OAuth2 application configured for FitTrackee (go to the "Apps" section
of your FitTrackee account to configure this)
- The app needs access to the
workouts:read
,workouts:write
, andprofile:read
scopes (the last one so we can get the user's timezone for activities without GPS data) - You'll need to use the "Id" and "Secret" (only showed at creation time) to configure this script, so make sure to write them down
- When you create your app, the Application URL and Redirect URL can be pretty
much anything, but must start with
https://
. I usedhttps://localhost/
- The app needs access to the
- A Strava account with an "API Application" created and enabled (see
API Settings)
- The name, category, website, etc. can all be whatever you want since this is for private use. You can't use the term "Strava" in your application name though, I guess so users don't think your app is an official Strava app
- You'll need to use your "Client ID" and "Client Secret" from this page
- Poetry installed on the system where this tool will run
- A Python installation version 3.9 or higher. Installing this will depend
on your specific system, but I highly recommend using
pyenv
, which will allow you to install any version of Python you'd like and choose between them as needed. Assuming you have at least one 3.9 or higher version installed withpyenv
, Poetry is smart enough to pick up that and use the correct Python version when installingstrava-to-fittrackee
.- If you prefer to use system packages, on recent versions of Ubuntu you should
be able to run
sudo apt install python3.9
. If using the system package, you'll probably also need to runpoetry env use 3.9
to configure poetry to use that specific version. - If using
pyenv
, running$ pyenv install 3.9.16
or$ pyenv install 3.10.10
should get you a version that will work withs2f.py
. In this case, Poetry should be clever enough to analyze the versions of Python you have installed and pick the correct one (although you can also run thatpoetry env use
command to specify a particular one)
- If you prefer to use system packages, on recent versions of Ubuntu you should
be able to run
Download or clone the code from this repository:
$ git clone https://github.com/jat255/strava-to-fittrackee.git
Copy the .env.example
file to a file named .env
and configure the values as
documented in order to set the appropriate API client IDs and secrets, as well as
the FitTrackee host (currently, https verification is not enabled because FitTrackee
is often hosted with a self-signed certificate).
Install the script's dependencies by running:
$ poetry install
This will create new virtual environment and install the module and script.
If you would like to update your version of the script, pull the latest code from git, and re-run the poetry install command in case there were any new dependencies added:
$ git pull
$ poetry install
Note: This happens infrequently, but in the event the script needs new permissions
(e.g. v0.2.0 required a new profile:read
permission), you will need to re-run the
application authorization flow. The best way to do this is to delete and re-create the
API application you created in the FitTrackee web interface (making sure to use the new
permissions required), delete the local .fittrackee.tokens.json
file, and update your
.env
file to use the updated client ID and client secret from the new API app.
Note: For any of the examples below, if you add the -v 2
option, you'll get additional
debugging output from the script.
The first thing to do is check you can call the script using the virtual environment created by Poetry. Run the following from the directory you downloaded the files to in order see the "help" output of the script, which will show what it can do:
$ poetry run python -m strava_to_fittrackee.s2f -h
usage: s2f.py [-h] [-v {0,1,2}] (-V | --setup-tokens | --sync | --download-all-strava | --upload-all-fittrackee | --delete-all-fittrackee | --upload-gpx GPX_FILE)
[--output-folder OUTPUT_FOLDER] [--input-folder INPUT_FOLDER]
This tool provides functionality to download activities from a Strava "athlete" and
upload them as workouts to a FitTrackee instance (see README.md for more details)
Examples (in your terminal):
$ python -m strava_to_fittrackee.s2f --sync
$ python -m strava_to_fittrackee.s2f --download-all-strava --output-folder <folder_name>
$ python -m strava_to_fittrackee.s2f --upload-all-fittrackee --input-folder <folder_name>
$ python -m strava_to_fittrackee.s2f --delete-all-fittrackee
Copyright (c) 2022-2023, Joshua Taillon
v0.2.0
optional arguments:
-h, --help show this help message and exit
-v {0,1,2}, --verbosity {0,1,2}
increase output verbosity (default: 1)
-V, --version display the program's version
--setup-tokens Setup initial token authentication for both Strava and FitTrackee. This will be done automatically if they are not already set up, but this option allows you to
do that without performing any other actions
--sync Download activities from Strava not currently present in FitTrackee and upload them to the FitTrackee instance
--download-all-strava
Download and store all Strava activities as GPX files in the given folder (default: "./gpx/", but can be changed with "--output-folder" option)
--upload-all-fittrackee
Upload all GPX files in the given folder as workouts to the configured FitTrackee instance. (default folder is "./gpx/", but can be configured with the "--input-
folder" option)
--delete-all-fittrackee
Delete all workouts in the configured FitTrackee instance
--upload-gpx GPX_FILE
Can be used to upload a single GPX file to the FitTrackee instance
--output-folder OUTPUT_FOLDER
Folder in which to store GPX files generated from Strava activities (if the "--download-all-strava" option is given; default: "./gpx")
--input-folder INPUT_FOLDER
Folder in which to find GPX files to be uploaded to FitTrackee (if the "--upload-all-fittrackee" option is given; default: "./gpx")
To get started with the script, you'll need to authorize your "API apps" (created earlier)
for both Strava and FitTrackee. To do this, run the script with the --setup-tokens
option:
$ poetry run python -m strava_to_fittrackee.s2f --setup-tokens
This will first present you (in the terminal) with a link to Strava, which (after logging in) will prompt you to authorize the application:
When you click "Authorize", your browser will be redirected to a URL starting with
http://localhost
that doesn't work. That's okay. Copy the entire URL from your browser's
address bar and paste it into the terminal, then press the enter key on your keyboard.
The URL you pasted contains a code
parameter that is used to generate authentication
tokens for the app, which it will then use to access the API on your behalf. These tokens
will be saved into a file named .strava.tokens.json
(unless you changed the location with
the STRAVA_TOKEN_FILE
setting). As long as that file is present, the script will be able
to use the access and refresh tokens it contains to access your data via the API.
(If you're curious, this link
describes the "OAuth" process and what the tokens are for).
Once you've pasted in the Strava code, the script will then perform the same process
for FitTrackee. You'll be presented with a link to your FitTrackee instance (which is built
from the FITTRACKEE_HOST
setting in the .env
file), and upon visiting it, you'll see the
authorization screen:
Click "Authorize" and again you'll be redirected to a URL that does not exist. Copy the URL
from your browser's address bar and paste into the terminal once again, and press "enter".
Assuming everything worked as designed, you'll now have two .json
files in your current
directory containing tokens for both Strava and FitTrackee.
If you run the --setup-tokens
option again, you'll notice that you don't get presented
with the authorization URLs again. This is because the script will read the token files
saved previously and check that they're valid (and refresh them, if not). You'll only
need to go through the URL authorization again if you delete or rename the token files.
Once your tokens are set up, run the script with the --sync
option:
$ poetry run python -m strava_to_fittrackee.s2f --sync
This will check your FitTrackee account for the last workout, and then fetch
all activities from Strava after that time, generating a GPX file for each one
then uploading it to FitTrackee. A link to the original Strava activity will
be included as a "note" that is displayed in FitTrackee. Currently, segments
will not be copied from the Strava API, although it's possible that could be
improved in the future. Sport types will be mapped as best as possible (see
the sport_id_map
value in the FitTrackee.upload_gpx()
method for specifics),
but FitTrackee doesn't have a corresponding sport type for every activity type
in Strava, so it will probably give an error if there's one it doesn't recognize.
If you run this command without any workouts in FitTrackee, it will attempt to sync all activities, which will take quite a while if you have many activities. Depending on the number of activities present, this will likely cause the code to go over the Strava API rate limits (currently 100 requests per 15 minutes and 1000 requests per day), since each activity requires 4 requests to get the location data required to build a GPX file. So, if you have more than about 200-250 activities or so, this process will take multiple days -- blame Strava's rate limits!
To workaround this, the script will automatically back-off while running and
sleep until the next 15 minute interval. There is no functionality to deal
with the 1000 request limit, so if that gets hit, it will just continue trying
every fifteen minutes (although it should start working on the next day if you
let it continue running). Alternatively, you can kill the program and restart
it the next day manually, and already-downloaded activities will be skipped.
If you'd like to see what the current API limits are at each point in the code,
run with DEBUG
level verbosity (-v 2
) to print more information about the
API responses.
If you hit the rate limit (with -v 2
enabled), you'll see output like follows:
DEBUG:s2f:Getting latitude and longitude for activity 123456789
DEBUG:s2f:Current API usage -- 15 minute: 101/100 -- daily: 732/1000
WARNING:s2f:Hit Strava API limit; sleeping until next 15 minute interval
WARNING:s2f:Time is now 2022-11-04T22:17:12.725154; Sleeping until at least 2022-11-04T22:30:00
During regular day-to-day use, this shouldn't be an issue, but it can cause some
annoyance/delays during the initial sync. The script can be run with --sync
at any
time, and it will be much faster on subsequent runs since it will only look for new
activities.
The script can be scheduled using a tool such as cron
, or the Windows task scheduler
to run at one ore more set times each day. A line such as the following will run
the script twice per hour every day, logging to a file named s2f.log
in the home directory of user
. This example assumes you installed Poetry to its
default location (run $ which poetry
to find the correct executable location if
yours is different):
5,35 * * * * cd /home/user/s2f && /home/user/.local/bin/poetry run python -m strava_to_fittrackee.s2f --sync -v 2 >> /home/user/s2f.log 2>&1
By changing to the s2f
directory and running the script via $ poetry run python s2f.py
,
Poetry should automatically determine the correct Python interpreter to use.
Although the --sync
option should work with large numbers of activities (subject to the rate
limitations described above), if you'd ever like to bulk export activities from Strava
and save the resulting GPX files to disk, use the --download-all-strava
option:
$ poetry run python -m strava_to_fittrackee.s2f --download-all-strava
By default, this will download all your activities into a subfolder named ./gpx
,
but this location can be changed with the --output-folder
option, e.g.:
$ poetry run python -m strava_to_fittrackee.s2f --download-all-strava --output-folder /home/user/Downloads/
You will likely run up against the Strava API limits with this method as well, and so
it will automatically wait for 15 minute intervals like --sync
. The resulting GPX
files will have the "activity type" (Hike, Walk, etc.) saved in the description
field,
and a link to the original Strava activity will be included as well.
A corollary to the bulk download option, you can run the script with the
--upload-all-fittrackee
option to upload a folder of GPX tracks to FitTrackee.
The default folder is ./gpx
(so it will work without changing any options after
running a download), but you can change the folder to read from with the
--input-folder
option.
This option may not work as-is with arbitrary GPX files produced by other means. The uploader code expects certain content in the GPX file that is written when downloading from Strava, so it may or may not work.
This is mostly a vestige of testing, but there's also an option (--upload-gpx
)
that will upload a single GPX file to the FitTrackee instance. Provide the path of the
file after the --upload-gpx
option as follows:
$ poetry run python -m strava_to_fittrackee.s2f --upload-gpx ./gpx/test_file.gpx
This is again mostly a leftover from testing, but the --delete-all-fittrackee
will use
the API to delete all workouts from the instance. The script will ask you to confirm that
this is really what you want to do before actually doing it:
$ poetry run python -m strava_to_fittrackee.s2f --delete-all-fittrackee -v 2
DEBUG:s2f:Initializing FitTrackeeConnector
DEBUG:s2f:Setting up FitTrackee auth
DEBUG:s2f:Using existing FitTrackee tokens with self-refreshing client
DEBUG:s2f:Getting all workouts from FitTrackee (in pages of 30)
DEBUG:s2f:Fetched page 1 of workouts (fetched 30 so far)
DEBUG:s2f:Fetched page 2 of workouts (fetched 60 so far)
DEBUG:s2f:Fetched page 3 of workouts (fetched 90 so far)
DEBUG:s2f:Fetched page 4 of workouts (fetched 120 so far)
DEBUG:s2f:Fetched page 5 of workouts (fetched 123 so far)
This will delete all 123 workouts in the configured FitTrackee instance!
Are you sure you want to do this? [y]es or [n]o:
- "Manual" activities are ignored:
- If you have manual (i.e. without GPS data) files in your Strava account, they will be ignored and not uploaded to FitTrackee (they could be, in theory, it just was not a priority at the time of writing)
- Activity types missing:
- This is moreso a limitation of FitTrackee, but by default, there are not sport types for every type of activity that's available on Strava. If you try to sync a sport type not supported in FitTrackee, it will probably error (I haven't tried, but I think it will fail).
- Emojis are not supported:
- If you have a Strava activity with emoji (or other non-ASCII characters), FitTrackee will choke on that input and will produce an error. To workaround this, rename any activities in Strava that have these characters and try again.
- FitTrackee returns
429 Client Error: TOO MANY REQUESTS
:- There is a default API limit of 300 requests per 5 minutes for FitTrackee.
On the initial sync, it's possible that you could push against this. Either wait
a little while, or change the
API_RATE_LIMITS
environment setting in your FitTrackee environment. I used the following:API_RATE_LIMITS==10000 per 5 minutes
- There is a default API limit of 300 requests per 5 minutes for FitTrackee.
On the initial sync, it's possible that you could push against this. Either wait
a little while, or change the
RuntimeError: Exiting because a lock file exists
...- If you see this error, it is because of a part of the code that checks for a
"lock file" that is created when the script first starts. The purpose of this
is to prevent multiple copies from running at the same time, which can result
in duplicate workouts (i.e. if two
--sync
operations start near the same time). In normal usage, the script will give an error if it detects thes2f.pid
file is present, and that file should be deleted when the first copy of the script exits. In some error situations, this file does not get cleaned up properly though, and you might have to delete it manually. Simply remove thes2f.pid
file and the script will run again.
- If you see this error, it is because of a part of the code that checks for a
"lock file" that is created when the script first starts. The purpose of this
is to prevent multiple copies from running at the same time, which can result
in duplicate workouts (i.e. if two