Merge remote-tracking branch 'upstream/master' into feature/plex_tv

This commit is contained in:
JonnyWong16 2021-05-23 19:12:38 -07:00
commit a9597a9008
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
14 changed files with 347 additions and 146 deletions

View file

@ -408,6 +408,11 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, CollectionMixin, MoodM
"""
return [part.file for part in self.iterParts() if part]
@property
def trackNumber(self):
""" Returns the track number. """
return self.index
def _defaultSyncTitle(self):
""" Returns str, default title for a new syncItem. """
return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title)

View file

@ -1698,6 +1698,12 @@ class HubMediaTag(PlexObject):
self.tagValue = utils.cast(int, data.attrib.get('tagValue'))
self.thumb = data.attrib.get('thumb')
def items(self, *args, **kwargs):
""" Return the list of items within this tag. """
if not self.key:
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
return self.fetchItems(self.key)
@utils.registerPlexObject
class Tag(HubMediaTag):

View file

@ -112,7 +112,7 @@ class MediaPart(PlexObject):
has64bitOffsets (bool): True if the file has 64 bit offsets.
hasThumbnail (bool): True if the file (track) has an embedded thumbnail.
id (int): The unique ID for this media part on the server.
indexes (str, None): sd if the file has generated BIF thumbnails.
indexes (str, None): sd if the file has generated preview (BIF) thumbnails.
key (str): API URL (ex: /library/parts/46618/1389985872/file.mkv).
optimizedForStreaming (bool): True if the file is optimized for streaming.
packetLength (int): The packet length of the file.
@ -157,6 +157,11 @@ class MediaPart(PlexObject):
streams.extend(items)
return streams
@property
def hasPreviewThumbnails(self):
""" Returns True if the media part has generated preview (BIF) thumbnails. """
return self.indexes == 'sd'
def videoStreams(self):
""" Returns a list of :class:`~plexapi.media.VideoStream` objects in this MediaPart. """
return [stream for stream in self.streams if isinstance(stream, VideoStream)]
@ -558,6 +563,13 @@ class Optimized(PlexObject):
self.target = data.attrib.get('target')
self.targetTagID = data.attrib.get('targetTagID')
def items(self):
""" Returns a list of all :class:`~plexapi.media.Video` objects
in this optimized item.
"""
key = '%s/%s/items' % (self._initpath, self.id)
return self.fetchItems(key)
def remove(self):
""" Remove an Optimized item"""
key = '%s/%s' % (self._initpath, self.id)
@ -641,59 +653,43 @@ class MediaTag(PlexObject):
the construct used for things such as Country, Director, Genre, etc.
Attributes:
server (:class:`~plexapi.server.PlexServer`): Server this client is connected to.
filter (str): The library filter for the tag.
id (id): Tag ID (This seems meaningless except to use it as a unique id).
role (str): Unknown
key (str): API URL (/library/section/<librarySectionID>/all?<filter>).
role (str): The name of the character role for :class:`~plexapi.media.Role` only.
tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of
person for Directors and Roles (ex: Animation, Stephen Graham, etc).
<Hub_Search_Attributes>: Attributes only applicable in search results from
PlexServer :func:`~plexapi.server.PlexServer.search`. They provide details of which
library section the tag was found as well as the url to dig deeper into the results.
* key (str): API URL to dig deeper into this tag (ex: /library/sections/1/all?actor=9081).
* librarySectionID (int): Section ID this tag was generated from.
* librarySectionTitle (str): Library section title this tag was found.
* librarySectionType (str): Media type of the library section this tag was found.
* tagType (int): Tag type ID.
* thumb (str): URL to thumbnail image.
thumb (str): URL to thumbnail image for :class:`~plexapi.media.Role` only.
"""
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.filter = data.attrib.get('filter')
self.id = cast(int, data.attrib.get('id'))
self.key = data.attrib.get('key')
self.role = data.attrib.get('role')
self.tag = data.attrib.get('tag')
# additional attributes only from hub search
self.key = data.attrib.get('key')
self.librarySectionID = cast(int, data.attrib.get('librarySectionID'))
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.librarySectionType = data.attrib.get('librarySectionType')
self.tagType = cast(int, data.attrib.get('tagType'))
self.thumb = data.attrib.get('thumb')
def items(self, *args, **kwargs):
""" Return the list of items within this tag. This function is only applicable
in search results from PlexServer :func:`~plexapi.server.PlexServer.search`.
"""
parent = self._parent()
self._librarySectionID = utils.cast(int, parent._data.attrib.get('librarySectionID'))
self._librarySectionKey = parent._data.attrib.get('librarySectionKey')
self._librarySectionTitle = parent._data.attrib.get('librarySectionTitle')
self._parentType = parent.TYPE
if self._librarySectionKey and self.filter:
self.key = '%s/all?%s&type=%s' % (
self._librarySectionKey, self.filter, utils.searchType(self._parentType))
def items(self):
""" Return the list of items within this tag. """
if not self.key:
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
raise BadRequest('Key is not defined for this tag: %s. '
'Reload the parent object.' % self.tag)
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.
@ -705,36 +701,11 @@ class Collection(MediaTag):
TAG = 'Collection'
FILTER = 'collection'
@utils.registerPlexObject
class Label(MediaTag):
""" Represents a single Label media tag.
Attributes:
TAG (str): 'Label'
FILTER (str): 'label'
"""
TAG = 'Label'
FILTER = 'label'
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single Tag media tag.
Attributes:
TAG (str): 'Tag'
FILTER (str): 'tag'
"""
TAG = 'Tag'
FILTER = 'tag'
def _loadData(self, data):
self._data = data
self.id = cast(int, data.attrib.get('id', 0))
self.filter = data.attrib.get('filter')
self.tag = data.attrib.get('tag')
self.title = self.tag
def collection(self):
""" Return the :class:`~plexapi.collection.Collection` object for this collection tag.
"""
key = '%s/collections' % self._librarySectionKey
return self.fetchItem(key, etag='Directory', index=self.id)
@utils.registerPlexObject
@ -774,13 +745,15 @@ class Genre(MediaTag):
@utils.registerPlexObject
class Guid(GuidTag):
""" Represents a single Guid media tag.
class Label(MediaTag):
""" Represents a single Label media tag.
Attributes:
TAG (str): 'Guid'
TAG (str): 'Label'
FILTER (str): 'label'
"""
TAG = "Guid"
TAG = 'Label'
FILTER = 'label'
@utils.registerPlexObject
@ -795,6 +768,42 @@ class Mood(MediaTag):
FILTER = 'mood'
@utils.registerPlexObject
class Producer(MediaTag):
""" Represents a single Producer media tag.
Attributes:
TAG (str): 'Producer'
FILTER (str): 'producer'
"""
TAG = 'Producer'
FILTER = 'producer'
@utils.registerPlexObject
class Role(MediaTag):
""" Represents a single Role (actor/actress) media tag.
Attributes:
TAG (str): 'Role'
FILTER (str): 'role'
"""
TAG = 'Role'
FILTER = 'role'
@utils.registerPlexObject
class Similar(MediaTag):
""" Represents a single Similar media tag.
Attributes:
TAG (str): 'Similar'
FILTER (str): 'similar'
"""
TAG = 'Similar'
FILTER = 'similar'
@utils.registerPlexObject
class Style(MediaTag):
""" Represents a single Style media tag.
@ -807,6 +816,53 @@ class Style(MediaTag):
FILTER = 'style'
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single Tag media tag.
Attributes:
TAG (str): 'Tag'
FILTER (str): 'tag'
"""
TAG = 'Tag'
FILTER = 'tag'
@utils.registerPlexObject
class Writer(MediaTag):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Writer'
FILTER (str): 'writer'
"""
TAG = 'Writer'
FILTER = 'writer'
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 Guid(GuidTag):
""" Represents a single Guid media tag.
Attributes:
TAG (str): 'Guid'
"""
TAG = 'Guid'
class BaseImage(PlexObject):
""" Base class for all Art, Banner, and Poster objects.
@ -849,54 +905,6 @@ class Poster(BaseImage):
""" Represents a single Poster object. """
@utils.registerPlexObject
class Producer(MediaTag):
""" Represents a single Producer media tag.
Attributes:
TAG (str): 'Producer'
FILTER (str): 'producer'
"""
TAG = 'Producer'
FILTER = 'producer'
@utils.registerPlexObject
class Role(MediaTag):
""" Represents a single Role (actor/actress) media tag.
Attributes:
TAG (str): 'Role'
FILTER (str): 'role'
"""
TAG = 'Role'
FILTER = 'role'
@utils.registerPlexObject
class Similar(MediaTag):
""" Represents a single Similar media tag.
Attributes:
TAG (str): 'Similar'
FILTER (str): 'similar'
"""
TAG = 'Similar'
FILTER = 'similar'
@utils.registerPlexObject
class Writer(MediaTag):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Writer'
FILTER (str): 'writer'
"""
TAG = 'Writer'
FILTER = 'writer'
@utils.registerPlexObject
class Chapter(PlexObject):
""" Represents a single Writer media tag.

