From e742608a919d6dccc753d3b51f95b0ff25df61de Mon Sep 17 00:00:00 2001 From: mediaminister Date: Tue, 23 Dec 2025 11:43:38 +0100 Subject: [PATCH] Fix subtitles --- resources/lib/kodiutils.py | 2 ++ resources/lib/play/__init__.py | 4 ++- resources/lib/play/content.py | 45 ++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/resources/lib/kodiutils.py b/resources/lib/kodiutils.py index 43bc4a0..d1daad8 100644 --- a/resources/lib/kodiutils.py +++ b/resources/lib/kodiutils.py @@ -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: diff --git a/resources/lib/play/__init__.py b/resources/lib/play/__init__.py index c6b0d16..b72221f 100644 --- a/resources/lib/play/__init__.py +++ b/resources/lib/play/__init__.py @@ -11,7 +11,7 @@ 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 @@ -19,6 +19,7 @@ def __init__(self, uuid=None, url=None, stream_type=None, license_url=None, lice :type license_url: str :type license_headers: str :type license_keys: dict + :type subtitles: list[str] """ self.uuid = uuid self.url = url @@ -26,6 +27,7 @@ def __init__(self, uuid=None, url=None, stream_type=None, license_url=None, lice 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__ diff --git a/resources/lib/play/content.py b/resources/lib/play/content.py index 4660c55..a201c12 100644 --- a/resources/lib/play/content.py +++ b/resources/lib/play/content.py @@ -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) @@ -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: @@ -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]