Enigma2 plugin to to play various online streams (mostly Latvian).

ipwww_common.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. # -*- coding: utf-8 -*-
  2. import sys
  3. import os
  4. import re
  5. import requests
  6. from requests.packages import urllib3
  7. #Below is required to get around an ssl issue
  8. urllib3.disable_warnings()
  9. import cookielib
  10. import urllib
  11. import HTMLParser
  12. import codecs
  13. import xbmc
  14. import xbmcaddon
  15. import xbmcgui
  16. import xbmcplugin
  17. ADDON = xbmcaddon.Addon(id='plugin.video.iplayerwww')
  18. def GetAddonInfo():
  19. addon_info = {}
  20. addon_info["id"] = __addonid__
  21. addon_info["addon"] = xbmcaddon.Addon(__addonid__)
  22. addon_info["language"] = addon_info["addon"].getLocalizedString
  23. addon_info["version"] = addon_info["addon"].getAddonInfo("version")
  24. addon_info["path"] = addon_info["addon"].getAddonInfo("path")
  25. addon_info["profile"] = xbmc.translatePath(addon_info["addon"].getAddonInfo('profile'))
  26. return addon_info
  27. __addonid__ = "plugin.video.iplayerwww"
  28. __addoninfo__ = GetAddonInfo()
  29. DIR_USERDATA = xbmc.translatePath(__addoninfo__["profile"])
  30. cookie_jar = None
  31. if(not os.path.exists(DIR_USERDATA)):
  32. os.makedirs(DIR_USERDATA)
  33. def translation(id):
  34. return xbmcaddon.Addon(__addonid__).getLocalizedString(id)
  35. re_subtitles = re.compile('^\s*<p.*?begin=\"(.*?)(\.([0-9]+))?\"\s+.*?end=\"(.*?)(\.([0-9]+))?\"\s*>(.*?)</p>')
  36. def ParseImageUrl(url):
  37. return url.replace("{recipe}", "832x468")
  38. def download_subtitles(url):
  39. # Download and Convert the TTAF format to srt
  40. # SRT:
  41. # 1
  42. # 00:01:22,490 --> 00:01:26,494
  43. # Next round!
  44. #
  45. # 2
  46. # 00:01:33,710 --> 00:01:37,714
  47. # Now that we've moved to paradise, there's nothing to eat.
  48. #
  49. # TT:
  50. # <p begin="0:01:12.400" end="0:01:13.880">Thinking.</p>
  51. outfile = os.path.join(DIR_USERDATA, 'iplayer.srt')
  52. # print "Downloading subtitles from %s to %s"%(url, outfile)
  53. fw = codecs.open(outfile, 'w', encoding='utf-8')
  54. if not url:
  55. fw.write("1\n0:00:00,001 --> 0:01:00,001\nNo subtitles available\n\n")
  56. fw.close()
  57. return
  58. txt = OpenURL(url)
  59. # print txt
  60. i = 0
  61. prev = None
  62. # some of the subtitles are a bit rubbish in particular for live tv
  63. # with lots of needless repeats. The follow code will collapse sequences
  64. # of repeated subtitles into a single subtitles that covers the total time
  65. # period. The downside of this is that it would mess up in the rare case
  66. # where a subtitle actually needs to be repeated
  67. for line in txt.split('\n'):
  68. entry = None
  69. m = re_subtitles.match(line)
  70. # print line
  71. # print m
  72. if m:
  73. if(m.group(3)):
  74. start_mil = "%s000" % m.group(3) # pad out to ensure 3 digits
  75. else:
  76. start_mil = "000"
  77. if(m.group(6)):
  78. end_mil = "%s000" % m.group(6)
  79. else:
  80. end_mil = "000"
  81. ma = {'start': m.group(1),
  82. 'start_mil': start_mil[:3],
  83. 'end': m.group(4),
  84. 'end_mil': end_mil[:3],
  85. 'text': m.group(7)}
  86. # ma['text'] = ma['text'].replace('&amp;', '&')
  87. # ma['text'] = ma['text'].replace('&gt;', '>')
  88. # ma['text'] = ma['text'].replace('&lt;', '<')
  89. ma['text'] = ma['text'].replace('<br />', '\n')
  90. ma['text'] = ma['text'].replace('<br/>', '\n')
  91. ma['text'] = re.sub('<.*?>', '', ma['text'])
  92. ma['text'] = re.sub('&#[0-9]+;', '', ma['text'])
  93. # ma['text'] = ma['text'].replace('<.*?>', '')
  94. # print ma
  95. if not prev:
  96. # first match - do nothing wait till next line
  97. prev = ma
  98. continue
  99. if prev['text'] == ma['text']:
  100. # current line = previous line then start a sequence to be collapsed
  101. prev['end'] = ma['end']
  102. prev['end_mil'] = ma['end_mil']
  103. else:
  104. i += 1
  105. entry = "%d\n%s,%s --> %s,%s\n%s\n\n" % (
  106. i, prev['start'], prev['start_mil'], prev['end'], prev['end_mil'], prev['text'])
  107. prev = ma
  108. elif prev:
  109. i += 1
  110. entry = "%d\n%s,%s --> %s,%s\n%s\n\n" % (
  111. i, prev['start'], prev['start_mil'], prev['end'], prev['end_mil'], prev['text'])
  112. if entry:
  113. fw.write(entry)
  114. fw.close()
  115. return outfile
  116. def InitialiseCookieJar():
  117. cookie_file = os.path.join(DIR_USERDATA,'iplayer.cookies')
  118. cj = cookielib.LWPCookieJar(cookie_file)
  119. if(os.path.exists(cookie_file)):
  120. try:
  121. cj.load(ignore_discard=True)
  122. except:
  123. xbmcgui.Dialog().notification(translation(30400), translation(30402), xbmcgui.NOTIFICATION_ERROR)
  124. return cj
  125. cookie_jar = InitialiseCookieJar()
  126. def SignInBBCiD():
  127. sign_in_url="https://ssl.bbc.co.uk/id/signin"
  128. username=ADDON.getSetting('bbc_id_username')
  129. password=ADDON.getSetting('bbc_id_password')
  130. post_data={
  131. 'unique': username,
  132. 'password': password,
  133. 'rememberme':'0'}
  134. r = OpenURLPost(sign_in_url, post_data)
  135. if (r.status_code == 302):
  136. xbmcgui.Dialog().notification(translation(30308), translation(30309))
  137. else:
  138. xbmcgui.Dialog().notification(translation(30308), translation(30310))
  139. def SignOutBBCiD():
  140. sign_out_url="https://ssl.bbc.co.uk/id/signout"
  141. OpenURL(sign_out_url)
  142. cookie_jar.clear_session_cookies()
  143. if (StatusBBCiD()):
  144. xbmcgui.Dialog().notification(translation(30326), translation(30310))
  145. else:
  146. xbmcgui.Dialog().notification(translation(30326), translation(30309))
  147. def StatusBBCiD():
  148. status_url="https://ssl.bbc.co.uk/id/status"
  149. html=OpenURL(status_url)
  150. if("You are signed in" in html):
  151. return True
  152. return False
  153. def CheckLogin(logged_in):
  154. if(logged_in == True or StatusBBCiD() == True):
  155. logged_in = True
  156. return True
  157. elif ADDON.getSetting('bbc_id_enabled') != 'true':
  158. xbmcgui.Dialog().ok(translation(30308), translation(30311))
  159. else:
  160. attemptLogin = xbmcgui.Dialog().yesno(translation(30308), translation(30312))
  161. if attemptLogin:
  162. SignInBBCiD()
  163. if(StatusBBCiD()):
  164. xbmcgui.Dialog().notification(translation(30308), translation(30309))
  165. logged_in = True;
  166. return True;
  167. else:
  168. xbmcgui.Dialog().notification(translation(30308), translation(30310))
  169. return False
  170. def OpenURL(url):
  171. headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:38.0) Gecko/20100101 Firefox/43.0'}
  172. try:
  173. r = requests.get(url, headers=headers, cookies=cookie_jar)
  174. except requests.exceptions.RequestException as e:
  175. dialog = xbmcgui.Dialog()
  176. dialog.ok(translation(30400), "%s" % e)
  177. sys.exit(1)
  178. try:
  179. for cookie in r.cookies:
  180. cookie_jar.set_cookie(cookie)
  181. #Set ignore_discard to overcome issue of not having session
  182. #as cookie_jar is reinitialised for each action.
  183. cookie_jar.save(ignore_discard=True)
  184. except:
  185. pass
  186. return HTMLParser.HTMLParser().unescape(r.content.decode('utf-8'))
  187. def OpenURLPost(url, post_data):
  188. headers = {
  189. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:38.0) Gecko/20100101 Firefox/43.0',
  190. 'Host':'ssl.bbc.co.uk',
  191. 'Accept':'*/*',
  192. 'Referer':'https://ssl.bbc.co.uk/id/signin',
  193. 'Content-Type':'application/x-www-form-urlencoded'}
  194. try:
  195. r = requests.post(url, headers=headers, data=post_data, allow_redirects=False, cookies=cookie_jar)
  196. except requests.exceptions.RequestException as e:
  197. dialog = xbmcgui.Dialog()
  198. dialog.ok(translation(30400), "%s" % e)
  199. sys.exit(1)
  200. try:
  201. for cookie in r.cookies:
  202. cookie_jar.set_cookie(cookie)
  203. #Set ignore_discard to overcome issue of not having session
  204. #as cookie_jar is reinitialised for each action.
  205. cookie_jar.save(ignore_discard=True)
  206. except:
  207. pass
  208. return r
  209. def GetCookieJar():
  210. return cookie_jar
  211. # Creates a 'urlencoded' string from a unicode input
  212. def utf8_quote_plus(unicode):
  213. return urllib.quote_plus(unicode.encode('utf-8'))
  214. # Gets a unicode string from a 'urlencoded' string
  215. def utf8_unquote_plus(str):
  216. return urllib.unquote_plus(str).decode('utf-8')
  217. def AddMenuEntry(name, url, mode, iconimage, description, subtitles_url, aired=None, resolution=None, logged_in=False):
  218. """Adds a new line to the Kodi list of playables.
  219. It is used in multiple ways in the plugin, which are distinguished by modes.
  220. """
  221. if not iconimage:
  222. iconimage="DefaultFolder.png"
  223. listitem_url = (sys.argv[0] + "?url=" + utf8_quote_plus(url) + "&mode=" + str(mode) +
  224. "&name=" + utf8_quote_plus(name) +
  225. "&iconimage=" + utf8_quote_plus(iconimage) +
  226. "&description=" + utf8_quote_plus(description) +
  227. "&subtitles_url=" + utf8_quote_plus(subtitles_url) +
  228. "&logged_in=" + str(logged_in))
  229. if aired:
  230. ymd = aired.split('-')
  231. date_string = ymd[2] + '/' + ymd[1] + '/' + ymd[0]
  232. else:
  233. date_string = ""
  234. # Modes 201-299 will create a new playable line, otherwise create a new directory line.
  235. if mode in (201, 202, 203, 204, 211, 212, 213, 214):
  236. isFolder = False
  237. # Mode 119 is not a folder, but it is also not a playable.
  238. elif mode == 119:
  239. isFolder = False
  240. else:
  241. isFolder = True
  242. listitem = xbmcgui.ListItem(label=name, label2=description,
  243. iconImage="DefaultFolder.png", thumbnailImage=iconimage)
  244. if aired:
  245. listitem.setInfo("video", {
  246. "title": name,
  247. "plot": description,
  248. "plotoutline": description,
  249. "date": date_string,
  250. "aired": aired})
  251. else:
  252. listitem.setInfo("video", {
  253. "title": name,
  254. "plot": description,
  255. "plotoutline": description})
  256. video_streaminfo = {'codec': 'h264'}
  257. if not isFolder:
  258. if resolution:
  259. resolution = resolution.split('x')
  260. video_streaminfo['aspect'] = round(int(resolution[0]) / int(resolution[1]), 2)
  261. video_streaminfo['width'] = resolution[0]
  262. video_streaminfo['height'] = resolution[1]
  263. listitem.addStreamInfo('video', video_streaminfo)
  264. listitem.addStreamInfo('audio', {'codec': 'aac', 'language': 'en', 'channels': 2})
  265. if subtitles_url:
  266. listitem.addStreamInfo('subtitle', {'language': 'en'})
  267. # Mode 119 is not a folder, but it is also not a playable.
  268. if mode == 119:
  269. listitem.setProperty("IsPlayable", 'false')
  270. else:
  271. listitem.setProperty("IsPlayable", str(not isFolder).lower())
  272. listitem.setProperty("IsFolder", str(isFolder).lower())
  273. listitem.setProperty("Property(Addon.Name)", "iPlayer WWW")
  274. xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
  275. url=listitem_url, listitem=listitem, isFolder=isFolder)
  276. xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
  277. return True
  278. def CreateBaseDirectory(content_type):
  279. if content_type == "video":
  280. AddMenuEntry(translation(30300), 'iplayer', 106, '', '', '')
  281. AddMenuEntry(translation(30317), 'url', 109, '', '', '')
  282. AddMenuEntry(translation(30301), 'url', 105, '', '', '')
  283. AddMenuEntry(translation(30302), 'url', 102, '', '', '')
  284. AddMenuEntry(translation(30327), 'url', 120, '', '', '')
  285. AddMenuEntry(translation(30303), 'url', 103, '', '', '')
  286. AddMenuEntry(translation(30304), 'url', 104, '', '', '')
  287. AddMenuEntry(translation(30305), 'url', 101, '', '', '')
  288. AddMenuEntry(translation(30328), 'url', 118, '', '', '')
  289. AddMenuEntry(translation(30306), 'url', 107, '', '', '')
  290. AddMenuEntry(translation(30307), 'url', 108, '', '', '')
  291. AddMenuEntry(translation(30325), 'url', 119, '', '', '')
  292. elif content_type == "audio":
  293. AddMenuEntry(translation(30321), 'url', 113, '', '', '')
  294. AddMenuEntry(translation(30302), 'url', 112, '', '', '')
  295. AddMenuEntry(translation(30303), 'url', 114, '', '', '')
  296. AddMenuEntry(translation(30304), 'url', 115, '', '', '')
  297. AddMenuEntry(translation(30301), 'url', 116, '', '', '')
  298. AddMenuEntry(translation(30307), 'url', 117, '', '', '')
  299. AddMenuEntry(translation(30325), 'url', 119, '', '', '')
  300. else:
  301. AddMenuEntry((translation(30323)+translation(30300)),
  302. 'iplayer', 106, '', '', '')
  303. AddMenuEntry((translation(30323)+translation(30317)),
  304. 'url', 109, '', '', '')
  305. AddMenuEntry((translation(30323)+translation(30301)),
  306. 'url', 105, '', '', '')
  307. AddMenuEntry((translation(30323)+translation(30302)),
  308. 'url', 102, '', '', '')
  309. AddMenuEntry((translation(30323)+translation(30327)),
  310. 'url', 120, '', '', '')
  311. AddMenuEntry((translation(30323)+translation(30303)),
  312. 'url', 103, '', '', '')
  313. AddMenuEntry((translation(30323)+translation(30304)),
  314. 'url', 104, '', '', '')
  315. AddMenuEntry((translation(30323)+translation(30305)),
  316. 'url', 101, '', '', '')
  317. AddMenuEntry((translation(30323)+translation(30328)),
  318. 'url', 118, '', '', '')
  319. AddMenuEntry((translation(30323)+translation(30306)),
  320. 'url', 107, '', '', '')
  321. AddMenuEntry((translation(30323)+translation(30307)),
  322. 'url', 108, '', '', '')
  323. AddMenuEntry((translation(30324)+translation(30321)),
  324. 'url', 113, '', '', '')
  325. AddMenuEntry((translation(30324)+translation(30302)),
  326. 'url', 112, '', '', '')
  327. AddMenuEntry((translation(30324)+translation(30303)),
  328. 'url', 114, '', '', '')
  329. AddMenuEntry((translation(30324)+translation(30304)),
  330. 'url', 115, '', '', '')
  331. AddMenuEntry((translation(30324)+translation(30301)),
  332. 'url', 116, '', '', '')
  333. AddMenuEntry((translation(30324)+translation(30307)),
  334. 'url', 117, '', '', '')
  335. AddMenuEntry(translation(30325), 'url', 119, '', '', '')