diff --git a/plexapi/library.py b/plexapi/library.py index 700187861..497130da0 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -219,7 +219,7 @@ def add(self, name='', type='', agent='', scanner='', location='', language='en' **Show Preferences** * **agent** (str): com.plexapp.agents.none, com.plexapp.agents.thetvdb, com.plexapp.agents.themoviedb, - tv.plex.agent.series + tv.plex.agents.series * **enableBIFGeneration** (bool): Enable video preview thumbnails. Default value true. * **episodeSort** (int): Episode order. Default -1 Possible options: 0:Oldest first, 1:Newest first. * **flattenSeasons** (int): Seasons. Default value 0 Possible options: 0:Show,1:Hide. diff --git a/plexapi/video.py b/plexapi/video.py index bb0942fb6..e8840ec51 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -605,10 +605,12 @@ class Season(Video, ArtMixin, PosterMixin, CollectionMixin): parentIndex (int): Plex index number for the show. parentKey (str): API URL of the show (/library/metadata/). parentRatingKey (int): Unique key identifying the show. + parentStudio (str): Studio that created show. parentTheme (str): URL to show theme resource (/library/metadata//theme/). parentThumb (str): URL to show thumbnail image (/library/metadata//thumb/). parentTitle (str): Name of the show for the season. viewedLeafCount (int): Number of items marked as played in the season view. + year (int): Year the season was released. """ TAG = 'Directory' TYPE = 'season' @@ -623,13 +625,15 @@ def _loadData(self, data): self.key = self.key.replace('/children', '') # FIX_BUG_50 self.leafCount = utils.cast(int, data.attrib.get('leafCount')) self.parentGuid = data.attrib.get('parentGuid') - self.parentIndex = data.attrib.get('parentIndex') + self.parentIndex = utils.cast(int, data.attrib.get('parentIndex')) self.parentKey = data.attrib.get('parentKey') self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey')) + self.parentStudio = data.attrib.get('parentStudio') self.parentTheme = data.attrib.get('parentTheme') self.parentThumb = data.attrib.get('parentThumb') self.parentTitle = data.attrib.get('parentTitle') self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) + self.year = utils.cast(int, data.attrib.get('year')) def __iter__(self): for episode in self.episodes(): @@ -756,12 +760,13 @@ class Episode(Video, Playable, ArtMixin, PosterMixin, CollectionMixin, DirectorM parentRatingKey (int): Unique key identifying the season. parentThumb (str): URL to season thumbnail image (/library/metadata//thumb/). parentTitle (str): Name of the season for the episode. + parentYear (int): Year the season was released. rating (float): Episode rating (7.9; 9.8; 8.1). skipParent (bool): True if the show's seasons are set to hidden. userRating (float): User rating (2.0; 8.0). viewOffset (int): View offset in milliseconds. writers (List<:class:`~plexapi.media.Writer`>): List of writers objects. - year (int): Year episode was released. + year (int): Year the episode was released. """ TAG = 'Video' TYPE = 'episode' @@ -798,6 +803,7 @@ def _loadData(self, data): self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey')) self.parentThumb = data.attrib.get('parentThumb') self.parentTitle = data.attrib.get('parentTitle') + self.parentYear = utils.cast(int, data.attrib.get('parentYear')) self.rating = utils.cast(float, data.attrib.get('rating')) self.skipParent = utils.cast(bool, data.attrib.get('skipParent', '0')) self.userRating = utils.cast(float, data.attrib.get('userRating')) diff --git a/tests/test_video.py b/tests/test_video.py index cfa620900..81775ff33 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -187,9 +187,9 @@ def test_video_Movie_attrs(movies): assert utils.is_datetime(movie.addedAt) if movie.art: assert utils.is_art(movie.art) - assert float(movie.rating) >= 6.4 + assert utils.is_float(movie.rating) assert movie.ratingImage == 'rottentomatoes://image.rating.ripe' - assert movie.audienceRating >= 8.5 + assert utils.is_float(movie.audienceRating) assert movie.audienceRatingImage == 'rottentomatoes://image.rating.upright' movie.reload() # RELOAD assert movie.chapterSource is None @@ -203,6 +203,7 @@ def test_video_Movie_attrs(movies): 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] + assert movie.actors == movie.roles if movie.writers: assert "Nina Paley" in [i.tag for i in movie.writers] assert movie.duration >= 160000 @@ -557,48 +558,24 @@ def test_video_Movie_hubs(movies): assert hub.size == 1 -def test_video_Show(show): - assert show.title == "Game of Thrones" - - -def test_video_Episode_updateProgress(episode, patched_http_call): - episode.updateProgress(10 * 60 * 1000) # 10 minutes. - - -def test_video_Episode_updateTimeline(episode, patched_http_call): - episode.updateTimeline( - 10 * 60 * 1000, state="playing", duration=episode.duration - ) # 10 minutes. - - -def test_video_Episode_stop(episode, mocker, patched_http_call): - mocker.patch.object( - episode, "session", return_value=list(mocker.MagicMock(id="hello")) - ) - episode.stop(reason="It's past bedtime!") - - def test_video_Show_attrs(show): assert utils.is_datetime(show.addedAt) if show.art: assert utils.is_art(show.art) - if show.banner: - assert utils.is_banner(show.banner) assert utils.is_int(show.childCount) assert show.contentRating in utils.CONTENTRATINGS assert utils.is_int(show.duration, gte=1600000) - assert utils.is_section(show._initpath) # Check reloading the show loads the full list of genres - assert not {"Adventure", "Drama"} - {i.tag for i in show.genres} show.reload() - assert show.audienceRating is None # TODO: Change when updating test to the Plex TV agent - assert show.audienceRatingImage is None # TODO: Change when updating test to the Plex TV agent + assert utils.is_float(show.audienceRating) + assert show.audienceRatingImage == "themoviedb://image.rating" assert show.autoDeletionItemPolicyUnwatchedLibrary == 0 assert show.autoDeletionItemPolicyWatchedLibrary == 0 assert show.episodeSort == -1 assert show.flattenSeasons == -1 - assert sorted([i.tag for i in show.genres]) == ["Adventure", "Drama", "Fantasy"] - assert show.guids == [] # TODO: Change when updating test to the Plex TV agent + assert "Drama" in [i.tag for i in show.genres] + assert show.guid == "plex://show/5d9c086c46115600200aa2fe" + assert "tvdb://121361" in [i.id for i in show.guids] # So the initkey should have changed because of the reload assert utils.is_metadata(show._initpath) assert utils.is_int(show.index) @@ -612,25 +589,16 @@ def test_video_Show_attrs(show): assert show.network is None assert utils.is_datetime(show.originallyAvailableAt) assert show.originalTitle is None - assert show.rating >= 8.0 + assert show.rating is None assert utils.is_int(show.ratingKey) - assert sorted([i.tag for i in show.roles])[:4] == [ - "Aidan Gillen", - "Aimee Richardson", - "Alexander Siddig", - "Alfie Allen", - ] # noqa - assert sorted([i.tag for i in show.actors])[:4] == [ - "Aidan Gillen", - "Aimee Richardson", - "Alexander Siddig", - "Alfie Allen", - ] # noqa + if show.roles: + assert "Emilia Clarke" in [i.tag for i in show.roles] + assert show.actors == show.roles assert show._server._baseurl == utils.SERVER_BASEURL assert show.showOrdering in (None, 'aired') - assert show.studio == "HBO" + assert show.studio == "Revolution Sun Studios" assert utils.is_string(show.summary, gte=100) - assert show.tagline is None + assert show.tagline == "Winter is coming." assert utils.is_metadata(show.theme, contains="/theme/") if show.thumb: assert utils.is_thumb(show.thumb) @@ -642,7 +610,7 @@ def test_video_Show_attrs(show): assert utils.is_datetime(show.updatedAt) assert utils.is_int(show.viewCount, gte=0) assert utils.is_int(show.viewedLeafCount, gte=0) - assert show.year in (2011, 2010) + assert show.year == 2011 assert show.url(None) is None @@ -754,10 +722,8 @@ def test_video_Show_mixins_edit_advanced_settings(show): def test_video_Show_mixins_images(show): test_mixins.edit_art(show) - test_mixins.edit_banner(show) test_mixins.edit_poster(show) test_mixins.attr_artUrl(show) - test_mixins.attr_bannerUrl(show) test_mixins.attr_posterUrl(show) @@ -776,6 +742,126 @@ def test_video_Show_media_tags(show): test_media.tag_similar(show) +def test_video_Season(show): + seasons = show.seasons() + assert len(seasons) == 2 + assert ["Season 1", "Season 2"] == [s.title for s in seasons[:2]] + assert show.season("Season 1") == seasons[0] + + +def test_video_Season_history(show): + season = show.season("Season 1") + season.markWatched() + history = season.history() + assert len(history) + season.markUnwatched() + + +def test_video_Season_attrs(show): + season = show.season("Season 1") + assert utils.is_datetime(season.addedAt) + if season.art: + assert utils.is_art(season.art) + assert season.guid == "plex://season/602e67d31d3358002c411c39" + assert "tvdb://364731" in [i.id for i in season.guids] + assert season.index == 1 + assert utils.is_metadata(season._initpath) + assert utils.is_metadata(season.key) + assert utils.is_datetime(season.lastViewedAt) + assert utils.is_int(season.leafCount, gte=3) + assert season.listType == "video" + assert season.parentGuid == "plex://show/5d9c086c46115600200aa2fe" + assert season.parentIndex == 1 + assert utils.is_metadata(season.parentKey) + assert utils.is_int(season.parentRatingKey) + assert season.parentStudio == "Revolution Sun Studios" + if season.parentThumb: + assert utils.is_thumb(season.parentThumb) + assert season.parentTitle == "Game of Thrones" + assert utils.is_int(season.ratingKey) + assert season._server._baseurl == utils.SERVER_BASEURL + assert utils.is_string(season.summary, gte=100) + if season.thumb: + assert utils.is_thumb(season.thumb) + assert season.title == "Season 1" + assert season.titleSort == "Season 1" + assert season.type == "season" + assert utils.is_datetime(season.updatedAt) + assert utils.is_int(season.viewCount, gte=0) + assert utils.is_int(season.viewedLeafCount, gte=0) + assert utils.is_int(season.seasonNumber) + assert season.year is None + + +def test_video_Season_show(show): + season = show.seasons()[0] + season_by_name = show.season("Season 1") + assert show.ratingKey == season.parentRatingKey and season_by_name.parentRatingKey + assert season.ratingKey == season_by_name.ratingKey + + +def test_video_Season_watched(show): + season = show.season("Season 1") + season.markWatched() + assert season.isWatched + + +def test_video_Season_unwatched(show): + season = show.season("Season 1") + season.markUnwatched() + assert not season.isWatched + + +def test_video_Season_get(show): + episode = show.season("Season 1").get("Winter Is Coming") + assert episode.title == "Winter Is Coming" + + +def test_video_Season_episode(show): + episode = show.season("Season 1").get("Winter Is Coming") + assert episode.title == "Winter Is Coming" + + +def test_video_Season_episode_by_index(show): + episode = show.season(season=1).episode(episode=1) + assert episode.index == 1 + + +def test_video_Season_episodes(show): + episodes = show.season("Season 2").episodes() + assert len(episodes) >= 1 + + +def test_video_Season_mixins_images(show): + season = show.season(season=1) + test_mixins.edit_art(season) + test_mixins.edit_poster(season) + test_mixins.attr_artUrl(season) + test_mixins.attr_posterUrl(season) + + +def test_video_Season_mixins_tags(show): + season = show.season(season=1) + test_mixins.edit_collection(season) + + +def test_video_Episode_updateProgress(episode, patched_http_call): + episode.updateProgress(10 * 60 * 1000) # 10 minutes. + + +def test_video_Episode_updateTimeline(episode, patched_http_call): + episode.updateTimeline( + 10 * 60 * 1000, state="playing", duration=episode.duration + ) # 10 minutes. + + +def test_video_Episode_stop(episode, mocker, patched_http_call): + mocker.patch.object( + episode, "session", return_value=list(mocker.MagicMock(id="hello")) + ) + episode.stop(reason="It's past bedtime!") + + def test_video_Episode(show): episode = show.episode("Winter Is Coming") assert episode == show.episode(season=1, episode=1) @@ -828,18 +914,23 @@ def test_video_Episode_attrs(episode): assert utils.is_datetime(episode.addedAt) if episode.art: assert utils.is_art(episode.art) - assert episode.audienceRating is None # TODO: Change when updating test to the Plex TV agent - assert episode.audienceRatingImage is None # TODO: Change when updating test to the Plex TV agent + assert utils.is_float(episode.audienceRating) + assert episode.audienceRatingImage == "themoviedb://image.rating" assert episode.contentRating in utils.CONTENTRATINGS - if len(episode.directors): - assert [i.tag for i in episode.directors] == ["Tim Van Patten"] + if episode.directors: + assert "Timothy Van Patten" in [i.tag for i in episode.directors] assert utils.is_int(episode.duration, gte=120000) if episode.grandparentArt: assert utils.is_art(episode.grandparentArt) + assert episode.grandparentGuid == "plex://show/5d9c086c46115600200aa2fe" + assert utils.is_metadata(episode.grandparentKey) + assert utils.is_int(episode.grandparentRatingKey) + assert utils.is_metadata(episode.grandparentTheme) if episode.grandparentThumb: assert utils.is_thumb(episode.grandparentThumb) assert episode.grandparentTitle == "Game of Thrones" - assert episode.guids == [] # TODO: Change when updating test to the Plex TV agent + 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 @@ -847,13 +938,16 @@ def test_video_Episode_attrs(episode): assert utils.is_metadata(episode.key) assert episode.listType == "video" assert utils.is_datetime(episode.originallyAvailableAt) - assert episode.parentIndex == 1 + 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: assert utils.is_thumb(episode.parentThumb) - assert episode.rating >= 7.7 + assert episode.parentTitle == "Season 1" + assert episode.parentYear is None + assert episode.rating is None assert utils.is_int(episode.ratingKey) assert episode._server._baseurl == utils.SERVER_BASEURL assert episode.skipParent is False @@ -865,12 +959,12 @@ def test_video_Episode_attrs(episode): assert not episode.transcodeSessions assert episode.type == "episode" assert utils.is_datetime(episode.updatedAt) + assert episode.userRating is None assert utils.is_int(episode.viewCount, gte=0) assert episode.viewOffset == 0 - assert sorted([i.tag for i in episode.writers]) == sorted( - ["David Benioff", "D. B. Weiss"] - ) - assert episode.year == 2011 + if episode.writers: + assert "D. B. Weiss" in [i.tag for i in episode.writers] + assert episode.year is None assert episode.isWatched in [True, False] assert len(episode.locations) == 1 assert len(episode.locations[0]) >= 10 @@ -947,110 +1041,6 @@ def test_video_Episode_media_tags(episode): test_media.tag_writer(episode) -def test_video_Season(show): - seasons = show.seasons() - assert len(seasons) == 2 - assert ["Season 1", "Season 2"] == [s.title for s in seasons[:2]] - assert show.season("Season 1") == seasons[0] - - -def test_video_Season_history(show): - season = show.season("Season 1") - season.markWatched() - history = season.history() - assert len(history) - season.markUnwatched() - - -def test_video_Season_attrs(show): - season = show.season("Season 1") - assert utils.is_datetime(season.addedAt) - if season.art: - assert utils.is_art(season.art) - assert season.guids == [] # TODO: Change when updating test to the Plex TV agent - assert season.index == 1 - assert utils.is_metadata(season._initpath) - assert utils.is_metadata(season.key) - assert utils.is_datetime(season.lastViewedAt) - assert utils.is_int(season.leafCount, gte=3) - assert season.listType == "video" - assert utils.is_metadata(season.parentKey) - assert utils.is_int(season.parentRatingKey) - if season.parentThumb: - assert utils.is_thumb(season.parentThumb) - assert season.parentTitle == "Game of Thrones" - assert utils.is_int(season.ratingKey) - assert season._server._baseurl == utils.SERVER_BASEURL - assert season.summary == "" - if season.thumb: - assert utils.is_thumb(season.thumb) - assert season.title == "Season 1" - assert season.titleSort == "Season 1" - assert season.type == "season" - assert utils.is_datetime(season.updatedAt) - assert utils.is_int(season.viewCount, gte=0) - assert utils.is_int(season.viewedLeafCount, gte=0) - assert utils.is_int(season.seasonNumber) - - -def test_video_Season_show(show): - season = show.seasons()[0] - season_by_name = show.season("Season 1") - assert show.ratingKey == season.parentRatingKey and season_by_name.parentRatingKey - assert season.ratingKey == season_by_name.ratingKey - - -def test_video_Season_watched(show): - season = show.season("Season 1") - season.markWatched() - assert season.isWatched - - -def test_video_Season_unwatched(show): - season = show.season("Season 1") - season.markUnwatched() - assert not season.isWatched - - -def test_video_Season_get(show): - episode = show.season("Season 1").get("Winter Is Coming") - assert episode.title == "Winter Is Coming" - - -def test_video_Season_episode(show): - episode = show.season("Season 1").get("Winter Is Coming") - assert episode.title == "Winter Is Coming" - - -def test_video_Season_episode_by_index(show): - episode = show.season(season=1).episode(episode=1) - assert episode.index == 1 - - -def test_video_Season_episodes(show): - episodes = show.season("Season 2").episodes() - assert len(episodes) >= 1 - - -def test_video_Season_mixins_images(show): - season = show.season(season=1) - test_mixins.edit_art(season) - test_mixins.edit_poster(season) - test_mixins.attr_artUrl(season) - test_mixins.attr_posterUrl(season) - - -def test_video_Season_mixins_tags(show): - season = show.season(season=1) - test_mixins.edit_collection(season) - - -def test_video_Season_media_tags(show): - season = show.season(season=1) - season.reload() - test_media.tag_collection(season) - - 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] diff --git a/tools/plex-bootstraptest.py b/tools/plex-bootstraptest.py index a2835aff3..46cb00334 100755 --- a/tools/plex-bootstraptest.py +++ b/tools/plex-bootstraptest.py @@ -522,7 +522,7 @@ def alert_callback(data): location="/data/Movies" if opts.no_docker is False else movies_path, agent="tv.plex.agents.movie", scanner="Plex Movie", - language='en-US', + language="en-US", expected_media_count=num_movies, ) ) @@ -537,8 +537,9 @@ def alert_callback(data): name="TV Shows", type="show", location="/data/TV-Shows" if opts.no_docker is False else tvshows_path, - agent="com.plexapp.agents.thetvdb", - scanner="Plex Series Scanner", + agent="tv.plex.agents.series", + scanner="Plex TV Series", + language="en-US", expected_media_count=num_ep, ) )