Merge pull request #603 from adamcik/feature/lookup-stream-metadata

Add metadata lookup to streaming backend.
This commit is contained in:
Thomas Adamcik 2013-12-05 15:06:01 -08:00
commit 1a032c5f92
6 changed files with 44 additions and 17 deletions

View File

@ -25,6 +25,10 @@ a temporary regression of :issue:`527`.
- Added support for deprecated config values in order to allow for - Added support for deprecated config values in order to allow for
graceful removal of :confval:`local/tag_cache_file`. graceful removal of :confval:`local/tag_cache_file`.
**Streaming backend**
- Live lookup of URI metadata has been added. (Fixes :issue:`540`)
**Internal changes** **Internal changes**
- Events from the audio actor, backends, and core actor are now emitted - Events from the audio actor, backends, and core actor are now emitted

View File

@ -42,6 +42,10 @@ Configuration values
Whitelist of URI schemas to allow streaming from. Values should be Whitelist of URI schemas to allow streaming from. Values should be
separated by either comma or newline. separated by either comma or newline.
.. confval:: stream/timeout
Number of milliseconds before giving up looking up stream metadata.
Usage Usage
===== =====

View File

@ -83,15 +83,14 @@ class Scanner(object):
"""Polls for messages to collect data.""" """Polls for messages to collect data."""
start = time.time() start = time.time()
timeout_s = self._timeout_ms / float(1000) timeout_s = self._timeout_ms / float(1000)
poll_timeout_ns = 1000
data = {} data = {}
while time.time() - start < timeout_s: 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: if message.type == gst.MESSAGE_ERROR:
pass # polling the bus timed out.
elif message.type == gst.MESSAGE_ERROR:
raise exceptions.ScannerError(message.parse_error()[0]) raise exceptions.ScannerError(message.parse_error()[0])
elif message.type == gst.MESSAGE_EOS: elif message.type == gst.MESSAGE_EOS:
return data return data
@ -133,7 +132,7 @@ def audio_data_to_track(data):
def _retrieve(source_key, target_key, target): def _retrieve(source_key, target_key, target):
if source_key in data: 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_ALBUM, 'name', album_kwargs)
_retrieve(gst.TAG_TRACK_COUNT, 'num_tracks', album_kwargs) _retrieve(gst.TAG_TRACK_COUNT, 'num_tracks', album_kwargs)
@ -156,6 +155,11 @@ def audio_data_to_track(data):
_retrieve( _retrieve(
'musicbrainz-albumartistid', 'musicbrainz_id', albumartist_kwargs) '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]: if gst.TAG_DATE in data and data[gst.TAG_DATE]:
date = data[gst.TAG_DATE] date = data[gst.TAG_DATE]
try: try:
@ -168,9 +172,13 @@ def audio_data_to_track(data):
if albumartist_kwargs: if albumartist_kwargs:
album_kwargs['artists'] = [Artist(**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['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) track_kwargs['album'] = Album(**album_kwargs)
if ('name' in artist_kwargs if ('name' in artist_kwargs

View File

@ -19,6 +19,8 @@ class Extension(ext.Extension):
def get_config_schema(self): def get_config_schema(self):
schema = super(Extension, self).get_config_schema() schema = super(Extension, self).get_config_schema()
schema['protocols'] = config.List() schema['protocols'] = config.List()
schema['timeout'] = config.Integer(
minimum=1000, maximum=1000 * 60 * 60)
return schema return schema
def validate_environment(self): def validate_environment(self):

View File

@ -5,7 +5,8 @@ import urlparse
import pykka 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.backends import base
from mopidy.models import Track from mopidy.models import Track
@ -16,7 +17,8 @@ class StreamBackend(pykka.ThreadingActor, base.Backend):
def __init__(self, config, audio): def __init__(self, config, audio):
super(StreamBackend, self).__init__() 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.playback = base.BasePlaybackProvider(audio=audio, backend=self)
self.playlists = None self.playlists = None
@ -24,14 +26,20 @@ class StreamBackend(pykka.ThreadingActor, base.Backend):
config['stream']['protocols']) 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): 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): def lookup(self, uri):
if urlparse.urlsplit(uri).scheme not in self.backend.uri_schemes: if urlparse.urlsplit(uri).scheme not in self.backend.uri_schemes:
return [] return []
# TODO: actually lookup the stream metadata by getting tags in same
# way as we do for updating the local library with mopidy.scanner try:
# Note that we would only want the stream metadata at this stage, data = self._scanner.scan(uri)
# not the currently playing track's. track = scan.audio_data_to_track(data)
return [Track(uri=uri, name=uri)] except exceptions.ScannerError as e:
logger.warning('Problem looking up %s: %s', uri, e)
track = Track(uri=uri, name=uri)
return [track]

View File

@ -8,3 +8,4 @@ protocols =
rtmp rtmp
rtmps rtmps
rtsp rtsp
timeout = 5000