Merge lp:~hermann-kaser/pogo/lastfm-plugin into lp:~jendrikseipp/pogo/trunk

Proposed by Hermann Kaser
Status: Needs review
Proposed branch: lp:~hermann-kaser/pogo/lastfm-plugin
Merge into: lp:~jendrikseipp/pogo/trunk
Diff against target: 407 lines (+398/-0)
2 files modified
res/Lastfm.glade (+276/-0)
src/modules/Lastfm.py (+122/-0)
To merge this branch: bzr merge lp:~hermann-kaser/pogo/lastfm-plugin
Reviewer Review Type Date Requested Status
Jendrik Seipp Needs Fixing
Review via email: mp+90511@code.launchpad.net

Description of the change

Last.fm scrobbler thingie plugin.

To post a comment you must log in.
Revision history for this message
Jendrik Seipp (jendrikseipp) wrote :

Thanks for your work Hermann. I think we can make the plugin even better however by using code from decibel, the player pogo was forked from. There a scrobbling plugin was already included. The major benefit is that it doesn't need the additional pylast dependency and has a nice ui integration already. The code would have to be adapted slightly but I guess it should be straightforward.

If you like you can try copying http://bazaar.launchpad.net/~athropos/decibel-audio-player/trunk/view/head:/src/modules/AudioScrobbler.py into pogos modules directory. You will have to remove consts.MODCAT_INTERNET from MOD_INFO, I don't know about other changes.

review: Needs Fixing

Unmerged revisions

449. By Hermann Kaser

