Closed riccardo1991 closed 6 years ago
I tried by myself but I do not have the possibility to test the code now. Can you check if this mod works? Note: this is with 1 live event only since we do not know the returned JSON structure with multiple live.
from phate89lib import kodiutils, rutils, staticutils
import urllib, datetime, calendar, os
from urlparse import urlparse
import dateutil.parser
import xml.etree.ElementTree as ET
from tempfile import mkstemp
webutils=rutils.RUtils()
webutils.log=kodiutils.log
useragent = "RSI.CH Video Addon"
useragent_q = urllib.quote_plus(useragent)
webutils.USERAGENT = useragent
#site logic
def getVideoLinks(id):
return getVideoLinksFromUrl('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/video/play/{id}.json'.format(id=id))
def getVideoLinksFromUrl(url):
resp = webutils.getJson(url)
if not resp:
return False
if not ('Video' in resp and 'Playlists' in resp['Video'] and 'Playlist' in resp['Video']['Playlists']):
return False
ret = []
for pl in resp['Video']['Playlists']['Playlist']:
for purl in pl['url']:
plout= {}
plout['protocol'] = pl['@protocol']
plout['quality'] = purl['@quality']
plout['url'] = purl['text']
ret.append(plout)
if 'Subtitles' in resp['Video'] and 'TTMLUrl' in resp['Video']['Subtitles']:
ret.append({'protocol':'TTMLUrl', 'quality':'', 'url': resp['Video']['Subtitles']['TTMLUrl']})
return ret
def getAuthString(url):
#obtain the auth url
sUrl=urlparse(url).path.split('/')
if len(sUrl) < 3:
return False
auth=webutils.getJson('http://tp.srgssr.ch/akahd/token?acl={path}*'.format(path=urllib.quote_plus('/{p1}/{p2}/'.format(p1=sUrl[1],p2=sUrl[2]))))
if 'token' in auth and 'authparams' in auth['token']:
return '?' + auth['token']['authparams']
else:
return False;
def getCategories():
jsn = webutils.getJson('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/tv/topic.json')
if not jsn or 'Topics' not in jsn or 'Topic' not in jsn['Topics']:
return False
return jsn['Topics']['Topic']
def getProgramVideos(id, page=1, pageSize=10, parseAll=False):
resp = webutils.getJson('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/assetSet/listByAssetGroup/{id}.json?pageNumber={page}&pageSize={pageSize}'.format(id=id,page=page,pageSize=pageSize))
ret = getVideoInfo(resp)
if parseAll and ret and len(ret)>0:
ret.extend(getProgramVideos(id, int(page)+1, pageSize, True))
return ret
def getDateVideos(id):
resp = webutils.getJson('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/video/episodesByDate.json?day={id}'.format(id=id))
return getVideoInfo(resp)
def getCategoryVideos(id, pageSize=10):
resp = webutils.getJson('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/video/editorialPlayerLatestByTopic/{id}.json?pageSize={pageSize}'.format(id=id,pageSize=pageSize))
return getVideoInfo(resp)
def getPicksVideos(pageSize=10):
resp = webutils.getJson('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/video/editorialPlayerPicks.json?pageSize={pageSize}'.format(pageSize=pageSize))
return getVideoInfo(resp)
def getVideoInfo(jsn):
if not jsn:
return False
vids = []
if 'AssetSets' in jsn and 'AssetSet' in jsn['AssetSets']:
vids = jsn['AssetSets']['AssetSet']
elif 'Videos' in jsn and 'Video' in jsn['Videos']:
vids = jsn['Videos']['Video']
else:
return False
ret = []
for vid in vids:
if 'Assets' in vid:
if 'Video' in vid['Assets'] and len(vid['Assets']['Video']) > 0:
vid = vid['Assets']['Video'][0]
else:
continue
if 'AssetMetadatas' in vid and 'AssetMetadata' in vid['AssetMetadatas'] and len(vid['AssetMetadatas']['AssetMetadata'])>0:
vd=vid['AssetMetadatas']['AssetMetadata'][0]
title = ''
if 'AssetSet' in vid and 'Show' in vid['AssetSet'] and 'title' in vid['AssetSet']['Show']:
title = vid['AssetSet']['Show']['title'] + ' - '
date = ''
if ('createdDate' in vd):
date = dateutil.parser.parse(vd['createdDate'])
ret.append({'id': vd['id'], 'title': title + vd['title'], 'plot': vd['description'], 'date': vd['createdDate'], 'premiered': str(date), 'image': getImage(vid)})
return ret
def getImage(vid):
if 'Image' in vid and \
'ImageRepresentations' in vid['Image'] and \
'ImageRepresentation' in vid['Image']['ImageRepresentations'] and \
len(vid['Image']['ImageRepresentations']['ImageRepresentation'])>0 and \
'url' in vid['Image']['ImageRepresentations']['ImageRepresentation'][0]:
return vid['Image']['ImageRepresentations']['ImageRepresentation'][0]['url']
else:
return 'http://ws.srf.ch/asset/image/audio/b6813d84-7d73-444a-92b0-9fd5eb4606cc/NOT_SPECIFIED.jpg'
def getPrograms(letter=''):
jsn = webutils.getJson('http://il.srgssr.ch/integrationlayer/1.0/ue/rsi/tv/assetGroup/editorialPlayerAlphabetical.json')
if not jsn or 'AssetGroups' not in jsn or 'Show' not in jsn['AssetGroups']:
return False
ret = []
for pr in jsn['AssetGroups']['Show']:
if letter =='' or letter[0].lower()==pr['title'][0].lower():
ret.append({'id': pr['id'], 'title': pr['title'], 'plot': pr['description'], 'image': getImage(pr)})
return ret
def getChannels():
jsn = webutils.getJson('http://www.rsi.ch/play/tv/livemodule?layout=json')
if not jsn or 'teaser' not in jsn:
return False
return jsn['teaser']
def ttmlToSrt(url):
if not url:
return False
txt = webutils.getText(url)
if not txt:
return False
root = ET.fromstring(txt.encode('utf-8'))
if not root:
return False
count = 0
output=''
for sub in root[0][0]:
count+=1
startT=datetime.datetime.strptime(sub.attrib['begin'], '%H:%M:%S.%f').strftime('%H:%M:%S,%f')[:-3]
endT=datetime.datetime.strptime(sub.attrib['end'], '%H:%M:%S.%f').strftime('%H:%M:%S,%f')[:-3]
text=''
for line in sub:
if line.tag.endswith('span'):
text+=line.text.strip()
elif line.tag.endswith('br'):
text+='\r\n'
output+='{count}\r\n{start} --> {end}\r\n{text}\r\n\r\n'.format(count=count,start=startT,end=endT,text=text.encode('utf-8'))
f, path = mkstemp()
os.write(f, output)
os.close(f)
return path
#kodi logic
def loadList():
kodiutils.addListItem(kodiutils.LANGUAGE(32012), params={"mode": "programs" })
kodiutils.addListItem(kodiutils.LANGUAGE(32013), params={"mode": "live" })
kodiutils.addListItem(kodiutils.LANGUAGE(32014), params={"mode": "categories" })
kodiutils.addListItem(kodiutils.LANGUAGE(32015), params={"mode": "dates" })
kodiutils.addListItem(kodiutils.LANGUAGE(32016), params={"mode": "picks" })
kodiutils.addListItem(kodiutils.LANGUAGE(32017), params={"mode": "sport" })
kodiutils.endScript()
def addProgramsItems():
kodiutils.setContent('videos')
progs = getPrograms()
if progs:
for p in progs:
p['mediatype']='video'
kodiutils.addListItem(p['title'], params={"id": p['id'], "mode": "program" }, videoInfo=p, thumb=p['image'])
kodiutils.endScript()
def addLive():
kodiutils.setContent('videos')
chs = getChannels()
if chs:
for ch in chs:
kodiutils.addListItem(ch['title'], params={"mode": "video", "id": ch['id'] }, thumb=ch['logoUrl'], videoInfo={'mediatype': 'video'}, isFolder=False)
kodiutils.endScript()
def addCategories():
cats = getCategories()
if cats:
for cat in cats:
kodiutils.addListItem(cat['title'], params={"mode": "category", "id": cat['id'] })
kodiutils.endScript()
def addDates():
day = datetime.date.today()
for x in range(0, 10):
kodiutils.addListItem(kodiutils.LANGUAGE(32020).format(dayname=kodiutils.LANGUAGE(32022+day.weekday()),day=day.day,month=kodiutils.LANGUAGE(32028+day.month),year=day.year), params={"mode": "date", "id": str(day) })
day = day - datetime.timedelta(days=1)
for year in range( day.year, 1950, -1):
kodiutils.addListItem(str(year), params={"mode": "year", "year": year })
kodiutils.endScript()
def addYear(year):
for month in range(1, 13):
kodiutils.addListItem(kodiutils.LANGUAGE(32021).format(month=kodiutils.LANGUAGE(32028+month),year=year), params={"mode": "month", "month": month, "year": year })
kodiutils.endScript()
def addMonth(month, year):
for day in calendar.Calendar().itermonthdates(int(year), int(month)):
kodiutils.addListItem(kodiutils.LANGUAGE(32020).format(dayname=kodiutils.LANGUAGE(32022+day.weekday()),day=day.day,month=kodiutils.LANGUAGE(32028+day.month),year=day.year), params={"mode": "date", "id": str(day) })
kodiutils.endScript()
def addVideosItems(id='', type=1, page=1):
kodiutils.setContent('videos')
res = []
loadAll = kodiutils.getSettingAsBool('loadAll')
elPerPage = kodiutils.getSetting('elPerPage')
if type==1:
res = getProgramVideos(id, page, elPerPage, loadAll)
elif type==2:
res = getDateVideos(id)
elif type==3:
res = getCategoryVideos(id, elPerPage)
elif type==4:
res = getPicksVideos(elPerPage)
if res:
for ep in res:
ep['mediatype']='video'
kodiutils.addListItem(ep['title'], params={"id": ep['id'], "mode": "video" }, thumb=ep['image'], videoInfo=ep, isFolder=False)
if type == 1 and (not loadAll) and len(getProgramVideos(id, int(page)+1, elPerPage))>0:
kodiutils.addListItem(kodiutils.LANGUAGE(32011), params={"id": id, "mode": "program", "page": int(page) + 1})
kodiutils.endScript()
def addSport():
kodiutils.setContent('videos')
liveSportList = getLiveSportLink()
if liveSportList:
for live in liveSportList:
kodiutils.addListItem(live['title'], params={"mode": "watch_sport", "url": live['url'] }, thumb=live['thumbURL'], videoInfo={'mediatype': 'video'}, isFolder=False)
kodiutils.endScript()
def getLiveSportLink():
# Get list of live sport events.
resp = webutils.getJson("https://event.api.swisstxt.ch/v1/events/rsi/live")
# If there are no live events, return False.
if not resp:
return False
# This is with an event only, to update when is available the structure of the live with multiple events.
liveSports = []
live1 = {}
live1['url'] = resp['hls']
live1['title'] = resp['title']
live1['thumbURL'] = resp['imageUrl']
liveSports.append(live1)
return liveSports
def watchLiveSport(url):
auth = getAuthString(url)
kodiutils.setResolvedUrl('{url}?{auth}|User-Agent={ua}'.format(url=url,auth=auth,ua=urllib.quote_plus(useragent)))
kodiutils.setResolvedUrl("",solved=False)
def watchVideo(id):
links = getVideoLinks(id)
neededQ = ""
if kodiutils.getSettingAsBool('PreferHD'):
neededQ = 'HD'
else:
neededQ = 'SD'
fUrl = ''
for url in links:
if url['protocol'] == 'HTTP-HLS':
fUrl = url['url']
if url['quality'] == neededQ:
break
if fUrl:
auth = getAuthString(fUrl)
if auth:
subs = []
if kodiutils.getSettingAsBool('parseSubs'):
for url in links:
if url['protocol'] == 'TTMLUrl':
subpath = ttmlToSrt(url['url'])
if subpath:
subs.append(subpath)
kodiutils.setResolvedUrl('{url}?{auth}|User-Agent={ua}'.format(url=fUrl,auth=auth,ua=urllib.quote_plus(useragent)), subs=subs)
kodiutils.setResolvedUrl("",solved=False)
#main
params = staticutils.getParams()
if not params or 'mode' not in params:
loadList()
else:
if params['mode'] == "programs":
addProgramsItems()
elif params['mode'] == "program":
addVideosItems(params['id'], type=1, page=params.get('page', 1))
elif params['mode'] == "live":
addLive()
elif params['mode'] == "dates":
addDates()
elif params['mode'] == "date":
addVideosItems(params['id'], type=2)
elif params['mode'] == "year":
addYear(params['year'])
elif params['mode'] == "month":
addMonth(params['month'], params['year'])
elif params['mode'] == "categories":
addCategories()
elif params['mode'] == "category":
addVideosItems(params['id'], type=3)
elif params['mode'] == "picks":
addVideosItems(type=4)
elif params['mode'] == "video":
watchVideo(params['id'])
elif params['mode'] == "sport":
addSport()
elif params['mode'] == "watch_sport":
watchLiveSport(params['url'])
else:
loadList()
Ok I edited a little bit your code and pushed it.. if you can test it and give me feedback I'll push it to repo
Now there are no live events, so I get no results, I will test tomorrow when there is the football live.
PS Scrivo in inglese anche se sono italiano semmai qualcuno poi volesse intervenire o leggere 👍
I tried to work on this during my spare time today. I can get the live event data (title, url and image) but something is failing during the playback. I get the HLS link (.m3u8) as for the live channels and other videos. Can be that is there a different endpoint for getting the auth token for such events?
11:31:08.606 T:123145405353984 ERROR: ffmpeg[700006245000]: [hls,applehttp] Error when loading first segment 'https://srgssrgop20b-lh.akamaihd.net/i/enc20b_gop@141648/segment254871311_250_av-p.ts?sd=6&rebase=on&id=AgDAeUQCAhXy0T7NKFt89EFi22AxHYyDYGG3+OfVBb2P+btttNfcci6CeJax55T575lcbURwApaBlg%3d%3d'
11:31:08.630 T:123145405353984 DEBUG: Error, could not open file https://srgssrgop20b-lh.akamaihd.net/i/enc20b_gop@141648/master.m3u8??hdnts=exp=1529400666~acl=/i/enc20b_gop@141648/*~hmac=a8036cb598633af3da5baf9af1978b10dcc906d978d3f2d1e7dc76ac050ee067
So apparently it is working now. Don't know why didn't work yesterday. I also had a Kodi crush today reopening the stream while it was already playing. More investigation is needed but I think for now it's enough for an alpha/beta release.
I have a suggestion, insert a section with the live streams for the sport events
https://www.rsi.ch/sport/streaming/
.The endpoint to get the JSON containing the URLs is not working with these streams (the ones with a URN from swisstxt), but decompiling the Android apk I found the endpoint for such streams:
https://event.api.swisstxt.ch/v1/events/rsi/byEventItemId?eids=<EVENT_ID>
e.g.https://event.api.swisstxt.ch/v1/events/rsi/byEventItemId?eids=288158
Edit: I just found another interesting endpoint:
https://event.api.swisstxt.ch/v1/events/rsi/live
. This can even avoid to get the eventID. Now there is just a live ongoing, on the 23rd of June I will check if with multiple events ongoing they are all listed in this endpoint.