-
Notifications
You must be signed in to change notification settings - Fork 0
/
spotify-analysis.js
167 lines (151 loc) · 6.35 KB
/
spotify-analysis.js
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
const SpotifyWebApi = require('spotify-web-api-node')
const EventEmitter = require('events')
const math = require('mathjs')
const util = require('util')
const { column, sort, re } = require('mathjs')
async function _getUserInfo(userAccessToken) {
var spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(userAccessToken)
//console.log('getting user data')
let data = await spotifyApi.getMe()
//console.log(`${data.body.display_name}'s id is ${data.body.id}`)
return data.body
}
async function getUserData(userAccessToken) {
let userInfo = await getUserInfo(userAccessToken)
let data = await getUserSongList(userAccessToken)
return { userInfo, data }
}
/**
*
* @param {string} userAccessToken the access token of the user from spotify
* @returns {avatar_url: string, username: string} the user's avatar and username
*/
async function getUserInfo(userAccessToken) {
let data = await _getUserInfo(userAccessToken)
return {
avatar_url: (data.images.length < 1) ? null : data.images[0].url,
username: data.display_name,
}
}
async function _getUserTracks(userAccessToken) {
var spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(userAccessToken)
let playlistsData = await spotifyApi.getUserPlaylists(
(await _getUserInfo(userAccessToken)).id
)
// console.log(playlistsData.body)
var trackEmitter = new EventEmitter()
var tracks = []
trackEmitter.on('track', (vector) => {
tracks.push(vector)
})
await Promise.all(
playlistsData.body.items.map(async (playlist) => {
let playlistInfo = await spotifyApi.getPlaylist(playlist.id)
playlistInfo.body.tracks.items.forEach(track => trackEmitter.emit('track', track))
})
)
return tracks
}
function _normalize(vector) {
// normalizes vector with mean and standard deviation
var mean = math.mean(vector)
var std = math.std(vector)
return vector.map(x => (x - mean) / std)
}
/**
*
* @param {string} userAccessToken the access token of the user from spotify
* @returns {Array[Object{name: string, id: string, feature: Array[float]}]} an array of all of the user's tracks, each track is an object with the track's name, id, and feature vector
*/
async function getUserSongList(userAccessToken) {
// get user tracks[id, name, featrues {danceability, energy, key, loudness, mode, speechiness, acousticness, instrumentalness, liveness, valence, tempo, duration_ms, time_signature}]
let spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(userAccessToken)
let tracks = await _getUserTracks(userAccessToken)
var addedTracks = []
var iter = 0
let trackLength = tracks.length
do {
addedTracks.forEach(t => tracks.push(t))
addedTracks = []
let data = await spotifyApi.getMySavedTracks({ limit: 50, offset: iter*50})
data.body.items.forEach(t => addedTracks.push(t))
iter++
}while(addedTracks.length > 0)
console.log(`${tracks.length-trackLength} liked tracks added.`)
var idNameMap = {}
tracks.forEach(track => idNameMap[track.track.id] = track.track.name)
var userSongList = []
var vis = new Set()
for (let i = 0; i < tracks.length; i += 48) {
let trackIds = tracks.slice(i, i + 48).map(track => track.track.id)
let features = await spotifyApi.getAudioFeaturesForTracks([trackIds])
features.body.audio_features.forEach(
feature => {
if(vis.has(feature.id)) return
vis.add(feature.id)
userSongList.push({
name: idNameMap[feature.id],
id: feature.id,
feature: _normalize([
feature.danceability,
feature.energy,
feature.key,
feature.loudness,
feature.mode,
feature.speechiness,
feature.acousticness,
feature.instrumentalness,
feature.liveness,
feature.valence,
feature.tempo/100,
// (feature.duration_ms / 1000.0) / 60.0, // convert to minutes
// feature.time_signature
])
})
}
)
}
return userSongList
}
/**
*
* @param {string} userAccessToken the access token of the user from spotify
* @returns {Array[Array[string]]} an 2d array of the user's playlist data, each playlist is an array of song features
*/
async function getUserMatrix(userAccessToken) {
let userSongList = await getUserSongList(userAccessToken)
let userSongListMatrix = []
userSongList.forEach(song => userSongListMatrix.push(song.feature))
return userSongListMatrix
}
/**
*
* @param {string} userAccessToken the access token of the user from spotify
* @returns {nodes:[id:string],links:[source:string, target:string, value: float]} nodes and links for the user's playlist graph
*/
async function getUserNodeLinkData(accessToken) {
let userSongList = await getUserSongList(accessToken)
nodes = []
links = []
for (let i = 0; i < userSongList.length; i++) {
nodes.push({ id: userSongList[i].name, group: 1 })
let sorted = [...userSongList]
sorted.sort((a, b) => math.distance(userSongList[i].feature, a.feature) - math.distance(userSongList[i].feature, b.feature))
//console.log(sorted.map(v=>math.distance(v.feature, userSongList[i].feature)),'\n',userSongList[i].name)
links.push({ source: userSongList[i].name, target: sorted[1].name, value: 1 / math.distance(userSongList[i].feature, sorted[1].feature) })
links.push({ source: userSongList[i].name, target: sorted[2].name, value: 1 / math.distance(userSongList[i].feature, sorted[2].feature) })
}
return { nodes, links }
}
// ; (async () => {
// let accessToken = 'BQA1UH_hutOZT08eKnAsNOBDpCWCd4lZckRHJ7MWeBBS8tknkUoLeKwh8qWw8xm9Im7WLt5pBTxgkS4BTrMaA9-2MmEJuU4JY7__XKD8neVS-4OjZ0PBNI-P_bvHnfx6msqsl45scIMJWS3_UR0Dkf10Cp_bsxHzKpJb-w3SFSCfcdTvIfBB'
// let songs = await getUserSongList(accessToken)
// })()
exports.getUserMatrix = getUserMatrix
exports.getUserSongList = getUserSongList
exports.getUserNodeLinkData = getUserNodeLinkData
exports.getUserInfo = getUserInfo
exports.getUserData = getUserData