Add Last.fm plugin

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'res/Lastfm.glade'
2--- res/Lastfm.glade 1970-01-01 00:00:00 +0000
3+++ res/Lastfm.glade 2012-01-27 19:34:24 +0000
4@@ -0,0 +1,276 @@
5+<?xml version="1.0" encoding="UTF-8"?>
6+<glade-interface>
7+ <!-- interface-requires gtk+ 2.6 -->
8+ <!-- interface-naming-policy toplevel-contextual -->
9+ <widget class="GtkWindow" id="win-main">
10+ <property name="can_focus">False</property>
11+ <property name="window_position">center</property>
12+ <child>
13+ <widget class="GtkVBox" id="vbox1">
14+ <property name="can_focus">False</property>
15+ <property name="border_width">12</property>
16+ <property name="spacing">18</property>
17+ <child>
18+ <widget class="GtkVBox" id="vbox2">
19+ <property name="can_focus">False</property>
20+ <property name="spacing">18</property>
21+ <child>
22+ <widget class="GtkFrame" id="frame1">
23+ <property name="can_focus">False</property>
24+ <property name="label_xalign">0</property>
25+ <property name="shadow_type">none</property>
26+ <child>
27+ <widget class="GtkAlignment" id="alignment1">
28+ <property name="can_focus">False</property>
29+ <property name="top_padding">6</property>
30+ <property name="left_padding">12</property>
31+ <child>
32+ <widget class="GtkVBox" id="vbox4">
33+ <property name="can_focus">False</property>
34+ <property name="spacing">6</property>
35+ <child>
36+ <widget class="GtkVBox" id="vbox5">
37+ <property name="can_focus">False</property>
38+ <child>
39+ <widget class="GtkHBox" id="hbox4">
40+ <property name="can_focus">False</property>
41+ <property name="spacing">6</property>
42+ <child>
43+ <widget class="GtkLabel" id="label5">
44+ <property name="can_focus">False</property>
45+ <property name="label" translatable="yes">Username:</property>
46+ </widget>
47+ <packing>
48+ <property name="expand">False</property>
49+ <property name="fill">True</property>
50+ <property name="position">0</property>
51+ </packing>
52+ </child>
53+ <child>
54+ <widget class="GtkEntry" id="txt-username">
55+ <property name="can_focus">True</property>
56+ <property name="tooltip" translatable="yes">Filenames to look for (e.g., folder, cover)</property>
57+ <property name="primary_icon_activatable">False</property>
58+ <property name="secondary_icon_activatable">False</property>
59+ <property name="primary_icon_sensitive">True</property>
60+ <property name="secondary_icon_sensitive">True</property>
61+ </widget>
62+ <packing>
63+ <property name="expand">True</property>
64+ <property name="fill">True</property>
65+ <property name="position">1</property>
66+ </packing>
67+ </child>
68+ </widget>
69+ <packing>
70+ <property name="expand">True</property>
71+ <property name="fill">True</property>
72+ <property name="position">0</property>
73+ </packing>
74+ </child>
75+ </widget>
76+ <packing>
77+ <property name="expand">True</property>
78+ <property name="fill">True</property>
79+ <property name="position">0</property>
80+ </packing>
81+ </child>
82+ <child>
83+ <widget class="GtkVBox" id="vbox3">
84+ <property name="can_focus">False</property>
85+ <child>
86+ <widget class="GtkHBox" id="hbox2">
87+ <property name="can_focus">False</property>
88+ <property name="spacing">6</property>
89+ <child>
90+ <widget class="GtkLabel" id="label2">
91+ <property name="can_focus">False</property>
92+ <property name="label" translatable="yes">Password:</property>
93+ </widget>
94+ <packing>
95+ <property name="expand">False</property>
96+ <property name="fill">True</property>
97+ <property name="position">0</property>
98+ </packing>
99+ </child>
100+ <child>
101+ <widget class="GtkEntry" id="txt-password">
102+ <property name="can_focus">True</property>
103+ <property name="has_tooltip">True</property>
104+ <property name="tooltip" translatable="yes">Filenames to look for (e.g., folder, cover)</property>
105+ <property name="visibility">False</property>
106+ <property name="invisible_char">•</property>
107+ <property name="invisible_char_set">True</property>
108+ <property name="primary_icon_activatable">False</property>
109+ <property name="secondary_icon_activatable">False</property>
110+ <property name="primary_icon_sensitive">True</property>
111+ <property name="secondary_icon_sensitive">True</property>
112+ </widget>
113+ <packing>
114+ <property name="expand">True</property>
115+ <property name="fill">True</property>
116+ <property name="position">1</property>
117+ </packing>
118+ </child>
119+ </widget>
120+ <packing>
121+ <property name="expand">True</property>
122+ <property name="fill">True</property>
123+ <property name="position">0</property>
124+ </packing>
125+ </child>
126+ </widget>
127+ <packing>
128+ <property name="expand">True</property>
129+ <property name="fill">True</property>
130+ <property name="position">1</property>
131+ </packing>
132+ </child>
133+ <child>
134+ <widget class="GtkHButtonBox" id="hbuttonbox4">
135+ <property name="visible">True</property>
136+ <property name="can_focus">False</property>
137+ <property name="layout_style">end</property>
138+ <child>
139+ <widget class="GtkImage" id="img-lastfm">
140+ <property name="visible">True</property>
141+ <property name="can_focus">False</property>
142+ <property name="stock">gtk-missing-image</property>
143+ </widget>
144+ <packing>
145+ <property name="expand">False</property>
146+ <property name="fill">False</property>
147+ <property name="pack_type">end</property>
148+ <property name="position">0</property>
149+ </packing>
150+ </child>
151+ </widget>
152+ <packing>
153+ <property name="expand">False</property>
154+ <property name="fill">False</property>
155+ <property name="position">2</property>
156+ </packing>
157+ </child>
158+ </widget>
159+ </child>
160+ </widget>
161+ </child>
162+ <child>
163+ <widget class="GtkLabel" id="label1">
164+ <property name="can_focus">False</property>
165+ <property name="label" translatable="yes">&lt;b&gt;Last.fm login&lt;/b&gt;</property>
166+ <property name="use_markup">True</property>
167+ </widget>
168+ <packing>
169+ <property name="type">label_item</property>
170+ </packing>
171+ </child>
172+ </widget>
173+ <packing>
174+ <property name="expand">False</property>
175+ <property name="fill">True</property>
176+ <property name="position">0</property>
177+ </packing>
178+ </child>
179+ <child>
180+ <placeholder/>
181+ </child>
182+ <child>
183+ <placeholder/>
184+ </child>
185+ </widget>
186+ <packing>
187+ <property name="expand">True</property>
188+ <property name="fill">True</property>
189+ <property name="position">0</property>
190+ </packing>
191+ </child>
192+ <child>
193+ <widget class="GtkHSeparator" id="hseparator1">
194+ <property name="can_focus">False</property>
195+ </widget>
196+ <packing>
197+ <property name="expand">False</property>
198+ <property name="fill">True</property>
199+ <property name="position">1</property>
200+ </packing>
201+ </child>
202+ <child>
203+ <widget class="GtkHBox" id="hbox1">
204+ <property name="can_focus">False</property>
205+ <property name="spacing">6</property>
206+ <child>
207+ <widget class="GtkHButtonBox" id="hbuttonbox1">
208+ <property name="can_focus">False</property>
209+ <child>
210+ <widget class="GtkButton" id="btn-help">
211+ <property name="label">gtk-help</property>
212+ <property name="can_focus">True</property>
213+ <property name="receives_default">True</property>
214+ <property name="use_action_appearance">False</property>
215+ <property name="use_stock">True</property>
216+ </widget>
217+ <packing>
218+ <property name="expand">False</property>
219+ <property name="fill">False</property>
220+ <property name="position">0</property>
221+ </packing>
222+ </child>
223+ </widget>
224+ <packing>
225+ <property name="expand">False</property>
226+ <property name="fill">True</property>
227+ <property name="position">0</property>
228+ </packing>
229+ </child>
230+ <child>
231+ <widget class="GtkHButtonBox" id="hbuttonbox2">
232+ <property name="can_focus">False</property>
233+ <property name="spacing">12</property>
234+ <property name="layout_style">end</property>
235+ <child>
236+ <widget class="GtkButton" id="btn-cancel">
237+ <property name="label">gtk-cancel</property>
238+ <property name="can_focus">True</property>
239+ <property name="receives_default">True</property>
240+ <property name="use_action_appearance">False</property>
241+ <property name="use_stock">True</property>
242+ </widget>
243+ <packing>
244+ <property name="expand">False</property>
245+ <property name="fill">False</property>
246+ <property name="position">0</property>
247+ </packing>
248+ </child>
249+ <child>
250+ <widget class="GtkButton" id="btn-ok">
251+ <property name="label">gtk-ok</property>
252+ <property name="can_focus">True</property>
253+ <property name="receives_default">True</property>
254+ <property name="use_action_appearance">False</property>
255+ <property name="use_stock">True</property>
256+ </widget>
257+ <packing>
258+ <property name="expand">False</property>
259+ <property name="fill">False</property>
260+ <property name="position">1</property>
261+ </packing>
262+ </child>
263+ </widget>
264+ <packing>
265+ <property name="expand">True</property>
266+ <property name="fill">True</property>
267+ <property name="position">1</property>
268+ </packing>
269+ </child>
270+ </widget>
271+ <packing>
272+ <property name="expand">False</property>
273+ <property name="fill">True</property>
274+ <property name="position">2</property>
275+ </packing>
276+ </child>
277+ </widget>
278+ </child>
279+ </widget>
280+</glade-interface>
281
282=== added file 'src/modules/Lastfm.py'
283--- src/modules/Lastfm.py 1970-01-01 00:00:00 +0000
284+++ src/modules/Lastfm.py 2012-01-27 19:34:24 +0000
285@@ -0,0 +1,122 @@
286+# -*- coding: utf-8 -*-
287+#
288+# Copyright (c) 2007 François Ingelrest (Francois.Ingelrest@gmail.com)
289+# Copyright (c) 2010 Jendrik Seipp (jendrikseipp@web.de)
290+#
291+# This program is free software; you can redistribute it and/or modify
292+# it under the terms of the GNU General Public License as published by
293+# the Free Software Foundation; either version 2 of the License, or
294+# (at your option) any later version.
295+#
296+# This program is distributed in the hope that it will be useful,
297+# but WITHOUT ANY WARRANTY; without even the implied warranty of
298+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
299+# GNU Library General Public License for more details.
300+#
301+# You should have received a copy of the GNU General Public License
302+# along with this program; if not, write to the Free Software
303+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
304+
305+import modules, os, tools, traceback, pylast, time
306+
307+from tools import consts, prefs
308+from gettext import gettext as _
309+from tools.log import logger
310+
311+
312+# Module information
313+MOD_INFO = ('Last.fm', _('Last.fm Scrobbler'), _('The last.fm scrobbler'), [], False, True)
314+MOD_NAME = MOD_INFO[modules.MODINFO_NAME]
315+
316+AS_API_KEY = 'f3fb2121e6e62a01db3bfc7957ee88a1' # Hermann's Audioscrobbler key
317+AS_API_SECRET = 'fd330f4dee8a10c44096e94e36774e39'
318+
319+class Lastfm(modules.ThreadedModule):
320+
321+ def __init__(self):
322+ """ Constructor """
323+ handlers = {
324+ consts.MSG_EVT_NEW_TRACK: self.onNewTrack,
325+ consts.MSG_EVT_TRACK_POSITION: self.trackPos,
326+ }
327+
328+ modules.ThreadedModule.__init__(self, handlers)
329+
330+ self._track = False
331+ self._track_sent = False
332+ self._to_scrobble = False
333+
334+ self._lastfmUsr = prefs.get(__name__, 'lastfm-username', "")
335+ self._lastfmPwd = prefs.get(__name__, 'lastfm-password', "")
336+
337+ # --== Message handlers ==--
338+
339+ def onNewTrack(self, track):
340+ self._track_sent = False
341+ if track.getArtist() != consts.UNKNOWN_ARTIST and track.getAlbum() != consts.UNKNOWN_ALBUM \
342+ and self._lastfmUsr and self._lastfmPwd:
343+ self._track = track
344+
345+ # calculate submission point
346+ # track must be longer than 30 seconds
347+ if track.getLength() < 30:
348+ self._track_sent = True
349+
350+ # and then scrobbling shall be done halfway through the track
351+ # or at the 4 minute mark (240 seconds), whichever comes first
352+ if track.getLength() > 480:
353+ self._to_scrobble = 240
354+ else:
355+ self._to_scrobble = int(track.getLength() / 2)
356+
357+ def trackPos(self, seconds):
358+ if self._track and not self._track_sent:
359+ if self._to_scrobble <= seconds:
360+ self._track_sent = True
361+
362+ network = pylast.LastFMNetwork(api_key = AS_API_KEY, api_secret = AS_API_SECRET,
363+ username = self._lastfmUsr, password_hash = pylast.md5(self._lastfmPwd))
364+ try:
365+ network.scrobble(
366+ artist = self._track.getArtist(),
367+ title = self._track.getTitle(),
368+ timestamp = int(time.time()) - self._to_scrobble,
369+ duration = self._track.getLength()
370+ )
371+ except Exception, e:
372+ pass
373+
374+ # --== Configuration ==--
375+
376+ def configure(self, parent):
377+ if self.cfgWin is None:
378+ from gui.window import Window
379+
380+ self.cfgWin = Window('Lastfm.glade', 'vbox1', __name__, MOD_INFO[modules.MODINFO_L10N], 320, 265)
381+ self.cfgWin.getWidget('img-lastfm').set_from_file(os.path.join(consts.dirPix, 'audioscrobbler.png'))
382+ self.cfgWin.getWidget('btn-ok').connect('clicked', self.onBtnOk)
383+ self.cfgWin.getWidget('btn-help').connect('clicked', self.onBtnHelp)
384+ self.cfgWin.getWidget('btn-cancel').connect('clicked', lambda btn: self.cfgWin.hide())
385+
386+ if not self.cfgWin.isVisible():
387+ lastfmUsr = prefs.get(__name__, 'lastfm-username', "")
388+ lastfmPwd = prefs.get(__name__, 'lastfm-password', "")
389+
390+ self.cfgWin.getWidget('btn-ok').grab_focus()
391+ self.cfgWin.getWidget('txt-username').set_text(lastfmUsr)
392+ self.cfgWin.getWidget('txt-password').set_text(lastfmPwd)
393+
394+ self.cfgWin.show()
395+
396+ def onBtnOk(self, btn):
397+ """ Save configuration """
398+ self._lastfmUsr = self.cfgWin.getWidget('txt-username').get_text()
399+ self._lastfmPwd = self.cfgWin.getWidget('txt-password').get_text()
400+
401+ prefs.set(__name__, 'lastfm-username', self._lastfmUsr)
402+ prefs.set(__name__, 'lastfm-password', self._lastfmPwd)
403+
404+ self.cfgWin.hide()
405+
406+ def onBtnHelp(self, btn):
407+ pass

Subscribers

People subscribed via source and target branches