-
Notifications
You must be signed in to change notification settings - Fork 0
/
lastfm_utils.py
executable file
·283 lines (218 loc) · 8.21 KB
/
lastfm_utils.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
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/python
# vim: set fileencoding=utf-8 :
"""Queries the databases for songs without albums recorded, and queries
Last.fm for the album. This is done as a separate process, as the
Last.fm API limits how fast you can grab data from it."""
# TODO:
#
# * Handle retries when pylast.NetworkError occurs
from __future__ import print_function
import time
import datetime
import sys
import os
import pylast
import ConfigParser
import whats_on_translate
VERBOSE = False
def main():
"""Looks for songs without an album attribute and fills them in
via Last.fm"""
print(datetime.datetime.now())
lastfm = get_lastfm_conn()
if lastfm is None:
sys.exit(-1)
tracks = [
{'Artist': 'Foo Fighters', 'Title': 'Monkey Wrench'}, # Good
{'Artist': 'Trooper',
'Title': 'The Boys In The Bright White Sports Cars'}, # Override
{'Artist': 'Prince', 'Title': '- 1999 -'}, # Override
{'Artist': 'Glenn Morrison feat. Islove',
'Title': 'Goodbye'}, # Valid track, no album
{'Artist': 'qe7wuyeS', 'Title': 'tHaw6Seq'}, # Invalid
]
for track in tracks:
track_info = get_lastfm_track_info( track, lastfm )
print(track_info)
print(datetime.datetime.now())
def get_lastfm_conn(cfg_file = '.lastfm'):
"""Acquires a lastfm network connection
Returns a connection if successful, None if it failed
"""
lastfm = None
config = ConfigParser.ConfigParser()
if not os.path.isfile(cfg_file) or (config.read(cfg_file)[0] != cfg_file):
print("Error reading config file")
else:
username = config.get('Credentials', 'username')
password_hash = config.get('Credentials', 'password_hash')
api_key = config.get('Credentials', 'api_key')
api_secret = config.get('Credentials', 'api_secret')
try:
lastfm = pylast.LastFMNetwork(api_key = api_key,
api_secret = api_secret,
username = username,
password_hash = password_hash)
except pylast.WSError as error:
print("Failed to log in:", error)
return lastfm
def get_lastfm_track_info( track_details, lastfm ):
"""Gets all necessary track details from lastfm
Does overrides for mislabelled songs, and attempts translations
for common ways of changing artist / track details
Arguments:
track_details -- dict containing keys for Artist and Title
lastfm -- authenticated connection to the Last.FM API
Returns None if failed
Dict containing: Artist, Title, Album, MBID, LFID
"""
# Four possible outcomes of track details:
#
# * artist or song is overridden, then looked up
# => store with valid artist, title, album, MBID, LFID
# * track is looked up and is not in db
# => store with artist, title, album = NULL, MBID = NULL, LFID = NULL
# * track is looked up, is in db, but does not have an album associated
# * try again with common translations
# * found album
# => store with valid artist, title, album, MBID, LFID
# * did not find album
# => store with artist, title, album = NULL, MBID = NULL, LFID
#
# * LFID = NULL means that a translation or override yaml entry
# needs to be created
# * MBID = NULL means that an album is not set up for that track yet
# Apply any artist / song overrides
artist = whats_on_translate.override_artist(track_details['Artist'])
song = whats_on_translate.override_song((track_details['Artist'],
track_details['Title']))
if artist is None:
artist = track_details['Artist']
if song is None:
song = track_details['Title']
if VERBOSE:
print("Looking for ", artist, song)
track = get_lastfm_track( {'Artist': artist, 'Title': song}, lastfm )
if track:
album = get_lastfm_album( track )
mbid = get_lastfm_mbid( track )
track_id = get_lastfm_id( track )
ret = {'Artist': artist, 'Title': song}
if album:
ret['Album'] = album
if mbid:
ret['MBID'] = mbid
if track_id:
ret['LFID'] = track_id
return ret
else:
return None
def get_lastfm_track( track_details, lastfm ):
"""Gets a lastfm track
Arguments:
track_details -- dict containing keys for Artist and Title
lastfm -- authenticated connection to the Last.FM API
Returns a lastfm track object if successful, None if failed
"""
track = None
album = None
# Find the track
# If it's valid
# check if its album is valid
# If the track is invalid or its album is invalid
# Translate it
# Search again
# If the album is not none
# Return this track
# Else
# Return the original track can be None or valid
# Last.fm limits API calls to five per second from a specific IP
time.sleep(0.2)
try:
track = lastfm.get_track(track_details['Artist'],
track_details['Title'])
if VERBOSE:
print("Found track:", track)
get_lastfm_id( track )
except pylast.WSError as error:
print("Track not found:", error, track_details)
if track is not None:
album = get_lastfm_album( track )
if VERBOSE:
print("Valid track, checking for album:", album)
orig_track = track
if track is None or album is None:
if VERBOSE:
print("Invalid track or album, translating")
artist = whats_on_translate.translate_artist(track_details['Artist'])
song = whats_on_translate.translate_song((track_details['Artist'],
track_details['Title']))
if artist != track_details['Artist'] or song != track_details['Title']:
time.sleep(0.2)
try:
track = lastfm.get_track(artist, song)
if VERBOSE:
print("Found translated track:", track, artist, song)
except pylast.WSError as error:
print("Track not found:", error, track_details)
if track is not None:
album = get_lastfm_album( track )
if album is None:
if VERBOSE:
print("No album for", track,
"using original", orig_track)
if album is None:
track = orig_track
else:
#if VERBOSE:
if True:
print("No translations for", track_details)
return track
def get_lastfm_id( track ):
"""Gets lastfm ID for a track"""
track_id = None
try:
track_id = track.get_id()
except pylast.MalformedResponseError as error:
print("Malformed response", error)
except pylast.WSError as error:
if VERBOSE:
print("LastFM ID not found:", error, track)
return track_id
def get_lastfm_mbid( track ):
"""Gets Musicbrainz ID for a track"""
mbid = None
try:
mbid = track.get_mbid()
except pylast.MalformedResponseError as error:
print("Malformed response", error)
except pylast.WSError as error:
if VERBOSE:
print("MBID not found:", error, track)
return mbid
def get_lastfm_artist( track ):
"""Gets artist for a track"""
artist = None
try:
artist = track.get_artist()
except pylast.MalformedResponseError as error:
print("Malformed response", error)
except pylast.WSError as error:
if VERBOSE:
print("Artist not found:", error, track)
return artist
def get_lastfm_album( track ):
"""Gets album for a track"""
title = None
try:
album = track.get_album()
if album is not None:
title = album.get_title()
except pylast.MalformedResponseError as error:
print("Malformed response", error)
except pylast.WSError as error:
if VERBOSE:
print("Album not found:", error, track)
return title
if __name__ == "__main__":
main()