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 [ ]: