Rewrite Plex tests to use mocked payloads (#44044)
parent
caf14b78d1
commit
0426b211f6
|
@ -694,8 +694,6 @@ omit =
|
|||
homeassistant/components/pjlink/media_player.py
|
||||
homeassistant/components/plaato/*
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/models.py
|
||||
homeassistant/components/plex/sensor.py
|
||||
homeassistant/components/plum_lightpad/light.py
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
homeassistant/components/point/*
|
||||
|
|
|
@ -146,7 +146,7 @@ class PlexServer:
|
|||
available_servers = [
|
||||
(x.name, x.clientIdentifier)
|
||||
for x in self.account.resources()
|
||||
if "server" in x.provides
|
||||
if "server" in x.provides and x.presence
|
||||
]
|
||||
|
||||
if not available_servers:
|
||||
|
|
|
@ -3,13 +3,261 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.plex.const import DOMAIN
|
||||
from homeassistant.components.plex.const import DOMAIN, PLEX_SERVER_CONFIG, SERVERS
|
||||
from homeassistant.const import CONF_URL
|
||||
|
||||
from .const import DEFAULT_DATA, DEFAULT_OPTIONS
|
||||
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, PLEX_DIRECT_URL
|
||||
from .helpers import websocket_connected
|
||||
from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer
|
||||
from .mock_classes import MockGDM
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
|
||||
def plex_server_url(entry):
|
||||
"""Return a protocol-less URL from a config entry."""
|
||||
return entry.data[PLEX_SERVER_CONFIG][CONF_URL].split(":", 1)[-1]
|
||||
|
||||
|
||||
@pytest.fixture(name="album", scope="session")
|
||||
def album_fixture():
|
||||
"""Load album payload and return it."""
|
||||
return load_fixture("plex/album.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="artist_albums", scope="session")
|
||||
def artist_albums_fixture():
|
||||
"""Load artist's albums payload and return it."""
|
||||
return load_fixture("plex/artist_albums.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="children_20", scope="session")
|
||||
def children_20_fixture():
|
||||
"""Load children payload for item 20 and return it."""
|
||||
return load_fixture("plex/children_20.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="children_30", scope="session")
|
||||
def children_30_fixture():
|
||||
"""Load children payload for item 30 and return it."""
|
||||
return load_fixture("plex/children_30.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="children_200", scope="session")
|
||||
def children_200_fixture():
|
||||
"""Load children payload for item 200 and return it."""
|
||||
return load_fixture("plex/children_200.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="children_300", scope="session")
|
||||
def children_300_fixture():
|
||||
"""Load children payload for item 300 and return it."""
|
||||
return load_fixture("plex/children_300.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="empty_library", scope="session")
|
||||
def empty_library_fixture():
|
||||
"""Load an empty library payload and return it."""
|
||||
return load_fixture("plex/empty_library.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="empty_payload", scope="session")
|
||||
def empty_payload_fixture():
|
||||
"""Load an empty payload and return it."""
|
||||
return load_fixture("plex/empty_payload.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="grandchildren_300", scope="session")
|
||||
def grandchildren_300_fixture():
|
||||
"""Load grandchildren payload for item 300 and return it."""
|
||||
return load_fixture("plex/grandchildren_300.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_movies_all", scope="session")
|
||||
def library_movies_all_fixture():
|
||||
"""Load payload for all items in the movies library and return it."""
|
||||
return load_fixture("plex/library_movies_all.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_tvshows_all", scope="session")
|
||||
def library_tvshows_all_fixture():
|
||||
"""Load payload for all items in the tvshows library and return it."""
|
||||
return load_fixture("plex/library_tvshows_all.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_music_all", scope="session")
|
||||
def library_music_all_fixture():
|
||||
"""Load payload for all items in the music library and return it."""
|
||||
return load_fixture("plex/library_music_all.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_movies_sort", scope="session")
|
||||
def library_movies_sort_fixture():
|
||||
"""Load sorting payload for movie library and return it."""
|
||||
return load_fixture("plex/library_movies_sort.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_tvshows_sort", scope="session")
|
||||
def library_tvshows_sort_fixture():
|
||||
"""Load sorting payload for tvshow library and return it."""
|
||||
return load_fixture("plex/library_tvshows_sort.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_music_sort", scope="session")
|
||||
def library_music_sort_fixture():
|
||||
"""Load sorting payload for music library and return it."""
|
||||
return load_fixture("plex/library_music_sort.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library", scope="session")
|
||||
def library_fixture():
|
||||
"""Load library payload and return it."""
|
||||
return load_fixture("plex/library.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="library_sections", scope="session")
|
||||
def library_sections_fixture():
|
||||
"""Load library sections payload and return it."""
|
||||
return load_fixture("plex/library_sections.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="media_1", scope="session")
|
||||
def media_1_fixture():
|
||||
"""Load media payload for item 1 and return it."""
|
||||
return load_fixture("plex/media_1.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="media_30", scope="session")
|
||||
def media_30_fixture():
|
||||
"""Load media payload for item 30 and return it."""
|
||||
return load_fixture("plex/media_30.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="media_100", scope="session")
|
||||
def media_100_fixture():
|
||||
"""Load media payload for item 100 and return it."""
|
||||
return load_fixture("plex/media_100.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="media_200", scope="session")
|
||||
def media_200_fixture():
|
||||
"""Load media payload for item 200 and return it."""
|
||||
return load_fixture("plex/media_200.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="player_plexweb_resources", scope="session")
|
||||
def player_plexweb_resources_fixture():
|
||||
"""Load resources payload for a Plex Web player and return it."""
|
||||
return load_fixture("plex/player_plexweb_resources.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="playlists", scope="session")
|
||||
def playlists_fixture():
|
||||
"""Load payload for all playlists and return it."""
|
||||
return load_fixture("plex/playlists.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="playlist_500", scope="session")
|
||||
def playlist_500_fixture():
|
||||
"""Load payload for playlist 500 and return it."""
|
||||
return load_fixture("plex/playlist_500.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="playqueue_created", scope="session")
|
||||
def playqueue_created_fixture():
|
||||
"""Load payload for playqueue creation response and return it."""
|
||||
return load_fixture("plex/playqueue_created.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="plex_server_accounts", scope="session")
|
||||
def plex_server_accounts_fixture():
|
||||
"""Load payload accounts on the Plex server and return it."""
|
||||
return load_fixture("plex/plex_server_accounts.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="plex_server_base", scope="session")
|
||||
def plex_server_base_fixture():
|
||||
"""Load base payload for Plex server info and return it."""
|
||||
return load_fixture("plex/plex_server_base.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="plex_server_default", scope="session")
|
||||
def plex_server_default_fixture(plex_server_base):
|
||||
"""Load default payload for Plex server info and return it."""
|
||||
return plex_server_base.format(
|
||||
name="Plex Server 1", machine_identifier="unique_id_123"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="plex_server_clients", scope="session")
|
||||
def plex_server_clients_fixture():
|
||||
"""Load available clients payload for Plex server and return it."""
|
||||
return load_fixture("plex/plex_server_clients.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="plextv_account", scope="session")
|
||||
def plextv_account_fixture():
|
||||
"""Load account info from plex.tv and return it."""
|
||||
return load_fixture("plex/plextv_account.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="plextv_resources_base", scope="session")
|
||||
def plextv_resources_base_fixture():
|
||||
"""Load base payload for plex.tv resources and return it."""
|
||||
return load_fixture("plex/plextv_resources_base.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="plextv_resources", scope="session")
|
||||
def plextv_resources_fixture(plextv_resources_base):
|
||||
"""Load default payload for plex.tv resources and return it."""
|
||||
return plextv_resources_base.format(second_server_enabled=0)
|
||||
|
||||
|
||||
@pytest.fixture(name="session_base", scope="session")
|
||||
def session_base_fixture():
|
||||
"""Load the base session payload and return it."""
|
||||
return load_fixture("plex/session_base.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="session_default", scope="session")
|
||||
def session_default_fixture(session_base):
|
||||
"""Load the default session payload and return it."""
|
||||
return session_base.format(user_id=1)
|
||||
|
||||
|
||||
@pytest.fixture(name="session_new_user", scope="session")
|
||||
def session_new_user_fixture(session_base):
|
||||
"""Load the new user session payload and return it."""
|
||||
return session_base.format(user_id=1001)
|
||||
|
||||
|
||||
@pytest.fixture(name="session_photo", scope="session")
|
||||
def session_photo_fixture():
|
||||
"""Load a photo session payload and return it."""
|
||||
return load_fixture("plex/session_photo.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="session_plexweb", scope="session")
|
||||
def session_plexweb_fixture():
|
||||
"""Load a Plex Web session payload and return it."""
|
||||
return load_fixture("plex/session_plexweb.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="security_token", scope="session")
|
||||
def security_token_fixture():
|
||||
"""Load a security token payload and return it."""
|
||||
return load_fixture("plex/security_token.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="show_seasons", scope="session")
|
||||
def show_seasons_fixture():
|
||||
"""Load a show's seasons payload and return it."""
|
||||
return load_fixture("plex/show_seasons.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="sonos_resources", scope="session")
|
||||
def sonos_resources_fixture():
|
||||
"""Load Sonos resources payload and return it."""
|
||||
return load_fixture("plex/sonos_resources.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="entry")
|
||||
|
@ -23,14 +271,6 @@ def mock_config_entry():
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_plex_account():
|
||||
"""Mock the PlexAccount class and return the used instance."""
|
||||
plex_account = MockPlexAccount()
|
||||
with patch("plexapi.myplex.MyPlexAccount", return_value=plex_account):
|
||||
yield plex_account
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_websocket():
|
||||
"""Mock the PlexWebsocket class."""
|
||||
|
@ -39,15 +279,112 @@ def mock_websocket():
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_plex_server(hass, entry, mock_plex_account, mock_websocket):
|
||||
def mock_plex_calls(
|
||||
entry,
|
||||
requests_mock,
|
||||
children_20,
|
||||
children_30,
|
||||
children_200,
|
||||
children_300,
|
||||
empty_library,
|
||||
grandchildren_300,
|
||||
library,
|
||||
library_sections,
|
||||
library_movies_all,
|
||||
library_movies_sort,
|
||||
library_music_all,
|
||||
library_music_sort,
|
||||
library_tvshows_all,
|
||||
library_tvshows_sort,
|
||||
media_1,
|
||||
media_30,
|
||||
media_100,
|
||||
media_200,
|
||||
playlists,
|
||||
playlist_500,
|
||||
plextv_account,
|
||||
plextv_resources,
|
||||
plex_server_accounts,
|
||||
plex_server_clients,
|
||||
plex_server_default,
|
||||
security_token,
|
||||
):
|
||||
"""Mock Plex API calls."""
|
||||
requests_mock.get("https://plex.tv/users/account", text=plextv_account)
|
||||
requests_mock.get("https://plex.tv/api/resources", text=plextv_resources)
|
||||
|
||||
url = plex_server_url(entry)
|
||||
|
||||
for server in [url, PLEX_DIRECT_URL]:
|
||||
requests_mock.get(server, text=plex_server_default)
|
||||
requests_mock.get(f"{server}/accounts", text=plex_server_accounts)
|
||||
|
||||
requests_mock.get(f"{url}/clients", text=plex_server_clients)
|
||||
requests_mock.get(f"{url}/library", text=library)
|
||||
requests_mock.get(f"{url}/library/sections", text=library_sections)
|
||||
|
||||
requests_mock.get(f"{url}/library/onDeck", text=empty_library)
|
||||
requests_mock.get(f"{url}/library/sections/1/sorts", text=library_movies_sort)
|
||||
requests_mock.get(f"{url}/library/sections/2/sorts", text=library_tvshows_sort)
|
||||
requests_mock.get(f"{url}/library/sections/3/sorts", text=library_music_sort)
|
||||
|
||||
requests_mock.get(f"{url}/library/sections/1/all", text=library_movies_all)
|
||||
requests_mock.get(f"{url}/library/sections/2/all", text=library_tvshows_all)
|
||||
requests_mock.get(f"{url}/library/sections/3/all", text=library_music_all)
|
||||
|
||||
requests_mock.get(f"{url}/library/metadata/200/children", text=children_200)
|
||||
requests_mock.get(f"{url}/library/metadata/300/children", text=children_300)
|
||||
requests_mock.get(f"{url}/library/metadata/300/allLeaves", text=grandchildren_300)
|
||||
|
||||
requests_mock.get(f"{url}/library/metadata/1", text=media_1)
|
||||
requests_mock.get(f"{url}/library/metadata/30", text=media_30)
|
||||
requests_mock.get(f"{url}/library/metadata/100", text=media_100)
|
||||
requests_mock.get(f"{url}/library/metadata/200", text=media_200)
|
||||
|
||||
requests_mock.get(f"{url}/library/metadata/20/children", text=children_20)
|
||||
requests_mock.get(f"{url}/library/metadata/30/children", text=children_30)
|
||||
|
||||
requests_mock.get(f"{url}/playlists", text=playlists)
|
||||
requests_mock.get(f"{url}/playlists/500/items", text=playlist_500)
|
||||
requests_mock.get(f"{url}/security/token", text=security_token)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_plex_server(
|
||||
hass,
|
||||
entry,
|
||||
mock_websocket,
|
||||
mock_plex_calls,
|
||||
requests_mock,
|
||||
empty_payload,
|
||||
session_default,
|
||||
session_photo,
|
||||
session_plexweb,
|
||||
):
|
||||
"""Set up and return a mocked Plex server instance."""
|
||||
|
||||
async def _wrapper(**kwargs):
|
||||
"""Wrap the fixture to allow passing arguments to the MockPlexServer instance."""
|
||||
"""Wrap the fixture to allow passing arguments to the setup method."""
|
||||
config_entry = kwargs.get("config_entry", entry)
|
||||
disable_clients = kwargs.pop("disable_clients", False)
|
||||
disable_gdm = kwargs.pop("disable_gdm", True)
|
||||
plex_server = MockPlexServer(**kwargs)
|
||||
with patch("plexapi.server.PlexServer", return_value=plex_server), patch(
|
||||
client_type = kwargs.pop("client_type", None)
|
||||
session_type = kwargs.pop("session_type", None)
|
||||
|
||||
if client_type == "plexweb":
|
||||
session = session_plexweb
|
||||
elif session_type == "photo":
|
||||
session = session_photo
|
||||
else:
|
||||
session = session_default
|
||||
|
||||
url = plex_server_url(entry)
|
||||
requests_mock.get(f"{url}/status/sessions", text=session)
|
||||
|
||||
if disable_clients:
|
||||
requests_mock.get(f"{url}/clients", text=empty_payload)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.plex.GDM",
|
||||
return_value=MockGDM(disabled=disable_gdm),
|
||||
):
|
||||
|
@ -56,6 +393,8 @@ def setup_plex_server(hass, entry, mock_plex_account, mock_websocket):
|
|||
await hass.async_block_till_done()
|
||||
websocket_connected(mock_websocket)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
plex_server = hass.data[DOMAIN][SERVERS][entry.unique_id]
|
||||
return plex_server
|
||||
|
||||
return _wrapper
|
||||
|
|
|
@ -61,3 +61,5 @@ DEFAULT_OPTIONS = {
|
|||
const.CONF_USE_EPISODE_ART: False,
|
||||
}
|
||||
}
|
||||
|
||||
PLEX_DIRECT_URL = "https://1-2-3-4.123456789001234567890.plex.direct:32400"
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
"""Mock classes used in tests."""
|
||||
from functools import lru_cache
|
||||
|
||||
from aiohttp.helpers import reify
|
||||
from plexapi.exceptions import NotFound
|
||||
|
||||
from homeassistant.components.plex.const import (
|
||||
CONF_SERVER,
|
||||
CONF_SERVER_IDENTIFIER,
|
||||
PLEX_SERVER_CONFIG,
|
||||
)
|
||||
from homeassistant.const import CONF_URL
|
||||
|
||||
from .const import DEFAULT_DATA, MOCK_SERVERS, MOCK_USERS
|
||||
|
||||
GDM_SERVER_PAYLOAD = [
|
||||
{
|
||||
|
@ -94,520 +81,3 @@ class MockGDM:
|
|||
self.entries = GDM_CLIENT_PAYLOAD
|
||||
else:
|
||||
self.entries = GDM_SERVER_PAYLOAD
|
||||
|
||||
|
||||
class MockResource:
|
||||
"""Mock a PlexAccount resource."""
|
||||
|
||||
def __init__(self, index, kind="server"):
|
||||
"""Initialize the object."""
|
||||
if kind == "server":
|
||||
self.name = MOCK_SERVERS[index][CONF_SERVER]
|
||||
self.clientIdentifier = MOCK_SERVERS[index][ # pylint: disable=invalid-name
|
||||
CONF_SERVER_IDENTIFIER
|
||||
]
|
||||
self.provides = ["server"]
|
||||
self.device = MockPlexServer(index)
|
||||
else:
|
||||
self.name = f"plex.tv Resource Player {index+10}"
|
||||
self.clientIdentifier = f"client-{index+10}"
|
||||
self.provides = ["player"]
|
||||
self.device = MockPlexClient(
|
||||
baseurl=f"http://192.168.0.1{index}:32500", index=index + 10
|
||||
)
|
||||
self.presence = index == 0
|
||||
self.publicAddressMatches = True
|
||||
|
||||
def connect(self, timeout):
|
||||
"""Mock the resource connect method."""
|
||||
return self.device
|
||||
|
||||
|
||||
class MockPlexAccount:
|
||||
"""Mock a PlexAccount instance."""
|
||||
|
||||
def __init__(self, servers=1, players=3):
|
||||
"""Initialize the object."""
|
||||
self._resources = []
|
||||
for index in range(servers):
|
||||
self._resources.append(MockResource(index))
|
||||
for index in range(players):
|
||||
self._resources.append(MockResource(index, kind="player"))
|
||||
|
||||
def resource(self, name):
|
||||
"""Mock the PlexAccount resource lookup method."""
|
||||
return [x for x in self._resources if x.name == name][0]
|
||||
|
||||
def resources(self):
|
||||
"""Mock the PlexAccount resources listing method."""
|
||||
return self._resources
|
||||
|
||||
def sonos_speaker(self, speaker_name):
|
||||
"""Mock the PlexAccount Sonos lookup method."""
|
||||
return MockPlexSonosClient(speaker_name)
|
||||
|
||||
|
||||
class MockPlexSystemAccount:
|
||||
"""Mock a PlexSystemAccount instance."""
|
||||
|
||||
def __init__(self, index):
|
||||
"""Initialize the object."""
|
||||
# Start accountIDs at 1 to set proper owner.
|
||||
self.name = list(MOCK_USERS)[index]
|
||||
self.accountID = index + 1
|
||||
|
||||
|
||||
class MockPlexServer:
|
||||
"""Mock a PlexServer instance."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
index=0,
|
||||
config_entry=None,
|
||||
num_users=len(MOCK_USERS),
|
||||
session_type="video",
|
||||
):
|
||||
"""Initialize the object."""
|
||||
if config_entry:
|
||||
self._data = config_entry.data
|
||||
else:
|
||||
self._data = DEFAULT_DATA
|
||||
|
||||
self._baseurl = self._data[PLEX_SERVER_CONFIG][CONF_URL]
|
||||
self.friendlyName = self._data[CONF_SERVER]
|
||||
self.machineIdentifier = self._data[CONF_SERVER_IDENTIFIER]
|
||||
|
||||
self._systemAccounts = list(map(MockPlexSystemAccount, range(num_users)))
|
||||
|
||||
self._clients = []
|
||||
self._session = None
|
||||
self._sessions = []
|
||||
self.set_clients(num_users)
|
||||
self.set_sessions(num_users, session_type)
|
||||
|
||||
self._cache = {}
|
||||
|
||||
def set_clients(self, num_clients):
|
||||
"""Set up mock PlexClients for this PlexServer."""
|
||||
self._clients = [
|
||||
MockPlexClient(baseurl=self._baseurl, index=x) for x in range(num_clients)
|
||||
]
|
||||
|
||||
def set_sessions(self, num_sessions, session_type):
|
||||
"""Set up mock PlexSessions for this PlexServer."""
|
||||
self._sessions = [
|
||||
MockPlexSession(self._clients[x], mediatype=session_type, index=x)
|
||||
for x in range(num_sessions)
|
||||
]
|
||||
|
||||
def clear_clients(self):
|
||||
"""Clear all active PlexClients."""
|
||||
self._clients = []
|
||||
|
||||
def clear_sessions(self):
|
||||
"""Clear all active PlexSessions."""
|
||||
self._sessions = []
|
||||
|
||||
def clients(self):
|
||||
"""Mock the clients method."""
|
||||
return self._clients
|
||||
|
||||
def createToken(self):
|
||||
"""Mock the createToken method."""
|
||||
return "temporary_token"
|
||||
|
||||
def sessions(self):
|
||||
"""Mock the sessions method."""
|
||||
return self._sessions
|
||||
|
||||
def systemAccounts(self):
|
||||
"""Mock the systemAccounts lookup method."""
|
||||
return self._systemAccounts
|
||||
|
||||
def url(self, path, includeToken=False):
|
||||
"""Mock method to generate a server URL."""
|
||||
return f"{self._baseurl}{path}"
|
||||
|
||||
@property
|
||||
def accounts(self):
|
||||
"""Mock the accounts property."""
|
||||
return set(MOCK_USERS)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Mock version of PlexServer."""
|
||||
return "1.0"
|
||||
|
||||
@reify
|
||||
def library(self):
|
||||
"""Mock library object of PlexServer."""
|
||||
return MockPlexLibrary(self)
|
||||
|
||||
def playlist(self, playlist):
|
||||
"""Mock the playlist lookup method."""
|
||||
return MockPlexMediaItem(playlist, mediatype="playlist")
|
||||
|
||||
@lru_cache
|
||||
def playlists(self):
|
||||
"""Mock the playlists lookup method with a lazy init."""
|
||||
return [
|
||||
MockPlexPlaylist(
|
||||
self.library.section("Movies").all()
|
||||
+ self.library.section("TV Shows").all()
|
||||
),
|
||||
MockPlexPlaylist(self.library.section("Music").all()),
|
||||
]
|
||||
|
||||
def fetchItem(self, item):
|
||||
"""Mock the fetchItem method."""
|
||||
for section in self.library.sections():
|
||||
result = section.fetchItem(item)
|
||||
if result:
|
||||
return result
|
||||
|
||||
|
||||
class MockPlexClient:
|
||||
"""Mock a PlexClient instance."""
|
||||
|
||||
def __init__(self, server=None, baseurl=None, token=None, index=0):
|
||||
"""Initialize the object."""
|
||||
self.machineIdentifier = f"client-{index+1}"
|
||||
self._baseurl = baseurl
|
||||
self._index = index
|
||||
|
||||
def url(self, key):
|
||||
"""Mock the url method."""
|
||||
return f"{self._baseurl}{key}"
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
"""Mock the device attribute."""
|
||||
return "DEVICE"
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
"""Mock the platform attribute."""
|
||||
return "PLATFORM"
|
||||
|
||||
@property
|
||||
def product(self):
|
||||
"""Mock the product attribute."""
|
||||
if self._index == 1:
|
||||
return "Plex Web"
|
||||
return "PRODUCT"
|
||||
|
||||
@property
|
||||
def protocolCapabilities(self):
|
||||
"""Mock the protocolCapabilities attribute."""
|
||||
return ["playback"]
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Mock the state attribute."""
|
||||
return "playing"
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""Mock the title attribute."""
|
||||
return "TITLE"
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Mock the version attribute."""
|
||||
return "1.0"
|
||||
|
||||
def proxyThroughServer(self, value=True, server=None):
|
||||
"""Mock the proxyThroughServer method."""
|
||||
pass
|
||||
|
||||
def playMedia(self, item):
|
||||
"""Mock the playMedia method."""
|
||||
pass
|
||||
|
||||
|
||||
class MockPlexSession:
|
||||
"""Mock a PlexServer.sessions() instance."""
|
||||
|
||||
def __init__(self, player, mediatype, index=0):
|
||||
"""Initialize the object."""
|
||||
self.TYPE = mediatype
|
||||
self.usernames = [list(MOCK_USERS)[index]]
|
||||
self.players = [player]
|
||||
self._section = MockPlexLibrarySection("Movies")
|
||||
self.sessionKey = index + 1
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Mock the duration attribute."""
|
||||
return 10000000
|
||||
|
||||
@property
|
||||
def librarySectionID(self):
|
||||
"""Mock the librarySectionID attribute."""
|
||||
return 1
|
||||
|
||||
@property
|
||||
def ratingKey(self):
|
||||
"""Mock the ratingKey attribute."""
|
||||
return 123
|
||||
|
||||
def section(self):
|
||||
"""Mock the section method."""
|
||||
return self._section
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
"""Mock the summary attribute."""
|
||||
return "SUMMARY"
|
||||
|
||||
@property
|
||||
def thumbUrl(self):
|
||||
"""Mock the thumbUrl attribute."""
|
||||
return "http://1.2.3.4/thumb"
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""Mock the title attribute."""
|
||||
return "TITLE"
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Mock the type attribute."""
|
||||
return "movie"
|
||||
|
||||
@property
|
||||
def viewOffset(self):
|
||||
"""Mock the viewOffset attribute."""
|
||||
return 0
|
||||
|
||||
@property
|
||||
def year(self):
|
||||
"""Mock the year attribute."""
|
||||
return 2020
|
||||
|
||||
|
||||
class MockPlexLibrary:
|
||||
"""Mock a Plex Library instance."""
|
||||
|
||||
def __init__(self, plex_server):
|
||||
"""Initialize the object."""
|
||||
self._plex_server = plex_server
|
||||
self._sections = {}
|
||||
|
||||
for kind in ["Movies", "Music", "TV Shows", "Photos"]:
|
||||
self._sections[kind] = MockPlexLibrarySection(kind)
|
||||
|
||||
def section(self, title):
|
||||
"""Mock the LibrarySection lookup."""
|
||||
section = self._sections.get(title)
|
||||
if section:
|
||||
return section
|
||||
raise NotFound
|
||||
|
||||
def sections(self):
|
||||
"""Return all available sections."""
|
||||
return self._sections.values()
|
||||
|
||||
def sectionByID(self, section_id):
|
||||
"""Mock the sectionByID lookup."""
|
||||
return [x for x in self.sections() if x.key == section_id][0]
|
||||
|
||||
def onDeck(self):
|
||||
"""Mock an empty On Deck folder."""
|
||||
return []
|
||||
|
||||
def recentlyAdded(self):
|
||||
"""Mock an empty Recently Added folder."""
|
||||
return []
|
||||
|
||||
|
||||
class MockPlexLibrarySection:
|
||||
"""Mock a Plex LibrarySection instance."""
|
||||
|
||||
def __init__(self, library):
|
||||
"""Initialize the object."""
|
||||
self.title = library
|
||||
|
||||
if library == "Music":
|
||||
self._item = MockPlexArtist("Artist")
|
||||
elif library == "TV Shows":
|
||||
self._item = MockPlexShow("TV Show")
|
||||
else:
|
||||
self._item = MockPlexMediaItem(library[:-1])
|
||||
|
||||
def get(self, query):
|
||||
"""Mock the get lookup method."""
|
||||
if self._item.title == query:
|
||||
return self._item
|
||||
raise NotFound
|
||||
|
||||
def all(self):
|
||||
"""Mock the all method."""
|
||||
return [self._item]
|
||||
|
||||
def fetchItem(self, ratingKey):
|
||||
"""Return a specific item."""
|
||||
for item in self.all():
|
||||
if item.ratingKey == ratingKey:
|
||||
return item
|
||||
if item._children:
|
||||
for child in item._children:
|
||||
if child.ratingKey == ratingKey:
|
||||
return child
|
||||
|
||||
def onDeck(self):
|
||||
"""Mock an empty On Deck folder."""
|
||||
return []
|
||||
|
||||
def recentlyAdded(self):
|
||||
"""Mock an empty Recently Added folder."""
|
||||
return self.all()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Mock the library type."""
|
||||
if self.title == "Movies":
|
||||
return "movie"
|
||||
if self.title == "Music":
|
||||
return "artist"
|
||||
if self.title == "TV Shows":
|
||||
return "show"
|
||||
if self.title == "Photos":
|
||||
return "photo"
|
||||
|
||||
@property
|
||||
def TYPE(self):
|
||||
"""Return the library type."""
|
||||
return self.type
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
"""Mock the key identifier property."""
|
||||
return str(id(self.title))
|
||||
|
||||
def search(self, **kwargs):
|
||||
"""Mock the LibrarySection search method."""
|
||||
if kwargs.get("libtype") == "movie":
|
||||
return self.all()
|
||||
|
||||
def update(self):
|
||||
"""Mock the update call."""
|
||||
pass
|
||||
|
||||
|
||||
class MockPlexMediaItem:
|
||||
"""Mock a Plex Media instance."""
|
||||
|
||||
def __init__(self, title, mediatype="video", year=2020):
|
||||
"""Initialize the object."""
|
||||
self.title = str(title)
|
||||
self.type = mediatype
|
||||
self.thumbUrl = "http://1.2.3.4/thumb.png"
|
||||
self.year = year
|
||||
self._children = []
|
||||
|
||||
def __iter__(self):
|
||||
"""Provide iterator."""
|
||||
yield from self._children
|
||||
|
||||
@property
|
||||
def ratingKey(self):
|
||||
"""Mock the ratingKey property."""
|
||||
return id(self.title)
|
||||
|
||||
|
||||
class MockPlexPlaylist(MockPlexMediaItem):
|
||||
"""Mock a Plex Playlist instance."""
|
||||
|
||||
def __init__(self, items):
|
||||
"""Initialize the object."""
|
||||
super().__init__(f"Playlist ({len(items)} Items)", "playlist")
|
||||
for item in items:
|
||||
self._children.append(item)
|
||||
|
||||
|
||||
class MockPlexShow(MockPlexMediaItem):
|
||||
"""Mock a Plex Show instance."""
|
||||
|
||||
def __init__(self, show):
|
||||
"""Initialize the object."""
|
||||
super().__init__(show, "show")
|
||||
for index in range(1, 5):
|
||||
self._children.append(MockPlexSeason(index))
|
||||
|
||||
def season(self, season):
|
||||
"""Mock the season lookup method."""
|
||||
return [x for x in self._children if x.title == f"Season {season}"][0]
|
||||
|
||||
|
||||
class MockPlexSeason(MockPlexMediaItem):
|
||||
"""Mock a Plex Season instance."""
|
||||
|
||||
def __init__(self, season):
|
||||
"""Initialize the object."""
|
||||
super().__init__(f"Season {season}", "season")
|
||||
for index in range(1, 10):
|
||||
self._children.append(MockPlexMediaItem(f"Episode {index}", "episode"))
|
||||
|
||||
def episode(self, episode):
|
||||
"""Mock the episode lookup method."""
|
||||
return self._children[episode - 1]
|
||||
|
||||
|
||||
class MockPlexAlbum(MockPlexMediaItem):
|
||||
"""Mock a Plex Album instance."""
|
||||
|
||||
def __init__(self, album):
|
||||
"""Initialize the object."""
|
||||
super().__init__(album, "album")
|
||||
for index in range(1, 10):
|
||||
self._children.append(MockPlexMediaTrack(index))
|
||||
|
||||
def track(self, track):
|
||||
"""Mock the track lookup method."""
|
||||
try:
|
||||
return [x for x in self._children if x.title == track][0]
|
||||
except IndexError:
|
||||
raise NotFound
|
||||
|
||||
def tracks(self):
|
||||
"""Mock the tracks lookup method."""
|
||||
return self._children
|
||||
|
||||
|
||||
class MockPlexArtist(MockPlexMediaItem):
|
||||
"""Mock a Plex Artist instance."""
|
||||
|
||||
def __init__(self, artist):
|
||||
"""Initialize the object."""
|
||||
super().__init__(artist, "artist")
|
||||
self._album = MockPlexAlbum("Album")
|
||||
|
||||
def album(self, album):
|
||||
"""Mock the album lookup method."""
|
||||
return self._album
|
||||
|
||||
def get(self, track):
|
||||
"""Mock the track lookup method."""
|
||||
return self._album.track(track)
|
||||
|
||||
|
||||
class MockPlexMediaTrack(MockPlexMediaItem):
|
||||
"""Mock a Plex Track instance."""
|
||||
|
||||
def __init__(self, index=1):
|
||||
"""Initialize the object."""
|
||||
super().__init__(f"Track {index}", "track")
|
||||
self.index = index
|
||||
|
||||
|
||||
class MockPlexSonosClient:
|
||||
"""Mock a PlexSonosClient instance."""
|
||||
|
||||
def __init__(self, name):
|
||||
"""Initialize the object."""
|
||||
self.name = name
|
||||
|
||||
def playMedia(self, item):
|
||||
"""Mock the playMedia method."""
|
||||
pass
|
||||
|
|
|
@ -10,7 +10,7 @@ from homeassistant.components.websocket_api.const import ERR_UNKNOWN_ERROR, TYPE
|
|||
from .const import DEFAULT_DATA
|
||||
|
||||
|
||||
async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websocket):
|
||||
async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_mock):
|
||||
"""Test getting Plex clients from plex.tv."""
|
||||
websocket_client = await hass_ws_client(hass)
|
||||
|
||||
|
@ -51,8 +51,10 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websock
|
|||
result = msg["result"]
|
||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "server"
|
||||
assert result[ATTR_MEDIA_CONTENT_ID] == DEFAULT_DATA[CONF_SERVER_IDENTIFIER]
|
||||
assert len(result["children"]) == len(mock_plex_server.library.sections()) + len(
|
||||
SPECIAL_METHODS
|
||||
# Library Sections + Special Sections + Playlists
|
||||
assert (
|
||||
len(result["children"])
|
||||
== len(mock_plex_server.library.sections()) + len(SPECIAL_METHODS) + 1
|
||||
)
|
||||
|
||||
tvshows = next(iter(x for x in result["children"] if x["title"] == "TV Shows"))
|
||||
|
@ -149,9 +151,14 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websock
|
|||
result = msg["result"]
|
||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "show"
|
||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
||||
assert result["title"] == mock_plex_server.fetchItem(result_id).title
|
||||
assert result["title"] == mock_plex_server.fetch_item(result_id).title
|
||||
|
||||
# Browse into a non-existent TV season
|
||||
unknown_key = 99999999999999
|
||||
requests_mock.get(
|
||||
f"{mock_plex_server.url_in_use}/library/metadata/{unknown_key}", status_code=404
|
||||
)
|
||||
|
||||
msg_id += 1
|
||||
await websocket_client.send_json(
|
||||
{
|
||||
|
@ -159,7 +166,7 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websock
|
|||
"type": "media_player/browse_media",
|
||||
"entity_id": media_players[0],
|
||||
ATTR_MEDIA_CONTENT_TYPE: result["children"][0][ATTR_MEDIA_CONTENT_TYPE],
|
||||
ATTR_MEDIA_CONTENT_ID: str(99999999999999),
|
||||
ATTR_MEDIA_CONTENT_ID: str(unknown_key),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -35,16 +35,11 @@ from homeassistant.const import (
|
|||
CONF_URL,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
|
||||
from .const import DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN, PLEX_DIRECT_URL
|
||||
from .helpers import trigger_plex_update, wait_for_debouncer
|
||||
from .mock_classes import (
|
||||
MockGDM,
|
||||
MockPlexAccount,
|
||||
MockPlexClient,
|
||||
MockPlexServer,
|
||||
MockResource,
|
||||
)
|
||||
from .mock_classes import MockGDM
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
@ -82,7 +77,7 @@ async def test_bad_credentials(hass):
|
|||
assert result["errors"][CONF_TOKEN] == "faulty_credentials"
|
||||
|
||||
|
||||
async def test_bad_hostname(hass):
|
||||
async def test_bad_hostname(hass, mock_plex_calls):
|
||||
"""Test when an invalid address is provided."""
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
|
@ -96,12 +91,9 @@ async def test_bad_hostname(hass):
|
|||
assert result["step_id"] == "user"
|
||||
|
||||
with patch(
|
||||
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
|
||||
), patch.object(
|
||||
MockResource, "connect", side_effect=requests.exceptions.ConnectionError
|
||||
), patch(
|
||||
"plexauth.PlexAuth.initiate_auth"
|
||||
), patch(
|
||||
"plexapi.myplex.MyPlexResource.connect",
|
||||
side_effect=requests.exceptions.ConnectionError,
|
||||
), patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
|
@ -148,8 +140,9 @@ async def test_unknown_exception(hass):
|
|||
assert result["reason"] == "unknown"
|
||||
|
||||
|
||||
async def test_no_servers_found(hass):
|
||||
async def test_no_servers_found(hass, mock_plex_calls, requests_mock, empty_payload):
|
||||
"""Test when no servers are on an account."""
|
||||
requests_mock.get("https://plex.tv/api/resources", text=empty_payload)
|
||||
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
|
@ -162,9 +155,7 @@ async def test_no_servers_found(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
with patch(
|
||||
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0)
|
||||
), patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
with patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
|
@ -181,11 +172,9 @@ async def test_no_servers_found(hass):
|
|||
assert result["errors"]["base"] == "no_servers"
|
||||
|
||||
|
||||
async def test_single_available_server(hass):
|
||||
async def test_single_available_server(hass, mock_plex_calls):
|
||||
"""Test creating an entry with one server available."""
|
||||
|
||||
mock_plex_server = MockPlexServer()
|
||||
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"internal_url": "http://example.local:8123"},
|
||||
|
@ -197,9 +186,7 @@ async def test_single_available_server(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch(
|
||||
"plexapi.server.PlexServer", return_value=mock_plex_server
|
||||
), patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
with patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
|
@ -212,20 +199,27 @@ async def test_single_available_server(hass):
|
|||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName
|
||||
|
||||
server_id = result["data"][CONF_SERVER_IDENTIFIER]
|
||||
mock_plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
assert result["title"] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name
|
||||
assert (
|
||||
result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
result["data"][CONF_SERVER_IDENTIFIER]
|
||||
== mock_plex_server.machine_identifier
|
||||
)
|
||||
assert (
|
||||
result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use
|
||||
)
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
||||
|
||||
|
||||
async def test_multiple_servers_with_selection(hass):
|
||||
async def test_multiple_servers_with_selection(
|
||||
hass, mock_plex_calls, requests_mock, plextv_resources_base
|
||||
):
|
||||
"""Test creating an entry with multiple servers available."""
|
||||
|
||||
mock_plex_server = MockPlexServer()
|
||||
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"internal_url": "http://example.local:8123"},
|
||||
|
@ -237,11 +231,11 @@ async def test_multiple_servers_with_selection(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
with patch(
|
||||
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2)
|
||||
), patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
||||
"plexauth.PlexAuth.initiate_auth"
|
||||
), patch(
|
||||
requests_mock.get(
|
||||
"https://plex.tv/api/resources",
|
||||
text=plextv_resources_base.format(second_server_enabled=1),
|
||||
)
|
||||
with patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
|
@ -261,20 +255,27 @@ async def test_multiple_servers_with_selection(hass):
|
|||
user_input={CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER]},
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName
|
||||
|
||||
server_id = result["data"][CONF_SERVER_IDENTIFIER]
|
||||
mock_plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
assert result["title"] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name
|
||||
assert (
|
||||
result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
result["data"][CONF_SERVER_IDENTIFIER]
|
||||
== mock_plex_server.machine_identifier
|
||||
)
|
||||
assert (
|
||||
result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use
|
||||
)
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
||||
|
||||
|
||||
async def test_adding_last_unconfigured_server(hass):
|
||||
async def test_adding_last_unconfigured_server(
|
||||
hass, mock_plex_calls, requests_mock, plextv_resources_base
|
||||
):
|
||||
"""Test automatically adding last unconfigured server when multiple servers on account."""
|
||||
|
||||
mock_plex_server = MockPlexServer()
|
||||
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"internal_url": "http://example.local:8123"},
|
||||
|
@ -294,11 +295,12 @@ async def test_adding_last_unconfigured_server(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
with patch(
|
||||
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2)
|
||||
), patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
||||
"plexauth.PlexAuth.initiate_auth"
|
||||
), patch(
|
||||
requests_mock.get(
|
||||
"https://plex.tv/api/resources",
|
||||
text=plextv_resources_base.format(second_server_enabled=1),
|
||||
)
|
||||
|
||||
with patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
|
@ -311,16 +313,25 @@ async def test_adding_last_unconfigured_server(hass):
|
|||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName
|
||||
|
||||
server_id = result["data"][CONF_SERVER_IDENTIFIER]
|
||||
mock_plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
assert result["title"] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name
|
||||
assert (
|
||||
result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
result["data"][CONF_SERVER_IDENTIFIER]
|
||||
== mock_plex_server.machine_identifier
|
||||
)
|
||||
assert (
|
||||
result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use
|
||||
)
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
||||
|
||||
|
||||
async def test_all_available_servers_configured(hass):
|
||||
async def test_all_available_servers_configured(
|
||||
hass, entry, requests_mock, plextv_account, plextv_resources_base
|
||||
):
|
||||
"""Test when all available servers are already configured."""
|
||||
|
||||
await async_process_ha_core_config(
|
||||
|
@ -328,13 +339,7 @@ async def test_all_available_servers_configured(hass):
|
|||
{"internal_url": "http://example.local:8123"},
|
||||
)
|
||||
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER],
|
||||
CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER],
|
||||
},
|
||||
).add_to_hass(hass)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
|
@ -350,9 +355,13 @@ async def test_all_available_servers_configured(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
with patch(
|
||||
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2)
|
||||
), patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
requests_mock.get("https://plex.tv/users/account", text=plextv_account)
|
||||
requests_mock.get(
|
||||
"https://plex.tv/api/resources",
|
||||
text=plextv_resources_base.format(second_server_enabled=1),
|
||||
)
|
||||
|
||||
with patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
|
@ -432,32 +441,22 @@ async def test_missing_option_flow(hass, entry, mock_plex_server):
|
|||
}
|
||||
|
||||
|
||||
async def test_option_flow_new_users_available(
|
||||
hass, caplog, entry, mock_websocket, setup_plex_server
|
||||
):
|
||||
async def test_option_flow_new_users_available(hass, entry, setup_plex_server):
|
||||
"""Test config options multiselect defaults when new Plex users are seen."""
|
||||
OPTIONS_OWNER_ONLY = copy.deepcopy(DEFAULT_OPTIONS)
|
||||
OPTIONS_OWNER_ONLY[MP_DOMAIN][CONF_MONITORED_USERS] = {"Owner": {"enabled": True}}
|
||||
OPTIONS_OWNER_ONLY[MP_DOMAIN][CONF_MONITORED_USERS] = {"User 1": {"enabled": True}}
|
||||
entry.options = OPTIONS_OWNER_ONLY
|
||||
|
||||
with patch("homeassistant.components.plex.server.PlexClient", new=MockPlexClient):
|
||||
mock_plex_server = await setup_plex_server(
|
||||
config_entry=entry, disable_gdm=False
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_plex_server = await setup_plex_server(config_entry=entry)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
|
||||
|
||||
new_users = [x for x in mock_plex_server.accounts if x not in monitored_users]
|
||||
assert len(monitored_users) == 1
|
||||
assert len(new_users) == 2
|
||||
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
|
||||
result = await hass.config_entries.options.async_init(
|
||||
entry.entry_id, context={"source": "test"}, data=None
|
||||
)
|
||||
|
@ -465,7 +464,7 @@ async def test_option_flow_new_users_available(
|
|||
assert result["step_id"] == "plex_mp_settings"
|
||||
multiselect_defaults = result["data_schema"].schema["monitored_users"].options
|
||||
|
||||
assert "[Owner]" in multiselect_defaults["Owner"]
|
||||
assert "[Owner]" in multiselect_defaults["User 1"]
|
||||
for user in new_users:
|
||||
assert "[New]" in multiselect_defaults[user]
|
||||
|
||||
|
@ -529,7 +528,7 @@ async def test_callback_view(hass, aiohttp_client):
|
|||
assert resp.status == 200
|
||||
|
||||
|
||||
async def test_manual_config(hass):
|
||||
async def test_manual_config(hass, mock_plex_calls):
|
||||
"""Test creating via manual configuration."""
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
|
@ -587,8 +586,6 @@ async def test_manual_config(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual_setup"
|
||||
|
||||
mock_plex_server = MockPlexServer()
|
||||
|
||||
MANUAL_SERVER = {
|
||||
CONF_HOST: MOCK_SERVERS[0][CONF_HOST],
|
||||
CONF_PORT: MOCK_SERVERS[0][CONF_PORT],
|
||||
|
@ -647,26 +644,26 @@ async def test_manual_config(hass):
|
|||
assert result["step_id"] == "manual_setup"
|
||||
assert result["errors"]["base"] == "ssl_error"
|
||||
|
||||
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
||||
"homeassistant.components.plex.PlexWebsocket", autospec=True
|
||||
), patch(
|
||||
with patch("homeassistant.components.plex.PlexWebsocket", autospec=True), patch(
|
||||
"homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True)
|
||||
), patch(
|
||||
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input=MANUAL_SERVER
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
|
||||
server_id = result["data"][CONF_SERVER_IDENTIFIER]
|
||||
mock_plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
assert result["title"] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
||||
|
||||
|
||||
async def test_manual_config_with_token(hass):
|
||||
async def test_manual_config_with_token(hass, mock_plex_calls):
|
||||
"""Test creating via manual configuration with only token."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -683,37 +680,36 @@ async def test_manual_config_with_token(hass):
|
|||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual_setup"
|
||||
|
||||
mock_plex_server = MockPlexServer()
|
||||
|
||||
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch(
|
||||
"plexapi.server.PlexServer", return_value=mock_plex_server
|
||||
), patch(
|
||||
with patch(
|
||||
"homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True)
|
||||
), patch(
|
||||
"homeassistant.components.plex.PlexWebsocket", autospec=True
|
||||
):
|
||||
), patch("homeassistant.components.plex.PlexWebsocket", autospec=True):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN}
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName
|
||||
assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
|
||||
server_id = result["data"][CONF_SERVER_IDENTIFIER]
|
||||
mock_plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
assert result["title"] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name
|
||||
assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use
|
||||
assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
||||
|
||||
|
||||
async def test_setup_with_limited_credentials(hass, entry, setup_plex_server):
|
||||
"""Test setup with a user with limited permissions."""
|
||||
with patch.object(
|
||||
MockPlexServer, "systemAccounts", side_effect=plexapi.exceptions.Unauthorized
|
||||
with patch(
|
||||
"plexapi.server.PlexServer.systemAccounts",
|
||||
side_effect=plexapi.exceptions.Unauthorized,
|
||||
) as mock_accounts:
|
||||
mock_plex_server = await setup_plex_server()
|
||||
|
||||
assert mock_accounts.called
|
||||
|
||||
plex_server = hass.data[DOMAIN][SERVERS][mock_plex_server.machineIdentifier]
|
||||
plex_server = hass.data[DOMAIN][SERVERS][mock_plex_server.machine_identifier]
|
||||
assert len(plex_server.accounts) == 0
|
||||
assert plex_server.owner is None
|
||||
|
||||
|
@ -745,6 +741,7 @@ async def test_integration_discovery(hass):
|
|||
|
||||
async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket):
|
||||
"""Test setup and reauthorization of a Plex token."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"internal_url": "http://example.local:8123"},
|
||||
|
@ -752,8 +749,8 @@ async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket):
|
|||
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
with patch.object(
|
||||
mock_plex_server, "clients", side_effect=plexapi.exceptions.Unauthorized
|
||||
with patch(
|
||||
"plexapi.server.PlexServer.clients", side_effect=plexapi.exceptions.Unauthorized
|
||||
), patch("plexapi.server.PlexServer", side_effect=plexapi.exceptions.Unauthorized):
|
||||
trigger_plex_update(mock_websocket)
|
||||
await wait_for_debouncer(hass)
|
||||
|
@ -767,9 +764,7 @@ async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket):
|
|||
|
||||
flow_id = flows[0]["flow_id"]
|
||||
|
||||
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch(
|
||||
"plexapi.server.PlexServer", return_value=mock_plex_server
|
||||
), patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
with patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN"
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(flow_id, user_input={})
|
||||
|
@ -787,7 +782,7 @@ async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket):
|
|||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
assert entry.data[CONF_SERVER] == mock_plex_server.friendlyName
|
||||
assert entry.data[CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
assert entry.data[PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
assert entry.data[CONF_SERVER] == mock_plex_server.friendly_name
|
||||
assert entry.data[CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier
|
||||
assert entry.data[PLEX_SERVER_CONFIG][CONF_URL] == PLEX_DIRECT_URL
|
||||
assert entry.data[PLEX_SERVER_CONFIG][CONF_TOKEN] == "BRAND_NEW_TOKEN"
|
||||
|
|
|
@ -15,11 +15,11 @@ from homeassistant.config_entries import (
|
|||
ENTRY_STATE_SETUP_RETRY,
|
||||
)
|
||||
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, STATE_IDLE
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import DEFAULT_DATA, DEFAULT_OPTIONS
|
||||
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, PLEX_DIRECT_URL
|
||||
from .helpers import trigger_plex_update, wait_for_debouncer
|
||||
from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
@ -31,7 +31,7 @@ async def test_set_config_entry_unique_id(hass, entry, mock_plex_server):
|
|||
|
||||
assert (
|
||||
hass.config_entries.async_entries(const.DOMAIN)[0].unique_id
|
||||
== mock_plex_server.machineIdentifier
|
||||
== mock_plex_server.machine_identifier
|
||||
)
|
||||
|
||||
|
||||
|
@ -79,9 +79,9 @@ async def test_unload_config_entry(hass, entry, mock_plex_server):
|
|||
assert entry is config_entries[0]
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id]
|
||||
assert loaded_server.plex_server == mock_plex_server
|
||||
assert loaded_server == mock_plex_server
|
||||
|
||||
websocket = hass.data[const.DOMAIN][const.WEBSOCKETS][server_id]
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
|
@ -89,7 +89,7 @@ async def test_unload_config_entry(hass, entry, mock_plex_server):
|
|||
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||
|
||||
|
||||
async def test_setup_with_photo_session(hass, entry, mock_websocket, setup_plex_server):
|
||||
async def test_setup_with_photo_session(hass, entry, setup_plex_server):
|
||||
"""Test setup component with config."""
|
||||
await setup_plex_server(session_type="photo")
|
||||
|
||||
|
@ -97,7 +97,9 @@ async def test_setup_with_photo_session(hass, entry, mock_websocket, setup_plex_
|
|||
assert entry.state == ENTRY_STATE_LOADED
|
||||
await hass.async_block_till_done()
|
||||
|
||||
media_player = hass.states.get("media_player.plex_product_title")
|
||||
media_player = hass.states.get(
|
||||
"media_player.plex_plex_for_android_tv_shield_android_tv"
|
||||
)
|
||||
assert media_player.state == STATE_IDLE
|
||||
|
||||
await wait_for_debouncer(hass)
|
||||
|
@ -106,14 +108,17 @@ async def test_setup_with_photo_session(hass, entry, mock_websocket, setup_plex_
|
|||
assert sensor.state == "0"
|
||||
|
||||
|
||||
async def test_setup_when_certificate_changed(hass, entry):
|
||||
async def test_setup_when_certificate_changed(
|
||||
hass,
|
||||
requests_mock,
|
||||
empty_payload,
|
||||
plex_server_accounts,
|
||||
plex_server_default,
|
||||
plextv_account,
|
||||
plextv_resources,
|
||||
):
|
||||
"""Test setup component when the Plex certificate has changed."""
|
||||
|
||||
old_domain = "1-2-3-4.1234567890abcdef1234567890abcdef.plex.direct"
|
||||
old_url = f"https://{old_domain}:32400"
|
||||
|
||||
OLD_HOSTNAME_DATA = copy.deepcopy(DEFAULT_DATA)
|
||||
OLD_HOSTNAME_DATA[const.PLEX_SERVER_CONFIG][CONF_URL] = old_url
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
class WrongCertHostnameException(requests.exceptions.SSLError):
|
||||
"""Mock the exception showing a mismatched hostname."""
|
||||
|
@ -123,6 +128,12 @@ async def test_setup_when_certificate_changed(hass, entry):
|
|||
f"hostname '{old_domain}' doesn't match"
|
||||
)
|
||||
|
||||
old_domain = "1-2-3-4.1111111111ffffff1111111111ffffff.plex.direct"
|
||||
old_url = f"https://{old_domain}:32400"
|
||||
|
||||
OLD_HOSTNAME_DATA = copy.deepcopy(DEFAULT_DATA)
|
||||
OLD_HOSTNAME_DATA[const.PLEX_SERVER_CONFIG][CONF_URL] = old_url
|
||||
|
||||
old_entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
data=OLD_HOSTNAME_DATA,
|
||||
|
@ -130,46 +141,45 @@ async def test_setup_when_certificate_changed(hass, entry):
|
|||
unique_id=DEFAULT_DATA["server_id"],
|
||||
)
|
||||
|
||||
requests_mock.get("https://plex.tv/users/account", text=plextv_account)
|
||||
requests_mock.get("https://plex.tv/api/resources", text=plextv_resources)
|
||||
requests_mock.get(old_url, exc=WrongCertHostnameException)
|
||||
|
||||
# Test with account failure
|
||||
with patch(
|
||||
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
|
||||
), patch(
|
||||
"plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized
|
||||
):
|
||||
old_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(old_entry.entry_id) is False
|
||||
await hass.async_block_till_done()
|
||||
requests_mock.get(f"{old_url}/accounts", status_code=401)
|
||||
old_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(old_entry.entry_id) is False
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert old_entry.state == ENTRY_STATE_SETUP_ERROR
|
||||
await hass.config_entries.async_unload(old_entry.entry_id)
|
||||
|
||||
# Test with no servers found
|
||||
with patch(
|
||||
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
|
||||
), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0)):
|
||||
assert await hass.config_entries.async_setup(old_entry.entry_id) is False
|
||||
await hass.async_block_till_done()
|
||||
requests_mock.get(f"{old_url}/accounts", text=plex_server_accounts)
|
||||
requests_mock.get("https://plex.tv/api/resources", text=empty_payload)
|
||||
|
||||
assert await hass.config_entries.async_setup(old_entry.entry_id) is False
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert old_entry.state == ENTRY_STATE_SETUP_ERROR
|
||||
await hass.config_entries.async_unload(old_entry.entry_id)
|
||||
|
||||
# Test with success
|
||||
with patch(
|
||||
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
|
||||
), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
|
||||
assert await hass.config_entries.async_setup(old_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
new_url = PLEX_DIRECT_URL
|
||||
requests_mock.get("https://plex.tv/api/resources", text=plextv_resources)
|
||||
requests_mock.get(new_url, text=plex_server_default)
|
||||
requests_mock.get(f"{new_url}/accounts", text=plex_server_accounts)
|
||||
|
||||
assert await hass.config_entries.async_setup(old_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
|
||||
assert old_entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
assert (
|
||||
old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL]
|
||||
== entry.data[const.PLEX_SERVER_CONFIG][CONF_URL]
|
||||
)
|
||||
assert old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] == new_url
|
||||
|
||||
|
||||
async def test_tokenless_server(hass, entry, mock_websocket, setup_plex_server):
|
||||
async def test_tokenless_server(entry, setup_plex_server):
|
||||
"""Test setup with a server with token auth disabled."""
|
||||
TOKENLESS_DATA = copy.deepcopy(DEFAULT_DATA)
|
||||
TOKENLESS_DATA[const.PLEX_SERVER_CONFIG].pop(CONF_TOKEN, None)
|
||||
|
@ -179,18 +189,13 @@ async def test_tokenless_server(hass, entry, mock_websocket, setup_plex_server):
|
|||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
|
||||
async def test_bad_token_with_tokenless_server(hass, entry):
|
||||
async def test_bad_token_with_tokenless_server(
|
||||
hass, entry, mock_websocket, setup_plex_server, requests_mock
|
||||
):
|
||||
"""Test setup with a bad token and a server with token auth disabled."""
|
||||
with patch("plexapi.server.PlexServer", return_value=MockPlexServer()), patch(
|
||||
"plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized
|
||||
), patch(
|
||||
"homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True)
|
||||
), patch(
|
||||
"homeassistant.components.plex.PlexWebsocket", autospec=True
|
||||
) as mock_websocket:
|
||||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
requests_mock.get("https://plex.tv/users/account", status_code=401)
|
||||
|
||||
await setup_plex_server()
|
||||
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
|
|
|
@ -3,39 +3,29 @@ from unittest.mock import patch
|
|||
|
||||
from plexapi.exceptions import NotFound
|
||||
|
||||
from homeassistant.components.plex.const import DOMAIN, SERVERS
|
||||
|
||||
|
||||
async def test_plex_tv_clients(hass, entry, mock_plex_account, setup_plex_server):
|
||||
async def test_plex_tv_clients(
|
||||
hass, entry, setup_plex_server, requests_mock, player_plexweb_resources
|
||||
):
|
||||
"""Test getting Plex clients from plex.tv."""
|
||||
resource = next(
|
||||
x
|
||||
for x in mock_plex_account.resources()
|
||||
if x.name.startswith("plex.tv Resource Player")
|
||||
)
|
||||
with patch.object(resource, "connect", side_effect=NotFound):
|
||||
mock_plex_server = await setup_plex_server()
|
||||
requests_mock.get("/resources", text=player_plexweb_resources)
|
||||
|
||||
with patch("plexapi.myplex.MyPlexResource.connect", side_effect=NotFound):
|
||||
await setup_plex_server()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
media_players_before = len(hass.states.async_entity_ids("media_player"))
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
|
||||
# Ensure one more client is discovered
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
mock_plex_server = await setup_plex_server()
|
||||
plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
await setup_plex_server()
|
||||
media_players_after = len(hass.states.async_entity_ids("media_player"))
|
||||
assert media_players_after == media_players_before + 1
|
||||
|
||||
# Ensure only plex.tv resource client is found
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
mock_plex_server = await setup_plex_server(num_users=0)
|
||||
plex_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
assert len(hass.states.async_entity_ids("media_player")) == 1
|
||||
|
||||
# Ensure cache gets called
|
||||
await plex_server._async_update_platforms()
|
||||
await hass.async_block_till_done()
|
||||
# Ensure only plex.tv resource client is found
|
||||
with patch("plexapi.server.PlexServer.sessions", return_value=[]):
|
||||
await setup_plex_server(disable_clients=True)
|
||||
|
||||
assert len(hass.states.async_entity_ids("media_player")) == 1
|
||||
|
|
|
@ -10,21 +10,25 @@ from homeassistant.components.media_player.const import (
|
|||
)
|
||||
from homeassistant.components.plex.const import (
|
||||
CONF_SERVER,
|
||||
CONF_SERVER_IDENTIFIER,
|
||||
DOMAIN,
|
||||
PLEX_SERVER_CONFIG,
|
||||
SERVERS,
|
||||
SERVICE_PLAY_ON_SONOS,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_URL
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import DEFAULT_OPTIONS, SECONDARY_DATA
|
||||
from .const import DEFAULT_OPTIONS, MOCK_SERVERS, SECONDARY_DATA
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_sonos_playback(hass, mock_plex_server):
|
||||
async def test_sonos_playback(
|
||||
hass, mock_plex_server, requests_mock, playqueue_created, sonos_resources
|
||||
):
|
||||
"""Test playing media on a Sonos speaker."""
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
loaded_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
# Test Sonos integration lookup failure
|
||||
|
@ -43,18 +47,23 @@ async def test_sonos_playback(hass, mock_plex_server):
|
|||
)
|
||||
|
||||
# Test success with plex_key
|
||||
requests_mock.get("https://sonos.plex.tv/resources", text=sonos_resources)
|
||||
requests_mock.get(
|
||||
"https://sonos.plex.tv/player/playback/playMedia", status_code=200
|
||||
)
|
||||
requests_mock.post("/playqueues", text=playqueue_created)
|
||||
with patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="media_player.sonos_kitchen",
|
||||
), patch("plexapi.playqueue.PlayQueue.create"):
|
||||
return_value="Speaker 2",
|
||||
):
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PLAY_ON_SONOS,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.sonos_kitchen",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "2",
|
||||
ATTR_MEDIA_CONTENT_ID: "100",
|
||||
},
|
||||
True,
|
||||
)
|
||||
|
@ -63,8 +72,8 @@ async def test_sonos_playback(hass, mock_plex_server):
|
|||
with patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="media_player.sonos_kitchen",
|
||||
), patch("plexapi.playqueue.PlayQueue.create"):
|
||||
return_value="Speaker 2",
|
||||
):
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PLAY_ON_SONOS,
|
||||
|
@ -80,8 +89,8 @@ async def test_sonos_playback(hass, mock_plex_server):
|
|||
with patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="media_player.sonos_kitchen",
|
||||
), patch.object(mock_plex_server, "fetchItem", side_effect=NotFound):
|
||||
return_value="Speaker 2",
|
||||
), patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound):
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PLAY_ON_SONOS,
|
||||
|
@ -97,7 +106,7 @@ async def test_sonos_playback(hass, mock_plex_server):
|
|||
with patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="media_player.sonos_kitchen",
|
||||
return_value="Speaker 2",
|
||||
):
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
|
@ -116,7 +125,7 @@ async def test_sonos_playback(hass, mock_plex_server):
|
|||
), patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="media_player.sonos_kitchen",
|
||||
return_value="Speaker 2",
|
||||
), patch(
|
||||
"plexapi.playqueue.PlayQueue.create"
|
||||
):
|
||||
|
@ -132,7 +141,17 @@ async def test_sonos_playback(hass, mock_plex_server):
|
|||
)
|
||||
|
||||
|
||||
async def test_playback_multiple_servers(hass, mock_websocket, setup_plex_server):
|
||||
async def test_playback_multiple_servers(
|
||||
hass,
|
||||
setup_plex_server,
|
||||
requests_mock,
|
||||
caplog,
|
||||
empty_payload,
|
||||
playqueue_created,
|
||||
plex_server_accounts,
|
||||
plex_server_base,
|
||||
sonos_resources,
|
||||
):
|
||||
"""Test playing media when multiple servers available."""
|
||||
secondary_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
|
@ -141,21 +160,60 @@ async def test_playback_multiple_servers(hass, mock_websocket, setup_plex_server
|
|||
unique_id=SECONDARY_DATA["server_id"],
|
||||
)
|
||||
|
||||
secondary_url = SECONDARY_DATA[PLEX_SERVER_CONFIG][CONF_URL]
|
||||
secondary_name = SECONDARY_DATA[CONF_SERVER]
|
||||
secondary_id = SECONDARY_DATA[CONF_SERVER_IDENTIFIER]
|
||||
requests_mock.get(
|
||||
secondary_url,
|
||||
text=plex_server_base.format(
|
||||
name=secondary_name, machine_identifier=secondary_id
|
||||
),
|
||||
)
|
||||
requests_mock.get(f"{secondary_url}/accounts", text=plex_server_accounts)
|
||||
requests_mock.get(f"{secondary_url}/clients", text=empty_payload)
|
||||
requests_mock.get(f"{secondary_url}/status/sessions", text=empty_payload)
|
||||
|
||||
await setup_plex_server()
|
||||
await setup_plex_server(config_entry=secondary_entry)
|
||||
|
||||
requests_mock.get("https://sonos.plex.tv/resources", text=sonos_resources)
|
||||
requests_mock.get(
|
||||
"https://sonos.plex.tv/player/playback/playMedia", status_code=200
|
||||
)
|
||||
requests_mock.post("/playqueues", text=playqueue_created)
|
||||
|
||||
with patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="media_player.sonos_kitchen",
|
||||
), patch("plexapi.playqueue.PlayQueue.create"):
|
||||
return_value="Speaker 2",
|
||||
):
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PLAY_ON_SONOS,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.sonos_kitchen",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: f'{{"plex_server": "{SECONDARY_DATA[CONF_SERVER]}", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}}',
|
||||
ATTR_MEDIA_CONTENT_ID: '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}',
|
||||
},
|
||||
True,
|
||||
)
|
||||
|
||||
assert (
|
||||
"Multiple Plex servers configured, choose with 'plex_server' key" in caplog.text
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
hass.components.sonos,
|
||||
"get_coordinator_name",
|
||||
return_value="Speaker 2",
|
||||
):
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PLAY_ON_SONOS,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.sonos_kitchen",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: f'{{"plex_server": "{MOCK_SERVERS[0][CONF_SERVER]}", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}}',
|
||||
},
|
||||
True,
|
||||
)
|
||||
|
|
|
@ -28,29 +28,18 @@ from homeassistant.const import ATTR_ENTITY_ID
|
|||
|
||||
from .const import DEFAULT_DATA, DEFAULT_OPTIONS
|
||||
from .helpers import trigger_plex_update, wait_for_debouncer
|
||||
from .mock_classes import (
|
||||
MockPlexAccount,
|
||||
MockPlexAlbum,
|
||||
MockPlexArtist,
|
||||
MockPlexLibrary,
|
||||
MockPlexLibrarySection,
|
||||
MockPlexMediaItem,
|
||||
MockPlexSeason,
|
||||
MockPlexServer,
|
||||
MockPlexShow,
|
||||
)
|
||||
|
||||
|
||||
async def test_new_users_available(hass, entry, mock_websocket, setup_plex_server):
|
||||
async def test_new_users_available(hass, entry, setup_plex_server):
|
||||
"""Test setting up when new users available on Plex server."""
|
||||
MONITORED_USERS = {"Owner": {"enabled": True}}
|
||||
MONITORED_USERS = {"User 1": {"enabled": True}}
|
||||
OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS)
|
||||
OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS
|
||||
entry.options = OPTIONS_WITH_USERS
|
||||
|
||||
mock_plex_server = await setup_plex_server(config_entry=entry)
|
||||
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
|
||||
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
|
||||
|
||||
|
@ -58,17 +47,18 @@ async def test_new_users_available(hass, entry, mock_websocket, setup_plex_serve
|
|||
assert len(monitored_users) == 1
|
||||
assert len(ignored_users) == 0
|
||||
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
|
||||
|
||||
async def test_new_ignored_users_available(
|
||||
hass, caplog, entry, mock_websocket, setup_plex_server
|
||||
hass,
|
||||
caplog,
|
||||
entry,
|
||||
mock_websocket,
|
||||
setup_plex_server,
|
||||
requests_mock,
|
||||
session_new_user,
|
||||
):
|
||||
"""Test setting up when new users available on Plex server but are ignored."""
|
||||
MONITORED_USERS = {"Owner": {"enabled": True}}
|
||||
MONITORED_USERS = {"User 1": {"enabled": True}}
|
||||
OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS)
|
||||
OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS
|
||||
OPTIONS_WITH_USERS[MP_DOMAIN][CONF_IGNORE_NEW_SHARED_USERS] = True
|
||||
|
@ -76,43 +66,50 @@ async def test_new_ignored_users_available(
|
|||
|
||||
mock_plex_server = await setup_plex_server(config_entry=entry)
|
||||
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
requests_mock.get(
|
||||
f"{mock_plex_server.url_in_use}/status/sessions",
|
||||
text=session_new_user,
|
||||
)
|
||||
trigger_plex_update(mock_websocket)
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
|
||||
active_sessions = mock_plex_server._plex_server.sessions()
|
||||
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
|
||||
|
||||
ignored_users = [x for x in mock_plex_server.accounts if x not in monitored_users]
|
||||
|
||||
assert len(monitored_users) == 1
|
||||
assert len(ignored_users) == 2
|
||||
|
||||
for ignored_user in ignored_users:
|
||||
ignored_client = [
|
||||
x.players[0]
|
||||
for x in mock_plex_server.sessions()
|
||||
if x.usernames[0] == ignored_user
|
||||
][0]
|
||||
assert (
|
||||
f"Ignoring {ignored_client.product} client owned by '{ignored_user}'"
|
||||
in caplog.text
|
||||
)
|
||||
x.players[0] for x in active_sessions if x.usernames[0] == ignored_user
|
||||
]
|
||||
if ignored_client:
|
||||
assert (
|
||||
f"Ignoring {ignored_client[0].product} client owned by '{ignored_user}'"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
assert sensor.state == str(len(active_sessions))
|
||||
|
||||
|
||||
async def test_network_error_during_refresh(
|
||||
hass, caplog, mock_plex_server, mock_websocket
|
||||
):
|
||||
async def test_network_error_during_refresh(hass, caplog, mock_plex_server):
|
||||
"""Test network failures during refreshes."""
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
loaded_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
active_sessions = mock_plex_server._plex_server.sessions()
|
||||
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
assert sensor.state == str(len(active_sessions))
|
||||
|
||||
with patch.object(mock_plex_server, "clients", side_effect=RequestException):
|
||||
with patch("plexapi.server.PlexServer.clients", side_effect=RequestException):
|
||||
await loaded_server._async_update_platforms()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -129,25 +126,31 @@ async def test_gdm_client_failure(hass, mock_websocket, setup_plex_server):
|
|||
mock_plex_server = await setup_plex_server(disable_gdm=False)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
active_sessions = mock_plex_server._plex_server.sessions()
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
assert sensor.state == str(len(active_sessions))
|
||||
|
||||
with patch.object(mock_plex_server, "clients", side_effect=RequestException):
|
||||
with patch("plexapi.server.PlexServer.clients", side_effect=RequestException):
|
||||
trigger_plex_update(mock_websocket)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_mark_sessions_idle(hass, mock_plex_server, mock_websocket):
|
||||
async def test_mark_sessions_idle(
|
||||
hass, mock_plex_server, mock_websocket, requests_mock, empty_payload
|
||||
):
|
||||
"""Test marking media_players as idle when sessions end."""
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
active_sessions = mock_plex_server._plex_server.sessions()
|
||||
|
||||
mock_plex_server.clear_clients()
|
||||
mock_plex_server.clear_sessions()
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(active_sessions))
|
||||
|
||||
url = mock_plex_server.url_in_use
|
||||
requests_mock.get(f"{url}/clients", text=empty_payload)
|
||||
requests_mock.get(f"{url}/status/sessions", text=empty_payload)
|
||||
|
||||
trigger_plex_update(mock_websocket)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -157,43 +160,46 @@ async def test_mark_sessions_idle(hass, mock_plex_server, mock_websocket):
|
|||
assert sensor.state == "0"
|
||||
|
||||
|
||||
async def test_ignore_plex_web_client(hass, entry, mock_websocket, setup_plex_server):
|
||||
async def test_ignore_plex_web_client(hass, entry, setup_plex_server):
|
||||
"""Test option to ignore Plex Web clients."""
|
||||
OPTIONS = copy.deepcopy(DEFAULT_OPTIONS)
|
||||
OPTIONS[MP_DOMAIN][CONF_IGNORE_PLEX_WEB_CLIENTS] = True
|
||||
entry.options = OPTIONS
|
||||
|
||||
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0)):
|
||||
mock_plex_server = await setup_plex_server(config_entry=entry)
|
||||
await wait_for_debouncer(hass)
|
||||
mock_plex_server = await setup_plex_server(
|
||||
config_entry=entry, client_type="plexweb", disable_clients=True
|
||||
)
|
||||
await wait_for_debouncer(hass)
|
||||
|
||||
active_sessions = mock_plex_server._plex_server.sessions()
|
||||
sensor = hass.states.get("sensor.plex_plex_server_1")
|
||||
assert sensor.state == str(len(mock_plex_server.accounts))
|
||||
assert sensor.state == str(len(active_sessions))
|
||||
|
||||
media_players = hass.states.async_entity_ids("media_player")
|
||||
|
||||
assert len(media_players) == int(sensor.state) - 1
|
||||
|
||||
|
||||
async def test_media_lookups(hass, mock_plex_server, mock_websocket):
|
||||
async def test_media_lookups(hass, mock_plex_server, requests_mock, playqueue_created):
|
||||
"""Test media lookups to Plex server."""
|
||||
server_id = mock_plex_server.machineIdentifier
|
||||
server_id = mock_plex_server.machine_identifier
|
||||
loaded_server = hass.data[DOMAIN][SERVERS][server_id]
|
||||
|
||||
# Plex Key searches
|
||||
media_player_id = hass.states.async_entity_ids("media_player")[0]
|
||||
with patch("homeassistant.components.plex.PlexServer.create_playqueue"):
|
||||
assert await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: media_player_id,
|
||||
ATTR_MEDIA_CONTENT_TYPE: DOMAIN,
|
||||
ATTR_MEDIA_CONTENT_ID: 123,
|
||||
},
|
||||
True,
|
||||
)
|
||||
with patch.object(MockPlexServer, "fetchItem", side_effect=NotFound):
|
||||
requests_mock.post("/playqueues", text=playqueue_created)
|
||||
requests_mock.get("/player/playback/playMedia", status_code=200)
|
||||
assert await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: media_player_id,
|
||||
ATTR_MEDIA_CONTENT_TYPE: DOMAIN,
|
||||
ATTR_MEDIA_CONTENT_ID: 1,
|
||||
},
|
||||
True,
|
||||
)
|
||||
with patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound):
|
||||
assert await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
|
@ -206,20 +212,18 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket):
|
|||
)
|
||||
|
||||
# TV show searches
|
||||
with patch.object(MockPlexLibrary, "section", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE, library_name="Not a Library", show_name="TV Show"
|
||||
)
|
||||
is None
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE, library_name="Not a Library", show_name="TV Show"
|
||||
)
|
||||
with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="Not a TV Show"
|
||||
)
|
||||
is None
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="Not a TV Show"
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE, library_name="TV Shows", episode_name="An Episode"
|
||||
|
@ -233,36 +237,34 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket):
|
|||
MEDIA_TYPE_EPISODE,
|
||||
library_name="TV Shows",
|
||||
show_name="TV Show",
|
||||
season_number=2,
|
||||
season_number=1,
|
||||
)
|
||||
assert loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE,
|
||||
library_name="TV Shows",
|
||||
show_name="TV Show",
|
||||
season_number=2,
|
||||
season_number=1,
|
||||
episode_number=3,
|
||||
)
|
||||
with patch.object(MockPlexShow, "season", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE,
|
||||
library_name="TV Shows",
|
||||
show_name="TV Show",
|
||||
season_number=2,
|
||||
)
|
||||
is None
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE,
|
||||
library_name="TV Shows",
|
||||
show_name="TV Show",
|
||||
season_number=2,
|
||||
)
|
||||
with patch.object(MockPlexSeason, "episode", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE,
|
||||
library_name="TV Shows",
|
||||
show_name="TV Show",
|
||||
season_number=2,
|
||||
episode_number=1,
|
||||
)
|
||||
is None
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_EPISODE,
|
||||
library_name="TV Shows",
|
||||
show_name="TV Show",
|
||||
season_number=2,
|
||||
episode_number=1,
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
# Music searches
|
||||
assert (
|
||||
|
@ -286,47 +288,43 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket):
|
|||
artist_name="Artist",
|
||||
album_name="Album",
|
||||
)
|
||||
with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Not an Artist",
|
||||
album_name="Album",
|
||||
)
|
||||
is None
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Not an Artist",
|
||||
album_name="Album",
|
||||
)
|
||||
with patch.object(MockPlexArtist, "album", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Artist",
|
||||
album_name="Not an Album",
|
||||
)
|
||||
is None
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Artist",
|
||||
album_name="Not an Album",
|
||||
)
|
||||
with patch.object(MockPlexAlbum, "track", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Artist",
|
||||
album_name=" Album",
|
||||
track_name="Not a Track",
|
||||
)
|
||||
is None
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Artist",
|
||||
album_name=" Album",
|
||||
track_name="Not a Track",
|
||||
)
|
||||
with patch.object(MockPlexArtist, "get", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Artist",
|
||||
track_name="Not a Track",
|
||||
)
|
||||
is None
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
artist_name="Artist",
|
||||
track_name="Not a Track",
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MUSIC,
|
||||
library_name="Music",
|
||||
|
@ -353,44 +351,33 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket):
|
|||
)
|
||||
|
||||
# Playlist searches
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="A Playlist")
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="Playlist 1")
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST) is None
|
||||
with patch.object(MockPlexServer, "playlist", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_PLAYLIST, playlist_name="Not a Playlist"
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert (
|
||||
loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="Not a Playlist")
|
||||
is None
|
||||
)
|
||||
|
||||
# Legacy Movie searches
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, video_name="Movie") is None
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, library_name="Movies") is None
|
||||
assert loaded_server.lookup_media(
|
||||
MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Movie"
|
||||
MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Movie 1"
|
||||
)
|
||||
with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Not a Movie"
|
||||
)
|
||||
is None
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Not a Movie"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
# Movie searches
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_MOVIE, title="Movie") is None
|
||||
assert loaded_server.lookup_media(MEDIA_TYPE_MOVIE, library_name="Movies") is None
|
||||
assert loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Movie"
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Movie 1"
|
||||
)
|
||||
with patch.object(MockPlexLibrarySection, "search", side_effect=BadRequest):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Not a Movie"
|
||||
)
|
||||
is None
|
||||
)
|
||||
with patch.object(MockPlexLibrarySection, "search", return_value=[]):
|
||||
with patch("plexapi.library.LibrarySection.search", side_effect=BadRequest):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Not a Movie"
|
||||
|
@ -398,25 +385,8 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket):
|
|||
is None
|
||||
)
|
||||
|
||||
similar_movies = []
|
||||
for title in "Duplicate Movie", "Duplicate Movie 2":
|
||||
similar_movies.append(MockPlexMediaItem(title))
|
||||
with patch.object(
|
||||
loaded_server.library.section("Movies"), "search", return_value=similar_movies
|
||||
):
|
||||
found_media = loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Duplicate Movie"
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Movie"
|
||||
)
|
||||
assert found_media.title == "Duplicate Movie"
|
||||
|
||||
duplicate_movies = []
|
||||
for title in "Duplicate Movie - Original", "Duplicate Movie - Remake":
|
||||
duplicate_movies.append(MockPlexMediaItem(title))
|
||||
with patch.object(
|
||||
loaded_server.library.section("Movies"), "search", return_value=duplicate_movies
|
||||
):
|
||||
assert (
|
||||
loaded_server.lookup_media(
|
||||
MEDIA_TYPE_MOVIE, library_name="Movies", title="Duplicate Movie"
|
||||
)
|
||||
) is None
|
||||
) is None
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
"""Tests for various Plex services."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.plex.const import (
|
||||
CONF_SERVER,
|
||||
CONF_SERVER_IDENTIFIER,
|
||||
|
@ -9,77 +7,84 @@ from homeassistant.components.plex.const import (
|
|||
SERVICE_REFRESH_LIBRARY,
|
||||
SERVICE_SCAN_CLIENTS,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
CONF_TOKEN,
|
||||
CONF_URL,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.const import CONF_URL
|
||||
|
||||
from .const import MOCK_SERVERS, MOCK_TOKEN
|
||||
from .mock_classes import MockPlexLibrarySection
|
||||
from .const import DEFAULT_OPTIONS, SECONDARY_DATA
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_refresh_library(hass, mock_plex_server, setup_plex_server):
|
||||
async def test_refresh_library(
|
||||
hass,
|
||||
mock_plex_server,
|
||||
setup_plex_server,
|
||||
requests_mock,
|
||||
empty_payload,
|
||||
plex_server_accounts,
|
||||
plex_server_base,
|
||||
):
|
||||
"""Test refresh_library service call."""
|
||||
url = mock_plex_server.url_in_use
|
||||
refresh = requests_mock.get(f"{url}/library/sections/1/refresh", status_code=200)
|
||||
|
||||
# Test with non-existent server
|
||||
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"server_name": "Not a Server", "library_name": "Movies"},
|
||||
True,
|
||||
)
|
||||
assert not mock_update.called
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"server_name": "Not a Server", "library_name": "Movies"},
|
||||
True,
|
||||
)
|
||||
assert not refresh.called
|
||||
|
||||
# Test with non-existent library
|
||||
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"library_name": "Not a Library"},
|
||||
True,
|
||||
)
|
||||
assert not mock_update.called
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"library_name": "Not a Library"},
|
||||
True,
|
||||
)
|
||||
assert not refresh.called
|
||||
|
||||
# Test with valid library
|
||||
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"library_name": "Movies"},
|
||||
True,
|
||||
)
|
||||
assert mock_update.called
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"library_name": "Movies"},
|
||||
True,
|
||||
)
|
||||
assert refresh.call_count == 1
|
||||
|
||||
# Add a second configured server
|
||||
secondary_url = SECONDARY_DATA[PLEX_SERVER_CONFIG][CONF_URL]
|
||||
secondary_name = SECONDARY_DATA[CONF_SERVER]
|
||||
secondary_id = SECONDARY_DATA[CONF_SERVER_IDENTIFIER]
|
||||
requests_mock.get(
|
||||
secondary_url,
|
||||
text=plex_server_base.format(
|
||||
name=secondary_name, machine_identifier=secondary_id
|
||||
),
|
||||
)
|
||||
requests_mock.get(f"{secondary_url}/accounts", text=plex_server_accounts)
|
||||
requests_mock.get(f"{secondary_url}/clients", text=empty_payload)
|
||||
requests_mock.get(f"{secondary_url}/status/sessions", text=empty_payload)
|
||||
|
||||
entry_2 = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_SERVER: MOCK_SERVERS[1][CONF_SERVER],
|
||||
PLEX_SERVER_CONFIG: {
|
||||
CONF_TOKEN: MOCK_TOKEN,
|
||||
CONF_URL: f"https://{MOCK_SERVERS[1][CONF_HOST]}:{MOCK_SERVERS[1][CONF_PORT]}",
|
||||
CONF_VERIFY_SSL: True,
|
||||
},
|
||||
CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][CONF_SERVER_IDENTIFIER],
|
||||
},
|
||||
data=SECONDARY_DATA,
|
||||
options=DEFAULT_OPTIONS,
|
||||
unique_id=SECONDARY_DATA["server_id"],
|
||||
)
|
||||
|
||||
await setup_plex_server(config_entry=entry_2)
|
||||
|
||||
# Test multiple servers available but none specified
|
||||
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"library_name": "Movies"},
|
||||
True,
|
||||
)
|
||||
assert not mock_update.called
|
||||
assert await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_LIBRARY,
|
||||
{"library_name": "Movies"},
|
||||
True,
|
||||
)
|
||||
assert refresh.call_count == 1
|
||||
|
||||
|
||||
async def test_scan_clients(hass, mock_plex_server):
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<MediaContainer size="1" allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="ba0c2140-c6ef-448a-9d1b-31020741d014" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053">
|
||||
<Directory ratingKey="200" key="/library/metadata/200/children" parentRatingKey="300" guid="plex://album/12345" parentGuid="plex://artist/12345" studio="Warp" type="album" title="Album" parentKey="/library/metadata/300" librarySectionTitle="Music" librarySectionID="3" librarySectionKey="/library/sections/5" parentTitle="Artist" summary="" index="1" viewCount="5" lastViewedAt="1605456703" year="2019" thumb="/library/metadata/200/thumb/1602534481" art="/library/metadata/300/art/1595543202" parentThumb="/library/metadata/300/thumb/1595543202" originallyAvailableAt="2019-01-01" leafCount="9" viewedLeafCount="2" addedAt="1602534474" updatedAt="1602534481" loudnessAnalysisVersion="2">
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,4 @@
|
|||
<MediaContainer size="1" allowSync="1" art="/library/metadata/300/art/1595543202" identifier="com.plexapp.plugins.library" key="300" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="ba0c2140-c6ef-448a-9d1b-31020741d014" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" parentIndex="1" parentTitle="Artist" summary="Artist summary." thumb="/library/metadata/300/thumb/1595543202" title1="Music" title2="Artist" viewGroup="album" viewMode="65592">
|
||||
<Directory ratingKey="200" key="/library/metadata/200/children" parentRatingKey="300" guid="plex://album/12345" parentGuid="plex://artist/12345" studio="Studio" type="album" title="Album" parentKey="/library/metadata/300" parentTitle="Artist" summary="" index="1" viewCount="5" lastViewedAt="1605456703" year="2019" thumb="/library/metadata/200/thumb/1602534481" art="/library/metadata/300/art/1595543202" parentThumb="/library/metadata/300/thumb/1595543202" originallyAvailableAt="2019-01-01" addedAt="1602534474" updatedAt="1602534481" loudnessAnalysisVersion="2">
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,11 @@
|
|||
<MediaContainer allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="2" librarySectionTitle="TV Shows" librarySectionUUID="905308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" size="10"><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/10?lang=en" index="1" key="/library/metadata/10" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="10" summary="Elaborate Summary." thumb="/library/metadata/10/thumb/1590245886" title="Episode 1" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/11?lang=en" index="2" key="/library/metadata/11" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="11" summary="Elaborate Summary." thumb="/library/metadata/11/thumb/1590245886" title="Episode 2" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/12?lang=en" index="3" key="/library/metadata/12" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="12" summary="Elaborate Summary." thumb="/library/metadata/12/thumb/1590245886" title="Episode 3" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/13?lang=en" index="4" key="/library/metadata/13" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="13" summary="Elaborate Summary." thumb="/library/metadata/13/thumb/1590245886" title="Episode 4" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/14?lang=en" index="5" key="/library/metadata/14" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="14" summary="Elaborate Summary." thumb="/library/metadata/14/thumb/1590245886" title="Episode 5" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/15?lang=en" index="6" key="/library/metadata/15" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="15" summary="Elaborate Summary." thumb="/library/metadata/15/thumb/1590245886" title="Episode 6" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/16?lang=en" index="7" key="/library/metadata/16" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="16" summary="Elaborate Summary." thumb="/library/metadata/16/thumb/1590245886" title="Episode 7" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/17?lang=en" index="8" key="/library/metadata/17" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="17" summary="Elaborate Summary." thumb="/library/metadata/17/thumb/1590245886" title="Episode 8" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/18?lang=en" index="9" key="/library/metadata/18" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="18" summary="Elaborate Summary." thumb="/library/metadata/18/thumb/1590245886" title="Episode 9" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video><Video addedAt="1408989944" art="/library/metadata/30/art/1441479050" contentRating="TV-Y" duration="1419520" grandparentArt="/library/metadata/30/art/1441479050" grandparentGuid="com.plexapp.agents.thetvdb://54321?lang=en" grandparentKey="/library/metadata/30" grandparentRatingKey="30" grandparentTheme="/library/metadata/30/theme/1441479050" grandparentThumb="/library/metadata/30/thumb/1441479050" grandparentTitle="TV Show" guid="com.plexapp.agents.thetvdb://12345/1/19?lang=en" index="10" key="/library/metadata/19" lastViewedAt="1438105107" librarySectionID="2" librarySectionKey="/library/sections/2" librarySectionTitle="TV Shows" originallyAvailableAt="2000-01-01" parentGuid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentIndex="1" parentKey="/library/metadata/20" parentRatingKey="20" parentThumb="/library/metadata/20/thumb/1441479050" parentTitle="Season 1" ratingKey="19" summary="Elaborate Summary." thumb="/library/metadata/19/thumb/1590245886" title="Episode 10" type="episode" updatedAt="1590245886" viewCount="14" year="2000">
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video></MediaContainer>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,4 @@
|
|||
<MediaContainer size="1" allowSync="1" art="/library/metadata/30/art/1488495294" banner="/library/metadata/30/banner/1488495294" identifier="com.plexapp.plugins.library" key="30" librarySectionID="2" librarySectionTitle="TV Shows" librarySectionUUID="1d8c8690-2dc5-48e6-9b54-accfacd0067c" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" parentIndex="1" parentTitle="TV Show" parentYear="2000" sortAsc="1" summary="Show summary." theme="/library/metadata/30/theme/1488495294" thumb="/library/metadata/30/thumb/1488495294" title1="TV Shows" title2="TV Show" viewGroup="season" viewMode="458810">
|
||||
<Directory ratingKey="20" key="/library/metadata/20/children" parentRatingKey="30" guid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentGuid="com.plexapp.agents.thetvdb://12345?lang=en" type="season" title="Season 1" parentKey="/library/metadata/30" parentTitle="TV Show" summary="" index="1" parentIndex="1" viewCount="20" lastViewedAt="1524197296" thumb="/library/metadata/20/thumb/1488495294" art="/library/metadata/30/art/1488495294" parentThumb="/library/metadata/30/thumb/1488495294" parentTheme="/library/metadata/30/theme/1488495294" leafCount="14" viewedLeafCount="14" addedAt="1377827368" updatedAt="1488495294">
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,4 @@
|
|||
<MediaContainer size="1" allowSync="1" art="/library/metadata/300/art/1595543202" identifier="com.plexapp.plugins.library" key="300" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="ba0c2140-c6ef-448a-9d1b-31020741d014" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" parentIndex="1" parentTitle="Artist" summary="Artist summary." thumb="/library/metadata/300/thumb/1595543202" title1="Music" title2="Artist" viewGroup="album" viewMode="65592">
|
||||
<Directory ratingKey="200" key="/library/metadata/200/children" parentRatingKey="300" guid="plex://album/12345" parentGuid="plex://artist/12345" studio="Studio" type="album" title="Album" parentKey="/library/metadata/300" parentTitle="Artist" summary="" index="1" viewCount="5" lastViewedAt="1605456703" year="2019" thumb="/library/metadata/200/thumb/1602534481" art="/library/metadata/300/art/1595543202" parentThumb="/library/metadata/300/thumb/1595543202" originallyAvailableAt="2019-01-01" addedAt="1602534474" updatedAt="1602534481" loudnessAnalysisVersion="2">
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1 @@
|
|||
<MediaContainer identifier="com.plexapp.plugins.library" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" size="0" />
|
|
@ -0,0 +1 @@
|
|||
<MediaContainer size="0" />
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,5 @@
|
|||
<MediaContainer size="3" allowSync="0" art="/:/resources/library-art.png" content="" identifier="com.plexapp.plugins.library" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" title1="Plex Library" title2="">
|
||||
<Directory key="sections" title="Library Sections" />
|
||||
<Directory key="recentlyAdded" title="Recently Added Content" />
|
||||
<Directory key="onDeck" title="On Deck Content" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,51 @@
|
|||
<MediaContainer allowSync="1" art="/:/resources/movie-fanart.jpg" identifier="com.plexapp.plugins.library" librarySectionID="1" librarySectionTitle="Movies" librarySectionUUID="805308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" size="5" sortAsc="1" thumb="/:/resources/movie.png" title="Movies" title1="All Movies" viewGroup="movie" viewMode="131122"><Video addedAt="1377829261" art="/library/metadata/1/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/1" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="1" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/1/thumb/1590245989" title="Movie 1" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
</Video><Video addedAt="1377829261" art="/library/metadata/2/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/2" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="2" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/2/thumb/1590245989" title="Movie 2" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
</Video><Video addedAt="1377829261" art="/library/metadata/3/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/3" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="3" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/3/thumb/1590245989" title="Movie 3" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
</Video><Video addedAt="1377829261" art="/library/metadata/4/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/4" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="4" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/4/thumb/1590245989" title="Movie 4" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
</Video><Video addedAt="1377829261" art="/library/metadata/5/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/5" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="5" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/5/thumb/1590245989" title="Movie 5" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
</Video></MediaContainer>
|
|
@ -0,0 +1,10 @@
|
|||
<MediaContainer size="8" allowSync="0" art="/:/resources/movie-fanart.jpg" content="secondary" identifier="com.plexapp.plugins.library" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" thumb="/:/resources/video.png" title1="Movies" viewGroup="secondary" viewMode="65592">
|
||||
<Directory default="asc" defaultDirection="asc" descKey="titleSort:desc" firstCharacterKey="/library/sections/1/firstCharacter" key="titleSort" title="Title" />
|
||||
<Directory defaultDirection="desc" descKey="originallyAvailableAt:desc" key="originallyAvailableAt" title="Release Date" />
|
||||
<Directory defaultDirection="desc" descKey="rating:desc" key="rating" title="Critic Rating" />
|
||||
<Directory defaultDirection="desc" descKey="audienceRating:desc" key="audienceRating" title="Audience Rating" />
|
||||
<Directory defaultDirection="desc" descKey="duration:desc" key="duration" title="Duration" />
|
||||
<Directory defaultDirection="desc" descKey="addedAt:desc" key="addedAt" title="Date Added" />
|
||||
<Directory defaultDirection="desc" descKey="lastViewedAt:desc" key="lastViewedAt" title="Date Viewed" />
|
||||
<Directory defaultDirection="asc" descKey="mediaHeight:desc" key="mediaHeight" title="Resolution" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,6 @@
|
|||
<MediaContainer size="1" allowSync="1" art="/:/resources/artist-fanart.jpg" identifier="com.plexapp.plugins.library" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="ba0c2140-c6ef-448a-9d1b-31020741d014" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" sortAsc="1" thumb="/:/resources/artist.png" title1="Music" title2="All Artists" viewGroup="artist" viewMode="131124">
|
||||
<Directory ratingKey="300" key="/library/metadata/300/children" guid="plex://artist/12345" type="artist" title="Artist" summary="Artist summary." index="1" viewCount="64" lastViewedAt="1605456703" thumb="/library/metadata/300/thumb/1595543202" art="/library/metadata/300/art/1595543202" addedAt="1595543193" updatedAt="1595543202">
|
||||
<Genre tag="Electronic" />
|
||||
<Country tag="United Kingdom" />
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,7 @@
|
|||
<MediaContainer size="5" allowSync="0" art="/:/resources/artist-fanart.jpg" content="secondary" identifier="com.plexapp.plugins.library" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" thumb="/:/resources/artist.png" title1="Music" viewGroup="secondary" viewMode="65592">
|
||||
<Directory default="asc" defaultDirection="asc" descKey="titleSort:desc" firstCharacterKey="/library/sections/3/firstCharacter" key="titleSort" title="Title" />
|
||||
<Directory defaultDirection="desc" descKey="userRating:desc" key="userRating" title="Rating" />
|
||||
<Directory defaultDirection="desc" descKey="addedAt:desc" key="addedAt" title="Date Added" />
|
||||
<Directory defaultDirection="desc" descKey="lastViewedAt:desc" key="lastViewedAt" title="Date Played" />
|
||||
<Directory defaultDirection="desc" descKey="viewCount:desc" key="viewCount" title="Plays" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,11 @@
|
|||
<MediaContainer size="3" allowSync="0" identifier="com.plexapp.plugins.library" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" title1="Plex Library">
|
||||
<Directory allowSync="1" art="/:/resources/movie-fanart.jpg" composite="/library/sections/1/composite/1605409122" filters="1" refreshing="0" thumb="/:/resources/movie.png" key="1" type="movie" title="Movies" agent="com.plexapp.agents.imdb" scanner="Plex Movie Scanner" language="en" uuid="41a28495-035e-46b0-ac84-878f096614da" updatedAt="1602461679" createdAt="1429510140" scannedAt="1605409122" content="1" directory="1" contentChangedAt="116893155" hidden="0">
|
||||
<Location id="1" path="/storage/movies" />
|
||||
</Directory>
|
||||
<Directory allowSync="1" art="/:/resources/show-fanart.jpg" composite="/library/sections/2/composite/1605461424" filters="1" refreshing="0" thumb="/:/resources/show.png" key="2" type="show" title="TV Shows" agent="com.plexapp.agents.thetvdb" scanner="Plex Series Scanner" language="en" uuid="80208576-f7d1-406d-b6d8-aa96a5362131" updatedAt="1602523323" createdAt="1429510140" scannedAt="1605461424" content="1" directory="1" contentChangedAt="117133678" hidden="0">
|
||||
<Location id="2" path="/storage/tvshows" />
|
||||
</Directory>
|
||||
<Directory allowSync="1" art="/:/resources/artist-fanart.jpg" composite="/library/sections/3/composite/1605413685" filters="1" refreshing="0" thumb="/:/resources/artist.png" key="3" type="artist" title="Music" agent="tv.plex.agents.music" scanner="Plex Music" language="en" uuid="1eeace5d-4839-45e8-90b0-8d03b3375744" updatedAt="1602211102" createdAt="1430432959" scannedAt="1605413685" content="1" directory="1" contentChangedAt="116260421" hidden="1">
|
||||
<Location id="3" path="/storage/music" />
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,6 @@
|
|||
<MediaContainer allowSync="1" art="/:/resources/show-fanart.jpg" identifier="com.plexapp.plugins.library" librarySectionID="2" librarySectionTitle="TV Shows" librarySectionUUID="905308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" size="1" sortAsc="1" thumb="/:/resources/show.png" title="TV Shows" title1="All Shows" viewGroup="show" viewMode="131122"><Directory addedAt="1377827407" art="/library/metadata/30/art/1488495292" banner="/library/metadata/30/banner/1488495292" childCount="5" contentRating="TV-Y" duration="3000000" guid="com.plexapp.agents.thetvdb://12345?lang=en" index="1" key="/library/metadata/30/children" leafCount="100" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/194407" rating="9.0" ratingKey="30" studio="TV Studio" summary="Elaborate summary." theme="/library/metadata/30/theme/1488495292" thumb="/library/metadata/30/thumb/1488495292" title="TV Show" type="show" updatedAt="1488495292" viewedLeafCount="0" year="2000">
|
||||
<Genre tag="Action" />
|
||||
<Genre tag="Animated" />
|
||||
<Role tag="Some Actor" />
|
||||
<Role tag="Another One" />
|
||||
</Directory></MediaContainer>
|
|
@ -0,0 +1,8 @@
|
|||
<MediaContainer size="6" allowSync="0" art="/:/resources/show-fanart.jpg" content="secondary" identifier="com.plexapp.plugins.library" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" thumb="/:/resources/show.png" title1="TV Shows" viewGroup="secondary" viewMode="65592">
|
||||
<Directory default="asc" defaultDirection="asc" descKey="titleSort:desc" firstCharacterKey="/library/sections/2/firstCharacter" key="titleSort" title="Title" />
|
||||
<Directory defaultDirection="desc" descKey="originallyAvailableAt:desc" key="originallyAvailableAt" title="Release Date" />
|
||||
<Directory defaultDirection="desc" descKey="rating:desc" key="rating" title="Critic Rating" />
|
||||
<Directory defaultDirection="desc" descKey="unviewedLeafCount:desc" key="unviewedLeafCount" title="Unplayed" />
|
||||
<Directory defaultDirection="desc" descKey="episode.addedAt:desc" key="episode.addedAt" title="Last Episode Date Added" />
|
||||
<Directory defaultDirection="desc" descKey="lastViewedAt:desc" key="lastViewedAt" title="Date Viewed" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,11 @@
|
|||
<MediaContainer allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="1" librarySectionTitle="Movies" librarySectionUUID="805308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" size="1"><Video addedAt="1377829261" art="/library/metadata/1/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/1" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="1" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/1/thumb/1590245989" title="Movie 1" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video></MediaContainer>
|
|
@ -0,0 +1 @@
|
|||
<MediaContainer allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="005308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" size="1"><Track addedAt="1600999261" art="/library/metadata/300/art/1605462131" duration="250000" grandparentArt="/library/metadata/300/art/1605462131" grandparentGuid="plex://artist/12345" grandparentKey="/library/metadata/300" grandparentRatingKey="300" grandparentThumb="/library/metadata/300/thumb/1605462131" grandparentTitle="Arist Name" guid="plex://track/12345" index="1" key="/library/metadata/100" lastViewedAt="1603309346" librarySectionID="3" librarySectionKey="/library/sections/3" librarySectionTitle="Music" parentGuid="plex://album/12345" parentIndex="1" parentKey="/library/metadata/200" parentRatingKey="200" parentThumb="/library/metadata/200/thumb/1605462119" parentTitle="Album Title" ratingKey="100" summary="" thumb="/library/metadata/200/thumb/1605462119" title="Track 1" type="track" updatedAt="1605462119" viewCount="1"><Media audioChannels="2" audioCodec="mp3" bitrate="256" container="mp3" duration="250000" id="381515" /><Part container="mp3" duration="250000" file="/storage/music/Artist Name/Album Name/Track Name.mp3" id="381939" key="/library/parts/381939/1602996958/file.mp3" size="5000000" /><Stream albumGain="-10.34" albumPeak="1.000000" albumRange="8.429853" audioChannelLayout="stereo" bitrate="256" channels="2" codec="mp3" displayTitle="Unknown (MP3 Stereo)" extendedDisplayTitle="Unknown (MP3 Stereo)" gain="-10.34" id="766687" index="0" loudness="-11.38" lra="7.80" peak="0.870300" samplingRate="44100" selected="1" streamType="2" /></Track></MediaContainer>
|
|
@ -0,0 +1,4 @@
|
|||
<MediaContainer size="1" allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="ba0c2140-c6ef-448a-9d1b-31020741d014" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053">
|
||||
<Directory ratingKey="200" key="/library/metadata/200/children" parentRatingKey="300" guid="plex://album/12345" parentGuid="plex://artist/12345" studio="Warp" type="album" title="Album" parentKey="/library/metadata/300" librarySectionTitle="Music" librarySectionID="3" librarySectionKey="/library/sections/5" parentTitle="Artist" summary="" index="1" viewCount="5" lastViewedAt="1605456703" year="2019" thumb="/library/metadata/200/thumb/1602534481" art="/library/metadata/300/art/1595543202" parentThumb="/library/metadata/300/thumb/1595543202" originallyAvailableAt="2019-01-01" leafCount="9" viewedLeafCount="2" addedAt="1602534474" updatedAt="1602534481" loudnessAnalysisVersion="2">
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,6 @@
|
|||
<MediaContainer allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="2" librarySectionTitle="TV Shows" librarySectionUUID="905308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" size="1"><Directory addedAt="1377827407" art="/library/metadata/30/art/1488495292" banner="/library/metadata/30/banner/1488495292" childCount="5" contentRating="TV-Y" duration="3000000" guid="com.plexapp.agents.thetvdb://12345?lang=en" index="1" key="/library/metadata/30/children" leafCount="100" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/194407" rating="9.0" ratingKey="30" studio="TV Studio" summary="Elaborate summary." theme="/library/metadata/30/theme/1488495292" thumb="/library/metadata/30/thumb/1488495292" title="TV Show" type="show" updatedAt="1488495292" viewedLeafCount="0" year="2000">
|
||||
<Genre tag="Action" />
|
||||
<Genre tag="Animated" />
|
||||
<Role tag="Some Actor" />
|
||||
<Role tag="Another One" />
|
||||
<Location path="/storage/tvshows/TV Show" /></Directory></MediaContainer>
|
|
@ -0,0 +1 @@
|
|||
<MediaContainer size="1"><Player deviceClass="pc" machineIdentifier="plexweb_id" platform="Chrome" platformVersion="14.0" product="Plex Web" protocol="plex" protocolCapabilities="timeline,playback,navigation,mirror,playqueues" protocolVersion="3" title="Chrome" version="4.47.1" /></MediaContainer>
|
|
@ -0,0 +1,11 @@
|
|||
<MediaContainer composite="/playlists/{key}/composite/1606158679" duration="5000" leafCount="1" playlistType="video" ratingKey="500" size="1" smart="0" title="Playlist 500"><Video addedAt="1377829261" art="/library/metadata/1/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/1" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="1" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/1/thumb/1590245989" title="Movie 1" type="movie" updatedAt="1590245989" viewCount="1" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
<Media aspectRatio="2.35" audioChannels="6" audioCodec="dca" audioProfile="dts" bitrate="7500" container="mkv" duration="9000000" height="544" id="2637" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720" width="1280" /><Part audioProfile="dts" container="mkv" duration="9000000" file="/storage/videos/video.mkv" id="4631" key="/library/parts/4631/1215643935/file.mkv" size="8500000000" videoProfile="high" /><Stream bitDepth="8" bitrate="6000" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="544" codedWidth="1280" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.976" hasScalingMatrix="0" height="544" id="21428" index="0" language="English" languageCode="eng" level="51" profile="high" refFrames="8" scanType="progressive" streamType="1" title="x264 @ 6000 kbps" width="1280" /><Stream audioChannelLayout="5.1(side)" bitDepth="16" bitrate="1500" channels="6" codec="dca" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1500 kbps (English)" id="21429" index="1" language="English" languageCode="eng" profile="dts" samplingRate="48000" selected="1" streamType="2" title="DTS 5.1 @ 1500 kbps" /><Stream codec="srt" default="1" displayTitle="English (SRT)" extendedDisplayTitle="English (SRT)" id="21430" index="2" language="English" languageCode="eng" streamType="3" /></Video></MediaContainer>
|
|
@ -0,0 +1,6 @@
|
|||
<MediaContainer size="2">
|
||||
<Playlist ratingKey="500" key="/playlists/500/items" guid="com.plexapp.agents.none://9a8f4a48-dd89-40e0-955b-286285350fdf" type="playlist" title="Playlist 1" summary="" smart="0" playlistType="video" composite="/playlists/500/composite/1597983847" viewCount="2" lastViewedAt="1568512403" duration="5054000" leafCount="1" addedAt="1505969338" updatedAt="1597983847">
|
||||
</Playlist>
|
||||
<Playlist ratingKey="501" key="/playlists/501/items" guid="com.plexapp.agents.none://9a8f4a48-dd89-40e0-955b-286285350fdf" type="playlist" title="Playlist 2" summary="" smart="0" playlistType="video" composite="/playlists/501/composite/1597983847" viewCount="5" lastViewedAt="1568512403" duration="5054000" leafCount="1" addedAt="1505969339" updatedAt="1597983847">
|
||||
</Playlist>
|
||||
</MediaContainer>
|
|
@ -0,0 +1 @@
|
|||
<MediaContainer allowSync="1" identifier="com.plexapp.plugins.library" librarySectionID="3" librarySectionTitle="Music" librarySectionUUID="905308ec-5019-43d4-a449-75d2b9e42f93" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" playQueueID="11111" playQueueSelectedItemID="98610" playQueueSelectedItemOffset="0" playQueueSelectedMetadataItemID="100" playQueueShuffled="0" playQueueSourceURI="library://ba0c2140-c6ef-448a-9d1b-31020741d014/item//library/metadata/100" playQueueTotalCount="1" playQueueVersion="1" size="1"><Track addedAt="1600999261" art="/library/metadata/300/art/1605462131" duration="250000" grandparentArt="/library/metadata/300/art/1605462131" grandparentGuid="plex://artist/12345" grandparentKey="/library/metadata/300" grandparentRatingKey="300" grandparentThumb="/library/metadata/300/thumb/1605462131" grandparentTitle="Arist Name" guid="plex://track/12345" index="1" key="/library/metadata/100" lastViewedAt="1603309346" librarySectionID="3" librarySectionKey="/library/sections/3" librarySectionTitle="Music" parentGuid="plex://album/12345" parentIndex="1" parentKey="/library/metadata/200" parentRatingKey="200" parentThumb="/library/metadata/200/thumb/1605462119" parentTitle="Album Title" ratingKey="100" summary="" thumb="/library/metadata/200/thumb/1605462119" title="Track 1" type="track" updatedAt="1605462119" viewCount="1"><Media audioChannels="2" audioCodec="mp3" bitrate="256" container="mp3" duration="250000" id="381515" playQueueItemID="98610" /><Part container="mp3" duration="250000" file="/storage/music/Artist Name/Album Name/Track Name.mp3" id="381939" key="/library/parts/381939/1602996958/file.mp3" size="5000000" /><Stream albumGain="-10.34" albumPeak="1.000000" albumRange="8.429853" audioChannelLayout="stereo" bitrate="256" channels="2" codec="mp3" displayTitle="Unknown (MP3 Stereo)" extendedDisplayTitle="Unknown (MP3 Stereo)" gain="-10.34" id="766687" index="0" loudness="-11.38" lra="7.80" peak="0.870300" samplingRate="44100" selected="1" streamType="2" /></Track></MediaContainer>
|
|
@ -0,0 +1,6 @@
|
|||
<MediaContainer size="4" identifier="com.plexapp.system.accounts">
|
||||
<Account id="0" key="/accounts/0" name="" defaultAudioLanguage="en" autoSelectAudio="1" defaultSubtitleLanguage="en" subtitleMode="1" thumb="" />
|
||||
<Account id="1" key="/accounts/1" name="User 1" defaultAudioLanguage="en" autoSelectAudio="1" defaultSubtitleLanguage="en" subtitleMode="1" thumb="" />
|
||||
<Account id="1000" key="/accounts/1000" name="User 1000" defaultAudioLanguage="en" autoSelectAudio="1" defaultSubtitleLanguage="en" subtitleMode="1" thumb="" />
|
||||
<Account id="1001" key="/accounts/1001" name="User 1001" defaultAudioLanguage="en" autoSelectAudio="1" defaultSubtitleLanguage="en" subtitleMode="1" thumb="" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,27 @@
|
|||
<MediaContainer size="25" allowCameraUpload="1" allowChannelAccess="1" allowMediaDeletion="1" allowSharing="1" allowSync="1" allowTuners="1" backgroundProcessing="1" certificate="1" companionProxy="1" countryCode="usa" diagnostics="logs,databases,streaminglogs" eventStream="1" friendlyName="{name}" hubSearch="1" itemClusters="1" livetv="7" machineIdentifier="{machine_identifier}" mediaProviders="1" multiuser="1" myPlex="1" myPlexMappingState="mapped" myPlexSigninState="ok" myPlexSubscription="1" myPlexUsername="myplexusername@email.com" offlineTranscode="1" ownerFeatures="adaptive_bitrate,camera_upload,cloudsync,collections,content_filter,download_certificates,dvr,federated-auth,hardware_transcoding,home,hwtranscode,item_clusters,kevin-bacon,livetv,loudness,lyrics,music_videos,news,pass,photo_autotags,photos-v5,photosV6-edit,photosV6-tv-albums,premium_music_metadata,radio,server-manager,session_bandwidth_restrictions,session_kick,shared-radio,sync,trailers,tuner-sharing,type-first,unsupportedtuners,webhooks" photoAutoTag="1" platform="Linux" platformVersion="20.04.1 LTS (Focal Fossa)" pluginHost="1" pushNotifications="0" readOnlyLibraries="0" requestParametersInCookie="1" streamingBrainABRVersion="3" streamingBrainVersion="2" sync="1" transcoderActiveVideoSessions="0" transcoderAudio="1" transcoderLyrics="1" transcoderPhoto="1" transcoderSubtitles="1" transcoderVideo="1" transcoderVideoBitrates="64,96,208,320,720,1500,2000,3000,4000,8000,10000,12000,20000" transcoderVideoQualities="0,1,2,3,4,5,6,7,8,9,10,11,12" transcoderVideoResolutions="128,128,160,240,320,480,768,720,720,1080,1080,1080,1080" updatedAt="1605463238" updater="1" version="1.20.4.3517-ab5e1197c" voiceSearch="1">
|
||||
<Directory count="1" key="activities" title="activities" />
|
||||
<Directory count="1" key="butler" title="butler" />
|
||||
<Directory count="1" key="channels" title="channels" />
|
||||
<Directory count="1" key="clients" title="clients" />
|
||||
<Directory count="1" key="devices" title="devices" />
|
||||
<Directory count="1" key="diagnostics" title="diagnostics" />
|
||||
<Directory count="1" key="hubs" title="hubs" />
|
||||
<Directory count="3" key="library" title="library" />
|
||||
<Directory count="3" key="livetv" title="livetv" />
|
||||
<Directory count="3" key="media" title="media" />
|
||||
<Directory count="2" key="metadata" title="metadata" />
|
||||
<Directory count="1" key="neighborhood" title="neighborhood" />
|
||||
<Directory count="1" key="playQueues" title="playQueues" />
|
||||
<Directory count="1" key="player" title="player" />
|
||||
<Directory count="1" key="playlists" title="playlists" />
|
||||
<Directory count="1" key="resources" title="resources" />
|
||||
<Directory count="1" key="search" title="search" />
|
||||
<Directory count="1" key="server" title="server" />
|
||||
<Directory count="1" key="servers" title="servers" />
|
||||
<Directory count="1" key="statistics" title="statistics" />
|
||||
<Directory count="1" key="system" title="system" />
|
||||
<Directory count="1" key="transcode" title="transcode" />
|
||||
<Directory count="2" key="tv%2Eplex%2Eproviders%2Eepg%2Ecloud%3A2" title="tv.plex.providers.epg.cloud:2" />
|
||||
<Directory count="1" key="updater" title="updater" />
|
||||
<Directory count="1" key="user" title="user" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,3 @@
|
|||
<MediaContainer size="1">
|
||||
<Server name="SHIELD Android TV" host="1.2.3.11" address="1.2.3.11" port="32500" machineIdentifier="1234567890123456-com-plexapp-android" version="8.8.2.21525" protocol="plex" product="Plex for Android (TV)" deviceClass="mobile" protocolVersion="1" protocolCapabilities="timeline,playback,mirror,playqueues,provider-playback" />
|
||||
</MediaContainer>
|
|
@ -0,0 +1,15 @@
|
|||
<user email="myplexusername@email.com" id="12345" uuid="1234567890" mailing_list_status="active" thumb="https://plex.tv/users/1234567890abcdef/avatar?c=11111" username="User 1" title="User 1" cloudSyncDevice="" locale="" authenticationToken="faketoken" authToken="faketoken" scrobbleTypes="" restricted="0" home="1" guest="0" queueEmail="queue+1234567890@save.plex.tv" queueUid="" hasPassword="true" homeSize="2" maxHomeSize="15" secure="1" certificateVersion="2">
|
||||
<subscription active="1" status="Active" plan="lifetime">
|
||||
<feature id="companions_sonos"/>
|
||||
</subscription>
|
||||
<roles>
|
||||
<role id="plexpass"/>
|
||||
</roles>
|
||||
<entitlements all="1"/>
|
||||
<profile_settings default_audio_language="en" default_subtitle_language="en" auto_select_subtitle="1" auto_select_audio="1" default_subtitle_accessibility="0" default_subtitle_forced="0"/>
|
||||
<services/>
|
||||
<username>testuser</username>
|
||||
<email>testuser@email.com</email>
|
||||
<joined-at type="datetime">2000-01-01 12:34:56 UTC</joined-at>
|
||||
<authentication-token>faketoken</authentication-token>
|
||||
</user>
|
|
@ -0,0 +1,21 @@
|
|||
<MediaContainer size="5">
|
||||
<Device name="Plex Server 1" product="Plex Media Server" productVersion="1.20.4.3517-ab5e1197c" platform="Linux" platformVersion="20.04.1 LTS (Focal Fossa)" device="PC" clientIdentifier="unique_id_123" createdAt="1429510140" lastSeenAt="1605500006" provides="server" owned="1" accessToken="faketoken" publicAddress="10.20.30.40" httpsRequired="0" synced="0" relay="0" dnsRebindingProtection="0" natLoopbackSupported="1" publicAddressMatches="1" presence="1">
|
||||
<Connection protocol="https" address="1.2.3.4" port="32400" uri="https://1-2-3-4.123456789001234567890.plex.direct:32400" local="1"/>
|
||||
</Device>
|
||||
<Device name="Plex Server 2" product="Plex Media Server" productVersion="1.20.4.3517-ab5e1197c" platform="Linux" platformVersion="20.04.1 LTS (Focal Fossa)" device="PC" clientIdentifier="unique_id_456" createdAt="1429510140" lastSeenAt="1605500006" provides="server" owned="1" accessToken="faketoken" publicAddress="10.20.30.40" httpsRequired="0" synced="0" relay="0" dnsRebindingProtection="0" natLoopbackSupported="1" publicAddressMatches="1" presence="{second_server_enabled}">
|
||||
<Connection protocol="https" address="4.3.2.1" port="32400" uri="https://4-3-2-1.123456789001234567890.plex.direct:32400" local="1"/>
|
||||
</Device>
|
||||
<Device name="Chrome" product="Plex Web" productVersion="4.46.2" platform="Chrome" platformVersion="14.0" device="OSX" clientIdentifier="plexweb_id" createdAt="1578086003" lastSeenAt="1605461664" provides="client,player,pubsub-player" owned="1" publicAddress="10.20.30.40" publicAddressMatches="1" presence="1" accessToken="faketoken">
|
||||
<Connection protocol="https" address="1.2.3.5" port="32400" uri="https://1-2-3-5.123456789001234567890.plex.direct:32400" local="1"/>
|
||||
<Connection protocol="https" address="10.20.30.40" port="35872" uri="https://10-20-30-40.123456789001234567890.plex.direct:35872" local="0"/>
|
||||
</Device>
|
||||
<Device name="AppleTV" product="Plex for Apple TV" productVersion="7.9" platform="tvOS" platformVersion="14.2" device="Apple TV" clientIdentifier="A10E4083-BF1A-4586-B884-C638A32D5285" createdAt="1447217545" lastSeenAt="1605495521" provides="client,player,pubsub-player,provider-playback" owned="1" publicAddress="10.20.30.40" publicAddressMatches="1" presence="0">
|
||||
<Connection protocol="http" address="1.2.3.6" port="32500" uri="http://1.2.3.6:32500" local="1"/>
|
||||
</Device>
|
||||
<Device name="jPhone" product="Plex for iOS" productVersion="7.9" platform="iOS" platformVersion="14.2" device="iPhone" clientIdentifier="CDB83941-F8C2-4B56-989E-F3EFD0165BC1" createdAt="1537584529" lastSeenAt="1605501046" provides="client,controller,sync-target,player,pubsub-player,provider-playback" owned="1" publicAddress="10.20.30.40" publicAddressMatches="1" presence="0">
|
||||
<Connection protocol="http" address="1.2.3.7" port="32500" uri="http://1.2.3.7:32500" local="1"/>
|
||||
</Device>
|
||||
<Device name="SHIELD Android TV" product="Plex for Android (TV)" productVersion="8.8.2.21525" platform="Android" platformVersion="9" device="SHIELD Android TV" clientIdentifier="2f2a5ae50a45837c-com-plexapp-android" createdAt="1584850408" lastSeenAt="1605384938" provides="player,pubsub-player,controller" owned="1" publicAddress="10.20.30.40" publicAddressMatches="1" presence="1">
|
||||
<Connection protocol="http" address="1.2.3.11" port="32500" uri="http://1.2.3.11:32500" local="1"/>
|
||||
</Device>
|
||||
</MediaContainer>
|
|
@ -0,0 +1 @@
|
|||
<MediaContainer size="0" token="transient-1234567890" />
|
|
@ -0,0 +1,11 @@
|
|||
<MediaContainer size="1"><Video addedAt="1377829261" art="/library/metadata/1/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/1" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="1" sessionKey="1" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/1/thumb/1590245989" title="Movie 1" type="movie" updatedAt="1590245989" viewCount="1" viewOffset="0" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
<Player address="1.2.3.11" device="SHIELD Android TV" deviceClass="stb" local="1" machineIdentifier="1234567890123456-com-plexapp-android" model="darcy" platform="Android" platformVersion="9" product="Plex for Android (TV)" profile="Android" protocolVersion="1" relayed="0" remotePublicAddress="10.20.30.40" secure="1" state="playing" title="SHIELD Android TV" userID="{user_id}" vendor="NVIDIA" version="8.9.2.21619" /><User id="{user_id}" thumb="https://plex.tv/users/1234567890abcdef/avatar?c=11111" title="User {user_id}" /><Session bandwidth="7000" id="session_id_1" location="lan" /><Media audioChannels="2" audioCodec="aac" audioProfile="dts" bitrate="6000" container="mp4" duration="9000000" height="544" id="2637" optimizedForStreaming="1" protocol="dash" selected="1" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720p" width="1280"><Part audioProfile="dts" bitrate="6000" container="mp4" decision="transcode" duration="9000000" height="544" id="4631" optimizedForStreaming="1" protocol="dash" selected="1" videoProfile="high" width="1280"><Stream bitrate="6000" codec="h264" decision="copy" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.975999999999999" height="544" id="21428" language="English" languageCode="eng" location="segments-video" streamType="1" width="1280" /><Stream bitrate="256" bitrateMode="cbr" channels="2" codec="aac" decision="transcode" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1536 kbps (English)" id="21429" language="English" languageCode="eng" location="segments-audio" selected="1" streamType="2" /></Part></Media></Video></MediaContainer>
|
|
@ -0,0 +1,5 @@
|
|||
<MediaContainer size="1"><Photo addedAt="1605739344" createdAtAccuracy="local" createdAtTZOffset="-18000" guid="local://999" index="1" key="/library/metadata/999" librarySectionID="4" librarySectionKey="/library/sections/4" librarySectionTitle="Photos" originallyAvailableAt="2020-10-31" ratingKey="999" sessionKey="1" summary="" thumb="/library/metadata/999/thumb/1605739344" title="Photo 1" type="photo" updatedAt="1605739344" viewOffset="0" year="2020">
|
||||
<Media aspectRatio="1.33" container="jpeg" height="2880" id="381658" width="1620">
|
||||
<Part container="jpeg" file="/storage/photos/Photo 1.jpeg" id="382082" key="/library/parts/382082/1604162245/file.jpeg" size="500000" />
|
||||
</Media>
|
||||
<Player address="1.2.3.11" device="SHIELD Android TV" deviceClass="stb" local="1" machineIdentifier="1234567890123456-com-plexapp-android" model="darcy" platform="Android" platformVersion="9" product="Plex for Android (TV)" profile="Android" protocolVersion="1" relayed="0" remotePublicAddress="10.20.30.40" secure="1" state="playing" title="SHIELD Android TV" userID="1" vendor="NVIDIA" version="8.9.2.21619" /><User id="1" thumb="https://plex.tv/users/1234567890abcdef/avatar?c=11111" title="User 1" /></Photo></MediaContainer>
|
|
@ -0,0 +1,11 @@
|
|||
<MediaContainer size="1"><Video addedAt="1377829261" art="/library/metadata/1/art/1590245989" audienceRating="9.5" audienceRatingImage="rottentomatoes://image.rating.upright" chapterSource="agent" contentRating="R" duration="9000000" guid="com.plexapp.agents.imdb://tt0123456?lang=en" key="/library/metadata/1" lastViewedAt="1505969509" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originallyAvailableAt="2000-01-01" primaryExtraKey="/library/metadata/195540" rating="9.0" ratingImage="rottentomatoes://image.rating.certified" ratingKey="1" sessionKey="1" studio="Studio Entertainment" summary="Some elaborate summary." tagline="Witty saying." thumb="/library/metadata/1/thumb/1590245989" title="Movie 1" type="movie" updatedAt="1590245989" viewCount="1" viewOffset="0" year="2000">
|
||||
<Genre count="119" filter="genre=25578" id="25578" tag="Sci-Fi" />
|
||||
<Genre count="197" filter="genre=87" id="87" tag="Action" />
|
||||
<Director count="4" filter="director=100" id="100" tag="Famous Director" />
|
||||
<Writer count="2" filter="writer=50000" id="50000" tag="A Writer" />
|
||||
<Producer count="3" filter="producer=2000" id="2000" tag="Dr. Producer" />
|
||||
<Country count="452" filter="country=1105" id="1105" tag="USA" />
|
||||
<Role count="25" filter="actor=1" id="1" role="Character 1" tag="Actor 1" thumb="http://4.3.2.1/t/p/original/1.jpg" />
|
||||
<Role count="2" filter="actor=2" id="2" role="Character 2" tag="Actor 2" thumb="http://4.3.2.1/t/p/original/2.jpg" />
|
||||
<Role filter="actor=3" id="3" role="Character 3" tag="Actor 3" thumb="http://4.3.2.1/t/p/original/3.jpg" />
|
||||
<Player address="1.2.3.5" device="OSX" deviceClass="pc" machineIdentifier="plexweb_id" model="hosted" platform="Chrome" platformVersion="14.0" product="Plex Web" protocol="plex" protocolVersion="3" remotePublicAddress="10.20.30.40" state="playing" title="Chrome" userID="1" vendor="" version="4" /><User id="1" thumb="https://plex.tv/users/1234567890abcdef/avatar?c=11111" title="User 1" /><Session bandwidth="7000" id="session_id_1" location="lan" /><Media audioChannels="2" audioCodec="aac" audioProfile="dts" bitrate="6000" container="mp4" duration="9000000" height="544" id="2637" optimizedForStreaming="1" protocol="dash" selected="1" videoCodec="h264" videoFrameRate="24p" videoProfile="high" videoResolution="720p" width="1280"><Part audioProfile="dts" bitrate="6000" container="mp4" decision="transcode" duration="9000000" height="544" id="4631" optimizedForStreaming="1" protocol="dash" selected="1" videoProfile="high" width="1280"><Stream bitrate="6000" codec="h264" decision="copy" default="1" displayTitle="720p (H.264)" extendedDisplayTitle="x264 @ 6000 kbps (720p H.264)" frameRate="23.975999999999999" height="544" id="21428" language="English" languageCode="eng" location="segments-video" streamType="1" width="1280" /><Stream bitrate="256" bitrateMode="cbr" channels="2" codec="aac" decision="transcode" default="1" displayTitle="English (DTS 5.1)" extendedDisplayTitle="DTS 5.1 @ 1536 kbps (English)" id="21429" language="English" languageCode="eng" location="segments-audio" selected="1" streamType="2" /></Part></Media></Video></MediaContainer>
|
|
@ -0,0 +1,4 @@
|
|||
<MediaContainer size="1" allowSync="1" art="/library/metadata/30/art/1488495294" banner="/library/metadata/30/banner/1488495294" identifier="com.plexapp.plugins.library" key="30" librarySectionID="2" librarySectionTitle="TV Shows" librarySectionUUID="1d8c8690-2dc5-48e6-9b54-accfacd0067c" mediaTagPrefix="/system/bundle/media/flags/" mediaTagVersion="1603922053" nocache="1" parentIndex="1" parentTitle="TV Show" parentYear="2000" sortAsc="1" summary="Show summary." theme="/library/metadata/30/theme/1488495294" thumb="/library/metadata/30/thumb/1488495294" title1="TV Shows" title2="TV Show" viewGroup="season" viewMode="458810">
|
||||
<Directory ratingKey="20" key="/library/metadata/20/children" parentRatingKey="30" guid="com.plexapp.agents.thetvdb://12345/1?lang=en" parentGuid="com.plexapp.agents.thetvdb://12345?lang=en" type="season" title="Season 1" parentKey="/library/metadata/30" parentTitle="TV Show" summary="" index="1" parentIndex="1" viewCount="20" lastViewedAt="1524197296" thumb="/library/metadata/20/thumb/1488495294" art="/library/metadata/30/art/1488495294" parentThumb="/library/metadata/30/thumb/1488495294" parentTheme="/library/metadata/30/theme/1488495294" leafCount="14" viewedLeafCount="14" addedAt="1377827368" updatedAt="1488495294">
|
||||
</Directory>
|
||||
</MediaContainer>
|
|
@ -0,0 +1,5 @@
|
|||
<MediaContainer size="3">
|
||||
<Player title="Speaker 1" machineIdentifier="RINCON_12345678901234561:1234567891" deviceClass="speaker" product="Sonos" platform="Sonos" platformVersion="56.0-76060" protocol="plex" protocolVersion="1" protocolCapabilities="timeline,playback,playqueues,provider-playback" lanIP="192.168.1.11"/>
|
||||
<Player title="Speaker 2 + 1" machineIdentifier="RINCON_12345678901234562:1234567892" deviceClass="speaker" product="Sonos" platform="Sonos" platformVersion="56.0-76060" protocol="plex" protocolVersion="1" protocolCapabilities="timeline,playback,playqueues,provider-playback" lanIP="192.168.1.12"/>
|
||||
<Player title="Speaker 3" machineIdentifier="RINCON_12345678901234563:1234567893" deviceClass="speaker" product="Sonos" platform="Sonos" platformVersion="56.0-76060" protocol="plex" protocolVersion="1" protocolCapabilities="timeline,playback,playqueues,provider-playback" lanIP="192.168.1.13"/>
|
||||
</MediaContainer>
|
Loading…
Reference in New Issue