Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some improvements to the HA services (again) #1596

Merged
merged 3 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 52 additions & 39 deletions custom_components/mass/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
ATTR_RADIO_MODE = "radio_mode"
ATTR_MEDIA_ID = "media_id"
ATTR_MEDIA_TYPE = "media_type"
ATTR_ARTIST = "artist"
ATTR_ALBUM = "album"


async def async_setup_entry(
Expand Down Expand Up @@ -125,10 +127,12 @@ async def handle_player_added(event: MassEvent) -> None:
platform.async_register_entity_service(
SERVICE_PLAY_MEDIA_ADVANCED,
{
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
vol.Required(ATTR_MEDIA_ID): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
vol.Exclusive(ATTR_MEDIA_ENQUEUE, "enqueue_announce"): vol.Coerce(QueueOption),
vol.Exclusive(ATTR_MEDIA_ANNOUNCE, "enqueue_announce"): cv.boolean,
vol.Optional(ATTR_ARTIST): cv.string,
vol.Optional(ATTR_ALBUM): cv.string,
vol.Optional(ATTR_RADIO_MODE): vol.Coerce(bool),
},
"_async_play_media_advanced",
Expand Down Expand Up @@ -382,10 +386,12 @@ async def async_play_media(
async def _async_play_media_advanced(
self,
media_id: list[str],
artist: str | None = None,
album: str | None = None,
enqueue: MediaPlayerEnqueue | QueueOption | None = QueueOption.PLAY,
announce: bool | None = None, # noqa: ARG002
radio_mode: bool | None = None, # noqa: ARG002
media_type: str | None = None, # noqa: ARG002
radio_mode: bool | None = None,
media_type: str | None = None,
) -> None:
"""Send the play_media command to the media player."""
# pylint: disable=too-many-arguments
Expand All @@ -403,37 +409,42 @@ async def _async_play_media_advanced(
media_uris.append(item.uri)
continue
# lookup by name
if item := await self._get_item_by_name(media_id_str, media_type):
if item := await self._get_item_by_name(media_id_str, artist, album, media_type):
media_uris.append(item.uri)

if not media_uris:
return
raise MediaNotFoundError(f"Could not resolve {media_id} to playable media item")

# determine active queue to send the play request to
if queue := self.mass.players.get_player_queue(self.player.active_source):
queue_id = queue.queue_id
else:
queue_id = self.player_id

# announce/alert support (WIP)
if announce and radio_mode:
radio_mode = None
if announce is None and "/api/tts_proxy" in media_id:
announce = True
if announce:
raise NotImplementedError("Music Assistant does not yet support announcements")

await self.mass.players.play_media(
queue_id, media=media_uris, option=enqueue, radio_mode=radio_mode
)

# announce/alert support
# is_tts = "/api/tts_proxy" in media_id
# if announce or is_tts:
# self.hass.create_task(
# self.player.active_queue.play_announcement(media_id, is_tts)
# )
# else:
# await self.player.active_queue.play_media(media_id, queue_opt)

async def async_browse_media(
self, media_content_type=None, media_content_id=None
) -> BrowseMedia:
"""Implement the websocket media browsing helper."""
return await async_browse_media(self.hass, self.mass, media_content_id, media_content_type)

async def _get_item_by_name(
self, name: str, media_type: str | None = None
self,
name: str,
artist: str | None = None,
album: str | None = None,
media_type: str | None = None,
) -> MediaItemType | None:
"""Try to find a media item (such as a playlist) by name."""
# pylint: disable=too-many-nested-blocks
Expand All @@ -443,42 +454,44 @@ async def _get_item_by_name(
for x in (
self.mass.music.get_library_playlists,
self.mass.music.get_library_radios,
self.mass.music.get_library_albums,
self.mass.music.get_library_tracks,
self.mass.music.get_library_albums,
self.mass.music.get_library_artists,
)
if not media_type or media_type.lower() in x.__name__
]
if not media_type:
# address (possible) voice command with mediatype in search string
for media_type_str in ("artist", "album", "track", "playlist"):
media_type_subst_str = f"{media_type_str} "
if media_type_subst_str in searchname:
media_type = MediaType(media_type_str)
searchname = searchname.replace(media_type_subst_str, "")
break

# prefer (exact) lookup in the library by name
for func in library_functions:
result = await func(search=searchname)
for item in result.items:
# handle optional artist filter
if (
artist
and (artists := getattr(item, "artists", None))
and not any(x for x in artists if x.name.lower() == artist.lower())
):
continue
# handle optional album filter
if (
album
and (item_album := getattr(item, "album", None))
and item_album.name.lower() != album.lower()
):
continue
if searchname == item.name.lower():
return item
# repeat but account for tracks or albums where an artist name is used
if func in (self.mass.music.get_library_tracks, self.mass.music.get_library_albums):
for splitter in (" - ", " by "):
if splitter in searchname:
artistname, title = searchname.split(splitter, 1)
result = await func(search=title)
for item in result.items:
if item.name.lower() != title:
continue
for artist in item.artists:
if artist.name.lower() == artistname:
return item
# nothing found in the library, fallback to search
# nothing found in the library, fallback to global search
search_name = name
if album and artist:
search_name = f"{artist} - {album} - {name}"
elif album:
search_name = f"{album} - {name}"
elif artist:
search_name = f"{artist} - {name}"
result = await self.mass.music.search(
searchname, media_types=[media_type] if media_type else MediaType.ALL
search_query=search_name,
media_types=[media_type] if media_type else MediaType.ALL,
limit=5,
)
for results in (
result.tracks,
Expand Down
19 changes: 16 additions & 3 deletions custom_components/mass/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

SERVICE_SEARCH = "search"
ATTR_MEDIA_TYPE = "media_type"
ATTR_QUERY = "query"
ATTR_SEARCH_NAME = "name"
ATTR_SEARCH_ARTIST = "artist"
ATTR_SEARCH_ALBUM = "album"
ATTR_LIMIT = "limit"


Expand All @@ -25,8 +27,17 @@ def register_services(hass: HomeAssistant) -> None:
async def handle_search(call: ServiceCall) -> ServiceResponse:
"""Handle queue_command service."""
mass = get_mass(hass)
search_name = call.data[ATTR_SEARCH_NAME]
search_artist = call.data.get(ATTR_SEARCH_ARTIST)
search_album = call.data.get(ATTR_SEARCH_ALBUM)
if search_album and search_artist:
search_name = f"{search_artist} - {search_album} - {search_name}"
elif search_album:
search_name = f"{search_album} - {search_name}"
elif search_artist:
search_name = f"{search_artist} - {search_name}"
result = await mass.music.search(
search_query=call.data[ATTR_QUERY],
search_query=search_name,
media_types=call.data.get(ATTR_MEDIA_TYPE, MediaType.ALL),
limit=call.data[ATTR_LIMIT],
)
Expand Down Expand Up @@ -64,8 +75,10 @@ def compact_item(item: dict[str, Any]) -> dict[str, Any]:
handle_search,
schema=vol.Schema(
{
vol.Required(ATTR_QUERY): cv.string,
vol.Required(ATTR_SEARCH_NAME): cv.string,
vol.Optional(ATTR_MEDIA_TYPE): vol.All(cv.ensure_list, [vol.Coerce(MediaType)]),
vol.Optional(ATTR_SEARCH_ARTIST): cv.string,
vol.Optional(ATTR_SEARCH_ALBUM): cv.string,
vol.Optional(ATTR_LIMIT, default=5): vol.Coerce(int),
}
),
Expand Down
27 changes: 24 additions & 3 deletions custom_components/mass/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ play_media:
example: "true"
selector:
boolean:
artist:
required: false
example: "Queen"
selector:
text:
album:
required: false
example: "News of the world"
selector:
text:
radio_mode:
required: false
advanced: true
Expand All @@ -55,9 +65,9 @@ play_media:

search:
fields:
query:
name:
required: true
example: "Queen - Innuendo"
example: "We Are The Champions"
selector:
text:
media_type:
Expand All @@ -72,10 +82,21 @@ search:
- playlist
- track
- radio
artist:
required: false
example: "Queen"
selector:
text:
album:
required: false
example: "News of the world"
selector:
text:
limit:
required: false
advanced: true
example: 25
default: 25
default: 5
selector:
number:
min: 1
Expand Down
26 changes: 21 additions & 5 deletions custom_components/mass/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"name": "Announce",
"description": "If the media should be played as an announcement."
},
"artist": {
"name": "Artist name",
"description": "When specifying a track or album by name in the Media ID field, you can optionally restrict results by this artist name."
},
"album": {
"name": "Album name",
"description": "When specifying a track by name in the Media ID field, you can optionally restrict results by this album name."
},
"radio_mode": {
"name": "Enable Radio Mode",
"description": "Enable radio mode to auto generate a playlist based on the selection."
Expand All @@ -89,13 +97,21 @@
"name": "Search Music Assistant",
"description": "Perform a global search on the Music Assistant library and all providers.",
"fields": {
"query": {
"name": "Query",
"description": "The search query."
"name": {
"name": "Search name",
"description": "The name/title to search for."
},
"media_type": {
"name": "Content type(s)",
"description": "The type of the content to search. Such as artist, album, track or playlist. All types if omitted."
"name": "Media type(s)",
"description": "The type of the content to search. Such as artist, album, track, radio or playlist. All types if omitted."
},
"artist": {
"name": "Artist name",
"description": "When specifying a track or album name in the name field, you can optionally restrict results by this artist name."
},
"album": {
"name": "Album name",
"description": "When specifying a track name in the name field, you can optionally restrict results by this album name."
},
"limit": {
"name": "Limit",
Expand Down
24 changes: 20 additions & 4 deletions custom_components/mass/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
"name": "Announce",
"description": "If the media should be played as an announcement."
},
"artist": {
"name": "Artist name",
"description": "When specifying a track or album by name in the Media ID field, you can optionally restrict results by this artist name."
},
"album": {
"name": "Album name",
"description": "When specifying a track by name in the Media ID field, you can optionally restrict results by this album name."
},
"radio_mode": {
"name": "Enable Radio Mode",
"description": "Enable radio mode to auto generate a playlist based on the selection."
Expand All @@ -89,13 +97,21 @@
"name": "Search Music Assistant",
"description": "Perform a global search on the Music Assistant library and all providers.",
"fields": {
"query": {
"name": "Query",
"description": "The search query."
"name": {
"name": "Search name",
"description": "The name/title to search for."
},
"media_type": {
"name": "Media type(s)",
"description": "The type of the content to search. Such as artist, album, track or playlist. All types if omitted."
"description": "The type of the content to search. Such as artist, album, track, radio or playlist. All types if omitted."
},
"artist": {
"name": "Artist name",
"description": "When specifying a track or album name in the name field, you can optionally restrict results by this artist name."
},
"album": {
"name": "Album name",
"description": "When specifying a track name in the name field, you can optionally restrict results by this album name."
},
"limit": {
"name": "Limit",
Expand Down