plex-prerolls/schedule_preroll.py

399 lines
15 KiB
Python
Raw Normal View History

2021-01-01 16:12:22 +00:00
#!/usr/bin/python
"""Schedule Plex server related Pre-roll Intro videos
A helper script to automate management of Plex pre-rolls.
Define when you want different pre-rolls to play throughout the year.
Set it and forget it!
Optional Arguments:
2021-01-01 18:51:22 +00:00
-h, --help show this help message and exit
-v, --version show the version number and exit
-l LOG_CONFIG_FILE, --logconfig-path LOG_CONFIG_FILE
Path to logging config file. [Default: ./logging.conf]
-c CONFIG_FILE, --config-path CONFIG_FILE
Path to Config.ini to use for Plex Server info. [Default: ./config.ini]
-s SCHEDULE_FILE, --schedule-path SCHEDULE_FILE
Path to pre-roll schedule file (YAML) to be use. [Default: ./preroll_schedules.yaml]
2021-01-01 16:12:22 +00:00
Requirements:
- See Requirements.txt for Python modules
Scheduling:
Add to system scheduler such as:
> crontab -e
> 0 0 * * * python path/to/schedule_preroll.py >/dev/null 2>&1
Raises:
FileNotFoundError: [description]
KeyError: [description]
ConfigError: [description]
FileNotFoundError: [description]
"""
import os
import sys
import logging
import logging.config
import requests
import datetime
import yaml
from argparse import ArgumentParser
from configparser import ConfigParser
from configparser import Error as ConfigError
from plexapi.server import PlexServer, CONFIG
# import local modules
import plexutil
logger = logging.getLogger(__name__)
filename = os.path.basename(sys.argv[0])
SCRIPT_NAME = os.path.splitext(filename)[0]
def getArguments():
"""Return command line arguments
See https://docs.python.org/3/howto/argparse.html
Returns:
argparse.Namespace: Namespace object
"""
description = 'Automate scheduling of pre-roll intros for Plex'
2021-01-01 18:31:15 +00:00
version = '0.7.2'
2021-01-01 16:12:22 +00:00
config_default = './config.ini'
2021-01-01 18:31:15 +00:00
log_config_default = './logging.conf'
2021-01-01 16:12:22 +00:00
schedule_default = './preroll_schedules.yaml'
parser = ArgumentParser(description='{}'.format(description))
parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(version), help='show the version number and exit')
2021-01-01 18:31:15 +00:00
parser.add_argument('-l', '--logconfig-path', dest='log_config_file', default=log_config_default, action='store', help='Path to logging config file. [Default: {}]'.format(log_config_default))
parser.add_argument('-c', '--config-path', dest='config_file', action='store', help='Path to Config.ini to use for Plex Server info. [Default: {}]'.format(config_default))
parser.add_argument('-s', '--schedule-path', dest='schedule_file', action='store', help='Path to pre-roll schedule file (YAML) to be use. [Default: {}]'.format(schedule_default))
2021-01-01 16:12:22 +00:00
args = parser.parse_args()
return args
def getYAMLSchema():
"""Return the main schema layout of the preroll_schedules.yaml file
Returns:
dict: Dict of main schema items
"""
schema = {'default': None, 'monthly': None, 'weekly': None, 'date_range': None, 'misc': None}
return schema
def getWeekRange(year, weeknum):
"""Return the starting/ending date range of a given year/week
Args:
2021-01-01 18:31:15 +00:00
year (int): Year to calc range for
weeknum (int): Month of the year (1-12)
2021-01-01 16:12:22 +00:00
Returns:
Date: Start date of the Year/Month
Date: End date of the Year/Month
"""
start = datetime.datetime.strptime('{}-W{}-0'.format(year, int(weeknum)-1), "%Y-W%W-%w").date()
end = start + datetime.timedelta(days=6)
return start, end
def getMonthRange(year, monthnum):
"""Return the starting/ending date range of a given year/month
Args:
2021-01-01 18:31:15 +00:00
year (int): Year to calc range for
2021-01-01 16:12:22 +00:00
monthnum (int): Month of the year (1-12)
Returns:
Date: Start date of the Year/Month
Date: End date of the Year/Month
"""
start = datetime.date(year, monthnum, 1)
next_month = start.replace(day=28) + datetime.timedelta(days=4)
end = next_month - datetime.timedelta(days=next_month.day)
return start, end
2021-01-01 18:31:15 +00:00
def getPrerollSchedule(schedule_file=None):
2021-01-01 16:12:22 +00:00
"""Return a listing of defined preroll schedules for searching/use
2021-01-01 18:31:15 +00:00
Args:
schedule_file (string): path/to/schedule_preroll.yaml style config file (YAML Format)
2021-01-01 16:12:22 +00:00
Raises:
FileNotFoundError: If no schedule config file exists
Returns:
list: list of schedules (Dict: {Type, StartDate, EndDate, Path})
"""
default_files = ['preroll_schedules.yaml', 'preroll_schedules.yml']
2021-01-01 18:31:15 +00:00
filename = None
if schedule_file:
if os.path.exists(schedule_file):
filename = schedule_file
2021-01-01 16:12:22 +00:00
else:
2021-01-01 18:31:15 +00:00
raise FileNotFoundError('Preroll Schedule file -s "{}" not found'.format(schedule_file))
2021-01-01 16:12:22 +00:00
else:
for f in default_files:
if os.path.exists(f):
2021-01-01 18:31:15 +00:00
filename = f
2021-01-01 16:12:22 +00:00
break
# if we still cant find a schedule file, we hae to abort
2021-01-01 18:31:15 +00:00
if not filename:
2021-01-01 16:12:22 +00:00
msg = 'No {} Found'.format(' / '.join(default_files))
logger.critical(msg)
raise FileNotFoundError(msg)
2021-01-01 18:31:15 +00:00
with open(filename, 'r') as file:
2021-01-01 16:12:22 +00:00
#contents = yaml.load(file, Loader=yaml.SafeLoader)
contents = yaml.load(file, Loader=yaml.FullLoader)
today = datetime.date.today()
schedule = []
for schedule_type in getYAMLSchema():
if schedule_type == 'weekly':
try:
use = contents[schedule_type]['enabled']
if use:
for i in range(1,53):
try:
path = contents[schedule_type][i]
if path:
entry = {}
start, end = getWeekRange(today.year, i)
entry['Type'] = schedule_type
entry['StartDate'] = start
entry['EndDate'] = end
entry['Path'] = path
schedule.append(entry)
except KeyError as e:
# skip KeyError for missing Weeks
logger.debug('Key Value not found: "{}"->"{}", skipping week'.format(schedule_type, i))
continue
except KeyError as e:
logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e)
raise e
elif schedule_type == 'monthly':
try:
use = contents[schedule_type]['enabled']
if use:
for i in range(1,13):
month_abrev = datetime.date(today.year, i, 1).strftime('%b').lower()
try:
path = contents[schedule_type][month_abrev]
if path:
entry = {}
start, end = getMonthRange(today.year, i)
entry['Type'] = schedule_type
entry['StartDate'] = start
entry['EndDate'] = end
entry['Path'] = path
schedule.append(entry)
except KeyError as e:
# skip KeyError for missing Months
logger.warning('Key Value not found: "{}"->"{}", skipping month'.format(schedule_type, month_abrev))
continue
except KeyError as e:
logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e)
raise e
elif schedule_type == 'date_range':
try:
use = contents[schedule_type]['enabled']
if use:
for r in contents[schedule_type]['ranges']:
try:
path = r['path']
if path:
entry = {}
entry['Type'] = schedule_type
entry['StartDate'] = r['start_date']
entry['EndDate'] = r['end_date']
entry['Path'] = path
schedule.append(entry)
except KeyError as e:
#logger.error('Key Value not found: "{}"'.format(schedule_type), exc_info=e)
raise e
except KeyError as e:
logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e)
raise e
elif schedule_type == 'misc':
try:
use = contents[schedule_type]['enabled']
if use:
try:
path = contents[schedule_type]['always_use']
if path:
entry = {}
entry['Type'] = schedule_type
entry['StartDate'] = datetime.date(today.year, today.month, today.day)
entry['EndDate'] = datetime.date(today.year, today.month, today.day)
entry['Path'] = path
schedule.append(entry)
except KeyError as e:
#logger.error('Key Value not found: "{}"'.format(schedule_type), exc_info=e)
raise e
except KeyError as e:
logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e)
raise e
elif schedule_type == 'default':
try:
use = contents[schedule_type]['enabled']
if use:
try:
path = contents[schedule_type]['path']
if path:
entry = {}
entry['Type'] = schedule_type
entry['StartDate'] = datetime.date(today.year, today.month, today.day)
entry['EndDate'] = datetime.date(today.year, today.month, today.day)
entry['Path'] = path
schedule.append(entry)
except KeyError as e:
#logger.error('Key Value not found: "{}"'.format(schedule_type), exc_info=e)
raise e
except KeyError as e:
logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e)
raise e
else:
continue
# Sort list so most recent Ranges appear first
schedule.sort(reverse=True, key=lambda x:x['StartDate'])
return schedule
def buildListingString(items, play_all=False):
"""Build the Plex formatted string of preroll paths
Args:
2021-01-01 18:31:15 +00:00
items (list): List of preroll video paths to place into a string listing
play_all (bool, optional): Play all videos. [Default: False (Random choice)]
2021-01-01 16:12:22 +00:00
Returns:
string: CSV Listing (, or ;) based on play_all param of preroll video paths
"""
if play_all:
# use , to play all entries
listing = ','.join(items)
else:
pass
#use ; to play random selection
listing = ';'.join(items)
return listing
2021-01-01 18:31:15 +00:00
def getPrerollListingString(schedule, for_date=None):
2021-01-01 16:12:22 +00:00
"""Return listing of preroll videos to be used by Plex
2021-01-01 18:31:15 +00:00
Args:
schedule (list): List of schedule entries (See: getPrerollSchedule)
for_Date (date, optional): Date to process pre-roll string for [Default: Today]
Useful if wanting to test what different schedules produce
2021-01-01 16:12:22 +00:00
Returns:
2021-01-01 18:31:15 +00:00
string: listing of preroll video paths to be used for Extras. CSV style: (;|,)
2021-01-01 16:12:22 +00:00
"""
listing = ''
entries = dict(getYAMLSchema())
2021-01-01 18:31:15 +00:00
# prep the storage lists
2021-01-01 16:12:22 +00:00
for e in getYAMLSchema():
entries[e] = []
2021-01-01 18:31:15 +00:00
# determine which date to build the listing for
if for_date:
check_date = for_date
else:
check_date = datetime.date.today()
# process the schedule for the given date
2021-01-01 16:12:22 +00:00
for entry in schedule:
2021-01-01 18:31:15 +00:00
try:
if entry['StartDate'] <= check_date <= entry['EndDate']:
2021-01-01 16:12:22 +00:00
entry_type = entry['Type']
2021-01-01 18:31:15 +00:00
path = entry['Path']
2021-01-01 16:12:22 +00:00
2021-01-01 18:31:15 +00:00
if path:
entries[entry_type].append(path)
except KeyError as ke:
logger.warning('KeyError with entry "{}"'.format(entry), exc_info=ke)
continue
2021-01-01 16:12:22 +00:00
# Build the merged output based or order of Priority
merged_list = []
if entries['misc']:
merged_list.extend(entries['misc'])
if entries['date_range']:
merged_list.extend(entries['date_range'])
if entries['weekly'] and not entries['date_range']:
merged_list.extend(entries['weekly'])
if entries['monthly'] \
and not entries['weekly'] and not entries['date_range']:
merged_list.extend(entries['monthly'])
if entries['default'] \
and not entries['monthly'] and not entries['weekly'] and not entries['date_range']:
merged_list.extend(entries['default'])
listing = buildListingString(merged_list)
return listing
2021-01-01 18:31:15 +00:00
def savePrerollList(plex, preroll_listing):
2021-01-01 16:12:22 +00:00
"""Save Plex Preroll info to PlexServer settings
Args:
plex (PlexServer): Plex server to update
preroll_listing (string, list): csv listing or List of preroll paths to save
"""
# if happend to send in an Iterable List, merge to a string
if type(preroll_listing) is list:
preroll_listing = buildListingString(preroll_listing)
plex.settings.get('cinemaTrailersPrerollID').set(preroll_listing)
plex.settings.save()
if __name__ == '__main__':
args = getArguments()
2021-01-01 18:31:15 +00:00
plexutil.setupLogger(args.log_config_file)
2021-01-01 16:12:22 +00:00
cfg = plexutil.getPlexConfig(args.config_file)
# Initialize Session information
sess = requests.Session()
# Ignore verifying the SSL certificate
sess.verify = False # '/path/to/certfile'
# If verify is set to a path of a directory (not a cert file),
# the directory needs to be processed with the c_rehash utility
# from 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)
try:
plex = PlexServer(cfg['PLEX_URL'], cfg['PLEX_TOKEN'], session=sess)
except Exception as e:
logger.error('Error Connecting to Plex', exc_info=e)
raise e
2021-01-01 18:31:15 +00:00
schedule = getPrerollSchedule(args.schedule_file)
prerolls = getPrerollListingString(schedule)
2021-01-01 16:12:22 +00:00
logger.info('Saving Preroll List: "{}"'.format(prerolls))
2021-01-01 18:31:15 +00:00
savePrerollList(plex, prerolls)