From a036e84a20c7430d42ba4d05110ce546bcf0ced1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 5 Dec 2013 22:43:27 +0100 Subject: [PATCH 1/6] audio: Update scanner to not use gst.Bus.poll Turns out poll sets up it's own mainloop in the default context causing us to segfault. --- mopidy/audio/scan.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index 12476c2c..6999d664 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -83,15 +83,14 @@ class Scanner(object): """Polls for messages to collect data.""" start = time.time() timeout_s = self._timeout_ms / float(1000) - poll_timeout_ns = 1000 data = {} while time.time() - start < timeout_s: - message = self._bus.poll(gst.MESSAGE_ANY, poll_timeout_ns) + if not self._bus.have_pending(): + continue + message = self._bus.pop() - if message is None: - pass # polling the bus timed out. - elif message.type == gst.MESSAGE_ERROR: + if message.type == gst.MESSAGE_ERROR: raise exceptions.ScannerError(message.parse_error()[0]) elif message.type == gst.MESSAGE_EOS: return data From 1379c38370770a994da67bbe48c2fff297f0b600 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 5 Dec 2013 22:56:19 +0100 Subject: [PATCH 2/6] audio: Improve audio_data_to_track handling. - Handle missing or none data for duration and mtime - Add organization, location and copyright mapping used for streams. --- mopidy/audio/scan.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index 6999d664..f797a84d 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -132,7 +132,7 @@ def audio_data_to_track(data): def _retrieve(source_key, target_key, target): if source_key in data: - target[target_key] = data[source_key] + target.setdefault(target_key, data[source_key]) _retrieve(gst.TAG_ALBUM, 'name', album_kwargs) _retrieve(gst.TAG_TRACK_COUNT, 'num_tracks', album_kwargs) @@ -155,6 +155,11 @@ def audio_data_to_track(data): _retrieve( 'musicbrainz-albumartistid', 'musicbrainz_id', albumartist_kwargs) + # For streams, will not override if a better value has already been set. + _retrieve(gst.TAG_ORGANIZATION, 'name', track_kwargs) + _retrieve(gst.TAG_LOCATION, 'comment', track_kwargs) + _retrieve(gst.TAG_COPYRIGHT, 'comment', track_kwargs) + if gst.TAG_DATE in data and data[gst.TAG_DATE]: date = data[gst.TAG_DATE] try: @@ -167,9 +172,13 @@ def audio_data_to_track(data): if albumartist_kwargs: album_kwargs['artists'] = [Artist(**albumartist_kwargs)] + if data['mtime']: + track_kwargs['last_modified'] = int(data['mtime']) + + if data[gst.TAG_DURATION]: + track_kwargs['length'] = data[gst.TAG_DURATION] // gst.MSECOND + track_kwargs['uri'] = data['uri'] - track_kwargs['last_modified'] = int(data['mtime']) - track_kwargs['length'] = data[gst.TAG_DURATION] // gst.MSECOND track_kwargs['album'] = Album(**album_kwargs) if ('name' in artist_kwargs From d9b704d0d888328ffe7b1f7107f9a9623047dc25 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 5 Dec 2013 22:58:48 +0100 Subject: [PATCH 3/6] streaming: Add scanner lookup of stream metadata. This adds support for looking up metada for all any any protocols the streaming backend will support. This should also ensure that file:// files get metadata. --- mopidy/backends/stream/__init__.py | 2 ++ mopidy/backends/stream/actor.py | 26 +++++++++++++++++--------- mopidy/backends/stream/ext.conf | 1 + 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/mopidy/backends/stream/__init__.py b/mopidy/backends/stream/__init__.py index 061ac5d0..28e2deba 100644 --- a/mopidy/backends/stream/__init__.py +++ b/mopidy/backends/stream/__init__.py @@ -19,6 +19,8 @@ class Extension(ext.Extension): def get_config_schema(self): schema = super(Extension, self).get_config_schema() schema['protocols'] = config.List() + schema['timeout'] = config.Integer( + minimum=1000, maximum=1000*60*60) return schema def validate_environment(self): diff --git a/mopidy/backends/stream/actor.py b/mopidy/backends/stream/actor.py index 86df447d..49034191 100644 --- a/mopidy/backends/stream/actor.py +++ b/mopidy/backends/stream/actor.py @@ -5,7 +5,8 @@ import urlparse import pykka -from mopidy import audio as audio_lib +from mopidy import audio as audio_lib, exceptions +from mopidy.audio import scan from mopidy.backends import base from mopidy.models import Track @@ -16,7 +17,8 @@ class StreamBackend(pykka.ThreadingActor, base.Backend): def __init__(self, config, audio): super(StreamBackend, self).__init__() - self.library = StreamLibraryProvider(backend=self) + self.library = StreamLibraryProvider( + backend=self, timeout=config['stream']['timeout']) self.playback = base.BasePlaybackProvider(audio=audio, backend=self) self.playlists = None @@ -24,14 +26,20 @@ class StreamBackend(pykka.ThreadingActor, base.Backend): config['stream']['protocols']) -# TODO: Should we consider letting lookup know how to expand common playlist -# formats (m3u, pls, etc) for http(s) URIs? class StreamLibraryProvider(base.BaseLibraryProvider): + def __init__(self, backend, timeout): + super(StreamLibraryProvider, self).__init__(backend) + self._scanner = scan.Scanner(min_duration=None, timeout=timeout) + def lookup(self, uri): if urlparse.urlsplit(uri).scheme not in self.backend.uri_schemes: return [] - # TODO: actually lookup the stream metadata by getting tags in same - # way as we do for updating the local library with mopidy.scanner - # Note that we would only want the stream metadata at this stage, - # not the currently playing track's. - return [Track(uri=uri, name=uri)] + + try: + data = self._scanner.scan(uri) + track = scan.audio_data_to_track(data) + except exceptions.ScannerError as e: + logger.warning('Problem looking up %s - %s', uri, e) + track = Track(uri=uri, name=uri) + + return [track] diff --git a/mopidy/backends/stream/ext.conf b/mopidy/backends/stream/ext.conf index dc0287da..811dec88 100644 --- a/mopidy/backends/stream/ext.conf +++ b/mopidy/backends/stream/ext.conf @@ -8,3 +8,4 @@ protocols = rtmp rtmps rtsp +timeout = 5000 From e37b1a17548f47b3c672421ebac4d24fb48a6dfe Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 5 Dec 2013 23:01:57 +0100 Subject: [PATCH 4/6] docs: Add streaming metadata lookup to changelog --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 395e968b..09b7840c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -25,6 +25,10 @@ a temporary regression of :issue:`527`. - Added support for deprecated config values in order to allow for graceful removal of :confval:`local/tag_cache_file`. +**Streaming backend** + +- Live lookup of URI metadata has been added. (Fixes :issue:`540`) + **Internal changes** - Events from the audio actor, backends, and core actor are now emitted From 7c7db636595443ba4b54b5601dd7b2e0fbf56955 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 5 Dec 2013 23:03:50 +0100 Subject: [PATCH 5/6] docs: Add stream/timeout config value to docs --- docs/ext/stream.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ext/stream.rst b/docs/ext/stream.rst index ee413b31..30bc22ab 100644 --- a/docs/ext/stream.rst +++ b/docs/ext/stream.rst @@ -42,6 +42,10 @@ Configuration values Whitelist of URI schemas to allow streaming from. Values should be separated by either comma or newline. +.. confval:: stream/timeout + + Number of milliseconds before giving up looking up stream metadata. + Usage ===== From 0fac8120d4466d88e5499be8f7a17959d915b6a7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 6 Dec 2013 00:04:36 +0100 Subject: [PATCH 6/6] streaming: Code review adjustments --- mopidy/backends/stream/__init__.py | 2 +- mopidy/backends/stream/actor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/stream/__init__.py b/mopidy/backends/stream/__init__.py index 28e2deba..47dd6151 100644 --- a/mopidy/backends/stream/__init__.py +++ b/mopidy/backends/stream/__init__.py @@ -20,7 +20,7 @@ class Extension(ext.Extension): schema = super(Extension, self).get_config_schema() schema['protocols'] = config.List() schema['timeout'] = config.Integer( - minimum=1000, maximum=1000*60*60) + minimum=1000, maximum=1000 * 60 * 60) return schema def validate_environment(self): diff --git a/mopidy/backends/stream/actor.py b/mopidy/backends/stream/actor.py index 49034191..c807e09d 100644 --- a/mopidy/backends/stream/actor.py +++ b/mopidy/backends/stream/actor.py @@ -39,7 +39,7 @@ class StreamLibraryProvider(base.BaseLibraryProvider): data = self._scanner.scan(uri) track = scan.audio_data_to_track(data) except exceptions.ScannerError as e: - logger.warning('Problem looking up %s - %s', uri, e) + logger.warning('Problem looking up %s: %s', uri, e) track = Track(uri=uri, name=uri) return [track]