Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/lib/kodiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ def play(stream, title=None, art_dict=None, info_dict=None, prop_dict=None, stre
play_item.setProperties(prop_dict)
if stream_dict:
play_item.addStreamInfo('video', stream_dict)
if stream.subtitles:
play_item.setSubtitles(stream.subtitles)

# Setup Inputstream Adaptive
if kodi_version_major() >= 19:
Expand Down
4 changes: 3 additions & 1 deletion resources/lib/play/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@
class ResolvedStream:
""" Defines a stream that we can play"""

def __init__(self, uuid=None, url=None, stream_type=None, license_url=None, license_headers=None, license_keys=None):
def __init__(self, uuid=None, url=None, stream_type=None, license_url=None, license_headers=None, license_keys=None, subtitles=None):
"""
:type uuid: str
:type url: str
:type stream_type: str
:type license_url: str
:type license_headers: str
:type license_keys: dict
:type subtitles: list[str]
"""
self.uuid = uuid
self.url = url
self.stream_type = stream_type
self.license_url = license_url
self.license_headers = license_headers
self.license_keys = license_keys
self.subtitles = subtitles

def __repr__(self):
return "%r" % self.__dict__
45 changes: 43 additions & 2 deletions resources/lib/play/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ def get_stream(self, uuid: str, content_type: str) -> ResolvedStream:

manifest_urls = data.get('manifestUrls') or {}
manifest_url = None
subtitle_url = None
stream_type = None

# Manifest URLs (DASH or HLS)
Expand All @@ -344,11 +345,12 @@ def get_stream(self, uuid: str, content_type: str) -> ResolvedStream:
elif data.get('adType') == 'SSAI' and data.get('ssai'):
ssai = data['ssai']
ssai_url = (
f"https://pubads.g.doubleclick.net/ondemand/dash/content/"
f"{ssai.get('contentSourceID')}/vid/{ssai.get('videoID')}/streams"
f'https://pubads.g.doubleclick.net/ondemand/dash/content/'
f'{ssai.get('contentSourceID')}/vid/{ssai.get('videoID')}/streams'
)
ad_data = json.loads(utils.post_url(ssai_url, data=''))
manifest_url = ad_data.get('stream_manifest')
subtitle_url = self.extract_subtitle_from_manifest(manifest_url)
stream_type = STREAM_DASH

if not manifest_url or not stream_type:
Expand Down Expand Up @@ -380,8 +382,47 @@ def get_stream(self, uuid: str, content_type: str) -> ResolvedStream:
license_url=self.LICENSE_URL,
license_headers=license_headers,
license_keys=license_keys,
subtitles=[subtitle_url],
)

def extract_subtitle_from_manifest(self, manifest_url):
"""Extract subtitle URL from a DASH manifest"""
from xml.etree.ElementTree import fromstring

manifest_data = utils.get_url(manifest_url)
manifest = fromstring(manifest_data)

ns = {'mpd': 'urn:mpeg:dash:schema:mpd:2011'}

base_url_el = manifest.find('mpd:BaseURL', ns)
if base_url_el is None or not base_url_el.text:
return None

base_url = base_url_el.text

for adaption in manifest.iterfind(
'.//mpd:AdaptationSet[@contentType="text"]', ns
):
rep = adaption.find('mpd:Representation', ns)
if rep is None:
continue

sub_base = rep.find('mpd:BaseURL', ns)
if sub_base is None or not sub_base.text:
continue

sub_path = sub_base.text
if 'T888' not in sub_path:
continue

# Strip period-specific suffix safely
name, _, ext = sub_path.rpartition('.')
clean_path = name.rsplit('_', 1)[0] + '.' + ext

return base_url + clean_path

return None

def get_program_tree(self):
""" Get a content tree with information about all the programs.
:rtype list[Program]
Expand Down