#!/usr/bin/env python import os, sys import socket import logging import logging.handlers from datetime import datetime import sqlite3 import thread,SocketServer,SimpleHTTPServer from plexapi.server import PlexServer from plexapi.client import PlexClient from yattag import Doc from yattag import indent class PseudoDailyScheduleController(): def __init__(self, server, token, clients, controllerServerPath = '', controllerServerPort = '8000', debugMode = False, htmlPseudoTitle = "Daily PseudoChannel" ): self.PLEX = PlexServer(server, token) self.BASE_URL = server self.TOKEN = token self.PLEX_CLIENTS = clients self.CONTROLLER_SERVER_PATH = controllerServerPath self.CONTROLLER_SERVER_PORT = controllerServerPort if controllerServerPort != '' else '80' self.DEBUG = debugMode self.webserverStarted = False self.HTML_PSEUDO_TITLE = htmlPseudoTitle try: self.my_logger = logging.getLogger('MyLogger') self.my_logger.setLevel(logging.DEBUG) self.handler = logging.handlers.SysLogHandler(address = '/dev/log') self.my_logger.addHandler(self.handler) except: pass ''' * * 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): backgroundImagePath = None backgroundImgURL = '' try: backgroundImagePath = self.PLEX.library.section(section).get(title) except: return backgroundImgURL if backgroundImagePath != None and isinstance(backgroundImagePath.art, str): backgroundImgURL = self.BASE_URL+backgroundImagePath.art+"?X-Plex-Token="+self.TOKEN return backgroundImgURL def start_server(self): if self.webserverStarted == False and self.CONTROLLER_SERVER_PATH != '': """Changing dir to the schedules dir.""" web_dir = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'schedules')) os.chdir(web_dir) PORT = int(self.CONTROLLER_SERVER_PORT) class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def log_message(self, format, *args): return global httpd try: #print "Starting webserver at port: ", PORT # create the httpd handler for the simplehttpserver # we set the allow_reuse_address incase something hangs can still bind to port class ReusableTCPServer(SocketServer.TCPServer): allow_reuse_address=True # specify the httpd service on 0.0.0.0 (all interfaces) on port 80 httpd = ReusableTCPServer(("0.0.0.0", PORT),MyHandler) # thread this mofo thread.start_new_thread(httpd.serve_forever,()) # handle keyboard interrupts except KeyboardInterrupt: core.print_info("Exiting the SET web server...") httpd.socket.close() except socket.error, exc: print "Caught exception socket.error : %s" % exc # handle the rest #except Exception: # print "[*] Exiting the SET web server...\n" # httpd.socket.close() self.webserverStarted = True def get_xml_from_daily_schedule(self, currentTime, bgImageURL, datalist): now = datetime.now() time = now.strftime("%B %d, %Y") doc, tag, text, line = Doc( ).ttl() doc.asis('<?xml version="1.0" encoding="UTF-8"?>') with tag('schedule', currently_playing_bg_image=bgImageURL if bgImageURL != None else ''): for row in datalist: if str(row[11]) == "Commercials" and self.DEBUG == False: continue timeB = datetime.strptime(row[8], '%I:%M:%S %p') 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])), ): text(row[8]) elif currentTime.hour == timeB.hour and currentTime.minute == timeB.minute: 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])), ): text(row[8]) else: 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])), ): text(row[8]) return indent(doc.getvalue()) ''' * * 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, nowPlayingTitle): now = datetime.now() time = now.strftime("%B %d, %Y") doc, tag, text, line = Doc( ).ttl() doc.asis('<!DOCTYPE html>') with tag('html'): with tag('head'): with tag('title'): text(time + " - Daily Pseudo Schedule") doc.asis('<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">') doc.asis('<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>') doc.asis('<link rel="shortcut icon" href="https://raw.githubusercontent.com/justinemter/pseudo-channel/master/favicon.ico" type="image/x-icon">') doc.asis(""" <script> $(function(){ var refreshFlag = ''; """ +"""var controllerServerPath ='"""+self.CONTROLLER_SERVER_PATH+":"+self.CONTROLLER_SERVER_PORT+"""'; if(controllerServerPath != ''){ console.log("here"); window.setInterval(function(){ $.ajax({ url: controllerServerPath+"/pseudo_refresh.txt", async: true, // asynchronous request? (synchronous requests are discouraged...) cache: false, // with this, you can force the browser to not make cache of the retrieved data dataType: "text", // jQuery will infer this, but you can set explicitly success: function( data, textStatus, jqXHR ) { newFlag = data; if(refreshFlag != ''){ if (refreshFlag != newFlag){ location.reload(); } else { //do nothing console.log("skip"); } } else { refreshFlag = newFlag; } } }); }, 1000); } else { setTimeout(function() {location.reload();}, 30000); } }); </script> """) if bgImageURL != None: doc.asis('<style>body{ background:transparent!important; } html { background: url('+bgImageURL+') no-repeat center center fixed; -webkit-background-size: cover;-moz-background-size: cover;-o-background-size: cover;background-size: cover;}.make-white { padding: 24px; background:rgba(255,255,255, 0.9); }</style>') with tag('body'): with tag('div', klass='container mt-3'): with tag('div', klass='row make-white'): with tag('div'): with tag('div'): line('h1', self.HTML_PSEUDO_TITLE, klass='col-12 pl-0') with tag('div'): line('h3', time, klass='col-12 pl-1') line('h3', "Now Playing: "+nowPlayingTitle, klass='col-12 pl-1', style="color:red;") with tag('table', klass='col-12 table table-bordered table-hover'): 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 for row in datalist: if str(row[11]) == "Commercials" and self.DEBUG == False: continue numberIncrease += 1 with tag('tbody'): if currentTime != None: currentTime = currentTime.replace(year=1900, month=1, day=1) timeBStart = datetime.strptime(row[8], '%I:%M:%S %p') timeBStart = timeBStart.replace(year=1900, month=1, day=1) try: timeBEnd = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S.%f') except: timeBEnd = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S') #print timeBStart if currentTime == None: 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]) elif (currentTime - timeBStart).total_seconds() >= 0 and \ (timeBEnd - currentTime).total_seconds() >= 0: #if self.DEBUG: print "+++++ Currently Playing:", row[3] with tag('tr', klass='bg-info'): 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]) else: 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]) return indent(doc.getvalue()) ''' * * Create 'schedules' dir & write the generated html to .html file. * @param data: html string * @return null * ''' def write_schedule_to_file(self, data): now = datetime.now() fileName = "index.html" writepath = './' if os.path.basename(os.getcwd()) == "schedules" else "./schedules/" if not os.path.exists(writepath): os.makedirs(writepath) if os.path.exists(writepath+fileName): os.remove(writepath+fileName) mode = 'a' if os.path.exists(writepath) else 'w' with open(writepath+fileName, mode) as f: f.write(data) self.start_server() ''' * * Create 'schedules' dir & write the generated xml to .xml file. * @param data: xml string * @return null * ''' def write_xml_to_file(self, data): now = datetime.now() fileName = "pseudo_schedule.xml" writepath = './' if os.path.basename(os.getcwd()) == "schedules" else "./schedules/" if not os.path.exists(writepath): os.makedirs(writepath) if os.path.exists(writepath+fileName): os.remove(writepath+fileName) mode = 'a' if os.path.exists(writepath) else 'w' with open(writepath+fileName, mode) as f: f.write(data) ''' * * Write 0 or 1 to file for the ajax in the schedule.html to know when to refresh * @param data: xml string * @return null * ''' def write_refresh_bool_to_file(self): fileName = "pseudo_refresh.txt" writepath = './' if os.path.basename(os.getcwd()) == "schedules" else "./schedules/" first_line = '' if not os.path.exists(writepath): os.makedirs(writepath) if not os.path.exists(writepath+fileName): file(writepath+fileName, 'w').close() mode = 'r+' with open(writepath+fileName, mode) as f: f.seek(0) first_line = f.read() if self.DEBUG: print "+++++ Html refresh flag: {}".format(first_line) if first_line == '' or first_line == "0": f.seek(0) f.truncate() f.write("1") else: f.seek(0) f.truncate() f.write("0") ''' * * 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, offset, customSectionName): try: if mediaType == "TV Shows": print "Here, Trying to play custom type: ", customSectionName mediaItems = self.PLEX.library.section(customSectionName).get(mediaParentTitle).episodes() for item in mediaItems: if item.title == mediaTitle: self._dispatch_play_media(media=item, offset=offset) break elif mediaType == "Movies" or mediaType == "Commercials": movie = self.PLEX.library.section(customSectionName).get(mediaTitle) self._dispatch_play_media(media=movie, offset=offset) else: print("##### Not sure how to play {}".format(customSectionName)) print "+++++ Done." except Exception as e: print e.__doc__ print e.message print "##### There was an error trying to play the media." pass def _dispatch_play_media(self, media, offset): for client in self.PLEX_CLIENTS: if type(client) == str: clientItem = self.PLEX.client(client) elif type(client) == dict: clientItem = PlexClient(server=self.PLEX, baseurl=client['baseurl']) clientItem.playMedia(media, offset=offset) def stop_media(self): try: self.my_logger.debug('Trying to stop media.') for client in self.PLEX_CLIENTS: clientItem = self.PLEX.client(client) clientItem.stop(mtype='video') self.my_logger.debug('Done.') except Exception as e: self.my_logger.debug('stop_media - except.', e) pass ''' * * 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: if self.DEBUG: 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)) self.write_refresh_bool_to_file() break def play(self, row, datalist, offset=0): print str("##### Starting Media: '{}'".format(row[3])).encode('UTF-8') print str("##### Media Offset: '{}' seconds.".format(int(offset / 1000))).encode('UTF-8') if self.DEBUG: print str(row).encode('UTF-8') timeB = datetime.strptime(row[8], '%I:%M:%S %p') print "Here, row[13]", row[13] self.play_media(row[11], row[6], row[3], offset, row[13]) self.write_schedule_to_file( self.get_html_from_daily_schedule( timeB, self.get_show_photo( row[13], row[6] if row[11] == "TV Shows" else row[3] ), datalist, row[6] + " - " + row[3] if row[11] == "TV Shows" else row[3] ) ) self.write_refresh_bool_to_file() """Generate / write XML to file """ self.write_xml_to_file( self.get_xml_from_daily_schedule( timeB, self.get_show_photo( row[13], row[6] if row[11] == "TV Shows" else row[3] ), datalist ) ) try: self.my_logger.debug('Trying to play: ' + row[3]) except: pass ''' * * 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())""" try: self.my_logger.debug('TV Controller') except: pass 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], row[13]) self.write_schedule_to_file( self.get_html_from_daily_schedule( timeB, self.get_show_photo( row[13], row[6] if row[11] == "TV Shows" else row[3] ), datalist, row[6] + " - " + row[3] if row[11] == "TV Shows" else row[3] ) ) self.write_refresh_bool_to_file() """Generate / write XML to file """ self.write_xml_to_file( self.get_xml_from_daily_schedule( timeB, self.get_show_photo( row[13], row[6] if row[11] == "TV Shows" else row[3] ), datalist ) ) try: self.my_logger.debug('Trying to play: ' + row[3]) except: pass break datalistLengthMonitor += 1 if datalistLengthMonitor >= len(datalist): self.check_for_end_time(datalist) def manually_get_now_playing_bg_image(self, currentTime, datalist): increase_var = 0 for row in datalist: #print row[8] #print row[9] if str(row[11]) == "Commercials" and self.DEBUG == False: continue timeBStart = datetime.strptime(row[8], '%I:%M:%S %p') timeBStart = timeBStart.replace(year=1900, month=1, day=1) try: timeBEnd = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S.%f') except: timeBEnd = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S') #print ((currentTime - timeBStart).total_seconds() >= 0 and \ # (timeBEnd - currentTime).total_seconds() >= 0) #print currentTime.minute #print timeBStart.minute if (currentTime - timeBStart).total_seconds() >= 0 and \ (timeBEnd - currentTime).total_seconds() >= 0: print "+++++ Made the conditional & found item: {}".format(row[6]) return self.get_show_photo( row[13], row[6] if row[11] == "TV Shows" else row[3] ) else: pass increase_var += 1 if len(datalist) >= increase_var: print("+++++ In 'manually_get_now_playing_bg_image()'. " "Reached the end of the schedule. No bgImages found.") return None def manually_get_now_playing_title(self, currentTime, datalist): increase_var = 0 for row in datalist: #print row[8] #print row[9] """if str(row[11]) == "Commercials" and self.DEBUG == False: continue""" timeBStart = datetime.strptime(row[8], '%I:%M:%S %p') timeBStart = timeBStart.replace(year=1900, month=1, day=1) try: timeBEnd = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S.%f') except: timeBEnd = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S') #print ((currentTime - timeBStart).total_seconds() >= 0 and \ # (timeBEnd - currentTime).total_seconds() >= 0) #print currentTime.minute #print timeBStart.minute if (currentTime - timeBStart).total_seconds() >= 0 and \ (timeBEnd - currentTime).total_seconds() >= 0: print "+++++ Made the conditional & found item: {}".format(row[6]) return row[6] + " - " + row[3] if row[11] == "TV Shows" else row[3] else: pass increase_var += 1 if len(datalist) >= increase_var: print("+++++ In 'manually_get_now_playing_title()'. " "Reached the end of the schedule. No bgImages found.") return '' def make_xml_schedule(self, datalist): print "##### ", "Writing XML / HTML to file." now = datetime.now() now = now.replace(year=1900, month=1, day=1) bgImage = self.manually_get_now_playing_bg_image(now, datalist) itemTitle = self.manually_get_now_playing_title(now, datalist) print "+++++ The path to the bgImage: {}".format(bgImage) self.write_refresh_bool_to_file() self.write_schedule_to_file(self.get_html_from_daily_schedule(now, bgImage, datalist, itemTitle)) self.write_xml_to_file(self.get_xml_from_daily_schedule(None, None, datalist))