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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212
  1. # -*- coding: utf-8 -*-
  2. from __future__ import division
  3. import os
  4. import sys
  5. import re
  6. import datetime
  7. import time
  8. import json
  9. from operator import itemgetter
  10. from ipwww_common import translation, AddMenuEntry, OpenURL, \
  11. CheckLogin, CreateBaseDirectory, GetCookieJar, \
  12. ParseImageUrl, download_subtitles
  13. import xbmc
  14. import xbmcgui
  15. import xbmcplugin
  16. import xbmcaddon
  17. ADDON = xbmcaddon.Addon(id='plugin.video.iplayerwww')
  18. def RedButtonDialog():
  19. dialog = xbmcgui.Dialog()
  20. ret = dialog.yesno(translation(30405), translation(30406), '',
  21. translation(30407), translation(30409), translation(30408))
  22. if ret:
  23. ListRedButton()
  24. def ListRedButton():
  25. channel_list = [
  26. ('sport_stream_01', 'BBC Red Button 1'),
  27. ('sport_stream_02', 'BBC Red Button 2'),
  28. ('sport_stream_03', 'BBC Red Button 3'),
  29. ('sport_stream_04', 'BBC Red Button 4'),
  30. ('sport_stream_05', 'BBC Red Button 5'),
  31. ('sport_stream_06', 'BBC Red Button 6'),
  32. ('sport_stream_07', 'BBC Red Button 7'),
  33. ('sport_stream_08', 'BBC Red Button 8'),
  34. ('sport_stream_09', 'BBC Red Button 9'),
  35. ('sport_stream_10', 'BBC Red Button 10'),
  36. ('sport_stream_11', 'BBC Red Button 11'),
  37. ('sport_stream_12', 'BBC Red Button 12'),
  38. ('sport_stream_13', 'BBC Red Button 13'),
  39. ('sport_stream_14', 'BBC Red Button 14'),
  40. ('sport_stream_15', 'BBC Red Button 15'),
  41. ('sport_stream_16', 'BBC Red Button 16'),
  42. ('sport_stream_17', 'BBC Red Button 17'),
  43. ('sport_stream_18', 'BBC Red Button 18'),
  44. ('sport_stream_19', 'BBC Red Button 19'),
  45. ('sport_stream_20', 'BBC Red Button 20'),
  46. ('sport_stream_21', 'BBC Red Button 21'),
  47. ('sport_stream_22', 'BBC Red Button 22'),
  48. ('sport_stream_23', 'BBC Red Button 23'),
  49. ('sport_stream_24', 'BBC Red Button 24'),
  50. ]
  51. for id, name in channel_list:
  52. if ADDON.getSetting('streams_autoplay') == 'true':
  53. AddMenuEntry(name, id, 204, '', '', '')
  54. else:
  55. AddMenuEntry(name, id, 129, '', '', '')
  56. # ListLive creates menu entries for all live channels.
  57. def ListLive():
  58. channel_list = [
  59. ('bbc_one_hd', 'bbc_one', 'BBC One'),
  60. ('bbc_two_hd', 'bbc_two', 'BBC Two'),
  61. ('bbc_four_hd', 'bbc_four', 'BBC Four'),
  62. ('cbbc_hd', 'cbbc', 'CBBC'),
  63. ('cbeebies_hd', 'cbeebies', 'CBeebies'),
  64. ('bbc_news24', 'bbc_news24', 'BBC News Channel'),
  65. ('bbc_parliament', 'bbc_parliament', 'BBC Parliament'),
  66. ('bbc_alba', 'bbc_alba', 'Alba'),
  67. ('s4cpbs', 's4c', 'S4C'),
  68. ('bbc_one_london', 'bbc_one', 'BBC One London'),
  69. ('bbc_one_scotland_hd', 'bbc_one_scotland', 'BBC One Scotland'),
  70. ('bbc_one_northern_ireland_hd', 'bbc_one_northern_ireland', 'BBC One Northern Ireland'),
  71. ('bbc_one_wales_hd', 'bbc_one_wales', 'BBC One Wales'),
  72. ('bbc_two_scotland', 'bbc_two', 'BBC Two Scotland'),
  73. ('bbc_two_northern_ireland_digital', 'bbc_two', 'BBC Two Northern Ireland'),
  74. ('bbc_two_wales_digital', 'bbc_two', 'BBC Two Wales'),
  75. ]
  76. for id, img, name in channel_list:
  77. iconimage = xbmc.translatePath(
  78. os.path.join('special://home/addons/plugin.video.iplayerwww/media', img + '.png'))
  79. if ADDON.getSetting('streams_autoplay') == 'true':
  80. AddMenuEntry(name, id, 203, iconimage, '', '')
  81. else:
  82. AddMenuEntry(name, id, 123, iconimage, '', '')
  83. def ListAtoZ():
  84. """List programmes based on alphabetical order.
  85. Only creates the corresponding directories for each character.
  86. """
  87. characters = [
  88. ('A', 'a'), ('B', 'b'), ('C', 'c'), ('D', 'd'), ('E', 'e'), ('F', 'f'),
  89. ('G', 'g'), ('H', 'h'), ('I', 'i'), ('J', 'j'), ('K', 'k'), ('L', 'l'),
  90. ('M', 'm'), ('N', 'n'), ('O', 'o'), ('P', 'p'), ('Q', 'q'), ('R', 'r'),
  91. ('S', 's'), ('T', 't'), ('U', 'u'), ('V', 'v'), ('W', 'w'), ('X', 'x'),
  92. ('Y', 'y'), ('Z', 'z'), ('0-9', '0-9')]
  93. if int(ADDON.getSetting('scrape_atoz')) == 1:
  94. pDialog = xbmcgui.DialogProgressBG()
  95. pDialog.create(translation(30319))
  96. page = 1
  97. total_pages = len(characters)
  98. for name, url in characters:
  99. GetAtoZPage(url)
  100. percent = int(100*page/total_pages)
  101. pDialog.update(percent,translation(30319),name)
  102. page += 1
  103. pDialog.close()
  104. else:
  105. for name, url in characters:
  106. AddMenuEntry(name, url, 124, '', '', '')
  107. def ListChannelAtoZ():
  108. """List programmes for each channel based on alphabetical order.
  109. Only creates the corresponding directories for each channel.
  110. """
  111. channel_list = [
  112. ('bbcone', 'bbc_one', 'BBC One'),
  113. ('bbctwo', 'bbc_two', 'BBC Two'),
  114. ('tv/bbcthree', 'bbc_three', 'BBC Three'),
  115. ('bbcfour', 'bbc_four', 'BBC Four'),
  116. ('tv/cbbc', 'cbbc', 'CBBC'),
  117. ('tv/cbeebies', 'cbeebies', 'CBeebies'),
  118. ('tv/bbcnews', 'bbc_news24', 'BBC News Channel'),
  119. ('tv/bbcparliament', 'bbc_parliament', 'BBC Parliament'),
  120. ('tv/bbcalba', 'bbc_alba', 'Alba'),
  121. ('tv/s4c', 's4c', 'S4C'),
  122. ]
  123. for id, img, name in channel_list:
  124. iconimage = xbmc.translatePath(
  125. os.path.join('special://home/addons/plugin.video.iplayerwww/media', img + '.png'))
  126. url = "http://www.bbc.co.uk/%s/a-z" % id
  127. AddMenuEntry(name, url, 128, iconimage, '', '')
  128. def GetAtoZPage(url):
  129. """Allows to list programmes based on alphabetical order.
  130. Creates the list of programmes for one character.
  131. """
  132. link = OpenURL('http://www.bbc.co.uk/iplayer/a-z/%s' % url)
  133. match = re.compile(
  134. '<a href="/iplayer/brand/(.+?)".+?<span class="title">(.+?)</span>',
  135. re.DOTALL).findall(link)
  136. for programme_id, name in match:
  137. AddMenuEntry(name, programme_id, 121, '', '', '')
  138. def ParseAired(aired):
  139. """Parses a string format %d %b %Y to %d/%n/%Y otherwise empty string."""
  140. if aired:
  141. try:
  142. # Need to use equivelent for datetime.strptime() due to weird TypeError.
  143. return datetime.datetime(*(time.strptime(aired[0], '%d %b %Y')[0:6])).strftime('%d/%m/%Y')
  144. except ValueError:
  145. pass
  146. return ''
  147. def FirstShownToAired(first_shown):
  148. """Converts the 'First shown' tag to %Y %m %d format."""
  149. release_parts = first_shown.split(' ')
  150. if len(release_parts) == 1:
  151. month = '01'
  152. day = '01'
  153. year = first_shown
  154. else:
  155. year = release_parts[-1]
  156. month = release_parts[-2]
  157. monthDict={
  158. 'Jan':'01', 'Feb':'02', 'Mar':'03', 'Apr':'04', 'May':'05', 'Jun':'06',
  159. 'Jul':'07', 'Aug':'08', 'Sep':'09', 'Oct':'10', 'Nov':'11', 'Dec':'12'}
  160. if month in monthDict:
  161. month = monthDict[month]
  162. day = release_parts[-3].rjust(2,'0')
  163. else:
  164. month = '01'
  165. day = '01'
  166. aired = year + '-' + month + '-' + day
  167. return aired
  168. def GetEpisodes(url):
  169. new_url = 'http://www.bbc.co.uk/iplayer/episodes/%s' % url
  170. ScrapeEpisodes(new_url)
  171. def GetGroup(url):
  172. new_url = "http://www.bbc.co.uk/iplayer/group/%s" % url
  173. ScrapeEpisodes(new_url)
  174. def ScrapeEpisodes(page_url):
  175. """Creates a list of programmes on one standard HTML page.
  176. ScrapeEpisodes contains a number of special treatments, which are only needed for
  177. specific pages, e.g. Search, but allows to use a single function for all kinds
  178. of pages.
  179. """
  180. pDialog = xbmcgui.DialogProgressBG()
  181. pDialog.create(translation(30319))
  182. html = OpenURL(page_url)
  183. total_pages = 1
  184. current_page = 1
  185. page_range = range(1)
  186. paginate = re.search(r'<div class="paginate.*?</div>', html, re.DOTALL)
  187. next_page = 1
  188. if paginate:
  189. if int(ADDON.getSetting('paginate_episodes')) == 0:
  190. current_page_match = re.search(r'page=(\d*)', page_url)
  191. if current_page_match:
  192. current_page = int(current_page_match.group(1))
  193. page_range = range(current_page, current_page+1)
  194. next_page_match = re.search(r'<span class="next txt">.+?href="(.*?page=)(.*?)"',
  195. paginate.group(0),
  196. re.DOTALL)
  197. if next_page_match:
  198. page_base_url = next_page_match.group(1)
  199. next_page = int(next_page_match.group(2))
  200. else:
  201. next_page = current_page
  202. page_range = range(current_page, current_page+1)
  203. else:
  204. pages = re.findall(r'<li class="page.*?</li>',paginate.group(0),re.DOTALL)
  205. if pages:
  206. last = pages[-1]
  207. last_page = re.search(r'<a href="(.*?page=)(.*?)"',last)
  208. page_base_url = last_page.group(1)
  209. total_pages = int(last_page.group(2))
  210. page_range = range(1, total_pages+1)
  211. for page in page_range:
  212. if page > current_page:
  213. page_url = 'http://www.bbc.co.uk' + page_base_url + str(page)
  214. html = OpenURL(page_url)
  215. # NOTE remove inner li to match outer li
  216. # <li data-version-type="hd">
  217. html = re.compile(r'<li data-version-type.*?</li>',
  218. flags=(re.DOTALL | re.MULTILINE)).sub('', html)
  219. # <li class="list-item programme" data-ip-id="p026f2t4">
  220. list_items = re.findall(r'<li class="list-item.*?</li>', html, flags=(re.DOTALL | re.MULTILINE))
  221. list_item_num = 1
  222. for li in list_items:
  223. # <li class="list-item unavailable" data-ip-id="b06sq9xj">
  224. unavailable_match = re.search(
  225. '<li class="list-item.*?unavailable.*?"',
  226. li, flags=(re.DOTALL | re.MULTILINE))
  227. if unavailable_match:
  228. continue
  229. # <li class="list-item search-group" data-ip-id="b06rdtx0">
  230. search_group = False
  231. search_group_match = re.search(
  232. '<li class="list-item.*?search-group.*?"',
  233. li, flags=(re.DOTALL | re.MULTILINE))
  234. if search_group_match:
  235. search_group = True
  236. main_url = None
  237. # <a href="/iplayer/episode/p026gmw9/world-of-difference-the-models"
  238. # title="World of Difference, The Models" class="list-item-link stat"
  239. url_match = re.search(
  240. r'<a.*?href="(.*?)".*?list-item-link.*?>',
  241. li, flags=(re.DOTALL | re.MULTILINE))
  242. if url_match:
  243. url = url_match.group(1)
  244. if url:
  245. main_url = 'http://www.bbc.co.uk' + url
  246. name = ''
  247. title = ''
  248. #<div class="title top-title">World of Difference</div>
  249. title_match = re.search(
  250. r'<div class="title top-title">\s*(.*?)\s*</div>',
  251. li, flags=(re.DOTALL | re.MULTILINE))
  252. if title_match:
  253. title = title_match.group(1)
  254. name = title
  255. subtitle = None
  256. #<div class="subtitle">The Models</div>
  257. subtitle_match = re.search(
  258. r'<div class="subtitle">\s*(.*?)\s*</div>',
  259. li, flags=(re.DOTALL | re.MULTILINE))
  260. if subtitle_match:
  261. subtitle = subtitle_match.group(1)
  262. if subtitle:
  263. name = name + " - " + subtitle
  264. icon = ''
  265. type = None
  266. # <div class="r-image" data-ip-type="episode"
  267. # data-ip-src="http://ichef.bbci.co.uk/images/ic/336x189/p026vl1q.jpg">
  268. # <div class="r-image" data-ip-type="group"
  269. # data-ip-src="http://ichef.bbci.co.uk/images/ic/336x189/p037ty9z.jpg">
  270. image_match = re.search(
  271. r'<div class="r-image".+?data-ip-type="(.*?)".+?data-ip-src="http://ichef.bbci.co.uk/images/ic/336x189/(.*?)\.jpg"',
  272. li, flags=(re.DOTALL | re.MULTILINE))
  273. if image_match:
  274. type = image_match.group(1)
  275. image = image_match.group(2)
  276. if image:
  277. icon = "http://ichef.bbci.co.uk/images/ic/832x468/" + image + ".jpg"
  278. synopsis = ''
  279. # <p class="synopsis">What was it like to be a top fashion model 30 years ago? (1978)</p>
  280. synopsis_match = re.search(
  281. r'<p class="synopsis">\s*(.*?)\s*</p>',
  282. li, flags=(re.DOTALL | re.MULTILINE))
  283. if synopsis_match:
  284. synopsis = synopsis_match.group(1)
  285. aired = ''
  286. # <span class="release">\nFirst shown: 8 Jun 1967\n</span>
  287. release_match = re.search(
  288. r'<span class="release">.*?First shown:\s*(.*?)\n.*?</span>',
  289. li, flags=(re.DOTALL | re.MULTILINE))
  290. if release_match:
  291. release = release_match.group(1)
  292. if release:
  293. aired = FirstShownToAired(release)
  294. episodes = None
  295. # <a class="view-more-container avail stat" href="/iplayer/episodes/p00db1jf" data-progress-state="">
  296. # <a class="view-more-container sibling stat"
  297. # href="/iplayer/search?q=doctor&amp;search_group_id=urn:bbc:programmes:b06qbs4n">
  298. episodes_match = re.search(
  299. r'<a class="view-more-container.+?stat".+?href="(.*?)"',
  300. li, flags=(re.DOTALL | re.MULTILINE))
  301. if episodes_match:
  302. episodes = episodes_match.group(1)
  303. more = None
  304. # <em class="view-more-heading">27</em>
  305. more_match = re.search(
  306. r'<em class="view-more-heading">(.*?)</em>',
  307. li, flags=(re.DOTALL | re.MULTILINE))
  308. if more_match:
  309. more = more_match.group(1)
  310. if episodes:
  311. episodes_url = 'http://www.bbc.co.uk' + episodes
  312. if search_group:
  313. AddMenuEntry('[B]%s[/B] - %s' % (title, translation(30318)),
  314. episodes_url, 128, icon, '', '')
  315. else:
  316. AddMenuEntry('[B]%s[/B] - %s %s' % (title, more, translation(30313)),
  317. episodes_url, 128, icon, '', '')
  318. elif more:
  319. AddMenuEntry('[B]%s[/B] - %s %s' % (title, more, translation(30313)),
  320. main_url, 128, icon, '', '')
  321. if type != "group":
  322. CheckAutoplay(name , main_url, icon, synopsis, aired)
  323. percent = int(100*(page+list_item_num/len(list_items))/total_pages)
  324. pDialog.update(percent,translation(30319),name)
  325. list_item_num += 1
  326. percent = int(100*page/total_pages)
  327. pDialog.update(percent,translation(30319))
  328. if int(ADDON.getSetting('paginate_episodes')) == 0:
  329. if current_page < next_page:
  330. page_url = 'http://www.bbc.co.uk' + page_base_url + str(next_page)
  331. AddMenuEntry(" [COLOR ffffa500]%s >>[/COLOR]" % translation(30320), page_url, 128, '', '', '')
  332. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
  333. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
  334. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
  335. pDialog.close()
  336. def ListCategories():
  337. """Parses the available categories and creates directories for selecting one of them.
  338. The category names are scraped from the website.
  339. """
  340. html = OpenURL('http://www.bbc.co.uk/iplayer')
  341. match = re.compile(
  342. '<a href="/iplayer/categories/(.+?)" class="stat">(.+?)</a>'
  343. ).findall(html)
  344. for url, name in match:
  345. AddMenuEntry(name, url, 125, '', '', '')
  346. def ListCategoryFilters(url):
  347. """Parses the available category filters (if available) and creates directories for selcting them.
  348. If there are no filters available, all programmes will be listed using GetFilteredCategory.
  349. """
  350. NEW_URL = 'http://www.bbc.co.uk/iplayer/categories/%s/all?sort=atoz' % url
  351. # Read selected category's page.
  352. html = OpenURL(NEW_URL)
  353. # Some categories offer filters, we want to provide these filters as options.
  354. match1 = re.findall(
  355. '<li class="filter"> <a class="name" href="/iplayer/categories/(.+?)"> (.+?)</a>',
  356. html,
  357. re.DOTALL)
  358. if match1:
  359. AddMenuEntry('All', url, 126, '', '', '')
  360. for url, name in match1:
  361. AddMenuEntry(name, url, 126, '', '', '')
  362. else:
  363. GetFilteredCategory(url)
  364. def GetFilteredCategory(url):
  365. """Parses the programmes available in the category view."""
  366. NEW_URL = 'http://www.bbc.co.uk/iplayer/categories/%s/all?sort=atoz' % url
  367. ScrapeEpisodes(NEW_URL)
  368. def ListChannelHighlights():
  369. """Creates a list directories linked to the highlights section of each channel."""
  370. channel_list = [
  371. ('bbcone', 'bbc_one', 'BBC One'),
  372. ('bbctwo', 'bbc_two', 'BBC Two'),
  373. ('tv/bbcthree', 'bbc_three', 'BBC Three'),
  374. ('bbcfour', 'bbc_four', 'BBC Four'),
  375. ('tv/cbbc', 'cbbc', 'CBBC'),
  376. ('tv/cbeebies', 'cbeebies', 'CBeebies'),
  377. ('tv/bbcnews', 'bbc_news24', 'BBC News Channel'),
  378. ('tv/bbcparliament', 'bbc_parliament', 'BBC Parliament'),
  379. ('tv/bbcalba', 'bbc_alba', 'Alba'),
  380. ('tv/s4c', 's4c', 'S4C'),
  381. ]
  382. for id, img, name in channel_list:
  383. iconimage = xbmc.translatePath(
  384. os.path.join('special://home/addons/plugin.video.iplayerwww/media', img + '.png'))
  385. AddMenuEntry(name, id, 106, iconimage, '', '')
  386. def ListHighlights(highlights_url):
  387. """Creates a list of the programmes in the highlights section.
  388. """
  389. html = OpenURL('http://www.bbc.co.uk/%s' % highlights_url)
  390. inner_anchors = re.findall(r'<a.*?(?!<a).*?</a>',html,flags=(re.DOTALL | re.MULTILINE))
  391. # First find all groups as we need to store some properties of groups for later reuse.
  392. group_properties = []
  393. # NOTE find episode count first
  394. episode_count = dict()
  395. groups = [a for a in inner_anchors if re.match(
  396. r'<a[^<]*?class="grouped-items__cta.*?data-object-type="group-list-link".*?',
  397. a, flags=(re.DOTALL | re.MULTILINE))]
  398. for group in groups:
  399. href = ''
  400. href_match = re.match(
  401. r'<a[^<]*?href="(.*?)"',
  402. group, flags=(re.DOTALL | re.MULTILINE))
  403. if href_match:
  404. href = href_match.group(1)
  405. count_match = re.search(
  406. r'>View all ([0-9]*).*?</a>',
  407. group, flags=(re.DOTALL | re.MULTILINE))
  408. if count_match:
  409. count = count_match.group(1)
  410. episode_count[href] = count
  411. groups = [a for a in inner_anchors if re.match(
  412. r'<a[^<]*?class="grouped-items__title.*?data-object-type="group-list-link".*?',
  413. a, flags=(re.DOTALL | re.MULTILINE))]
  414. for group in groups:
  415. href = ''
  416. href_match = re.match(
  417. r'<a[^<]*?href="(.*?)"',
  418. group, flags=(re.DOTALL | re.MULTILINE))
  419. if href_match:
  420. href = href_match.group(1)
  421. name = ''
  422. name_match = re.search(
  423. r'<strong>(.*?)</strong>',
  424. group, flags=(re.DOTALL | re.MULTILINE))
  425. if name_match:
  426. name = name_match.group(1)
  427. count = ''
  428. if href in episode_count:
  429. count = episode_count[href]
  430. url = 'http://www.bbc.co.uk' + href
  431. # Unfortunately, the group type is not inside the links, so we need to search the whole HTML.
  432. group_type = ''
  433. group_type_match = re.search(
  434. r'data-group-name="'+name+'".+?data-group-type="(.+?)"',
  435. html, flags=(re.DOTALL | re.MULTILINE))
  436. if group_type_match:
  437. group_type = group_type_match.group(1)
  438. position = ''
  439. position_match = re.search(
  440. r'data-object-position="(.+?)-ALL"',
  441. group, flags=(re.DOTALL | re.MULTILINE))
  442. if position_match:
  443. group_properties.append(
  444. [position_match.group(1),
  445. name, group_type])
  446. AddMenuEntry('[B]%s: %s[/B] - %s %s' % (translation(30314), name, count, translation(30315)),
  447. url, 128, '', '', '')
  448. # Some programmes show up twice in HTML, once inside the groups, once outside.
  449. # We need to parse both to avoid duplicates and to make sure we get all of them.
  450. episodelist = []
  451. # <a\n href="/iplayer/episode/b06tr74y/eastenders-24122015"\n class="grouped-items__list-link
  452. listeds = [a for a in inner_anchors if re.search(
  453. r'class="grouped-items__list-link',
  454. a, flags=(re.DOTALL | re.MULTILINE))]
  455. for listed in listeds:
  456. episode_id = ''
  457. # <a\n href="/iplayer/episode/b06tr74y/eastenders-24122015"
  458. id_match = re.match(
  459. r'<a.*?href="/iplayer/episode/(.*?)/',
  460. listed, flags=(re.DOTALL | re.MULTILINE))
  461. if id_match:
  462. episode_id = id_match.group(1)
  463. name = ''
  464. # <p class="grouped-items__title grouped-items__title--item typo typo--skylark">
  465. # <strong>EastEnders</strong></p>
  466. title_match = re.search(
  467. r'<.*?class="grouped-items__title.*?<strong>(.*?)</strong>',
  468. listed, flags=(re.DOTALL | re.MULTILINE))
  469. if title_match:
  470. name = title_match.group(1)
  471. name = re.compile(r'<.*?>', flags=(re.DOTALL | re.MULTILINE)).sub('', name)
  472. # <p class="grouped-items__subtitle typo typo--canary">24/12/2015</p>
  473. subtitle_match = re.search(
  474. r'<.*?class="grouped-items__subtitle.*?>(.*?)<',
  475. listed, flags=(re.DOTALL | re.MULTILINE))
  476. if subtitle_match:
  477. name = name + ' - ' + subtitle_match.group(1)
  478. # Assign correct group based on the position of the episode
  479. position = ''
  480. position_match = re.search(
  481. r'data-object-position="(.+?)"',
  482. listed, flags=(re.DOTALL | re.MULTILINE))
  483. if position_match:
  484. for n,i in enumerate(group_properties):
  485. if re.match(i[0], position_match.group(1), flags=(re.DOTALL | re.MULTILINE)):
  486. position = i[1]
  487. # For series-catchup groups, we need to modify the title.
  488. if i[2] == 'series-catchup':
  489. name = i[1]+': '+name
  490. episodelist.append(
  491. [episode_id,
  492. name,
  493. "%s %s" % (translation(30316), position),
  494. 'DefaultVideo.png',
  495. '']
  496. )
  497. # < a\nhref="/iplayer/episode/p036gq3z/bbc-music-introducing-from-buddhist-monk-to-rock-star"\n
  498. # class="single-item stat"
  499. singles = [a for a in inner_anchors if re.search(
  500. r'class="single-item',
  501. a, flags=(re.DOTALL | re.MULTILINE))]
  502. for single in singles:
  503. object_type = ''
  504. # data-object-type="episode-backfill"
  505. data_object_type = re.search(
  506. r'data-object-type="(.*?)"',
  507. single, flags=(re.DOTALL | re.MULTILINE))
  508. if data_object_type:
  509. object_type = data_object_type.group(1)
  510. if object_type == "episode-backfill":
  511. if (highlights_url not in ['tv/bbcnews', 'tv/bbcparliament', 'tv/s4c']):
  512. continue
  513. episode_id = ''
  514. url = ''
  515. # <a\nhref="/iplayer/episode/p036gq3z/bbc-music-introducing-from-buddhist-monk-to-rock-star"
  516. if object_type == "editorial-promo":
  517. id_match = re.match(
  518. r'<a.*?href="(.*?)"',
  519. single, flags=(re.DOTALL | re.MULTILINE))
  520. else:
  521. id_match = re.match(
  522. r'<a.*?href="/iplayer/episode/(.*?)/',
  523. single, flags=(re.DOTALL | re.MULTILINE))
  524. if id_match:
  525. episode_id = id_match.group(1)
  526. url = 'http://www.bbc.co.uk/iplayer/episode/' + episode_id
  527. name = ''
  528. # <h3 class="single-item__title typo typo--skylark"><strong>BBC Music Introducing</strong></h3>
  529. title_match = re.search(
  530. r'<.*?class="single-item__title.*?<strong>(.*?)</strong>',
  531. single, flags=(re.DOTALL | re.MULTILINE))
  532. if title_match:
  533. name = title_match.group(1)
  534. name = re.compile(r'<.*?>', flags=(re.DOTALL | re.MULTILINE)).sub('', name)
  535. # <p class="single-item__subtitle typo typo--canary">From Buddhist Monk to Rock Star</p>
  536. subtitle_match = re.search(
  537. r'<.*?class="single-item__subtitle.*?>(.*?)<',
  538. single, flags=(re.DOTALL | re.MULTILINE))
  539. if subtitle_match:
  540. name = name + ' - ' + subtitle_match.group(1)
  541. icon = ''
  542. # <div class="r-image" data-ip-type="episode"
  543. # data-ip-src="http://ichef.bbci.co.uk/images/ic/406x228/p036gtc5.jpg">
  544. image_match = re.search(
  545. r'<.*?class="r-image.*?data-ip-src="(.*?)"',
  546. single, flags=(re.DOTALL | re.MULTILINE))
  547. if image_match:
  548. icon = image_match.group(1)
  549. desc = ''
  550. # <p class="single-item__overlay__desc">
  551. # The remarkable rise of Ngawang Lodup - from BBC Introducing to performing at the O2 Arena</p>
  552. desc_match = re.search(
  553. r'<.*?class="single-item__overlay__desc.*?>(.*?)<',
  554. single, flags=(re.DOTALL | re.MULTILINE))
  555. if desc_match:
  556. desc = desc_match.group(1)
  557. aired = ''
  558. # <p class="single-item__overlay__subtitle">First shown: 4 Nov 2015</p>
  559. release_match = re.search(
  560. r'<.*?class="single-item__overlay__subtitle">First shown: (.*?)<',
  561. single, flags=(re.DOTALL | re.MULTILINE))
  562. if release_match:
  563. release = release_match.group(1)
  564. if release:
  565. aired = FirstShownToAired(release)
  566. add_entry = True
  567. for n,i in enumerate(episodelist):
  568. if i[0]==episode_id:
  569. episodelist[n][2]=desc
  570. episodelist[n][3]=icon
  571. episodelist[n][4]=aired
  572. add_entry = False
  573. if add_entry:
  574. if object_type == "editorial-promo":
  575. if episode_id:
  576. AddMenuEntry('[B]%s[/B]' % (name), episode_id, 128, icon, '', '')
  577. else:
  578. if url:
  579. CheckAutoplay(name, url, icon, desc, aired)
  580. # Finally add all programmes which have been identified as part of a group before.
  581. for episode in episodelist:
  582. episode_url = "http://www.bbc.co.uk/iplayer/episode/%s" % episode[0]
  583. if ((ADDON.getSetting('suppress_incomplete') == 'false') or (not episode[4] == '')):
  584. if episode[0]:
  585. CheckAutoplay(episode[1], episode_url, episode[3], episode[2], episode[4])
  586. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
  587. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
  588. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
  589. def ListMostPopular():
  590. """Scrapes all episodes of the most popular page."""
  591. ScrapeEpisodes("http://www.bbc.co.uk/iplayer/group/most-popular")
  592. def AddAvailableStreamItem(name, url, iconimage, description):
  593. """Play a streamm based on settings for preferred catchup source and bitrate."""
  594. stream_ids = ScrapeAvailableStreams(url)
  595. if stream_ids['stream_id_ad']:
  596. streams_all = ParseStreams(stream_ids['stream_id_ad'])
  597. elif stream_ids['stream_id_sl']:
  598. streams_all = ParseStreams(stream_ids['stream_id_sl'])
  599. else:
  600. streams_all = ParseStreams(stream_ids['stream_id_st'])
  601. if streams_all[1]:
  602. # print "Setting subtitles URL"
  603. subtitles_url = streams_all[1][0]
  604. # print subtitles_url
  605. else:
  606. subtitles_url = ''
  607. streams = streams_all[0]
  608. source = int(ADDON.getSetting('catchup_source'))
  609. bitrate = int(ADDON.getSetting('catchup_bitrate'))
  610. # print "Selected source is %s"%source
  611. # print "Selected bitrate is %s"%bitrate
  612. # print streams
  613. if source > 0:
  614. if bitrate > 0:
  615. # Case 1: Selected source and selected bitrate
  616. match = [x for x in streams if ((x[0] == source) and (x[1] == bitrate))]
  617. if len(match) == 0:
  618. # Fallback: Use same bitrate but different supplier.
  619. match = [x for x in streams if (x[1] == bitrate)]
  620. if len(match) == 0:
  621. # Second Fallback: Use any lower bitrate from selected source.
  622. match = [x for x in streams if (x[0] == source) and (x[1] in range(1, bitrate))]
  623. match.sort(key=lambda x: x[1], reverse=True)
  624. if len(match) == 0:
  625. # Third Fallback: Use any lower bitrate from any source.
  626. match = [x for x in streams if (x[1] in range(1, bitrate))]
  627. match.sort(key=lambda x: x[1], reverse=True)
  628. else:
  629. # Case 2: Selected source and any bitrate
  630. match = [x for x in streams if (x[0] == source)]
  631. if len(match) == 0:
  632. # Fallback: Use any source and any bitrate
  633. match = streams
  634. match.sort(key=lambda x: x[1], reverse=True)
  635. else:
  636. if bitrate > 0:
  637. # Case 3: Any source and selected bitrate
  638. match = [x for x in streams if (x[1] == bitrate)]
  639. if len(match) == 0:
  640. # Fallback: Use any source and any lower bitrate
  641. match = streams
  642. match = [x for x in streams if (x[1] in range(1, bitrate))]
  643. match.sort(key=lambda x: x[1], reverse=True)
  644. else:
  645. # Case 4: Any source and any bitrate
  646. # Play highest available bitrate
  647. match = streams
  648. match.sort(key=lambda x: x[1], reverse=True)
  649. PlayStream(name, match[0][2], iconimage, description, subtitles_url)
  650. def GetAvailableStreams(name, url, iconimage, description):
  651. """Calls AddAvailableStreamsDirectory based on user settings"""
  652. #print url
  653. stream_ids = ScrapeAvailableStreams(url)
  654. AddAvailableStreamsDirectory(name, stream_ids['stream_id_st'], iconimage, description)
  655. # If we searched for Audio Described programmes and they have been found, append them to the list.
  656. if stream_ids['stream_id_ad']:
  657. AddAvailableStreamsDirectory(name + ' - (Audio Described)', stream_ids['stream_id_ad'], iconimage, description)
  658. # If we search for Signed programmes and they have been found, append them to the list.
  659. if stream_ids['stream_id_sl']:
  660. AddAvailableStreamsDirectory(name + ' - (Signed)', stream_ids['stream_id_sl'], iconimage, description)
  661. def Search(search_entered):
  662. """Simply calls the online search function. The search is then evaluated in EvaluateSearch."""
  663. if search_entered is None:
  664. keyboard = xbmc.Keyboard('', 'Search iPlayer')
  665. keyboard.doModal()
  666. if keyboard.isConfirmed():
  667. search_entered = keyboard.getText() .replace(' ', '%20') # sometimes you need to replace spaces with + or %20
  668. if search_entered is None:
  669. return False
  670. NEW_URL = 'http://www.bbc.co.uk/iplayer/search?q=%s' % search_entered
  671. ScrapeEpisodes(NEW_URL)
  672. def AddAvailableLiveStreamItem(name, channelname, iconimage):
  673. """Play a live stream based on settings for preferred live source and bitrate."""
  674. stream_bitrates = [9999, 0.1, 0.2, 0.3, 0.6, 1.0, 1.8, 3.1, 5.5]
  675. if int(ADDON.getSetting('live_source')) == 1:
  676. providers = [('ak', 'Akamai')]
  677. elif int(ADDON.getSetting('live_source')) == 2:
  678. providers = [('llnw', 'Limelight')]
  679. else:
  680. providers = [('ak', 'Akamai'), ('llnw', 'Limelight')]
  681. bitrate_selected = int(ADDON.getSetting('live_bitrate'))
  682. if bitrate_selected > len(stream_bitrates) - 1:
  683. bitrate_selected = 0
  684. ADDON.setSetting('live_bitrate', str(bitrate_selected))
  685. streams_available = ParseLiveStreams(channelname, providers)
  686. # print streams_available
  687. # Play the prefered option
  688. if bitrate_selected > 0:
  689. match = [x for x in streams_available if (x[1] == stream_bitrates[bitrate_selected])]
  690. if len(match) == 0:
  691. # Fallback: Use any bitrate lower than the selected from any source.
  692. match = [x for x in streams_available if (x[1] <= stream_bitrates[bitrate_selected] )]
  693. match.sort(key=lambda x: x[1], reverse=True)
  694. if len(match) == 0:
  695. # Fallback: Selected bitrate is too low. Use lowest available bitrate.
  696. match = sorted(streams_available, key=lambda x: x[1], reverse=False)
  697. # print "Selected bitrate is %s"%stream_bitrates[bitrate_selected]
  698. # print match
  699. # print "Playing %s from %s with bitrate %s"%(name, match[0][4], match [0][1])
  700. if len(match) > 0: #TODO error message
  701. PlayStream(name, match[0][4], iconimage, '', '')
  702. # Play the fastest available stream of the preferred provider
  703. else:
  704. PlayStream(name, streams_available[0][4], iconimage, '', '')
  705. def AddAvailableRedButtonItem(name, channelname):
  706. """Play a live stream based on settings for preferred live source and bitrate."""
  707. stream_bitrates = [9999, 0.1, 0.2, 0.3, 0.6, 1.0, 1.8, 3.1, 5.5]
  708. if int(ADDON.getSetting('live_source')) == 1:
  709. providers = [('ak', 'Akamai')]
  710. elif int(ADDON.getSetting('live_source')) == 2:
  711. providers = [('llnw', 'Limelight')]
  712. else:
  713. providers = [('ak', 'Akamai'), ('llnw', 'Limelight')]
  714. bitrate_selected = int(ADDON.getSetting('live_bitrate'))
  715. if bitrate_selected > len(stream_bitrates) - 1:
  716. bitrate_selected = 0
  717. ADDON.setSetting('live_bitrate', str(bitrate_selected))
  718. streams_available = ParseRedButtonStreams(channelname, providers)
  719. # Play the prefered option
  720. if bitrate_selected > 0:
  721. match = [x for x in streams_available if (x[1] == stream_bitrates[bitrate_selected])]
  722. if len(match) == 0:
  723. # Fallback: Use any bitrate lower than the selected from any source.
  724. match = [x for x in streams_available if (x[1] <= stream_bitrates[bitrate_selected] )]
  725. match.sort(key=lambda x: x[1], reverse=True)
  726. if len(match) == 0:
  727. # Fallback: Selected bitrate is too low. Use lowest available bitrate.
  728. match = sorted(streams_available, key=lambda x: x[1], reverse=False)
  729. # print "Selected bitrate is %s"%stream_bitrates[bitrate_selected]
  730. # print match
  731. # print "Playing %s from %s with bitrate %s"%(name, match[0][4], match [0][1])
  732. if len(match) > 0: #TODO error message
  733. PlayStream(name, match[0][0], '', '', '')
  734. # Play the fastest available stream of the preferred provider
  735. else:
  736. PlayStream(name, streams_available[0][0], '', '', '')
  737. def AddAvailableLiveStreamsDirectory(name, channelname, iconimage):
  738. """Retrieves the available live streams for a channel
  739. Args:
  740. name: only used for displaying the channel.
  741. iconimage: only used for displaying the channel.
  742. channelname: determines which channel is queried.
  743. """
  744. streams = ParseLiveStreams(channelname, '')
  745. # Add each stream to the Kodi selection menu.
  746. for id, bitrate, codecs, resolution, url, provider_name in streams:
  747. # For easier selection use colors to indicate high and low bitrate streams
  748. if bitrate > 2.1:
  749. color = 'ff008000'
  750. elif bitrate > 1.0:
  751. color = 'ffffff00'
  752. elif bitrate > 0.6:
  753. color = 'ffffa500'
  754. else:
  755. color = 'ffff0000'
  756. title = name + ' - [I][COLOR %s]%0.1f Mbps[/COLOR] [COLOR fff1f1f1]%s[/COLOR][/I]' % (
  757. color, bitrate, provider_name)
  758. # Finally add them to the selection menu.
  759. AddMenuEntry(title, url, 201, iconimage, '', '')
  760. def AddAvailableRedButtonDirectory(name, channelname):
  761. """Retrieves the available live streams for a channel
  762. Args:
  763. name: only used for displaying the channel.
  764. iconimage: only used for displaying the channel.
  765. channelname: determines which channel is queried.
  766. """
  767. streams = ParseRedButtonStreams(channelname, '')
  768. # Add each stream to the Kodi selection menu.
  769. for url, bitrate, provider_name in streams:
  770. # For easier selection use colors to indicate high and low bitrate streams
  771. if bitrate > 2.1:
  772. color = 'ff008000'
  773. elif bitrate > 1.0:
  774. color = 'ffffff00'
  775. elif bitrate > 0.6:
  776. color = 'ffffa500'
  777. else:
  778. color = 'ffff0000'
  779. title = name + ' - [I][COLOR %s]%0.1f Mbps[/COLOR] [COLOR fff1f1f1]%s[/COLOR][/I]' % (
  780. color, bitrate, provider_name)
  781. # Finally add them to the selection menu.
  782. AddMenuEntry(title, url, 201, '', '', '')
  783. def ListWatching(logged_in):
  784. if(CheckLogin(logged_in) == False):
  785. CreateBaseDirectory('video')
  786. return
  787. identity_cookie = None
  788. cookie_jar = None
  789. cookie_jar = GetCookieJar()
  790. for cookie in cookie_jar:
  791. if (cookie.name == 'IDENTITY'):
  792. identity_cookie = cookie.value
  793. break
  794. url = "https://ibl.api.bbci.co.uk/ibl/v1/user/watching?identity_cookie=%s" % identity_cookie
  795. html = OpenURL(url)
  796. json_data = json.loads(html)
  797. watching_list = json_data.get('watching').get('elements')
  798. for watching in watching_list:
  799. programme = watching.get('programme')
  800. episode = watching.get('episode')
  801. title = episode.get('title')
  802. subtitle = episode.get('subtitle')
  803. if(subtitle):
  804. title += ", " + subtitle
  805. episode_id = episode.get('id')
  806. plot = episode.get('synopses').get('large') or " "
  807. aired = episode.get('release_date')
  808. image_url = ParseImageUrl(episode.get('images').get('standard'))
  809. aired = ParseAired(aired)
  810. url="http://www.bbc.co.uk/iplayer/episode/%s" % (episode_id)
  811. CheckAutoplay(title, url, image_url, plot, aired)
  812. def ListFavourites(logged_in):
  813. if(CheckLogin(logged_in) == False):
  814. CreateBaseDirectory('video')
  815. return
  816. """Scrapes all episodes of the favourites page."""
  817. html = OpenURL('http://www.bbc.co.uk/iplayer/usercomponents/favourites/programmes.json')
  818. json_data = json.loads(html)
  819. # favourites = json_data.get('favourites')
  820. programmes = json_data.get('programmes')
  821. for programme in programmes:
  822. id = programme.get('id')
  823. url = "http://www.bbc.co.uk/iplayer/brand/%s" % (id)
  824. title = programme.get('title')
  825. initial_child = programme.get('initial_children')[0]
  826. subtitle = initial_child.get('subtitle')
  827. episode_title = title
  828. if subtitle:
  829. episode_title = title + ' - ' + subtitle
  830. image=initial_child.get('images')
  831. image_url=ParseImageUrl(image.get('standard'))
  832. synopses = initial_child.get('synopses')
  833. plot = synopses.get('small')
  834. try:
  835. aired = FirstShownToAired(initial_child.get('release_date'))
  836. except:
  837. aired = ''
  838. CheckAutoplay(episode_title, url, image_url, plot, aired)
  839. more = programme.get('count')
  840. if more:
  841. episodes_url = "http://www.bbc.co.uk/iplayer/episodes/" + id
  842. AddMenuEntry('[B]%s[/B] - %s %s' % (title, more, translation(30313)),
  843. episodes_url, 128, image_url, '', '')
  844. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
  845. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
  846. def PlayStream(name, url, iconimage, description, subtitles_url):
  847. if iconimage == '':
  848. iconimage = 'DefaultVideo.png'
  849. html = OpenURL(url)
  850. check_geo = re.search(
  851. '<H1>Access Denied</H1>', html)
  852. if check_geo or not html:
  853. # print "Geoblock detected, raising error message"
  854. dialog = xbmcgui.Dialog()
  855. dialog.ok(translation(30400), translation(30401))
  856. raise
  857. liz = xbmcgui.ListItem(name, iconImage='DefaultVideo.png', thumbnailImage=iconimage)
  858. liz.setInfo(type='Video', infoLabels={'Title': name})
  859. liz.setProperty("IsPlayable", "true")
  860. liz.setPath(url)
  861. if subtitles_url and ADDON.getSetting('subtitles') == 'true':
  862. subtitles_file = download_subtitles(subtitles_url)
  863. xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz)
  864. if subtitles_url and ADDON.getSetting('subtitles') == 'true':
  865. # Successfully started playing something?
  866. while True:
  867. if xbmc.Player().isPlaying():
  868. break
  869. else:
  870. xbmc.sleep(500)
  871. xbmc.Player().setSubtitles(subtitles_file)
  872. def AddAvailableStreamsDirectory(name, stream_id, iconimage, description):
  873. """Will create one menu entry for each available stream of a particular stream_id"""
  874. # print "Stream ID: %s"%stream_id
  875. streams = ParseStreams(stream_id)
  876. # print streams
  877. if streams[1]:
  878. # print "Setting subtitles URL"
  879. subtitles_url = streams[1][0]
  880. # print subtitles_url
  881. else:
  882. subtitles_url = ''
  883. suppliers = ['', 'Akamai', 'Limelight', 'Level3']
  884. bitrates = [0, 800, 1012, 1500, 1800, 2400, 3116, 5510]
  885. for supplier, bitrate, url, resolution in sorted(streams[0], key=itemgetter(1), reverse=True):
  886. if bitrate in (5, 7):
  887. color = 'ff008000'
  888. elif bitrate == 6:
  889. color = 'ff0084ff'
  890. elif bitrate in (3, 4):
  891. color = 'ffffff00'
  892. else:
  893. color = 'ffffa500'
  894. title = name + ' - [I][COLOR %s]%0.1f Mbps[/COLOR] [COLOR ffd3d3d3]%s[/COLOR][/I]' % (
  895. color, bitrates[bitrate] / 1000, suppliers[supplier])
  896. AddMenuEntry(title, url, 201, iconimage, description, subtitles_url, resolution=resolution)
  897. def ParseStreams(stream_id):
  898. retlist = []
  899. # print "Parsing streams for PID: %s"%stream_id[0]
  900. # Open the page with the actual strem information and display the various available streams.
  901. NEW_URL = "http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/%s" % stream_id[0]
  902. html = OpenURL(NEW_URL)
  903. # Parse the different streams and add them as new directory entries.
  904. match = re.compile(
  905. 'connection authExpires=".+?href="(.+?)".+?supplier="mf_(.+?)".+?transferFormat="(.+?)"'
  906. ).findall(html)
  907. for m3u8_url, supplier, transfer_format in match:
  908. tmp_sup = 0
  909. tmp_br = 0
  910. if transfer_format == 'hls':
  911. if supplier == 'akamai_uk_hls':
  912. tmp_sup = 1
  913. elif supplier == 'limelight_uk_hls':
  914. tmp_sup = 2
  915. m3u8_breakdown = re.compile('(.+?)iptv.+?m3u8(.+?)$').findall(m3u8_url)
  916. #print m3u8_breakdown
  917. # print m3u8_url
  918. m3u8_html = OpenURL(m3u8_url)
  919. m3u8_match = re.compile('BANDWIDTH=(.+?),.+?RESOLUTION=(.+?)\n(.+?)\n').findall(m3u8_html)
  920. for bandwidth, resolution, stream in m3u8_match:
  921. # print bandwidth
  922. # print resolution
  923. #print stream
  924. url = "%s%s%s" % (m3u8_breakdown[0][0], stream, m3u8_breakdown[0][1])
  925. #print url
  926. if int(bandwidth) == 1012300:
  927. tmp_br = 2
  928. elif int(bandwidth) == 1799880:
  929. tmp_br = 4
  930. elif int(bandwidth) == 3116400:
  931. tmp_br = 6
  932. elif int(bandwidth) == 5509880:
  933. tmp_br = 7
  934. retlist.append((tmp_sup, tmp_br, url, resolution))
  935. # It may be useful to parse these additional streams as a default as they offer additional bandwidths.
  936. match = re.compile(
  937. 'kind="video".+?connection href="(.+?)".+?supplier="(.+?)".+?transferFormat="(.+?)"'
  938. ).findall(html)
  939. # print match
  940. unique = []
  941. [unique.append(item) for item in match if item not in unique]
  942. # print unique
  943. for m3u8_url, supplier, transfer_format in unique:
  944. tmp_sup = 0
  945. tmp_br = 0
  946. if transfer_format == 'hls':
  947. if supplier == 'akamai_hls_open':
  948. tmp_sup = 1
  949. elif supplier == 'limelight_hls_open':
  950. tmp_sup = 2
  951. m3u8_breakdown = re.compile('.+?master.m3u8(.+?)$').findall(m3u8_url)
  952. # print m3u8_url
  953. # print m3u8_breakdown
  954. m3u8_html = OpenURL(m3u8_url)
  955. # print m3u8_html
  956. m3u8_match = re.compile('BANDWIDTH=(.+?),RESOLUTION=(.+?),.+?\n(.+?)\n').findall(m3u8_html)
  957. # print m3u8_match
  958. for bandwidth, resolution, stream in m3u8_match:
  959. # print bandwidth
  960. # print resolution
  961. # print stream
  962. url = "%s%s" % (stream, m3u8_breakdown[0][0])
  963. # This is not entirely correct, displayed bandwidth may be higher or lower than actual bandwidth.
  964. if int(bandwidth) <= 801000:
  965. tmp_br = 1
  966. elif int(bandwidth) <= 1510000:
  967. tmp_br = 3
  968. elif int(bandwidth) <= 2410000:
  969. tmp_br = 5
  970. retlist.append((tmp_sup, tmp_br, url, resolution))
  971. match = re.compile('service="captions".+?connection href="(.+?)"').findall(html)
  972. # print "Subtitle URL: %s"%match
  973. # print retlist
  974. if not match:
  975. # print "No streams found"
  976. check_geo = re.search(
  977. '<error id="geolocation"/>', html)
  978. if check_geo:
  979. # print "Geoblock detected, raising error message"
  980. dialog = xbmcgui.Dialog()
  981. dialog.ok(translation(30400), translation(30401))
  982. raise
  983. return retlist, match
  984. def ParseLiveStreams(channelname, providers):
  985. if providers == '':
  986. providers = [('ak', 'Akamai'), ('llnw', 'Limelight')]
  987. streams = []
  988. for provider_url, provider_name in providers:
  989. # First we query the available streams from this website
  990. if channelname in ['bbc_parliament', 'bbc_alba', 's4cpbs', 'bbc_one_london',
  991. 'bbc_two_wales_digital', 'bbc_two_northern_ireland_digital', 'bbc_two_scotland']:
  992. device = 'hls_tablet'
  993. else:
  994. device = 'abr_hdtv'
  995. url = 'http://a.files.bbci.co.uk/media/live/manifesto/audio_video/simulcast/hls/uk/%s/%s/%s.m3u8' \
  996. % (device, provider_url, channelname)
  997. html = OpenURL(url)
  998. match = re.compile('#EXT-X-STREAM-INF:PROGRAM-ID=(.+?),BANDWIDTH=(.+?),CODECS="(.*?)",RESOLUTION=(.+?)\s*(.+?.m3u8)').findall(html)
  999. # Add provider name to the stream list.
  1000. streams.extend([list(stream) + [provider_name] for stream in match])
  1001. # Convert bitrate to Mbps for further processing
  1002. for i in range(len(streams)):
  1003. streams[i][1] = round(int(streams[i][1])/1000000.0, 1)
  1004. # Return list sorted by bitrate
  1005. return sorted(streams, key=lambda x: (x[1]), reverse=True)
  1006. def ParseRedButtonStreams(channelname, providers):
  1007. if providers == '':
  1008. providers = [('ak', 'Akamai'), ('llnw', 'Limelight')]
  1009. streams = []
  1010. for provider_url, provider_name in providers:
  1011. url = "http://a.files.bbci.co.uk/media/live/manifesto/audio_video/webcast/hds/uk/pc/%s/%s.f4m" % (provider_url, channelname)
  1012. html = OpenURL(url)
  1013. match = re.compile('<media href="(.+?)" bitrate="(.+?)"/>').findall(html)
  1014. streams.extend([list(stream) + [provider_name] for stream in match])
  1015. # Convert bitrate to Mbps for further processing
  1016. for i in range(len(streams)):
  1017. streams[i][1] = round(int(streams[i][1])/1000.0, 1)
  1018. streams[i][0] = re.sub('.f4m$', '.m3u8', streams[i][0])
  1019. # Return list sorted by bitrate
  1020. return sorted(streams, key=lambda x: (x[1]), reverse=True)
  1021. def ScrapeAvailableStreams(url):
  1022. # Open page and retrieve the stream ID
  1023. html = OpenURL(url)
  1024. # Search for standard programmes.
  1025. stream_id_st = re.compile('"vpid":"(.+?)"').findall(html)
  1026. # Optionally, Signed programmes can be searched for. These have a different ID.
  1027. if ADDON.getSetting('search_signed') == 'true':
  1028. stream_id_sl = re.compile('data-download-sl="bbc-ipd:download/.+?/(.+?)/sd/').findall(html)
  1029. else:
  1030. stream_id_sl = []
  1031. # Optionally, Audio Described programmes can be searched for. These have a different ID.
  1032. if ADDON.getSetting('search_ad') == 'true':
  1033. url_ad = re.compile('<a href="(.+?)" class="version link watch-ad-on"').findall(html)
  1034. url_tmp = "http://www.bbc.co.uk%s" % url_ad[0]
  1035. html = OpenURL(url_tmp)
  1036. stream_id_ad = re.compile('"vpid":"(.+?)"').findall(html)
  1037. # print stream_id_ad
  1038. else:
  1039. stream_id_ad = []
  1040. return {'stream_id_st': stream_id_st, 'stream_id_sl': stream_id_sl, 'stream_id_ad': stream_id_ad}
  1041. def CheckAutoplay(name, url, iconimage, plot, aired=None):
  1042. if ADDON.getSetting('streams_autoplay') == 'true':
  1043. AddMenuEntry(name, url, 202, iconimage, plot, '', aired=aired)
  1044. else:
  1045. AddMenuEntry(name, url, 122, iconimage, plot, '', aired=aired)