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
4 changes: 2 additions & 2 deletions plugin.video.cbc/addon.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.cbc"
name="Canadian Broadcasting Corp (CBC)"
version="4.0.19+matrix.1"
version="4.0.20+matrix.1"
provider-name="micahg,t1m,smf007,oshanrube">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
Expand All @@ -28,6 +28,6 @@
<forum>https://forum.kodi.tv/showthread.php?tid=328421</forum>
<website>https://watch.cbc.ca/</website>
<source>https://github.com/micahg/plugin.video.cbc</source>
<news>- Fix live channels and IPTV</news>
<news>- Updates for new CBC Gem API</news>
</extension>
</addon>
238 changes: 71 additions & 167 deletions plugin.video.cbc/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@

getString = xbmcaddon.Addon().getLocalizedString
LIVE_CHANNELS = getString(30004)
GEMS = {
'featured': getString(30005),
'shows': getString(30006),
'documentaries': getString(30024),
'kids': getString(30025)
}
SEARCH = getString(30026)


Expand Down Expand Up @@ -56,38 +50,64 @@ def authorize():
return True


def play(labels, image, url):
def play(labels, image, data):
"""Play the stream using the configured player."""
item = xbmcgui.ListItem(labels['title'], path=url)
if image:
item.setArt({'thumb': image, 'poster': image})
item.setInfo(type="Video", infoLabels=labels)
helper = inputstreamhelper.Helper('hls')
if not xbmcaddon.Addon().getSettingBool("ffmpeg") and helper.check_inputstream():
item.setProperty('inputstream', 'inputstream.adaptive')
item.setProperty('inputstream.adaptive.manifest_type', 'hls')
xbmcplugin.setResolvedUrl(plugin.handle, True, item)
if url is None:
if not 'url' in data:
xbmcgui.Dialog().ok(getString(30010), getString(30011))

return

(lic, tok) = GemV2.get_stream_drm(data)
is_helper = None
mime = None
drm = None
if data['type'] == 'hls':
is_helper = inputstreamhelper.Helper('hls')
elif data['type'] == 'dash':
drm = 'com.widevine.alpha'
is_helper = inputstreamhelper.Helper('mpd', drm=drm)
mime = 'application/dash+xml'

if is_helper is None:
xbmcgui.Dialog().ok(getString(30027), getString(30027))
return

if is_helper.check_inputstream():
url = data['url']
play_item = xbmcgui.ListItem(path=url)
play_item.setInfo(type="Video", infoLabels=labels)
if mime:
play_item.setMimeType(mime)
play_item.setContentLookup(False)

if int(xbmc.getInfoLabel('System.BuildVersion').split('.')[0]) >= 19:
play_item.setProperty('inputstream', is_helper.inputstream_addon)
else:
play_item.setProperty('inputstreamaddon', is_helper.inputstream_addon)

if drm:
play_item.setProperty('inputstream.adaptive.license_type', drm)
license_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
'Content-Type': 'application/octet-stream',
'Origin': 'https://gem.cbc.ca',
'x-dt-auth-token': tok, # string containing "Bearer eyJ...."
}
license_config = [ lic, urlencode(license_headers), 'R{SSM}', 'R']
license_key = '|'.join(license_config)
play_item.setProperty('inputstream.adaptive.license_key', license_key)
xbmcplugin.setResolvedUrl(plugin.handle, True, play_item)

def add_items(handle, items):
for item in items:
list_item = xbmcgui.ListItem(item['title'])
list_item.setInfo(type="Video", infoLabels=CBC.get_labels(item))
image = item['image'].replace('(Size)', '224')
image = item['image']['url'].replace('(Size)', '224')
list_item.setArt({'thumb': image, 'poster': image})
item_type = item['type']
is_folder = True
if item_type == 'SHOW':
url = plugin.url_for(gem_show_menu, item['id'])
elif item_type == 'ASSET':
url = plugin.url_for(gem_asset, item['id'])
list_item.setProperty('IsPlayable', 'true')
is_folder = False
elif item_type == 'SEASON':
# ignore the season and go to the show (its what the web UI does)
url = plugin.url_for(gem_show_menu, item['id'].split('/')[0])
if item_type.lower() == 'show' or item_type.lower() == 'media':
p = GemV2.normalized_format_path(item)
url = plugin.url_for(layout_menu, p)
else:
log(f'Unable to handle shelf item type "{item_type}".', True)
url = None
Expand Down Expand Up @@ -143,9 +163,12 @@ def live_channels_add_only(station):
@plugin.route('/channels/play')
def play_live_channel():
labels = dict(parse_qsl(plugin.args['labels'][0])) if 'labels' in plugin.args else None
chans = LiveChannels()
url = chans.get_channel_stream(plugin.args['id'][0])
return play(labels, plugin.args['image'][0], url)
data = GemV2.get_stream(plugin.args['id'][0], plugin.args['app_code'][0])
if not 'url' in data:
log('Failed to get stream URL, attempting to authorize.')
if authorize():
data = GemV2.get_stream(plugin.args['id'][0], plugin.args['app_code'][0])
return play(labels, plugin.args['image'][0] if 'image' in plugin.args else None, data)

