Graphs and tables for your Spotify account.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
8.2 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. # imports {{{ #
  2. import requests
  3. import math
  4. import pprint
  5. import os
  6. import json
  7. from django.db.models import Count, Q, F
  8. from django.http import JsonResponse
  9. from django.core import serializers
  10. from django.utils import timezone
  11. from .models import *
  12. from login.models import User
  13. from django.db.models import FloatField
  14. from django.db.models.functions import Cast
  15. # }}} imports #
  16. console_logging = True
  17. # console_logging = False
  18. artists_genre_processed = 0
  19. features_processed = 0
  20. # update_track_genres {{{ #
  21. def update_track_genres(user_obj):
  22. """Updates user_obj's tracks with the most common genre associated with the
  23. songs' artist(s).
  24. :user_obj: User object who's tracks are being updated.
  25. :returns: None
  26. """
  27. tracks_processed = 0
  28. user_tracks = Track.objects.filter(users__exact=user_obj)
  29. for track in user_tracks:
  30. # just using this variable to save another call to db
  31. track_artists = list(track.artists.all())
  32. # TODO: Use the most popular genre of the first artist as the Track genre
  33. first_artist_genres = track_artists[0].genres.all().order_by('-num_songs')
  34. undefined_genre_obj = Genre.objects.get(name="undefined")
  35. most_common_genre = first_artist_genres.first() if first_artist_genres.first() is \
  36. not undefined_genre_obj else first_artist_genres[1]
  37. track.genre = most_common_genre if most_common_genre is not None \
  38. else undefined_genre_obj
  39. track.save()
  40. tracks_processed += 1
  41. if console_logging:
  42. print("Added '{}' as genre for song #{} - '{}'".format(
  43. track.genre,
  44. tracks_processed,
  45. track.name,
  46. ))
  47. # }}} update_track_genres #
  48. # save_track_obj {{{ #
  49. def save_track_obj(track_dict, artists, user_obj):
  50. """Make an entry in the database for this track if it doesn't exist already.
  51. :track_dict: dictionary from the API call containing track information.
  52. :artists: artists of the song, passed in as a list of Artist objects.
  53. :user_obj: User object for which this Track is to be associated with.
  54. :returns: (The created/retrieved Track object, created)
  55. """
  56. track_query = Track.objects.filter(id__exact=track_dict['id'])
  57. if len(track_query) != 0:
  58. return track_query[0], False
  59. else:
  60. new_track = Track.objects.create(
  61. id=track_dict['id'],
  62. year=track_dict['album']['release_date'].split('-')[0],
  63. popularity=int(track_dict['popularity']),
  64. runtime=int(float(track_dict['duration_ms']) / 1000),
  65. name=track_dict['name'],
  66. )
  67. # have to add artists and user_obj after saving object since track needs to
  68. # have ID before filling in m2m field
  69. for artist in artists:
  70. new_track.artists.add(artist)
  71. new_track.users.add(user_obj)
  72. new_track.save()
  73. return new_track, True
  74. # }}} save_track_obj #
  75. # get_audio_features {{{ #
  76. def get_audio_features(headers, track_objs):
  77. """Creates and saves a new AudioFeatures objects for the respective
  78. track_objs. track_objs should contain the API limit for a single call
  79. (FEATURES_LIMIT) for maximum efficiency.
  80. :headers: headers containing the API token
  81. :track_objs: Track objects to associate with the new AudioFeatures object
  82. :returns: None
  83. """
  84. track_ids = str.join(",", [track_obj.id for track_obj in track_objs])
  85. params = {'ids': track_ids}
  86. features_response = requests.get("https://api.spotify.com/v1/audio-features",
  87. headers=headers,
  88. params={'ids': track_ids}
  89. ).json()['audio_features']
  90. # pprint.pprint(features_response)
  91. useless_keys = [ "key", "mode", "type", "liveness", "id", "uri",
  92. "track_href", "analysis_url", "time_signature", ]
  93. for i in range(len(track_objs)):
  94. if features_response[i] is not None:
  95. # Data that we don't need
  96. cur_features_obj = AudioFeatures()
  97. cur_features_obj.track = track_objs[i]
  98. for key, val in features_response[i].items():
  99. if key not in useless_keys:
  100. setattr(cur_features_obj, key, val)
  101. cur_features_obj.save()
  102. if console_logging:
  103. global features_processed
  104. features_processed += 1
  105. print("Added features for song #{} - {}".format(
  106. features_processed, track_objs[i].name))
  107. # }}} get_audio_features #
  108. # process_artist_genre {{{ #
  109. def process_artist_genre(genre_name, artist_obj):
  110. """Increase count for corresponding Genre object to genre_name and associate that
  111. Genre object with artist_obj.
  112. :genre_name: Name of genre.
  113. :artist_obj: Artist object to associate Genre object with
  114. :returns: None
  115. """
  116. genre_obj, created = Genre.objects.get_or_create(name=genre_name, defaults={'num_songs': 1})
  117. if not created:
  118. genre_obj.num_songs = F('num_songs') + 1
  119. genre_obj.save()
  120. artist_obj.genres.add(genre_obj)
  121. artist_obj.save()
  122. # }}} process_artist_genre #
  123. # add_artist_genres {{{ #
  124. def add_artist_genres(headers, artist_objs):
  125. """Adds genres to artist_objs and increases the count the respective Genre
  126. object. artist_objs should contain the API limit for a single call
  127. (ARTIST_LIMIT) for maximum efficiency.
  128. :headers: For making the API call.
  129. :artist_objs: List of Artist objects for which to add/tally up genres for.
  130. :returns: None
  131. """
  132. artist_ids = str.join(",", [artist_obj.id for artist_obj in artist_objs])
  133. artists_response = requests.get('https://api.spotify.com/v1/artists/',
  134. headers=headers,
  135. params={'ids': artist_ids},
  136. ).json()['artists']
  137. for i in range(len(artist_objs)):
  138. if len(artists_response[i]['genres']) == 0:
  139. process_artist_genre("undefined", artist_objs[i])
  140. else:
  141. for genre in artists_response[i]['genres']:
  142. process_artist_genre(genre, artist_objs[i])
  143. if console_logging:
  144. global artists_genre_processed
  145. artists_genre_processed += 1
  146. print("Added genres for artist #{} - {}".format(
  147. artists_genre_processed, artist_objs[i].name))
  148. # }}} add_artist_genres #
  149. # get_artists_in_genre {{{ #
  150. def get_artists_in_genre(user, genre):
  151. """Return count of artists in genre.
  152. :user: User object to return data for.
  153. :genre: genre to count artists for. (string)
  154. :returns: dict of artists in the genre along with the number of songs they
  155. have.
  156. """
  157. genre_obj = Genre.objects.get(name=genre)
  158. tracks_in_genre = Track.objects.filter(genre=genre_obj, users=user)
  159. track_count = tracks_in_genre.count()
  160. user_artists = Artist.objects.filter(track__users=user) # use this variable to save on db queries
  161. total_artist_counts = tracks_in_genre.aggregate(counts=Count('artists'))['counts']
  162. processed_artist_counts = {}
  163. for artist in user_artists:
  164. processed_artist_counts[artist.name] = round(artist.track_set
  165. .filter(genre=genre_obj, users=user)
  166. .count() * track_count / total_artist_counts, 2)
  167. return processed_artist_counts
  168. # }}} get_artists_in_genre #
  169. def get_user_header(user_obj):
  170. """Returns the authorization string needed to make an API call.
  171. :user_obj: User to return the auth string for.
  172. :returns: the authorization string used for the header in a Spotify API
  173. call.
  174. """
  175. seconds_elapsed = (timezone.now() -
  176. user_obj.access_obtained_at).total_seconds()
  177. if seconds_elapsed >= user_obj.access_expires_in:
  178. req_body = {
  179. 'grant_type': 'refresh_token',
  180. 'refresh_token': user_obj.refresh_token,
  181. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  182. 'client_secret': os.environ['SPOTIFY_CLIENT_SECRET']
  183. }
  184. token_response = requests.post('https://accounts.spotify.com/api/token',
  185. data=req_body).json()
  186. user_obj.access_token = token_response['access_token']
  187. user_obj.access_expires_in = token_response['expires_in']
  188. user_obj.save()
  189. return {'Authorization': "Bearer " + user_obj.access_token}