The branch, frodo has been updated
via 83ef7583020996425860d708b8bf95dcb69d8948 (commit)
from e99448aeadf8f5433deb957e08e3095096be0676 (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=83ef7583020996425860d708b8bf95dcb69d8948
commit 83ef7583020996425860d708b8bf95dcb69d8948
Author: Martijn Kaijser <[email protected]>
Date: Wed Jul 17 23:12:30 2013 +0200
[plugin.video.youtube] 4.4.6
diff --git a/plugin.video.youtube/YouTubeCore.py
b/plugin.video.youtube/YouTubeCore.py
index 2ff1547..d955e49 100644
--- a/plugin.video.youtube/YouTubeCore.py
+++ b/plugin.video.youtube/YouTubeCore.py
@@ -22,7 +22,6 @@ import time
import socket
import urllib
import urllib2
-#import chardet
try:
import simplejson as json
@@ -430,8 +429,8 @@ class YouTubeCore():
else:
request.add_header('User-Agent', self.common.USERAGENT)
- if get("no-language-cookie", "false") == "false":
- cookie += "PREF=f1=50000000&hl=en;"
+ if get("no-language-cookie", "false") == "false" and False:
+ cookie += "PREF=f1=50000000&hl=en; "
if get("login", "false") == "true":
self.common.log("got login")
@@ -442,17 +441,11 @@ class YouTubeCore():
return ret_obj
# This should be a call to self.login._httpLogin()
- if self.settings.getSetting("login_cookies") == "":
+ if self.settings.getSetting("cookies_saved") != "true":
if isinstance(self.login, str):
self.login = sys.modules["__main__"].login
self.login._httpLogin()
- if self.settings.getSetting("login_cookies") != "":
- tcookies = eval(self.settings.getSetting("login_cookies"))
- self.common.log("Adding login cookies: " +
repr(tcookies.keys()))
- for key in tcookies.keys():
- cookie += "%s=%s;" % ( key, tcookies[key])
-
if get("referer", "false") != "false":
self.common.log("Added referer: %s" % get("referer"))
request.add_header('Referer', get("referer"))
@@ -462,7 +455,6 @@ class YouTubeCore():
if cookie:
self.common.log("Setting cookie: " + cookie)
- request.add_header('Cookie', cookie)
con = urllib2.urlopen(request)
@@ -489,23 +481,6 @@ class YouTubeCore():
if e.code == 400 or True:
self.common.log("Unhandled HTTPError : [%s] %s " % (e.code,
msg), 1)
- if msg.find("yt:quota") > 1:
- self.common.log("Hit quota... sleeping for 10 seconds")
- time.sleep(10)
- elif msg.find("too_many_recent_calls") > 1:
- self.common.log("Hit quota... sleeping for 10 seconds")
- time.sleep(10)
- elif err.find("Token invalid") > -1:
- self.common.log("refreshing token")
- self._oRefreshToken()
- elif err.find("User Rate Limit Exceeded") > -1:
- self.common.log("Hit limit... Sleeping for 10 seconds")
- time.sleep(10)
- else:
- if e.fp:
- cont = e.fp.read()
- self.common.log("HTTPError - Headers: " + str(e.headers) +
" - Content: " + cont)
-
params["error"] = get("error", 0) + 1
ret_obj = self._fetchPage(params)
@@ -570,7 +545,7 @@ class YouTubeCore():
if error.find("[") > -1:
error = error[0:error.find("[")]
error = urllib.unquote(error.replace("\n", " ").replace(" ", "
")).replace("'", "'")
- self.common.log("returning error : " + error.strip())
+ self.common.log("returning error : " + repr(error.strip()))
return error.strip()
# If no error was found. But fetchPage has an error level of 3+,
return the fetchPage content.
diff --git a/plugin.video.youtube/YouTubeLogin.py
b/plugin.video.youtube/YouTubeLogin.py
index 485582d..b046485 100644
--- a/plugin.video.youtube/YouTubeLogin.py
+++ b/plugin.video.youtube/YouTubeLogin.py
@@ -97,9 +97,9 @@ class YouTubeLogin():
url = self.urls[u"oauth_api_login"]
logged_in = False
- fetch_options = {"link": url, "no-language-cookie": "true"}
+ fetch_options = {"link": url}
step = 0
- self.common.log("Part A")
+ self.common.log("Go to the api login page")
while not logged_in and fetch_options and step < 6:
self.common.log("Step : " + str(step))
step += 1
@@ -107,6 +107,14 @@ class YouTubeLogin():
ret = self.core._fetchPage(fetch_options)
fetch_options = False
+ for accounts in self.common.parseDOM(ret["content"], "ol",
attrs={"id": "account-list"}):
+ self.common.log("Detected google plus with page
administrator.")
+ acurl = self.common.parseDOM(accounts, "a", ret="href")
+ acname = self.common.parseDOM(accounts, "span",
attrs={"class": "account-name"})
+ if len(acurl):
+ fetch_options = {"link": acurl[0].replace("&", "&")}
+ continue
+
newurl = self.common.parseDOM(ret["content"], "form",
attrs={"method": "POST"}, ret="action")
state_wrapper = self.common.parseDOM(ret["content"], "input",
attrs={"id": "state_wrapper"}, ret="value")
@@ -114,8 +122,8 @@ class YouTubeLogin():
url_data = {"state_wrapper": state_wrapper[0],
"submit_access": "true"}
- fetch_options = {"link": newurl[0].replace("&", "&"),
"url_data": url_data, "no-language-cookie": "true"}
- self.common.log("Part B")
+ fetch_options = {"link": newurl[0].replace("&", "&"),
"url_data": url_data}
+ self.common.log("Press 'Accept' button")
continue
code = self.common.parseDOM(ret["content"], "input", attrs={"id":
"code"}, ret="value")
@@ -127,22 +135,21 @@ class YouTubeLogin():
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"grant_type": "authorization_code"}
fetch_options = {"link": url, "url_data": url_data}
- self.common.log("Part C")
+ self.common.log("Extract and use access code")
continue
# use token
if ret["content"].find("access_token") > -1:
- self.common.log("Part D")
+ self.common.log("Saving access_token")
oauth = json.loads(ret["content"])
if len(oauth) > 0:
- self.common.log("Part D " + repr(oauth["expires_in"]))
self.settings.setSetting("oauth2_expires_at",
str(int(oauth["expires_in"]) + time.time()))
self.settings.setSetting("oauth2_access_token",
oauth["access_token"])
self.settings.setSetting("oauth2_refresh_token",
oauth["refresh_token"])
logged_in = True
- self.common.log("Done:" +
self.settings.getSetting("username"))
+ self.common.log("Done: " +
self.settings.getSetting("username"))
if logged_in:
return (self.language(30030), 200)
@@ -153,15 +160,12 @@ class YouTubeLogin():
def _httpLogin(self, params={}):
get = params.get
self.common.log("")
- status = 500
if get("new", "false") == "true" or get("page", "false") != "false":
- self.settings.setSetting("login_info", "")
- self.settings.setSetting("SID", "")
- self.settings.setSetting("login_cookies", "")
- elif self.settings.getSetting("login_info") != "":
- self.common.log("returning existing login info: " +
self.settings.getSetting("login_info"))
- return (self.settings.getSetting("login_info"), 200)
+ self.settings.setSetting("cookies_saved", "false")
+ elif self.settings.getSetting("cookies_saved") == "true":
+ self.common.log("Use saved cookies")
+ return (self.settings.getSetting("cookies_saved"), 200)
fetch_options = {"link": get("link", "http://www.youtube.com/")}
@@ -195,8 +199,9 @@ class YouTubeLogin():
if len(nick) > 0 and nick[0] != "Sign In":
self.common.log("Logged in. Parsing data: " + repr(nick))
- status = self._getLoginInfo(nick)
- return(ret, status)
+ sys.modules["__main__"].cookiejar.save()
+ self.settings.setSetting("cookies_saved", "true")
+ return(ret, 200)
# Click login link on youtube.com
newurl = self.common.parseDOM(ret["content"], "button",
attrs={"href": ".*?ServiceLogin.*?"}, ret="href")
@@ -211,7 +216,7 @@ class YouTubeLogin():
if len(newurl) > 0:
(galx, url_data) = self._fillLoginInfo(ret)
if len(galx) > 0 and len(url_data) > 0:
- fetch_options = {"link": newurl[0], "no-language-cookie":
"true", "url_data": url_data, "hidden": "true", "referer": ret["location"]}
+ fetch_options = {"link": newurl[0], "url_data": url_data,
"hidden": "true", "referer": ret["location"]}
self.common.log("Part B")
self.common.log("fetch options: " + repr(fetch_options),
10) # WARNING, SHOWS LOGIN INFO/PASSWORD
continue
@@ -220,7 +225,7 @@ class YouTubeLogin():
if len(newurl) > 0:
newurl = newurl[0].replace("&", "&")
newurl = newurl[newurl.find("'") + 5:newurl.rfind("'")]
- fetch_options = {"link": newurl, "no-language-cookie": "true",
"referer": ret["location"]}
+ fetch_options = {"link": newurl, "referer": ret["location"]}
self.common.log("Part C: " + repr(fetch_options))
continue
@@ -231,7 +236,7 @@ class YouTubeLogin():
return (False, 500)
new_part = self.common.parseDOM(ret["content"], "form",
attrs={"name": "verifyForm"}, ret="action")
- fetch_options = {"link": new_part[0], "url_data": url_data,
"no-language-cookie": "true", "referer": ret["location"]}
+ fetch_options = {"link": new_part[0].replace("&", "&"),
"url_data": url_data, "referer": ret["location"]}
self.common.log("Part D: " + repr(fetch_options))
continue
@@ -245,7 +250,7 @@ class YouTubeLogin():
"GALX": galx}
target_url = self.common.parseDOM(ret["content"], "form",
attrs={"name": "hiddenpost"}, ret="action")
- fetch_options = {"link": target_url[0], "url_data": url_data,
"no-language-cookie": "true", "referer": ret["location"]}
+ fetch_options = {"link": target_url[0], "url_data": url_data,
"referer": ret["location"]}
self.common.log("Part E: " + repr(fetch_options))
continue
@@ -254,107 +259,49 @@ class YouTubeLogin():
# Check for errors.
return (self.core._findErrors(ret), 303)
- return (ret, status)
+ return (ret, 500)
def _fillLoginInfo(self, ret):
+ self.common.log("")
content = ret["content"]
- rmShown = self.common.parseDOM(content, "input", attrs={"name":
"rmShown"}, ret="value")
- cont = self.common.parseDOM(content, "input", attrs={"name":
"continue"}, ret="value")
- uilel = self.common.parseDOM(content, "input", attrs={"name":
"uilel"}, ret="value") # Deprecated?
- if len(uilel) == 0: # Deprecated?
- uilel = self.common.parseDOM(content, "input", attrs=
{"id":"uilel"}, ret="value")
- if len(uilel) == 0 and ret["new_url"].find("uilel=") > -1:
- uilel = ret["new_url"][ret["new_url"].find("uilel=")+6]
- if uilel.find("&") > -1:
- uilel = uilel[:uilel.find("&")]
- uilel = [uilel]
- dsh = self.common.parseDOM(content, "input", attrs={"name": "dsh"},
ret="value")
- if len(dsh) == 0:
- dsh = self.common.parseDOM(content, "input", attrs={"id": "dsh"},
ret="value")
-
- galx = self.common.parseDOM(content, "input", attrs={"name": "GALX"},
ret="value")
- uname = self.pluginsettings.userName()
- pword = self.pluginsettings.userPassword()
-
- if pword == "":
- pword = self.common.getUserInput(self.language(30628), hidden=True)
-
- if len(galx) == 0 or len(cont) == 0 or len(uilel) == 0 or len(dsh) ==
0 or len(rmShown) == 0 or uname == "" or pword == "":
- self.common.log("_fillLoginInfo missing values for login form " +
repr(galx) + repr(cont) + repr(uilel) + repr(dsh) + repr(rmShown) + repr(uname)
+ str(len(pword)))
- return ("", {})
- else:
- galx = galx[0]
- url_data = {"pstMsg": "0",
- "ltmpl": "sso",
- "dnConn": "",
- "continue": cont[0],
- "service": "youtube",
- "uilel": uilel[0],
- "dsh": dsh[0],
- "hl": "en_US",
- "timeStmp": "",
- "secTok": "",
- "GALX": galx,
- "Email": uname,
- "Passwd": pword,
- "PersistentCookie": "yes",
- "rmShown": rmShown[0],
- "signin": "Sign in",
- "asts": ""
- }
- return (galx, url_data)
+
+ url_data = {}
+
+ for name in self.common.parseDOM(content, "input", ret="name"):
+ for val in self.common.parseDOM(content, "input", attrs={"name":
name}, ret="value"):
+ url_data[name] = val
+
+ self.common.log("Extracted url_data: " + repr(url_data), 0)
+ url_data["Email"] = self.pluginsettings.userName()
+ url_data["Passwd"] = self.pluginsettings.userPassword()
+ if url_data["Passwd"] == "":
+ url_data["Passwd"] =
self.common.getUserInput(self.language(30628), hidden=True)
+
+ self.common.log("Done")
+ return (url_data["GALX"], url_data)
def _fillUserPin(self, content):
- self.common.log(repr(content), 5)
- smsToken = self.common.parseDOM(content, "input", attrs={"name":
"smsToken"}, ret="value")
- self.smsToken = smsToken
+ self.common.log("")
+ form = self.common.parseDOM(content, "form", attrs={"name":
"verifyForm"}, ret=True)
+
+ url_data = {}
+ for name in self.common.parseDOM(form, "input", ret="name"):
+ for val in self.common.parseDOM(form, "input", attrs={"name":
name}, ret="value"):
+ url_data[name] = val
+
+ self.common.log("url_data: " + repr(form), 0)
+
+ if "smsToken" in url_data:
+ self.smsToken = url_data["smsToken"]
+ if "continue" in url_data:
+ url_data["continue"] = url_data["continue"].replace("&", "&")
userpin = self.common.getUserInputNumbers(self.language(30627))
if len(userpin) > 0:
- url_data = {"smsToken": smsToken[0],
- "PersistentCookie": "yes",
- "smsUserPin": userpin,
- "smsVerifyPin": "Verify",
- "timeStmp": "",
- "secTok": ""}
+ url_data["smsUserPin"] = userpin
self.common.log("Done: " + repr(url_data))
+ url_data["smsVerifyPin"] = "Verify" # Overwrite this variable
since it might contain unicode.
return url_data
else:
- self.common.log("Replace this with a message telling users that
they didn't enter a pin")
+ self.common.log("Error")
return {}
-
- def _getLoginInfo(self, nick):
- self.common.log(nick)
- status = 303
-
- # Save cookiefile in settings
- cookies = self.common.getCookieInfoAsHTML()
- login_info = self.common.parseDOM(cookies, "cookie", attrs={"name":
"LOGIN_INFO"}, ret="value")
- SID = self.common.parseDOM(cookies, "cookie", attrs={"name": "SID",
"domain": ".youtube.com"}, ret="value")
- scookies = {}
- self.common.log("COOKIES:" + repr(cookies))
- tnames = re.compile(" name='(.*?)' ").findall(cookies)
- for key in tnames:
- tval = self.common.parseDOM(cookies, "cookie", attrs={"name":
key}, ret="value")
- if len(tval) > 0:
- scookies[key] = tval[0]
- self.common.log("COOKIES:" + repr(scookies))
-
- if len(login_info) == 1:
- self.common.log("LOGIN_INFO: " + repr(login_info))
- self.settings.setSetting("login_info", login_info[0])
- else:
- self.common.log("Failed to get LOGIN_INFO from youtube: " +
repr(login_info))
-
- if len(SID) == 1:
- self.common.log("SID: " + repr(SID))
- self.settings.setSetting("SID", SID[0])
- else:
- self.common.log("Failed to get SID from youtube: " + repr(SID))
-
- if len(SID) == 1 and len(login_info) == 1:
- status = 200
- self.settings.setSetting("login_cookies", repr(scookies))
-
- self.common.log("Done")
- return status
diff --git a/plugin.video.youtube/YouTubeNavigation.py
b/plugin.video.youtube/YouTubeNavigation.py
index 4773d0f..e4c3a7c 100644
--- a/plugin.video.youtube/YouTubeNavigation.py
+++ b/plugin.video.youtube/YouTubeNavigation.py
@@ -64,8 +64,7 @@ class YouTubeNavigation():
{'Title':self.language(30016) ,'path':"/root/explore/feeds/rated"
, 'thumbnail':"top" , 'login':"false" ,
'feed':"feed_rated" },
{'Title':self.language(30052) ,'path':"/root/explore/music"
, 'thumbnail':"music" , 'login':"false" ,
'store':"disco_searches", "folder":"true" },
{'Title':self.language(30040) ,'path':"/root/explore/music/new"
, 'thumbnail':"search" , 'login':"false" ,
'scraper':"search_disco"},
- {'Title':self.language(30055)
,'path':"/root/explore/music/top100" , 'thumbnail':"music"
, 'login':"false" , 'scraper':'music_top100'},
- {'Title':self.language(30032) ,'path':"/root/explore/trailers"
, 'thumbnail':"trailers" , 'login':"false" ,
'scraper':'trailers'},
+ {'Title':self.language(30032) ,'path':"/root/explore/trailers"
, 'thumbnail':"trailers" , 'login':"false" ,
'feed':'uploads', 'contact':"trailers", "external":"true"},
{'Title':self.language(30051) ,'path':"/root/explore/live"
, 'thumbnail':"live" , 'login':"false" ,
'feed':"feed_live" },
{'Title':self.language(30019) ,'path':"/root/recommended"
, 'thumbnail':"recommended" , 'login':"true" ,
'user_feed':"recommended" },
{'Title':self.language(30008) ,'path':"/root/watch_later"
, 'thumbnail':"watch_later" , 'login':"true" ,
'user_feed':"watch_later" },
@@ -574,10 +573,6 @@ class YouTubeNavigation():
if not get("external"):
cm.append((self.language(30539),
"XBMC.RunPlugin(%s?path=%s&action=delete_playlist&playlist=%s&)" %
(sys.argv[0], item("path"), item("playlist"))))
- if (item("scraper") == "music_top100"):
- cm.append((self.language(30520),
"XBMC.RunPlugin(%s?path=%s&action=play_all&scraper=music_top100&)" %
(sys.argv[0], item("path"))))
- cm.append((self.language(30522),
"XBMC.RunPlugin(%s?path=%s&action=play_all&shuffle=true&scraper=music_top100&)"
% (sys.argv[0], item("path"))))
-
if (item("scraper") == "search_disco"):
cm.append((self.language(30520),
"XBMC.RunPlugin(%s?path=%s&action=play_all&scraper=search_disco&search=%s&)" %
(sys.argv[0], item("path"), item("search"))))
cm.append((self.language(30522),
"XBMC.RunPlugin(%s?path=%s&action=play_all&shuffle=true&scraper=search_disco&search=%s&)"
% (sys.argv[0], item("path"), item("search"))))
diff --git a/plugin.video.youtube/YouTubePlayer.py
b/plugin.video.youtube/YouTubePlayer.py
index 280f43f..d49364c 100644
--- a/plugin.video.youtube/YouTubePlayer.py
+++ b/plugin.video.youtube/YouTubePlayer.py
@@ -93,6 +93,7 @@ class YouTubePlayer():
self.xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]),
succeeded=True, listitem=listitem)
if self.settings.getSetting("lang_code") != "0" or
self.settings.getSetting("annotations") == "true":
+ self.common.log("BLAAAAAAAAAAAAAAAAAAAAAA: " +
repr(self.settings.getSetting("lang_code")))
self.subtitles.addSubtitles(video)
if (get("watch_later") == "true" and get("playlist_entry_id")):
@@ -214,7 +215,7 @@ class YouTubePlayer():
self.common.log(u"- construct_video_url failed, video_url not set")
return video_url
- if get("action") != "download":
+ if get("action") != "download" and video_url.find("rtmp") == -1:
video_url += '|' +
urllib.urlencode({'User-Agent':self.common.USERAGENT})
self.common.log(u"Done")
@@ -254,15 +255,16 @@ class YouTubePlayer():
def checkForErrors(self, video):
status = 200
- if video[u"video_url"] == u"":
+ if "video_url" not in video or video[u"video_url"] == u"":
status = 303
- vget = video.get
- if vget(u"live_play"):
- video[u'apierror'] = self.language(30612)
- elif vget(u"stream_map"):
- video[u'apierror'] = self.language(30620)
- else:
- video[u'apierror'] = self.language(30618)
+ if u"apierror" not in video:
+ vget = video.get
+ if vget(u"live_play"):
+ video[u'apierror'] = self.language(30612)
+ elif vget(u"stream_map"):
+ video[u'apierror'] = self.language(30620)
+ else:
+ video[u'apierror'] = self.language(30618)
return (video, status)
@@ -282,7 +284,12 @@ class YouTubePlayer():
(links, video) = self.extractVideoLinksFromYoutube(video, params)
- video[u"video_url"] = self.selectVideoQuality(params, links)
+ if len(links) != 0:
+ video[u"video_url"] = self.selectVideoQuality(params, links)
+ elif "hlsvp" in video:
+ #hls selects the quality based on available bitrate (adaptive
quality), no need to select it here
+ video[u"video_url"] = video[u"hlsvp"]
+ self.common.log("Using hlsvp url %s" % video[u"video_url"])
(video, status) = self.checkForErrors(video)
@@ -290,6 +297,13 @@ class YouTubePlayer():
return (video, status)
+ def removeAdditionalEndingDelimiter(self, data):
+ pos = data.find("};")
+ if pos != -1:
+ self.common.log(u"found extra delimiter, removing")
+ data = data[:pos + 1]
+ return data
+
def extractFlashVars(self, data):
flashvars = {}
found = False
@@ -303,10 +317,12 @@ class YouTubePlayer():
continue
data = line[p1 + 1:p2]
break
+ data = self.removeAdditionalEndingDelimiter(data)
if found:
data = json.loads(data)
flashvars = data["args"]
+ self.common.log("Step2: " + repr(data))
self.common.log(u"flashvars: " + repr(flashvars), 2)
return flashvars
@@ -322,6 +338,9 @@ class YouTubePlayer():
if flashvars.has_key(u"ttsurl"):
video[u"ttsurl"] = flashvars[u"ttsurl"]
+ if flashvars.has_key(u"hlsvp"):
+ video[u"hlsvp"] = flashvars[u"hlsvp"]
+
for url_desc in flashvars[u"url_encoded_fmt_stream_map"].split(u","):
url_desc_map = cgi.parse_qs(url_desc)
self.common.log(u"url_map: " + repr(url_desc_map), 2)
@@ -342,11 +361,37 @@ class YouTubePlayer():
if url_desc_map.has_key(u"sig"):
url = url + u"&signature=" + url_desc_map[u"sig"][0]
+ elif url_desc_map.has_key(u"s"):
+ sig = url_desc_map[u"s"][0]
+ url = url + u"&signature=" + self.decrypt_signature(sig)
links[key] = url
return links
+ def decrypt_signature(self, s):
+ ''' use decryption solution by Youtube-DL project '''
+ if len(s) == 88:
+ return s[48] + s[81:67:-1] + s[82] + s[66:62:-1] + s[85] +
s[61:48:-1] + s[67] + s[47:12:-1] + s[3] + s[11:3:-1] + s[2] + s[12]
+ elif len(s) == 87:
+ return s[62] + s[82:62:-1] + s[83] + s[61:52:-1] + s[0] +
s[51:2:-1]
+ elif len(s) == 86:
+ return s[2:63] + s[82] + s[64:82] + s[63]
+ elif len(s) == 85:
+ return s[76] + s[82:76:-1] + s[83] + s[75:60:-1] + s[0] +
s[59:50:-1] + s[1] + s[49:2:-1]
+ elif len(s) == 84:
+ return s[83:36:-1] + s[2] + s[35:26:-1] + s[3] + s[25:3:-1] + s[26]
+ elif len(s) == 83:
+ return s[6] + s[3:6] + s[33] + s[7:24] + s[0] + s[25:33] + s[53] +
s[34:53] + s[24] + s[54:]
+ elif len(s) == 82:
+ return s[36] + s[79:67:-1] + s[81] + s[66:40:-1] + s[33] +
s[39:36:-1] + s[40] + s[35] + s[0] + s[67] + s[32:0:-1] + s[34]
+ elif len(s) == 81:
+ return s[6] + s[3:6] + s[33] + s[7:24] + s[0] + s[25:33] + s[2] +
s[34:53] + s[24] + s[54:81]
+ elif len(s) == 92:
+ return s[25] + s[3:25] + s[0] + s[26:42] + s[79] + s[43:79] +
s[91] + s[80:83];
+ else:
+ self.common.log(u'Unable to decrypt signature, key length %d not
supported; retrying might work' % (len(s)))
+
def getVideoPageFromYoutube(self, get):
login = "false"
@@ -354,6 +399,7 @@ class YouTubePlayer():
login = "true"
page = self.core._fetchPage({u"link": self.urls[u"video_stream"] %
get(u"videoid"), "login": login})
+ self.common.log("Step1: " + repr(page["content"].find("ytplayer")))
if not page:
page = {u"status":303}
@@ -376,7 +422,7 @@ class YouTubePlayer():
self.login._httpLogin({"new":"true"})
result = self.getVideoPageFromYoutube(get)
else:
- self.utils.showMessage(self.language(30600),
self.language(30622))
+ video[u"apierror"] = self.language(30622)
if result[u"status"] != 200:
self.common.log(u"Couldn't get video page from YouTube")
@@ -384,7 +430,7 @@ class YouTubePlayer():
links = self.scrapeWebPageForVideoLinks(result, video)
- if len(links) == 0:
+ if len(links) == 0 and not( "hlsvp" in video ):
self.common.log(u"Couldn't find video url- or stream-map.")
if not u"apierror" in video:
diff --git a/plugin.video.youtube/YouTubePlaylistControl.py
b/plugin.video.youtube/YouTubePlaylistControl.py
index b66ac2b..b35f98f 100644
--- a/plugin.video.youtube/YouTubePlaylistControl.py
+++ b/plugin.video.youtube/YouTubePlaylistControl.py
@@ -50,10 +50,9 @@ class YouTubePlaylistControl():
print repr(result)
elif get("scraper") == "liked_videos":
(result, status) = self.getLikedVideos(params)
- elif get("scraper") == "music_top100":
- result = self.getYouTubeTop100(params)
elif get("playlist"):
params["user_feed"] = "playlist"
+ params["login"] = "true"
result = self.getUserFeed(params)
elif get("user_feed") in ["recommended", "watch_later",
"newsubscriptions", "favorites"]:
params["login"] = "true"
@@ -149,14 +148,6 @@ class YouTubePlaylistControl():
return self.feeds.listAll(params)
- def getYouTubeTop100(self, params={}):
- (result, status) = self.scraper.scrapeYouTubeTop100(params)
-
- if status == 200:
- (result, status) = self.core.getBatchDetails(result, params)
-
- return result
-
def getLikedVideos(self, params={}):
get = params.get
if not get("scraper") or not get("login"):
diff --git a/plugin.video.youtube/YouTubePluginSettings.py
b/plugin.video.youtube/YouTubePluginSettings.py
index e82392a..daba307 100644
--- a/plugin.video.youtube/YouTubePluginSettings.py
+++ b/plugin.video.youtube/YouTubePluginSettings.py
@@ -36,7 +36,7 @@ class YouTubePluginSettings():
return [5, 10, 15, 20, 25][int(self.settings.getSetting("timeout"))]
def userHasProvidedValidCredentials(self):
- return (self.settings.getSetting("username") != "" and
self.settings.getSetting("oauth2_access_token"))
+ return (self.settings.getSetting("username") != "" and
self.settings.getSetting("oauth2_access_token") != "")
def userName(self):
return self.settings.getSetting("username")
diff --git a/plugin.video.youtube/YouTubeScraper.py
b/plugin.video.youtube/YouTubeScraper.py
index 072b92e..04ca569 100644
--- a/plugin.video.youtube/YouTubeScraper.py
+++ b/plugin.video.youtube/YouTubeScraper.py
@@ -64,25 +64,6 @@ class YouTubeScraper():
return ([], 303)
-#================================= trailers
===========================================
-
- def scraperTop100Trailers(self, params):
- self.common.log("" + repr(params))
- url = self.createUrl(params)
-
- result = self.core._fetchPage({"link":url})
-
- trailers_playlist = self.common.parseDOM(result["content"], "a",
attrs={"class":"yt-playall-link .*?"}, ret="href")[0]
-
- if trailers_playlist.find("list=") > 0:
- trailers_playlist =
trailers_playlist[trailers_playlist.find("list=") + len("list="):]
- if (trailers_playlist.rfind("&") > 0):
- trailers_playlist =
trailers_playlist[:trailers_playlist.rfind("&")]
-
- return self.feeds.listPlaylist({"user_feed": "playlist",
"playlist" : trailers_playlist})
-
- return ([], 303)
-
#=================================== Music
============================================
def searchDisco(self, params={}):
@@ -103,34 +84,6 @@ class YouTubeScraper():
return ([], 303)
- def scrapeYouTubeTop100(self, params={}):
- self.common.log("")
-
- url = self.createUrl(params)
-
- result = self.core._fetchPage({"link": url})
-
- if result["status"] == 200:
- list_url = self.common.parseDOM(result["content"], "a",
attrs={"id": 'popular-tracks'}, ret="href")[0]
- return self.scrapeWeeklyTop100Playlist(list_url)
-
- self.common.log("Done")
- return ([], 303)
-
- def scrapeWeeklyTop100Playlist(self, list_url):
- self.common.log("")
- url = self.urls["main"] + list_url
-
- result = self.core._fetchPage({"link":url })
-
- if result["status"] == 200:
- playlist = self.common.parseDOM(result["content"], "ol",
attrs={"id": 'watch7-playlist-tray'})
- print repr(playlist)
- videos = self.common.parseDOM(playlist, "li", attrs={"class":
'video-list-item.*?'}, ret="data-video-id")
-
- return(videos, result["status"])
-
- return ([], 303)
#================================== Common
============================================
def getNewResultsFunction(self, params={}):
get = params.get
@@ -142,13 +95,6 @@ class YouTubeScraper():
if (get("scraper") in ["liked_videos", "watched_history"]):
function = self.scrapeUserLikedVideos
- if (get("scraper") == "music_top100"):
- params["batch"] = "true"
- function = self.scrapeYouTubeTop100
-
- if get("scraper") == "trailers":
- function = self.scraperTop100Trailers
-
if function:
params["new_results_function"] = function
@@ -168,12 +114,6 @@ class YouTubeScraper():
else:
url += "?p=" + page
- if get("scraper") == "music_top100":
- url = self.urls["disco_main"]
-
- if get("scraper") == "trailers":
- url = self.urls["trailers"]
-
if (get("scraper") in "search_disco"):
url = self.urls["disco_search"] % urllib.quote_plus(get("search"))
@@ -238,10 +178,6 @@ class YouTubeScraper():
return (result, status)
def scrape(self, params={}):
- get = params.get
- if get("scraper") == "trailers":
- return self.scraperTop100Trailers(params)
-
self.getNewResultsFunction(params)
result = self.paginator(params)
diff --git a/plugin.video.youtube/YouTubeSubtitleControl.py
b/plugin.video.youtube/YouTubeSubtitleControl.py
index b1d9a49..8909440 100644
--- a/plugin.video.youtube/YouTubeSubtitleControl.py
+++ b/plugin.video.youtube/YouTubeSubtitleControl.py
@@ -100,36 +100,26 @@ class YouTubeSubtitleControl():
self.common.log(u"Code list and sublist length mismatch: " +
repr(codelist) + " - " + repr(sublist))
return ""
- if len(codelist) > 0:
- # Fallback to first in list.
- subtitle = sublist[0].replace(u" ", u"%20")
- code = codelist[0]
-
lang_code = ["off", "en", "es", "de", "fr", "it",
"ja"][int(self.settings.getSetting("lang_code"))]
self.common.log(u"selected language: " + repr(lang_code))
- if True:
- for i in range(0, len(codelist)):
- data = codelist[i].lower()
- if data.find("-") > -1:
- data = data[:data.find("-")]
-
- if codelist[i].find(lang_code) > -1:
- subtitle = sublist[i].replace(" ", "%20")
- code = codelist[i]
- self.common.log(u"found subtitle specified: " +
subtitle + " - " + code)
- break
-
- if codelist[i].find("en") > -1:
- subtitle = sublist[i].replace(" ", "%20")
- code = "en"
- self.common.log(u"found subtitle default: " + subtitle
+ " - " + code)
+
+ for i in range(0, len(codelist)):
+ if codelist[i].find(lang_code) > -1:
+ subtitle = sublist[i].replace(u" ", u"%20")
+ code = codelist[i]
+ self.common.log(u"found subtitle specified: " + subtitle +
" - " + code)
+ break
+
+ if codelist[i].find("en") > -1:
+ subtitle = sublist[i].replace(u" ", u"%20")
+ code = "en"
+ self.common.log(u"found subtitle default: " + subtitle + "
- " + code)
if code:
url = self.urls["close_caption_url"] % (get("videoid"), code)
if len(subtitle) > 0:
url += "&name=" + subtitle
-
self.common.log(u"found subtitle url: " + repr(url))
return url
diff --git a/plugin.video.youtube/addon.xml b/plugin.video.youtube/addon.xml
index d217be0..036332a 100644
--- a/plugin.video.youtube/addon.xml
+++ b/plugin.video.youtube/addon.xml
@@ -1,9 +1,9 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
-<addon id="plugin.video.youtube" name="YouTube" provider-name="TheCollective"
version="4.4.4">
+<addon id="plugin.video.youtube" name="YouTube" provider-name="TheCollective"
version="4.4.6">
<requires>
<import addon="xbmc.python" version="2.1.0" />
<import addon="script.module.simplejson" version="2.0.10" />
- <import addon="script.common.plugin.cache" version="2.5.1" />
+ <import addon="script.common.plugin.cache" version="2.5.2" />
<import addon="script.module.parsedom" version="2.5.1" />
<import addon="script.module.simple.downloader" version="1.9.4" />
</requires>
diff --git a/plugin.video.youtube/changelog.txt
b/plugin.video.youtube/changelog.txt
index f3a082d..fb0e41f 100644
--- a/plugin.video.youtube/changelog.txt
+++ b/plugin.video.youtube/changelog.txt
@@ -1,9 +1,5 @@
[B]TODO:[/B]
- Fix RTMP support.
-- Unit test new functions in storage...
-- Replace scraper with feeds for: shows.
-- Integration tests on all user actions.
-- Use extractJS and urlparse.parse_qs.
- UTF8/16 does not work consistently(Verify failures against minidom
implementation)
- Embed playback fallback
@@ -12,6 +8,20 @@
- [XBMC] When sorting items, it's impossible to get them to return to their
original order
- [XBMC] Has Excessive Memory use after running the plugin for prolonged
periods of time
- [RTMPDUMP] Doesn't support handshake type 10 which is required by youtube.
+- Youtube implemented encryption of signatures, without knowing the proper
decryption method this VEVO content will remain unstable
+
+[B]Version 3.4.6[/B]
+- Fixed: Age verification working again due
+- Added: Partial hack to support youtubes new encrypted signatures, this will
break again.
+- Added: Support a 5th login method.
+- Changed: Much better cookie management
+- Changed: More robust http login
+
+[B]Version 3.4.5[/B]
+- Added support for hls only streams. (Thanks to simplyintricate on github)
+- Fixed edge case where youtube video object contained an extra delimiter
breakning the video player
+- Changed: Now requires valid Login to use play all on users playlists
+- Changed: Switched trailers to use youtube api
[B]Version 3.4.4[/B]
- Fixed playback after youtube javascript site changes
diff --git a/plugin.video.youtube/default.py b/plugin.video.youtube/default.py
index fa62579..1082caa 100644
--- a/plugin.video.youtube/default.py
+++ b/plugin.video.youtube/default.py
@@ -16,20 +16,21 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
+import os
import sys
import xbmc
-import xbmcplugin
-import xbmcaddon
import xbmcgui
import urllib2
+import xbmcaddon
import cookielib
+import xbmcplugin
try:
import xbmcvfs
except ImportError:
import xbmcvfsdummy as xbmcvfs
# plugin constants
-version = "4.4.4"
+version = "4.4.6"
plugin = "YouTube-" + version
author = "TheCollective"
url = "www.xbmc.com"
@@ -51,7 +52,17 @@ login = ""
player = ""
cache = ""
-cookiejar = cookielib.LWPCookieJar()
+path = xbmc.translatePath(settings.getAddonInfo("profile"))
+path = os.path.join(path, 'yt-cookiejar.txt')
+print("Loading cookies from :" + repr(path))
+cookiejar = cookielib.LWPCookieJar(path)
+
+if xbmcvfs.exists(path):
+ try:
+ cookiejar.load()
+ except:
+ pass
+
cookie_handler = urllib2.HTTPCookieProcessor(cookiejar)
opener = urllib2.build_opener(cookie_handler)
diff --git a/plugin.video.youtube/resources/language/English/strings.xml
b/plugin.video.youtube/resources/language/English/strings.xml
index 2ae9de2..65dbcd7 100644
--- a/plugin.video.youtube/resources/language/English/strings.xml
+++ b/plugin.video.youtube/resources/language/English/strings.xml
@@ -36,7 +36,7 @@
<string id="30029">Contact</string>
<string id="30030">Refreshing folder..</string>
<string id="30031">Login success</string>
- <string id="30032">YouTube Top 100 Trailers</string>
+ <string id="30032">YouTube Trailers</string>
<string id="30033">Popular</string>
<string id="30034">In Theaters</string>
<string id="30035">Latest</string>
-----------------------------------------------------------------------
Summary of changes:
plugin.video.youtube/YouTubeCore.py | 33 +----
plugin.video.youtube/YouTubeLogin.py | 173 +++++++-------------
plugin.video.youtube/YouTubeNavigation.py | 7 +-
plugin.video.youtube/YouTubePlayer.py | 70 +++++++--
plugin.video.youtube/YouTubePlaylistControl.py | 11 +-
plugin.video.youtube/YouTubePluginSettings.py | 2 +-
plugin.video.youtube/YouTubeScraper.py | 64 -------
plugin.video.youtube/YouTubeSubtitleControl.py | 34 ++---
plugin.video.youtube/addon.xml | 4 +-
plugin.video.youtube/changelog.txt | 18 ++-
plugin.video.youtube/default.py | 19 ++-
.../resources/language/English/strings.xml | 2 +-
12 files changed, 169 insertions(+), 268 deletions(-)
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
See everything from the browser to the database with AppDynamics
Get end-to-end visibility with application monitoring from AppDynamics
Isolate bottlenecks and diagnose root cause in seconds.
Start your free trial of AppDynamics Pro today!
http://pubads.g.doubleclick.net/gampad/clk?id=48808831&iu=/4140/ostg.clktrk
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons