Add media_player platform to Jellyfin (#76801)

pull/80918/head^2
Ongy 2022-10-25 04:45:01 +02:00 committed by GitHub
parent 9b331abe91
commit 3759be09df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2947 additions and 6 deletions

View File

@ -26,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
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:
raise ConfigEntryNotReady("Cannot connect to Jellyfin server") from ex
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]
coordinators: dict[str, JellyfinDataUpdateCoordinator[Any]] = {
"sessions": SessionsDataUpdateCoordinator(hass, client, server_info),
"sessions": SessionsDataUpdateCoordinator(hass, client, server_info, user_id),
}
for coordinator in coordinators.values():
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = JellyfinData(
client_device_id=entry.data[CONF_CLIENT_DEVICE_ID],
jellyfin_client=client,
coordinators=coordinators,
)

View 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

View File

@ -15,7 +15,7 @@ from homeassistant import exceptions
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
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(
@ -92,6 +92,25 @@ def _get_user_id(api: API) -> str:
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):
"""Error to indicate the server is unreachable."""

View File

@ -2,6 +2,7 @@
import logging
from typing import Final
from homeassistant.components.media_player import MediaClass, MediaType
from homeassistant.const import Platform, __version__ as hass_version
DOMAIN: Final = "jellyfin"
@ -32,7 +33,6 @@ ITEM_TYPE_MOVIE: Final = "Movie"
MAX_IMAGE_WIDTH: Final = 500
MAX_STREAMING_BITRATE: Final = "140000000"
MEDIA_SOURCE_KEY_PATH: Final = "Path"
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_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__)

View File

@ -32,18 +32,20 @@ class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT]):
hass: HomeAssistant,
api_client: JellyfinClient,
system_info: dict[str, Any],
user_id: str,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass=hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=30),
update_interval=timedelta(seconds=10),
)
self.api_client: JellyfinClient = api_client
self.server_id: str = system_info["Id"]
self.server_name: str = system_info["Name"]
self.server_version: str | None = system_info.get("Version")
self.user_id: str = user_id
async def _async_update_data(self) -> JellyfinDataT:
"""Get the latest data from Jellyfin."""

View 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,
)

View File

@ -12,5 +12,6 @@ from .coordinator import JellyfinDataUpdateCoordinator
class JellyfinData:
"""Data for the Jellyfin integration."""
client_device_id: str
jellyfin_client: JellyfinClient
coordinators: dict[str, JellyfinDataUpdateCoordinator]

View File

@ -73,6 +73,12 @@ def mock_api() -> MagicMock:
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.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
@ -121,3 +127,27 @@ async def init_integration(
await hass.async_block_till_done()
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")

View 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": {}
}

View 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
}

View 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
}

View 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
}

View 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"
)