Skip to content

Commit 659b549

Browse files
JonnyWong16Copilot
andauthored
Add support for Common Sense Media (#1553)
* Add support for Common Sense Media * Update tag types * Add tests for Common Senses Media * Single commonSenseMedia object * Fix docstring example indent Co-authored-by: Copilot <[email protected]> * Fix Common Sense Media reload docstring Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 00e7226 commit 659b549

File tree

4 files changed

+184
-4
lines changed

4 files changed

+184
-4
lines changed

plexapi/media.py

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

plexapi/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
'mood': 300,
8181
'style': 301,
8282
'format': 302,
83+
'subformat': 303,
8384
'similar': 305,
8485
'concert': 306,
8586
'banner': 311,
@@ -92,7 +93,9 @@
9293
'network': 319,
9394
'showOrdering': 322,
9495
'clearLogo': 323,
96+
'commonSenseMedia': 324,
9597
'place': 400,
98+
'sharedWidth': 500,
9699
}
97100
REVERSETAGTYPES = {v: k for k, v in TAGTYPES.items()}
98101

plexapi/video.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,12 @@ class Movie(
349349
TYPE (str): 'movie'
350350
audienceRating (float): Audience rating (usually from Rotten Tomatoes).
351351
audienceRatingImage (str): Key to audience rating image (rottentomatoes://image.rating.spilled).
352-
chapters (List<:class:`~plexapi.media.Chapter`>): List of Chapter objects.
352+
chapters (List<:class:`~plexapi.media.Chapter`>): List of chapter objects.
353353
chapterSource (str): Chapter source (agent; media; mixed).
354354
collections (List<:class:`~plexapi.media.Collection`>): List of collection objects.
355+
commonSenseMedia (:class:`~plexapi.media.CommonSenseMedia`): Common Sense Media object.
355356
contentRating (str) Content rating (PG-13; NR; TV-G).
356-
countries (List<:class:`~plexapi.media.Country`>): List of countries objects.
357+
countries (List<:class:`~plexapi.media.Country`>): List of country objects.
357358
directors (List<:class:`~plexapi.media.Director`>): List of director objects.
358359
duration (int): Duration of the movie in milliseconds.
359360
editionTitle (str): The edition title of the movie (e.g. Director's Cut, Extended Edition, etc.).
@@ -426,6 +427,10 @@ def chapters(self):
426427
def collections(self):
427428
return self.findItems(self._data, media.Collection)
428429

430+
@cached_data_property
431+
def commonSenseMedia(self):
432+
return self.findItem(self._data, media.CommonSenseMedia)
433+
429434
@cached_data_property
430435
def countries(self):
431436
return self.findItems(self._data, media.Country)
@@ -566,6 +571,7 @@ class Show(
566571
100 = On next refresh).
567572
childCount (int): Number of seasons (including Specials) in the show.
568573
collections (List<:class:`~plexapi.media.Collection`>): List of collection objects.
574+
commonSenseMedia (:class:`~plexapi.media.CommonSenseMedia`): Common Sense Media object.
569575
contentRating (str) Content rating (PG-13; NR; TV-G).
570576
duration (int): Typical duration of the show episodes in milliseconds.
571577
enableCreditsMarkerGeneration (int): Setting that indicates if credits markers detection is enabled.
@@ -651,6 +657,10 @@ def _loadData(self, data):
651657
def collections(self):
652658
return self.findItems(self._data, media.Collection)
653659

660+
@cached_data_property
661+
def commonSenseMedia(self):
662+
return self.findItem(self._data, media.CommonSenseMedia)
663+
654664
@cached_data_property
655665
def genres(self):
656666
return self.findItems(self._data, media.Genre)
@@ -984,7 +994,7 @@ class Episode(
984994
TYPE (str): 'episode'
985995
audienceRating (float): Audience rating (TMDB or TVDB).
986996
audienceRatingImage (str): Key to audience rating image (tmdb://image.rating).
987-
chapters (List<:class:`~plexapi.media.Chapter`>): List of Chapter objects.
997+
chapters (List<:class:`~plexapi.media.Chapter`>): List of chapter objects.
988998
chapterSource (str): Chapter source (agent; media; mixed).
989999
collections (List<:class:`~plexapi.media.Collection`>): List of collection objects.
9901000
contentRating (str) Content rating (PG-13; NR; TV-G).

tests/test_video.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,53 @@ def test_video_Show_streamingServices(show):
10191019
assert show.streamingServices()
10201020

10211021

1022+
def test_video_Show_commonSenseMedia(show):
1023+
commonSenseMedia = show.commonSenseMedia
1024+
assert utils.is_int(commonSenseMedia.id)
1025+
assert commonSenseMedia.oneLiner
1026+
1027+
ageRating = commonSenseMedia.ageRatings[0]
1028+
assert ageRating.type == 'official'
1029+
assert utils.is_float(ageRating.age, gte=0.0)
1030+
assert utils.is_float(ageRating.rating, gte=0.0)
1031+
1032+
1033+
@pytest.mark.authenticated
1034+
def test_video_Show_commonSenseMedia_full(account_plexpass, show):
1035+
commonSenseMedia = show.commonSenseMedia
1036+
commonSenseMedia.reload()
1037+
assert commonSenseMedia.anyGood
1038+
assert commonSenseMedia.key
1039+
assert commonSenseMedia.oneLiner
1040+
assert commonSenseMedia.parentsNeedToKnow
1041+
1042+
ageRatings = commonSenseMedia.ageRatings
1043+
assert len(ageRatings) == 3
1044+
types = {r.type for r in ageRatings}
1045+
assert types == {'official', 'child', 'adult'}
1046+
ageRating = next(r for r in ageRatings if r.type == 'official')
1047+
assert utils.is_float(ageRating.age, gte=0.0)
1048+
if ageRating.ageGroup is not None:
1049+
assert ageRating.ageGroup
1050+
assert utils.is_float(ageRating.rating, gte=0.0)
1051+
if ageRating.ratingCount is not None:
1052+
assert utils.is_int(ageRating.ratingCount, gte=0)
1053+
1054+
talkingPoints = commonSenseMedia.talkingPoints
1055+
assert len(talkingPoints)
1056+
talkingPoint = talkingPoints[0]
1057+
assert talkingPoint.tag
1058+
1059+
parentalAdvisoryTopics = commonSenseMedia.parentalAdvisoryTopics
1060+
assert len(parentalAdvisoryTopics)
1061+
parentalAdvisoryTopic = parentalAdvisoryTopics[0]
1062+
assert parentalAdvisoryTopic.id
1063+
assert parentalAdvisoryTopic.label
1064+
assert utils.is_bool(parentalAdvisoryTopic.positive)
1065+
assert utils.is_float(parentalAdvisoryTopic.rating, gte=0.0)
1066+
assert parentalAdvisoryTopic.tag
1067+
1068+
10221069
def test_video_Season(show):
10231070
seasons = show.seasons()
10241071
assert len(seasons) == 2

0 commit comments

Comments
 (0)