diff --git a/plexapi/audio.py b/plexapi/audio.py index 09419d47..c169adfe 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -2,18 +2,15 @@ PlexAudio """ from plexapi import utils +from plexapi.compat import urlencode from plexapi.media import Media, Genre, Producer from plexapi.exceptions import Unsupported -from plexapi.utils import NA -from plexapi.utils import cast, toDatetime, register_libtype -from plexapi.video import Video # TODO: remove this when Audio class can stand on its own legs -try: - from urllib import urlencode # Python2 -except ImportError: - from urllib.parse import urlencode # Python3 +from plexapi.video import Video +NA = utils.NA -class Audio(Video): # TODO: inherit from PlexPartialObject, like the Video class does +# TODO: inherit from PlexPartialObject, like the Video class does +class Audio(Video): def _loadData(self, data): self.type = data.attrib.get('type', NA) @@ -24,9 +21,9 @@ class Audio(Video): # TODO: inherit from PlexPartialObject, like the Video clas self.summary = data.attrib.get('summary', NA) self.art = data.attrib.get('art', NA) self.thumb = data.attrib.get('thumb', NA) - self.addedAt = toDatetime(data.attrib.get('addedAt', NA)) - self.updatedAt = toDatetime(data.attrib.get('updatedAt', NA)) - self.sessionKey = cast(int, data.attrib.get('sessionKey', NA)) + self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA)) + self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA)) + self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA)) self.user = self._find_user(data) # for active sessions self.player = self._find_player(data) # for active sessions self.transcodeSession = self._find_transcodeSession(data) @@ -67,7 +64,7 @@ class Audio(Video): # TODO: inherit from PlexPartialObject, like the Video clas self._loadData(data[0]) -@register_libtype +@utils.register_libtype class Artist(Audio): TYPE = 'artist' @@ -77,14 +74,14 @@ class Artist(Audio): self.studio = data.attrib.get('studio', NA) self.contentRating = data.attrib.get('contentRating', NA) self.rating = data.attrib.get('rating', NA) - self.year = cast(int, data.attrib.get('year', NA)) + self.year = utils.cast(int, data.attrib.get('year', NA)) self.banner = data.attrib.get('banner', NA) self.theme = data.attrib.get('theme', NA) - self.duration = cast(int, data.attrib.get('duration', NA)) - self.originallyAvailableAt = toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d') - self.leafCount = cast(int, data.attrib.get('leafCount', NA)) - self.viewedLeafCount = cast(int, data.attrib.get('viewedLeafCount', NA)) - self.childCount = cast(int, data.attrib.get('childCount', NA)) + self.duration = utils.cast(int, data.attrib.get('duration', NA)) + self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d') + self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA)) + self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount', NA)) + self.childCount = utils.cast(int, data.attrib.get('childCount', NA)) self.titleSort = data.attrib.get('titleSort', NA) def albums(self): @@ -116,7 +113,7 @@ class Artist(Audio): self.server.query('/library/metadata/%s/refresh' % self.ratingKey) -@register_libtype +@utils.register_libtype class Album(Audio): TYPE = 'album' @@ -133,9 +130,9 @@ class Album(Audio): self.parentIndex = data.attrib.get('parentIndex', NA) self.parentThumb = data.attrib.get('parentThumb', NA) self.parentTheme = data.attrib.get('parentTheme', NA) - self.leafCount = cast(int, data.attrib.get('leafCount', NA)) - self.viewedLeafCount = cast(int, data.attrib.get('viewedLeafCount', NA)) - self.year = cast(int, data.attrib.get('year', NA)) + self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA)) + self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount', NA)) + self.year = utils.cast(int, data.attrib.get('year', NA)) def tracks(self, watched=None): childrenKey = '/library/metadata/%s/children' % self.ratingKey @@ -158,7 +155,7 @@ class Album(Audio): return self.tracks(watched=False) -@register_libtype +@utils.register_libtype class Track(Audio): TYPE = 'track' @@ -177,8 +174,8 @@ class Track(Audio): self.contentRating = data.attrib.get('contentRating', NA) self.index = data.attrib.get('index', NA) self.rating = data.attrib.get('rating', NA) - self.duration = cast(int, data.attrib.get('duration', NA)) - self.originallyAvailableAt = toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d') + self.duration = utils.cast(int, data.attrib.get('duration', NA)) + self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d') @property def thumbUrl(self): diff --git a/plexapi/compat.py b/plexapi/compat.py new file mode 100644 index 00000000..c0870eb6 --- /dev/null +++ b/plexapi/compat.py @@ -0,0 +1,18 @@ +""" +Python 2/3 compatability +Always try Py3 first +""" +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + +try: + from urllib.parse import quote +except ImportError: + from urllib import quote + +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import ConfigParser diff --git a/plexapi/library.py b/plexapi/library.py index 432a2355..9818e867 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -134,16 +134,16 @@ class LibrarySection(object): path = '/library/sections/%s/all' % self.key return utils.find_item(self.server, path, title) - def search(self, title, filter='all', vtype=None, **tags): + def search(self, title, filter='all', libtype=None, **tags): """ Search section content. title: Title to search (pass None to search all titles). - filter: One of {'all', 'newest', 'onDeck', 'recentlyAdded', 'recentlyViewed', 'unwatched'}. - videotype: One of {'movie', 'show', 'season', 'episode'}. + filter: One of {all, newest, onDeck, recentlyAdded, recentlyViewed, unwatched}. + libtype: One of {movie, show, season, episode, artist, album, track}. tags: One of {country, director, genre, producer, actor, writer}. """ args = {} if title: args['title'] = title - if vtype: args['type'] = utils.search_type(vtype) + if libtype: args['type'] = utils.search_type(libtype) for tag, obj in tags.items(): args[tag] = obj.id query = '/library/sections/%s/%s%s' % (self.key, filter, utils.joinArgs(args)) @@ -181,7 +181,7 @@ class MovieSection(LibrarySection): return self._secondary_list('resolution', input) def search(self, title, filter='all', **tags): - return super(MovieSection, self).search(title, filter=filter, vtype='movie', **tags) + return super(MovieSection, self).search(title, filter=filter, libtype='movie', **tags) class ShowSection(LibrarySection): @@ -191,10 +191,10 @@ class ShowSection(LibrarySection): return self._primary_list('recentlyViewedShows') def search(self, title, filter='all', **tags): - return super(ShowSection, self).search(title, filter=filter, vtype='show', **tags) + return super(ShowSection, self).search(title, filter=filter, libtype='show', **tags) def searchEpisodes(self, title, filter='all', **tags): - return super(ShowSection, self).search(title, filter=filter, vtype='episode', **tags) + return super(ShowSection, self).search(title, filter=filter, libtype='episode', **tags) class MusicSection(LibrarySection): diff --git a/plexapi/server.py b/plexapi/server.py index 2004e840..682b7e1c 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -5,6 +5,8 @@ import requests from requests.status_codes import _codes as codes from plexapi import BASE_HEADERS, TIMEOUT from plexapi import log, utils +from plexapi import audio, video # flake8:noqa; required +from plexapi.compat import quote from plexapi.client import Client from plexapi.exceptions import BadRequest, NotFound from plexapi.library import Library @@ -12,11 +14,6 @@ from plexapi.myplex import MyPlexAccount from plexapi.playqueue import PlayQueue from xml.etree import ElementTree -try: - from urllib import quote # Python2 -except ImportError: - from urllib.parse import quote # Python3 - TOTAL_QUERIES = 0 DEFAULT_BASEURI = 'http://localhost:32400' diff --git a/plexapi/sync.py b/plexapi/sync.py index bc760ee8..22c6f620 100644 --- a/plexapi/sync.py +++ b/plexapi/sync.py @@ -2,17 +2,16 @@ PlexAPI Sync """ import requests +from plexapi import utils from plexapi.exceptions import NotFound -from plexapi.video import list_items -from plexapi.utils import cast class SyncItem(object): def __init__(self, device, data, servers=None): self.device = device self.servers = servers - self.id = cast(int, data.attrib.get('id')) - self.version = cast(int, data.attrib.get('version')) + self.id = utils.cast(int, data.attrib.get('id')) + self.version = utils.cast(int, data.attrib.get('version')) self.rootTitle = data.attrib.get('rootTitle') self.title = data.attrib.get('title') self.metadataType = data.attrib.get('metadataType') @@ -34,7 +33,7 @@ class SyncItem(object): def getMedia(self): server = self.server().connect() - items = list_items(server, '/sync/items/{0}'.format(self.id)) + items = utils.list_items(server, '/sync/items/{0}'.format(self.id)) return items def markAsDone(self, sync_id): diff --git a/plexapi/utils.py b/plexapi/utils.py index 721bbb30..a8f51bef 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -2,13 +2,8 @@ PlexAPI Utils """ from datetime import datetime -from threading import Thread -from Queue import Queue +from plexapi.compat import quote from plexapi.exceptions import UnknownType -try: - from urllib import quote # Python2 -except ImportError: - from urllib.parse import quote # Python3 # Registry of library types we may come across when parsing XML. This allows us to @@ -81,6 +76,19 @@ def build_item(server, elem, initpath): raise UnknownType('Unknown library type: %s' % libtype) +def cast(func, value): + if value not in [None, NA]: + if func == bool: + return bool(int(value)) + elif func in [int, float]: + try: + return func(value) + except ValueError: + return float('nan') + return func(value) + return value + + def find_key(server, key): path = '/library/metadata/{0}'.format(key) try: @@ -98,6 +106,15 @@ def find_item(server, path, title): raise NotFound('Unable to find item: %s' % title) +def joinArgs(args): + if not args: return '' + arglist = [] + for key in sorted(args, key=lambda x:x.lower()): + value = str(args[key]) + arglist.append('%s=%s' % (key, quote(value))) + return '?%s' % '&'.join(arglist) + + def list_items(server, path, libtype=None, watched=None): items = [] for elem in server.query(path): @@ -112,54 +129,16 @@ def list_items(server, path, libtype=None, watched=None): def search_type(libtype): - if libtype == LIBRARY_TYPES['movie'].TYPE: return 1 - elif libtype == LIBRARY_TYPES['show'].TYPE: return 2 - elif libtype == LIBRARY_TYPES['season'].TYPE: return 3 - elif libtype == LIBRARY_TYPES['episode'].TYPE: return 4 - elif libtype == LIBRARY_TYPES['artist'].TYPE: return 8 - elif libtype == LIBRARY_TYPES['album'].TYPE: return 9 - elif libtype == LIBRARY_TYPES['track'].TYPE: return 10 + if libtype == 'movie': return 1 + elif libtype == 'show': return 2 + elif libtype == 'season': return 3 + elif libtype == 'episode': return 4 + elif libtype == 'artist': return 8 + elif libtype == 'album': return 9 + elif libtype == 'track': return 10 raise NotFound('Unknown libtype: %s' % libtype) -def cast(func, value): - if value not in [None, NA]: - if func == bool: - return bool(int(value)) - elif func in [int, float]: - try: - return func(value) - except ValueError: - return float('nan') - return func(value) - return value - - -def joinArgs(args): - if not args: return '' - arglist = [] - for key in sorted(args, key=lambda x:x.lower()): - value = str(args[key]) - arglist.append('%s=%s' % (key, quote(value))) - return '?%s' % '&'.join(arglist) - - -def threaded(funcs, *args, **kwargs): - def _run(func, _args, _kwargs, results): - results.put(func(*_args, **_kwargs)) - threads = [] - results = Queue(len(funcs) + 1) - for func in funcs: - targs = [func, args, kwargs, results] - threads.append(Thread(target=_run, args=targs)) - for thread in threads: - thread.start() - for thread in threads: - thread.join() - results.put(None) - return results - - def toDatetime(value, format=None): if value and value != NA: if format: value = datetime.strptime(value, format) diff --git a/plexapi/video.py b/plexapi/video.py index 49a348ff..c7bd29fc 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -5,13 +5,9 @@ import re from requests import put from plexapi import media, utils from plexapi.client import Client +from plexapi.compat import urlencode from plexapi.myplex import MyPlexUser from plexapi.exceptions import Unsupported -try: - from urllib import urlencode # Python2 -except ImportError: - from urllib.parse import urlencode # Python3 - NA = utils.NA