290 lines
10 KiB
Python
290 lines
10 KiB
Python
# -*- coding: UTF-8 -*-
|
|
# * Copyright (C) 2011 Libor Zoubek
|
|
# *
|
|
# *
|
|
# * 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 2, 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; see the file COPYING. If not, write to
|
|
# * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
# * http://www.gnu.org/copyleft/gpl.html
|
|
# *
|
|
# */
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import traceback
|
|
|
|
import util
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), 'server'))
|
|
|
|
RESOLVERS = []
|
|
util.debug('%s searching for modules' % __name__)
|
|
for module in os.listdir(os.path.join(os.path.dirname(__file__), 'server')):
|
|
if module == '__init__.py' or module[-3:] != '.py':
|
|
continue
|
|
module = module[:-3]
|
|
exec('import %s' % module)
|
|
resolver = eval(module)
|
|
util.debug('found %s %s' % (resolver, dir(resolver)))
|
|
|
|
if not hasattr(resolver, '__priority__'):
|
|
resolver.__priority__ = 0
|
|
RESOLVERS.append(resolver)
|
|
del module
|
|
RESOLVERS = sorted(RESOLVERS, key=lambda m: -m.__priority__)
|
|
util.debug('done')
|
|
|
|
|
|
def item():
|
|
return {'name': '', 'url': '', 'quality': '???', 'surl': '', 'subs': '', 'headers': {}}
|
|
|
|
|
|
def resolve(url):
|
|
"""
|
|
resolves given url by asking all resolvers
|
|
|
|
returns None if no resolver advised to be able to resolve this url
|
|
returns False if resolver did his job, but did not return any value (thus failed)
|
|
returns Array of resolved objects in positive usecase
|
|
"""
|
|
url = util.decode_html(url)
|
|
util.info('Resolving ' + url)
|
|
resolver = _get_resolver(url)
|
|
value = None
|
|
if resolver is None:
|
|
return None
|
|
util.info('Using resolver \'%s\'' % str(resolver.__name__));
|
|
try:
|
|
value = resolver.resolve(url)
|
|
except:
|
|
traceback.print_exc()
|
|
if value is None:
|
|
return False
|
|
default = item()
|
|
|
|
def fix_stream(i, url, resolver, default):
|
|
""" fix missing but required values """
|
|
if 'name' not in list(i.keys()):
|
|
i['name'] = resolver.__name__
|
|
if 'surl' not in list(i.keys()):
|
|
i['surl'] = url
|
|
for key in list(default.keys()):
|
|
if key not in list(i.keys()):
|
|
i[key] = default[key]
|
|
|
|
[fix_stream(i, url, resolver, default) for i in value]
|
|
return sorted(value, key=lambda i: i['quality'])
|
|
|
|
|
|
def _get_resolver(url):
|
|
util.debug('Get resolver for ' + url)
|
|
for r in RESOLVERS:
|
|
util.debug('querying %s' % r)
|
|
if r.supports(url):
|
|
return r
|
|
|
|
|
|
def can_resolve(url):
|
|
""" Returns true if we are able to resolve stream by given URL """
|
|
return _get_resolver(url) is not None
|
|
|
|
|
|
def filter_resolvable(url):
|
|
if url.find('facebook') > 0 or url.find('yield') > 0:
|
|
return
|
|
return url.strip('\'\"')
|
|
|
|
|
|
def findstreams(data, regexes=None):
|
|
"""
|
|
Finds streams in given data. Respects caller add-on settings about
|
|
quality and asks user if necessary.
|
|
|
|
:param data: A string (piece of text / HTML code), an array of URLs or an
|
|
array of dictionaries, where 'url' key stores actual URL and
|
|
all other keys not present in item() are being copied to the
|
|
resolved stream dictionary
|
|
:param regexes: An array of strings - regular expressions, each MUST define
|
|
named group called 'url', which retrieves resolvable URL
|
|
(that one is passed to resolve operation); only used
|
|
with 'data' of type 'string'
|
|
:returns: An array of resolved objects, None if at least 1 resolver failed
|
|
to resolve and nothing else was found, an empty array if no
|
|
resolvers for URLs has been found or False if none of regexes
|
|
found anything
|
|
"""
|
|
|
|
def get_url(obj):
|
|
return obj['url'] if isinstance(obj, dict) else obj
|
|
|
|
urls = []
|
|
resolvables = []
|
|
resolved = []
|
|
not_found = False
|
|
if isinstance(data, str) and regexes:
|
|
for regex in regexes:
|
|
for match in re.finditer(regex, data, re.IGNORECASE | re.DOTALL):
|
|
urls.append(match.group('url'))
|
|
elif isinstance(data, list):
|
|
urls = data
|
|
else:
|
|
raise TypeError
|
|
for url in urls:
|
|
if isinstance(url, dict):
|
|
url['url'] = filter_resolvable(url['url'])
|
|
else:
|
|
url = filter_resolvable(url)
|
|
if url and url not in resolvables:
|
|
util.info('Found resolvable ' + get_url(url))
|
|
resolvables.append(url)
|
|
if len(resolvables) == 0:
|
|
util.info('No resolvables found!')
|
|
return False
|
|
for url in resolvables:
|
|
streams = resolve(get_url(url))
|
|
if streams is None:
|
|
util.info('No resolver found for ' + get_url(url))
|
|
not_found = True
|
|
elif not streams:
|
|
util.info('There was an error resolving ' + get_url(url))
|
|
elif len(streams) > 0:
|
|
for stream in streams:
|
|
if isinstance(url, dict):
|
|
for key in list(url.keys()):
|
|
if key not in stream:
|
|
stream[key] = url[key]
|
|
elif key not in item():
|
|
if isinstance(stream[key], str) and \
|
|
isinstance(url[key], str):
|
|
stream[key] = url[key] + ' +' + stream[key]
|
|
elif isinstance(stream[key], list) and \
|
|
isinstance(url[key], list):
|
|
stream[key] = url[key] + stream[key]
|
|
elif isinstance(stream[key], dict) and \
|
|
isinstance(url[key], dict):
|
|
stream[key].update(url[key])
|
|
resolved.append(stream)
|
|
if len(resolved) == 0:
|
|
if not_found:
|
|
return []
|
|
return None
|
|
resolved = sorted(resolved, key=lambda i: i['quality'])
|
|
resolved = sorted(resolved, key=lambda i: len(i['quality']))
|
|
resolved.reverse()
|
|
return resolved
|
|
|
|
|
|
q_map = {'3': '720p', '4': '480p', '5': '360p'}
|
|
|
|
|
|
def filter_by_quality(resolved, q):
|
|
util.info('filtering by quality setting ' + q)
|
|
if q == '0':
|
|
return resolved
|
|
sources = {}
|
|
ret = []
|
|
# first group streams by source url
|
|
for item in resolved:
|
|
if item['surl'] in list(sources.keys()):
|
|
sources[item['surl']].append(item)
|
|
else:
|
|
sources[item['surl']] = [item]
|
|
if q == '1':
|
|
# always return best quality from each source
|
|
for key in list(sources.keys()):
|
|
ret.append(sources[key][0])
|
|
elif q == '2':
|
|
# always return worse quality from each source
|
|
for key in list(sources.keys()):
|
|
ret.append(sources[key][-1])
|
|
else:
|
|
# we try to select sources of desired qualities
|
|
quality = q_map[q]
|
|
# 3,4,5 are 720,480,360
|
|
for key in list(sources.keys()):
|
|
added = False
|
|
for item in sources[key]:
|
|
if quality == item['quality']:
|
|
ret.append(item)
|
|
added = True
|
|
if not added:
|
|
util.debug('Desired quality %s not found, adding best found' % quality)
|
|
ret.append(sources[key][-1])
|
|
# sort results again, so best quality streams appear first
|
|
ret = sorted(ret, key=lambda i: i['quality'])
|
|
if not q == '2':
|
|
ret.reverse()
|
|
return ret
|
|
|
|
def filter_by_language(resolved, lang):
|
|
util.info('filtering by language setting ' + lang)
|
|
if lang == '0':
|
|
return resolved
|
|
ret = []
|
|
# first group streams by source url
|
|
for item in resolved:
|
|
if 'lang' in item and item['lang'] != '':
|
|
util.info(item)
|
|
if lang == '1' and re.match('en', item['lang'], re.IGNORECASE):
|
|
ret.append(item)
|
|
if lang == '2' and re.match('cs|cz|čeština', item['lang'], re.IGNORECASE):
|
|
ret.append(item)
|
|
return ret
|
|
|
|
def findstreams_multi(data, regexes):
|
|
"""
|
|
Finds streams in given data according to given regexes
|
|
respects caller addon's setting about desired quality, asks user if needed
|
|
assumes, that all resolvables need to be returned, but in particular quality
|
|
@param data piece of text (HTML code) to search in
|
|
@param regexes - array of strings - regular expressions, each MUST define named group called 'url'
|
|
which retrieves resolvable URL (that one is passsed to resolve operation)
|
|
@return array of dictionaries with keys: name,url,quality,surl
|
|
@return None if at least 1 resoler failed to resolve and nothing else has been found
|
|
@return [] if no resolvable URLs or no resolvers for URL has been found
|
|
"""
|
|
resolved = []
|
|
# keep list of found urls to aviod having duplicates
|
|
urls = []
|
|
error = False
|
|
for regex in regexes:
|
|
for match in re.finditer(regex, data, re.IGNORECASE | re.DOTALL):
|
|
print('Found resolvable %s ' % match.group('url'))
|
|
streams = resolve(match.group('url'))
|
|
if isinstance(streams, list) and streams:
|
|
util.debug('There was an error resolving ' + match.group('url'))
|
|
error = True
|
|
if streams is not None:
|
|
if len(streams) > 0:
|
|
for stream in streams:
|
|
resolved.append(stream)
|
|
if error and len(resolved) == 0:
|
|
return None
|
|
if len(resolved) == 0:
|
|
return []
|
|
resolved = sorted(resolved, key=lambda i: i['quality'])
|
|
resolved = sorted(resolved, key=lambda i: len(i['quality']))
|
|
resolved2 = resolved
|
|
resolved2.reverse()
|
|
qualities = {}
|
|
for item in resolved2:
|
|
if item['quality'] in list(qualities.keys()):
|
|
qualities[item['quality']].append(item)
|
|
else:
|
|
qualities[item['quality']] = [item]
|
|
# now .. we must sort items to be in same order as they were found on page
|
|
for q in list(qualities.keys()):
|
|
qualities[q] = sorted(qualities[q], key=lambda i: resolved.index(i))
|
|
return qualities
|