Skip to content

Commit 999f2db

Browse files
authored
Add support for WIDEVINE encrypted DASH streams (#15)
* Remove audio support in `play_video` * Add support for WIDEVINE encrypted DASH streams * Python version in checks
1 parent 89910f2 commit 999f2db

File tree

5 files changed

+65
-22
lines changed

5 files changed

+65
-22
lines changed

.github/workflows/addoncheck-matrix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Python 3.10
1313
uses: actions/setup-python@v1
1414
with:
15-
python-version: 3.10.4
15+
python-version: 3.10.5
1616
- name: Install dependencies
1717
run: |
1818
python -m pip install --upgrade pip

.github/workflows/addoncheck-nexus.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Python 3.10
1313
uses: actions/setup-python@v1
1414
with:
15-
python-version: 3.10.4
15+
python-version: 3.10.5
1616
- name: Install dependencies
1717
run: |
1818
python -m pip install --upgrade pip

.github/workflows/flake8.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Python 3.10.4
1313
uses: actions/setup-python@v1
1414
with:
15-
python-version: 3.10.4
15+
python-version: 3.10.5
1616
- name: Install dependencies
1717
run: |
1818
python -m pip install --upgrade pip

addon.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<addon id="script.module.srgssr" name="SRG SSR" version="2.1.0" provider-name="Alexander Seiler">
33
<requires>
44
<import addon="xbmc.python" version="3.0.0"/>
5+
<import addon="script.module.inputstreamhelper" version="0.5.10"/>
56
<import addon="script.module.simplecache" version="2.0.2"/>
67
<import addon="script.module.requests" version="2.22.0"/>
78
<import addon="script.module.youtube_channels" version="0.2.0"/>

lib/srgssr.py

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import xbmcaddon
3838
import xbmcvfs
3939

40+
import inputstreamhelper
4041
import simplecache
4142
import youtube_channels
4243

@@ -1010,27 +1011,28 @@ def get_auth_url(self, url, segment_data=None):
10101011
url += ('?' if '?' not in url else '&') + auth_params
10111012
return url
10121013

1013-
def play_video(self, media_id_or_urn, audio=False):
1014+
def play_video(self, media_id_or_urn):
10141015
"""
10151016
Gets the stream information starts to play it.
10161017
10171018
Keyword arguments:
10181019
media_id_or_urn -- the urn or id of the media to play
1019-
audio -- boolean value to indicate if the content is
1020-
audio (default: False)
10211020
"""
10221021
if media_id_or_urn.startswith('urn:'):
10231022
urn = media_id_or_urn
10241023
media_id = media_id_or_urn.split(':')[-1]
10251024
else:
1026-
media_type = 'audio' if audio else 'video'
1025+
# TODO: Could fail for livestreams
1026+
media_type = 'video'
10271027
urn = 'urn:' + self.bu + ':' + media_type + ':' + media_id_or_urn
10281028
media_id = media_id_or_urn
10291029
self.log('play_video, urn = ' + urn + ', media_id = ' + media_id)
10301030

10311031
detail_url = ('https://il.srgssr.ch/integrationlayer/2.0/'
10321032
'mediaComposition/byUrn/' + urn)
10331033
json_response = json.loads(self.open_url(detail_url))
1034+
title = utils.try_get(json_response, ['episode', 'title'], str, urn)
1035+
10341036
chapter_list = utils.try_get(
10351037
json_response, 'chapterList', data_type=list, default=[])
10361038
if not chapter_list:
@@ -1053,27 +1055,22 @@ def play_video(self, media_id_or_urn, audio=False):
10531055
'HD': '',
10541056
}
10551057

