# -*- coding: utf-8 -*- import sys import os import re import requests from requests.packages import urllib3 #Below is required to get around an ssl issue urllib3.disable_warnings() import cookielib import urllib import HTMLParser import codecs import time import xbmc import xbmcaddon import xbmcgui import xbmcplugin ADDON = xbmcaddon.Addon(id='plugin.video.iplayerwww') def GetAddonInfo(): addon_info = {} addon_info["id"] = addonid addon_info["addon"] = xbmcaddon.Addon(addonid) addon_info["language"] = addon_info["addon"].getLocalizedString addon_info["version"] = addon_info["addon"].getAddonInfo("version") addon_info["path"] = addon_info["addon"].getAddonInfo("path") addon_info["profile"] = xbmc.translatePath(addon_info["addon"].getAddonInfo('profile')) return addon_info addonid = "plugin.video.iplayerwww" addoninfo = GetAddonInfo() DIR_USERDATA = xbmc.translatePath(addoninfo["profile"]) cookie_jar = None user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0' headers = {'User-Agent': user_agent} if(not os.path.exists(DIR_USERDATA)): os.makedirs(DIR_USERDATA) def translation(id): return xbmcaddon.Addon(addonid).getLocalizedString(id) re_subtitles = re.compile('^\s*(.*?)

') def ParseImageUrl(url): return url.replace("{recipe}", "832x468") def download_subtitles(url): # Download and Convert the TTAF format to srt # SRT: # 1 # 00:01:22,490 --> 00:01:26,494 # Next round! # # 2 # 00:01:33,710 --> 00:01:37,714 # Now that we've moved to paradise, there's nothing to eat. # # TT: #

Thinking.

