Add PlayedUnplayedMixin (#984)

* Add PlayedUnplayedMixin

* Deprecate Video isPlayed, markWatched, and markUnwatched

* Rename all deprecated methods

* Return self to enable method chaining

* Update tests for audio markPlayed / markUnplayed using history

* Remove duplicate video history tests
This commit is contained in:
JonnyWong16 2022-08-26 12:14:24 -07:00 committed by GitHub
parent 716d8edb52
commit f0ed19c2fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 132 additions and 131 deletions

View file

@ -81,8 +81,8 @@ Usage Examples
.. code-block:: python
# Example 2: Mark all Game of Thrones episodes watched.
plex.library.section('TV Shows').get('Game of Thrones').markWatched()
# Example 2: Mark all Game of Thrones episodes as played.
plex.library.section('TV Shows').get('Game of Thrones').markPlayed()
.. code-block:: python

View file

@ -6,7 +6,7 @@ from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject, PlexSession
from plexapi.exceptions import BadRequest
from plexapi.mixins import (
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin,
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, PlayedUnplayedMixin, RatingMixin,
ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, ThemeMixin, ThemeUrlMixin,
OriginallyAvailableMixin, SortTitleMixin, StudioMixin, SummaryMixin, TitleMixin,
TrackArtistMixin, TrackDiscNumberMixin, TrackNumberMixin,
@ -15,7 +15,7 @@ from plexapi.mixins import (
from plexapi.playlist import Playlist
class Audio(PlexPartialObject):
class Audio(PlexPartialObject, PlayedUnplayedMixin):
""" Base class for all audio objects including :class:`~plexapi.audio.Artist`,
:class:`~plexapi.audio.Album`, and :class:`~plexapi.audio.Track`.

View file

@ -786,7 +786,8 @@ class Playable:
""" Set the watched progress for this video.
Note that setting the time to 0 will not work.
Use `markWatched` or `markUnwatched` to achieve
Use :func:`~plexapi.mixins.PlayedMixin.markPlayed` or
:func:`~plexapi.mixins.PlayedMixin.markUnplayed` to achieve
that goal.
Parameters:

View file

@ -255,6 +255,45 @@ class HubsMixin:
return self.findItems(data, Hub)
class PlayedUnplayedMixin:
""" Mixin for Plex objects that can be marked played and unplayed. """
@property
def isPlayed(self):
""" Returns True if this video is played. """
return bool(self.viewCount > 0) if self.viewCount else False
def markPlayed(self):
""" Mark the Plex object as played. """
key = '/:/scrobble'
params = {'key': self.ratingKey, 'identifier': 'com.plexapp.plugins.library'}
self._server.query(key, params=params)
return self
def markUnplayed(self):
""" Mark the Plex object as unplayed. """
key = '/:/unscrobble'
params = {'key': self.ratingKey, 'identifier': 'com.plexapp.plugins.library'}
self._server.query(key, params=params)
return self
@property
@deprecated('use "isPlayed" instead', stacklevel=3)
def isWatched(self):
""" Returns True if the show is watched. """
return self.isPlayed
@deprecated('use "markPlayed" instead')
def markWatched(self):
""" Mark the video as played. """
self.markPlayed()
@deprecated('use "markUnplayed" instead')
def markUnwatched(self):
""" Mark the video as unplayed. """
self.markUnplayed()
class RatingMixin:
""" Mixin for Plex objects that can have user star ratings. """

View file

@ -6,7 +6,7 @@ from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject, PlexSession
from plexapi.exceptions import BadRequest
from plexapi.mixins import (
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin,
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, PlayedUnplayedMixin, RatingMixin,
ArtUrlMixin, ArtMixin, BannerMixin, PosterUrlMixin, PosterMixin, ThemeUrlMixin, ThemeMixin,
ContentRatingMixin, OriginallyAvailableMixin, OriginalTitleMixin, SortTitleMixin, StudioMixin,
SummaryMixin, TaglineMixin, TitleMixin,
@ -15,7 +15,7 @@ from plexapi.mixins import (
)
class Video(PlexPartialObject):
class Video(PlexPartialObject, PlayedUnplayedMixin):
""" Base class for all video objects including :class:`~plexapi.video.Movie`,
:class:`~plexapi.video.Show`, :class:`~plexapi.video.Season`,
:class:`~plexapi.video.Episode`, and :class:`~plexapi.video.Clip`.
@ -71,25 +71,10 @@ class Video(PlexPartialObject):
self.userRating = utils.cast(float, data.attrib.get('userRating'))
self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0))
@property
def isWatched(self):
""" Returns True if this video is watched. """
return bool(self.viewCount > 0) if self.viewCount else False
def url(self, part):
""" Returns the full url for something. Typically used for getting a specific image. """
return self._server.url(part, includeToken=True) if part else None
def markWatched(self):
""" Mark the video as played. """
key = '/:/scrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
self._server.query(key)
def markUnwatched(self):
""" Mark the video as unplayed. """
key = '/:/unscrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
self._server.query(key)
def augmentation(self):
""" Returns a list of :class:`~plexapi.library.Hub` objects.
Augmentation returns hub items relating to online media sources
@ -523,8 +508,8 @@ class Show(
return self.roles
@property
def isWatched(self):
""" Returns True if the show is fully watched. """
def isPlayed(self):
""" Returns True if the show is fully played. """
return bool(self.viewedLeafCount == self.leafCount)
def onDeck(self):
@ -678,8 +663,8 @@ class Season(
] if p])
@property
def isWatched(self):
""" Returns True if the season is fully watched. """
def isPlayed(self):
""" Returns True if the season is fully played. """
return bool(self.viewedLeafCount == self.leafCount)
@property

View file

@ -1,20 +1,6 @@
# -*- coding: utf-8 -*-
def test_mark_movie_watched(movie):
movie.markUnwatched()
print('Marking movie watched: %s' % movie)
print('View count: %s' % movie.viewCount)
movie.markWatched()
movie.reload()
print('View count: %s' % movie.viewCount)
assert movie.viewCount == 1, 'View count 0 after watched.'
movie.markUnwatched()
movie.reload()
print('View count: %s' % movie.viewCount)
assert movie.viewCount == 0, 'View count 1 after unwatched.'
def test_refresh_section(tvshows):
tvshows.refresh()

View file

@ -111,8 +111,8 @@ def test_client_playback(plex, client, movies, proxy):
client.stop(mtype)
time.sleep(1)
finally:
print("movie.markWatched")
movie.markWatched()
print("movie.markPlayed")
movie.markPlayed()
time.sleep(2)
@ -138,6 +138,6 @@ def test_client_timeline(plex, client, movies, proxy):
time.sleep(10)
assert client.isPlayingMedia() is False
finally:
print("movie.markWatched()")
movie.markWatched()
print("movie.markPlayed()")
movie.markPlayed()
time.sleep(2)

View file

@ -161,7 +161,7 @@ def test_Collection_add_move_remove(collection, movies):
assert movie not in collection
# Reset collection sort due to bug with corrupted XML response
# for movies that have been moved in a collection and have
# progress (updateProgress) or marked as played (markWatched)
# progress (updateProgress) or marked as played (markPlayed)
collection.sortUpdate("release")

View file

@ -2,76 +2,85 @@
def test_history_Movie(movie):
movie.markWatched()
movie.markPlayed()
history = movie.history()
assert len(history)
movie.markUnwatched()
movie.markUnplayed()
def test_history_Show(show):
show.markWatched()
show.markPlayed()
history = show.history()
assert len(history)
show.markUnwatched()
show.markUnplayed()
def test_history_Season(show):
season = show.season("Season 1")
season.markWatched()
season.markPlayed()
history = season.history()
assert len(history)
season.markUnwatched()
season.markUnplayed()
def test_history_Episode(episode):
episode.markWatched()
episode.markPlayed()
history = episode.history()
assert len(history)
episode.markUnwatched()
episode.markUnplayed()
def test_history_Artist(artist):
artist.markPlayed()
history = artist.history()
assert len(history)
artist.markUnplayed()
def test_history_Album(album):
album.markPlayed()
history = album.history()
assert len(history)
album.markUnplayed()
def test_history_Track(track):
track.markPlayed()
history = track.history()
assert len(history)
track.markUnplayed()
def test_history_MyAccount(account, movie, show):
movie.markWatched()
show.markWatched()
movie.markPlayed()
show.markPlayed()
history = account.history()
assert len(history)
movie.markUnwatched()
show.markUnwatched()
movie.markUnplayed()
show.markUnplayed()
def test_history_MyLibrary(plex, movie, show):
movie.markWatched()
show.markWatched()
movie.markPlayed()
show.markPlayed()
history = plex.library.history()
assert len(history)
movie.markUnwatched()
show.markUnwatched()
movie.markUnplayed()
show.markUnplayed()
def test_history_MySection(plex, movie):
movie.markWatched()
movie.markPlayed()
history = plex.library.section("Movies").history()
assert len(history)
movie.markUnwatched()
movie.markUnplayed()
def test_history_MyServer(plex, movie):
movie.markWatched()
movie.markPlayed()
history = plex.history()
assert len(history)
movie.markUnwatched()
movie.markUnplayed()
def test_history_User(account, shared_username):

View file

@ -120,7 +120,7 @@ def test_library_fetchItem(plex, movie):
def test_library_onDeck(plex, movie):
movie.updateProgress(movie.duration // 4) # set progress to 25%
assert movie in plex.library.onDeck()
movie.markUnwatched()
movie.markUnplayed()
def test_library_recentlyAdded(plex):
@ -264,10 +264,10 @@ def test_library_deleteMediaPreviews(movies):
def test_library_MovieSection_onDeck(movie, movies, tvshows, episode):
movie.updateProgress(movie.duration // 4) # set progress to 25%
assert movie in movies.onDeck()
movie.markUnwatched()
movie.markUnplayed()
episode.updateProgress(episode.duration // 4)
assert episode in tvshows.onDeck()
episode.markUnwatched()
episode.markUnplayed()
def test_library_MovieSection_searchMovies(movies):

View file

@ -208,10 +208,10 @@ def test_server_playlists(plex, show):
def test_server_history(plex, movie):
movie.markWatched()
movie.markPlayed()
history = plex.history()
assert len(history)
movie.markUnwatched()
movie.markUnplayed()
def test_server_Server_query(plex):

View file

@ -125,7 +125,7 @@ def test_add_episode_to_sync(clear_sync_device, episode):
def test_limited_watched(clear_sync_device, show):
show.markUnwatched()
show.markUnplayed()
new_item = show.sync(
VIDEO_QUALITY_3_MBPS_720p, client=clear_sync_device, limit=5, unwatched=False
)
@ -143,7 +143,7 @@ def test_limited_watched(clear_sync_device, show):
)
assert 5 == len(media_list)
assert [e.ratingKey for e in episodes] == [m.ratingKey for m in media_list]
episodes[0].markWatched()
episodes[0].markPlayed()
show._server.refreshSync()
media_list = utils.wait_until(
get_media, delay=0.25, timeout=3, item=item, server=show._server
@ -153,7 +153,7 @@ def test_limited_watched(clear_sync_device, show):
def test_limited_unwatched(clear_sync_device, show):
show.markUnwatched()
show.markUnplayed()
new_item = show.sync(
VIDEO_QUALITY_3_MBPS_720p, client=clear_sync_device, limit=5, unwatched=True
)
@ -171,7 +171,7 @@ def test_limited_unwatched(clear_sync_device, show):
)
assert len(episodes) == len(media_list)
assert [e.ratingKey for e in episodes] == [m.ratingKey for m in media_list]
episodes[0].markWatched()
episodes[0].markPlayed()
show._server.refreshSync()
episodes = show.episodes(viewCount=0)[:5]
media_list = utils.wait_until(
@ -182,7 +182,7 @@ def test_limited_unwatched(clear_sync_device, show):
def test_unlimited_and_watched(clear_sync_device, show):
show.markUnwatched()
show.markUnplayed()
new_item = show.sync(
VIDEO_QUALITY_3_MBPS_720p, client=clear_sync_device, unwatched=False
)
@ -200,7 +200,7 @@ def test_unlimited_and_watched(clear_sync_device, show):
)
assert len(episodes) == len(media_list)
assert [e.ratingKey for e in episodes] == [m.ratingKey for m in media_list]
episodes[0].markWatched()
episodes[0].markPlayed()
show._server.refreshSync()
episodes = show.episodes()
media_list = utils.wait_until(
@ -211,7 +211,7 @@ def test_unlimited_and_watched(clear_sync_device, show):
def test_unlimited_and_unwatched(clear_sync_device, show):
show.markUnwatched()
show.markUnplayed()
new_item = show.sync(
VIDEO_QUALITY_3_MBPS_720p, client=clear_sync_device, unwatched=True
)
@ -229,7 +229,7 @@ def test_unlimited_and_unwatched(clear_sync_device, show):
)
assert len(episodes) == len(media_list)
assert [e.ratingKey for e in episodes] == [m.ratingKey for m in media_list]
episodes[0].markWatched()
episodes[0].markPlayed()
show._server.refreshSync()
episodes = show.episodes(viewCount=0)
media_list = utils.wait_until(

View file

@ -340,6 +340,16 @@ def test_video_Movie_isFullObject_and_reload(plex):
assert len(movie_via_section_search.roles) >= 3
def test_video_movie_watched(movie):
movie.markUnplayed()
movie.markPlayed()
movie.reload()
assert movie.viewCount == 1
movie.markUnplayed()
movie.reload()
assert movie.viewCount == 0
def test_video_Movie_isPartialObject(movie):
assert movie.isPartialObject()
movie._autoReload = False
@ -410,13 +420,6 @@ def test_video_Movie_upload_select_remove_subtitle(movie, subtitle):
pass
def test_video_Movie_history(movie):
movie.markWatched()
history = movie.history()
assert len(history)
movie.markUnwatched()
def test_video_Movie_match(movies):
sectionAgent = movies.agent
sectionAgents = [agent.identifier for agent in movies.agents() if agent.shortIdentifier != 'none']
@ -760,30 +763,23 @@ def test_video_Show_episode(show):
show.episode(season=1337, episode=1337)
def test_video_Show_history(show):
show.markWatched()
history = show.history()
assert len(history)
show.markUnwatched()
def test_video_Show_watched(tvshows):
show = tvshows.get("The 100")
episode = show.episodes()[0]
episode.markWatched()
episode.markPlayed()
watched = show.watched()
assert len(watched) == 1 and watched[0].title == "Pilot"
episode.markUnwatched()
episode.markUnplayed()
def test_video_Show_unwatched(tvshows):
show = tvshows.get("The 100")
episodes = show.episodes()
episode = episodes[0]
episode.markWatched()
episode.markPlayed()
unwatched = show.unwatched()
assert len(unwatched) == len(episodes) - 1
episode.markUnwatched()
episode.markUnplayed()
def test_video_Show_settings(show):
@ -803,7 +799,7 @@ def test_video_Show_reload(plex):
def test_video_Show_episodes(tvshows):
show = tvshows.get("The 100")
episodes = show.episodes()
episodes[0].markWatched()
episodes[0].markPlayed()
unwatched = show.episodes(viewCount=0)
assert len(unwatched) == len(episodes) - 1
@ -836,16 +832,16 @@ def test_video_Show_analyze(show):
show = show.analyze()
def test_video_Show_markWatched(show):
show.markWatched()
def test_video_Show_markPlayed(show):
show.markPlayed()
show.reload()
assert show.isWatched
assert show.isPlayed
def test_video_Show_markUnwatched(show):
show.markUnwatched()
def test_video_Show_markUnplayed(show):
show.markUnplayed()
show.reload()
assert not show.isWatched
assert not show.isPlayed
def test_video_Show_refresh(show):
@ -856,8 +852,8 @@ def test_video_Show_get(show):
assert show.get("Winter Is Coming").title == "Winter Is Coming"
def test_video_Show_isWatched(show):
assert not show.isWatched
def test_video_Show_isPlayed(show):
assert not show.isPlayed
def test_video_Show_section(show):
@ -933,14 +929,6 @@ def test_video_Season(show):
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)
@ -988,16 +976,16 @@ def test_video_Season_show(show):
def test_video_Season_watched(show):
season = show.season("Season 1")
season.markWatched()
season.markPlayed()
season.reload()
assert season.isWatched
assert season.isPlayed
def test_video_Season_unwatched(show):
season = show.season("Season 1")
season.markUnwatched()
season.markUnplayed()
season.reload()
assert not season.isWatched
assert not season.isPlayed
def test_video_Season_get(show):
@ -1082,13 +1070,6 @@ def test_video_Episode(show):
show.episode(season=1337, episode=1337)
def test_video_Episode_history(episode):
episode.markWatched()
history = episode.history()
assert len(history)
episode.markUnwatched()
def test_video_Episode_hidden_season(episode):
assert episode.skipParent is False
assert episode.parentRatingKey
@ -1182,7 +1163,7 @@ def test_video_Episode_attrs(episode):
if episode.writers:
assert "David Benioff" in [i.tag for i in episode.writers]
assert episode.year is None
assert episode.isWatched in [True, False]
assert episode.isPlayed in [True, False]
assert len(episode.locations) == 1
assert len(episode.locations[0]) >= 10
assert episode.seasonEpisode == "s01e01"
@ -1222,20 +1203,20 @@ def test_video_Episode_attrs(episode):
def test_video_Episode_watched(tvshows):
season = tvshows.get("The 100").season(1)
episode = season.episode(1)
episode.markWatched()
episode.markPlayed()
watched = season.watched()
assert len(watched) == 1 and watched[0].title == "Pilot"
episode.markUnwatched()
episode.markUnplayed()
def test_video_Episode_unwatched(tvshows):
season = tvshows.get("The 100").season(1)
episodes = season.episodes()
episode = episodes[0]
episode.markWatched()
episode.markPlayed()
unwatched = season.unwatched()
assert len(unwatched) == len(episodes) - 1
episode.markUnwatched()
episode.markUnplayed()
@pytest.mark.xfail(reason="Changing images fails randomly")