Merge branch '189-use-supported-schemas'
This commit is contained in:
@@ -19,6 +19,8 @@ v0.11.1
|
||||
* Fixed issue where users couldn't log in to LMS due to Sublime Music always
|
||||
sending version number "1.15.0" instead of figuring out what version of the
|
||||
API the server actually reports.
|
||||
* Fixed issue where edits to the music provider configurations were applied even
|
||||
if ESC was pressed. (#247)
|
||||
|
||||
v0.11.0
|
||||
=======
|
||||
|
@@ -438,7 +438,6 @@ class Adapter(abc.ABC):
|
||||
Examples of values that could be provided include ``http``, ``https``, ``file``,
|
||||
or ``ftp``.
|
||||
"""
|
||||
# TODO (#189) actually use this
|
||||
return ()
|
||||
|
||||
@property
|
||||
@@ -446,18 +445,19 @@ class Adapter(abc.ABC):
|
||||
"""
|
||||
Whether or not the adapter supports :class:`get_cover_art_uri`.
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def can_stream(self) -> bool:
|
||||
def can_get_song_file_uri(self) -> bool:
|
||||
"""
|
||||
Whether or not the adapter can provide a stream URI.
|
||||
Whether or not the adapter supports :class:`get_song_file_uri`.
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def can_get_song_uri(self) -> bool:
|
||||
def can_get_song_stream_uri(self) -> bool:
|
||||
"""
|
||||
Whether or not the adapter supports :class:`get_song_uri`.
|
||||
Whether or not the adapter supports :class:`get_song_stream_uri`.
|
||||
"""
|
||||
return False
|
||||
|
||||
@@ -645,20 +645,26 @@ class Adapter(abc.ABC):
|
||||
"""
|
||||
raise self._check_can_error("get_cover_art_uri")
|
||||
|
||||
def get_song_uri(self, song_id: str, scheme: str, stream: bool = False) -> str:
|
||||
def get_song_file_uri(self, song_id: str, schemes: Iterable[str]) -> str:
|
||||
"""
|
||||
Get a URI for a given song.
|
||||
Get a URI for a given song. This URI must give the full file.
|
||||
|
||||
:param song_id: The ID of the song to get a URI for.
|
||||
:param scheme: The URI scheme that should be returned. It is guaranteed that
|
||||
``scheme`` will be one of the schemes returned by
|
||||
:param schemes: A set of URI schemes that can be returned. It is guaranteed that
|
||||
all of the items in ``schemes`` will be one of the schemes returned by
|
||||
:class:`supported_schemes`.
|
||||
:param stream: Whether or not the URI returned should be a stream URI. This will
|
||||
only be ``True`` if :class:`supports_streaming` returns ``True``.
|
||||
:returns: The URI for the given song.
|
||||
"""
|
||||
# TODO (#189)
|
||||
raise self._check_can_error("get_song_uri")
|
||||
raise self._check_can_error("get_song_file_uri")
|
||||
|
||||
def get_song_stream_uri(self, song_id: str) -> str:
|
||||
"""
|
||||
Get a URI for streaming the given song.
|
||||
|
||||
:param song_id: The ID of the song to get the stream URI for.
|
||||
:returns: the stream URI for the given song.
|
||||
"""
|
||||
raise self._check_can_error("get_song_stream_uri")
|
||||
|
||||
def get_song_details(self, song_id: str) -> Song:
|
||||
"""
|
||||
|
@@ -101,7 +101,7 @@ class FilesystemAdapter(CachingAdapter):
|
||||
|
||||
# TODO (#200) make these dependent on cache state. Need to do this kinda efficiently
|
||||
can_get_cover_art_uri = True
|
||||
can_get_song_uri = True
|
||||
can_get_song_file_uri = True
|
||||
can_get_song_details = True
|
||||
can_get_artist = True
|
||||
can_get_albums = True
|
||||
@@ -286,7 +286,7 @@ class FilesystemAdapter(CachingAdapter):
|
||||
|
||||
raise CacheMissError()
|
||||
|
||||
def get_song_uri(self, song_id: str, scheme: str, stream: bool = False) -> str:
|
||||
def get_song_file_uri(self, song_id: str, schemes: Iterable[str]) -> str:
|
||||
song = models.Song.get_or_none(models.Song.id == song_id)
|
||||
if not song:
|
||||
if self.is_cache:
|
||||
|
@@ -539,17 +539,6 @@ class AdapterManager:
|
||||
|
||||
return future_finished
|
||||
|
||||
@staticmethod
|
||||
def _get_scheme() -> str:
|
||||
# TODO (#189): eventually this will come from the players
|
||||
assert AdapterManager._instance
|
||||
scheme_priority = ("https", "http")
|
||||
schemes = sorted(
|
||||
AdapterManager._instance.ground_truth_adapter.supported_schemes,
|
||||
key=scheme_priority.index,
|
||||
)
|
||||
return list(schemes)[0]
|
||||
|
||||
@staticmethod
|
||||
def get_supported_artist_query_types() -> Set[AlbumSearchQuery.Type]:
|
||||
assert AdapterManager._instance
|
||||
@@ -666,13 +655,17 @@ class AdapterManager:
|
||||
return AdapterManager._ground_truth_can_do("delete_playlist")
|
||||
|
||||
@staticmethod
|
||||
def can_get_song_filename_or_stream() -> bool:
|
||||
return AdapterManager._ground_truth_can_do("get_song_uri")
|
||||
def can_get_song_file_uri() -> bool:
|
||||
return AdapterManager._ground_truth_can_do("get_song_file_uri")
|
||||
|
||||
@staticmethod
|
||||
def can_get_song_stream_uri() -> bool:
|
||||
return AdapterManager._ground_truth_can_do("get_song_stream_uri")
|
||||
|
||||
@staticmethod
|
||||
def can_batch_download_songs() -> bool:
|
||||
# We can only download from the ground truth adapter.
|
||||
return AdapterManager._ground_truth_can_do("get_song_uri")
|
||||
return AdapterManager._ground_truth_can_do("get_song_file_uri")
|
||||
|
||||
@staticmethod
|
||||
def can_get_genres() -> bool:
|
||||
@@ -805,122 +798,133 @@ class AdapterManager:
|
||||
CachingAdapter.CachedDataKey.PLAYLIST_DETAILS, playlist_id
|
||||
)
|
||||
|
||||
# TODO (#189): allow this to take a set of schemes and unify with
|
||||
# get_cover_art_filename
|
||||
@staticmethod
|
||||
def get_cover_art_uri(cover_art_id: str = None, size: int = 300) -> str:
|
||||
def _get_networked_scheme() -> str:
|
||||
assert AdapterManager._instance
|
||||
networked_scheme_priority = ("https", "http")
|
||||
return sorted(
|
||||
AdapterManager._instance.ground_truth_adapter.supported_schemes,
|
||||
key=lambda s: networked_scheme_priority.index(s),
|
||||
)[0]
|
||||
|
||||
@staticmethod
|
||||
def get_cover_art_uri(
|
||||
cover_art_id: Optional[str],
|
||||
scheme: str,
|
||||
size: int = 300,
|
||||
before_download: Callable[[], None] = None,
|
||||
force: bool = False,
|
||||
allow_download: bool = True,
|
||||
) -> Result[str]:
|
||||
existing_filename = str(
|
||||
Path(__file__).parent.joinpath("images/default-album-art.png")
|
||||
)
|
||||
if (
|
||||
not AdapterManager._ground_truth_can_do("get_cover_art_uri")
|
||||
or not cover_art_id
|
||||
):
|
||||
return ""
|
||||
|
||||
return AdapterManager._instance.ground_truth_adapter.get_cover_art_uri(
|
||||
cover_art_id, AdapterManager._get_scheme(), size=size
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_cover_art_filename(
|
||||
cover_art_id: str = None,
|
||||
before_download: Callable[[], None] = None,
|
||||
force: bool = False, # TODO (#202): rename to use_ground_truth_adapter?
|
||||
allow_download: bool = True,
|
||||
) -> Result[str]:
|
||||
existing_cover_art_filename = str(
|
||||
Path(__file__).parent.joinpath("images/default-album-art.png")
|
||||
)
|
||||
if cover_art_id is None:
|
||||
return Result(existing_cover_art_filename)
|
||||
return Result(existing_filename if scheme == "file" else "")
|
||||
|
||||
assert AdapterManager._instance
|
||||
|
||||
# If the ground truth adapter can't provide cover art, just give up immediately.
|
||||
if not AdapterManager._ground_truth_can_do("get_cover_art_uri"):
|
||||
return Result(existing_cover_art_filename)
|
||||
|
||||
# There could be partial data if the cover art exists, but for some reason was
|
||||
# marked out-of-date.
|
||||
if AdapterManager._can_use_cache(force, "get_cover_art_uri"):
|
||||
assert AdapterManager._instance.caching_adapter
|
||||
try:
|
||||
return Result(
|
||||
AdapterManager._instance.caching_adapter.get_cover_art_uri(
|
||||
cover_art_id, "file", size=300
|
||||
)
|
||||
)
|
||||
except CacheMissError as e:
|
||||
if e.partial_data is not None:
|
||||
existing_cover_art_filename = cast(str, e.partial_data)
|
||||
logging.info(f'Cache Miss on {"get_cover_art_uri"}.')
|
||||
except Exception:
|
||||
logging.exception(
|
||||
f'Error on {"get_cover_art_uri"} retrieving from cache.'
|
||||
)
|
||||
|
||||
if AdapterManager._instance.caching_adapter and force:
|
||||
AdapterManager._instance.caching_adapter.invalidate_data(
|
||||
CachingAdapter.CachedDataKey.COVER_ART_FILE, cover_art_id
|
||||
)
|
||||
|
||||
if not allow_download or (
|
||||
AdapterManager._offline_mode
|
||||
and AdapterManager._instance.ground_truth_adapter.is_networked
|
||||
):
|
||||
return Result(existing_cover_art_filename)
|
||||
|
||||
future: Result[str] = AdapterManager._create_download_result(
|
||||
AdapterManager._instance.ground_truth_adapter.get_cover_art_uri(
|
||||
cover_art_id, AdapterManager._get_scheme(), size=300
|
||||
),
|
||||
cover_art_id,
|
||||
before_download,
|
||||
default_value=existing_cover_art_filename,
|
||||
supported_schemes = (
|
||||
AdapterManager._instance.ground_truth_adapter.supported_schemes
|
||||
)
|
||||
|
||||
if AdapterManager._instance.caching_adapter:
|
||||
future.add_done_callback(
|
||||
AdapterManager._create_caching_done_callback(
|
||||
# If the scheme is supported natively, then return it.
|
||||
if scheme in supported_schemes:
|
||||
uri = AdapterManager._instance.ground_truth_adapter.get_cover_art_uri(
|
||||
cover_art_id, scheme, size=size
|
||||
)
|
||||
return Result(uri)
|
||||
|
||||
# If the scheme is "file", then we may need to try to download.
|
||||
if scheme == "file" and (
|
||||
"http" in supported_schemes or "https" in supported_schemes
|
||||
):
|
||||
if AdapterManager._can_use_cache(force, "get_cover_art_uri"):
|
||||
assert AdapterManager._instance.caching_adapter
|
||||
try:
|
||||
return Result(
|
||||
AdapterManager._instance.caching_adapter.get_cover_art_uri(
|
||||
cover_art_id, "file", size=size
|
||||
)
|
||||
)
|
||||
except CacheMissError as e:
|
||||
if e.partial_data is not None:
|
||||
existing_filename = cast(str, e.partial_data)
|
||||
logging.info("Cache Miss on get_cover_art_uri.")
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Error on get_cover_art_uri retrieving from cache."
|
||||
)
|
||||
|
||||
# If we are forcing, invalidate the existing cached data.
|
||||
if AdapterManager._instance.caching_adapter and force:
|
||||
AdapterManager._instance.caching_adapter.invalidate_data(
|
||||
CachingAdapter.CachedDataKey.COVER_ART_FILE, cover_art_id
|
||||
)
|
||||
|
||||
if not allow_download or (
|
||||
AdapterManager._offline_mode
|
||||
and AdapterManager._instance.ground_truth_adapter.is_networked
|
||||
):
|
||||
return Result(existing_filename)
|
||||
|
||||
# Create a download result.
|
||||
future = AdapterManager._create_download_result(
|
||||
AdapterManager._instance.ground_truth_adapter.get_cover_art_uri(
|
||||
cover_art_id, AdapterManager._get_networked_scheme(), size=size,
|
||||
),
|
||||
cover_art_id,
|
||||
before_download,
|
||||
default_value=existing_filename,
|
||||
)
|
||||
|
||||
return future
|
||||
if AdapterManager._instance.caching_adapter:
|
||||
future.add_done_callback(
|
||||
AdapterManager._create_caching_done_callback(
|
||||
CachingAdapter.CachedDataKey.COVER_ART_FILE, cover_art_id
|
||||
)
|
||||
)
|
||||
|
||||
return future
|
||||
|
||||
return Result("")
|
||||
|
||||
# TODO (#189): allow this to take a set of schemes
|
||||
@staticmethod
|
||||
def get_song_filename_or_stream(
|
||||
song: Song, format: str = None, force_stream: bool = False
|
||||
) -> str:
|
||||
def get_song_file_uri(song: Song) -> str:
|
||||
assert AdapterManager._instance
|
||||
cached_song_filename = None
|
||||
if AdapterManager._can_use_cache(force_stream, "get_song_uri"):
|
||||
assert AdapterManager._instance.caching_adapter
|
||||
if AdapterManager._can_use_cache(False, "get_song_file_uri"):
|
||||
assert (caching_adapter := AdapterManager._instance.caching_adapter)
|
||||
try:
|
||||
return AdapterManager._instance.caching_adapter.get_song_uri(
|
||||
song.id, "file"
|
||||
)
|
||||
if "file" not in caching_adapter.supported_schemes:
|
||||
raise Exception("file not a supported scheme")
|
||||
|
||||
return caching_adapter.get_song_file_uri(song.id, "file")
|
||||
except CacheMissError as e:
|
||||
if e.partial_data is not None:
|
||||
cached_song_filename = cast(str, e.partial_data)
|
||||
logging.info(f'Cache Miss on {"get_song_filename_or_stream"}.')
|
||||
logging.info("Cache Miss on get_song_file_uri.")
|
||||
except Exception:
|
||||
logging.exception(
|
||||
f'Error on {"get_song_filename_or_stream"} retrieving from cache.'
|
||||
)
|
||||
logging.exception("Error on get_song_file_uri retrieving from cache.")
|
||||
|
||||
ground_truth_adapter = AdapterManager._instance.ground_truth_adapter
|
||||
if (
|
||||
not AdapterManager._ground_truth_can_do("stream")
|
||||
or not AdapterManager._ground_truth_can_do("get_song_uri")
|
||||
or (
|
||||
AdapterManager._instance.ground_truth_adapter.is_networked
|
||||
and AdapterManager._offline_mode
|
||||
)
|
||||
not AdapterManager._ground_truth_can_do("get_song_file_uri")
|
||||
or (ground_truth_adapter.is_networked and AdapterManager._offline_mode)
|
||||
or ("file" not in ground_truth_adapter.supported_schemes)
|
||||
):
|
||||
raise CacheMissError(partial_data=cached_song_filename)
|
||||
|
||||
return AdapterManager._instance.ground_truth_adapter.get_song_uri(
|
||||
song.id, AdapterManager._get_scheme(), stream=True,
|
||||
return ground_truth_adapter.get_song_file_uri(song.id, "file")
|
||||
|
||||
@staticmethod
|
||||
def get_song_stream_uri(song: Song) -> str:
|
||||
assert AdapterManager._instance
|
||||
# TODO
|
||||
return AdapterManager._instance.ground_truth_adapter.get_song_stream_uri(
|
||||
song.id
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -968,7 +972,9 @@ class AdapterManager:
|
||||
# Download the actual song file.
|
||||
try:
|
||||
# If the song file is already cached, just indicate done immediately.
|
||||
AdapterManager._instance.caching_adapter.get_song_uri(song_id, "file")
|
||||
AdapterManager._instance.caching_adapter.get_song_file_uri(
|
||||
song_id, "file"
|
||||
)
|
||||
AdapterManager._instance.download_limiter_semaphore.release()
|
||||
AdapterManager._instance.song_download_progress(
|
||||
song_id, DownloadProgress(DownloadProgress.Type.DONE),
|
||||
@@ -984,8 +990,8 @@ class AdapterManager:
|
||||
song_tmp_filename_result: Result[
|
||||
str
|
||||
] = AdapterManager._create_download_result(
|
||||
AdapterManager._instance.ground_truth_adapter.get_song_uri(
|
||||
song_id, AdapterManager._get_scheme()
|
||||
AdapterManager._instance.ground_truth_adapter.get_song_file_uri(
|
||||
song_id, AdapterManager._get_networked_scheme()
|
||||
),
|
||||
song_id,
|
||||
lambda: before_download(song_id),
|
||||
|
@@ -281,7 +281,8 @@ class SubsonicAdapter(Adapter):
|
||||
can_get_playlist_details = True
|
||||
can_get_playlists = True
|
||||
can_get_song_details = True
|
||||
can_get_song_uri = True
|
||||
can_get_song_file_uri = True
|
||||
can_get_song_stream_uri = True
|
||||
can_scrobble_song = True
|
||||
can_search = True
|
||||
can_stream = True
|
||||
@@ -537,10 +538,14 @@ class SubsonicAdapter(Adapter):
|
||||
params = {"id": cover_art_id, "size": size, **self._get_params()}
|
||||
return self._make_url("getCoverArt") + "?" + urlencode(params)
|
||||
|
||||
def get_song_uri(self, song_id: str, scheme: str, stream: bool = False) -> str:
|
||||
def get_song_file_uri(self, song_id: str, schemes: Iterable[str]) -> str:
|
||||
assert any(s in schemes for s in self.supported_schemes)
|
||||
params = {"id": song_id, **self._get_params()}
|
||||
endpoint = "stream" if stream else "download"
|
||||
return self._make_url(endpoint) + "?" + urlencode(params)
|
||||
return self._make_url("download") + "?" + urlencode(params)
|
||||
|
||||
def get_song_stream_uri(self, song_id: str) -> str:
|
||||
params = {"id": song_id, **self._get_params()}
|
||||
return self._make_url("stream") + "?" + urlencode(params)
|
||||
|
||||
def get_song_details(self, song_id: str) -> API.Song:
|
||||
song = self._get_json(self._make_url("getSong"), id=song_id).song
|
||||
|
@@ -7,6 +7,7 @@ from datetime import timedelta
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
try:
|
||||
import osxmmkeys
|
||||
@@ -35,6 +36,7 @@ except Exception:
|
||||
from .adapters import (
|
||||
AdapterManager,
|
||||
AlbumSearchQuery,
|
||||
CacheMissError,
|
||||
DownloadProgress,
|
||||
Result,
|
||||
SongCacheStatus,
|
||||
@@ -469,8 +471,8 @@ class SublimeMusicApp(Gtk.Application):
|
||||
)
|
||||
|
||||
def make_playlist_tuple(p: Playlist) -> GLib.Variant:
|
||||
cover_art_filename = AdapterManager.get_cover_art_filename(
|
||||
p.cover_art, allow_download=False,
|
||||
cover_art_filename = AdapterManager.get_cover_art_uri(
|
||||
p.cover_art, "file", allow_download=False,
|
||||
).result()
|
||||
return (f"/playlist/{p.id}", p.name, cover_art_filename or "")
|
||||
|
||||
@@ -1112,9 +1114,27 @@ class SublimeMusicApp(Gtk.Application):
|
||||
if order_token != self.song_playing_order_token:
|
||||
return
|
||||
|
||||
# TODO (#189): make this actually use the player's allowed list of schemes
|
||||
# to play.
|
||||
uri = AdapterManager.get_song_filename_or_stream(song)
|
||||
uri = None
|
||||
try:
|
||||
if "file" in self.player_manager.supported_schemes:
|
||||
uri = AdapterManager.get_song_file_uri(song)
|
||||
except CacheMissError:
|
||||
logging.debug("Couldn't find the file, will attempt to stream.")
|
||||
|
||||
if not uri:
|
||||
try:
|
||||
uri = AdapterManager.get_song_stream_uri(song)
|
||||
except Exception:
|
||||
pass
|
||||
if (
|
||||
not uri
|
||||
or urlparse(uri).scheme not in self.player_manager.supported_schemes
|
||||
):
|
||||
self.app_config.state.current_notification = UIState.UINotification(
|
||||
markup=f"<b>Unable to play {song.title}.</b>",
|
||||
icon="dialog-error",
|
||||
)
|
||||
return
|
||||
|
||||
# Prevent it from doing the thing where it continually loads
|
||||
# songs when it has to download.
|
||||
@@ -1165,8 +1185,8 @@ class SublimeMusicApp(Gtk.Application):
|
||||
)
|
||||
song_notification.show()
|
||||
|
||||
cover_art_result = AdapterManager.get_cover_art_filename(
|
||||
song.cover_art
|
||||
cover_art_result = AdapterManager.get_cover_art_uri(
|
||||
song.cover_art, "file"
|
||||
)
|
||||
cover_art_result.add_done_callback(
|
||||
lambda f: on_cover_art_download_complete(f.result())
|
||||
@@ -1190,7 +1210,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
|
||||
os.system(f"osascript -e '{' '.join(osascript_command)}'")
|
||||
except Exception:
|
||||
logging.exception(
|
||||
logging.warning(
|
||||
"Unable to display notification. Is a notification daemon running?" # noqa: E501
|
||||
)
|
||||
|
||||
@@ -1215,7 +1235,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
assert self.player_manager
|
||||
if self.player_manager.can_start_playing_with_no_latency:
|
||||
self.player_manager.play_media(
|
||||
AdapterManager.get_song_filename_or_stream(song),
|
||||
AdapterManager.get_song_file_uri(song),
|
||||
self.app_config.state.song_progress,
|
||||
song,
|
||||
)
|
||||
|
@@ -111,7 +111,7 @@ def decode_providers(
|
||||
else None
|
||||
),
|
||||
caching_adapter_config=(
|
||||
ConfigurationStore(**config.get("caching_adapter_config", {}))
|
||||
ConfigurationStore(**(config.get("caching_adapter_config") or {}))
|
||||
),
|
||||
)
|
||||
for id_, config in providers_dict.items()
|
||||
|
@@ -277,8 +277,8 @@ class DBusManager:
|
||||
).result()
|
||||
|
||||
try:
|
||||
cover_art = AdapterManager.get_cover_art_filename(
|
||||
playlist.cover_art, allow_download=False
|
||||
cover_art = AdapterManager.get_cover_art_uri(
|
||||
playlist.cover_art, "file", allow_download=False
|
||||
).result()
|
||||
except CacheMissError:
|
||||
cover_art = ""
|
||||
@@ -319,8 +319,8 @@ class DBusManager:
|
||||
)
|
||||
|
||||
try:
|
||||
cover_art = AdapterManager.get_cover_art_filename(
|
||||
song.cover_art, allow_download=False
|
||||
cover_art = AdapterManager.get_cover_art_uri(
|
||||
song.cover_art, "file", allow_download=False
|
||||
).result()
|
||||
except CacheMissError:
|
||||
cover_art = ""
|
||||
|
@@ -237,8 +237,7 @@ class ChromecastPlayer(Player):
|
||||
song = AdapterManager.get_song_details(
|
||||
self._serving_song_id.value.decode()
|
||||
).result()
|
||||
filename = AdapterManager.get_song_filename_or_stream(song)
|
||||
assert filename.startswith("file://")
|
||||
filename = AdapterManager.get_song_file_uri(song)
|
||||
with open(filename[7:], "rb") as fin:
|
||||
song_buffer = io.BytesIO(fin.read())
|
||||
|
||||
@@ -300,7 +299,15 @@ class ChromecastPlayer(Player):
|
||||
uri = f"http://{host_ip}:{self.config.get(LAN_PORT_KEY)}/s/{token.decode()}"
|
||||
logging.info("Serving {song.name} at {uri}")
|
||||
|
||||
cover_art_url = AdapterManager.get_cover_art_uri(song.cover_art, size=1000)
|
||||
assert AdapterManager._instance
|
||||
networked_scheme_priority = ("https", "http")
|
||||
scheme = sorted(
|
||||
AdapterManager._instance.ground_truth_adapter.supported_schemes,
|
||||
key=lambda s: networked_scheme_priority.index(s),
|
||||
)[0]
|
||||
cover_art_url = AdapterManager.get_cover_art_uri(
|
||||
song.cover_art, scheme, size=1000
|
||||
)
|
||||
self._current_chromecast.media_controller.play_media(
|
||||
uri,
|
||||
# Just pretend that whatever we send it is mp3, even if it isn't.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
|
||||
|
||||
from sublime.adapters.api_objects import Song
|
||||
|
||||
@@ -89,6 +89,12 @@ class PlayerManager:
|
||||
if current_player_type := self._get_current_player_type():
|
||||
return self.players.get(current_player_type)
|
||||
|
||||
@property
|
||||
def supported_schemes(self) -> Set[str]:
|
||||
if cp := self._get_current_player():
|
||||
return cp.supported_schemes
|
||||
return set()
|
||||
|
||||
@property
|
||||
def can_start_playing_with_no_latency(self) -> bool:
|
||||
if self._current_device_id:
|
||||
|
@@ -839,8 +839,8 @@ class AlbumsGrid(Gtk.Overlay):
|
||||
artwork.set_from_file(filename.result())
|
||||
artwork.set_loading(False)
|
||||
|
||||
cover_art_filename_future = AdapterManager.get_cover_art_filename(
|
||||
item.album.cover_art
|
||||
cover_art_filename_future = AdapterManager.get_cover_art_uri(
|
||||
item.album.cover_art, "file"
|
||||
)
|
||||
if cover_art_filename_future.data_is_available:
|
||||
on_artwork_downloaded(cover_art_filename_future)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
from random import randint
|
||||
from typing import cast, List, Sequence
|
||||
|
||||
@@ -462,7 +463,7 @@ class ArtistDetailPanel(Gtk.Box):
|
||||
self.albums_list.update(artist, app_config, force=force)
|
||||
|
||||
@util.async_callback(
|
||||
AdapterManager.get_cover_art_filename,
|
||||
partial(AdapterManager.get_cover_art_uri, scheme="file"),
|
||||
before_download=lambda self: self.artist_artwork.set_loading(True),
|
||||
on_failure=lambda self, e: self.artist_artwork.set_loading(False),
|
||||
)
|
||||
|
@@ -51,8 +51,10 @@ class AlbumWithSongs(Gtk.Box):
|
||||
artist_artwork.set_from_file(f.result())
|
||||
artist_artwork.set_loading(False)
|
||||
|
||||
cover_art_filename_future = AdapterManager.get_cover_art_filename(
|
||||
album.cover_art, before_download=lambda: artist_artwork.set_loading(True),
|
||||
cover_art_filename_future = AdapterManager.get_cover_art_uri(
|
||||
album.cover_art,
|
||||
"file",
|
||||
before_download=lambda: artist_artwork.set_loading(True),
|
||||
)
|
||||
cover_art_filename_future.add_done_callback(
|
||||
lambda f: GLib.idle_add(cover_art_future_done, f)
|
||||
|
@@ -1065,7 +1065,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
image.set_loading(False)
|
||||
image.set_from_file(f.result())
|
||||
|
||||
artwork_future = AdapterManager.get_cover_art_filename(cover_art_id)
|
||||
artwork_future = AdapterManager.get_cover_art_uri(cover_art_id, "file")
|
||||
artwork_future.add_done_callback(lambda f: GLib.idle_add(image_callback, f))
|
||||
|
||||
return row
|
||||
@@ -1196,7 +1196,7 @@ class DownloadStatusBox(Gtk.Box):
|
||||
image.set_loading(False)
|
||||
image.set_from_file(f.result())
|
||||
|
||||
artwork_future = AdapterManager.get_cover_art_filename(self.song.cover_art)
|
||||
artwork_future = AdapterManager.get_cover_art_uri(self.song.cover_art, "file")
|
||||
artwork_future.add_done_callback(lambda f: GLib.idle_add(image_callback, f))
|
||||
|
||||
def update_progress(self, progress_fraction: float):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import copy
|
||||
import math
|
||||
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional, Set, Tuple
|
||||
|
||||
@@ -246,7 +246,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
def get_cover_art_filename_or_create_future(
|
||||
cover_art_id: Optional[str], idx: int, order_token: int
|
||||
) -> Optional[str]:
|
||||
cover_art_result = AdapterManager.get_cover_art_filename(cover_art_id)
|
||||
cover_art_result = AdapterManager.get_cover_art_uri(cover_art_id, "file")
|
||||
if not cover_art_result.data_is_available:
|
||||
cover_art_result.add_done_callback(
|
||||
make_idle_index_capturing_function(
|
||||
@@ -331,7 +331,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.editing_play_queue_song_list = False
|
||||
|
||||
@util.async_callback(
|
||||
AdapterManager.get_cover_art_filename,
|
||||
partial(AdapterManager.get_cover_art_uri, scheme="file"),
|
||||
before_download=lambda self: self.album_art.set_loading(True),
|
||||
on_failure=lambda self, e: self.album_art.set_loading(False),
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from functools import lru_cache
|
||||
from functools import lru_cache, partial
|
||||
from random import randint
|
||||
from typing import Any, cast, Dict, Iterable, List, Tuple
|
||||
|
||||
@@ -679,7 +679,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
|
||||
self.playlist_action_buttons.show_all()
|
||||
|
||||
@util.async_callback(
|
||||
AdapterManager.get_cover_art_filename,
|
||||
partial(AdapterManager.get_cover_art_uri, scheme="file"),
|
||||
before_download=lambda self: self.playlist_artwork.set_loading(True),
|
||||
on_failure=lambda self, e: self.playlist_artwork.set_loading(False),
|
||||
)
|
||||
|
@@ -371,13 +371,13 @@ def test_invalidate_song_file(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.invalidate_data(KEYS.COVER_ART_FILE, "s1")
|
||||
|
||||
with pytest.raises(CacheMissError):
|
||||
cache_adapter.get_song_uri("1", "file")
|
||||
cache_adapter.get_song_file_uri("1", "file")
|
||||
|
||||
with pytest.raises(CacheMissError):
|
||||
cache_adapter.get_cover_art_uri("s1", "file", size=300)
|
||||
|
||||
# Make sure it didn't delete the other song.
|
||||
assert cache_adapter.get_song_uri("2", "file").endswith("song2.mp3")
|
||||
assert cache_adapter.get_song_file_uri("2", "file").endswith("song2.mp3")
|
||||
|
||||
|
||||
def test_malformed_song_path(cache_adapter: FilesystemAdapter):
|
||||
@@ -390,10 +390,10 @@ def test_malformed_song_path(cache_adapter: FilesystemAdapter):
|
||||
KEYS.SONG_FILE, "2", ("fine/path/song2.mp3", MOCK_SONG_FILE2, None)
|
||||
)
|
||||
|
||||
song_uri = cache_adapter.get_song_uri("1", "file")
|
||||
song_uri = cache_adapter.get_song_file_uri("1", "file")
|
||||
assert song_uri.endswith(f"/music/{MOCK_SONG_FILE_HASH}")
|
||||
|
||||
song_uri2 = cache_adapter.get_song_uri("2", "file")
|
||||
song_uri2 = cache_adapter.get_song_file_uri("2", "file")
|
||||
assert song_uri2.endswith("fine/path/song2.mp3")
|
||||
|
||||
|
||||
@@ -467,7 +467,7 @@ def test_delete_song_data(cache_adapter: FilesystemAdapter):
|
||||
KEYS.COVER_ART_FILE, "s1", MOCK_ALBUM_ART,
|
||||
)
|
||||
|
||||
music_file_path = cache_adapter.get_song_uri("1", "file")
|
||||
music_file_path = cache_adapter.get_song_file_uri("1", "file")
|
||||
cover_art_path = cache_adapter.get_cover_art_uri("s1", "file", size=300)
|
||||
|
||||
cache_adapter.delete_data(KEYS.SONG_FILE, "1")
|
||||
@@ -477,7 +477,7 @@ def test_delete_song_data(cache_adapter: FilesystemAdapter):
|
||||
assert not Path(cover_art_path).exists()
|
||||
|
||||
try:
|
||||
cache_adapter.get_song_uri("1", "file")
|
||||
cache_adapter.get_song_file_uri("1", "file")
|
||||
assert 0, "DID NOT raise CacheMissError"
|
||||
except CacheMissError as e:
|
||||
assert e.partial_data is None
|
||||
|
Reference in New Issue
Block a user