"""
Kodi urlresolver plugin
Copyright (C) 2014 smokdpi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
from urlresolver import common, hmf
from urlresolver.resolver import UrlResolver, ResolverError
from lib import helpers
import re
import xbmc
import json
import urllib2
class GoogleResolver(UrlResolver):
name = "googlevideo"
domains = ["googlevideo.com", "googleusercontent.com", "get.google.com",
"plus.google.com", "googledrive.com", "drive.google.com", "docs.google.com", "youtube.googleapis.com", "bp.blogspot.com", "blogger.com"]
pattern = 'https?://(.*?(?:\.googlevideo|\.bp\.blogspot|blogger|(?:plus|drive|get|docs)\.google|google(?:usercontent|drive|apis))\.com)/(.*?(?:videoplayback\?|[\?&]authkey|host/)*.+)'
def __init__(self):
self.net = common.Net()
self.headers = {'User-Agent': common.FF_USER_AGENT}
self.url_matches = ['redirector.', 'googleusercontent', '.bp.blogspot.com']
self.itag_map = {'5': '240', '6': '270', '17': '144', '18': '360', '22': '720', '34': '360', '35': '480',
'36': '240', '37': '1080', '38': '3072', '43': '360', '44': '480', '45': '720', '46': '1080',
'82': '360 [3D]', '83': '480 [3D]', '84': '720 [3D]', '85': '1080p [3D]', '100': '360 [3D]',
'101': '480 [3D]', '102': '720 [3D]', '92': '240', '93': '360', '94': '480', '95': '720',
'96': '1080', '132': '240', '151': '72', '133': '240', '134': '360', '135': '480',
'136': '720', '137': '1080', '138': '2160', '160': '144', '264': '1440',
'298': '720', '299': '1080', '266': '2160', '167': '360', '168': '480', '169': '720',
'170': '1080', '218': '480', '219': '480', '242': '240', '243': '360', '244': '480',
'245': '480', '246': '480', '247': '720', '248': '1080', '271': '1440', '272': '2160',
'302': '2160', '303': '1080', '308': '1440', '313': '2160', '315': '2160', '59': '480'}
def __key(self, item):
try: return int(re.search('(\d+)', item[0]).group(1))
except: return 0
def get_media_url(self, host, media_id):
video = None
web_url = self.get_url(host, media_id)
if xbmc.getCondVisibility('System.HasAddon(plugin.video.gdrive)') and self.get_setting('use_gdrive'):
doc_id = re.search('[-\w]{25,}', web_url)
if doc_id:
common.kodi.notify(header=None, msg='Resolving with GDRIVE', duration=3000)
video = 'plugin://plugin.video.gdrive/?mode=video&instance=gdrive1&filename=%s&content_type=video' % doc_id.group(1)
if not video:
response, video_urls = self._parse_google(web_url)
if video_urls:
video_urls.sort(key=self.__key, reverse=True)
video = helpers.pick_source(video_urls)
if response is not None:
res_headers = response.get_headers(as_dict=True)
if 'Set-Cookie' in res_headers:
self.headers['Cookie'] = res_headers['Set-Cookie']
if not video:
if any(url_match in web_url for url_match in self.url_matches):
video = self._parse_redirect(web_url, hdrs=self.headers)
elif 'googlevideo.' in web_url:
video = web_url + helpers.append_headers(self.headers)
elif 'plugin://' not in video:
if any(url_match in video for url_match in self.url_matches):
video = self._parse_redirect(video, hdrs=self.headers)
if video:
if 'plugin://' in video: # google plus embedded videos may result in this
return video
else:
return video + helpers.append_headers(self.headers)
raise ResolverError('File not found')
def get_url(self, host, media_id):
return 'https://%s/%s' % (host, media_id)
def _parse_redirect(self, url, hdrs={}):
class NoRedirection(urllib2.HTTPErrorProcessor):
def http_response(self, request, response):
return response
opener = urllib2.build_opener(NoRedirection)
opener = urllib2.install_opener(opener)
request = urllib2.Request(url, headers=hdrs)
try: response = urllib2.urlopen(request)
except urllib2.HTTPError as e:
if e.code == 429 or e.code == 403:
msg = 'Daily view limit reached'
common.kodi.notify(header=None, msg=msg, duration=3000)
raise ResolverError(msg)
response_headers = dict([(item[0].title(), item[1]) for item in response.info().items()])
cookie = response_headers.get('Set-Cookie', None)
if cookie:
self.headers.update({'Cookie': cookie})
return response.geturl()
def _parse_google(self, link):
sources = []
response = None
if re.match('https?://get[.]', link):
if link.endswith('/'): link = link[:-1]
vid_id = link.split('/')[-1]
response = self.net.http_GET(link)
sources = self.__parse_gget(vid_id, response.content)
elif re.match('https?://plus[.]', link):
response = self.net.http_GET(link)
sources = self.__parse_gplus(response.content)
elif 'drive.google' in link or 'docs.google' in link:
link = link.replace("/preview", "/edit")
response = self.net.http_GET(link)
sources = self._parse_gdocs(response.content)
elif 'youtube.googleapis.com' in link:
cid = re.search('cid=([\w]+)', link)
if cid: link = 'https://drive.google.com/file/d/%s/edit' % cid.groups(1)
else: raise ResolverError('ID not found')
response = self.net.http_GET(link)
sources = self._parse_gdocs(response.content)
elif 'blogger.com/video.g?token=' in link:
# Quick hack till I figure the direction to take this plugin
response = self.net.http_GET(link)
source = re.search('''['"]play_url["']\s*:\s*["']([^"']+)''', response.content)
if source:
sources = [("Unknown Quality", source.group(1).decode('unicode-escape'))]
return response, sources
def __parse_gplus(self, html):
sources = []
match = re.search('[^&]+).*?&itag=(?P[^&]+)', item4):
link = match.group('link')
itag = match.group('itag')
quality = self.itag_map.get(itag, 'Unknown Quality [%s]' % itag)
sources.append((quality, link))
if sources:
return sources
return sources
def _parse_gdocs(self, html):
urls = []
for match in re.finditer('\[\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\]', html):
key, value = match.groups()
if key == 'fmt_stream_map':
items = value.split(',')
for item in items:
_source_itag, source_url = item.split('|')
if isinstance(source_url, unicode):
source_url = source_url.encode('utf-8')
source_url = source_url.decode('unicode_escape')
quality = self.itag_map.get(_source_itag, 'Unknown Quality [%s]' % _source_itag)
source_url = urllib2.unquote(source_url)
urls.append((quality, source_url))
return urls
return urls
@staticmethod
def parse_json(html):
if html:
try:
if not isinstance(html, unicode):
if html.startswith('\xef\xbb\xbf'):
html = html[3:]
elif html.startswith('\xfe\xff'):
html = html[2:]
js_data = json.loads(html)
if js_data is None:
return {}
else:
return js_data
except ValueError:
return {}
else:
return {}
@classmethod
def get_settings_xml(cls):
xml = super(cls, cls).get_settings_xml()
xml.append('' % (cls.__name__))
return xml