JBOPS/fun/playlist_manager.py
2018-11-04 01:34:35 -04:00

503 lines
18 KiB
Python

"""
Description: Create and share playlists based on Most Popular TV/Movies from Tautulli
and Aired this day in history.
Author: blacktwin
Requires: requests, plexapi
Create, share, and clean Playlists for users.
optional arguments:
-h, --help show this help message and exit
--jbop Playlist selector.
Choices: (history, popularTv, popularMovies)
--action {add,remove,update,show,share}
Action selector.
add - create new playlist for admin or users
remove - remove playlist type or name from admin or users
update - remove playlist type and create new playlist type for admin or users
show - show contents of playlist type or admin or users current playlists
share - share existing playlist by title from admin to users
--users {] The Plex usernames to create/share to or delete from.
Choices: (USERNAMES)
--libraries [ ...] Space separated list of case sensitive names to
process. Allowed names are:
Choices: (LIBRARIES)
--self Create playlist for admin.
Default: False
--days DAYS The time range to calculate statistics.
Default: 30
--top TOP The number of top items to list.
Default: 5
--playlists Space separated list of case sensitive names to
process. Allowed names are:
Choices: (PLAYLISTS)
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 history --libraries Movies "TV Shows" --action add
Create Aired Today Playlist from Movies and TV Shows libraries and share to users bob, Black Twin and admin user
python playlist_manager.py --jbop history --libraries Movies "TV Shows" --action add --users bob "Black Twin" --self
Update previous Aired Today Playlist(s) from Movies and TV Shows libraries and share to users bob and Black Twin
python playlist_manager.py --jbop history --libraries Movies "TV Shows" --action update --users bob "Black Twin"
Delete all previous Aired Today Playlist(s) from users bob and Black Twin
python playlist_manager.py --jbop history --action remove --users bob "Black Twin"
Create 5 Most Popular TV Shows (30 days) Playlist and share to users bob and Black Twin
python playlist_manager.py --jbop popularTv --action add --users bob "Black Twin"
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"
Excluding;
--user becomes excluded if --allUsers is set
python playlist_manager.py --action show --allUsers --user USER
- Show all users current Playlists... all users but USER
"""
import sys
import requests
import argparse
import operator
import datetime
from plexapi.server import PlexServer, CONFIG
### EDIT SETTINGS ###
PLEX_URL = ''
PLEX_TOKEN = ''
TAUTULLI_URL = ''
TAUTULLI_APIKEY = ''
## CODE BELOW ##
if not PLEX_URL:
PLEX_URL = CONFIG.data['auth'].get('server_baseurl')
if not PLEX_TOKEN:
PLEX_TOKEN = CONFIG.data['auth'].get('server_token')
if not TAUTULLI_URL:
TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl')
if not TAUTULLI_APIKEY:
TAUTULLI_APIKEY = CONFIG.data['auth'].get('tautulli_apikey')
# Defaults
DAYS = 30
TOP = 5
sess = requests.Session()
# Ignore verifying the SSL certificate
sess.verify = False # '/path/to/certfile'
# If verify is set to a path to a directory,
# the directory must have been processed using the c_rehash utility supplied
# with OpenSSL.
if sess.verify is False:
# Disable the warning that the request is insecure, we know that...
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
account = plex.myPlexAccount()
user_lst = [x.title for x in plex.myPlexAccount().users()]
section_lst = [x.title for x in plex.library.sections()]
playlist_lst = [x.title for x in plex.playlists()]
today = datetime.datetime.now().date()
def actions():
"""
add - create new playlist for admin or users
remove - remove playlist type or name from admin or users
update - remove playlist type and create new playlist type for admin or users
show - show contents of playlist type or admin or users current playlists
share - share existing playlist by title from admin to users
"""
return ['add', 'remove', 'update', 'show', 'share']
def selectors():
"""Playlist selections and titles
"""
selections = {'history': 'Aired Today {month}-{day}',
'popularTv': 'Most Popular Movies ({days} days)',
'popularMovies': 'Most Popular TV Shows ({days} days)',
'genre': '{title} Playlist',
'random': '{count} Random Playlist',
'studio': 'Studio: {title} Playlist',
'network': 'Network: {title} Playlist',
'labels': 'Labels: {title} Playlist',
'collections': 'Collections: {title} Playlist',
'country': 'Country: {title} Playlist',
'writer': 'Writer: {title} Playlist',
'director': 'Director: {title} Playlist',
'actor': 'Actor: {title} Playlist'}
return selections
def get_home_stats(time_range, stats_count):
# Get the homepage watch statistics.
payload = {'apikey': TAUTULLI_APIKEY,
'cmd': 'get_home_stats',
'time_range': time_range,
'stats_count': stats_count,
'stats_type': 0} # stats_type = plays
try:
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
response = r.json()
res_data = response['response']['data']
return res_data
except Exception as e:
sys.stderr.write("Tautulli API 'get_home_stats' request failed: {0}.".format(e))
def sort_by_dates(video):
"""Find air dates of content
Parameters
----------
video
video object to find air date
Returns
-------
list
list of rating key and date aired
"""
try:
ad_year = video.originallyAvailableAt.year
ad_month = video.originallyAvailableAt.month
ad_day = video.originallyAvailableAt.day
ad_week = int(datetime.date(ad_year, ad_month, ad_day).strftime("%V"))
if ad_month == today.month and ad_day == today.day:
# todo-me return object
return [[video.ratingKey] + [str(video.originallyAvailableAt)]]
except Exception as e:
# print(e)
return
def get_all_content(library_name):
"""Get all movies or episodes from LIBRARY_NAME
Parameters
----------
library_name: list
list of library objects
Returns
-------
list
Sorted list of Movie and episodes that
aired on today's date.
"""
child_lst = []
for library in library_name:
for child in plex.library.section(library).all():
if child.type == 'movie':
if sort_by_dates(child):
item_date = sort_by_dates(child)
child_lst += item_date
elif child.type == 'show':
for episode in child.episodes():
if sort_by_dates(episode):
item_date = sort_by_dates(episode)
child_lst += item_date
else:
pass
# 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]
return play_lst
def share_playlists(playlist_titles, users):
"""
Parameters
----------
playlist_titles
users
Returns
-------
"""
for user in users:
for title in playlist_titles:
print("...Shared {title} playlist to '{user}'.".format(title=title, user=user))
plex.playlist(title).copyToUser(user)
exit()
def show_playlist(playlist_title, playlist_keys):
"""
Parameters
----------
playlist_keys
Returns
-------
"""
playlist_list = []
for key in playlist_keys:
plex_obj = plex.fetchItem(key)
if plex_obj.type == 'show':
for episode in plex_obj.episodes():
title = "{}".format(episode._prettyfilename())
playlist_list.append(title)
else:
title = "{} ({})".format(plex_obj.title, plex_obj.year)
playlist_list.append(title)
print("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
----------
playlist_title
playlist_keys
server
user
Returns
-------
"""
playlist_list = []
for key in playlist_keys:
try:
plex_obj = server.fetchItem(key)
if plex_obj.type == 'show':
for episode in plex_obj.episodes():
playlist_list.append(episode)
else:
playlist_list.append(plex_obj)
except Exception as e:
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:
print('Rating Key: {}, may have been deleted or moved.'.format(key))
# print("Error: {}".format(e))
if playlist_list:
server.createPlaylist(playlist_title, playlist_list)
print("...Added {title} playlist to '{user}'.".format(title=playlist_title, user=user))
def delete_playlist(playlist_dict):
"""
Parameters
----------
playlist_dict
Returns
-------
"""
server = playlist_dict['server']
jbop = playlist_dict['jbop']
user = playlist_dict['user']
pop_movie = playlist_dict['pop_movie']
pop_tv = playlist_dict['pop_tv']
try:
for playlist in server.playlists():
if jbop == 'history':
if playlist.title.startswith('Aired Today'):
playlist.delete()
print("...Deleted {playlist.title} for '{user}'."
.format(playlist=playlist, user=user))
elif jbop == 'popularMovies':
if playlist.title == pop_movie:
playlist.delete()
print("...Deleted {playlist.title} for '{user}'."
.format(playlist=playlist, user=user))
elif jbop == 'popularTv':
if playlist.title == pop_tv:
playlist.delete()
print("...Deleted {playlist.title} for '{user}'."
.format(playlist=playlist, user=user))
except:
# print("Playlist not found on '{user}' account".format(user=user))
pass
if __name__ == "__main__":
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'
'Choices: (%(choices)s)')
parser.add_argument('--action', required=True, choices=actions(),
help='Action selector.'
'{}'.format(actions.__doc__))
parser.add_argument('--user', nargs='+', choices=user_lst, metavar='',
help='The Plex usernames to create/share to or delete from. Allowed names are: \n'
'Choices: %(choices)s')
parser.add_argument('--allUsers', default=False, action='store_true',
help='Select all users.')
parser.add_argument('--libraries', nargs='+', choices=section_lst, metavar='',
help='Space separated list of case sensitive names to process. Allowed names are: \n'
'Choices: %(choices)s')
parser.add_argument('--self', default=False, action='store_true',
help='Create playlist for admin. \n'
'Default: %(default)s')
parser.add_argument('--days', type=str, default=DAYS,
help='The time range to calculate statistics. \n'
'Default: %(default)s')
parser.add_argument('--top', type=str, default=TOP,
help='The number of top items to list. \n'
'Default: %(default)s')
parser.add_argument('--playlists', nargs='+', choices=playlist_lst, metavar='',
help='Space separated list of case sensitive names to process. Allowed names are: \n'
'Choices: %(choices)s')
parser.add_argument('--name', type=str,
help='Custom name for playlist.')
parser.add_argument('--limit', type=int, default=False,
help='Limit the amount items to be added to a playlist.')
# todo-me custom naming for playlists --name?
# todo-me custom limits to playlist --limit?
opts = parser.parse_args()
# print(opts)
users = ''
plex_servers = []
pop_movie_title = selectors()['popularMovies'].format(days=opts.days)
pop_tv_title = selectors()['popularTv'].format(days=opts.days)
playlist_dict = {'jbop': opts.jbop,
'pop_tv': pop_tv_title,
'pop_movie': pop_movie_title}
# Defining users
if opts.allUsers and not opts.user:
users = user_lst
elif not opts.allUsers and opts.user:
users = opts.user
elif opts.allUsers and opts.user:
# If allUsers is used then any users listed will be excluded
for user in opts.user:
user_lst.remove(user)
users = user_lst
if users:
for user in users:
if opts.action == 'share':
print("Sharing playlist(s)...")
share_playlists(opts.playlists, users)
user_acct = account.user(user)
plex_servers.append({
'server': PlexServer(PLEX_URL, user_acct.get_token(plex.machineIdentifier)),
'user': user})
if opts.self:
plex_servers.append({'server': plex,
'user': 'admin'})
else:
plex_servers.append({'server': plex,
'user': 'admin'})
if opts.action == 'remove':
print("Deleting the playlist(s)...")
for x in plex_servers:
playlist_dict['server'] = x['server']
playlist_dict['user'] = x['user']
delete_playlist(playlist_dict)
else:
if opts.jbop == 'history':
try:
keys_list = get_all_content(opts.libraries)
except TypeError as e:
print("Libraries are not defined for {}. Use --libraries.".format(opts.jbop))
exit("Error: {}".format(e))
title = selectors()['history'].format(month=today.month, day=today.day)
if opts.jbop == 'popularTv':
home_stats = get_home_stats(opts.days, opts.top)
for stat in home_stats:
if stat['stat_id'] == 'popular_tv':
keys_list = [x['rating_key'] for x in stat['rows']]
title = pop_tv_title
if opts.jbop == 'popularMovies':
home_stats = get_home_stats(opts.days, opts.top)
for stat in home_stats:
if stat['stat_id'] == 'popular_movies':
keys_list = [x['rating_key'] for x in stat['rows']]
title = pop_movie_title
if opts.jbop and opts.action == 'show':
show_playlist(title, keys_list)
if opts.action == 'update':
print("Deleting the playlist(s)...")
for x in plex_servers:
playlist_dict['server'] = x['server']
playlist_dict['user'] = x['user']
delete_playlist(playlist_dict)
print('Creating playlist(s)...')
for x in plex_servers:
create_playlist(title, keys_list, x['server'], x['user'])
# todo-me allow for update or another action to remove watched items
# todo-me use Tautulli to imitate Smart Playlist
if opts.action == 'add':
print('Creating playlist(s)...')
for x in plex_servers:
create_playlist(title, keys_list, x['server'], x['user'])
if opts.action == 'show':
print("Displaying the user's playlist(s)...")
for x in plex_servers:
user = x['user']
playlist = [y.title for y in x['server'].playlists()]
print("{}'s current playlist(s): {}".format(user, ', '.join(playlist)))
print("Done.")