2019-06-21 06:55:11 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
"""
|
|
|
|
Description: Create and share playlists based on Most Popular TV/Movies from Tautulli
|
|
|
|
and Aired this day in history.
|
|
|
|
Author: blacktwin
|
2022-05-11 14:31:16 +00:00
|
|
|
Requires: requests, plexapi, flatten_json
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Create, share, and clean Playlists for users.
|
|
|
|
|
|
|
|
optional arguments:
|
|
|
|
-h, --help show this help message and exit
|
|
|
|
--jbop Playlist selector.
|
2018-12-21 14:13:06 +00:00
|
|
|
Choices: (historyToday, historyWeek, historyMonth, popularTv, popularMovies)
|
2018-12-23 07:02:22 +00:00
|
|
|
--action 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
|
2020-07-13 23:53:03 +00:00
|
|
|
export - export playlist by title from admin to users
|
2018-10-19 06:31:55 +00:00
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
--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)
|
2018-12-23 07:02:22 +00:00
|
|
|
--allLibraries Select all libraries.
|
2018-09-26 13:10:40 +00:00
|
|
|
--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
|
2018-12-23 07:02:22 +00:00
|
|
|
--playlists Space separated list of case sensitive names to
|
2018-10-19 06:31:55 +00:00
|
|
|
process. Allowed names are:
|
|
|
|
Choices: (PLAYLISTS)
|
2019-01-03 16:14:32 +00:00
|
|
|
--allPlaylist Select all playlists.
|
2018-12-23 07:02:22 +00:00
|
|
|
--name NAME Custom name for playlist.
|
|
|
|
--limit LIMIT Limit the amount items to be added to a playlist.
|
|
|
|
--filter FILTER Search filtered metadata fields
|
|
|
|
Filters: (mood unwatched country contentRating collection label director duplicate
|
|
|
|
studio actor year genre guid resolution decade network).
|
|
|
|
--search SEARCH Search non-filtered metadata fields for keywords in title, summary, etc.
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
Use with cron or task to schedule runs
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-10-07 13:15:20 +00:00
|
|
|
Create Aired Today Playlist from Movies and TV Shows libraries for admin user
|
2018-12-21 14:13:06 +00:00
|
|
|
python playlist_manager.py --jbop historyToday --libraries Movies "TV Shows" --action add
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Create Aired Today Playlist from Movies and TV Shows libraries and share to users bob, Black Twin and admin user
|
2018-12-21 14:13:06 +00:00
|
|
|
python playlist_manager.py --jbop historyToday --libraries Movies "TV Shows" --action add --users bob "Black Twin" --self
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Update previous Aired Today Playlist(s) from Movies and TV Shows libraries and share to users bob and Black Twin
|
2018-12-21 14:13:06 +00:00
|
|
|
python playlist_manager.py --jbop historyToday --libraries Movies "TV Shows" --action update --users bob "Black Twin"
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Delete all previous Aired Today Playlist(s) from users bob and Black Twin
|
2018-12-21 14:13:06 +00:00
|
|
|
python playlist_manager.py --jbop historyToday --action remove --users bob "Black Twin"
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Create 5 Most Popular TV Shows (30 days) Playlist and share to users bob and Black Twin
|
2020-10-28 20:09:55 +00:00
|
|
|
python playlist_manager.py --jbop popularTv --libraries "TV Shows" --action add --users bob "Black Twin"
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Create 10 Most Popular Movies (60 days) Playlist and share to users bob and Black Twin
|
2020-10-28 20:09:55 +00:00
|
|
|
python playlist_manager.py --jbop popularMovies --libraries Movies --action add --users bob "Black Twin" --days 60 --top 10
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-10-19 06:31:55 +00:00
|
|
|
Show 5 Most Popular TV Shows (30 days) Playlist
|
2020-10-28 20:09:55 +00:00
|
|
|
python playlist_manager.py --jbop popularTv --libraries "TV Shows" --action show
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-10-19 06:31:55 +00:00
|
|
|
Show all users current playlists
|
|
|
|
python playlist_manager.py --action show --allUsers
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-10-19 06:31:55 +00:00
|
|
|
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"
|
2020-07-13 23:53:03 +00:00
|
|
|
|
|
|
|
Export each of an user's Playlists contents to a json file in the root of the script
|
2022-04-22 02:35:03 +00:00
|
|
|
python playlist_manager.py --action export --user USER --playlists "Most Popular Movies (30 days)" "New Hot"
|
2020-07-13 23:53:03 +00:00
|
|
|
|
|
|
|
Export each of an user's Playlists contents to a csv file in the root of the script
|
2022-04-22 02:35:03 +00:00
|
|
|
python playlist_manager.py --action export --user USER --allPlaylists --export csv
|
|
|
|
|
|
|
|
Import a user's exported Playlist json file to the admin's Movies library
|
|
|
|
python playlist_manager.py --action import --self --importJson "User-Title-Playlist.json" --libraries "Movies"
|
|
|
|
|
|
|
|
Import a admin's exported Playlist json file to users shared Movies library
|
|
|
|
python playlist_manager.py --action import --users User1 "Another User" --importJson "User-Title-Playlist.json" --libraries "Movies"
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-24 14:38:29 +00:00
|
|
|
Search and Filter;
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-24 14:38:29 +00:00
|
|
|
metadata_field_name = title, summary, etc.
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-24 14:38:29 +00:00
|
|
|
--search {metadata_field_name}=value
|
|
|
|
search through metadata field for existence of value.
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-24 14:38:29 +00:00
|
|
|
--search {metadata_field_name}=value1,value2,*
|
|
|
|
search through metadata field for existence of values.
|
|
|
|
*comma separated for AND (value1 AND value2 AND *)
|
2019-06-21 06:55:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2018-10-19 06:31:55 +00:00
|
|
|
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
|
|
|
|
|
2018-12-23 07:02:22 +00:00
|
|
|
--libraries becomes excluded if --allLibraries is set
|
|
|
|
python playlist_manager.py --jbop historyToday --allLibraries --libraries Movies --action add
|
|
|
|
- Create Aired Today Playlist from every library by Movies for admin user
|
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
"""
|
2020-07-04 20:31:02 +00:00
|
|
|
from __future__ import unicode_literals
|
2018-09-26 13:10:40 +00:00
|
|
|
|
2020-07-04 20:23:47 +00:00
|
|
|
from builtins import str
|
2018-09-26 13:10:40 +00:00
|
|
|
import sys
|
2019-07-29 15:20:52 +00:00
|
|
|
import os
|
2022-04-21 18:43:08 +00:00
|
|
|
import json
|
2019-02-03 21:58:40 +00:00
|
|
|
import random
|
2019-07-29 15:20:52 +00:00
|
|
|
import logging
|
2018-09-26 13:10:40 +00:00
|
|
|
import requests
|
|
|
|
import argparse
|
|
|
|
import operator
|
|
|
|
import datetime
|
2019-05-10 04:05:55 +00:00
|
|
|
from collections import Counter
|
2018-09-26 13:10:40 +00:00
|
|
|
from plexapi.server import PlexServer, CONFIG
|
|
|
|
|
2019-07-29 15:20:52 +00:00
|
|
|
filename = os.path.basename(__file__)
|
|
|
|
filename = filename.split('.')[0]
|
|
|
|
|
|
|
|
logger = logging.getLogger(filename)
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
error_format = logging.Formatter('%(asctime)s:%(name)s:%(funcName)s:%(message)s')
|
|
|
|
stream_format = logging.Formatter('%(message)s')
|
|
|
|
|
|
|
|
file_handler = logging.FileHandler('{}.log'.format(filename))
|
|
|
|
file_handler.setLevel(logging.ERROR)
|
|
|
|
file_handler.setFormatter(error_format)
|
|
|
|
|
|
|
|
stream_handler = logging.StreamHandler()
|
|
|
|
stream_handler.setFormatter(stream_format)
|
|
|
|
|
|
|
|
logger.addHandler(file_handler)
|
|
|
|
logger.addHandler(stream_handler)
|
|
|
|
|
|
|
|
|
2019-06-21 06:55:11 +00:00
|
|
|
# ### EDIT SETTINGS ###
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
PLEX_URL = ''
|
|
|
|
PLEX_TOKEN = ''
|
|
|
|
TAUTULLI_URL = ''
|
|
|
|
TAUTULLI_APIKEY = ''
|
|
|
|
|
2019-06-21 06:55:11 +00:00
|
|
|
# ## CODE BELOW ##
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
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()
|
2022-10-02 20:25:47 +00:00
|
|
|
user_lst = [x.title for x in plex.myPlexAccount().users() if x.servers]
|
2018-12-23 07:02:22 +00:00
|
|
|
sections = plex.library.sections()
|
2019-01-03 15:39:55 +00:00
|
|
|
sections_dict = {x.key: x.title for x in sections}
|
2021-05-24 14:02:42 +00:00
|
|
|
filters_lst = list(set([y.key for x in sections if x.type != 'photo' for y in x.listFields()]))
|
2021-02-27 07:05:39 +00:00
|
|
|
admin_playlist_lst = [x for x in plex.playlists()]
|
2018-09-26 13:10:40 +00:00
|
|
|
today = datetime.datetime.now().date()
|
2018-12-21 14:11:05 +00:00
|
|
|
weeknum = datetime.date(today.year, today.month, today.day).isocalendar()[1]
|
2022-04-21 18:43:08 +00:00
|
|
|
json_check = sorted([f for f in os.listdir('.') if os.path.isfile(f) and f.endswith(".json")],
|
|
|
|
key=os.path.getmtime)
|
2018-09-26 13:10:40 +00:00
|
|
|
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-10-19 05:32:00 +00:00
|
|
|
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
|
2020-07-13 23:45:50 +00:00
|
|
|
export - export playlist by title from admin to users
|
2018-10-19 05:32:00 +00:00
|
|
|
"""
|
2022-04-21 18:43:28 +00:00
|
|
|
return ['add', 'remove', 'update', 'show', 'share', 'export', 'import']
|
2018-10-19 05:32:00 +00:00
|
|
|
|
|
|
|
|
2018-11-04 05:34:35 +00:00
|
|
|
def selectors():
|
2018-12-22 02:17:08 +00:00
|
|
|
"""Predefined Playlist selections and titles
|
2018-11-04 05:34:35 +00:00
|
|
|
"""
|
2019-05-24 14:38:29 +00:00
|
|
|
selections = {'historyToday': 'Aired Today {month}-{day} in History',
|
2018-12-21 14:11:05 +00:00
|
|
|
'historyWeek': 'Aired This Week ({week}) in History',
|
|
|
|
'historyMonth': 'Aired in {month}',
|
2018-11-23 20:45:21 +00:00
|
|
|
'popularTv': 'Most Popular TV Shows ({days} days)',
|
|
|
|
'popularMovies': 'Most Popular Movies ({days} days)',
|
2019-05-24 14:38:29 +00:00
|
|
|
'custom': '{custom} Playlist',
|
2020-03-12 19:50:34 +00:00
|
|
|
'random': '{count} Random {libraries} Playlist',
|
|
|
|
'label': '{custom}',
|
|
|
|
'collection': '{custom}'
|
2018-12-22 02:17:08 +00:00
|
|
|
}
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-11-04 05:34:35 +00:00
|
|
|
return selections
|
|
|
|
|
2018-12-22 02:17:08 +00:00
|
|
|
|
2019-01-04 18:09:43 +00:00
|
|
|
def exclusions(all_true, select, all_items):
|
2019-01-04 18:49:05 +00:00
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
all_true: bool
|
|
|
|
All of something (allLibraries, allPlaylists, allUsers)
|
|
|
|
select: list
|
|
|
|
List from arguments (user, playlists, libraries)
|
|
|
|
all_items: list or dict
|
|
|
|
List or Dictionary of all possible somethings
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
2019-01-05 06:05:30 +00:00
|
|
|
output: list or dict
|
2019-01-04 18:49:05 +00:00
|
|
|
List of what was included/excluded
|
|
|
|
"""
|
2019-01-04 18:09:43 +00:00
|
|
|
output = ''
|
|
|
|
if isinstance(all_items, list):
|
|
|
|
output = []
|
|
|
|
if all_true and not select:
|
|
|
|
output = all_items
|
|
|
|
elif not all_true and select:
|
2020-07-13 23:47:10 +00:00
|
|
|
for item in all_items:
|
|
|
|
if isinstance(item, str):
|
|
|
|
return select
|
|
|
|
else:
|
|
|
|
if item.title in select:
|
|
|
|
output.append(item)
|
2019-01-04 18:09:43 +00:00
|
|
|
elif all_true and select:
|
|
|
|
for x in select:
|
|
|
|
all_items.remove(x)
|
|
|
|
output = all_items
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-04 18:09:43 +00:00
|
|
|
elif isinstance(all_items, dict):
|
|
|
|
output = {}
|
|
|
|
if all_true and not select:
|
|
|
|
output = all_items
|
|
|
|
elif not all_true and select:
|
|
|
|
for key, value in all_items.items():
|
|
|
|
if value in select:
|
|
|
|
output[key] = value
|
|
|
|
elif all_true and select:
|
|
|
|
for key, value in all_items.items():
|
|
|
|
if value not in select:
|
|
|
|
output[key] = value
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-04 18:09:43 +00:00
|
|
|
return output
|
|
|
|
|
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
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,
|
2019-06-21 06:55:11 +00:00
|
|
|
'stats_type': 0} # stats_type = plays
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
2018-12-21 14:11:05 +00:00
|
|
|
def sort_by_dates(video, date_type):
|
2018-10-30 04:02:19 +00:00
|
|
|
"""Find air dates of content
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
video
|
|
|
|
video object to find air date
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
list
|
|
|
|
list of rating key and date aired
|
|
|
|
"""
|
|
|
|
|
|
|
|
try:
|
2018-10-30 04:02:19 +00:00
|
|
|
ad_year = video.originallyAvailableAt.year
|
|
|
|
ad_month = video.originallyAvailableAt.month
|
|
|
|
ad_day = video.originallyAvailableAt.day
|
2018-12-21 14:11:05 +00:00
|
|
|
ad_week = int(datetime.date(ad_year, ad_month, ad_day).isocalendar()[1])
|
|
|
|
|
|
|
|
if date_type == 'historyToday':
|
|
|
|
if ad_month == today.month and ad_day == today.day:
|
|
|
|
return [[video.ratingKey] + [str(video.originallyAvailableAt)]]
|
2019-07-29 15:26:50 +00:00
|
|
|
elif date_type == 'historyWeek':
|
2018-12-21 14:11:05 +00:00
|
|
|
if ad_week == weeknum:
|
|
|
|
return [[video.ratingKey] + [str(video.originallyAvailableAt)]]
|
2019-07-29 15:26:50 +00:00
|
|
|
elif date_type == 'historyMonth':
|
2018-12-21 14:11:05 +00:00
|
|
|
if ad_month == today.month:
|
|
|
|
return [[video.ratingKey] + [str(video.originallyAvailableAt)]]
|
2019-07-29 15:26:50 +00:00
|
|
|
else:
|
|
|
|
logger.debug("{} is outside of range for {}".format(video.title, date_type))
|
|
|
|
pass
|
2018-12-21 14:11:05 +00:00
|
|
|
# todo-me return object
|
2019-07-29 15:20:52 +00:00
|
|
|
except Exception as e:
|
2019-09-08 14:30:21 +00:00
|
|
|
logger.error("Error:{} for {}".format(e, video._prettyfilename()))
|
|
|
|
# exit()
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
|
2019-06-18 02:23:56 +00:00
|
|
|
def multi_filter_search(keyword_dict, library, search_eps=None):
|
2019-05-28 17:34:53 +00:00
|
|
|
"""Allowing for multiple filter or search values
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-28 17:34:53 +00:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
keyword_dict: dict
|
|
|
|
library: class
|
2019-06-18 02:23:56 +00:00
|
|
|
search_eps: bool
|
2019-05-28 17:34:53 +00:00
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
list
|
|
|
|
items that include all searched or filtered values
|
|
|
|
"""
|
|
|
|
multi_lst = []
|
2019-06-18 02:23:56 +00:00
|
|
|
ep_lst = []
|
|
|
|
logs = {}
|
|
|
|
ep_logs = []
|
2019-05-28 17:34:53 +00:00
|
|
|
# How many keywords
|
|
|
|
keyword_count = len(keyword_dict)
|
|
|
|
for key, values in keyword_dict.items():
|
|
|
|
if isinstance(values, list):
|
|
|
|
keyword_count += 1
|
|
|
|
for value in values:
|
|
|
|
search_dict = {}
|
|
|
|
search_dict[key] = value
|
2019-06-18 02:23:56 +00:00
|
|
|
if search_eps:
|
|
|
|
logs["data"] = [{key: value}]
|
2019-12-04 17:13:10 +00:00
|
|
|
for show in library.all():
|
|
|
|
for episode in show.episodes(**search_dict):
|
|
|
|
ep_lst += [episode.ratingKey]
|
|
|
|
ep_logs += [episode.title, episode.summary]
|
2019-06-18 02:23:56 +00:00
|
|
|
|
|
|
|
logs["data"].append({"keys": ep_lst, "info": ep_logs})
|
|
|
|
search_lst = ep_lst
|
|
|
|
else:
|
|
|
|
search_lst = [item.ratingKey for item in library.all(**search_dict)]
|
2019-05-28 17:34:53 +00:00
|
|
|
multi_lst += search_lst
|
|
|
|
else:
|
2019-06-18 02:23:56 +00:00
|
|
|
if search_eps:
|
2019-12-04 17:13:10 +00:00
|
|
|
for show in library.all():
|
|
|
|
for episode in show.episodes(**{key: values}):
|
|
|
|
ep_lst += [episode.ratingKey]
|
2019-06-18 02:23:56 +00:00
|
|
|
multi_lst += ep_lst
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-06-18 02:23:56 +00:00
|
|
|
else:
|
|
|
|
multi_lst += [item.ratingKey for item in library.all(**{key: values})]
|
2019-05-28 17:34:53 +00:00
|
|
|
counts = Counter(multi_lst)
|
|
|
|
# Use amount of keywords to check that all keywords were found in results
|
2019-06-18 02:23:56 +00:00
|
|
|
search_lst = [id for id in multi_lst if counts[id] >= keyword_count]
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-28 17:34:53 +00:00
|
|
|
return list(set(search_lst))
|
|
|
|
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-02-03 21:58:40 +00:00
|
|
|
def get_content(libraries, jbop, filters=None, search=None, limit=None):
|
2019-06-21 06:55:11 +00:00
|
|
|
"""Get all movies or episodes from LIBRARY_NAME.
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2019-01-03 15:39:55 +00:00
|
|
|
libraries: dict
|
|
|
|
dict of libraries key and name
|
2018-12-21 21:05:17 +00:00
|
|
|
jbop: str
|
2018-12-21 14:11:05 +00:00
|
|
|
jbop value for searching
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
list
|
|
|
|
Sorted list of Movie and episodes that
|
|
|
|
aired on today's date.
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
"""
|
|
|
|
child_lst = []
|
2019-01-01 04:38:30 +00:00
|
|
|
filter_lst = []
|
|
|
|
search_lst = []
|
2019-05-28 17:34:53 +00:00
|
|
|
keywords = {}
|
|
|
|
tags = "__tag__icontains"
|
2019-01-01 07:46:48 +00:00
|
|
|
|
2019-01-01 04:38:30 +00:00
|
|
|
if search or filters:
|
|
|
|
if search:
|
|
|
|
# todo-me replace with documentation showing the available search operators
|
2019-05-10 04:05:55 +00:00
|
|
|
keywords = {key + '__icontains': value for key, value in search.items()}
|
2019-01-01 07:46:48 +00:00
|
|
|
# Loop through each library
|
2019-01-03 15:39:55 +00:00
|
|
|
for library in libraries.keys():
|
|
|
|
plex_library = plex.library.sectionByID(library)
|
2019-01-01 07:46:48 +00:00
|
|
|
library_type = plex_library.type
|
|
|
|
# Find media type, if show then search/filter episodes
|
|
|
|
if library_type == 'movie':
|
|
|
|
# Decisions to stack filter and search
|
2019-05-10 04:05:55 +00:00
|
|
|
if keywords:
|
2019-05-28 17:34:53 +00:00
|
|
|
child_lst += multi_filter_search(keywords, plex_library)
|
2019-01-01 07:46:48 +00:00
|
|
|
if filters:
|
2019-05-28 17:34:53 +00:00
|
|
|
for key, value in filters.items():
|
|
|
|
# Only genre filtering should allow multiple values and allow for AND statement
|
2020-11-26 04:02:52 +00:00
|
|
|
if key == "genre":
|
|
|
|
child_lst += multi_filter_search({key + tags: value}, plex_library)
|
2019-05-28 17:34:53 +00:00
|
|
|
else:
|
|
|
|
filter_lst = [movie.ratingKey for movie in plex_library.search(**{key: value})]
|
|
|
|
child_lst += filter_lst
|
2019-05-10 04:05:55 +00:00
|
|
|
if keywords and filters:
|
2019-01-01 14:43:30 +00:00
|
|
|
child_lst += list(set(filter_lst) & set(search_lst))
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-01 07:46:48 +00:00
|
|
|
elif library_type == 'show':
|
2019-02-03 21:58:40 +00:00
|
|
|
# Decisions to stack filter and search
|
2019-05-10 04:05:55 +00:00
|
|
|
if keywords:
|
2019-06-18 02:23:56 +00:00
|
|
|
search_lst = multi_filter_search(keywords, plex_library, search_eps=True)
|
2019-01-01 14:03:13 +00:00
|
|
|
child_lst += search_lst
|
2019-01-01 07:46:48 +00:00
|
|
|
if filters:
|
2019-05-28 18:09:09 +00:00
|
|
|
for key, value in filters.items():
|
|
|
|
# Only genre filtering should allow multiple values and allow for AND statement
|
2020-11-26 04:02:52 +00:00
|
|
|
if key == "genre":
|
2019-05-28 18:09:09 +00:00
|
|
|
shows_lst = multi_filter_search({key: value}, plex_library)
|
|
|
|
else:
|
2019-07-24 12:10:18 +00:00
|
|
|
shows_lst = [show.ratingKey for show in plex_library.search(**{key: value})]
|
2019-05-28 18:09:09 +00:00
|
|
|
for showkey in shows_lst:
|
|
|
|
show = plex.fetchItem(showkey)
|
|
|
|
for episode in show.episodes():
|
|
|
|
filter_lst += [episode.ratingKey]
|
|
|
|
child_lst += filter_lst
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-05-10 04:05:55 +00:00
|
|
|
if keywords and filters:
|
2019-01-01 14:43:30 +00:00
|
|
|
child_lst += list(set(filter_lst) & set(search_lst))
|
2019-01-01 07:46:48 +00:00
|
|
|
else:
|
|
|
|
pass
|
2019-02-18 19:15:40 +00:00
|
|
|
# Keep only results found from both search and filters
|
2019-05-10 04:05:55 +00:00
|
|
|
if keywords and filters:
|
2019-02-18 19:15:40 +00:00
|
|
|
child_lst = list(set(i for i in child_lst if child_lst.count(i) > 1))
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-12-21 21:05:17 +00:00
|
|
|
play_lst = child_lst
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-12-21 21:05:17 +00:00
|
|
|
else:
|
2019-07-24 12:11:15 +00:00
|
|
|
for library_id in libraries.keys():
|
|
|
|
plex_library = plex.library.sectionByID(library_id)
|
2019-02-03 21:58:40 +00:00
|
|
|
library_type = plex_library.type
|
2019-07-24 12:12:55 +00:00
|
|
|
if library_type == 'movie':
|
2019-02-03 21:58:40 +00:00
|
|
|
for child in plex_library.all():
|
2019-07-29 15:26:50 +00:00
|
|
|
if jbop.startswith("history"):
|
2019-02-03 21:58:40 +00:00
|
|
|
if sort_by_dates(child, jbop):
|
|
|
|
item_date = sort_by_dates(child, jbop)
|
2018-12-21 21:05:17 +00:00
|
|
|
child_lst += item_date
|
2019-02-03 21:58:40 +00:00
|
|
|
else:
|
2019-07-29 15:26:50 +00:00
|
|
|
child_lst += [child.ratingKey]
|
|
|
|
elif library_type == 'show':
|
|
|
|
for child in plex_library.all():
|
|
|
|
for episode in child.episodes():
|
|
|
|
if jbop.startswith("history"):
|
2019-09-08 03:31:21 +00:00
|
|
|
if sort_by_dates(episode, jbop):
|
|
|
|
item_date = sort_by_dates(episode, jbop)
|
|
|
|
child_lst += item_date
|
2019-07-29 15:26:50 +00:00
|
|
|
else:
|
|
|
|
child_lst += [episode.ratingKey]
|
|
|
|
else:
|
|
|
|
pass
|
2019-02-03 21:58:40 +00:00
|
|
|
# check if sort_by_dates was used
|
|
|
|
if isinstance(child_lst[0], list):
|
|
|
|
# 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))
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-02-03 21:58:40 +00:00
|
|
|
# Remove date used for sorting
|
|
|
|
play_lst = [x[0] for x in aired_lst]
|
|
|
|
else:
|
2019-07-29 15:20:52 +00:00
|
|
|
# todo-me probably will want to check limit by itself
|
2019-07-24 12:12:55 +00:00
|
|
|
if jbop == "random" and limit:
|
|
|
|
child_lst = random.sample(child_lst, limit)
|
2019-02-03 21:58:40 +00:00
|
|
|
play_lst = child_lst
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
return play_lst
|
|
|
|
|
|
|
|
|
2019-02-03 21:58:40 +00:00
|
|
|
def build_playlist(jbop, libraries=None, days=None, top=None, filters=None, search=None, limit=None):
|
2018-12-21 14:41:10 +00:00
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
jbop: str
|
2018-12-21 21:05:17 +00:00
|
|
|
The predefined Playlist type
|
2019-01-03 15:39:55 +00:00
|
|
|
libraries: dict
|
|
|
|
{key: name}
|
2018-12-21 21:05:17 +00:00
|
|
|
Libraries to use to build Playlist
|
2018-12-21 14:41:10 +00:00
|
|
|
days: int
|
2018-12-21 21:05:17 +00:00
|
|
|
Days to search for Top Movies/Tv Shows
|
2018-12-21 14:41:10 +00:00
|
|
|
top: int
|
2018-12-21 21:05:17 +00:00
|
|
|
Limit to search for Top Movies/Tv Shows
|
2018-12-21 14:41:10 +00:00
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
2019-02-08 16:59:05 +00:00
|
|
|
key_list
|
2018-12-21 14:41:10 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
keys_list = []
|
2019-02-04 04:54:55 +00:00
|
|
|
if jbop in ['popularTv', 'popularMovies']:
|
|
|
|
home_stats = get_home_stats(days, top)
|
|
|
|
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
|
2021-07-16 03:20:17 +00:00
|
|
|
x['section_id'] in libraries.keys()]
|
2019-02-04 04:54:55 +00:00
|
|
|
else:
|
2019-02-03 21:58:40 +00:00
|
|
|
try:
|
|
|
|
keys_list = get_content(libraries, jbop, filters, search, limit)
|
|
|
|
except TypeError as e:
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.exception("Libraries are not defined for {}. Use --libraries.".format(jbop))
|
|
|
|
exit()
|
2018-12-21 14:41:10 +00:00
|
|
|
|
2019-02-08 16:59:05 +00:00
|
|
|
return keys_list
|
2018-12-21 14:41:10 +00:00
|
|
|
|
2018-12-21 21:05:17 +00:00
|
|
|
|
2021-10-25 04:41:10 +00:00
|
|
|
def share_playlists(playlists, users):
|
2018-10-19 05:14:24 +00:00
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
2021-10-25 04:41:10 +00:00
|
|
|
playlists: list
|
2019-01-05 06:05:30 +00:00
|
|
|
list of playlist titles
|
|
|
|
users: list
|
|
|
|
list of user names
|
2018-10-19 05:14:24 +00:00
|
|
|
"""
|
|
|
|
for user in users:
|
2021-10-25 04:41:10 +00:00
|
|
|
for playlist in playlists:
|
|
|
|
if isinstance(playlist, str):
|
|
|
|
title = playlist
|
|
|
|
else:
|
|
|
|
title = playlist.title
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("...Shared {title} playlist to '{user}'.".format(title=title, user=user))
|
2018-10-19 05:14:24 +00:00
|
|
|
plex.playlist(title).copyToUser(user)
|
|
|
|
|
|
|
|
exit()
|
|
|
|
|
|
|
|
|
2018-10-19 04:32:55 +00:00
|
|
|
def show_playlist(playlist_title, playlist_keys):
|
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
2019-01-05 06:05:30 +00:00
|
|
|
playlist_title: str
|
|
|
|
playlist's title
|
|
|
|
playlist_keys: list
|
|
|
|
list of rating keys for playlist
|
2018-10-19 04:32:55 +00:00
|
|
|
"""
|
|
|
|
playlist_list = []
|
|
|
|
for key in playlist_keys:
|
2019-01-03 15:39:55 +00:00
|
|
|
# todo-me add try to catch when Tautulli reports a rating key that is now missing from Plex
|
2018-10-19 04:32:55 +00:00
|
|
|
plex_obj = plex.fetchItem(key)
|
|
|
|
if plex_obj.type == 'show':
|
|
|
|
for episode in plex_obj.episodes():
|
2020-10-26 13:09:09 +00:00
|
|
|
title = str("{}".format(episode._prettyfilename()))
|
|
|
|
playlist_list.append(title)
|
2020-11-03 03:50:57 +00:00
|
|
|
else:
|
|
|
|
title = str("{} ({})".format(plex_obj._prettyfilename(), plex_obj.year))
|
|
|
|
playlist_list.append(title)
|
2018-10-19 04:32:55 +00:00
|
|
|
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info(u"Contents of Playlist {title}:\n{playlist}".format(
|
2019-06-21 06:55:11 +00:00
|
|
|
title=playlist_title,
|
|
|
|
playlist=', '.join(playlist_list)))
|
2018-10-19 04:32:55 +00:00
|
|
|
exit()
|
2019-06-21 06:55:11 +00:00
|
|
|
|
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
def create_playlist(playlist_title, playlist_keys, server, user):
|
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
2019-01-05 06:05:30 +00:00
|
|
|
playlist_title: str
|
|
|
|
playlist title
|
|
|
|
playlist_keys: list
|
|
|
|
list of rating keys for playlist
|
|
|
|
server: class
|
|
|
|
server instance
|
|
|
|
user: str
|
|
|
|
users name
|
2018-09-26 13:10:40 +00:00
|
|
|
"""
|
|
|
|
playlist_list = []
|
|
|
|
for key in playlist_keys:
|
2018-10-19 06:01:29 +00:00
|
|
|
try:
|
|
|
|
plex_obj = server.fetchItem(key)
|
2018-11-02 14:29:19 +00:00
|
|
|
if plex_obj.type == 'show':
|
|
|
|
for episode in plex_obj.episodes():
|
|
|
|
playlist_list.append(episode)
|
|
|
|
else:
|
|
|
|
playlist_list.append(plex_obj)
|
2019-06-21 06:55:11 +00:00
|
|
|
except Exception:
|
2018-11-02 14:29:19 +00:00
|
|
|
try:
|
|
|
|
obj = plex.fetchItem(key)
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.exception("{} may not have permission to this title: {}".format(user, obj.title))
|
2018-11-02 14:29:19 +00:00
|
|
|
pass
|
2019-06-21 06:55:11 +00:00
|
|
|
except Exception:
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.exception('Rating Key: {}, may have been deleted or moved.'.format(key))
|
2018-09-26 13:10:40 +00:00
|
|
|
|
2018-10-19 06:01:29 +00:00
|
|
|
if playlist_list:
|
2021-07-16 03:21:20 +00:00
|
|
|
server.createPlaylist(playlist_title, items=playlist_list)
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("...Added Playlist: {title} to '{user}'.".format(title=playlist_title, user=user))
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
|
2021-08-04 12:40:38 +00:00
|
|
|
def delete_playlist(playlist_dict, title, jbop=None):
|
2018-09-26 13:10:40 +00:00
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
2019-02-09 06:20:41 +00:00
|
|
|
playlist_dict: dict
|
|
|
|
Server and user information
|
|
|
|
title: str, list
|
|
|
|
Playlist title(s)
|
2018-09-26 13:10:40 +00:00
|
|
|
"""
|
2018-11-03 13:37:33 +00:00
|
|
|
server = playlist_dict['server']
|
|
|
|
user = playlist_dict['user']
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
try:
|
2019-01-04 15:03:10 +00:00
|
|
|
# todo-me this needs improvement
|
2018-09-26 13:10:40 +00:00
|
|
|
for playlist in server.playlists():
|
2019-02-09 06:14:21 +00:00
|
|
|
if isinstance(title, str):
|
|
|
|
# If str then updating playlist
|
2021-08-04 12:40:38 +00:00
|
|
|
# The same title should catch popular* jbops
|
2019-02-09 06:14:21 +00:00
|
|
|
if playlist.title == title:
|
|
|
|
playlist.delete()
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("...Deleted Playlist: {playlist.title} for '{user}'."
|
2019-02-09 06:14:21 +00:00
|
|
|
.format(playlist=playlist, user=user))
|
2021-08-04 12:40:38 +00:00
|
|
|
else:
|
|
|
|
# catching for history* jbops
|
|
|
|
titleTemplate = selectors().get(jbop)
|
|
|
|
titleStart = titleTemplate.split('{')[0]
|
2021-08-17 12:00:45 +00:00
|
|
|
if titleStart and playlist.title.startswith(titleStart):
|
2021-08-04 12:40:38 +00:00
|
|
|
playlist.delete()
|
|
|
|
logger.info("...Deleted Playlist: {playlist.title} for '{user}'."
|
|
|
|
.format(playlist=playlist, user=user))
|
2019-02-09 06:14:21 +00:00
|
|
|
if isinstance(title, list):
|
|
|
|
# If list then removing selected playlists
|
2020-12-10 02:02:50 +00:00
|
|
|
playlists_titles = [playlist if isinstance(playlist, str) else playlist.title for playlist in title]
|
2020-10-26 20:05:00 +00:00
|
|
|
if playlist.title in playlists_titles:
|
2019-02-09 06:14:21 +00:00
|
|
|
playlist.delete()
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("...Deleted Playlist: {playlist.title} for '{user}'."
|
2019-02-09 06:14:21 +00:00
|
|
|
.format(playlist=playlist, user=user))
|
|
|
|
|
2019-06-21 06:55:11 +00:00
|
|
|
except Exception:
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.exception("Playlist not found on '{user}' account".format(user=user))
|
2018-09-26 13:10:40 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-02-08 16:59:05 +00:00
|
|
|
def create_title(jbop, libraries, days, filters, search, limit):
|
2019-02-09 06:20:41 +00:00
|
|
|
"""
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-02-09 06:20:41 +00:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
jbop: str
|
|
|
|
Playlist selector
|
|
|
|
libraries: dict
|
|
|
|
Plex libraries information
|
|
|
|
days: int
|
|
|
|
Amount of days for Popular media types
|
|
|
|
filters: dict
|
|
|
|
Plex media filters
|
|
|
|
search: dict
|
|
|
|
Search terms
|
|
|
|
limit
|
|
|
|
Playlist size limit
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
title
|
|
|
|
"""
|
2019-02-08 16:59:05 +00:00
|
|
|
title = ''
|
|
|
|
if jbop == 'historyToday':
|
|
|
|
title = selectors()['historyToday'].format(month=today.month, day=today.day)
|
|
|
|
|
|
|
|
elif jbop == 'historyWeek':
|
|
|
|
title = selectors()['historyWeek'].format(week=weeknum)
|
|
|
|
|
|
|
|
elif jbop == 'historyMonth':
|
|
|
|
title = selectors()['historyMonth'].format(month=today.strftime("%B"))
|
|
|
|
|
|
|
|
elif jbop == 'custom':
|
|
|
|
if search and not filters:
|
2019-05-10 04:05:55 +00:00
|
|
|
title_lst = []
|
|
|
|
for values in search.values():
|
|
|
|
if isinstance(values, list):
|
|
|
|
title_lst += values
|
|
|
|
else:
|
|
|
|
title_lst += [values]
|
|
|
|
title = " ".join(title_lst)
|
2019-02-08 16:59:05 +00:00
|
|
|
elif filters and not search:
|
2019-05-28 17:34:53 +00:00
|
|
|
title_lst = []
|
|
|
|
for values in filters.values():
|
|
|
|
if isinstance(values, list):
|
|
|
|
title_lst += values
|
|
|
|
else:
|
|
|
|
title_lst += [values]
|
|
|
|
title = " ".join(title_lst)
|
2019-02-08 16:59:05 +00:00
|
|
|
elif search and filters:
|
2019-02-17 06:20:18 +00:00
|
|
|
search_title = ' '.join(search.values())
|
|
|
|
filters_title = ' '.join(filters.values())
|
2019-02-08 16:59:05 +00:00
|
|
|
title = filters_title + ' ' + search_title
|
2019-02-17 06:20:18 +00:00
|
|
|
# Capitalize each word in title
|
|
|
|
title = " ".join(word.capitalize() for word in title.split())
|
2019-02-08 16:59:05 +00:00
|
|
|
title = selectors()['custom'].format(custom=title)
|
|
|
|
|
|
|
|
elif jbop == 'random':
|
|
|
|
if not limit:
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("Random selector needs a limit. Use --limit.")
|
2019-02-08 16:59:05 +00:00
|
|
|
exit()
|
|
|
|
title = selectors()['random'].format(count=limit, libraries='/'.join(libraries.values()))
|
|
|
|
|
2019-02-19 05:10:31 +00:00
|
|
|
elif jbop == 'popularTv':
|
|
|
|
title = selectors()['popularTv'].format(days=days)
|
|
|
|
elif jbop == 'popularMovies':
|
|
|
|
title = selectors()['popularMovies'].format(days=days)
|
2019-02-08 16:59:05 +00:00
|
|
|
|
|
|
|
return title
|
|
|
|
|
|
|
|
|
2022-04-21 18:03:46 +00:00
|
|
|
def export_min(item):
|
2020-07-13 23:48:24 +00:00
|
|
|
"""
|
2022-04-21 18:03:46 +00:00
|
|
|
|
2020-07-13 23:48:24 +00:00
|
|
|
Parameters
|
|
|
|
----------
|
2022-04-21 18:03:46 +00:00
|
|
|
item: class
|
|
|
|
A Playlist item
|
2020-07-13 23:48:24 +00:00
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
2022-04-21 18:03:46 +00:00
|
|
|
export_dict
|
2020-07-13 23:48:24 +00:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if item.isPartialObject:
|
|
|
|
item.reload()
|
|
|
|
except:
|
|
|
|
pass
|
2022-04-21 18:03:46 +00:00
|
|
|
export_dict = dict()
|
2020-07-13 23:48:24 +00:00
|
|
|
item_dict = vars(item)
|
2022-04-21 18:03:46 +00:00
|
|
|
guids = []
|
|
|
|
if item.TYPE in ['track', 'episode']:
|
|
|
|
export_list = ['grandparentGuid', 'grandparentTitle', 'parentGuid', 'parentTitle', 'guid', 'title', 'guids']
|
|
|
|
else:
|
|
|
|
export_list = ['guid', 'title', 'guids']
|
|
|
|
for export in item_dict.items():
|
|
|
|
if export[0] in export_list:
|
|
|
|
if export[0] == 'guids':
|
|
|
|
for guid in export[1]:
|
|
|
|
guids.append(guid.id)
|
|
|
|
export_dict.update({'guids': guids})
|
|
|
|
else:
|
|
|
|
export_dict.update(dict([export]))
|
|
|
|
|
|
|
|
return export_dict
|
2020-07-13 23:48:24 +00:00
|
|
|
|
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
if __name__ == "__main__":
|
2019-06-21 06:55:11 +00:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description="Create, share, and clean Playlists for users.",
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
2018-10-19 05:14:24 +00:00
|
|
|
# todo-me use parser grouping instead of choices for action and jbop?
|
2018-11-04 05:34:35 +00:00
|
|
|
parser.add_argument('--jbop', choices=selectors().keys(), metavar='',
|
2018-09-26 13:10:40 +00:00
|
|
|
help='Playlist selector.\n'
|
|
|
|
'Choices: (%(choices)s)')
|
2018-10-19 05:32:00 +00:00
|
|
|
parser.add_argument('--action', required=True, choices=actions(),
|
|
|
|
help='Action selector.'
|
|
|
|
'{}'.format(actions.__doc__))
|
2020-12-05 04:17:25 +00:00
|
|
|
parser.add_argument('--users', nargs='+', choices=user_lst, metavar='',
|
2018-12-21 05:14:11 +00:00
|
|
|
help='The Plex usernames to create/share to or delete from. Allowed names are:\n'
|
2018-09-26 13:10:40 +00:00
|
|
|
'Choices: %(choices)s')
|
2018-10-19 03:57:42 +00:00
|
|
|
parser.add_argument('--allUsers', default=False, action='store_true',
|
|
|
|
help='Select all users.')
|
2019-01-03 15:39:55 +00:00
|
|
|
parser.add_argument('--libraries', nargs='+', choices=sections_dict.values(), metavar='',
|
2018-12-21 05:14:11 +00:00
|
|
|
help='Space separated list of case sensitive names to process. Allowed names are:\n'
|
2018-09-26 13:10:40 +00:00
|
|
|
'Choices: %(choices)s')
|
2018-12-23 07:02:22 +00:00
|
|
|
parser.add_argument('--allLibraries', default=False, action='store_true',
|
|
|
|
help='Select all libraries.')
|
2018-09-26 13:10:40 +00:00
|
|
|
parser.add_argument('--self', default=False, action='store_true',
|
2018-12-21 05:14:11 +00:00
|
|
|
help='Create playlist for admin.\n'
|
2018-09-26 13:10:40 +00:00
|
|
|
'Default: %(default)s')
|
|
|
|
parser.add_argument('--days', type=str, default=DAYS,
|
2018-12-21 05:14:11 +00:00
|
|
|
help='The time range to calculate statistics.\n'
|
2018-09-26 13:10:40 +00:00
|
|
|
'Default: %(default)s')
|
|
|
|
parser.add_argument('--top', type=str, default=TOP,
|
2018-12-21 05:14:11 +00:00
|
|
|
help='The number of top items to list.\n'
|
2018-09-26 13:10:40 +00:00
|
|
|
'Default: %(default)s')
|
2019-01-04 15:03:10 +00:00
|
|
|
parser.add_argument('--playlists', nargs='+', metavar='',
|
|
|
|
help='Enter Playlist name to be managed.')
|
2019-01-03 16:14:32 +00:00
|
|
|
parser.add_argument('--allPlaylists', default=False, action='store_true',
|
|
|
|
help='Select all playlists.')
|
2018-11-03 13:37:33 +00:00
|
|
|
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.')
|
2018-12-22 02:17:08 +00:00
|
|
|
parser.add_argument('--filter', action='append', type=lambda kv: kv.split("="),
|
2018-12-23 07:03:01 +00:00
|
|
|
help='Search filtered metadata fields.\n'
|
2019-05-28 18:09:09 +00:00
|
|
|
'Filters: ({}).'.format(', '.join(filters_lst)))
|
2019-02-19 15:07:59 +00:00
|
|
|
parser.add_argument('--search', action='append', type=lambda kv: kv.split("="),
|
2018-12-22 02:17:08 +00:00
|
|
|
help='Search non-filtered metadata fields for keywords '
|
|
|
|
'in title, summary, etc.')
|
2020-07-13 23:45:50 +00:00
|
|
|
parser.add_argument('--export', choices=['csv', 'json'], default='json',
|
|
|
|
help='Space separated list of case sensitive names to process. Allowed names are:\n'
|
|
|
|
'Choices: %(choices)s\nDefault: %(default)s)')
|
2022-04-21 18:43:28 +00:00
|
|
|
parser.add_argument('--importJson', nargs='?', type=str, choices=json_check, metavar='',
|
|
|
|
help='Filename of json file to use. \n(choices: %(choices)s)')
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
opts = parser.parse_args()
|
|
|
|
|
2019-01-01 04:38:30 +00:00
|
|
|
title = ''
|
2018-12-21 21:24:32 +00:00
|
|
|
search = ''
|
2019-01-01 04:38:30 +00:00
|
|
|
filters = ''
|
2019-01-03 16:14:32 +00:00
|
|
|
playlists = []
|
2019-01-01 04:38:30 +00:00
|
|
|
keys_list = []
|
2019-02-05 05:48:35 +00:00
|
|
|
playlist_dict = {'data': []}
|
2019-02-08 16:59:05 +00:00
|
|
|
|
2018-12-21 21:05:17 +00:00
|
|
|
if opts.search:
|
2019-02-18 19:24:40 +00:00
|
|
|
search = dict(opts.search)
|
2019-05-10 04:05:55 +00:00
|
|
|
for k, v in search.items():
|
2019-05-28 17:34:53 +00:00
|
|
|
# If comma separated search then consider searching values with AND statement
|
2019-05-10 04:05:55 +00:00
|
|
|
if "," in v:
|
|
|
|
search[k] = v.split(",")
|
2019-01-01 04:38:30 +00:00
|
|
|
if opts.filter:
|
2019-05-28 17:34:53 +00:00
|
|
|
if len(opts.filter) >= 2:
|
|
|
|
# Check if filter key was used twice or more
|
|
|
|
filter_key = opts.filter[0][0]
|
|
|
|
filter_count = sum(f.count(filter_key) for f in opts.filter)
|
|
|
|
# If filter key used more than once than consider filtering values with OR statement
|
|
|
|
if filter_count > 1:
|
|
|
|
filters_lst = []
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-01 04:38:30 +00:00
|
|
|
filters = dict(opts.filter)
|
2019-05-28 17:34:53 +00:00
|
|
|
for k, v in filters.items():
|
|
|
|
# If comma separated filter then consider filtering values with AND statement
|
|
|
|
if "," in v:
|
|
|
|
filters[k] = v.split(",")
|
2019-01-01 14:26:50 +00:00
|
|
|
# Check if provided filter exist, exit if it doesn't exist
|
2019-05-28 18:09:09 +00:00
|
|
|
if not (set(filters.keys()) & set(filters_lst)):
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.error('({}) was not found in filters list: [{}]'
|
2019-05-28 18:09:09 +00:00
|
|
|
.format(' '.join(filters.keys()), ', '.join(filters_lst)))
|
2019-01-01 14:26:50 +00:00
|
|
|
exit()
|
2018-10-19 04:32:55 +00:00
|
|
|
|
2018-10-19 03:57:42 +00:00
|
|
|
# Defining users
|
2020-12-05 04:17:25 +00:00
|
|
|
users = exclusions(opts.allUsers, opts.users, user_lst)
|
2019-01-04 18:09:43 +00:00
|
|
|
|
2018-12-23 07:02:22 +00:00
|
|
|
# Defining libraries
|
2019-01-04 18:09:43 +00:00
|
|
|
libraries = exclusions(opts.allLibraries, opts.libraries, sections_dict)
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-06 01:22:32 +00:00
|
|
|
# Defining selected playlists
|
2021-02-27 07:05:39 +00:00
|
|
|
selected_playlists = exclusions(opts.allPlaylists, opts.playlists, admin_playlist_lst)
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-12-21 05:14:11 +00:00
|
|
|
# Create user server objects
|
2018-10-19 03:57:42 +00:00
|
|
|
if users:
|
|
|
|
for user in users:
|
2019-02-02 14:23:54 +00:00
|
|
|
# todo-me smart playlists will have to recreated in users server instance
|
2019-01-06 01:22:32 +00:00
|
|
|
if opts.action == 'share' and selected_playlists:
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("Sharing playlist(s)...")
|
2019-01-06 01:22:32 +00:00
|
|
|
share_playlists(selected_playlists, users)
|
2018-09-26 13:10:40 +00:00
|
|
|
user_acct = account.user(user)
|
2019-01-04 18:21:34 +00:00
|
|
|
user_server = PlexServer(PLEX_URL, user_acct.get_token(plex.machineIdentifier))
|
2020-07-13 23:47:10 +00:00
|
|
|
all_playlists = [pl for pl in user_server.playlists()]
|
2019-01-06 01:22:32 +00:00
|
|
|
user_selected = exclusions(opts.allPlaylists, opts.playlists, all_playlists)
|
2019-02-02 14:44:55 +00:00
|
|
|
playlist_dict['data'].append({
|
2019-01-04 18:21:34 +00:00
|
|
|
'server': user_server,
|
|
|
|
'user': user,
|
2019-01-06 01:22:32 +00:00
|
|
|
'user_selected': user_selected,
|
2019-02-08 16:59:05 +00:00
|
|
|
'all_playlists': all_playlists})
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-06 01:22:32 +00:00
|
|
|
if opts.self or not users:
|
2019-06-21 06:55:11 +00:00
|
|
|
playlist_dict['data'].append({
|
|
|
|
'server': plex,
|
|
|
|
'user': 'admin',
|
|
|
|
'user_selected': selected_playlists,
|
2021-02-27 07:05:39 +00:00
|
|
|
'all_playlists': admin_playlist_lst})
|
2019-02-02 14:44:55 +00:00
|
|
|
|
|
|
|
if not opts.jbop and opts.action == 'show':
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("Displaying the user's playlist(s)...")
|
2019-02-02 14:23:54 +00:00
|
|
|
for data in playlist_dict['data']:
|
|
|
|
user = data['user']
|
2020-12-05 04:17:45 +00:00
|
|
|
playlists = [playlist if isinstance(playlist, str) else playlist.title for playlist in data['all_playlists']]
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("{}'s current playlist(s): {}".format(user, ', '.join(playlists)))
|
2019-01-05 06:07:01 +00:00
|
|
|
exit()
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2022-04-21 18:43:28 +00:00
|
|
|
if libraries and opts.importJson == None:
|
2019-02-08 16:59:05 +00:00
|
|
|
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)
|
2019-02-09 06:14:21 +00:00
|
|
|
|
|
|
|
# Remove or build playlists
|
|
|
|
if opts.action == 'remove':
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("Deleting the playlist(s)...")
|
2019-02-09 06:14:21 +00:00
|
|
|
for data in playlist_dict['data']:
|
|
|
|
titles = data['user_selected']
|
2020-11-03 05:36:16 +00:00
|
|
|
# if no titles were selected then assume jbop, libraries, and title used
|
|
|
|
titles = titles if titles else title
|
2019-02-09 06:14:21 +00:00
|
|
|
delete_playlist(data, titles)
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-02 17:48:56 +00:00
|
|
|
# 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):
|
2019-02-03 21:58:40 +00:00
|
|
|
if opts.jbop == 'random':
|
|
|
|
keys_list = random.sample((keys_list), opts.limit)
|
|
|
|
else:
|
|
|
|
keys_list = keys_list[:opts.limit]
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2019-01-03 16:14:32 +00:00
|
|
|
# Setting custom name if provided
|
2019-01-01 14:26:50 +00:00
|
|
|
if opts.name:
|
|
|
|
title = opts.name
|
2020-03-12 19:50:59 +00:00
|
|
|
elif not opts.name and opts.jbop in ['collection', 'label']:
|
|
|
|
logger.error("If using --jbop collection or label, "
|
|
|
|
"you must provide a custom name")
|
|
|
|
exit()
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-12-21 14:41:10 +00:00
|
|
|
if opts.jbop and opts.action == 'show':
|
2019-07-29 15:20:52 +00:00
|
|
|
if len(libraries) > 0:
|
|
|
|
show_playlist(title, keys_list)
|
|
|
|
else:
|
|
|
|
logger.error("Missing --libraries or --allLibraries")
|
|
|
|
exit()
|
2018-09-26 13:10:40 +00:00
|
|
|
|
|
|
|
if opts.action == 'update':
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("Deleting the playlist(s)...")
|
2019-02-02 14:44:55 +00:00
|
|
|
for data in playlist_dict['data']:
|
2021-08-04 12:40:38 +00:00
|
|
|
delete_playlist(data, title, opts.jbop)
|
|
|
|
logger.info('Creating playlist(s)...')
|
2019-02-02 14:44:55 +00:00
|
|
|
for data in playlist_dict['data']:
|
|
|
|
create_playlist(title, keys_list, data['server'], data['user'])
|
2019-06-21 06:55:11 +00:00
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
if opts.action == 'add':
|
2020-03-12 19:51:35 +00:00
|
|
|
if opts.jbop == 'collection':
|
|
|
|
logger.info('Creating collection(s)...')
|
|
|
|
for key in keys_list:
|
|
|
|
item = plex.fetchItem(int(key))
|
|
|
|
item.addCollection([title])
|
|
|
|
|
|
|
|
elif opts.jbop == 'label':
|
|
|
|
logger.info('Creating label(s)...')
|
|
|
|
for key in keys_list:
|
|
|
|
item = plex.fetchItem(int(key))
|
|
|
|
item.addLabel([title])
|
|
|
|
else:
|
|
|
|
logger.info('Creating playlist(s)...')
|
|
|
|
for data in playlist_dict['data']:
|
|
|
|
create_playlist(title, keys_list, data['server'], data['user'])
|
2020-07-13 23:48:24 +00:00
|
|
|
|
|
|
|
if opts.action == 'export':
|
|
|
|
logger.info("Exporting the user's playlist(s)...")
|
|
|
|
# Only import if exporting
|
|
|
|
import json
|
2020-07-17 18:12:47 +00:00
|
|
|
import csv
|
2020-07-13 23:48:24 +00:00
|
|
|
from flatten_json import flatten
|
|
|
|
|
|
|
|
for data in playlist_dict['data']:
|
|
|
|
user = data['user']
|
|
|
|
if data['user_selected']:
|
|
|
|
playlists = data['user_selected']
|
|
|
|
else:
|
|
|
|
playlists = data['all_playlists']
|
|
|
|
for pl in playlists:
|
2022-04-21 18:42:21 +00:00
|
|
|
logger.info("Exporting {}'s playlist: {}".format(user, pl.title))
|
2022-04-22 02:35:03 +00:00
|
|
|
pl_dict = {'title': pl.title}
|
|
|
|
if pl.smart:
|
|
|
|
pl_dict['smartFilters'] = pl.filters()
|
|
|
|
else:
|
|
|
|
pl_dict['items'] = []
|
|
|
|
items = plex.fetchItem(pl.ratingKey).items()
|
|
|
|
for item in items:
|
|
|
|
item_dict = export_min(item)
|
|
|
|
pl_dict['items'].append(item_dict)
|
2020-07-13 23:48:24 +00:00
|
|
|
|
2022-04-21 18:04:42 +00:00
|
|
|
title = pl.title
|
2020-07-13 23:48:24 +00:00
|
|
|
output_file = '{}-{}-Playlist.{}'.format(user, title, opts.export)
|
|
|
|
if opts.export == 'json':
|
|
|
|
with open(output_file, 'w') as fp:
|
2022-04-21 18:05:57 +00:00
|
|
|
json.dump(pl_dict, fp, indent=4, sort_keys=True)
|
2020-07-13 23:48:24 +00:00
|
|
|
elif opts.export == 'csv':
|
|
|
|
columns = []
|
|
|
|
data_list = []
|
2022-04-21 18:05:57 +00:00
|
|
|
for rows in pl_dict['items']:
|
2020-07-13 23:48:24 +00:00
|
|
|
flat_data = flatten(rows)
|
2022-04-21 18:05:57 +00:00
|
|
|
columns += list(rows)
|
|
|
|
data_list.append(rows)
|
2020-07-17 18:12:47 +00:00
|
|
|
with open(output_file, 'w', encoding='UTF-8', newline='') as data_file:
|
2020-07-13 23:48:24 +00:00
|
|
|
columns = sorted(list(set(columns)))
|
2020-07-17 18:12:47 +00:00
|
|
|
|
|
|
|
writer = csv.DictWriter(data_file, fieldnames=columns)
|
|
|
|
writer.writeheader()
|
2022-04-21 18:05:57 +00:00
|
|
|
for data in pl_dict['items']:
|
2020-07-17 18:12:47 +00:00
|
|
|
writer.writerow(data)
|
2020-07-13 23:48:24 +00:00
|
|
|
|
2022-04-21 18:43:28 +00:00
|
|
|
logger.info("Exported (./{})".format(output_file))
|
|
|
|
|
|
|
|
if opts.action == 'import':
|
|
|
|
if len(opts.libraries) > 1:
|
|
|
|
logger.error("Import can only use one library.")
|
|
|
|
exit()
|
|
|
|
logger.info('Using {} file to import.'.format(opts.importJson))
|
|
|
|
with open(''.join(opts.importJson)) as json_data:
|
|
|
|
import_json = json.load(json_data)
|
|
|
|
title = import_json['title']
|
2022-04-22 02:35:03 +00:00
|
|
|
section = opts.libraries[0]
|
|
|
|
if import_json.get('items'):
|
|
|
|
items = []
|
|
|
|
for item in import_json['items']:
|
|
|
|
import_library = plex.library.section(opts.libraries[0])
|
|
|
|
plex_guid = item['guid'].rsplit('/', 1)[1]
|
|
|
|
item_guid = plex.library.search(guid=item['guid'])
|
|
|
|
items += item_guid
|
|
|
|
logger.info("Total items from playlist to import: {}".format(len(items)))
|
|
|
|
for user in playlist_dict['data']:
|
|
|
|
logger.info("Importing playlist {} to user {}".format(title, user['user']))
|
|
|
|
user['server'].createPlaylist(title, section=section, items=items)
|
|
|
|
else:
|
|
|
|
logger.info("Importing Smart Playlist: {}".format(title))
|
|
|
|
limit = import_json["smartFilters"].get("limit")
|
|
|
|
libtype = import_json["smartFilters"].get("libtype")
|
|
|
|
sort = import_json["smartFilters"].get("sort")
|
|
|
|
filters = import_json["smartFilters"].get("filters")
|
|
|
|
for user in playlist_dict['data']:
|
|
|
|
logger.info("Importing playlist {} to user {}".format(title, user['user']))
|
|
|
|
user['server'].createPlaylist(title, section=section, smart=True, limit=limit,
|
|
|
|
libtype=libtype, sort=sort, filters=filters)
|
2022-04-21 18:43:28 +00:00
|
|
|
|
2018-09-26 13:10:40 +00:00
|
|
|
|
2019-07-29 15:20:52 +00:00
|
|
|
logger.info("Done.")
|