diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 46f3bd0e..05cedd70 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -418,6 +418,14 @@ msgctxt "#33009"
msgid "Invalid username or password"
msgstr ""
+msgctxt "#33013"
+msgid "Choose the audio stream"
+msgstr ""
+
+msgctxt "#33014"
+msgid "Choose the subtitles stream"
+msgstr ""
+
msgctxt "#33015"
msgid "Delete file from Emby?"
msgstr ""
@@ -757,3 +765,15 @@ msgstr ""
msgctxt "#33156"
msgid "A patch has been applied!"
msgstr ""
+
+msgctxt "#33157"
+msgid "Audio only"
+msgstr ""
+
+msgctxt "#33158"
+msgid "Subtitles only"
+msgstr ""
+
+msgctxt "#33159"
+msgid "Enable audio/subtitles selection"
+msgstr ""
diff --git a/resources/lib/emby/core/api.py b/resources/lib/emby/core/api.py
index 8c5159d4..f58d2929 100644
--- a/resources/lib/emby/core/api.py
+++ b/resources/lib/emby/core/api.py
@@ -172,6 +172,9 @@ def delete_item(item_id):
def get_local_trailers(item_id):
return user_items("/%s/LocalTrailers" % item_id)
+def get_transcode_settings():
+ return _get('System/Configuration/encoding')
+
def get_ancestors(item_id):
return items("/%s/Ancestors" % item_id, params={
'UserId': "{UserId}"
diff --git a/resources/lib/entrypoint/default.py b/resources/lib/entrypoint/default.py
index fb70a866..fc6d95ce 100644
--- a/resources/lib/entrypoint/default.py
+++ b/resources/lib/entrypoint/default.py
@@ -69,7 +69,7 @@ class Events(object):
elif mode =='play':
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':
event('PlayPlaylist', {'Id': params['id'], 'ServerId': server})
diff --git a/resources/lib/helper/playutils.py b/resources/lib/helper/playutils.py
index 5564adaa..0c1d371b 100644
--- a/resources/lib/helper/playutils.py
+++ b/resources/lib/helper/playutils.py
@@ -13,6 +13,7 @@ import xbmcvfs
import api
import database
import client
+import collections
from . import _, settings, window, dialog
from libraries import requests
from downloader import TheVoid
@@ -102,7 +103,7 @@ class PlayUtils(object):
return sources
- def select_source(self, sources):
+ def select_source(self, sources, audio=None, subtitle=None):
if len(sources) > 1:
selection = []
@@ -119,7 +120,7 @@ class PlayUtils(object):
else:
source = sources[0]
- self.get(source)
+ self.get(source, audio, subtitle)
return source
@@ -171,7 +172,7 @@ class PlayUtils(object):
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.
'''
@@ -192,10 +193,10 @@ class PlayUtils(object):
else:
LOG.info("--[ transcode ]")
- self.transcode(source)
+ self.transcode(source, audio, subtitle)
- self.info['AudioStreamIndex'] = source.get('DefaultAudioStreamIndex')
- self.info['SubtitleStreamIndex'] = source.get('DefaultSubtitleStreamIndex')
+ self.info['AudioStreamIndex'] = self.info.get('AudioStreamIndex') or source.get('DefaultAudioStreamIndex')
+ self.info['SubtitleStreamIndex'] = self.info.get('SubtitleStreamIndex') or source.get('DefaultSubtitleStreamIndex')
self.item['PlaybackInfo'].update(self.info)
def live_stream(self, source):
@@ -217,13 +218,23 @@ class PlayUtils(object):
return info['MediaSource']
- def transcode(self, source):
+ def transcode(self, source, audio=None, subtitle=None):
if not 'TranscodingUrl' in source:
raise Exception("use get_sources to get transcoding url")
self.info['Method'] = "Transcode"
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'] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
@@ -422,8 +433,7 @@ class PlayUtils(object):
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']))
+ url = self.get_subtitles(source, stream, index)
if url is None:
continue
@@ -462,7 +472,7 @@ class PlayUtils(object):
path = os.path.join(temp, filename)
try:
- response = requests.get(src, stream=True)
+ response = requests.get(src, stream=True, verify=False)
response.raise_for_status()
except Exception as e:
raise
@@ -473,3 +483,113 @@ class PlayUtils(object):
del response
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
diff --git a/resources/lib/monitor.py b/resources/lib/monitor.py
index 309896dd..0b4e248d 100644
--- a/resources/lib/monitor.py
+++ b/resources/lib/monitor.py
@@ -55,7 +55,7 @@ class Monitor(xbmc.Monitor):
'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken',
'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem',
'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes',
- 'GetTheme', 'Playstate', 'GeneralCommand'):
+ 'GetTheme', 'Playstate', 'GeneralCommand', 'GetTranscodeOptions'):
return
data = json.loads(data)[0]
@@ -139,6 +139,12 @@ class Monitor(xbmc.Monitor):
window('emby_%s.json' % data['VoidName'], users)
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':
if data['Type'] == 'Video':
@@ -183,8 +189,10 @@ class Monitor(xbmc.Monitor):
elif method == 'Play':
- items = server['api'].get_items(data['ItemIds'])
- PlaylistWorker(data.get('ServerId'), items['Items'], data['PlayCommand'] == 'PlayNow',
+ item = server['api'].get_item(data['ItemIds'].pop(0))
+ data['ItemIds'].insert(0, item)
+
+ PlaylistWorker(data.get('ServerId'), data['ItemIds'], data['PlayCommand'] == 'PlayNow',
data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
data.get('SubtitleStreamIndex')).start()
diff --git a/resources/lib/objects/actions.py b/resources/lib/objects/actions.py
index a681660f..6996a4c0 100644
--- a/resources/lib/objects/actions.py
+++ b/resources/lib/objects/actions.py
@@ -43,7 +43,7 @@ class Actions(object):
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.
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])
try:
- if self.detect_widgets(item):
+ if not playlist and self.detect_widgets(item):
LOG.info(" [ play/widget ]")
raise IndexError
@@ -172,10 +172,10 @@ class Actions(object):
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])
- started = False
+ item = items[0]
+ playlist = self.get_playlist(item)
if clear:
playlist.clear()
@@ -183,32 +183,35 @@ class Actions(object):
else:
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()
- 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)
- source = play.select_source(play.get_sources())
- 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)
+ listitem.setPath(path)
+ playlist.add(path, listitem, index)
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):
objects = Objects()
@@ -237,8 +240,15 @@ class Actions(object):
self.listitem_video(obj, listitem, item, seektime)
- if 'PlaybackInfo' in item and seektime:
- item['PlaybackInfo']['CurrentPosition'] = obj['Resume']
+ if 'PlaybackInfo' in item:
+
+ 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)
diff --git a/resources/lib/player.py b/resources/lib/player.py
index 2516ab15..b7508904 100644
--- a/resources/lib/player.py
+++ b/resources/lib/player.py
@@ -254,9 +254,14 @@ class Player(xbmc.Player):
''' Report playback progress to emby server.
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
item = self.played[current_file]
@@ -366,7 +371,7 @@ class Player(xbmc.Player):
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'])
diff --git a/resources/settings.xml b/resources/settings.xml
index 6d60086d..3135493f 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -41,7 +41,7 @@
-
+