From ec94449a63d59afea4cdb2f62bd742926b44b827 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Thu, 15 Jan 2015 22:25:34 +0100 Subject: [PATCH 1/7] Handle tags_changed in Core and send event to CoreListener --- mopidy/core/actor.py | 20 ++++++++++++++++++++ mopidy/core/listener.py | 11 +++++++++++ 2 files changed, 31 insertions(+) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index 75c06f69..ccd1e4c5 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -102,6 +102,26 @@ class Core( # Forward event from mixer to frontends CoreListener.send('mute_changed', mute=mute) + def tags_changed(self, tags): + # Should return only one audio instance + audios = pykka.ActorRegistry.get_by_class(audio.Audio) + + if audios and len(audios) == 1: + audio_proxy = audios[0].proxy() + + # Gets metadata + future = audio_proxy.get_current_tags() + tags_data = future.get() + if not tags_data or not isinstance(tags_data, dict): + return + + # Convert to track and set playback + track = audio.utils.convert_tags_to_track(tags_data) + self.playback.current_track = track + + # Send event to frontends + CoreListener.send('track_metadata_changed', track_metadata=track) + class Backends(list): def __init__(self, backends): diff --git a/mopidy/core/listener.py b/mopidy/core/listener.py index 2c027e1b..c94037b2 100644 --- a/mopidy/core/listener.py +++ b/mopidy/core/listener.py @@ -163,3 +163,14 @@ class CoreListener(listener.Listener): :type time_position: int """ pass + + def track_metadata_changed(self, track_metadata): + """ + Called whenever current track's metadata changed + + *MAY* be implemented by actor. + + :param track_metadata: the track with metadata + :type track_metadata: :class:`mopidy.models.Track` + """ + pass From e4dd04cfb77e0cd930a84ad4f7d983a5248a4b90 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Fri, 16 Jan 2015 21:41:55 +0100 Subject: [PATCH 2/7] One step beyond --- mopidy/core/actor.py | 12 +++--------- mopidy/core/listener.py | 5 +---- mopidy/core/playback.py | 8 ++++++++ mopidy/mpd/actor.py | 3 +++ mopidy/mpd/protocol/current_playlist.py | 4 ++++ mopidy/mpd/translator.py | 22 ++++++++++++++++++++++ 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index ccd1e4c5..15a94665 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -109,18 +109,12 @@ class Core( if audios and len(audios) == 1: audio_proxy = audios[0].proxy() - # Gets metadata + # Request available metadata and put in playback future = audio_proxy.get_current_tags() - tags_data = future.get() - if not tags_data or not isinstance(tags_data, dict): - return - - # Convert to track and set playback - track = audio.utils.convert_tags_to_track(tags_data) - self.playback.current_track = track + self.playback.current_metadata = future.get() # Send event to frontends - CoreListener.send('track_metadata_changed', track_metadata=track) + CoreListener.send('current_metadata_changed') class Backends(list): diff --git a/mopidy/core/listener.py b/mopidy/core/listener.py index c94037b2..9d952473 100644 --- a/mopidy/core/listener.py +++ b/mopidy/core/listener.py @@ -164,13 +164,10 @@ class CoreListener(listener.Listener): """ pass - def track_metadata_changed(self, track_metadata): + def current_metadata_changed(self): """ Called whenever current track's metadata changed *MAY* be implemented by actor. - - :param track_metadata: the track with metadata - :type track_metadata: :class:`mopidy.models.Track` """ pass diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index ef3cc4b2..4b5f4b77 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -126,6 +126,14 @@ class PlaybackController(object): mute = property(get_mute, set_mute) """Mute state as a :class:`True` if muted, :class:`False` otherwise""" + def get_current_metadata(self): + return self.current_metadata + + current_metadata = None + """ + The currently playing metadata :class:`dict`, or :class:`None`. + """ + # Methods # TODO: remove this. diff --git a/mopidy/mpd/actor.py b/mopidy/mpd/actor.py index c8123c32..1f213812 100644 --- a/mopidy/mpd/actor.py +++ b/mopidy/mpd/actor.py @@ -71,3 +71,6 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener): def mute_changed(self, mute): self.send_idle('output') + + def current_metadata_changed(self): + self.send_idle('playlist') diff --git a/mopidy/mpd/protocol/current_playlist.py b/mopidy/mpd/protocol/current_playlist.py index 33c090e3..09121df1 100644 --- a/mopidy/mpd/protocol/current_playlist.py +++ b/mopidy/mpd/protocol/current_playlist.py @@ -278,6 +278,10 @@ def plchanges(context, version): if int(version) < context.core.tracklist.version.get(): return translator.tracks_to_mpd_format( context.core.tracklist.tl_tracks.get()) + elif int(version) == context.core.tracklist.version.get(): + return translator.metadata_track_to_mpd_format( + context.core.playback.current_tl_track.get(), + context.core.playback.current_metadata.get()) @protocol.commands.add('plchangesposid', version=protocol.INT) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 23fb2874..95b1b263 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -86,6 +86,28 @@ def track_to_mpd_format(track, position=None): return result +def metadata_track_to_mpd_format(track, metadata): + # TODO: replace track data with metadata + result = [] + if track: + if isinstance(track, TlTrack): + (tlid, track) = track + else: + (tlid, track) = (None, track) + result = [ + ('file', track.uri or ''), + ('Time', track.length and (track.length // 1000) or 0), + ('Artist', artists_to_mpd_format(track.artists)), + ('Album', track.album and track.album.name or ''), + ] + if metadata and 'title' in metadata: + result.append(('Title', metadata['title'])) + else: + result.append(('Title', track.name or '')) + + return result + + def artists_to_mpd_format(artists): """ Format track artists for output to MPD client. From 7ee1935315ab0499e2c3f6ba8ca65cbb1794a27b Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Fri, 16 Jan 2015 23:45:07 +0100 Subject: [PATCH 3/7] MPD gets metadata's updates from stream --- mopidy/core/actor.py | 4 ++- mopidy/core/playback.py | 9 +++--- mopidy/mpd/protocol/current_playlist.py | 4 +-- mopidy/mpd/translator.py | 37 ++++++++++++------------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index 15a94665..4ad38cb6 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -7,6 +7,7 @@ import pykka from mopidy import audio, backend, mixer from mopidy.audio import PlaybackState +from mopidy.audio.utils import convert_tags_to_track from mopidy.core.history import HistoryController from mopidy.core.library import LibraryController from mopidy.core.listener import CoreListener @@ -111,7 +112,8 @@ class Core( # Request available metadata and put in playback future = audio_proxy.get_current_tags() - self.playback.current_metadata = future.get() + mtdata = future.get() + self.playback.current_md_track = convert_tags_to_track(mtdata) # Send event to frontends CoreListener.send('current_metadata_changed') diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index 4b5f4b77..ad99e6ec 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -126,12 +126,13 @@ class PlaybackController(object): mute = property(get_mute, set_mute) """Mute state as a :class:`True` if muted, :class:`False` otherwise""" - def get_current_metadata(self): - return self.current_metadata + def get_current_metadata_track(self): + return self.current_md_track - current_metadata = None + current_md_track = None """ - The currently playing metadata :class:`dict`, or :class:`None`. + The currently playing metadata :class:`mopidy.models.Track`, + or :class:`None`. """ # Methods diff --git a/mopidy/mpd/protocol/current_playlist.py b/mopidy/mpd/protocol/current_playlist.py index 09121df1..d5464791 100644 --- a/mopidy/mpd/protocol/current_playlist.py +++ b/mopidy/mpd/protocol/current_playlist.py @@ -278,10 +278,10 @@ def plchanges(context, version): if int(version) < context.core.tracklist.version.get(): return translator.tracks_to_mpd_format( context.core.tracklist.tl_tracks.get()) - elif int(version) == context.core.tracklist.version.get(): + else: return translator.metadata_track_to_mpd_format( context.core.playback.current_tl_track.get(), - context.core.playback.current_metadata.get()) + context.core.playback.current_md_track.get()) @protocol.commands.add('plchangesposid', version=protocol.INT) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 95b1b263..0788b2d6 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals import re -from mopidy.models import TlTrack +from mopidy.models import TlTrack, Track # TODO: special handling of local:// uri scheme normalize_path_re = re.compile(r'[^/]+') @@ -87,25 +87,24 @@ def track_to_mpd_format(track, position=None): def metadata_track_to_mpd_format(track, metadata): - # TODO: replace track data with metadata - result = [] - if track: - if isinstance(track, TlTrack): - (tlid, track) = track - else: - (tlid, track) = (None, track) - result = [ - ('file', track.uri or ''), - ('Time', track.length and (track.length // 1000) or 0), - ('Artist', artists_to_mpd_format(track.artists)), - ('Album', track.album and track.album.name or ''), - ] - if metadata and 'title' in metadata: - result.append(('Title', metadata['title'])) - else: - result.append(('Title', track.name or '')) + """ + Create new Track with a mix of track and metadata + and convert it to mpd format + """ + # Sanity check + if track is None or metadata is None: + return None - return result + # + if isinstance(track, TlTrack): + (tlid, track) = track + + track_kwargs = {k: v for k, v in track.__dict__.items() if v} + for k, v in metadata.__dict__.items(): + if v: + track_kwargs[k] = v + result_track = Track(**track_kwargs) + return track_to_mpd_format(result_track) def artists_to_mpd_format(artists): From eeed2973f1236a25b598586c1634cbc225d359d9 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Sat, 17 Jan 2015 14:30:40 +0100 Subject: [PATCH 4/7] Fix metadata refresh with more than one pl in tracklist --- mopidy/core/actor.py | 32 ++++++++++++++++++------- mopidy/mpd/protocol/current_playlist.py | 23 ++++++++++++++---- mopidy/mpd/translator.py | 23 +----------------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index 4ad38cb6..dbcfc9a1 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -14,6 +14,7 @@ from mopidy.core.listener import CoreListener from mopidy.core.playback import PlaybackController from mopidy.core.playlists import PlaylistsController from mopidy.core.tracklist import TracklistController +from mopidy.models import TlTrack, Track from mopidy.utils import versioning @@ -107,16 +108,31 @@ class Core( # Should return only one audio instance audios = pykka.ActorRegistry.get_by_class(audio.Audio) - if audios and len(audios) == 1: - audio_proxy = audios[0].proxy() + # Validity checks + if audios is None or len(audios) != 1: + return + if self.playback.current_tl_track is None: + return - # Request available metadata and put in playback - future = audio_proxy.get_current_tags() - mtdata = future.get() - self.playback.current_md_track = convert_tags_to_track(mtdata) + audio_proxy = audios[0].proxy() - # Send event to frontends - CoreListener.send('current_metadata_changed') + # Request available metadata and set a track + future = audio_proxy.get_current_tags() + mt_track = convert_tags_to_track(future.get()) + + # Merge current_tl_track with metadata in current_md_track + c_track = self.playback.current_tl_track.track + track_kwargs = {k: v for k, v in c_track.__dict__.items() if v} + for k, v in mt_track.__dict__.items(): + if v: + track_kwargs[k] = v + + self.playback.current_md_track = TlTrack(**{ + 'tlid': self.playback.current_tl_track.tlid, + 'track': Track(**track_kwargs)}) + + # Send event to frontends + CoreListener.send('current_metadata_changed') class Backends(list): diff --git a/mopidy/mpd/protocol/current_playlist.py b/mopidy/mpd/protocol/current_playlist.py index d5464791..bc89afcb 100644 --- a/mopidy/mpd/protocol/current_playlist.py +++ b/mopidy/mpd/protocol/current_playlist.py @@ -275,13 +275,26 @@ def plchanges(context, version): - Calls ``plchanges "-1"`` two times per second to get the entire playlist. """ # XXX Naive implementation that returns all tracks as changed - if int(version) < context.core.tracklist.version.get(): + tracklist_version = context.core.tracklist.version.get() + iversion = int(version) + if iversion < tracklist_version: return translator.tracks_to_mpd_format( context.core.tracklist.tl_tracks.get()) - else: - return translator.metadata_track_to_mpd_format( - context.core.playback.current_tl_track.get(), - context.core.playback.current_md_track.get()) + elif iversion == tracklist_version: + # If version are equals, it is just a metadata update + # So we replace the updated track in playlist + current_md_track = context.core.playback.current_md_track.get() + if current_md_track is None: + return None + + ntl_tracks = [] + tl_tracks = context.core.tracklist.tl_tracks.get() + for tl_track in tl_tracks: + if tl_track.tlid == current_md_track.tlid: + ntl_tracks.append(current_md_track) + else: + ntl_tracks.append(tl_track) + return translator.tracks_to_mpd_format(ntl_tracks) @protocol.commands.add('plchangesposid', version=protocol.INT) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 0788b2d6..23fb2874 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals import re -from mopidy.models import TlTrack, Track +from mopidy.models import TlTrack # TODO: special handling of local:// uri scheme normalize_path_re = re.compile(r'[^/]+') @@ -86,27 +86,6 @@ def track_to_mpd_format(track, position=None): return result -def metadata_track_to_mpd_format(track, metadata): - """ - Create new Track with a mix of track and metadata - and convert it to mpd format - """ - # Sanity check - if track is None or metadata is None: - return None - - # - if isinstance(track, TlTrack): - (tlid, track) = track - - track_kwargs = {k: v for k, v in track.__dict__.items() if v} - for k, v in metadata.__dict__.items(): - if v: - track_kwargs[k] = v - result_track = Track(**track_kwargs) - return track_to_mpd_format(result_track) - - def artists_to_mpd_format(artists): """ Format track artists for output to MPD client. From ef950a5e15c97eb1445a283afbc6760eb2ec7f73 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Tue, 20 Jan 2015 20:01:54 +0100 Subject: [PATCH 5/7] Adds audio in Core --- mopidy/commands.py | 6 +++--- mopidy/core/actor.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mopidy/commands.py b/mopidy/commands.py index fecabe98..d9b4ce0e 100644 --- a/mopidy/commands.py +++ b/mopidy/commands.py @@ -279,7 +279,7 @@ class RootCommand(Command): mixer = self.start_mixer(config, mixer_class) audio = self.start_audio(config, mixer) backends = self.start_backends(config, backend_classes, audio) - core = self.start_core(mixer, backends) + core = self.start_core(mixer, backends, audio) self.start_frontends(config, frontend_classes, core) loop.run() except (exceptions.BackendError, @@ -360,9 +360,9 @@ class RootCommand(Command): return backends - def start_core(self, mixer, backends): + def start_core(self, mixer, backends, audio): logger.info('Starting Mopidy core') - return Core.start(mixer=mixer, backends=backends).proxy() + return Core.start(mixer=mixer, backends=backends, audio=audio).proxy() def start_frontends(self, config, frontend_classes, core): logger.info( diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index dbcfc9a1..60de442a 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -42,7 +42,7 @@ class Core( """The tracklist controller. An instance of :class:`mopidy.core.TracklistController`.""" - def __init__(self, mixer=None, backends=None): + def __init__(self, mixer=None, backends=None, audio=None): super(Core, self).__init__() self.backends = Backends(backends) @@ -59,6 +59,8 @@ class Core( self.tracklist = TracklistController(core=self) + self.audio = audio + def get_uri_schemes(self): futures = [b.uri_schemes for b in self.backends] results = pykka.get_all(futures) @@ -105,20 +107,18 @@ class Core( CoreListener.send('mute_changed', mute=mute) def tags_changed(self, tags): - # Should return only one audio instance - audios = pykka.ActorRegistry.get_by_class(audio.Audio) - # Validity checks - if audios is None or len(audios) != 1: + if not self.audio: return if self.playback.current_tl_track is None: return - audio_proxy = audios[0].proxy() + tags = self.audio.get_current_tags().get() + if not tags: + return # Request available metadata and set a track - future = audio_proxy.get_current_tags() - mt_track = convert_tags_to_track(future.get()) + mt_track = convert_tags_to_track(tags) # Merge current_tl_track with metadata in current_md_track c_track = self.playback.current_tl_track.track From 64cab9ae95444fc96362b1775024647636a697d3 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Tue, 20 Jan 2015 21:50:02 +0100 Subject: [PATCH 6/7] Rename current_md_track to current_metadata_track --- mopidy/core/actor.py | 4 ++-- mopidy/core/playback.py | 4 ++-- mopidy/mpd/protocol/current_playlist.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index 60de442a..ff60f190 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -120,14 +120,14 @@ class Core( # Request available metadata and set a track mt_track = convert_tags_to_track(tags) - # Merge current_tl_track with metadata in current_md_track + # Merge current_tl_track with metadata in current_metadata_track c_track = self.playback.current_tl_track.track track_kwargs = {k: v for k, v in c_track.__dict__.items() if v} for k, v in mt_track.__dict__.items(): if v: track_kwargs[k] = v - self.playback.current_md_track = TlTrack(**{ + self.playback.current_metadata_track = TlTrack(**{ 'tlid': self.playback.current_tl_track.tlid, 'track': Track(**track_kwargs)}) diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index ad99e6ec..2bc2fbe6 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -127,9 +127,9 @@ class PlaybackController(object): """Mute state as a :class:`True` if muted, :class:`False` otherwise""" def get_current_metadata_track(self): - return self.current_md_track + return self.current_metadata_track - current_md_track = None + current_metadata_track = None """ The currently playing metadata :class:`mopidy.models.Track`, or :class:`None`. diff --git a/mopidy/mpd/protocol/current_playlist.py b/mopidy/mpd/protocol/current_playlist.py index bc89afcb..e083ea7c 100644 --- a/mopidy/mpd/protocol/current_playlist.py +++ b/mopidy/mpd/protocol/current_playlist.py @@ -283,7 +283,7 @@ def plchanges(context, version): elif iversion == tracklist_version: # If version are equals, it is just a metadata update # So we replace the updated track in playlist - current_md_track = context.core.playback.current_md_track.get() + current_md_track = context.core.playback.current_metadata_track.get() if current_md_track is None: return None From 735d1662dce784a783565682487e1fcda38e662b Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Tue, 20 Jan 2015 22:05:14 +0100 Subject: [PATCH 7/7] Makes mpd 'currentsong' send song with metadata --- mopidy/mpd/protocol/status.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mopidy/mpd/protocol/status.py b/mopidy/mpd/protocol/status.py index 9dae635e..eabb9317 100644 --- a/mopidy/mpd/protocol/status.py +++ b/mopidy/mpd/protocol/status.py @@ -34,7 +34,9 @@ def currentsong(context): Displays the song info of the current song (same song that is identified in status). """ - tl_track = context.core.playback.current_tl_track.get() + tl_track = context.core.playback.current_metadata_track.get() + if tl_track is None: + tl_track = context.core.playback.current_tl_track.get() if tl_track is not None: position = context.core.tracklist.index(tl_track).get() return translator.track_to_mpd_format(tl_track, position=position)