Skip to content

Commit 0aeb6f9

Browse files
authored
Merge remote-tracking branch 'origin/master' into feature/delete_resources
2 parents 759e75e + 97c13e5 commit 0aeb6f9

19 files changed

+947
-63
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pyvenv.cfg
2727
MANIFEST
2828
venv/
2929
.venv/
30+
private.key
31+
public.key
3032

3133
# path for the test lib.
3234
plex/

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Installation & Documentation
3737
.. code-block:: python
3838
3939
pip install plexapi[alert] # Install with dependencies required for plexapi.alert
40+
pip install plexapi[jwt] # Install with dependencies required for Plex JWT authentication
4041
4142
Documentation_ can be found at Read the Docs.
4243

plexapi/audio.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from plexapi.exceptions import BadRequest
1313
from plexapi.mixins import (
1414
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, PlayedUnplayedMixin, RatingMixin,
15-
ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, ThemeMixin, ThemeUrlMixin,
15+
ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, SquareArtMixin, SquareArtUrlMixin, ThemeMixin, ThemeUrlMixin,
1616
ArtistEditMixins, AlbumEditMixins, TrackEditMixins
1717
)
1818
from plexapi.playlist import Playlist
@@ -181,7 +181,7 @@ def sonicallySimilar(
181181
class Artist(
182182
Audio,
183183
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin,
184-
ArtMixin, PosterMixin, ThemeMixin,
184+
ArtMixin, PosterMixin, SquareArtMixin, ThemeMixin,
185185
ArtistEditMixins
186186
):
187187
""" Represents a single Artist.
@@ -351,7 +351,7 @@ def metadataDirectory(self):
351351
class Album(
352352
Audio,
353353
SplitMergeMixin, UnmatchMatchMixin, RatingMixin,
354-
ArtMixin, PosterMixin, ThemeUrlMixin,
354+
ArtMixin, PosterMixin, SquareArtMixin, ThemeUrlMixin,
355355
AlbumEditMixins
356356
):
357357
""" Represents a single Album.
@@ -504,7 +504,7 @@ def metadataDirectory(self):
504504
class Track(
505505
Audio, Playable,
506506
ExtrasMixin, RatingMixin,
507-
ArtUrlMixin, PosterUrlMixin, ThemeUrlMixin,
507+
ArtUrlMixin, PosterUrlMixin, SquareArtUrlMixin, ThemeUrlMixin,
508508
TrackEditMixins
509509
):
510510
""" Represents a single Track.

plexapi/collection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from plexapi.library import LibrarySection, ManagedHub
99
from plexapi.mixins import (
1010
AdvancedSettingsMixin, SmartFilterMixin, HubsMixin, RatingMixin,
11-
ArtMixin, PosterMixin, ThemeMixin,
11+
ArtMixin, PosterMixin, SquareArtMixin, ThemeMixin,
1212
CollectionEditMixins
1313
)
1414
from plexapi.utils import deprecated
@@ -18,7 +18,7 @@
1818
class Collection(
1919
PlexPartialObject,
2020
AdvancedSettingsMixin, SmartFilterMixin, HubsMixin, RatingMixin,
21-
ArtMixin, PosterMixin, ThemeMixin,
21+
ArtMixin, PosterMixin, SquareArtMixin, ThemeMixin,
2222
CollectionEditMixins
2323
):
2424
""" Represents a single Collection.

plexapi/media.py

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ class MediaTag(PlexObject):
741741
742742
Attributes:
743743
filter (str): The library filter for the tag.
744-
id (id): Tag ID (This seems meaningless except to use it as a unique id).
744+
id (int): Tag ID (This seems meaningless except to use it as a unique id).
745745
key (str): API URL (/library/section/<librarySectionID>/all?<filter>).
746746
role (str): The name of the character role for :class:`~plexapi.media.Role` only.
747747
tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of
@@ -1116,6 +1116,11 @@ class Poster(BaseResource):
11161116
TAG = 'Photo'
11171117

11181118

1119+
class SquareArt(BaseResource):
1120+
""" Represents a single Square Art object. """
1121+
TAG = 'Photo'
1122+
1123+
11191124
class Theme(BaseResource):
11201125
""" Represents a single Theme object. """
11211126
TAG = 'Track'
@@ -1366,3 +1371,123 @@ class Level(PlexObject):
13661371
def _loadData(self, data):
13671372
""" Load attribute values from Plex XML response. """
13681373
self.loudness = utils.cast(float, data.attrib.get('v'))
1374+
1375+
1376+
@utils.registerPlexObject
1377+
class CommonSenseMedia(PlexObject):
1378+
""" Represents a single CommonSenseMedia media tag.
1379+
Note: This object is only loaded with partial data from a Plex Media Server.
1380+
Call `reload()` to load the full data from Plex Discover (Plex Pass required).
1381+
1382+
Attributes:
1383+
TAG (str): 'CommonSenseMedia'
1384+
ageRatings (List<:class:`~plexapi.media.AgeRating`>): List of AgeRating objects.
1385+
anyGood (str): A brief description of the media's quality.
1386+
id (int): The ID of the CommonSenseMedia tag.
1387+
key (str): The unique key for the CommonSenseMedia tag.
1388+
oneLiner (str): A brief description of the CommonSenseMedia tag.
1389+
parentalAdvisoryTopics (List<:class:`~plexapi.media.ParentalAdvisoryTopic`>):
1390+
List of ParentalAdvisoryTopic objects.
1391+
parentsNeedToKnow (str): A brief description of what parents need to know about the media.
1392+
talkingPoints (List<:class:`~plexapi.media.TalkingPoint`>): List of TalkingPoint objects.
1393+
1394+
Example:
1395+
1396+
.. code-block:: python
1397+
1398+
from plexapi.server import PlexServer
1399+
plex = PlexServer('http://localhost:32400', token='xxxxxxxxxxxxxxxxxxxx')
1400+
1401+
# Retrieve the Common Sense Media info for a movie
1402+
movie = plex.library.section('Movies').get('Cars')
1403+
commonSenseMedia = movie.commonSenseMedia
1404+
ageRating = commonSenseMedia.ageRatings[0].age
1405+
1406+
# Load the Common Sense Media info from Plex Discover (Plex Pass required)
1407+
commonSenseMedia.reload()
1408+
parentalAdvisoryTopics = commonSenseMedia.parentalAdvisoryTopics
1409+
talkingPoints = commonSenseMedia.talkingPoints
1410+
1411+
"""
1412+
TAG = 'CommonSenseMedia'
1413+
1414+
def _loadData(self, data):
1415+
self.ageRatings = self.findItems(data, AgeRating)
1416+
self.anyGood = data.attrib.get('anyGood')
1417+
self.id = utils.cast(int, data.attrib.get('id'))
1418+
self.key = data.attrib.get('key')
1419+
self.oneLiner = data.attrib.get('oneLiner')
1420+
self.parentalAdvisoryTopics = self.findItems(data, ParentalAdvisoryTopic)
1421+
self.parentsNeedToKnow = data.attrib.get('parentsNeedToKnow')
1422+
self.talkingPoints = self.findItems(data, TalkingPoint)
1423+
1424+
def _reload(self, **kwargs):
1425+
""" Reload the data for the Common Sense Media object. """
1426+
guid = self._parent().guid
1427+
if not guid.startswith('plex://'):
1428+
return self
1429+
1430+
ratingKey = guid.rsplit('/', 1)[-1]
1431+
account = self._server.myPlexAccount()
1432+
key = f'{account.METADATA}/library/metadata/{ratingKey}/commonsensemedia'
1433+
data = account.query(key)
1434+
self._findAndLoadElem(data)
1435+
return self
1436+
1437+
1438+
@utils.registerPlexObject
1439+
class AgeRating(PlexObject):
1440+
""" Represents a single AgeRating for a Common Sense Media tag.
1441+
1442+
Attributes:
1443+
TAG (str): 'AgeRating'
1444+
age (float): The age rating (e.g. 13, 17).
1445+
ageGroup (str): The age group for the rating (e.g. Little Kids, Teens, etc.).
1446+
rating (float): The star rating (out of 5).
1447+
ratingCount (int): The number of ratings contributing to the star rating.
1448+
type (str): The type of rating (official, adult, child).
1449+
"""
1450+
TAG = 'AgeRating'
1451+
1452+
def _loadData(self, data):
1453+
self.age = utils.cast(float, data.attrib.get('age'))
1454+
self.ageGroup = data.attrib.get('ageGroup')
1455+
self.rating = utils.cast(float, data.attrib.get('rating'))
1456+
self.ratingCount = utils.cast(int, data.attrib.get('ratingCount'))
1457+
self.type = data.attrib.get('type')
1458+
1459+
1460+
@utils.registerPlexObject
1461+
class TalkingPoint(PlexObject):
1462+
""" Represents a single TalkingPoint for a Common Sense Media tag.
1463+
1464+
Attributes:
1465+
TAG (str): 'TalkingPoint'
1466+
tag (str): The description of the talking point.
1467+
"""
1468+
TAG = 'TalkingPoint'
1469+
1470+
def _loadData(self, data):
1471+
self.tag = data.attrib.get('tag')
1472+
1473+
1474+
@utils.registerPlexObject
1475+
class ParentalAdvisoryTopic(PlexObject):
1476+
""" Represents a single ParentalAdvisoryTopic for a Common Sense Media tag.
1477+
1478+
Attributes:
1479+
TAG (str): 'ParentalAdvisoryTopic'
1480+
id (str): The ID of the topic (e.g. violence, language, etc.).
1481+
label (str): The label for the topic (e.g. Violence & Scariness, Language, etc.).
1482+
positive (bool): Whether the topic is considered positive.
1483+
rating (float): The rating of the topic (out of 5).
1484+
tag (str): The description of the parental advisory topic.
1485+
"""
1486+
TAG = 'ParentalAdvisoryTopic'
1487+
1488+
def _loadData(self, data):
1489+
self.id = data.attrib.get('id')
1490+
self.label = data.attrib.get('label')
1491+
self.positive = utils.cast(bool, data.attrib.get('positive'))
1492+
self.rating = utils.cast(float, data.attrib.get('rating'))
1493+
self.tag = data.attrib.get('tag')

plexapi/mixins.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def uploadArt(self, url=None, filepath=None):
382382
383383
Parameters:
384384
url (str): The full URL to the image to upload.
385-
filepath (str): The full file path the the image to upload or file-like object.
385+
filepath (str): The full file path to the image to upload or file-like object.
386386
"""
387387
if url:
388388
key = f'/library/metadata/{self.ratingKey}/arts?url={quote_plus(url)}'
@@ -443,7 +443,7 @@ def uploadLogo(self, url=None, filepath=None):
443443
444444
Parameters:
445445
url (str): The full URL to the image to upload.
446-
filepath (str): The full file path the the image to upload or file-like object.
446+
filepath (str): The full file path to the image to upload or file-like object.
447447
"""
448448
if url:
449449
key = f'/library/metadata/{self.ratingKey}/clearLogos?url={quote_plus(url)}'
@@ -509,7 +509,7 @@ def uploadPoster(self, url=None, filepath=None):
509509
510510
Parameters:
511511
url (str): The full URL to the image to upload.
512-
filepath (str): The full file path the the image to upload or file-like object.
512+
filepath (str): The full file path to the image to upload or file-like object.
513513
"""
514514
if url:
515515
key = f'/library/metadata/{self.ratingKey}/posters?url={quote_plus(url)}'
@@ -536,6 +536,71 @@ def deletePoster(self):
536536
return self
537537

538538

539+
class SquareArtUrlMixin:
540+
""" Mixin for Plex objects that can have a square art url. """
541+
542+
@property
543+
def squareArt(self):
544+
""" Return the API path to the square art image. """
545+
return next((i.url for i in self.images if i.type == 'backgroundSquare'), None)
546+
547+
@property
548+
def squareArtUrl(self):
549+
""" Return the square art url for the Plex object. """
550+
return self._server.url(self.squareArt, includeToken=True) if self.squareArt else None
551+
552+
553+
class SquareArtLockMixin:
554+
""" Mixin for Plex objects that can have a locked square art. """
555+
556+
def lockSquareArt(self):
557+
""" Lock the square art for a Plex object. """
558+
return self._edit(**{'squareArt.locked': 1})
559+
560+
def unlockSquareArt(self):
561+
""" Unlock the square art for a Plex object. """
562+
return self._edit(**{'squareArt.locked': 0})
563+
564+
565+
class SquareArtMixin(SquareArtUrlMixin, SquareArtLockMixin):
566+
""" Mixin for Plex objects that can have square art. """
567+
568+
def squareArts(self):
569+
""" Returns list of available :class:`~plexapi.media.SquareArt` objects. """
570+
return self.fetchItems(f'/library/metadata/{self.ratingKey}/squareArts', cls=media.SquareArt)
571+
572+
def uploadSquareArt(self, url=None, filepath=None):
573+
""" Upload a square art from a url or filepath.
574+
575+
Parameters:
576+
url (str): The full URL to the image to upload.
577+
filepath (str): The full file path to the image to upload or file-like object.
578+
"""
579+
if url:
580+
key = f'/library/metadata/{self.ratingKey}/squareArts?url={quote_plus(url)}'
581+
self._server.query(key, method=self._server._session.post)
582+
elif filepath:
583+
key = f'/library/metadata/{self.ratingKey}/squareArts'
584+
data = openOrRead(filepath)
585+
self._server.query(key, method=self._server._session.post, data=data)
586+
return self
587+
588+
def setSquareArt(self, squareArt):
589+
""" Set the square art for a Plex object.
590+
591+
Parameters:
592+
squareArt (:class:`~plexapi.media.SquareArt`): The square art object to select.
593+
"""
594+
squareArt.select()
595+
return self
596+
597+
def deleteSquareArt(self):
598+
""" Delete the square art from a Plex object. """
599+
key = f'/library/metadata/{self.ratingKey}/squareArt'
600+
self._server.query(key, method=self._server._session.delete)
601+
return self
602+
603+
539604
class ThemeUrlMixin:
540605
""" Mixin for Plex objects that can have a theme url. """
541606

0 commit comments

Comments
 (0)