mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add search functionality to jellyfin (#148822)
This commit is contained in:
parent
e28f02d163
commit
26a9af7371
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from functools import partial
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jellyfin_apiclient_python import JellyfinClient
|
from jellyfin_apiclient_python import JellyfinClient
|
||||||
@ -12,6 +13,7 @@ from homeassistant.components.media_player import (
|
|||||||
BrowseMedia,
|
BrowseMedia,
|
||||||
MediaClass,
|
MediaClass,
|
||||||
MediaType,
|
MediaType,
|
||||||
|
SearchMediaQuery,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
@ -156,6 +158,51 @@ def fetch_items(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def search_items(
|
||||||
|
hass: HomeAssistant, client: JellyfinClient, user_id: str, query: SearchMediaQuery
|
||||||
|
) -> list[BrowseMedia]:
|
||||||
|
"""Search items in Jellyfin server."""
|
||||||
|
search_result: list[BrowseMedia] = []
|
||||||
|
|
||||||
|
items: list[dict[str, Any]] = []
|
||||||
|
# Search for items based on media filter classes (or all if none specified)
|
||||||
|
media_types: list[MediaClass] | list[None] = []
|
||||||
|
if query.media_filter_classes:
|
||||||
|
media_types = query.media_filter_classes
|
||||||
|
else:
|
||||||
|
media_types = [None]
|
||||||
|
|
||||||
|
for media_type in media_types:
|
||||||
|
items_dict: dict[str, Any] = await hass.async_add_executor_job(
|
||||||
|
partial(
|
||||||
|
client.jellyfin.search_media_items,
|
||||||
|
term=query.search_query,
|
||||||
|
media=media_type,
|
||||||
|
parent_id=query.media_content_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
items.extend(items_dict.get("Items", []))
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
content_type: str = item["MediaType"]
|
||||||
|
|
||||||
|
response = BrowseMedia(
|
||||||
|
media_class=CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS.get(
|
||||||
|
content_type, MediaClass.DIRECTORY
|
||||||
|
),
|
||||||
|
media_content_id=item["Id"],
|
||||||
|
media_content_type=content_type,
|
||||||
|
title=item["Name"],
|
||||||
|
thumbnail=get_artwork_url(client, item),
|
||||||
|
can_play=bool(content_type in PLAYABLE_MEDIA_TYPES),
|
||||||
|
can_expand=item.get("IsFolder", False),
|
||||||
|
children=None,
|
||||||
|
)
|
||||||
|
search_result.append(response)
|
||||||
|
|
||||||
|
return search_result
|
||||||
|
|
||||||
|
|
||||||
async def get_media_info(
|
async def get_media_info(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
client: JellyfinClient,
|
client: JellyfinClient,
|
||||||
|
@ -11,12 +11,14 @@ from homeassistant.components.media_player import (
|
|||||||
MediaPlayerEntityFeature,
|
MediaPlayerEntityFeature,
|
||||||
MediaPlayerState,
|
MediaPlayerState,
|
||||||
MediaType,
|
MediaType,
|
||||||
|
SearchMedia,
|
||||||
|
SearchMediaQuery,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util.dt import parse_datetime
|
from homeassistant.util.dt import parse_datetime
|
||||||
|
|
||||||
from .browse_media import build_item_response, build_root_response
|
from .browse_media import build_item_response, build_root_response, search_items
|
||||||
from .client_wrapper import get_artwork_url
|
from .client_wrapper import get_artwork_url
|
||||||
from .const import CONTENT_TYPE_MAP, LOGGER, MAX_IMAGE_WIDTH
|
from .const import CONTENT_TYPE_MAP, LOGGER, MAX_IMAGE_WIDTH
|
||||||
from .coordinator import JellyfinConfigEntry, JellyfinDataUpdateCoordinator
|
from .coordinator import JellyfinConfigEntry, JellyfinDataUpdateCoordinator
|
||||||
@ -196,6 +198,7 @@ class JellyfinMediaPlayer(JellyfinClientEntity, MediaPlayerEntity):
|
|||||||
| MediaPlayerEntityFeature.PLAY
|
| MediaPlayerEntityFeature.PLAY
|
||||||
| MediaPlayerEntityFeature.STOP
|
| MediaPlayerEntityFeature.STOP
|
||||||
| MediaPlayerEntityFeature.SEEK
|
| MediaPlayerEntityFeature.SEEK
|
||||||
|
| MediaPlayerEntityFeature.SEARCH_MEDIA
|
||||||
)
|
)
|
||||||
|
|
||||||
if "Mute" in commands:
|
if "Mute" in commands:
|
||||||
@ -274,3 +277,13 @@ class JellyfinMediaPlayer(JellyfinClientEntity, MediaPlayerEntity):
|
|||||||
media_content_type,
|
media_content_type,
|
||||||
media_content_id,
|
media_content_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_search_media(
|
||||||
|
self,
|
||||||
|
query: SearchMediaQuery,
|
||||||
|
) -> SearchMedia:
|
||||||
|
"""Search the media player."""
|
||||||
|
result = await search_items(
|
||||||
|
self.hass, self.coordinator.api_client, self.coordinator.user_id, query
|
||||||
|
)
|
||||||
|
return SearchMedia(result=result)
|
||||||
|
@ -81,6 +81,7 @@ def mock_api() -> MagicMock:
|
|||||||
jf_api.get_item.side_effect = api_get_item_side_effect
|
jf_api.get_item.side_effect = api_get_item_side_effect
|
||||||
jf_api.get_media_folders.return_value = load_json_fixture("get-media-folders.json")
|
jf_api.get_media_folders.return_value = load_json_fixture("get-media-folders.json")
|
||||||
jf_api.user_items.side_effect = api_user_items_side_effect
|
jf_api.user_items.side_effect = api_user_items_side_effect
|
||||||
|
jf_api.search_media_items.return_value = load_json_fixture("user-items.json")
|
||||||
|
|
||||||
return jf_api
|
return jf_api
|
||||||
|
|
||||||
|
@ -363,6 +363,47 @@ async def test_browse_media(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_search_media(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_jellyfin: MagicMock,
|
||||||
|
mock_api: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test Jellyfin browse media."""
|
||||||
|
client = await hass_ws_client()
|
||||||
|
|
||||||
|
# browse root folder
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "media_player/search_media",
|
||||||
|
"entity_id": "media_player.jellyfin_device",
|
||||||
|
"media_content_id": "",
|
||||||
|
"media_content_type": "",
|
||||||
|
"search_query": "Fake Item 1",
|
||||||
|
"media_filter_classes": ["movie"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"]["result"] == [
|
||||||
|
{
|
||||||
|
"title": "FOLDER",
|
||||||
|
"media_class": MediaClass.DIRECTORY.value,
|
||||||
|
"media_content_type": "string",
|
||||||
|
"media_content_id": "FOLDER-UUID",
|
||||||
|
"children_media_class": None,
|
||||||
|
"can_play": False,
|
||||||
|
"can_expand": True,
|
||||||
|
"can_search": False,
|
||||||
|
"not_shown": 0,
|
||||||
|
"thumbnail": "http://localhost/Items/21af9851-8e39-43a9-9c47-513d3b9e99fc/Images/Primary.jpg",
|
||||||
|
"children": [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_new_client_connected(
|
async def test_new_client_connected(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
init_integration: MockConfigEntry,
|
init_integration: MockConfigEntry,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user