Activity Tracks¶

Endurabeats¶

2024-02-03
Per Halvorsen

This notebook serves as the foudation for the Endurabeats tool. I developed this tool to sync the music I played on Spotify during an activity with the activity data on Strava.

Goal¶

Find the tracks played during an activity.

Load¶

In [150]:
import json
import os 
import requests as r 
import pandas as pd
In [297]:
def load_tokens(filename):
    with open(filename, 'r') as f:
        return json.load(f)

spotify_tokens = load_tokens(os.environ["SPOTIFY_TOKENS_PATH"])
strava_tokens = load_tokens(os.environ["STRAVA_TOKENS_PATH"])

# strava_tokens, spotify_tokens

Load data from APIs¶

In [298]:
def get_recent_played(access_token):
    URL = "https://api.spotify.com/v1/me/player/recently-played"    # api-endpoint for recently played
    HEAD = {'Authorization': 'Bearer '+ access_token}               # provide auth. credentials
    PARAMS = {'limit':50}	                                        # default here is 20
    content = r.get(url=URL, headers=HEAD, params=PARAMS)
    return content.json()


def get_activities(access_token):
    URL = "https://www.strava.com/api/v3/athlete/activities"    # api-endpoint for recently played
    HEAD = {"Authorization": f"Bearer {access_token}"}
    content = r.get(URL, headers=HEAD)
    return content.json()


