Skip to content

Commit

Permalink
feat: Update to fix API live TV changes
Browse files Browse the repository at this point in the history
  • Loading branch information
micahg committed Jul 10, 2024
1 parent 2c77d6a commit df0117c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 23 deletions.
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.18+matrix.1"
version="4.0.19+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 a crash handling EPG HTML that has no useful information</news>
<news>- Fix live channels and IPTV</news>
</extension>
</addon>
18 changes: 8 additions & 10 deletions plugin.video.cbc/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,6 @@ def logout():
os.remove(get_cookie_file())


@plugin.route('/smil')
def play_smil():
"""Play an SMIL file."""
cbc = CBC()
url = cbc.parse_smil(plugin.args['url'][0])
labels = dict(parse_qsl(plugin.args['labels'][0])) if 'labels' in plugin.args else None
return play(labels, plugin.args['image'][0], url)


@plugin.route('/iptv/channels')
def iptv_channels():
"""Send a list of IPTV channels."""
Expand Down Expand Up @@ -149,6 +140,13 @@ def live_channels_add_only(station):
LiveChannels.add_only_iptv_channel(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)

@plugin.route('/channels')
def live_channels_menu():
"""Populate the menu with live channels."""
Expand All @@ -171,7 +169,7 @@ def live_channels_menu():
(getString(30017), 'RunPlugin({})'.format(plugin.url_for(live_channels_add_only, callsign))),
])
xbmcplugin.addDirectoryItem(plugin.handle,
plugin.url_for(play_smil, url=channel['content'][0]['url'],
plugin.url_for(play_live_channel, id=channel['idMedia'],
labels=urlencode(labels), image=image), item, False)
xbmcplugin.endOfDirectory(plugin.handle)

Expand Down
9 changes: 9 additions & 0 deletions plugin.video.cbc/resources/lib/cbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

CALLSIGN = 'cbc$callSign'
CHANNEL_GUID = 'guid'
CHANNEL_ID = 'idMedia'
API_KEY = '3f4beddd-2061-49b0-ae80-6f1f2ed65b37'
SCOPES = 'openid '\
'offline_access '\
Expand Down Expand Up @@ -258,6 +259,12 @@ def get_image(self, item):
return item['cbc$staticImage']
if 'cbc$featureImage' in item:
return item['cbc$featureImage']
if 'image' in item:
return item['image']
if 'images' in item:
if 'card' in item['images']:
if 'url' in item['images']['card']:
return item['images']['card']['url']
return None

@staticmethod
Expand All @@ -267,6 +274,8 @@ def get_callsign(item):
return item[CALLSIGN]
if CHANNEL_GUID in item:
return item[CHANNEL_GUID]
if CHANNEL_ID in item:
return item[CHANNEL_ID]
return None

@staticmethod
Expand Down
6 changes: 3 additions & 3 deletions plugin.video.cbc/resources/lib/epg.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
# Also important is that there needs to be symmetry between get_iptv_epg and
# get_iptv_channels (from livechannels.py) because of how iptvmanager works.
SPECIAL_GUIDES = {
'NN': 'https://www.cbc.ca/programguide/daily/{}/cbc_news_network',
'2265331267748': 'https://www.cbc.ca/programguide/daily/{}/cbc_comedy_fast',
'2076284995790': 'https://www.cbc.ca/programguide/daily/{}/cbc_news_explore',
15716: 'https://www.cbc.ca/programguide/daily/{}/cbc_comedy_fast',
15717: 'https://www.cbc.ca/programguide/daily/{}/cbc_news_explore',
15718: 'https://www.cbc.ca/programguide/daily/{}/cbc_news_network',
}

def get_iptv_epg():
Expand Down
60 changes: 52 additions & 8 deletions plugin.video.cbc/resources/lib/livechannels.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Module for live channels."""
from concurrent import futures
import json
from urllib.parse import urlencode

Expand All @@ -7,8 +8,8 @@
from resources.lib.utils import save_cookies, loadCookies, log, get_iptv_channels_file
from resources.lib.cbc import CBC

LIST_URL = 'https://tpfeed.cbc.ca/f/ExhSPC/t_t3UKJR6MAT?pretty=true&sort=pubDate%7Cdesc'
LIST_ELEMENT = 'entries'
LIST_URL = 'https://services.radio-canada.ca/ott/catalog/v2/gem/home?device=web'
LIST_ELEMENT = '2415871718'

class LiveChannels:
"""Class for live channels."""
Expand All @@ -30,34 +31,59 @@ def get_live_channels(self):
return None
save_cookies(self.session.cookies)

items = json.loads(resp.content)[LIST_ELEMENT]
return items
ret = None
for result in json.loads(resp.content)['lineups']['results']:
if result['key'] == LIST_ELEMENT:
ret = result['items']

future_to_callsign = {}
with futures.ThreadPoolExecutor(max_workers=20) as executor:
for i, channel in enumerate(ret):
callsign = CBC.get_callsign(channel)
future = executor.submit(self.get_channel_metadata, callsign)
future_to_callsign[future] = i

for future in futures.as_completed(future_to_callsign):
i = future_to_callsign[future]
metadata = future.result()
ret[i]['image'] = metadata['Metas']['imageHR']
return ret

def get_iptv_channels(self):
"""Get the channels in a IPTV Manager compatible list."""
cbc = CBC()
channels = self.get_live_channels()
channels = [channel for channel in channels if channel['feedType'].lower() == 'livelinear']
blocked = self.get_blocked_iptv_channels()
result = []
for channel in channels:
callsign = CBC.get_callsign(channel)

# if the user has omitted this from the list of their channels, don't populate it
if callsign in blocked:
if f'{callsign}' in blocked:
continue

labels = CBC.get_labels(channel)
image = cbc.get_image(channel)

# this one requires auth but gives back the HLS stream but requires Authorization: Client-Key asdfasdf
# https://services.radio-canada.ca/media/validation/v2/?appCode=medianetlive&connectionType=hd&deviceType=ipad&idMedia=15732&multibitrate=true&output=json&tech=hls&manifestType=desktop

# https://gem.cbc.ca/_next/static/chunks/c93403b3.adc94895e46a3939.js has client key (just showed up in HAR)

# THE FORMAT OF THESE IS VERY IMPORTANT
# - values is passed to /channels/play in default.py
# - channel_dict is used by the IPTVManager for the guide and stream is how the IPTV manager calls us back to play something
values = {
'url': channel['content'][0]['url'],
'id': callsign,
'image': image,
'labels': urlencode(labels)
}
channel_dict = {
'name': channel['title'],
'stream': 'plugin://plugin.video.cbc/smil?' + urlencode(values),
'stream': 'plugin://plugin.video.cbc/channels/play?' + urlencode(values),
'id': callsign,
'logo': image
'logo': image,
}

# Use "CBC Toronto" instead of "Toronto"
Expand All @@ -67,6 +93,24 @@ def get_iptv_channels(self):

return result

def get_channel_stream(self, id):
url = f'https://services.radio-canada.ca/media/validation/v2/?appCode=medianetlive&connectionType=hd&deviceType=ipad&idMedia={id}&multibitrate=true&output=json&tech=hls&manifestType=desktop'
resp = self.session.get(url)
if not resp.status_code == 200:
log('ERROR: {} returns status of {}'.format(LIST_URL, resp.status_code), True)
return None
save_cookies(self.session.cookies)
return json.loads(resp.content)['url']

def get_channel_metadata(self, id):
url = f'https://services.radio-canada.ca/media/meta/v1/index.ashx?appCode=medianetlive&idMedia={id}&output=jsonObject'
resp = self.session.get(url)
if not resp.status_code == 200:
log('ERROR: {} returns status of {}'.format(LIST_URL, resp.status_code), True)
return None
save_cookies(self.session.cookies)
return json.loads(resp.content)

@staticmethod
def get_blocked_iptv_channels():
"""Get the list of blocked channels."""
Expand Down

0 comments on commit df0117c

Please sign in to comment.