Files
mopidy-radionet/venv/lib/python3.7/site-packages/mopidy/stream/actor.py
2020-01-18 20:01:00 +01:00

188 lines
5.8 KiB
Python

import fnmatch
import logging
import re
import time
import urllib
import pykka
from mopidy import audio as audio_lib
from mopidy import backend, exceptions, stream
from mopidy.audio import scan, tags
from mopidy.internal import http, playlists
from mopidy.models import Track
logger = logging.getLogger(__name__)
class StreamBackend(pykka.ThreadingActor, backend.Backend):
def __init__(self, config, audio):
super().__init__()
self._scanner = scan.Scanner(
timeout=config["stream"]["timeout"], proxy_config=config["proxy"]
)
self._session = http.get_requests_session(
proxy_config=config["proxy"],
user_agent=(
f"{stream.Extension.dist_name}/{stream.Extension.version}"
),
)
blacklist = config["stream"]["metadata_blacklist"]
self._blacklist_re = re.compile(
r"^(%s)$" % "|".join(fnmatch.translate(u) for u in blacklist)
)
self._timeout = config["stream"]["timeout"]
self.library = StreamLibraryProvider(backend=self)
self.playback = StreamPlaybackProvider(audio=audio, backend=self)
self.playlists = None
self.uri_schemes = audio_lib.supported_uri_schemes(
config["stream"]["protocols"]
)
if "file" in self.uri_schemes and config["file"]["enabled"]:
logger.warning(
'The stream/protocols config value includes the "file" '
'protocol. "file" playback is now handled by Mopidy-File. '
"Please remove it from the stream/protocols config."
)
self.uri_schemes -= {"file"}
class StreamLibraryProvider(backend.LibraryProvider):
def lookup(self, uri):
if urllib.parse.urlsplit(uri).scheme not in self.backend.uri_schemes:
return []
if self.backend._blacklist_re.match(uri):
logger.debug("URI matched metadata lookup blacklist: %s", uri)
return [Track(uri=uri)]
_, scan_result = _unwrap_stream(
uri,
timeout=self.backend._timeout,
scanner=self.backend._scanner,
requests_session=self.backend._session,
)
if scan_result:
track = tags.convert_tags_to_track(scan_result.tags).replace(
uri=uri, length=scan_result.duration
)
else:
logger.warning("Problem looking up %s", uri)
track = Track(uri=uri)
return [track]
class StreamPlaybackProvider(backend.PlaybackProvider):
def translate_uri(self, uri):
if urllib.parse.urlsplit(uri).scheme not in self.backend.uri_schemes:
return None
if self.backend._blacklist_re.match(uri):
logger.debug("URI matched metadata lookup blacklist: %s", uri)
return uri
unwrapped_uri, _ = _unwrap_stream(
uri,
timeout=self.backend._timeout,
scanner=self.backend._scanner,
requests_session=self.backend._session,
)
return unwrapped_uri
# TODO: cleanup the return value of this.
def _unwrap_stream(uri, timeout, scanner, requests_session):
"""
Get a stream URI from a playlist URI, ``uri``.
Unwraps nested playlists until something that's not a playlist is found or
the ``timeout`` is reached.
"""
original_uri = uri
seen_uris = set()
deadline = time.time() + timeout
while time.time() < deadline:
if uri in seen_uris:
logger.info(
"Unwrapping stream from URI (%s) failed: "
"playlist referenced itself",
uri,
)
return None, None
else:
seen_uris.add(uri)
logger.debug("Unwrapping stream from URI: %s", uri)
try:
scan_timeout = deadline - time.time()
if scan_timeout < 0:
logger.info(
"Unwrapping stream from URI (%s) failed: "
"timed out in %sms",
uri,
timeout,
)
return None, None
scan_result = scanner.scan(uri, timeout=scan_timeout)
except exceptions.ScannerError as exc:
logger.debug("GStreamer failed scanning URI (%s): %s", uri, exc)
scan_result = None
if scan_result is not None:
has_interesting_mime = (
scan_result.mime is not None
and not scan_result.mime.startswith("text/")
and not scan_result.mime.startswith("application/")
)
if scan_result.playable or has_interesting_mime:
logger.debug(
"Unwrapped potential %s stream: %s", scan_result.mime, uri
)
return uri, scan_result
download_timeout = deadline - time.time()
if download_timeout < 0:
logger.info(
"Unwrapping stream from URI (%s) failed: timed out in %sms",
uri,
timeout,
)
return None, None
content = http.download(
requests_session, uri, timeout=download_timeout / 1000
)
if content is None:
logger.info(
"Unwrapping stream from URI (%s) failed: "
"error downloading URI %s",
original_uri,
uri,
)
return None, None
uris = playlists.parse(content)
if not uris:
logger.debug(
"Failed parsing URI (%s) as playlist; found potential stream.",
uri,
)
return uri, None
# TODO Test streams and return first that seems to be playable
new_uri = uris[0]
logger.debug("Parsed playlist (%s) and found new URI: %s", uri, new_uri)
uri = urllib.parse.urljoin(uri, new_uri)