diff --git a/README.md b/README.md index bc0d085..6f49497 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,11 @@ git clone https://github.com/BrianLindner/plex-schedule-prerolls.git ### Install Requirements -See `requirements.txt` for Python modules [link](requirements.txt) +Requires: + +- Python 3.8+ [may work on 3.5+ but not tested] + +See `requirements.txt` for Python modules used [link](requirements.txt) Install Python requirements @@ -102,8 +106,8 @@ monthly: date_range: enabled: (yes/no) ranges: - - start_date: 2020-01-01 - end_date: 2020-01-01 + - start_date: 2020-01-01 12:01:00 + end_date: 2020-01-01 16:59:59 path: /path/to/video.mp4 - start_date: 2020-07-03 end_date: 2020-07-05 @@ -124,6 +128,15 @@ default: path: /path/to/file.mp4;/path/to/file.mp4 ``` +#### Date Range Section: +Use it for *Day* or *Ranges of Time* needs; \ +Now with Time support! + +Formatting Supported: + +- Dates: yyyy-mm-dd +- DateTime: yyyy-mm-dd hh:mm:ss (24hr time format) + ### (Optional) Config `logger.conf` to your needs See: [https://docs.python.org/3/howto/logging.html](https://docs.python.org/3/howto/logging.html) @@ -213,4 +226,4 @@ useful if running other scripts/commands, using venv encapsulation, customizing ## Shout out to places to get Pre-Roll -[https://prerolls.video](https://prerolls.video) \ No newline at end of file +[https://prerolls.video](https://prerolls.video) diff --git a/plexutil.py b/plexutil.py index a2764d0..e6a548e 100644 --- a/plexutil.py +++ b/plexutil.py @@ -10,6 +10,7 @@ Raises: import os import sys import logging +import logging.config from configparser import ConfigParser from plexapi.server import PlexServer, CONFIG @@ -30,11 +31,12 @@ def getPlexConfig(config_file=None): Raises: KeyError: Config Params not found in config file(s) - FileNotFoundError: If cannot find a config file + FileNotFoundError: Cannot find a config file Returns: dict: Dict of config params {PLEX_URL, PLEX_TOKEN} """ + cfg = {} plex_url = '' plex_token = '' @@ -48,7 +50,7 @@ def getPlexConfig(config_file=None): if os.path.exists(config_file): filename = config_file else: - raise FileNotFoundError('Config file -c "{}" not found'.format(config_file)) + raise FileNotFoundError('Config file "{}" not found'.format(config_file)) else: filename = 'config.ini' @@ -66,7 +68,8 @@ def getPlexConfig(config_file=None): 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) + msg = 'Key Value not found {}' + logger.error(msg, exc_info=e) raise e else: msg = '[auth] section not found in LOCAL config.ini file' @@ -86,7 +89,8 @@ def getPlexConfig(config_file=None): 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) + msg = 'Key Value not found' + logger.error(msg, exc_info=e) raise e else: msg = "[auth] section not found in PlexAPI MAIN config.ini file" @@ -114,6 +118,7 @@ def setupLogger(log_config): KeyError: Problems processing logging config files FileNotFoundError: Problems with log file location, other """ + if os.path.exists(log_config): try: logging.config.fileConfig(log_config, disable_existing_loggers=False) @@ -126,10 +131,12 @@ def setupLogger(log_config): if not os.path.exists(logdir): try: - logger.debug('Creating log folder "{}"'.format(logdir)) + msg = 'Creating log folder "{}"'.format(logdir) + logger.debug(msg) os.makedirs(logdir, exist_ok=True) except Exception as e: - logger.error('Error creating log folder "{}"'.format(logdir)) + msg = 'Error creating log folder "{}"'.format(logdir) + logger.error(msg, exc_info=e) raise e elif logger.handlers: # if logger config loaded, but some file error happened @@ -140,10 +147,13 @@ def setupLogger(log_config): if not os.path.exists(logdir): try: - logger.debug('Creating log folder "{}"'.format(logdir)) + msg = 'Creating log folder "{}"'.format(logdir) + logger.debug(msg) + os.makedirs(logdir, exist_ok=True) except Exception as e: - logger.error('Error creating log folder "{}"'.format(logdir)) + msg = 'Error creating log folder "{}"'.format(logdir) + logger.error(msg, exc_info=e) raise e else: # not sure the issue, raise the exception @@ -153,7 +163,8 @@ def setupLogger(log_config): logging.config.fileConfig(log_config, disable_existing_loggers=False) else: - logger.debug('Logging Config file "{}" not available, will be using defaults'.format(log_config)) + msg = 'Logging Config file "{}" not available, will be using defaults'.format(log_config) + logger.debug(msg) if __name__ == '__main__': msg = 'Script not meant to be run directly, please import into other scripts.\n\n' + \ diff --git a/schedule_preroll.py b/schedule_preroll.py index 3a20ce2..acd90a1 100644 --- a/schedule_preroll.py +++ b/schedule_preroll.py @@ -29,19 +29,20 @@ Raises: ConfigError: [description] FileNotFoundError: [description] """ + import os import sys import logging -import logging.config import requests import datetime +from datetime import datetime, date, time, timedelta 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 local util modules import plexutil logger = logging.getLogger(__name__) @@ -57,16 +58,24 @@ def getArguments(): argparse.Namespace: Namespace object """ description = 'Automate scheduling of pre-roll intros for Plex' - version = '0.7.2' + version = '0.8.0' config_default = './config.ini' log_config_default = './logging.conf' 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_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)) + 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', action='store', + default=log_config_default, + 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)) args = parser.parse_args() return args @@ -77,7 +86,8 @@ def getYAMLSchema(): Returns: dict: Dict of main schema items """ - schema = {'default': None, 'monthly': None, 'weekly': None, 'date_range': None, 'misc': None} + schema = {'default': None, 'monthly': None, + 'weekly': None, 'date_range': None, 'misc': None} return schema def getWeekRange(year, weeknum): @@ -91,8 +101,9 @@ def getWeekRange(year, weeknum): 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) + start = datetime.strptime('{}-W{}-0'.format(year, int(weeknum)-1), + "%Y-W%W-%w").date() + end = start + timedelta(days=6) return start, end @@ -107,9 +118,9 @@ def getMonthRange(year, monthnum): 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) + start = date(year, monthnum, 1) + next_month = start.replace(day=28) + timedelta(days=4) + end = next_month - timedelta(days=next_month.day) return start, end @@ -132,16 +143,17 @@ def getPrerollSchedule(schedule_file=None): if os.path.exists(schedule_file): filename = schedule_file else: - raise FileNotFoundError('Preroll Schedule file -s "{}" not found'.format(schedule_file)) + msg = 'Pre-roll Schedule file "{}" not found'.format(schedule_file) + raise FileNotFoundError(msg) else: for f in default_files: if os.path.exists(f): filename = f break - # if we still cant find a schedule file, we hae to abort + # if we still cant find a schedule file, we abort if not filename: - msg = 'No {} Found'.format(' / '.join(default_files)) + msg = 'Missing schedule file: "{}"'.format('" / "'.join(default_files)) logger.critical(msg) raise FileNotFoundError(msg) @@ -149,7 +161,7 @@ def getPrerollSchedule(schedule_file=None): #contents = yaml.load(file, Loader=yaml.SafeLoader) contents = yaml.load(file, Loader=yaml.FullLoader) - today = datetime.date.today() + today = date.today() schedule = [] for schedule_type in getYAMLSchema(): if schedule_type == 'weekly': @@ -172,10 +184,12 @@ def getPrerollSchedule(schedule_file=None): schedule.append(entry) except KeyError as e: # skip KeyError for missing Weeks - logger.debug('Key Value not found: "{}"->"{}", skipping week'.format(schedule_type, i)) + msg = 'Key Value not found: "{}"->"{}", skipping week'.format(schedule_type, i) + logger.debug(msg) continue except KeyError as e: - logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e) + msg = 'Key Value not found in "{}" section'.format(schedule_type) + logger.error(msg, exc_info=e) raise e elif schedule_type == 'monthly': try: @@ -183,7 +197,7 @@ def getPrerollSchedule(schedule_file=None): if use: for i in range(1,13): - month_abrev = datetime.date(today.year, i, 1).strftime('%b').lower() + month_abrev = date(today.year, i, 1).strftime('%b').lower() try: path = contents[schedule_type][month_abrev] @@ -198,10 +212,12 @@ def getPrerollSchedule(schedule_file=None): 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)) + msg = 'Key Value not found: "{}"->"{}", skipping month'.format(schedule_type, month_abrev) + logger.warning(msg) continue except KeyError as e: - logger.error('Key Value not found in "{}" section'.format(schedule_type), exc_info=e) + msg = 'Key Value not found in "{}" section'.format(schedule_type) + logger.error(msg, exc_info=e) raise e elif schedule_type == 'date_range': try: @@ -223,7 +239,8 @@ def getPrerollSchedule(schedule_file=None): #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) + msg = 'Key Value not found in "{}" section'.format(schedule_type) + logger.error(msg, exc_info=e) raise e elif schedule_type == 'misc': try: @@ -235,8 +252,8 @@ def getPrerollSchedule(schedule_file=None): 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['StartDate'] = date(today.year, today.month, today.day) + entry['EndDate'] = date(today.year, today.month, today.day) entry['Path'] = path schedule.append(entry) @@ -244,7 +261,8 @@ def getPrerollSchedule(schedule_file=None): #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) + msg = 'Key Value not found in "{}" section'.format(schedule_type) + logger.error(msg, exc_info=e) raise e elif schedule_type == 'default': try: @@ -256,8 +274,9 @@ def getPrerollSchedule(schedule_file=None): 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['StartDate'] = date(today.year, today.month, today.day) + entry['EndDate'] = date(today.year, today.month, today.day) + entry['Path'] = path schedule.append(entry) @@ -265,7 +284,8 @@ def getPrerollSchedule(schedule_file=None): #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) + msg = 'Key Value not found in "{}" section'.format(schedule_type) + logger.error(msg, exc_info=e) raise e else: @@ -296,42 +316,58 @@ def buildListingString(items, play_all=False): return listing -def getPrerollListingString(schedule, for_date=None): +def getPrerollListingString(schedule, for_datetime=None): """Return listing of preroll videos to be used by Plex 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 + schedule (list): List of schedule entries (See: getPrerollSchedule) + for_datetime (datetime, optional): Date to process pre-roll string for [Default: Today] + Useful if wanting to test what different schedules produce Returns: string: listing of preroll video paths to be used for Extras. CSV style: (;|,) """ listing = '' - entries = dict(getYAMLSchema()) + entries = {} # prep the storage lists - for e in getYAMLSchema(): - entries[e] = [] - + for y in getYAMLSchema(): + entries[y] = [] # determine which date to build the listing for - if for_date: - check_date = for_date + if for_datetime: + if isinstance(for_datetime, datetime): + check_datetime = for_datetime + else: + check_datetime = datetime.combine(for_datetime, datetime.now().time()) else: - check_date = datetime.date.today() + check_datetime = datetime.now() # process the schedule for the given date for entry in schedule: try: - if entry['StartDate'] <= check_date <= entry['EndDate']: + entry_start = entry['StartDate'] + entry_end = entry['EndDate'] + if not isinstance(entry_start, datetime): + entry_start = datetime.combine(entry_start, datetime.min.time()) + if not isinstance(entry_end, datetime): + entry_end = datetime.combine(entry_end, datetime.max.time()) + + msg = 'checking "{}" against: "{}" - "{}"'.format(check_datetime, entry_start, entry_end) + logger.debug(msg) + + if entry_start <= check_datetime <= entry_end: entry_type = entry['Type'] path = entry['Path'] - + + msg = 'pass: Using "{}" - "{}"'.format(entry_start, entry_end) + logger.debug(msg) + if path: entries[entry_type].append(path) except KeyError as ke: - logger.warning('KeyError with entry "{}"'.format(entry), exc_info=ke) + msg = 'KeyError with entry "{}"'.format(entry) + logger.warning(msg, exc_info=ke) continue # Build the merged output based or order of Priority @@ -389,11 +425,14 @@ if __name__ == '__main__': try: plex = PlexServer(cfg['PLEX_URL'], cfg['PLEX_TOKEN'], session=sess) except Exception as e: - logger.error('Error Connecting to Plex', exc_info=e) + msg = 'Error connecting to Plex' + logger.error(msg, exc_info=e) raise e schedule = getPrerollSchedule(args.schedule_file) prerolls = getPrerollListingString(schedule) - logger.info('Saving Preroll List: "{}"'.format(prerolls)) - savePrerollList(plex, prerolls) \ No newline at end of file + savePrerollList(plex, prerolls) + + msg = 'Saved pre-roll list to server: "{}"'.format(prerolls) + logger.info(msg)