diff --git a/PseudoChannel.py b/PseudoChannel.py index cd072b3..ce37f0b 100644 --- a/PseudoChannel.py +++ b/PseudoChannel.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- from src import PseudoChannelDatabase @@ -7,6 +8,8 @@ from src import Episode from src import Music from src import Video from src import PseudoDailyScheduleController +from src import GoogleCalendar +from src import PseudoChannelCommercial from plexapi.server import PlexServer @@ -16,9 +19,13 @@ from datetime import time import calendar import itertools import argparse +import textwrap from xml.dom import minidom import xml.etree.ElementTree as ET +from threading import Timer +import signal + from time import sleep import pseudo_config as config @@ -28,704 +35,1106 @@ sys.setdefaultencoding('utf-8') class PseudoChannel(): - PLEX = PlexServer(config.baseurl, config.token) + PLEX = PlexServer(config.baseurl, config.token) + MEDIA = [] + GKEY = config.gkey - MEDIA = [] + USING_GOOGLE_CALENDAR = config.useGoogleCalendar - def __init__(self): + USING_COMMERCIAL_INJECTION = config.useCommercialInjection - self.db = PseudoChannelDatabase("pseudo-channel.db") + DAILY_UPDATE_TIME = config.dailyUpdateTime - self.controller = PseudoDailyScheduleController(config.baseurl, config.token, config.plexClients) + APP_TIME_FORMAT_STR = '%I:%M:%S %p' - """Database functions. + DEBUG = config.debug_mode - update_db(): Grab the media from the Plex DB and store it in the local pseudo-channel.db. + def __init__(self): - drop_db(): Drop the local database. Fresh start. + self.db = PseudoChannelDatabase("pseudo-channel.db") - update_schedule(): Update schedule with user defined times. + self.controller = PseudoDailyScheduleController( + config.baseurl, + config.token, + config.plexClients, + self.DEBUG + ) - drop_schedule(): Drop the user defined schedule table. + """Database functions. - generate_daily_schedule(): Generates daily schedule based on the "schedule" table. - """ + update_db(): Grab the media from the Plex DB and store it in the local pseudo-channel.db. - # Print iterations progress - def print_progress(self, iteration, total, prefix='', suffix='', decimals=1, bar_length=100): - """ - Call in a loop to create terminal progress bar - @params: - iteration - Required : current iteration (Int) - total - Required : total iterations (Int) - prefix - Optional : prefix string (Str) - suffix - Optional : suffix string (Str) - decimals - Optional : positive number of decimals in percent complete (Int) - bar_length - Optional : character length of bar (Int) - """ - str_format = "{0:." + str(decimals) + "f}" - percents = str_format.format(100 * (iteration / float(total))) - filled_length = int(round(bar_length * iteration / float(total))) - bar = '█' * filled_length + '-' * (bar_length - filled_length) + drop_db(): Drop the local database. Fresh start. - sys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%', suffix)), + update_schedule(): Update schedule with user defined times. - if iteration == total: - sys.stdout.write('\n') - sys.stdout.flush() + drop_schedule(): Drop the user defined schedule table. - def update_db(self): + generate_daily_schedule(): Generates daily schedule based on the "schedule" table. + """ - print("#### Updating Local Database") + # Print iterations progress + def print_progress(self, iteration, total, prefix='', suffix='', decimals=1, bar_length=100): + """ + Call in a loop to create terminal progress bar + @params: + iteration - Required : current iteration (Int) + total - Required : total iterations (Int) + prefix - Optional : prefix string (Str) + suffix - Optional : suffix string (Str) + decimals - Optional : positive number of decimals in percent complete (Int) + bar_length - Optional : character length of bar (Int) + """ + str_format = "{0:." + str(decimals) + "f}" + percents = str_format.format(100 * (iteration / float(total))) + filled_length = int(round(bar_length * iteration / float(total))) + bar = '█' * filled_length + '-' * (bar_length - filled_length) - self.db.create_tables() + sys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%', suffix)), - libs_dict = config.plexLibraries + if iteration == total: + sys.stdout.write('\n') + sys.stdout.flush() - sections = self.PLEX.library.sections() + def update_db(self): - for section in sections: + print("#### Updating Local Database") - for correct_lib_name, user_lib_name in libs_dict.items(): + self.db.create_tables() - if section.title.lower() in [x.lower() for x in user_lib_name]: + libs_dict = config.plexLibraries - if correct_lib_name == "Movies": + sections = self.PLEX.library.sections() - sectionMedia = self.PLEX.library.section(section.title).all() + for section in sections: - for i, media in enumerate(sectionMedia): + for correct_lib_name, user_lib_name in libs_dict.items(): - self.db.add_movies_to_db(1, media.title, media.duration) + if section.title.lower() in [x.lower() for x in user_lib_name]: - self.print_progress( - i + 1, - len(sectionMedia), - prefix = 'Progress '+section.title+": ", - suffix = 'Complete', - bar_length = 40 - ) + if correct_lib_name == "Movies": + sectionMedia = self.PLEX.library.section(section.title).all() - elif correct_lib_name == "TV Shows": + for i, media in enumerate(sectionMedia): - sectionMedia = self.PLEX.library.section(section.title).all() + self.db.add_movies_to_db(1, media.title, media.duration, media.key) - for i, media in enumerate(sectionMedia): + self.print_progress( + i + 1, + len(sectionMedia), + prefix = 'Progress '+section.title+": ", + suffix = 'Complete', + bar_length = 40 + ) - backgroundImagePath = self.PLEX.library.section(section.title).get(media.title) - backgroundImgURL = '' + elif correct_lib_name == "TV Shows": - if isinstance(backgroundImagePath.art, str): + sectionMedia = self.PLEX.library.section(section.title).all() - backgroundImgURL = config.baseurl+backgroundImagePath.art+"?X-Plex-Token="+config.token + for i, media in enumerate(sectionMedia): - self.db.add_shows_to_db(2, media.title, media.duration, '', backgroundImgURL) + backgroundImagePath = self.PLEX.library.section(section.title).get(media.title) - self.print_progress( - i + 1, - len(sectionMedia), - prefix = 'Progress '+section.title+": ", - suffix = 'Complete', - bar_length = 40 - ) + backgroundImgURL = '' - #add all episodes of each tv show to episodes table - episodes = self.PLEX.library.section(section.title).get(media.title).episodes() + if isinstance(backgroundImagePath.art, str): - for episode in episodes: + backgroundImgURL = config.baseurl+backgroundImagePath.art+"?X-Plex-Token="+config.token - duration = episode.duration + self.db.add_shows_to_db(2, media.title, media.duration, '', backgroundImgURL, media.key) - if duration: + self.print_progress( + i + 1, + len(sectionMedia), + prefix = 'Progress '+section.title+": ", + suffix = 'Complete', + bar_length = 40 + ) - self.db.add_episodes_to_db( - 4, - episode.title, - duration, - episode.index, - episode.parentIndex, - media.title - ) + #add all episodes of each tv show to episodes table + episodes = self.PLEX.library.section(section.title).get(media.title).episodes() - else: + for episode in episodes: - self.db.add_episodes_to_db( - 4, - episode.title, - 0, - episode.index, - episode.parentIndex, - media.title - ) + duration = episode.duration - elif correct_lib_name == "Commercials": + if duration: - sectionMedia = self.PLEX.library.section(section.title).all() + self.db.add_episodes_to_db( + 4, + episode.title, + duration, + episode.index, + episode.parentIndex, + media.title, + episode.key + ) - media_length = len(sectionMedia) + else: - for i, media in enumerate(sectionMedia): + self.db.add_episodes_to_db( + 4, + episode.title, + 0, + episode.index, + episode.parentIndex, + media.title, + episode.key + ) - self.db.add_commercials_to_db(3, media.title, media.duration) + elif correct_lib_name == "Commercials": - self.print_progress( - i + 1, - media_length, - prefix = 'Progress '+section.title+":", - suffix = 'Complete', - bar_length = 40 - ) + sectionMedia = self.PLEX.library.section(section.title).all() - def update_schedule(self): + media_length = len(sectionMedia) - self.db.create_tables() + for i, media in enumerate(sectionMedia): - self.db.remove_all_scheduled_items() + self.db.add_commercials_to_db(3, media.title, media.duration, media.key) - scheduled_days_list = [ - "mondays", - "tuesdays", - "wednesdays", - "thursdays", - "fridays", - "saturdays", - "sundays", - "weekdays", - "weekends", - "everyday" - ] + self.print_progress( + i + 1, + media_length, + prefix = 'Progress '+section.title+":", + suffix = 'Complete', + bar_length = 40 + ) - section_dict = { - "TV Shows" : ["series", "shows", "tv", "episodes", "tv shows", "show"], - "Movies" : ["movie", "movies", "films", "film"], - "Videos" : ["video", "videos", "vid"], - "Music" : ["music", "songs", "song", "tune", "tunes"] - } + def update_schedule_from_google_calendar(self): - tree = ET.parse('pseudo_schedule.xml') + self.gcal = GoogleCalendar(self.GKEY) - root = tree.getroot() + events = self.gcal.get_entries() - for child in root: + self.db.create_tables() - if child.tag in scheduled_days_list: + self.db.remove_all_scheduled_items() - for time in child.iter("time"): + scheduled_days_list = [ + "mondays", + "tuesdays", + "wednesdays", + "thursdays", + "fridays", + "saturdays", + "sundays", + "weekdays", + "weekends", + "everyday" + ] - for key, value in section_dict.items(): + section_dict = { + "TV Shows" : ["series", "shows", "tv", "episodes", "tv shows", "show"], + "Movies" : ["movie", "movies", "films", "film"], + "Videos" : ["video", "videos", "vid"], + "Music" : ["music", "songs", "song", "tune", "tunes"] + } - if time.attrib['type'] == key or time.attrib['type'] in value: + weekday_dict = { + "0" : ["mondays", "weekdays", "everyday"], + "1" : ["tuesdays", "weekdays", "everyday"], + "2" : ["wednesdays", "weekdays", "everyday"], + "3" : ["thursdays", "weekdays", "everyday"], + "4" : ["fridays", "weekdays", "everyday"], + "5" : ["saturdays", "weekends", "everyday"], + "6" : ["sundays", "weekends", "everyday"], + } - title = time.attrib['title'] + for event in events: - natural_start_time = self.translate_time(time.text) + titlelist = [x.strip() for x in event['summary'].split(',')] - natural_end_time = 0 + start = event['start'].get('dateTime', event['start'].get('date')) - section = key + s = datetime.datetime.strptime(start,"%Y-%m-%dT%H:%M:%S-07:00") - day_of_week = child.tag + weekno = s.weekday() - strict_time = time.attrib['strict-time'] + for key, value in section_dict.items(): - time_shift = time.attrib['time-shift'] + if str(titlelist[0]).lower() == key or str(titlelist[0]).lower() in value: - overlap_max = time.attrib['overlap-max'] + print "Adding {} to schedule.".format(titlelist[1]) - start_time_unix = datetime.datetime.strptime( - self.translate_time(time.text), - '%I:%M %p').strftime('%Y-%m-%d %H:%M:%S') + title = titlelist[1] - print "Adding: ", time.tag, section, time.text, time.attrib['title'] + # s.strftime('%I:%M'), event["summary"] + natural_start_time = self.translate_time(s.strftime(self.APP_TIME_FORMAT_STR)) - self.db.add_schedule_to_db( - 0, # mediaID - title, # title - 0, # duration - natural_start_time, # startTime - natural_end_time, # endTime - day_of_week, # dayOfWeek - start_time_unix, # startTimeUnix - section, # section - strict_time, # strictTime - time_shift, # timeShift - overlap_max, # overlapMax - ) + natural_end_time = 0 - def drop_db(self): + section = key - self.db.drop_db() + for dnum, daylist in weekday_dict.items(): - def drop_schedule(self): + #print int(weekno), int(dnum) - self.db.drop_schedule() + if int(weekno) == int(dnum): - def remove_all_scheduled_items(): + day_of_week = daylist[0] - self.db.remove_all_scheduled_items() + strict_time = titlelist[2] if len(titlelist) > 2 else "true" + #strict_time = "true" + time_shift = "5" - """App functions. + overlap_max = "" - generate_daily_schedule(): Generate the daily_schedule table. - """ + print natural_start_time - ''' - * - * Using datetime to figure out when the media item will end based on the scheduled start time or the offset - * generated by the previous media item. + start_time_unix = datetime.datetime.strptime( + self.translate_time(natural_start_time), + '%I:%M:%S %p').strftime('%Y-%m-%d %H:%M:%S') - * Returns time - * - ''' - ''' - * - * Returns time difference in minutes - * - ''' + #print "Adding: ", time.tag, section, time.text, time.attrib['title'] - def translate_time(self, timestr): + self.db.add_schedule_to_db( + 0, # mediaID + title, # title + 0, # duration + natural_start_time, # startTime + natural_end_time, # endTime + day_of_week, # dayOfWeek + start_time_unix, # startTimeUnix + section, # section + strict_time, # strictTime + time_shift, # timeShift + overlap_max, # overlapMax + ) - try: + def update_schedule(self): - return datetime.datetime.strptime(timestr, "%I:%M %p").strftime("%-I:%M %p") + self.db.create_tables() - except ValueError as e: + self.db.remove_all_scheduled_items() - pass + scheduled_days_list = [ + "mondays", + "tuesdays", + "wednesdays", + "thursdays", + "fridays", + "saturdays", + "sundays", + "weekdays", + "weekends", + "everyday" + ] - try: + section_dict = { + "TV Shows" : ["series", "shows", "tv", "episodes", "tv shows", "show"], + "Movies" : ["movie", "movies", "films", "film"], + "Videos" : ["video", "videos", "vid"], + "Music" : ["music", "songs", "song", "tune", "tunes"] + } - return datetime.datetime.strptime(timestr, "%H:%M").strftime("%-I:%M %p") + tree = ET.parse('pseudo_schedule.xml') - except ValueError as e: + root = tree.getroot() - pass + for child in root: - def time_diff(self, time1,time2): - ''' - * - * Getting the offest by comparing both times from the unix epoch time and getting the difference. - * - ''' - timeA = datetime.datetime.strptime(time1, "%I:%M %p") - timeB = datetime.datetime.strptime(time2, "%I:%M %p") - - timeAEpoch = calendar.timegm(timeA.timetuple()) - timeBEpoch = calendar.timegm(timeB.timetuple()) + if child.tag in scheduled_days_list: - tdelta = abs(timeAEpoch) - abs(timeBEpoch) + for time in child.iter("time"): - return int(tdelta/60) + for key, value in section_dict.items(): + if time.attrib['type'] == key or time.attrib['type'] in value: - ''' - * - * Passing in the endtime from the prev episode and desired start time of this episode, calculate the best start time + title = time.attrib['title'] - * Returns time - for new start time - * - ''' - def calculate_start_time(self, prevEndTime, intendedStartTime, timeGap, overlapMax): + natural_start_time = self.translate_time(time.text) - self.TIME_GAP = timeGap + natural_end_time = 0 - self.OVERLAP_GAP = timeGap + section = key - self.OVERLAP_MAX = overlapMax + day_of_week = child.tag - time1 = prevEndTime.strftime('%-I:%M %p') + strict_time = time.attrib['strict-time'] - timeB = datetime.datetime.strptime(intendedStartTime, '%I:%M %p').strftime('%-I:%M %p') + time_shift = time.attrib['time-shift'] - print "++++ Previous End Time: ", time1, "Intended start time: ", timeB + overlap_max = time.attrib['overlap-max'] - timeDiff = self.time_diff(time1, timeB) + start_time_unix = self.translate_time(time.text) - """print("timeDiff "+ str(timeDiff)) - print("startTimeUNIX: "+ str(intendedStartTime))""" + print "Adding: ", time.tag, section, time.text, time.attrib['title'] - newTimeObj = timeB + self.db.add_schedule_to_db( + 0, # mediaID + title, # title + 0, # duration + natural_start_time, # startTime + natural_end_time, # endTime + day_of_week, # dayOfWeek + start_time_unix, # startTimeUnix + section, # section + strict_time, # strictTime + time_shift, # timeShift + overlap_max, # overlapMax + ) - newStartTime = timeB + def drop_db(self): - ''' - * - * If time difference is negative, then we know there is overlap - * - ''' - if timeDiff < 0: - ''' - * - * If there is an overlap, then the overlapGap var in config will determine the next increment. If it is set to "15", then the show will will bump up to the next 15 minute interval past the hour. - * - ''' - timeset=[datetime.time(h,m).strftime("%H:%M") for h,m in itertools.product(xrange(0,24),xrange(0,60,int(self.OVERLAP_GAP)))] - - #print(timeset) + self.db.drop_db() - timeSetToUse = None + def drop_schedule(self): - for time in timeset: + self.db.drop_schedule() - #print(time) - theTimeSetInterval = datetime.datetime.strptime(time, '%H:%M') + def remove_all_scheduled_items(): - # print(theTimeSetInterval) + self.db.remove_all_scheduled_items() - # print(prevEndTime) - if theTimeSetInterval >= prevEndTime: - print "++++ There is overlap. Setting new time-interval:", theTimeSetInterval + """App functions. - newStartTime = theTimeSetInterval + generate_daily_schedule(): Generate the daily_schedule table. + """ - break + ''' + * + * Using datetime to figure out when the media item will end based on the scheduled start time or the offset + * generated by the previous media item. - #newStartTime = newTimeObj + datetime.timedelta(minutes=abs(timeDiff + overlapGap)) + * Returns time + * + ''' + ''' + * + * Returns time difference in minutes + * + ''' - elif (timeDiff >= 0) and (self.TIME_GAP != -1): + def translate_time(self, timestr): - ''' - * - * If there this value is configured, then the timeGap var in config will determine the next increment. - * If it is set to "15", then the show will will bump up to the next 15 minute interval past the hour. - * - ''' - timeset=[datetime.time(h,m).strftime("%H:%M") for h,m in itertools.product(xrange(0,24),xrange(0,60,int(self.TIME_GAP)))] - - # print(timeset) + try: - for time in timeset: + return datetime.datetime.strptime(timestr, '%I:%M %p').strftime(self.APP_TIME_FORMAT_STR) - theTimeSetInterval = datetime.datetime.strptime(time, '%H:%M') + except ValueError as e: - tempTimeTwoStr = datetime.datetime.strptime(time1, '%I:%M %p').strftime('%H:%M') + pass - formatted_time_two = datetime.datetime.strptime(tempTimeTwoStr, '%H:%M') + try: - if theTimeSetInterval >= formatted_time_two: + return datetime.datetime.strptime(timestr, '%I:%M:%S %p').strftime(self.APP_TIME_FORMAT_STR) - print "++++ Setting new time-interval:", theTimeSetInterval + except ValueError as e: - newStartTime = theTimeSetInterval + pass - break + try: - else: + return datetime.datetime.strptime(timestr, '%H:%M').strftime(self.APP_TIME_FORMAT_STR) - print("Not sure what to do here") + except ValueError as e: - return newStartTime.strftime('%-I:%M %p') + pass - def get_end_time_from_duration(self, startTime, duration): + return timestr - time = datetime.datetime.strptime(startTime, '%I:%M %p') + def time_diff(self, time1,time2): + ''' + * + * Getting the offest by comparing both times from the unix epoch time and getting the difference. + * + ''' + timeA = datetime.datetime.strptime(time1, '%I:%M:%S %p') + timeB = datetime.datetime.strptime(time2, '%I:%M:%S %p') + + timeAEpoch = calendar.timegm(timeA.timetuple()) + timeBEpoch = calendar.timegm(timeB.timetuple()) - show_time_plus_duration = time + datetime.timedelta(milliseconds=duration) + tdelta = abs(timeAEpoch) - abs(timeBEpoch) - #print(show_time_plus_duration.minute) + return int(tdelta/60) - return show_time_plus_duration - def generate_daily_schedule(self): + ''' + * + * Passing in the endtime from the prev episode and desired start time of this episode, calculate the best start time - print("#### Generating Daily Schedule") + * Returns time - for new start time + * + ''' + def calculate_start_time(self, prevEndTime, intendedStartTime, timeGap, overlapMax): - schedule = self.db.get_schedule() + self.TIME_GAP = timeGap - weekday_dict = { - "0" : ["mondays", "weekdays", "everyday"], - "1" : ["tuesdays", "weekdays", "everyday"], - "2" : ["wednesdays", "weekdays", "everyday"], - "3" : ["thursdays", "weekdays", "everyday"], - "4" : ["fridays", "weekdays", "everyday"], - "5" : ["saturdays", "weekends", "everyday"], - "6" : ["sundays", "weekends", "everyday"], - } + self.OVERLAP_GAP = timeGap - weekno = datetime.datetime.today().weekday() + self.OVERLAP_MAX = overlapMax - schedule_advance_watcher = 0 + time1 = prevEndTime.strftime('%I:%M:%S %p') - for entry in schedule: + timeB = datetime.datetime.strptime(intendedStartTime, '%I:%M:%S %p').strftime(self.APP_TIME_FORMAT_STR) - schedule_advance_watcher += 1 + print "++++ Previous End Time: ", time1, "Intended start time: ", timeB - section = entry[9] + timeDiff = self.time_diff(time1, timeB) - for key, val in weekday_dict.iteritems(): + """print("timeDiff "+ str(timeDiff)) + print("startTimeUNIX: "+ str(intendedStartTime))""" - if str(entry[7]) in str(val) and int(weekno) == int(key): + newTimeObj = timeB - if section == "TV Shows": + newStartTime = timeB - if entry[3] == "random": + ''' + * + * If time difference is negative, then we know there is overlap + * + ''' + if timeDiff < 0: + ''' + * + * If there is an overlap, then the overlapGap var in config will determine the next increment. If it is set to "15", then the show will will bump up to the next 15 minute interval past the hour. + * + ''' + timeset=[datetime.time(h,m).strftime("%H:%M") for h,m in itertools.product(xrange(0,24),xrange(0,60,int(self.OVERLAP_GAP)))] + + #print(timeset) - next_episode = self.db.get_random_episode() + timeSetToUse = None - else: + for time in timeset: - next_episode = self.db.get_next_episode(entry[3]) + #print(time) + theTimeSetInterval = datetime.datetime.strptime(time, '%H:%M') - if next_episode != None: - - episode = Episode( - section, # section_type - next_episode[3], # title - entry[5], # natural_start_time - self.get_end_time_from_duration(entry[5], next_episode[4]), # natural_end_time - next_episode[4], # duration - entry[7], # day_of_week - entry[10], # is_strict_time - entry[11], # time_shift - entry[12], # overlap_max - entry[3], # show_series_title - next_episode[5], # episode_number - next_episode[6] # season_number - ) + # print(theTimeSetInterval) - self.MEDIA.append(episode) + # print(prevEndTime) - else: + if theTimeSetInterval >= prevEndTime: - print("Cannot find TV Show Episode, {} in the local db".format(entry[3])) + print "++++ There is overlap. Setting new time-interval:", theTimeSetInterval - #print(episode) + newStartTime = theTimeSetInterval - elif section == "Movies": + break - if entry[3] == "random": + #newStartTime = newTimeObj + datetime.timedelta(minutes=abs(timeDiff + overlapGap)) - the_movie = self.db.get_random_movie() + elif (timeDiff >= 0) and (self.TIME_GAP != -1): - else: + ''' + * + * If there this value is configured, then the timeGap var in config will determine the next increment. + * If it is set to "15", then the show will will bump up to the next 15 minute interval past the hour. + * + ''' + timeset=[datetime.time(h,m).strftime("%H:%M") for h,m in itertools.product(xrange(0,24),xrange(0,60,int(self.TIME_GAP)))] + + # print(timeset) - the_movie = self.db.get_movie(entry[3]) + for time in timeset: - if the_movie != None: + theTimeSetInterval = datetime.datetime.strptime(time, '%H:%M') - movie = Movie( - section, # section_type - the_movie[3], # title - entry[5], # natural_start_time - self.get_end_time_from_duration(entry[5], the_movie[4]), # natural_end_time - the_movie[4], # duration - entry[7], # day_of_week - entry[10], # is_strict_time - entry[11], # time_shift - entry[12] # overlap_max - ) + tempTimeTwoStr = datetime.datetime.strptime(time1, self.APP_TIME_FORMAT_STR).strftime('%H:%M') - #print(movie.natural_end_time) + formatted_time_two = datetime.datetime.strptime(tempTimeTwoStr, '%H:%M') - self.MEDIA.append(movie) + if theTimeSetInterval >= formatted_time_two: - else: + print "++++ Setting new time-interval:", theTimeSetInterval - print("Cannot find Movie, {} in the local db".format(entry[3])) + newStartTime = theTimeSetInterval - elif section == "Music": + break - the_music = self.db.get_music(entry[3]) + else: - if the_music != None: + print("Not sure what to do here") - music = Music( - section, # section_type - the_music[3], # title - entry[5], # natural_start_time - self.get_end_time_from_duration(entry[5], the_music[4]), # natural_end_time - the_music[4], # duration - entry[7], # day_of_week - entry[10], # is_strict_time - entry[11], # time_shift - entry[12] # overlap_max - ) + return newStartTime.strftime('%I:%M:%S %p') - #print(music.natural_end_time) + def get_end_time_from_duration(self, startTime, duration): - self.MEDIA.append(music) + time = datetime.datetime.strptime(startTime, '%I:%M:%S %p') - else: + show_time_plus_duration = time + datetime.timedelta(milliseconds=duration) - print("Cannot find Music, {} in the local db".format(entry[3])) + #print(show_time_plus_duration.minute) - elif section == "Video": + return show_time_plus_duration - the_video = self.db.get_video(entry[3]) + def generate_daily_schedule(self): - if the_music != None: + print("#### Generating Daily Schedule") - video = Video( - section, # section_type - the_video[3], # title - entry[5], # natural_start_time - self.get_end_time_from_duration(entry[5], the_video[4]), # natural_end_time - the_video[4], # duration - entry[7], # day_of_week - entry[10], # is_strict_time - entry[11], # time_shift - entry[12] # overlap_max - ) + if self.USING_COMMERCIAL_INJECTION: + self.commercials = PseudoChannelCommercial( + self.db.get_commercials() + ) - #print(music.natural_end_time) + schedule = self.db.get_schedule() - self.MEDIA.append(video) + weekday_dict = { + "0" : ["mondays", "weekdays", "everyday"], + "1" : ["tuesdays", "weekdays", "everyday"], + "2" : ["wednesdays", "weekdays", "everyday"], + "3" : ["thursdays", "weekdays", "everyday"], + "4" : ["fridays", "weekdays", "everyday"], + "5" : ["saturdays", "weekends", "everyday"], + "6" : ["sundays", "weekends", "everyday"], + } - else: + weekno = datetime.datetime.today().weekday() - print("Cannot find Video, {} in the local db".format(entry[3])) + schedule_advance_watcher = 0 - else: + for entry in schedule: - pass + schedule_advance_watcher += 1 - """If we reached the end of the scheduled items for today, add them to the daily schedule + section = entry[9] - """ - if schedule_advance_watcher >= len(schedule): + for key, val in weekday_dict.iteritems(): - print "+++++ Finished processing time entries, recreating daily_schedule" + if str(entry[7]) in str(val) and int(weekno) == int(key): - previous_episode = None + if section == "TV Shows": - self.db.remove_all_daily_scheduled_items() + if str(entry[3]).lower() == "random": - for entry in self.MEDIA: + next_episode = self.db.get_random_episode() - #print entry.natural_end_time + else: - if previous_episode != None: + next_episode = self.db.get_next_episode(entry[3]) - natural_start_time = datetime.datetime.strptime(entry.natural_start_time, '%I:%M %p') + if next_episode != None: + + episode = Episode( + section, # section_type + next_episode[3], # title + entry[5], # natural_start_time + self.get_end_time_from_duration(self.translate_time(entry[5]), next_episode[4]), # natural_end_time + next_episode[4], # duration + entry[7], # day_of_week + entry[10], # is_strict_time + entry[11], # time_shift + entry[12], # overlap_max + next_episode[8] if len(next_episode) >= 9 else '', # plex id + entry[3], # show_series_title + next_episode[5], # episode_number + next_episode[6] # season_number + ) - natural_end_time = entry.natural_end_time + self.MEDIA.append(episode) - if entry.is_strict_time.lower() == "true": + else: - print "++++ Strict-time: {}".format(str(entry.title)) + print("Cannot find TV Show Episode, {} in the local db".format(entry[3])) - entry.end_time = self.get_end_time_from_duration( - self.translate_time(entry.start_time), - entry.duration - ) + #print(episode) - self.db.add_media_to_daily_schedule(entry) + elif section == "Movies": - previous_episode = entry + if str(entry[3]).lower() == "random": - else: + the_movie = self.db.get_random_movie() - print "++++ NOT strict-time: {}".format(str(entry.title).encode(sys.stdout.encoding, errors='replace')) + else: - new_starttime = self.calculate_start_time( - previous_episode.end_time, - entry.natural_start_time, - previous_episode.time_shift, - previous_episode.overlap_max - ) + the_movie = self.db.get_movie(entry[3]) - print "++++ New start time:", new_starttime + if the_movie != None: - entry.start_time = datetime.datetime.strptime(new_starttime, '%I:%M %p').strftime('%-I:%M %p') + movie = Movie( + section, # section_type + the_movie[3], # title + entry[5], # natural_start_time + self.get_end_time_from_duration(entry[5], the_movie[4]), # natural_end_time + the_movie[4], # duration + entry[7], # day_of_week + entry[10], # is_strict_time + entry[11], # time_shift + entry[12], # overlap_max + the_movie[6] # plex id + ) - entry.end_time = self.get_end_time_from_duration(entry.start_time, entry.duration) + #print(movie.natural_end_time) - self.db.add_media_to_daily_schedule(entry) + self.MEDIA.append(movie) - previous_episode = entry + else: - else: + print("Cannot find Movie, {} in the local db".format(entry[3])) - self.db.add_media_to_daily_schedule(entry) + elif section == "Music": - previous_episode = entry + the_music = self.db.get_music(entry[3]) + + if the_music != None: + + music = Music( + section, # section_type + the_music[3], # title + entry[5], # natural_start_time + self.get_end_time_from_duration(entry[5], the_music[4]), # natural_end_time + the_music[4], # duration + entry[7], # day_of_week + entry[10], # is_strict_time + entry[11], # time_shift + entry[12], # overlap_max + the_music[6], # plex id + ) + + #print(music.natural_end_time) + + self.MEDIA.append(music) + + else: + + print("Cannot find Music, {} in the local db".format(entry[3])) + + elif section == "Video": + + the_video = self.db.get_video(entry[3]) + + if the_music != None: + + video = Video( + section, # section_type + the_video[3], # title + entry[5], # natural_start_time + self.get_end_time_from_duration(entry[5], the_video[4]), # natural_end_time + the_video[4], # duration + entry[7], # day_of_week + entry[10], # is_strict_time + entry[11], # time_shift + entry[12], # overlap_max + the_video[6] # plex id + ) + + #print(music.natural_end_time) + + self.MEDIA.append(video) + + else: + + print("Cannot find Video, {} in the local db".format(entry[3])) + + else: + + pass + + """If we reached the end of the scheduled items for today, add them to the daily schedule + + """ + if schedule_advance_watcher >= len(schedule): + + print "+++++ Finished processing time entries, recreating daily_schedule" + + previous_episode = None + + self.db.remove_all_daily_scheduled_items() + + for entry in self.MEDIA: + + #print entry.natural_end_time + + if previous_episode != None: + + natural_start_time = datetime.datetime.strptime(entry.natural_start_time, self.APP_TIME_FORMAT_STR) + + natural_end_time = entry.natural_end_time + + if entry.is_strict_time.lower() == "true": + + print "++++ Strict-time: {}".format(str(entry.title)) + + entry.end_time = self.get_end_time_from_duration( + self.translate_time(entry.start_time), + entry.duration + ) + + """Get List of Commercials to inject""" + + if self.USING_COMMERCIAL_INJECTION: + + list_of_commercials = self.commercials.get_commercials_to_place_between_media( + previous_episode, + entry + ) + + for commercial in list_of_commercials: + + self.db.add_media_to_daily_schedule(commercial) + + self.db.add_media_to_daily_schedule(entry) + + previous_episode = entry + + else: + + print "++++ NOT strict-time: {}".format(str(entry.title).encode(sys.stdout.encoding, errors='replace')) + + new_starttime = self.calculate_start_time( + previous_episode.end_time, + entry.natural_start_time, + previous_episode.time_shift, + previous_episode.overlap_max + ) + + print "++++ New start time:", new_starttime + + entry.start_time = datetime.datetime.strptime(new_starttime, self.APP_TIME_FORMAT_STR).strftime('%I:%M:%S %p') + + entry.end_time = self.get_end_time_from_duration(entry.start_time, entry.duration) + + """Get List of Commercials to inject""" + if self.USING_COMMERCIAL_INJECTION: + list_of_commercials = self.commercials.get_commercials_to_place_between_media( + previous_episode, + entry + ) + + for commercial in list_of_commercials: + + self.db.add_media_to_daily_schedule(commercial) + + self.db.add_media_to_daily_schedule(entry) + + previous_episode = entry + + else: + + self.db.add_media_to_daily_schedule(entry) + + previous_episode = entry + + def run_commercial_injection(self): + + print "#### Running commercial injection." + + self.commercials = PseudoChannelCommercial( + self.db.get_commercials(), + self.db.get_daily_schedule() + ) + + commercials_to_inject = self.commercials.get_commercials_to_inject() + + print commercials_to_inject + + def run(self): + + """print datetime.datetime.now() + threading.Timer(1, self.run()).start()""" + pass + + def make_xml_schedule(self): + + self.controller.make_xml_schedule(self.db.get_daily_schedule()) + + def get_daily_schedule_as_media_object_list(self): + + for i, item in enumerate(self.db.get_daily_schedule(), start=0): + + if item[11] == "TV Shows": + + """episode = Episode( + + )""" + pass + + elif item[11] == "Movies": + + pass + + elif item[11] == "Music": + + pass + + elif item[11] == "Commercials": + + pass + + elif item[11] == "Videos": + + pass + + else: + + pass + + def show_clients(self): + + print "##### Connected Clients:" + + for i, client in enumerate(self.PLEX.clients()): + + print "+++++", str(i + 1)+".", "Client:", client.title + + def show_schedule(self): + + print "##### Daily Pseudo Schedule:" + + daily_schedule = self.db.get_daily_schedule() + + for i , entry in enumerate(daily_schedule): + + print "+++++", str(i + 1)+".", entry[8], entry[11], entry[6], " - ", entry[3] + + def exit_app(self): + + print " - Exiting Pseudo TV & cleaning up." + + for i in self.MEDIA: + + del i + + self.MEDIA = None + + self.controller = None + + self.db = None + + sleep(1) if __name__ == '__main__': - pseudo_channel = PseudoChannel() + pseudo_channel = PseudoChannel() - #pseudo_channel.db.create_tables() + #pseudo_channel.db.create_tables() - #pseudo_channel.update_db() + #pseudo_channel.update_db() - #pseudo_channel.update_schedule() + #pseudo_channel.update_schedule() - #pseudo_channel.generate_daily_schedule() + #pseudo_channel.generate_daily_schedule() - parser = argparse.ArgumentParser( - description="Pseudo Channel for Plex. Update pseduo_config.py & pseudo_schedule.xml before this step.", - usage="PseudoChannel.py [-u] update local db with plex db [-xml] update db with xml schedule data [-g] generate daily schedule [-r] run the app" - ) + banner = textwrap.dedent('''\ +# __ __ +# |__)_ _ _| _ / |_ _ _ _ _| _ +# | _)(-|_|(_|(_)\__| )(_|| )| )(-|. |_)\/ +# | / - parser.add_argument('-u', action='store_true') - parser.add_argument('-xml', action='store_true') - parser.add_argument('-g', action='store_true') - parser.add_argument('-r', action='store_true') + A Custom TV Channel for Plex +''') - ''' - * - * Show connected clients: "python PseudoChannel.py -u -xml -g -r" - * - ''' - parser.add_argument('-sc', action='store_true') + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description = banner) - globals().update(vars(parser.parse_args())) + ''' + * + * Primary arguments: "python PseudoChannel.py -u -xml -g -r" + * + ''' - args = parser.parse_args() + parser.add_argument('-u', '--update', + action='store_true', + help='Update the local database with Plex libraries.') + parser.add_argument('-xml', '--xml', + action='store_true', + help='Update the local database with the pseudo_schedule.xml.') + ''' + * + * Update Schedule based on Google Cal: "python PseudoChannel.py -gc" + * + ''' + parser.add_argument('-gc', '--google_calendar', + action='store_true', + help='Update the local database with entries in the google calendar.') - print(args) + parser.add_argument('-g', '--generate_schedule', + action='store_true', + help='Generate the daily schedule.') + parser.add_argument('-r', '--run', + action='store_true', + help='Run this program.') - if args.u: + ''' + * + * Show connected clients: "python PseudoChannel.py -c" + * + ''' + parser.add_argument('-c', '--show_clients', + action='store_true', + help='Show Plex clients.') - pseudo_channel.update_db() + ''' + * + * Show schedule (daily): "python PseudoChannel.py -s" + * + ''' + parser.add_argument('-s', '--show_schedule', + action='store_true', + help='Show scheduled media for today.') - if args.xml: + ''' + * + * Make XML / HTML Schedule: "python PseudoChannel.py -m" + * + ''' + parser.add_argument('-m', '--make_html', + action='store_true', + help='Makes the XML / HTML schedule based on the daily_schedule table.') - pseudo_channel.update_schedule() + ''' + * + * Make XML / HTML Schedule: "python PseudoChannel.py -i" + * + ''' + parser.add_argument('-i', '--inject_commercials', + action='store_true', + help='Squeeze commercials in any media gaps if possible.') - if args.g: + globals().update(vars(parser.parse_args())) - pseudo_channel.generate_daily_schedule() + args = parser.parse_args() - if args.r: + #print(args) - try: + if args.update: - print "++++ Running TV Controller" - - """Every minute on the minute check the DB startTimes of all media to - determine whether or not to play. Also, check the now_time to - see if it's midnight (or 23.59), if so then generate a new daily_schedule - - """ - while True: + pseudo_channel.update_db() - now = datetime.datetime.now() + if args.xml: - now_time = now.time() + pseudo_channel.update_schedule() - if now_time == time(23,59): + if args.google_calendar: - pseudo_channel.generate_daily_schedule() + pseudo_channel.update_schedule_from_google_calendar() - pseudo_channel.controller.tv_controller(pseudo_channel.db.get_daily_schedule()) + if args.generate_schedule: - t = datetime.datetime.utcnow() + pseudo_channel.generate_daily_schedule() - sleeptime = 60 - (t.second + t.microsecond/1000000.0) + if args.show_clients: - sleep(sleeptime) + pseudo_channel.show_clients() - except KeyboardInterrupt, e: + if args.show_schedule: - pass - + pseudo_channel.show_schedule() + + if args.make_html: + + pseudo_channel.make_xml_schedule() + + if args.inject_commercials: + + pseudo_channel.run_commercial_injection() + + if args.run: + + print banner + print "+++++ To run this in the background:" + print "+++++", "screen -d -m bash -c 'python PseudoChannel.py -r; exec sh'" + + """Every minute on the minute check the DB startTimes of all media to + determine whether or not to play. Also, check the now_time to + see if it's midnight (or 23.59), if so then generate a new daily_schedule + + """ + + the_daily_schedule = pseudo_channel.db.get_daily_schedule() + + daily_update_time = datetime.datetime.strptime( + pseudo_channel.translate_time( + pseudo_channel.DAILY_UPDATE_TIME + ), + pseudo_channel.APP_TIME_FORMAT_STR + ) + + try: + + def run_task(): + + now = datetime.datetime.now() + + now_time = now.time().replace(microsecond=0) + + #print time(11,59,00), now_time + + if now_time == time( + daily_update_time.hour, + daily_update_time.minute, + daily_update_time.second + ): + + if pseudo_channel.USING_GOOGLE_CALENDAR: + + pseudo_channel.update_schedule_from_google_calendar() + + else: + + pass + + pseudo_channel.generate_daily_schedule() + + pseudo_channel.make_xml_schedule() + + pseudo_channel.controller.tv_controller(the_daily_schedule) + + t = Timer(1, run_task, ()) + + t.start() + + print '{}'.format(datetime.datetime.now(), end="\r") + + except KeyboardInterrupt: + + print('Manual break by user') + + run_task() + + + + """try: + + + while True: + + now = datetime.datetime.now() + + now_time = now.time().replace(microsecond=0) + + #print time(11,59,00), now_time + + if now_time == time(00,00,00): + + if pseudo_channel.USING_GOOGLE_CALENDAR: + + pseudo_channel.update_schedule_from_google_calendar() + + sleep(.5) + + else: + + pass + + pseudo_channel.generate_daily_schedule() + + pseudo_channel.make_xml_schedule() + + pseudo_channel.controller.tv_controller(pseudo_channel.db.get_daily_schedule()) + + t = datetime.datetime.utcnow() + + #sleeptime = 60 - (t.second + t.microsecond/1000000.0) + + sleep(.5) + + except KeyboardInterrupt, e: + + pseudo_channel.exit_app() + + del pseudo_channel""" + diff --git a/pseudo_config.py b/pseudo_config.py index bd79793..2fb5681 100644 --- a/pseudo_config.py +++ b/pseudo_config.py @@ -1,32 +1,49 @@ -#!/usr/bin/python +#!/usr/bin/env python + """ - 1) Create a file outside of this proj dir called "plex_token.py": + 1) Create a file outside of this proj dir called "plex_token.py": - touch ../plex_token.py - - 2) add this line to the newly created file: + touch ../plex_token.py + + 2) add this line to the newly created file: - token = 'your plex token' + baseurl = 'the url to your server' + token = 'your plex token' - 3) Edit the "basurl" variable below to point to your Plex server + 3) Edit the "basurl" variable below to point to your Plex server - 4) Edit the "plexClients" variable to include the name of your plex client(s) this app will control. + 4) Edit the "plexClients" variable to include the name of your plex client(s) this app will control. - 5) Edit the "plexLibraries" variable to remap your specific library names to the app specific names. - ...for instance, if your Plex "Movies" are located in your Plex library as "Films", update that - line so it looks like: + 5) Edit the "plexLibraries" variable to remap your specific library names to the app specific names. + ...for instance, if your Plex "Movies" are located in your Plex library as "Films", update that + line so it looks like: - "Movies" : ["Films"], - + "Movies" : ["Films"], + + 6) For Google Calendar integration add your "gkey" to the "plex_token.py" file + ...(https://docs.simplecalendar.io/find-google-calendar-id/): + + gkey = "the key" + + 7) If using the Google Calendar integration exclusively, set this to true below: + + useGoogleCalendar + """ import os, sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # import ../plex_token.py -import plex_token as plex_token -baseurl = 'http://media.home:32400' +try: + import plex_token as plex_token +except ImportError as e: + print "+++++ Cannot find plex_token file. Make sure you create a plex_token.py file with the appropriate data." + raise e + +baseurl = plex_token.baseurl token = plex_token.token +gkey = plex_token.gkey ''' * @@ -36,8 +53,16 @@ token = plex_token.token plexClients = ['RasPlex'] plexLibraries = { - "TV Shows" : ["TV Shows"], - "Movies" : ["Movies"], - "Music" : ["Music"], - "Commercials" : ["Commercials"], + "TV Shows" : ["TV Shows"], + "Movies" : ["Movies"], + "Music" : ["Music"], + "Commercials" : ["Commercials"], } + +useGoogleCalendar = True + +useCommercialInjection = True + +dailyUpdateTime = "12:00 AM" + +debug_mode = False diff --git a/pseudo_schedule.xml b/pseudo_schedule.xml index f5e7ac8..9c0e60f 100644 --- a/pseudo_schedule.xml +++ b/pseudo_schedule.xml @@ -1,65 +1,65 @@ - - - - - - + + + + + + - - + + - - - - + + + + - - + + - + - - - - - + + + + + - + - - + + - - + + - + - - + + - + - - + + - - - - - + + + + + - - - + + + @@ -92,7 +92,7 @@ - + - + diff --git a/src/Commercial.py b/src/Commercial.py index 10b9a92..800f217 100644 --- a/src/Commercial.py +++ b/src/Commercial.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python + from Media import Media class Commercial(Media): - """Inherits Media. + """Inherits Media. Attributes: section_type: The type of library this is (i.e. "TV Shows") @@ -14,27 +16,29 @@ class Commercial(Media): is_strict_time: If strict time, then anchor to "natural_start_time" """ - def __init__( - self, - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ): + def __init__( + self, + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ): - super(Commercial, self).__init__( - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ) + super(Commercial, self).__init__( + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ) diff --git a/src/Episode.py b/src/Episode.py index 3c579d5..7286184 100644 --- a/src/Episode.py +++ b/src/Episode.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python + from Media import Media class Episode(Media): - """Inherits Media. + """Inherits Media. Attributes: section_type: The type of library this is (i.e. "TV Shows") @@ -13,38 +15,40 @@ class Episode(Media): day_of_week: When the content is scheduled to play is_strict_time: If strict time, then anchor to "natural_start_time" show_series_title: The series title (i.e. "Friends") - episode_number: The episode number in the Season - season_number: The number of season in the series. + episode_number: The episode number in the Season + season_number: The number of season in the series. """ - def __init__( - self, - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max, - show_series_title, - episode_number, - season_number - ): + def __init__( + self, + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id, + show_series_title, + episode_number, + season_number, + ): - super(Episode, self).__init__( - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ) + super(Episode, self).__init__( + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ) - self.show_series_title = show_series_title - self.episode_number = episode_number - self.season_number = season_number + self.show_series_title = show_series_title + self.episode_number = episode_number + self.season_number = season_number diff --git a/src/GoogleCalendar.py b/src/GoogleCalendar.py new file mode 100644 index 0000000..8ef2164 --- /dev/null +++ b/src/GoogleCalendar.py @@ -0,0 +1,104 @@ +from __future__ import print_function +import httplib2 +import os + +from apiclient import discovery +from oauth2client import client +from oauth2client import tools +from oauth2client.file import Storage + +import os.path as path +import sys + +import datetime + +"""try: + import argparse + flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() +except ImportError: + flags = None""" + + +class GoogleCalendar(): + + # If modifying these scopes, delete your previously saved credentials + # at ~/.credentials/calendar-python-quickstart.json + two_up = path.abspath(path.join(__file__ ,"../../../")) + home_dir = os.path.expanduser('~') + credential_dir = os.path.join(home_dir, 'client_secret.json') + SCOPES = 'https://www.googleapis.com/auth/calendar.readonly' + CLIENT_SECRET_FILE = credential_dir + APPLICATION_NAME = 'Google Calendar API Python Quickstart' + + KEY = '' + + def __init__(self, key): + + self.KEY = key + + def get_credentials(self): + """Gets valid user credentials from storage. + + If nothing has been stored, or if the stored credentials are invalid, + the OAuth2 flow is completed to obtain the new credentials. + + Returns: + Credentials, the obtained credential. + """ + home_dir = os.path.expanduser('~') + credential_dir = os.path.join(home_dir, '.credentials') + if not os.path.exists(credential_dir): + os.makedirs(credential_dir) + credential_path = os.path.join(credential_dir, + 'calendar-python-quickstart.json') + + store = Storage(credential_path) + credentials = store.get() + + if not credentials or credentials.invalid: + flow = client.flow_from_clientsecrets(self.CLIENT_SECRET_FILE, self.SCOPES) + flow.user_agent = self.APPLICATION_NAME + if flags: + credentials = tools.run_flow(flow, store, flags) + else: # Needed only for compatibility with Python 2.6 + credentials = tools.run(flow, store) + print('Storing credentials to ' + credential_path) + return credentials + + def get_entries(self): + """Shows basic usage of the Google Calendar API. + + Creates a Google Calendar API service object and outputs a list of the next + 10 events on the user's calendar. + """ + credentials = self.get_credentials() + http = credentials.authorize(httplib2.Http()) + service = discovery.build('calendar', 'v3', http=http) + + now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time + + end = (datetime.datetime.now() + datetime.timedelta(days=1)) + + end = end.isoformat() + 'Z' # 'Z' indicates UTC time + + #print(now) + + #print(end) + + print('##### Getting the upcoming calendar events') + eventsResult = service.events().list( + calendarId=self.KEY, timeMin=now, timeMax=end, maxResults=250, singleEvents=True, + orderBy='startTime').execute() + events = eventsResult.get('items', []) + + if not events: + print('No upcoming events found.') + for event in events: + #start = event['start'].get('dateTime', event['start'].get('date')) + #print(start, event['summary']) + pass + return events + + +if __name__ == '__main__': + pass \ No newline at end of file diff --git a/src/Media.py b/src/Media.py index 66e72d7..9c37abe 100644 --- a/src/Media.py +++ b/src/Media.py @@ -1,13 +1,15 @@ +#!/usr/bin/env python + """ *** Inherited by Commercial, Episode & Movie """ class Media(object): - plex_server_url = '' - plex_server_token = '' - media_image = '' + plex_server_url = '' + plex_server_token = '' + media_image = '' - """A base class for media objects. + """A base class for media objects. Attributes: section_type: The type of library this is (i.e. "TV Shows") @@ -19,29 +21,31 @@ class Media(object): is_strict_time: If strict time, then anchor to "natural_start_time" """ - def __init__( - self, - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ): + def __init__( + self, + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ): - self.section_type = section_type - self.title = title - self.natural_start_time = natural_start_time - self.natural_end_time = natural_end_time - self.duration = duration - self.day_of_week = day_of_week - self.is_strict_time = is_strict_time - self.time_shift = time_shift - self.overlap_max = overlap_max + self.section_type = section_type + self.title = title + self.natural_start_time = natural_start_time + self.natural_end_time = natural_end_time + self.duration = duration + self.day_of_week = day_of_week + self.is_strict_time = is_strict_time + self.time_shift = time_shift + self.overlap_max = overlap_max + self.plex_media_id = plex_media_id - self.start_time = natural_start_time - self.end_time = natural_end_time + self.start_time = natural_start_time + self.end_time = natural_end_time diff --git a/src/Movie.py b/src/Movie.py index a8258b0..37644fe 100644 --- a/src/Movie.py +++ b/src/Movie.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python + from Media import Media class Movie(Media): - """Inherits Media. + """Inherits Media. Attributes: section_type: The type of library this is (i.e. "TV Shows") @@ -14,27 +16,29 @@ class Movie(Media): is_strict_time: If strict time, then anchor to "natural_start_time" """ - def __init__( - self, - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ): + def __init__( + self, + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ): - super(Movie, self).__init__( - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ) + super(Movie, self).__init__( + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ) diff --git a/src/Music.py b/src/Music.py index 0ac53bb..e63bdd7 100644 --- a/src/Music.py +++ b/src/Music.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python + from Media import Media class Music(Media): - """Inherits Media. + """Inherits Media. Attributes: section_type: The type of library this is (i.e. "TV Shows") @@ -14,27 +16,29 @@ class Music(Media): is_strict_time: If strict time, then anchor to "natural_start_time" """ - def __init__( - self, - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ): + def __init__( + self, + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ): - super(Music, self).__init__( - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ) + super(Music, self).__init__( + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ) diff --git a/src/PseudoChannelCommercial.py b/src/PseudoChannelCommercial.py new file mode 100644 index 0000000..866bbe1 --- /dev/null +++ b/src/PseudoChannelCommercial.py @@ -0,0 +1,166 @@ +"""Commercial Functionality +""" +from random import shuffle +import random +import copy +from datetime import datetime +from datetime import timedelta +from dateutil.relativedelta import relativedelta +from src import Commercial + +class PseudoChannelCommercial(): + + MIN_DURATION_FOR_COMMERCIAL = 10 #seconds + daily_schedule = [] + + def __init__(self, commercials): + + self.commercials = commercials + + def get_commercials_to_inject(self): + + self.go() + + return None + + def get_random_commercial(self): + + random_commercial = random.choice(self.commercials) + + random_commercial_dur_seconds = (int(random_commercial[4])/1000)%60 + + while random_commercial_dur_seconds < self.MIN_DURATION_FOR_COMMERCIAL: + + random_commercial = random.choice(self.commercials) + + random_commercial_dur_seconds = (int(random_commercial[4])/1000)%60 + + return random_commercial + + def go(self): + + shuffled_commercial_list = copy.deepcopy(self.commercials) + + random.shuffle(self.commercials, random.random) + + #print shuffled_commercial_list + + prev_item = None + + for entry in self.daily_schedule: + + """First Episode""" + if prev_item == None: + + prev_item = entry + + else: + + prev_item_end_time = datetime.datetime.strptime(prev_item[9], '%Y-%m-%d %H:%M:%S.%f') + + curr_item_start_time = datetime.datetime.strptime(entry[8], '%I:%M:%S %p') + + time_diff = (curr_item_start_time - prev_item_end_time) + + days, hours, minutes = time_diff.days, time_diff.seconds // 3600, time_diff.seconds // 60 % 60 + + count = 0 + + commercial_list = [] + + commercial_dur_sum = 0 + + while int(time_diff.total_seconds()) >= commercial_dur_sum and count < len(self.commercials): + + + random_commercial = self.get_random_commercial() + + commercial_list.append(random_commercial) + + commercial_dur_sum += int(random_commercial[4]) + + print commercial_list + + prev_item = entry + + def timedelta_milliseconds(self, td): + return td.days*86400000 + td.seconds*1000 + td.microseconds/1000 + + def get_commercials_to_place_between_media(self, last_ep, now_ep): + + #print last_ep.end_time, now_ep.start_time + + prev_item_end_time = datetime.strptime(last_ep.end_time.strftime('%Y-%m-%d %H:%M:%S.%f'), '%Y-%m-%d %H:%M:%S.%f') + + curr_item_start_time = datetime.strptime(now_ep.start_time, '%I:%M:%S %p') + + time_diff = (curr_item_start_time - prev_item_end_time) + + count = 0 + + commercial_list = [] + + commercial_dur_sum = 0 + + time_diff_milli = self.timedelta_milliseconds(time_diff) + + last_commercial = None + + time_watch = prev_item_end_time + + new_commercial_start_time = prev_item_end_time + + #print "here", time_diff.seconds + + while curr_item_start_time > new_commercial_start_time and (count) < len(self.commercials)*100: + + random_commercial = self.get_random_commercial() + + #new_commercial_seconds = (int(random_commercial[4])/1000)%60 + + new_commercial_milli = int(random_commercial[4]) + + if last_commercial != None: + + #print last_commercial[3] + + new_commercial_start_time = last_commercial.end_time + + new_commercial_end_time = new_commercial_start_time + \ + timedelta(milliseconds=int(new_commercial_milli)) + + else: + + new_commercial_start_time = prev_item_end_time + + new_commercial_end_time = new_commercial_start_time + \ + timedelta(milliseconds=int(new_commercial_milli)) + + commercial_dur_sum += new_commercial_milli + + formatted_time_for_new_commercial = new_commercial_start_time.strftime('%I:%M:%S %p') + + new_commercial = Commercial( + "Commercials", + random_commercial[3], + formatted_time_for_new_commercial, # natural_start_time + new_commercial_end_time, + random_commercial[4], + "everyday", # day_of_week + "true", # is_strict_time + "1", # time_shift + "0", # overlap_max + "", # plex_media_id + ) + + last_commercial = new_commercial + + if new_commercial_end_time > curr_item_start_time: + + break + + commercial_list.append(new_commercial) + + #print "here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + + return commercial_list \ No newline at end of file diff --git a/src/PseudoChannelDatabase.py b/src/PseudoChannelDatabase.py index bed59f8..30eae25 100644 --- a/src/PseudoChannelDatabase.py +++ b/src/PseudoChannelDatabase.py @@ -1,472 +1,491 @@ +#!/usr/bin/env python + import sqlite3 import datetime import time class PseudoChannelDatabase(): - def __init__(self, db): + def __init__(self, db): - self.db = db + self.db = db - self.conn = sqlite3.connect(self.db) + self.conn = sqlite3.connect(self.db, check_same_thread=False) - self.cursor = self.conn.cursor() + self.cursor = self.conn.cursor() - """Database functions. + """Database functions. - Utilities, etc. - """ + Utilities, etc. + """ - def create_tables(self): + def create_tables(self): - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'movies(id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, lastPlayedDate TEXT)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'movies(id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, ' + 'lastPlayedDate TEXT, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'videos(id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'videos(id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'music(id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'music(id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'shows(id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, ' - 'lastEpisodeTitle TEXT, fullImageURL TEXT)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'shows(id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, ' + 'lastEpisodeTitle TEXT, fullImageURL TEXT, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'episodes(id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, ' - 'episodeNumber INTEGER, seasonNumber INTEGER, showTitle TEXT)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'episodes(id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, ' + 'episodeNumber INTEGER, seasonNumber INTEGER, showTitle TEXT, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'commercials(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, ' - 'mediaID INTEGER, title TEXT, duration INTEGER)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'commercials(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, ' + 'mediaID INTEGER, title TEXT, duration INTEGER, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, ' - 'mediaID INTEGER, title TEXT, duration INTEGER, startTime INTEGER, ' - 'endTime INTEGER, dayOfWeek TEXT, startTimeUnix INTEGER, section TEXT, ' - 'strictTime TEXT, timeShift TEXT, overlapMax TEXT)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, ' + 'mediaID INTEGER, title TEXT, duration INTEGER, startTime INTEGER, ' + 'endTime INTEGER, dayOfWeek TEXT, startTimeUnix INTEGER, section TEXT, ' + 'strictTime TEXT, timeShift TEXT, overlapMax TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'daily_schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, ' - 'mediaID INTEGER, title TEXT, episodeNumber INTEGER, seasonNumber INTEGER, ' - 'showTitle TEXT, duration INTEGER, startTime INTEGER, endTime INTEGER, ' - 'dayOfWeek TEXT, sectionType TEXT)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'daily_schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, ' + 'mediaID INTEGER, title TEXT, episodeNumber INTEGER, seasonNumber INTEGER, ' + 'showTitle TEXT, duration INTEGER, startTime INTEGER, endTime INTEGER, ' + 'dayOfWeek TEXT, sectionType TEXT, plexMediaID TEXT)') - self.cursor.execute('CREATE TABLE IF NOT EXISTS ' - 'app_settings(id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT)') + self.cursor.execute('CREATE TABLE IF NOT EXISTS ' + 'app_settings(id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT)') - #index - self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_episode_title ON episodes (title);') + #index + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_episode_title ON episodes (title);') - self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_movie_title ON movies (title);') + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_movie_title ON movies (title);') - self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_movie_title ON videos (title);') + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_shows_title ON shows (title);') - self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_music_title ON music (title);') + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_video_title ON videos (title);') - self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_commercial_title ON commercials (title);') + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_music_title ON music (title);') - self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_settings_version ON app_settings (version);') + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_commercial_title ON commercials (title);') - """Setting Basic Settings - - """ - try: - self.cursor.execute("INSERT OR REPLACE INTO app_settings " - "(version) VALUES (?)", - ("0.1",)) + self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_settings_version ON app_settings (version);') - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def drop_db(self): - - pass - - def drop_schedule(self): - - pass - - def remove_all_scheduled_items(self): - - sql = "DELETE FROM schedule WHERE id > -1" - - self.cursor.execute(sql) - - self.conn.commit() - - def remove_all_daily_scheduled_items(self): - - sql = "DELETE FROM daily_schedule WHERE id > -1" - - self.cursor.execute(sql) - - self.conn.commit() - - """Database functions. - - Setters, etc. - """ - - def add_movies_to_db(self, mediaID, title, duration): - unix = int(time.time()) - try: - self.cursor.execute("INSERT OR REPLACE INTO movies " - "(unix, mediaID, title, duration) VALUES (?, ?, ?, ?)", - (unix, mediaID, title, duration)) - - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def add_videos_to_db(self, mediaID, title, duration): - unix = int(time.time()) - try: - self.cursor.execute("INSERT OR REPLACE INTO videos " - "(unix, mediaID, title, duration) VALUES (?, ?, ?, ?)", - (unix, mediaID, title, duration)) - - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def add_shows_to_db(self, mediaID, title, duration, lastEpisodeTitle, fullImageURL): - unix = int(time.time()) - try: - self.cursor.execute("INSERT OR REPLACE INTO shows " - "(unix, mediaID, title, duration, lastEpisodeTitle, fullImageURL) VALUES (?, ?, ?, ?, ?, ?)", - (unix, mediaID, title, duration, lastEpisodeTitle, fullImageURL)) - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def add_episodes_to_db(self, mediaID, title, duration, episodeNumber, seasonNumber, showTitle): - unix = int(time.time()) - try: - self.cursor.execute("INSERT OR REPLACE INTO episodes " - "(unix, mediaID, title, duration, episodeNumber, seasonNumber, showTitle) VALUES (?, ?, ?, ?, ?, ?, ?)", - (unix, mediaID, title, duration, episodeNumber, seasonNumber, showTitle)) - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def add_commercials_to_db(self, mediaID, title, duration): - unix = int(time.time()) - try: - self.cursor.execute("INSERT OR REPLACE INTO commercials " - "(unix, mediaID, title, duration) VALUES (?, ?, ?, ?)", - (unix, mediaID, title, duration)) - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def add_schedule_to_db(self, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax): - unix = int(time.time()) - try: - self.cursor.execute("INSERT OR REPLACE INTO schedule " - "(unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax) " - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - (unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax)) - self.conn.commit() - # Catch the exception - except Exception as e: - # Roll back any change if something goes wrong - self.conn.rollback() - raise e - - def add_daily_schedule_to_db( - self, - mediaID, - title, - episodeNumber, - seasonNumber, - showTitle, - duration, - startTime, - endTime, - dayOfWeek, - sectionType - ): - - unix = int(time.time()) - - try: - - self.cursor.execute("INSERT OR REPLACE INTO daily_schedule " - "(unix, mediaID, title, episodeNumber, seasonNumber, " - "showTitle, duration, startTime, endTime, dayOfWeek, sectionType) " - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - ( - unix, - mediaID, - title, - episodeNumber, - seasonNumber, - showTitle, - duration, - startTime, - endTime, - dayOfWeek, - sectionType - )) + """Setting Basic Settings + + """ + try: + self.cursor.execute("INSERT OR REPLACE INTO app_settings " + "(version) VALUES (?)", + ("0.1",)) - self.conn.commit() + self.conn.commit() + # Catch the exception + except Exception as e: + # Roll back any change if something goes wrong + self.conn.rollback() + raise e - # Catch the exception - except Exception as e: + def drop_db(self): + + pass + + def drop_schedule(self): + + pass + + def remove_all_scheduled_items(self): + + sql = "DELETE FROM schedule WHERE id > -1" + + self.cursor.execute(sql) + + self.conn.commit() + + def remove_all_daily_scheduled_items(self): + + sql = "DELETE FROM daily_schedule WHERE id > -1" + + self.cursor.execute(sql) + + self.conn.commit() + + """Database functions. + + Setters, etc. + """ + + def add_movies_to_db(self, mediaID, title, duration, plexMediaID): + unix = int(time.time()) + try: + self.cursor.execute("REPLACE INTO movies " + "(unix, mediaID, title, duration, plexMediaID) VALUES (?, ?, ?, ?, ?)", + (unix, mediaID, title, duration, plexMediaID)) + + self.conn.commit() + # Catch the exception + except Exception as e: + # Roll back any change if something goes wrong + self.conn.rollback() + raise e + + def add_videos_to_db(self, mediaID, title, duration, plexMediaID): + unix = int(time.time()) + try: + self.cursor.execute("REPLACE INTO videos " + "(unix, mediaID, title, duration, plexMediaID) VALUES (?, ?, ?, ?, ?)", + (unix, mediaID, title, duration, plexMediaID)) + + self.conn.commit() + # Catch the exception + except Exception as e: + # Roll back any change if something goes wrong + self.conn.rollback() + raise e + + def add_shows_to_db(self, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID): + unix = int(time.time()) + try: + self.cursor.execute("REPLACE INTO shows " + "(unix, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID) VALUES (?, ?, ?, ?, ?, ?, ?)", + (unix, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID)) + self.conn.commit() + # Catch the exception + except Exception as e: + # Roll back any change if something goes wrong + self.conn.rollback() + raise e + + def add_episodes_to_db(self, mediaID, title, duration, episodeNumber, seasonNumber, showTitle, plexMediaID): + unix = int(time.time()) + try: + self.cursor.execute("REPLACE INTO episodes " + "(unix, mediaID, title, duration, episodeNumber, seasonNumber, showTitle, plexMediaID) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + (unix, mediaID, title, duration, episodeNumber, seasonNumber, showTitle, plexMediaID)) + self.conn.commit() + # Catch the exception + except Exception as e: + # Roll back any change if something goes wrong + self.conn.rollback() + raise e + + def add_commercials_to_db(self, mediaID, title, duration, plexMediaID): + unix = int(time.time()) + try: + self.cursor.execute("REPLACE INTO commercials " + "(unix, mediaID, title, duration, plexMediaID) VALUES (?, ?, ?, ?, ?)", + (unix, mediaID, title, duration, plexMediaID)) + self.conn.commit() + # Catch the exception + except Exception as e: + print plexMediaID + # Roll back any change if something goes wrong + self.conn.rollback() + raise e + + def add_schedule_to_db(self, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax): + unix = int(time.time()) + try: + self.cursor.execute("REPLACE INTO schedule " + "(unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + (unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax)) + self.conn.commit() + # Catch the exception + except Exception as e: + # Roll back any change if something goes wrong + self.conn.rollback() + raise e + + def add_daily_schedule_to_db( + self, + mediaID, + title, + episodeNumber, + seasonNumber, + showTitle, + duration, + startTime, + endTime, + dayOfWeek, + sectionType, + plexMediaID + ): + + unix = int(time.time()) + + try: + + self.cursor.execute("INSERT OR REPLACE INTO daily_schedule " + "(unix, mediaID, title, episodeNumber, seasonNumber, " + "showTitle, duration, startTime, endTime, dayOfWeek, sectionType, plexMediaID) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + unix, + mediaID, + title, + episodeNumber, + seasonNumber, + showTitle, + duration, + startTime, + endTime, + dayOfWeek, + sectionType, + plexMediaID + )) - # Roll back any change if something goes wrong + self.conn.commit() - self.conn.rollback() + # Catch the exception + except Exception as e: - raise e + # Roll back any change if something goes wrong - def add_media_to_daily_schedule(self, media): + self.conn.rollback() - print "#### Adding media to db", media.title, media.start_time + raise e - self.add_daily_schedule_to_db( - 0, - media.title, - media.episode_number if media.__class__.__name__ == "Episode" else 0, - media.season_number if media.__class__.__name__ == "Episode" else 0, - media.show_series_title if media.__class__.__name__ == "Episode" else '', - media.duration, - media.start_time, - media.end_time, - media.day_of_week, - media.section_type - ) + def add_media_to_daily_schedule(self, media): - """Database functions. + print "#### Adding media to db", media.title, media.start_time - Getters, etc. - """ - def get_media(self, title, mediaType): + self.add_daily_schedule_to_db( + 0, + media.title, + media.episode_number if media.__class__.__name__ == "Episode" else 0, + media.season_number if media.__class__.__name__ == "Episode" else 0, + media.show_series_title if media.__class__.__name__ == "Episode" else '', + media.duration, + media.start_time, + media.end_time, + media.day_of_week, + media.section_type, + media.plex_media_id + ) - media = mediaType + """Database functions. - sql = "SELECT * FROM "+media+" WHERE (title LIKE ?) COLLATE NOCASE" - self.cursor.execute(sql, ("%"+title+"%", )) - media_item = self.cursor.fetchone() + Getters, etc. + """ + def get_media(self, title, mediaType): - return media_item + media = mediaType - def get_schedule(self): + sql = "SELECT * FROM "+media+" WHERE (title LIKE ?) COLLATE NOCASE" + self.cursor.execute(sql, ("%"+title+"%", )) + media_item = self.cursor.fetchone() - self.cursor.execute("SELECT * FROM schedule ORDER BY datetime(startTimeUnix) ASC") + return media_item - datalist = list(self.cursor.fetchall()) + def get_schedule(self): - return datalist + self.cursor.execute("SELECT * FROM schedule ORDER BY datetime(startTimeUnix) ASC") - def get_daily_schedule(self): + datalist = list(self.cursor.fetchall()) - self.cursor.execute("SELECT * FROM daily_schedule ORDER BY datetime(startTime) ASC") + return datalist - datalist = list(self.cursor.fetchall()) + def get_daily_schedule(self): - return datalist + self.cursor.execute("SELECT * FROM daily_schedule ORDER BY datetime(startTime) ASC") - def get_movie(self, title): + datalist = list(self.cursor.fetchall()) - media = "movies" + print "##### Getting Daily Schedule from DB." - return self.get_media(title, media) + return datalist - def get_shows(self, title): + def get_movie(self, title): - media = "shows" + media = "movies" - return self.get_media(title, media) + return self.get_media(title, media) - def get_music(self, title): + def get_shows(self, title): - media = "music" + media = "shows" - return self.get_media(title, media) + return self.get_media(title, media) - def get_video(self, title): + def get_music(self, title): - media = "videos" + media = "music" - return self.get_media(title, media) + return self.get_media(title, media) - def get_episodes(self, title): + def get_video(self, title): - media = "episodes" + media = "videos" - return self.get_media(title, media) + return self.get_media(title, media) - def update_shows_table_with_last_episode(self, showTitle, lastEpisodeTitle): + def get_episodes(self, title): - sql1 = "UPDATE shows SET lastEpisodeTitle = ? WHERE title LIKE ? COLLATE NOCASE" + media = "episodes" - self.cursor.execute(sql1, (lastEpisodeTitle, showTitle, )) + return self.get_media(title, media) - self.conn.commit() + def get_commercials(self): - def get_first_episode(self, tvshow): + self.cursor.execute("SELECT * FROM commercials ORDER BY duration ASC") - sql = ("SELECT id, unix, mediaID, title, duration, MIN(episodeNumber), MIN(seasonNumber), " - "showTitle FROM episodes WHERE ( showTitle LIKE ?) COLLATE NOCASE") + datalist = list(self.cursor.fetchall()) - self.cursor.execute(sql, (tvshow, )) + return datalist - first_episode = self.cursor.fetchone() + def update_shows_table_with_last_episode(self, showTitle, lastEpisodeTitle): - return first_episode + sql1 = "UPDATE shows SET lastEpisodeTitle = ? WHERE title LIKE ? COLLATE NOCASE" - ''' - * - * When incrementing episodes in a series I am advancing by "id" - * - ''' - def get_episode_id(self, episodeTitle): + self.cursor.execute(sql1, (lastEpisodeTitle, showTitle, )) - sql = "SELECT id FROM episodes WHERE ( title LIKE ?) COLLATE NOCASE" + self.conn.commit() - self.cursor.execute(sql, (episodeTitle, )) + def get_first_episode(self, tvshow): - episode_id = self.cursor.fetchone() + sql = ("SELECT id, unix, mediaID, title, duration, MIN(episodeNumber), MIN(seasonNumber), " + "showTitle FROM episodes WHERE ( showTitle LIKE ?) COLLATE NOCASE") - return episode_id + self.cursor.execute(sql, (tvshow, )) - def get_random_episode(self): + first_episode = self.cursor.fetchone() - sql = "SELECT * FROM episodes WHERE id IN (SELECT id FROM episodes ORDER BY RANDOM() LIMIT 1)" + return first_episode - self.cursor.execute(sql) + ''' + * + * When incrementing episodes in a series I am advancing by "id" + * + ''' + def get_episode_id(self, episodeTitle): - return self.cursor.fetchone() + sql = "SELECT id FROM episodes WHERE ( title LIKE ?) COLLATE NOCASE" - def get_random_movie(self): + self.cursor.execute(sql, (episodeTitle, )) - sql = "SELECT * FROM movies WHERE id IN (SELECT id FROM movies ORDER BY RANDOM() LIMIT 1)" + episode_id = self.cursor.fetchone() - self.cursor.execute(sql) + return episode_id - return self.cursor.fetchone() + def get_random_episode(self): - def get_next_episode(self, series): + sql = "SELECT * FROM episodes WHERE id IN (SELECT id FROM episodes ORDER BY RANDOM() LIMIT 1)" - #print(series) - ''' - * - * As a way of storing a "queue", I am storing the *next episode title in the "shows" table so I can - * determine what has been previously scheduled for each show - * - ''' - self.cursor.execute("SELECT lastEpisodeTitle FROM shows WHERE title LIKE ? COLLATE NOCASE", (series, )) + self.cursor.execute(sql) - last_title_list = self.cursor.fetchone() - ''' - * - * If the last episode stored in the "shows" table is empty, then this is probably a first run... - * - ''' - if last_title_list and last_title_list[0] == '': + return self.cursor.fetchone() - ''' - * - * Find the first episode of the series - * - ''' - first_episode = self.get_first_episode(series) + def get_random_movie(self): - first_episode_title = first_episode[3] + sql = "SELECT * FROM movies WHERE id IN (SELECT id FROM movies ORDER BY RANDOM() LIMIT 1)" - #print(first_episode_title) - ''' - * - * Add this episdoe title to the "shows" table for the queue functionality to work - * - ''' - self.update_shows_table_with_last_episode(series, first_episode_title) + self.cursor.execute(sql) - return first_episode + return self.cursor.fetchone() - elif last_title_list: - ''' - * - * The last episode stored in the "shows" table was not empty... get the next episode in the series - * - ''' - #print("First episode already set in shows, advancing episodes forward") + def get_next_episode(self, series): - #print(str(self.get_episode_id(last_title_list[0]))) + #print(series) + ''' + * + * As a way of storing a "queue", I am storing the *next episode title in the "shows" table so I can + * determine what has been previously scheduled for each show + * + ''' + self.cursor.execute("SELECT lastEpisodeTitle FROM shows WHERE title LIKE ? COLLATE NOCASE", (series, )) - """ - * - * If this isn't a first run, then grabbing the next episode by incrementing id - * - """ - sql = ("SELECT * FROM episodes WHERE ( id > "+str(self.get_episode_id(last_title_list[0])[0])+ - " AND showTitle LIKE ? ) ORDER BY seasonNumber LIMIT 1 COLLATE NOCASE") + last_title_list = self.cursor.fetchone() + ''' + * + * If the last episode stored in the "shows" table is empty, then this is probably a first run... + * + ''' + if last_title_list and last_title_list[0] == '': - self.cursor.execute(sql, (series, )) - ''' - * - * Try and advance to the next episode in the series, if it returns None then that means it reached the end... - * - ''' - next_episode = self.cursor.fetchone() + ''' + * + * Find the first episode of the series + * + ''' + first_episode = self.get_first_episode(series) - if next_episode != None: + first_episode_title = first_episode[3] - #print(next_episode[3]) + #print(first_episode_title) + ''' + * + * Add this episdoe title to the "shows" table for the queue functionality to work + * + ''' + self.update_shows_table_with_last_episode(series, first_episode_title) - self.update_shows_table_with_last_episode(series, next_episode[3]) + return first_episode - return next_episode + elif last_title_list: + ''' + * + * The last episode stored in the "shows" table was not empty... get the next episode in the series + * + ''' + #print("First episode already set in shows, advancing episodes forward") - else: + #print(str(self.get_episode_id(last_title_list[0]))) - print("Not grabbing next episode restarting series, series must be over. Restarting from episode 1.") + """ + * + * If this isn't a first run, then grabbing the next episode by incrementing id + * + """ + sql = ("SELECT * FROM episodes WHERE ( id > "+str(self.get_episode_id(last_title_list[0])[0])+ + " AND showTitle LIKE ? ) ORDER BY seasonNumber LIMIT 1 COLLATE NOCASE") - first_episode = self.get_first_episode(series) + self.cursor.execute(sql, (series, )) + ''' + * + * Try and advance to the next episode in the series, if it returns None then that means it reached the end... + * + ''' + next_episode = self.cursor.fetchone() - self.update_shows_table_with_last_episode(series, first_episode[3]) + if next_episode != None: - return first_episode - - def get_commercials(self, title): + #print(next_episode[3]) - media = "commercials" + self.update_shows_table_with_last_episode(series, next_episode[3]) - sql = "SELECT * FROM "+media+" WHERE (title LIKE ?) COLLATE NOCASE" - self.cursor.execute(sql, (title, )) - datalist = list(self.cursor.fetchone()) - if datalist > 0: - print(datalist) + return next_episode - return datalist + else: - else: + print("Not grabbing next episode restarting series, series must be over. Restarting from episode 1.") - return None + first_episode = self.get_first_episode(series) + + self.update_shows_table_with_last_episode(series, first_episode[3]) + + return first_episode + + def get_commercial(self, title): + + media = "commercials" + + sql = "SELECT * FROM "+media+" WHERE (title LIKE ?) COLLATE NOCASE" + self.cursor.execute(sql, (title, )) + datalist = list(self.cursor.fetchone()) + if datalist > 0: + print(datalist) + + return datalist + + else: + + return None diff --git a/src/PseudoDailyScheduleController.py b/src/PseudoDailyScheduleController.py index 08e8448..a7dbf7a 100644 --- a/src/PseudoDailyScheduleController.py +++ b/src/PseudoDailyScheduleController.py @@ -1,8 +1,11 @@ +#!/usr/bin/env python + from plexapi.server import PlexServer from datetime import datetime import sqlite3 from yattag import Doc +from yattag import indent import os, sys import logging @@ -10,328 +13,459 @@ import logging.handlers class PseudoDailyScheduleController(): - def __init__(self, server, token, clients): + def __init__(self, server, token, clients, debugMode = False): - self.PLEX = PlexServer(server, token) + self.PLEX = PlexServer(server, token) - self.BASE_URL = server + self.BASE_URL = server - self.TOKEN = token + self.TOKEN = token - self.PLEX_CLIENTS = clients + self.PLEX_CLIENTS = clients - self.my_logger = logging.getLogger('MyLogger') - self.my_logger.setLevel(logging.DEBUG) + self.DEBUG = debugMode - self.handler = logging.handlers.SysLogHandler(address = '/dev/log') + self.my_logger = logging.getLogger('MyLogger') + self.my_logger.setLevel(logging.DEBUG) - self.my_logger.addHandler(self.handler) + self.handler = logging.handlers.SysLogHandler(address = '/dev/log') - ''' - * - * Get the full image url (including plex token) from the local db. - * @param seriesTitle: case-unsensitive string of the series title - * @return string: full path of to the show image - * - ''' - def get_show_photo(self, section, title): + self.my_logger.addHandler(self.handler) - backgroundImagePath = None + ''' + * + * Get the full image url (including plex token) from the local db. + * @param seriesTitle: case-unsensitive string of the series title + * @return string: full path of to the show image + * + ''' + def get_show_photo(self, section, title): - backgroundImgURL = '' + backgroundImagePath = None - try: + backgroundImgURL = '' - backgroundImagePath = self.PLEX.library.section(section).get(title) + try: - except: + backgroundImagePath = self.PLEX.library.section(section).get(title) - return backgroundImgURL + except: - if backgroundImagePath != None and isinstance(backgroundImagePath.art, str): + return backgroundImgURL - backgroundImgURL = self.BASE_URL+backgroundImagePath.art+"?X-Plex-Token="+self.TOKEN + if backgroundImagePath != None and isinstance(backgroundImagePath.art, str): - return backgroundImgURL + backgroundImgURL = self.BASE_URL+backgroundImagePath.art+"?X-Plex-Token="+self.TOKEN - ''' - * - * Get the generated html for the .html file that is the schedule. - * ...This is used whenever a show starts or stops in order to add and remove various styles. - * @param currentTime: datetime object - * @param bgImageURL: str of the image used for the background - * @return string: the generated html content - * - ''' - def get_html_from_daily_schedule(self, currentTime, bgImageURL, datalist): + return backgroundImgURL - now = datetime.now() + def get_xml_from_daily_schedule(self, currentTime, bgImageURL, datalist): - time = now.strftime("%B %d, %Y") + now = datetime.now() - doc, tag, text, line = Doc( + time = now.strftime("%B %d, %Y") - ).ttl() + doc, tag, text, line = Doc( - doc.asis('') + ).ttl() - with tag('html'): + doc.asis('') - with tag('head'): + with tag('schedule', currently_playing_bg_image=bgImageURL if bgImageURL != None else ''): - with tag('title'): + for row in datalist: - text(time + " - Daily Pseudo Schedule") + if str(row[11]) == "Commercials" and self.DEBUG == False: - doc.asis('') - doc.asis('') + continue - if bgImageURL != None: - doc.asis('') + timeB = datetime.strptime(row[8], '%I:%M:%S %p') - with tag('body'): + if currentTime == None: + + with tag('time', + ('data-key', str(row[12])), + ('data-current', 'false'), + ('data-type', str(row[11])), + ('data-title', str(row[3])), + ('data-start-time', str(row[8])), + ): - with tag('div', klass='container mt-3'): + text(row[8]) - with tag('div', klass='row make-white'): + elif currentTime.hour == timeB.hour and currentTime.minute == timeB.minute: - with tag('div'): + with tag('time', + ('data-key', str(row[12])), + ('data-current', 'true'), + ('data-type', str(row[11])), + ('data-title', str(row[3])), + ('data-start-time', str(row[8])), + ): - with tag('div'): + text(row[8]) - line('h1', "Daily Pseudo Schedule", klass='col-12 pl-0') + else: - with tag('div'): + with tag('time', + ('data-key', str(row[12])), + ('data-current', 'false'), + ('data-type', str(row[11])), + ('data-title', str(row[3])), + ('data-start-time', str(row[8])), + ): - line('h3', time, klass='col-12 pl-1') + text(row[8]) - with tag('table', klass='col-12 table table-bordered table-hover'): + return indent(doc.getvalue()) - with tag('thead', klass='table-info'): - with tag('tr'): - with tag('th'): - text('#') - with tag('th'): - text('Type') - with tag('th'): - text('Series') - with tag('th'): - text('Title') - with tag('th'): - text('Start Time') - numberIncrease = 0 + ''' + * + * Get the generated html for the .html file that is the schedule. + * ...This is used whenever a show starts or stops in order to add and remove various styles. + * @param currentTime: datetime object + * @param bgImageURL: str of the image used for the background + * @return string: the generated html content + * + ''' + def get_html_from_daily_schedule(self, currentTime, bgImageURL, datalist): - for row in datalist: + now = datetime.now() - numberIncrease += 1 + time = now.strftime("%B %d, %Y") - with tag('tbody'): + doc, tag, text, line = Doc( - timeB = datetime.strptime(row[8], '%I:%M %p') + ).ttl() - if currentTime == None: + doc.asis('') - with tag('tr'): - with tag('th', scope='row'): - text(numberIncrease) - with tag('td'): - text(row[11]) - with tag('td'): - text(row[6]) - with tag('td'): - text(row[3]) - with tag('td'): - text(row[8]) + with tag('html'): - elif currentTime.hour == timeB.hour and currentTime.minute == timeB.minute: + with tag('head'): - with tag('tr', klass='bg-info'): + with tag('title'): - with tag('th', scope='row'): - text(numberIncrease) - with tag('td'): - text(row[11]) - with tag('td'): - text(row[6]) - with tag('td'): - text(row[3]) - with tag('td'): - text(row[8]) + text(time + " - Daily Pseudo Schedule") - else: + doc.asis('') + doc.asis('') - with tag('tr'): - with tag('th', scope='row'): - text(numberIncrease) - with tag('td'): - text(row[11]) - with tag('td'): - text(row[6]) - with tag('td'): - text(row[3]) - with tag('td'): - text(row[8]) + if bgImageURL != None: + doc.asis('') + with tag('body'): - return doc.getvalue() + with tag('div', klass='container mt-3'): - ''' - * - * Create 'schedules' dir & write the generated html to .html file. - * @param data: html string - * @return null - * - ''' - def write_schedule_to_file(self, data): + with tag('div', klass='row make-white'): - now = datetime.now() + with tag('div'): - fileName = "index.html" + with tag('div'): - writepath = './schedules/' + line('h1', "Daily Pseudo Schedule", klass='col-12 pl-0') - if not os.path.exists(writepath): + with tag('div'): - os.makedirs(writepath) + line('h3', time, klass='col-12 pl-1') - if os.path.exists(writepath+fileName): - - os.remove(writepath+fileName) + with tag('table', klass='col-12 table table-bordered table-hover'): - mode = 'a' if os.path.exists(writepath) else 'w' + with tag('thead', klass='table-info'): + with tag('tr'): + with tag('th'): + text('#') + with tag('th'): + text('Type') + with tag('th'): + text('Series') + with tag('th'): + text('Title') + with tag('th'): + text('Start Time') - with open(writepath+fileName, mode) as f: + numberIncrease = 0 - f.write(data) + for row in datalist: - ''' - * - * Trigger "playMedia()" on the Python Plex API for specified media. - * @param mediaType: str: "TV Shows" - * @param mediaParentTitle: str: "Seinfeld" - * @param mediaTitle: str: "The Soup Nazi" - * @return null - * - ''' - def play_media(self, mediaType, mediaParentTitle, mediaTitle): + if str(row[11]) == "Commercials" and self.DEBUG == False: - if mediaType == "TV Shows": + continue - mediaItems = self.PLEX.library.section(mediaType).get(mediaParentTitle).episodes() + numberIncrease += 1 - for item in mediaItems: + with tag('tbody'): - # print(part.title) + timeB = datetime.strptime(row[8], '%I:%M:%S %p') - if item.title == mediaTitle: + if currentTime == None: - for client in self.PLEX_CLIENTS: + with tag('tr'): + with tag('th', scope='row'): + text(numberIncrease) + with tag('td'): + text(row[11]) + with tag('td'): + text(row[6]) + with tag('td'): + text(row[3]) + with tag('td'): + text(row[8]) - clientItem = self.PLEX.client(client) + elif currentTime.hour == timeB.hour and currentTime.minute == timeB.minute: - clientItem.playMedia(item) - - break + with tag('tr', klass='bg-info'): - elif mediaType == "Movies": + with tag('th', scope='row'): + text(numberIncrease) + with tag('td'): + text(row[11]) + with tag('td'): + text(row[6]) + with tag('td'): + text(row[3]) + with tag('td'): + text(row[8]) - movie = self.PLEX.library.section(mediaType).get(mediaTitle) + else: - for client in self.PLEX_CLIENTS: + with tag('tr'): + with tag('th', scope='row'): + text(numberIncrease) + with tag('td'): + text(row[11]) + with tag('td'): + text(row[6]) + with tag('td'): + text(row[3]) + with tag('td'): + text(row[8]) - clientItem = self.PLEX.client(client) - clientItem.playMedia(movie) + return indent(doc.getvalue()) - else: + ''' + * + * Create 'schedules' dir & write the generated html to .html file. + * @param data: html string + * @return null + * + ''' + def write_schedule_to_file(self, data): - print("Not sure how to play {}".format(mediaType)) - - ''' - * - * If tv_controller() does not find a "startTime" for scheduled media, search for an "endTime" match for now time. - * ...This is useful for clearing the generated html schedule when media ends and there is a gap before the next media. - * @param null - * @return null - * - ''' - def check_for_end_time(self, datalist): + now = datetime.now() - currentTime = datetime.now() + fileName = "index.html" - """c.execute("SELECT * FROM daily_schedule") + writepath = './schedules/' - datalist = list(c.fetchall()) - """ - for row in datalist: + if not os.path.exists(writepath): - try: - - endTime = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S.%f') + os.makedirs(writepath) - except ValueError: + if os.path.exists(writepath+fileName): + + os.remove(writepath+fileName) - endTime = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S') + mode = 'a' if os.path.exists(writepath) else 'w' - if currentTime.hour == endTime.hour: + with open(writepath+fileName, mode) as f: - if currentTime.minute == endTime.minute: + f.write(data) - print("Ok end time found") + ''' + * + * Create 'schedules' dir & write the generated xml to .xml file. + * @param data: xml string + * @return null + * + ''' + def write_xml_to_file(self, data): - self.write_schedule_to_file(self.get_html_from_daily_schedule(None, None, datalist)) + now = datetime.now() - break - ''' - * - * Check DB / current time. If that matches a scheduled shows startTime then trigger play via Plex API - * @param null - * @return null - * - ''' - def tv_controller(self, datalist): + fileName = "pseudo_schedule.xml" - datalistLengthMonitor = 0; + writepath = './schedules/' - currentTime = datetime.now() + if not os.path.exists(writepath): - """c.execute("SELECT * FROM daily_schedule ORDER BY datetime(startTimeUnix) ASC") + os.makedirs(writepath) - datalist = list(c.fetchall())""" + if os.path.exists(writepath+fileName): + + os.remove(writepath+fileName) - self.my_logger.debug('TV Controller') + mode = 'a' if os.path.exists(writepath) else 'w' - for row in datalist: + with open(writepath+fileName, mode) as f: - timeB = datetime.strptime(row[8], '%I:%M %p') + f.write(data) - if currentTime.hour == timeB.hour: + ''' + * + * Trigger "playMedia()" on the Python Plex API for specified media. + * @param mediaType: str: "TV Shows" + * @param mediaParentTitle: str: "Seinfeld" + * @param mediaTitle: str: "The Soup Nazi" + * @return null + * + ''' + def play_media(self, mediaType, mediaParentTitle, mediaTitle): - if currentTime.minute == timeB.minute: + if mediaType == "TV Shows": - print("Starting Epsisode: " + row[3]) - print(row) + mediaItems = self.PLEX.library.section(mediaType).get(mediaParentTitle).episodes() - self.play_media(row[11], row[6], row[3]) + for item in mediaItems: - self.write_schedule_to_file( - self.get_html_from_daily_schedule( - timeB, - self.get_show_photo( - row[11], - row[6] if row[11] == "TV Shows" else row[3] - ), - datalist - ) - ) + # print(part.title) - self.my_logger.debug('Trying to play: ' + row[3]) + if item.title == mediaTitle: - break + for client in self.PLEX_CLIENTS: - datalistLengthMonitor += 1 + clientItem = self.PLEX.client(client) - if datalistLengthMonitor >= len(datalist): + clientItem.playMedia(item) + + break - self.check_for_end_time(datalist) \ No newline at end of file + elif mediaType == "Movies": + + movie = self.PLEX.library.section(mediaType).get(mediaTitle) + + for client in self.PLEX_CLIENTS: + + clientItem = self.PLEX.client(client) + + clientItem.playMedia(movie) + + elif mediaType == "Commercials": + + movie = self.PLEX.library.section(mediaType).get(mediaTitle) + + for client in self.PLEX_CLIENTS: + + clientItem = self.PLEX.client(client) + + clientItem.playMedia(movie) + + else: + + print("Not sure how to play {}".format(mediaType)) + + ''' + * + * If tv_controller() does not find a "startTime" for scheduled media, search for an "endTime" match for now time. + * ...This is useful for clearing the generated html schedule when media ends and there is a gap before the next media. + * @param null + * @return null + * + ''' + def check_for_end_time(self, datalist): + + currentTime = datetime.now() + + """c.execute("SELECT * FROM daily_schedule") + + datalist = list(c.fetchall()) + """ + for row in datalist: + + try: + + endTime = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S.%f') + + except ValueError: + + endTime = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S') + + if currentTime.hour == endTime.hour: + + if currentTime.minute == endTime.minute: + + if currentTime.second == endTime.second: + + print("Ok end time found") + + self.write_schedule_to_file(self.get_html_from_daily_schedule(None, None, datalist)) + self.write_xml_to_file(self.get_xml_from_daily_schedule(None, None, datalist)) + + break + ''' + * + * Check DB / current time. If that matches a scheduled shows startTime then trigger play via Plex API + * @param null + * @return null + * + ''' + def tv_controller(self, datalist): + + datalistLengthMonitor = 0; + + currentTime = datetime.now() + + """c.execute("SELECT * FROM daily_schedule ORDER BY datetime(startTimeUnix) ASC") + + datalist = list(c.fetchall())""" + + self.my_logger.debug('TV Controller') + + for row in datalist: + + timeB = datetime.strptime(row[8], '%I:%M:%S %p') + + if currentTime.hour == timeB.hour: + + if currentTime.minute == timeB.minute: + + if currentTime.second == timeB.second: + + print("Starting Media: " + row[3]) + print(row) + + self.play_media(row[11], row[6], row[3]) + + self.write_schedule_to_file( + self.get_html_from_daily_schedule( + timeB, + self.get_show_photo( + row[11], + row[6] if row[11] == "TV Shows" else row[3] + ), + datalist + ) + ) + + """Generate / write XML to file + """ + self.write_xml_to_file( + self.get_xml_from_daily_schedule( + timeB, + self.get_show_photo( + row[11], + row[6] if row[11] == "TV Shows" else row[3] + ), + datalist + ) + ) + + self.my_logger.debug('Trying to play: ' + row[3]) + + break + + datalistLengthMonitor += 1 + + if datalistLengthMonitor >= len(datalist): + + self.check_for_end_time(datalist) + + def make_xml_schedule(self, datalist): + + print "+++++ ", "Writing XML / HTML to file." + + self.write_schedule_to_file(self.get_html_from_daily_schedule(None, None, datalist)) + self.write_xml_to_file(self.get_xml_from_daily_schedule(None, None, datalist)) \ No newline at end of file diff --git a/src/Video.py b/src/Video.py index d9f6a5d..6507255 100644 --- a/src/Video.py +++ b/src/Video.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python + from Media import Media class Video(Media): - """Inherits Media. + """Inherits Media. Attributes: section_type: The type of library this is (i.e. "TV Shows") @@ -14,27 +16,29 @@ class Video(Media): is_strict_time: If strict time, then anchor to "natural_start_time" """ - def __init__( - self, - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ): + def __init__( + self, + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ): - super(Video, self).__init__( - section_type, - title, - natural_start_time, - natural_end_time, - duration, - day_of_week, - is_strict_time, - time_shift, - overlap_max - ) + super(Video, self).__init__( + section_type, + title, + natural_start_time, + natural_end_time, + duration, + day_of_week, + is_strict_time, + time_shift, + overlap_max, + plex_media_id + ) diff --git a/src/__init__.py b/src/__init__.py index 21d061f..027281c 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -5,4 +5,6 @@ from Movie import Movie from Media import Media from Music import Music from Video import Video -from PseudoDailyScheduleController import PseudoDailyScheduleController \ No newline at end of file +from PseudoDailyScheduleController import PseudoDailyScheduleController +from GoogleCalendar import GoogleCalendar +from PseudoChannelCommercial import PseudoChannelCommercial \ No newline at end of file