Skip to content

Commit c3fdc93

Browse files
authored
Merge branch 'master' into feature/delete_resources
2 parents 96e2778 + 97c13e5 commit c3fdc93

19 files changed

Lines changed: 948 additions & 63 deletions

.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: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from plexapi.exceptions import BadRequest
1313
from plexapi.mixins import (
1414
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, PlayedUnplayedMixin, RatingMixin,
15-
ArtUrlMixin, ArtMixin, LogoMixin, LogoUrlMixin, PosterUrlMixin, PosterMixin, ThemeMixin, ThemeUrlMixin,
15+
ArtUrlMixin, ArtMixin, LogoMixin, LogoUrlMixin, PosterUrlMixin, PosterMixin, SquareArtMixin, SquareArtUrlMixin,
16+
ThemeMixin, ThemeUrlMixin,
1617
ArtistEditMixins, AlbumEditMixins, TrackEditMixins
1718
)
1819
from plexapi.playlist import Playlist
@@ -181,7 +182,7 @@ def sonicallySimilar(
181182
class Artist(
182183
Audio,
183184
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin,
184-
ArtMixin, LogoMixin, PosterMixin, ThemeMixin,
185+
ArtMixin, LogoMixin, PosterMixin, SquareArtMixin, ThemeMixin,
185186
ArtistEditMixins
186187
):
187188
""" Represents a single Artist.
@@ -351,7 +352,7 @@ def metadataDirectory(self):
351352
class Album(
352353
Audio,
353354
SplitMergeMixin, UnmatchMatchMixin, RatingMixin,
354-
ArtMixin, LogoMixin, PosterMixin, ThemeUrlMixin,
355+
ArtMixin, LogoMixin, PosterMixin, SquareArtMixin, ThemeUrlMixin,
355356
AlbumEditMixins
356357
):
357358
""" Represents a single Album.
@@ -504,7 +505,7 @@ def metadataDirectory(self):
504505
class Track(
505506
Audio, Playable,
506507
ExtrasMixin, RatingMixin,
507-
ArtUrlMixin, LogoUrlMixin, PosterUrlMixin, ThemeUrlMixin,
508+
ArtUrlMixin, LogoUrlMixin, PosterUrlMixin, SquareArtUrlMixin, ThemeUrlMixin,
508509
TrackEditMixins
509510
):
510511
""" 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, LogoMixin, PosterMixin, ThemeMixin,
11+
ArtMixin, LogoMixin, 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, LogoMixin, PosterMixin, ThemeMixin,
21+
ArtMixin, LogoMixin, 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)}'
@@ -447,7 +447,7 @@ def uploadLogo(self, url=None, filepath=None):
447447
448448
Parameters:
449449
url (str): The full URL to the image to upload.
450-
filepath (str): The full file path the the image to upload or file-like object.
450+
filepath (str): The full file path to the image to upload or file-like object.
451451
"""
452452
if url:
453453
key = f'/library/metadata/{self.ratingKey}/clearLogos?url={quote_plus(url)}'
@@ -513,7 +513,7 @@ def uploadPoster(self, url=None, filepath=None):
513513
514514
Parameters:
515515
url (str): The full URL to the image to upload.
516-
filepath (str): The full file path the the image to upload or file-like object.
516+
filepath (str): The full file path to the image to upload or file-like object.
517517
"""
518518
if url:
519519
key = f'/library/metadata/{self.ratingKey}/posters?url={quote_plus(url)}'
@@ -540,6 +540,71 @@ def deletePoster(self):
540540
return self
541541

542542

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

0 commit comments

Comments
 (0)