mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add media_player platform to Jellyfin (#76801)
This commit is contained in:
parent
9b331abe91
commit
3759be09df
@ -26,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_, connect_result = await validate_input(hass, dict(entry.data), client)
|
user_id, connect_result = await validate_input(hass, dict(entry.data), client)
|
||||||
except CannotConnect as ex:
|
except CannotConnect as ex:
|
||||||
raise ConfigEntryNotReady("Cannot connect to Jellyfin server") from ex
|
raise ConfigEntryNotReady("Cannot connect to Jellyfin server") from ex
|
||||||
except InvalidAuth:
|
except InvalidAuth:
|
||||||
@ -36,13 +36,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
server_info: dict[str, Any] = connect_result["Servers"][0]
|
server_info: dict[str, Any] = connect_result["Servers"][0]
|
||||||
|
|
||||||
coordinators: dict[str, JellyfinDataUpdateCoordinator[Any]] = {
|
coordinators: dict[str, JellyfinDataUpdateCoordinator[Any]] = {
|
||||||
"sessions": SessionsDataUpdateCoordinator(hass, client, server_info),
|
"sessions": SessionsDataUpdateCoordinator(hass, client, server_info, user_id),
|
||||||
}
|
}
|
||||||
|
|
||||||
for coordinator in coordinators.values():
|
for coordinator in coordinators.values():
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = JellyfinData(
|
hass.data[DOMAIN][entry.entry_id] = JellyfinData(
|
||||||
|
client_device_id=entry.data[CONF_CLIENT_DEVICE_ID],
|
||||||
jellyfin_client=client,
|
jellyfin_client=client,
|
||||||
coordinators=coordinators,
|
coordinators=coordinators,
|
||||||
)
|
)
|
||||||
|
179
homeassistant/components/jellyfin/browse_media.py
Normal file
179
homeassistant/components/jellyfin/browse_media.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
"""Support for media browsing."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from jellyfin_apiclient_python import JellyfinClient
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import BrowseError, MediaClass, MediaType
|
||||||
|
from homeassistant.components.media_player.browse_media import BrowseMedia
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .client_wrapper import get_artwork_url
|
||||||
|
from .const import CONTENT_TYPE_MAP, MEDIA_CLASS_MAP, MEDIA_TYPE_NONE
|
||||||
|
|
||||||
|
CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS: dict[str, str] = {
|
||||||
|
MediaType.MUSIC: MediaClass.MUSIC,
|
||||||
|
MediaType.SEASON: MediaClass.SEASON,
|
||||||
|
MediaType.TVSHOW: MediaClass.TV_SHOW,
|
||||||
|
"boxset": MediaClass.DIRECTORY,
|
||||||
|
"collection": MediaClass.DIRECTORY,
|
||||||
|
"library": MediaClass.DIRECTORY,
|
||||||
|
}
|
||||||
|
|
||||||
|
JF_SUPPORTED_LIBRARY_TYPES = ["movies", "music", "tvshows"]
|
||||||
|
|
||||||
|
PLAYABLE_MEDIA_TYPES = [
|
||||||
|
MediaType.EPISODE,
|
||||||
|
MediaType.MOVIE,
|
||||||
|
MediaType.MUSIC,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def item_payload(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
client: JellyfinClient,
|
||||||
|
user_id: str,
|
||||||
|
item: dict[str, Any],
|
||||||
|
) -> BrowseMedia:
|
||||||
|
"""Create response payload for a single media item."""
|
||||||
|
title = item["Name"]
|
||||||
|
thumbnail = get_artwork_url(client, item, 600)
|
||||||
|
|
||||||
|
media_content_id = item["Id"]
|
||||||
|
media_content_type = CONTENT_TYPE_MAP.get(item["Type"], MEDIA_TYPE_NONE)
|
||||||
|
|
||||||
|
return BrowseMedia(
|
||||||
|
title=title,
|
||||||
|
media_content_id=media_content_id,
|
||||||
|
media_content_type=media_content_type,
|
||||||
|
media_class=MEDIA_CLASS_MAP.get(item["Type"], MediaClass.DIRECTORY),
|
||||||
|
can_play=bool(media_content_type in PLAYABLE_MEDIA_TYPES and media_content_id),
|
||||||
|
can_expand=bool(item.get("IsFolder")),
|
||||||
|
children_media_class=None,
|
||||||
|
thumbnail=thumbnail,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def build_root_response(
|
||||||
|
hass: HomeAssistant, client: JellyfinClient, user_id: str
|
||||||
|
) -> BrowseMedia:
|
||||||
|
"""Create response payload for root folder."""
|
||||||
|
folders = await hass.async_add_executor_job(client.jellyfin.get_media_folders)
|
||||||
|
|
||||||
|
children = [
|
||||||
|
await item_payload(hass, client, user_id, folder)
|
||||||
|
for folder in folders["Items"]
|
||||||
|
if folder["CollectionType"] in JF_SUPPORTED_LIBRARY_TYPES
|
||||||
|
]
|
||||||
|
|
||||||
|
return BrowseMedia(
|
||||||
|
media_content_id="",
|
||||||
|
media_content_type="root",
|
||||||
|
media_class=MediaClass.DIRECTORY,
|
||||||
|
children_media_class=MediaClass.DIRECTORY,
|
||||||
|
title="Jellyfin",
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
children=children,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def build_item_response(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
client: JellyfinClient,
|
||||||
|
user_id: str,
|
||||||
|
media_content_type: str | None,
|
||||||
|
media_content_id: str,
|
||||||
|
) -> BrowseMedia:
|
||||||
|
"""Create response payload for the provided media query."""
|
||||||
|
title, media, thumbnail = await get_media_info(
|
||||||
|
hass, client, user_id, media_content_type, media_content_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if title is None or media is None:
|
||||||
|
raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
|
||||||
|
|
||||||
|
children = await asyncio.gather(
|
||||||
|
*(item_payload(hass, client, user_id, media_item) for media_item in media)
|
||||||
|
)
|
||||||
|
|
||||||
|
response = BrowseMedia(
|
||||||
|
media_class=CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS.get(
|
||||||
|
str(media_content_type), MediaClass.DIRECTORY
|
||||||
|
),
|
||||||
|
media_content_id=media_content_id,
|
||||||
|
media_content_type=str(media_content_type),
|
||||||
|
title=title,
|
||||||
|
can_play=bool(media_content_type in PLAYABLE_MEDIA_TYPES and media_content_id),
|
||||||
|
can_expand=True,
|
||||||
|
children=children,
|
||||||
|
thumbnail=thumbnail,
|
||||||
|
)
|
||||||
|
|
||||||
|
response.calculate_children_class()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_item(client: JellyfinClient, item_id: str) -> dict[str, Any] | None:
|
||||||
|
"""Fetch item from Jellyfin server."""
|
||||||
|
result = client.jellyfin.get_item(item_id)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
|
||||||
|
item: dict[str, Any] = result
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_items(
|
||||||
|
client: JellyfinClient,
|
||||||
|
params: dict[str, Any],
|
||||||
|
) -> list[dict[str, Any]] | None:
|
||||||
|
"""Fetch items from Jellyfin server."""
|
||||||
|
result = client.jellyfin.user_items(params=params)
|
||||||
|
|
||||||
|
if not result or "Items" not in result or len(result["Items"]) < 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
items: list[dict[str, Any]] = result["Items"]
|
||||||
|
|
||||||
|
return [
|
||||||
|
item
|
||||||
|
for item in items
|
||||||
|
if not item.get("IsFolder")
|
||||||
|
or (item.get("IsFolder") and item.get("ChildCount", 1) > 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_media_info(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
client: JellyfinClient,
|
||||||
|
user_id: str,
|
||||||
|
media_content_type: str | None,
|
||||||
|
media_content_id: str,
|
||||||
|
) -> tuple[str | None, list[dict[str, Any]] | None, str | None]:
|
||||||
|
"""Fetch media info."""
|
||||||
|
thumbnail: str | None = None
|
||||||
|
title: str | None = None
|
||||||
|
media: list[dict[str, Any]] | None = None
|
||||||
|
|
||||||
|
item = await hass.async_add_executor_job(fetch_item, client, media_content_id)
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
title = item["Name"]
|
||||||
|
thumbnail = get_artwork_url(client, item)
|
||||||
|
|
||||||
|
if item.get("IsFolder"):
|
||||||
|
media = await hass.async_add_executor_job(
|
||||||
|
fetch_items, client, {"parentId": media_content_id, "fields": "childCount"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not media or len(media) == 0:
|
||||||
|
media = None
|
||||||
|
|
||||||
|
return title, media, thumbnail
|
@ -15,7 +15,7 @@ from homeassistant import exceptions
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import CLIENT_VERSION, USER_AGENT, USER_APP_NAME
|
from .const import CLIENT_VERSION, ITEM_KEY_IMAGE_TAGS, USER_AGENT, USER_APP_NAME
|
||||||
|
|
||||||
|
|
||||||
async def validate_input(
|
async def validate_input(
|
||||||
@ -92,6 +92,25 @@ def _get_user_id(api: API) -> str:
|
|||||||
return userid
|
return userid
|
||||||
|
|
||||||
|
|
||||||
|
def get_artwork_url(
|
||||||
|
client: JellyfinClient, item: dict[str, Any], max_width: int = 600
|
||||||
|
) -> str | None:
|
||||||
|
"""Find a suitable thumbnail for an item."""
|
||||||
|
artwork_id: str = item["Id"]
|
||||||
|
artwork_type = "Primary"
|
||||||
|
parent_backdrop_id: str | None = item.get("ParentBackdropItemId")
|
||||||
|
|
||||||
|
if "Backdrop" in item[ITEM_KEY_IMAGE_TAGS]:
|
||||||
|
artwork_type = "Backdrop"
|
||||||
|
elif parent_backdrop_id:
|
||||||
|
artwork_type = "Backdrop"
|
||||||
|
artwork_id = parent_backdrop_id
|
||||||
|
elif "Primary" not in item[ITEM_KEY_IMAGE_TAGS]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return str(client.jellyfin.artwork(artwork_id, artwork_type, max_width))
|
||||||
|
|
||||||
|
|
||||||
class CannotConnect(exceptions.HomeAssistantError):
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate the server is unreachable."""
|
"""Error to indicate the server is unreachable."""
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import MediaClass, MediaType
|
||||||
from homeassistant.const import Platform, __version__ as hass_version
|
from homeassistant.const import Platform, __version__ as hass_version
|
||||||
|
|
||||||
DOMAIN: Final = "jellyfin"
|
DOMAIN: Final = "jellyfin"
|
||||||
@ -32,7 +33,6 @@ ITEM_TYPE_MOVIE: Final = "Movie"
|
|||||||
MAX_IMAGE_WIDTH: Final = 500
|
MAX_IMAGE_WIDTH: Final = 500
|
||||||
MAX_STREAMING_BITRATE: Final = "140000000"
|
MAX_STREAMING_BITRATE: Final = "140000000"
|
||||||
|
|
||||||
|
|
||||||
MEDIA_SOURCE_KEY_PATH: Final = "Path"
|
MEDIA_SOURCE_KEY_PATH: Final = "Path"
|
||||||
|
|
||||||
MEDIA_TYPE_AUDIO: Final = "Audio"
|
MEDIA_TYPE_AUDIO: Final = "Audio"
|
||||||
@ -44,5 +44,29 @@ SUPPORTED_COLLECTION_TYPES: Final = [COLLECTION_TYPE_MUSIC, COLLECTION_TYPE_MOVI
|
|||||||
USER_APP_NAME: Final = "Home Assistant"
|
USER_APP_NAME: Final = "Home Assistant"
|
||||||
USER_AGENT: Final = f"Home-Assistant/{CLIENT_VERSION}"
|
USER_AGENT: Final = f"Home-Assistant/{CLIENT_VERSION}"
|
||||||
|
|
||||||
PLATFORMS = [Platform.SENSOR]
|
CONTENT_TYPE_MAP = {
|
||||||
|
"Audio": MediaType.MUSIC,
|
||||||
|
"Episode": MediaType.EPISODE,
|
||||||
|
"Season": MediaType.SEASON,
|
||||||
|
"Series": MediaType.TVSHOW,
|
||||||
|
"Movie": MediaType.MOVIE,
|
||||||
|
"CollectionFolder": "collection",
|
||||||
|
"AggregateFolder": "library",
|
||||||
|
"Folder": "library",
|
||||||
|
"BoxSet": "boxset",
|
||||||
|
}
|
||||||
|
MEDIA_CLASS_MAP = {
|
||||||
|
"MusicAlbum": MediaClass.ALBUM,
|
||||||
|
"MusicArtist": MediaClass.ARTIST,
|
||||||
|
"Audio": MediaClass.MUSIC,
|
||||||
|
"Series": MediaClass.DIRECTORY,
|
||||||
|
"Movie": MediaClass.MOVIE,
|
||||||
|
"CollectionFolder": MediaClass.DIRECTORY,
|
||||||
|
"Folder": MediaClass.DIRECTORY,
|
||||||
|
"BoxSet": MediaClass.DIRECTORY,
|
||||||
|
"Episode": MediaClass.EPISODE,
|
||||||
|
"Season": MediaClass.SEASON,
|
||||||
|
}
|
||||||
|
|
||||||
|
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.SENSOR]
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
@ -32,18 +32,20 @@ class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT]):
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
api_client: JellyfinClient,
|
api_client: JellyfinClient,
|
||||||
system_info: dict[str, Any],
|
system_info: dict[str, Any],
|
||||||
|
user_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the coordinator."""
|
"""Initialize the coordinator."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass=hass,
|
hass=hass,
|
||||||
logger=LOGGER,
|
logger=LOGGER,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=30),
|
update_interval=timedelta(seconds=10),
|
||||||
)
|
)
|
||||||
self.api_client: JellyfinClient = api_client
|
self.api_client: JellyfinClient = api_client
|
||||||
self.server_id: str = system_info["Id"]
|
self.server_id: str = system_info["Id"]
|
||||||
self.server_name: str = system_info["Name"]
|
self.server_name: str = system_info["Name"]
|
||||||
self.server_version: str | None = system_info.get("Version")
|
self.server_version: str | None = system_info.get("Version")
|
||||||
|
self.user_id: str = user_id
|
||||||
|
|
||||||
async def _async_update_data(self) -> JellyfinDataT:
|
async def _async_update_data(self) -> JellyfinDataT:
|
||||||
"""Get the latest data from Jellyfin."""
|
"""Get the latest data from Jellyfin."""
|
||||||
|
295
homeassistant/components/jellyfin/media_player.py
Normal file
295
homeassistant/components/jellyfin/media_player.py
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
"""Support for the Jellyfin media player."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import (
|
||||||
|
MediaPlayerEntity,
|
||||||
|
MediaPlayerEntityDescription,
|
||||||
|
MediaPlayerEntityFeature,
|
||||||
|
MediaPlayerState,
|
||||||
|
MediaType,
|
||||||
|
)
|
||||||
|
from homeassistant.components.media_player.browse_media import BrowseMedia
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.util.dt import parse_datetime
|
||||||
|
|
||||||
|
from .browse_media import build_item_response, build_root_response
|
||||||
|
from .client_wrapper import get_artwork_url
|
||||||
|
from .const import CONTENT_TYPE_MAP, DOMAIN
|
||||||
|
from .coordinator import JellyfinDataUpdateCoordinator
|
||||||
|
from .entity import JellyfinEntity
|
||||||
|
from .models import JellyfinData
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Jellyfin media_player from a config entry."""
|
||||||
|
jellyfin_data: JellyfinData = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
coordinator = jellyfin_data.coordinators["sessions"]
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
(
|
||||||
|
JellyfinMediaPlayer(coordinator, session_id, session_data)
|
||||||
|
for session_id, session_data in coordinator.data.items()
|
||||||
|
if session_data["DeviceId"] != jellyfin_data.client_device_id
|
||||||
|
and session_data["Client"] != "Home Assistant"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity):
|
||||||
|
"""Represents a Jellyfin Player device."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: JellyfinDataUpdateCoordinator,
|
||||||
|
session_id: str,
|
||||||
|
session_data: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the Jellyfin Media Player entity."""
|
||||||
|
super().__init__(
|
||||||
|
coordinator,
|
||||||
|
MediaPlayerEntityDescription(
|
||||||
|
key=session_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session_id = session_id
|
||||||
|
self.session_data: dict[str, Any] | None = session_data
|
||||||
|
self.device_id: str = session_data["DeviceId"]
|
||||||
|
self.device_name: str = session_data["DeviceName"]
|
||||||
|
self.client_name: str = session_data["Client"]
|
||||||
|
self.app_version: str = session_data["ApplicationVersion"]
|
||||||
|
|
||||||
|
self.capabilities: dict[str, Any] = session_data["Capabilities"]
|
||||||
|
self.now_playing: dict[str, Any] | None = session_data.get("NowPlayingItem")
|
||||||
|
self.play_state: dict[str, Any] | None = session_data.get("PlayState")
|
||||||
|
|
||||||
|
if self.capabilities.get("SupportsPersistentIdentifier", False):
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self.device_id)},
|
||||||
|
manufacturer="Jellyfin",
|
||||||
|
model=self.client_name,
|
||||||
|
name=self.device_name,
|
||||||
|
sw_version=self.app_version,
|
||||||
|
via_device=(DOMAIN, coordinator.server_id),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._attr_device_info = None
|
||||||
|
self._attr_has_entity_name = False
|
||||||
|
self._attr_name = self.device_name
|
||||||
|
|
||||||
|
self._update_from_session_data()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
self.session_data = (
|
||||||
|
self.coordinator.data.get(self.session_id)
|
||||||
|
if self.coordinator.data is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.session_data is not None:
|
||||||
|
self.now_playing = self.session_data.get("NowPlayingItem")
|
||||||
|
self.play_state = self.session_data.get("PlayState")
|
||||||
|
else:
|
||||||
|
self.now_playing = None
|
||||||
|
self.play_state = None
|
||||||
|
|
||||||
|
self._update_from_session_data()
|
||||||
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_from_session_data(self) -> None:
|
||||||
|
"""Process session data to update entity properties."""
|
||||||
|
state = None
|
||||||
|
media_content_type = None
|
||||||
|
media_content_id = None
|
||||||
|
media_title = None
|
||||||
|
media_series_title = None
|
||||||
|
media_season = None
|
||||||
|
media_episode = None
|
||||||
|
media_album_name = None
|
||||||
|
media_album_artist = None
|
||||||
|
media_artist = None
|
||||||
|
media_track = None
|
||||||
|
media_duration = None
|
||||||
|
media_position = None
|
||||||
|
media_position_updated = None
|
||||||
|
volume_muted = False
|
||||||
|
volume_level = None
|
||||||
|
|
||||||
|
if self.session_data is not None:
|
||||||
|
state = MediaPlayerState.IDLE
|
||||||
|
media_position_updated = (
|
||||||
|
parse_datetime(self.session_data["LastPlaybackCheckIn"])
|
||||||
|
if self.now_playing
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.now_playing is not None:
|
||||||
|
state = MediaPlayerState.PLAYING
|
||||||
|
media_content_type = CONTENT_TYPE_MAP.get(self.now_playing["Type"], None)
|
||||||
|
media_content_id = self.now_playing["Id"]
|
||||||
|
media_title = self.now_playing["Name"]
|
||||||
|
media_duration = int(self.now_playing["RunTimeTicks"] / 10000000)
|
||||||
|
|
||||||
|
if media_content_type == MediaType.EPISODE:
|
||||||
|
media_content_type = MediaType.TVSHOW
|
||||||
|
media_series_title = self.now_playing.get("SeriesName")
|
||||||
|
media_season = self.now_playing.get("ParentIndexNumber")
|
||||||
|
media_episode = self.now_playing.get("IndexNumber")
|
||||||
|
elif media_content_type == MediaType.MUSIC:
|
||||||
|
media_album_name = self.now_playing.get("Album")
|
||||||
|
media_album_artist = self.now_playing.get("AlbumArtist")
|
||||||
|
media_track = self.now_playing.get("IndexNumber")
|
||||||
|
if media_artists := self.now_playing.get("Artists"):
|
||||||
|
media_artist = str(media_artists[0])
|
||||||
|
|
||||||
|
if self.play_state is not None:
|
||||||
|
if self.play_state.get("IsPaused"):
|
||||||
|
state = MediaPlayerState.PAUSED
|
||||||
|
|
||||||
|
media_position = (
|
||||||
|
int(self.play_state["PositionTicks"] / 10000000)
|
||||||
|
if "PositionTicks" in self.play_state
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
volume_muted = bool(self.play_state.get("IsMuted", False))
|
||||||
|
volume_level = (
|
||||||
|
float(self.play_state["VolumeLevel"] / 100)
|
||||||
|
if "VolumeLevel" in self.play_state
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_state = state
|
||||||
|
self._attr_is_volume_muted = volume_muted
|
||||||
|
self._attr_volume_level = volume_level
|
||||||
|
self._attr_media_content_type = media_content_type
|
||||||
|
self._attr_media_content_id = media_content_id
|
||||||
|
self._attr_media_title = media_title
|
||||||
|
self._attr_media_series_title = media_series_title
|
||||||
|
self._attr_media_season = media_season
|
||||||
|
self._attr_media_episode = media_episode
|
||||||
|
self._attr_media_album_name = media_album_name
|
||||||
|
self._attr_media_album_artist = media_album_artist
|
||||||
|
self._attr_media_artist = media_artist
|
||||||
|
self._attr_media_track = media_track
|
||||||
|
self._attr_media_duration = media_duration
|
||||||
|
self._attr_media_position = media_position
|
||||||
|
self._attr_media_position_updated_at = media_position_updated
|
||||||
|
self._attr_media_image_remotely_accessible = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_image_url(self) -> str | None:
|
||||||
|
"""Image url of current playing media."""
|
||||||
|
# We always need the now playing item.
|
||||||
|
# If there is none, there's also no url
|
||||||
|
if self.now_playing is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return get_artwork_url(self.coordinator.api_client, self.now_playing, 150)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self) -> int:
|
||||||
|
"""Flag media player features that are supported."""
|
||||||
|
commands: list[str] = self.capabilities.get("SupportedCommands", [])
|
||||||
|
controllable = self.capabilities.get("SupportsMediaControl", False)
|
||||||
|
features = 0
|
||||||
|
|
||||||
|
if controllable:
|
||||||
|
features |= (
|
||||||
|
MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||||
|
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||||
|
| MediaPlayerEntityFeature.PAUSE
|
||||||
|
| MediaPlayerEntityFeature.PLAY
|
||||||
|
| MediaPlayerEntityFeature.STOP
|
||||||
|
| MediaPlayerEntityFeature.SEEK
|
||||||
|
)
|
||||||
|
|
||||||
|
if "Mute" in commands:
|
||||||
|
features |= MediaPlayerEntityFeature.VOLUME_MUTE
|
||||||
|
|
||||||
|
if "VolumeSet" in commands:
|
||||||
|
features |= MediaPlayerEntityFeature.VOLUME_SET
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return if entity is available."""
|
||||||
|
return self.coordinator.last_update_success and self.session_data is not None
|
||||||
|
|
||||||
|
def media_seek(self, position: float) -> None:
|
||||||
|
"""Send seek command."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_seek(
|
||||||
|
self.session_id, int(position * 10000000)
|
||||||
|
)
|
||||||
|
|
||||||
|
def media_pause(self) -> None:
|
||||||
|
"""Send pause command."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_pause(self.session_id)
|
||||||
|
self._attr_state = MediaPlayerState.PAUSED
|
||||||
|
|
||||||
|
def media_play(self) -> None:
|
||||||
|
"""Send play command."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_unpause(self.session_id)
|
||||||
|
self._attr_state = MediaPlayerState.PLAYING
|
||||||
|
|
||||||
|
def media_play_pause(self) -> None:
|
||||||
|
"""Send the PlayPause command to the session."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_playpause(self.session_id)
|
||||||
|
|
||||||
|
def media_stop(self) -> None:
|
||||||
|
"""Send stop command."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_stop(self.session_id)
|
||||||
|
self._attr_state = MediaPlayerState.IDLE
|
||||||
|
|
||||||
|
def play_media(
|
||||||
|
self, media_type: str, media_id: str, **kwargs: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Play a piece of media."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_play_media(
|
||||||
|
self.session_id, [media_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_volume_level(self, volume: float) -> None:
|
||||||
|
"""Set volume level, range 0..1."""
|
||||||
|
self.coordinator.api_client.jellyfin.remote_set_volume(
|
||||||
|
self.session_id, int(volume * 100)
|
||||||
|
)
|
||||||
|
|
||||||
|
def mute_volume(self, mute: bool) -> None:
|
||||||
|
"""Mute the volume."""
|
||||||
|
if mute:
|
||||||
|
self.coordinator.api_client.jellyfin.remote_mute(self.session_id)
|
||||||
|
else:
|
||||||
|
self.coordinator.api_client.jellyfin.remote_unmute(self.session_id)
|
||||||
|
|
||||||
|
async def async_browse_media(
|
||||||
|
self, media_content_type: str | None = None, media_content_id: str | None = None
|
||||||
|
) -> BrowseMedia:
|
||||||
|
"""Return a BrowseMedia instance.
|
||||||
|
|
||||||
|
The BrowseMedia instance will be used by the "media_player/browse_media" websocket command.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if media_content_id is None or media_content_id == "media-source://jellyfin":
|
||||||
|
return await build_root_response(
|
||||||
|
self.hass, self.coordinator.api_client, self.coordinator.user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return await build_item_response(
|
||||||
|
self.hass,
|
||||||
|
self.coordinator.api_client,
|
||||||
|
self.coordinator.user_id,
|
||||||
|
media_content_type,
|
||||||
|
media_content_id,
|
||||||
|
)
|
@ -12,5 +12,6 @@ from .coordinator import JellyfinDataUpdateCoordinator
|
|||||||
class JellyfinData:
|
class JellyfinData:
|
||||||
"""Data for the Jellyfin integration."""
|
"""Data for the Jellyfin integration."""
|
||||||
|
|
||||||
|
client_device_id: str
|
||||||
jellyfin_client: JellyfinClient
|
jellyfin_client: JellyfinClient
|
||||||
coordinators: dict[str, JellyfinDataUpdateCoordinator]
|
coordinators: dict[str, JellyfinDataUpdateCoordinator]
|
||||||
|
@ -73,6 +73,12 @@ def mock_api() -> MagicMock:
|
|||||||
jf_api.get_user_settings.return_value = load_json_fixture("get-user-settings.json")
|
jf_api.get_user_settings.return_value = load_json_fixture("get-user-settings.json")
|
||||||
jf_api.sessions.return_value = load_json_fixture("sessions.json")
|
jf_api.sessions.return_value = load_json_fixture("sessions.json")
|
||||||
|
|
||||||
|
jf_api.artwork.side_effect = api_artwork_side_effect
|
||||||
|
jf_api.user_items.side_effect = api_user_items_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.user_items.side_effect = api_user_items_side_effect
|
||||||
|
|
||||||
return jf_api
|
return jf_api
|
||||||
|
|
||||||
|
|
||||||
@ -121,3 +127,27 @@ async def init_integration(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
return mock_config_entry
|
return mock_config_entry
|
||||||
|
|
||||||
|
|
||||||
|
def api_artwork_side_effect(*args, **kwargs):
|
||||||
|
"""Handle variable responses for artwork method."""
|
||||||
|
item_id = args[0]
|
||||||
|
art = args[1]
|
||||||
|
ext = "jpg"
|
||||||
|
|
||||||
|
return f"http://localhost/Items/{item_id}/Images/{art}.{ext}"
|
||||||
|
|
||||||
|
|
||||||
|
def api_get_item_side_effect(*args):
|
||||||
|
"""Handle variable responses for get_item method."""
|
||||||
|
return load_json_fixture("get-item-collection.json")
|
||||||
|
|
||||||
|
|
||||||
|
def api_user_items_side_effect(*args, **kwargs):
|
||||||
|
"""Handle variable responses for items method."""
|
||||||
|
params = kwargs.get("params", {}) if kwargs else {}
|
||||||
|
|
||||||
|
if "parentId" in params:
|
||||||
|
return load_json_fixture("user-items-parent-id.json")
|
||||||
|
|
||||||
|
return load_json_fixture("user-items.json")
|
||||||
|
504
tests/components/jellyfin/fixtures/get-item-collection.json
Normal file
504
tests/components/jellyfin/fixtures/get-item-collection.json
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
{
|
||||||
|
"Name": "FOLDER",
|
||||||
|
"OriginalTitle": "string",
|
||||||
|
"ServerId": "SERVER-UUID",
|
||||||
|
"Id": "FOLDER-UUID",
|
||||||
|
"Etag": "string",
|
||||||
|
"SourceType": "string",
|
||||||
|
"PlaylistItemId": "string",
|
||||||
|
"DateCreated": "2019-08-24T14:15:22Z",
|
||||||
|
"DateLastMediaAdded": "2019-08-24T14:15:22Z",
|
||||||
|
"ExtraType": "string",
|
||||||
|
"AirsBeforeSeasonNumber": 0,
|
||||||
|
"AirsAfterSeasonNumber": 0,
|
||||||
|
"AirsBeforeEpisodeNumber": 0,
|
||||||
|
"CanDelete": true,
|
||||||
|
"CanDownload": true,
|
||||||
|
"HasSubtitles": true,
|
||||||
|
"PreferredMetadataLanguage": "string",
|
||||||
|
"PreferredMetadataCountryCode": "string",
|
||||||
|
"SupportsSync": true,
|
||||||
|
"Container": "string",
|
||||||
|
"SortName": "string",
|
||||||
|
"ForcedSortName": "string",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"PremiereDate": "2019-08-24T14:15:22Z",
|
||||||
|
"ExternalUrls": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Url": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaSources": [
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "string",
|
||||||
|
"Path": "string",
|
||||||
|
"EncoderPath": "string",
|
||||||
|
"EncoderProtocol": "File",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "string",
|
||||||
|
"Size": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"IsRemote": true,
|
||||||
|
"ETag": "string",
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"ReadAtNativeFramerate": true,
|
||||||
|
"IgnoreDts": true,
|
||||||
|
"IgnoreIndex": true,
|
||||||
|
"GenPtsInput": true,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": true,
|
||||||
|
"RequiresOpening": true,
|
||||||
|
"OpenToken": "string",
|
||||||
|
"RequiresClosing": true,
|
||||||
|
"LiveStreamId": "string",
|
||||||
|
"BufferMs": 0,
|
||||||
|
"RequiresLooping": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Comment": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"FileName": "string",
|
||||||
|
"MimeType": "string",
|
||||||
|
"DeliveryUrl": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Formats": ["string"],
|
||||||
|
"Bitrate": 0,
|
||||||
|
"Timestamp": "None",
|
||||||
|
"RequiredHttpHeaders": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"TranscodingUrl": "string",
|
||||||
|
"TranscodingSubProtocol": "string",
|
||||||
|
"TranscodingContainer": "string",
|
||||||
|
"AnalyzeDurationMs": 0,
|
||||||
|
"DefaultAudioStreamIndex": 0,
|
||||||
|
"DefaultSubtitleStreamIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CriticRating": 0,
|
||||||
|
"ProductionLocations": ["string"],
|
||||||
|
"Path": "string",
|
||||||
|
"EnableMediaSourceDisplay": true,
|
||||||
|
"OfficialRating": "string",
|
||||||
|
"CustomRating": "string",
|
||||||
|
"ChannelId": "04b0b2a5-93cb-474d-8ea9-3df0f84eb0ff",
|
||||||
|
"ChannelName": "string",
|
||||||
|
"Overview": "string",
|
||||||
|
"Taglines": ["string"],
|
||||||
|
"Genres": ["string"],
|
||||||
|
"CommunityRating": 0,
|
||||||
|
"CumulativeRunTimeTicks": 0,
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"PlayAccess": "Full",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"ProductionYear": 0,
|
||||||
|
"IsPlaceHolder": true,
|
||||||
|
"Number": "string",
|
||||||
|
"ChannelNumber": "string",
|
||||||
|
"IndexNumber": 0,
|
||||||
|
"IndexNumberEnd": 0,
|
||||||
|
"ParentIndexNumber": 0,
|
||||||
|
"RemoteTrailers": [
|
||||||
|
{
|
||||||
|
"Url": "string",
|
||||||
|
"Name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ProviderIds": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"IsHD": true,
|
||||||
|
"IsFolder": true,
|
||||||
|
"ParentId": "c54e2d15-b5eb-48b7-9b04-53f376904b1e",
|
||||||
|
"Type": "CollectionFolder",
|
||||||
|
"People": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43",
|
||||||
|
"Role": "string",
|
||||||
|
"Type": "string",
|
||||||
|
"PrimaryImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Studios": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenreItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ParentLogoItemId": "c78d400f-de5c-421e-8714-4fb05d387233",
|
||||||
|
"ParentBackdropItemId": "c22fd826-17fc-44f4-9b04-1eb3e8fb9173",
|
||||||
|
"ParentBackdropImageTags": ["string"],
|
||||||
|
"LocalTrailerCount": 0,
|
||||||
|
"UserData": {
|
||||||
|
"Rating": 0,
|
||||||
|
"PlayedPercentage": 0,
|
||||||
|
"UnplayedItemCount": 0,
|
||||||
|
"PlaybackPositionTicks": 0,
|
||||||
|
"PlayCount": 0,
|
||||||
|
"IsFavorite": true,
|
||||||
|
"Likes": true,
|
||||||
|
"LastPlayedDate": "2019-08-24T14:15:22Z",
|
||||||
|
"Played": true,
|
||||||
|
"Key": "string",
|
||||||
|
"ItemId": "string"
|
||||||
|
},
|
||||||
|
"RecursiveItemCount": 0,
|
||||||
|
"ChildCount": 0,
|
||||||
|
"SeriesName": "string",
|
||||||
|
"SeriesId": "c7b70af4-4902-4a7e-95ab-28349b6c7afc",
|
||||||
|
"SeasonId": "badb6463-e5b7-45c5-8141-71204420ec8f",
|
||||||
|
"SpecialFeatureCount": 0,
|
||||||
|
"DisplayPreferencesId": "string",
|
||||||
|
"Status": "string",
|
||||||
|
"AirTime": "string",
|
||||||
|
"AirDays": ["Sunday"],
|
||||||
|
"Tags": ["string"],
|
||||||
|
"PrimaryImageAspectRatio": 0,
|
||||||
|
"Artists": ["string"],
|
||||||
|
"ArtistItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Album": "string",
|
||||||
|
"CollectionType": "string",
|
||||||
|
"DisplayOrder": "string",
|
||||||
|
"AlbumId": "21af9851-8e39-43a9-9c47-513d3b9e99fc",
|
||||||
|
"AlbumPrimaryImageTag": "string",
|
||||||
|
"SeriesPrimaryImageTag": "string",
|
||||||
|
"AlbumArtist": "string",
|
||||||
|
"AlbumArtists": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SeasonName": "string",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"PartCount": 0,
|
||||||
|
"MediaSourceCount": 0,
|
||||||
|
"ImageTags": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BackdropImageTags": ["string"],
|
||||||
|
"ScreenshotImageTags": ["string"],
|
||||||
|
"ParentLogoImageTag": "string",
|
||||||
|
"ParentArtItemId": "10c1875b-b82c-48e8-bae9-939a5e68dc2f",
|
||||||
|
"ParentArtImageTag": "string",
|
||||||
|
"SeriesThumbImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SeriesStudio": "string",
|
||||||
|
"ParentThumbItemId": "ae6ff707-333d-4994-be6d-b83ca1b35f46",
|
||||||
|
"ParentThumbImageTag": "string",
|
||||||
|
"ParentPrimaryImageItemId": "string",
|
||||||
|
"ParentPrimaryImageTag": "string",
|
||||||
|
"Chapters": [
|
||||||
|
{
|
||||||
|
"StartPositionTicks": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"ImagePath": "string",
|
||||||
|
"ImageDateModified": "2019-08-24T14:15:22Z",
|
||||||
|
"ImageTag": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocationType": "FileSystem",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"MediaType": "string",
|
||||||
|
"EndDate": "2019-08-24T14:15:22Z",
|
||||||
|
"LockedFields": ["Cast"],
|
||||||
|
"TrailerCount": 0,
|
||||||
|
"MovieCount": 0,
|
||||||
|
"SeriesCount": 0,
|
||||||
|
"ProgramCount": 0,
|
||||||
|
"EpisodeCount": 0,
|
||||||
|
"SongCount": 0,
|
||||||
|
"AlbumCount": 0,
|
||||||
|
"ArtistCount": 0,
|
||||||
|
"MusicVideoCount": 0,
|
||||||
|
"LockData": true,
|
||||||
|
"Width": 0,
|
||||||
|
"Height": 0,
|
||||||
|
"CameraMake": "string",
|
||||||
|
"CameraModel": "string",
|
||||||
|
"Software": "string",
|
||||||
|
"ExposureTime": 0,
|
||||||
|
"FocalLength": 0,
|
||||||
|
"ImageOrientation": "TopLeft",
|
||||||
|
"Aperture": 0,
|
||||||
|
"ShutterSpeed": 0,
|
||||||
|
"Latitude": 0,
|
||||||
|
"Longitude": 0,
|
||||||
|
"Altitude": 0,
|
||||||
|
"IsoSpeedRating": 0,
|
||||||
|
"SeriesTimerId": "string",
|
||||||
|
"ProgramId": "string",
|
||||||
|
"ChannelPrimaryImageTag": "string",
|
||||||
|
"StartDate": "2019-08-24T14:15:22Z",
|
||||||
|
"CompletionPercentage": 0,
|
||||||
|
"IsRepeat": true,
|
||||||
|
"EpisodeTitle": "string",
|
||||||
|
"ChannelType": "TV",
|
||||||
|
"Audio": "Mono",
|
||||||
|
"IsMovie": true,
|
||||||
|
"IsSports": true,
|
||||||
|
"IsSeries": true,
|
||||||
|
"IsLive": true,
|
||||||
|
"IsNews": true,
|
||||||
|
"IsKids": true,
|
||||||
|
"IsPremiere": true,
|
||||||
|
"TimerId": "string",
|
||||||
|
"CurrentProgram": {}
|
||||||
|
}
|
510
tests/components/jellyfin/fixtures/get-media-folders.json
Normal file
510
tests/components/jellyfin/fixtures/get-media-folders.json
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
{
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"Name": "COLLECTION FOLDER",
|
||||||
|
"OriginalTitle": "string",
|
||||||
|
"ServerId": "SERVER-UUID",
|
||||||
|
"Id": "COLLECTION-FOLDER-UUID",
|
||||||
|
"Etag": "string",
|
||||||
|
"SourceType": "string",
|
||||||
|
"PlaylistItemId": "string",
|
||||||
|
"DateCreated": "2019-08-24T14:15:22Z",
|
||||||
|
"DateLastMediaAdded": "2019-08-24T14:15:22Z",
|
||||||
|
"ExtraType": "string",
|
||||||
|
"AirsBeforeSeasonNumber": 0,
|
||||||
|
"AirsAfterSeasonNumber": 0,
|
||||||
|
"AirsBeforeEpisodeNumber": 0,
|
||||||
|
"CanDelete": true,
|
||||||
|
"CanDownload": true,
|
||||||
|
"HasSubtitles": true,
|
||||||
|
"PreferredMetadataLanguage": "string",
|
||||||
|
"PreferredMetadataCountryCode": "string",
|
||||||
|
"SupportsSync": true,
|
||||||
|
"Container": "string",
|
||||||
|
"SortName": "string",
|
||||||
|
"ForcedSortName": "string",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"PremiereDate": "2019-08-24T14:15:22Z",
|
||||||
|
"ExternalUrls": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Url": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaSources": [
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "string",
|
||||||
|
"Path": "string",
|
||||||
|
"EncoderPath": "string",
|
||||||
|
"EncoderProtocol": "File",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "string",
|
||||||
|
"Size": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"IsRemote": true,
|
||||||
|
"ETag": "string",
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"ReadAtNativeFramerate": true,
|
||||||
|
"IgnoreDts": true,
|
||||||
|
"IgnoreIndex": true,
|
||||||
|
"GenPtsInput": true,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": true,
|
||||||
|
"RequiresOpening": true,
|
||||||
|
"OpenToken": "string",
|
||||||
|
"RequiresClosing": true,
|
||||||
|
"LiveStreamId": "string",
|
||||||
|
"BufferMs": 0,
|
||||||
|
"RequiresLooping": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Comment": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"FileName": "string",
|
||||||
|
"MimeType": "string",
|
||||||
|
"DeliveryUrl": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Formats": ["string"],
|
||||||
|
"Bitrate": 0,
|
||||||
|
"Timestamp": "None",
|
||||||
|
"RequiredHttpHeaders": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"TranscodingUrl": "string",
|
||||||
|
"TranscodingSubProtocol": "string",
|
||||||
|
"TranscodingContainer": "string",
|
||||||
|
"AnalyzeDurationMs": 0,
|
||||||
|
"DefaultAudioStreamIndex": 0,
|
||||||
|
"DefaultSubtitleStreamIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CriticRating": 0,
|
||||||
|
"ProductionLocations": ["string"],
|
||||||
|
"Path": "string",
|
||||||
|
"EnableMediaSourceDisplay": true,
|
||||||
|
"OfficialRating": "string",
|
||||||
|
"CustomRating": "string",
|
||||||
|
"ChannelId": "04b0b2a5-93cb-474d-8ea9-3df0f84eb0ff",
|
||||||
|
"ChannelName": "string",
|
||||||
|
"Overview": "string",
|
||||||
|
"Taglines": ["string"],
|
||||||
|
"Genres": ["string"],
|
||||||
|
"CommunityRating": 0,
|
||||||
|
"CumulativeRunTimeTicks": 0,
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"PlayAccess": "Full",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"ProductionYear": 0,
|
||||||
|
"IsPlaceHolder": true,
|
||||||
|
"Number": "string",
|
||||||
|
"ChannelNumber": "string",
|
||||||
|
"IndexNumber": 0,
|
||||||
|
"IndexNumberEnd": 0,
|
||||||
|
"ParentIndexNumber": 0,
|
||||||
|
"RemoteTrailers": [
|
||||||
|
{
|
||||||
|
"Url": "string",
|
||||||
|
"Name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ProviderIds": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"IsHD": true,
|
||||||
|
"IsFolder": true,
|
||||||
|
"ParentId": "c54e2d15-b5eb-48b7-9b04-53f376904b1e",
|
||||||
|
"Type": "CollectionFolder",
|
||||||
|
"People": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43",
|
||||||
|
"Role": "string",
|
||||||
|
"Type": "string",
|
||||||
|
"PrimaryImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Studios": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenreItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ParentLogoItemId": "c78d400f-de5c-421e-8714-4fb05d387233",
|
||||||
|
"ParentBackdropItemId": "c22fd826-17fc-44f4-9b04-1eb3e8fb9173",
|
||||||
|
"ParentBackdropImageTags": ["string"],
|
||||||
|
"LocalTrailerCount": 0,
|
||||||
|
"UserData": {
|
||||||
|
"Rating": 0,
|
||||||
|
"PlayedPercentage": 0,
|
||||||
|
"UnplayedItemCount": 0,
|
||||||
|
"PlaybackPositionTicks": 0,
|
||||||
|
"PlayCount": 0,
|
||||||
|
"IsFavorite": true,
|
||||||
|
"Likes": true,
|
||||||
|
"LastPlayedDate": "2019-08-24T14:15:22Z",
|
||||||
|
"Played": true,
|
||||||
|
"Key": "string",
|
||||||
|
"ItemId": "string"
|
||||||
|
},
|
||||||
|
"RecursiveItemCount": 0,
|
||||||
|
"ChildCount": 0,
|
||||||
|
"SeriesName": "string",
|
||||||
|
"SeriesId": "SERIES-UUID",
|
||||||
|
"SeasonId": "SEASON-UUID",
|
||||||
|
"SpecialFeatureCount": 0,
|
||||||
|
"DisplayPreferencesId": "string",
|
||||||
|
"Status": "string",
|
||||||
|
"AirTime": "string",
|
||||||
|
"AirDays": ["Sunday"],
|
||||||
|
"Tags": ["string"],
|
||||||
|
"PrimaryImageAspectRatio": 0,
|
||||||
|
"Artists": ["string"],
|
||||||
|
"ArtistItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Album": "string",
|
||||||
|
"CollectionType": "tvshows",
|
||||||
|
"DisplayOrder": "string",
|
||||||
|
"AlbumId": "21af9851-8e39-43a9-9c47-513d3b9e99fc",
|
||||||
|
"AlbumPrimaryImageTag": "string",
|
||||||
|
"SeriesPrimaryImageTag": "string",
|
||||||
|
"AlbumArtist": "string",
|
||||||
|
"AlbumArtists": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SeasonName": "string",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"PartCount": 0,
|
||||||
|
"MediaSourceCount": 0,
|
||||||
|
"ImageTags": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BackdropImageTags": ["string"],
|
||||||
|
"ScreenshotImageTags": ["string"],
|
||||||
|
"ParentLogoImageTag": "string",
|
||||||
|
"ParentArtItemId": "10c1875b-b82c-48e8-bae9-939a5e68dc2f",
|
||||||
|
"ParentArtImageTag": "string",
|
||||||
|
"SeriesThumbImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SeriesStudio": "string",
|
||||||
|
"ParentThumbItemId": "ae6ff707-333d-4994-be6d-b83ca1b35f46",
|
||||||
|
"ParentThumbImageTag": "string",
|
||||||
|
"ParentPrimaryImageItemId": "string",
|
||||||
|
"ParentPrimaryImageTag": "string",
|
||||||
|
"Chapters": [
|
||||||
|
{
|
||||||
|
"StartPositionTicks": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"ImagePath": "string",
|
||||||
|
"ImageDateModified": "2019-08-24T14:15:22Z",
|
||||||
|
"ImageTag": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocationType": "FileSystem",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"MediaType": "string",
|
||||||
|
"EndDate": "2019-08-24T14:15:22Z",
|
||||||
|
"LockedFields": ["Cast"],
|
||||||
|
"TrailerCount": 0,
|
||||||
|
"MovieCount": 0,
|
||||||
|
"SeriesCount": 0,
|
||||||
|
"ProgramCount": 0,
|
||||||
|
"EpisodeCount": 0,
|
||||||
|
"SongCount": 0,
|
||||||
|
"AlbumCount": 0,
|
||||||
|
"ArtistCount": 0,
|
||||||
|
"MusicVideoCount": 0,
|
||||||
|
"LockData": true,
|
||||||
|
"Width": 0,
|
||||||
|
"Height": 0,
|
||||||
|
"CameraMake": "string",
|
||||||
|
"CameraModel": "string",
|
||||||
|
"Software": "string",
|
||||||
|
"ExposureTime": 0,
|
||||||
|
"FocalLength": 0,
|
||||||
|
"ImageOrientation": "TopLeft",
|
||||||
|
"Aperture": 0,
|
||||||
|
"ShutterSpeed": 0,
|
||||||
|
"Latitude": 0,
|
||||||
|
"Longitude": 0,
|
||||||
|
"Altitude": 0,
|
||||||
|
"IsoSpeedRating": 0,
|
||||||
|
"SeriesTimerId": "string",
|
||||||
|
"ProgramId": "string",
|
||||||
|
"ChannelPrimaryImageTag": "string",
|
||||||
|
"StartDate": "2019-08-24T14:15:22Z",
|
||||||
|
"CompletionPercentage": 0,
|
||||||
|
"IsRepeat": true,
|
||||||
|
"EpisodeTitle": "string",
|
||||||
|
"ChannelType": "TV",
|
||||||
|
"Audio": "Mono",
|
||||||
|
"IsMovie": true,
|
||||||
|
"IsSports": true,
|
||||||
|
"IsSeries": true,
|
||||||
|
"IsLive": true,
|
||||||
|
"IsNews": true,
|
||||||
|
"IsKids": true,
|
||||||
|
"IsPremiere": true,
|
||||||
|
"TimerId": "string",
|
||||||
|
"CurrentProgram": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TotalRecordCount": 0,
|
||||||
|
"StartIndex": 0
|
||||||
|
}
|
510
tests/components/jellyfin/fixtures/user-items-parent-id.json
Normal file
510
tests/components/jellyfin/fixtures/user-items-parent-id.json
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
{
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"Name": "EPISODE",
|
||||||
|
"OriginalTitle": "string",
|
||||||
|
"ServerId": "SERVER-UUID",
|
||||||
|
"Id": "EPISODE-UUID",
|
||||||
|
"Etag": "string",
|
||||||
|
"SourceType": "string",
|
||||||
|
"PlaylistItemId": "string",
|
||||||
|
"DateCreated": "2019-08-24T14:15:22Z",
|
||||||
|
"DateLastMediaAdded": "2019-08-24T14:15:22Z",
|
||||||
|
"ExtraType": "string",
|
||||||
|
"AirsBeforeSeasonNumber": 0,
|
||||||
|
"AirsAfterSeasonNumber": 0,
|
||||||
|
"AirsBeforeEpisodeNumber": 0,
|
||||||
|
"CanDelete": true,
|
||||||
|
"CanDownload": true,
|
||||||
|
"HasSubtitles": true,
|
||||||
|
"PreferredMetadataLanguage": "string",
|
||||||
|
"PreferredMetadataCountryCode": "string",
|
||||||
|
"SupportsSync": true,
|
||||||
|
"Container": "string",
|
||||||
|
"SortName": "string",
|
||||||
|
"ForcedSortName": "string",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"PremiereDate": "2019-08-24T14:15:22Z",
|
||||||
|
"ExternalUrls": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Url": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaSources": [
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "string",
|
||||||
|
"Path": "string",
|
||||||
|
"EncoderPath": "string",
|
||||||
|
"EncoderProtocol": "File",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "string",
|
||||||
|
"Size": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"IsRemote": true,
|
||||||
|
"ETag": "string",
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"ReadAtNativeFramerate": true,
|
||||||
|
"IgnoreDts": true,
|
||||||
|
"IgnoreIndex": true,
|
||||||
|
"GenPtsInput": true,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": true,
|
||||||
|
"RequiresOpening": true,
|
||||||
|
"OpenToken": "string",
|
||||||
|
"RequiresClosing": true,
|
||||||
|
"LiveStreamId": "string",
|
||||||
|
"BufferMs": 0,
|
||||||
|
"RequiresLooping": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Comment": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"FileName": "string",
|
||||||
|
"MimeType": "string",
|
||||||
|
"DeliveryUrl": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Formats": ["string"],
|
||||||
|
"Bitrate": 0,
|
||||||
|
"Timestamp": "None",
|
||||||
|
"RequiredHttpHeaders": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"TranscodingUrl": "string",
|
||||||
|
"TranscodingSubProtocol": "string",
|
||||||
|
"TranscodingContainer": "string",
|
||||||
|
"AnalyzeDurationMs": 0,
|
||||||
|
"DefaultAudioStreamIndex": 0,
|
||||||
|
"DefaultSubtitleStreamIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CriticRating": 0,
|
||||||
|
"ProductionLocations": ["string"],
|
||||||
|
"Path": "string",
|
||||||
|
"EnableMediaSourceDisplay": true,
|
||||||
|
"OfficialRating": "string",
|
||||||
|
"CustomRating": "string",
|
||||||
|
"ChannelId": "04b0b2a5-93cb-474d-8ea9-3df0f84eb0ff",
|
||||||
|
"ChannelName": "string",
|
||||||
|
"Overview": "string",
|
||||||
|
"Taglines": ["string"],
|
||||||
|
"Genres": ["string"],
|
||||||
|
"CommunityRating": 0,
|
||||||
|
"CumulativeRunTimeTicks": 0,
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"PlayAccess": "Full",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"ProductionYear": 0,
|
||||||
|
"IsPlaceHolder": true,
|
||||||
|
"Number": "string",
|
||||||
|
"ChannelNumber": "string",
|
||||||
|
"IndexNumber": 0,
|
||||||
|
"IndexNumberEnd": 0,
|
||||||
|
"ParentIndexNumber": 0,
|
||||||
|
"RemoteTrailers": [
|
||||||
|
{
|
||||||
|
"Url": "string",
|
||||||
|
"Name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ProviderIds": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"IsHD": true,
|
||||||
|
"IsFolder": false,
|
||||||
|
"ParentId": "FOLDER-UUID",
|
||||||
|
"Type": "Episode",
|
||||||
|
"People": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43",
|
||||||
|
"Role": "string",
|
||||||
|
"Type": "string",
|
||||||
|
"PrimaryImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Studios": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenreItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ParentLogoItemId": "c78d400f-de5c-421e-8714-4fb05d387233",
|
||||||
|
"ParentBackdropItemId": "c22fd826-17fc-44f4-9b04-1eb3e8fb9173",
|
||||||
|
"ParentBackdropImageTags": ["string"],
|
||||||
|
"LocalTrailerCount": 0,
|
||||||
|
"UserData": {
|
||||||
|
"Rating": 0,
|
||||||
|
"PlayedPercentage": 0,
|
||||||
|
"UnplayedItemCount": 0,
|
||||||
|
"PlaybackPositionTicks": 0,
|
||||||
|
"PlayCount": 0,
|
||||||
|
"IsFavorite": true,
|
||||||
|
"Likes": true,
|
||||||
|
"LastPlayedDate": "2019-08-24T14:15:22Z",
|
||||||
|
"Played": true,
|
||||||
|
"Key": "string",
|
||||||
|
"ItemId": "string"
|
||||||
|
},
|
||||||
|
"RecursiveItemCount": 0,
|
||||||
|
"ChildCount": 0,
|
||||||
|
"SeriesName": "string",
|
||||||
|
"SeriesId": "c7b70af4-4902-4a7e-95ab-28349b6c7afc",
|
||||||
|
"SeasonId": "badb6463-e5b7-45c5-8141-71204420ec8f",
|
||||||
|
"SpecialFeatureCount": 0,
|
||||||
|
"DisplayPreferencesId": "string",
|
||||||
|
"Status": "string",
|
||||||
|
"AirTime": "string",
|
||||||
|
"AirDays": ["Sunday"],
|
||||||
|
"Tags": ["string"],
|
||||||
|
"PrimaryImageAspectRatio": 0,
|
||||||
|
"Artists": ["string"],
|
||||||
|
"ArtistItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Album": "string",
|
||||||
|
"CollectionType": "string",
|
||||||
|
"DisplayOrder": "string",
|
||||||
|
"AlbumId": "21af9851-8e39-43a9-9c47-513d3b9e99fc",
|
||||||
|
"AlbumPrimaryImageTag": "string",
|
||||||
|
"SeriesPrimaryImageTag": "string",
|
||||||
|
"AlbumArtist": "string",
|
||||||
|
"AlbumArtists": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SeasonName": "string",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"PartCount": 0,
|
||||||
|
"MediaSourceCount": 0,
|
||||||
|
"ImageTags": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BackdropImageTags": ["string"],
|
||||||
|
"ScreenshotImageTags": ["string"],
|
||||||
|
"ParentLogoImageTag": "string",
|
||||||
|
"ParentArtItemId": "10c1875b-b82c-48e8-bae9-939a5e68dc2f",
|
||||||
|
"ParentArtImageTag": "string",
|
||||||
|
"SeriesThumbImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SeriesStudio": "string",
|
||||||
|
"ParentThumbItemId": "ae6ff707-333d-4994-be6d-b83ca1b35f46",
|
||||||
|
"ParentThumbImageTag": "string",
|
||||||
|
"ParentPrimaryImageItemId": "string",
|
||||||
|
"ParentPrimaryImageTag": "string",
|
||||||
|
"Chapters": [
|
||||||
|
{
|
||||||
|
"StartPositionTicks": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"ImagePath": "string",
|
||||||
|
"ImageDateModified": "2019-08-24T14:15:22Z",
|
||||||
|
"ImageTag": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocationType": "FileSystem",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"MediaType": "string",
|
||||||
|
"EndDate": "2019-08-24T14:15:22Z",
|
||||||
|
"LockedFields": ["Cast"],
|
||||||
|
"TrailerCount": 0,
|
||||||
|
"MovieCount": 0,
|
||||||
|
"SeriesCount": 0,
|
||||||
|
"ProgramCount": 0,
|
||||||
|
"EpisodeCount": 0,
|
||||||
|
"SongCount": 0,
|
||||||
|
"AlbumCount": 0,
|
||||||
|
"ArtistCount": 0,
|
||||||
|
"MusicVideoCount": 0,
|
||||||
|
"LockData": true,
|
||||||
|
"Width": 0,
|
||||||
|
"Height": 0,
|
||||||
|
"CameraMake": "string",
|
||||||
|
"CameraModel": "string",
|
||||||
|
"Software": "string",
|
||||||
|
"ExposureTime": 0,
|
||||||
|
"FocalLength": 0,
|
||||||
|
"ImageOrientation": "TopLeft",
|
||||||
|
"Aperture": 0,
|
||||||
|
"ShutterSpeed": 0,
|
||||||
|
"Latitude": 0,
|
||||||
|
"Longitude": 0,
|
||||||
|
"Altitude": 0,
|
||||||
|
"IsoSpeedRating": 0,
|
||||||
|
"SeriesTimerId": "string",
|
||||||
|
"ProgramId": "string",
|
||||||
|
"ChannelPrimaryImageTag": "string",
|
||||||
|
"StartDate": "2019-08-24T14:15:22Z",
|
||||||
|
"CompletionPercentage": 0,
|
||||||
|
"IsRepeat": true,
|
||||||
|
"EpisodeTitle": "string",
|
||||||
|
"ChannelType": "TV",
|
||||||
|
"Audio": "Mono",
|
||||||
|
"IsMovie": true,
|
||||||
|
"IsSports": true,
|
||||||
|
"IsSeries": true,
|
||||||
|
"IsLive": true,
|
||||||
|
"IsNews": true,
|
||||||
|
"IsKids": true,
|
||||||
|
"IsPremiere": true,
|
||||||
|
"TimerId": "string",
|
||||||
|
"CurrentProgram": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TotalRecordCount": 0,
|
||||||
|
"StartIndex": 0
|
||||||
|
}
|
510
tests/components/jellyfin/fixtures/user-items.json
Normal file
510
tests/components/jellyfin/fixtures/user-items.json
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
{
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"Name": "FOLDER",
|
||||||
|
"OriginalTitle": "string",
|
||||||
|
"ServerId": "SERVER-UUID",
|
||||||
|
"Id": "FOLDER-UUID",
|
||||||
|
"Etag": "string",
|
||||||
|
"SourceType": "string",
|
||||||
|
"PlaylistItemId": "string",
|
||||||
|
"DateCreated": "2019-08-24T14:15:22Z",
|
||||||
|
"DateLastMediaAdded": "2019-08-24T14:15:22Z",
|
||||||
|
"ExtraType": "string",
|
||||||
|
"AirsBeforeSeasonNumber": 0,
|
||||||
|
"AirsAfterSeasonNumber": 0,
|
||||||
|
"AirsBeforeEpisodeNumber": 0,
|
||||||
|
"CanDelete": true,
|
||||||
|
"CanDownload": true,
|
||||||
|
"HasSubtitles": true,
|
||||||
|
"PreferredMetadataLanguage": "string",
|
||||||
|
"PreferredMetadataCountryCode": "string",
|
||||||
|
"SupportsSync": true,
|
||||||
|
"Container": "string",
|
||||||
|
"SortName": "string",
|
||||||
|
"ForcedSortName": "string",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"PremiereDate": "2019-08-24T14:15:22Z",
|
||||||
|
"ExternalUrls": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Url": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaSources": [
|
||||||
|
{
|
||||||
|
"Protocol": "File",
|
||||||
|
"Id": "string",
|
||||||
|
"Path": "string",
|
||||||
|
"EncoderPath": "string",
|
||||||
|
"EncoderProtocol": "File",
|
||||||
|
"Type": "Default",
|
||||||
|
"Container": "string",
|
||||||
|
"Size": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"IsRemote": true,
|
||||||
|
"ETag": "string",
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"ReadAtNativeFramerate": true,
|
||||||
|
"IgnoreDts": true,
|
||||||
|
"IgnoreIndex": true,
|
||||||
|
"GenPtsInput": true,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"IsInfiniteStream": true,
|
||||||
|
"RequiresOpening": true,
|
||||||
|
"OpenToken": "string",
|
||||||
|
"RequiresClosing": true,
|
||||||
|
"LiveStreamId": "string",
|
||||||
|
"BufferMs": 0,
|
||||||
|
"RequiresLooping": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"Video3DFormat": "HalfSideBySide",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MediaAttachments": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Comment": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"FileName": "string",
|
||||||
|
"MimeType": "string",
|
||||||
|
"DeliveryUrl": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Formats": ["string"],
|
||||||
|
"Bitrate": 0,
|
||||||
|
"Timestamp": "None",
|
||||||
|
"RequiredHttpHeaders": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"TranscodingUrl": "string",
|
||||||
|
"TranscodingSubProtocol": "string",
|
||||||
|
"TranscodingContainer": "string",
|
||||||
|
"AnalyzeDurationMs": 0,
|
||||||
|
"DefaultAudioStreamIndex": 0,
|
||||||
|
"DefaultSubtitleStreamIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CriticRating": 0,
|
||||||
|
"ProductionLocations": ["string"],
|
||||||
|
"Path": "string",
|
||||||
|
"EnableMediaSourceDisplay": true,
|
||||||
|
"OfficialRating": "string",
|
||||||
|
"CustomRating": "string",
|
||||||
|
"ChannelId": "04b0b2a5-93cb-474d-8ea9-3df0f84eb0ff",
|
||||||
|
"ChannelName": "string",
|
||||||
|
"Overview": "string",
|
||||||
|
"Taglines": ["string"],
|
||||||
|
"Genres": ["string"],
|
||||||
|
"CommunityRating": 0,
|
||||||
|
"CumulativeRunTimeTicks": 0,
|
||||||
|
"RunTimeTicks": 0,
|
||||||
|
"PlayAccess": "Full",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"ProductionYear": 0,
|
||||||
|
"IsPlaceHolder": true,
|
||||||
|
"Number": "string",
|
||||||
|
"ChannelNumber": "string",
|
||||||
|
"IndexNumber": 0,
|
||||||
|
"IndexNumberEnd": 0,
|
||||||
|
"ParentIndexNumber": 0,
|
||||||
|
"RemoteTrailers": [
|
||||||
|
{
|
||||||
|
"Url": "string",
|
||||||
|
"Name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ProviderIds": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"IsHD": true,
|
||||||
|
"IsFolder": true,
|
||||||
|
"ParentId": "c54e2d15-b5eb-48b7-9b04-53f376904b1e",
|
||||||
|
"Type": "AggregateFolder",
|
||||||
|
"People": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43",
|
||||||
|
"Role": "string",
|
||||||
|
"Type": "string",
|
||||||
|
"PrimaryImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Studios": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GenreItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ParentLogoItemId": "c78d400f-de5c-421e-8714-4fb05d387233",
|
||||||
|
"ParentBackdropItemId": "c22fd826-17fc-44f4-9b04-1eb3e8fb9173",
|
||||||
|
"ParentBackdropImageTags": ["string"],
|
||||||
|
"LocalTrailerCount": 0,
|
||||||
|
"UserData": {
|
||||||
|
"Rating": 0,
|
||||||
|
"PlayedPercentage": 0,
|
||||||
|
"UnplayedItemCount": 0,
|
||||||
|
"PlaybackPositionTicks": 0,
|
||||||
|
"PlayCount": 0,
|
||||||
|
"IsFavorite": true,
|
||||||
|
"Likes": true,
|
||||||
|
"LastPlayedDate": "2019-08-24T14:15:22Z",
|
||||||
|
"Played": true,
|
||||||
|
"Key": "string",
|
||||||
|
"ItemId": "string"
|
||||||
|
},
|
||||||
|
"RecursiveItemCount": 0,
|
||||||
|
"ChildCount": 0,
|
||||||
|
"SeriesName": "string",
|
||||||
|
"SeriesId": "c7b70af4-4902-4a7e-95ab-28349b6c7afc",
|
||||||
|
"SeasonId": "badb6463-e5b7-45c5-8141-71204420ec8f",
|
||||||
|
"SpecialFeatureCount": 0,
|
||||||
|
"DisplayPreferencesId": "string",
|
||||||
|
"Status": "string",
|
||||||
|
"AirTime": "string",
|
||||||
|
"AirDays": ["Sunday"],
|
||||||
|
"Tags": ["string"],
|
||||||
|
"PrimaryImageAspectRatio": 0,
|
||||||
|
"Artists": ["string"],
|
||||||
|
"ArtistItems": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Album": "string",
|
||||||
|
"CollectionType": "string",
|
||||||
|
"DisplayOrder": "string",
|
||||||
|
"AlbumId": "21af9851-8e39-43a9-9c47-513d3b9e99fc",
|
||||||
|
"AlbumPrimaryImageTag": "string",
|
||||||
|
"SeriesPrimaryImageTag": "string",
|
||||||
|
"AlbumArtist": "string",
|
||||||
|
"AlbumArtists": [
|
||||||
|
{
|
||||||
|
"Name": "string",
|
||||||
|
"Id": "38a5a5bb-dc30-49a2-b175-1de0d1488c43"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SeasonName": "string",
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "string",
|
||||||
|
"CodecTag": "string",
|
||||||
|
"Language": "string",
|
||||||
|
"ColorRange": "string",
|
||||||
|
"ColorSpace": "string",
|
||||||
|
"ColorTransfer": "string",
|
||||||
|
"ColorPrimaries": "string",
|
||||||
|
"DvVersionMajor": 0,
|
||||||
|
"DvVersionMinor": 0,
|
||||||
|
"DvProfile": 0,
|
||||||
|
"DvLevel": 0,
|
||||||
|
"RpuPresentFlag": 0,
|
||||||
|
"ElPresentFlag": 0,
|
||||||
|
"BlPresentFlag": 0,
|
||||||
|
"DvBlSignalCompatibilityId": 0,
|
||||||
|
"Comment": "string",
|
||||||
|
"TimeBase": "string",
|
||||||
|
"CodecTimeBase": "string",
|
||||||
|
"Title": "string",
|
||||||
|
"VideoRange": "string",
|
||||||
|
"VideoRangeType": "string",
|
||||||
|
"VideoDoViTitle": "string",
|
||||||
|
"LocalizedUndefined": "string",
|
||||||
|
"LocalizedDefault": "string",
|
||||||
|
"LocalizedForced": "string",
|
||||||
|
"LocalizedExternal": "string",
|
||||||
|
"DisplayTitle": "string",
|
||||||
|
"NalLengthSize": "string",
|
||||||
|
"IsInterlaced": true,
|
||||||
|
"IsAVC": true,
|
||||||
|
"ChannelLayout": "string",
|
||||||
|
"BitRate": 0,
|
||||||
|
"BitDepth": 0,
|
||||||
|
"RefFrames": 0,
|
||||||
|
"PacketLength": 0,
|
||||||
|
"Channels": 0,
|
||||||
|
"SampleRate": 0,
|
||||||
|
"IsDefault": true,
|
||||||
|
"IsForced": true,
|
||||||
|
"Height": 0,
|
||||||
|
"Width": 0,
|
||||||
|
"AverageFrameRate": 0,
|
||||||
|
"RealFrameRate": 0,
|
||||||
|
"Profile": "string",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AspectRatio": "string",
|
||||||
|
"Index": 0,
|
||||||
|
"Score": 0,
|
||||||
|
"IsExternal": true,
|
||||||
|
"DeliveryMethod": "Encode",
|
||||||
|
"DeliveryUrl": "string",
|
||||||
|
"IsExternalUrl": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "string",
|
||||||
|
"PixelFormat": "string",
|
||||||
|
"Level": 0,
|
||||||
|
"IsAnamorphic": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"VideoType": "VideoFile",
|
||||||
|
"PartCount": 0,
|
||||||
|
"MediaSourceCount": 0,
|
||||||
|
"ImageTags": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BackdropImageTags": ["string"],
|
||||||
|
"ScreenshotImageTags": ["string"],
|
||||||
|
"ParentLogoImageTag": "string",
|
||||||
|
"ParentArtItemId": "10c1875b-b82c-48e8-bae9-939a5e68dc2f",
|
||||||
|
"ParentArtImageTag": "string",
|
||||||
|
"SeriesThumbImageTag": "string",
|
||||||
|
"ImageBlurHashes": {
|
||||||
|
"Primary": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Art": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Backdrop": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Logo": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Thumb": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Disc": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Box": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Screenshot": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Chapter": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"BoxRear": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SeriesStudio": "string",
|
||||||
|
"ParentThumbItemId": "ae6ff707-333d-4994-be6d-b83ca1b35f46",
|
||||||
|
"ParentThumbImageTag": "string",
|
||||||
|
"ParentPrimaryImageItemId": "string",
|
||||||
|
"ParentPrimaryImageTag": "string",
|
||||||
|
"Chapters": [
|
||||||
|
{
|
||||||
|
"StartPositionTicks": 0,
|
||||||
|
"Name": "string",
|
||||||
|
"ImagePath": "string",
|
||||||
|
"ImageDateModified": "2019-08-24T14:15:22Z",
|
||||||
|
"ImageTag": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocationType": "FileSystem",
|
||||||
|
"IsoType": "Dvd",
|
||||||
|
"MediaType": "string",
|
||||||
|
"EndDate": "2019-08-24T14:15:22Z",
|
||||||
|
"LockedFields": ["Cast"],
|
||||||
|
"TrailerCount": 0,
|
||||||
|
"MovieCount": 0,
|
||||||
|
"SeriesCount": 0,
|
||||||
|
"ProgramCount": 0,
|
||||||
|
"EpisodeCount": 0,
|
||||||
|
"SongCount": 0,
|
||||||
|
"AlbumCount": 0,
|
||||||
|
"ArtistCount": 0,
|
||||||
|
"MusicVideoCount": 0,
|
||||||
|
"LockData": true,
|
||||||
|
"Width": 0,
|
||||||
|
"Height": 0,
|
||||||
|
"CameraMake": "string",
|
||||||
|
"CameraModel": "string",
|
||||||
|
"Software": "string",
|
||||||
|
"ExposureTime": 0,
|
||||||
|
"FocalLength": 0,
|
||||||
|
"ImageOrientation": "TopLeft",
|
||||||
|
"Aperture": 0,
|
||||||
|
"ShutterSpeed": 0,
|
||||||
|
"Latitude": 0,
|
||||||
|
"Longitude": 0,
|
||||||
|
"Altitude": 0,
|
||||||
|
"IsoSpeedRating": 0,
|
||||||
|
"SeriesTimerId": "string",
|
||||||
|
"ProgramId": "string",
|
||||||
|
"ChannelPrimaryImageTag": "string",
|
||||||
|
"StartDate": "2019-08-24T14:15:22Z",
|
||||||
|
"CompletionPercentage": 0,
|
||||||
|
"IsRepeat": true,
|
||||||
|
"EpisodeTitle": "string",
|
||||||
|
"ChannelType": "TV",
|
||||||
|
"Audio": "Mono",
|
||||||
|
"IsMovie": true,
|
||||||
|
"IsSports": true,
|
||||||
|
"IsSeries": true,
|
||||||
|
"IsLive": true,
|
||||||
|
"IsNews": true,
|
||||||
|
"IsKids": true,
|
||||||
|
"IsPremiere": true,
|
||||||
|
"TimerId": "string",
|
||||||
|
"CurrentProgram": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TotalRecordCount": 0,
|
||||||
|
"StartIndex": 0
|
||||||
|
}
|
356
tests/components/jellyfin/test_media_player.py
Normal file
356
tests/components/jellyfin/test_media_player.py
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
"""Tests for the Jellyfin media_player platform."""
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
|
from homeassistant.components.jellyfin.const import DOMAIN
|
||||||
|
from homeassistant.components.media_player import (
|
||||||
|
ATTR_MEDIA_ALBUM_ARTIST,
|
||||||
|
ATTR_MEDIA_ALBUM_NAME,
|
||||||
|
ATTR_MEDIA_ARTIST,
|
||||||
|
ATTR_MEDIA_CONTENT_ID,
|
||||||
|
ATTR_MEDIA_CONTENT_TYPE,
|
||||||
|
ATTR_MEDIA_DURATION,
|
||||||
|
ATTR_MEDIA_EPISODE,
|
||||||
|
ATTR_MEDIA_POSITION,
|
||||||
|
ATTR_MEDIA_POSITION_UPDATED_AT,
|
||||||
|
ATTR_MEDIA_SEASON,
|
||||||
|
ATTR_MEDIA_SERIES_TITLE,
|
||||||
|
ATTR_MEDIA_TRACK,
|
||||||
|
ATTR_MEDIA_VOLUME_LEVEL,
|
||||||
|
ATTR_MEDIA_VOLUME_MUTED,
|
||||||
|
DOMAIN as MP_DOMAIN,
|
||||||
|
MediaClass,
|
||||||
|
MediaPlayerState,
|
||||||
|
MediaType,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
ATTR_ICON,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_player(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_jellyfin: MagicMock,
|
||||||
|
mock_api: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Jellyfin media player."""
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("media_player.jellyfin_device")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state == MediaPlayerState.PAUSED
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "JELLYFIN-DEVICE"
|
||||||
|
assert state.attributes.get(ATTR_ICON) is None
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 0.0
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is True
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_DURATION) == 60
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_POSITION) == 10
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_POSITION_UPDATED_AT)
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == "EPISODE-UUID"
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MediaType.TVSHOW
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) == "SERIES"
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_SEASON) == 1
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_EPISODE) == 3
|
||||||
|
|
||||||
|
entry = entity_registry.async_get(state.entity_id)
|
||||||
|
assert entry
|
||||||
|
assert entry.device_id
|
||||||
|
assert entry.entity_category is None
|
||||||
|
assert entry.unique_id == "SERVER-UUID-SESSION-UUID"
|
||||||
|
|
||||||
|
assert len(mock_api.sessions.mock_calls) == 1
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_api.sessions.mock_calls) == 2
|
||||||
|
|
||||||
|
mock_api.sessions.return_value = []
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=20))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_api.sessions.mock_calls) == 3
|
||||||
|
|
||||||
|
device = device_registry.async_get(entry.device_id)
|
||||||
|
assert device
|
||||||
|
assert device.configuration_url is None
|
||||||
|
assert device.connections == set()
|
||||||
|
assert device.entry_type is None
|
||||||
|
assert device.hw_version is None
|
||||||
|
assert device.identifiers == {(DOMAIN, "DEVICE-UUID")}
|
||||||
|
assert device.manufacturer == "Jellyfin"
|
||||||
|
assert device.name == "JELLYFIN-DEVICE"
|
||||||
|
assert device.sw_version == "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_player_music(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_jellyfin: MagicMock,
|
||||||
|
mock_api: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Jellyfin media player."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("media_player.jellyfin_device_four")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state == MediaPlayerState.PLAYING
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "JELLYFIN DEVICE FOUR"
|
||||||
|
assert state.attributes.get(ATTR_ICON) is None
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 1.0
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is False
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_DURATION) == 73
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_POSITION) == 22
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_POSITION_UPDATED_AT)
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == "MUSIC-UUID"
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MediaType.MUSIC
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_ALBUM_NAME) == "ALBUM"
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_ALBUM_ARTIST) == "Album Artist"
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_ARTIST) == "Contributing Artist"
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_TRACK) == 1
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) is None
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_SEASON) is None
|
||||||
|
assert state.attributes.get(ATTR_MEDIA_EPISODE) is None
|
||||||
|
|
||||||
|
entry = entity_registry.async_get(state.entity_id)
|
||||||
|
assert entry
|
||||||
|
assert entry.device_id is None
|
||||||
|
assert entry.entity_category is None
|
||||||
|
assert entry.unique_id == "SERVER-UUID-SESSION-UUID-FOUR"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_services(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_jellyfin: MagicMock,
|
||||||
|
mock_api: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test Jellyfin media player services."""
|
||||||
|
state = hass.states.get("media_player.jellyfin_device")
|
||||||
|
assert state
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"play_media",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
"media_content_type": "",
|
||||||
|
"media_content_id": "ITEM-UUID",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_play_media.mock_calls) == 1
|
||||||
|
assert mock_api.remote_play_media.mock_calls[0].args == (
|
||||||
|
"SESSION-UUID",
|
||||||
|
["ITEM-UUID"],
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"media_pause",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_pause.mock_calls) == 1
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"media_play",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_unpause.mock_calls) == 1
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"media_play_pause",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_playpause.mock_calls) == 1
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"media_seek",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
"seek_position": 10,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_seek.mock_calls) == 1
|
||||||
|
assert mock_api.remote_seek.mock_calls[0].args == (
|
||||||
|
"SESSION-UUID",
|
||||||
|
100000000,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"media_stop",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_stop.mock_calls) == 1
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"volume_set",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
"volume_level": 0.5,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_set_volume.mock_calls) == 1
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"volume_mute",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
"is_volume_muted": True,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_mute.mock_calls) == 1
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
"volume_mute",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: state.entity_id,
|
||||||
|
"is_volume_muted": False,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_api.remote_unmute.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_browse_media(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: ClientSession,
|
||||||
|
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/browse_media",
|
||||||
|
"entity_id": "media_player.jellyfin_device",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
expected_child_item = {
|
||||||
|
"title": "COLLECTION FOLDER",
|
||||||
|
"media_class": MediaClass.DIRECTORY.value,
|
||||||
|
"media_content_type": "collection",
|
||||||
|
"media_content_id": "COLLECTION-FOLDER-UUID",
|
||||||
|
"can_play": False,
|
||||||
|
"can_expand": True,
|
||||||
|
"thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
|
||||||
|
"children_media_class": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert response["result"]["media_content_id"] == ""
|
||||||
|
assert response["result"]["media_content_type"] == "root"
|
||||||
|
assert response["result"]["title"] == "Jellyfin"
|
||||||
|
assert response["result"]["children"][0] == expected_child_item
|
||||||
|
|
||||||
|
# browse collection folder
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "media_player/browse_media",
|
||||||
|
"entity_id": "media_player.jellyfin_device",
|
||||||
|
"media_content_type": "collection",
|
||||||
|
"media_content_id": "COLLECTION-FOLDER-UUID",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.receive_json()
|
||||||
|
expected_child_item = {
|
||||||
|
"title": "EPISODE",
|
||||||
|
"media_class": MediaClass.EPISODE.value,
|
||||||
|
"media_content_type": MediaType.EPISODE.value,
|
||||||
|
"media_content_id": "EPISODE-UUID",
|
||||||
|
"can_play": True,
|
||||||
|
"can_expand": False,
|
||||||
|
"thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
|
||||||
|
"children_media_class": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"]["media_content_id"] == "COLLECTION-FOLDER-UUID"
|
||||||
|
assert response["result"]["title"] == "FOLDER"
|
||||||
|
assert response["result"]["children"][0] == expected_child_item
|
||||||
|
|
||||||
|
# browse for collection without children
|
||||||
|
mock_api.user_items.side_effect = None
|
||||||
|
mock_api.user_items.return_value = {}
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"type": "media_player/browse_media",
|
||||||
|
"entity_id": "media_player.jellyfin_device",
|
||||||
|
"media_content_type": "collection",
|
||||||
|
"media_content_id": "COLLECTION-FOLDER-UUID",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"] is False
|
||||||
|
assert response["error"]
|
||||||
|
assert (
|
||||||
|
response["error"]["message"]
|
||||||
|
== "Media not found: collection / COLLECTION-FOLDER-UUID"
|
||||||
|
)
|
||||||
|
|
||||||
|
# browse for non-existent item
|
||||||
|
mock_api.get_item.side_effect = None
|
||||||
|
mock_api.get_item.return_value = {}
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"type": "media_player/browse_media",
|
||||||
|
"entity_id": "media_player.jellyfin_device",
|
||||||
|
"media_content_type": "collection",
|
||||||
|
"media_content_id": "COLLECTION-UUID-404",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"] is False
|
||||||
|
assert response["error"]
|
||||||
|
assert (
|
||||||
|
response["error"]["message"]
|
||||||
|
== "Media not found: collection / COLLECTION-UUID-404"
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user