outfile = os.path.join(DIR_USERDATA, 'iplayer.srt') # print "Downloading subtitles from %s to %s"%(url, outfile) fw = codecs.open(outfile, 'w', encoding='utf-8') if not url: fw.write("1\n0:00:00,001 --> 0:01:00,001\nNo subtitles available\n\n") fw.close() return txt = OpenURL(url) # print txt i = 0 prev = None # some of the subtitles are a bit rubbish in particular for live tv # with lots of needless repeats. The follow code will collapse sequences # of repeated subtitles into a single subtitles that covers the total time # period. The downside of this is that it would mess up in the rare case # where a subtitle actually needs to be repeated for line in txt.split('\n'): entry = None m = re_subtitles.match(line) # print line # print m if m: if(m.group(3)): start_mil = "%s000" % m.group(3) # pad out to ensure 3 digits else: start_mil = "000" if(m.group(6)): end_mil = "%s000" % m.group(6) else: end_mil = "000" ma = {'start': m.group(1), 'start_mil': start_mil[:3], 'end': m.group(4), 'end_mil': end_mil[:3], 'text': m.group(7)} # ma['text'] = ma['text'].replace('&', '&') # ma['text'] = ma['text'].replace('>', '>') # ma['text'] = ma['text'].replace('<', '<') ma['text'] = ma['text'].replace('
', '\n') ma['text'] = ma['text'].replace('
', '\n') ma['text'] = re.sub('<.*?>', '', ma['text']) ma['text'] = re.sub('&#[0-9]+;', '', ma['text']) # ma['text'] = ma['text'].replace('<.*?>', '') # print ma if not prev: # first match - do nothing wait till next line prev = ma continue if prev['text'] == ma['text']: # current line = previous line then start a sequence to be collapsed prev['end'] = ma['end'] prev['end_mil'] = ma['end_mil'] else: i += 1 entry = "%d\n%s,%s --> %s,%s\n%s\n\n" % ( i, prev['start'], prev['start_mil'], prev['end'], prev['end_mil'], prev['text']) prev = ma elif prev: i += 1 entry = "%d\n%s,%s --> %s,%s\n%s\n\n" % ( i, prev['start'], prev['start_mil'], prev['end'], prev['end_mil'], prev['text']) if entry: fw.write(entry) fw.close() return outfile def InitialiseCookieJar(): cookie_file = os.path.join(DIR_USERDATA,'iplayer.cookies') cj = cookielib.LWPCookieJar(cookie_file) if(os.path.exists(cookie_file)): try: cj.load(ignore_discard=True) except: xbmcgui.Dialog().notification(translation(30400), translation(30402), xbmcgui.NOTIFICATION_ERROR) return cj cookie_jar = InitialiseCookieJar() def SignInBBCiD(): sign_in_url="https://account.bbc.com/signin" username=ADDON.getSetting('bbc_id_username') password=ADDON.getSetting('bbc_id_password') post_data={ 'username': username, 'password': password, 'attempts':'0'} #Regular expression to get 'nonce' from login page p = re.compile('action="([^""]*)"') with requests.Session() as s: resp = s.get('https://www.bbc.com/', headers=headers) # Call the login page to get a 'nonce' for actual login signInUrl = 'https://session.bbc.com/session' resp = s.get(signInUrl, headers=headers) m = p.search(resp.text) url = m.group(1) url = "https://account.bbc.com%s" % url resp = s.post(url, data=post_data, headers=headers) for cookie in s.cookies: cookie_jar.set_cookie(cookie) cookie_jar.save(ignore_discard=True) with requests.Session() as s: resp = s.get('https://www.bbc.co.uk/iplayer', headers=headers) # Call the login page to get a 'nonce' for actual login signInUrl = 'https://www.bbc.co.uk/session' resp = s.get(signInUrl, headers=headers) m = p.search(resp.text) url = m.group(1) url = "https://account.bbc.com%s" % url resp = s.post(url, data=post_data, headers=headers) for cookie in s.cookies: cookie_jar.set_cookie(cookie) cookie_jar.save(ignore_discard=True) #if (r.status_code == 302): # xbmcgui.Dialog().notification(translation(30308), translation(30309)) #else: # xbmcgui.Dialog().notification(translation(30308), translation(30310)) def SignOutBBCiD(): sign_out_url="https://account.bbc.com/signout" OpenURL(sign_out_url) cookie_jar.clear() cookie_jar.save() if (StatusBBCiD()): xbmcgui.Dialog().notification(translation(30326), translation(30310)) else: xbmcgui.Dialog().notification(translation(30326), translation(30309)) def StatusBBCiD(): r = requests.head("https://account.bbc.com/account", cookies=cookie_jar, headers=headers, allow_redirects=False) if r.status_code == 200: return True else: return False def CheckLogin(logged_in): if(logged_in == True or StatusBBCiD() == True): logged_in = True return True elif ADDON.getSetting('bbc_id_enabled') != 'true': xbmcgui.Dialog().ok(translation(30308), translation(30311)) else: if ADDON.getSetting('bbc_id_autologin') == 'true': attemptLogin = True else: attemptLogin = xbmcgui.Dialog().yesno(translation(30308), translation(30312)) if attemptLogin: SignInBBCiD() if(StatusBBCiD()): if ADDON.getSetting('bbc_id_autologin') == 'false': xbmcgui.Dialog().notification(translation(30308), translation(30309)) logged_in = True; return True; else: xbmcgui.Dialog().notification(translation(30308), translation(30310)) return False def OpenURL(url): try: r = requests.get(url, headers=headers, cookies=cookie_jar) except requests.exceptions.RequestException as e: dialog = xbmcgui.Dialog() dialog.ok(translation(30400), "%s" % e) sys.exit(1) try: for cookie in r.cookies: cookie_jar.set_cookie(cookie) #Set ignore_discard to overcome issue of not having session #as cookie_jar is reinitialised for each action. cookie_jar.save(ignore_discard=True) except: pass return HTMLParser.HTMLParser().unescape(r.content.decode('utf-8')) def OpenURLPost(url, post_data): headers_ssl = { 'User-Agent': user_agent, 'Host':'ssl.bbc.co.uk', 'Accept':'*/*', 'Referer':'https://ssl.bbc.co.uk/id/signin', 'Content-Type':'application/x-www-form-urlencoded'} try: r = requests.post(url, headers=headers_ssl, data=post_data, allow_redirects=False, cookies=cookie_jar) except requests.exceptions.RequestException as e: dialog = xbmcgui.Dialog() dialog.ok(translation(30400), "%s" % e) sys.exit(1) try: for cookie in r.cookies: cookie_jar.set_cookie(cookie) #Set ignore_discard to overcome issue of not having session #as cookie_jar is reinitialised for each action. cookie_jar.save(ignore_discard=True) except: pass return r def GetCookieJar(): return cookie_jar # Creates a 'urlencoded' string from a unicode input def utf8_quote_plus(unicode): return urllib.quote_plus(unicode.encode('utf-8')) # Gets a unicode string from a 'urlencoded' string def utf8_unquote_plus(str): return urllib.unquote_plus(str).decode('utf-8') def AddMenuEntry(name, url, mode, iconimage, description, subtitles_url, aired=None, resolution=None, logged_in=False): """Adds a new line to the Kodi list of playables. It is used in multiple ways in the plugin, which are distinguished by modes. """ if not iconimage: iconimage="DefaultFolder.png" listitem_url = (sys.argv[0] + "?url=" + utf8_quote_plus(url) + "&mode=" + str(mode) + "&name=" + utf8_quote_plus(name) + "&iconimage=" + utf8_quote_plus(iconimage) + "&description=" + utf8_quote_plus(description) + "&subtitles_url=" + utf8_quote_plus(subtitles_url) + "&logged_in=" + str(logged_in)) if mode in (101,203,113,213): listitem_url = listitem_url + "&time=" + str(time.time()) if aired: ymd = aired.split('-') date_string = ymd[2] + '/' + ymd[1] + '/' + ymd[0] else: date_string = "" # Modes 201-299 will create a new playable line, otherwise create a new directory line. if mode in (201, 202, 203, 204, 211, 212, 213, 214): isFolder = False # Mode 119 is not a folder, but it is also not a playable. elif mode == 119: isFolder = False else: isFolder = True listitem = xbmcgui.ListItem(label=name, label2=description, iconImage="DefaultFolder.png", thumbnailImage=iconimage) if aired: listitem.setInfo("video", { "title": name, "plot": description, "plotoutline": description, "date": date_string, "aired": aired}) else: listitem.setInfo("video", { "title": name, "plot": description, "plotoutline": description}) video_streaminfo = {'codec': 'h264'} if not isFolder: if int(ADDON.getSetting('stream_protocol')) == 0: listitem.setPath(url) listitem.setProperty('inputstreamaddon', 'inputstream.adaptive') listitem.setProperty('inputstream.adaptive.manifest_type', 'mpd') else: if resolution: resolution = resolution.split('x') video_streaminfo['aspect'] = round(int(resolution[0]) / int(resolution[1]), 2) video_streaminfo['width'] = resolution[0] video_streaminfo['height'] = resolution[1] listitem.addStreamInfo('video', video_streaminfo) listitem.addStreamInfo('audio', {'codec': 'aac', 'language': 'en', 'channels': 2}) if subtitles_url: listitem.addStreamInfo('subtitle', {'language': 'en'}) # Mode 119 is not a folder, but it is also not a playable. if mode == 119: listitem.setProperty("IsPlayable", 'false') else: listitem.setProperty("IsPlayable", str(not isFolder).lower()) listitem.setProperty("IsFolder", str(isFolder).lower()) listitem.setProperty("Property(Addon.Name)", "iPlayer WWW") xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=listitem_url, listitem=listitem, isFolder=isFolder) xbmcplugin.setContent(int(sys.argv[1]), 'episodes') return True def KidsMode(): dialog = xbmcgui.Dialog() old_password = '' try: old_password = ADDON.getSetting('kids_password') old_password = old_password.decode('base64', 'strict') except: pass password = '' if old_password: password = dialog.input(translation(30181), type=xbmcgui.INPUT_ALPHANUM) if old_password == password: new_password = dialog.input(translation(30182), type=xbmcgui.INPUT_ALPHANUM) ADDON.setSetting('kids_password',new_password.encode('base64','strict')) quit() def ShowLicenceWarning(): if not (ADDON.getSetting("licence_warning_shown") == 'true'): dialog = xbmcgui.Dialog() ok = dialog.ok(translation(30405), translation(30410)) if ok: ADDON.setSetting("licence_warning_shown", 'true') def CreateBaseDirectory(content_type): if ADDON.getSetting('kids_password'): if ADDON.getSetting('streams_autoplay') == 'true': live_mode = 203 else: live_mode = 123 AddMenuEntry(translation(30329), 'cbeebies_hd', live_mode, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/cbeebies_hd.png' ), '', '') AddMenuEntry(translation(30330), 'cbbc_hd', live_mode, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/cbbc_hd.png' ), '', '') AddMenuEntry(translation(30331), 'cbeebies', 125, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/cbeebies_hd.png' ), '', '') AddMenuEntry(translation(30332), 'cbbc', 125, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/cbbc_hd.png' ), '', '') AddMenuEntry(translation(30333), 'p02pnn9d', 131, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/cbeebies_hd.png' ), '', '') return if content_type == "video": ShowLicenceWarning() if ADDON.getSetting("menu_video_highlights") == 'true': AddMenuEntry(translation(30300), 'iplayer', 198, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/top_rated.png' ), '', '') if ADDON.getSetting("menu_video_channel_highlights") == 'true': AddMenuEntry(translation(30317), 'url', 109, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/top_rated.png' ), '', '') if ADDON.getSetting("menu_video_most_popular") == 'true': AddMenuEntry(translation(30301), 'url', 105, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/popular.png' ), '', '') if ADDON.getSetting("menu_video_az") == 'true': AddMenuEntry(translation(30302), 'url', 102, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_video_channel_az") == 'true': AddMenuEntry(translation(30327), 'url', 120, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_video_categories") == 'true': AddMenuEntry(translation(30303), 'url', 103, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_video_search") == 'true': AddMenuEntry(translation(30304), 'url', 104, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/search.png' ), '', '') if ADDON.getSetting("menu_video_live") == 'true': AddMenuEntry(translation(30305), 'url', 101, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/tv.png' ), '', '') if ADDON.getSetting("menu_video_red_button") == 'true': AddMenuEntry(translation(30328), 'url', 118, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/tv.png' ), '', '') if ADDON.getSetting("menu_video_watching") == 'true': AddMenuEntry(translation(30306), 'url', 107, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') if ADDON.getSetting("menu_video_added") == 'true': AddMenuEntry(translation(30307), 'url', 108, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') AddMenuEntry(translation(30325), 'url', 119, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/settings.png' ), '', '') elif content_type == "audio": if ADDON.getSetting("menu_radio_live") == 'true': AddMenuEntry(translation(30321), 'url', 113, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/live.png' ), '', '') if ADDON.getSetting("menu_radio_az") == 'true': AddMenuEntry(translation(30302), 'url', 112, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_radio_categories") == 'true': AddMenuEntry(translation(30303), 'url', 114, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_radio_search") == 'true': AddMenuEntry(translation(30304), 'url', 115, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/search.png' ), '', '') if ADDON.getSetting("menu_radio_most_popular") == 'true': AddMenuEntry(translation(30301), 'url', 116, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/popular.png' ), '', '') if ADDON.getSetting("menu_radio_added") == 'true': AddMenuEntry(translation(30307), 'url', 117, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') if ADDON.getSetting("menu_radio_following") == 'true': AddMenuEntry(translation(30334), 'url', 199, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') AddMenuEntry(translation(30325), 'url', 119, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/settings.png' ), '', '') else: ShowLicenceWarning() if ADDON.getSetting("menu_video_highlights") == 'true': AddMenuEntry((translation(30323)+translation(30300)), 'iplayer', 198, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/top_rated.png' ), '', '') if ADDON.getSetting("menu_video_channel_highlights") == 'true': AddMenuEntry((translation(30323)+translation(30317)), 'url', 109, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/top_rated.png' ), '', '') if ADDON.getSetting("menu_video_most_popular") == 'true': AddMenuEntry((translation(30323)+translation(30301)), 'url', 105, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/popular.png' ), '', '') if ADDON.getSetting("menu_video_az") == 'true': AddMenuEntry((translation(30323)+translation(30302)), 'url', 102, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_video_channel_az") == 'true': AddMenuEntry((translation(30323)+translation(30327)), 'url', 120, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_video_categories") == 'true': AddMenuEntry((translation(30323)+translation(30303)), 'url', 103, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_video_search") == 'true': AddMenuEntry((translation(30323)+translation(30304)), 'url', 104, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/search.png' ), '', '') if ADDON.getSetting("menu_video_live") == 'true': AddMenuEntry((translation(30323)+translation(30305)), 'url', 101, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/tv.png' ), '', '') if ADDON.getSetting("menu_video_red_button") == 'true': AddMenuEntry((translation(30323)+translation(30328)), 'url', 118, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/tv.png' ), '', '') if ADDON.getSetting("menu_video_watching") == 'true': AddMenuEntry((translation(30323)+translation(30306)), 'url', 107, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') if ADDON.getSetting("menu_video_added") == 'true': AddMenuEntry((translation(30323)+translation(30307)), 'url', 108, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') if ADDON.getSetting("menu_radio_live") == 'true': AddMenuEntry((translation(30324)+translation(30321)), 'url', 113, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/live.png' ), '', '') if ADDON.getSetting("menu_radio_az") == 'true': AddMenuEntry((translation(30324)+translation(30302)), 'url', 112, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_radio_categories") == 'true': AddMenuEntry((translation(30324)+translation(30303)), 'url', 114, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/lists.png' ), '', '') if ADDON.getSetting("menu_radio_search") == 'true': AddMenuEntry((translation(30324)+translation(30304)), 'url', 115, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/search.png' ), '', '') if ADDON.getSetting("menu_radio_most_popular") == 'true': AddMenuEntry((translation(30324)+translation(30301)), 'url', 116, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/popular.png' ), '', '') if ADDON.getSetting("menu_radio_added") == 'true': AddMenuEntry((translation(30324)+translation(30307)), 'url', 117, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') if ADDON.getSetting("menu_radio_following") == 'true': AddMenuEntry((translation(30324)+translation(30334)), 'url', 199, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/favourites.png' ), '', '') AddMenuEntry(translation(30325), 'url', 119, xbmc.translatePath( 'special://home/addons/plugin.video.iplayerwww/media/settings.png' ), '', '')