From fa3b21216d1a3b937e926d289f8f18f8277b6cc7 Mon Sep 17 00:00:00 2001 From: Max Goltzsche Date: Wed, 27 Mar 2024 00:26:05 +0100 Subject: [PATCH] feat: add coverart support --- mopidy_subidy/__init__.py | 5 +++ mopidy_subidy/library.py | 11 ++++- mopidy_subidy/web.py | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 mopidy_subidy/web.py diff --git a/mopidy_subidy/__init__.py b/mopidy_subidy/__init__.py index d9f260a..4be6243 100644 --- a/mopidy_subidy/__init__.py +++ b/mopidy_subidy/__init__.py @@ -27,5 +27,10 @@ class SubidyExtension(ext.Extension): def setup(self, registry): from .backend import SubidyBackend + from .web import image_proxy_factory registry.add("backend", SubidyBackend) + registry.add("http:app", { + "name": self.ext_name, + "factory": image_proxy_factory, + }) diff --git a/mopidy_subidy/library.py b/mopidy_subidy/library.py index bc5e7c2..9222e49 100644 --- a/mopidy_subidy/library.py +++ b/mopidy_subidy/library.py @@ -1,7 +1,7 @@ import logging from mopidy import backend -from mopidy.models import Ref, SearchResult +from mopidy.models import Image, Ref, SearchResult from mopidy_subidy import uri logger = logging.getLogger(__name__) @@ -208,3 +208,12 @@ class SubidyLibraryProvider(backend.LibraryProvider): if "any" in query: return self.subsonic_api.find_as_search_result(query.get("any")[0]) return SearchResult(artists=self.subsonic_api.get_artists_as_artists()) + + def get_images(self, uris): + images = {} + for uri in uris: + if uri.startswith("subidy:album:"): + images[uri] = [Image(uri=f"/subidy/coverart/2{uri[14:]}")] + elif uri.startswith("subidy:song:"): + images[uri] = [Image(uri=f"/subidy/coverart/3{uri[13:]}")] + return images diff --git a/mopidy_subidy/web.py b/mopidy_subidy/web.py new file mode 100644 index 0000000..998ee99 --- /dev/null +++ b/mopidy_subidy/web.py @@ -0,0 +1,90 @@ +import os +import logging + +import tornado.web +import tornado.httpclient + +from urllib.parse import urlparse + +logger = logging.getLogger('subidy-web') + + +def image_proxy_factory(config, core): + return [ + (r"/coverart/(?P.+)", ImageProxyHandler, {"core": core, "config": config}) + ] + +class ImageProxyHandler(tornado.web.RequestHandler): + def initialize(self, core, config): + self.core = core + + subidy_config = config["subidy"] + self._subsonic_url=subidy_config["url"] + self._subsonic_username=subidy_config["username"] + self._subsonic_password=subidy_config["password"] + + async def get(self, **kwargs): + id = kwargs.get('id') + + logger.debug('Handle coverart %s request', id) + + def handle_response(response): + if (response.error and not + isinstance(response.error, tornado.httpclient.HTTPError)): + self.set_status(500) + self.write('Internal server error:\n' + str(response.error)) + else: + self.set_status(response.code, response.reason) + self._headers = tornado.httputil.HTTPHeaders() # clear tornado default header + + for header, v in response.headers.get_all(): + if header not in ('Content-Length', 'Transfer-Encoding', 'Content-Encoding', 'Connection'): + self.add_header(header, v) # some header appear multiple times, eg 'Set-Cookie' + + if response.body: + self.set_header('Content-Length', len(response.body)) + self.write(response.body) + self.finish() + + try: + if 'Proxy-Connection' in self.request.headers: + del self.request.headers['Proxy-Connection'] + resp = await fetch_request( + f"{self._subsonic_url}/rest/getCoverArt?id={id}", + method=self.request.method, headers=self.request.headers, + auth_username=self._subsonic_username, + auth_password=self._subsonic_password, + follow_redirects=False, allow_nonstandard_methods=False) + handle_response(resp) + except tornado.httpclient.HTTPError as e: + if hasattr(e, 'response') and e.response: + handle_response(e.response) + else: + self.set_status(500) + self.write('Internal server error:\n' + str(e)) + self.finish() + +def get_proxy(url): + url_parsed = urlparse(url, scheme='http') + proxy_key = '%s_proxy' % url_parsed.scheme + return os.environ.get(proxy_key) + + +def parse_proxy(proxy): + proxy_parsed = urlparse(proxy, scheme='http') + return proxy_parsed.hostname, proxy_parsed.port + + +def fetch_request(url, **kwargs): + proxy = get_proxy(url) + if proxy: + logger.debug('Forward request via upstream proxy %s', proxy) + tornado.httpclient.AsyncHTTPClient.configure( + 'tornado.curl_httpclient.CurlAsyncHTTPClient') + host, port = parse_proxy(proxy) + kwargs['proxy_host'] = host + kwargs['proxy_port'] = port + + req = tornado.httpclient.HTTPRequest(url, **kwargs) + client = tornado.httpclient.AsyncHTTPClient() + return client.fetch(req, raise_error=False)