View file

@ -470,6 +470,7 @@ class MyPlexAccount(PlexObject):
Parameters:
username (str): Username, email or id of the user to return.
"""
username = str(username)
for user in self.users():
# Home users don't have email, username etc.
if username.lower() == user.title.lower():

View file

@ -253,15 +253,13 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin):
return cls(server, data, initpath=key)
def copyToUser(self, user):
""" Copy playlist to another user account. """
from plexapi.server import PlexServer
myplex = self._server.myPlexAccount()
user = myplex.user(user)
# Get the token for your machine.
token = user.get_token(self._server.machineIdentifier)
# Login to your server using your friends credentials.
user_server = PlexServer(self._server._baseurl, token)
return self.create(user_server, self.title, self.items())
""" Copy playlist to another user account.
Parameters:
user (str): Username, email or user id of the user to copy the playlist to.
"""
userServer = self._server.switchUser(user)
return self.create(userServer, self.title, self.items())
def sync(self, videoQuality=None, photoResolution=None, audioBitrate=None, client=None, clientId=None, limit=None,
unwatched=False, title=None):

View file

@ -38,8 +38,9 @@ class PlexServer(PlexObject):
baseurl (str): Base url for to access the Plex Media Server (default: 'http://localhost:32400').
token (str): Required Plex authentication token to access the server.
session (requests.Session, optional): Use your own session object if you want to
cache the http responses from PMS
timeout (int): timeout in seconds on initial connect to server (default config.TIMEOUT).
cache the http responses from the server.
timeout (int, optional): Timeout in seconds on initial connection to the server
(default config.TIMEOUT).
Attributes:
allowCameraUpload (bool): True if server allows camera upload.
@ -105,12 +106,13 @@ class PlexServer(PlexObject):
self._token = logfilter.add_secret(token or CONFIG.get('auth.server_token'))
self._showSecrets = CONFIG.get('log.show_secrets', '').lower() == 'true'
self._session = session or requests.Session()
self._timeout = timeout
self._library = None # cached library
self._settings = None # cached settings
self._myPlexAccount = None # cached myPlexAccount
self._systemAccounts = None # cached list of SystemAccount
self._systemDevices = None # cached list of SystemDevice
data = self.query(self.key, timeout=timeout)
data = self.query(self.key, timeout=self._timeout)
super(PlexServer, self).__init__(self, data, self.key)
def _loadData(self, data):
@ -209,13 +211,45 @@ class PlexServer(PlexObject):
return self.fetchItems(key)
def createToken(self, type='delegation', scope='all'):
"""Create a temp access token for the server."""
""" Create a temp access token for the server. """
if not self._token:
# Handle unclaimed servers
return None
q = self.query('/security/token?type=%s&scope=%s' % (type, scope))
return q.attrib.get('token')
def switchUser(self, username, session=None, timeout=None):
""" Returns a new :class:`~plexapi.server.PlexServer` object logged in as the given username.
Note: Only the admin account can switch to other users.
Parameters:
username (str): Username, email or user id of the user to log in to the server.
session (requests.Session, optional): Use your own session object if you want to
cache the http responses from the server. This will default to the same
session as the admin account if no new session is provided.
timeout (int, optional): Timeout in seconds on initial connection to the server.
This will default to the same timeout as the admin account if no new timeout
is provided.
Example:
.. code-block:: python
from plexapi.server import PlexServer
# Login to the Plex server using the admin token
plex = PlexServer('http://plexserver:32400', token='2ffLuB84dqLswk9skLos')
# Login to the same Plex server using a different account
userPlex = plex.switchUser("Username")
"""
user = self.myPlexAccount().user(username)
userToken = user.get_token(self.machineIdentifier)
if session is None:
session = self._session
if timeout is None:
timeout = self._timeout
return PlexServer(self._baseurl, token=userToken, session=session, timeout=timeout)
def systemAccounts(self):
""" Returns a list of :class:`~plexapi.server.SystemAccount` objects this server contains. """
if self._systemAccounts is None:
@ -489,6 +523,7 @@ class PlexServer(PlexObject):
backgroundProcessing = self.fetchItem('/playlists?type=42')
return self.fetchItems('%s/items' % backgroundProcessing.key, cls=Optimized)
@deprecated('use "plexapi.media.Optimized.items()" instead')
def optimizedItem(self, optimizedID):
""" Returns single queued optimized item :class:`~plexapi.media.Video` object.
Allows for using optimized item ID to connect back to source item.

