3
This module makes Sonata submit the songs played to a Last.fm account.
7
self.scrobbler = scrobbler.Scrobbler(self.config)
8
self.scrobbler.import_module()
11
self.scrobbler.iterate()
13
self.scrobbler.handle_change_status(False, self.prevsonginfo)
17
import threading # init, np, post start threads init_thread, do_np, do_post
19
audioscrobbler = None # imported when first needed
21
import mpdhelper as mpdh
23
class Scrobbler(object):
24
def __init__(self, config):
28
self.scrob_post = None
29
self.scrob_start_time = ""
30
self.scrob_playing_duration = 0
31
self.scrob_last_prepared = ""
32
self.scrob_time_now = None
34
self.elapsed_now = None
36
def import_module(self, _show_error=False):
37
"""Import the audioscrobbler module"""
38
# We need to try to import audioscrobbler either when the app starts (if
39
# as_enabled=True) or if the user enables it in prefs.
41
if audioscrobbler is None:
45
"""Return True if the audioscrobbler module has been imported"""
46
return audioscrobbler is not None
49
"""Initialize the Audioscrobbler support if enabled and configured"""
50
if audioscrobbler is not None and self.config.as_enabled and len(self.config.as_username) > 0 and len(self.config.as_password_md5) > 0:
51
thread = threading.Thread(target=self.init_thread)
52
thread.setDaemon(True)
55
def init_thread(self):
56
if self.scrob is None:
57
self.scrob = audioscrobbler.AudioScrobbler()
58
if self.scrob_post is None:
59
self.scrob_post = self.scrob.post(self.config.as_username, self.config.as_password_md5, verbose=True)
61
if self.scrob_post.authenticated:
62
return # We are authenticated
64
self.scrob_post = self.scrob.post(self.config.as_username, self.config.as_password_md5, verbose=True)
66
self.scrob_post.auth()
68
print "Error authenticating audioscrobbler", e
69
self.scrob_post = None
74
"""Update the running time"""
75
self.scrob_time_now = time.time()
77
def handle_change_status(self, playing, prevsonginfo, songinfo=None, switched_from_stop_to_play=None, mpd_time_now=None):
78
"""Handle changes to play status, submitting info as appropriate"""
79
if prevsonginfo and 'time' in prevsonginfo:
80
prevsong_time = mpdh.get(prevsonginfo, 'time')
85
elapsed_prev = self.elapsed_now
86
self.elapsed_now, length = [float(c) for c in mpd_time_now.split(':')]
87
current_file = mpdh.get(songinfo, 'file')
88
if switched_from_stop_to_play:
89
# Switched from stop to play, prepare current track:
90
self.prepare(songinfo)
91
elif (prevsong_time and
92
(self.scrob_last_prepared != current_file or
93
(self.scrob_last_prepared == current_file and elapsed_prev and
94
abs(elapsed_prev-length)<=2 and self.elapsed_now<=2 and length>0))):
95
# New song is playing, post previous track if time criteria is met.
96
# In order to account for the situation where the same song is played twice in
97
# a row, we will check if the previous time was the end of the song and we're
98
# now at the beginning of the same song.. this technically isn't right in
99
# the case where a user seeks back to the beginning, but that's an edge case.
100
if self.scrob_playing_duration > 4 * 60 or self.scrob_playing_duration > int(prevsong_time)/2:
101
if self.scrob_start_time != "":
102
self.post(prevsonginfo)
103
# Prepare current track:
104
self.prepare(songinfo)
105
elif self.scrob_time_now:
106
# Keep track of the total amount of time that the current song
108
self.scrob_playing_duration += time.time() - self.scrob_time_now
112
if self.scrob_playing_duration > 4 * 60 or self.scrob_playing_duration > int(prevsong_time)/2:
113
# User stopped the client, post previous track if time
115
if self.scrob_start_time != "":
116
self.post(prevsonginfo)
118
def auth_changed(self):
119
"""Try to re-authenticate"""
121
if self.scrob_post.authenticated:
122
self.scrob_post = None
124
def prepare(self, songinfo):
125
if audioscrobbler is not None:
126
self.scrob_start_time = ""
127
self.scrob_last_prepared = ""
128
self.scrob_playing_duration = 0
130
if self.config.as_enabled and songinfo:
131
# No need to check if the song is 30 seconds or longer,
132
# audioscrobbler.py takes care of that.
133
if 'time' in songinfo:
136
self.scrob_start_time = str(int(time.time()))
137
self.scrob_last_prepared = mpdh.get(songinfo, 'file')
139
def np(self, songinfo):
140
thread = threading.Thread(target=self.do_np, args=(songinfo,))
141
thread.setDaemon(True)
144
def do_np(self, songinfo):
146
if self.config.as_enabled and self.scrob_post and songinfo:
147
if 'artist' in songinfo and \
148
'title' in songinfo and \
150
if not 'album' in songinfo:
153
album = mpdh.get(songinfo, 'album')
154
if not 'track' in songinfo:
157
tracknumber = mpdh.get(songinfo, 'track')
159
self.scrob_post.nowplaying(mpdh.get(songinfo, 'artist'),
160
mpdh.get(songinfo, 'title'),
161
mpdh.get(songinfo, 'time'),
164
self.scrob_start_time)
166
print sys.exc_info()[1]
169
def post(self, prevsonginfo):
171
if self.config.as_enabled and self.scrob_post and prevsonginfo:
172
if 'artist' in prevsonginfo and \
173
'title' in prevsonginfo and \
174
'time' in prevsonginfo:
175
if not 'album' in prevsonginfo:
178
album = mpdh.get(prevsonginfo, 'album')
179
if not 'track' in prevsonginfo:
182
tracknumber = mpdh.get(prevsonginfo, 'track')
184
self.scrob_post.addtrack(mpdh.get(prevsonginfo, 'artist'),
185
mpdh.get(prevsonginfo, 'title'),
186
mpdh.get(prevsonginfo, 'time'),
187
self.scrob_start_time,
191
print sys.exc_info()[1]
193
thread = threading.Thread(target=self.do_post)
194
thread.setDaemon(True)
196
self.scrob_start_time = ""
199
for _i in range(0, 3):
200
if not self.scrob_post:
202
if len(self.scrob_post.cache) == 0:
205
self.scrob_post.post()
206
except audioscrobbler.AudioScrobblerConnectionError, e:
210
def save_cache(self):
211
"""Save the cache in a file"""
212
filename = os.path.expanduser('~/.config/sonata/ascache')
214
self.scrob_post.savecache(filename)
216
def retrieve_cache(self):
217
filename = os.path.expanduser('~/.config/sonata/ascache')
219
self.scrob_post.retrievecache(filename)