mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-11-10 04:06:11 +00:00
Add manual transcode option
Update queue playlist
This commit is contained in:
parent
cdb3b54026
commit
24ab27bbe2
8 changed files with 212 additions and 46 deletions
|
@ -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 ""
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'])
|
||||
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<setting label="30522" id="transcode_h265" type="bool" default="false" />
|
||||
<setting label="30537" id="transcodeHi10P" type="bool" default="false"/>
|
||||
<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="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" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue