moving code over

This commit is contained in:
Brian Lindner 2021-01-01 11:12:22 -05:00
parent dd2830294c
commit f374dda928
No known key found for this signature in database
GPG Key ID: 8A53187BAA2C7197
4 changed files with 538 additions and 0 deletions

36
logging.conf Normal file
View File

@ -0,0 +1,36 @@
[loggers]
keys=root,main
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=consoleFormatter,fileFormatter
[logger_root]
level=WARNING
handlers=consoleHandler,fileHandler
[logger_main]
level=WARNING
handlers=consoleHandler,fileHandler
qualname=__main__
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=ERROR
formatter=consoleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=logging.handlers.RotatingFileHandler
level=WARNING
formatter=fileFormatter
args=('log/schedule_preroll.log', 'a', 20000, 5)
[formatter_consoleFormatter]
format=%(levelname)-8s - %(name)-12s - %(message)s
[formatter_fileFormatter]
format=[%(asctime)s] - %(levelname)-8s - %(name)-12s - %(message)s

104
plexutil.py Normal file
View File

@ -0,0 +1,104 @@
#!/usr/bin/python
"""Plex config parsing utilities
Raises:
FileNotFoundError: [description]
KeyError: [description]
KeyError: [description]
ConfigError: [description]
"""
import os
import sys
import logging
from configparser import ConfigParser
from plexapi.server import PlexServer, CONFIG
logger = logging.getLogger(__name__)
filename = os.path.basename(sys.argv[0])
SCRIPT_NAME = os.path.splitext(filename)[0]
def getPlexConfig(config_file=None):
"""Return Plex Config paramaters for connection info {PLEX_URL, PLEX_TOKEN}\n
Attempts to use either a local config.ini (primary) as well as a PlexAPI system config.ini (secondary)
Raises:
KeyError: Config Params not found in config file(s)
FileNotFoundError: If cannot find a config file
Returns:
dict: Dict of config params {PLEX_URL, PLEX_TOKEN}
"""
cfg = {}
plex_url = ''
plex_token = ''
use_local_config = False
use_plexapi_config = False
# Look for a local Config.ini file, use settings if present
local_config = ConfigParser()
if config_file != None:
if os.path.exists(config_file):
filename = config_file
else:
raise FileNotFoundError('Config file -c "{}" not found'.format(config_file))
else:
filename = 'config.ini'
#try reading a local file
local_config.read(filename)
if len(local_config.sections()) > 0: #len(found_config) > 0:
# if local config.ini file found, try to use local first
if local_config.has_section('auth'):
try:
server = local_config['auth']
plex_url = server['server_baseurl']
plex_token = server['server_token']
if len(plex_url) > 1 and len(plex_token) > 1:
use_local_config = True
except KeyError as e:
logger.error('Key Value not found {}', exc_info=e)
raise e
else:
msg = '[auth] section not found in LOCAL config.ini file'
logger.error(msg)
raise KeyError(msg)
if not use_local_config and len(CONFIG.sections()) > 0:
# use PlexAPI Default ~/.config/plexapi/config.ini OR from PLEXAPI_CONFIG_PATH
# IF not manually set locally in local Config.ini above
# See https://python-plexapi.readthedocs.io/en/latest/configuration.html
if CONFIG.has_section('auth'):
try:
server = CONFIG.data['auth']
plex_url = server.get('server_baseurl')
plex_token = server.get('server_token')
if len(plex_url) > 1 and len(plex_token) > 1:
use_plexapi_config = True
except KeyError as e:
logger.error('Key Value not found', exc_info=e)
raise e
else:
msg = "[auth] section not found in PlexAPI MAIN config.ini file"
logger.error(msg)
raise KeyError(msg)
if not use_local_config and not use_plexapi_config:
msg = 'No Plex config information found {server_baseurl, server_token}'
logger.error(msg)
raise ConfigError(msg)
cfg['PLEX_URL'] = plex_url
cfg['PLEX_TOKEN']= plex_token
return cfg
if __name__ == '__main__':
msg = 'Script not meant to be run directly, please import into other scripts.\n\n' + \
'usage:\nimport {}'.format(SCRIPT_NAME) + '\n' + \
'cfg = {}.getPlexConfig()'.format(SCRIPT_NAME) + '\n'
logger.error(msg)

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
plexapi==4.2.*
configparser==5.0.*
requests==2.25.*
pyyaml==5.3.*

394
schedule_preroll.py Normal file
View File

@ -0,0 +1,394 @@
#!/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.
Ideas include:
- Holiday pre-roll rotations
- Special occasions
- Summer/Winter rotations
- Breaking up the monoteny
- Keeping your family/friends on their toes!
Set it and forget it!
Optional Arguments:
-h, --help show this help message and exit
-c [], --config-path [] path/to/config.ini file to use
(Optional: uses local/plexapi default path if omitted)
-s [],--schedule-path [] path/to/preroll_schedules.yaml file to use
(Optional: uses local path if omitted)
-l [], --logconfig-path [] path/to/logging.conf logging configuration file
(Optional: uses local path if omitted)
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'
version = '0.7.0'
log_default = './logging.conf'
config_default = './config.ini'
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')
parser.add_argument('-l', '--logconfig-path', dest='log_config_file', default=log_default, action='store', help='Path to logging config file. Default: {}'.format(log_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))
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:
year (int): Year to calc range for
weeknum (int): Month of the year (1-12)
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:
year (int): Year to calc range for
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
def getPrerollSchedule(filename=None):
"""Return a listing of defined preroll schedules for searching/use
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']
schedule_file = None
if filename != None:
if os.path.exists(filename):
schedule_file = filename
else:
raise FileNotFoundError('Preroll Schedule file -s "{}" not found'.format(filename))
else:
for f in default_files:
if os.path.exists(f):
schedule_file = f
break
# if we still cant find a schedule file, we hae to abort
if not schedule_file:
msg = 'No {} Found'.format(' / '.join(default_files))
logger.critical(msg)
raise FileNotFoundError(msg)
with open(schedule_file, 'r') as file:
#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:
items (list): List of preroll video paths to place into a string listing
play_all (bool, optional): Play all videos. Defaults to False (Random choice)
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
def getPrerollListingString(schedule_file=None):
"""Return listing of preroll videos to be used by Plex
Returns:
string: listing of preroll video paths to be used for Extras
"""
listing = ''
entries = dict(getYAMLSchema())
today = datetime.date.today()
for e in getYAMLSchema():
entries[e] = []
# the the time series schedule of pre-rolls
schedule = getPrerollSchedule(schedule_file)
for entry in schedule:
if entry['StartDate'] <= today <= entry['EndDate']:
try:
entry_type = entry['Type']
except KeyError:
continue
path = entry['Path']
if path:
entries[entry_type].append(path)
# 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
def setPrerollList(plex, preroll_listing):
"""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()
log_config = args.log_config_file
if os.path.exists(log_config):
logging.config.fileConfig(log_config, disable_existing_loggers=False)
else:
logger.warning('Logging Config file "{}" not available')
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
prerolls = getPrerollListingString(args.schedule_file)
logger.info('Saving Preroll List: "{}"'.format(prerolls))
setPrerollList(plex, prerolls)