[gnome-music] player: put last_fm scrobbler code in its own class
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music] player: put last_fm scrobbler code in its own class
- Date: Tue, 9 Jan 2018 22:25:49 +0000 (UTC)
commit 9a2fe71c54095192004f599173588d0be946d2dc
Author: Jean Felder <jean felder gmail com>
Date: Tue Jan 9 22:06:11 2018 +0100
player: put last_fm scrobbler code in its own class
gnomemusic/Makefile.am | 2 +-
gnomemusic/player.py | 123 +++++++----------------------------
gnomemusic/scrobbler.py | 170 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 195 insertions(+), 100 deletions(-)
---
diff --git a/gnomemusic/Makefile.am b/gnomemusic/Makefile.am
index 408cfde..dff6325 100644
--- a/gnomemusic/Makefile.am
+++ b/gnomemusic/Makefile.am
@@ -14,6 +14,6 @@ app_PYTHON = \
playlists.py\
utils.py \
query.py \
+ scrobbler.py \
searchbar.py \
window.py
-
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index bbc0474..059124f 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -30,6 +30,10 @@
# code, but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version.
+from collections import deque
+import logging
+from random import randint
+import time
from gi.repository import GIRepository
GIRepository.Repository.prepend_search_path('libgd')
@@ -40,22 +44,17 @@ gi.require_version('GstAudio', '1.0')
gi.require_version('GstPbutils', '1.0')
from gi.repository import Gtk, Gdk, GLib, Gio, GObject, Gst, GstAudio, GstPbutils
from gettext import gettext as _, ngettext
-from random import randint
-from collections import deque
+
+from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.grilo import grilo
from gnomemusic.playlists import Playlists
+from gnomemusic.scrobbler import LastFmScrobbler
import gnomemusic.utils as utils
-playlists = Playlists.get_default()
-from hashlib import md5
-import requests
-import time
-from threading import Thread
-from gnomemusic import log
-import logging
logger = logging.getLogger(__name__)
+playlists = Playlists.get_default()
class RepeatType:
@@ -146,25 +145,7 @@ class Player(GObject.GObject):
self.playlist_insert_handler = 0
self.playlist_delete_handler = 0
- self._check_last_fm()
-
- @log
- def _check_last_fm(self):
- try:
- self.last_fm = None
- gi.require_version('Goa', '1.0')
- from gi.repository import Goa
- client = Goa.Client.new_sync(None)
- accounts = client.get_accounts()
-
- for obj in accounts:
- account = obj.get_account()
- if account.props.provider_name == "Last.fm":
- self.last_fm = obj.get_oauth2_based()
- return
- except Exception as e:
- logger.info("Error reading Last.fm credentials: %s" % str(e))
- self.last_fm = None
+ self._lastfm = LastFmScrobbler()
@log
def _on_replaygain_setting_changed(self, settings, value):
@@ -602,15 +583,14 @@ class Player(GObject.GObject):
artist = utils.get_artist_name(media)
self.artistLabel.set_label(artist)
- self._currentArtist = artist
self.coverImg.set_from_surface(self._loading_icon_surface)
self.cache.lookup(media, ArtSize.XSMALL, self._on_cache_lookup, None)
- self._currentTitle = utils.get_media_title(media)
- self.titleLabel.set_label(self._currentTitle)
+ title = utils.get_media_title(media)
+ self.titleLabel.set_label(title)
- self._currentTimestamp = int(time.time())
+ self._time_stamp = int(time.time())
url = media.get_url()
if url != self.player.get_value('current-uri', 0):
@@ -681,9 +661,7 @@ class Player(GObject.GObject):
self.player.set_state(Gst.State.PLAYING)
self._update_position_callback()
if media:
- t = Thread(target=self.update_now_playing_in_lastfm, args=(media.get_url(),))
- t.setDaemon(True)
- t.start()
+ self._lastfm.now_playing(media)
if not self.timeout and self.progressScale.get_realized():
self._update_timeout()
@@ -950,67 +928,10 @@ class Player(GObject.GObject):
def _set_duration(self, duration):
self.duration = duration
self.played_seconds = 0
- self.scrobbled = False
+ self._scrobbled = False
self.progressScale.set_range(0.0, duration * 60)
@log
- def scrobble_song(self, url):
- # Update playlists
- playlists.update_all_static_playlists()
-
- if self.last_fm:
- api_key = self.last_fm.props.client_id
- sk = self.last_fm.call_get_access_token_sync(None)[0]
- secret = self.last_fm.props.client_secret
-
- sig = "api_key%sartist[0]%smethodtrack.scrobblesk%stimestamp[0]%strack[0]%s%s" %\
- (api_key, self._currentArtist, sk, self._currentTimestamp, self._currentTitle, secret)
-
- api_sig = md5(sig.encode()).hexdigest()
- requests_dict = {
- "api_key": api_key,
- "method": "track.scrobble",
- "artist[0]": self._currentArtist,
- "track[0]": self._currentTitle,
- "timestamp[0]": self._currentTimestamp,
- "sk": sk,
- "api_sig": api_sig
- }
- try:
- r = requests.post("https://ws.audioscrobbler.com/2.0/", requests_dict)
- if r.status_code != 200:
- logger.warn("Failed to scrobble track: %s %s" % (r.status_code, r.reason))
- logger.warn(r.text)
- except Exception as e:
- logger.warn(e)
-
- @log
- def update_now_playing_in_lastfm(self, url):
- if self.last_fm:
- api_key = self.last_fm.props.client_id
- sk = self.last_fm.call_get_access_token_sync(None)[0]
- secret = self.last_fm.props.client_secret
-
- sig = "api_key%sartist%smethodtrack.updateNowPlayingsk%strack%s%s" % \
- (api_key, self._currentArtist, sk, self._currentTitle, secret)
-
- api_sig = md5(sig.encode()).hexdigest()
- request_dict = {
- "api_key": api_key,
- "method": "track.updateNowPlaying",
- "artist": self._currentArtist,
- "track": self._currentTitle,
- "sk": sk,
- "api_sig": api_sig
- }
- try:
- r = requests.post("https://ws.audioscrobbler.com/2.0/", request_dict)
- if r.status_code != 200:
- logger.warn("Failed to update currently played track: %s %s" % (r.status_code, r.reason))
- logger.warn(r.text)
- except Exception as e:
- logger.warn(e)
-
def _update_position_callback(self):
position = self.player.query_position(Gst.Format.TIME)[1] / 1000000000
if position > 0:
@@ -1018,6 +939,7 @@ class Player(GObject.GObject):
self._update_timeout()
return False
+ @log
def _update_seconds_callback(self):
self._on_progress_value_changed(None)
@@ -1026,16 +948,19 @@ class Player(GObject.GObject):
self.played_seconds += self.seconds_period / 1000
try:
percentage = self.played_seconds / self.duration
- if not self.scrobbled and percentage > 0.4:
+ if not self._scrobbled and percentage > 0.4:
current_media = self.get_current_media()
- self.scrobbled = True
+ self._scrobbled = True
if current_media:
+ # FIXME: we should not need to update static
+ # playlists here but removing it may introduce
+ # a bug. So, we keep it for the time being.
+ playlists.update_all_static_playlists()
grilo.bump_play_count(self.get_current_media())
grilo.set_last_played(current_media)
- just_played_url = self.get_current_media().get_url()
- t = Thread(target=self.scrobble_song, args=(just_played_url,))
- t.setDaemon(True)
- t.start()
+ self._lastfm.scrobble(
+ current_media, self._time_stamp)
+
except Exception as e:
logger.warn("Error: %s, %s", e.__class__, e)
return True
diff --git a/gnomemusic/scrobbler.py b/gnomemusic/scrobbler.py
new file mode 100644
index 0000000..3d06e95
--- /dev/null
+++ b/gnomemusic/scrobbler.py
@@ -0,0 +1,170 @@
+# Copyright (c) 2018 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music. This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered. If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version.
+
+import gi
+
+from hashlib import md5
+import logging
+import requests
+from threading import Thread
+
+from gnomemusic import log
+import gnomemusic.utils as utils
+
+
+logger = logging.getLogger(__name__)
+
+
+class LastFmScrobbler():
+ """Scrobble songs to Last.fm"""
+
+ def __repr__(self):
+ return '<LastFmScrobbler>'
+
+ @log
+ def __init__(self):
+ self._authentication = None
+ self._connect()
+
+ def _connect(self):
+ """Connect to Last.fm using gnome-online-accounts"""
+ try:
+ gi.require_version('Goa', '1.0')
+ from gi.repository import Goa
+ client = Goa.Client.new_sync(None)
+ accounts = client.get_accounts()
+
+ for obj in accounts:
+ account = obj.get_account()
+ if account.props.provider_name == "Last.fm":
+ self._authentication = obj.get_oauth2_based()
+ return
+ except Exception as e:
+ logger.info("Error reading Last.fm credentials: %s" % str(e))
+
+ @log
+ def _scrobble(self, media, time_stamp):
+ """Internal method called by self.scrobble"""
+ if self._authentication is None:
+ return
+
+ api_key = self._authentication.props.client_id
+ sk = self._authentication.call_get_access_token_sync(None)[0]
+ secret = self._authentication.props.client_secret
+
+ artist = utils.get_artist_name(media)
+ title = utils.get_media_title(media)
+
+ sig = ("api_key{}artist[0]{}methodtrack.scrobblesk{}timestamp[0]"
+ "{}track[0]{}{}").format(
+ api_key, artist, sk, time_stamp, title, secret)
+
+ api_sig = md5(sig.encode()).hexdigest()
+ request_dict = {
+ "api_key": api_key,
+ "method": "track.scrobble",
+ "artist[0]": artist,
+ "track[0]": title,
+ "timestamp[0]": time_stamp,
+ "sk": sk,
+ "api_sig": api_sig
+ }
+
+ try:
+ r = requests.post(
+ "https://ws.audioscrobbler.com/2.0/", request_dict)
+ if r.status_code != 200:
+ logger.warn(
+ "Failed to scrobble track: %s %s" %
+ (r.status_code, r.reason))
+ logger.warn(r.text)
+ except Exception as e:
+ logger.warn(e)
+
+ @log
+ def scrobble(self, media, time_stamp):
+ """Scrobble a song to Last.fm.
+
+ If not connected to Last.fm nothing happens
+ Creates a new thread to make the request
+
+ :param media: Grilo media item
+ :param time_stamp: song loaded time (epoch time)
+ """
+ if self._authentication is None:
+ return
+
+ t = Thread(target=self._scrobble, args=(media, time_stamp))
+ t.setDaemon(True)
+ t.start()
+
+ @log
+ def _now_playing(self, media):
+ """Internal method called by self.now_playing"""
+ api_key = self._authentication.props.client_id
+ sk = self._authentication.call_get_access_token_sync(None)[0]
+ secret = self._authentication.props.client_secret
+
+ artist = utils.get_artist_name(media)
+ title = utils.get_media_title(media)
+
+ sig = ("api_key{}artist{}methodtrack.updateNowPlayingsk{}track"
+ "{}{}").format(api_key, artist, sk, title, secret)
+
+ api_sig = md5(sig.encode()).hexdigest()
+ request_dict = {
+ "api_key": api_key,
+ "method": "track.updateNowPlaying",
+ "artist": artist,
+ "track": title,
+ "sk": sk,
+ "api_sig": api_sig
+ }
+
+ try:
+ r = requests.post(
+ "https://ws.audioscrobbler.com/2.0/", request_dict)
+ if r.status_code != 200:
+ logger.warn(
+ "Failed to update currently played track: %s %s" %
+ (r.status_code, r.reason))
+ logger.warn(r.text)
+ except Exception as e:
+ logger.warn(e)
+
+ @log
+ def now_playing(self, media):
+ """Set now playing song to Last.fm
+
+ If not connected to Last.fm nothing happens
+ Creates a new thread to make the request
+
+ :param media: Grilo media item
+ """
+ if self._authentication is None:
+ return
+
+ t = Thread(target=self._now_playing, args=(media,))
+ t.setDaemon(True)
+ t.start()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]