diff --git a/utility/add_label_recently_added.py b/utility/add_label_recently_added.py new file mode 100644 index 0000000..e3bfad7 --- /dev/null +++ b/utility/add_label_recently_added.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Description: Automatically add a label to recently added items in your Plex library +# Author: /u/SwiftPanda16 +# Requires: requests +# Tautulli script trigger: +# * Notify on recently added +# Tautulli script conditions: +# * Filter which media to add labels to using conditions. Examples: +# [ Media Type | is | movie ] +# [ Show Name | is | Game of Thrones ] +# [ Album Name | is | Reputation ] +# [ Video Resolution | is | 4k ] +# [ Genre | contains | horror ] +# Tautulli script arguments: +# * Recently Added: +# --title {title} --section_id {section_id} --media_type {media_type} --rating_key {rating_key} --parent_rating_key {parent_rating_key} --grandparent_rating_key {grandparent_rating_key} --label "Label" + +import argparse +import os +import requests + + +### OVERRIDES - ONLY EDIT IF RUNNING SCRIPT WITHOUT TAUTULLI ### + +PLEX_URL = '' +PLEX_TOKEN = '' + + +### CODE BELOW ### + +PLEX_URL = PLEX_URL or os.getenv('PLEX_URL', PLEX_URL) +PLEX_TOKEN = PLEX_TOKEN or os.getenv('PLEX_TOKEN', PLEX_TOKEN) + +MEDIA_TYPES_PARENT_VALUES = {'movie': 1, 'show': 2, 'season': 2, 'episode': 2, 'album': 9, 'track': 9} + + +def add_label(media_type_value, rating_key, section_id, label): + headers = {'X-Plex-Token': PLEX_TOKEN} + params = {'type': media_type_value, + 'id': rating_key, + 'label.locked': 1, + 'label[0].tag.tag': label + } + + url = '{base_url}/library/sections/{section_id}/all'.format(base_url=PLEX_URL, section_id=section_id) + r = requests.put(url, headers=headers, params=params) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--title', required=True) + parser.add_argument('--section_id', required=True) + parser.add_argument('--media_type', required=True) + parser.add_argument('--rating_key', required=True) + parser.add_argument('--parent_rating_key', required=True) + parser.add_argument('--grandparent_rating_key', required=True) + parser.add_argument('--label', required=True) + opts = parser.parse_args() + + if opts.media_type not in MEDIA_TYPES_PARENT_VALUES: + print("Cannot add label to '{opts.title}': Invalid media type '{opts.media_type}'".format(opts=opts)) + else: + media_type_value = MEDIA_TYPES_PARENT_VALUES[opts.media_type] + rating_key = '' + + if opts.media_type in ('movie', 'show', 'album'): + rating_key = opts.rating_key + elif opts.media_type in ('season', 'track'): + rating_key = opts.parent_rating_key + elif opts.media_type in ('episode'): + rating_key = opts.grandparent_rating_key + + if rating_key and rating_key.isdigit(): + add_label(media_type_value, int(rating_key), opts.section_id, opts.label) + print("The label '{opts.label}' was added to '{opts.title}' ({rating_key}).".format(opts=opts, rating_key=rating_key)) + else: + print("Cannot add label to '{opts.title}': Invalid rating key '{rating_key}'".format(opts=opts, rating_key=rating_key)) diff --git a/utility/hide_episode_spoilers.py b/utility/hide_episode_spoilers.py new file mode 100644 index 0000000..7df4d7c --- /dev/null +++ b/utility/hide_episode_spoilers.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Description: Automatically change episode artwork in Plex to hide spoilers. +# Author: /u/SwiftPanda16 +# Requires: plexapi, requests +# Tautulli script trigger: +# * Notify on recently added +# * Notify on watched (optional - to remove the artwork after being watched) +# Tautulli script conditions: +# * Condition {1}: +# [ Media Type | is | episode ] +# * Condition {2} (optional): +# [ Library Name | is | DVR ] +# [ Show Namme | is | Game of Thrones ] +# Tautulli script arguments: +# * Recently Added: +# To use an image file (can be image in the same directory as this script, or full path to an image): +# --rating_key {rating_key} --file {file} --image spoilers.png +# To blur the episode artwork (optional blur in pixels): +# --rating_key {rating_key} --file {file} --blur 25 +# * Watched (optional): +# --rating_key {rating_key} --file {file} --remove + +import argparse +import os +import requests +import shutil +from plexapi.server import PlexServer + +TAUTULLI_URL = '' +TAUTULLI_APIKEY = '' +PLEX_URL = '' +PLEX_TOKEN = '' + +# Environmental Variables +TAUTULLI_URL = os.getenv('TAUTULLI_URL', TAUTULLI_URL) +TAUTULLI_APIKEY = os.getenv('TAUTULLI_APIKEY', TAUTULLI_APIKEY) +PLEX_URL = os.getenv('PLEX_URL', PLEX_URL) +PLEX_TOKEN = os.getenv('PLEX_TOKEN', PLEX_TOKEN) + + +def get_blurred_image(rating_key, blur=25): + params = {'apikey': TAUTULLI_APIKEY, + 'cmd': 'pms_image_proxy', + 'img': '/library/metadata/{}/thumb'.format(rating_key), + 'width': 545, + 'height': 307, + 'opacity': 100, + 'background': '000000', + 'blur': blur, + 'img_format': 'png', + 'fallback': 'art' + } + + r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=params, stream=True) + if r.status_code == 200: + r.raw.decode_content = True + return r.raw + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--rating_key', required=True, type=int) + parser.add_argument('--file', required=True) + parser.add_argument('--image') + parser.add_argument('--blur', type=int, default=25) + parser.add_argument('--remove', action='store_true') + opts = parser.parse_args() + + if opts.image: + # File path to episode artwork using the same episode file name + episode_artwork = os.path.splitext(opts.file)[0] + os.path.splitext(opts.image)[1] + + # Copy the image to the episode artwork + shutil.copy2(opts.image, episode_artwork) + + # Refresh metadata for the TV show + plex = PlexServer(PLEX_URL, PLEX_TOKEN) + plex.fetchItem(opts.rating_key).show().refresh() + + elif opts.blur: + # File path to episode artwork using the same episode file name + episode_artwork = os.path.splitext(opts.file)[0] + '.png' + + # Get the blurred artwork from Tautulli + blurred_artwork = get_blurred_image(opts.rating_key, opts.blur) + if blurred_artwork: + # Copy the image to the episode artwork + with open(episode_artwork, 'wb') as f: + shutil.copyfileobj(blurred_artwork, f) + + # Refresh metadata for the TV show + plex = PlexServer(PLEX_URL, PLEX_TOKEN) + plex.fetchItem(opts.rating_key).show().refresh() + + elif opts.remove: + # File path to episode artwork using the same episode file name without extension + episode_path = os.path.dirname(opts.file) + episode_filename = os.path.splitext(os.path.basename(opts.file))[0] + + # Find image files with the same name as the episode + for filename in os.listdir(episode_path): + if filename.startswith(episode_filename) and filename.endswith(('.jpg', '.png')): + # Delete the episode artwork image file + os.remove(os.path.join(episode_path, filename)) + + # Refresh metadata for the TV show + plex = PlexServer(PLEX_URL, PLEX_TOKEN) + plex.fetchItem(opts.rating_key).show().refresh() diff --git a/utility/mark_multiepisode_watched.py b/utility/mark_multiepisode_watched.py new file mode 100644 index 0000000..8878a8d --- /dev/null +++ b/utility/mark_multiepisode_watched.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Description: Automatically mark a multi-episode file as watched in Plex. +# Author: /u/SwiftPanda16 +# Requires: plexapi +# Tautulli script trigger: +# * Notify on watched +# Tautulli script conditions: +# * Condition {1}: +# [ Media Type | is | episode ] +# * Condition {2} (optional): +# [ Username | is | username ] +# Tautulli script arguments: +# * Watched: +# --rating_key {rating_key} --filename {filename} + +import argparse +import os +from plexapi.server import PlexServer + +PLEX_URL = '' +PLEX_TOKEN = '' + +# Environmental Variables +PLEX_URL = os.getenv('PLEX_URL', PLEX_URL) +PLEX_USER_TOKEN = os.getenv('PLEX_USER_TOKEN', PLEX_TOKEN) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--rating_key', required=True, type=int) + parser.add_argument('--filename', required=True) + opts = parser.parse_args() + + plex = PlexServer(PLEX_URL, PLEX_USER_TOKEN) + + for episode in plex.fetchItem(opts.rating_key).season().episodes(): + if episode.ratingKey == opts.rating_key: + continue + if any(opts.filename in part.file for media in episode.media for part in media.parts): + print("Marking multi-episode file '{grandparentTitle} - S{parentIndex}E{index}' as watched.".format( + grandparentTitle=episode.grandparentTitle.encode('UTF-8'), + parentIndex=str(episode.parentIndex).zfill(2), + index=str(episode.index).zfill(2))) + episode.markWatched() diff --git a/utility/recently_added_collection.py b/utility/recently_added_collection.py new file mode 100644 index 0000000..5b429e7 --- /dev/null +++ b/utility/recently_added_collection.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Description: Automatically add a movie to a collection based on release date. +# Author: /u/SwiftPanda16 +# Requires: plexapi +# Tautulli script trigger: +# * Notify on recently added +# Tautulli script conditions: +# * Filter which media to add to collection. +# [ Media Type | is | movie ] +# [ Library Name | is | Movies ] +# Tautulli script arguments: +# * Recently Added: +# --rating_key {rating_key} --collection "New Releases" --days 180 + +import argparse +import os +from datetime import datetime, timedelta +from plexapi.server import PlexServer + +PLEX_URL = '' +PLEX_TOKEN = '' + +# Environmental Variables +PLEX_URL = os.getenv('PLEX_URL', PLEX_URL) +PLEX_TOKEN = os.getenv('PLEX_TOKEN', PLEX_TOKEN) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--rating_key', required=True, type=int) + parser.add_argument('--collection', required=True) + parser.add_argument('--days', required=True, type=int) + opts = parser.parse_args() + + threshold_date = datetime.now() - timedelta(days=opts.days) + + plex = PlexServer(PLEX_URL, PLEX_TOKEN) + + movie = plex.fetchItem(opts.rating_key) + + if movie.originallyAvailableAt >= threshold_date: + movie.addCollection(opts.collection) + print("Added collection '{}' to '{}'.".format(opts.collection, movie.title.encode('UTF-8'))) + + for m in movie.section().search(collection=opts.collection): + if m.originallyAvailableAt < threshold_date: + m.removeCollection(opts.collection) + print("Removed collection '{}' from '{}'.".format(opts.collection, m.title.encode('UTF-8'))) diff --git a/utility/spoilers.png b/utility/spoilers.png new file mode 100644 index 0000000..df6f44f Binary files /dev/null and b/utility/spoilers.png differ