@plugin.route('/channels')
def live_channels_menu():
Expand All @@ -169,131 +192,11 @@ def live_channels_menu():
(getString(30017), 'RunPlugin({})'.format(plugin.url_for(live_channels_add_only, callsign))),
])
xbmcplugin.addDirectoryItem(plugin.handle,
plugin.url_for(play_live_channel, id=channel['idMedia'],
plugin.url_for(play_live_channel, id=channel['idMedia'], app_code='medianetlive',
labels=urlencode(labels), image=image), item, False)
xbmcplugin.endOfDirectory(plugin.handle)


@plugin.route('/gem/show/episode')
def gem_episode():
"""Play an episode."""
json_str = plugin.args['query'][0]
episode = json.loads(json_str)

# get the url, and failing that, attempt authorization, then retry
resp = GemV2().get_episode(episode['url'])
url = None if not resp else resp['url'] if 'url' in resp else None
if not url:
log('Failed to get stream URL, attempting to authorize.')
if authorize():
resp = GemV2().get_episode(episode['url'])
url = resp['url'] if 'url' in resp else None

labels = episode['labels']
play(labels, None, url)


@plugin.route('/gem/show/season')
def gem_show_season():
"""Create a menu for a show season."""
xbmcplugin.setContent(plugin.handle, 'videos')
json_str = plugin.args['query'][0]
# remember show['season'] is season details but there is general show info in show as well
show = json.loads(json_str)
for episode in show['season']['assets']:
item = xbmcgui.ListItem(episode['title'])
image = episode['image'].replace('(Size)', '224')
item.setArt({'thumb': image, 'poster': image})
item.setProperty('IsPlayable', 'true')
labels = GemV2.get_labels(show, episode)
item.setInfo(type="Video", infoLabels=labels)
episode_info = {'url': episode['playSession']['url'], 'labels': labels}
url = plugin.url_for(gem_episode, query=json.dumps(episode_info))
xbmcplugin.addDirectoryItem(plugin.handle, url, item, False)
xbmcplugin.endOfDirectory(plugin.handle)


@plugin.route('/gem/asset/<path:asset>')
def gem_asset(asset):
asset_layout = GemV2.get_asset_by_id(asset)
resp = GemV2.get_episode(asset_layout['playSession']['url'])
url = None if not resp else resp['url'] if 'url' in resp else None
if not url:
log('Failed to get stream URL, attempting to authorize.')
if authorize():
resp = GemV2().get_episode(asset_layout['playSession']['url'])
url = resp['url'] if 'url' in resp else None
labels = GemV2.get_labels({'title': asset_layout['series']}, asset_layout)
image = asset_layout['image']
play(labels, image, url)


def gem_add_film_assets(assets):
for asset in assets:
labels = GemV2.get_labels({'title': asset['series']}, asset)
image = asset['image']
item = xbmcgui.ListItem(labels['title'])
item.setInfo(type="Video", infoLabels=labels)
item.setArt({'thumb': image, 'poster': image})
item.setProperty('IsPlayable', 'true')
episode_info = {'url': asset['playSession']['url'], 'labels': labels}
url = plugin.url_for(gem_episode, query=json.dumps(episode_info))
xbmcplugin.addDirectoryItem(plugin.handle, url, item, False)


@plugin.route('/gem/show/<show_id>')
def gem_show_menu(show_id):
"""Create a menu for a shelfs items."""
xbmcplugin.setContent(plugin.handle, 'videos')
show_layout = GemV2.get_show_layout_by_id(show_id)
show = {k: v for (k, v) in show_layout.items() if k not in ['sponsors', 'seasons']}
for season in show_layout['seasons']:

# films seem to have been shoe-horned (with teeth) into the structure oddly -- compensate
if season['title'] == 'Film':
gem_add_film_assets(season['assets'])
else:
labels = GemV2.get_labels(season, season)
item = xbmcgui.ListItem(season['title'])
item.setInfo(type="Video", infoLabels=labels)
image = season['image'].replace('(Size)', '224')
item.setArt({'thumb': image, 'poster': image})
show['season'] = season
url = plugin.url_for(gem_show_season, query=json.dumps(show))
xbmcplugin.addDirectoryItem(plugin.handle, url, item, True)

xbmcplugin.endOfDirectory(plugin.handle)


@plugin.route('/gem/shelf')
def gem_shelf_menu():
"""Create a menu item for each shelf."""
handle = plugin.handle
xbmcplugin.setContent(handle, 'videos')
json_str = plugin.args['query'][0]
shelf_items = json.loads(json_str)
add_items(handle, shelf_items)
xbmcplugin.addSortMethod(plugin.handle, xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE)
xbmcplugin.endOfDirectory(handle)


@plugin.route('/gem/categories/<category_id>')
def gem_category_menu(category_id):
"""Populate a menu with categorical content."""
handle = plugin.handle
xbmcplugin.setContent(handle, 'videos')
category = GemV2.get_category(category_id)
for show in category['items']:
item = xbmcgui.ListItem(show['title'])
item.setInfo(type="Video", infoLabels=CBC.get_labels(show))
image = show['image'].replace('(Size)', '224')
item.setArt({'thumb': image, 'poster': image})
url = plugin.url_for(gem_show_menu, show['id'])
xbmcplugin.addDirectoryItem(handle, url, item, True)
xbmcplugin.addSortMethod(handle, xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE)
xbmcplugin.endOfDirectory(handle)


@plugin.route('/gem/search')
def search():
handle = plugin.handle
Expand All @@ -303,23 +206,24 @@ def search():
xbmcplugin.endOfDirectory(handle)


@plugin.route('/gem/layout/<layout>')
@plugin.route('/gem/layout/<path:layout>')
def layout_menu(layout):
"""Populate the menu with featured items."""
handle = plugin.handle
xbmcplugin.setContent(handle, 'videos')
layout = GemV2.get_layout(layout)
if 'categories' in layout:
for category in layout['categories']:
item = xbmcgui.ListItem(category['title'])
url = plugin.url_for(gem_category_menu, category['id'])
xbmcplugin.addDirectoryItem(handle, url, item, True)
if 'shelves' in layout:
for shelf in layout['shelves']:
item = xbmcgui.ListItem(shelf['title'])
shelf_items = json.dumps(shelf['items'])
url = plugin.url_for(gem_shelf_menu, query=shelf_items)
xbmcplugin.addDirectoryItem(handle, url, item, True)
for f in GemV2.get_format(layout):
n = GemV2.normalized_format_item(f)
p = GemV2.normalized_format_path(f)
item = xbmcgui.ListItem(n['label'])
if 'art' in n:
item.setArt(n['art'])
item.setInfo(type="Video", infoLabels=n['info_labels'])
if n['playable']:
item.setProperty('IsPlayable', 'true')
url = plugin.url_for(play_live_channel, id=p, app_code=n['app_code'])
else:
url = plugin.url_for(layout_menu, p)
xbmcplugin.addDirectoryItem(handle, url, item, not n['playable'])
xbmcplugin.endOfDirectory(handle)


Expand All @@ -334,8 +238,8 @@ def main_menu():

handle = plugin.handle
xbmcplugin.setContent(handle, 'videos')
for key, value in GEMS.items():
xbmcplugin.addDirectoryItem(handle, plugin.url_for(layout_menu, key), xbmcgui.ListItem(value), True)
for c in GemV2.get_browse():
xbmcplugin.addDirectoryItem(handle, plugin.url_for(layout_menu, c['url']), xbmcgui.ListItem(c['title']), True)
xbmcplugin.addDirectoryItem(handle, plugin.url_for(live_channels_menu), xbmcgui.ListItem(LIVE_CHANNELS), True)
xbmcplugin.addDirectoryItem(handle, plugin.url_for(search), xbmcgui.ListItem(SEARCH), True)
xbmcplugin.endOfDirectory(handle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,7 @@ msgstr ""
msgctxt "#30026"
msgid "Search"
msgstr ""

msgctxt "#30027"
msgid "Unsupported DRM"
msgstr ""
15 changes: 0 additions & 15 deletions plugin.video.cbc/resources/lib/cbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,21 +325,6 @@ def get_labels(item):
return labels


def parse_smil(self, smil):
"""Parse a SMIL file for the video."""
resp = self.session.get(smil)

if not resp.status_code == 200:
log(f'ERROR: {smil} returns status of {resp.status_code}', True)
return None
save_cookies(self.session.cookies)

dom = parseString(resp.content)
seq = dom.getElementsByTagName('seq')[0]
video = seq.getElementsByTagName('video')[0]
src = video.attributes['src'].value
return src

@staticmethod
def get_session():
"""Get a requests session object with CBC cookies."""
Expand Down
Loading