Add manual transcode option

Update queue playlist
This commit is contained in:
angelblue05 2018-09-09 00:44:23 -05:00
parent cdb3b54026
commit 24ab27bbe2
8 changed files with 212 additions and 46 deletions

View file

@ -418,6 +418,14 @@ msgctxt "#33009"
msgid "Invalid username or password" msgid "Invalid username or password"
msgstr "" msgstr ""
msgctxt "#33013"
msgid "Choose the audio stream"
msgstr ""
msgctxt "#33014"
msgid "Choose the subtitles stream"
msgstr ""
msgctxt "#33015" msgctxt "#33015"
msgid "Delete file from Emby?" msgid "Delete file from Emby?"
msgstr "" msgstr ""
@ -757,3 +765,15 @@ msgstr ""
msgctxt "#33156" msgctxt "#33156"
msgid "A patch has been applied!" msgid "A patch has been applied!"
msgstr "" msgstr ""
msgctxt "#33157"
msgid "Audio only"
msgstr ""
msgctxt "#33158"
msgid "Subtitles only"
msgstr ""
msgctxt "#33159"
msgid "Enable audio/subtitles selection"
msgstr ""

View file

@ -172,6 +172,9 @@ def delete_item(item_id):
def get_local_trailers(item_id): def get_local_trailers(item_id):
return user_items("/%s/LocalTrailers" % item_id) return user_items("/%s/LocalTrailers" % item_id)
def get_transcode_settings():
return _get('System/Configuration/encoding')
def get_ancestors(item_id): def get_ancestors(item_id):
return items("/%s/Ancestors" % item_id, params={ return items("/%s/Ancestors" % item_id, params={
'UserId': "{UserId}" 'UserId': "{UserId}"

View file

@ -69,7 +69,7 @@ class Events(object):
elif mode =='play': elif mode =='play':
item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get() item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get()
Actions(params.get('server')).play(item, params.get('dbid')) Actions(params.get('server')).play(item, params.get('dbid'), playlist=params.get('playlist') == 'true')
elif mode == 'playlist': elif mode == 'playlist':
event('PlayPlaylist', {'Id': params['id'], 'ServerId': server}) event('PlayPlaylist', {'Id': params['id'], 'ServerId': server})

View file

@ -13,6 +13,7 @@ import xbmcvfs
import api import api
import database import database
import client import client
import collections
from . import _, settings, window, dialog from . import _, settings, window, dialog
from libraries import requests from libraries import requests
from downloader import TheVoid from downloader import TheVoid
@ -102,7 +103,7 @@ class PlayUtils(object):
return sources return sources
def select_source(self, sources): def select_source(self, sources, audio=None, subtitle=None):
if len(sources) > 1: if len(sources) > 1:
selection = [] selection = []
@ -119,7 +120,7 @@ class PlayUtils(object):
else: else:
source = sources[0] source = sources[0]
self.get(source) self.get(source, audio, subtitle)
return source return source
@ -171,7 +172,7 @@ class PlayUtils(object):
return False return False
def get(self, source): def get(self, source, audio=None, subtitle=None):
''' The server returns sources based on the MaxStreamingBitrate value and other filters. ''' The server returns sources based on the MaxStreamingBitrate value and other filters.
''' '''
@ -192,10 +193,10 @@ class PlayUtils(object):
else: else:
LOG.info("--[ transcode ]") LOG.info("--[ transcode ]")
self.transcode(source) self.transcode(source, audio, subtitle)
self.info['AudioStreamIndex'] = source.get('DefaultAudioStreamIndex') self.info['AudioStreamIndex'] = self.info.get('AudioStreamIndex') or source.get('DefaultAudioStreamIndex')
self.info['SubtitleStreamIndex'] = source.get('DefaultSubtitleStreamIndex') self.info['SubtitleStreamIndex'] = self.info.get('SubtitleStreamIndex') or source.get('DefaultSubtitleStreamIndex')
self.item['PlaybackInfo'].update(self.info) self.item['PlaybackInfo'].update(self.info)
def live_stream(self, source): def live_stream(self, source):
@ -217,13 +218,23 @@ class PlayUtils(object):
return info['MediaSource'] return info['MediaSource']
def transcode(self, source): def transcode(self, source, audio=None, subtitle=None):
if not 'TranscodingUrl' in source: if not 'TranscodingUrl' in source:
raise Exception("use get_sources to get transcoding url") raise Exception("use get_sources to get transcoding url")
self.info['Method'] = "Transcode" self.info['Method'] = "Transcode"
base, params = source['TranscodingUrl'].split('?') base, params = source['TranscodingUrl'].split('?')
if settings('skipDialogTranscode') != "3" and source.get('MediaStreams'):
url_parsed = params.split('&')
for i in url_parsed:
if 'AudioStreamIndex' in i or 'AudioBitrate' in i or 'SubtitleStreamIndex' in i: # handle manually
url_parsed.remove(i)
params = "%s%s" % ('&'.join(url_parsed), self.get_audio_subs(source, audio, subtitle))
self.info['Path'] = "%s/emby%s?%s" % (self.info['ServerAddress'], base.replace('stream', "master"), params) self.info['Path'] = "%s/emby%s?%s" % (self.info['ServerAddress'], base.replace('stream', "master"), params)
self.info['Path'] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution()) self.info['Path'] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
@ -422,8 +433,7 @@ class PlayUtils(object):
if 'DeliveryUrl' in stream: if 'DeliveryUrl' in stream:
url = "%s/emby%s" % (self.info['ServerAddress'], stream['DeliveryUrl']) url = "%s/emby%s" % (self.info['ServerAddress'], stream['DeliveryUrl'])
else: else:
url = ("%s/emby/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % url = self.get_subtitles(source, stream, index)
(self.info['ServerAddress'], self.item['Id'], source['Id'], index, stream['Codec'], self.info['Token']))
if url is None: if url is None:
continue continue
@ -462,7 +472,7 @@ class PlayUtils(object):
path = os.path.join(temp, filename) path = os.path.join(temp, filename)
try: try:
response = requests.get(src, stream=True) response = requests.get(src, stream=True, verify=False)
response.raise_for_status() response.raise_for_status()
except Exception as e: except Exception as e:
raise raise
@ -473,3 +483,113 @@ class PlayUtils(object):
del response del response
return path return path
def get_audio_subs(self, source, audio=None, subtitle=None):
''' For transcoding only
Present the list of audio/subs to select from, before playback starts.
Since Emby returns all possible tracks together, sort them.
IsTextSubtitleStream if true, is available to download from server.
'''
prefs = ""
audio_streams = collections.OrderedDict()
subs_streams = collections.OrderedDict()
streams = source['MediaStreams']
for stream in streams:
index = stream['Index']
stream_type = stream['Type']
if stream_type == 'Audio':
codec = stream['Codec']
channel = stream.get('ChannelLayout', "")
if 'Language' in stream:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channel)
else:
track = "%s - %s %s" % (index, codec, channel)
audio_streams[track] = index
elif stream_type == 'Subtitle':
if 'Language' in stream:
track = "%s - %s" % (index, stream['Language'])
else:
track = "%s - %s" % (index, stream['Codec'])
if stream['IsDefault']:
track = "%s - Default" % track
if stream['IsForced']:
track = "%s - Forced" % track
subs_streams[track] = index
skip_dialog = int(settings('skipDialogTranscode') or 0)
audio_selected = None
if audio:
audio_selected = audio
elif skip_dialog in (0, 1):
if len(audio_streams) > 1:
selection = list(audio_streams.keys())
resp = dialog("select", _(33013), selection)
audio_selected = audio_streams[selection[resp]] if resp else source['DefaultAudioStreamIndex']
else: # Only one choice
audio_selected = audio_streams[next(iter(audio_streams))]
else:
audio_selected = source['DefaultAudioStreamIndex']
self.info['AudioStreamIndex'] = audio_selected
prefs += "&AudioStreamIndex=%s" % audio_selected
prefs += "&AudioBitrate=384000" if streams[audio_selected].get('Channels', 0) > 2 else "&AudioBitrate=192000"
if subtitle:
index = subtitle
server_settings = TheVoid('GetTranscodeOptions', {'ServerId': self.info['ServerId']}).get()
stream = streams[index]
if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:
self.info['SubtitleUrl'] = self.get_subtitles(source, stream, index)
else:
prefs += "&SubtitleStreamIndex=%s" % index
self.info['SubtitleStreamIndex'] = index
elif skip_dialog in (0, 2) and len(subs_streams):
selection = list(['No subtitles']) + list(subs_streams.keys())
resp = dialog("select", _(33014), selection)
if resp:
index = subs_streams[selection[resp]] if resp > -1 else source.get('DefaultSubtitleStreamIndex')
if index is not None:
server_settings = TheVoid('GetTranscodeOptions', {'ServerId': self.info['ServerId']}).get()
stream = streams[index]
if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:
self.info['SubtitleUrl'] = self.get_subtitles(source, stream, index)
else:
prefs += "&SubtitleStreamIndex=%s" % index
self.info['SubtitleStreamIndex'] = index
return prefs
def get_subtitles(self, source, stream, index):
if 'DeliveryUrl' in stream:
url = "%s/emby%s" % (self.info['ServerAddress'], stream['DeliveryUrl'])
else:
url = ("%s/emby/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" %
(self.info['ServerAddress'], self.item['Id'], source['Id'], index, stream['Codec'], self.info['Token']))
return url

View file

@ -55,7 +55,7 @@ class Monitor(xbmc.Monitor):
'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken', 'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken',
'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem', 'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem',
'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes', 'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes',
'GetTheme', 'Playstate', 'GeneralCommand'): 'GetTheme', 'Playstate', 'GeneralCommand', 'GetTranscodeOptions'):
return return
data = json.loads(data)[0] data = json.loads(data)[0]
@ -139,6 +139,12 @@ class Monitor(xbmc.Monitor):
window('emby_%s.json' % data['VoidName'], users) window('emby_%s.json' % data['VoidName'], users)
LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName'])
elif method == 'GetTranscodeOptions':
result = server['api'].get_transcode_settings()
window('emby_%s.json' % data['VoidName'], result)
LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName'])
elif method == 'GetThemes': elif method == 'GetThemes':
if data['Type'] == 'Video': if data['Type'] == 'Video':
@ -183,8 +189,10 @@ class Monitor(xbmc.Monitor):
elif method == 'Play': elif method == 'Play':
items = server['api'].get_items(data['ItemIds']) item = server['api'].get_item(data['ItemIds'].pop(0))
PlaylistWorker(data.get('ServerId'), items['Items'], data['PlayCommand'] == 'PlayNow', data['ItemIds'].insert(0, item)
PlaylistWorker(data.get('ServerId'), data['ItemIds'], data['PlayCommand'] == 'PlayNow',
data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'), data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
data.get('SubtitleStreamIndex')).start() data.get('SubtitleStreamIndex')).start()

