mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-21 19:23:05 +00:00
Finish documenting video
This commit is contained in:
parent
1815e67804
commit
fc1c10e554
5 changed files with 181 additions and 96 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -2,21 +2,22 @@ syntax: glob
|
|||
*.db
|
||||
*.egg-info
|
||||
*.log
|
||||
*.orig
|
||||
*.pickle
|
||||
*.pyc
|
||||
*.sublime-*
|
||||
*.swp
|
||||
*__pycache__*
|
||||
.cache/
|
||||
.coverage
|
||||
.idea/
|
||||
.Python
|
||||
bin/
|
||||
build
|
||||
dist
|
||||
docs/_build/
|
||||
htmlcov
|
||||
include/
|
||||
lib/
|
||||
pip-selfcheck.json
|
||||
pyvenv.cfg
|
||||
htmlcov
|
||||
.coverage
|
||||
*.orig
|
||||
pyvenv.cfg
|
|
@ -14,7 +14,7 @@ class PlexNotifier(threading.Thread):
|
|||
callback (func): Callback function to call on recieved messages.
|
||||
|
||||
NOTE: You need websocket-client installed in order to use this feature.
|
||||
>> pip install websocket-client
|
||||
>> pip install websocket-client
|
||||
"""
|
||||
key = '/:/websockets/notifications'
|
||||
|
||||
|
@ -47,6 +47,7 @@ class PlexNotifier(threading.Thread):
|
|||
self._ws.close()
|
||||
|
||||
def _onMessage(self, ws, message):
|
||||
""" Called when websocket message is recieved. """
|
||||
try:
|
||||
data = json.loads(message)['NotificationContainer']
|
||||
log.debug('Notify: %s', data)
|
||||
|
@ -56,4 +57,5 @@ class PlexNotifier(threading.Thread):
|
|||
log.error('PlexNotifier Msg Error: %s', err)
|
||||
|
||||
def _onError(self, ws, err):
|
||||
""" Called when websocket error is recieved. """
|
||||
log.error('PlexNotifier Error: %s' % err)
|
||||
|
|
|
@ -79,6 +79,7 @@ def firstAttr(elem, *attrs):
|
|||
|
||||
|
||||
def getattributeOrNone(obj, self, attr):
|
||||
""" Returns result from __getattribute__ or None if not found. """
|
||||
try:
|
||||
return super(obj, self).__getattribute__(attr)
|
||||
except AttributeError:
|
||||
|
@ -136,8 +137,7 @@ def searchType(libtype):
|
|||
""" Returns the integer value of the library string type.
|
||||
|
||||
Parameters:
|
||||
libtype (str): Library type to lookup (movie, show, season, episode,
|
||||
artist, album, track)
|
||||
libtype (str): LibType to lookup (movie, show, season, episode, artist, album, track)
|
||||
|
||||
Raises:
|
||||
NotFound: Unknown libtype
|
||||
|
|
234
plexapi/video.py
234
plexapi/video.py
|
@ -25,6 +25,7 @@ class Video(PlexPartialObject):
|
|||
viewCount (int): Count of times this item was accessed.
|
||||
"""
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
self._data = data
|
||||
self.listType = 'video'
|
||||
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
|
||||
|
@ -40,6 +41,11 @@ class Video(PlexPartialObject):
|
|||
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
|
||||
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)
|
||||
|
||||
@property
|
||||
def thumbUrl(self):
|
||||
""" Return url to for the thumbnail image. """
|
||||
|
@ -64,6 +70,8 @@ class Movie(Video, Playable):
|
|||
""" Represents a single Movie.
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Diectory'
|
||||
TYPE (str): 'movie'
|
||||
art (str): Key to movie artwork (/library/metadata/<ratingkey>/art/<artid>)
|
||||
audienceRating (float): Audience rating (usually from Rotten Tomatoes).
|
||||
audienceRatingImage (str): Key to audience rating image (rottentomatoes://image.rating.spilled)
|
||||
|
@ -81,13 +89,21 @@ class Movie(Video, Playable):
|
|||
userRating (float): User rating (2.0; 8.0).
|
||||
viewOffset (int): View offset in milliseconds.
|
||||
year (int): Year movie was released.
|
||||
|
||||
# TODO: Finish documenting plexapi.video.Movie
|
||||
collections (List<:class:`~plexapi.media.Collection`>): List of collections this media belongs.
|
||||
countries (List<:class:`~plexapi.media.Country`>): List of countries objects.
|
||||
directors (List<:class:`~plexapi.media.Director`>): List of director objects.
|
||||
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
|
||||
genres (List<:class:`~plexapi.media.Genre`>): List of genre objects.
|
||||
media (List<:class:`~plexapi.media.Media`>): List of media objects.
|
||||
producers (List<:class:`~plexapi.media.Producer`>): List of producers objects.
|
||||
roles (List<:class:`~plexapi.media.Role`>): List of role objects.
|
||||
writers (List<:class:`~plexapi.media.Writer`>): List of writers objects.
|
||||
"""
|
||||
TAG = 'Video'
|
||||
TYPE = 'movie'
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Video._loadData(self, data)
|
||||
Playable._loadData(self, data)
|
||||
self.art = data.attrib.get('art')
|
||||
|
@ -120,12 +136,9 @@ class Movie(Video, Playable):
|
|||
|
||||
@property
|
||||
def actors(self):
|
||||
""" Alias to self.roles. """
|
||||
return self.roles
|
||||
|
||||
@property
|
||||
def isWatched(self):
|
||||
return bool(self.viewCount > 0)
|
||||
|
||||
@property
|
||||
def locations(self):
|
||||
""" This does not exist in plex xml response but is added to have a common
|
||||
|
@ -134,6 +147,14 @@ class Movie(Video, Playable):
|
|||
return [p.file for p in self.iterParts() if p]
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
""" Download video files to specified directory.
|
||||
|
||||
Parameters:
|
||||
savepath (str): Defaults to current working dir.
|
||||
keep_orginal_name (bool): True to keep the original file name otherwise
|
||||
a friendlier is generated.
|
||||
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`.
|
||||
"""
|
||||
downloaded = []
|
||||
locs = [i for i in self.iterParts() if i]
|
||||
for loc in locs:
|
||||
|
@ -154,12 +175,36 @@ class Movie(Video, Playable):
|
|||
|
||||
@utils.registerPlexObject
|
||||
class Show(Video):
|
||||
""" Represents a single Show (including all seasons and episodes).
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Diectory'
|
||||
TYPE (str): 'show'
|
||||
art (str): Key to show artwork (/library/metadata/<ratingkey>/art/<artid>)
|
||||
banner (str): Key to banner artwork (/library/metadata/<ratingkey>/art/<artid>)
|
||||
childCount (int): Unknown.
|
||||
contentRating (str) Content rating (PG-13; NR; TV-G).
|
||||
duration (int): Duration of show in milliseconds.
|
||||
guid (str): Plex GUID (com.plexapp.agents.imdb://tt4302938?lang=en).
|
||||
index (int): Plex index (?)
|
||||
leafCount (int): Unknown.
|
||||
locations (list<str>): List of locations paths.
|
||||
originallyAvailableAt (datetime): Datetime show was released.
|
||||
rating (float): Show rating (7.9; 9.8; 8.1).
|
||||
studio (str): Studio that created show (Di Bonaventura Pictures; 21 Laps Entertainment).
|
||||
theme (str): Key to theme resource (/library/metadata/<ratingkey>/theme/<themeid>)
|
||||
viewedLeafCount (int): Unknown.
|
||||
year (int): Year the show was released.
|
||||
genres (List<:class:`~plexapi.media.Genre`>): List of genre objects.
|
||||
roles (List<:class:`~plexapi.media.Role`>): List of role objects.
|
||||
"""
|
||||
TAG = 'Directory'
|
||||
TYPE = 'show'
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Video._loadData(self, data)
|
||||
# fix the key if this was loaded from search..
|
||||
# fix key if loaded from search
|
||||
self.key = self.key.replace('/children', '')
|
||||
self.art = data.attrib.get('art')
|
||||
self.banner = data.attrib.get('banner')
|
||||
|
@ -182,14 +227,16 @@ class Show(Video):
|
|||
|
||||
@property
|
||||
def actors(self):
|
||||
""" Alias to self.roles. """
|
||||
return self.roles
|
||||
|
||||
@property
|
||||
def isWatched(self):
|
||||
""" Returns True if this show is fully watched. """
|
||||
return bool(self.viewedLeafCount == self.leafCount)
|
||||
|
||||
def seasons(self, **kwargs):
|
||||
"""Returns a list of Season."""
|
||||
""" Returns a list of :class:`~plexapi.video.Season` objects. """
|
||||
key = '/library/metadata/%s/children' % self.ratingKey
|
||||
return self.fetchItems(key, **kwargs)
|
||||
|
||||
|
@ -205,33 +252,21 @@ class Show(Video):
|
|||
return self.fetchItem(key, etag='Directory', title__iexact=title)
|
||||
|
||||
def episodes(self, **kwargs):
|
||||
""" Returs a list of Episode """
|
||||
""" Returns a list of :class:`~plexapi.video.Episode` objects. """
|
||||
key = '/library/metadata/%s/allLeaves' % self.ratingKey
|
||||
return self.fetchItems(key, **kwargs)
|
||||
|
||||
def episode(self, title=None, season=None, episode=None):
|
||||
"""Find a episode using a title or season and episode.
|
||||
""" Find a episode using a title or season and episode.
|
||||
|
||||
Note:
|
||||
Both season and episode is required if title is missing.
|
||||
|
||||
Args:
|
||||
title (str): Default None
|
||||
season (int): Season number, default None
|
||||
episode (int): Episode number, default None
|
||||
Parameters:
|
||||
title (str): Title of the episode to return
|
||||
season (int): Season number (default:None; required if title not specified).
|
||||
episode (int): Episode number (default:None; required if title not specified).
|
||||
|
||||
Raises:
|
||||
ValueError: If season and episode is missing.
|
||||
NotFound: If the episode is missing.
|
||||
|
||||
Returns:
|
||||
Episode
|
||||
|
||||
Examples:
|
||||
>>> plex.search('The blacklist')[0].episode(season=1, episode=1)
|
||||
<Episode:116263:The.Freelancer>
|
||||
>>> plex.search('The blacklist')[0].episode('The Freelancer')
|
||||
<Episode:116263:The.Freelancer>
|
||||
"""
|
||||
if title:
|
||||
key = '/library/metadata/%s/allLeaves' % self.ratingKey
|
||||
|
@ -244,22 +279,26 @@ class Show(Video):
|
|||
raise TypeError('Missing argument: title or season and episode are required')
|
||||
|
||||
def watched(self):
|
||||
"""Return a list of watched episodes"""
|
||||
""" Returns list of watched :class:`~plexapi.video.Episode` objects. """
|
||||
return self.episodes(viewCount__gt=0)
|
||||
|
||||
def unwatched(self):
|
||||
"""Return a list of unwatched episodes"""
|
||||
""" Returns list of unwatched :class:`~plexapi.video.Episode` objects. """
|
||||
return self.episodes(viewCount=0)
|
||||
|
||||
def get(self, title):
|
||||
"""Get a Episode with a title.
|
||||
|
||||
Args:
|
||||
title (str): fx Secret santa
|
||||
"""
|
||||
return self.episode(title)
|
||||
def get(self, title=None, season=None, episode=None):
|
||||
""" Alias to :func:`~plexapi.video.Show.episode()`. """
|
||||
return self.episode(title, season, episode)
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
""" Download video files to specified directory.
|
||||
|
||||
Parameters:
|
||||
savepath (str): Defaults to current working dir.
|
||||
keep_orginal_name (bool): True to keep the original file name otherwise
|
||||
a friendlier is generated.
|
||||
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`.
|
||||
"""
|
||||
downloaded = []
|
||||
for ep in self.episodes():
|
||||
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
||||
|
@ -270,16 +309,25 @@ class Show(Video):
|
|||
|
||||
@utils.registerPlexObject
|
||||
class Season(Video):
|
||||
""" Represents a single Show Season (including all episodes).
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Diectory'
|
||||
TYPE (str): 'season'
|
||||
leafCount (int): Number of episodes in season.
|
||||
index (int): Season number.
|
||||
parentKey (str): Key to this seasons :class:`~plexapi.video.Show`.
|
||||
parentRatingKey (int): Unique key for this seasons :class:`~plexapi.video.Show`.
|
||||
parentTitle (str): Title of this seasons :class:`~plexapi.video.Show`.
|
||||
viewedLeafCount (int): Number of watched episodes in season.
|
||||
"""
|
||||
TAG = 'Directory'
|
||||
TYPE = 'season'
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Used to set the attributes
|
||||
|
||||
Args:
|
||||
data (Element): Usually built from server.query
|
||||
"""
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Video._loadData(self, data)
|
||||
# fix key if loaded from search
|
||||
self.key = self.key.replace('/children', '')
|
||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
||||
self.index = utils.cast(int, data.attrib.get('index'))
|
||||
|
@ -297,61 +345,62 @@ class Season(Video):
|
|||
|
||||
@property
|
||||
def isWatched(self):
|
||||
""" Returns True if this season is fully watched. """
|
||||
return bool(self.viewedLeafCount == self.leafCount)
|
||||
|
||||
@property
|
||||
def seasonNumber(self):
|
||||
"""Returns season number."""
|
||||
""" Returns season number. """
|
||||
return self.index
|
||||
|
||||
def episodes(self, **kwargs):
|
||||
""" Returs a list of Episode. """
|
||||
""" Returns a list of :class:`~plexapi.video.Episode` objects. """
|
||||
key = '/library/metadata/%s/children' % self.ratingKey
|
||||
return self.fetchItems(key, **kwargs)
|
||||
|
||||
def episode(self, title=None, num=None):
|
||||
def episode(self, title=None, episode=None):
|
||||
""" Returns the episode with the given title or number.
|
||||
|
||||
Parameters:
|
||||
title (str): Title of the episode to return.
|
||||
num (int): Number of the episode to return (if title not specified).
|
||||
episode (int): Episode number (default:None; required if title not specified).
|
||||
|
||||
Raises:
|
||||
TypeError: If title and episode is missing.
|
||||
NotFound: If that episode cant be found.
|
||||
|
||||
Examples:
|
||||
>>> plex.search('The blacklist').season(1).episode(episode=1)
|
||||
<Episode:116263:The.Freelancer>
|
||||
>>> plex.search('The blacklist').season(1).episode('The Freelancer')
|
||||
<Episode:116263:The.Freelancer>
|
||||
|
||||
"""
|
||||
if not title and not num:
|
||||
if not title and not episode:
|
||||
raise BadRequest('Missing argument, you need to use title or episode.')
|
||||
|
||||
key = '/library/metadata/%s/children' % self.ratingKey
|
||||
if title:
|
||||
return self.fetchItem(key, title=title)
|
||||
return self.fetchItem(key, seasonNumber=self.index, index=num)
|
||||
return self.fetchItem(key, seasonNumber=self.index, index=episode)
|
||||
|
||||
def get(self, title):
|
||||
""" Alias for self.episode. """
|
||||
return self.episode(title)
|
||||
def get(self, title=None, episode=None):
|
||||
""" Alias to :func:`~plexapi.video.Season.episode()`. """
|
||||
return self.episode(title, episode)
|
||||
|
||||
def show(self):
|
||||
"""Return this seasons show."""
|
||||
""" Return this seasons :func:`~plexapi.video.Show`.. """
|
||||
return self.fetchItem(self.parentKey)
|
||||
|
||||
def watched(self):
|
||||
"""Returns a list of watched Episode"""
|
||||
""" Returns list of watched :class:`~plexapi.video.Episode` objects. """
|
||||
return self.episodes(watched=True)
|
||||
|
||||
def unwatched(self):
|
||||
"""Returns a list of unwatched Episode"""
|
||||
""" Returns list of unwatched :class:`~plexapi.video.Episode` objects. """
|
||||
return self.episodes(watched=False)
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
""" Download video files to specified directory.
|
||||
|
||||
Parameters:
|
||||
savepath (str): Defaults to current working dir.
|
||||
keep_orginal_name (bool): True to keep the original file name otherwise
|
||||
a friendlier is generated.
|
||||
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`.
|
||||
"""
|
||||
downloaded = []
|
||||
for ep in self.episodes():
|
||||
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
||||
|
@ -362,15 +411,40 @@ class Season(Video):
|
|||
|
||||
@utils.registerPlexObject
|
||||
class Episode(Video, Playable):
|
||||
""" Represents a single Shows Episode.
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Diectory'
|
||||
TYPE (str): 'episode'
|
||||
art (str): Key to episode artwork (/library/metadata/<ratingkey>/art/<artid>)
|
||||
chapterSource (str): Unknown (media).
|
||||
contentRating (str) Content rating (PG-13; NR; TV-G).
|
||||
duration (int): Duration of episode in milliseconds.
|
||||
grandparentArt (str): Key to this episodes :class:`~plexapi.video.Show` artwork.
|
||||
grandparentKey (str): Key to this episodes :class:`~plexapi.video.Show`.
|
||||
grandparentRatingKey (str): Unique key for this episodes :class:`~plexapi.video.Show`.
|
||||
grandparentTheme (str): Key to this episodes :class:`~plexapi.video.Show` theme.
|
||||
grandparentThumb (str): Key to this episodes :class:`~plexapi.video.Show` thumb.
|
||||
grandparentTitle (str): Title of this episodes :class:`~plexapi.video.Show`.
|
||||
guid (str): Plex GUID (com.plexapp.agents.imdb://tt4302938?lang=en).
|
||||
index (int): Episode number.
|
||||
originallyAvailableAt (datetime): Datetime episode was released.
|
||||
parentIndex (str): Season number of episode.
|
||||
parentKey (str): Key to this episodes :class:`~plexapi.video.Season`.
|
||||
parentRatingKey (int): Unique key for this episodes :class:`~plexapi.video.Season`.
|
||||
parentThumb (str): Key to this episodes thumbnail.
|
||||
rating (float): Movie rating (7.9; 9.8; 8.1).
|
||||
viewOffset (int): View offset in milliseconds.
|
||||
year (int): Year episode was released.
|
||||
directors (List<:class:`~plexapi.media.Director`>): List of director objects.
|
||||
media (List<:class:`~plexapi.media.Media`>): List of media objects.
|
||||
writers (List<:class:`~plexapi.media.Writer`>): List of writers objects.
|
||||
"""
|
||||
TAG = 'Video'
|
||||
TYPE = 'episode'
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Used to set the attributes
|
||||
|
||||
Args:
|
||||
data (Element): Usually built from server.query
|
||||
"""
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Video._loadData(self, data)
|
||||
Playable._loadData(self, data)
|
||||
self._seasonNumber = None # cached season number
|
||||
|
@ -405,14 +479,21 @@ class Episode(Video, Playable):
|
|||
'%s-s%se%s' % (self.grandparentTitle.replace(' ','-')[:20], self.seasonNumber, self.index),
|
||||
] if p])
|
||||
|
||||
def _prettyfilename(self):
|
||||
""" Returns a human friendly filename. """
|
||||
return '%s.S%sE%s' % (self.grandparentTitle.replace(' ', '.'),
|
||||
str(self.seasonNumber).zfill(2), str(self.index).zfill(2))
|
||||
|
||||
@property
|
||||
def isWatched(self):
|
||||
"""Returns True if watched, False if not."""
|
||||
return bool(self.viewCount > 0)
|
||||
def locations(self):
|
||||
""" This does not exist in plex xml response but is added to have a common
|
||||
interface to get the location of the Movie/Show
|
||||
"""
|
||||
return [part.file for part in self.iterParts() if part]
|
||||
|
||||
@property
|
||||
def seasonNumber(self):
|
||||
"""Return this episode seasonnumber."""
|
||||
""" Returns this episodes season number. """
|
||||
if self._seasonNumber is None:
|
||||
self._seasonNumber = self.parentIndex if self.parentIndex else self.season().seasonNumber
|
||||
return utils.cast(int, self._seasonNumber)
|
||||
|
@ -424,20 +505,9 @@ class Episode(Video, Playable):
|
|||
return self._server.url(self.grandparentThumb)
|
||||
|
||||
def season(self):
|
||||
"""Return this episode Season"""
|
||||
"""" Return this episodes :func:`~plexapi.video.Season`.. """
|
||||
return self.fetchItem(self.parentKey)
|
||||
|
||||
def show(self):
|
||||
"""Return this episodes Show"""
|
||||
"""" Return this episodes :func:`~plexapi.video.Show`.. """
|
||||
return self.fetchItem(self.grandparentKey)
|
||||
|
||||
@property
|
||||
def locations(self):
|
||||
""" This does not exist in plex xml response but is added to have a common
|
||||
interface to get the location of the Movie/Show
|
||||
"""
|
||||
return [p.file for p in self.iterParts() if p]
|
||||
|
||||
def _prettyfilename(self):
|
||||
return '%s.S%sE%s' % (self.grandparentTitle.replace(' ', '.'),
|
||||
str(self.seasonNumber).zfill(2), str(self.index).zfill(2))
|
||||
|
|
|
@ -6,6 +6,7 @@ each media type. The resulting list can be compared with the current object
|
|||
implementation in python-plex api to track new attributes and depricate old ones.
|
||||
"""
|
||||
import argparse, copy, pickle, plexapi, os, sys, time
|
||||
from os.path import abspath, dirname, join
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from plexapi import library
|
||||
|
@ -14,7 +15,7 @@ from plexapi.myplex import MyPlexAccount
|
|||
from plexapi.server import PlexServer
|
||||
from plexapi.playqueue import PlayQueue
|
||||
|
||||
CACHEPATH = '/tmp/findattrs.pickle'
|
||||
CACHEPATH = join(dirname(abspath(__file__)), 'findattrs.pickle')
|
||||
NAMESPACE = {
|
||||
'xml': defaultdict(int),
|
||||
'obj': defaultdict(int),
|
||||
|
@ -22,7 +23,7 @@ NAMESPACE = {
|
|||
'categories': defaultdict(set),
|
||||
'total': 0,
|
||||
'old': 0,
|
||||
'new': 0,
|
||||
'new': 0
|
||||
}
|
||||
IGNORES = {
|
||||
'server.PlexServer': ['baseurl', 'token', 'session'],
|
||||
|
@ -46,12 +47,10 @@ DONT_RELOAD = (
|
|||
'photo.Photoalbum',
|
||||
'server.Account',
|
||||
'client.PlexClient', # we dont have the token to reload.
|
||||
#'server.PlexServer', # setting version to None? :(
|
||||
)
|
||||
TAGATTRS = {
|
||||
'Media': 'media',
|
||||
'Country': 'countries',
|
||||
|
||||
}
|
||||
STOP_RECURSING_AT = (
|
||||
#'media.MediaPart',
|
||||
|
@ -157,11 +156,11 @@ class PlexAttributes():
|
|||
|
||||
def _parse_client(self):
|
||||
for device in self.account.devices():
|
||||
client = device.connect(safe=True)
|
||||
client = self._safe_connect(device)
|
||||
if client is not None:
|
||||
self._load_attrs(client, 'myplex')
|
||||
for client in self.plex.clients():
|
||||
client.connect(safe=True)
|
||||
self._safe_connect(client)
|
||||
self._load_attrs(client, 'client')
|
||||
|
||||
def _parse_playlist(self):
|
||||
|
@ -215,7 +214,8 @@ class PlexAttributes():
|
|||
|
||||
def _load_obj_attrs(self, clsname, obj, attrs):
|
||||
if clsname in STOP_RECURSING_AT: return None
|
||||
if isinstance(obj, PlexObject) and clsname not in DONT_RELOAD: obj.reload(safe=True)
|
||||
if isinstance(obj, PlexObject) and clsname not in DONT_RELOAD:
|
||||
self._safe_reload(obj)
|
||||
for attr, value in obj.__dict__.items():
|
||||
if value is None or isinstance(value, (str, bool, float, int, datetime)):
|
||||
if not attr.startswith('_') and attr not in IGNORES.get(clsname, []):
|
||||
|
@ -271,6 +271,18 @@ class PlexAttributes():
|
|||
return _('old', 'red')
|
||||
return _(' ', 'green')
|
||||
|
||||
def _safe_connect(self, elem):
|
||||
try:
|
||||
return elem.connect()
|
||||
except:
|
||||
return None
|
||||
|
||||
def _safe_reload(self, elem):
|
||||
try:
|
||||
elem.reload()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _(text, color):
|
||||
FMTSTR = '\033[%dm%s\033[0m'
|
||||
|
|
Loading…
Reference in a new issue