Merge pull request #171 from Arcanemagus/style-cleanup
Massive style cleanup
This commit is contained in:
commit
f011d53d8c
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Description: Create and share playlists based on Most Popular TV/Movies from Tautulli
|
||||
and Aired this day in history.
|
||||
@ -43,7 +46,7 @@ optional arguments:
|
||||
|
||||
Example:
|
||||
Use with cron or task to schedule runs
|
||||
|
||||
|
||||
Create Aired Today Playlist from Movies and TV Shows libraries for admin user
|
||||
python playlist_manager.py --jbop historyToday --libraries Movies "TV Shows" --action add
|
||||
|
||||
@ -61,29 +64,29 @@ optional arguments:
|
||||
|
||||
Create 10 Most Popular Movies (60 days) Playlist and share to users bob and Black Twin
|
||||
python playlist_manager.py --jbop popularMovies --action add --users bob "Black Twin" --days 60 --top 10
|
||||
|
||||
|
||||
Show 5 Most Popular TV Shows (30 days) Playlist
|
||||
python playlist_manager.py --jbop popularTv --action show
|
||||
|
||||
|
||||
Show all users current playlists
|
||||
python playlist_manager.py --action show --allUsers
|
||||
|
||||
|
||||
Share existing admin Playlists "My Custom Playlist" and "Another Playlist" with all users
|
||||
python playlist_manager.py --action share --allUsers --playlists "My Custom Playlist" "Another Playlist"
|
||||
|
||||
|
||||
Search and Filter;
|
||||
|
||||
|
||||
metadata_field_name = title, summary, etc.
|
||||
|
||||
|
||||
--search {metadata_field_name}=value
|
||||
search through metadata field for existence of value.
|
||||
|
||||
|
||||
--search {metadata_field_name}=value1,value2,*
|
||||
search through metadata field for existence of values.
|
||||
*comma separated for AND (value1 AND value2 AND *)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Excluding;
|
||||
|
||||
--user becomes excluded if --allUsers is set
|
||||
@ -106,14 +109,14 @@ import unicodedata
|
||||
from collections import Counter
|
||||
from plexapi.server import PlexServer, CONFIG
|
||||
|
||||
### EDIT SETTINGS ###
|
||||
# ### EDIT SETTINGS ###
|
||||
|
||||
PLEX_URL = ''
|
||||
PLEX_TOKEN = ''
|
||||
TAUTULLI_URL = ''
|
||||
TAUTULLI_APIKEY = ''
|
||||
|
||||
## CODE BELOW ##
|
||||
# ## CODE BELOW ##
|
||||
|
||||
if not PLEX_URL:
|
||||
PLEX_URL = CONFIG.data['auth'].get('server_baseurl')
|
||||
@ -150,6 +153,7 @@ playlist_lst = [x.title for x in plex.playlists()]
|
||||
today = datetime.datetime.now().date()
|
||||
weeknum = datetime.date(today.year, today.month, today.day).isocalendar()[1]
|
||||
|
||||
|
||||
def actions():
|
||||
"""
|
||||
add - create new playlist for admin or users
|
||||
@ -172,7 +176,7 @@ def selectors():
|
||||
'custom': '{custom} Playlist',
|
||||
'random': '{count} Random {libraries} Playlist'
|
||||
}
|
||||
|
||||
|
||||
return selections
|
||||
|
||||
|
||||
@ -203,7 +207,7 @@ def exclusions(all_true, select, all_items):
|
||||
for x in select:
|
||||
all_items.remove(x)
|
||||
output = all_items
|
||||
|
||||
|
||||
elif isinstance(all_items, dict):
|
||||
output = {}
|
||||
if all_true and not select:
|
||||
@ -216,7 +220,7 @@ def exclusions(all_true, select, all_items):
|
||||
for key, value in all_items.items():
|
||||
if value not in select:
|
||||
output[key] = value
|
||||
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@ -226,7 +230,7 @@ def get_home_stats(time_range, stats_count):
|
||||
'cmd': 'get_home_stats',
|
||||
'time_range': time_range,
|
||||
'stats_count': stats_count,
|
||||
'stats_type': 0} # stats_type = plays
|
||||
'stats_type': 0} # stats_type = plays
|
||||
|
||||
try:
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
@ -269,14 +273,14 @@ def sort_by_dates(video, date_type):
|
||||
return [[video.ratingKey] + [str(video.originallyAvailableAt)]]
|
||||
|
||||
# todo-me return object
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# print(e)
|
||||
return
|
||||
|
||||
|
||||
def multi_filter_search(keyword_dict, library, search_eps=None):
|
||||
"""Allowing for multiple filter or search values
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
keyword_dict: dict
|
||||
@ -318,17 +322,18 @@ def multi_filter_search(keyword_dict, library, search_eps=None):
|
||||
for episode in show.episodes(**{key: values}):
|
||||
ep_lst += [episode.ratingKey]
|
||||
multi_lst += ep_lst
|
||||
|
||||
|
||||
else:
|
||||
multi_lst += [item.ratingKey for item in library.all(**{key: values})]
|
||||
counts = Counter(multi_lst)
|
||||
# Use amount of keywords to check that all keywords were found in results
|
||||
search_lst = [id for id in multi_lst if counts[id] >= keyword_count]
|
||||
|
||||
|
||||
return list(set(search_lst))
|
||||
|
||||
|
||||
def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
||||
"""Get all movies or episodes from LIBRARY_NAME
|
||||
"""Get all movies or episodes from LIBRARY_NAME.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -342,6 +347,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
||||
list
|
||||
Sorted list of Movie and episodes that
|
||||
aired on today's date.
|
||||
|
||||
"""
|
||||
child_lst = []
|
||||
filter_lst = []
|
||||
@ -378,7 +384,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
||||
child_lst += filter_lst
|
||||
if keywords and filters:
|
||||
child_lst += list(set(filter_lst) & set(search_lst))
|
||||
|
||||
|
||||
elif library_type == 'show':
|
||||
# Decisions to stack filter and search
|
||||
if keywords:
|
||||
@ -402,7 +408,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
||||
for episode in show.episodes():
|
||||
filter_lst += [episode.ratingKey]
|
||||
child_lst += filter_lst
|
||||
|
||||
|
||||
if keywords and filters:
|
||||
child_lst += list(set(filter_lst) & set(search_lst))
|
||||
else:
|
||||
@ -410,9 +416,9 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
||||
# Keep only results found from both search and filters
|
||||
if keywords and filters:
|
||||
child_lst = list(set(i for i in child_lst if child_lst.count(i) > 1))
|
||||
|
||||
|
||||
play_lst = child_lst
|
||||
|
||||
|
||||
else:
|
||||
for library in libraries.keys():
|
||||
plex_library = plex.library.sectionByID(library)
|
||||
@ -440,7 +446,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
||||
# Sort by original air date, oldest first
|
||||
# todo-me move sorting and add more sorting options
|
||||
aired_lst = sorted(child_lst, key=operator.itemgetter(1))
|
||||
|
||||
|
||||
# Remove date used for sorting
|
||||
play_lst = [x[0] for x in aired_lst]
|
||||
else:
|
||||
@ -474,7 +480,7 @@ def build_playlist(jbop, libraries=None, days=None, top=None, filters=None, sear
|
||||
for stat in home_stats:
|
||||
if stat['stat_id'] in ['popular_tv', 'popular_movies']:
|
||||
keys_list += [x['rating_key'] for x in stat['rows'] if
|
||||
str(x['section_id']) in libraries.keys()]
|
||||
str(x['section_id']) in libraries.keys()]
|
||||
else:
|
||||
try:
|
||||
keys_list = get_content(libraries, jbop, filters, search, limit)
|
||||
@ -525,11 +531,12 @@ def show_playlist(playlist_title, playlist_keys):
|
||||
title = unicodedata.normalize('NFKD', title).encode('ascii', 'ignore').translate(None, "'")
|
||||
playlist_list.append(title)
|
||||
|
||||
print(u"Contents of Playlist {title}:\n{playlist}".format(title=playlist_title,
|
||||
playlist=', '.join(playlist_list)))
|
||||
print(u"Contents of Playlist {title}:\n{playlist}".format(
|
||||
title=playlist_title,
|
||||
playlist=', '.join(playlist_list)))
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
|
||||
def create_playlist(playlist_title, playlist_keys, server, user):
|
||||
"""
|
||||
Parameters
|
||||
@ -552,13 +559,13 @@ def create_playlist(playlist_title, playlist_keys, server, user):
|
||||
playlist_list.append(episode)
|
||||
else:
|
||||
playlist_list.append(plex_obj)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
try:
|
||||
obj = plex.fetchItem(key)
|
||||
print("{} may not have permission to this title: {}".format(user, obj.title))
|
||||
# print("Error: {}".format(e))
|
||||
pass
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
print('Rating Key: {}, may have been deleted or moved.'.format(key))
|
||||
# print("Error: {}".format(e))
|
||||
|
||||
@ -578,7 +585,7 @@ def delete_playlist(playlist_dict, title):
|
||||
"""
|
||||
server = playlist_dict['server']
|
||||
user = playlist_dict['user']
|
||||
|
||||
|
||||
try:
|
||||
# todo-me this needs improvement
|
||||
for playlist in server.playlists():
|
||||
@ -595,14 +602,14 @@ def delete_playlist(playlist_dict, title):
|
||||
print("...Deleted Playlist: {playlist.title} for '{user}'."
|
||||
.format(playlist=playlist, user=user))
|
||||
|
||||
except:
|
||||
except Exception:
|
||||
# print("Playlist not found on '{user}' account".format(user=user))
|
||||
pass
|
||||
|
||||
|
||||
def create_title(jbop, libraries, days, filters, search, limit):
|
||||
"""
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jbop: str
|
||||
@ -672,9 +679,9 @@ def create_title(jbop, libraries, days, filters, search, limit):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description="Create, share, and clean Playlists for users.",
|
||||
formatter_class = argparse.RawTextHelpFormatter)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create, share, and clean Playlists for users.",
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
# todo-me use parser grouping instead of choices for action and jbop?
|
||||
parser.add_argument('--jbop', choices=selectors().keys(), metavar='',
|
||||
help='Playlist selector.\n'
|
||||
@ -715,7 +722,7 @@ if __name__ == "__main__":
|
||||
parser.add_argument('--search', action='append', type=lambda kv: kv.split("="),
|
||||
help='Search non-filtered metadata fields for keywords '
|
||||
'in title, summary, etc.')
|
||||
|
||||
|
||||
opts = parser.parse_args()
|
||||
|
||||
title = ''
|
||||
@ -739,7 +746,7 @@ if __name__ == "__main__":
|
||||
# If filter key used more than once than consider filtering values with OR statement
|
||||
if filter_count > 1:
|
||||
filters_lst = []
|
||||
|
||||
|
||||
filters = dict(opts.filter)
|
||||
for k, v in filters.items():
|
||||
# If comma separated filter then consider filtering values with AND statement
|
||||
@ -756,10 +763,10 @@ if __name__ == "__main__":
|
||||
|
||||
# Defining libraries
|
||||
libraries = exclusions(opts.allLibraries, opts.libraries, sections_dict)
|
||||
|
||||
|
||||
# Defining selected playlists
|
||||
selected_playlists = exclusions(opts.allPlaylists, opts.playlists, playlist_lst)
|
||||
|
||||
|
||||
# Create user server objects
|
||||
if users:
|
||||
for user in users:
|
||||
@ -776,12 +783,13 @@ if __name__ == "__main__":
|
||||
'user': user,
|
||||
'user_selected': user_selected,
|
||||
'all_playlists': all_playlists})
|
||||
|
||||
|
||||
if opts.self or not users:
|
||||
playlist_dict['data'].append({'server': plex,
|
||||
'user': 'admin',
|
||||
'user_selected': selected_playlists,
|
||||
'all_playlists': playlist_lst})
|
||||
playlist_dict['data'].append({
|
||||
'server': plex,
|
||||
'user': 'admin',
|
||||
'user_selected': selected_playlists,
|
||||
'all_playlists': playlist_lst})
|
||||
|
||||
if not opts.jbop and opts.action == 'show':
|
||||
print("Displaying the user's playlist(s)...")
|
||||
@ -790,7 +798,7 @@ if __name__ == "__main__":
|
||||
playlists = data['all_playlists']
|
||||
print("{}'s current playlist(s): {}".format(user, ', '.join(playlists)))
|
||||
exit()
|
||||
|
||||
|
||||
if libraries:
|
||||
title = create_title(opts.jbop, libraries, opts.days, filters, search, opts.limit)
|
||||
keys_list = build_playlist(opts.jbop, libraries, opts.days, opts.top, filters, search, opts.limit)
|
||||
@ -801,18 +809,18 @@ if __name__ == "__main__":
|
||||
for data in playlist_dict['data']:
|
||||
titles = data['user_selected']
|
||||
delete_playlist(data, titles)
|
||||
|
||||
|
||||
# Check if limit exist and if it's greater than the pulled list of rating keys
|
||||
if opts.limit and len(keys_list) > int(opts.limit):
|
||||
if opts.jbop == 'random':
|
||||
keys_list = random.sample((keys_list), opts.limit)
|
||||
else:
|
||||
keys_list = keys_list[:opts.limit]
|
||||
|
||||
|
||||
# Setting custom name if provided
|
||||
if opts.name:
|
||||
title = opts.name
|
||||
|
||||
|
||||
if opts.jbop and opts.action == 'show':
|
||||
show_playlist(title, keys_list)
|
||||
|
||||
@ -823,7 +831,7 @@ if __name__ == "__main__":
|
||||
print('Creating playlist(s)...')
|
||||
for data in playlist_dict['data']:
|
||||
create_playlist(title, keys_list, data['server'], data['user'])
|
||||
|
||||
|
||||
if opts.action == 'add':
|
||||
print('Creating playlist(s)...')
|
||||
for data in playlist_dict['data']:
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Author: Bailey Belvis (https://github.com/philosowaffle)
|
||||
#
|
||||
@ -6,18 +9,16 @@
|
||||
#
|
||||
# - Enable `Upload Posters to Imgur for Notifications` - required for lights to match the posters color scheme
|
||||
# - Triggers - PlexLifx supports the following triggers, enable the ones you are interested in.
|
||||
# - Notify on Playback Start
|
||||
# - Notify on Playback Stop
|
||||
# - Notify on Playback Resume
|
||||
# - Notify on Playback Pause
|
||||
# - Notify on Playback Start
|
||||
# - Notify on Playback Stop
|
||||
# - Notify on Playback Resume
|
||||
# - Notify on Playback Pause
|
||||
#
|
||||
# - Copy paste the following line to each of the Triggers you enabled (found on the Arguments tab):
|
||||
# -a {action} -mt {media_type} -mi {machine_id} -rk {rating_key} -pu {poster_url}
|
||||
#
|
||||
# -a {action} -mt {media_type} -mi {machine_id} -rk {rating_key} -pu {poster_url}
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import hashlib
|
||||
import shutil
|
||||
import numpy
|
||||
import argparse
|
||||
@ -99,10 +100,10 @@ filtered_players = [] if PlayerUUIDs == "none" else PlayerUUIDs.split(',')
|
||||
logger.debug("Filtered Players: " + filtered_players.__str__())
|
||||
|
||||
events = [
|
||||
'play',
|
||||
'pause',
|
||||
'resume',
|
||||
'stop'
|
||||
'play',
|
||||
'pause',
|
||||
'resume',
|
||||
'stop'
|
||||
]
|
||||
|
||||
##############################
|
||||
@ -115,33 +116,33 @@ num_colors = NumColors if NumColors else 4
|
||||
color_quality = ColorQuality if ColorQuality else 1
|
||||
|
||||
if not APIKey:
|
||||
logger.error("Missing LIFX API Key")
|
||||
exit(1)
|
||||
logger.error("Missing LIFX API Key")
|
||||
exit(1)
|
||||
else:
|
||||
lifx_api_key = APIKey
|
||||
logger.debug("LIFX API Key: " + lifx_api_key)
|
||||
lifx_api_key = APIKey
|
||||
logger.debug("LIFX API Key: " + lifx_api_key)
|
||||
|
||||
pifx = PIFX(lifx_api_key)
|
||||
|
||||
lights = []
|
||||
if Lights:
|
||||
lights_use_name = True
|
||||
lights = Lights.split(',')
|
||||
lights_use_name = True
|
||||
lights = Lights.split(',')
|
||||
|
||||
tmp = []
|
||||
for light in lights:
|
||||
tmp.append(light.strip())
|
||||
lights = tmp
|
||||
tmp = []
|
||||
for light in lights:
|
||||
tmp.append(light.strip())
|
||||
lights = tmp
|
||||
else:
|
||||
lights_detail = pifx.list_lights()
|
||||
for light in lights_detail:
|
||||
lights.append(light['id'])
|
||||
shuffle(lights)
|
||||
lights_detail = pifx.list_lights()
|
||||
for light in lights_detail:
|
||||
lights.append(light['id'])
|
||||
shuffle(lights)
|
||||
|
||||
scenes_details = pifx.list_scenes()
|
||||
scenes = dict()
|
||||
for scene in scenes_details:
|
||||
scenes[scene['name']] = scene['uuid']
|
||||
scenes[scene['name']] = scene['uuid']
|
||||
|
||||
logger.debug(scenes)
|
||||
logger.debug(lights)
|
||||
@ -154,7 +155,7 @@ default_play_uuid = scenes[default_play_theme]
|
||||
|
||||
number_of_lights = len(lights)
|
||||
if number_of_lights < num_colors:
|
||||
num_colors = number_of_lights
|
||||
num_colors = number_of_lights
|
||||
|
||||
light_groups = numpy.array_split(numpy.array(lights), num_colors)
|
||||
|
||||
@ -167,15 +168,15 @@ logger.debug("Color Quality: " + color_quality.__str__())
|
||||
##############################
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('-a', '--action', action='store', default='',
|
||||
help='The action that triggered the script.')
|
||||
help='The action that triggered the script.')
|
||||
p.add_argument('-mt', '--media_type', action='store', default='',
|
||||
help='The media type of the media being played.')
|
||||
help='The media type of the media being played.')
|
||||
p.add_argument('-mi', '--machine_id', action='store', default='',
|
||||
help='The machine id of where the media is playing.')
|
||||
help='The machine id of where the media is playing.')
|
||||
p.add_argument('-rk', '--rating_key', action='store', default='',
|
||||
help='The unique identifier for the media.')
|
||||
help='The unique identifier for the media.')
|
||||
p.add_argument('-pu', '--poster_url', action='store', default='',
|
||||
help='The poster url for the media playing.')
|
||||
help='The poster url for the media playing.')
|
||||
|
||||
parser = p.parse_args()
|
||||
|
||||
@ -196,22 +197,22 @@ logger.debug("Media Guid: " + media_guid)
|
||||
logger.debug("Poster Url: " + poster_url)
|
||||
|
||||
# Only perform action for event play/pause/resume/stop for TV and Movies
|
||||
if not event in events:
|
||||
logger.debug("Invalid action: " + event)
|
||||
exit()
|
||||
if event not in events:
|
||||
logger.debug("Invalid action: " + event)
|
||||
exit()
|
||||
|
||||
if (media_type != "movie") and (media_type != "episode"):
|
||||
logger.debug("Media type was not movie or episode, ignoring.")
|
||||
exit()
|
||||
logger.debug("Media type was not movie or episode, ignoring.")
|
||||
exit()
|
||||
|
||||
# If we configured only specific players to be able to play with the lights
|
||||
if filtered_players:
|
||||
try:
|
||||
if player_uuid not in filtered_players:
|
||||
logger.info(player_uuid + " player is not able to play with the lights")
|
||||
exit()
|
||||
except Exception as e:
|
||||
logger.error("Failed to check uuid - " + e.__str__())
|
||||
try:
|
||||
if player_uuid not in filtered_players:
|
||||
logger.info(player_uuid + " player is not able to play with the lights")
|
||||
exit()
|
||||
except Exception as e:
|
||||
logger.error("Failed to check uuid - " + e.__str__())
|
||||
|
||||
# Setup Thumbnail directory paths
|
||||
upload_folder = os.getcwd() + '\\tmp'
|
||||
@ -219,63 +220,63 @@ thumb_folder = os.path.join(upload_folder, media_guid)
|
||||
thumb_path = os.path.join(thumb_folder, "thumb.jpg")
|
||||
|
||||
if event == 'stop':
|
||||
if os.path.exists(thumb_folder):
|
||||
logger.debug("Removing Directory: " + thumb_folder)
|
||||
shutil.rmtree(thumb_folder)
|
||||
if os.path.exists(thumb_folder):
|
||||
logger.debug("Removing Directory: " + thumb_folder)
|
||||
shutil.rmtree(thumb_folder)
|
||||
|
||||
pifx.activate_scene(default_pause_uuid)
|
||||
exit()
|
||||
pifx.activate_scene(default_pause_uuid)
|
||||
exit()
|
||||
|
||||
if event == 'pause':
|
||||
pifx.activate_scene(default_pause_uuid)
|
||||
exit()
|
||||
pifx.activate_scene(default_pause_uuid)
|
||||
exit()
|
||||
|
||||
if event == 'play' or event == "resume":
|
||||
|
||||
# If the file already exists then we don't need to re-upload the image
|
||||
if not os.path.exists(thumb_folder):
|
||||
try:
|
||||
logger.debug("Making Directory: " + thumb_folder)
|
||||
os.makedirs(thumb_folder)
|
||||
urllib.urlretrieve(poster_url, thumb_path)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.info("No file found in request")
|
||||
pifx.activate_scene(default_play_uuid)
|
||||
exit()
|
||||
# If the file already exists then we don't need to re-upload the image
|
||||
if not os.path.exists(thumb_folder):
|
||||
try:
|
||||
logger.debug("Making Directory: " + thumb_folder)
|
||||
os.makedirs(thumb_folder)
|
||||
urllib.urlretrieve(poster_url, thumb_path)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.info("No file found in request")
|
||||
pifx.activate_scene(default_play_uuid)
|
||||
exit()
|
||||
|
||||
# Determine Color Palette for Lights
|
||||
color_thief = ColorThief(thumb_path)
|
||||
if num_colors >= 2:
|
||||
palette = color_thief.get_palette(color_count=num_colors, quality=color_quality)
|
||||
else:
|
||||
palette = [color_thief.get_color(quality=color_quality)]
|
||||
logger.debug("Color Palette: " + palette.__str__())
|
||||
color_thief = ColorThief(thumb_path)
|
||||
if num_colors >= 2:
|
||||
palette = color_thief.get_palette(color_count=num_colors, quality=color_quality)
|
||||
else:
|
||||
palette = [color_thief.get_color(quality=color_quality)]
|
||||
logger.debug("Color Palette: " + palette.__str__())
|
||||
|
||||
# Set Color Palette
|
||||
pifx.set_state(selector='all', power="off")
|
||||
for index in range(len(light_groups)):
|
||||
try:
|
||||
color = palette[index]
|
||||
light_group = light_groups[index]
|
||||
pifx.set_state(selector='all', power="off")
|
||||
for index in range(len(light_groups)):
|
||||
try:
|
||||
color = palette[index]
|
||||
light_group = light_groups[index]
|
||||
|
||||
logger.debug(light_group)
|
||||
logger.debug(color)
|
||||
logger.debug(light_group)
|
||||
logger.debug(color)
|
||||
|
||||
color_rgb = ', '.join(str(c) for c in color)
|
||||
color_rgb = "rgb:" + color_rgb
|
||||
color_rgb = color_rgb.replace(" ", "")
|
||||
color_rgb = ', '.join(str(c) for c in color)
|
||||
color_rgb = "rgb:" + color_rgb
|
||||
color_rgb = color_rgb.replace(" ", "")
|
||||
|
||||
for light_id in light_group:
|
||||
if lights_use_name:
|
||||
selector = "label:" + light_id
|
||||
else:
|
||||
selector = light_id
|
||||
for light_id in light_group:
|
||||
if lights_use_name:
|
||||
selector = "label:" + light_id
|
||||
else:
|
||||
selector = light_id
|
||||
|
||||
logger.debug("Setting light: " + selector + " to color: " + color_rgb)
|
||||
pifx.set_state(selector=selector, power="on", color=color_rgb, brightness=brightness, duration=duration)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.debug("Setting light: " + selector + " to color: " + color_rgb)
|
||||
pifx.set_state(selector=selector, power="on", color=color_rgb, brightness=brightness, duration=duration)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
exit()
|
||||
|
@ -1,11 +1,14 @@
|
||||
'''
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
https://gist.github.com/blacktwin/4ccb79c7d01a95176b8e88bf4890cd2b
|
||||
'''
|
||||
"""
|
||||
|
||||
from plexapi.server import PlexServer
|
||||
import random
|
||||
import re
|
||||
|
||||
|
||||
baseurl = 'http://localhost:32400'
|
||||
token = 'xxxxx'
|
||||
plex = PlexServer(baseurl, token)
|
||||
@ -44,8 +47,11 @@ def sylco(word):
|
||||
if word[-2:] == "es" or word[-2:] == "ed":
|
||||
doubleAndtripple_1 = len(re.findall(r'[eaoui][eaoui]', word))
|
||||
if doubleAndtripple_1 > 1 or len(re.findall(r'[eaoui][^eaoui]', word)) > 1:
|
||||
if word[-3:] == "ted" or word[-3:] == "tes" or word[-3:] == "ses" or word[-3:] == "ied" or word[
|
||||
-3:] == "ies":
|
||||
if word[-3:] == "ted" or \
|
||||
word[-3:] == "tes" or \
|
||||
word[-3:] == "ses" or \
|
||||
word[-3:] == "ied" or \
|
||||
word[-3:] == "ies":
|
||||
pass
|
||||
else:
|
||||
disc += 1
|
||||
@ -140,8 +146,7 @@ def sylco(word):
|
||||
if word in exception_add:
|
||||
syls += 1
|
||||
|
||||
|
||||
# calculate the output
|
||||
# calculate the output
|
||||
return numVowels - disc + syls
|
||||
|
||||
|
||||
@ -190,13 +195,13 @@ for x in LIBRARIES_LST:
|
||||
|
||||
m_lst = hi_build(sections_lst, 5) + hi_build(sections_lst, 7) + hi_build(sections_lst, 5)
|
||||
# to see word and syllable count uncomment below print.
|
||||
#print(m_lst)
|
||||
# print(m_lst)
|
||||
|
||||
stanz1 = ' '.join(m_lst[0].keys())
|
||||
stanz2 = ' '.join(m_lst[1].keys())
|
||||
stanz3 = ' '.join(m_lst[2].keys())
|
||||
|
||||
lines = stanz1,stanz2,stanz3
|
||||
lines = stanz1, stanz2, stanz3
|
||||
lines = '\n'.join(lines)
|
||||
print('')
|
||||
print(lines.lower())
|
||||
|
@ -1,4 +1,7 @@
|
||||
'''
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
If server admin stream is experiencing buffering and there are concurrent transcode streams from
|
||||
another user, kill concurrent transcode stream that has the lowest percent complete. Message in
|
||||
kill stream will list why it was killed ('Server Admin's stream take priority and this user has X
|
||||
@ -11,7 +14,7 @@ Tautulli > Settings > Notification Agents > Scripts > Bell icon:
|
||||
Tautulli > Settings > Notification Agents > Scripts > Gear icon:
|
||||
Buffer Warnings: kill_else_if_buffering.py
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
import requests
|
||||
from operator import itemgetter
|
||||
@ -19,7 +22,7 @@ import unicodedata
|
||||
from plexapi.server import PlexServer
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
PLEX_TOKEN = 'xxxx'
|
||||
PLEX_URL = 'http://localhost:32400'
|
||||
|
||||
@ -27,8 +30,8 @@ DEFAULT_REASON = 'Server Admin\'s stream takes priority and {user}(you) has {x}
|
||||
' {user}\'s stream of {video} is {time}% complete. Should be finished in {comp} minutes. ' \
|
||||
'Try again then.'
|
||||
|
||||
ADMIN_USER = ('Admin') # additional usernames can be added ('Admin', 'user2')
|
||||
##
|
||||
ADMIN_USER = ('Admin') # Additional usernames can be added ('Admin', 'user2')
|
||||
# ##
|
||||
|
||||
sess = requests.Session()
|
||||
sess.verify = False
|
||||
@ -71,17 +74,17 @@ def main():
|
||||
|
||||
# Remove users with only 1 stream. Targeting users with multiple concurrent streams
|
||||
filtered_dict = {key: value for key, value in user_dict.items()
|
||||
if len(value) is not 1}
|
||||
if len(value) != 1}
|
||||
|
||||
# Find who to kill and who will be finishing first.
|
||||
if filtered_dict:
|
||||
for users in filtered_dict.values():
|
||||
to_kill = min(users, key=itemgetter(1))
|
||||
to_finish = max(users, key=itemgetter(1))
|
||||
|
||||
|
||||
MESSAGE = DEFAULT_REASON.format(user=to_finish[3], x=len(filtered_dict.values()[0]),
|
||||
video=to_finish[2], time=to_finish[1], comp=to_finish[4])
|
||||
|
||||
|
||||
print(MESSAGE)
|
||||
kill_session(to_kill[0], MESSAGE)
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Description: Use conditions to kill a stream
|
||||
Author: Blacktwin, Arcanemagus, Samwiseg0, JonnyWong16, DirtyCajunRice
|
||||
@ -372,8 +375,8 @@ class Stream:
|
||||
if self.session_exists is False:
|
||||
sys.stdout.write(
|
||||
"Session '{}' from user '{}' is no longer active "
|
||||
.format(self.session_id, self.username)
|
||||
+ "on the server, stopping monitoring.\n")
|
||||
.format(self.session_id, self.username) +
|
||||
"on the server, stopping monitoring.\n")
|
||||
return False
|
||||
|
||||
now = datetime.now()
|
||||
@ -392,8 +395,8 @@ class Stream:
|
||||
elif self.state == 'playing' or self.state == 'buffering':
|
||||
sys.stdout.write(
|
||||
"Session '{}' from user '{}' has been resumed, "
|
||||
.format(self.session_id, self.username)
|
||||
+ "stopping monitoring.\n")
|
||||
.format(self.session_id, self.username) +
|
||||
"stopping monitoring.\n")
|
||||
return False
|
||||
|
||||
|
||||
@ -536,7 +539,7 @@ class Notification:
|
||||
"value": message,
|
||||
"short": False
|
||||
}
|
||||
],
|
||||
],
|
||||
"thumb_url": poster_url,
|
||||
"footer": footer,
|
||||
"ts": time.time()
|
||||
@ -562,8 +565,8 @@ if __name__ == "__main__":
|
||||
parser.add_argument('--sessionId',
|
||||
help='The unique identifier for the stream.')
|
||||
parser.add_argument('--notify', type=int,
|
||||
help='Notification Agent ID number to Agent to send '
|
||||
+ 'notification.')
|
||||
help='Notification Agent ID number to Agent to ' +
|
||||
'send notification.')
|
||||
parser.add_argument('--limit', type=int, default=(20 * 60), # 20 minutes
|
||||
help='The time session is allowed to remain paused.')
|
||||
parser.add_argument('--interval', type=int, default=30,
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Description: Limiting Plex users by plays, watches, or total time from Tautulli.
|
||||
Author: Blacktwin, Arcanemagus
|
||||
@ -48,7 +51,7 @@ import argparse
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import os
|
||||
from plexapi.server import PlexServer, CONFIG
|
||||
from plexapi.server import PlexServer
|
||||
from time import time as ttime
|
||||
from time import sleep
|
||||
|
||||
@ -65,6 +68,7 @@ TAUTULLI_APIKEY = os.getenv('TAUTULLI_APIKEY', TAUTULLI_APIKEY)
|
||||
TAUTULLI_ENCODING = os.getenv('TAUTULLI_ENCODING', 'UTF-8')
|
||||
|
||||
# Using CONFIG file
|
||||
# from plexapi.server import CONFIG
|
||||
# PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL)
|
||||
# PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN)
|
||||
# TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl', TAUTULLI_URL)
|
||||
@ -88,7 +92,7 @@ if sess.verify is False:
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
|
||||
lib_dict = {x.title : x.key for x in plex.library.sections()}
|
||||
lib_dict = {x.title: x.key for x in plex.library.sections()}
|
||||
|
||||
|
||||
SELECTOR = ['watch', 'plays', 'time', 'limit']
|
||||
@ -144,7 +148,7 @@ def get_activity(session_id=None):
|
||||
try:
|
||||
req = sess.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = req.json()
|
||||
|
||||
|
||||
if session_id:
|
||||
res_data = response['response']['data']
|
||||
else:
|
||||
@ -271,7 +275,7 @@ if __name__ == "__main__":
|
||||
'Default: %(default)s')
|
||||
parser.add_argument('--duration', type=int, default=0,
|
||||
help='Duration of item that triggered script agent.')
|
||||
|
||||
|
||||
opts = parser.parse_args()
|
||||
|
||||
total_limit = 0
|
||||
@ -327,7 +331,7 @@ if __name__ == "__main__":
|
||||
else:
|
||||
print('Session; {} has been dropped. Stopping monitoring of stream.'.format(opts.sessionId))
|
||||
exit()
|
||||
|
||||
|
||||
print('Total {} ({} + current item duration {}) is greater than limit ({}).'
|
||||
.format(opts.jbop, total_jbop, opts.duration, total_limit))
|
||||
terminate_session(opts.sessionId, message, opts.notify, opts.username)
|
||||
@ -346,16 +350,16 @@ if __name__ == "__main__":
|
||||
if not message:
|
||||
message = LIMIT_MESSAGE.format(delay=opts.delay)
|
||||
ep_watched = [data['watched_status'] for data in history['data']
|
||||
if data['grandparent_rating_key'] == opts.grandparent_rating_key
|
||||
and data['watched_status'] == 1]
|
||||
if data['grandparent_rating_key'] == opts.grandparent_rating_key and
|
||||
data['watched_status'] == 1]
|
||||
if not ep_watched:
|
||||
ep_watched = 0
|
||||
else:
|
||||
ep_watched = sum(ep_watched)
|
||||
|
||||
stopped_time = [data['stopped'] for data in history['data']
|
||||
if data['grandparent_rating_key'] == opts.grandparent_rating_key
|
||||
and data['watched_status'] == 1]
|
||||
if data['grandparent_rating_key'] == opts.grandparent_rating_key and
|
||||
data['watched_status'] == 1]
|
||||
if not stopped_time:
|
||||
stopped_time = unix_time
|
||||
else:
|
||||
@ -366,9 +370,9 @@ if __name__ == "__main__":
|
||||
sys.exit(1)
|
||||
|
||||
if ep_watched >= total_limit:
|
||||
print("{}'s limit is {} and has watched {} episodes of this show today."
|
||||
.format(opts.username, total_limit, ep_watched))
|
||||
print("{}'s limit is {} and has watched {} episodes of this show today.".format(
|
||||
opts.username, total_limit, ep_watched))
|
||||
terminate_session(opts.sessionId, message, opts.notify, opts.username)
|
||||
else:
|
||||
print("{}'s limit is {} but has only watched {} episodes of this show today."
|
||||
.format(opts.username, total_limit, ep_watched))
|
||||
print("{}'s limit is {} but has only watched {} episodes of this show today.".format(
|
||||
opts.username, total_limit, ep_watched))
|
||||
|
@ -20,7 +20,7 @@ Triggers: Playback Start
|
||||
|
||||
Arguments:
|
||||
```
|
||||
--jbop watch --username {username} --sessionId {session_id} --limit plays=3 --notify 1 --killMessage "You have met your limit of 3 watches."
|
||||
--jbop watch --username {username} --sessionId {session_id} --limit plays=3 --notify 1 --killMessage "You have met your limit of 3 watches."
|
||||
```
|
||||
|
||||
### Limit user to total Plays/Watches in a specific library (Movies)
|
||||
@ -29,7 +29,7 @@ Triggers: Playback Start
|
||||
|
||||
Arguments:
|
||||
```
|
||||
--jbop watch --username {username} --sessionId {session_id} --limit plays=3 --section Movies --killMessage "You have met your limit of 3 watches."
|
||||
--jbop watch --username {username} --sessionId {session_id} --limit plays=3 --section Movies --killMessage "You have met your limit of 3 watches."
|
||||
```
|
||||
|
||||
### Limit user to total time watching
|
||||
@ -59,4 +59,3 @@ Arguments:
|
||||
```
|
||||
--jbop time --username {username} --sessionId {session_id} --duration {duration} --limit hours=2 --killMessage "You have met your limit of 3 days and 10 hours."
|
||||
```
|
||||
|
||||
|
@ -248,4 +248,4 @@ Add `--debug` to enable debug logging.
|
||||
|
||||
All examples use \[ `Transcode Decision` | `is` | `transcode` \] which will kill any variant of transcoding.
|
||||
If you want to allow audio or container transcoding and only drop video transcodes, your condition would change to
|
||||
\[ `Video Decision` | `is` | `transcode` \]
|
||||
\[ `Video Decision` | `is` | `transcode` \]
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Use Tautulli draw a map connecting Server to Clients based on IP addresses.
|
||||
|
||||
@ -30,12 +33,12 @@ import json
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
import argparse
|
||||
import numpy as np
|
||||
# import numpy as np
|
||||
import time
|
||||
import webbrowser
|
||||
import re
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = '' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
@ -259,7 +262,7 @@ def draw_map(map_type, geo_dict, filename, headless, leg_choice):
|
||||
import matplotlib.pyplot as plt
|
||||
from mpl_toolkits.basemap import Basemap
|
||||
|
||||
## Map stuff ##
|
||||
# ## Map stuff ##
|
||||
plt.figure(figsize=(16, 9), dpi=100, frameon=False)
|
||||
lon_r = 0
|
||||
lon_l = 0
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
# Maps
|
||||
|
||||
Maps are created with either Matplotlib/Basemap or as a geojson file on an anonymous gist.
|
||||
@ -46,7 +45,7 @@ Choose which map type you'd like by using the `-l` argument:
|
||||
|
||||
- [x] Add check for user count in user_table to allow for greater than 25 users - [Pull](https://github.com/blacktwin/JBOPS/pull/3)
|
||||
- [x] If platform is missing from PLATFORM_COLORS use DEFAULT_COLOR - [Pull](https://github.com/blacktwin/JBOPS/pull/4)
|
||||
- [x] Add arg to allow for runs in headless (mpl.use("Agg"))
|
||||
- [x] Add arg to allow for runs in headless (mpl.use("Agg"))
|
||||
- [x] Add pass on N/A values for Lon/Lat - [Pull](https://github.com/blacktwin/JBOPS/pull/2)
|
||||
|
||||
### Feature updates:
|
||||
@ -57,6 +56,3 @@ Choose which map type you'd like by using the `-l` argument:
|
||||
- [ ] Find server's external IP, geolocation. Allow custom location to override
|
||||
- [ ] Add arg for tracert visualization from server to client
|
||||
- [ ] Animate tracert visualization? gif?
|
||||
|
||||
|
||||
|
||||
|
@ -5,4 +5,4 @@
|
||||
requests
|
||||
matplotlib
|
||||
numpy
|
||||
basemap
|
||||
basemap
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Find what was added TFRAME ago and not watched and notify admin using Tautulli.
|
||||
|
||||
TAUTULLI_URL + delete_media_info_cache?section_id={section_id}
|
||||
@ -12,7 +14,7 @@ import time
|
||||
TFRAME = 1.577e+7 # ~ 6 months in seconds
|
||||
TODAY = time.time()
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = '' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8183/' # Your Tautulli URL
|
||||
LIBRARY_NAMES = ['Movies', 'TV Shows'] # Name of libraries you want to check.
|
||||
@ -162,10 +164,10 @@ for library in libraries:
|
||||
# Find movie rating_key.
|
||||
show_lst += [int(lib.rating_key)]
|
||||
except Exception as e:
|
||||
print "Rating_key failed: {e}".format(e=e)
|
||||
print("Rating_key failed: {e}".format(e=e))
|
||||
|
||||
except Exception as e:
|
||||
print "Library media info failed: {e}".format(e=e)
|
||||
print("Library media info failed: {e}".format(e=e))
|
||||
|
||||
for show in show_lst:
|
||||
try:
|
||||
@ -181,7 +183,7 @@ for show in show_lst:
|
||||
u" not been watched.<d/t> <dd>File location: {x.file}</dd> <br>".format(x=meta, when=added)]
|
||||
|
||||
except Exception as e:
|
||||
print "Metadata failed. Likely end of range: {e}".format(e=e)
|
||||
print("Metadata failed. Likely end of range: {e}".format(e=e))
|
||||
|
||||
if notify_lst:
|
||||
BODY_TEXT = """\
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Send an email with what was added to Plex in the past week using Tautulli.
|
||||
Email includes title (TV: Show Name: Episode Name; Movie: Movie Title), time added, image, and summary.
|
||||
@ -35,22 +38,21 @@ import uuid
|
||||
import argparse
|
||||
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = '' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
LIBRARY_NAMES = ['Movies', 'TV Shows'] # Name of libraries you want to check.
|
||||
LIBRARY_NAMES = ['Movies', 'TV Shows'] # Name of libraries you want to check.
|
||||
|
||||
# Email settings
|
||||
name = '' # Your name
|
||||
sender = '' # From email address
|
||||
to = [sender] # Whoever you want to email [sender, 'name@example.com']
|
||||
name = '' # Your name
|
||||
sender = '' # From email address
|
||||
to = [sender] # Whoever you want to email [sender, 'name@example.com']
|
||||
# Emails will be sent as BCC.
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
email_port = 587 # Email port (Gmail: 587)
|
||||
email_username = '' # Your email username
|
||||
email_password = '' # Your email password
|
||||
email_subject = 'Tautulli Added Last {} day(s) Notification' #The email subject
|
||||
email_username = '' # Your email username
|
||||
email_password = '' # Your email password
|
||||
email_subject = 'Tautulli Added Last {} day(s) Notification' # The email subject
|
||||
|
||||
# Default sizing for pictures
|
||||
# Poster
|
||||
@ -60,7 +62,8 @@ poster_w = 100
|
||||
art_h = 100
|
||||
art_w = 205
|
||||
|
||||
## /EDIT THESE SETTINGS ##
|
||||
# ## /EDIT THESE SETTINGS ##
|
||||
|
||||
|
||||
class METAINFO(object):
|
||||
def __init__(self, data=None):
|
||||
@ -220,17 +223,21 @@ def build_html(rating_key, height, width, pic_type):
|
||||
if meta.grandparent_title == '' or meta.media_type == 'movie':
|
||||
# Movies
|
||||
notify = u"<dt>{x.title} ({x.rating_key}) was added {when}.</dt>" \
|
||||
u"</dt> <dd> <table> <tr> <th>" \
|
||||
'<img src="cid:{cid}" alt="{alt}" width="{width}" height="{height}"> </th>' \
|
||||
u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>" \
|
||||
.format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, width=width, height=height,**image)
|
||||
u"</dt> <dd> <table> <tr> <th>" \
|
||||
'<img src="cid:{cid}" alt="{alt}" width="{width}" height="{height}"> </th>' \
|
||||
u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>" \
|
||||
.format(
|
||||
x=meta, when=added, alt=cgi.escape(meta.rating_key),
|
||||
quote=True, width=width, height=height, **image)
|
||||
else:
|
||||
# Shows
|
||||
notify = u"<dt>{x.grandparent_title}: {x.title} ({x.rating_key}) was added {when}." \
|
||||
u"</dt> <dd> <table> <tr> <th>" \
|
||||
'<img src="cid:{cid}" alt="{alt}" width="{width}" height="{height}"> </th>' \
|
||||
u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>" \
|
||||
.format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, width=width, height=height, **image)
|
||||
u"</dt> <dd> <table> <tr> <th>" \
|
||||
'<img src="cid:{cid}" alt="{alt}" width="{width}" height="{height}"> </th>' \
|
||||
u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>" \
|
||||
.format(
|
||||
x=meta, when=added, alt=cgi.escape(meta.rating_key),
|
||||
quote=True, width=width, height=height, **image)
|
||||
|
||||
image_text = MIMEText(u'[image: {title}]'.format(**image), 'plain', 'utf-8')
|
||||
|
||||
@ -258,8 +265,10 @@ def send_email(msg_text_lst, notify_lst, image_lst, to, days):
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
""".format(notify_lst="\n".join(notify_lst).encode("utf-8"), LIBRARY_NAMES=" & ".join(LIBRARY_NAMES)
|
||||
, quote=True, ), 'html', 'utf-8')
|
||||
""".format(
|
||||
notify_lst="\n".join(notify_lst).encode("utf-8"),
|
||||
LIBRARY_NAMES=" & ".join(LIBRARY_NAMES),
|
||||
quote=True), 'html', 'utf-8')
|
||||
|
||||
message = MIMEMultipart('related')
|
||||
message['Subject'] = email_subject.format(days)
|
||||
@ -291,19 +300,17 @@ def send_email(msg_text_lst, notify_lst, image_lst, to, days):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description="Send an email with what was added to Plex in the past week using Tautulli.")
|
||||
parser.add_argument('-t', '--type', help='Metadata picture type from Plex.',
|
||||
required= True, choices=['art', 'poster'])
|
||||
required=True, choices=['art', 'poster'])
|
||||
parser.add_argument('-s', '--size', help='Metadata picture size from Plex {Height Width}.', nargs='*')
|
||||
parser.add_argument('-d', '--days', help='Time frame for which to check recently added to Plex.',
|
||||
required= True, type=int)
|
||||
required=True, type=int)
|
||||
parser.add_argument('-u', '--users', help='Which users from Plex will be emailed.',
|
||||
nargs='+', default='self', type=str)
|
||||
parser.add_argument('-i', '--ignore', help='Which users from Plex to ignore.',
|
||||
nargs='+', default='None', type=str)
|
||||
|
||||
|
||||
opts = parser.parse_args()
|
||||
|
||||
TODAY = int(time.time())
|
||||
@ -328,8 +335,10 @@ if __name__ == '__main__':
|
||||
|
||||
# Gather all users email addresses
|
||||
if opts.users == ['all']:
|
||||
[to.append(x['email']) for x in get_users() if x['email'] is not None and x['email'] not in to
|
||||
and x['username'] not in opts.ignore]
|
||||
[to.append(x['email']) for x in get_users()
|
||||
if x['email'] is not None and
|
||||
x['email'] not in to and
|
||||
x['username'] not in opts.ignore]
|
||||
elif opts.users != ['all'] and opts.users != 'self':
|
||||
for get_users in get_users():
|
||||
for arg_users in opts.users:
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Delay Notification Agent message for concurrent streams
|
||||
|
||||
@ -19,7 +22,7 @@ import sys
|
||||
import argparse
|
||||
from time import sleep
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = '' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
CONCURRENT_TOTAL = 2
|
||||
@ -44,7 +47,7 @@ BODY_TEXT = """\
|
||||
|
||||
|
||||
def get_activity():
|
||||
# Get the current activity on the PMS.
|
||||
"""Get the current activity on the PMS."""
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_activity'}
|
||||
|
||||
@ -60,7 +63,7 @@ def get_activity():
|
||||
|
||||
|
||||
def send_notification(subject_text, body_text):
|
||||
# Format notification text
|
||||
"""Format notification text."""
|
||||
try:
|
||||
subject = subject_text.format(p=p, total=cc_total)
|
||||
body = body_text.format(p=p, total=cc_total, time=TIMEOUT / 60)
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Notify users of recently added episode to show that they have watched at least LIMIT times via email.
|
||||
Also notify users of new movies.
|
||||
@ -22,11 +25,11 @@ import smtplib
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
IGNORE_LST = ['123456', '123456'] # User_ids
|
||||
IGNORE_LST = ['123456', '123456'] # User_ids
|
||||
LIMIT = 3
|
||||
|
||||
# Email settings
|
||||
@ -74,6 +77,7 @@ TV_BODY = """\
|
||||
|
||||
user_dict = {}
|
||||
|
||||
|
||||
class Users(object):
|
||||
def __init__(self, data=None):
|
||||
d = data or {}
|
||||
@ -108,6 +112,7 @@ def get_user(user_id):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_user' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_users():
|
||||
# Get the user list from Tautulli.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -134,8 +139,9 @@ def get_history(showkey):
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
res_data = response['response']['data']['data']
|
||||
return [UserHIS(data=d) for d in res_data if d['watched_status'] == 1
|
||||
and d['media_type'].lower() in ('episode', 'show')]
|
||||
return [UserHIS(data=d) for d in res_data
|
||||
if d['watched_status'] == 1 and
|
||||
d['media_type'].lower() in ('episode', 'show')]
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e))
|
||||
@ -177,7 +183,7 @@ def get_email(show):
|
||||
|
||||
|
||||
def send_email(to, email_subject, body_html):
|
||||
### Do not edit below ###
|
||||
# ## Do not edit below ###
|
||||
message = MIMEText(body_html, 'html')
|
||||
message['Subject'] = email_subject
|
||||
message['From'] = email.utils.formataddr((name, sender))
|
||||
@ -188,7 +194,7 @@ def send_email(to, email_subject, body_html):
|
||||
mailserver.login(email_username, email_password)
|
||||
mailserver.sendmail(sender, to, message.as_string())
|
||||
mailserver.quit()
|
||||
print 'Email sent'
|
||||
print('Email sent')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@ -241,7 +247,7 @@ if __name__ == '__main__':
|
||||
body_html = MOVIE_BODY.format(p=p)
|
||||
send_email(to, email_subject, body_html)
|
||||
|
||||
elif p.media_type in ['show', 'season', 'episode']:
|
||||
elif p.media_type in ['show', 'season', 'episode']:
|
||||
email_subject = TV_SUBJECT.format(p=p)
|
||||
to = get_email(int(p.grandparent_rating_key))
|
||||
body_html = TV_BODY.format(p=p)
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Pulling together User IP information and Email.
|
||||
|
||||
@ -19,7 +21,7 @@ import argparse
|
||||
import requests
|
||||
import sys
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = '' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
NOTIFIER_ID = 12 # The notification notifier ID
|
||||
@ -39,9 +41,9 @@ BODY_TEXT = """\
|
||||
<head></head>
|
||||
<body>
|
||||
<p>Hi!<br>
|
||||
<br><a href="mailto:{u.email}"><img src="{u.user_thumb}" alt="Poster unavailable" height="50" width="50"></a>
|
||||
<br><a href="mailto:{u.email}"><img src="{u.user_thumb}" alt="Poster unavailable" height="50" width="50"></a>
|
||||
{p.user} has watched {p.media_type}:{p.title} from a new IP address: {p.ip_address}<br>
|
||||
<br>On {p.platform}[{p.player}] in
|
||||
<br>On {p.platform}[{p.player}] in
|
||||
<a href="http://maps.google.com/?q={g.city},{g.country},{g.postal_code}">{g.city}, {g.country} {g.postal_code}</a>
|
||||
at {p.timestamp} on {p.datestamp}<br>
|
||||
<br><br>
|
||||
@ -69,7 +71,7 @@ class UserEmail(object):
|
||||
|
||||
|
||||
def get_user_ip_addresses(user_id='', ip_address=''):
|
||||
# Get the user IP list from Tautulli
|
||||
"""Get the user IP list from Tautulli."""
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_user_ips',
|
||||
'user_id': user_id,
|
||||
@ -99,7 +101,7 @@ def get_user_ip_addresses(user_id='', ip_address=''):
|
||||
|
||||
|
||||
def get_geoip_info(ip_address=''):
|
||||
# Get the geo IP lookup from Tautulli
|
||||
"""Get the geo IP lookup from Tautulli."""
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_geoip_lookup',
|
||||
'ip_address': ip_address}
|
||||
@ -123,7 +125,7 @@ def get_geoip_info(ip_address=''):
|
||||
|
||||
|
||||
def get_user_email(user_id=''):
|
||||
# Get the user email from Tautulli
|
||||
"""Get the user email from Tautulli."""
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_user',
|
||||
'user_id': user_id}
|
||||
@ -147,7 +149,7 @@ def get_user_email(user_id=''):
|
||||
|
||||
|
||||
def send_notification(arguments=None, geodata=None, useremail=None):
|
||||
# Format notification text
|
||||
"""Format notification text."""
|
||||
try:
|
||||
subject = SUBJECT_TEXT.format(p=arguments, g=geodata, u=useremail)
|
||||
body = BODY_TEXT.format(p=arguments, g=geodata, u=useremail)
|
||||
|
@ -1,10 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Tautulli > Settings > Notification Agents > Scripts > Bell icon:
|
||||
[X] Notify on Recently Added
|
||||
Tautulli > Settings > Notification Agents > Scripts > Gear icon:
|
||||
Recently Added: notify_on_added.py
|
||||
|
||||
Tautulli > Settings > Notifications > Script > Script Arguments:
|
||||
|
||||
Tautulli > Settings > Notifications > Script > Script Arguments:
|
||||
-sn {show_name} -ena {episode_name} -ssn {season_num00} -enu {episode_num00} -srv {server_name} -med {media_type} -pos {poster_url} -tt {title} -sum {summary} -lbn {library_name}
|
||||
|
||||
You can add more arguments if you want more details in the email body
|
||||
@ -13,7 +16,6 @@ You can add more arguments if you want more details in the email body
|
||||
from email.mime.text import MIMEText
|
||||
import email.utils
|
||||
import smtplib
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
|
||||
@ -50,24 +52,24 @@ users = [{'email': 'user1@gmail.com',
|
||||
{'email': 'user3@gmail.com',
|
||||
'shows': ('show1', 'show2', 'show3', 'show4')
|
||||
}]
|
||||
|
||||
|
||||
# Kill script now if show_name is not in lists
|
||||
too = list('Match' for u in users if p.show_name in u['shows'])
|
||||
if not too:
|
||||
print 'Kill script now show_name is not in lists'
|
||||
exit()
|
||||
print('Kill script now show_name is not in lists')
|
||||
exit()
|
||||
|
||||
# Join email addresses
|
||||
to = list([u['email'] for u in users if p.show_name in u['shows']])
|
||||
|
||||
# Email settings
|
||||
name = 'Tautulli' # Your name
|
||||
sender = 'sender' # From email address
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
name = 'Tautulli' # Your name
|
||||
sender = 'sender' # From email address
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
email_port = 587 # Email port (Gmail: 587)
|
||||
email_username = 'email' # Your email username
|
||||
email_password = 'password' # Your email password
|
||||
email_subject = 'New episode for ' + p.show_name + ' is available on ' + p.plex_server # The email subject
|
||||
email_username = 'email' # Your email username
|
||||
email_password = 'password' # Your email password
|
||||
email_subject = 'New episode for ' + p.show_name + ' is available on ' + p.plex_server # The email subject
|
||||
|
||||
# Detailed body for tv shows
|
||||
show_html = """\
|
||||
@ -84,14 +86,13 @@ show_html = """\
|
||||
</html>
|
||||
""".format(p=p)
|
||||
|
||||
### Do not edit below ###
|
||||
# ### Do not edit below ###
|
||||
# Check to see whether it is a tv show
|
||||
if p.show_type.lower() == 'show' or p.show_type.lower() == 'episode':
|
||||
message = MIMEText(show_html, 'html')
|
||||
message['Subject'] = email_subject
|
||||
message['From'] = email.utils.formataddr((name, sender))
|
||||
|
||||
|
||||
|
||||
mailserver = smtplib.SMTP(email_server, email_port)
|
||||
mailserver.starttls()
|
||||
mailserver.ehlo()
|
||||
@ -99,4 +100,4 @@ if p.show_type.lower() == 'show' or p.show_type.lower() == 'episode':
|
||||
mailserver.sendmail(sender, to, message.as_string())
|
||||
mailserver.quit()
|
||||
else:
|
||||
exit()
|
||||
exit()
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Description: Notify only if recently aired/released
|
||||
Author: Blacktwin
|
||||
@ -64,6 +67,7 @@ aired_date = datetime.strptime(air_date, date_format)
|
||||
today = date.today()
|
||||
delta = today - aired_date.date()
|
||||
|
||||
|
||||
def notify_recently_added(rating_key, notifier_id):
|
||||
# Get the metadata for a media item.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -79,8 +83,9 @@ def notify_recently_added(rating_key, notifier_id):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'notify_recently_added' request failed: {0}.".format(e))
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if delta.days < RECENT_DAYS:
|
||||
notify_recently_added(rating_key, NOTIFIER_ID)
|
||||
else:
|
||||
print("Not recent enough, no notification to be sent.")
|
||||
print("Not recent enough, no notification to be sent.")
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Notify users of recently added episode to show that they have watched at least LIMIT times via email.
|
||||
Block users with IGNORE_LST.
|
||||
@ -21,11 +24,11 @@ import smtplib
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
IGNORE_LST = [123456, 123456] # User_ids
|
||||
IGNORE_LST = [123456, 123456] # User_ids
|
||||
LIMIT = 3
|
||||
|
||||
# Email settings
|
||||
@ -38,6 +41,7 @@ email_password = '' # Your email password
|
||||
|
||||
user_dict = {}
|
||||
|
||||
|
||||
class Users(object):
|
||||
def __init__(self, data=None):
|
||||
d = data or {}
|
||||
@ -74,7 +78,10 @@ def get_user(user_id):
|
||||
|
||||
|
||||
def get_history(showkey):
|
||||
# Get the user history from Tautulli. Length matters!
|
||||
"""Get the user history from Tautulli.
|
||||
|
||||
Length matters!
|
||||
"""
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_history',
|
||||
'grandparent_rating_key': showkey,
|
||||
@ -84,8 +91,9 @@ def get_history(showkey):
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
res_data = response['response']['data']['data']
|
||||
return [UserHIS(data=d) for d in res_data if d['watched_status'] == 1
|
||||
and d['media_type'].lower() in ('episode', 'show')]
|
||||
return [UserHIS(data=d) for d in res_data
|
||||
if d['watched_status'] == 1 and
|
||||
d['media_type'].lower() in ('episode', 'show')]
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e))
|
||||
@ -188,7 +196,7 @@ if __name__ == '__main__':
|
||||
</html>
|
||||
""".format(p=p)
|
||||
|
||||
### Do not edit below ###
|
||||
# ## Do not edit below ###
|
||||
message = MIMEText(show_html, 'html')
|
||||
message['Subject'] = email_subject
|
||||
message['From'] = email.utils.formataddr((name, sender))
|
||||
@ -199,4 +207,4 @@ if __name__ == '__main__':
|
||||
mailserver.login(email_username, email_password)
|
||||
mailserver.sendmail(sender, to, message.as_string())
|
||||
mailserver.quit()
|
||||
print 'Email sent'
|
||||
print('Email sent')
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Pulling together User IP information and Email.
|
||||
Enable the API under Settings > Access Control and remember your API key.
|
||||
@ -13,9 +16,9 @@ from email.mime.text import MIMEText
|
||||
import email.utils
|
||||
import smtplib
|
||||
|
||||
## -sn {show_name} -ena {episode_name} -ssn {season_num00} -enu {episode_num00} -srv {server_name} -med {media_type} -pos {poster_url} -tt {title} -sum {summary} -lbn {library_name} -ip {ip_address} -us {user} -uid {user_id} -pf {platform} -pl {player} -da {datestamp} -ti {timestamp}
|
||||
# ## -sn {show_name} -ena {episode_name} -ssn {season_num00} -enu {episode_num00} -srv {server_name} -med {media_type} -pos {poster_url} -tt {title} -sum {summary} -lbn {library_name} -ip {ip_address} -us {user} -uid {user_id} -pf {platform} -pl {player} -da {datestamp} -ti {timestamp}
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'xxxxxxxxxxx' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
@ -44,17 +47,20 @@ BODY_TEXT = """\
|
||||
"""
|
||||
|
||||
# Email settings
|
||||
name = '' # Your name
|
||||
sender = '' # From email address
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
name = '' # Your name
|
||||
sender = '' # From email address
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
email_port = 587 # Email port (Gmail: 587)
|
||||
email_username = '' # Your email username
|
||||
email_password = '' # Your email password
|
||||
email_username = '' # Your email username
|
||||
email_password = '' # Your email password
|
||||
email_subject = "New IP has been detected using Plex."
|
||||
|
||||
IGNORE_LST = ['123456', '123456'] # User_id
|
||||
IGNORE_LST = ['123456', '123456'] # User_id
|
||||
|
||||
|
||||
# ##Geo Space##
|
||||
|
||||
|
||||
##Geo Space##
|
||||
class GeoData(object):
|
||||
def __init__(self, data=None):
|
||||
data = data or {}
|
||||
@ -62,22 +68,28 @@ class GeoData(object):
|
||||
self.city = data.get('city', 'N/A')
|
||||
self.postal_code = data.get('postal_code', 'N/A')
|
||||
|
||||
##USER Space##
|
||||
|
||||
# ##USER Space##
|
||||
|
||||
|
||||
class UserEmail(object):
|
||||
def __init__(self, data=None):
|
||||
data = data or {}
|
||||
self.email = data.get('email', 'N/A')
|
||||
self.user_id = data.get('user_id', 'N/A')
|
||||
self.user_thumb = data.get('user_thumb', 'N/A')
|
||||
|
||||
##API Space##
|
||||
|
||||
|
||||
# ##API Space##
|
||||
|
||||
|
||||
def get_user_ip_addresses(user_id='', ip_address=''):
|
||||
# Get the user IP list from Tautulli
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_user_ips',
|
||||
'user_id': user_id,
|
||||
'search': ip_address}
|
||||
|
||||
|
||||
try:
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
@ -99,6 +111,7 @@ def get_user_ip_addresses(user_id='', ip_address=''):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_user_ip_addresses' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_geoip_info(ip_address=''):
|
||||
# Get the geo IP lookup from Tautulli
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -146,6 +159,7 @@ def get_user_email(user_id=''):
|
||||
sys.stderr.write("Tautulli API 'get_user' request failed: {0}.".format(e))
|
||||
return UserEmail()
|
||||
|
||||
|
||||
def send_notification(arguments=None, geodata=None, useremail=None):
|
||||
# Format notification text
|
||||
try:
|
||||
@ -163,12 +177,12 @@ def send_notification(arguments=None, geodata=None, useremail=None):
|
||||
mailserver.login(email_username, email_password)
|
||||
mailserver.sendmail(sender, u.email, message.as_string())
|
||||
mailserver.quit()
|
||||
print 'Email sent'
|
||||
print('Email sent')
|
||||
except Exception as e:
|
||||
sys.stderr.write("Email Failure: {0}.".format(e))
|
||||
|
||||
def clr_sql(ip):
|
||||
|
||||
def clr_sql(ip):
|
||||
try:
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'sql',
|
||||
@ -179,6 +193,7 @@ def clr_sql(ip):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_sql' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Parse arguments from Tautulli
|
||||
parser = argparse.ArgumentParser()
|
||||
@ -188,7 +203,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('-us', '--user', action='store', default='',
|
||||
help='Username of the person watching the stream')
|
||||
parser.add_argument('-uid', '--user_id', action='store', default='',
|
||||
help='User_ID of the person watching the stream')
|
||||
help='User_ID of the person watching the stream')
|
||||
parser.add_argument('-med', '--media_type', action='store', default='',
|
||||
help='The media type of the stream')
|
||||
parser.add_argument('-tt', '--title', action='store', default='',
|
||||
@ -217,7 +232,7 @@ if __name__ == '__main__':
|
||||
help='The summary of the TV show')
|
||||
parser.add_argument('-lbn', '--library_name', action='store', default='',
|
||||
help='The name of the TV show')
|
||||
|
||||
|
||||
p = parser.parse_args()
|
||||
|
||||
if p.user_id not in IGNORE_LST:
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
1. Install the requests module for python.
|
||||
pip install requests
|
||||
@ -14,18 +17,18 @@ Tautulli > Settings > Notifications > Script > Script Arguments:
|
||||
https://gist.github.com/blacktwin/261c416dbed08291e6d12f6987d9bafa
|
||||
"""
|
||||
|
||||
from twitter import *
|
||||
from twitter import Twitter, OAuth
|
||||
import argparse
|
||||
import requests
|
||||
import os
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TOKEN = ''
|
||||
TOKEN_SECRET = ''
|
||||
CONSUMER_KEY = ''
|
||||
CONSUMER_SECRET = ''
|
||||
|
||||
TITLE_FIND = ['Friends'] # Title to ignore ['Snow White']
|
||||
TITLE_FIND = ['Friends'] # Title to ignore ['Snow White']
|
||||
TWITTER_USER = ' @username'
|
||||
|
||||
BODY_TEXT = ''
|
||||
@ -80,12 +83,13 @@ if __name__ == '__main__':
|
||||
|
||||
p = parser.parse_args()
|
||||
|
||||
|
||||
if p.media_type == 'movie':
|
||||
BODY_TEXT = MOVIE_TEXT.format(media_type=p.media_type, title=p.title, duration=p.duration)
|
||||
elif p.media_type == 'episode':
|
||||
BODY_TEXT = TV_TEXT.format(media_type=p.media_type, show_name=p.show_name, title=p.title,
|
||||
season_num00=p.season_num, episode_num00=p.episode_num, duration=p.duration)
|
||||
BODY_TEXT = TV_TEXT.format(
|
||||
media_type=p.media_type, show_name=p.show_name, title=p.title,
|
||||
season_num00=p.season_num, episode_num00=p.episode_num,
|
||||
duration=p.duration)
|
||||
else:
|
||||
exit()
|
||||
|
||||
@ -101,7 +105,6 @@ if __name__ == '__main__':
|
||||
for chunk in request:
|
||||
image.write(chunk)
|
||||
|
||||
|
||||
t_upload = Twitter(domain='upload.twitter.com',
|
||||
auth=OAuth(TOKEN, TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET))
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Find when media was added between STARTFRAME and ENDFRAME to Plex through Tautulli.
|
||||
|
||||
Some Exceptions have been commented out to supress what is printed.
|
||||
Some Exceptions have been commented out to supress what is printed.
|
||||
Uncomment Exceptions if you run into problem and need to investigate.
|
||||
"""
|
||||
|
||||
@ -9,22 +12,20 @@ import requests
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
STARTFRAME = 1480550400 # 2016, Dec 1 in seconds
|
||||
ENDFRAME = 1488326400 # 2017, March 1 in seconds
|
||||
STARTFRAME = 1480550400 # 2016, Dec 1 in seconds
|
||||
ENDFRAME = 1488326400 # 2017, March 1 in seconds
|
||||
|
||||
TODAY = int(time.time())
|
||||
LASTMONTH = int(TODAY - 2629743) # 2629743 = 1 month in seconds
|
||||
LASTMONTH = int(TODAY - 2629743) # 2629743 = 1 month in seconds
|
||||
|
||||
# Uncomment to change range to 1 month ago - Today
|
||||
# STARTFRAME = LASTMONTH
|
||||
# ENDFRAME = TODAY
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
LIBRARY_NAMES = ['TV Shows', 'Movies'] # Names of your libraries you want to check.
|
||||
LIBRARY_NAMES = ['TV Shows', 'Movies'] # Names of your libraries you want to check.
|
||||
|
||||
|
||||
class LIBINFO(object):
|
||||
@ -70,6 +71,7 @@ def get_new_rating_keys(rating_key, media_type):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_new_rating_keys' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_library_media_info(section_id):
|
||||
# Get the data on the Tautulli media info tables. Length matters!
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -88,6 +90,7 @@ def get_library_media_info(section_id):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_library_media_info' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_metadata(rating_key):
|
||||
# Get the metadata for a media item.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -106,6 +109,7 @@ def get_metadata(rating_key):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_metadata' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def update_library_media_info(section_id):
|
||||
# Get the data on the Tautulli media info tables.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -122,6 +126,7 @@ def update_library_media_info(section_id):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'update_library_media_info' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_libraries_table():
|
||||
# Get the data on the Tautulli libraries table.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -181,15 +186,16 @@ for i in sorted(show_lst, reverse=True):
|
||||
# Shows
|
||||
print(u"{x.grandparent_title}: {x.title} ({x.rating_key}) was added {when}.".format(x=x, when=added))
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# Remove commented print below to investigate problems.
|
||||
# print("Metadata failed. Likely end of range: {e}").format(e=e)
|
||||
# Remove break if not finding files in range
|
||||
break
|
||||
|
||||
print("There were {amount} files added between {start}:{end}".format(amount=len(count_lst),
|
||||
start=time.ctime(float(STARTFRAME)),
|
||||
end=time.ctime(float(ENDFRAME))))
|
||||
print("There were {amount} files added between {start}:{end}".format(
|
||||
amount=len(count_lst),
|
||||
start=time.ctime(float(STARTFRAME)),
|
||||
end=time.ctime(float(ENDFRAME))))
|
||||
print("Total movies: {}".format(count_lst.count('movie')))
|
||||
print("Total shows: {}".format(count_lst.count('show') + count_lst.count('episode')))
|
||||
print("Total size of files added: {}MB".format(sum(size_lst)>>20))
|
||||
print("Total size of files added: {}MB".format(sum(size_lst) >> 20))
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 1. Install the requests module for python.
|
||||
# pip install requests
|
||||
# 2. Add script arguments in Tautulli.
|
||||
@ -10,7 +13,7 @@ import sys
|
||||
user = sys.argv[1]
|
||||
title = sys.argv[2]
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXXXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
NOTIFIER_ID = 10 # The notification notifier ID for Tautulli
|
||||
@ -25,7 +28,7 @@ BODY_TEXT = """\
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
""" %(user, title)
|
||||
""" % (user, title)
|
||||
|
||||
|
||||
class UserHIS(object):
|
||||
@ -33,9 +36,9 @@ class UserHIS(object):
|
||||
data = data or {}
|
||||
self.watched = [d['watched_status'] for d in data]
|
||||
|
||||
|
||||
|
||||
def get_history():
|
||||
# Get the user IP list from Tautulli
|
||||
"""Get the history from Tautulli."""
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_history',
|
||||
'user': user,
|
||||
|
@ -1,19 +1,23 @@
|
||||
'''
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Run script by itself. Will look for WARN code followed by /library/metadata/ str in Plex logs.
|
||||
This is find files that are corrupt or having playback issues.
|
||||
I corrupted a file to test.
|
||||
'''
|
||||
"""
|
||||
|
||||
import requests
|
||||
import sys
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
lib_met = []
|
||||
err_title = []
|
||||
|
||||
|
||||
class PlexLOG(object):
|
||||
def __init__(self, data=None):
|
||||
self.error_msg = []
|
||||
@ -43,6 +47,7 @@ def get_plex_log():
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_plex_log' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_history(key):
|
||||
# Get the user IP list from Tautulli
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -59,6 +64,7 @@ def get_history(key):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
p_log = get_plex_log()
|
||||
for co, msg in p_log.error_msg:
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import psutil
|
||||
import requests
|
||||
|
||||
@ -6,20 +9,21 @@ drive = 'F:'
|
||||
|
||||
disk = psutil.disk_partitions()
|
||||
|
||||
TAUTULLI_URL = 'http://localhost:8182/' # Your Tautulli URL
|
||||
TAUTULLI_APIKEY = 'xxxxxx' # Enter your Tautulli API Key
|
||||
NOTIFIER_LST = [10, 11] # The Tautulli notifier notifier id found here: https://github.com/drzoidberg33/plexpy/blob/master/plexpy/notifiers.py#L43
|
||||
NOTIFY_SUBJECT = 'Tautulli' # The notification subject
|
||||
NOTIFY_BODY = 'The Plex disk {0} was not found'.format(drive) # The notification body
|
||||
TAUTULLI_URL = 'http://localhost:8182/' # Your Tautulli URL
|
||||
TAUTULLI_APIKEY = 'xxxxxx' # Enter your Tautulli API Key
|
||||
NOTIFIER_LST = [10, 11] # The Tautulli notifier notifier id found here: https://github.com/drzoidberg33/plexpy/blob/master/plexpy/notifiers.py#L43
|
||||
NOTIFY_SUBJECT = 'Tautulli' # The notification subject
|
||||
NOTIFY_BODY = 'The Plex disk {0} was not found'.format(drive) # The notification body
|
||||
|
||||
disk_check = [True for i in disk if drive in i.mountpoint]
|
||||
|
||||
if not disk_check:
|
||||
# Send the notification through Tautulli
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'notify',
|
||||
'subject': NOTIFY_SUBJECT,
|
||||
'body': NOTIFY_BODY}
|
||||
payload = {
|
||||
'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'notify',
|
||||
'subject': NOTIFY_SUBJECT,
|
||||
'body': NOTIFY_BODY}
|
||||
|
||||
for notifier in NOTIFIER_LST:
|
||||
payload['notifier_id'] = notifier
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Use Tautulli to print plays by library from 0, 1, 7, or 30 days ago. 0 = total
|
||||
|
||||
@ -26,14 +29,15 @@ import requests
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
|
||||
TAUTULLI_APIKEY = 'xxxxx' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
OUTPUT = 'Library: {section}\nDays: {days}\nPlays: {plays}'
|
||||
|
||||
## CODE BELOW ##
|
||||
# ## CODE BELOW ##
|
||||
|
||||
|
||||
def get_library_names():
|
||||
# Get a list of new rating keys for the PMS of all of the item's parent/children.
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Use Tautulli to pull plays by library
|
||||
|
||||
@ -6,8 +9,8 @@ optional arguments:
|
||||
-l [ ...], --libraries [ ...]
|
||||
Space separated list of case sensitive names to process. Allowed names are:
|
||||
(choices: All Library Names)
|
||||
|
||||
|
||||
|
||||
|
||||
Usage:
|
||||
plays_by_library.py -l "TV Shows" Movies
|
||||
TV Shows - Plays: 2859
|
||||
@ -18,18 +21,17 @@ Usage:
|
||||
import requests
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
# import json
|
||||
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
|
||||
TAUTULLI_APIKEY = 'xxxxxx' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
|
||||
OUTPUT = '{section} - Plays: {plays}'
|
||||
|
||||
## CODE BELOW ##
|
||||
# ## CODE BELOW ##
|
||||
|
||||
|
||||
def get_libraries_table(sections=None):
|
||||
# Get a list of new rating keys for the PMS of all of the item's parent/children.
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
usage: plex_netflix_check.py [-h] [-l [...]] [-s ] [-t ]
|
||||
|
||||
@ -29,15 +32,14 @@ import argparse
|
||||
from xmljson import badgerfish as bf
|
||||
from lxml.html import fromstring
|
||||
from time import sleep
|
||||
import json
|
||||
from plexapi.server import PlexServer
|
||||
# pip install plexapi
|
||||
|
||||
|
||||
## Edit ##
|
||||
# ## Edit ##
|
||||
PLEX_URL = 'http://localhost:32400'
|
||||
PLEX_TOKEN = 'xxxx'
|
||||
## /Edit ##
|
||||
# ## /Edit ##
|
||||
|
||||
sess = requests.Session()
|
||||
sess.verify = False
|
||||
@ -57,7 +59,7 @@ def instantwatch_search(name, media_type, site, search_limit):
|
||||
elif media_type == 'episode':
|
||||
content_type = '4'
|
||||
else:
|
||||
content_type =''
|
||||
content_type = ''
|
||||
|
||||
payload = {'content_type': content_type,
|
||||
'q': name.lower()}
|
||||
@ -108,7 +110,7 @@ def instantwatch_search(name, media_type, site, search_limit):
|
||||
pass
|
||||
|
||||
for data in results['span']:
|
||||
if data['@class'] == 'title' and search_limit is not 0:
|
||||
if data['@class'] == 'title' and search_limit != 0:
|
||||
if str(data['a']['$']).lower().startswith(name.lower()):
|
||||
if amazon_id:
|
||||
if data['a']['@data-title-id'] == amazon_id:
|
||||
@ -119,10 +121,10 @@ def instantwatch_search(name, media_type, site, search_limit):
|
||||
print('Page: {}{}'.format(NETFLIX_URL, data['a']['@data-title-id']))
|
||||
results_count += 1
|
||||
search_limit -= 1
|
||||
if search_limit is 0:
|
||||
if search_limit == 0:
|
||||
limit = True
|
||||
|
||||
elif data['@class'] == 'title' and search_limit is 0 and limit is False:
|
||||
elif data['@class'] == 'title' and search_limit == 0 and limit is False:
|
||||
if data['a']['$'].lower().startswith(name.lower()):
|
||||
if amazon_id:
|
||||
if data['a']['@data-title-id'] == amazon_id:
|
||||
@ -206,7 +208,7 @@ def main():
|
||||
'(choices: %(choices)s)\n(default: %(default)s)')
|
||||
parser.add_argument('-site', '--site', metavar='', choices=['Netflix', 'Amazon', 'Both'], nargs='?',
|
||||
default='Both', help='Refine search for name by using type.\n'
|
||||
'(choices: %(choices)s)\n(default: %(default)s)')
|
||||
'(choices: %(choices)s)\n(default: %(default)s)')
|
||||
parser.add_argument('-sl', '--search_limit', metavar='', nargs='?', type=int, default=5,
|
||||
help='Define number of search returns from page. Zero returns all.'
|
||||
'\n(default: %(default)s)')
|
||||
@ -223,5 +225,6 @@ def main():
|
||||
else:
|
||||
plex_library_search(opts.library[0], opts.site, opts.episodes, opts.search_limit)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Description: Comparing content between two or more Plex servers.
|
||||
Creates .json file in script directory of server compared.
|
||||
@ -103,14 +105,14 @@ def get_meta(meta):
|
||||
"""
|
||||
thumb_url = '{}{}?X-Plex-Token={}'.format(
|
||||
meta._server._baseurl, meta.thumb, meta._server._token)
|
||||
|
||||
|
||||
meta_dict = {'title': meta.title,
|
||||
'rating': meta.rating if
|
||||
meta.rating != None else 0.0,
|
||||
meta.rating is not None else 0.0,
|
||||
'genres': [x.tag for x in meta.genres],
|
||||
'server': [meta._server.friendlyName],
|
||||
'thumb': [thumb_url]
|
||||
}
|
||||
}
|
||||
if meta.guid:
|
||||
# guid will return (com.plexapp.agents.imdb://tt4302938?lang=en)
|
||||
# Agents will differ between servers.
|
||||
@ -193,8 +195,9 @@ def org_diff(lst_dicts, media_type, main_server):
|
||||
# Sort item list by Plex rating
|
||||
# Duplicates will use originals rating
|
||||
meta_lst = sorted(meta_lst, key=lambda d: d['rating'], reverse=True)
|
||||
diff_dict[mtype] = {'combined': {'count': len(meta_lst),
|
||||
'list': meta_lst}}
|
||||
diff_dict[mtype] = {'combined': {
|
||||
'count': len(meta_lst),
|
||||
'list': meta_lst}}
|
||||
|
||||
print('...finding {}s missing from {}'.format(
|
||||
mtype, main_server))
|
||||
@ -205,13 +208,15 @@ def org_diff(lst_dicts, media_type, main_server):
|
||||
# Main Server name is absent in items server list
|
||||
elif main_server in item['server'] and len(item['server']) == 1:
|
||||
unique.append(item)
|
||||
diff_dict[mtype].update({'missing': {'count': len(missing),
|
||||
'list': missing}})
|
||||
diff_dict[mtype].update({'missing': {
|
||||
'count': len(missing),
|
||||
'list': missing}})
|
||||
|
||||
print('...finding {}s unique to {}'.format(
|
||||
mtype, main_server))
|
||||
diff_dict[mtype].update({'unique': {'count': len(unique),
|
||||
'list': unique}})
|
||||
diff_dict[mtype].update({'unique': {
|
||||
'count': len(unique),
|
||||
'list': unique}})
|
||||
|
||||
return diff_dict
|
||||
|
||||
@ -220,7 +225,7 @@ if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Comparing content between two or more Plex servers.",
|
||||
formatter_class = argparse.RawTextHelpFormatter)
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument('--server', required=True, choices=SERVER_DICT.keys(),
|
||||
action='append', nargs='?', metavar='',
|
||||
help='Choose servers to connect to and compare.\n'
|
||||
@ -272,7 +277,8 @@ if __name__ == "__main__":
|
||||
|
||||
main_dict = org_diff(combined_lst, opts.media_type, main_server.friendlyName)
|
||||
|
||||
filename = 'diff_{}_{}_servers.json'.format(opts.server[0],'_'.join(servers))
|
||||
filename = 'diff_{}_{}_servers.json'.format(
|
||||
opts.server[0], '_'.join(servers))
|
||||
|
||||
with open(filename, 'w') as fp:
|
||||
json.dump(main_dict, fp, indent=4, sort_keys=True)
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Use Tautulli to count how many plays per user occurred this week.
|
||||
Notify via Tautulli Notification
|
||||
@ -10,7 +13,7 @@ import time
|
||||
TODAY = int(time.time())
|
||||
LASTWEEK = int(TODAY - 7 * 24 * 60 * 60)
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
SUBJECT_TEXT = "Tautulli Weekly Plays Per User"
|
||||
@ -29,12 +32,13 @@ class UserHIS(object):
|
||||
self.full_title = d['full_title']
|
||||
self.date = d['date']
|
||||
|
||||
|
||||
def get_history():
|
||||
# Get the Tautulli history. Count matters!!!
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_history',
|
||||
'length': 100000}
|
||||
|
||||
|
||||
try:
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
@ -42,10 +46,11 @@ def get_history():
|
||||
res_data = response['response']['data']['data']
|
||||
return [UserHIS(data=d) for d in res_data if d['watched_status'] == 1 and
|
||||
LASTWEEK < d['date'] < TODAY]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def send_notification(BODY_TEXT):
|
||||
# Format notification text
|
||||
try:
|
||||
@ -73,13 +78,15 @@ def send_notification(BODY_TEXT):
|
||||
sys.stderr.write("Tautulli API 'notify' request failed: {0}.".format(e))
|
||||
return None
|
||||
|
||||
|
||||
def add_to_dictlist(d, key, val):
|
||||
if key not in d:
|
||||
d[key] = [val]
|
||||
else:
|
||||
d[key].append(val)
|
||||
|
||||
user_dict ={}
|
||||
|
||||
user_dict = {}
|
||||
notify_lst = []
|
||||
|
||||
[add_to_dictlist(user_dict, h.user, h.media) for h in get_history()]
|
||||
@ -106,7 +113,9 @@ BODY_TEXT = """\
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
""".format(notify_lst="\n".join(notify_lst).encode("utf-8"),end=time.ctime(float(TODAY)),
|
||||
start=time.ctime(float(LASTWEEK)))
|
||||
""".format(
|
||||
notify_lst="\n".join(notify_lst).encode("utf-8"),
|
||||
end=time.ctime(float(TODAY)),
|
||||
start=time.ctime(float(LASTWEEK)))
|
||||
|
||||
send_notification(BODY_TEXT)
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import argparse
|
||||
from plexapi.myplex import MyPlexAccount
|
||||
@ -36,6 +38,7 @@ VERIFY_SSL = False
|
||||
|
||||
timestr = time.strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
|
||||
class Connection:
|
||||
def __init__(self, url=None, apikey=None, verify_ssl=False):
|
||||
self.url = url
|
||||
@ -47,15 +50,15 @@ class Connection:
|
||||
pool_block=True)
|
||||
self.session.mount('http://', self.adapters)
|
||||
self.session.mount('https://', self.adapters)
|
||||
|
||||
|
||||
# Ignore verifying the SSL certificate
|
||||
if verify_ssl is False:
|
||||
self.session.verify = False
|
||||
# Disable the warning that the request is insecure, we know that...
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
|
||||
|
||||
class Library(object):
|
||||
def __init__(self, data=None):
|
||||
d = data or {}
|
||||
@ -66,38 +69,38 @@ class Library(object):
|
||||
try:
|
||||
self.parent_count = d['parent_count']
|
||||
self.child_count = d['child_count']
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# print(e)
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class Tautulli:
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
|
||||
def _call_api(self, cmd, payload, method='GET'):
|
||||
payload['cmd'] = cmd
|
||||
payload['apikey'] = self.connection.apikey
|
||||
|
||||
|
||||
try:
|
||||
response = self.connection.session.request(method, self.connection.url + '/api/v2', params=payload)
|
||||
except RequestException as e:
|
||||
print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e))
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
except ValueError:
|
||||
print("Failed to parse json response for Tautulli API cmd '{}'".format(cmd))
|
||||
return
|
||||
|
||||
|
||||
if response_json['response']['result'] == 'success':
|
||||
return response_json['response']['data']
|
||||
else:
|
||||
error_msg = response_json['response']['message']
|
||||
print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg))
|
||||
return
|
||||
|
||||
|
||||
def get_watched_history(self, user=None, section_id=None, rating_key=None, start=None, length=None):
|
||||
"""Call Tautulli's get_history api endpoint"""
|
||||
payload = {"order_column": "full_title",
|
||||
@ -112,14 +115,14 @@ class Tautulli:
|
||||
payload["start"] = start
|
||||
if length:
|
||||
payload["lengh"] = length
|
||||
|
||||
|
||||
history = self._call_api('get_history', payload)
|
||||
|
||||
|
||||
return [d for d in history['data'] if d['watched_status'] == 1]
|
||||
|
||||
def get_libraries(self):
|
||||
"""Call Tautulli's get_libraries api endpoint"""
|
||||
|
||||
|
||||
payload = {}
|
||||
return self._call_api('get_libraries', payload)
|
||||
|
||||
@ -131,7 +134,7 @@ class Plex:
|
||||
if token and url:
|
||||
session = Connection().session
|
||||
self.server = PlexServer(baseurl=url, token=token, session=session)
|
||||
|
||||
|
||||
def all_users(self):
|
||||
"""All users
|
||||
Returns
|
||||
@ -141,9 +144,9 @@ class Plex:
|
||||
users = {self.account.title: self.account}
|
||||
for user in self.account.users():
|
||||
users[user.title] = user
|
||||
|
||||
|
||||
return users
|
||||
|
||||
|
||||
def all_sections(self):
|
||||
"""All sections from server
|
||||
Returns
|
||||
@ -152,7 +155,7 @@ class Plex:
|
||||
{section title: section object}
|
||||
"""
|
||||
sections = {section.title: section for section in self.server.library.sections()}
|
||||
|
||||
|
||||
return sections
|
||||
|
||||
def all_sections_totals(self, library=None):
|
||||
@ -174,17 +177,17 @@ class Plex:
|
||||
section_total = len(section.search(libtype='episode'))
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
if library:
|
||||
return section_total
|
||||
|
||||
|
||||
section_totals[section.title] = section_total
|
||||
|
||||
return section_totals
|
||||
|
||||
|
||||
def make_pie(user_dict, sections_dict, title, filename=None, headless=None):
|
||||
|
||||
|
||||
import matplotlib as mpl
|
||||
mpl.rcParams['text.color'] = FONT_COLOR
|
||||
mpl.rcParams['axes.labelcolor'] = FONT_COLOR
|
||||
@ -209,7 +212,7 @@ def make_pie(user_dict, sections_dict, title, filename=None, headless=None):
|
||||
ax.pie(fracs, explode=EXPLODE, colors=COLORS, pctdistance=1.3,
|
||||
autopct='%1.1f%%', shadow=True, startangle=300, radius=0.8,
|
||||
wedgeprops=dict(width=0.5, edgecolor=BACKGROUND_COLOR))
|
||||
|
||||
|
||||
if user_position == 0:
|
||||
ax.set_title("{}: {}".format(library, library_total), bbox=BBOX_PROPS,
|
||||
ha='center', va='bottom', size=12)
|
||||
@ -223,7 +226,7 @@ def make_pie(user_dict, sections_dict, title, filename=None, headless=None):
|
||||
plt.suptitle(title, bbox=BBOX_PROPS, size=15)
|
||||
plt.tight_layout()
|
||||
fig.subplots_adjust(top=0.88)
|
||||
|
||||
|
||||
if filename:
|
||||
plt.savefig('{}_{}.png'.format(filename, timestr), facecolor=BACKGROUND_COLOR)
|
||||
print('Image saved as: {}_{}.png'.format(filename, timestr))
|
||||
@ -235,13 +238,13 @@ if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description="Show watched percentage of users by libraries.",
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
|
||||
|
||||
servers = parser.add_mutually_exclusive_group()
|
||||
servers.add_argument('--plex', default=False, action='store_true',
|
||||
help='Pull data from Plex')
|
||||
help='Pull data from Plex')
|
||||
servers.add_argument('--tautulli', default=False, action='store_true',
|
||||
help='Pull data from Tautulli')
|
||||
|
||||
help='Pull data from Tautulli')
|
||||
|
||||
parser.add_argument('--libraries', nargs='*', metavar='library',
|
||||
help='Libraries to scan for watched content.')
|
||||
parser.add_argument('--users', nargs='*', metavar='users',
|
||||
@ -251,20 +254,19 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--filename', type=str, default='Users_Watched_{}'.format(timestr), metavar='',
|
||||
help='Filename of pie chart. None will not save. \n(default: %(default)s)')
|
||||
parser.add_argument('--headless', action='store_true', help='Run headless.')
|
||||
|
||||
|
||||
opts = parser.parse_args()
|
||||
|
||||
|
||||
sections_totals_dict = {}
|
||||
sections_dict = {}
|
||||
user_dict = {}
|
||||
title = "User's Watch Percentage by Library\nFrom: {}"
|
||||
|
||||
|
||||
if opts.plex:
|
||||
admin_account = Plex(PLEX_TOKEN)
|
||||
plex_server = Plex(PLEX_TOKEN, PLEX_URL)
|
||||
title = title.format(plex_server.server.friendlyName)
|
||||
|
||||
|
||||
for library in opts.libraries:
|
||||
section_total = plex_server.all_sections_totals(library)
|
||||
sections_totals_dict[library] = section_total
|
||||
@ -284,7 +286,7 @@ if __name__ == '__main__':
|
||||
section_watched_total = len(section_watched_lst)
|
||||
percent_watched = 100 * (float(section_watched_total) / float(section_total))
|
||||
print(" {} has watched {} items ({}%).".format(user, section_watched_total, int(percent_watched)))
|
||||
|
||||
|
||||
if user_dict.get(user):
|
||||
user_dict[user].update({library: section_watched_total})
|
||||
else:
|
||||
@ -295,7 +297,7 @@ if __name__ == '__main__':
|
||||
user_dict[user].update({library: 0})
|
||||
else:
|
||||
user_dict[user] = {library: 0}
|
||||
|
||||
|
||||
elif opts.tautulli:
|
||||
# Create a Tautulli instance
|
||||
tautulli_server = Tautulli(Connection(url=TAUTULLI_URL.rstrip('/'),
|
||||
@ -307,7 +309,7 @@ if __name__ == '__main__':
|
||||
for section in tautulli_sections:
|
||||
library = Library(section)
|
||||
sections_dict[library.title] = library
|
||||
|
||||
|
||||
for library in opts.libraries:
|
||||
section = sections_dict[library]
|
||||
if section.type == "movie":
|
||||
@ -317,7 +319,7 @@ if __name__ == '__main__':
|
||||
else:
|
||||
print("Not doing that...")
|
||||
section_total = 0
|
||||
|
||||
|
||||
print("Section: {}, has {} items.".format(library, section_total))
|
||||
sections_totals_dict[library] = section_total
|
||||
for user in opts.users:
|
||||
@ -337,7 +339,7 @@ if __name__ == '__main__':
|
||||
elif not all([tt_watched]):
|
||||
break
|
||||
start += count
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(user, e)
|
||||
|
||||
@ -351,4 +353,4 @@ if __name__ == '__main__':
|
||||
user_dict[user] = {library: section_watched_total}
|
||||
|
||||
if opts.pie:
|
||||
make_pie(user_dict, sections_totals_dict, title, opts.filename, opts.headless)
|
||||
make_pie(user_dict, sections_totals_dict, title, opts.filename, opts.headless)
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Pull library and user statistics of last week.
|
||||
|
||||
@ -13,7 +16,7 @@ import requests
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
# import json
|
||||
from operator import itemgetter
|
||||
import argparse
|
||||
|
||||
@ -72,6 +75,7 @@ BODY_TEXT = """\
|
||||
|
||||
# /EDIT THESE SETTINGS #
|
||||
|
||||
|
||||
def get_history(section_id, check_date):
|
||||
# Get the Tautulli history.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -156,7 +160,7 @@ def send_notification(body_text):
|
||||
|
||||
def sizeof_fmt(num, suffix='B'):
|
||||
# Function found https://stackoverflow.com/a/1094933
|
||||
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
|
||||
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
|
||||
if abs(num) < 1024.0:
|
||||
return "%3.1f%s%s" % (num, unit, suffix)
|
||||
num /= 1024.0
|
||||
@ -171,7 +175,7 @@ def date_split(to_split):
|
||||
|
||||
|
||||
def add_to_dictval(d, key, val):
|
||||
#print(d, key, val)
|
||||
# print(d, key, val)
|
||||
if key not in d:
|
||||
d[key] = val
|
||||
else:
|
||||
@ -191,7 +195,7 @@ def get_server_stats(date_ranges):
|
||||
user_stats_lst = []
|
||||
user_stats_dict = {}
|
||||
user_names_lst = []
|
||||
user_durations_lst =[]
|
||||
user_durations_lst = []
|
||||
|
||||
print('Checking library stats.')
|
||||
for sections in get_libraries():
|
||||
@ -290,5 +294,6 @@ def main():
|
||||
print('Sending message.')
|
||||
send_notification(BODY_TEXT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
11
setup.cfg
Normal file
11
setup.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
; Contains configuration for various linters
|
||||
|
||||
; E501: Disable line length limits (for now)
|
||||
; W504: Require newlines after binary operators, use W503 for requiring the
|
||||
; operators on the next line
|
||||
|
||||
[flake8]
|
||||
ignore = E501,W504
|
||||
|
||||
[pylama]
|
||||
ignore = E501,W504
|
@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Use Tautulli to pull last IP address from user and add to List of IP addresses and networks that are allowed without auth in Plex.
|
||||
|
||||
optional arguments:
|
||||
@ -13,14 +15,14 @@ optional arguments:
|
||||
(default: None)
|
||||
|
||||
List of IP addresses is cleared before adding new IPs
|
||||
'''
|
||||
"""
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
PLEX_TOKEN = 'xxxx'
|
||||
PLEX_URL = 'http://localhost:32400'
|
||||
TAUTULLI_APIKEY = 'xxxx' # Your Tautulli API key
|
||||
@ -78,7 +80,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('-u', '--users', nargs='+', type=str, choices=user_lst, metavar='',
|
||||
help='Space separated list of case sensitive names to process. Allowed names are: \n'
|
||||
'(choices: %(choices)s) \n(default: %(default)s)')
|
||||
parser.add_argument('-c', '--clear', nargs='?',default=None, metavar='',
|
||||
parser.add_argument('-c', '--clear', nargs='?', default=None, metavar='',
|
||||
help='Clear List of IP addresses and networks that are allowed without auth in Plex: \n'
|
||||
'(default: %(default)s)')
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
From a list of TV shows, check if users in a list has watched shows episodes.
|
||||
If all users in list have watched an episode of listed show, then delete episode.
|
||||
@ -9,8 +12,7 @@ import requests
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'xxxxx' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
SHOW_LST = [123456, 123456, 123456, 123456] # Show rating keys.
|
||||
@ -109,8 +111,9 @@ for user in USER_LST:
|
||||
|
||||
for meta_dict in meta_lst:
|
||||
if set(USER_LST) == set(meta_dict['watched_by']):
|
||||
print("{} {} has been watched by {}".format(meta_dict['grandparent_title'].encode('UTF-8'),
|
||||
meta_dict['title'].encode('UTF-8'),
|
||||
" & ".join(USER_LST)))
|
||||
print("{} {} has been watched by {}".format(
|
||||
meta_dict['grandparent_title'].encode('UTF-8'),
|
||||
meta_dict['title'].encode('UTF-8'),
|
||||
" & ".join(USER_LST)))
|
||||
print("Removing {}".format(meta_dict['file']))
|
||||
os.remove(meta_dict['file'])
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Description: Enable or disable all users remote access to Tautulli
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Enable or disable all users remote access to Tautulli.
|
||||
|
||||
Author: DirtyCajunRice
|
||||
Requires: requests, python3.6+
|
||||
"""
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Find location of Plex metadata.
|
||||
|
||||
@ -18,7 +20,7 @@ import hashlib
|
||||
import argparse
|
||||
import requests
|
||||
|
||||
## Edit ##
|
||||
# ## Edit ##
|
||||
PLEX_URL = ''
|
||||
PLEX_TOKEN = ''
|
||||
PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL)
|
||||
@ -28,7 +30,7 @@ PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN)
|
||||
PLEX_LOCAL_TV_PATH = os.path.join(os.getenv('LOCALAPPDATA'), 'Plex Media Server\Metadata\TV Shows')
|
||||
PLEX_LOCAL_MOVIE_PATH = os.path.join(os.getenv('LOCALAPPDATA'), 'Plex Media Server\Metadata\Movies')
|
||||
PLEX_LOCAL_ALBUM_PATH = os.path.join(os.getenv('LOCALAPPDATA'), 'Plex Media Server\Metadata\Albums')
|
||||
## /Edit ##
|
||||
# ## /Edit ##
|
||||
|
||||
sess = requests.Session()
|
||||
# Ignore verifying the SSL certificate
|
||||
@ -44,6 +46,7 @@ if sess.verify is False:
|
||||
|
||||
plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
|
||||
|
||||
|
||||
def hash_to_path(hash_str, path, title, media_type, artist=None):
|
||||
full_hash = hashlib.sha1(hash_str).hexdigest()
|
||||
hash_path = '{}\{}{}'.format(full_hash[0], full_hash[1::1], '.bundle')
|
||||
@ -54,12 +57,11 @@ def hash_to_path(hash_str, path, title, media_type, artist=None):
|
||||
output = "{} titled: {}\nPath: {}".format(media_type.title(), title, full_path)
|
||||
print(output)
|
||||
|
||||
|
||||
def get_plex_hash(search, mediatype=None):
|
||||
for searched in plex.search(search, mediatype=mediatype):
|
||||
# Remove special characters from name
|
||||
clean_title = re.sub('\W+',' ', searched.title)
|
||||
if searched.type == 'show':
|
||||
# Need to find guid.
|
||||
if searched.type == 'show':
|
||||
# Get tvdb_if from first episode
|
||||
db_id = searched.episodes()[0].guid
|
||||
# Find str to pop
|
||||
@ -79,7 +81,7 @@ def get_plex_hash(search, mediatype=None):
|
||||
hash_str = 'local://{}'.format(local_id)
|
||||
else:
|
||||
hash_str = searched.tracks()[0].guid.replace('/1?lang=en', '?lang=en')
|
||||
#print(searched.__dict__.items())
|
||||
# print(searched.__dict__.items())
|
||||
hash_to_path(hash_str, PLEX_LOCAL_ALBUM_PATH, searched.title, searched.type, searched.parentTitle)
|
||||
|
||||
elif searched.type == 'artist':
|
||||
@ -89,6 +91,7 @@ def get_plex_hash(search, mediatype=None):
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Helping navigate Plex's locally stored data.")
|
||||
parser.add_argument('-s', '--search', required=True, help='Search Plex for title.')
|
||||
|
@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
Find what was added TFRAME ago and not watched using Tautulli.
|
||||
|
||||
"""
|
||||
|
||||
import requests
|
||||
@ -9,14 +10,14 @@ import sys
|
||||
import time
|
||||
import os
|
||||
|
||||
TFRAME = 1.577e+7 # ~ 6 months in seconds
|
||||
TFRAME = 1.577e+7 # ~ 6 months in seconds
|
||||
TODAY = time.time()
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'XXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
LIBRARY_NAMES = ['My TV Shows', 'My Movies'] # Name of libraries you want to check.
|
||||
LIBRARY_NAMES = ['My TV Shows', 'My Movies'] # Name of libraries you want to check.
|
||||
|
||||
|
||||
class LIBINFO(object):
|
||||
@ -81,7 +82,7 @@ def get_metadata(rating_key):
|
||||
res_data = response['response']['data']
|
||||
return METAINFO(data=res_data)
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# sys.stderr.write("Tautulli API 'get_metadata' request failed: {0}.".format(e))
|
||||
pass
|
||||
|
||||
@ -103,6 +104,7 @@ def get_library_media_info(section_id):
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_library_media_info' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_libraries_table():
|
||||
# Get the data on the Tautulli libraries table.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
@ -117,7 +119,8 @@ def get_libraries_table():
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_libraries_table' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
|
||||
def delete_files(tmp_lst):
|
||||
del_file = raw_input('Delete all unwatched files? (yes/no)').lower()
|
||||
if del_file.startswith('y'):
|
||||
@ -127,6 +130,7 @@ def delete_files(tmp_lst):
|
||||
else:
|
||||
print('Ok. doing nothing.')
|
||||
|
||||
|
||||
show_lst = []
|
||||
path_lst = []
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Description: Get a list of "Serial Transcoders"
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Get a list of "Serial Transcoders".
|
||||
|
||||
Author: DirtyCajunRice
|
||||
Requires: requests, plexapi, python3.6+
|
||||
"""
|
||||
|
@ -1,8 +1,9 @@
|
||||
# -*- encoding: UTF-8 -*-
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
'''
|
||||
"""
|
||||
https://gist.github.com/blacktwin/f435aa0ccd498b0840d2407d599bf31d
|
||||
'''
|
||||
"""
|
||||
|
||||
import os
|
||||
import httplib2
|
||||
@ -12,15 +13,11 @@ from oauth2client.file import Storage
|
||||
from googleapiclient.discovery import build
|
||||
from oauth2client.client import OAuth2WebServerFlow
|
||||
|
||||
import time, shutil, sys
|
||||
|
||||
# Copy your credentials from the console
|
||||
# https://console.developers.google.com
|
||||
CLIENT_ID = ''
|
||||
CLIENT_SECRET = ''
|
||||
OUT_PATH = '' # Output Path
|
||||
|
||||
|
||||
OUT_PATH = '' # Output Path
|
||||
|
||||
OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
|
||||
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
@ -48,6 +45,7 @@ http = credentials.authorize(http)
|
||||
|
||||
drive_service = build('drive', 'v2', http=http)
|
||||
|
||||
|
||||
def list_files(service):
|
||||
page_token = None
|
||||
while True:
|
||||
@ -90,11 +88,11 @@ for item in list_files(drive_service):
|
||||
if 'mimeType' in item and 'image/jpeg' in item['mimeType'] or 'video/mp4' in item['mimeType']:
|
||||
download_url = item['downloadUrl']
|
||||
else:
|
||||
print 'ERROR getting %s' % item.get('title')
|
||||
print item
|
||||
print dir(item)
|
||||
print('ERROR getting %s' % item.get('title'))
|
||||
print(item)
|
||||
print(dir(item))
|
||||
if download_url:
|
||||
print "downloading %s" % item.get('title')
|
||||
print("downloading %s" % item.get('title'))
|
||||
resp, content = drive_service._http.request(download_url)
|
||||
if resp.status == 200:
|
||||
if os.path.isfile(outfile):
|
||||
|
@ -1,13 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Description: Removes Shows from On Deck
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Removes Shows from On Deck.
|
||||
|
||||
Author: Blacktwin
|
||||
Requires: requests, plexapi
|
||||
|
||||
Example:
|
||||
python off_deck.py --action deck --user Steve
|
||||
- Display what shows are on Steve's On Deck
|
||||
|
||||
|
||||
python off_deck.py --action deck --shows "The Simpsons" Seinfeld
|
||||
- The Simpsons and Seinfeld will be removed from On Deck
|
||||
|
||||
@ -17,10 +19,10 @@ Requires: requests, plexapi
|
||||
python off_deck.py --action deck --playlist "Favorite Shows!"
|
||||
- Any Show found in Favorite Shows playlist will be remove
|
||||
from On Deck
|
||||
|
||||
|
||||
python off_deck.py --action watch --user Steve
|
||||
- Display what shows are on Steve's Continue Watching
|
||||
|
||||
|
||||
python off_deck.py --action watch --shows "The Simpsons" Seinfeld
|
||||
- The Simpsons and Seinfeld will be removed from Continue Watching
|
||||
|
||||
@ -99,9 +101,9 @@ def get_con_watch(server, off_deck=None):
|
||||
if item.grandparentTitle in off_deck:
|
||||
print('Removing {}: S{:02}E{:02} {} from Continue Watching '
|
||||
'by marking watched.'.format(
|
||||
item.grandparentTitle.encode('UTF-8'),
|
||||
int(item.parentIndex), int(item.index),
|
||||
item.title.encode('UTF-8')))
|
||||
item.grandparentTitle.encode('UTF-8'),
|
||||
int(item.parentIndex), int(item.index),
|
||||
item.title.encode('UTF-8')))
|
||||
item.markWatched()
|
||||
else:
|
||||
if item.type == 'episode' and item.viewOffset > 0:
|
||||
@ -115,7 +117,7 @@ def get_con_watch(server, off_deck=None):
|
||||
item.grandparentTitle.encode('UTF-8'),
|
||||
int(item.parentIndex), int(item.index),
|
||||
item.title.encode('UTF-8'), offset))
|
||||
|
||||
|
||||
|
||||
def get_on_deck(server, off_deck=None):
|
||||
"""
|
||||
@ -142,12 +144,13 @@ def get_on_deck(server, off_deck=None):
|
||||
watched_statuses['grandparent'] = grandparent
|
||||
watched_statuses['episodes'] = []
|
||||
for episode in grandparent.episodes():
|
||||
watched_statuses['episodes'].append({'object': episode,
|
||||
'viewCount': episode.viewCount})
|
||||
watched_statuses['episodes'].append({
|
||||
'object': episode,
|
||||
'viewCount': episode.viewCount})
|
||||
else:
|
||||
if item.type == 'episode':
|
||||
on_deck.append(item)
|
||||
|
||||
|
||||
if on_deck:
|
||||
watched_statuses['on_deck'] = on_deck
|
||||
return watched_statuses
|
||||
@ -204,7 +207,7 @@ if __name__ == '__main__':
|
||||
off_deck['grandparent'].markUnwatched()
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
print('Resetting watch counts...')
|
||||
for item in ep_list:
|
||||
print('Resetting view count for {}: S{:02}E{:02} {}'.format(
|
||||
@ -214,7 +217,7 @@ if __name__ == '__main__':
|
||||
# if viewCount was 0 then make 1 so as not to return to On Deck.
|
||||
for _ in range(item['viewCount'] if item['viewCount'] != 0 else 1):
|
||||
item['object'].markWatched()
|
||||
|
||||
|
||||
elif opts.action == 'watch':
|
||||
print('Finding shows marked Continue Watching...')
|
||||
get_con_watch(plex_server, to_remove)
|
||||
get_con_watch(plex_server, to_remove)
|
||||
|
@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Invite new users to share Plex libraries.
|
||||
|
||||
optional arguments:
|
||||
@ -33,7 +35,7 @@ Usage:
|
||||
plex_api_invite.py --libraries Movies --user USER --movieRatings G, PG-13
|
||||
- Share Movie library with USER but restrict them to only G and PG-13 titles.
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
from plexapi.server import PlexServer, CONFIG
|
||||
import argparse
|
||||
@ -71,11 +73,11 @@ def invite(user, sections, allowSync, camera, channels, filterMovies, filterTele
|
||||
allowCameraUpload=camera, allowChannels=channels, filterMovies=filterMovies,
|
||||
filterTelevision=filterTelevision, filterMusic=filterMusic)
|
||||
print('Invited {user} to share libraries: \n{sections}'.format(sections=sections, user=user))
|
||||
if allowSync == True:
|
||||
if allowSync is True:
|
||||
print('Sync: Enabled')
|
||||
if camera == True:
|
||||
if camera is True:
|
||||
print('Camera Upload: Enabled')
|
||||
if channels == True:
|
||||
if channels is True:
|
||||
print('Plugins: Enabled')
|
||||
if filterMovies:
|
||||
print('Movie Filters: {}'.format(filterMovies))
|
||||
@ -161,4 +163,4 @@ if __name__ == "__main__":
|
||||
|
||||
for user in opts.user:
|
||||
invite(user, libraries, sync, camera, channels,
|
||||
filterMovies, filterTelevision, filterMusic)
|
||||
filterMovies, filterTelevision, filterMusic)
|
||||
|
@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Set as cron or task for times of allowing and not allowing user access to server.
|
||||
Unsharing will kill any current stream from user before unsharing.
|
||||
|
||||
@ -33,7 +35,7 @@ Usage:
|
||||
- Unshared all libraries with USER.
|
||||
- USER is still exists as a Friend or Home User
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
import argparse
|
||||
|
@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Description: Pull Movie and TV Show poster images from Plex. Save to Movie and TV Show directories in scripts working directory.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Pull Movie and TV Show poster images from Plex.
|
||||
|
||||
Saves the poster images to Movie and TV Show directories in scripts working
|
||||
directory.
|
||||
|
||||
Author: Blacktwin
|
||||
Requires: plexapi
|
||||
|
||||
@ -15,7 +20,7 @@ import re
|
||||
import os
|
||||
import urllib
|
||||
|
||||
library_name = ['Movies','TV Shows'] # You library names
|
||||
library_name = ['Movies', 'TV Shows'] # Your library names
|
||||
|
||||
PLEX_URL = ''
|
||||
PLEX_TOKEN = ''
|
||||
@ -50,7 +55,7 @@ if not os.path.isdir(show_path):
|
||||
for library in library_name:
|
||||
for child in plex.library.section(library).all():
|
||||
# Clean names of special characters
|
||||
name = re.sub('\W+',' ', child.title)
|
||||
name = re.sub('\W+', ' ', child.title)
|
||||
# Add (year) to name
|
||||
name = '{} ({})'.format(name, child.year)
|
||||
# Pull URL for poster
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Share or unshare libraries.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Share or unshare libraries.
|
||||
|
||||
optional arguments:
|
||||
-h, --help Show this help message and exit
|
||||
@ -65,7 +66,7 @@ Usage:
|
||||
|
||||
plex_api_share.py --backup
|
||||
- Backup all user shares to a json file
|
||||
|
||||
|
||||
plex_api_share.py --backup --user USER
|
||||
- Backup USER shares to a json file
|
||||
|
||||
@ -96,7 +97,7 @@ Usage:
|
||||
plex_api_share.py --share -u USER --allLibraries --libraries Movies
|
||||
- Shared [all libraries but Movies] with USER.
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
from plexapi.server import PlexServer, CONFIG
|
||||
import time
|
||||
@ -137,13 +138,14 @@ movies_keys = [x.key for x in plex.library.sections() if x.type == 'movie']
|
||||
show_keys = [x.key for x in plex.library.sections() if x.type == 'show']
|
||||
|
||||
json_check = sorted([f for f in os.listdir('.') if os.path.isfile(f) and
|
||||
f.endswith(".json") and f.startswith(plex.friendlyName)],
|
||||
f.endswith(".json") and
|
||||
f.startswith(plex.friendlyName)],
|
||||
key=os.path.getmtime)
|
||||
|
||||
my_server_names = []
|
||||
# Find all owners server names. For owners with multiple servers.
|
||||
for res in plex.myPlexAccount().resources():
|
||||
if res.provides == 'server' and res.owned == True:
|
||||
if res.provides == 'server' and res.owned is True:
|
||||
my_server_names.append(res.name)
|
||||
|
||||
|
||||
@ -151,7 +153,7 @@ def get_ratings_lst(section_id):
|
||||
headers = {'Accept': 'application/json'}
|
||||
params = {'X-Plex-Token': PLEX_TOKEN}
|
||||
content = sess.get("{}/library/sections/{}/contentRating".format(PLEX_URL, section_id),
|
||||
headers=headers, params=params)
|
||||
headers=headers, params=params)
|
||||
|
||||
ratings_keys = content.json()['MediaContainer']['Directory']
|
||||
ratings_lst = [x['title'] for x in ratings_keys]
|
||||
@ -166,7 +168,7 @@ def filter_clean(filter_type):
|
||||
labels = v.replace('%20', ' ')
|
||||
labels = labels.split('%2C')
|
||||
clean[k] = labels
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
return clean
|
||||
|
||||
@ -193,7 +195,7 @@ def find_shares(user):
|
||||
if server.name == plex.friendlyName:
|
||||
sections = []
|
||||
for section in server.sections():
|
||||
if section.shared == True:
|
||||
if section.shared is True:
|
||||
sections.append(section.title)
|
||||
user_backup['sections'] = sections
|
||||
|
||||
@ -219,17 +221,17 @@ def share(user, sections, allowSync, camera, channels, filterMovies, filterTelev
|
||||
filterTelevision=filterTelevision, filterMusic=filterMusic)
|
||||
if sections:
|
||||
print('{user}\'s updated shared libraries: \n{sections}'.format(sections=sections, user=user))
|
||||
if allowSync == True:
|
||||
if allowSync is True:
|
||||
print('Sync: Enabled')
|
||||
if allowSync == False:
|
||||
if allowSync is False:
|
||||
print('Sync: Disabled')
|
||||
if camera == True:
|
||||
if camera is True:
|
||||
print('Camera Upload: Enabled')
|
||||
if camera == False:
|
||||
if camera is False:
|
||||
print('Camera Upload: Disabled')
|
||||
if channels == True:
|
||||
if channels is True:
|
||||
print('Plugins: Enabled')
|
||||
if channels == False:
|
||||
if channels is False:
|
||||
print('Plugins: Disabled')
|
||||
if filterMovies:
|
||||
print('Movie Filters: {}'.format(filterMovies))
|
||||
@ -241,7 +243,7 @@ def share(user, sections, allowSync, camera, channels, filterMovies, filterTelev
|
||||
print('Show Filters:')
|
||||
if filterMusic:
|
||||
print('Music Filters: {}'.format(filterMusic))
|
||||
if filterMusic == {} and filterMusic != None:
|
||||
if filterMusic == {} and filterMusic is not None:
|
||||
print('Music Filters:')
|
||||
|
||||
|
||||
@ -286,7 +288,7 @@ if __name__ == "__main__":
|
||||
help='Show all shares by library.')
|
||||
|
||||
# For Plex Pass members
|
||||
if plex.myPlexSubscription == True:
|
||||
if plex.myPlexSubscription is True:
|
||||
movie_ratings = []
|
||||
show_ratings = []
|
||||
for movie in movies_keys:
|
||||
@ -371,7 +373,7 @@ if __name__ == "__main__":
|
||||
for k, v in user_lst.items():
|
||||
if v == user:
|
||||
del user_lst[k]
|
||||
|
||||
|
||||
users = user_lst.keys()
|
||||
|
||||
# Defining libraries
|
||||
@ -384,7 +386,7 @@ if __name__ == "__main__":
|
||||
for library in opts.libraries:
|
||||
sections_lst.remove(library)
|
||||
libraries = sections_lst
|
||||
|
||||
|
||||
if opts.libraryShares:
|
||||
users = user_lst.keys()
|
||||
user_sections = {}
|
||||
@ -396,11 +398,10 @@ if __name__ == "__main__":
|
||||
for user, sections in user_sections.items():
|
||||
for section in sections:
|
||||
section_users.setdefault(section, []).append(user)
|
||||
|
||||
|
||||
for section, users in section_users.items():
|
||||
print("{} is shared to the following users:\n {}\n".format(section, ", ".join(users)))
|
||||
|
||||
|
||||
print("{} is shared to the following users:\n {}\n".format(section, ", ".join(users)))
|
||||
|
||||
# Share, Unshare, Kill, Add, or Remove
|
||||
for user in users:
|
||||
user_shares = find_shares(user)
|
||||
@ -485,4 +486,4 @@ if __name__ == "__main__":
|
||||
print('Restoring user {}\'s shares and settings...'.format(user['title']))
|
||||
share(user['title'], user['sections'], user['allowSync'], user['camera'],
|
||||
user['channels'], user['filterMovies'], user['filterTelevision'],
|
||||
user['filterMusic'])
|
||||
user['filterMusic'])
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Change show deletion settings by library.
|
||||
|
||||
@ -33,7 +35,7 @@ PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN)
|
||||
|
||||
# Allowed days/episodes to keep or delete
|
||||
WATCHED_LST = [0, 1, 7]
|
||||
UNWATCHED_LST = [0, 5, 3, 1, -3, -7,-30]
|
||||
UNWATCHED_LST = [0, 5, 3, 1, -3, -7, -30]
|
||||
|
||||
sess = requests.Session()
|
||||
# Ignore verifying the SSL certificate
|
||||
@ -57,10 +59,10 @@ def set_show(rating_key, action, number):
|
||||
path = '{}/prefs'.format(rating_key)
|
||||
try:
|
||||
params = {'X-Plex-Token': PLEX_TOKEN,
|
||||
action: number
|
||||
}
|
||||
action: number
|
||||
}
|
||||
|
||||
r = requests.put(PLEX_URL + path, params=params, verify=False)
|
||||
r = requests.put(PLEX_URL + path, params=params, verify=False)
|
||||
print(r.url)
|
||||
except Exception as e:
|
||||
print('Error: {}'.format(e))
|
||||
|
@ -1,27 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Pull poster images from Imgur and places them inside Shows root folder.
|
||||
/path/to/show/Show.jpg
|
||||
|
||||
|
||||
Skips download if showname.jpg exists or if show does not exist.
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
import requests
|
||||
import urllib
|
||||
import os
|
||||
|
||||
|
||||
## Edit ##
|
||||
# ## Edit ##
|
||||
|
||||
# Imgur info
|
||||
CLIENT_ID = 'xxxxx' # Tautulli Settings > Notifications > Imgur Client ID
|
||||
ALBUM_ID = '7JeSw' # http://imgur.com/a/7JeSw <--- 7JeSw is the ablum_id
|
||||
CLIENT_ID = 'xxxxx' # Tautulli Settings > Notifications > Imgur Client ID
|
||||
ALBUM_ID = '7JeSw' # http://imgur.com/a/7JeSw <--- 7JeSw is the ablum_id
|
||||
|
||||
# Local info
|
||||
SHOW_PATH = 'D:\\Shows\\'
|
||||
|
||||
## /Edit ##
|
||||
# ## /Edit ##
|
||||
|
||||
|
||||
class IMGURINFO(object):
|
||||
def __init__(self, data=None):
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Build playlist from popular tracks.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Build playlist from popular tracks.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@ -14,7 +15,7 @@ optional arguments:
|
||||
|
||||
* LIBRARY_EXCLUDE are excluded from libraries choice.
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
import requests
|
||||
@ -59,8 +60,8 @@ def fetch(path):
|
||||
|
||||
header = {'Accept': 'application/json'}
|
||||
params = {'X-Plex-Token': PLEX_TOKEN,
|
||||
'includePopularLeaves': '1'
|
||||
}
|
||||
'includePopularLeaves': '1'
|
||||
}
|
||||
|
||||
r = requests.get(url + path, headers=header, params=params, verify=False)
|
||||
return r.json()['MediaContainer']['Metadata'][0]['PopularLeaves']['Metadata']
|
||||
@ -97,7 +98,7 @@ if __name__ == "__main__":
|
||||
|
||||
parser.add_argument('--tracks', nargs='?', default=False, type=int, metavar='',
|
||||
help='Specify the track length you would like the playlist.')
|
||||
parser.add_argument('--random',nargs='?', default=False, type=int, metavar='',
|
||||
parser.add_argument('--random', nargs='?', default=False, type=int, metavar='',
|
||||
help='Randomly select N artists.')
|
||||
|
||||
opts = parser.parse_args()
|
||||
@ -128,7 +129,7 @@ if __name__ == "__main__":
|
||||
|
||||
if opts.tracks and opts.random:
|
||||
playlist = random.sample((playlist), opts.tracks)
|
||||
|
||||
|
||||
elif opts.tracks and not opts.random:
|
||||
playlist = playlist[:opts.tracks]
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Download theme songs from Plex TV Shows. Theme songs are mp3 and named by shows as displayed by Plex.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Download theme songs from Plex TV Shows.
|
||||
|
||||
Theme songs are mp3 and named by shows as displayed by Plex.
|
||||
Songs are saved in a 'Theme Songs' directory located in script's path.
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
from plexapi.server import PlexServer, CONFIG
|
||||
@ -13,14 +16,14 @@ import re
|
||||
import urllib
|
||||
import requests
|
||||
|
||||
## Edit ##
|
||||
# ## Edit ##
|
||||
PLEX_URL = ''
|
||||
PLEX_TOKEN = ''
|
||||
PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL)
|
||||
PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN)
|
||||
|
||||
TV_LIBRARY = 'TV Shows' # Name of your TV Show library
|
||||
## /Edit ##
|
||||
TV_LIBRARY = 'TV Shows' # Name of your TV Show library
|
||||
# ## /Edit ##
|
||||
|
||||
sess = requests.Session()
|
||||
# Ignore verifying the SSL certificate
|
||||
@ -47,7 +50,7 @@ if not os.path.isdir(out_path):
|
||||
# Get episodes from TV Shows
|
||||
for show in plex.library.section(TV_LIBRARY).all():
|
||||
# Remove special characters from name
|
||||
filename = '{}.mp3'.format(re.sub('\W+',' ', show.title))
|
||||
filename = '{}.mp3'.format(re.sub('\W+', ' ', show.title))
|
||||
# Set output path
|
||||
theme_path = os.path.join(out_path, filename)
|
||||
# Get tvdb_if from first episode, no need to go through all episodes
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""
|
||||
Delete all playlists from Plex using PlexAPI
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Delete all playlists from Plex.
|
||||
|
||||
|
||||
"""
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Description: Purge Tautulli users that no longer exist as a friend in Plex
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Purge Tautulli users that no longer exist as a friend in Plex.
|
||||
|
||||
Author: DirtyCajunRice
|
||||
Requires: requests, plexapi, python3.6+
|
||||
"""
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Refresh the next episode of show once current episode is watched.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Refresh the next episode of show once current episode is watched.
|
||||
|
||||
Check Tautulli's Watched Percent in Tautulli > Settings > General
|
||||
|
||||
1. Tautulli > Settings > Notification Agents > Scripts > Bell icon:
|
||||
@ -12,7 +14,7 @@ Check Tautulli's Watched Percent in Tautulli > Settings > General
|
||||
3. Tautulli > Settings > Notifications > Script > Script Arguments:
|
||||
{show_name} {episode_num00} {season_num00}
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
import requests
|
||||
import sys
|
||||
@ -42,7 +44,7 @@ plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
|
||||
show_name = sys.argv[1]
|
||||
next_ep_num = int(sys.argv[2])
|
||||
season_num = int(sys.argv[3])
|
||||
TV_LIBRARY = 'My TV Shows' # Name of your TV Shows library
|
||||
TV_LIBRARY = 'My TV Shows' # Name of your TV Shows library
|
||||
|
||||
current_season = season_num - 1
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Unshare or Remove users who have been inactive for X days. Prints out last seen for all users.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Unshare or Remove users who have been inactive for X days. Prints out last seen for all users.
|
||||
|
||||
Just run.
|
||||
|
||||
@ -114,6 +115,3 @@ for user in TAUTULLI_USERS:
|
||||
else:
|
||||
print('{}, and has reached their inactivity limit. Unsharing.'.format(OUTPUT))
|
||||
ACCOUNT.updateFriend(PLEX_USERS[UID], SERVER, removeSections=True)
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""
|
||||
Find Movies that have been watched by a list of users.
|
||||
If all users have watched movie than delete.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Find and delete Movies that have been watched by a list of users.
|
||||
|
||||
Deletion is prompted
|
||||
"""
|
||||
@ -11,11 +12,12 @@ import os
|
||||
import shutil
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ##
|
||||
# ## EDIT THESE SETTINGS ##
|
||||
TAUTULLI_APIKEY = 'xxxxxxxx' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
LIBRARY_NAMES = ['My Movies'] # Whatever your movie libraries are called.
|
||||
USER_LST = ['Joe', 'Alex'] # Name of users
|
||||
LIBRARY_NAMES = ['My Movies'] # Whatever your movie libraries are called.
|
||||
USER_LST = ['Joe', 'Alex'] # Name of users
|
||||
|
||||
|
||||
class UserHIS(object):
|
||||
def __init__(self, data=None):
|
||||
@ -81,6 +83,7 @@ def delete_files(tmp_lst):
|
||||
else:
|
||||
print('Ok. doing nothing.')
|
||||
|
||||
|
||||
movie_dict = {}
|
||||
movie_lst = []
|
||||
delete_lst = []
|
||||
@ -119,7 +122,7 @@ for user in USER_LST:
|
||||
|
||||
for movie_dict in movie_lst:
|
||||
if set(USER_LST) == set(movie_dict['watched_by']):
|
||||
print(u"{} has been watched by {}".format(movie_dict['title']," & ".join(USER_LST)))
|
||||
print(u"{} has been watched by {}".format(movie_dict['title'], " & ".join(USER_LST)))
|
||||
delete_lst.append(movie_dict['file'])
|
||||
|
||||
delete_files(delete_lst)
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Share functions from https://gist.github.com/JonnyWong16/f8139216e2748cb367558070c1448636
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Share functions from https://gist.github.com/JonnyWong16/f8139216e2748cb367558070c1448636
|
||||
|
||||
Once user stream count hits LIMIT they are unshared from libraries expect for banned library.
|
||||
Once user stream count is below LIMIT and banned library video is watched their shares are restored.
|
||||
@ -30,7 +31,6 @@ Tautulli will continue displaying that user is watching after unshare is execute
|
||||
Tautulli will update after ~5 minutes and no longer display user's stream in ACTIVITY.
|
||||
Tautulli will think that user has stopped.
|
||||
|
||||
|
||||
Create new library with one video.
|
||||
Name library and video whatever you want.
|
||||
|
||||
@ -59,7 +59,7 @@ import email.utils
|
||||
import smtplib
|
||||
|
||||
|
||||
## EDIT THESE SETTINGS ###
|
||||
# ## EDIT THESE SETTINGS ###
|
||||
|
||||
TAUTULLI_APIKEY = 'XXXXXX' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
@ -74,8 +74,8 @@ SERVER_ID = "XXXXX" # Example: https://i.imgur.com/EjaMTUk.png
|
||||
# UserID2: [LibraryID1, LibraryID2]}
|
||||
|
||||
USER_LIBRARIES = {123456: [123456, 123456, 123456, 123456, 123456, 123456]}
|
||||
BAN_LIBRARY = {123456: [123456]} # {UserID1: [LibraryID1], UserID2: [LibraryID1]}
|
||||
BAN_RATING = 123456 # Banned rating_key to check if it's been watched.
|
||||
BAN_LIBRARY = {123456: [123456]} # {UserID1: [LibraryID1], UserID2: [LibraryID1]}
|
||||
BAN_RATING = 123456 # Banned rating_key to check if it's been watched.
|
||||
|
||||
LIMIT = 3
|
||||
VIOLATION_LIMIT = 3
|
||||
@ -99,15 +99,15 @@ BODY_TEXT = """\
|
||||
"""
|
||||
|
||||
# Email settings
|
||||
name = '' # Your name
|
||||
sender = '' # From email address
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
name = '' # Your name
|
||||
sender = '' # From email address
|
||||
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
|
||||
email_port = 587 # Email port (Gmail: 587)
|
||||
email_username = '' # Your email username
|
||||
email_password = '' # Your email password
|
||||
email_username = '' # Your email username
|
||||
email_password = '' # Your email password
|
||||
|
||||
|
||||
## DO NOT EDIT BELOW ##
|
||||
# ## DO NOT EDIT BELOW ##
|
||||
|
||||
class Activity(object):
|
||||
def __init__(self, data=None):
|
||||
@ -216,7 +216,7 @@ def unshare(user_id):
|
||||
return
|
||||
|
||||
elif r.status_code == 400:
|
||||
print r.content
|
||||
print(r.content)
|
||||
return
|
||||
|
||||
elif r.status_code == 200:
|
||||
@ -256,6 +256,7 @@ def get_activity():
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_activity' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def send_notification(to=None, friendly=None, val_cnt=None, val_tot=None, mess=None):
|
||||
# Format notification text
|
||||
try:
|
||||
@ -295,9 +296,9 @@ if __name__ == "__main__":
|
||||
try:
|
||||
if act_lst.count(user) >= LIMIT:
|
||||
# Trigger for first and next violation
|
||||
unshare(user) # Remove libraries
|
||||
share(user, BAN) # Share banned library
|
||||
sys.stdout.write("Shared BAN_LIBRARY with user {0}".format(i))
|
||||
unshare(user) # Remove libraries
|
||||
share(user, BAN) # Share banned library
|
||||
sys.stdout.write("Shared BAN_LIBRARY with user {0}".format(user))
|
||||
if type(history) is int:
|
||||
# Next violation, history of banned video.
|
||||
send_notification(mail_add, friendly, history, VIOLATION_LIMIT, FIRST_WARN)
|
||||
@ -308,10 +309,10 @@ if __name__ == "__main__":
|
||||
elif type(history) is int:
|
||||
# Trigger to share
|
||||
if share(user, UNBAN) == 400:
|
||||
exit() # User has history of watching banned video but libraries are already restored.
|
||||
exit() # User has history of watching banned video but libraries are already restored.
|
||||
else:
|
||||
unshare(user) # Remove banned library
|
||||
share(user, UNBAN) # Restore libraries
|
||||
unshare(user) # Remove banned library
|
||||
share(user, UNBAN) # Restore libraries
|
||||
elif history == 'ban':
|
||||
# Trigger for ban
|
||||
unshare(user)
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Description: Sync the watch status from one Plex or Tautulli user to other users across any owned server.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Sync the watch status from one Plex or Tautulli user to other users across any owned server.
|
||||
|
||||
Author: Blacktwin
|
||||
Requires: requests, plexapi, argparse
|
||||
|
||||
@ -87,7 +89,7 @@ class Connection:
|
||||
pool_block=True)
|
||||
self.session.mount('http://', self.adapters)
|
||||
self.session.mount('https://', self.adapters)
|
||||
|
||||
|
||||
# Ignore verifying the SSL certificate
|
||||
if verify_ssl is False:
|
||||
self.session.verify = False
|
||||
@ -115,7 +117,7 @@ class Metadata(object):
|
||||
self.title = ep_name.lstrip()
|
||||
else:
|
||||
self.title = d['full_title']
|
||||
|
||||
|
||||
# For History
|
||||
try:
|
||||
if d['watched_status']:
|
||||
@ -133,32 +135,32 @@ class Metadata(object):
|
||||
class Tautulli:
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
|
||||
def _call_api(self, cmd, payload, method='GET'):
|
||||
payload['cmd'] = cmd
|
||||
payload['apikey'] = self.connection.apikey
|
||||
|
||||
|
||||
try:
|
||||
response = self.connection.session.request(method, self.connection.url + '/api/v2', params=payload)
|
||||
except RequestException as e:
|
||||
print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e))
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
except ValueError:
|
||||
print("Failed to parse json response for Tautulli API cmd '{}'".format(cmd))
|
||||
return
|
||||
|
||||
|
||||
if response_json['response']['result'] == 'success':
|
||||
return response_json['response']['data']
|
||||
else:
|
||||
error_msg = response_json['response']['message']
|
||||
print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg))
|
||||
return
|
||||
|
||||
|
||||
def get_watched_history(self, user=None, section_id=None, rating_key=None, start=None, length=None):
|
||||
"""Call Tautulli's get_history api endpoint"""
|
||||
"""Call Tautulli's get_history api endpoint."""
|
||||
payload = {"order_column": "full_title",
|
||||
"order_dir": "asc"}
|
||||
if user:
|
||||
@ -171,20 +173,18 @@ class Tautulli:
|
||||
payload["start"] = start
|
||||
if length:
|
||||
payload["lengh"] = length
|
||||
|
||||
|
||||
history = self._call_api('get_history', payload)
|
||||
|
||||
|
||||
return [d for d in history['data'] if d['watched_status'] == 1]
|
||||
|
||||
|
||||
def get_metadata(self, rating_key):
|
||||
"""Call Tautulli's get_metadata api endpoint"""
|
||||
|
||||
"""Call Tautulli's get_metadata api endpoint."""
|
||||
payload = {"rating_key": rating_key}
|
||||
return self._call_api('get_metadata', payload)
|
||||
|
||||
|
||||
def get_libraries(self):
|
||||
"""Call Tautulli's get_libraries api endpoint"""
|
||||
|
||||
"""Call Tautulli's get_libraries api endpoint."""
|
||||
payload = {}
|
||||
return self._call_api('get_libraries', payload)
|
||||
|
||||
@ -196,37 +196,43 @@ class Plex:
|
||||
if token and url:
|
||||
session = Connection().session
|
||||
self.server = PlexServer(baseurl=url, token=token, session=session)
|
||||
|
||||
|
||||
def admin_servers(self):
|
||||
"""All owned servers
|
||||
"""Get all owned servers.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: dict
|
||||
|
||||
"""
|
||||
resources = {}
|
||||
for resource in self.account.resources():
|
||||
if 'server' in [resource.provides] and resource.owned == True:
|
||||
if 'server' in [resource.provides] and resource.owned is True:
|
||||
resources[resource.name] = resource
|
||||
|
||||
|
||||
return resources
|
||||
|
||||
|
||||
def all_users(self):
|
||||
"""All users
|
||||
"""Get all users.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: dict
|
||||
|
||||
"""
|
||||
users = {self.account.title: self.account}
|
||||
for user in self.account.users():
|
||||
users[user.title] = user
|
||||
|
||||
|
||||
return users
|
||||
|
||||
|
||||
def all_sections(self):
|
||||
"""All sections from all owned servers
|
||||
"""Get all sections from all owned servers.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: dict
|
||||
|
||||
"""
|
||||
data = {}
|
||||
servers = self.admin_servers()
|
||||
@ -235,21 +241,23 @@ class Plex:
|
||||
connect = server.connect()
|
||||
sections = {section.title: section for section in connect.library.sections()}
|
||||
data[name] = sections
|
||||
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def users_access(self):
|
||||
"""Users access across all owned servers
|
||||
"""Get users access across all owned servers.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: dict
|
||||
|
||||
"""
|
||||
all_users = self.all_users().values()
|
||||
admin_servers = self.admin_servers()
|
||||
all_sections = self.all_sections()
|
||||
|
||||
|
||||
data = {self.account.title: {"account": self.account}}
|
||||
|
||||
|
||||
for user in all_users:
|
||||
if not data.get(user.title):
|
||||
servers = []
|
||||
@ -257,7 +265,7 @@ class Plex:
|
||||
if admin_servers.get(server.name):
|
||||
access = {}
|
||||
sections = {section.title: section for section in server.sections()
|
||||
if section.shared == True}
|
||||
if section.shared is True}
|
||||
access['server'] = {server.name: admin_servers.get(server.name)}
|
||||
access['sections'] = sections
|
||||
servers += [access]
|
||||
@ -278,7 +286,8 @@ class Plex:
|
||||
|
||||
|
||||
def connect_to_server(server_obj, user_account):
|
||||
"""Find server url and connect using user token
|
||||
"""Find server url and connect using user token.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
server_obj: class
|
||||
@ -287,10 +296,11 @@ def connect_to_server(server_obj, user_account):
|
||||
Returns
|
||||
-------
|
||||
user_connection.server: class
|
||||
|
||||
"""
|
||||
server_name = server_obj.name
|
||||
user = user_account.title
|
||||
|
||||
|
||||
print('Connecting {} to {}...'.format(user, server_name))
|
||||
server_connection = server_obj.connect()
|
||||
baseurl = server_connection._baseurl.split('.')
|
||||
@ -300,14 +310,15 @@ def connect_to_server(server_obj, user_account):
|
||||
token = PLEX_TOKEN
|
||||
else:
|
||||
token = user_account.get_token(server_connection.machineIdentifier)
|
||||
|
||||
|
||||
user_connection = Plex(url=url, token=token)
|
||||
|
||||
|
||||
return user_connection.server
|
||||
|
||||
|
||||
def check_users_access(access, user, server_name, libraries=None):
|
||||
"""Check user's access to server. If allowed connect.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
access: dict
|
||||
@ -318,6 +329,7 @@ def check_users_access(access, user, server_name, libraries=None):
|
||||
Returns
|
||||
-------
|
||||
server_connection: class
|
||||
|
||||
"""
|
||||
try:
|
||||
_user = access.get(user)
|
||||
@ -333,7 +345,7 @@ def check_users_access(access, user, server_name, libraries=None):
|
||||
if library_check:
|
||||
server_connection = connect_to_server(server_obj, _user['account'])
|
||||
return server_connection
|
||||
|
||||
|
||||
elif not library_check:
|
||||
print("User does not have access to this library.")
|
||||
# Not syncing by libraries
|
||||
@ -349,7 +361,8 @@ def check_users_access(access, user, server_name, libraries=None):
|
||||
|
||||
|
||||
def sync_watch_status(watched, section, accountTo, userTo, same_server=False):
|
||||
"""
|
||||
"""Sync watched status between two users.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
watched: list
|
||||
@ -362,6 +375,7 @@ def sync_watch_status(watched, section, accountTo, userTo, same_server=False):
|
||||
User's server class of sync to user
|
||||
same_server: bool
|
||||
Are serverFrom and serverTo the same
|
||||
|
||||
"""
|
||||
print('Marking watched...')
|
||||
sectionTo = accountTo.library.section(section)
|
||||
@ -387,7 +401,7 @@ def sync_watch_status(watched, section, accountTo, userTo, same_server=False):
|
||||
fetch_check.markWatched()
|
||||
title = fetch_check._prettyfilename()
|
||||
print("Synced watched status of {} to account {}...".format(title, userTo))
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
@ -407,11 +421,11 @@ if __name__ == '__main__':
|
||||
requiredNamed.add_argument('--userTo', nargs='*', metavar='user=server', required=True,
|
||||
type=lambda kv: kv.split("="),
|
||||
help='Select user and server to sync to.')
|
||||
|
||||
|
||||
opts = parser.parse_args()
|
||||
# print(opts)
|
||||
tautulli_server = ''
|
||||
|
||||
|
||||
libraries = []
|
||||
all_sections = {}
|
||||
watchedFrom = ''
|
||||
@ -420,15 +434,15 @@ if __name__ == '__main__':
|
||||
start = 0
|
||||
plex_admin = Plex(PLEX_TOKEN)
|
||||
plex_access = plex_admin.users_access()
|
||||
|
||||
|
||||
userFrom, serverFrom = opts.userFrom
|
||||
|
||||
|
||||
if serverFrom == "Tautulli":
|
||||
# Create a Tautulli instance
|
||||
tautulli_server = Tautulli(Connection(url=TAUTULLI_URL.rstrip('/'),
|
||||
apikey=TAUTULLI_APIKEY,
|
||||
verify_ssl=VERIFY_SSL))
|
||||
|
||||
|
||||
if serverFrom == "Tautulli" and opts.libraries:
|
||||
# Pull all libraries from Tautulli
|
||||
_sections = {}
|
||||
@ -452,19 +466,19 @@ if __name__ == '__main__':
|
||||
else:
|
||||
print("No matching library name '{}'".format(library))
|
||||
exit()
|
||||
|
||||
|
||||
# If server is Plex and synciing libraries, check access
|
||||
if serverFrom != "Tautulli" and libraries:
|
||||
print("Checking {}'s access to {}".format(userFrom, serverFrom))
|
||||
watchedFrom = check_users_access(plex_access, userFrom, serverFrom, libraries)
|
||||
|
||||
|
||||
if libraries:
|
||||
print("Finding watched items in libraries...")
|
||||
plexTo = []
|
||||
|
||||
|
||||
for user, server_name in opts.userTo:
|
||||
plexTo.append([user, check_users_access(plex_access, user, server_name, libraries)])
|
||||
|
||||
|
||||
for _library in libraries:
|
||||
watched_lst = []
|
||||
print("Checking {}'s library: '{}' watch statuses...".format(userFrom, _library.title))
|
||||
@ -492,7 +506,7 @@ if __name__ == '__main__':
|
||||
else:
|
||||
for item in sectionFrom.search(unwatched=False):
|
||||
watched_lst.append(item)
|
||||
|
||||
|
||||
for user in plexTo:
|
||||
username, server = user
|
||||
if server == serverFrom:
|
||||
@ -502,7 +516,7 @@ if __name__ == '__main__':
|
||||
elif opts.ratingKey and serverFrom == "Tautulli":
|
||||
plexTo = []
|
||||
watched_item = []
|
||||
|
||||
|
||||
if userFrom != "Tautulli":
|
||||
print("Request manually triggered to update watch status")
|
||||
tt_watched = tautulli_server.get_watched_history(user=userFrom, rating_key=opts.ratingKey)
|
||||
@ -511,18 +525,18 @@ if __name__ == '__main__':
|
||||
else:
|
||||
print("Rating Key {} was not reported as watched in Tautulli for user {}".format(opts.ratingKey, userFrom))
|
||||
exit()
|
||||
|
||||
|
||||
elif userFrom == "Tautulli":
|
||||
print("Request from Tautulli notification agent to update watch status")
|
||||
watched_item = Metadata(tautulli_server.get_metadata(opts.ratingKey))
|
||||
|
||||
|
||||
for user, server_name in opts.userTo:
|
||||
# Check access and connect
|
||||
plexTo.append([user, check_users_access(plex_access, user, server_name, libraries)])
|
||||
|
||||
|
||||
for user in plexTo:
|
||||
username, server = user
|
||||
sync_watch_status([watched_item], watched_item.libraryName, server, username)
|
||||
|
||||
|
||||
else:
|
||||
print("You aren't using this script correctly... bye!")
|
||||
print("You aren't using this script correctly... bye!")
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Description: Sync Tautulli friendly names with Ombi aliases (Tautulli as master)
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Sync Tautulli friendly names with Ombi aliases (Tautulli as master).
|
||||
|
||||
Author: DirtyCajunRice
|
||||
Requires: requests, python3.6+
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user