View file

@ -340,6 +340,11 @@ class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, Split
"""
return [part.file for part in self.iterParts() if part]
@property
def hasPreviewThumbnails(self):
""" Returns True if any of the media parts has generated preview (BIF) thumbnails. """
return any(part.hasPreviewThumbnails for media in self.media for part in media.parts)
def _prettyfilename(self):
# This is just for compat.
return self.title
@ -648,7 +653,7 @@ class Season(Video, ArtMixin, PosterMixin, CollectionMixin):
@property
def seasonNumber(self):
""" Returns season number. """
""" Returns the season number. """
return self.index
def episodes(self, **kwargs):
@ -839,17 +844,22 @@ class Episode(Video, Playable, ArtMixin, PosterMixin, CollectionMixin, DirectorM
"""
return [part.file for part in self.iterParts() if part]
@property
def episodeNumber(self):
""" Returns the episode number. """
return self.index
@property
def seasonNumber(self):
""" Returns the episodes season number. """
""" Returns the episode's season number. """
if self._seasonNumber is None:
self._seasonNumber = self.parentIndex if self.parentIndex else self.season().seasonNumber
return utils.cast(int, self._seasonNumber)
@property
def seasonEpisode(self):
""" Returns the s00e00 string containing the season and episode. """
return 's%se%s' % (str(self.seasonNumber).zfill(2), str(self.index).zfill(2))
""" Returns the s00e00 string containing the season and episode numbers. """
return 's%se%s' % (str(self.seasonNumber).zfill(2), str(self.episodeNumber).zfill(2))
@property
def hasIntroMarker(self):
@ -858,6 +868,11 @@ class Episode(Video, Playable, ArtMixin, PosterMixin, CollectionMixin, DirectorM
self.reload()
return any(marker.type == 'intro' for marker in self.markers)
@property
def hasPreviewThumbnails(self):
""" Returns True if any of the media parts has generated preview (BIF) thumbnails. """
return any(part.hasPreviewThumbnails for media in self.media for part in media.parts)
def season(self):
"""" Return the episode's :class:`~plexapi.video.Season`. """
return self.fetchItem(self.parentKey)

View file

@ -234,11 +234,11 @@ def movie(movies):
@pytest.fixture()
def collection(movies):
try:
return movies.collections(title="marvel")[0]
return movies.collections(title="Marvel")[0]
except IndexError:
movie = movies.get("Elephants Dream")
movie.addCollection("marvel")
return movies.collections(title="marvel")[0]
movie.addCollection("Marvel")
return movies.collections(title="Marvel")[0]
@pytest.fixture()

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from . import conftest as utils
from . import test_mixins
from . import test_media, test_mixins
def test_audio_Artist_attr(artist):
@ -87,6 +87,16 @@ def test_audio_Artist_mixins_tags(artist):
test_mixins.edit_style(artist)
def test_audio_Artist_media_tags(artist):
artist.reload()
test_media.tag_collection(artist)
test_media.tag_country(artist)
test_media.tag_genre(artist)
test_media.tag_mood(artist)
test_media.tag_similar(artist)
test_media.tag_style(artist)
def test_audio_Album_attrs(album):
assert utils.is_datetime(album.addedAt)
if album.art:
@ -165,6 +175,15 @@ def test_audio_Album_mixins_tags(album):
test_mixins.edit_style(album)
def test_audio_Album_media_tags(album):
album.reload()
test_media.tag_collection(album)
test_media.tag_genre(album)
test_media.tag_label(album)
test_media.tag_mood(album)
test_media.tag_style(album)
def test_audio_Track_attrs(album):
track = album.get("As Colourful As Ever").reload()
assert utils.is_datetime(track.addedAt)
@ -180,7 +199,8 @@ def test_audio_Track_attrs(album):
assert utils.is_thumb(track.grandparentThumb)
assert track.grandparentTitle == "Broke For Free"
assert track.guid.startswith("mbid://") or track.guid.startswith("plex://track/")
assert int(track.index) == 1
assert track.index == 1
assert track.trackNumber == track.index
assert utils.is_metadata(track._initpath)
assert utils.is_metadata(track.key)
assert utils.is_datetime(track.lastViewedAt)
@ -294,6 +314,12 @@ def test_audio_Track_mixins_tags(track):
test_mixins.edit_mood(track)
def test_audio_Track_media_tags(track):
track.reload()
test_media.tag_collection(track)
test_media.tag_mood(track)
def test_audio_Audio_section(artist, album, track):
assert artist.section()
assert album.section()

View file

@ -33,7 +33,7 @@ def test_Collection_attrs(collection):
assert collection.summary == ""
assert collection.thumb.startswith("/library/collections/%s/composite" % collection.ratingKey)
assert collection.thumbBlurHash is None
assert collection.title == "marvel"
assert collection.title == "Marvel"
assert collection.titleSort == collection.title
assert collection.type == "collection"
assert utils.is_datetime(collection.updatedAt)

54
tests/test_media.py Normal file
View file

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
def _test_media_tag(obj, attr):
tags = getattr(obj, attr)
if tags:
assert obj in tags[0].items()
def tag_collection(obj):
_test_media_tag(obj, "collections")
def tag_country(obj):
_test_media_tag(obj, "countries")
def tag_director(obj):
_test_media_tag(obj, "directors")
def tag_genre(obj):
_test_media_tag(obj, "genres")
def tag_label(obj):
_test_media_tag(obj, "labels")
def tag_mood(obj):
_test_media_tag(obj, "moods")
def tag_producer(obj):
_test_media_tag(obj, "producers")
def tag_role(obj):
_test_media_tag(obj, "roles")
def tag_similar(obj):
_test_media_tag(obj, "similar")
def tag_style(obj):
_test_media_tag(obj, "styles")
def tag_tag(obj):
_test_media_tag(obj, "tags")
def tag_writer(obj):
_test_media_tag(obj, "writers")

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from . import test_mixins
from . import test_media, test_mixins
def test_photo_Photoalbum(photoalbum):
@ -20,3 +20,8 @@ def test_photo_Photoalbum_mixins_images(photoalbum):
def test_photo_Photo_mixins_tags(photo):
test_mixins.edit_tag(photo)
def test_photo_Photo_media_tags(photo):
photo.reload()
test_media.tag_tag(photo)

View file

@ -135,6 +135,7 @@ def test_server_search(plex, movie):
assert hub_tag.tagType == 1
assert hub_tag.tagValue is None
assert hub_tag.thumb is None
assert movie in hub_tag.items()
# Test director search
director = movie.directors[0]
assert plex.search(director.tag, mediatype="director")

View file

@ -8,7 +8,7 @@ import pytest
from plexapi.exceptions import BadRequest, NotFound
from . import conftest as utils
from . import test_mixins
from . import test_media, test_mixins
def test_video_Movie(movies, movie):
@ -59,6 +59,30 @@ def test_video_Movie_mixins_tags(movie):
test_mixins.edit_writer(movie)
def test_video_Movie_media_tags(movie):
movie.reload()
test_media.tag_collection(movie)
test_media.tag_country(movie)
test_media.tag_director(movie)
test_media.tag_genre(movie)
test_media.tag_label(movie)
test_media.tag_producer(movie)
test_media.tag_role(movie)
test_media.tag_similar(movie)
test_media.tag_writer(movie)
def test_video_Movie_media_tags_Exception(movie):
with pytest.raises(BadRequest):
movie.genres[0].items()
def test_video_Movie_media_tags_collection(movie, collection):
movie.reload()
collection_tag = next(c for c in movie.collections if c.tag == "Marvel")
assert collection == collection_tag.collection()
def test_video_Movie_getStreamURL(movie, account):
key = movie.ratingKey
assert movie.getStreamURL() == (
@ -188,6 +212,7 @@ def test_video_Movie_attrs(movies):
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 movie.hasPreviewThumbnails is False
assert utils.is_metadata(movie._initpath)
assert utils.is_metadata(movie.key)
assert movie.languageOverride is None
@ -338,6 +363,7 @@ def test_video_Movie_attrs(movies):
assert part.exists
assert len(part.file) >= 10
assert part.has64bitOffsets is False
assert part.hasPreviewThumbnails is False
assert part.hasThumbnail is None
assert utils.is_int(part.id)
assert part.indexes is None
@ -707,6 +733,15 @@ def test_video_Show_mixins_tags(show):
test_mixins.edit_label(show)
def test_video_Show_media_tags(show):
show.reload()
test_media.tag_collection(show)
test_media.tag_genre(show)
test_media.tag_label(show)
test_media.tag_role(show)
test_media.tag_similar(show)
def test_video_Season(show):
seasons = show.seasons()
assert len(seasons) == 2
@ -896,13 +931,16 @@ def test_video_Episode_attrs(episode):
assert episode.grandparentTitle == "Game of Thrones"
assert episode.guid == "plex://episode/5d9c1275e98e47001eb84029"
assert "tvdb://3254641" in [i.id for i in episode.guids]
assert episode.hasPreviewThumbnails is False
assert episode.index == 1
assert episode.episodeNumber == episode.index
assert utils.is_metadata(episode._initpath)
assert utils.is_metadata(episode.key)
assert episode.listType == "video"
assert utils.is_datetime(episode.originallyAvailableAt)
assert episode.parentGuid == "plex://season/602e67d31d3358002c411c39"
assert utils.is_int(episode.parentIndex)
assert episode.seasonNumber == episode.parentIndex
assert utils.is_metadata(episode.parentKey)
assert utils.is_int(episode.parentRatingKey)
if episode.parentThumb:
@ -930,6 +968,7 @@ def test_video_Episode_attrs(episode):
assert episode.isWatched in [True, False]
assert len(episode.locations) == 1
assert len(episode.locations[0]) >= 10
assert episode.seasonEpisode == "s01e01"
# Media
media = episode.media[0]
assert media.aspectRatio == 1.78
@ -953,6 +992,7 @@ def test_video_Episode_attrs(episode):
assert part.container in utils.CONTAINERS
assert utils.is_int(part.duration, gte=150000)
assert len(part.file) >= 10
assert part.hasPreviewThumbnails is False
assert utils.is_int(part.id)
assert utils.is_metadata(part._initpath)
assert len(part.key) >= 10
@ -994,6 +1034,13 @@ def test_video_Episode_mixins_tags(episode):
test_mixins.edit_writer(episode)
def test_video_Episode_media_tags(episode):
episode.reload()
test_media.tag_collection(episode)
test_media.tag_director(episode)
test_media.tag_writer(episode)
def test_that_reload_return_the_same_object(plex):
# we want to check this that all the urls are correct
movie_library_search = plex.library.section("Movies").search("Elephants Dream")[0]
@ -1104,7 +1151,7 @@ def test_video_optimize(movie, plex):
assert len(plex.conversions()) == 0
assert len(plex.optimizedItems()) == 1
optimized = plex.optimizedItems()[0]
video = plex.optimizedItem(optimizedID=optimized.id)
assert movie.key == video.key
videos = optimized.items()
assert movie in videos
plex.optimizedItems(removeAll=True)
assert len(plex.optimizedItems()) == 0