@ -1,9 +1,13 @@
# imports {{{ #
# imports {{{ #
import requests
import requests
import math
import math
import pprint
import pprint
from .models import Artist , User , Track , AudioFeatures
from .models import Artist , User , Track , AudioFeatures
from django.db.models import Count
from django.http import JsonResponse
from django.core import serializers
import json
# }}} imports #
# }}} imports #
@ -28,7 +32,6 @@ def parse_library(headers, tracks, library_stats, user):
payload = { ' limit ' : str ( limit ) }
payload = { ' limit ' : str ( limit ) }
# use two separate variables to track, because the average popularity also requires num_samples
# use two separate variables to track, because the average popularity also requires num_samples
num_samples = 0 # number of actual track samples
num_samples = 0 # number of actual track samples
feature_data_points = 0 # number of feature data analyses (some tracks do not have analyses available)
# iterate until hit requested num of tracks
# iterate until hit requested num of tracks
for _ in range ( 0 , tracks , limit ) :
for _ in range ( 0 , tracks , limit ) :
@ -39,22 +42,27 @@ def parse_library(headers, tracks, library_stats, user):
# TODO: refactor the for loop body into helper function
# TODO: refactor the for loop body into helper function
# iterate through each track
# iterate through each track
for track_dict in saved_tracks_response [ ' items ' ] :
for track_dict in saved_tracks_response [ ' items ' ] :
num_samples + = 1
# 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 ' ] :
increase_artist_count ( headers , artist_dict [ ' name ' ] ,
artist_obj , artist_created = Artist . objects . get_or_create (
artist_dict [ ' id ' ] , library_stats )
track_artists . append ( Artist . objects . get_or_create (
artist_id = artist_dict [ ' id ' ] ,
artist_id = artist_dict [ ' id ' ] ,
name = artist_dict [ ' name ' ] ,
name = artist_dict [ ' name ' ] ,
) [ 0 ] )
)
update_artist_genre ( headers , artist_obj )
# get_or_create() returns a tuple (obj, created)
track_artists . append ( artist_obj )
track_obj , track_created = save_track_obj ( track_dict [ ' track ' ] , track_artists , user )
track_obj = save_track_obj ( track_dict [ ' track ' ] , track_artists , user )
# if a new track is not created, the associated audio feature does not need to be created again
get_track_info ( track_dict [ ' track ' ] , library_stats , num_samples )
if track_created :
audio_features_dict = get_audio_features ( headers ,
save_audio_features ( headers , track_dict [ ' track ' ] [ ' id ' ] , track_obj )
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 :
if len ( audio_features_dict ) != 0 :
# Track the number of audio analyses for calculating
# Track the number of audio analyses for calculating
# audio feature averages and standard deviations on the fly
# audio feature averages and standard deviations on the fly
@ -62,62 +70,56 @@ def parse_library(headers, tracks, library_stats, user):
for feature , feature_data in audio_features_dict . items ( ) :
for feature , feature_data in audio_features_dict . items ( ) :
update_audio_feature_stats ( feature , feature_data ,
update_audio_feature_stats ( feature , feature_data ,
feature_data_points , library_stats )
feature_data_points , library_stats )
"""
# calculates num_songs with offset + songs retrieved
# calculates num_songs with offset + songs retrieved
library_stats [ ' num_songs ' ] = offset + len ( saved_tracks_response [ ' items ' ] )
offset + = limit
offset + = limit
calculate_genres_from_artists ( headers , library_stats )
# calculate_genres_from_artists(headers, library_stats )
# pprint.pprint(library_stats)
# pprint.pprint(library_stats)
# }}} parse_library #
# }}} parse_library #
# save_track_obj {{{ #
# save_track_obj {{{ #
def save_track_obj ( track_dict , artists , user ) :
def save_track_obj ( track_dict , artists , user ) :
""" Make an entry in the database for this track if it doesn ' t exist already.
""" Make an entry in the database for this track if it doesn ' t exist already.
: track_dict : dictionary from the API call containing track information.
: track_dict : dictionary from the API call containing track information.
: 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 .
: 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 .
: returns : ( The created / retrieved Track object , created )
"""
"""
track_obj_query = Track . objects . filter ( track_id__exact = track_dict [ ' id ' ] )
print ( track_dict [ ' name ' ] )
if len ( track_obj_query ) == 0 :
new_track , created = Track . objects . get_or_create (
new_track = Track . objects . create (
track_id = track_dict [ ' id ' ] ,
track_id = track_dict [ ' id ' ] ,
year = track_dict [ ' album ' ] [ ' release_date ' ] . split ( ' - ' ) [ 0 ] ,
year = track_dict [ ' album ' ] [ ' release_date ' ] . split ( ' - ' ) [ 0 ] ,
popularity = int ( track_dict [ ' popularity ' ] ) ,
popularity = int ( track_dict [ ' popularity ' ] ) ,
runtime = int ( float ( track_dict [ ' duration_ms ' ] ) / 1000 ) ,
runtime = int ( float ( track_dict [ ' duration_ms ' ] ) / 1000 ) ,
name = track_dict [ ' name ' ] ,
name = track_dict [ ' name ' ] ,
)
)
# print("pop/run: ", new_track.popularity, new_track.runtime)
# have to add artists and user after saving object since track needs to
# have to add artists and user after saving object since track needs to
# have ID before filling in m2m field
# have ID before filling in m2m field
if created :
for artist in artists :
for artist in artists :
new_track . artists . add ( artist )
new_track . artists . add ( artist )
new_track . users . add ( user )
new_track . users . add ( user )
new_track . save ( )
new_track . save ( )
return new_track
return new_track , created
elif len ( track_obj_query ) == 1 :
return track_obj_query [ 0 ]
# }}} save_track_obj #
# }}} save_track_obj #
# get_audio_features {{{ #
# get_audio_features {{{ #
def get _audio_features( headers , track_id , track ) :
def save _audio_features( headers , track_id , track ) :
""" Returns the audio features of a soundtrack
""" Creates and saves a new AudioFeatures object
Args :
Args :
headers : headers containing the API token
headers : headers containing the API token
track_id : the id of the soundtrack , needed to query the Spotify API
track_id : the id of the soundtrack , needed to query the Spotify API
track : Track object to associate with the AudioFeatures object
track : Track object to associate with the new AudioFeatures object
Returns :
A dictionary with the features as its keys , if audio feature data is missing for the track ,
an empty dictionary is returned .
"""
"""
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 ( )
@ -137,7 +139,6 @@ def get_audio_features(headers, track_id, track):
setattr ( audio_features_entry , key , val )
setattr ( audio_features_entry , key , val )
audio_features_entry . save ( )
audio_features_entry . save ( )
return features_dict
# }}} get_audio_features #
# }}} get_audio_features #
@ -296,30 +297,28 @@ def get_track_info(track_dict, library_stats, sample_size):
# }}} get_track_info #
# }}} get_track_info #
# calculate_genres_from_artists {{{ #
# update_genres_from_artists {{{ #
def calculate_genres_from_artists ( headers , library_stats ) :
def update_artist_genre ( headers , artist_obj ) :
""" Tallies up genre counts based on artists in library_stats.
""" Updates the top genre for an artist by querying the Spotify API
: headers : For making the API call .
: headers : For making the API call .
: library_stats : Dictionary containing the data mined from user ' s Spotify library
: artist_obj : the Artist object whose genre field will be updated
: returns : None
: returns : None
"""
"""
for artist_entry in library_stats [ ' artists ' ] . values ( ) :
artist_response = requests . get ( ' https://api.spotify.com/v1/artists/ ' + artist_obj . artist_id , headers = headers ) . json ( )
artist_response = requests . get ( ' https://api.spotify.com/v1/artists/ ' + artist_entry [ ' id ' ] , headers = headers ) . json ( )
# increase each genre count by artist count
for genre in artist_response [ ' genres ' ] :
increase_nested_key ( ' genres ' , genre , library_stats , artist_entry [ ' count ' ] )
# update genre for artist in database with top genre
# update genre for artist in database with top genre
Artist . objects . filter ( artist_id = artist_entry [ ' id ' ] ) . update ( genre = artist_response [ ' genres ' ] [ 0 ] )
artist_obj . genre = artist_response [ ' genres ' ] [ 0 ]
artist_obj . save ( )
# }}} calculate_genres_from_artists #
# }}} calculate_genres_from_artists #
# process_library_stats {{{ #
# process_library_stats {{{ #
def process_library_stats ( library_stats ) :
def process_library_stats ( library_stats ) :
""" Processes library_stats into format more suitable for D3 consumption
""" Processes library_stats into format more suitable for D3 consumption
@ -372,3 +371,15 @@ def process_library_stats(library_stats):
return processed_library_stats
return processed_library_stats
# }}} process_library_stats #
# }}} process_library_stats #
def get_genre_data ( user ) :
""" Return genre data needed to create the graph user.
: user : User object for which to return the data for .
: returns : List of dicts containing counts for each genre .
"""
pass
# user_tracks = Track.objects.filter(users__exact=user)
# for track in user_tracks:
# print(track.name)
xxxxxxxxxx