View file

@ -43,7 +43,7 @@ class Actions(object):
return xbmc.PlayList(xbmc.PLAYLIST_VIDEO) return xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
def play(self, item, db_id=None, transcode=False): def play(self, item, db_id=None, transcode=False, playlist=False):
''' Play item based on if playback started from widget ot not. ''' Play item based on if playback started from widget ot not.
To get everything to work together, play the first item in the stack with setResolvedUrl, To get everything to work together, play the first item in the stack with setResolvedUrl,
@ -63,7 +63,7 @@ class Actions(object):
self.stack[0][1].setPath(self.stack[0][0]) self.stack[0][1].setPath(self.stack[0][0])
try: try:
if self.detect_widgets(item): if not playlist and self.detect_widgets(item):
LOG.info(" [ play/widget ]") LOG.info(" [ play/widget ]")
raise IndexError raise IndexError
@ -172,10 +172,10 @@ class Actions(object):
def play_playlist(self, items, clear=True, seektime=None, audio=None, subtitle=None): def play_playlist(self, items, clear=True, seektime=None, audio=None, subtitle=None):
''' Play a list of items. Creates a new playlist. ''' Play a list of items. Creates a new playlist. Add additional items as plugin listing.
''' '''
playlist = self.get_playlist(items[0]) item = items[0]
started = False playlist = self.get_playlist(item)
if clear: if clear:
playlist.clear() playlist.clear()
@ -183,32 +183,35 @@ class Actions(object):
else: else:
index = max(playlist.getposition(), 0) + 1 # Can return -1 index = max(playlist.getposition(), 0) + 1 # Can return -1
for order, item in enumerate(items): listitem = xbmcgui.ListItem()
LOG.info("[ playlist/%s ] %s", item['Id'], item['Name'])
play = playutils.PlayUtils(item, False, self.server_id, self.server)
source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem)
item['PlaybackInfo']['AudioStreamIndex'] = audio or item['PlaybackInfo']['AudioStreamIndex']
item['PlaybackInfo']['SubtitleStreamIndex'] = subtitle or item['PlaybackInfo'].get('SubtitleStreamIndex')
self.set_listitem(item, listitem, None, True if seektime else False)
listitem.setPath(item['PlaybackInfo']['Path'])
playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id)
playlist.add(item['PlaybackInfo']['Path'], listitem, index)
index += 1
if clear:
xbmc.Player().play(playlist)
for item in items[1:]:
listitem = xbmcgui.ListItem() listitem = xbmcgui.ListItem()
LOG.info("[ playlist/%s ] %s", item['Id'], item['Name']) LOG.info("[ playlist/%s ]", item)
path = "plugin://plugin.video.emby/?mode=play&id=%s&playlist=true" % item
play = playutils.PlayUtils(item, False, self.server_id, self.server) listitem.setPath(path)
source = play.select_source(play.get_sources()) playlist.add(path, listitem, index)
play.set_external_subs(source, listitem)
if order == 0: # First item
item['PlaybackInfo']['AudioStreamIndex'] = audio or item['PlaybackInfo']['AudioStreamIndex']
item['PlaybackInfo']['SubtitleStreamIndex'] = subtitle or item['PlaybackInfo'].get('SubtitleStreamIndex')
self.set_listitem(item, listitem, None, True if order == 0 and seektime else False)
listitem.setPath(item['PlaybackInfo']['Path'])
playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id)
playlist.add(item['PlaybackInfo']['Path'], listitem, index)
index += 1 index += 1
if not started and clear:
started = True
xbmc.Player().play(playlist)
def set_listitem(self, item, listitem, db_id=None, seektime=None, intro=False): def set_listitem(self, item, listitem, db_id=None, seektime=None, intro=False):
objects = Objects() objects = Objects()
@ -237,8 +240,15 @@ class Actions(object):
self.listitem_video(obj, listitem, item, seektime) self.listitem_video(obj, listitem, item, seektime)
if 'PlaybackInfo' in item and seektime: if 'PlaybackInfo' in item:
item['PlaybackInfo']['CurrentPosition'] = obj['Resume']
if seektime:
item['PlaybackInfo']['CurrentPosition'] = obj['Resume']
if 'SubtitleUrl' in item['PlaybackInfo']:
LOG.info("[ subtitles ] %s", item['PlaybackInfo']['SubtitleUrl'])
listitem.setSubtitles([item['PlaybackInfo']['SubtitleUrl']])
listitem.setContentLookup(False) listitem.setContentLookup(False)

View file

@ -254,9 +254,14 @@ class Player(xbmc.Player):
''' Report playback progress to emby server. ''' Report playback progress to emby server.
Check if the user seek. Check if the user seek.
''' '''
current_file = self.getPlayingFile() try:
current_file = self.getPlayingFile()
if current_file not in self.played:
return
except Exception as error:
LOG.error(error)
if current_file not in self.played:
return return
item = self.played[current_file] item = self.played[current_file]
@ -366,7 +371,7 @@ class Player(xbmc.Player):
elif item['PlayMethod'] == 'Transcode': elif item['PlayMethod'] == 'Transcode':
LOG.info("Transcoding for %s terminated.", item['Id']) LOG.info("<[ transcode/%s ]", item['Id'])
item['Server']['api'].close_transcode(item['DeviceId']) item['Server']['api'].close_transcode(item['DeviceId'])

View file

@ -41,7 +41,7 @@
<setting label="30522" id="transcode_h265" type="bool" default="false" /> <setting label="30522" id="transcode_h265" type="bool" default="false" />
<setting label="30537" id="transcodeHi10P" type="bool" default="false"/> <setting label="30537" id="transcodeHi10P" type="bool" default="false"/>
<setting label="33114" id="enableExternalSubs" type="bool" default="true" /> <setting label="33114" id="enableExternalSubs" type="bool" default="true" />
<setting label="33159" id="skipDialogTranscode" type="enum" lvalues="305|33157|33158|13106" visible="true" default="3" />
<setting label="33115" type="lsep" /> <setting label="33115" type="lsep" />
<setting label="30160" id="videoBitrate" type="enum" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|25.0 Mbps HD|30.0 Mbps HD|35.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="true" default="20" /> <setting label="30160" id="videoBitrate" type="enum" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|25.0 Mbps HD|30.0 Mbps HD|35.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="true" default="20" />