mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Merge branch 'master' into feature/edit_tags
This commit is contained in:
commit
88e69d4664
16 changed files with 239 additions and 77 deletions
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: bug_report
|
||||
about: Create a report to help us improve
|
||||
name: Bug Report
|
||||
about: Create a bug report to help us improve.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
@ -8,7 +8,7 @@ assignees: ''
|
|||
---
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the issue is.
|
||||
A clear and concise description the issue.
|
||||
|
||||
**Code snipppets**
|
||||
Add code snippet to help explain your problem.
|
||||
|
@ -19,9 +19,9 @@ A clear and concise description of what you expected to happen.
|
|||
|
||||
**Enviroment (please complete the following information):**
|
||||
- OS: [e.g. Linux, Windows, Docker]
|
||||
- Plex version [eg. version 1.19.3.2852]
|
||||
- Python Version [e.g. 2.7.15, 3.6]
|
||||
- PlexAPI version [e.g. 3.6.0]
|
||||
- Plex version [eg. version 1.21.1.3830]
|
||||
- Python Version [e.g. 3.7.8]
|
||||
- PlexAPI version [e.g. 4.3.0]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the issue here.
|
||||
|
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord
|
||||
url: https://discord.gg/GtAnnZAkuw
|
||||
about: Please use Discord to ask for support.
|
|
@ -36,6 +36,10 @@ Documentation_ can be found at Read the Docs.
|
|||
|
||||
.. _Documentation: http://python-plexapi.readthedocs.io/en/latest/
|
||||
|
||||
Join our Discord_ for support and discussion.
|
||||
|
||||
.. _Discord: https://discord.gg/GtAnnZAkuw
|
||||
|
||||
|
||||
Getting a PlexServer Instance
|
||||
-----------------------------
|
||||
|
@ -108,8 +112,8 @@ Usage Examples
|
|||
|
||||
# Example 6: List all movies directed by the same person as Elephants Dream.
|
||||
movies = plex.library.section('Movies')
|
||||
die_hard = movies.get('Elephants Dream')
|
||||
director = die_hard.directors[0]
|
||||
elephants_dream = movies.get('Elephants Dream')
|
||||
director = elephants_dream.directors[0]
|
||||
for movie in movies.search(None, director=director):
|
||||
print(movie.title)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ CONFIG = PlexConfig(CONFIG_PATH)
|
|||
|
||||
# PlexAPI Settings
|
||||
PROJECT = 'PlexAPI'
|
||||
VERSION = '4.2.0'
|
||||
VERSION = '4.3.1'
|
||||
TIMEOUT = CONFIG.get('plexapi.timeout', 30, int)
|
||||
X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int)
|
||||
X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool)
|
||||
|
|
|
@ -168,11 +168,12 @@ class Library(PlexObject):
|
|||
|
||||
**Movie Preferences**
|
||||
|
||||
* **agent** (str): com.plexapp.agents.none, com.plexapp.agents.imdb, com.plexapp.agents.themoviedb
|
||||
* **agent** (str): com.plexapp.agents.none, com.plexapp.agents.imdb, tv.plex.agents.movie,
|
||||
com.plexapp.agents.themoviedb
|
||||
* **enableBIFGeneration** (bool): Enable video preview thumbnails. Default value true.
|
||||
* **enableCinemaTrailers** (bool): Enable Cinema Trailers. Default value true.
|
||||
* **includeInGlobal** (bool): Include in dashboard. Default value true.
|
||||
* **scanner** (str): Plex Movie Scanner, Plex Video Files Scanner
|
||||
* **scanner** (str): Plex Movie, Plex Movie Scanner, Plex Video Files Scanner, Plex Video Files
|
||||
|
||||
**IMDB Movie Options** (com.plexapp.agents.imdb)
|
||||
|
||||
|
@ -1595,7 +1596,17 @@ class Collections(PlexPartialObject, LabelMixin):
|
|||
@deprecated('use "items" instead')
|
||||
def children(self):
|
||||
return self.fetchItems(self.key)
|
||||
|
||||
|
||||
@property
|
||||
def thumbUrl(self):
|
||||
""" Return the thumbnail url for the collection."""
|
||||
return self._server.url(self.thumb, includeToken=True) if self.thumb else None
|
||||
|
||||
@property
|
||||
def artUrl(self):
|
||||
""" Return the art url for the collection."""
|
||||
return self._server.url(self.art, includeToken=True) if self.art else None
|
||||
|
||||
def item(self, title):
|
||||
""" Returns the item in the collection that matches the specified title.
|
||||
|
||||
|
|
|
@ -433,7 +433,14 @@ class LyricStream(MediaPartStream):
|
|||
|
||||
@utils.registerPlexObject
|
||||
class Session(PlexObject):
|
||||
""" Represents a current session. """
|
||||
""" Represents a current session.
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Session'
|
||||
id (str): The unique identifier for the session.
|
||||
bandwidth (int): The Plex streaming brain reserved bandwidth for the session.
|
||||
location (str): The location of the session (lan, wan, or cellular)
|
||||
"""
|
||||
TAG = 'Session'
|
||||
|
||||
def _loadData(self, data):
|
||||
|
@ -444,7 +451,41 @@ class Session(PlexObject):
|
|||
|
||||
@utils.registerPlexObject
|
||||
class TranscodeSession(PlexObject):
|
||||
""" Represents a current transcode session. """
|
||||
""" Represents a current transcode session.
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'TranscodeSession'
|
||||
audioChannels (int): The number of audio channels of the transcoded media.
|
||||
audioCodec (str): The audio codec of the transcoded media.
|
||||
audioDecision (str): The transcode decision for the audio stream.
|
||||
complete (bool): True if the transcode is complete.
|
||||
container (str): The container of the transcoded media.
|
||||
context (str): The context for the transcode sesson.
|
||||
duration (int): The duration of the transcoded media in milliseconds.
|
||||
height (int): The height of the transcoded media in pixels.
|
||||
key (str): API URL (ex: /transcode/sessions/<id>).
|
||||
maxOffsetAvailable (float): Unknown.
|
||||
minOffsetAvailable (float): Unknown.
|
||||
progress (float): The progress percentage of the transcode.
|
||||
protocol (str): The protocol of the transcode.
|
||||
remaining (int): Unknown.
|
||||
size (int): The size of the transcoded media in bytes.
|
||||
sourceAudioCodec (str): The audio codec of the source media.
|
||||
sourceVideoCodec (str): The video codec of the source media.
|
||||
speed (float): The speed of the transcode.
|
||||
subtitleDecision (str): The transcode decision for the subtitle stream
|
||||
throttled (bool): True if the transcode is throttled.
|
||||
timestamp (int): The epoch timestamp when the transcode started.
|
||||
transcodeHwDecoding (str): The hardware transcoding decoder engine.
|
||||
transcodeHwDecodingTitle (str): The title of the hardware transcoding decoder engine.
|
||||
transcodeHwEncoding (str): The hardware transcoding encoder engine.
|
||||
transcodeHwEncodingTitle (str): The title of the hardware transcoding encoder engine.
|
||||
transcodeHwFullPipeline (str): True if hardware decoding and encoding is being used for the transcode.
|
||||
transcodeHwRequested (str): True if hardware transcoding was requested for the transcode.
|
||||
videoCodec (str): The video codec of the transcoded media.
|
||||
videoDecision (str): The transcode decision for the video stream.
|
||||
width (str): The width of the transcoded media in pixels.
|
||||
"""
|
||||
TAG = 'TranscodeSession'
|
||||
|
||||
def _loadData(self, data):
|
||||
|
@ -453,17 +494,30 @@ class TranscodeSession(PlexObject):
|
|||
self.audioChannels = cast(int, data.attrib.get('audioChannels'))
|
||||
self.audioCodec = data.attrib.get('audioCodec')
|
||||
self.audioDecision = data.attrib.get('audioDecision')
|
||||
self.complete = cast(bool, data.attrib.get('complete', '0'))
|
||||
self.container = data.attrib.get('container')
|
||||
self.context = data.attrib.get('context')
|
||||
self.duration = cast(int, data.attrib.get('duration'))
|
||||
self.height = cast(int, data.attrib.get('height'))
|
||||
self.key = data.attrib.get('key')
|
||||
self.maxOffsetAvailable = cast(float, data.attrib.get('maxOffsetAvailable'))
|
||||
self.minOffsetAvailable = cast(float, data.attrib.get('minOffsetAvailable'))
|
||||
self.progress = cast(float, data.attrib.get('progress'))
|
||||
self.protocol = data.attrib.get('protocol')
|
||||
self.remaining = cast(int, data.attrib.get('remaining'))
|
||||
self.speed = cast(int, data.attrib.get('speed'))
|
||||
self.throttled = cast(int, data.attrib.get('throttled'))
|
||||
self.size = cast(int, data.attrib.get('size'))
|
||||
self.sourceAudioCodec = data.attrib.get('sourceAudioCodec')
|
||||
self.sourceVideoCodec = data.attrib.get('sourceVideoCodec')
|
||||
self.speed = cast(float, data.attrib.get('speed'))
|
||||
self.subtitleDecision = data.attrib.get('subtitleDecision')
|
||||
self.throttled = cast(bool, data.attrib.get('throttled', '0'))
|
||||
self.timestamp = cast(float, data.attrib.get('timeStamp'))
|
||||
self.transcodeHwDecoding = data.attrib.get('transcodeHwDecoding')
|
||||
self.transcodeHwDecodingTitle = data.attrib.get('transcodeHwDecodingTitle')
|
||||
self.transcodeHwEncoding = data.attrib.get('transcodeHwEncoding')
|
||||
self.transcodeHwEncodingTitle = data.attrib.get('transcodeHwEncodingTitle')
|
||||
self.transcodeHwFullPipeline = cast(bool, data.attrib.get('transcodeHwFullPipeline', '0'))
|
||||
self.transcodeHwRequested = cast(bool, data.attrib.get('transcodeHwRequested', '0'))
|
||||
self.videoCodec = data.attrib.get('videoCodec')
|
||||
self.videoDecision = data.attrib.get('videoDecision')
|
||||
self.width = cast(int, data.attrib.get('width'))
|
||||
|
@ -627,6 +681,19 @@ class MediaTag(PlexObject):
|
|||
return self.fetchItems(self.key)
|
||||
|
||||
|
||||
class GuidTag(PlexObject):
|
||||
""" Base class for guid tags used only for Guids, as they contain only a string identifier
|
||||
|
||||
Attributes:
|
||||
id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB).
|
||||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
self._data = data
|
||||
self.id = data.attrib.get('id')
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class Collection(MediaTag):
|
||||
""" Represents a single Collection media tag.
|
||||
|
@ -706,6 +773,16 @@ class Genre(MediaTag):
|
|||
FILTER = 'genre'
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class Guid(GuidTag):
|
||||
""" Represents a single Guid media tag.
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Guid'
|
||||
"""
|
||||
TAG = "Guid"
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class Mood(MediaTag):
|
||||
""" Represents a single Mood media tag.
|
||||
|
|
|
@ -947,31 +947,38 @@ class MyPlexResource(PlexObject):
|
|||
def connect(self, ssl=None, timeout=None):
|
||||
""" Returns a new :class:`~plexapi.server.PlexServer` or :class:`~plexapi.client.PlexClient` object.
|
||||
Often times there is more than one address specified for a server or client.
|
||||
This function will prioritize local connections before remote and HTTPS before HTTP.
|
||||
This function will prioritize local connections before remote or relay and HTTPS before HTTP.
|
||||
After trying to connect to all available addresses for this resource and
|
||||
assuming at least one connection was successful, the PlexServer object is built and returned.
|
||||
|
||||
Parameters:
|
||||
ssl (optional): Set True to only connect to HTTPS connections. Set False to
|
||||
ssl (bool, optional): Set True to only connect to HTTPS connections. Set False to
|
||||
only connect to HTTP connections. Set None (default) to connect to any
|
||||
HTTP or HTTPS connection.
|
||||
timeout (int, optional): The timeout in seconds to attempt each connection.
|
||||
|
||||
Raises:
|
||||
:exc:`~plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource.
|
||||
"""
|
||||
# Sort connections from (https, local) to (http, remote)
|
||||
# Only check non-local connections unless we own the resource
|
||||
connections = sorted(self.connections, key=lambda c: c.local, reverse=True)
|
||||
owned_or_unowned_non_local = lambda x: self.owned or (not self.owned and not x.local)
|
||||
https = [c.uri for c in connections if owned_or_unowned_non_local(c)]
|
||||
http = [c.httpuri for c in connections if owned_or_unowned_non_local(c)]
|
||||
cls = PlexServer if 'server' in self.provides else PlexClient
|
||||
# Force ssl, no ssl, or any (default)
|
||||
if ssl is True: connections = https
|
||||
elif ssl is False: connections = http
|
||||
else: connections = https + http
|
||||
# Keys in the order we want the connections to be sorted
|
||||
locations = ['local', 'remote', 'relay']
|
||||
schemes = ['https', 'http']
|
||||
connections_dict = {location: {scheme: [] for scheme in schemes} for location in locations}
|
||||
for connection in self.connections:
|
||||
# Only check non-local connections unless we own the resource
|
||||
if self.owned or (not self.owned and not connection.local):
|
||||
location = 'relay' if connection.relay else ('local' if connection.local else 'remote')
|
||||
connections_dict[location]['http'].append(connection.httpuri)
|
||||
connections_dict[location]['https'].append(connection.uri)
|
||||
if ssl is True: schemes.remove('http')
|
||||
elif ssl is False: schemes.remove('https')
|
||||
connections = []
|
||||
for location in locations:
|
||||
for scheme in schemes:
|
||||
connections.extend(connections_dict[location][scheme])
|
||||
# Try connecting to all known resource connections in parellel, but
|
||||
# only return the first server (in order) that provides a response.
|
||||
cls = PlexServer if 'server' in self.provides else PlexClient
|
||||
listargs = [[cls, url, self.accessToken, timeout] for url in connections]
|
||||
log.debug('Testing %s resource connections..', len(listargs))
|
||||
results = utils.threaded(_connect, listargs)
|
||||
|
|
|
@ -45,7 +45,7 @@ class Photoalbum(PlexPartialObject):
|
|||
self.fields = self.findItems(data, media.Field)
|
||||
self.guid = data.attrib.get('guid')
|
||||
self.index = utils.cast(int, data.attrib.get('index'))
|
||||
self.key = data.attrib.get('key', '')
|
||||
self.key = data.attrib.get('key', '').replace('/children', '') # FIX_BUG_50
|
||||
self.librarySectionID = data.attrib.get('librarySectionID')
|
||||
self.librarySectionKey = data.attrib.get('librarySectionKey')
|
||||
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
|
||||
|
|
|
@ -546,6 +546,10 @@ class PlexServer(PlexObject):
|
|||
""" Returns a list of all active session (currently playing) media objects. """
|
||||
return self.fetchItems('/status/sessions')
|
||||
|
||||
def transcodeSessions(self):
|
||||
""" Returns a list of all active :class:`~plexapi.media.TranscodeSession` objects. """
|
||||
return self.fetchItems('/transcode/sessions')
|
||||
|
||||
def startAlertListener(self, callback=None):
|
||||
""" Creates a websocket connection to the Plex Server to optionally recieve
|
||||
notifications. These often include messages from Plex about media scans
|
||||
|
|
|
@ -276,6 +276,7 @@ class Movie(Playable, Video, CollectionMixin, CountryMixin, DirectorMixin, Genre
|
|||
directors (List<:class:`~plexapi.media.Director`>): List of director objects.
|
||||
duration (int): Duration of the movie in milliseconds.
|
||||
genres (List<:class:`~plexapi.media.Genre`>): List of genre objects.
|
||||
guids (List<:class:`~plexapi.media.Guid`>): List of guid objects.
|
||||
labels (List<:class:`~plexapi.media.Label`>): List of label objects.
|
||||
media (List<:class:`~plexapi.media.Media`>): List of media objects.
|
||||
originallyAvailableAt (datetime): Datetime the movie was released.
|
||||
|
@ -311,6 +312,7 @@ class Movie(Playable, Video, CollectionMixin, CountryMixin, DirectorMixin, Genre
|
|||
self.directors = self.findItems(data, media.Director)
|
||||
self.duration = utils.cast(int, data.attrib.get('duration'))
|
||||
self.genres = self.findItems(data, media.Genre)
|
||||
self.guids = self.findItems(data, media.Guid)
|
||||
self.labels = self.findItems(data, media.Label)
|
||||
self.media = self.findItems(data, media.Media)
|
||||
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
|
||||
|
|
|
@ -40,6 +40,8 @@ CONTENTRATINGS = {"TV-14", "TV-MA", "G", "NR", "Not Rated"}
|
|||
FRAMERATES = {"24p", "PAL", "NTSC"}
|
||||
PROFILES = {"advanced simple", "main", "constrained baseline"}
|
||||
RESOLUTIONS = {"sd", "480", "576", "720", "1080"}
|
||||
HW_DECODERS = {'dxva2', 'videotoolbox', 'mediacodecndk', 'vaapi', 'nvdec'}
|
||||
HW_ENCODERS = {'qsv', 'mf', 'videotoolbox', 'mediacodecndk', 'vaapi', 'nvenc', 'x264'}
|
||||
ENTITLEMENTS = {
|
||||
"ios",
|
||||
"roku",
|
||||
|
@ -105,17 +107,20 @@ def pytest_runtest_setup(item):
|
|||
# ---------------------------------
|
||||
|
||||
|
||||
def get_account():
|
||||
return MyPlexAccount()
|
||||
@pytest.fixture(scope="session")
|
||||
def sess():
|
||||
session = requests.Session()
|
||||
session.request = partial(session.request, timeout=120)
|
||||
return session
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def account():
|
||||
def account(sess):
|
||||
if SERVER_TOKEN:
|
||||
return get_account()
|
||||
return MyPlexAccount(session=sess)
|
||||
assert MYPLEX_USERNAME, "Required MYPLEX_USERNAME not specified."
|
||||
assert MYPLEX_PASSWORD, "Required MYPLEX_PASSWORD not specified."
|
||||
return get_account()
|
||||
return MyPlexAccount(session=sess)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
@ -150,14 +155,14 @@ def mocked_account(requests_mock):
|
|||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def plex(request):
|
||||
def plex(request, sess):
|
||||
assert SERVER_BASEURL, "Required SERVER_BASEURL not specified."
|
||||
session = requests.Session()
|
||||
|
||||
if request.param == TEST_AUTHENTICATED:
|
||||
token = get_account().authenticationToken
|
||||
token = MyPlexAccount(session=sess).authenticationToken
|
||||
else:
|
||||
token = None
|
||||
return PlexServer(SERVER_BASEURL, token, session=session)
|
||||
return PlexServer(SERVER_BASEURL, token, session=sess)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
@ -166,7 +171,7 @@ def sync_device(account_synctarget):
|
|||
device = account_synctarget.device(clientId=SYNC_DEVICE_IDENTIFIER)
|
||||
except NotFound:
|
||||
device = createMyPlexDevice(SYNC_DEVICE_HEADERS, account_synctarget)
|
||||
|
||||
|
||||
assert device
|
||||
assert "sync-target" in device.provides
|
||||
return device
|
||||
|
|
|
@ -29,3 +29,8 @@ SERVER_RESOURCES = """<MediaContainer size="3">
|
|||
<StatisticsResources timespan="6" at="1609708619" hostCpuUtilization="10.000" processCpuUtilization="4.415" hostMemoryUtilization="64.281" processMemoryUtilization="3.669"/>
|
||||
</MediaContainer>
|
||||
"""
|
||||
|
||||
SEVER_TRANSCODE_SESSIONS = """<MediaContainer size="1">
|
||||
<TranscodeSession key="qucs2leop3yzm0sng4urq1o0" throttled="0" complete="0" progress="1.2999999523162842" size="73138224" speed="6.4000000953674316" duration="6654989" remaining="988" context="streaming" sourceVideoCodec="h264" sourceAudioCodec="dca" videoDecision="transcode" audioDecision="transcode" protocol="dash" container="mp4" videoCodec="h264" audioCodec="aac" audioChannels="2" transcodeHwRequested="1" transcodeHwDecoding="dxva2" transcodeHwDecodingTitle="Windows (DXVA2)" transcodeHwEncoding="qsv" transcodeHwEncodingTitle="Intel (QuickSync)" transcodeHwFullPipeline="0" timeStamp="1611533677.0316164" maxOffsetAvailable="84.000667334000667" minOffsetAvailable="0" height="720" width="1280" />
|
||||
</MediaContainer>
|
||||
"""
|
||||
|
|
|
@ -23,7 +23,7 @@ def test_library_Library_sectionByID_is_equal_section(plex, movies):
|
|||
|
||||
|
||||
def test_library_sectionByID_with_attrs(plex, movies):
|
||||
assert movies.agent == "com.plexapp.agents.imdb"
|
||||
assert movies.agent == "tv.plex.agents.movie"
|
||||
# This seems to fail for some reason.
|
||||
# my account alloew of sync, didnt find any about settings about the library.
|
||||
# assert movies.allowSync is ('sync' in plex.ownerFeatures)
|
||||
|
@ -35,11 +35,11 @@ def test_library_sectionByID_with_attrs(plex, movies):
|
|||
assert movies.filters == "1"
|
||||
assert movies._initpath == "/library/sections"
|
||||
assert utils.is_int(movies.key)
|
||||
assert movies.language == "en"
|
||||
assert movies.language == "en-US"
|
||||
assert len(movies.locations) == 1
|
||||
assert len(movies.locations[0]) >= 10
|
||||
assert movies.refreshing is False
|
||||
assert movies.scanner == "Plex Movie Scanner"
|
||||
assert movies.scanner == "Plex Movie"
|
||||
assert movies._server._baseurl == utils.SERVER_BASEURL
|
||||
assert movies.thumb == "/:/resources/movie.png"
|
||||
assert movies.title == "Movies"
|
||||
|
@ -153,8 +153,8 @@ def test_library_MovieSection_refresh(movies, patched_http_call):
|
|||
|
||||
|
||||
def test_library_MovieSection_search_genre(movie, movies):
|
||||
animation = [i for i in movie.genres if i.tag == "Animation"]
|
||||
assert len(movies.search(genre=animation[0])) > 1
|
||||
genre = movie.genres[0]
|
||||
assert len(movies.search(genre=genre)) >= 1
|
||||
|
||||
|
||||
def test_library_MovieSection_cancelUpdate(movies):
|
||||
|
@ -256,7 +256,7 @@ def test_library_editAdvanced_default(movies):
|
|||
movies.reload()
|
||||
movies.defaultAdvanced()
|
||||
for setting in movies.settings():
|
||||
assert int(setting.value) == int(setting.default)
|
||||
assert str(setting.value) == str(setting.default)
|
||||
|
||||
|
||||
def test_library_Collection_modeUpdate(collection):
|
||||
|
@ -314,6 +314,16 @@ def test_library_Collection_items(collection):
|
|||
assert len(items) == 1
|
||||
|
||||
|
||||
def test_library_Collection_thumbUrl(collection):
|
||||
assert utils.SERVER_BASEURL in collection.thumbUrl
|
||||
assert "/library/collections/" in collection.thumbUrl
|
||||
assert "/composite/" in collection.thumbUrl
|
||||
|
||||
|
||||
def test_library_Collection_artUrl(collection):
|
||||
assert collection.artUrl is None # Collections don't have default art
|
||||
|
||||
|
||||
def test_library_Collection_mixins_tags(collection):
|
||||
test_mixins.edit_label(collection)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from plexapi.utils import download
|
|||
from requests import Session
|
||||
|
||||
from . import conftest as utils
|
||||
from .payloads import SERVER_RESOURCES
|
||||
from .payloads import SERVER_RESOURCES, SEVER_TRANSCODE_SESSIONS
|
||||
|
||||
|
||||
def test_server_attr(plex, account):
|
||||
|
@ -458,3 +458,41 @@ def test_server_dashboard_resources(plex, requests_mock):
|
|||
assert utils.is_float(resource.processCpuUtilization, gte=0.0)
|
||||
assert utils.is_float(resource.processMemoryUtilization, gte=0.0)
|
||||
assert resource.timespan == 6 # Default seconds timespan
|
||||
|
||||
|
||||
def test_server_transcode_sessions(plex, requests_mock):
|
||||
url = plex.url("/transcode/sessions")
|
||||
requests_mock.get(url, text=SEVER_TRANSCODE_SESSIONS)
|
||||
transcode_sessions = plex.transcodeSessions()
|
||||
assert len(transcode_sessions)
|
||||
session = transcode_sessions[0]
|
||||
assert session.audioChannels == 2
|
||||
assert session.audioCodec in utils.CODECS
|
||||
assert session.audioDecision == "transcode"
|
||||
assert session.complete is False
|
||||
assert session.container in utils.CONTAINERS
|
||||
assert session.context == "streaming"
|
||||
assert utils.is_int(session.duration, gte=100000)
|
||||
assert utils.is_int(session.height, gte=480)
|
||||
assert len(session.key)
|
||||
assert utils.is_float(session.maxOffsetAvailable, gte=0.0)
|
||||
assert utils.is_float(session.minOffsetAvailable, gte=0.0)
|
||||
assert utils.is_float(session.progress)
|
||||
assert session.protocol == "dash"
|
||||
assert utils.is_int(session.remaining)
|
||||
assert utils.is_int(session.size)
|
||||
assert session.sourceAudioCodec in utils.CODECS
|
||||
assert session.sourceVideoCodec in utils.CODECS
|
||||
assert utils.is_float(session.speed)
|
||||
assert session.subtitleDecision is None
|
||||
assert session.throttled is False
|
||||
assert utils.is_float(session.timestamp, gte=1600000000)
|
||||
assert session.transcodeHwDecoding in utils.HW_DECODERS
|
||||
assert session.transcodeHwDecodingTitle == "Windows (DXVA2)"
|
||||
assert session.transcodeHwEncoding in utils.HW_ENCODERS
|
||||
assert session.transcodeHwEncodingTitle == "Intel (QuickSync)"
|
||||
assert session.transcodeHwFullPipeline is False
|
||||
assert session.transcodeHwRequested is True
|
||||
assert session.videoCodec in utils.CODECS
|
||||
assert session.videoDecision == "transcode"
|
||||
assert utils.is_int(session.width, gte=852)
|
||||
|
|
|
@ -83,7 +83,7 @@ def test_video_Movie_isFullObject_and_reload(plex):
|
|||
movie_via_section_search.reload()
|
||||
assert movie_via_section_search.isFullObject() is True
|
||||
# If the verify that the object has been reloaded. xml from search only returns 3 actors.
|
||||
assert len(movie_via_section_search.roles) > 3
|
||||
assert len(movie_via_section_search.roles) >= 3
|
||||
|
||||
|
||||
def test_video_Movie_isPartialObject(movie):
|
||||
|
@ -154,27 +154,30 @@ def test_video_Movie_attrs(movies):
|
|||
assert utils.is_datetime(movie.addedAt)
|
||||
assert utils.is_metadata(movie.art)
|
||||
assert movie.artUrl
|
||||
assert movie.audienceRating == 8.5
|
||||
# Disabled this since it failed on the last run, wasnt in the original xml result.
|
||||
# assert movie.audienceRatingImage == 'rottentomatoes://image.rating.upright'
|
||||
assert float(movie.rating) >= 6.4
|
||||
assert movie.ratingImage == 'rottentomatoes://image.rating.ripe'
|
||||
assert movie.audienceRating >= 8.5
|
||||
assert movie.audienceRatingImage == 'rottentomatoes://image.rating.upright'
|
||||
movie.reload() # RELOAD
|
||||
assert movie.chapterSource is None
|
||||
assert movie.collections == []
|
||||
assert not movie.collections
|
||||
assert movie.contentRating in utils.CONTENTRATINGS
|
||||
assert all([i.tag in ["US", "USA"] for i in movie.countries])
|
||||
assert [i.tag for i in movie.directors] == ["Nina Paley"]
|
||||
if movie.countries:
|
||||
assert "United States of America" in [i.tag for i in movie.countries]
|
||||
if movie.producers:
|
||||
assert "Nina Paley" in [i.tag for i in movie.producers]
|
||||
if movie.directors:
|
||||
assert "Nina Paley" in [i.tag for i in movie.directors]
|
||||
if movie.roles:
|
||||
assert "Reena Shah" in [i.tag for i in movie.roles]
|
||||
if movie.writers:
|
||||
assert "Nina Paley" in [i.tag for i in movie.writers]
|
||||
assert movie.duration >= 160000
|
||||
assert movie.fields == []
|
||||
assert not movie.fields
|
||||
assert movie.posters()
|
||||
assert sorted([i.tag for i in movie.genres]) == [
|
||||
"Animation",
|
||||
"Comedy",
|
||||
"Drama",
|
||||
"Fantasy",
|
||||
"Musical",
|
||||
"Romance",
|
||||
]
|
||||
assert movie.guid == "com.plexapp.agents.imdb://tt1172203?lang=en"
|
||||
assert "Animation" in [i.tag for i in movie.genres]
|
||||
assert "imdb://tt1172203" in [i.id for i in movie.guids]
|
||||
assert movie.guid == "plex://movie/5d776846880197001ec967c6"
|
||||
assert utils.is_metadata(movie._initpath)
|
||||
assert utils.is_metadata(movie.key)
|
||||
assert utils.is_datetime(movie.lastViewedAt)
|
||||
|
@ -185,16 +188,7 @@ def test_video_Movie_attrs(movies):
|
|||
assert movie.playlistItemID is None
|
||||
if movie.primaryExtraKey:
|
||||
assert utils.is_metadata(movie.primaryExtraKey)
|
||||
assert [i.tag for i in movie.producers] == []
|
||||
assert float(movie.rating) >= 6.4
|
||||
# assert movie.ratingImage == 'rottentomatoes://image.rating.ripe'
|
||||
assert movie.ratingKey >= 1
|
||||
assert set(sorted([i.tag for i in movie.roles])) >= {
|
||||
"Aladdin Ullah",
|
||||
"Annette Hanshaw",
|
||||
"Aseem Chhabra",
|
||||
"Debargo Sanyal",
|
||||
} # noqa
|
||||
assert movie._server._baseurl == utils.SERVER_BASEURL
|
||||
assert movie.sessionKey is None
|
||||
assert movie.studio == "Nina Paley"
|
||||
|
@ -210,7 +204,6 @@ def test_video_Movie_attrs(movies):
|
|||
assert movie.viewCount == 0
|
||||
assert utils.is_int(movie.viewOffset, gte=0)
|
||||
assert movie.viewedAt is None
|
||||
assert sorted([i.tag for i in movie.writers][:4]) == ["Nina Paley"] # noqa
|
||||
assert movie.year == 2008
|
||||
# Audio
|
||||
audio = movie.media[0].parts[0].audioStreams()[0]
|
||||
|
@ -328,7 +321,7 @@ def test_video_Movie_attrs(movies):
|
|||
assert part.container in utils.CONTAINERS
|
||||
assert part.decision is None
|
||||
assert part.deepAnalysisVersion is None or utils.is_int(part.deepAnalysisVersion)
|
||||
assert utils.is_int(part.duration, 160000)
|
||||
assert utils.is_int(part.duration, gte=160000)
|
||||
assert part.exists
|
||||
assert len(part.file) >= 10
|
||||
assert part.has64bitOffsets is False
|
||||
|
|
|
@ -520,8 +520,9 @@ if __name__ == "__main__":
|
|||
name="Movies",
|
||||
type="movie",
|
||||
location="/data/Movies" if opts.no_docker is False else movies_path,
|
||||
agent="com.plexapp.agents.imdb",
|
||||
scanner="Plex Movie Scanner",
|
||||
agent="tv.plex.agents.movie",
|
||||
scanner="Plex Movie",
|
||||
language='en-US',
|
||||
expected_media_count=num_movies,
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue