From 442f2dfb03d219a9f15aee4bc20a78adb135c1a9 Mon Sep 17 00:00:00 2001
From: natyusha <985941+natyusha@users.noreply.github.com>
Date: Thu, 6 Jun 2024 14:34:23 -0400
Subject: [PATCH] Add the "Suggestive Dialogue" content descriptor
- also add a TV-14 rating upgrade for anime with a 300 weight of the "sex" tag (barely affects any non restricted series)
- thanks to revam for the idea to use anidb's "sexual humour" tag
- formatting changes
- bump version
---
Contents/Code/__init__.py | 69 +++++++++----------
Contents/Info.plist | 2 +-
.../Scanners/Series/Shoko Relay Scanner.py | 4 +-
README.md | 3 +-
4 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/Contents/Code/__init__.py b/Contents/Code/__init__.py
index bc5fb0d..7bc887a 100644
--- a/Contents/Code/__init__.py
+++ b/Contents/Code/__init__.py
@@ -7,7 +7,7 @@ def ValidatePrefs():
pass
def Start():
- Log('======================[Shoko Relay Agent v1.1.16]======================')
+ Log('======================[Shoko Relay Agent v1.1.17]======================')
HTTP.Headers['Accept'] = 'application/json'
HTTP.ClearCache() # Clear the cache possibly removing stuck metadata
HTTP.CacheTime = 0.1 # Reduce the cache time as much as possible since Shoko has all the metadata
@@ -82,7 +82,7 @@ def Update(self, metadata, media, lang, force):
if not Prefs['SingleSeasonOrdering'] and try_get(series_data['TvDB'], 0, None):
tvdb_title, tvdb_id = try_get(series_data['TvDB'][0], 'Title', None), try_get(series_data['TvDB'][0], 'ID', None)
if tvdb_title: tvdb_check = True
- else: tvdb_check, tvdb_title = False, 'N/A (CRITICAL: Removed from TvDB or Missing Data) - Falling Back to AniDB Ordering!' # Account for rare cases where Shoko has a TvDb ID that returns no data
+ else: tvdb_check, tvdb_title = False, 'N/A (CRITICAL: Removed from TvDB or Missing Data) - Falling Back to AniDB Ordering!' # Account for rare cases where Shoko has a TvDB ID that returns no data
Log('TvDB Check (Title [ID]): %s [%s]' % (tvdb_title, tvdb_id))
else: tvdb_check = False
@@ -143,24 +143,24 @@ def Update(self, metadata, media, lang, force):
series_tags = HttpReq('api/v3/Series/%s/Tags?filter=1&excludeDescriptions=true&orderByName=false&onlyVerified=true' % aid) # http://127.0.0.1:8111/api/v3/Series/24/Tags?filter=1&excludeDescriptions=true&orderByName=false&onlyVerified=true
metadata.genres.clear()
- ## Filter out weighted tags by the configured tag weight but leave ones weighted 0 as that means that they are unweighted tags
- tags, content_rating, content_descriptor, descriptor_s, descriptor_v = [], None, '', '', ''
+ ## Filter out weighted tags by the configured tag weight but leave ones weighted 0 as that means that they are unweighted (high priority) tags
+ tags, c_rating, descriptor, descriptor_d, descriptor_s, descriptor_v = [], None, '', '', '', ''
for tag in series_tags:
if (tag['Weight'] == 0 or tag['Weight'] >= int(Prefs['minimumTagWeight'])): tags.append(title_case(tag['Name'])) # Convert tags to title case and add them to the list
- if Prefs['contentRatings']: # Prep weight based content ratings (if enabled) using the content indicators described here: https://wiki.anidb.net/Categories:Content_Indicators
- indicator = tag['Name'].lower() # Raise ratings to TV-14 and then TV-MA if the weight exceeds 400 and 500 respectively
- if indicator == 'nudity':
- descriptor_s = 'S'
- if tag['Weight'] >= 400 and content_rating != 'TV-MA': content_rating = 'TV-14' # Full frontal nudity with nipples and/or visible genitals
- if tag['Weight'] >= 500: content_rating = 'TV-MA' # Most borderline porn / hentai
- if indicator == 'sex':
- descriptor_s = 'S' # 400 for sex is 99% TV-MA material
- if tag['Weight'] >= 400: content_rating = 'TV-MA' # Sexual activity that is "on camera", but most of the action is indirectly visible
- if indicator == 'violence':
- descriptor_v = 'V'
- if tag['Weight'] >= 400 and content_rating != 'TV-MA': content_rating = 'TV-14' # Any violence causing death and/or serious physical dismemberment (e.g. a limb is cut off)
- if tag['Weight'] >= 500: content_rating = 'TV-MA' # Added gore, repetitive killing/mutilation of more than 1 individual
- if descriptor_s or descriptor_v: content_descriptor = '-' + descriptor_s + descriptor_v
+ ## Prep weight based content ratings (if enabled) using the content indicators described here: https://wiki.anidb.net/Categories:Content_Indicators
+ if Prefs['contentRatings']:
+ indicator, weight = tag['Name'].lower(), tag['Weight']
+ if indicator == 'nudity' or indicator == 'violence': # Raise ratings for the "Nudity" and "Violence" tags to TV-14 and then TV-MA if the weight exceeds 400 and 500 respectively
+ if indicator == 'nudity': descriptor_s = 'S' # Apply the "Sexual Situations" descriptor for the "Nudity" tag
+ if indicator == 'violence': descriptor_v = 'V' # Apply the "Violence" descriptor for the "Violence" tag
+ if weight >= 400 and c_rating != 'TV-MA': c_rating = 'TV-14' # Weight:400 = Full frontal nudity with nipples and/or visible genitals OR Any violence causing death and/or serious physical dismemberment (e.g. a limb is cut off)
+ if weight >= 500: c_rating = 'TV-MA' # Weight:500 = Most borderline porn / hentai OR Added gore, repetitive killing/mutilation of more than 1 individual
+ if indicator == 'sex': # Raise ratings for the "Sex" tag to TV-MA if the weight exceeds 400
+ descriptor_s = 'S' # Apply the "Sexual Situations" descriptor for the "Sex" tag to TV-14 and then TV-MA if the weight exceeds 300 and 400 respectively
+ if weight >= 300 and c_rating != 'TV-MA': c_rating = 'TV-14' # Weight:300 = Sexual activity that is "on camera", but most of the action is not even indirectly visible
+ if weight >= 400: c_rating = 'TV-MA' # Weight:400 = Sexual activity that is "on camera", but most of the action is indirectly visible (99% TV-MA material)
+ if indicator == 'sexual humour': descriptor_d = 'D' # Apply the "Suggestive Dialogue" descriptor as a special case for the "Sexual Humour" tag
+ if descriptor_d or descriptor_s or descriptor_v: descriptor = '-' + descriptor_d + descriptor_s + descriptor_v
metadata.genres = tags
Log('Genres: %s' % ', '.join(tags))
@@ -181,18 +181,17 @@ def Update(self, metadata, media, lang, force):
# Get Content Rating (assumed from Genres)
## A rough approximation of: http://www.tvguidelines.org/resources/TheRatings.pdf
## Uses the target audience tags on AniDB: https://anidb.net/tag/2606/animetb
- ## Uses the content indicator tags + weights on AniDB: https://anidb.net/tag/2604/animetb
if Prefs['contentRatings']:
- if not content_rating: # If the rating wasn't already determined using the content indicators above take the lowest target audience rating
- if 'Kodomo' in tags: content_rating = 'TV-Y'
- elif 'Mina' in tags: content_rating = 'TV-G'
- elif 'Shoujo' in tags or 'Shounen' in tags: content_rating = 'TV-PG'
- elif 'Josei' in tags or 'Seinen' in tags: content_rating = 'TV-14'
- if 'Borderline Porn' in tags: content_rating = 'TV-MA' # Override any previous rating for borderline porn content
- if content_rating: content_rating += content_descriptor # Append the content descriptor using the content indicators above
- if '18 Restricted' in tags: content_rating = 'X' # Override any previous rating and remove content indicators for 18 restricted content
-
- metadata.content_rating = content_rating
+ if not c_rating: # If the rating wasn't already determined using the content indicators above take the lowest target audience rating
+ if 'Kodomo' in tags: c_rating = 'TV-Y'
+ elif 'Mina' in tags: c_rating = 'TV-G'
+ elif 'Shoujo' in tags or 'Shounen' in tags: c_rating = 'TV-PG'
+ elif 'Josei' in tags or 'Seinen' in tags: c_rating = 'TV-14'
+ if 'Borderline Porn' in tags: c_rating = 'TV-MA' # Override any previous rating for borderline porn content
+ if c_rating: c_rating += descriptor # Append the content descriptor using the content indicators above
+ if '18 Restricted' in tags: c_rating = 'X' # Override any previous rating and remove content indicators for 18 restricted content
+
+ metadata.content_rating = c_rating
Log('Content Rating (Assumed): %s' % metadata.content_rating)
# Get Posters & Backgrounds
@@ -357,7 +356,7 @@ def Update(self, metadata, media, lang, force):
# Set custom negative season names
for season_num in metadata.seasons:
season_title = None
- if season_num == '-1': season_title = 'Credits'
+ if season_num == '-1' : season_title = 'Credits'
elif season_num == '-2': season_title = 'Trailers'
elif season_num == '-3': season_title = 'Parodies'
elif season_num == '-4': season_title = 'Other'
@@ -412,11 +411,11 @@ def title_case(text):
force_upper = ('3d', 'bdsm', 'cg', 'cgi', 'ed', 'fff', 'ffm', 'ii', 'milf', 'mmf', 'mmm', 'npc', 'op', 'rpg', 'tbs', 'tv')
# Special cases where a specific capitalisation style is preferred
force_special = {'comicfesta': 'ComicFesta', 'd\'etat': 'd\'Etat', 'noitamina': 'noitaminA'}
- text = re.sub(r'[\'\w\d]+\b', lambda t: t.group(0).capitalize(), text) # Capitalise all words accounting for apostrophes
- for key in force_lower: text = re.sub(r'\b' + key + r'\b', key.lower(), text, flags=re.I) # Convert words from force_lower to lowercase
- for key in force_upper: text = re.sub(r'\b' + key + r'\b', key.upper(), text, flags=re.I) # Convert words from force_upper to uppercase
- text = text[:1].upper() + text[1:] # Force capitalise the first character no matter what
- if ' ' in text: text = (lambda t: t[0] + ' ' + t[1][:1].upper() + t[1][1:])(text.rsplit(' ', 1)) # Force capitalise the first character of the last word no matter what
+ text = re.sub(r'[\'\w\d]+\b', lambda t: t.group(0).capitalize(), text) # Capitalise all words accounting for apostrophes
+ for key in force_lower: text = re.sub(r'\b' + key + r'\b', key.lower(), text, flags=re.I) # Convert words from force_lower to lowercase
+ for key in force_upper: text = re.sub(r'\b' + key + r'\b', key.upper(), text, flags=re.I) # Convert words from force_upper to uppercase
+ text = text[:1].upper() + text[1:] # Force capitalise the first character no matter what
+ if ' ' in text: text = (lambda t: t[0] + ' ' + t[1][:1].upper() + t[1][1:])(text.rsplit(' ', 1)) # Force capitalise the first character of the last word no matter what
for key, value in force_special.items(): text = re.sub(r'\b' + key + r'\b', value, text, flags=re.I) # Apply special cases as a last step
return text
diff --git a/Contents/Info.plist b/Contents/Info.plist
index acceecd..4d75b7f 100644
--- a/Contents/Info.plist
+++ b/Contents/Info.plist
@@ -5,7 +5,7 @@
CFBundleName
Shoko Relay
CFBundleVersion
- 1.1.16
+ 1.1.17
CFBundleIdentifier
com.plexapp.agents.shokorelay
PlexClientPlatforms
diff --git a/Contents/Scanners/Series/Shoko Relay Scanner.py b/Contents/Scanners/Series/Shoko Relay Scanner.py
index a4623cb..bd4e488 100644
--- a/Contents/Scanners/Series/Shoko Relay Scanner.py
+++ b/Contents/Scanners/Series/Shoko Relay Scanner.py
@@ -75,7 +75,7 @@ def Scan(path, files, mediaList, subdirs, language=None, root=None):
if files: Log.debug('[Files] %s' % ', '.join(files))
for subdir in subdirs: Log.debug('[Folder] %s' % os.path.relpath(subdir, root))
- Log.info('===========================[Shoko Relay Scanner v1.1.16]' + '=' * 244)
+ Log.info('===========================[Shoko Relay Scanner v1.1.17]' + '=' * 244)
if files:
# Scan for video files
@@ -117,7 +117,7 @@ def Scan(path, files, mediaList, subdirs, language=None, root=None):
if not Prefs['SingleSeasonOrdering'] and try_get(series_data['TvDB'], 0, None):
tvdb_title, tvdb_id = try_get(series_data['TvDB'][0], 'Title', None), try_get(series_data['TvDB'][0], 'ID', None)
if tvdb_title: tvdb_check, tvdb_title = True, tvdb_title.encode('utf-8')
- else: tvdb_check, tvdb_title = False, 'N/A (CRITICAL: Removed from TvDB or Missing Data) - Falling Back to AniDB Ordering!' # Account for rare cases where Shoko has a TvDb ID that returns no data
+ else: tvdb_check, tvdb_title = False, 'N/A (CRITICAL: Removed from TvDB or Missing Data) - Falling Back to AniDB Ordering!' # Account for rare cases where Shoko has a TvDB ID that returns no data
Log.info(' TvDB Check (Title [ID]): %s [%s]' % (tvdb_title, tvdb_id))
else: tvdb_check = False
diff --git a/README.md b/README.md
index e0b4853..f669abb 100644
--- a/README.md
+++ b/README.md
@@ -344,9 +344,10 @@ If "assumed content ratings" are enabled in the agent settings the [target audie
| Mina | TV-G |
| Shoujo, Shounen | TV-PG |
| Josei, Seinen | TV-14 |
+| Sexual Humour | TV-\*-D |
| Nudity, Sex | TV-\*-S |
| **\*\*** Violence | TV-14-V |
-| **\*\*** Nudity | TV-14-S |
+| **\*\*** Nudity, **\*\+** Sex | TV-14-S |
| Borderline Porn (override) | TV-MA |
| **\*\*\+** Nudity, **\*\*** Sex | TV-MA-S |
| **\*\*\+** Violence | TV-MA-V |