-
Notifications
You must be signed in to change notification settings - Fork 0
/
aggregator.py
122 lines (93 loc) · 3.77 KB
/
aggregator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from spotipy.client import Spotify
from spotipy.oauth2 import SpotifyClientCredentials
from typing import List
import json
import os
import queue
import sys
class Artist:
def __init__(self, id: str, name: str):
self.id = id
self.name = name
def __str__(self):
return f'{self.name} ({self.id})'
class Track:
def __init__(self, id: str, name: str, artist: Artist):
self.id = id
self.name = name
self.artist = artist
def __str__(self):
return f'{self.name} - {self.artist.name} ({self.id})'
def deserialize_artist(json_data: dict) -> Artist:
'''Creates an Artist object from json.'''
return Artist(json_data['id'], json_data['name'])
def deserialize_track(json_data: dict, artist: Artist) -> Track:
'''Creates an Track object from json.'''
return Track(json_data['id'], json_data['name'], artist)
def get_related_artists(spotify_client: Spotify, seed_artist: Artist,
max_depth: int, max_neighbors: int) -> List[Artist]:
'''Starting at a "seed" artist, performs a breadth first search to find related artists.'''
artist_queue = queue.Queue()
artist_queue.put((seed_artist, 0))
visited = set([seed_artist.id])
artists = []
while not artist_queue.empty():
artist, depth = artist_queue.get()
artists.append(artist)
if depth < max_depth:
related_artists_data = spotify_client.artist_related_artists(artist.id)['artists']
related = [
deserialize_artist(artist_data)
for artist_data
in related_artists_data]
neighbors_visited = 0
for related_artist in related:
if neighbors_visited == max_neighbors:
break
if related_artist.id not in visited:
artist_queue.put((related_artist, depth + 1))
visited.add(related_artist.id)
neighbors_visited += 1
return artists
def get_artist_tracks(spotify_client: Spotify, artist: Artist, count: int) -> List[Track]:
'''Gets a list of an artist's top tracks.'''
artist_tracks_data = spotify_client.artist_top_tracks(artist.id)['tracks']
artist_tracks = [
deserialize_track(track_data, artist)
for track_data
in artist_tracks_data[:count]]
return artist_tracks
if __name__ == '__main__':
# The maximum distance from the seed artist that the algorithm will search.
MAX_DEPTH = 3
# The maximum number of related artists that will be included in the search
# for each artist.
MAX_NEIGHBORS = 3
# The number of tracks that are pulled from each artist's top tracks.
TRACKS_PER_ARTIST = 5
if len(sys.argv) < 2:
print('Usage: python3 aggregator.py <artist id>')
sys.exit(1)
seed_artist_id = sys.argv[1]
if not os.path.exists('config.json'):
print('config.json file not present!')
sys.exit(1)
config_file = open('config.json', 'r')
config = json.load(config_file)
for key in ['SPOTIFY_CLIENT_ID', 'SPOTIFY_CLIENT_SECRET']:
if key not in config:
print(f'{key} missing from config.json!')
sys.exit(1)
credentials = SpotifyClientCredentials(
config['SPOTIFY_CLIENT_ID'],
config['SPOTIFY_CLIENT_SECRET'])
client = Spotify(client_credentials_manager=credentials)
seed_artist_data = client.artist(seed_artist_id)
seed_artist = deserialize_artist(seed_artist_data)
artists = get_related_artists(client, seed_artist, MAX_DEPTH, MAX_NEIGHBORS)
tracks = []
for artist in artists:
artist_tracks = get_artist_tracks(client, artist, TRACKS_PER_ARTIST)
tracks.extend(artist_tracks)
for track in tracks:
print(track)