scrobbler: Move to external extension
This commit is contained in:
parent
fd6bb4ba43
commit
6400a45a0e
@ -50,4 +50,3 @@ Frontend implementations
|
|||||||
* :mod:`mopidy.frontends.http`
|
* :mod:`mopidy.frontends.http`
|
||||||
* :mod:`mopidy.frontends.mpd`
|
* :mod:`mopidy.frontends.mpd`
|
||||||
* :mod:`mopidy.frontends.mpris`
|
* :mod:`mopidy.frontends.mpris`
|
||||||
* :mod:`mopidy.frontends.scrobbler`
|
|
||||||
|
|||||||
@ -7,6 +7,13 @@ This changelog is used to track all major changes to Mopidy.
|
|||||||
v0.16.0 (UNRELEASED)
|
v0.16.0 (UNRELEASED)
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
**Extensions**
|
||||||
|
|
||||||
|
- The Last.fm scrobbler has been moved to its own external extension,
|
||||||
|
`Mopidy-Scrobbler <https://github.com/mopidy/mopidy-scrobbler>`. You'll need
|
||||||
|
to install it in addition to Mopidy if you want it to continue to work as it
|
||||||
|
used to.
|
||||||
|
|
||||||
**Audio**
|
**Audio**
|
||||||
|
|
||||||
- Added support for parsing and playback of playlists in GStreamer. What this
|
- Added support for parsing and playback of playlists in GStreamer. What this
|
||||||
|
|||||||
@ -77,6 +77,21 @@ Issues:
|
|||||||
https://github.com/mopidy/mopidy/issues
|
https://github.com/mopidy/mopidy/issues
|
||||||
|
|
||||||
|
|
||||||
|
Mopidy-Scrobbler
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Extension for scrobbling played tracks to Last.fm.
|
||||||
|
|
||||||
|
Author:
|
||||||
|
Stein Magnus Jodal
|
||||||
|
PyPI:
|
||||||
|
`Mopidy-Scrobbler <https://pypi.python.org/pypi/Mopidy-Scrobbler>`_
|
||||||
|
GitHub:
|
||||||
|
`mopidy/mopidy-scrobbler <https://github.com/mopidy/mopidy-scrobbler>`_
|
||||||
|
Issues:
|
||||||
|
https://github.com/mopidy/mopidy-scrobbler/issues
|
||||||
|
|
||||||
|
|
||||||
Mopidy-SomaFM
|
Mopidy-SomaFM
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
.. _ext-scrobbler:
|
|
||||||
|
|
||||||
****************
|
|
||||||
Mopidy-Scrobbler
|
|
||||||
****************
|
|
||||||
|
|
||||||
This extension scrobbles the music you play to your `Last.fm
|
|
||||||
<http://www.last.fm>`_ profile.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This extension requires a free user account at Last.fm.
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
============
|
|
||||||
|
|
||||||
.. literalinclude:: ../../requirements/scrobbler.txt
|
|
||||||
|
|
||||||
|
|
||||||
Default configuration
|
|
||||||
=====================
|
|
||||||
|
|
||||||
.. literalinclude:: ../../mopidy/frontends/scrobbler/ext.conf
|
|
||||||
:language: ini
|
|
||||||
|
|
||||||
|
|
||||||
Configuration values
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. confval:: scrobbler/enabled
|
|
||||||
|
|
||||||
If the scrobbler extension should be enabled or not.
|
|
||||||
|
|
||||||
.. confval:: scrobbler/username
|
|
||||||
|
|
||||||
Your Last.fm username.
|
|
||||||
|
|
||||||
.. confval:: scrobbler/password
|
|
||||||
|
|
||||||
Your Last.fm password.
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
The extension is enabled by default if all dependencies are available. You just
|
|
||||||
need to add your Last.fm username and password to the
|
|
||||||
``~/.config/mopidy/mopidy.conf`` file:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[scrobbler]
|
|
||||||
username = myusername
|
|
||||||
password = mysecret
|
|
||||||
@ -25,9 +25,9 @@ Glossary
|
|||||||
frontend
|
frontend
|
||||||
A part of Mopidy *using* the :term:`core` API. Existing frontends
|
A part of Mopidy *using* the :term:`core` API. Existing frontends
|
||||||
include the :ref:`MPD server <ext-mpd>`, the :ref:`MPRIS/D-Bus
|
include the :ref:`MPD server <ext-mpd>`, the :ref:`MPRIS/D-Bus
|
||||||
integration <ext-mpris>`, the :ref:`Last.fm scrobbler <ext-scrobbler>`,
|
integration <ext-mpris>`, the Last.fm scrobbler, and the :ref:`HTTP
|
||||||
and the :ref:`HTTP server <ext-http>` with JavaScript API. See
|
server <ext-http>` with JavaScript API. See :ref:`frontend-api` for
|
||||||
:ref:`frontend-api` for details.
|
details.
|
||||||
|
|
||||||
mixer
|
mixer
|
||||||
A GStreamer element that controls audio volume.
|
A GStreamer element that controls audio volume.
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import mopidy
|
|
||||||
from mopidy import config, exceptions, ext
|
|
||||||
|
|
||||||
|
|
||||||
class Extension(ext.Extension):
|
|
||||||
|
|
||||||
dist_name = 'Mopidy-Scrobbler'
|
|
||||||
ext_name = 'scrobbler'
|
|
||||||
version = mopidy.__version__
|
|
||||||
|
|
||||||
def get_default_config(self):
|
|
||||||
conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf')
|
|
||||||
return config.read(conf_file)
|
|
||||||
|
|
||||||
def get_config_schema(self):
|
|
||||||
schema = super(Extension, self).get_config_schema()
|
|
||||||
schema['username'] = config.String()
|
|
||||||
schema['password'] = config.Secret()
|
|
||||||
return schema
|
|
||||||
|
|
||||||
def validate_environment(self):
|
|
||||||
try:
|
|
||||||
import pylast # noqa
|
|
||||||
except ImportError as e:
|
|
||||||
raise exceptions.ExtensionError('pylast library not found', e)
|
|
||||||
|
|
||||||
def get_frontend_classes(self):
|
|
||||||
from .actor import ScrobblerFrontend
|
|
||||||
return [ScrobblerFrontend]
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
|
|
||||||
import pykka
|
|
||||||
import pylast
|
|
||||||
|
|
||||||
from mopidy.core import CoreListener
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.frontends.scrobbler')
|
|
||||||
|
|
||||||
API_KEY = '2236babefa8ebb3d93ea467560d00d04'
|
|
||||||
API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
|
|
||||||
|
|
||||||
|
|
||||||
class ScrobblerFrontend(pykka.ThreadingActor, CoreListener):
|
|
||||||
def __init__(self, config, core):
|
|
||||||
super(ScrobblerFrontend, self).__init__()
|
|
||||||
self.config = config
|
|
||||||
self.lastfm = None
|
|
||||||
self.last_start_time = None
|
|
||||||
|
|
||||||
def on_start(self):
|
|
||||||
try:
|
|
||||||
self.lastfm = pylast.LastFMNetwork(
|
|
||||||
api_key=API_KEY, api_secret=API_SECRET,
|
|
||||||
username=self.config['scrobbler']['username'],
|
|
||||||
password_hash=pylast.md5(self.config['scrobbler']['password']))
|
|
||||||
logger.info('Scrobbler connected to Last.fm')
|
|
||||||
except (pylast.NetworkError, pylast.MalformedResponseError,
|
|
||||||
pylast.WSError) as e:
|
|
||||||
logger.error('Error during Last.fm setup: %s', e)
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def track_playback_started(self, tl_track):
|
|
||||||
track = tl_track.track
|
|
||||||
artists = ', '.join([a.name for a in track.artists])
|
|
||||||
duration = track.length and track.length // 1000 or 0
|
|
||||||
self.last_start_time = int(time.time())
|
|
||||||
logger.debug('Now playing track: %s - %s', artists, track.name)
|
|
||||||
try:
|
|
||||||
self.lastfm.update_now_playing(
|
|
||||||
artists,
|
|
||||||
(track.name or ''),
|
|
||||||
album=(track.album and track.album.name or ''),
|
|
||||||
duration=str(duration),
|
|
||||||
track_number=str(track.track_no),
|
|
||||||
mbid=(track.musicbrainz_id or ''))
|
|
||||||
except (pylast.ScrobblingError, pylast.NetworkError,
|
|
||||||
pylast.MalformedResponseError, pylast.WSError) as e:
|
|
||||||
logger.warning('Error submitting playing track to Last.fm: %s', e)
|
|
||||||
|
|
||||||
def track_playback_ended(self, tl_track, time_position):
|
|
||||||
track = tl_track.track
|
|
||||||
artists = ', '.join([a.name for a in track.artists])
|
|
||||||
duration = track.length and track.length // 1000 or 0
|
|
||||||
time_position = time_position // 1000
|
|
||||||
if duration < 30:
|
|
||||||
logger.debug('Track too short to scrobble. (30s)')
|
|
||||||
return
|
|
||||||
if time_position < duration // 2 and time_position < 240:
|
|
||||||
logger.debug(
|
|
||||||
'Track not played long enough to scrobble. (50% or 240s)')
|
|
||||||
return
|
|
||||||
if self.last_start_time is None:
|
|
||||||
self.last_start_time = int(time.time()) - duration
|
|
||||||
logger.debug('Scrobbling track: %s - %s', artists, track.name)
|
|
||||||
try:
|
|
||||||
self.lastfm.scrobble(
|
|
||||||
artists,
|
|
||||||
(track.name or ''),
|
|
||||||
str(self.last_start_time),
|
|
||||||
album=(track.album and track.album.name or ''),
|
|
||||||
track_number=str(track.track_no),
|
|
||||||
duration=str(duration),
|
|
||||||
mbid=(track.musicbrainz_id or ''))
|
|
||||||
except (pylast.ScrobblingError, pylast.NetworkError,
|
|
||||||
pylast.MalformedResponseError, pylast.WSError) as e:
|
|
||||||
logger.warning('Error submitting played track to Last.fm: %s', e)
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
[scrobbler]
|
|
||||||
enabled = true
|
|
||||||
username =
|
|
||||||
password =
|
|
||||||
3
setup.py
3
setup.py
@ -29,7 +29,7 @@ setup(
|
|||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
'spotify': ['pyspotify >= 1.9, < 2'],
|
'spotify': ['pyspotify >= 1.9, < 2'],
|
||||||
'scrobbler': ['pylast >= 0.5.7'],
|
'scrobbler': ['Mopidy-Scrobbler'],
|
||||||
'http': ['cherrypy >= 3.2.2', 'ws4py >= 0.2.3'],
|
'http': ['cherrypy >= 3.2.2', 'ws4py >= 0.2.3'],
|
||||||
},
|
},
|
||||||
test_suite='nose.collector',
|
test_suite='nose.collector',
|
||||||
@ -45,7 +45,6 @@ setup(
|
|||||||
],
|
],
|
||||||
'mopidy.ext': [
|
'mopidy.ext': [
|
||||||
'http = mopidy.frontends.http:Extension [http]',
|
'http = mopidy.frontends.http:Extension [http]',
|
||||||
'scrobbler = mopidy.frontends.scrobbler:Extension [scrobbler]',
|
|
||||||
'local = mopidy.backends.local:Extension',
|
'local = mopidy.backends.local:Extension',
|
||||||
'mpd = mopidy.frontends.mpd:Extension',
|
'mpd = mopidy.frontends.mpd:Extension',
|
||||||
'mpris = mopidy.frontends.mpris:Extension',
|
'mpris = mopidy.frontends.mpris:Extension',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user