2018-05-04 17:25:12 +02:00
|
|
|
import re
|
2019-01-14 19:52:54 +01:00
|
|
|
import threading
|
2018-11-07 18:04:37 +01:00
|
|
|
import time
|
2023-10-01 16:38:22 +02:00
|
|
|
from typing import Collection, Optional, Union
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
from platypush.plugins import action
|
|
|
|
from platypush.plugins.music import MusicPlugin
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2019-07-01 22:26:04 +02:00
|
|
|
|
2017-11-03 04:08:47 +01:00
|
|
|
class MusicMpdPlugin(MusicPlugin):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
This plugin allows you to interact with an MPD/Mopidy music server.
|
|
|
|
|
|
|
|
`MPD <https://www.musicpd.org/>`_ is a flexible server-side
|
|
|
|
protocol/application for handling music collections and playing music,
|
|
|
|
mostly aimed to manage local libraries.
|
|
|
|
|
|
|
|
`Mopidy <https://www.mopidy.com/>`_ is an evolution of MPD, compatible with
|
|
|
|
the original protocol and with support for multiple music sources through
|
|
|
|
plugins (e.g. Spotify, TuneIn, Soundcloud, local files etc.).
|
|
|
|
|
|
|
|
.. note:: As of Mopidy 3.0 MPD is an optional interface provided by the
|
|
|
|
``mopidy-mpd`` extension. Make sure that you have the extension
|
|
|
|
installed and enabled on your instance to use this plugin with your
|
|
|
|
server.
|
|
|
|
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
|
2019-01-14 19:54:59 +01:00
|
|
|
_client_lock = threading.RLock()
|
2019-01-14 19:52:54 +01:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
def __init__(self, host: str, port: int = 6600):
|
2017-12-18 01:10:51 +01:00
|
|
|
"""
|
2018-06-25 00:49:45 +02:00
|
|
|
:param host: MPD IP/hostname
|
|
|
|
:param port: MPD port (default: 6600)
|
2017-12-18 01:10:51 +01:00
|
|
|
"""
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
super().__init__()
|
2017-12-18 01:10:51 +01:00
|
|
|
self.host = host
|
|
|
|
self.port = port
|
2019-01-10 17:51:33 +01:00
|
|
|
self.client = None
|
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
def _connect(self, n_tries: int = 2):
|
2019-12-01 23:35:05 +01:00
|
|
|
import mpd
|
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
with self._client_lock:
|
|
|
|
if self.client:
|
2023-10-01 16:38:22 +02:00
|
|
|
return self.client
|
2019-05-30 02:07:28 +02:00
|
|
|
|
|
|
|
error = None
|
|
|
|
while n_tries > 0:
|
|
|
|
try:
|
|
|
|
n_tries -= 1
|
2023-02-20 20:35:33 +01:00
|
|
|
self.client = mpd.MPDClient()
|
2019-05-30 02:07:28 +02:00
|
|
|
self.client.connect(self.host, self.port)
|
|
|
|
return self.client
|
|
|
|
except Exception as e:
|
|
|
|
error = e
|
2023-09-24 16:54:43 +02:00
|
|
|
self.logger.warning(
|
2023-10-01 16:38:22 +02:00
|
|
|
'Connection exception: %s%s',
|
|
|
|
e,
|
|
|
|
(': Retrying' if n_tries > 0 else ''),
|
2023-09-24 16:54:43 +02:00
|
|
|
)
|
2019-05-30 02:07:28 +02:00
|
|
|
time.sleep(0.5)
|
|
|
|
|
|
|
|
self.client = None
|
2021-04-05 00:58:44 +02:00
|
|
|
if error:
|
|
|
|
raise error
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
return self.client
|
|
|
|
|
|
|
|
def _exec(self, method: str, *args, **kwargs):
|
2019-05-30 02:07:28 +02:00
|
|
|
error = None
|
|
|
|
n_tries = int(kwargs.pop('n_tries')) if 'n_tries' in kwargs else 2
|
2023-09-24 16:54:43 +02:00
|
|
|
return_status = (
|
|
|
|
kwargs.pop('return_status') if 'return_status' in kwargs else True
|
|
|
|
)
|
2019-01-10 17:51:33 +01:00
|
|
|
|
2019-05-30 02:07:28 +02:00
|
|
|
while n_tries > 0:
|
|
|
|
try:
|
|
|
|
self._connect()
|
|
|
|
n_tries -= 1
|
|
|
|
with self._client_lock:
|
|
|
|
response = getattr(self.client, method)(*args, **kwargs)
|
|
|
|
|
|
|
|
if return_status:
|
|
|
|
return self.status().output
|
|
|
|
return response
|
|
|
|
except Exception as e:
|
|
|
|
error = str(e)
|
2023-09-24 16:54:43 +02:00
|
|
|
self.logger.warning(
|
2023-10-01 16:38:22 +02:00
|
|
|
'Exception while executing MPD method %s: %s', method, error
|
2023-09-24 16:54:43 +02:00
|
|
|
)
|
2019-05-30 02:07:28 +02:00
|
|
|
self.client = None
|
|
|
|
|
|
|
|
return None, error
|
2017-12-13 03:37:28 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def play(self, resource: Optional[str] = None, **__):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Play a resource by path/URI.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
|
|
|
:param resource: Resource path/URI
|
|
|
|
:type resource: str
|
|
|
|
"""
|
|
|
|
|
2017-12-27 10:18:51 +01:00
|
|
|
if resource:
|
2020-01-01 21:16:27 +01:00
|
|
|
self.add(resource, position=0)
|
|
|
|
return self.play_pos(0)
|
|
|
|
|
2017-12-13 03:37:28 +01:00
|
|
|
return self._exec('play')
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def play_pos(self, pos: int):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Play a track in the current playlist by position number.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param pos: Position number.
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
|
2018-02-05 09:45:35 +01:00
|
|
|
return self._exec('play', pos)
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def pause(self, *_, **__):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Pause playback"""
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
status = self._status()['state']
|
|
|
|
return self._exec('pause') if status == 'play' else self._exec('play')
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2018-05-25 18:04:32 +02:00
|
|
|
def pause_if_playing(self):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Pause playback only if it's playing"""
|
2023-10-01 16:38:22 +02:00
|
|
|
status = self._status()['state']
|
|
|
|
return self._exec('pause') if status == 'play' else None
|
2018-05-25 18:04:32 +02:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2018-05-25 18:04:32 +02:00
|
|
|
def play_if_paused(self):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Play only if it's paused (resume)"""
|
2023-10-01 16:38:22 +02:00
|
|
|
status = self._status()['state']
|
|
|
|
return self._exec('play') if status == 'pause' else None
|
2018-05-25 18:04:32 +02:00
|
|
|
|
2018-09-25 19:40:00 +02:00
|
|
|
@action
|
|
|
|
def play_if_paused_or_stopped(self):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Play only if it's paused or stopped"""
|
2023-10-01 16:38:22 +02:00
|
|
|
status = self._status()['state']
|
|
|
|
return self._exec('play') if status in ('pause', 'stop') else None
|
2018-09-25 19:40:00 +02:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def stop(self, *_, **__):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Stop playback"""
|
2017-12-13 03:37:28 +01:00
|
|
|
return self._exec('stop')
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2017-12-28 13:13:09 +01:00
|
|
|
def play_or_stop(self):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Play or stop (play state toggle)"""
|
2023-10-01 16:38:22 +02:00
|
|
|
status = self._status()['state']
|
2018-07-06 02:08:38 +02:00
|
|
|
if status == 'play':
|
|
|
|
return self._exec('stop')
|
2023-10-01 16:38:22 +02:00
|
|
|
return self._exec('play')
|
2017-12-28 13:13:09 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def playid(self, track_id: str):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Play a track by ID.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param track_id: Track ID.
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2018-02-05 00:55:19 +01:00
|
|
|
return self._exec('playid', track_id)
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def next(self, *_, **__):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Play the next track"""
|
2017-12-13 03:37:28 +01:00
|
|
|
return self._exec('next')
|
2017-11-03 15:06:29 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2024-01-13 22:35:18 +01:00
|
|
|
def previous(self, **__):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Play the previous track"""
|
2017-12-13 03:37:28 +01:00
|
|
|
return self._exec('previous')
|
2017-11-03 15:06:29 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def setvol(self, vol: int):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Set the volume.
|
|
|
|
|
|
|
|
..warning :: **DEPRECATED**, use :meth:`.set_volume` instead.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param vol: Volume value (range: 0-100).
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2020-12-26 15:03:12 +01:00
|
|
|
return self.set_volume(vol)
|
|
|
|
|
|
|
|
@action
|
2024-01-13 22:35:18 +01:00
|
|
|
def set_volume(self, volume: int, **__):
|
2020-12-26 15:03:12 +01:00
|
|
|
"""
|
|
|
|
Set the volume.
|
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param volume: Volume value (range: 0-100).
|
2020-12-26 15:03:12 +01:00
|
|
|
"""
|
|
|
|
return self._exec('setvol', str(volume))
|
2017-11-03 15:06:29 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2024-01-13 22:35:18 +01:00
|
|
|
def volup(self, step: Optional[float] = None, **kwargs):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Turn up the volume.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2024-01-13 22:35:18 +01:00
|
|
|
:param step: Volume up step (default: 5%).
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2024-01-13 22:35:18 +01:00
|
|
|
step = step or kwargs.get('delta') or 5
|
2023-10-01 16:38:22 +02:00
|
|
|
volume = int(self._status()['volume'])
|
2024-01-13 22:35:18 +01:00
|
|
|
new_volume = min(volume + step, 100)
|
2020-03-08 13:04:00 +01:00
|
|
|
return self.setvol(new_volume)
|
2017-12-24 13:15:37 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2024-01-13 22:35:18 +01:00
|
|
|
def voldown(self, step: Optional[float] = None, **kwargs):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Turn down the volume.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2024-01-13 22:35:18 +01:00
|
|
|
:param step: Volume down step (default: 5%).
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2024-01-13 22:35:18 +01:00
|
|
|
step = step or kwargs.get('delta') or 5
|
2023-10-01 16:38:22 +02:00
|
|
|
volume = int(self._status()['volume'])
|
2024-01-13 22:35:18 +01:00
|
|
|
new_volume = max(volume - step, 0)
|
2020-03-08 13:04:00 +01:00
|
|
|
return self.setvol(new_volume)
|
2017-12-24 13:15:37 +01:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
def _toggle(self, key: str, value: Optional[bool] = None):
|
|
|
|
if value is None:
|
|
|
|
value = bool(self._status()[key])
|
|
|
|
return self._exec(key, int(value))
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def random(self, value: Optional[bool] = None):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Set random mode.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param value: If set, set the random state this value (true/false).
|
|
|
|
Default: None (toggle current state).
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
return self._toggle('random', value)
|
2018-01-31 01:32:07 +01:00
|
|
|
|
2019-06-03 23:37:19 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def consume(self, value: Optional[bool] = None):
|
2019-06-03 23:37:19 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Set consume mode.
|
2019-06-03 23:37:19 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param value: If set, set the consume state this value (true/false).
|
|
|
|
Default: None (toggle current state)
|
2019-06-03 23:37:19 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
return self._toggle('consume', value)
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def single(self, value: Optional[bool] = None):
|
2019-06-03 23:37:19 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Set single mode.
|
2019-06-03 23:37:19 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param value: If set, set the consume state this value (true/false).
|
|
|
|
Default: None (toggle current state)
|
2019-06-03 23:37:19 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
return self._toggle('single', value)
|
2019-06-03 23:37:19 +02:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def repeat(self, value: Optional[bool] = None):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Set repeat mode.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param value: If set, set the repeat state this value (true/false).
|
|
|
|
Default: None (toggle current state)
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
return self._toggle('repeat', value)
|
2018-01-31 01:32:07 +01:00
|
|
|
|
2018-08-06 22:44:02 +02:00
|
|
|
@action
|
|
|
|
def shuffle(self):
|
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Shuffles the current playlist.
|
2018-08-06 22:44:02 +02:00
|
|
|
"""
|
|
|
|
return self._exec('shuffle')
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def save(self, name: str):
|
2019-06-03 23:37:19 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Save the current tracklist to a new playlist with the specified name.
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
:param name: Name of the playlist
|
|
|
|
"""
|
|
|
|
return self._exec('save', name)
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def add(self, resource: str, *_, position: Optional[int] = None, **__):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Add a resource (track, album, artist, folder etc.) to the current
|
|
|
|
playlist.
|
2019-06-02 00:54:49 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param resource: Resource path or URI.
|
|
|
|
:param position: Position where the track(s) will be inserted (default:
|
|
|
|
end of the playlist).
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
|
2018-09-25 09:42:46 +02:00
|
|
|
if isinstance(resource, list):
|
|
|
|
for r in resource:
|
2019-01-03 10:37:11 +01:00
|
|
|
r = self._parse_resource(r)
|
2018-09-25 09:42:46 +02:00
|
|
|
try:
|
2019-01-09 18:43:13 +01:00
|
|
|
if position is None:
|
|
|
|
self._exec('add', r)
|
|
|
|
else:
|
|
|
|
self._exec('addid', r, position)
|
2018-09-25 09:42:46 +02:00
|
|
|
except Exception as e:
|
2023-10-01 16:38:22 +02:00
|
|
|
self.logger.warning('Could not add %s: %s', r, e)
|
2018-09-25 09:42:46 +02:00
|
|
|
|
|
|
|
return self.status().output
|
|
|
|
|
2019-01-03 10:37:11 +01:00
|
|
|
r = self._parse_resource(resource)
|
2019-01-09 18:43:13 +01:00
|
|
|
|
|
|
|
if position is None:
|
2019-06-03 23:37:19 +02:00
|
|
|
return self._exec('add', r)
|
2019-01-09 18:43:13 +01:00
|
|
|
return self._exec('addid', r, position)
|
2019-01-03 10:37:11 +01:00
|
|
|
|
2019-06-02 00:54:49 +02:00
|
|
|
@action
|
|
|
|
def delete(self, positions):
|
|
|
|
"""
|
|
|
|
Delete the playlist item(s) in the specified position(s).
|
|
|
|
|
|
|
|
:param positions: Positions of the tracks to be removed
|
|
|
|
:type positions: list[int]
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
:return: The modified playlist
|
2019-06-02 00:54:49 +02:00
|
|
|
"""
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
for pos in sorted(positions, key=int, reverse=True):
|
|
|
|
self._exec('delete', pos)
|
|
|
|
return self.playlistinfo()
|
|
|
|
|
|
|
|
@action
|
|
|
|
def rm(self, playlist):
|
|
|
|
"""
|
|
|
|
Permanently remove playlist(s) by name
|
|
|
|
|
|
|
|
:param playlist: Name or list of playlist names to remove
|
|
|
|
:type playlist: str or list[str]
|
|
|
|
"""
|
|
|
|
|
|
|
|
if isinstance(playlist, str):
|
|
|
|
playlist = [playlist]
|
|
|
|
elif not isinstance(playlist, list):
|
2023-10-01 16:38:22 +02:00
|
|
|
raise RuntimeError(f'Invalid type for playlist: {type(playlist)}')
|
2019-06-03 23:37:19 +02:00
|
|
|
|
|
|
|
for p in playlist:
|
|
|
|
self._exec('rm', p)
|
2019-06-02 00:54:49 +02:00
|
|
|
|
|
|
|
@action
|
|
|
|
def move(self, from_pos, to_pos):
|
|
|
|
"""
|
|
|
|
Move the playlist item in position <from_pos> to position <to_pos>
|
|
|
|
|
|
|
|
:param from_pos: Track current position
|
|
|
|
:type from_pos: int
|
|
|
|
|
|
|
|
:param to_pos: Track new position
|
|
|
|
:type to_pos: int
|
|
|
|
"""
|
|
|
|
return self._exec('move', from_pos, to_pos)
|
|
|
|
|
2019-01-03 10:37:11 +01:00
|
|
|
@classmethod
|
|
|
|
def _parse_resource(cls, resource):
|
2019-01-30 09:08:29 +01:00
|
|
|
if not resource:
|
2023-10-01 16:38:22 +02:00
|
|
|
return None
|
2019-01-30 09:08:29 +01:00
|
|
|
|
2021-04-05 00:58:44 +02:00
|
|
|
m = re.search(r'^https?://open\.spotify\.com/([^?]+)', resource)
|
2020-03-08 13:04:00 +01:00
|
|
|
if m:
|
2023-10-01 16:38:22 +02:00
|
|
|
resource = 'spotify:' + m.group(1).replace('/', ':')
|
2019-01-30 09:04:15 +01:00
|
|
|
|
2019-01-30 09:08:29 +01:00
|
|
|
if resource.startswith('spotify:'):
|
|
|
|
resource = resource.split('?')[0]
|
|
|
|
|
2021-04-05 00:58:44 +02:00
|
|
|
m = re.match(r'spotify:playlist:(.*)', resource)
|
2019-02-28 16:33:36 +01:00
|
|
|
if m:
|
2019-01-03 10:37:11 +01:00
|
|
|
# Old Spotify URI format, convert it to new
|
2019-02-28 18:57:22 +01:00
|
|
|
resource = 'spotify:user:spotify:playlist:' + m.group(1)
|
2019-01-03 10:37:11 +01:00
|
|
|
return resource
|
2017-11-03 15:06:29 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2019-06-04 15:59:07 +02:00
|
|
|
def load(self, playlist, play=True):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
Load and play a playlist by name
|
|
|
|
|
|
|
|
:param playlist: Playlist name
|
|
|
|
:type playlist: str
|
2019-06-04 15:59:07 +02:00
|
|
|
|
|
|
|
:param play: Start playback after loading the playlist (default: True)
|
|
|
|
:type play: bool
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
|
2019-06-04 15:59:07 +02:00
|
|
|
ret = self._exec('load', playlist)
|
|
|
|
if play:
|
|
|
|
self.play()
|
|
|
|
return ret
|
2017-11-03 15:06:29 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2024-01-13 22:35:18 +01:00
|
|
|
def clear(self, **__):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Clear the current playlist"""
|
2017-12-13 03:37:28 +01:00
|
|
|
return self._exec('clear')
|
2017-11-03 15:06:29 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def seekcur(self, value: float):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2020-12-26 15:03:12 +01:00
|
|
|
Seek to the specified position (DEPRECATED, use :meth:`.seek` instead).
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param value: Seek position in seconds, or delta string (e.g. '+15' or
|
|
|
|
'-15') to indicate a seek relative to the current position
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2020-12-26 15:03:12 +01:00
|
|
|
return self.seek(value)
|
|
|
|
|
|
|
|
@action
|
2024-01-13 22:35:18 +01:00
|
|
|
def seek(self, position: float, **__):
|
2020-12-26 15:03:12 +01:00
|
|
|
"""
|
|
|
|
Seek to the specified position
|
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param position: Seek position in seconds, or delta string (e.g. '+15'
|
|
|
|
or '-15') to indicate a seek relative to the current position
|
2020-12-26 15:03:12 +01:00
|
|
|
"""
|
|
|
|
return self._exec('seekcur', position)
|
2018-01-31 10:35:14 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2017-12-27 10:18:51 +01:00
|
|
|
def forward(self):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Go forward by 15 seconds"""
|
2017-12-27 10:18:51 +01:00
|
|
|
return self._exec('seekcur', '+15')
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2017-12-27 10:18:51 +01:00
|
|
|
def back(self):
|
2023-09-24 16:54:43 +02:00
|
|
|
"""Go backward by 15 seconds"""
|
2017-12-27 10:18:51 +01:00
|
|
|
return self._exec('seekcur', '-15')
|
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
def _status(self) -> dict:
|
|
|
|
n_tries = 2
|
|
|
|
error = None
|
|
|
|
|
|
|
|
while n_tries > 0:
|
|
|
|
try:
|
|
|
|
n_tries -= 1
|
|
|
|
self._connect()
|
|
|
|
if self.client:
|
|
|
|
return self.client.status() # type: ignore
|
|
|
|
except Exception as e:
|
|
|
|
error = e
|
|
|
|
self.logger.warning('Exception while getting MPD status: %s', e)
|
|
|
|
self.client = None
|
|
|
|
|
|
|
|
raise AssertionError(str(error))
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def status(self, *_, **__):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
:returns: The current state.
|
|
|
|
|
|
|
|
Example response::
|
|
|
|
|
|
|
|
output = {
|
|
|
|
"volume": "9",
|
|
|
|
"repeat": "0",
|
|
|
|
"random": "0",
|
|
|
|
"single": "0",
|
|
|
|
"consume": "0",
|
|
|
|
"playlist": "52",
|
|
|
|
"playlistlength": "14",
|
|
|
|
"xfade": "0",
|
|
|
|
"state": "play",
|
|
|
|
"song": "9",
|
|
|
|
"songid": "3061",
|
|
|
|
"nextsong": "10",
|
|
|
|
"nextsongid": "3062",
|
|
|
|
"time": "161:255",
|
|
|
|
"elapsed": "161.967",
|
|
|
|
"bitrate": "320"
|
|
|
|
}
|
2021-07-17 22:14:15 +02:00
|
|
|
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
return self._status()
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2018-01-13 02:52:06 +01:00
|
|
|
def currentsong(self):
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
Legacy alias for :meth:`.current_track`.
|
|
|
|
"""
|
|
|
|
return self.current_track()
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def current_track(self, *_, **__):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
:returns: The currently played track.
|
|
|
|
|
|
|
|
Example response::
|
|
|
|
|
|
|
|
output = {
|
|
|
|
"file": "spotify:track:7CO5ADlDN3DcR2pwlnB14P",
|
|
|
|
"time": "255",
|
|
|
|
"artist": "Elbow",
|
|
|
|
"album": "Little Fictions",
|
|
|
|
"title": "Kindling",
|
|
|
|
"date": "2017",
|
|
|
|
"track": "10",
|
|
|
|
"pos": "9",
|
|
|
|
"id": "3061",
|
|
|
|
"albumartist": "Elbow",
|
|
|
|
"x-albumuri": "spotify:album:6q5KhDhf9BZkoob7uAnq19"
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
2019-01-10 17:51:33 +01:00
|
|
|
track = self._exec('currentsong', return_status=False)
|
2023-10-01 16:38:22 +02:00
|
|
|
if not isinstance(track, dict):
|
|
|
|
return None
|
|
|
|
|
2023-09-24 16:54:43 +02:00
|
|
|
if 'title' in track and (
|
|
|
|
'artist' not in track
|
|
|
|
or not track['artist']
|
|
|
|
or re.search('^https?://', track['file'])
|
|
|
|
or re.search('^tunein:', track['file'])
|
|
|
|
):
|
2021-04-05 00:58:44 +02:00
|
|
|
m = re.match(r'^\s*(.+?)\s+-\s+(.*)\s*$', track['title'])
|
2018-05-04 17:25:12 +02:00
|
|
|
if m and m.group(1) and m.group(2):
|
|
|
|
track['artist'] = m.group(1)
|
|
|
|
track['title'] = m.group(2)
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
return track
|
2018-01-13 02:52:06 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2018-01-30 00:54:46 +01:00
|
|
|
def playlistinfo(self):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
:returns: The tracks in the current playlist as a list of dicts.
|
|
|
|
|
|
|
|
Example output::
|
|
|
|
|
|
|
|
output = [
|
|
|
|
{
|
|
|
|
"file": "spotify:track:79VtgIoznishPUDWO7Kafu",
|
|
|
|
"time": "355",
|
|
|
|
"artist": "Elbow",
|
|
|
|
"album": "Little Fictions",
|
|
|
|
"title": "Trust the Sun",
|
|
|
|
"date": "2017",
|
|
|
|
"track": "3",
|
|
|
|
"pos": "10",
|
|
|
|
"id": "3062",
|
|
|
|
"albumartist": "Elbow",
|
|
|
|
"x-albumuri": "spotify:album:6q5KhDhf9BZkoob7uAnq19"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"file": "spotify:track:3EzTre0pxmoMYRuhJKMHj6",
|
|
|
|
"time": "219",
|
|
|
|
"artist": "Elbow",
|
|
|
|
"album": "Little Fictions",
|
|
|
|
"title": "Gentle Storm",
|
|
|
|
"date": "2017",
|
|
|
|
"track": "2",
|
|
|
|
"pos": "11",
|
|
|
|
"id": "3063",
|
|
|
|
"albumartist": "Elbow",
|
|
|
|
"x-albumuri": "spotify:album:6q5KhDhf9BZkoob7uAnq19"
|
|
|
|
},
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
|
2019-01-10 17:51:33 +01:00
|
|
|
return self._exec('playlistinfo', return_status=False)
|
2018-01-30 00:54:46 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def get_playlists(self, *_, **__):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
:returns: The playlists available on the server as a list of dicts.
|
|
|
|
|
|
|
|
Example response::
|
|
|
|
|
|
|
|
output = [
|
|
|
|
{
|
|
|
|
"playlist": "Rock",
|
|
|
|
"last-modified": "2018-06-25T21:28:19Z"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"playlist": "Jazz",
|
|
|
|
"last-modified": "2018-06-24T22:28:29Z"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# ...
|
|
|
|
}
|
|
|
|
]
|
2023-10-01 16:38:22 +02:00
|
|
|
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
playlists: list = self._exec( # type: ignore
|
|
|
|
'listplaylists', return_status=False
|
2023-09-24 16:54:43 +02:00
|
|
|
)
|
2023-10-01 16:38:22 +02:00
|
|
|
return sorted(playlists, key=lambda p: p['playlist'])
|
2018-01-30 00:54:46 +01:00
|
|
|
|
2019-06-07 23:07:36 +02:00
|
|
|
@action
|
2021-07-17 22:14:15 +02:00
|
|
|
def listplaylists(self):
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
2021-07-17 22:14:15 +02:00
|
|
|
Deprecated alias for :meth:`.playlists`.
|
|
|
|
"""
|
|
|
|
return self.get_playlists()
|
2019-06-07 23:07:36 +02:00
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def get_playlist(self, playlist: str, *_, with_tracks: bool = False, **__):
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
List the items in the specified playlist.
|
|
|
|
|
|
|
|
:param playlist: Name of the playlist
|
2023-10-01 16:38:22 +02:00
|
|
|
:param with_tracks: If True then the list of tracks in the playlist will
|
|
|
|
be returned as well (default: False).
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
return self._exec(
|
|
|
|
'listplaylistinfo' if with_tracks else 'listplaylist',
|
2023-09-24 16:54:43 +02:00
|
|
|
playlist,
|
|
|
|
return_status=False,
|
|
|
|
)
|
2021-07-17 22:14:15 +02:00
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def listplaylist(self, name: str):
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
Deprecated alias for :meth:`.playlist`.
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
|
|
|
return self._exec('listplaylist', name, return_status=False)
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def listplaylistinfo(self, name: str):
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Deprecated alias for :meth:`.playlist` with ``with_tracks=True``.
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
2021-07-17 22:14:15 +02:00
|
|
|
return self.get_playlist(name, with_tracks=True)
|
2019-06-07 23:07:36 +02:00
|
|
|
|
2019-06-07 17:17:58 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def add_to_playlist(self, playlist: str, resources: Union[str, Collection[str]]):
|
2019-06-07 17:17:58 +02:00
|
|
|
"""
|
|
|
|
Add one or multiple resources to a playlist.
|
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
:param playlist: Playlist name
|
|
|
|
:param resources: URI or path of the resource(s) to be added
|
2019-06-07 17:17:58 +02:00
|
|
|
"""
|
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
if isinstance(resources, str):
|
|
|
|
resources = [resources]
|
2019-06-07 17:17:58 +02:00
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
for res in resources:
|
|
|
|
self._exec('playlistadd', playlist, res)
|
2019-06-07 17:17:58 +02:00
|
|
|
|
2019-06-07 23:07:36 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def playlistadd(self, name: str, uri: str):
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
Deprecated alias for :meth:`.add_to_playlist`.
|
|
|
|
"""
|
|
|
|
return self.add_to_playlist(name, uri)
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def remove_from_playlist(
|
|
|
|
self, playlist: str, resources: Union[int, Collection[int]], *_, **__
|
|
|
|
):
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
|
|
|
Remove one or multiple tracks from a playlist.
|
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
:param playlist: Playlist name
|
|
|
|
:param resources: Position or list of positions to remove
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
if isinstance(resources, str):
|
|
|
|
resources = int(resources)
|
|
|
|
if isinstance(resources, int):
|
|
|
|
resources = [resources]
|
2019-06-07 23:07:36 +02:00
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
for p in sorted(resources, reverse=True):
|
|
|
|
self._exec('playlistdelete', playlist, p)
|
2019-06-07 23:07:36 +02:00
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def playlist_move(self, playlist: str, from_pos: int, to_pos: int, *_, **__):
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
2021-07-17 22:14:15 +02:00
|
|
|
Change the position of a track in the specified playlist.
|
2019-06-07 23:07:36 +02:00
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
:param playlist: Playlist name
|
2019-06-07 23:07:36 +02:00
|
|
|
:param from_pos: Original track position
|
|
|
|
:param to_pos: New track position
|
|
|
|
"""
|
2021-07-17 22:14:15 +02:00
|
|
|
self._exec('playlistmove', playlist, from_pos, to_pos)
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def playlistdelete(self, name: str, pos: int):
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
Deprecated alias for :meth:`.remove_from_playlist`.
|
|
|
|
"""
|
|
|
|
return self.remove_from_playlist(name, pos)
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def playlistmove(self, name: str, from_pos: int, to_pos: int):
|
2021-07-17 22:14:15 +02:00
|
|
|
"""
|
|
|
|
Deprecated alias for :meth:`.playlist_move`.
|
|
|
|
"""
|
|
|
|
return self.playlist_move(name, from_pos=from_pos, to_pos=to_pos)
|
2019-06-07 23:07:36 +02:00
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def playlistclear(self, name: str):
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Clears all the elements from the specified playlist.
|
2019-06-07 23:07:36 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param name: Playlist name.
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
|
|
|
self._exec('playlistclear', name)
|
|
|
|
|
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def rename(self, name: str, new_name: str):
|
2019-06-07 23:07:36 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Rename a playlist.
|
2019-06-07 23:07:36 +02:00
|
|
|
|
|
|
|
:param name: Original playlist name
|
|
|
|
:param new_name: New playlist name
|
|
|
|
"""
|
|
|
|
self._exec('rename', name, new_name)
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def lsinfo(self, uri: Optional[str] = None):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Returns the list of playlists and directories on the server.
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
|
2023-09-24 16:54:43 +02:00
|
|
|
return (
|
|
|
|
self._exec('lsinfo', uri, return_status=False)
|
|
|
|
if uri
|
|
|
|
else self._exec('lsinfo', return_status=False)
|
|
|
|
)
|
2018-01-30 00:54:46 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def plchanges(self, version: int):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
Show what has changed on the current playlist since a specified playlist
|
|
|
|
version number.
|
|
|
|
|
|
|
|
:param version: Version number
|
|
|
|
:returns: A list of dicts representing the songs being added since the specified version
|
|
|
|
"""
|
2019-01-10 17:51:33 +01:00
|
|
|
return self._exec('plchanges', version, return_status=False)
|
2018-02-05 00:55:19 +01:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def searchaddplaylist(self, name: str):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
Search and add a playlist by (partial or full) name.
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
:param name: Playlist name, can be partial.
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
resp: list = self._exec('listplaylists', return_status=False) # type: ignore
|
2023-09-24 16:54:43 +02:00
|
|
|
playlists = [
|
2023-10-01 16:38:22 +02:00
|
|
|
pl['playlist'] for pl in resp if name.lower() in pl['playlist'].lower()
|
2023-09-24 16:54:43 +02:00
|
|
|
]
|
2018-04-15 16:31:23 +02:00
|
|
|
|
2023-10-01 16:38:22 +02:00
|
|
|
if not playlists:
|
|
|
|
return None
|
|
|
|
|
|
|
|
self._exec('clear')
|
|
|
|
self._exec('load', playlists[0])
|
|
|
|
self._exec('play')
|
|
|
|
return {'playlist': playlists[0]}
|
2018-04-15 16:31:23 +02:00
|
|
|
|
2020-03-08 13:04:00 +01:00
|
|
|
@staticmethod
|
|
|
|
def _make_filter(f: dict) -> list:
|
|
|
|
ll = []
|
|
|
|
for k, v in f.items():
|
|
|
|
ll.extend([k, v])
|
|
|
|
return ll
|
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def find(self, filter: dict, *args, **kwargs): # pylint: disable=redefined-builtin
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
Find in the database/library by filter.
|
|
|
|
|
2020-03-08 13:04:00 +01:00
|
|
|
:param filter: Search filter (e.g. ``{"artist": "Led Zeppelin", "album": "IV"}``)
|
2018-06-25 00:49:45 +02:00
|
|
|
:returns: list[dict]
|
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
filter_list = self._make_filter(filter)
|
|
|
|
return self._exec('find', *filter_list, *args, return_status=False, **kwargs)
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def findadd(
|
|
|
|
self, filter: dict, *args, **kwargs # pylint: disable=redefined-builtin
|
|
|
|
):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
Find in the database/library by filter and add to the current playlist.
|
|
|
|
|
2020-03-08 13:04:00 +01:00
|
|
|
:param filter: Search filter (e.g. ``{"artist": "Led Zeppelin", "album": "IV"}``)
|
2018-06-25 00:49:45 +02:00
|
|
|
:returns: list[dict]
|
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
filter_list = self._make_filter(filter)
|
|
|
|
return self._exec('findadd', *filter_list, *args, return_status=False, **kwargs)
|
2018-06-25 00:49:45 +02:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-09-24 16:54:43 +02:00
|
|
|
def search(
|
|
|
|
self,
|
|
|
|
*args,
|
2023-10-01 16:38:22 +02:00
|
|
|
query: Optional[Union[str, dict]] = None,
|
|
|
|
filter: Optional[dict] = None, # pylint: disable=redefined-builtin
|
|
|
|
**kwargs,
|
2023-09-24 16:54:43 +02:00
|
|
|
):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
Free search by filter.
|
|
|
|
|
2021-07-17 22:14:15 +02:00
|
|
|
:param query: Free-text query or search structured filter (e.g. ``{"artist": "Led Zeppelin", "album": "IV"}``).
|
|
|
|
:param filter: Structured search filter (e.g. ``{"artist": "Led Zeppelin", "album": "IV"}``) - same as
|
|
|
|
``query``, it's still here for back-compatibility reasons.
|
2018-06-25 00:49:45 +02:00
|
|
|
:returns: list[dict]
|
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
assert query or filter, 'Specify either `query` or `filter`'
|
|
|
|
|
|
|
|
filt = filter
|
|
|
|
if isinstance(query, str):
|
|
|
|
filt = query
|
|
|
|
elif isinstance(query, dict):
|
|
|
|
filt = {**(filter or {}), **query}
|
|
|
|
|
|
|
|
filter_list = self._make_filter(filt) if isinstance(filt, dict) else [query]
|
|
|
|
|
|
|
|
items: list = self._exec( # type: ignore
|
|
|
|
'search', *filter_list, *args, return_status=False, **kwargs
|
|
|
|
)
|
2018-04-15 11:15:43 +02:00
|
|
|
|
|
|
|
# Spotify results first
|
2023-09-24 16:54:43 +02:00
|
|
|
return sorted(
|
|
|
|
items, key=lambda item: 0 if item['file'].startswith('spotify:') else 1
|
|
|
|
)
|
2018-04-12 13:04:56 +02:00
|
|
|
|
2018-07-06 02:08:38 +02:00
|
|
|
@action
|
2023-10-01 16:38:22 +02:00
|
|
|
def searchadd(self, filter: dict, *args, **kwargs):
|
2018-06-25 00:49:45 +02:00
|
|
|
"""
|
|
|
|
Free search by filter and add the results to the current playlist.
|
|
|
|
|
2020-03-08 13:04:00 +01:00
|
|
|
:param filter: Search filter (e.g. ``{"artist": "Led Zeppelin", "album": "IV"}``)
|
2018-06-25 00:49:45 +02:00
|
|
|
:returns: list[dict]
|
|
|
|
"""
|
2023-10-01 16:38:22 +02:00
|
|
|
filter_list = self._make_filter(filter)
|
|
|
|
return self._exec(
|
|
|
|
'searchadd', *filter_list, *args, return_status=False, **kwargs
|
|
|
|
)
|
2018-01-13 02:52:06 +01:00
|
|
|
|
2017-11-03 04:08:47 +01:00
|
|
|
|
2020-03-08 13:04:00 +01:00
|
|
|
# vim:sw=4:ts=4:et:
|