def stop_show(): """ Tells Kodi to stop playing :return: None """ kodi = KodiResource() kodi.player_stop()
async def event_listener(): """ This makes a websocket connection to Kodi and listens to all events raised, for each event raised, a JSON object is formatted as string adn is stored as a line in a log file. The log file naming convention is yyyy-mm-dd.txt adn the new file is created sunday 00:00 of every week Our websocket connection was based of this example: https://pypi.org/project/websockets/ :return: None """ async with websockets.connect(parameters.KODI_WEBSOCKET_URL, ping_interval=None) as websocket: print('Kodi connected') await websocket.send( '{"jsonrpc":"2.0","method":"JSONRPC.NotifyAll","params": { "sender": "tom", "message": "x" }}' ) log = None kodi = KodiResource() async for message in websocket: event = await websocket.recv() now = datetime.datetime.now() event_data = kodi.player_get_item() row = '{"date": "' + now.strftime( "%Y-%m-%d %H:%M:%S" ) + '", "event": ' + event + ', "item": ' + json.dumps( event_data) + '}' # calculate name of the file, this row should be saved to, # sunday 00:00 of every week week_start = now - datetime.timedelta(days=now.isoweekday() % 7) file_name = week_start.strftime("%Y-%m-%d") + ".txt" print(file_name, row) if log is None or file_name != log.name: if log: # the currently open file is no the log we want to save to, so close it log.close() # open the new file in 'append' mode log = open(parameters.EVENT_LOG_PATH + file_name, 'a') log.write(row + '\n') # flush contents of python file writing buffer to file, # makes sure what we've written is immediately inserted log.flush()
class RecordBroadcast(EventLogReader): """ Used to automatically add timers, if the show is being watched by the user """ def __init__(self, offset_filename): super(RecordBroadcast, self).__init__(offset_filename) self.db = DatabaseResource() self.kodi = KodiResource() def on_event(self, event_dict): """ If we get a Player.OnAvChange and the user is watching a live broadcast, and assuming its not a disliked show, we add a timer. :param event_dict: Contains 3 elements; date, event, item :return: None """ if event_dict['event']['method'] == "Player.OnAVChange": print('---\n%s' % event_dict['event']['method']) tv_show = True try: if event_dict['event']['params']['data']['type'] == "channel": pass else: tv_show = False except KeyError: if event_dict['event']['params']['data']['item'][ 'type'] == "channel": pass else: tv_show = False if tv_show: channel_id = event_dict['event']['params']['data']['item'][ 'id'] broadcastdetails_json = self.kodi.pvr_get_channel_details( channel_id) if not broadcastdetails_json: # this is an old record. Skip it print('Broadcast details not found. No Timer added') pass elif 'broadcastnow' not in broadcastdetails_json: # this is an old record. Skip it print('Broadcast details not found. No Timer added') pass else: title = broadcastdetails_json['broadcastnow']['title'] broadcast_id = broadcastdetails_json['broadcastnow'][ 'broadcastid'] dislikes = Dislikes(self.db.connection.cursor()) if not dislikes.is_disliked(title): TimerManager().add_timer(title, broadcast_id) else: print("%s is a disliked show, no timer created" % title) else: print("This is not a live broadcast")
def watched_recording(): """ When a recording that is being played has already been seen by the user, this method will delete the recording. Steps taken: 1) Ask Kodi what is being played 2) Find and delete recording using TVHeadend :return: None """ kodi = KodiResource() tv = TvHeadEndResource() item_dict = kodi.player_get_item() title = item_dict['title'] plot = item_dict['plot'] if title == "": print("Current Kodi player doesn't have a title") else: tv.find_and_delete_recordings(title, plot=plot) print(title, plot) return title
def dislike_show(): """ Marks a shows as 'disliked'. This method takes several steps: 1) asks Kodi what is being watched 2) searches the Shows table for the same show 3) updates the Show record with disliked = 1 4) delete all recordings of the show 5) delete any timers associated with the show :return: None """ connection = mysql.connector.connect(user=parameters.DB_USER, database=parameters.DB_NAME) cursor = connection.cursor() kodi = KodiResource() item_dict = kodi.player_get_item() update_shows_with_dislikes = ("UPDATE shows " "SET disliked = 1 " "WHERE title = %s") title = item_dict['title'] cursor.execute(update_shows_with_dislikes, (title, )) connection.commit() tv = TvHeadEndResource() tv.find_and_delete_recordings(title) print(title) timer = TimerManager() timer_ids = timer.find_timer_ids(title) if len(timer_ids) > 0: print("deleting : ", title) timer.delete_timers(timer_ids)
def __init__(self, offset_filename): super(RecordBroadcast, self).__init__(offset_filename) self.db = DatabaseResource() self.kodi = KodiResource()
def play_something(): """ Finds a recording to play and tells kodi to play it, the steps are; 1) Get a list of all recordings from Kodi 2) Grab the title of the first recording 3) Get the last season/episode watched from the database 4) Find the next episode to watch in the recordings list 5) Flay the net episode :param title: Optionally a specific title to watch :return: None """ kodi = KodiResource() tv = TvHeadEndResource() record_dict = kodi.pvr_get_recordings() if len(record_dict) == 0: return title = record_dict[0]['label'] connection = mysql.connector.connect(user=parameters.DB_USER, database=parameters.DB_NAME) cursor = connection.cursor() select_season_info = ( "select s.show_id, e.season, max(e.episode) last_episode " "from shows s " "inner join episodes e on s.show_id = e.show_id " "where s.title = %s " "group by s.show_id, e.season " "order by e.season desc " "limit 1; ") cursor.execute(select_season_info, (title, )) next_episode = 0 for (show_id, season, last_episode) in cursor: next_episode = last_episode + 1 print("seen", season, last_episode, "looking for", season, next_episode) resume_current = False player_recording = None recording_ids = [] print("Check for new episode...") for record in record_dict: # print(record) if record['label'].lower() == title.lower(): # print(record['label'], record['recordingid']) recording_ids.append(record['recordingid']) record_details_dict = kodi.pvr_get_recording_details_batch(recording_ids) for line in record_details_dict: episode_dict = series_info(line['plot']) if episode_dict['episode'] < next_episode - 2: tv.find_and_delete_recordings(line['label'], plot=line['plot']) if episode_dict['episode'] == next_episode - 1 and line['resume'][ 'position'] > 0: print("Resume current episode", next_episode - 1) player_recording = { "id": line['recordingid'], "title": line['label'] } resume_current = True if episode_dict['episode'] == next_episode: print("Found next episode", next_episode) player_recording = { "id": line['recordingid'], "title": line['label'] } if not player_recording: print("No unwatched future episodes") if player_recording: print('Playing', player_recording['title']) if resume_current: # Kodi asks user if they want to start from beginning or to resume, by default it highlights the resume # option, therefore we want send a post to "select" current choice. This is my hack for getting around # Kodi's api limitation in that there is no command to skip the option. The delay is because our player.open # post doesnt give a response until the set programme is being played, so we create a timer delay ,rather # than a time.sleep, for the select request to run despite there no request # after given seconds for Kodi to catch up t = Timer(2.0, kodi.input_select) t.start() print("play") kodi.player_open(player_recording['id']) else: kodi.player_open(player_recording['id']) return player_recording['title'] return "nothing"
async def event_listener(): """ This makes a websocket connection to Kodi and listens to all events raised, for each event raised, a JSON object is formatted as string adn is stored as a line in a log file. The log file naming convention is yyyy-mm-dd.txt adn the new file is created sunday 00:00 of every week Our websocket connection was based of this example: https://pypi.org/project/websockets/ :return: None """ async with websockets.connect(parameters.KODI_WEBSOCKET_URL, ping_interval=None) as websocket: print('Kodi connected') await websocket.send( '{"jsonrpc":"2.0","method":"JSONRPC.NotifyAll","params": { "sender": "tom", "message": "x" }}' ) log = None kodi = KodiResource() async for message in websocket: if message == '': print('Skipping empty event') continue now = datetime.datetime.now() event = json.loads(message) event_data = event['params']['data'] or {} if 'item' in event_data: event_item = event_data['item'] or {} else: event_item = {} if 'channeltype' in event_item and event_item[ 'channeltype'] == 'tv': data = kodi.pvr_get_channel_details(event_item['id'], [ "thumbnail", "channeltype", "hidden", "locked", "channel", "lastplayed", "broadcastnow", "broadcastnext", "uniqueid", "icon", "channelnumber", "subchannelnumber", "isrecording" ]) else: data = {} item = kodi.player_get_item(quiet=True) or {} row = json.dumps({ "date": now.strftime("%Y-%m-%d %H:%M:%S"), "event": event, "data": data, "item": item }) # calculate name of the file, this row should be saved to, # sunday 00:00 of every week week_start = now - datetime.timedelta(days=now.isoweekday() % 7) file_name = week_start.strftime("%Y-%m-%d") + ".txt" print(file_name, row) if log is None or file_name != log.name: if log: # the currently open file is no the log we want to save to, so close it log.close() # open the new file in 'append' mode log = open(parameters.EVENT_LOG_PATH + file_name, 'a') log.write(row + '\n') # flush contents of python file writing buffer to file, # makes sure what we've written is immediately inserted log.flush()
def __init__(self, offset_filename): super(RecordSimilarBroadcasts, self).__init__(offset_filename) self.db = DatabaseResource() self.kodi = KodiResource() self.tvdb = TheTvDbResource()
class RecordSimilarBroadcasts(EventLogReader): """ Take a look at what the user usually watches and find other shows for similar genres with the same channels in the kodi programme guide and score the results in an ordered list, make timers for these in this order """ def __init__(self, offset_filename): super(RecordSimilarBroadcasts, self).__init__(offset_filename) self.db = DatabaseResource() self.kodi = KodiResource() self.tvdb = TheTvDbResource() def on_event(self, event_dict): """ :param event_dict: Contains 3 elements; date, event, item :return: None """ # needs a trigger event from log fav_channel = ( "select c.channel, count(c.channel) " "from shows s " "inner join show_channel_map m on s.show_ID = m.show_ID " "inner join channels c on m.channel_ID = c.channel_ID " "where s.disliked = 0 " "group by c.channel " "order by 2 desc ") fav_genre = ("select g.genre, count(m.genre_id) " "from genres g " "inner join show_genre_map m on g.genre_id = m.genre_id " "inner join shows s on s.show_ID = m.show_ID " "where s.disliked = 0 " "group by genre " "order by 2 desc ") print('---\nScanning for similar broadcasts...') fav_channels = [] self.db.cursor.execute(fav_channel) for (fav_channel, fav_channel_count) in self.db.cursor: fav_channels.append((fav_channel, fav_channel_count)) print('Top channels', fav_channels) fav_genres = [] self.db.cursor.execute(fav_genre) for (fav_genre, fav_genre_count) in self.db.cursor: fav_genres.append((fav_genre, fav_genre_count)) print('Top genres', fav_genres) dislikes = Dislikes(self.db.cursor) # For each of the favourite channels find a valid Kodi PVR channel ID. # Store this in channel_ids channel_ids = [] pvr_channels = self.kodi.pvr_get_channels() for pvr_channel in pvr_channels: for (fav_channel, fav_channel_count) in fav_channels: if pvr_channel['label'] == fav_channel: channel_ids.append((pvr_channel, fav_channel_count)) # Populate timer_dict with Kodi broadcasts matching a favourite channel and favourite genre # Make sure we also store in the timer_dict value, 'channel', 'channel_popularity' and 'genre_popularity' timer_dict = {} for (pvr_channel, fav_channel_count) in channel_ids: channel_id = pvr_channel['channelid'] broadcasts = self.kodi.pvr_get_broadcasts(channel_id) if broadcasts: for broadcast in broadcasts: if dislikes.is_disliked(broadcast['title']): print('Skipping disliked show %s' % broadcast['title']) continue if not series_info(broadcast['plot'])['episode'] == 0: genres = broadcast['genre'] genre_popularity = [] match = False for (fav_genre, fav_genre_count) in fav_genres: for genre in genres: if fav_genre == genre: genre_popularity.append(fav_genre_count) match = True if match: broadcast['channel'] = pvr_channel['label'] broadcast['channel_popularity'] = fav_channel_count broadcast['genre_popularity'] = genre_popularity timer_dict[broadcast["label"]] = broadcast # add rating from tvdb to each value in timer_dict, default rating is 0.7 for title in timer_dict.keys(): series_rating = 0.7 # default rating, should there be no rating found on thetvdb.com series_dict = self.tvdb.search_series(title) if series_dict: series_id = series_dict[0]['id'] series_info_dict = self.tvdb.series_info(series_id) if series_info_dict: rating = series_info_dict['siteRating'] if rating > 0: series_rating = rating / 10 timer_dict[title]["rating"] = series_rating print("Rating for %s is %s/10" % (title, series_rating * 10)) # Create a list of timer candidates. Create a score for each candidate candidates = [] for broadcast in timer_dict.values(): sum_genre_pop = 0 for genre_pop in broadcast['genre_popularity']: sum_genre_pop += genre_pop avg_genre_popularity = sum_genre_pop / len( broadcast['genre_popularity']) score = (broadcast['channel_popularity'] + avg_genre_popularity) / broadcast['rating'] broadcast = { "title": broadcast['label'], "genre": broadcast['genre'], "channel": broadcast['channel'], "broadcastid": broadcast['broadcastid'], "score": score } candidates.append(broadcast) # Sort candidates by score, reverse order candidates = sorted(candidates, key=lambda sugg_dict: sugg_dict['score'], reverse=True) # pprint(candidates) # Add timers for the top 10 candidates only final_list = candidates[:10] for broadcast in final_list: print( 'Found similar broadcast "%s" %s on %s' % (broadcast['title'], broadcast['genre'], broadcast['channel']))