raw_recent_played = get_recent_played(spotify_tokens["access_token"])
raw_activities = get_activities(strava_tokens["access_token"])
In [170]:
raw_activities
Out[170]:
[{'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Yoga',
  'distance': 0.0,
  'moving_time': 1628,
  'elapsed_time': 1628,
  'total_elevation_gain': 0,
  'type': 'Yoga',
  'sport_type': 'Yoga',
  'id': 10688279212,
  'start_date': '2024-02-03T14:55:59Z',
  'start_date_local': '2024-02-03T15:55:59Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 1,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10688279212', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 28,
  'has_heartrate': False,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': False,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11431077680,
  'upload_id_str': '11431077680',
  'external_id': 'garmin_ping_319007367479',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Workout',
  'distance': 404.9,
  'moving_time': 3862,
  'elapsed_time': 3862,
  'total_elevation_gain': 0,
  'type': 'Workout',
  'sport_type': 'Workout',
  'id': 10682232510,
  'start_date': '2024-02-02T17:03:10Z',
  'start_date_local': '2024-02-02T18:03:10Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10682232510', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.105,
  'max_speed': 2.413,
  'average_cadence': 57.1,
  'average_temp': 19,
  'has_heartrate': True,
  'average_heartrate': 90.3,
  'max_heartrate': 104.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11424838041,
  'upload_id_str': '11424838041',
  'external_id': 'garmin_ping_318839375356',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Run',
  'distance': 5906.5,
  'moving_time': 1865,
  'elapsed_time': 1865,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10678755706,
  'start_date': '2024-02-02T05:47:05Z',
  'start_date_local': '2024-02-02T06:47:05Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10678755706', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 3.167,
  'max_speed': 3.836,
  'average_cadence': 75.7,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 161.7,
  'max_heartrate': 178.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11421258441,
  'upload_id_str': '11421258441',
  'external_id': 'garmin_ping_318743050014',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Weight Training',
  'distance': 0.0,
  'moving_time': 768,
  'elapsed_time': 768,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10672180060,
  'start_date': '2024-02-01T06:14:17Z',
  'start_date_local': '2024-02-01T07:14:17Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10672180060', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 134.9,
  'max_heartrate': 176.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11414516045,
  'upload_id_str': '11414516045',
  'external_id': 'garmin_ping_318555835642',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Run',
  'distance': 6130.7,
  'moving_time': 1984,
  'elapsed_time': 1984,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10672137037,
  'start_date': '2024-02-01T05:37:09Z',
  'start_date_local': '2024-02-01T06:37:09Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10672137037', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 3.09,
  'max_speed': 3.708,
  'average_cadence': 74.2,
  'average_temp': 24,
  'has_heartrate': True,
  'average_heartrate': 161.2,
  'max_heartrate': 173.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11414471523,
  'upload_id_str': '11414471523',
  'external_id': 'garmin_ping_318553128934',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Weight Training',
  'distance': 0.0,
  'moving_time': 2466,
  'elapsed_time': 2466,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10668078046,
  'start_date': '2024-01-31T14:49:16Z',
  'start_date_local': '2024-01-31T15:49:16Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10668078046', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 111.6,
  'max_heartrate': 133.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11410330460,
  'upload_id_str': '11410330460',
  'external_id': 'garmin_ping_318439398884',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 3339.4,
  'moving_time': 1145,
  'elapsed_time': 1145,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10667818129,
  'start_date': '2024-01-31T14:24:34Z',
  'start_date_local': '2024-01-31T15:24:34Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10667818129', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.917,
  'max_speed': 3.306,
  'average_cadence': 71.9,
  'average_temp': 24,
  'has_heartrate': True,
  'average_heartrate': 126.3,
  'max_heartrate': 148.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11410063447,
  'upload_id_str': '11410063447',
  'external_id': 'garmin_ping_318432830339',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Run',
  'distance': 10011.5,
  'moving_time': 3014,
  'elapsed_time': 3014,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10658118191,
  'start_date': '2024-01-30T05:30:04Z',
  'start_date_local': '2024-01-30T06:30:04Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10658118191', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 3.322,
  'max_speed': 4.349,
  'average_cadence': 73.6,
  'average_temp': 25,
  'has_heartrate': True,
  'average_heartrate': 176.3,
  'max_heartrate': 196.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11400097488,
  'upload_id_str': '11400097488',
  'external_id': 'garmin_ping_318174904070',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Weight Training',
  'distance': 0.0,
  'moving_time': 1772,
  'elapsed_time': 1772,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10651602596,
  'start_date': '2024-01-29T05:57:34Z',
  'start_date_local': '2024-01-29T06:57:34Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10651602596', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 129.8,
  'max_heartrate': 166.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11393416089,
  'upload_id_str': '11393416089',
  'external_id': 'garmin_ping_317985527844',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Run',
  'distance': 4206.7,
  'moving_time': 1460,
  'elapsed_time': 1593,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10651559000,
  'start_date': '2024-01-29T05:27:37Z',
  'start_date_local': '2024-01-29T06:27:37Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10651559000', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.881,
  'max_speed': 3.259,
  'average_cadence': 72.0,
  'average_temp': 23,
  'has_heartrate': True,
  'average_heartrate': 153.8,
  'max_heartrate': 169.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11393370787,
  'upload_id_str': '11393370787',
  'external_id': 'garmin_ping_317982870988',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 9258.2,
  'moving_time': 2658,
  'elapsed_time': 2752,
  'total_elevation_gain': 57.0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10616549153,
  'start_date': '2024-01-23T21:04:33Z',
  'start_date_local': '2024-01-23T15:04:33Z',
  'timezone': '(GMT-06:00) America/Chicago',
  'utc_offset': -21600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 5,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10616549153',
   'summary_polyline': 'ekliEn{gmQuBqBQ]Ce@UKW@]UU]_@We@Wg@Au@QYHCB[Pe@Ic@Le@IMQEMAm@Ki@MMMEa@@WPa@Dm@?{@NQ?e@WSWSOe@Q_@As@Fg@IK@g@HQX@`@LbBARETCj@Hf@GRGFK@]YQCc@AkBHW?UCqAU[C]Ew@DUA]m@g@m@KCu@?]AQG[]a@o@[Uq@WGKGi@AeBFgBCcADaBAgBBq@Cg@Fi@Po@FiAEWOQEE{ANBFD?HFAFAJWd@a@GeAc@Y_@y@c@aB]k@Xc@JGAiA]{@OwBi@u@Ac@G_@c@s@MSIE[GcAIMKL?v@[j@w@X_@Xg@|@]\\y@Vs@Dm@C]HMPiCtF{@~AwBdDUl@[fAKHOK_@c@]Wu@}@h@`@?YLUd@Or@m@l@gAh@i@`AwBfA}Ah@}At@sAl@gCCQBSNQNG\\FP@D?PONi@@_@AMGUg@c@GKWcAOYs@s@e@G]U_@r@G`@OJK?GG]w@W_@g@c@cAiBWUc@MsACs@Oi@Ae@FeAXsAb@UNa@FE?GG?EFG`Bs@FIHc@A_@FKjAg@`@Gt@Ct@@bAJxAFxAt@d@Fj@EJBLT?n@Hz@Rd@`@Rj@BREXKd@?dAl@t@T`A^nA^lARFRFDJMNIl@FRFp@^^Ln@\\lAf@vAn@XHj@J`AFp@JNCHGJcAF_BC_DDKh@EnACpAFZHAvEIr@A\\JpA@dDDj@E\\Aj@IpAC?SOAE@KH]Ka@E?AGLKJA|AJHFDVAZMx@UbABbAAbBEhCBhAArBB|A@TJV~@`@TR`@p@\\Tj@D\\?b@Jf@n@N^`@DbBIh@FbART`@D|AH~ALRd@@f@GfAm@h@Of@BJFLX@jBBD`@DlADh@Jh@TVN`@`@b@v@XdAJ`A@|@B@X?L\\HDJ@XGf@CbA?\\KFc@Gy@Am@DgAIuADk@H[DiCe@_CEq@Kw@Dk@VYb@EPB`@CXSf@Bx@Z^ZrAxADL?^DJHFRC^JhBbB',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [33.16196708008647, -96.71195606701076],
  'end_latlng': [33.162045953795314, -96.71206151135266],
  'average_speed': 3.483,
  'max_speed': 5.55,
  'average_cadence': 74.9,
  'average_temp': 15,
  'has_heartrate': True,
  'average_heartrate': 172.5,
  'max_heartrate': 189.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 231.2,
  'elev_low': 214.2,
  'upload_id': 11357096980,
  'upload_id_str': '11357096980',
  'external_id': 'garmin_ping_316996352061',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 11141.8,
  'moving_time': 3140,
  'elapsed_time': 3363,
  'total_elevation_gain': 77.0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10578603390,
  'start_date': '2024-01-17T20:37:07Z',
  'start_date_local': '2024-01-17T14:37:07Z',
  'timezone': '(GMT-06:00) America/Chicago',
  'utc_offset': -21600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 8,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10578603390',
   'summary_polyline': 'eoliEhwgmQGm@GIUA_@Ii@m@w@k@UOg@Gy@HSNWFg@Ee@F_@MOe@Ey@U]KESAc@Ne@Fu@A}@HMAc@Qe@c@KGm@Iy@Hm@Ig@@C@YXCTNdCK`AFn@MXE?m@[EAcD?q@E_AOoB@QAMK]q@Y[SGs@@g@GiAwAq@[][Eo@?gBFsAAeBHoEEcA@GXaA@WDy@AGISIGSIeB\\HAPF@FCXIPEBU?uAk@W[eAm@i@I]OMA_@Jc@VG@e@Ke@OeAQeA[i@KsAI]SUUOEe@EC@MV_@TI@SCYDe@Pa@h@[^i@T_ANwA@q@jAUd@_AdAqAnCiAnBuAtBUd@CJBXADG?iAaAMUDo@`@Of@]\\]^s@\\]Va@Te@b@gAVc@b@k@Te@^cAt@sATcANk@Bk@JYVQr@@TGHIJg@Ao@OWYYKOOs@O[k@m@a@Um@OWDWZKh@[Fm@iAg@e@Qc@c@u@KW]]e@ImAEy@Q_A@qBf@u@Zg@VU@S_@AiC@}AKyEKa@Yo@]_AS}@Ws@EUU}C?k@KkABqAEa@_@eB_@eAc@s@MKe@GCEBa@jATXZ`Bj@`Aj@`At@NR^\\n@^lA|@v@r@^VTJ`@`@x@TXLIh@Ud@gAv@IHSf@I`ABf@?hAJbAFP|AhAf@Pd@Ft@Bt@Lh@THHVd@FT\\zFPx@d@dAPRZLd@Hh@f@Vb@Hd@N`@^\\P\\?VOx@QTSDe@GMBQNI\\Gr@Od@Mt@IPq@hAm@~AqAnBi@tAMRm@r@_@t@MNi@`@i@XG^HV\\TTXh@b@^P`@Xn@XhAZbAJVAN@TEn@Aj@IjC{@XQZGn@SpAY`BS`@@tBPfAVxElBbBx@~@Zr@J|BCDCJ]AiAEsC?sDAmGQmAHS@MAOGGEMBCRAzAJPT@TGr@JVNHJCHGv@q@RGr@Df@Lb@Dv@?fAMf@?d@Dv@Cl@Bx@A`@Oh@a@f@m@dAcALSJ?^Zl@t@p@j@XRJRY~@?Rj@j@v@XI|HL|A@\\LX~@HjC@b@AJEP[@UA_A@m@FeA@yDHkA?i@FWvB?FV?dBBTZb@ZLnG@HD@D?pBDf@j@Jv@Db@@jACtADFH?jAAxFBJHH',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [33.16298212856054, -96.71198355965316],
  'end_latlng': [33.16240788437426, -96.71161383390427],
  'average_speed': 3.548,
  'max_speed': 5.154,
  'average_cadence': 76.0,
  'average_temp': 16,
  'has_heartrate': True,
  'average_heartrate': 171.8,
  'max_heartrate': 187.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 235.4,
  'elev_low': 214.4,
  'upload_id': 11317969580,
  'upload_id_str': '11317969580',
  'external_id': 'garmin_ping_315891713292',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Run',
  'distance': 6666.0,
  'moving_time': 2187,
  'elapsed_time': 2198,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10532885628,
  'start_date': '2024-01-10T18:04:24Z',
  'start_date_local': '2024-01-10T19:04:24Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 1,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10532885628', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 3.048,
  'max_speed': 3.697,
  'average_cadence': 75.4,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 170.7,
  'max_heartrate': 186.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11270109900,
  'upload_id_str': '11270109900',
  'external_id': 'garmin_ping_314587249010',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 10009.3,
  'moving_time': 3127,
  'elapsed_time': 3216,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10505981253,
  'start_date': '2024-01-06T14:48:44Z',
  'start_date_local': '2024-01-06T15:48:44Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10505981253', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 3.201,
  'max_speed': 4.667,
  'average_cadence': 75.8,
  'average_temp': 28,
  'has_heartrate': True,
  'average_heartrate': 169.9,
  'max_heartrate': 194.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11242084563,
  'upload_id_str': '11242084563',
  'external_id': 'garmin_ping_313801732408',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Lunch Walk',
  'distance': 3648.7,
  'moving_time': 2416,
  'elapsed_time': 3396,
  'total_elevation_gain': 28.0,
  'type': 'Walk',
  'sport_type': 'Walk',
  'id': 10505041872,
  'start_date': '2024-01-06T11:38:21Z',
  'start_date_local': '2024-01-06T12:38:21Z',
  'timezone': '(GMT+01:00) Europe/Oslo',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 2,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10505041872',
   'summary_polyline': 'abtlJary`Av@|@j@LJLHL\\nAd@hAHJf@Hh@|DBt@P|ATxA~@jEb@rCVlADl@ZdAj@bCNVPz@T|AAf@DCTJLbA`@`CHn@@`@MjE?hAAfAIv@@z@MnC?bBJ~BHp@Pt@LZBRP|B@`@NrARhAZlARf@FXBXEn@S~ACj@ETINEDIGA@MdFQrCMrAMr@Ut@Kh@a@vC]hDQzAO|@Q\\?COx@_@tCSxBYrAOh@c@lEENMLO|@I|@Ah@YpAc@~DO`AKl@INKj@CCEN_@jCELCb@@b@a@rDGTOZIJENCf@i@bEYjAEDKT[tAEl@@zAW`BMZEB[C_@D}@c@SYWAIFSOUCKJA\\ILI`AMn@KXSfAOd@I~@U`AKt@?CXhAA[m@pAq@~B',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [59.91163372993469, 10.785717917606235],
  'end_latlng': [59.91357657127082, 10.743066435679793],
  'average_speed': 1.51,
  'max_speed': 3.821,
  'average_cadence': 52.8,
  'average_temp': 23,
  'has_heartrate': True,
  'average_heartrate': 99.9,
  'max_heartrate': 126.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 56.2,
  'elev_low': 6.4,
  'upload_id': 11241107219,
  'upload_id_str': '11241107219',
  'external_id': 'garmin_ping_313781855488',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 6660.9,
  'moving_time': 2239,
  'elapsed_time': 2239,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10505041710,
  'start_date': '2024-01-04T16:23:24Z',
  'start_date_local': '2024-01-04T17:23:24Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10505041710', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.975,
  'max_speed': 5.125,
  'average_cadence': 73.2,
  'average_temp': 27,
  'has_heartrate': True,
  'average_heartrate': 167.9,
  'max_heartrate': 196.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11241107100,
  'upload_id_str': '11241107100',
  'external_id': 'garmin_ping_313781851789',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Run',
  'distance': 4205.0,
  'moving_time': 1750,
  'elapsed_time': 1750,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10505041676,
  'start_date': '2024-01-02T17:46:38Z',
  'start_date_local': '2024-01-02T18:46:38Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 0,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10505041676', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.403,
  'max_speed': 3.031,
  'average_cadence': 72.5,
  'average_temp': 27,
  'has_heartrate': True,
  'average_heartrate': 145.1,
  'max_heartrate': 160.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11241106855,
  'upload_id_str': '11241106855',
  'external_id': 'garmin_ping_313781848961',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Weight Training',
  'distance': 0.0,
  'moving_time': 1472,
  'elapsed_time': 1472,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10445623922,
  'start_date': '2023-12-27T14:39:03Z',
  'start_date_local': '2023-12-27T15:39:03Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10445623922', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 145.8,
  'max_heartrate': 171.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11179157577,
  'upload_id_str': '11179157577',
  'external_id': 'garmin_ping_311953915358',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 6668.2,
  'moving_time': 2320,
  'elapsed_time': 2320,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10445623811,
  'start_date': '2023-12-27T13:52:05Z',
  'start_date_local': '2023-12-27T14:52:05Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 2,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10445623811', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.874,
  'max_speed': 4.465,
  'average_cadence': 74.4,
  'average_temp': 23,
  'has_heartrate': True,
  'average_heartrate': 167.5,
  'max_heartrate': 194.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11179157452,
  'upload_id_str': '11179157452',
  'external_id': 'garmin_ping_311953912708',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Weight Training',
  'distance': 0.0,
  'moving_time': 1319,
  'elapsed_time': 1319,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10397019081,
  'start_date': '2023-12-17T16:15:57Z',
  'start_date_local': '2023-12-17T17:15:57Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10397019081', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 29,
  'has_heartrate': True,
  'average_heartrate': 119.7,
  'max_heartrate': 150.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11128954770,
  'upload_id_str': '11128954770',
  'external_id': 'garmin_ping_310337252537',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 10074.7,
  'moving_time': 3508,
  'elapsed_time': 3508,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10397019641,
  'start_date': '2023-12-17T15:09:11Z',
  'start_date_local': '2023-12-17T16:09:11Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10397019641', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.872,
  'max_speed': 3.974,
  'average_cadence': 73.6,
  'average_temp': 28,
  'has_heartrate': True,
  'average_heartrate': 178.2,
  'max_heartrate': 191.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11128954520,
  'upload_id_str': '11128954520',
  'external_id': 'garmin_ping_310337242878',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Nordic Ski',
  'distance': 13220.6,
  'moving_time': 6498,
  'elapsed_time': 7393,
  'total_elevation_gain': 357.0,
  'type': 'NordicSki',
  'sport_type': 'NordicSki',
  'id': 10389992274,
  'start_date': '2023-12-16T13:33:58Z',
  'start_date_local': '2023-12-16T14:33:58Z',
  'timezone': '(GMT+01:00) Europe/Oslo',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 6,
  'comment_count': 0,
  'athlete_count': 3,
  'photo_count': 0,
  'map': {'id': 'a10389992274',
   'summary_polyline': 's{llJwjfaA?_@QiAEw@AoBDaBMkCe@yFEK^zBMuB@wAJgA]oBCiDJkCFo@N]\\UhBIhANdAQ|Ab@p@hAzAtAhCtCd@hAXX`@DfAfATBILHo@r@eANs@VWZeAPKb@iAd@cB@eAj@sBVe@|BwG`@cDW}MBy@Py@Di@McCs@eBGu@VcDHmE@oGGk@@]K_AWcFMoFWoAQU[gBSeBWuAQeB@[[kB]uAw@yBa@uBi@yAOq@E}Ck@}CQIe@TcAtCcAp@iAvBwBdAe@@wCg@{Bq@qBsB}AaC[s@mAaBcAm@_@Em@c@yAyBcAgAmAg@w@GiC}AuEn@sBI_AKwEoAeAMyCl@kAWkEb@_@CeBkAsBQuCf@kCx@sAAiDg@WDMVo@f@QDMOIUC{BVuCGeCBuBL}A`@eBRiB^}AP{ABaAI{IF_AJm@Zu@|@y@z@YMa@MHSKSNo@Cw@}@YGGUaBqBs@gAq@wAaAkAkAoB{@eCq@_E[qAs@oEy@{CUeBCu@]s@cAuAQi@EaALe@JG?UWaCuB}GgBmHs@eBOq@MeCM{AD_APwASsDZyB?[J]@[BEGnALjAMPEx@LnC[nBCl@b@|DVdAn@`BdBzI`A`Cv@fCNlA@`@Cd@UbA@VE@DJV?p@l@d@rAN|@PzBnA`FZlCh@zBl@pDHVr@j@hAnCjAhAb@fAZf@`DdD`A`@v@HPRL^@h@Ov@e@D}@v@e@`AKv@AbBDrBP~Cs@|DO~Aw@|EMfBJzCInAg@nC@n@HOTGjA}@xBPj@Rl@A|A[PHLKf@C\\Ub@GxE@TKdAXrAf@bDQn@a@bAR`BOr@BxB\\V`@jBDfAVvDPhA]`AIpAz@rBRLT~An@b@n@NHN^lAtAnB|@h@j@b@TX`@`@nApDtEfDbBlBAt@ZV@RY`Bq@lAaCl@YVcAl@gAz@Db@rBd@pF\\`A`AdEn@xB@^lA~HJjC|@bBNz@NhCE`@Df@ENDJPrACd@HfAb@lDDhB?nB[|DG|BQx@G~AHl@\\\\b@t@E@?ZFh@BfBKdAMRBfEZ`KI~@k@xB[x@[bBQDa@tAgApCo@rC[|@{BvEm@~ADN[]_@@i@k@g@OkAcCYUEBW_@G_@gBaAyAoBkAi@]KwAJUOwBMSZOr@IvC',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [59.87336757592857, 10.84704838693142],
  'end_latlng': [59.87276793457568, 10.852464437484741],
  'average_speed': 2.035,
  'max_speed': 9.258,
  'average_temp': 24,
  'has_heartrate': False,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': False,
  'elev_high': 264.2,
  'elev_low': 157.4,
  'upload_id': 11121535121,
  'upload_id_str': '11121535121',
  'external_id': 'garmin_ping_310123433770',
  'from_accepted_tag': True,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Nordic Ski',
  'distance': 6468.9,
  'moving_time': 2994,
  'elapsed_time': 8562,
  'total_elevation_gain': 181.0,
  'type': 'NordicSki',
  'sport_type': 'NordicSki',
  'id': 10397016677,
  'start_date': '2023-12-16T12:11:24Z',
  'start_date_local': '2023-12-16T13:11:24Z',
  'timezone': '(GMT+01:00) Europe/Oslo',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 1,
  'kudos_count': 5,
  'comment_count': 0,
  'athlete_count': 4,
  'photo_count': 0,
  'map': {'id': 'a10397016677',
   'summary_polyline': 'i|llJosfaA@a@?cBSyCUgCCq@B{@Js@Fu@Eo@Oe@Gm@EeCHkCNaALa@JKLCNAd@Jp@K`ANX?bASPDt@^RBvAjBfAbA^j@ZTbA|ANf@NVHJ\\PVZLH\\N\\DBAFOABD]No@Xg@AMBIJMHMFEF[LOFQLMBIT}@N[BW`@qAFYLYBM@UFKVmA`AgBP}@x@eC\\wABsAO_BGcGG}@DSA_@By@Jc@Fk@Au@MkBUw@Um@Cg@DuAPqBPuE?q@BgCEeAUaCQ_DK{@C_DGqAC]IYm@yAKa@a@wCY{AIkAc@uCsAgF[}AW}@Kg@E]IaDIm@YmAGMYMU?OHU`@g@bBGNSPMBUJC?q@|AERORGDK?QF}@r@QHGBg@?y@Oc@Qs@UiAWYM[CgBcBS[KSGCk@q@Yc@e@cAOUkB}Ao@a@SSWOMOo@cAoA{AYSSGc@IYK_@ASG]Qi@o@MAe@We@?MB[H[Bk@XO?K@IDK@e@?OCMGSAKC[D]BWEGMECk@IyA]oA_@m@Kc@EWDsAf@YDg@@gAEy@FYCo@BKAMBa@KUOYY[USIy@Gi@B]Gc@?E?SNI@SLO?EDS@UDUHs@\\c@NO@iAEOGIAGDK?IDSDK?GEMAIIIAEESGm@EMHI@EHC?KPYVSFOKIOA]?aABKAQDg@CK@EFE?EDEBQFM@YGe@B{@Cg@BKIs@@k@BOAOVeBH[@MNo@VwBHa@JUB]DOBWBELcABy@EOCCEM?g@E]?cGR_ARm@PUb@a@RGZCPGHMDS?SQMCGC?EDE?GJEOEBELGAG@KJKCQBO]IACKEAGSS[IAGKSEEOEACEa@[Y[MQUM[q@a@s@g@y@QOQYW[EWYq@_@e@m@uAGQs@qDq@qEOi@QiAs@{CY_DYg@mAmAS]EWAQB[VcABW?_@QcAQy@y@mBc@yAMi@WuAm@gCGIKi@@OK_@s@yA',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [59.87345843575895, 10.847598826512694],
  'end_latlng': [59.891640124842525, 10.907219052314758],
  'average_speed': 2.161,
  'max_speed': 9.522,
  'average_temp': 17,
  'average_watts': 105.2,
  'max_watts': 380,
  'weighted_average_watts': 122,
  'kilojoules': 315.1,
  'device_watts': True,
  'has_heartrate': True,
  'average_heartrate': 121.7,
  'max_heartrate': 156.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 260.6,
  'elev_low': 161.8,
  'upload_id': 11128952413,
  'upload_id_str': '11128952413',
  'external_id': 'garmin_ping_310337228123',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Weight Training',
  'distance': 0.0,
  'moving_time': 2307,
  'elapsed_time': 2307,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10380405316,
  'start_date': '2023-12-14T18:08:25Z',
  'start_date_local': '2023-12-14T19:08:25Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 2,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10380405316', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 28,
  'has_heartrate': True,
  'average_heartrate': 124.1,
  'max_heartrate': 151.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11111516794,
  'upload_id_str': '11111516794',
  'external_id': 'garmin_ping_309815410734',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Run',
  'distance': 2768.6,
  'moving_time': 1204,
  'elapsed_time': 1204,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10380149955,
  'start_date': '2023-12-14T17:41:10Z',
  'start_date_local': '2023-12-14T18:41:10Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10380149955', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.3,
  'max_speed': 2.986,
  'average_cadence': 74.3,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 163.0,
  'max_heartrate': 180.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11111255088,
  'upload_id_str': '11111255088',
  'external_id': 'garmin_ping_309808812331',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Run',
  'distance': 8879.2,
  'moving_time': 2969,
  'elapsed_time': 2969,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10369911775,
  'start_date': '2023-12-12T17:31:54Z',
  'start_date_local': '2023-12-12T18:31:54Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10369911775', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.991,
  'max_speed': 4.605,
  'average_cadence': 74.9,
  'average_temp': 24,
  'has_heartrate': True,
  'average_heartrate': 168.2,
  'max_heartrate': 192.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11100619755,
  'upload_id_str': '11100619755',
  'external_id': 'garmin_ping_309477573591',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Morning Nordic Ski',
  'distance': 7998.1,
  'moving_time': 2687,
  'elapsed_time': 3025,
  'total_elevation_gain': 122.0,
  'type': 'NordicSki',
  'sport_type': 'NordicSki',
  'id': 10357303721,
  'start_date': '2023-12-10T09:50:38Z',
  'start_date_local': '2023-12-10T10:50:38Z',
  'timezone': '(GMT+01:00) Europe/Oslo',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 17,
  'kudos_count': 8,
  'comment_count': 0,
  'athlete_count': 2,
  'photo_count': 0,
  'map': {'id': 'a10357303721',
   'summary_polyline': 's}jlJareaARt@RX`@d@NDd@ID@NRrAxDZfABR@r@GtAIn@QXKBc@SW[W]Ug@C[C{AM}@Qg@MU_@]EIa@}BUmBIg@IQKAIBEHET@|@HtA@xABf@Ch@EFE?MQIgBIg@K_@CECFIDIAOIm@g@]i@i@gBe@u@WKm@ISMKQGg@ZcCN_@FELALBHDxAdBjBfC\\X`@N\\BHG?MWoCIWGKOKq@_@s@W][IKKWGc@BYDILMJC\\LNHh@j@l@vA^^JXPvBDfAJn@X`BFRP`@j@r@TBTIH@NJ^dAlAtDBVChBIv@KXKDM?KCm@g@Q[Q{@C_AMgAQi@e@i@Ua@i@{CQoAMc@QKG@GRALJvABrA?`AB\\Eh@GJEACCGSEyAG[GOe@MSKQK]_@Yo@_@wAQ[YY]SKCSASEIEKQCM?_@TuBNg@NKJATJrAxAx@dAl@~@NP\\ZTHN@VCJI@KGc@EcAKm@EKSSSMs@_@i@QQMOQMWGYAO@UBQPWP?XLXTl@bAXp@TXT\\H\\B^TrD\\jBNf@RZb@d@R@NGF?TV\\`Af@bBNRHVJZD^?|AG`AKZGFI@KA[U[a@S[Ie@G}AUuAKYi@q@MWi@qCUgBIMOIGBINAHFhB@bA?tBAl@MOIWGqAMg@KK_@KUMm@e@QEEc@Ow@I]MWe@i@MEq@GICMMKSEi@T_CPe@NKL?h@^l@r@fBfCRb@x@f@^DTG@MGgAGa@Gu@ISGGu@_@eAe@]YEMK_AB]HIPGZNb@Z\\d@f@jA^b@Nv@TrDHr@P~@Pd@Rb@b@h@LBb@AJFNPb@lAt@dCJh@B^CxAGp@KZIFI?UI]_@a@m@Kk@GiAKaAQc@u@}@MW_@uCSsBKUOEGBEFCVBbAHlEC^ELEBEAGIEkAGg@IWQOk@Qc@]c@y@e@}AYg@]Sy@GOIMUEKEY@KRgBH[JYFIFEPAD@JHvBbCdAxAb@`@RJVDT@JE@U[oC[[wBiA[]M_@CWBUFSLKLCJ@d@TTP`@f@\\z@FNNPJNJZFVDr@N~CHf@D`@Pl@HR^h@LVTDd@EPHFJ\\dAZpARh@Tp@BPBh@EfAKbAOVIBIAMEMI]_@QYM_@Ge@A[',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [59.86453916877508, 10.847949357703328],
  'end_latlng': [59.863721346482635, 10.846687210723758],
  'average_speed': 2.977,
  'max_speed': 8.628,
  'average_temp': 14,
  'average_watts': 127.5,
  'max_watts': 507,
  'weighted_average_watts': 138,
  'kilojoules': 342.7,
  'device_watts': True,
  'has_heartrate': True,
  'average_heartrate': 157.4,
  'max_heartrate': 178.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 140.6,
  'elev_low': 124.2,
  'upload_id': 11087390931,
  'upload_id_str': '11087390931',
  'external_id': 'garmin_ping_309076417975',
  'from_accepted_tag': False,
  'pr_count': 6,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Evening Run',
  'distance': 7080.1,
  'moving_time': 2310,
  'elapsed_time': 2742,
  'total_elevation_gain': 71.0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10344112132,
  'start_date': '2023-12-07T17:18:53Z',
  'start_date_local': '2023-12-07T18:18:53Z',
  'timezone': '(GMT+01:00) Europe/Oslo',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 7,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10344112132',
   'summary_polyline': 'uctlJaty`A`Ax@j@n@NDb@bBJv@FfANXJi@?WFKHDTb@TFNAB@R`Bd@lCb@xDZzBd@`Cl@|BJ|@Dx@l@hB`A~EDp@APRNFRZpAV~ADz@?dBErAAlCEfCKjBChAAlA@n@XvDPjAPtBLfAhAdFDnBEnAMx@Ur@Ix@IhBOnCSvBO|@Sv@g@xDYbC_@nCGXUn@SpAc@pBS`BCv@Oz@IN]dEUfBs@dEC^M^[fBYlAMbAMXK`@SdBUrAAh@Sx@Kt@W~@k@bE[`BQzA]pBK|@CpBQjAG\\Uh@SrBQ|@S|A_@jBOhBYhAKr@Ov@OpAS`@e@fBMv@@Nf@{AF_@DIHa@HQJ[Lk@B]@OCK@UHQZkAPy@NcANe@VwAFi@DKZkCEUAg@F{Aj@gF^eCZ{AHk@^cBRs@Iw@Vo@D[r@kDd@{ADg@D_BLuAb@mBd@kCD_@q@IUOcA[WKMOa@aB[{@AISq@KQaAwA{AcBc@_@SYSOESe@{@Om@E_@[_@Sw@O_BUgAW}@UYAKV{@F]`@}BHcAL{@GMu@QyAcA]a@GaAYgC?SFgADUZg@Di@LSJWTqAD_APaBBu@@iAAa@@e@Pe@JgA^eCDq@`@}BpAiE`@aARs@Lw@Ps@r@{BVo@j@sBNu@d@cATw@FOTmBDG~@e@\\k@^c@j@cADUEi@BUB_BC}@[oBWoCcAgDGkAQw@Se@aAyCYm@W[OGqA]w@O]MIIW_@Oy@',
   'resource_state': 2},
  'trainer': False,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [59.91287073120475, 10.786831537261605],
  'end_latlng': [59.913376830518246, 10.786710418760777],
  'average_speed': 3.065,
  'max_speed': 4.68,
  'average_cadence': 78.4,
  'average_temp': 12,
  'has_heartrate': True,
  'average_heartrate': 157.8,
  'max_heartrate': 180.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 60.6,
  'elev_low': 6.8,
  'upload_id': 11073653382,
  'upload_id_str': '11073653382',
  'external_id': 'garmin_ping_308637535072',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Weight Training',
  'distance': 0.0,
  'moving_time': 1516,
  'elapsed_time': 1516,
  'total_elevation_gain': 0,
  'type': 'WeightTraining',
  'sport_type': 'WeightTraining',
  'id': 10342792090,
  'start_date': '2023-12-06T16:50:33Z',
  'start_date_local': '2023-12-06T17:50:33Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 3,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10342792090', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 0.0,
  'max_speed': 0.0,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 137.6,
  'max_heartrate': 175.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11072289129,
  'upload_id_str': '11072289129',
  'external_id': 'garmin_ping_308598886613',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False},
 {'resource_state': 2,
  'athlete': {'id': 40990567, 'resource_state': 1},
  'name': 'Afternoon Run',
  'distance': 4222.7,
  'moving_time': 1570,
  'elapsed_time': 1570,
  'total_elevation_gain': 0,
  'type': 'Run',
  'sport_type': 'Run',
  'workout_type': None,
  'id': 10342791527,
  'start_date': '2023-12-06T16:18:39Z',
  'start_date_local': '2023-12-06T17:18:39Z',
  'timezone': '(GMT+01:00) Africa/Algiers',
  'utc_offset': 3600.0,
  'location_city': None,
  'location_state': None,
  'location_country': None,
  'achievement_count': 0,
  'kudos_count': 4,
  'comment_count': 0,
  'athlete_count': 1,
  'photo_count': 0,
  'map': {'id': 'a10342791527', 'summary_polyline': '', 'resource_state': 2},
  'trainer': True,
  'commute': False,
  'manual': False,
  'private': False,
  'visibility': 'everyone',
  'flagged': False,
  'gear_id': None,
  'start_latlng': [],
  'end_latlng': [],
  'average_speed': 2.69,
  'max_speed': 3.488,
  'average_cadence': 74.8,
  'average_temp': 26,
  'has_heartrate': True,
  'average_heartrate': 161.6,
  'max_heartrate': 179.0,
  'heartrate_opt_out': False,
  'display_hide_heartrate_option': True,
  'elev_high': 0.0,
  'elev_low': 0.0,
  'upload_id': 11072288620,
  'upload_id_str': '11072288620',
  'external_id': 'garmin_ping_308598873919',
  'from_accepted_tag': False,
  'pr_count': 0,
  'total_photo_count': 0,
  'has_kudoed': False}]
In [299]:
raw_recent_played
Out[299]:
{'error': {'status': 401, 'message': 'The access token expired'}}

Extra data¶

Some extra data saved to help overlap with some activities.

In [236]:
extra_tracks = pd.read_csv("../spotify/history_240130_240203.csv")
extra_tracks
Out[236]:
start id artist name
0 2024-01-30T04:33:47.317Z 2h1a5N6j2o6AYATr89sYIb Channel Tres Controller - Walker & Royce Remix
1 2024-01-30T04:38:39.749Z 2qNfRjvI0SQ5JUVKWg1eOK Offer Nissim Fucker
2 2024-01-30T04:40:58.325Z 37cv9aDLaYWjNF8poD202x Walker & Royce My Own Thang (feat. Sophiegrophy) - John Tejad...
3 2024-01-30T04:46:24.898Z 37cv9aDLaYWjNF8poD202x Walker & Royce My Own Thang (feat. Sophiegrophy) - John Tejad...
4 2024-01-30T04:50:56.829Z 4WH09DYN9V10uNnPx23EM7 Freaky Boy I Fink U Freeky - I Think You're Freaky
... ... ... ... ...
181 2024-02-03T10:45:46.516Z 1cWHaz5GDrhfnkXTIYlGHh Brian Eno Stars - Remastered 2005
182 2024-02-03T10:50:13.092Z 3an5DoMqoNpJEQwfA48A4c Brian Eno Lizard Point - Remastered 2004
183 2024-02-03T10:59:25.110Z 2JbqOGlvO809TTEtteJFwp Brian Eno The Lost Day - Remastered 2004
184 2024-02-03T11:04:53.077Z 33xP7dQigSmT5apPCcDVBE Brian Eno Tal Coat - Remastered 2004
185 2024-02-03T11:07:54.131Z 0vI3D1jkXAaqX4wmFVTYX7 Brian Eno Shadow - Remastered 2004

186 rows × 4 columns

Convert to dataframe¶

In [173]:
recent_played_df = pd.DataFrame(raw_recent_played["items"])

recent_played_df.head()
Out[173]:
track played_at context
0 {'album': {'album_type': 'single', 'artists': ... 2024-02-03T20:22:58.221Z {'type': 'playlist', 'href': 'https://api.spot...
1 {'album': {'album_type': 'compilation', 'artis... 2024-02-03T20:22:51.340Z {'type': 'playlist', 'href': 'https://api.spot...
2 {'album': {'album_type': 'compilation', 'artis... 2024-02-03T20:18:41.765Z {'type': 'playlist', 'href': 'https://api.spot...
3 {'album': {'album_type': 'single', 'artists': ... 2024-02-03T15:31:35.065Z {'type': 'playlist', 'href': 'https://api.spot...
4 {'album': {'album_type': 'single', 'artists': ... 2024-02-03T15:28:47.082Z {'type': 'playlist', 'href': 'https://api.spot...
In [174]:
activities_df = pd.DataFrame(raw_activities)

print(*activities_df.columns.to_list(), sep="\n")
resource_state
athlete
name
distance
moving_time
elapsed_time
total_elevation_gain
type
sport_type
id
start_date
start_date_local
timezone
utc_offset
location_city
location_state
location_country
achievement_count
kudos_count
comment_count
athlete_count
photo_count
map
trainer
commute
manual
private
visibility
flagged
gear_id
start_latlng
end_latlng
average_speed
max_speed
average_temp
has_heartrate
heartrate_opt_out
display_hide_heartrate_option
elev_high
elev_low
upload_id
upload_id_str
external_id
from_accepted_tag
pr_count
total_photo_count
has_kudoed
average_cadence
average_heartrate
max_heartrate
workout_type
average_watts
max_watts
weighted_average_watts
kilojoules
device_watts

Preprocessing¶

Prepare the dataframes to be easily comparable.

In [193]:
def preprocess_activities(activities_df):
    activities_df = activities_df.copy()

    # stored in Oslo timezone
    activities_df["start"] = pd.to_datetime(activities_df["start_date"])

    # build end_date from start_date and elapsed_time
    activities_df["end"] = activities_df["start"] + pd.to_timedelta(activities_df["elapsed_time"], unit="s")

    # start and end are in UTC, convert to Oslo timezone
    activities_df["start"] = activities_df["start"].dt.tz_convert("Europe/Oslo")
    activities_df["end"] = activities_df["end"].dt.tz_convert("Europe/Oslo")

    # drop unnecessary columns
    return activities_df[["athlete", "id", "start", "end"]]


activities = preprocess_activities(activities_df)
activities.head()
Out[193]:
athlete id start end
0 {'id': 40990567, 'resource_state': 1} 10688279212 2024-02-03 15:55:59+01:00 2024-02-03 16:23:07+01:00
1 {'id': 40990567, 'resource_state': 1} 10682232510 2024-02-02 18:03:10+01:00 2024-02-02 19:07:32+01:00
2 {'id': 40990567, 'resource_state': 1} 10678755706 2024-02-02 06:47:05+01:00 2024-02-02 07:18:10+01:00
3 {'id': 40990567, 'resource_state': 1} 10672180060 2024-02-01 07:14:17+01:00 2024-02-01 07:27:05+01:00
4 {'id': 40990567, 'resource_state': 1} 10672137037 2024-02-01 06:37:09+01:00 2024-02-01 07:10:13+01:00
In [227]:
def preprocess_tracks(recent_played_df):
    recent_played_df = recent_played_df.copy().sort_values("played_at")

    # convert to datetime
    recent_played_df["start"] = pd.to_datetime(recent_played_df["played_at"], format='mixed')

    # build end_time
    expected_end_time = recent_played_df["start"] + pd.to_timedelta(recent_played_df["track"].apply(lambda x: x["duration_ms"]), unit="ms")
    next_song_start = recent_played_df["start"].shift(1)
    recent_played_df["end"] = expected_end_time.combine(next_song_start, min)

    # rename to start, end, name, artist, track id
    recent_played_df["track_name"] = recent_played_df["track"].apply(lambda x: x["name"])
    recent_played_df["artist"] = recent_played_df["track"].apply(lambda x: x["artists"][0]["name"])
    recent_played_df["id"] = recent_played_df["track"].apply(lambda x: x["id"])

    # start and end are in UTC, convert to Oslo timezone
    recent_played_df["start"] = recent_played_df["start"].dt.tz_convert("Europe/Oslo")
    recent_played_df["end"] = recent_played_df["end"].dt.tz_convert("Europe/Oslo")

    # drop unnecessary columns
    return recent_played_df[["start", "end", "track_name", "artist", "id"]]

tracks = preprocess_tracks(recent_played_df)
tracks.head()
Out[227]:
start end track_name artist id
49 2024-02-03 12:04:53.077000+01:00 2024-02-03 12:10:20.983000+01:00 Tal Coat - Remastered 2004 Brian Eno 33xP7dQigSmT5apPCcDVBE
48 2024-02-03 12:07:54.131000+01:00 2024-02-03 12:04:53.077000+01:00 Shadow - Remastered 2004 Brian Eno 0vI3D1jkXAaqX4wmFVTYX7
47 2024-02-03 12:13:25.107000+01:00 2024-02-03 12:07:54.131000+01:00 Lantern Marsh - Remastered 2004 Brian Eno 5QUimuUIhEtuMU475o4xVB
46 2024-02-03 12:18:52.089000+01:00 2024-02-03 12:13:25.107000+01:00 Unfamiliar Wind (Leeks Hills) - Remastered 2004 Brian Eno 4SngUdjKC7Njrz5UVik26A
45 2024-02-03 12:23:00.075000+01:00 2024-02-03 12:18:52.089000+01:00 A Clearing - Remastered 2004 Brian Eno 4y5qZUZU5sojHT033PFP26
In [237]:
def preprocess_extra_tracks(extra_tracks):
    extra_tracks = extra_tracks.copy().sort_values("start", ascending=False)
    extra_tracks["start"] = pd.to_datetime(extra_tracks["start"])

    # build end_time
    expected_end_time = pd.to_datetime(extra_tracks["start"]) + pd.to_timedelta(30, unit="m")
    next_song_start = pd.to_datetime(extra_tracks["start"]).shift(1)
    extra_tracks["end"] = expected_end_time.combine(next_song_start, min)

    # rename name to track_name to help pandas
    if "name" in extra_tracks.columns:
        extra_tracks["track_name"] = extra_tracks["name"]

    # my timezone is off, need to add an hour (hack)
    if extra_tracks.start.iloc[0].hour < 6:
        extra_tracks["start"] = extra_tracks["start"] + pd.to_timedelta(1, unit="h")
        extra_tracks["end"] = extra_tracks["end"] + pd.to_timedelta(1, unit="h")

    # start and end are in UTC, convert to Oslo timezone
    extra_tracks["start"] = extra_tracks["start"].dt.tz_convert("Europe/Oslo")
    extra_tracks["end"] = extra_tracks["end"].dt.tz_convert("Europe/Oslo")

    return extra_tracks[["start", "end", "track_name", "artist", "id"]]

extra_tracks = preprocess_extra_tracks(extra_tracks)
extra_tracks.head()
Out[237]:
start end track_name artist id
185 2024-02-03 12:07:54.131000+01:00 2024-02-03 12:37:54.131000+01:00 Shadow - Remastered 2004 Brian Eno 0vI3D1jkXAaqX4wmFVTYX7
184 2024-02-03 12:04:53.077000+01:00 2024-02-03 12:07:54.131000+01:00 Tal Coat - Remastered 2004 Brian Eno 33xP7dQigSmT5apPCcDVBE
183 2024-02-03 11:59:25.110000+01:00 2024-02-03 12:04:53.077000+01:00 The Lost Day - Remastered 2004 Brian Eno 2JbqOGlvO809TTEtteJFwp
182 2024-02-03 11:50:13.092000+01:00 2024-02-03 11:59:25.110000+01:00 Lizard Point - Remastered 2004 Brian Eno 3an5DoMqoNpJEQwfA48A4c
181 2024-02-03 11:45:46.516000+01:00 2024-02-03 11:50:13.092000+01:00 Stars - Remastered 2005 Brian Eno 1cWHaz5GDrhfnkXTIYlGHh
In [293]:
# merge all tracks, drop duplicates and sort by start time
all_tracks = pd.concat([tracks, extra_tracks]).drop_duplicates().sort_values("start").reset_index(drop=True)
all_tracks.head()
Out[293]:
start end track_name artist id
0 2024-01-30 05:33:47.317000+01:00 2024-01-30 05:38:39.749000+01:00 Controller - Walker & Royce Remix Channel Tres 2h1a5N6j2o6AYATr89sYIb
1 2024-01-30 05:38:39.749000+01:00 2024-01-30 05:40:58.325000+01:00 Fucker Offer Nissim 2qNfRjvI0SQ5JUVKWg1eOK
2 2024-01-30 05:40:58.325000+01:00 2024-01-30 05:46:24.898000+01:00 My Own Thang (feat. Sophiegrophy) - John Tejad... Walker & Royce 37cv9aDLaYWjNF8poD202x
3 2024-01-30 05:46:24.898000+01:00 2024-01-30 05:50:56.829000+01:00 My Own Thang (feat. Sophiegrophy) - John Tejad... Walker & Royce 37cv9aDLaYWjNF8poD202x
4 2024-01-30 05:50:56.829000+01:00 2024-01-30 05:57:07.859000+01:00 I Fink U Freeky - I Think You're Freaky Freaky Boy 4WH09DYN9V10uNnPx23EM7

Find songs per activity¶

In [251]:
activities.head()
Out[251]:
athlete id start end
0 {'id': 40990567, 'resource_state': 1} 10688279212 2024-02-03 15:55:59+01:00 2024-02-03 16:23:07+01:00
1 {'id': 40990567, 'resource_state': 1} 10682232510 2024-02-02 18:03:10+01:00 2024-02-02 19:07:32+01:00
2 {'id': 40990567, 'resource_state': 1} 10678755706 2024-02-02 06:47:05+01:00 2024-02-02 07:18:10+01:00
3 {'id': 40990567, 'resource_state': 1} 10672180060 2024-02-01 07:14:17+01:00 2024-02-01 07:27:05+01:00
4 {'id': 40990567, 'resource_state': 1} 10672137037 2024-02-01 06:37:09+01:00 2024-02-01 07:10:13+01:00
In [263]:
recent_activities = activities[activities["start"] > all_tracks["start"].min()].copy()

recent_activities
Out[263]:
athlete id start end
0 {'id': 40990567, 'resource_state': 1} 10688279212 2024-02-03 15:55:59+01:00 2024-02-03 16:23:07+01:00
1 {'id': 40990567, 'resource_state': 1} 10682232510 2024-02-02 18:03:10+01:00 2024-02-02 19:07:32+01:00
2 {'id': 40990567, 'resource_state': 1} 10678755706 2024-02-02 06:47:05+01:00 2024-02-02 07:18:10+01:00
3 {'id': 40990567, 'resource_state': 1} 10672180060 2024-02-01 07:14:17+01:00 2024-02-01 07:27:05+01:00
4 {'id': 40990567, 'resource_state': 1} 10672137037 2024-02-01 06:37:09+01:00 2024-02-01 07:10:13+01:00
5 {'id': 40990567, 'resource_state': 1} 10668078046 2024-01-31 15:49:16+01:00 2024-01-31 16:30:22+01:00
6 {'id': 40990567, 'resource_state': 1} 10667818129 2024-01-31 15:24:34+01:00 2024-01-31 15:43:39+01:00
7 {'id': 40990567, 'resource_state': 1} 10658118191 2024-01-30 06:30:04+01:00 2024-01-30 07:20:18+01:00
In [264]:
x = recent_activities.iloc[-1]

def overlap(x, y):
    return (x.start < y.end) & (x.end > y.start)

def build_track_str(x):
    return f"{x.track_name} - {x.artist}"

def get_tracklist(x, y):
    return y[overlap(x, y)].apply(build_track_str, axis=1).to_list()

get_tracklist(x, all_tracks)
Out[264]:
['Power - Hoxton Whores',
 'Thank You (Not So Bad) - Dimitri Vegas & Like Mike',
 'Renaissance - BUNT.',
 'Praise The Lord (Da Shine) (feat. Skepta) - Durdenhauer Edit - Durdenhauer',
 'Vois sur ton chemin - Techno Mix - BENNETT',
 'La leçon particulière - Lavern',
 'Tell Me Why - MEDUZA Remix - Supermode',
 'Prada - cassö',
 'Miss You - southstar',
 'Mask Off - Nicolas Julian',
 'Tell Me Why - Maddix Remix - Supermode',
 'A Milli - SIDEPIECE Remix - Lil Wayne',
 'No Type - Pegassi',
 'Turn On The Lights again.. (feat. Future) - Fred again..']
In [267]:
recent_activities["tracklist"] = recent_activities.apply(lambda x: get_tracklist(x, all_tracks), axis=1)
recent_activities
Out[267]:
athlete id start end tracklist
0 {'id': 40990567, 'resource_state': 1} 10688279212 2024-02-03 15:55:59+01:00 2024-02-03 16:23:07+01:00 [Sound Bath Escape - The Tibetan Singing Bowls...
1 {'id': 40990567, 'resource_state': 1} 10682232510 2024-02-02 18:03:10+01:00 2024-02-02 19:07:32+01:00 []
2 {'id': 40990567, 'resource_state': 1} 10678755706 2024-02-02 06:47:05+01:00 2024-02-02 07:18:10+01:00 [Thank You (Not So Bad) - Dimitri Vegas & Like...
3 {'id': 40990567, 'resource_state': 1} 10672180060 2024-02-01 07:14:17+01:00 2024-02-01 07:27:05+01:00 []
4 {'id': 40990567, 'resource_state': 1} 10672137037 2024-02-01 06:37:09+01:00 2024-02-01 07:10:13+01:00 []
5 {'id': 40990567, 'resource_state': 1} 10668078046 2024-01-31 15:49:16+01:00 2024-01-31 16:30:22+01:00 [Satisfaction - Hardwell & Maddix Remix - Davi...
6 {'id': 40990567, 'resource_state': 1} 10667818129 2024-01-31 15:24:34+01:00 2024-01-31 15:43:39+01:00 [Heute Nacht - Maddix, Prada - cassö]
7 {'id': 40990567, 'resource_state': 1} 10658118191 2024-01-30 06:30:04+01:00 2024-01-30 07:20:18+01:00 [Power - Hoxton Whores, Thank You (Not So Bad)...

Sweet! Now our activities have the list of tracks played during them. NExt step is to update the descriptions of the activities with these tracks.

In [296]:
recent_activities.apply(get_tracklist, y=all_tracks, axis=1)
Out[296]:
0    [Sound Bath Escape - The Tibetan Singing Bowls...
1                                                   []
2    [Thank You (Not So Bad) - Dimitri Vegas & Like...
3                                                   []
4                                                   []
5    [Satisfaction - Hardwell & Maddix Remix - Davi...
6                [Heute Nacht - Maddix, Prada - cassö]
7    [Power - Hoxton Whores, Thank You (Not So Bad)...
dtype: object

Add tracklists to activity descriptions¶

To do this properly, we first need to get_activity(id) to make sure the description doesn't already have a tracklist. If there is no tracklist present, we can then update_activity(id, params={}) with description + tracklist.

In [291]:
def get_activity(id, access_token=strava_tokens["access_token"]):
    URL = f"https://www.strava.com/api/v3/activities/{id}"    # api-endpoint for recently played
    HEAD = {"Authorization": f"Bearer {access_token}"}
    content = r.get(URL, headers=HEAD)
    return content.json()


def update_activity(id, data, access_token=strava_tokens["access_token"]):
    URL = f"https://www.strava.com/api/v3/activities/{id}"    # api-endpoint for recently played
    HEAD = {"Authorization": f"Bearer {access_token}"}
    content = r.put(URL, headers=HEAD, data=data)
    return content


def contains_tracklist(description):
    return (description is not None) and "Tracklist" in description


def add_tracklist(id, tracklist, access_token=strava_tokens["access_token"]):
    activity = get_activity(id, access_token)
    description = activity["description"]
    tracks = "\n".join(tracklist)
    if contains_tracklist(description) or len(tracks) == 0:
        return description
    description = (
        description + tracklist_template.format(tracks) 
        if description else tracklist_template.format(tracks)
    )
    content = update_activity(id, {"description": description}, access_token)
    print(content.json()) if content.status_code != 200 else None
    return get_activity(id, access_token)["description"]


tracklist_template = """
Tracklist:
{}

Uploaded automatically using https://github.com/pmhalvor/endurabeats/
"""

add_tracklist(10672137037, [])
Out[291]:
''
In [292]:
recent_activities_w_descrptions = recent_activities.copy().apply(lambda x: add_tracklist(x.id, x.tracklist), axis=1)
recent_activities_w_descrptions
Out[292]:
0    \nTracklist:\nSound Bath Escape - The Tibetan ...
1                                                 None
2    \nTracklist:\nThank You (Not So Bad) - Dimitri...
3                                                 None
4                                                     
5    \nTracklist:\nSatisfaction - Hardwell & Maddix...
6    \nTracklist:\nHeute Nacht - Maddix\nPrada - ca...
7    \nTracklist:\nPower - Hoxton Whores\nThank You...
dtype: object
In [ ]: