|
@ -14,7 +14,8 @@ import json |
|
|
USER_TRACKS_LIMIT = 50 |
|
|
USER_TRACKS_LIMIT = 50 |
|
|
# ARTIST_LIMIT = 50 |
|
|
# ARTIST_LIMIT = 50 |
|
|
ARTIST_LIMIT = 25 |
|
|
ARTIST_LIMIT = 25 |
|
|
FEATURES_LIMIT = 100 |
|
|
|
|
|
|
|
|
# FEATURES_LIMIT = 100 |
|
|
|
|
|
FEATURES_LIMIT = 25 |
|
|
|
|
|
|
|
|
# parse_library {{{ # |
|
|
# parse_library {{{ # |
|
|
|
|
|
|
|
@ -33,61 +34,66 @@ def parse_library(headers, tracks, user): |
|
|
offset = 0 |
|
|
offset = 0 |
|
|
payload = {'limit': str(USER_TRACKS_LIMIT)} |
|
|
payload = {'limit': str(USER_TRACKS_LIMIT)} |
|
|
artist_genre_queue = [] |
|
|
artist_genre_queue = [] |
|
|
|
|
|
features_queue = [] |
|
|
|
|
|
|
|
|
# iterate until hit requested num of tracks |
|
|
# iterate until hit requested num of tracks |
|
|
for _ in range(0, tracks, USER_TRACKS_LIMIT): |
|
|
for _ in range(0, tracks, USER_TRACKS_LIMIT): |
|
|
payload['offset'] = str(offset) |
|
|
payload['offset'] = str(offset) |
|
|
# get current set of tracks |
|
|
|
|
|
saved_tracks_response = requests.get('https://api.spotify.com/v1/me/tracks', headers=headers, params=payload).json() |
|
|
|
|
|
|
|
|
saved_tracks_response = requests.get('https://api.spotify.com/v1/me/tracks', |
|
|
|
|
|
headers=headers, |
|
|
|
|
|
params=payload).json() |
|
|
|
|
|
|
|
|
# TODO: refactor the for loop body into helper function |
|
|
|
|
|
# iterate through each track |
|
|
|
|
|
for track_dict in saved_tracks_response['items']: |
|
|
for track_dict in saved_tracks_response['items']: |
|
|
|
|
|
# add artists {{{ # |
|
|
|
|
|
|
|
|
# update artist info before track so that Track object can reference |
|
|
# update artist info before track so that Track object can reference |
|
|
# Artist object |
|
|
# Artist object |
|
|
track_artists = [] |
|
|
track_artists = [] |
|
|
for artist_dict in track_dict['track']['artists']: |
|
|
for artist_dict in track_dict['track']['artists']: |
|
|
artist_obj, artist_created = Artist.objects.get_or_create( |
|
|
artist_obj, artist_created = Artist.objects.get_or_create( |
|
|
artist_id=artist_dict['id'], |
|
|
artist_id=artist_dict['id'], |
|
|
name=artist_dict['name'], |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
name=artist_dict['name'],) |
|
|
|
|
|
# only add/tally up artist genres if new |
|
|
if artist_created: |
|
|
if artist_created: |
|
|
artist_genre_queue.append(artist_obj) |
|
|
artist_genre_queue.append(artist_obj) |
|
|
if len(artist_genre_queue) == ARTIST_LIMIT: |
|
|
if len(artist_genre_queue) == ARTIST_LIMIT: |
|
|
add_artist_genres(headers, artist_genre_queue) |
|
|
add_artist_genres(headers, artist_genre_queue) |
|
|
artist_genre_queue = [] |
|
|
artist_genre_queue = [] |
|
|
|
|
|
|
|
|
# update_artist_genre(headers, artist_obj) |
|
|
|
|
|
# get_or_create() returns a tuple (obj, created) |
|
|
|
|
|
track_artists.append(artist_obj) |
|
|
track_artists.append(artist_obj) |
|
|
|
|
|
|
|
|
# top_genre = get_top_genre(headers, |
|
|
|
|
|
# track_dict['track']['artists'][0]['id']) |
|
|
|
|
|
|
|
|
# }}} add artists # |
|
|
|
|
|
|
|
|
|
|
|
# WIP: get most common genre |
|
|
top_genre = "" |
|
|
top_genre = "" |
|
|
track_obj, track_created = save_track_obj(track_dict['track'], |
|
|
track_obj, track_created = save_track_obj(track_dict['track'], |
|
|
track_artists, top_genre, user) |
|
|
track_artists, top_genre, user) |
|
|
|
|
|
|
|
|
# if a new track is not created, the associated audio feature does not need to be created again |
|
|
|
|
|
# if track_created: |
|
|
|
|
|
save_audio_features(headers, track_dict['track']['id'], track_obj) |
|
|
|
|
|
""" |
|
|
|
|
|
TODO: Put this logic in another function |
|
|
|
|
|
# Audio analysis could be empty if not present in Spotify database |
|
|
|
|
|
if len(audio_features_dict) != 0: |
|
|
|
|
|
# Track the number of audio analyses for calculating |
|
|
|
|
|
# audio feature averages and standard deviations on the fly |
|
|
|
|
|
feature_data_points += 1 |
|
|
|
|
|
for feature, feature_data in audio_features_dict.items(): |
|
|
|
|
|
update_audio_feature_stats(feature, feature_data, |
|
|
|
|
|
feature_data_points, library_stats) |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
# add audio features {{{ # |
|
|
|
|
|
|
|
|
|
|
|
# if a new track is not created, the associated audio feature does |
|
|
|
|
|
# not need to be created again |
|
|
|
|
|
if track_created: |
|
|
|
|
|
features_queue.append(track_obj) |
|
|
|
|
|
if len(features_queue) == FEATURES_LIMIT: |
|
|
|
|
|
get_audio_features(headers, features_queue) |
|
|
|
|
|
features_queue = [] |
|
|
|
|
|
|
|
|
|
|
|
# }}} add audio features # |
|
|
|
|
|
|
|
|
# calculates num_songs with offset + songs retrieved |
|
|
# calculates num_songs with offset + songs retrieved |
|
|
offset += USER_TRACKS_LIMIT |
|
|
offset += USER_TRACKS_LIMIT |
|
|
# pprint.pprint(library_stats) |
|
|
|
|
|
|
|
|
|
|
|
# update artists left in queue since there will be probably be leftover |
|
|
|
|
|
# artists that didn't hit ARTIST_LIMIT |
|
|
|
|
|
|
|
|
# clean-up {{{ # |
|
|
|
|
|
|
|
|
|
|
|
# update remaining artists without genres and songs without features if |
|
|
|
|
|
# there are any |
|
|
|
|
|
if len(artist_genre_queue) > 0: |
|
|
add_artist_genres(headers, artist_genre_queue) |
|
|
add_artist_genres(headers, artist_genre_queue) |
|
|
|
|
|
if len(features_queue) > 0: |
|
|
|
|
|
get_audio_features(headers, features_queue) |
|
|
|
|
|
|
|
|
|
|
|
# }}} clean-up # |
|
|
|
|
|
|
|
|
update_track_genres(user) |
|
|
update_track_genres(user) |
|
|
|
|
|
|
|
|
# }}} parse_library # |
|
|
# }}} parse_library # |
|
@ -118,6 +124,7 @@ def save_track_obj(track_dict, artists, top_genre, user): |
|
|
:artists: artists of the song, passed in as a list of Artist objects. |
|
|
:artists: artists of the song, passed in as a list of Artist objects. |
|
|
:top_genre: top genre associated with this track (see get_top_genre). |
|
|
:top_genre: top genre associated with this track (see get_top_genre). |
|
|
:user: User object for which this Track is to be associated with. |
|
|
:user: User object for which this Track is to be associated with. |
|
|
|
|
|
|
|
|
:returns: (The created/retrieved Track object, created) |
|
|
:returns: (The created/retrieved Track object, created) |
|
|
|
|
|
|
|
|
""" |
|
|
""" |
|
@ -144,6 +151,33 @@ def save_track_obj(track_dict, artists, top_genre, user): |
|
|
|
|
|
|
|
|
# }}} save_track_obj # |
|
|
# }}} save_track_obj # |
|
|
|
|
|
|
|
|
|
|
|
def get_audio_features(headers, track_objs): |
|
|
|
|
|
"""Creates and saves a new AudioFeatures objects for the respective |
|
|
|
|
|
track_objs. track_objs should contain the API limit for a single call |
|
|
|
|
|
(FEATURES_LIMIT) for maximum efficiency. |
|
|
|
|
|
|
|
|
|
|
|
:headers: headers containing the API token |
|
|
|
|
|
:track_objs: Track objects to associate with the new AudioFeatures object |
|
|
|
|
|
|
|
|
|
|
|
:returns: None |
|
|
|
|
|
""" |
|
|
|
|
|
track_ids = str.join(",", [track_obj.track_id for track_obj in track_objs]) |
|
|
|
|
|
params = {'ids': track_ids} |
|
|
|
|
|
features_response = requests.get("https://api.spotify.com/v1/audio-features", |
|
|
|
|
|
headers=headers,params=params).json()['audio_features'] |
|
|
|
|
|
# pprint.pprint(features_response) |
|
|
|
|
|
|
|
|
|
|
|
useless_keys = [ "key", "mode", "type", "liveness", "id", "uri", "track_href", "analysis_url", "time_signature", ] |
|
|
|
|
|
for i in range(len(track_objs)): |
|
|
|
|
|
if features_response[i] is not None: |
|
|
|
|
|
# Data that we don't need |
|
|
|
|
|
cur_features_obj = AudioFeatures() |
|
|
|
|
|
cur_features_obj.track = track_objs[i] |
|
|
|
|
|
for key, val in features_response[i].items(): |
|
|
|
|
|
if key not in useless_keys: |
|
|
|
|
|
setattr(cur_features_obj, key, val) |
|
|
|
|
|
cur_features_obj.save() |
|
|
|
|
|
|
|
|
# get_audio_features {{{ # |
|
|
# get_audio_features {{{ # |
|
|
|
|
|
|
|
|
def save_audio_features(headers, track_id, track): |
|
|
def save_audio_features(headers, track_id, track): |
|
@ -157,8 +191,6 @@ def save_audio_features(headers, track_id, track): |
|
|
""" |
|
|
""" |
|
|
|
|
|
|
|
|
response = requests.get("https://api.spotify.com/v1/audio-features/{}".format(track_id), headers = headers).json() |
|
|
response = requests.get("https://api.spotify.com/v1/audio-features/{}".format(track_id), headers = headers).json() |
|
|
if track_id is '5S1IUPueD0xE0vj4zU3nSf': |
|
|
|
|
|
pprint.pprint(response) |
|
|
|
|
|
if 'error' in response: |
|
|
if 'error' in response: |
|
|
return |
|
|
return |
|
|
|
|
|
|
|
@ -383,10 +415,11 @@ def add_artist_genres(headers, artist_objs): |
|
|
|
|
|
|
|
|
""" |
|
|
""" |
|
|
artist_ids = str.join(",", [artist_obj.artist_id for artist_obj in artist_objs]) |
|
|
artist_ids = str.join(",", [artist_obj.artist_id for artist_obj in artist_objs]) |
|
|
# print(len(artist_objs), artist_ids) |
|
|
|
|
|
|
|
|
print(len(artist_objs), artist_ids) |
|
|
params = {'ids': artist_ids} |
|
|
params = {'ids': artist_ids} |
|
|
artists_response = requests.get('https://api.spotify.com/v1/artists/', |
|
|
artists_response = requests.get('https://api.spotify.com/v1/artists/', |
|
|
headers=headers, params=params).json()['artists'] |
|
|
headers=headers, params=params).json()['artists'] |
|
|
|
|
|
# pprint.pprint(artists_response) |
|
|
for i in range(len(artist_objs)): |
|
|
for i in range(len(artist_objs)): |
|
|
for genre in artists_response[i]['genres']: |
|
|
for genre in artists_response[i]['genres']: |
|
|
genre_obj, created = Genre.objects.get_or_create(name=genre, |
|
|
genre_obj, created = Genre.objects.get_or_create(name=genre, |
|
|