From 874281828aaee633692de1fc8ca422d2915fdd6e Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Wed, 27 Sep 2017 10:28:08 -0400 Subject: [PATCH 01/11] Add alertlistener example --- tools/plex-alertlistener.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 tools/plex-alertlistener.py diff --git a/tools/plex-alertlistener.py b/tools/plex-alertlistener.py new file mode 100755 index 00000000..0b5698c0 --- /dev/null +++ b/tools/plex-alertlistener.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Listen to plex alerts and print them to the console. +Because we're using print as a function, example only works in Python3. +""" +import time +from plexapi.server import PlexServer + + +def _print(msg): + print(msg) + + +if __name__ == '__main__': + try: + plex = PlexServer() + listener = plex.startAlertListener(_print) + while True: + time.sleep(1) + except KeyboardInterrupt: + listener.stop() From e0a213c5e4c16c541bde7995fc093038f866c875 Mon Sep 17 00:00:00 2001 From: tdorsey Date: Thu, 28 Sep 2017 06:43:49 -0400 Subject: [PATCH 02/11] Fix TypeError when viewCount is not set Fixes #190 --- plexapi/video.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plexapi/video.py b/plexapi/video.py index 19fc04b4..245ec57d 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -44,8 +44,11 @@ class Video(PlexPartialObject): @property def isWatched(self): """ Returns True if this video is watched. """ - return bool(self.viewCount > 0) - + if not self.viewCount: + return False + else: + return bool(self.viewCount > 0) + @property def thumbUrl(self): """ Return url to for the thumbnail image. """ From 17532a8f7d61561de5b02c4c10dd4ffcc1502cd1 Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 29 Sep 2017 17:45:57 +0200 Subject: [PATCH 03/11] Add guid to LibrarySection filters for Movie, Show --- plexapi/library.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plexapi/library.py b/plexapi/library.py index a38f8684..309749a7 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -550,7 +550,7 @@ class MovieSection(LibrarySection): Attributes: ALLOWED_FILTERS (list): List of allowed search filters. ('unwatched', 'duplicate', 'year', 'decade', 'genre', 'contentRating', 'collection', - 'director', 'actor', 'country', 'studio', 'resolution') + 'director', 'actor', 'country', 'studio', 'resolution', 'guid') ALLOWED_SORT (list): List of allowed sorting keys. ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating', 'mediaHeight', 'duration') @@ -558,7 +558,8 @@ class MovieSection(LibrarySection): TYPE (str): 'movie' """ ALLOWED_FILTERS = ('unwatched', 'duplicate', 'year', 'decade', 'genre', 'contentRating', - 'collection', 'director', 'actor', 'country', 'studio', 'resolution') + 'collection', 'director', 'actor', 'country', 'studio', 'resolution', + 'guid') ALLOWED_SORT = ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating', 'mediaHeight', 'duration') TAG = 'Directory' @@ -570,13 +571,14 @@ class ShowSection(LibrarySection): Attributes: ALLOWED_FILTERS (list): List of allowed search filters. ('unwatched', - 'year', 'genre', 'contentRating', 'network', 'collection') + 'year', 'genre', 'contentRating', 'network', 'collection', 'guid') ALLOWED_SORT (list): List of allowed sorting keys. ('addedAt', 'lastViewedAt', 'originallyAvailableAt', 'titleSort', 'rating', 'unwatched') TAG (str): 'Directory' TYPE (str): 'show' """ - ALLOWED_FILTERS = ('unwatched', 'year', 'genre', 'contentRating', 'network', 'collection') + ALLOWED_FILTERS = ('unwatched', 'year', 'genre', 'contentRating', 'network', 'collection', + 'guid') ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'originallyAvailableAt', 'titleSort', 'rating', 'unwatched') TAG = 'Directory' From 67c859d5844e1324cb23c311ddbdb22c19105a18 Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Fri, 29 Sep 2017 12:10:55 -0400 Subject: [PATCH 04/11] Update video.py --- plexapi/video.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plexapi/video.py b/plexapi/video.py index 245ec57d..4e658fc5 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -44,10 +44,7 @@ class Video(PlexPartialObject): @property def isWatched(self): """ Returns True if this video is watched. """ - if not self.viewCount: - return False - else: - return bool(self.viewCount > 0) + return bool(self.viewCount > 0) if self.viewCount else False @property def thumbUrl(self): From 8507c603b4d8aab3708e90a619f41400aeb54898 Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Fri, 29 Sep 2017 12:11:14 -0400 Subject: [PATCH 05/11] Update video.py --- plexapi/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index 4e658fc5..3c29fe8d 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -45,7 +45,7 @@ class Video(PlexPartialObject): def isWatched(self): """ Returns True if this video is watched. """ return bool(self.viewCount > 0) if self.viewCount else False - + @property def thumbUrl(self): """ Return url to for the thumbnail image. """ From 566107697aa87857f89946eb532926c34ab9e555 Mon Sep 17 00:00:00 2001 From: Hellowlol Date: Fri, 29 Sep 2017 23:55:41 +0200 Subject: [PATCH 06/11] download improvements - Add missing showstatus doc - add name and total to tqdm so the bar is displayed properly --- plexapi/utils.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plexapi/utils.py b/plexapi/utils.py index 38282019..fd007f7c 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -242,6 +242,7 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, chunksize (int): What chunksize read/write at the time. mocked (bool): Helper to do evertything except write the file. unpack (bool): Unpack the zip file. + showstatus(bool): Display a progressbar. Example: >>> download(a_episode.getStreamURL(), a_episode.location) @@ -254,10 +255,12 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, # make sure the savepath directory exists savepath = savepath or os.getcwd() compat.makedirs(savepath, exist_ok=True) + # try getting filename from header if not specified in arguments (used for logs, db) if not filename and response.headers.get('Content-Disposition'): filename = re.findall(r'filename=\"(.+)\"', response.headers.get('Content-Disposition')) filename = filename[0] if filename[0] else None + filename = os.path.basename(filename) fullpath = os.path.join(savepath, filename) # append file.ext from content-type if not already there @@ -266,24 +269,31 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, contenttype = response.headers.get('content-type') if contenttype and 'image' in contenttype: fullpath += contenttype.split('/')[1] + # check this is a mocked download (testing) if mocked: log.debug('Mocked download %s', fullpath) return fullpath + # save the file to disk log.info('Downloading: %s', fullpath) if showstatus: - bar = tqdm(unit='B', unit_scale=True) + total = int(response.headers.get('content-length', 0)) + bar = tqdm(unit='B', unit_scale=True, total=title, desc=filename) + with open(fullpath, 'wb') as handle: for chunk in response.iter_content(chunk_size=chunksize): handle.write(chunk) if showstatus: bar.update(len(chunk)) + + if showstatus: + bar.close() # check we want to unzip the contents if fullpath.endswith('zip') and unpack: with zipfile.ZipFile(fullpath, 'r') as handle: handle.extractall(savepath) - # finished; return fillpath + return fullpath From ceada3c0b3c9fd1e079ed78b83654c60bf8ea4b2 Mon Sep 17 00:00:00 2001 From: Hellowlol Date: Sat, 30 Sep 2017 01:32:27 +0200 Subject: [PATCH 07/11] Fix suggestion bug... --- plexapi/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/utils.py b/plexapi/utils.py index fd007f7c..2f1beb90 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -279,7 +279,7 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, log.info('Downloading: %s', fullpath) if showstatus: total = int(response.headers.get('content-length', 0)) - bar = tqdm(unit='B', unit_scale=True, total=title, desc=filename) + bar = tqdm(unit='B', unit_scale=True, total=total, desc=filename) with open(fullpath, 'wb') as handle: for chunk in response.iter_content(chunk_size=chunksize): From b3a328261aff76d2a3a61e8edce2824a19daa900 Mon Sep 17 00:00:00 2001 From: Hellowlol Date: Sat, 30 Sep 2017 08:44:24 +0200 Subject: [PATCH 08/11] fix installUpdate i had forgotten a / --- plexapi/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/server.py b/plexapi/server.py index 905df9f9..6c27b6e3 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -300,7 +300,7 @@ class PlexServer(PlexObject): """ Install the newest version of Plex Media Server. """ # We can add this but dunno how useful this is since it sometimes # requires user action using a gui. - part = 'updater/apply' + part = '/updater/apply' release = self.check_for_update(force=True, download=True) if release and release.version != self.version: # figure out what method this is.. From d1d1667944e1d7e7b082a5e7f9216c713fe4d5be Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Sat, 30 Sep 2017 13:23:16 -0400 Subject: [PATCH 09/11] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index dcc30021..1ffddd62 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ Python-PlexAPI Overview -------- -Python bindings for the Plex API. Our goal is to match all capabilities of the official +Unofficial Python bindings for the Plex API. Our goal is to match all capabilities of the official Plex Web Client. A few of the many features we currently support are: * Navigate local or remote shared libraries. From e2f90ea81d42601e2e50be0b0b7e40dfffab4548 Mon Sep 17 00:00:00 2001 From: Hellowlol Date: Sat, 30 Sep 2017 19:45:35 +0200 Subject: [PATCH 10/11] add broken delete to parts. --- plexapi/media.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plexapi/media.py b/plexapi/media.py index 396699ac..d0ca76b5 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from plexapi import utils +from plexapi import log, utils from plexapi.base import PlexObject from plexapi.exceptions import BadRequest from plexapi.utils import cast @@ -85,6 +85,16 @@ class MediaPart(PlexObject): self.size = cast(int, data.attrib.get('size')) self.streams = self._buildStreams(data) + def delete(self): + log.debug('Deleting %s', self.file) + part = self._initpath + '/media/%s' % self.id + try: + return self._server.query(part, method=self._server._session.delete) + except BadRequest: + log.error("Failed to delete %s. This could be because you havn't allowed " + "items to be deleted" % part) + raise + def _buildStreams(self, data): streams = [] for elem in data: From 828b41e0ec6ec613ad1d7047626348b44e8c3e44 Mon Sep 17 00:00:00 2001 From: Hellowlol Date: Sat, 30 Sep 2017 23:16:40 +0200 Subject: [PATCH 11/11] delete should be part of the media not the media part --- plexapi/media.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/plexapi/media.py b/plexapi/media.py index d0ca76b5..b5e969c9 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -52,6 +52,15 @@ class Media(PlexObject): self.width = cast(int, data.attrib.get('width')) self.parts = self.findItems(data, MediaPart) + def delete(self): + part = self._initpath + '/media/%s' % self.id + try: + return self._server.query(part, method=self._server._session.delete) + except BadRequest: + log.error("Failed to delete %s. This could be because you havn't allowed " + "items to be deleted" % part) + raise + @utils.registerPlexObject class MediaPart(PlexObject): @@ -85,16 +94,6 @@ class MediaPart(PlexObject): self.size = cast(int, data.attrib.get('size')) self.streams = self._buildStreams(data) - def delete(self): - log.debug('Deleting %s', self.file) - part = self._initpath + '/media/%s' % self.id - try: - return self._server.query(part, method=self._server._session.delete) - except BadRequest: - log.error("Failed to delete %s. This could be because you havn't allowed " - "items to be deleted" % part) - raise - def _buildStreams(self, data): streams = [] for elem in data: