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.

165 lines
5.5 KiB

  1. from django.shortcuts import render, redirect
  2. from django.http import HttpResponse, HttpResponseBadRequest
  3. import math
  4. import random
  5. import requests
  6. import os
  7. import urllib
  8. from datetime import datetime
  9. TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'
  10. def generate_random_string(length):
  11. """Generates a random string of a certain length
  12. Args:
  13. length: the desired length of the randomized string
  14. Returns:
  15. A random string
  16. """
  17. rand_str = ""
  18. possible_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  19. for _ in range(length):
  20. rand_str += possible_chars[random.randint(0, len(possible_chars) - 1)]
  21. return rand_str
  22. def token_expired(token_obtained_at, valid_for):
  23. """Returns True if token expired, False if otherwise
  24. Args:
  25. token_obtained_at: datetime object representing the date and time when the token was obtained
  26. valid_for: the time duration for which the token is valid, in seconds
  27. """
  28. time_elapsed = (datetime.today() - token_obtained_at).total_seconds()
  29. return time_elapsed >= valid_for
  30. # Create your views here.
  31. def index(request):
  32. return render(request, 'spotifyvis/index.html')
  33. def login(request):
  34. # use a randomly generated state string to prevent cross-site request forgery attacks
  35. state_str = generate_random_string(16)
  36. request.session['state_string'] = state_str
  37. payload = {
  38. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  39. 'response_type': 'code',
  40. 'redirect_uri': 'http://localhost:8000/callback',
  41. 'state': state_str,
  42. 'scope': 'user-library-read',
  43. 'show_dialog': False
  44. }
  45. params = urllib.parse.urlencode(payload) # turn the payload dict into a query string
  46. authorize_url = "https://accounts.spotify.com/authorize/?{}".format(params)
  47. return redirect(authorize_url)
  48. def callback(request):
  49. # Attempt to retrieve the authorization code from the query string
  50. try:
  51. code = request.GET['code']
  52. except KeyError:
  53. return HttpResponseBadRequest("<h1>Problem with login</h1>")
  54. payload = {
  55. 'grant_type': 'authorization_code',
  56. 'code': code,
  57. 'redirect_uri': 'http://localhost:8000/callback',
  58. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  59. 'client_secret': os.environ['SPOTIFY_CLIENT_SECRET'],
  60. }
  61. response = requests.post('https://accounts.spotify.com/api/token', data = payload).json()
  62. # despite its name, datetime.today() returns a datetime object, not a date object
  63. # use datetime.strptime() to get a datetime object from a string
  64. request.session['token_obtained_at'] = datetime.strftime(datetime.today(), TIME_FORMAT)
  65. request.session['access_token'] = response['access_token']
  66. request.session['refresh_token'] = response['refresh_token']
  67. request.session['valid_for'] = response['expires_in']
  68. print(response)
  69. return redirect('user_data')
  70. def user_data(request):
  71. token_obtained_at = datetime.strptime(request.session['token_obtained_at'], TIME_FORMAT)
  72. valid_for = int(request.session['valid_for'])
  73. if token_expired(token_obtained_at, valid_for):
  74. req_body = {
  75. 'grant_type': 'refresh_token',
  76. 'refresh_token': request.session['refresh_token'],
  77. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  78. 'client_secret': os.environ['SPOTIFY_CLIENT_SECRET']
  79. }
  80. refresh_token_response = requests.post('https://accounts.spotify.com/api/token', data = req_body).json()
  81. request.session['access_token'] = refresh_token_response['access_token']
  82. request.session['valid_for'] = refresh_token_response['expires_in']
  83. auth_token_str = "Bearer " + request.session['access_token']
  84. headers = {
  85. 'Authorization': auth_token_str
  86. }
  87. user_data_response = requests.get('https://api.spotify.com/v1/me', headers = headers).json()
  88. context = {
  89. 'user_name': user_data_response['display_name'],
  90. 'id': user_data_response['id'],
  91. }
  92. return render(request, 'spotifyvis/user_data.html', context)
  93. def get_features(track_id, token):
  94. """Returns the features of a soundtrack
  95. Args:
  96. track_id: the id of the soundtrack, needed to query the Spotify API
  97. token: an access token for the Spotify API
  98. Returns:
  99. A dictionary with the features as its keys
  100. """
  101. headers = {
  102. 'Authorization': token,
  103. }
  104. response = requests.get("https://api.spotify.com/v1/audio-features/{}".format(track_id), headers = headers).json()
  105. features_dict = {}
  106. # Data that we don't need
  107. useless_keys = [
  108. "key", "mode", "type", "liveness", "id", "uri", "track_href", "analysis_url", "time_signature",
  109. ]
  110. for key, val in response.items():
  111. if key not in useless_keys:
  112. features_dict[key] = val
  113. return features_dict
  114. def update_std_dev(cur_mean, new_data_point, sample_size):
  115. """Calculates the standard deviation for a sample without storing all data points
  116. Args:
  117. cur_mean: the current mean for N = (sample_size - 1)
  118. new_data_point: a new data point
  119. sample_size: sample size including the new data point
  120. Returns:
  121. (updated_mean, std_dev)
  122. """
  123. # This is an implementationof Welford's method
  124. # http://jonisalonen.com/2013/deriving-welfords-method-for-computing-variance/
  125. new_mean = ((sample_size - 1) * cur_mean + new_data_point) / sample_size
  126. std_dev = (new_data_point - new_mean) * (new_data_point - cur_mean)
  127. return new_mean, std_dev