Merge pull request #603 from adamcik/feature/lookup-stream-metadata
Add metadata lookup to streaming backend.
This commit is contained in:
commit
1a032c5f92
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
=====
|
=====
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -8,3 +8,4 @@ protocols =
|
|||||||
rtmp
|
rtmp
|
||||||
rtmps
|
rtmps
|
||||||
rtsp
|
rtsp
|
||||||
|
timeout = 5000
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user