diff --git a/resources/lib/emby/core/api.py b/resources/lib/emby/core/api.py index a5e93911..63009225 100644 --- a/resources/lib/emby/core/api.py +++ b/resources/lib/emby/core/api.py @@ -159,6 +159,13 @@ def get_next(index=None, limit=1): 'StartIndex': None if index is None else int(index) }) +def get_adjacent_episodes(show_id, item_id): + return shows("/%s/Episodes" % show_id, { + 'UserId': "{UserId}", + 'AdjacentTo': item_id, + 'Fields': "Overview" + }) + def get_genres(parent_id=None): return _get("Genres", { 'ParentId': parent_id, diff --git a/resources/lib/helper/playutils.py b/resources/lib/helper/playutils.py index 9f362c38..3cc1907f 100644 --- a/resources/lib/helper/playutils.py +++ b/resources/lib/helper/playutils.py @@ -46,7 +46,8 @@ def set_properties(item, method, server_id=None): 'SubsMapping': info.get('Subtitles'), 'AudioStreamIndex': info.get('AudioStreamIndex'), 'SubtitleStreamIndex': info.get('SubtitleStreamIndex'), - 'CurrentPosition': info.get('CurrentPosition') + 'CurrentPosition': info.get('CurrentPosition'), + 'CurrentEpisode': info.get('CurrentEpisode') }) window('emby_play.json', current) @@ -400,6 +401,14 @@ class PlayUtils(object): } if settings('transcode_h265.bool'): profile['DirectPlayProfiles'][0]['VideoCodec'] = "h264,mpeg4,mpeg2video" + else: + profile['TranscodingProfiles'].insert(0, { + "Container": "m3u8", + "Type": "Video", + "AudioCodec": "aac,mp3,ac3,opus,flac,vorbis", + "VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video", + "MaxAudioChannels": "6" + }) if settings('transcodeHi10P.bool'): profile['CodecProfiles'].append( diff --git a/resources/lib/helper/utils.py b/resources/lib/helper/utils.py index c2f5db31..2d51d6c6 100644 --- a/resources/lib/helper/utils.py +++ b/resources/lib/helper/utils.py @@ -2,6 +2,7 @@ ################################################################################################# +import binascii import json import logging import os @@ -127,13 +128,20 @@ def find(dict, item): if re.match(key, item, re.I): return dict[key] -def event(method, data=None): +def event(method, data=None, sender=None, hexlify=False): ''' Data is a dictionary. ''' data = data or {} - xbmc.executebuiltin('NotifyAll(plugin.video.emby, %s, "[%s]")' % (method, json.dumps(data).replace('"', '\\"'))) - LOG.debug("---[ event: %s ] %s", method, data) + sender = sender or "plugin.video.emby" + + if hexlify: + data = '\\"[\\"{0}\\"]\\"'.format(binascii.hexlify(json.dumps(data))) + else: + data = '"[%s]"' % json.dumps(data).replace('"', '\\"') + + xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data)) + LOG.debug("---[ event: %s/%s ] %s", sender, method, data) def dialog(dialog_type, *args, **kwargs): diff --git a/resources/lib/monitor.py b/resources/lib/monitor.py index 83234acf..1ebe3873 100644 --- a/resources/lib/monitor.py +++ b/resources/lib/monitor.py @@ -2,6 +2,7 @@ ################################################################################################# +import binascii import json import logging import threading @@ -45,8 +46,8 @@ class Monitor(xbmc.Monitor): LOG.info("--<[ kodi scan/%s ]", library) def onNotification(self, sender, method, data): - - if sender.lower() not in ('plugin.video.emby', 'xbmc'): + + if sender.lower() not in ('plugin.video.emby', 'xbmc', 'upnextprovider'): return if sender == 'plugin.video.emby': @@ -61,6 +62,18 @@ class Monitor(xbmc.Monitor): return data = json.loads(data)[0] + + elif sender == 'upnextprovider': + method = method.split('.')[1] + + if method not in ('plugin.video.emby_play_action'): + return + + data = json.loads(data) + method = "Play" + + if data: + data = json.loads(binascii.unhexlify(data[0])) else: if method not in ('Player.OnPlay', 'VideoLibrary.OnUpdate', 'Player.OnAVChange'): return diff --git a/resources/lib/player.py b/resources/lib/player.py index d0d77fe8..5df8af61 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -9,7 +9,8 @@ import os import xbmc import xbmcvfs -from helper import _, window, settings, dialog, JSONRPC +from objects.obj import Objects +from helper import _, api, window, settings, dialog, event, JSONRPC from emby import Emby ################################################################################################# @@ -24,6 +25,7 @@ class Player(xbmc.Player): # Borg - multiple instances, shared state _shared_state = {} played = {} + up_next = False def __init__(self): @@ -35,6 +37,7 @@ class Player(xbmc.Player): ''' We may need to wait for info to be set in kodi monitor. Accounts for scenario where Kodi starts playback and exits immediately. ''' + self.up_next = False count = 0 monitor = xbmc.Monitor() @@ -219,6 +222,49 @@ class Player(xbmc.Player): else: item['SubtitleStreamIndex'] = subs + tracks + 1 + def next_up(self): + + current_file = self.getPlayingFile() + item = self.played[current_file] + objects = Objects() + + if item['Type'] != 'Episode': + return + + next_items = item['Server']['api'].get_adjacent_episodes(item['CurrentEpisode']['tvshowid'], item['Id']) + + for index, next_item in enumerate(next_items['Items']): + if next_item['Id'] == item['Id']: + + try: + next_item = next_items['Items'][index + 1] + except IndexError: + LOG.warn("No next up episode.") + + return + + break + + API = api.API(next_item, item['Server']['auth/server-address']) + data = objects.map(next_item, "UpNext") + artwork = API.get_all_artwork(objects.map(next_item, 'ArtworkParent'), True) + data['art'] = { + 'tvshow.poster': artwork.get('Series.Primary'), + 'tvshow.fanart': None, + 'thumb': artwork.get('Primary') + } + if artwork['Backdrop']: + data['art']['tvshow.fanart'] = artwork['Backdrop'][0] + + next_info = { + 'play_info': {'ItemIds': [data['episodeid']], 'ServerId': item['ServerId'], 'PlayCommand': 'PlayNow'}, + 'current_episode': item['CurrentEpisode'], + 'next_episode': data + } + + LOG.debug("--[ next up ] %s", json.dumps(next_info, indent=4)) + event("upnext_data", next_info, hexlify=True) + def onPlayBackPaused(self): current_file = self.getPlayingFile() @@ -268,15 +314,32 @@ class Player(xbmc.Player): item = self.played[current_file] + if window('emby.external.bool'): + return + if not report: previous = item['CurrentPosition'] item['CurrentPosition'] = int(self.getTime()) + if int(item['CurrentPosition']) == 1: + return + + try: + played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime']) * 100 + except ZeroDivisionError: # Runtime is 0. + played = 0 + + if played > 2.0 and not self.up_next: + + self.up_next = True + self.next_up() + if (item['CurrentPosition'] - previous) < 30: return + result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]}) result = result.get('result', {}) item['Volume'] = result.get('volume') @@ -330,18 +393,10 @@ class Player(xbmc.Player): if item: window('emby.skip.%s.bool' % item['Id'], True) - if item['CurrentPosition'] and item['Runtime']: + if window('emby.external.bool'): + window('emby.external', clear=True) - try: - if window('emby.external.bool'): - window('emby.external', clear=True) - raise ValueError - - played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime']) - except ZeroDivisionError: # Runtime is 0. - played = 0 - except ValueError: - played = 100 + if int(item['CurrentPosition']) == 1: item['CurrentPosition'] = int(item['Runtime']) data = {