1056-
if audio:
1057-
candidates = [res for res in resource_list if utils.try_get(
1058-
res, 'protocol') in ('HTTP', 'HTTPS', 'HTTP-MP3-STREAM')]
1059-
for candi in candidates:
1060-
if utils.try_get(candi, 'quality') in ('HD', 'HQ'):
1061-
stream_url = candi['url']
1062-
break
1063-
else:
1064-
stream_url = candidates[0]['url']
1065-
1066-
play_item = xbmcgui.ListItem(media_id, path=stream_url)
1067-
xbmcplugin.setResolvedUrl(self.handle, True, play_item)
1068-
return
1069-
10701058
mf_type = 'hls'
1059+
drm = False
10711060
for resource in resource_list:
1061+
if utils.try_get(resource, 'drmList', data_type=list, default=[]):
1062+
drm = True
1063+
break
1064+
10721065
if utils.try_get(resource, 'protocol') == 'HLS':
10731066
for key in ('SD', 'HD'):
10741067
if utils.try_get(resource, 'quality') == key:
10751068
stream_urls[key] = utils.try_get(resource, 'url')
10761069

1070+
if drm:
1071+
self.play_drm(urn, title, resource_list)
1072+
return
1073+
10771074
if not stream_urls['SD'] and not stream_urls['HD']:
10781075
self.log('play_video: no stream URL found.')
10791076
return
@@ -1121,7 +1118,6 @@ def play_video(self, media_id_or_urn, audio=False):
11211118
new_query, parsed_url.fragment)
11221119
auth_url = surl_result.geturl()
11231120
self.log(f'play_video, auth_url = {auth_url}')
1124-
title = utils.try_get(json_response, ['episode', 'title'], str, urn)
11251121
play_item = xbmcgui.ListItem(title, path=auth_url)
11261122
if self.subtitles:
11271123
subs = self.get_subtitles(stream_url, urn)
@@ -1134,6 +1130,52 @@ def play_video(self, media_id_or_urn, audio=False):
11341130

11351131
xbmcplugin.setResolvedUrl(self.handle, True, play_item)
11361132

1133+
def play_drm(self, urn, title, resource_list):
1134+
self.log(f'play_drm: urn = {urn}')
1135+
preferred_quality = 'HD' if self.prefer_hd else 'SD'
1136+
resource_data = {
1137+
'url': '',
1138+
'lic_url': '',
1139+
}
1140+
for resource in resource_list:
1141+
url = utils.try_get(resource, 'url')
1142+
if not url:
1143+
continue
1144+
quality = utils.try_get(resource, 'quality')
1145+
lic_url = ''
1146+
if utils.try_get(resource, 'protocol') == 'DASH':
1147+
drmlist = utils.try_get(
1148+
resource, 'drmList', data_type=list, default=[])
1149+
for item in drmlist:
1150+
if utils.try_get(item, 'type') == 'WIDEVINE':
1151+
lic_url = utils.try_get(item, 'licenseUrl')
1152+
resource_data['url'] = url
1153+
resource_data['lic_url'] = lic_url
1154+
if resource_data['lic_url'] and quality == preferred_quality:
1155+
break
1156+
1157+
if not resource_data['url'] or not resource_data['lic_url']:
1158+
self.log('play_drm: No stream found')
1159+
return
1160+
1161+
manifest_type = 'mpd'
1162+
drm = 'com.widevine.alpha'
1163+
helper = inputstreamhelper.Helper(manifest_type, drm=drm)
1164+
if not helper.check_inputstream():
1165+
self.log('play_drm: Unable to setup drm')
1166+
return
1167+
1168+
play_item = xbmcgui.ListItem(
1169+
title, path=self.get_auth_url(resource_data['url']))
1170+
ia = 'inputstream.adaptive'
1171+
play_item.setProperty('inputstream', ia)
1172+
lic_key = f'{resource_data["lic_url"]}|' \
1173+
'Content-Type=application/octet-stream|R{SSM}|'
1174+
play_item.setProperty(f'{ia}.manifest_type', manifest_type)
1175+
play_item.setProperty(f'{ia}.license_type', drm)
1176+
play_item.setProperty(f'{ia}.license_key', lic_key)
1177+
xbmcplugin.setResolvedUrl(self.handle, True, play_item)
1178+
11371179
def get_subtitles(self, url, name):
11381180
"""
11391181
Returns subtitles from an url

0 commit comments

Comments
 (0)