mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-06-23 08:30:32 +00:00
Tool black: auto-format Python code
This commit is contained in:
parent
e4d8084c25
commit
7763762212
54 changed files with 6545 additions and 4723 deletions
|
@ -26,81 +26,81 @@ class Transcode(object):
|
|||
Disabled = 3
|
||||
MediaDefault = 4
|
||||
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
def set_properties(item, method, server_id=None):
|
||||
"""Set all properties for playback detection."""
|
||||
info = item.get("PlaybackInfo") or {}
|
||||
|
||||
''' Set all properties for playback detection.
|
||||
'''
|
||||
info = item.get('PlaybackInfo') or {}
|
||||
current = window("jellyfin_play.json") or []
|
||||
current.append(
|
||||
{
|
||||
"Type": item["Type"],
|
||||
"Id": item["Id"],
|
||||
"Path": info["Path"],
|
||||
"PlayMethod": method,
|
||||
"PlayOption": "Addon" if info.get("PlaySessionId") else "Native",
|
||||
"MediaSourceId": info.get("MediaSourceId", item["Id"]),
|
||||
"Runtime": item.get("RunTimeTicks"),
|
||||
"PlaySessionId": info.get("PlaySessionId", str(uuid4()).replace("-", "")),
|
||||
"ServerId": server_id,
|
||||
"DeviceId": client.get_device_id(),
|
||||
"SubsMapping": info.get("Subtitles"),
|
||||
"AudioStreamIndex": info.get("AudioStreamIndex"),
|
||||
"SubtitleStreamIndex": info.get("SubtitleStreamIndex"),
|
||||
"CurrentPosition": info.get("CurrentPosition"),
|
||||
"CurrentEpisode": info.get("CurrentEpisode"),
|
||||
}
|
||||
)
|
||||
|
||||
current = window('jellyfin_play.json') or []
|
||||
current.append({
|
||||
'Type': item['Type'],
|
||||
'Id': item['Id'],
|
||||
'Path': info['Path'],
|
||||
'PlayMethod': method,
|
||||
'PlayOption': 'Addon' if info.get('PlaySessionId') else 'Native',
|
||||
'MediaSourceId': info.get('MediaSourceId', item['Id']),
|
||||
'Runtime': item.get('RunTimeTicks'),
|
||||
'PlaySessionId': info.get('PlaySessionId', str(uuid4()).replace("-", "")),
|
||||
'ServerId': server_id,
|
||||
'DeviceId': client.get_device_id(),
|
||||
'SubsMapping': info.get('Subtitles'),
|
||||
'AudioStreamIndex': info.get('AudioStreamIndex'),
|
||||
'SubtitleStreamIndex': info.get('SubtitleStreamIndex'),
|
||||
'CurrentPosition': info.get('CurrentPosition'),
|
||||
'CurrentEpisode': info.get('CurrentEpisode')
|
||||
})
|
||||
|
||||
window('jellyfin_play.json', current)
|
||||
window("jellyfin_play.json", current)
|
||||
|
||||
|
||||
class PlayUtils(object):
|
||||
|
||||
def __init__(self, item, force_transcode=False, server_id=None, server=None, api_client=None):
|
||||
|
||||
''' Item will be updated with the property PlaybackInfo, which
|
||||
holds all the playback information.
|
||||
'''
|
||||
def __init__(
|
||||
self, item, force_transcode=False, server_id=None, server=None, api_client=None
|
||||
):
|
||||
"""Item will be updated with the property PlaybackInfo, which
|
||||
holds all the playback information.
|
||||
"""
|
||||
self.item = item
|
||||
self.item['PlaybackInfo'] = {}
|
||||
self.item["PlaybackInfo"] = {}
|
||||
self.api_client = api_client
|
||||
self.info = {
|
||||
'ServerId': server_id,
|
||||
'ServerAddress': server,
|
||||
'ForceTranscode': force_transcode,
|
||||
'Token': api_client.config.data['auth.token']
|
||||
"ServerId": server_id,
|
||||
"ServerAddress": server,
|
||||
"ForceTranscode": force_transcode,
|
||||
"Token": api_client.config.data["auth.token"],
|
||||
}
|
||||
|
||||
def get_sources(self, source_id=None):
|
||||
|
||||
''' Return sources based on the optional source_id or the device profile.
|
||||
'''
|
||||
info = self.api_client.get_play_info(self.item['Id'], self.get_device_profile())
|
||||
"""Return sources based on the optional source_id or the device profile."""
|
||||
info = self.api_client.get_play_info(self.item["Id"], self.get_device_profile())
|
||||
LOG.info(info)
|
||||
self.info['PlaySessionId'] = info['PlaySessionId']
|
||||
self.info["PlaySessionId"] = info["PlaySessionId"]
|
||||
sources = []
|
||||
|
||||
if not info.get('MediaSources'):
|
||||
if not info.get("MediaSources"):
|
||||
LOG.info("No MediaSources found.")
|
||||
|
||||
elif source_id:
|
||||
for source in info:
|
||||
|
||||
if source['Id'] == source_id:
|
||||
if source["Id"] == source_id:
|
||||
sources.append(source)
|
||||
|
||||
break
|
||||
|
||||
elif not self.is_selection(info) or len(info['MediaSources']) == 1:
|
||||
elif not self.is_selection(info) or len(info["MediaSources"]) == 1:
|
||||
|
||||
LOG.info("Skip source selection.")
|
||||
sources.append(info['MediaSources'][0])
|
||||
sources.append(info["MediaSources"][0])
|
||||
|
||||
else:
|
||||
sources.extend([x for x in info['MediaSources']])
|
||||
sources.extend([x for x in info["MediaSources"]])
|
||||
|
||||
return sources
|
||||
|
||||
|
@ -110,7 +110,7 @@ class PlayUtils(object):
|
|||
selection = []
|
||||
|
||||
for source in sources:
|
||||
selection.append(source.get('Name', "na"))
|
||||
selection.append(source.get("Name", "na"))
|
||||
|
||||
resp = dialog("select", translate(33130), selection)
|
||||
|
||||
|
@ -127,25 +127,23 @@ class PlayUtils(object):
|
|||
return source
|
||||
|
||||
def is_selection(self, sources):
|
||||
|
||||
''' Do not allow source selection for.
|
||||
'''
|
||||
if self.item['MediaType'] != 'Video':
|
||||
"""Do not allow source selection for."""
|
||||
if self.item["MediaType"] != "Video":
|
||||
LOG.debug("MediaType is not a video.")
|
||||
|
||||
return False
|
||||
|
||||
elif self.item['Type'] == 'TvChannel':
|
||||
elif self.item["Type"] == "TvChannel":
|
||||
LOG.debug("TvChannel detected.")
|
||||
|
||||
return False
|
||||
|
||||
elif len(sources) == 1 and sources[0]['Type'] == 'Placeholder':
|
||||
elif len(sources) == 1 and sources[0]["Type"] == "Placeholder":
|
||||
LOG.debug("Placeholder detected.")
|
||||
|
||||
return False
|
||||
|
||||
elif 'SourceType' in self.item and self.item['SourceType'] != 'Library':
|
||||
elif "SourceType" in self.item and self.item["SourceType"] != "Library":
|
||||
LOG.debug("SourceType not from library.")
|
||||
|
||||
return False
|
||||
|
@ -156,7 +154,7 @@ class PlayUtils(object):
|
|||
|
||||
self.direct_play(source)
|
||||
|
||||
if xbmcvfs.exists(self.info['Path']):
|
||||
if xbmcvfs.exists(self.info["Path"]):
|
||||
LOG.info("Path exists.")
|
||||
|
||||
return True
|
||||
|
@ -167,7 +165,7 @@ class PlayUtils(object):
|
|||
|
||||
def is_strm(self, source):
|
||||
|
||||
if source.get('Container') == 'strm' or self.item['Path'].endswith('.strm'):
|
||||
if source.get("Container") == "strm" or self.item["Path"].endswith(".strm"):
|
||||
LOG.info("strm detected")
|
||||
|
||||
return True
|
||||
|
@ -175,31 +173,37 @@ class PlayUtils(object):
|
|||
return False
|
||||
|
||||
def get(self, source, audio=None, subtitle=None):
|
||||
"""The server returns sources based on the MaxStreamingBitrate value and other filters.
|
||||
prop: jellyfinfilename for ?? I thought it was to pass the real path to subtitle add-ons but it's not working?
|
||||
"""
|
||||
self.info["MediaSourceId"] = source["Id"]
|
||||
|
||||
''' The server returns sources based on the MaxStreamingBitrate value and other filters.
|
||||
prop: jellyfinfilename for ?? I thought it was to pass the real path to subtitle add-ons but it's not working?
|
||||
'''
|
||||
self.info['MediaSourceId'] = source['Id']
|
||||
if source.get("RequiresClosing"):
|
||||
|
||||
if source.get('RequiresClosing'):
|
||||
"""Server returning live tv stream for direct play is hardcoded with 127.0.0.1."""
|
||||
self.info["LiveStreamId"] = source["LiveStreamId"]
|
||||
source["SupportsDirectPlay"] = False
|
||||
source["Protocol"] = "LiveTV"
|
||||
|
||||
''' Server returning live tv stream for direct play is hardcoded with 127.0.0.1.
|
||||
'''
|
||||
self.info['LiveStreamId'] = source['LiveStreamId']
|
||||
source['SupportsDirectPlay'] = False
|
||||
source['Protocol'] = "LiveTV"
|
||||
if self.info["ForceTranscode"]:
|
||||
|
||||
if self.info['ForceTranscode']:
|
||||
source["SupportsDirectPlay"] = False
|
||||
source["SupportsDirectStream"] = False
|
||||
|
||||
source['SupportsDirectPlay'] = False
|
||||
source['SupportsDirectStream'] = False
|
||||
|
||||
if source.get('Protocol') == 'Http' or source['SupportsDirectPlay'] and (self.is_strm(source) or not settings('playFromStream.bool') and self.is_file_exists(source)):
|
||||
if (
|
||||
source.get("Protocol") == "Http"
|
||||
or source["SupportsDirectPlay"]
|
||||
and (
|
||||
self.is_strm(source)
|
||||
or not settings("playFromStream.bool")
|
||||
and self.is_file_exists(source)
|
||||
)
|
||||
):
|
||||
|
||||
LOG.info("--[ direct play ]")
|
||||
self.direct_play(source)
|
||||
|
||||
elif source['SupportsDirectStream'] or source['SupportsDirectPlay']:
|
||||
elif source["SupportsDirectStream"] or source["SupportsDirectPlay"]:
|
||||
|
||||
LOG.info("--[ direct stream ]")
|
||||
self.direct_url(source)
|
||||
|
@ -208,158 +212,209 @@ class PlayUtils(object):
|
|||
LOG.info("--[ transcode ]")
|
||||
self.transcode(source, audio, subtitle)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
API = api.API(self.item, self.info['ServerAddress'])
|
||||
window('jellyfinfilename', value=API.get_file_path(source.get('Path')))
|
||||
API = api.API(self.item, self.info["ServerAddress"])
|
||||
window("jellyfinfilename", value=API.get_file_path(source.get("Path")))
|
||||
|
||||
def live_stream(self, source):
|
||||
|
||||
''' Get live stream media info.
|
||||
'''
|
||||
info = self.api_client.get_live_stream(self.item['Id'], self.info['PlaySessionId'], source['OpenToken'], self.get_device_profile())
|
||||
"""Get live stream media info."""
|
||||
info = self.api_client.get_live_stream(
|
||||
self.item["Id"],
|
||||
self.info["PlaySessionId"],
|
||||
source["OpenToken"],
|
||||
self.get_device_profile(),
|
||||
)
|
||||
LOG.info(info)
|
||||
|
||||
if info['MediaSource'].get('RequiresClosing'):
|
||||
self.info['LiveStreamId'] = source['LiveStreamId']
|
||||
if info["MediaSource"].get("RequiresClosing"):
|
||||
self.info["LiveStreamId"] = source["LiveStreamId"]
|
||||
|
||||
return info['MediaSource']
|
||||
return info["MediaSource"]
|
||||
|
||||
def transcode(self, source, audio=None, subtitle=None):
|
||||
|
||||
if 'TranscodingUrl' not in source:
|
||||
if "TranscodingUrl" not in source:
|
||||
raise Exception("use get_sources to get transcoding url")
|
||||
|
||||
self.info['Method'] = "Transcode"
|
||||
self.info["Method"] = "Transcode"
|
||||
|
||||
if self.item['MediaType'] == 'Video':
|
||||
base, params = source['TranscodingUrl'].split('?')
|
||||
url_parsed = params.split('&')
|
||||
manual_tracks = ''
|
||||
if self.item["MediaType"] == "Video":
|
||||
base, params = source["TranscodingUrl"].split("?")
|
||||
url_parsed = params.split("&")
|
||||
manual_tracks = ""
|
||||
|
||||
# manual bitrate
|
||||
url_parsed = [p for p in url_parsed if 'AudioBitrate' not in p and 'VideoBitrate' not in p]
|
||||
url_parsed = [
|
||||
p
|
||||
for p in url_parsed
|
||||
if "AudioBitrate" not in p and "VideoBitrate" not in p
|
||||
]
|
||||
|
||||
if settings('skipDialogTranscode') != Transcode.Enabled and source.get('MediaStreams'):
|
||||
if settings("skipDialogTranscode") != Transcode.Enabled and source.get(
|
||||
"MediaStreams"
|
||||
):
|
||||
# manual tracks
|
||||
url_parsed = [p for p in url_parsed if 'AudioStreamIndex' not in p and 'SubtitleStreamIndex' not in p]
|
||||
url_parsed = [
|
||||
p
|
||||
for p in url_parsed
|
||||
if "AudioStreamIndex" not in p and "SubtitleStreamIndex" not in p
|
||||
]
|
||||
manual_tracks = self.get_audio_subs(source, audio, subtitle)
|
||||
|
||||
audio_bitrate = self.get_transcoding_audio_bitrate()
|
||||
video_bitrate = self.get_max_bitrate() - audio_bitrate
|
||||
|
||||
params = "%s%s" % ('&'.join(url_parsed), manual_tracks)
|
||||
params += "&VideoBitrate=%s&AudioBitrate=%s" % (video_bitrate, audio_bitrate)
|
||||
params = "%s%s" % ("&".join(url_parsed), manual_tracks)
|
||||
params += "&VideoBitrate=%s&AudioBitrate=%s" % (
|
||||
video_bitrate,
|
||||
audio_bitrate,
|
||||
)
|
||||
|
||||
video_type = 'live' if source['Protocol'] == 'LiveTV' else 'master'
|
||||
base = base.replace('stream' if 'stream' in base else 'master', video_type, 1)
|
||||
self.info['Path'] = "%s%s?%s" % (self.info['ServerAddress'], base, params)
|
||||
self.info['Path'] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
|
||||
video_type = "live" if source["Protocol"] == "LiveTV" else "master"
|
||||
base = base.replace(
|
||||
"stream" if "stream" in base else "master", video_type, 1
|
||||
)
|
||||
self.info["Path"] = "%s%s?%s" % (self.info["ServerAddress"], base, params)
|
||||
self.info["Path"] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
|
||||
else:
|
||||
self.info['Path'] = "%s/%s" % (self.info['ServerAddress'], source['TranscodingUrl'])
|
||||
self.info["Path"] = "%s/%s" % (
|
||||
self.info["ServerAddress"],
|
||||
source["TranscodingUrl"],
|
||||
)
|
||||
|
||||
return self.info['Path']
|
||||
return self.info["Path"]
|
||||
|
||||
def direct_play(self, source):
|
||||
|
||||
API = api.API(self.item, self.info['ServerAddress'])
|
||||
self.info['Method'] = "DirectPlay"
|
||||
self.info['Path'] = API.get_file_path(source.get('Path'))
|
||||
API = api.API(self.item, self.info["ServerAddress"])
|
||||
self.info["Method"] = "DirectPlay"
|
||||
self.info["Path"] = API.get_file_path(source.get("Path"))
|
||||
|
||||
return self.info['Path']
|
||||
return self.info["Path"]
|
||||
|
||||
def direct_url(self, source):
|
||||
|
||||
self.info['Method'] = "DirectStream"
|
||||
self.info["Method"] = "DirectStream"
|
||||
|
||||
if self.item['Type'] == "Audio":
|
||||
self.info['Path'] = "%s/Audio/%s/stream.%s?static=true&api_key=%s" % (
|
||||
self.info['ServerAddress'],
|
||||
self.item['Id'],
|
||||
source.get('Container', "mp4").split(',')[0],
|
||||
self.info['Token']
|
||||
if self.item["Type"] == "Audio":
|
||||
self.info["Path"] = "%s/Audio/%s/stream.%s?static=true&api_key=%s" % (
|
||||
self.info["ServerAddress"],
|
||||
self.item["Id"],
|
||||
source.get("Container", "mp4").split(",")[0],
|
||||
self.info["Token"],
|
||||
)
|
||||
else:
|
||||
self.info['Path'] = "%s/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s" % (
|
||||
self.info['ServerAddress'],
|
||||
self.item['Id'],
|
||||
source['Id'],
|
||||
self.info['Token']
|
||||
self.info["Path"] = (
|
||||
"%s/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s"
|
||||
% (
|
||||
self.info["ServerAddress"],
|
||||
self.item["Id"],
|
||||
source["Id"],
|
||||
self.info["Token"],
|
||||
)
|
||||
)
|
||||
|
||||
return self.info['Path']
|
||||
return self.info["Path"]
|
||||
|
||||
def get_max_bitrate(self):
|
||||
|
||||
''' Get the video quality based on add-on settings.
|
||||
Max bit rate supported by server: 2147483 (max signed 32bit integer)
|
||||
'''
|
||||
bitrate = [500, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000,
|
||||
7000, 8000, 9000, 10000, 12000, 14000, 16000, 18000,
|
||||
20000, 25000, 30000, 35000, 40000, 100000, 1000000, 2147483]
|
||||
return bitrate[int(settings('maxBitrate') or 24)] * 1000
|
||||
"""Get the video quality based on add-on settings.
|
||||
Max bit rate supported by server: 2147483 (max signed 32bit integer)
|
||||
"""
|
||||
bitrate = [
|
||||
500,
|
||||
1000,
|
||||
1500,
|
||||
2000,
|
||||
2500,
|
||||
3000,
|
||||
4000,
|
||||
5000,
|
||||
6000,
|
||||
7000,
|
||||
8000,
|
||||
9000,
|
||||
10000,
|
||||
12000,
|
||||
14000,
|
||||
16000,
|
||||
18000,
|
||||
20000,
|
||||
25000,
|
||||
30000,
|
||||
35000,
|
||||
40000,
|
||||
100000,
|
||||
1000000,
|
||||
2147483,
|
||||
]
|
||||
return bitrate[int(settings("maxBitrate") or 24)] * 1000
|
||||
|
||||
def get_resolution(self):
|
||||
return int(xbmc.getInfoLabel('System.ScreenWidth')), int(xbmc.getInfoLabel('System.ScreenHeight'))
|
||||
return int(xbmc.getInfoLabel("System.ScreenWidth")), int(
|
||||
xbmc.getInfoLabel("System.ScreenHeight")
|
||||
)
|
||||
|
||||
def get_directplay_video_codec(self):
|
||||
codecs = ['h264', 'hevc', 'h265', 'mpeg4', 'mpeg2video', 'vc1', 'vp9', 'av1']
|
||||
codecs = ["h264", "hevc", "h265", "mpeg4", "mpeg2video", "vc1", "vp9", "av1"]
|
||||
|
||||
if settings('transcode_h265.bool'):
|
||||
codecs.remove('hevc')
|
||||
codecs.remove('h265')
|
||||
if settings("transcode_h265.bool"):
|
||||
codecs.remove("hevc")
|
||||
codecs.remove("h265")
|
||||
|
||||
if settings('transcode_mpeg2.bool'):
|
||||
codecs.remove('mpeg2video')
|
||||
if settings("transcode_mpeg2.bool"):
|
||||
codecs.remove("mpeg2video")
|
||||
|
||||
if settings('transcode_vc1.bool'):
|
||||
codecs.remove('vc1')
|
||||
if settings("transcode_vc1.bool"):
|
||||
codecs.remove("vc1")
|
||||
|
||||
if settings('transcode_vp9.bool'):
|
||||
codecs.remove('vp9')
|
||||
if settings("transcode_vp9.bool"):
|
||||
codecs.remove("vp9")
|
||||
|
||||
if settings('transcode_av1.bool'):
|
||||
codecs.remove('av1')
|
||||
if settings("transcode_av1.bool"):
|
||||
codecs.remove("av1")
|
||||
|
||||
return ','.join(codecs)
|
||||
return ",".join(codecs)
|
||||
|
||||
def get_transcoding_video_codec(self):
|
||||
codecs = ['h264', 'hevc', 'h265', 'mpeg4', 'mpeg2video', 'vc1']
|
||||
codecs = ["h264", "hevc", "h265", "mpeg4", "mpeg2video", "vc1"]
|
||||
|
||||
if settings('transcode_h265.bool'):
|
||||
codecs.remove('hevc')
|
||||
codecs.remove('h265')
|
||||
if settings("transcode_h265.bool"):
|
||||
codecs.remove("hevc")
|
||||
codecs.remove("h265")
|
||||
else:
|
||||
if settings('videoPreferredCodec') == 'H265/HEVC':
|
||||
codecs.insert(2, codecs.pop(codecs.index('h264')))
|
||||
if settings("videoPreferredCodec") == "H265/HEVC":
|
||||
codecs.insert(2, codecs.pop(codecs.index("h264")))
|
||||
|
||||
if settings('transcode_mpeg2.bool'):
|
||||
codecs.remove('mpeg2video')
|
||||
if settings("transcode_mpeg2.bool"):
|
||||
codecs.remove("mpeg2video")
|
||||
|
||||
if settings('transcode_vc1.bool'):
|
||||
codecs.remove('vc1')
|
||||
if settings("transcode_vc1.bool"):
|
||||
codecs.remove("vc1")
|
||||
|
||||
return ','.join(codecs)
|
||||
return ",".join(codecs)
|
||||
|
||||
def get_transcoding_audio_codec(self):
|
||||
codecs = ['aac', 'mp3', 'ac3', 'opus', 'flac', 'vorbis']
|
||||
codecs = ["aac", "mp3", "ac3", "opus", "flac", "vorbis"]
|
||||
|
||||
preferred = settings('audioPreferredCodec').lower()
|
||||
preferred = settings("audioPreferredCodec").lower()
|
||||
if preferred in codecs:
|
||||
codecs.insert(0, codecs.pop(codecs.index(preferred)))
|
||||
|
||||
return ','.join(codecs)
|
||||
return ",".join(codecs)
|
||||
|
||||
def get_transcoding_audio_bitrate(self):
|
||||
bitrate = [96, 128, 160, 192, 256, 320, 384]
|
||||
return bitrate[int(settings('audioBitrate') or 6)] * 1000
|
||||
return bitrate[int(settings("audioBitrate") or 6)] * 1000
|
||||
|
||||
def get_device_profile(self):
|
||||
|
||||
''' Get device profile based on the add-on settings.
|
||||
'''
|
||||
"""Get device profile based on the add-on settings."""
|
||||
profile = {
|
||||
"Name": "Kodi",
|
||||
"MaxStaticBitrate": self.get_max_bitrate(),
|
||||
|
@ -372,154 +427,96 @@ class PlayUtils(object):
|
|||
"Container": "m3u8",
|
||||
"AudioCodec": self.get_transcoding_audio_codec(),
|
||||
"VideoCodec": self.get_transcoding_video_codec(),
|
||||
"MaxAudioChannels": settings('audioMaxChannels')
|
||||
"MaxAudioChannels": settings("audioMaxChannels"),
|
||||
},
|
||||
{
|
||||
"Type": "Audio"
|
||||
},
|
||||
{
|
||||
"Type": "Photo",
|
||||
"Container": "jpeg"
|
||||
}
|
||||
{"Type": "Audio"},
|
||||
{"Type": "Photo", "Container": "jpeg"},
|
||||
],
|
||||
"DirectPlayProfiles": [
|
||||
{
|
||||
"Type": "Video",
|
||||
"VideoCodec": self.get_directplay_video_codec()
|
||||
},
|
||||
{
|
||||
"Type": "Audio"
|
||||
},
|
||||
{
|
||||
"Type": "Photo"
|
||||
}
|
||||
{"Type": "Video", "VideoCodec": self.get_directplay_video_codec()},
|
||||
{"Type": "Audio"},
|
||||
{"Type": "Photo"},
|
||||
],
|
||||
"ResponseProfiles": [],
|
||||
"ContainerProfiles": [],
|
||||
"CodecProfiles": [],
|
||||
"SubtitleProfiles": [
|
||||
{
|
||||
"Format": "srt",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "srt",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "ass",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "ass",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "sub",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "sub",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "ssa",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "ssa",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "smi",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "smi",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "pgssub",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "pgssub",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "dvdsub",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "dvdsub",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "pgs",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "pgs",
|
||||
"Method": "External"
|
||||
}
|
||||
]
|
||||
{"Format": "srt", "Method": "External"},
|
||||
{"Format": "srt", "Method": "Embed"},
|
||||
{"Format": "ass", "Method": "External"},
|
||||
{"Format": "ass", "Method": "Embed"},
|
||||
{"Format": "sub", "Method": "Embed"},
|
||||
{"Format": "sub", "Method": "External"},
|
||||
{"Format": "ssa", "Method": "Embed"},
|
||||
{"Format": "ssa", "Method": "External"},
|
||||
{"Format": "smi", "Method": "Embed"},
|
||||
{"Format": "smi", "Method": "External"},
|
||||
{"Format": "pgssub", "Method": "Embed"},
|
||||
{"Format": "pgssub", "Method": "External"},
|
||||
{"Format": "dvdsub", "Method": "Embed"},
|
||||
{"Format": "dvdsub", "Method": "External"},
|
||||
{"Format": "pgs", "Method": "Embed"},
|
||||
{"Format": "pgs", "Method": "External"},
|
||||
],
|
||||
}
|
||||
|
||||
if settings('transcodeHi10P.bool'):
|
||||
profile['CodecProfiles'].append(
|
||||
if settings("transcodeHi10P.bool"):
|
||||
profile["CodecProfiles"].append(
|
||||
{
|
||||
'Type': 'Video',
|
||||
'codec': 'h264',
|
||||
'Conditions': [
|
||||
"Type": "Video",
|
||||
"codec": "h264",
|
||||
"Conditions": [
|
||||
{
|
||||
'Condition': "LessThanEqual",
|
||||
'Property': "VideoBitDepth",
|
||||
'Value': "8"
|
||||
"Condition": "LessThanEqual",
|
||||
"Property": "VideoBitDepth",
|
||||
"Value": "8",
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
if settings('transcode_h265_rext.bool'):
|
||||
profile['CodecProfiles'].append(
|
||||
if settings("transcode_h265_rext.bool"):
|
||||
profile["CodecProfiles"].append(
|
||||
{
|
||||
'Type': 'Video',
|
||||
'codec': 'h265,hevc',
|
||||
'Conditions': [
|
||||
"Type": "Video",
|
||||
"codec": "h265,hevc",
|
||||
"Conditions": [
|
||||
{
|
||||
'Condition': "EqualsAny",
|
||||
'Property': "VideoProfile",
|
||||
'Value': "main|main 10"
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoProfile",
|
||||
"Value": "main|main 10",
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
if self.info['ForceTranscode']:
|
||||
profile['DirectPlayProfiles'] = []
|
||||
if self.info["ForceTranscode"]:
|
||||
profile["DirectPlayProfiles"] = []
|
||||
|
||||
if self.item['Type'] == 'TvChannel':
|
||||
profile['TranscodingProfiles'].insert(0, {
|
||||
"Container": "ts",
|
||||
"Type": "Video",
|
||||
"AudioCodec": "mp3,aac",
|
||||
"VideoCodec": "h264",
|
||||
"Context": "Streaming",
|
||||
"Protocol": "hls",
|
||||
"MaxAudioChannels": "2",
|
||||
"MinSegments": "1",
|
||||
"BreakOnNonKeyFrames": True
|
||||
})
|
||||
if self.item["Type"] == "TvChannel":
|
||||
profile["TranscodingProfiles"].insert(
|
||||
0,
|
||||
{
|
||||
"Container": "ts",
|
||||
"Type": "Video",
|
||||
"AudioCodec": "mp3,aac",
|
||||
"VideoCodec": "h264",
|
||||
"Context": "Streaming",
|
||||
"Protocol": "hls",
|
||||
"MaxAudioChannels": "2",
|
||||
"MinSegments": "1",
|
||||
"BreakOnNonKeyFrames": True,
|
||||
},
|
||||
)
|
||||
|
||||
return profile
|
||||
|
||||
def set_external_subs(self, source, listitem):
|
||||
|
||||
''' Try to download external subs locally, so we can label them.
|
||||
Since Jellyfin returns all possible tracks together, sort them.
|
||||
IsTextSubtitleStream if true, is available to download from server.
|
||||
'''
|
||||
if not settings('enableExternalSubs.bool') or not source['MediaStreams']:
|
||||
"""Try to download external subs locally, so we can label them.
|
||||
Since Jellyfin returns all possible tracks together, sort them.
|
||||
IsTextSubtitleStream if true, is available to download from server.
|
||||
"""
|
||||
if not settings("enableExternalSubs.bool") or not source["MediaStreams"]:
|
||||
return
|
||||
|
||||
subs = []
|
||||
|
@ -528,12 +525,19 @@ class PlayUtils(object):
|
|||
|
||||
server_settings = self.api_client.get_transcode_settings()
|
||||
|
||||
for stream in source['MediaStreams']:
|
||||
if stream['SupportsExternalStream'] and stream['Type'] == 'Subtitle' and stream['DeliveryMethod'] == 'External':
|
||||
if not stream['IsExternal'] and not server_settings['EnableSubtitleExtraction']:
|
||||
for stream in source["MediaStreams"]:
|
||||
if (
|
||||
stream["SupportsExternalStream"]
|
||||
and stream["Type"] == "Subtitle"
|
||||
and stream["DeliveryMethod"] == "External"
|
||||
):
|
||||
if (
|
||||
not stream["IsExternal"]
|
||||
and not server_settings["EnableSubtitleExtraction"]
|
||||
):
|
||||
continue
|
||||
|
||||
index = stream['Index']
|
||||
index = stream["Index"]
|
||||
url = self.get_subtitles(source, stream, index)
|
||||
|
||||
if url is None:
|
||||
|
@ -541,8 +545,12 @@ class PlayUtils(object):
|
|||
|
||||
LOG.info("[ subtitles/%s ] %s", index, url)
|
||||
|
||||
if 'Language' in stream:
|
||||
filename = "%s.%s.%s" % (source['Id'], stream['Language'], stream['Codec'])
|
||||
if "Language" in stream:
|
||||
filename = "%s.%s.%s" % (
|
||||
source["Id"],
|
||||
stream["Language"],
|
||||
stream["Codec"],
|
||||
)
|
||||
|
||||
try:
|
||||
subs.append(self.download_external_subs(url, filename))
|
||||
|
@ -556,15 +564,16 @@ class PlayUtils(object):
|
|||
kodi += 1
|
||||
|
||||
listitem.setSubtitles(subs)
|
||||
self.item['PlaybackInfo']['Subtitles'] = mapping
|
||||
self.item["PlaybackInfo"]["Subtitles"] = mapping
|
||||
|
||||
@classmethod
|
||||
def download_external_subs(cls, src, filename):
|
||||
|
||||
''' Download external subtitles to temp folder
|
||||
to be able to have proper names to streams.
|
||||
'''
|
||||
temp = translate_path("special://profile/addon_data/plugin.video.jellyfin/temp/")
|
||||
"""Download external subtitles to temp folder
|
||||
to be able to have proper names to streams.
|
||||
"""
|
||||
temp = translate_path(
|
||||
"special://profile/addon_data/plugin.video.jellyfin/temp/"
|
||||
)
|
||||
|
||||
if not xbmcvfs.exists(temp):
|
||||
xbmcvfs.mkdir(temp)
|
||||
|
@ -572,59 +581,61 @@ class PlayUtils(object):
|
|||
path = os.path.join(temp, filename)
|
||||
|
||||
try:
|
||||
response = requests.get(src, stream=True, verify=settings('sslverify.bool'))
|
||||
response = requests.get(src, stream=True, verify=settings("sslverify.bool"))
|
||||
response.raise_for_status()
|
||||
except Exception as error:
|
||||
LOG.exception(error)
|
||||
raise
|
||||
else:
|
||||
response.encoding = 'utf-8'
|
||||
with open(path, 'wb') as f:
|
||||
response.encoding = "utf-8"
|
||||
with open(path, "wb") as f:
|
||||
f.write(response.content)
|
||||
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.
|
||||
|
||||
''' For transcoding only
|
||||
Present the list of audio/subs to select from, before playback starts.
|
||||
|
||||
Since Jellyfin returns all possible tracks together, sort them.
|
||||
IsTextSubtitleStream if true, is available to download from server.
|
||||
'''
|
||||
Since Jellyfin returns all possible tracks together, sort them.
|
||||
IsTextSubtitleStream if true, is available to download from server.
|
||||
"""
|
||||
prefs = ""
|
||||
audio_streams = list()
|
||||
subs_streams = list()
|
||||
streams = source['MediaStreams']
|
||||
streams = source["MediaStreams"]
|
||||
|
||||
server_settings = self.api_client.get_transcode_settings()
|
||||
allow_burned_subs = settings('allowBurnedSubs.bool')
|
||||
allow_burned_subs = settings("allowBurnedSubs.bool")
|
||||
|
||||
for stream in streams:
|
||||
|
||||
index = stream['Index']
|
||||
stream_type = stream['Type']
|
||||
index = stream["Index"]
|
||||
stream_type = stream["Type"]
|
||||
|
||||
if stream_type == 'Audio':
|
||||
if stream_type == "Audio":
|
||||
|
||||
audio_streams.append(index)
|
||||
|
||||
elif stream_type == 'Subtitle':
|
||||
if stream['IsExternal']:
|
||||
if not stream['SupportsExternalStream'] and not allow_burned_subs:
|
||||
elif stream_type == "Subtitle":
|
||||
if stream["IsExternal"]:
|
||||
if not stream["SupportsExternalStream"] and not allow_burned_subs:
|
||||
continue
|
||||
else:
|
||||
avail_for_extraction = stream['SupportsExternalStream'] and server_settings['EnableSubtitleExtraction']
|
||||
avail_for_extraction = (
|
||||
stream["SupportsExternalStream"]
|
||||
and server_settings["EnableSubtitleExtraction"]
|
||||
)
|
||||
if not avail_for_extraction and not allow_burned_subs:
|
||||
continue
|
||||
|
||||
subs_streams.append(index)
|
||||
|
||||
skip_dialog = int(settings('skipDialogTranscode') or 0)
|
||||
skip_dialog = int(settings("skipDialogTranscode") or 0)
|
||||
|
||||
def get_track_title(track_index):
|
||||
return streams[track_index]['DisplayTitle'] or ("Track %s" % track_index)
|
||||
return streams[track_index]["DisplayTitle"] or ("Track %s" % track_index)
|
||||
|
||||
# Select audio stream
|
||||
audio_selected = None
|
||||
|
@ -633,7 +644,7 @@ class PlayUtils(object):
|
|||
# NOTE: "DefaultAudioStreamIndex" is the default according to Jellyfin.
|
||||
# The media's default is marked by the "IsDefault" value.
|
||||
for track_index in audio_streams:
|
||||
if streams[track_index]['IsDefault']:
|
||||
if streams[track_index]["IsDefault"]:
|
||||
audio = track_index
|
||||
break
|
||||
|
||||
|
@ -648,16 +659,16 @@ class PlayUtils(object):
|
|||
if resp > -1:
|
||||
audio_selected = audio_streams[resp]
|
||||
else:
|
||||
audio_selected = source['DefaultAudioStreamIndex']
|
||||
audio_selected = source["DefaultAudioStreamIndex"]
|
||||
elif audio_streams:
|
||||
# Only one choice
|
||||
audio_selected = audio_streams[0]
|
||||
|
||||
else:
|
||||
audio_selected = source['DefaultAudioStreamIndex']
|
||||
audio_selected = source["DefaultAudioStreamIndex"]
|
||||
|
||||
if audio_selected is not None:
|
||||
self.info['AudioStreamIndex'] = audio_selected
|
||||
self.info["AudioStreamIndex"] = audio_selected
|
||||
prefs += "&AudioStreamIndex=%s" % audio_selected
|
||||
|
||||
# Select audio stream
|
||||
|
@ -665,7 +676,7 @@ class PlayUtils(object):
|
|||
|
||||
if skip_dialog == Transcode.MediaDefault:
|
||||
for track_index in subs_streams:
|
||||
if streams[track_index]['IsDefault']:
|
||||
if streams[track_index]["IsDefault"]:
|
||||
subtitle = track_index
|
||||
break
|
||||
|
||||
|
@ -673,7 +684,9 @@ class PlayUtils(object):
|
|||
subtitle_selected = subtitle
|
||||
|
||||
elif skip_dialog in (Transcode.Enabled, Transcode.Subtitle) and subs_streams:
|
||||
selection = list(['No subtitles']) + list(map(get_track_title, subs_streams))
|
||||
selection = list(["No subtitles"]) + list(
|
||||
map(get_track_title, subs_streams)
|
||||
)
|
||||
resp = dialog("select", translate(33014), selection) - 1
|
||||
# Possible responses:
|
||||
# >=0 Subtitle track
|
||||
|
@ -686,27 +699,36 @@ class PlayUtils(object):
|
|||
if subtitle_selected is not None:
|
||||
server_settings = self.api_client.get_transcode_settings()
|
||||
stream = streams[track_index]
|
||||
if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:
|
||||
self.info['SubtitleUrl'] = self.get_subtitles(source, stream, subtitle_selected)
|
||||
self.info['SubtitleStreamIndex'] = subtitle_selected
|
||||
if (
|
||||
server_settings["EnableSubtitleExtraction"]
|
||||
and stream["SupportsExternalStream"]
|
||||
):
|
||||
self.info["SubtitleUrl"] = self.get_subtitles(
|
||||
source, stream, subtitle_selected
|
||||
)
|
||||
self.info["SubtitleStreamIndex"] = subtitle_selected
|
||||
elif allow_burned_subs:
|
||||
prefs += "&SubtitleStreamIndex=%s" % subtitle_selected
|
||||
self.info['SubtitleStreamIndex'] = subtitle_selected
|
||||
self.info["SubtitleStreamIndex"] = subtitle_selected
|
||||
|
||||
return prefs
|
||||
|
||||
def get_subtitles(self, source, stream, index):
|
||||
|
||||
if stream['IsTextSubtitleStream'] and 'DeliveryUrl' in stream and stream['DeliveryUrl'].lower().startswith('/videos'):
|
||||
url = "%s%s" % (self.info['ServerAddress'], stream['DeliveryUrl'])
|
||||
if (
|
||||
stream["IsTextSubtitleStream"]
|
||||
and "DeliveryUrl" in stream
|
||||
and stream["DeliveryUrl"].lower().startswith("/videos")
|
||||
):
|
||||
url = "%s%s" % (self.info["ServerAddress"], stream["DeliveryUrl"])
|
||||
else:
|
||||
url = "%s/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % (
|
||||
self.info['ServerAddress'],
|
||||
self.item['Id'],
|
||||
source['Id'],
|
||||
self.info["ServerAddress"],
|
||||
self.item["Id"],
|
||||
source["Id"],
|
||||
index,
|
||||
stream['Codec'],
|
||||
self.info['Token']
|
||||
stream["Codec"],
|
||||
self.info["Token"],
|
||||
)
|
||||
|
||||
return url
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue