updates
This commit is contained in:
Executable
+12
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Appends the main plugin dir to the PYTHONPATH if an internal package cannot be imported.
|
||||
# Examples: In Plex Media Server all modules are under "Code.*" package, and in Enigma2 under "Plugins.Extensions.*"
|
||||
try:
|
||||
# from core import logger
|
||||
import core
|
||||
except:
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# Client for api.tvalacarta.info
|
||||
# ------------------------------------------------------------
|
||||
|
||||
import urllib
|
||||
|
||||
import config
|
||||
import jsontools
|
||||
import logger
|
||||
import scrapertools
|
||||
|
||||
MAIN_URL = ""
|
||||
API_KEY = "nzgJy84P9w54H2w"
|
||||
DEFAULT_HEADERS = [["User-Agent", config.PLUGIN_NAME + " " + config.get_platform()]]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------
|
||||
# Common function for API calls
|
||||
# ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Make a remote call using post, ensuring api key is here
|
||||
def remote_call(url, parameters={}, require_session=True):
|
||||
logger.info("url=" + url + ", parameters=" + repr(parameters))
|
||||
|
||||
if not url.startswith("http"):
|
||||
url = MAIN_URL + "/" + url
|
||||
|
||||
if not "api_key" in parameters:
|
||||
parameters["api_key"] = API_KEY
|
||||
|
||||
# Add session token if not here
|
||||
# if not "s" in parameters and require_session:
|
||||
# parameters["s"] = get_session_token()
|
||||
|
||||
headers = DEFAULT_HEADERS
|
||||
post = urllib.urlencode(parameters)
|
||||
|
||||
response_body = scrapertools.downloadpage(url, post, headers)
|
||||
|
||||
return jsontools.load(response_body)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------
|
||||
# Plugin service calls
|
||||
# ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
def plugins_get_all_packages():
|
||||
logger.info()
|
||||
|
||||
parameters = {"plugin": config.PLUGIN_NAME, "platform": config.get_platform()}
|
||||
return remote_call("plugins/get_all_packages.php", parameters)
|
||||
|
||||
|
||||
def plugins_get_latest_packages():
|
||||
logger.info()
|
||||
|
||||
parameters = {"plugin": config.PLUGIN_NAME, "platform": config.get_platform()}
|
||||
return remote_call("plugins/get_latest_packages.php", parameters)
|
||||
Executable
+318
@@ -0,0 +1,318 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# channeltools - Herramientas para trabajar con canales
|
||||
# ------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
import config
|
||||
import jsontools
|
||||
import logger
|
||||
|
||||
DEFAULT_UPDATE_URL = "/channels/"
|
||||
dict_channels_parameters = dict()
|
||||
|
||||
|
||||
def is_adult(channel_name):
|
||||
logger.info("channel_name=" + channel_name)
|
||||
channel_parameters = get_channel_parameters(channel_name)
|
||||
return channel_parameters["adult"]
|
||||
|
||||
|
||||
def get_channel_parameters(channel_name):
|
||||
global dict_channels_parameters
|
||||
|
||||
if channel_name not in dict_channels_parameters:
|
||||
try:
|
||||
channel_parameters = get_channel_json(channel_name)
|
||||
# logger.debug(channel_parameters)
|
||||
if channel_parameters:
|
||||
# cambios de nombres y valores por defecto
|
||||
channel_parameters["title"] = channel_parameters.pop("name")
|
||||
channel_parameters["channel"] = channel_parameters.pop("id")
|
||||
|
||||
# si no existe el key se declaran valor por defecto para que no de fallos en las funciones que lo llaman
|
||||
channel_parameters["update_url"] = channel_parameters.get("update_url", DEFAULT_UPDATE_URL)
|
||||
channel_parameters["language"] = channel_parameters.get("language", "all")
|
||||
channel_parameters["adult"] = channel_parameters.get("adult", False)
|
||||
channel_parameters["active"] = channel_parameters.get("active", False)
|
||||
channel_parameters["include_in_global_search"] = channel_parameters.get("include_in_global_search",
|
||||
False)
|
||||
channel_parameters["categories"] = channel_parameters.get("categories", list())
|
||||
|
||||
channel_parameters["thumbnail"] = channel_parameters.get("thumbnail", "")
|
||||
channel_parameters["banner"] = channel_parameters.get("banner", "")
|
||||
channel_parameters["fanart"] = channel_parameters.get("fanart", "")
|
||||
|
||||
# Imagenes: se admiten url y archivos locales dentro de "resources/images"
|
||||
if channel_parameters.get("thumbnail") and "://" not in channel_parameters["thumbnail"]:
|
||||
channel_parameters["thumbnail"] = os.path.join(config.get_runtime_path(), "resources", "media",
|
||||
"channels", "thumb", channel_parameters["thumbnail"])
|
||||
if channel_parameters.get("banner") and "://" not in channel_parameters["banner"]:
|
||||
channel_parameters["banner"] = os.path.join(config.get_runtime_path(), "resources", "media",
|
||||
"channels", "banner", channel_parameters["banner"])
|
||||
if channel_parameters.get("fanart") and "://" not in channel_parameters["fanart"]:
|
||||
channel_parameters["fanart"] = os.path.join(config.get_runtime_path(), "resources", "media",
|
||||
"channels", "fanart", channel_parameters["fanart"])
|
||||
|
||||
# Obtenemos si el canal tiene opciones de configuración
|
||||
channel_parameters["has_settings"] = False
|
||||
if 'settings' in channel_parameters:
|
||||
# if not isinstance(channel_parameters['settings'], list):
|
||||
# channel_parameters['settings'] = [channel_parameters['settings']]
|
||||
|
||||
# if "include_in_global_search" in channel_parameters['settings']:
|
||||
# channel_parameters["include_in_global_search"] = channel_parameters['settings']
|
||||
# ["include_in_global_search"].get('default', False)
|
||||
#
|
||||
# found = False
|
||||
# for el in channel_parameters['settings']:
|
||||
# for key in el.items():
|
||||
# if 'include_in' not in key:
|
||||
# channel_parameters["has_settings"] = True
|
||||
# found = True
|
||||
# break
|
||||
# if found:
|
||||
# break
|
||||
|
||||
for s in channel_parameters['settings']:
|
||||
if 'id' in s:
|
||||
if s['id'] == "include_in_global_search":
|
||||
channel_parameters["include_in_global_search"] = True
|
||||
elif not s['id'].startswith("include_in_") and \
|
||||
(s.get('enabled', False) or s.get('visible', False)):
|
||||
channel_parameters["has_settings"] = True
|
||||
|
||||
del channel_parameters['settings']
|
||||
|
||||
# Compatibilidad
|
||||
if 'compatible' in channel_parameters:
|
||||
# compatible python
|
||||
python_compatible = True
|
||||
if 'python' in channel_parameters["compatible"]:
|
||||
import sys
|
||||
python_condition = channel_parameters["compatible"]['python']
|
||||
if sys.version_info < tuple(map(int, (python_condition.split(".")))):
|
||||
python_compatible = False
|
||||
|
||||
# compatible addon_version
|
||||
addon_version_compatible = True
|
||||
if 'addon_version' in channel_parameters["compatible"]:
|
||||
import versiontools
|
||||
addon_version_condition = channel_parameters["compatible"]['addon_version']
|
||||
addon_version = int(addon_version_condition.replace(".", "").ljust(len(str(
|
||||
versiontools.get_current_plugin_version())), '0'))
|
||||
if versiontools.get_current_plugin_version() < addon_version:
|
||||
addon_version_compatible = False
|
||||
|
||||
channel_parameters["compatible"] = python_compatible and addon_version_compatible
|
||||
else:
|
||||
channel_parameters["compatible"] = True
|
||||
|
||||
dict_channels_parameters[channel_name] = channel_parameters
|
||||
|
||||
else:
|
||||
# para evitar casos donde canales no están definidos como configuración
|
||||
# lanzamos la excepcion y asi tenemos los valores básicos
|
||||
raise Exception
|
||||
|
||||
except Exception, ex:
|
||||
logger.error(channel_name + ".json error \n%s" % ex)
|
||||
channel_parameters = dict()
|
||||
channel_parameters["channel"] = ""
|
||||
channel_parameters["adult"] = False
|
||||
channel_parameters['active'] = False
|
||||
channel_parameters["compatible"] = True
|
||||
channel_parameters["language"] = ""
|
||||
channel_parameters["update_url"] = DEFAULT_UPDATE_URL
|
||||
return channel_parameters
|
||||
|
||||
return dict_channels_parameters[channel_name]
|
||||
|
||||
|
||||
def get_channel_json(channel_name):
|
||||
# logger.info("channel_name=" + channel_name)
|
||||
import filetools
|
||||
try:
|
||||
channel_path = filetools.join(config.get_runtime_path(), "channels", channel_name + ".json")
|
||||
# logger.info("channel_data=" + channel_path)
|
||||
channel_json = jsontools.load(filetools.read(channel_path))
|
||||
# logger.info("channel_json= %s" % channel_json)
|
||||
|
||||
except Exception, ex:
|
||||
template = "An exception of type %s occured. Arguments:\n%r"
|
||||
message = template % (type(ex).__name__, ex.args)
|
||||
logger.error(" %s" % message)
|
||||
channel_json = None
|
||||
|
||||
return channel_json
|
||||
|
||||
|
||||
def get_channel_controls_settings(channel_name):
|
||||
# logger.info("channel_name=" + channel_name)
|
||||
dict_settings = {}
|
||||
|
||||
list_controls = get_channel_json(channel_name).get('settings', list())
|
||||
|
||||
for c in list_controls:
|
||||
if 'id' not in c or 'type' not in c or 'default' not in c:
|
||||
# Si algun control de la lista no tiene id, type o default lo ignoramos
|
||||
continue
|
||||
|
||||
# new dict with key(id) and value(default) from settings
|
||||
dict_settings[c['id']] = c['default']
|
||||
|
||||
return list_controls, dict_settings
|
||||
|
||||
|
||||
def get_channel_setting(name, channel, default=None):
|
||||
"""
|
||||
Retorna el valor de configuracion del parametro solicitado.
|
||||
|
||||
Devuelve el valor del parametro 'name' en la configuracion propia del canal 'channel'.
|
||||
|
||||
Busca en la ruta \addon_data\plugin.video.alfa\settings_channels el archivo channel_data.json y lee
|
||||
el valor del parametro 'name'. Si el archivo channel_data.json no existe busca en la carpeta channels el archivo
|
||||
channel.json y crea un archivo channel_data.json antes de retornar el valor solicitado. Si el parametro 'name'
|
||||
tampoco existe en el el archivo channel.json se devuelve el parametro default.
|
||||
|
||||
|
||||
@param name: nombre del parametro
|
||||
@type name: str
|
||||
@param channel: nombre del canal
|
||||
@type channel: str
|
||||
@param default: valor devuelto en caso de que no exista el parametro name
|
||||
@type default: cualquiera
|
||||
|
||||
@return: El valor del parametro 'name'
|
||||
@rtype: El tipo del valor del parametro
|
||||
|
||||
"""
|
||||
file_settings = os.path.join(config.get_data_path(), "settings_channels", channel + "_data.json")
|
||||
dict_settings = {}
|
||||
dict_file = {}
|
||||
if os.path.exists(file_settings):
|
||||
# Obtenemos configuracion guardada de ../settings/channel_data.json
|
||||
try:
|
||||
dict_file = jsontools.load(open(file_settings, "rb").read())
|
||||
if isinstance(dict_file, dict) and 'settings' in dict_file:
|
||||
dict_settings = dict_file['settings']
|
||||
except EnvironmentError:
|
||||
logger.error("ERROR al leer el archivo: %s" % file_settings)
|
||||
|
||||
if not dict_settings or name not in dict_settings:
|
||||
# Obtenemos controles del archivo ../channels/channel.json
|
||||
try:
|
||||
list_controls, default_settings = get_channel_controls_settings(channel)
|
||||
except:
|
||||
default_settings = {}
|
||||
|
||||
if name in default_settings: # Si el parametro existe en el channel.json creamos el channel_data.json
|
||||
default_settings.update(dict_settings)
|
||||
dict_settings = default_settings
|
||||
dict_file['settings'] = dict_settings
|
||||
# Creamos el archivo ../settings/channel_data.json
|
||||
json_data = jsontools.dump(dict_file)
|
||||
try:
|
||||
open(file_settings, "wb").write(json_data)
|
||||
except EnvironmentError:
|
||||
logger.error("ERROR al salvar el archivo: %s" % file_settings)
|
||||
|
||||
# Devolvemos el valor del parametro local 'name' si existe, si no se devuelve default
|
||||
return dict_settings.get(name, default)
|
||||
|
||||
|
||||
def set_channel_setting(name, value, channel):
|
||||
"""
|
||||
Fija el valor de configuracion del parametro indicado.
|
||||
|
||||
Establece 'value' como el valor del parametro 'name' en la configuracion propia del canal 'channel'.
|
||||
Devuelve el valor cambiado o None si la asignacion no se ha podido completar.
|
||||
|
||||
Si se especifica el nombre del canal busca en la ruta \addon_data\plugin.video.alfa\settings_channels el
|
||||
archivo channel_data.json y establece el parametro 'name' al valor indicado por 'value'.
|
||||
Si el parametro 'name' no existe lo añade, con su valor, al archivo correspondiente.
|
||||
|
||||
@param name: nombre del parametro
|
||||
@type name: str
|
||||
@param value: valor del parametro
|
||||
@type value: str
|
||||
@param channel: nombre del canal
|
||||
@type channel: str
|
||||
|
||||
@return: 'value' en caso de que se haya podido fijar el valor y None en caso contrario
|
||||
@rtype: str, None
|
||||
|
||||
"""
|
||||
# Creamos la carpeta si no existe
|
||||
if not os.path.exists(os.path.join(config.get_data_path(), "settings_channels")):
|
||||
os.mkdir(os.path.join(config.get_data_path(), "settings_channels"))
|
||||
|
||||
file_settings = os.path.join(config.get_data_path(), "settings_channels", channel + "_data.json")
|
||||
dict_settings = {}
|
||||
|
||||
dict_file = None
|
||||
|
||||
if os.path.exists(file_settings):
|
||||
# Obtenemos configuracion guardada de ../settings/channel_data.json
|
||||
try:
|
||||
dict_file = jsontools.load(open(file_settings, "r").read())
|
||||
dict_settings = dict_file.get('settings', {})
|
||||
except EnvironmentError:
|
||||
logger.error("ERROR al leer el archivo: %s" % file_settings)
|
||||
|
||||
dict_settings[name] = value
|
||||
|
||||
# comprobamos si existe dict_file y es un diccionario, sino lo creamos
|
||||
if dict_file is None or not dict_file:
|
||||
dict_file = {}
|
||||
|
||||
dict_file['settings'] = dict_settings
|
||||
|
||||
# Creamos el archivo ../settings/channel_data.json
|
||||
try:
|
||||
json_data = jsontools.dump(dict_file)
|
||||
open(file_settings, "w").write(json_data)
|
||||
except EnvironmentError:
|
||||
logger.error("ERROR al salvar el archivo: %s" % file_settings)
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def get_channel_module(channel_name, package="channels"):
|
||||
# Sustituye al que hay en servertools.py ...
|
||||
# ...pero añade la posibilidad de incluir un paquete diferente de "channels"
|
||||
if "." not in channel_name:
|
||||
channel_module = __import__('%s.%s' % (package, channel_name), None, None, ['%s.%s' % (package, channel_name)])
|
||||
else:
|
||||
channel_module = __import__(channel_name, None, None, [channel_name])
|
||||
return channel_module
|
||||
|
||||
|
||||
def get_channel_remote_url(channel_name):
|
||||
channel_parameters = get_channel_parameters(channel_name)
|
||||
remote_channel_url = channel_parameters["update_url"] + channel_name + ".py"
|
||||
remote_version_url = channel_parameters["update_url"] + channel_name + ".json"
|
||||
|
||||
logger.info("remote_channel_url=" + remote_channel_url)
|
||||
logger.info("remote_version_url=" + remote_version_url)
|
||||
|
||||
return remote_channel_url, remote_version_url
|
||||
|
||||
|
||||
def get_channel_local_path(channel_name):
|
||||
if channel_name != "channelselector":
|
||||
local_channel_path = os.path.join(config.get_runtime_path(), 'channels', channel_name + ".py")
|
||||
local_version_path = os.path.join(config.get_runtime_path(), 'channels', channel_name + ".json")
|
||||
local_compiled_path = os.path.join(config.get_runtime_path(), 'channels', channel_name + ".pyo")
|
||||
else:
|
||||
local_channel_path = os.path.join(config.get_runtime_path(), channel_name + ".py")
|
||||
local_version_path = os.path.join(config.get_runtime_path(), channel_name + ".json")
|
||||
local_compiled_path = os.path.join(config.get_runtime_path(), channel_name + ".pyo")
|
||||
|
||||
logger.info("local_channel_path=" + local_channel_path)
|
||||
logger.info("local_version_path=" + local_version_path)
|
||||
logger.info("local_compiled_path=" + local_compiled_path)
|
||||
|
||||
return local_channel_path, local_version_path, local_compiled_path
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Cloudflare decoder
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
import time
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
from core import logger
|
||||
|
||||
|
||||
class Cloudflare:
|
||||
def __init__(self, response):
|
||||
self.timeout = 5
|
||||
self.domain = urlparse.urlparse(response["url"])[1]
|
||||
self.protocol = urlparse.urlparse(response["url"])[0]
|
||||
self.js_data = {}
|
||||
self.header_data = {}
|
||||
|
||||
if not "var s,t,o,p,b,r,e,a,k,i,n,g,f" in response["data"] or "chk_jschl" in response["url"]:
|
||||
return
|
||||
|
||||
try:
|
||||
self.js_data["auth_url"] = \
|
||||
re.compile('<form id="challenge-form" action="([^"]+)" method="get">').findall(response["data"])[0]
|
||||
self.js_data["params"] = {}
|
||||
self.js_data["params"]["jschl_vc"] = \
|
||||
re.compile('<input type="hidden" name="jschl_vc" value="([^"]+)"/>').findall(response["data"])[0]
|
||||
self.js_data["params"]["pass"] = \
|
||||
re.compile('<input type="hidden" name="pass" value="([^"]+)"/>').findall(response["data"])[0]
|
||||
var, self.js_data["value"] = \
|
||||
re.compile('var s,t,o,p,b,r,e,a,k,i,n,g,f[^:]+"([^"]+)":([^\n]+)};', re.DOTALL).findall(response["data"])[0]
|
||||
self.js_data["op"] = re.compile(var + "([\+|\-|\*|\/])=([^;]+)", re.MULTILINE).findall(response["data"])
|
||||
self.js_data["wait"] = int(re.compile("\}, ([\d]+)\);", re.MULTILINE).findall(response["data"])[0]) / 1000
|
||||
except:
|
||||
logger.debug("Metodo #1 (javascript): NO disponible")
|
||||
self.js_data = {}
|
||||
|
||||
if "refresh" in response["headers"]:
|
||||
try:
|
||||
self.header_data["wait"] = int(response["headers"]["refresh"].split(";")[0])
|
||||
self.header_data["auth_url"] = response["headers"]["refresh"].split("=")[1].split("?")[0]
|
||||
self.header_data["params"] = {}
|
||||
self.header_data["params"]["pass"] = response["headers"]["refresh"].split("=")[2]
|
||||
except:
|
||||
logger.debug("Metodo #2 (headers): NO disponible")
|
||||
self.header_data = {}
|
||||
|
||||
@property
|
||||
def wait_time(self):
|
||||
if self.js_data.get("wait", 0):
|
||||
return self.js_data["wait"]
|
||||
else:
|
||||
return self.header_data.get("wait", 0)
|
||||
|
||||
@property
|
||||
def is_cloudflare(self):
|
||||
return self.header_data.get("wait", 0) > 0 or self.js_data.get("wait", 0) > 0
|
||||
|
||||
def get_url(self):
|
||||
# Metodo #1 (javascript)
|
||||
if self.js_data.get("wait", 0):
|
||||
jschl_answer = self.decode(self.js_data["value"])
|
||||
|
||||
for op, v in self.js_data["op"]:
|
||||
jschl_answer = eval(str(jschl_answer) + op + str(self.decode(v)))
|
||||
|
||||
self.js_data["params"]["jschl_answer"] = jschl_answer + len(self.domain)
|
||||
|
||||
response = "%s://%s%s?%s" % (
|
||||
self.protocol, self.domain, self.js_data["auth_url"], urllib.urlencode(self.js_data["params"]))
|
||||
|
||||
time.sleep(self.js_data["wait"])
|
||||
|
||||
return response
|
||||
|
||||
# Metodo #2 (headers)
|
||||
if self.header_data.get("wait", 0):
|
||||
response = "%s://%s%s?%s" % (
|
||||
self.protocol, self.domain, self.header_data["auth_url"], urllib.urlencode(self.header_data["params"]))
|
||||
|
||||
time.sleep(self.header_data["wait"])
|
||||
|
||||
return response
|
||||
|
||||
def decode(self, data):
|
||||
t = time.time()
|
||||
timeout = False
|
||||
|
||||
while not timeout:
|
||||
data = re.sub("\[\]", "''", data)
|
||||
data = re.sub("!\+''", "+1", data)
|
||||
data = re.sub("!''", "0", data)
|
||||
data = re.sub("!0", "1", data)
|
||||
|
||||
if "(" in data:
|
||||
x, y = data.rfind("("), data.find(")", data.rfind("(")) + 1
|
||||
part = data[x + 1:y - 1]
|
||||
else:
|
||||
x = 0
|
||||
y = len(data)
|
||||
part = data
|
||||
|
||||
val = ""
|
||||
|
||||
if not part.startswith("+"): part = "+" + part
|
||||
|
||||
for i, ch in enumerate(part):
|
||||
if ch == "+":
|
||||
if not part[i + 1] == "'":
|
||||
if val == "": val = 0
|
||||
if type(val) == str:
|
||||
val = val + self.get_number(part, i + 1)
|
||||
else:
|
||||
val = val + int(self.get_number(part, i + 1))
|
||||
else:
|
||||
val = str(val)
|
||||
val = val + self.get_number(part, i + 1) or "0"
|
||||
|
||||
if type(val) == str: val = "'%s'" % val
|
||||
data = data[0:x] + str(val) + data[y:]
|
||||
|
||||
timeout = time.time() - t > self.timeout
|
||||
|
||||
if not "+" in data and not "(" in data and not ")" in data:
|
||||
return int(self.get_number(data))
|
||||
|
||||
def get_number(self, str, start=0):
|
||||
ret = ""
|
||||
for chr in str[start:]:
|
||||
try:
|
||||
int(chr)
|
||||
except:
|
||||
if ret: break
|
||||
else:
|
||||
ret += chr
|
||||
return ret
|
||||
Executable
+473
@@ -0,0 +1,473 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# Parámetros de configuración (kodi)
|
||||
# ------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
PLUGIN_NAME = "alfa"
|
||||
|
||||
__settings__ = xbmcaddon.Addon(id="plugin.video." + PLUGIN_NAME)
|
||||
__language__ = __settings__.getLocalizedString
|
||||
|
||||
|
||||
def get_platform(full_version=False):
|
||||
"""
|
||||
Devuelve la información la version de xbmc o kodi sobre el que se ejecuta el plugin
|
||||
|
||||
@param full_version: indica si queremos toda la informacion o no
|
||||
@type full_version: bool
|
||||
@rtype: str o dict
|
||||
@return: Si el paramentro full_version es True se retorna un diccionario con las siguientes claves:
|
||||
'num_version': (float) numero de version en formato XX.X
|
||||
'name_version': (str) nombre clave de cada version
|
||||
'video_db': (str) nombre del archivo que contiene la base de datos de videos
|
||||
'plaform': (str) esta compuesto por "kodi-" o "xbmc-" mas el nombre de la version segun corresponda.
|
||||
Si el parametro full_version es False (por defecto) se retorna el valor de la clave 'plaform' del diccionario anterior.
|
||||
"""
|
||||
|
||||
ret = {}
|
||||
codename = {"10": "dharma", "11": "eden", "12": "frodo",
|
||||
"13": "gotham", "14": "helix", "15": "isengard",
|
||||
"16": "jarvis", "17": "krypton", "18": "leia"}
|
||||
code_db = {'10': 'MyVideos37.db', '11': 'MyVideos60.db', '12': 'MyVideos75.db',
|
||||
'13': 'MyVideos78.db', '14': 'MyVideos90.db', '15': 'MyVideos93.db',
|
||||
'16': 'MyVideos99.db', '17': 'MyVideos107.db', '18': 'MyVideos108.db'}
|
||||
|
||||
num_version = xbmc.getInfoLabel('System.BuildVersion')
|
||||
num_version = re.match("\d+\.\d+", num_version).group(0)
|
||||
ret['name_version'] = codename.get(num_version.split('.')[0], num_version)
|
||||
ret['video_db'] = code_db.get(num_version.split('.')[0], "")
|
||||
ret['num_version'] = float(num_version)
|
||||
if ret['num_version'] < 14:
|
||||
ret['platform'] = "xbmc-" + ret['name_version']
|
||||
else:
|
||||
ret['platform'] = "kodi-" + ret['name_version']
|
||||
|
||||
if full_version:
|
||||
return ret
|
||||
else:
|
||||
return ret['platform']
|
||||
|
||||
|
||||
def is_xbmc():
|
||||
return True
|
||||
|
||||
|
||||
def get_videolibrary_support():
|
||||
return True
|
||||
|
||||
|
||||
def get_system_platform():
|
||||
""" fonction: pour recuperer la platform que xbmc tourne """
|
||||
platform = "unknown"
|
||||
if xbmc.getCondVisibility("system.platform.linux"):
|
||||
platform = "linux"
|
||||
elif xbmc.getCondVisibility("system.platform.windows"):
|
||||
platform = "windows"
|
||||
elif xbmc.getCondVisibility("system.platform.osx"):
|
||||
platform = "osx"
|
||||
return platform
|
||||
|
||||
|
||||
def get_all_settings_addon():
|
||||
# Lee el archivo settings.xml y retorna un diccionario con {id: value}
|
||||
import scrapertools
|
||||
|
||||
infile = open(os.path.join(get_data_path(), "settings.xml"), "r")
|
||||
data = infile.read()
|
||||
infile.close()
|
||||
|
||||
ret = {}
|
||||
matches = scrapertools.find_multiple_matches(data, '<setting id="([^"]*)" value="([^"]*)')
|
||||
|
||||
for _id, value in matches:
|
||||
ret[_id] = get_setting(_id)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def open_settings():
|
||||
settings_pre = get_all_settings_addon()
|
||||
__settings__.openSettings()
|
||||
settings_post = get_all_settings_addon()
|
||||
|
||||
# cb_validate_config (util para validar cambios realizados en el cuadro de dialogo)
|
||||
if settings_post.get('adult_aux_intro_password', None):
|
||||
# Hemos accedido a la seccion de Canales para adultos
|
||||
from platformcode import platformtools
|
||||
if 'adult_password' not in settings_pre:
|
||||
adult_password = set_setting('adult_password', '0000')
|
||||
else:
|
||||
adult_password = settings_pre['adult_password']
|
||||
|
||||
if settings_post['adult_aux_intro_password'] == adult_password:
|
||||
# La contraseña de acceso es correcta
|
||||
|
||||
# Cambio de contraseña
|
||||
if settings_post['adult_aux_new_password1']:
|
||||
if settings_post['adult_aux_new_password1'] == settings_post['adult_aux_new_password2']:
|
||||
adult_password = set_setting('adult_password', settings_post['adult_aux_new_password1'])
|
||||
else:
|
||||
platformtools.dialog_ok("Canales para adultos",
|
||||
"Los campos 'Nueva contraseña' y 'Confirmar nueva contraseña' no coinciden.",
|
||||
"Entre de nuevo en 'Preferencias' para cambiar la contraseña")
|
||||
|
||||
# Fijar adult_pin
|
||||
adult_pin = ""
|
||||
if settings_post["adult_request_password"] == True:
|
||||
adult_pin = adult_password
|
||||
set_setting("adult_pin", adult_pin)
|
||||
|
||||
else:
|
||||
platformtools.dialog_ok("Canales para adultos", "La contraseña no es correcta.",
|
||||
"Los cambios realizados en esta sección no se guardaran.")
|
||||
|
||||
# Deshacer cambios
|
||||
set_setting("adult_mode", settings_pre.get("adult_mode", 0))
|
||||
set_setting("adult_request_password", settings_pre.get("adult_request_password", True))
|
||||
|
||||
# Borramos settings auxiliares
|
||||
set_setting('adult_aux_intro_password', '')
|
||||
set_setting('adult_aux_new_password1', '')
|
||||
set_setting('adult_aux_new_password2', '')
|
||||
|
||||
# si se ha cambiado la ruta de la videoteca llamamos a comprobar directorios para que lo cree y pregunte
|
||||
# automaticamente si configurar la videoteca
|
||||
if settings_pre.get("videolibrarypath", None) != settings_post.get("videolibrarypath", None) or \
|
||||
settings_pre.get("folder_movies", None) != settings_post.get("folder_movies", None) or \
|
||||
settings_pre.get("folder_tvshows", None) != settings_post.get("folder_tvshows", None):
|
||||
verify_directories_created()
|
||||
|
||||
else:
|
||||
# si se ha puesto que se quiere autoconfigurar y se había creado el directorio de la videoteca
|
||||
if not settings_pre.get("videolibrary_kodi", None) and settings_post.get("videolibrary_kodi", None) \
|
||||
and settings_post.get("videolibrary_kodi_flag", None) == 1:
|
||||
|
||||
from platformcode import xbmc_videolibrary
|
||||
xbmc_videolibrary.ask_set_content(2, silent=True)
|
||||
|
||||
|
||||
def get_setting(name, channel="", server="", default=None):
|
||||
"""
|
||||
Retorna el valor de configuracion del parametro solicitado.
|
||||
|
||||
Devuelve el valor del parametro 'name' en la configuracion global, en la configuracion propia del canal 'channel'
|
||||
o en la del servidor 'server'.
|
||||
|
||||
Los parametros channel y server no deben usarse simultaneamente. Si se especifica el nombre del canal se devolvera
|
||||
el resultado de llamar a channeltools.get_channel_setting(name, channel, default). Si se especifica el nombre del
|
||||
servidor se devolvera el resultado de llamar a servertools.get_channel_setting(name, server, default). Si no se
|
||||
especifica ninguno de los anteriores se devolvera el valor del parametro en la configuracion global si existe o
|
||||
el valor default en caso contrario.
|
||||
|
||||
@param name: nombre del parametro
|
||||
@type name: str
|
||||
@param channel: nombre del canal
|
||||
@type channel: str
|
||||
@param server: nombre del servidor
|
||||
@type server: str
|
||||
@param default: valor devuelto en caso de que no exista el parametro name
|
||||
@type default: cualquiera
|
||||
|
||||
@return: El valor del parametro 'name'
|
||||
@rtype: El tipo del valor del parametro
|
||||
|
||||
"""
|
||||
|
||||
# Specific channel setting
|
||||
if channel:
|
||||
# logger.info("config.get_setting reading channel setting '"+name+"' from channel json")
|
||||
from core import channeltools
|
||||
value = channeltools.get_channel_setting(name, channel, default)
|
||||
# logger.info("config.get_setting -> '"+repr(value)+"'")
|
||||
return value
|
||||
|
||||
# Specific server setting
|
||||
elif server:
|
||||
# logger.info("config.get_setting reading server setting '"+name+"' from server json")
|
||||
from core import servertools
|
||||
value = servertools.get_server_setting(name, server, default)
|
||||
# logger.info("config.get_setting -> '"+repr(value)+"'")
|
||||
return value
|
||||
|
||||
# Global setting
|
||||
else:
|
||||
# logger.info("config.get_setting reading main setting '"+name+"'")
|
||||
value = __settings__.getSetting(name)
|
||||
if not value:
|
||||
return default
|
||||
# Translate Path if start with "special://"
|
||||
if value.startswith("special://") and "videolibrarypath" not in name:
|
||||
value = xbmc.translatePath(value)
|
||||
|
||||
# hack para devolver el tipo correspondiente
|
||||
settings_types = get_settings_types()
|
||||
|
||||
if settings_types.get(name) in ['enum', 'number']:
|
||||
try:
|
||||
value = int(value)
|
||||
except Exception, ex:
|
||||
from core import logger
|
||||
logger.error("Error al convertir '%s' de tipo 'enum','number' \n%s" % (name, ex))
|
||||
|
||||
elif settings_types.get(name) == 'bool':
|
||||
value = value == 'true'
|
||||
|
||||
elif name not in settings_types:
|
||||
try:
|
||||
if value in ['true', 'false']:
|
||||
if value == 'true':
|
||||
aux_val = True
|
||||
else:
|
||||
aux_val = False
|
||||
value = bool(aux_val)
|
||||
else:
|
||||
t = eval(value)
|
||||
value = t[0](t[1])
|
||||
except Exception, ex:
|
||||
from core import logger
|
||||
logger.error("Error al convertir '%s' se pasa como tipo 'None'\n%s" % (name, ex))
|
||||
value = None
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def set_setting(name, value, channel="", server=""):
|
||||
"""
|
||||
Fija el valor de configuracion del parametro indicado.
|
||||
|
||||
Establece 'value' como el valor del parametro 'name' en la configuracion global o en la configuracion propia del
|
||||
canal 'channel'.
|
||||
Devuelve el valor cambiado o None si la asignacion no se ha podido completar.
|
||||
|
||||
Si se especifica el nombre del canal busca en la ruta \addon_data\plugin.video.alfa\settings_channels el
|
||||
archivo channel_data.json y establece el parametro 'name' al valor indicado por 'value'. Si el archivo
|
||||
channel_data.json no existe busca en la carpeta channels el archivo channel.json y crea un archivo channel_data.json
|
||||
antes de modificar el parametro 'name'.
|
||||
Si el parametro 'name' no existe lo añade, con su valor, al archivo correspondiente.
|
||||
|
||||
|
||||
Parametros:
|
||||
name -- nombre del parametro
|
||||
value -- valor del parametro
|
||||
channel [opcional] -- nombre del canal
|
||||
|
||||
Retorna:
|
||||
'value' en caso de que se haya podido fijar el valor y None en caso contrario
|
||||
|
||||
"""
|
||||
if channel:
|
||||
from core import channeltools
|
||||
return channeltools.set_channel_setting(name, value, channel)
|
||||
elif server:
|
||||
from core import servertools
|
||||
return servertools.set_server_setting(name, value, server)
|
||||
else:
|
||||
try:
|
||||
settings_types = get_settings_types()
|
||||
|
||||
if settings_types.get(name) == 'bool':
|
||||
if value:
|
||||
new_value = "true"
|
||||
else:
|
||||
new_value = "false"
|
||||
|
||||
elif settings_types.get(name):
|
||||
new_value = str(value)
|
||||
|
||||
else:
|
||||
if isinstance(value, basestring):
|
||||
new_value = "(%s, %s)" % (type(value).__name__, repr(value))
|
||||
|
||||
else:
|
||||
new_value = "(%s, %s)" % (type(value).__name__, value)
|
||||
|
||||
__settings__.setSetting(name, new_value)
|
||||
|
||||
except Exception, ex:
|
||||
from core import logger
|
||||
logger.error("Error al convertir '%s' no se guarda el valor \n%s" % (name, ex))
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def get_settings_types():
|
||||
"""
|
||||
Devuelve un diccionario con los parametros (key) de la configuracion global y sus tipos (value)
|
||||
|
||||
:return: dict
|
||||
"""
|
||||
win10000 = xbmcgui.Window(10000)
|
||||
settings_types = win10000.getProperty(PLUGIN_NAME + "_settings_types")
|
||||
|
||||
if not settings_types:
|
||||
infile = open(os.path.join(get_runtime_path(), "resources", "settings.xml"))
|
||||
data = infile.read()
|
||||
infile.close()
|
||||
|
||||
matches = re.findall('<setting id="([^"]*)" type="([^"]*)', data)
|
||||
settings_types = "{%s}" % ",".join("'%s': '%s'" % tup for tup in matches)
|
||||
|
||||
win10000.setProperty(PLUGIN_NAME + "_settings_types", settings_types)
|
||||
|
||||
return eval(settings_types)
|
||||
|
||||
|
||||
def get_localized_string(code):
|
||||
dev = __language__(code)
|
||||
|
||||
try:
|
||||
dev = dev.encode("utf-8")
|
||||
except:
|
||||
pass
|
||||
|
||||
return dev
|
||||
|
||||
|
||||
def get_videolibrary_config_path():
|
||||
value = get_setting("videolibrarypath")
|
||||
if value == "":
|
||||
verify_directories_created()
|
||||
value = get_setting("videolibrarypath")
|
||||
return value
|
||||
|
||||
|
||||
def get_videolibrary_path():
|
||||
return xbmc.translatePath(get_videolibrary_config_path())
|
||||
|
||||
|
||||
def get_temp_file(filename):
|
||||
return xbmc.translatePath(os.path.join("special://temp/", filename))
|
||||
|
||||
|
||||
def get_runtime_path():
|
||||
return xbmc.translatePath(__settings__.getAddonInfo('Path'))
|
||||
|
||||
|
||||
def get_data_path():
|
||||
dev = xbmc.translatePath(__settings__.getAddonInfo('Profile'))
|
||||
|
||||
# Crea el directorio si no existe
|
||||
if not os.path.exists(dev):
|
||||
os.makedirs(dev)
|
||||
|
||||
return dev
|
||||
|
||||
|
||||
def get_cookie_data():
|
||||
import os
|
||||
ficherocookies = os.path.join(get_data_path(), 'cookies.dat')
|
||||
|
||||
cookiedatafile = open(ficherocookies, 'r')
|
||||
cookiedata = cookiedatafile.read()
|
||||
cookiedatafile.close()
|
||||
|
||||
return cookiedata
|
||||
|
||||
|
||||
# Test if all the required directories are created
|
||||
def verify_directories_created():
|
||||
from core import logger
|
||||
from core import filetools
|
||||
from platformcode import xbmc_videolibrary
|
||||
|
||||
config_paths = [["videolibrarypath", "videolibrary"],
|
||||
["downloadpath", "downloads"],
|
||||
["downloadlistpath", "downloads/list"],
|
||||
["settings_path", "settings_channels"]]
|
||||
|
||||
for path, default in config_paths:
|
||||
saved_path = get_setting(path)
|
||||
|
||||
# videoteca
|
||||
if path == "videolibrarypath":
|
||||
if not saved_path:
|
||||
saved_path = xbmc_videolibrary.search_library_path()
|
||||
if saved_path:
|
||||
set_setting(path, saved_path)
|
||||
|
||||
if not saved_path:
|
||||
saved_path = "special://profile/addon_data/plugin.video." + PLUGIN_NAME + "/" + default
|
||||
set_setting(path, saved_path)
|
||||
|
||||
saved_path = xbmc.translatePath(saved_path)
|
||||
if not filetools.exists(saved_path):
|
||||
logger.debug("Creating %s: %s" % (path, saved_path))
|
||||
filetools.mkdir(saved_path)
|
||||
|
||||
config_paths = [["folder_movies", "CINE"],
|
||||
["folder_tvshows", "SERIES"]]
|
||||
|
||||
flag_call = True
|
||||
for path, default in config_paths:
|
||||
saved_path = get_setting(path)
|
||||
|
||||
if not saved_path:
|
||||
saved_path = default
|
||||
set_setting(path, saved_path)
|
||||
|
||||
content_path = filetools.join(get_videolibrary_path(), saved_path)
|
||||
if not filetools.exists(content_path):
|
||||
logger.debug("Creating %s: %s" % (path, content_path))
|
||||
|
||||
# si se crea el directorio
|
||||
if filetools.mkdir(content_path):
|
||||
if flag_call:
|
||||
# le pasamos el valor para que sepamos que se ha pasado por creación de directorio
|
||||
xbmc_videolibrary.ask_set_content(1)
|
||||
flag_call = False
|
||||
|
||||
try:
|
||||
from core import scrapertools
|
||||
# Buscamos el archivo addon.xml del skin activo
|
||||
skindir = filetools.join(xbmc.translatePath("special://home"), 'addons', xbmc.getSkinDir(),
|
||||
'addon.xml')
|
||||
# Extraemos el nombre de la carpeta de resolución por defecto
|
||||
folder = ""
|
||||
data = filetools.read(skindir)
|
||||
res = scrapertools.find_multiple_matches(data, '(<res .*?>)')
|
||||
for r in res:
|
||||
if 'default="true"' in r:
|
||||
folder = scrapertools.find_single_match(r, 'folder="([^"]+)"')
|
||||
break
|
||||
|
||||
# Comprobamos si existe en el addon y sino es así, la creamos
|
||||
default = filetools.join(get_runtime_path(), 'resources', 'skins', 'Default')
|
||||
if folder and not filetools.exists(filetools.join(default, folder)):
|
||||
filetools.mkdir(filetools.join(default, folder))
|
||||
|
||||
# Copiamos el archivo a dicha carpeta desde la de 720p si éste no existe o si el tamaño es diferente
|
||||
if folder and folder != '720p':
|
||||
for root, folders, files in filetools.walk(filetools.join(default, '720p')):
|
||||
for f in files:
|
||||
if not filetools.exists(filetools.join(default, folder, f)) or \
|
||||
(filetools.getsize(filetools.join(default, folder, f)) !=
|
||||
filetools.getsize(filetools.join(default, '720p', f))):
|
||||
filetools.copy(filetools.join(default, '720p', f),
|
||||
filetools.join(default, folder, f),
|
||||
True)
|
||||
except:
|
||||
import traceback
|
||||
logger.error("Al comprobar o crear la carpeta de resolución")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def get_thumb(thumb_name):
|
||||
path = os.path.join(get_runtime_path(), "resources", "media", "general")
|
||||
|
||||
# if config.get_setting("icons"): # TODO obtener de la configuración el pack de thumbs seleccionado
|
||||
# preferred_thumb = config.get_setting("icons")
|
||||
# else:
|
||||
# preferred_thumb = os.sep + "default"
|
||||
|
||||
preferred_thumb = os.sep + "default"
|
||||
web_path = path + preferred_thumb + os.sep
|
||||
|
||||
return os.path.join(web_path, thumb_name)
|
||||
Executable
+546
@@ -0,0 +1,546 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Clase Downloader
|
||||
Downloader(url, path [, filename, headers, resume])
|
||||
|
||||
url : string - url para descargar
|
||||
path : string - Directorio donde se guarda la descarga
|
||||
filename : [opt] string - Nombre de archivo para guardar
|
||||
headers : [opt] dict - Headers para usar en la descarga
|
||||
resume : [opt] bool - continuar una descarga previa en caso de existir, por defecto True
|
||||
|
||||
|
||||
metodos:
|
||||
start_dialog() Inicia la descarga mostrando el progreso
|
||||
start() Inicia la descarga en segundo plano
|
||||
stop(erase = False) Detiene la descarga, con erase = True elimina los datos descargados
|
||||
|
||||
"""
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
import urlparse
|
||||
from threading import Thread, Lock
|
||||
|
||||
from core import filetools
|
||||
from core import logger
|
||||
|
||||
|
||||
class Downloader:
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def connections(self):
|
||||
return len([c for c in self._download_info["parts"] if
|
||||
c["status"] in [self.states.downloading, self.states.connecting]]), self._max_connections
|
||||
|
||||
@property
|
||||
def downloaded(self):
|
||||
return self.__change_units__(sum([c["current"] - c["start"] for c in self._download_info["parts"]]))
|
||||
|
||||
@property
|
||||
def average_speed(self):
|
||||
return self.__change_units__(self._average_speed)
|
||||
|
||||
@property
|
||||
def speed(self):
|
||||
return self.__change_units__(self._speed)
|
||||
|
||||
@property
|
||||
def remaining_time(self):
|
||||
if self.speed[0] and self._file_size:
|
||||
t = (self.size[0] - self.downloaded[0]) / self.speed[0]
|
||||
else:
|
||||
t = 0
|
||||
|
||||
return time.strftime("%H:%M:%S", time.gmtime(t))
|
||||
|
||||
@property
|
||||
def download_url(self):
|
||||
return self.url
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.__change_units__(self._file_size)
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
if self._file_size:
|
||||
return float(self.downloaded[0]) * 100 / float(self._file_size)
|
||||
elif self._state == self.states.completed:
|
||||
return 100
|
||||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self._filename
|
||||
|
||||
@property
|
||||
def fullpath(self):
|
||||
return os.path.abspath(filetools.join(self._path, self._filename))
|
||||
|
||||
# Funciones
|
||||
def start_dialog(self, title="Descargando..."):
|
||||
from platformcode import platformtools
|
||||
progreso = platformtools.dialog_progress(title, "Iniciando descarga...")
|
||||
self.start()
|
||||
while self.state == self.states.downloading and not progreso.iscanceled():
|
||||
time.sleep(0.1)
|
||||
line1 = "%s" % (self.filename)
|
||||
line2 = "%.2f%% - %.2f %s de %.2f %s a %.2f %s/s (%d/%d)" % (
|
||||
self.progress, self.downloaded[1], self.downloaded[2], self.size[1], self.size[2],
|
||||
self.speed[1], self.speed[2], self.connections[0], self.connections[1])
|
||||
line3 = "Tiempo restante: %s" % (self.remaining_time)
|
||||
|
||||
progreso.update(int(self.progress), line1, line2, line3)
|
||||
if self.state == self.states.downloading:
|
||||
self.stop()
|
||||
progreso.close()
|
||||
|
||||
def start(self):
|
||||
if self._state == self.states.error: return
|
||||
conns = []
|
||||
for x in range(self._max_connections):
|
||||
try:
|
||||
conns.append(self.__open_connection__("0", ""))
|
||||
except:
|
||||
self._max_connections = x
|
||||
self._threads = [
|
||||
Thread(target=self.__start_part__, name="Downloader %s/%s" % (x + 1, self._max_connections)) for x
|
||||
in range(self._max_connections)]
|
||||
break
|
||||
del conns
|
||||
self._start_time = time.time() - 1
|
||||
self._state = self.states.downloading
|
||||
self._speed_thread.start()
|
||||
self._save_thread.start()
|
||||
|
||||
for t in self._threads: t.start()
|
||||
|
||||
def stop(self, erase=False):
|
||||
if self._state == self.states.downloading:
|
||||
# Detenemos la descarga
|
||||
self._state = self.states.stopped
|
||||
for t in self._threads:
|
||||
if t.isAlive(): t.join()
|
||||
|
||||
if self._save_thread.isAlive(): self._save_thread.join()
|
||||
|
||||
if self._seekable:
|
||||
# Guardamos la info al final del archivo
|
||||
self.file.seek(0, 2)
|
||||
offset = self.file.tell()
|
||||
self.file.write(str(self._download_info))
|
||||
self.file.write("%0.16d" % offset)
|
||||
|
||||
self.file.close()
|
||||
|
||||
if erase: os.remove(filetools.join(self._path, self._filename))
|
||||
|
||||
def __speed_metter__(self):
|
||||
self._speed = 0
|
||||
self._average_speed = 0
|
||||
|
||||
downloaded = self._start_downloaded
|
||||
downloaded2 = self._start_downloaded
|
||||
t = time.time()
|
||||
t2 = time.time()
|
||||
time.sleep(1)
|
||||
|
||||
while self.state == self.states.downloading:
|
||||
self._average_speed = (self.downloaded[0] - self._start_downloaded) / (time.time() - self._start_time)
|
||||
self._speed = (self.downloaded[0] - self._start_downloaded) / (time.time() - self._start_time)
|
||||
# self._speed = (self.downloaded[0] - downloaded) / (time.time() -t)
|
||||
|
||||
if time.time() - t > 5:
|
||||
t = t2
|
||||
downloaded = downloaded2
|
||||
t2 = time.time()
|
||||
downloaded2 = self.downloaded[0]
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# Funciones internas
|
||||
def __init__(self, url, path, filename=None, headers=[], resume=True, max_connections=10, block_size=2 ** 17,
|
||||
part_size=2 ** 24, max_buffer=10):
|
||||
# Parametros
|
||||
self._resume = resume
|
||||
self._path = path
|
||||
self._filename = filename
|
||||
self._max_connections = max_connections
|
||||
self._block_size = block_size
|
||||
self._part_size = part_size
|
||||
self._max_buffer = max_buffer
|
||||
|
||||
try:
|
||||
import xbmc
|
||||
self.tmp_path = xbmc.translatePath("special://temp/")
|
||||
except:
|
||||
self.tmp_path = os.getenv("TEMP") or os.getenv("TMP") or os.getenv("TMPDIR")
|
||||
|
||||
self.states = type('states', (),
|
||||
{"stopped": 0, "connecting": 1, "downloading": 2, "completed": 3, "error": 4, "saving": 5})
|
||||
|
||||
self._state = self.states.stopped
|
||||
self._download_lock = Lock()
|
||||
self._headers = {
|
||||
"User-Agent": "Kodi/15.2 (Windows NT 10.0; WOW64) App_Bitness/32 Version/15.2-Git:20151019-02e7013"}
|
||||
self._speed = 0
|
||||
self._buffer = {}
|
||||
self._seekable = True
|
||||
|
||||
self._threads = [Thread(target=self.__start_part__, name="Downloader %s/%s" % (x + 1, self._max_connections))
|
||||
for x in range(self._max_connections)]
|
||||
self._speed_thread = Thread(target=self.__speed_metter__, name="Speed Meter")
|
||||
self._save_thread = Thread(target=self.__save_file__, name="File Writer")
|
||||
|
||||
# Actualizamos los headers
|
||||
self._headers.update(dict(headers))
|
||||
|
||||
# Separamos los headers de la url
|
||||
self.__url_to_headers__(url)
|
||||
|
||||
# Obtenemos la info del servidor
|
||||
self.__get_download_headers__()
|
||||
|
||||
self._file_size = int(self.response_headers.get("content-length", "0"))
|
||||
|
||||
if not self.response_headers.get("accept-ranges") == "bytes" or self._file_size == 0:
|
||||
self._max_connections = 1
|
||||
self._part_size = 0
|
||||
self._resume = False
|
||||
|
||||
# Obtenemos el nombre del archivo
|
||||
self.__get_download_filename__()
|
||||
|
||||
# Abrimos en modo "a+" para que cree el archivo si no existe, luego en modo "r+b" para poder hacer seek()
|
||||
self.file = filetools.file_open(filetools.join(self._path, self._filename), "a+")
|
||||
self.file = filetools.file_open(filetools.join(self._path, self._filename), "r+b")
|
||||
|
||||
if self._file_size >= 2 ** 31 or not self._file_size:
|
||||
try:
|
||||
self.file.seek(2 ** 31)
|
||||
except OverflowError:
|
||||
self._seekable = False
|
||||
logger.info("No se puede hacer seek() ni tell() en ficheros mayores de 2GB")
|
||||
|
||||
self.__get_download_info__()
|
||||
|
||||
logger.info("Descarga inicializada: Partes: %s | Ruta: %s | Archivo: %s | Tamaño: %s" % (
|
||||
len(self._download_info["parts"]), self._path, self._filename, self._download_info["size"]))
|
||||
|
||||
def __url_to_headers__(self, url):
|
||||
# Separamos la url de los headers adicionales
|
||||
self.url = url.split("|")[0]
|
||||
|
||||
# headers adicionales
|
||||
if "|" in url:
|
||||
self._headers.update(dict([[header.split("=")[0], urllib.unquote_plus(header.split("=")[1])] for header in
|
||||
url.split("|")[1].split("&")]))
|
||||
|
||||
def __get_download_headers__(self):
|
||||
if self.url.startswith("https"):
|
||||
try:
|
||||
conn = urllib2.urlopen(urllib2.Request(self.url.replace("https", "http"), headers=self._headers))
|
||||
conn.fp._sock.close()
|
||||
self.url = self.url.replace("https", "http")
|
||||
except:
|
||||
pass
|
||||
|
||||
for x in range(3):
|
||||
try:
|
||||
if not sys.hexversion > 0x0204FFFF:
|
||||
conn = urllib2.urlopen(urllib2.Request(self.url, headers=self._headers))
|
||||
conn.fp._sock.close()
|
||||
else:
|
||||
conn = urllib2.urlopen(urllib2.Request(self.url, headers=self._headers), timeout=5)
|
||||
|
||||
except:
|
||||
self.response_headers = dict()
|
||||
self._state = self.states.error
|
||||
else:
|
||||
self.response_headers = conn.headers.dict
|
||||
self._state = self.states.stopped
|
||||
break
|
||||
|
||||
def __get_download_filename__(self):
|
||||
# Obtenemos nombre de archivo y extension
|
||||
if "filename" in self.response_headers.get("content-disposition",
|
||||
"") and "attachment" in self.response_headers.get(
|
||||
"content-disposition", ""):
|
||||
cd_filename, cd_ext = os.path.splitext(urllib.unquote_plus(
|
||||
re.compile("attachment; filename ?= ?[\"|']?([^\"']+)[\"|']?").match(
|
||||
self.response_headers.get("content-disposition")).group(1)))
|
||||
if "filename" in self.response_headers.get("content-disposition", "") and "inline" in self.response_headers.get(
|
||||
"content-disposition", ""):
|
||||
cd_filename, cd_ext = os.path.splitext(urllib.unquote_plus(
|
||||
re.compile("inline; filename ?= ?[\"|']?([^\"']+)[\"|']?").match(
|
||||
self.response_headers.get("content-disposition")).group(1)))
|
||||
else:
|
||||
cd_filename, cd_ext = "", ""
|
||||
|
||||
url_filename, url_ext = os.path.splitext(
|
||||
urllib.unquote_plus(filetools.basename(urlparse.urlparse(self.url)[2])))
|
||||
if self.response_headers.get("content-type", "application/octet-stream") <> "application/octet-stream":
|
||||
mime_ext = mimetypes.guess_extension(self.response_headers.get("content-type"))
|
||||
else:
|
||||
mime_ext = ""
|
||||
|
||||
# Seleccionamos el nombre mas adecuado
|
||||
if cd_filename:
|
||||
self.remote_filename = cd_filename
|
||||
if not self._filename:
|
||||
self._filename = cd_filename
|
||||
|
||||
elif url_filename:
|
||||
self.remote_filename = url_filename
|
||||
if not self._filename:
|
||||
self._filename = url_filename
|
||||
|
||||
# Seleccionamos la extension mas adecuada
|
||||
if cd_ext:
|
||||
if not cd_ext in self._filename: self._filename += cd_ext
|
||||
if self.remote_filename: self.remote_filename += cd_ext
|
||||
elif mime_ext:
|
||||
if not mime_ext in self._filename: self._filename += mime_ext
|
||||
if self.remote_filename: self.remote_filename += mime_ext
|
||||
elif url_ext:
|
||||
if not url_ext in self._filename: self._filename += url_ext
|
||||
if self.remote_filename: self.remote_filename += url_ext
|
||||
|
||||
def __change_units__(self, value):
|
||||
import math
|
||||
units = ["B", "KB", "MB", "GB"]
|
||||
if value <= 0:
|
||||
return 0, 0, units[0]
|
||||
else:
|
||||
return value, value / 1024.0 ** int(math.log(value, 1024)), units[int(math.log(value, 1024))]
|
||||
|
||||
def __get_download_info__(self):
|
||||
# Continuamos con una descarga que contiene la info al final del archivo
|
||||
self._download_info = {}
|
||||
|
||||
try:
|
||||
if not self._resume:
|
||||
raise Exception()
|
||||
self.file.seek(-16, 2)
|
||||
offset = int(self.file.read())
|
||||
self.file.seek(offset)
|
||||
data = self.file.read()[:-16]
|
||||
self._download_info = eval(data)
|
||||
if not self._download_info["size"] == self._file_size:
|
||||
raise Exception()
|
||||
self.file.seek(offset)
|
||||
self.file.truncate()
|
||||
|
||||
if not self._seekable:
|
||||
for part in self._download_info["parts"]:
|
||||
if part["start"] >= 2 ** 31 and part["status"] == self.states.completed:
|
||||
part["status"] == self.states.stopped
|
||||
part["current"] == part["start"]
|
||||
|
||||
self._start_downloaded = sum([c["current"] - c["start"] for c in self._download_info["parts"]])
|
||||
self.pending_parts = set(
|
||||
[x for x, a in enumerate(self._download_info["parts"]) if not a["status"] == self.states.completed])
|
||||
self.completed_parts = set(
|
||||
[x for x, a in enumerate(self._download_info["parts"]) if a["status"] == self.states.completed])
|
||||
self.save_parts = set()
|
||||
self.download_parts = set()
|
||||
|
||||
# La info no existe o no es correcta, comenzamos de 0
|
||||
except:
|
||||
self._download_info["parts"] = []
|
||||
if self._file_size and self._part_size:
|
||||
for x in range(0, self._file_size, self._part_size):
|
||||
end = x + self._part_size - 1
|
||||
if end >= self._file_size: end = self._file_size - 1
|
||||
self._download_info["parts"].append(
|
||||
{"start": x, "end": end, "current": x, "status": self.states.stopped})
|
||||
else:
|
||||
self._download_info["parts"].append(
|
||||
{"start": 0, "end": self._file_size - 1, "current": 0, "status": self.states.stopped})
|
||||
|
||||
self._download_info["size"] = self._file_size
|
||||
self._start_downloaded = 0
|
||||
self.pending_parts = set([x for x in range(len(self._download_info["parts"]))])
|
||||
self.completed_parts = set()
|
||||
self.save_parts = set()
|
||||
self.download_parts = set()
|
||||
|
||||
self.file.seek(0)
|
||||
self.file.truncate()
|
||||
|
||||
def __open_connection__(self, start, end):
|
||||
headers = self._headers.copy()
|
||||
if not end: end = ""
|
||||
headers.update({"Range": "bytes=%s-%s" % (start, end)})
|
||||
if not sys.hexversion > 0x0204FFFF:
|
||||
conn = urllib2.urlopen(urllib2.Request(self.url, headers=headers))
|
||||
else:
|
||||
conn = urllib2.urlopen(urllib2.Request(self.url, headers=headers), timeout=5)
|
||||
return conn
|
||||
|
||||
def __check_consecutive__(self, id):
|
||||
return id == 0 or (len(self.completed_parts) >= id and sorted(self.completed_parts)[id - 1] == id - 1)
|
||||
|
||||
def __save_file__(self):
|
||||
logger.info("Thread iniciado: %s" % threading.current_thread().name)
|
||||
|
||||
while self._state == self.states.downloading:
|
||||
if not self.pending_parts and not self.download_parts and not self.save_parts: # Descarga finalizada
|
||||
self._state = self.states.completed
|
||||
self.file.close()
|
||||
continue
|
||||
|
||||
elif not self.save_parts:
|
||||
continue
|
||||
|
||||
save_id = min(self.save_parts)
|
||||
|
||||
if not self._seekable and self._download_info["parts"][save_id][
|
||||
"start"] >= 2 ** 31 and not self.__check_consecutive__(save_id):
|
||||
continue
|
||||
|
||||
if self._seekable or self._download_info["parts"][save_id]["start"] < 2 ** 31:
|
||||
self.file.seek(self._download_info["parts"][save_id]["start"])
|
||||
|
||||
try:
|
||||
# file = open(os.path.join(self.tmp_path, self._filename + ".part%s" % save_id), "rb")
|
||||
# self.file.write(file.read())
|
||||
# file.close()
|
||||
# os.remove(os.path.join(self.tmp_path, self._filename + ".part%s" % save_id))
|
||||
for a in self._buffer.pop(save_id):
|
||||
self.file.write(a)
|
||||
self.save_parts.remove(save_id)
|
||||
self.completed_parts.add(save_id)
|
||||
self._download_info["parts"][save_id]["status"] = self.states.completed
|
||||
except:
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
self._state = self.states.error
|
||||
|
||||
if self.save_parts:
|
||||
for s in self.save_parts:
|
||||
self._download_info["parts"][s]["status"] = self.states.stopped
|
||||
self._download_info["parts"][s]["current"] = self._download_info["parts"][s]["start"]
|
||||
|
||||
logger.info("Thread detenido: %s" % threading.current_thread().name)
|
||||
|
||||
def __get_part_id__(self):
|
||||
self._download_lock.acquire()
|
||||
if len(self.pending_parts):
|
||||
id = min(self.pending_parts)
|
||||
self.pending_parts.remove(id)
|
||||
self.download_parts.add(id)
|
||||
self._download_lock.release()
|
||||
return id
|
||||
else:
|
||||
self._download_lock.release()
|
||||
return None
|
||||
|
||||
def __set_part_connecting__(self, id):
|
||||
logger.info("ID: %s Estableciendo conexión" % id)
|
||||
self._download_info["parts"][id]["status"] = self.states.connecting
|
||||
|
||||
def __set_part__error__(self, id):
|
||||
logger.info("ID: %s Error al descargar" % id)
|
||||
self._download_info["parts"][id]["status"] = self.states.error
|
||||
self.pending_parts.add(id)
|
||||
self.download_parts.remove(id)
|
||||
|
||||
def __set_part__downloading__(self, id):
|
||||
logger.info("ID: %s Descargando datos..." % id)
|
||||
self._download_info["parts"][id]["status"] = self.states.downloading
|
||||
|
||||
def __set_part_completed__(self, id):
|
||||
logger.info("ID: %s ¡Descarga finalizada!" % id)
|
||||
self._download_info["parts"][id]["status"] = self.states.saving
|
||||
self.download_parts.remove(id)
|
||||
self.save_parts.add(id)
|
||||
while self._state == self.states.downloading and len(self._buffer) > self._max_connections + self._max_buffer:
|
||||
time.sleep(0.1)
|
||||
|
||||
def __set_part_stopped__(self, id):
|
||||
if self._download_info["parts"][id]["status"] == self.states.downloading:
|
||||
self._download_info["parts"][id]["status"] = self.states.stopped
|
||||
self.download_parts.remove(id)
|
||||
self.pending_parts.add(id)
|
||||
|
||||
def __open_part_file__(self, id):
|
||||
file = open(os.path.join(self.tmp_path, self._filename + ".part%s" % id), "a+")
|
||||
file = open(os.path.join(self.tmp_path, self._filename + ".part%s" % id), "r+b")
|
||||
file.seek(self._download_info["parts"][id]["current"] - self._download_info["parts"][id]["start"])
|
||||
return file
|
||||
|
||||
def __start_part__(self):
|
||||
logger.info("Thread Iniciado: %s" % threading.current_thread().name)
|
||||
while self._state == self.states.downloading:
|
||||
id = self.__get_part_id__()
|
||||
if id is None: break
|
||||
|
||||
self.__set_part_connecting__(id)
|
||||
|
||||
try:
|
||||
connection = self.__open_connection__(self._download_info["parts"][id]["current"],
|
||||
self._download_info["parts"][id]["end"])
|
||||
except:
|
||||
self.__set_part__error__(id)
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
self.__set_part__downloading__(id)
|
||||
# file = self.__open_part_file__(id)
|
||||
|
||||
if not id in self._buffer:
|
||||
self._buffer[id] = []
|
||||
speed = []
|
||||
|
||||
while self._state == self.states.downloading:
|
||||
try:
|
||||
start = time.time()
|
||||
buffer = connection.read(self._block_size)
|
||||
speed.append(len(buffer) / ((time.time() - start) or 0.001))
|
||||
except:
|
||||
logger.info("ID: %s Error al descargar los datos" % id)
|
||||
self._download_info["parts"][id]["status"] = self.states.error
|
||||
self.pending_parts.add(id)
|
||||
self.download_parts.remove(id)
|
||||
break
|
||||
else:
|
||||
if len(buffer) and self._download_info["parts"][id]["current"] < self._download_info["parts"][id][
|
||||
"end"]:
|
||||
# file.write(buffer)
|
||||
self._buffer[id].append(buffer)
|
||||
self._download_info["parts"][id]["current"] += len(buffer)
|
||||
if len(speed) > 10:
|
||||
velocidad_minima = sum(speed) / len(speed) / 3
|
||||
velocidad = speed[-1]
|
||||
vm = self.__change_units__(velocidad_minima)
|
||||
v = self.__change_units__(velocidad)
|
||||
|
||||
if velocidad_minima > speed[-1] and velocidad_minima > speed[-2] and \
|
||||
self._download_info["parts"][id]["current"] < \
|
||||
self._download_info["parts"][id]["end"]:
|
||||
connection.fp._sock.close()
|
||||
logger.info(
|
||||
"ID: %s ¡Reiniciando conexión! | Velocidad minima: %.2f %s/s | Velocidad: %.2f %s/s" % \
|
||||
(id, vm[1], vm[2], v[1], v[2]))
|
||||
# file.close()
|
||||
break
|
||||
else:
|
||||
self.__set_part_completed__(id)
|
||||
connection.fp._sock.close()
|
||||
# file.close()
|
||||
break
|
||||
|
||||
self.__set_part_stopped__(id)
|
||||
logger.info("Thread detenido: %s" % threading.current_thread().name)
|
||||
Executable
+1194
File diff suppressed because it is too large
Load Diff
Executable
+2239
File diff suppressed because it is too large
Load Diff
Executable
+579
@@ -0,0 +1,579 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# filetools
|
||||
# Gestion de archivos con discriminación samba/local
|
||||
# ------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from core import logger
|
||||
from core import scrapertools
|
||||
from platformcode import platformtools
|
||||
|
||||
try:
|
||||
from lib.sambatools import libsmb as samba
|
||||
except:
|
||||
samba = None
|
||||
# Python 2.4 No compatible con modulo samba, hay que revisar
|
||||
|
||||
# Windows es "mbcs" linux, osx, android es "utf8"
|
||||
if os.name == "nt":
|
||||
fs_encoding = ""
|
||||
else:
|
||||
fs_encoding = "utf8"
|
||||
|
||||
|
||||
def validate_path(path):
|
||||
"""
|
||||
Elimina cáracteres no permitidos
|
||||
@param path: cadena a validar
|
||||
@type path: str
|
||||
@rtype: str
|
||||
@return: devuelve la cadena sin los caracteres no permitidos
|
||||
"""
|
||||
chars = ":*?<>|"
|
||||
if path.lower().startswith("smb://"):
|
||||
import re
|
||||
parts = re.split(r'smb://(.+?)/(.+)', path)[1:3]
|
||||
return "smb://" + parts[0] + "/" + ''.join([c for c in parts[1] if c not in chars])
|
||||
|
||||
else:
|
||||
if path.find(":\\") == 1:
|
||||
unidad = path[0:3]
|
||||
path = path[2:]
|
||||
else:
|
||||
unidad = ""
|
||||
|
||||
return unidad + ''.join([c for c in path if c not in chars])
|
||||
|
||||
|
||||
def encode(path, _samba=False):
|
||||
"""
|
||||
Codifica una ruta según el sistema operativo que estemos utilizando.
|
||||
El argumento path tiene que estar codificado en utf-8
|
||||
@type path unicode o str con codificación utf-8
|
||||
@param path parámetro a codificar
|
||||
@type _samba bool
|
||||
@para _samba si la ruta es samba o no
|
||||
@rtype: str
|
||||
@return ruta codificada en juego de caracteres del sistema o utf-8 si samba
|
||||
"""
|
||||
if not type(path) == unicode:
|
||||
path = unicode(path, "utf-8", "ignore")
|
||||
|
||||
if path.lower().startswith("smb://") or _samba:
|
||||
path = path.encode("utf-8", "ignore")
|
||||
else:
|
||||
if fs_encoding:
|
||||
path = path.encode(fs_encoding, "ignore")
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def decode(path):
|
||||
"""
|
||||
Convierte una cadena de texto al juego de caracteres utf-8
|
||||
eliminando los caracteres que no estén permitidos en utf-8
|
||||
@type: str, unicode, list de str o unicode
|
||||
@param path: puede ser una ruta o un list() con varias rutas
|
||||
@rtype: str
|
||||
@return: ruta codificado en UTF-8
|
||||
"""
|
||||
if type(path) == list:
|
||||
for x in range(len(path)):
|
||||
if not type(path[x]) == unicode:
|
||||
path[x] = path[x].decode(fs_encoding, "ignore")
|
||||
path[x] = path[x].encode("utf-8", "ignore")
|
||||
else:
|
||||
if not type(path) == unicode:
|
||||
path = path.decode(fs_encoding, "ignore")
|
||||
path = path.encode("utf-8", "ignore")
|
||||
return path
|
||||
|
||||
|
||||
def read(path, linea_inicio=0, total_lineas=None):
|
||||
"""
|
||||
Lee el contenido de un archivo y devuelve los datos
|
||||
@param path: ruta del fichero
|
||||
@type path: str
|
||||
@param linea_inicio: primera linea a leer del fichero
|
||||
@type linea_inicio: int positivo
|
||||
@param total_lineas: numero maximo de lineas a leer. Si es None o superior al total de lineas se leera el
|
||||
fichero hasta el final.
|
||||
@type total_lineas: int positivo
|
||||
@rtype: str
|
||||
@return: datos que contiene el fichero
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
f = samba.smb_open(path, "rb")
|
||||
else:
|
||||
f = open(path, "rb")
|
||||
|
||||
data = []
|
||||
for x, line in enumerate(f):
|
||||
if x < linea_inicio: continue
|
||||
if len(data) == total_lineas: break
|
||||
data.append(line)
|
||||
f.close()
|
||||
except:
|
||||
logger.error("ERROR al leer el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
else:
|
||||
return "".join(data)
|
||||
|
||||
|
||||
def write(path, data):
|
||||
"""
|
||||
Guarda los datos en un archivo
|
||||
@param path: ruta del archivo a guardar
|
||||
@type path: str
|
||||
@param data: datos a guardar
|
||||
@type data: str
|
||||
@rtype: bool
|
||||
@return: devuelve True si se ha escrito correctamente o False si ha dado un error
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
f = samba.smb_open(path, "wb")
|
||||
else:
|
||||
f = open(path, "wb")
|
||||
|
||||
f.write(data)
|
||||
f.close()
|
||||
except:
|
||||
logger.error("ERROR al guardar el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def file_open(path, mode="r"):
|
||||
"""
|
||||
Abre un archivo
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@rtype: str
|
||||
@return: objeto file
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
return samba.smb_open(path, mode)
|
||||
else:
|
||||
return open(path, mode)
|
||||
except:
|
||||
logger.error("ERROR al abrir el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
platformtools.dialog_notification("Error al abrir", path)
|
||||
return False
|
||||
|
||||
|
||||
def rename(path, new_name):
|
||||
"""
|
||||
Renombra un archivo o carpeta
|
||||
@param path: ruta del fichero o carpeta a renombrar
|
||||
@type path: str
|
||||
@param new_name: nuevo nombre
|
||||
@type new_name: str
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
new_name = encode(new_name, True)
|
||||
samba.rename(path, join(dirname(path), new_name))
|
||||
else:
|
||||
new_name = encode(new_name, False)
|
||||
os.rename(path, os.path.join(os.path.dirname(path), new_name))
|
||||
except:
|
||||
logger.error("ERROR al renombrar el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
platformtools.dialog_notification("Error al renombrar", path)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def move(path, dest):
|
||||
"""
|
||||
Mueve un archivo
|
||||
@param path: ruta del fichero a mover
|
||||
@type path: str
|
||||
@param dest: ruta donde mover
|
||||
@type dest: str
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
try:
|
||||
# samba/samba
|
||||
if path.lower().startswith("smb://") and dest.lower().startswith("smb://"):
|
||||
dest = encode(dest, True)
|
||||
path = encode(path, True)
|
||||
samba.rename(path, dest)
|
||||
|
||||
# local/local
|
||||
elif not path.lower().startswith("smb://") and not dest.lower().startswith("smb://"):
|
||||
dest = encode(dest)
|
||||
path = encode(path)
|
||||
os.rename(path, dest)
|
||||
# mixto En este caso se copia el archivo y luego se elimina el de origen
|
||||
else:
|
||||
return copy(path, dest) == True and remove(path) == True
|
||||
except:
|
||||
logger.error("ERROR al mover el archivo: %s" % path)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def copy(path, dest, silent=False):
|
||||
"""
|
||||
Copia un archivo
|
||||
@param path: ruta del fichero a copiar
|
||||
@type path: str
|
||||
@param dest: ruta donde copiar
|
||||
@type dest: str
|
||||
@param silent: se muestra o no el cuadro de dialogo
|
||||
@type silent: bool
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
try:
|
||||
fo = file_open(path, "rb")
|
||||
fd = file_open(dest, "wb")
|
||||
if fo and fd:
|
||||
if not silent:
|
||||
dialogo = platformtools.dialog_progress("Copiando archivo", "")
|
||||
size = getsize(path)
|
||||
copiado = 0
|
||||
while True:
|
||||
if not silent:
|
||||
dialogo.update(copiado * 100 / size, basename(path))
|
||||
buf = fo.read(1024 * 1024)
|
||||
if not buf:
|
||||
break
|
||||
if not silent and dialogo.iscanceled():
|
||||
dialogo.close()
|
||||
return False
|
||||
fd.write(buf)
|
||||
copiado += len(buf)
|
||||
if not silent:
|
||||
dialogo.close()
|
||||
except:
|
||||
logger.error("ERROR al copiar el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def exists(path):
|
||||
"""
|
||||
Comprueba si existe una carpeta o fichero
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: Retorna True si la ruta existe, tanto si es una carpeta como un archivo
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
return samba.exists(path)
|
||||
else:
|
||||
return os.path.exists(path)
|
||||
except:
|
||||
logger.error("ERROR al comprobar la ruta: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
|
||||
def isfile(path):
|
||||
"""
|
||||
Comprueba si la ruta es un fichero
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: Retorna True si la ruta existe y es un archivo
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
return samba.isfile(path)
|
||||
else:
|
||||
return os.path.isfile(path)
|
||||
except:
|
||||
logger.error("ERROR al comprobar el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
|
||||
def isdir(path):
|
||||
"""
|
||||
Comprueba si la ruta es un directorio
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: Retorna True si la ruta existe y es un directorio
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
return samba.isdir(path)
|
||||
else:
|
||||
return os.path.isdir(path)
|
||||
except:
|
||||
logger.error("ERROR al comprobar el directorio: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
|
||||
def getsize(path):
|
||||
"""
|
||||
Obtiene el tamaño de un archivo
|
||||
@param path: ruta del fichero
|
||||
@type path: str
|
||||
@rtype: str
|
||||
@return: tamaño del fichero
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
return long(samba.get_attributes(path).file_size)
|
||||
else:
|
||||
return os.path.getsize(path)
|
||||
except:
|
||||
logger.error("ERROR al obtener el tamaño: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return 0L
|
||||
|
||||
|
||||
def remove(path):
|
||||
"""
|
||||
Elimina un archivo
|
||||
@param path: ruta del fichero a eliminar
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
samba.remove(path)
|
||||
else:
|
||||
os.remove(path)
|
||||
except:
|
||||
logger.error("ERROR al eliminar el archivo: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
platformtools.dialog_notification("Error al eliminar el archivo", path)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def rmdirtree(path):
|
||||
"""
|
||||
Elimina un directorio y su contenido
|
||||
@param path: ruta a eliminar
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
for raiz, subcarpetas, ficheros in samba.walk(path, topdown=False):
|
||||
for f in ficheros:
|
||||
samba.remove(join(decode(raiz), decode(f)))
|
||||
for s in subcarpetas:
|
||||
samba.rmdir(join(decode(raiz), decode(s)))
|
||||
samba.rmdir(path)
|
||||
else:
|
||||
import shutil
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
except:
|
||||
logger.error("ERROR al eliminar el directorio: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
platformtools.dialog_notification("Error al eliminar el directorio", path)
|
||||
return False
|
||||
else:
|
||||
return not exists(path)
|
||||
|
||||
|
||||
def rmdir(path):
|
||||
"""
|
||||
Elimina un directorio
|
||||
@param path: ruta a eliminar
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
samba.rmdir(path)
|
||||
else:
|
||||
os.rmdir(path)
|
||||
except:
|
||||
logger.error("ERROR al eliminar el directorio: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
platformtools.dialog_notification("Error al eliminar el directorio", path)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def mkdir(path):
|
||||
"""
|
||||
Crea un directorio
|
||||
@param path: ruta a crear
|
||||
@type path: str
|
||||
@rtype: bool
|
||||
@return: devuelve False en caso de error
|
||||
"""
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
samba.mkdir(path)
|
||||
else:
|
||||
os.mkdir(path)
|
||||
except:
|
||||
logger.error("ERROR al crear el directorio: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
platformtools.dialog_notification("Error al crear el directorio", path)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def walk(top, topdown=True, onerror=None):
|
||||
"""
|
||||
Lista un directorio de manera recursiva
|
||||
@param top: Directorio a listar, debe ser un str "UTF-8"
|
||||
@type top: str
|
||||
@param topdown: se escanea de arriba a abajo
|
||||
@type topdown: bool
|
||||
@param onerror: muestra error para continuar con el listado si tiene algo seteado sino levanta una excepción
|
||||
@type onerror: bool
|
||||
***El parametro followlinks que por defecto es True, no se usa aqui, ya que en samba no discrimina los links
|
||||
"""
|
||||
top = encode(top)
|
||||
if top.lower().startswith("smb://"):
|
||||
for a, b, c in samba.walk(top, topdown, onerror):
|
||||
# list(b) es para que haga una copia del listado de directorios
|
||||
# si no da error cuando tiene que entrar recursivamente en directorios con caracteres especiales
|
||||
yield decode(a), decode(list(b)), decode(c)
|
||||
else:
|
||||
for a, b, c in os.walk(top, topdown, onerror):
|
||||
# list(b) es para que haga una copia del listado de directorios
|
||||
# si no da error cuando tiene que entrar recursivamente en directorios con caracteres especiales
|
||||
yield decode(a), decode(list(b)), decode(c)
|
||||
|
||||
|
||||
def listdir(path):
|
||||
"""
|
||||
Lista un directorio
|
||||
@param path: Directorio a listar, debe ser un str "UTF-8"
|
||||
@type path: str
|
||||
@rtype: str
|
||||
@return: contenido de un directorio
|
||||
"""
|
||||
|
||||
path = encode(path)
|
||||
try:
|
||||
if path.lower().startswith("smb://"):
|
||||
return decode(samba.listdir(path))
|
||||
else:
|
||||
return decode(os.listdir(path))
|
||||
except:
|
||||
logger.error("ERROR al leer el directorio: %s" % path)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
|
||||
def join(*paths):
|
||||
"""
|
||||
Junta varios directorios
|
||||
Corrige las barras "/" o "\" segun el sistema operativo y si es o no smaba
|
||||
@rytpe: str
|
||||
@return: la ruta concatenada
|
||||
"""
|
||||
list_path = []
|
||||
if paths[0].startswith("/"):
|
||||
list_path.append("")
|
||||
|
||||
for path in paths:
|
||||
if path:
|
||||
list_path += path.replace("\\", "/").strip("/").split("/")
|
||||
|
||||
if list_path[0].lower() == "smb:":
|
||||
return "/".join(list_path)
|
||||
else:
|
||||
return os.sep.join(list_path)
|
||||
|
||||
|
||||
def split(path):
|
||||
"""
|
||||
Devuelve una tupla formada por el directorio y el nombre del fichero de una ruta
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@return: (dirname, basename)
|
||||
@rtype: tuple
|
||||
"""
|
||||
if path.lower().startswith("smb://"):
|
||||
if '/' not in path[6:]:
|
||||
path = path.replace("smb://", "smb:///", 1)
|
||||
return path.rsplit('/', 1)
|
||||
else:
|
||||
return os.path.split(path)
|
||||
|
||||
|
||||
def basename(path):
|
||||
"""
|
||||
Devuelve el nombre del fichero de una ruta
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@return: fichero de la ruta
|
||||
@rtype: str
|
||||
"""
|
||||
return split(path)[1]
|
||||
|
||||
|
||||
def dirname(path):
|
||||
"""
|
||||
Devuelve el directorio de una ruta
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@return: directorio de la ruta
|
||||
@rtype: str
|
||||
"""
|
||||
return split(path)[0]
|
||||
|
||||
|
||||
def is_relative(path):
|
||||
return "://" not in path and not path.startswith("/") and ":\\" not in path
|
||||
|
||||
|
||||
def remove_tags(title):
|
||||
"""
|
||||
devuelve el titulo sin tags como color
|
||||
@type title: str
|
||||
@param title: title
|
||||
@rtype: str
|
||||
@return: cadena sin tags
|
||||
"""
|
||||
logger.info()
|
||||
|
||||
title_without_tags = scrapertools.find_single_match(title, '\[color .+?\](.+)\[\/color\]')
|
||||
|
||||
if title_without_tags:
|
||||
return title_without_tags
|
||||
else:
|
||||
return title
|
||||
Executable
+260
@@ -0,0 +1,260 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# httptools
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import cookielib
|
||||
import gzip
|
||||
import os
|
||||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
import urlparse
|
||||
from StringIO import StringIO
|
||||
from threading import Lock
|
||||
|
||||
from core import config
|
||||
from core import logger
|
||||
from core.cloudflare import Cloudflare
|
||||
|
||||
cookies_lock = Lock()
|
||||
|
||||
cj = cookielib.MozillaCookieJar()
|
||||
ficherocookies = os.path.join(config.get_data_path(), "cookies.dat")
|
||||
|
||||
# Headers por defecto, si no se especifica nada
|
||||
default_headers = dict()
|
||||
default_headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0"
|
||||
default_headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
||||
default_headers["Accept-Language"] = "es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3"
|
||||
default_headers["Accept-Charset"] = "UTF-8"
|
||||
default_headers["Accept-Encoding"] = "gzip"
|
||||
|
||||
|
||||
def get_url_headers(url):
|
||||
domain_cookies = cj._cookies.get("." + urlparse.urlparse(url)[1], {}).get("/", {})
|
||||
|
||||
if "|" in url or not "cf_clearance" in domain_cookies:
|
||||
return url
|
||||
|
||||
headers = dict()
|
||||
headers["User-Agent"] = default_headers["User-Agent"]
|
||||
headers["Cookie"] = "; ".join(["%s=%s" % (c.name, c.value) for c in domain_cookies.values()])
|
||||
|
||||
return url + "|" + "&".join(["%s=%s" % (h, headers[h]) for h in headers])
|
||||
|
||||
|
||||
def load_cookies():
|
||||
cookies_lock.acquire()
|
||||
if os.path.isfile(ficherocookies):
|
||||
logger.info("Leyendo fichero cookies")
|
||||
try:
|
||||
cj.load(ficherocookies, ignore_discard=True)
|
||||
except:
|
||||
logger.info("El fichero de cookies existe pero es ilegible, se borra")
|
||||
os.remove(ficherocookies)
|
||||
cookies_lock.release()
|
||||
|
||||
|
||||
def save_cookies():
|
||||
cookies_lock.acquire()
|
||||
logger.info("Guardando cookies...")
|
||||
cj.save(ficherocookies, ignore_discard=True)
|
||||
cookies_lock.release()
|
||||
|
||||
|
||||
load_cookies()
|
||||
|
||||
|
||||
def downloadpage(url, post=None, headers=None, timeout=None, follow_redirects=True, cookies=True, replace_headers=False,
|
||||
add_referer=False, only_headers=False, bypass_cloudflare=True):
|
||||
"""
|
||||
Abre una url y retorna los datos obtenidos
|
||||
|
||||
@param url: url que abrir.
|
||||
@type url: str
|
||||
@param post: Si contiene algun valor este es enviado mediante POST.
|
||||
@type post: str
|
||||
@param headers: Headers para la petición, si no contiene nada se usara los headers por defecto.
|
||||
@type headers: dict, list
|
||||
@param timeout: Timeout para la petición.
|
||||
@type timeout: int
|
||||
@param follow_redirects: Indica si se han de seguir las redirecciones.
|
||||
@type follow_redirects: bool
|
||||
@param cookies: Indica si se han de usar las cookies.
|
||||
@type cookies: bool
|
||||
@param replace_headers: Si True, los headers pasados por el parametro "headers" sustituiran por completo los headers por defecto.
|
||||
Si False, los headers pasados por el parametro "headers" modificaran los headers por defecto.
|
||||
@type replace_headers: bool
|
||||
@param add_referer: Indica si se ha de añadir el header "Referer" usando el dominio de la url como valor.
|
||||
@type add_referer: bool
|
||||
@param only_headers: Si True, solo se descargarán los headers, omitiendo el contenido de la url.
|
||||
@type only_headers: bool
|
||||
@return: Resultado de la petición
|
||||
@rtype: HTTPResponse
|
||||
|
||||
Parametro Tipo Descripción
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
HTTPResponse.sucess: bool True: Peticion realizada correctamente | False: Error al realizar la petición
|
||||
HTTPResponse.code: int Código de respuesta del servidor o código de error en caso de producirse un error
|
||||
HTTPResponse.error: str Descripción del error en caso de producirse un error
|
||||
HTTPResponse.headers: dict Diccionario con los headers de respuesta del servidor
|
||||
HTTPResponse.data: str Respuesta obtenida del servidor
|
||||
HTTPResponse.time: float Tiempo empleado para realizar la petición
|
||||
|
||||
"""
|
||||
|
||||
response = {}
|
||||
|
||||
# Headers por defecto, si no se especifica nada
|
||||
request_headers = default_headers.copy()
|
||||
|
||||
# Headers pasados como parametros
|
||||
if headers is not None:
|
||||
if not replace_headers:
|
||||
request_headers.update(dict(headers))
|
||||
else:
|
||||
request_headers = dict(headers)
|
||||
|
||||
if add_referer:
|
||||
request_headers["Referer"] = "/".join(url.split("/")[:3])
|
||||
|
||||
url = urllib.quote(url, safe="%/:=&?~#+!$,;'@()*[]")
|
||||
|
||||
logger.info("----------------------------------------------")
|
||||
logger.info("downloadpage")
|
||||
logger.info("----------------------------------------------")
|
||||
logger.info("Timeout: %s" % timeout)
|
||||
logger.info("URL: " + url)
|
||||
logger.info("Dominio: " + urlparse.urlparse(url)[1])
|
||||
if post:
|
||||
logger.info("Peticion: POST")
|
||||
else:
|
||||
logger.info("Peticion: GET")
|
||||
logger.info("Usar Cookies: %s" % cookies)
|
||||
logger.info("Descargar Pagina: %s" % (not only_headers))
|
||||
logger.info("Fichero de Cookies: " + ficherocookies)
|
||||
logger.info("Headers:")
|
||||
for header in request_headers:
|
||||
logger.info("- %s: %s" % (header, request_headers[header]))
|
||||
|
||||
# Handlers
|
||||
handlers = [urllib2.HTTPHandler(debuglevel=False)]
|
||||
|
||||
if not follow_redirects:
|
||||
handlers.append(NoRedirectHandler())
|
||||
|
||||
if cookies:
|
||||
handlers.append(urllib2.HTTPCookieProcessor(cj))
|
||||
|
||||
opener = urllib2.build_opener(*handlers)
|
||||
|
||||
logger.info("Realizando Peticion")
|
||||
|
||||
# Contador
|
||||
inicio = time.time()
|
||||
|
||||
req = urllib2.Request(url, post, request_headers)
|
||||
|
||||
try:
|
||||
if urllib2.__version__ == "2.4":
|
||||
import socket
|
||||
deftimeout = socket.getdefaulttimeout()
|
||||
if timeout is not None:
|
||||
socket.setdefaulttimeout(timeout)
|
||||
handle = opener.open(req)
|
||||
socket.setdefaulttimeout(deftimeout)
|
||||
else:
|
||||
handle = opener.open(req, timeout=timeout)
|
||||
|
||||
except urllib2.HTTPError, handle:
|
||||
response["sucess"] = False
|
||||
response["code"] = handle.code
|
||||
response["error"] = handle.__dict__.get("reason", str(handle))
|
||||
response["headers"] = handle.headers.dict
|
||||
if not only_headers:
|
||||
response["data"] = handle.read()
|
||||
else:
|
||||
response["data"] = ""
|
||||
response["time"] = time.time() - inicio
|
||||
response["url"] = handle.geturl()
|
||||
|
||||
except Exception, e:
|
||||
response["sucess"] = False
|
||||
response["code"] = e.__dict__.get("errno", e.__dict__.get("code", str(e)))
|
||||
response["error"] = e.__dict__.get("reason", str(e))
|
||||
response["headers"] = {}
|
||||
response["data"] = ""
|
||||
response["time"] = time.time() - inicio
|
||||
response["url"] = url
|
||||
|
||||
else:
|
||||
response["sucess"] = True
|
||||
response["code"] = handle.code
|
||||
response["error"] = None
|
||||
response["headers"] = handle.headers.dict
|
||||
if not only_headers:
|
||||
response["data"] = handle.read()
|
||||
else:
|
||||
response["data"] = ""
|
||||
response["time"] = time.time() - inicio
|
||||
response["url"] = handle.geturl()
|
||||
|
||||
logger.info("Terminado en %.2f segundos" % (response["time"]))
|
||||
logger.info("Response sucess: %s" % (response["sucess"]))
|
||||
logger.info("Response code: %s" % (response["code"]))
|
||||
logger.info("Response error: %s" % (response["error"]))
|
||||
logger.info("Response data length: %s" % (len(response["data"])))
|
||||
logger.info("Response headers:")
|
||||
for header in response["headers"]:
|
||||
logger.info("- %s: %s" % (header, response["headers"][header]))
|
||||
|
||||
if cookies:
|
||||
save_cookies()
|
||||
|
||||
logger.info("Encoding: %s" % (response["headers"].get('content-encoding')))
|
||||
|
||||
if response["headers"].get('content-encoding') == 'gzip':
|
||||
logger.info("Descomprimiendo...")
|
||||
try:
|
||||
response["data"] = gzip.GzipFile(fileobj=StringIO(response["data"])).read()
|
||||
logger.info("Descomprimido")
|
||||
except:
|
||||
logger.info("No se ha podido descomprimir")
|
||||
|
||||
# Anti Cloudflare
|
||||
if bypass_cloudflare:
|
||||
cf = Cloudflare(response)
|
||||
if cf.is_cloudflare:
|
||||
logger.info("cloudflare detectado, esperando %s segundos..." % cf.wait_time)
|
||||
auth_url = cf.get_url()
|
||||
logger.info("Autorizando... url: %s" % auth_url)
|
||||
if downloadpage(auth_url, headers=request_headers, replace_headers=True).sucess:
|
||||
logger.info("Autorización correcta, descargando página")
|
||||
resp = downloadpage(url=response["url"], post=post, headers=headers, timeout=timeout,
|
||||
follow_redirects=follow_redirects,
|
||||
cookies=cookies, replace_headers=replace_headers, add_referer=add_referer)
|
||||
response["sucess"] = resp.sucess
|
||||
response["code"] = resp.code
|
||||
response["error"] = resp.error
|
||||
response["headers"] = resp.headers
|
||||
response["data"] = resp.data
|
||||
response["time"] = resp.time
|
||||
response["url"] = resp.url
|
||||
else:
|
||||
logger.info("No se ha podido autorizar")
|
||||
|
||||
return type('HTTPResponse', (), response)
|
||||
|
||||
|
||||
class NoRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
def http_error_302(self, req, fp, code, msg, headers):
|
||||
infourl = urllib.addinfourl(fp, headers, req.get_full_url())
|
||||
infourl.status = code
|
||||
infourl.code = code
|
||||
return infourl
|
||||
|
||||
http_error_300 = http_error_302
|
||||
http_error_301 = http_error_302
|
||||
http_error_303 = http_error_302
|
||||
http_error_307 = http_error_302
|
||||
Executable
+482
@@ -0,0 +1,482 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Item is the object we use for representing data
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import base64
|
||||
import copy
|
||||
import os
|
||||
import urllib
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
from core import jsontools as json
|
||||
|
||||
|
||||
class InfoLabels(dict):
|
||||
def __str__(self):
|
||||
return self.tostring(separador=',\r\t')
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
if name in ["season", "episode"]:
|
||||
# forzamos int() en season y episode
|
||||
try:
|
||||
super(InfoLabels, self).__setitem__(name, int(value))
|
||||
except:
|
||||
pass
|
||||
|
||||
elif name in ['IMDBNumber', 'imdb_id']:
|
||||
# Por compatibilidad hemos de guardar el valor en los tres campos
|
||||
super(InfoLabels, self).__setitem__('IMDBNumber', str(value))
|
||||
# super(InfoLabels, self).__setitem__('code', value)
|
||||
super(InfoLabels, self).__setitem__('imdb_id', str(value))
|
||||
|
||||
elif name == "mediatype" and value not in ["list", "movie", "tvshow", "season", "episode"]:
|
||||
super(InfoLabels, self).__setitem__('mediatype', 'list')
|
||||
|
||||
elif name in ['tmdb_id', 'tvdb_id', 'noscrap_id']:
|
||||
super(InfoLabels, self).__setitem__(name, str(value))
|
||||
else:
|
||||
super(InfoLabels, self).__setitem__(name, value)
|
||||
|
||||
# Python 2.4
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return super(InfoLabels, self).__getitem__(key)
|
||||
except:
|
||||
return self.__missing__(key)
|
||||
|
||||
def __missing__(self, key):
|
||||
"""
|
||||
Valores por defecto en caso de que la clave solicitada no exista.
|
||||
El parametro 'default' en la funcion obj_infoLabels.get(key,default) tiene preferencia sobre los aqui definidos.
|
||||
"""
|
||||
if key in ['rating']:
|
||||
# Ejemplo de clave q devuelve un str formateado como float por defecto
|
||||
return '0.0'
|
||||
|
||||
elif key == 'code':
|
||||
code = []
|
||||
# Añadir imdb_id al listado de codigos
|
||||
if 'imdb_id' in super(InfoLabels, self).keys() and super(InfoLabels, self).__getitem__('imdb_id'):
|
||||
code.append(super(InfoLabels, self).__getitem__('imdb_id'))
|
||||
|
||||
# Completar con el resto de codigos
|
||||
for scr in ['tmdb_id', 'tvdb_id', 'noscrap_id']:
|
||||
if scr in super(InfoLabels, self).keys() and super(InfoLabels, self).__getitem__(scr):
|
||||
value = "%s%s" % (scr[:-2], super(InfoLabels, self).__getitem__(scr))
|
||||
code.append(value)
|
||||
|
||||
# Opcion añadir un code del tipo aleatorio
|
||||
if not code:
|
||||
import time
|
||||
value = time.strftime("%Y%m%d%H%M%S", time.gmtime())
|
||||
code.append(value)
|
||||
super(InfoLabels, self).__setitem__('noscrap_id', value)
|
||||
|
||||
return code
|
||||
|
||||
elif key == 'mediatype':
|
||||
# "list", "movie", "tvshow", "season", "episode"
|
||||
if 'tvshowtitle' in super(InfoLabels, self).keys() \
|
||||
and super(InfoLabels, self).__getitem__('tvshowtitle') != "":
|
||||
if 'episode' in super(InfoLabels, self).keys() and super(InfoLabels, self).__getitem__('episode') != "":
|
||||
return 'episode'
|
||||
|
||||
if 'episodeName' in super(InfoLabels, self).keys() \
|
||||
and super(InfoLabels, self).__getitem__('episodeName') != "":
|
||||
return 'episode'
|
||||
|
||||
if 'season' in super(InfoLabels, self).keys() and super(InfoLabels, self).__getitem__('season') != "":
|
||||
return 'season'
|
||||
else:
|
||||
return 'tvshow'
|
||||
|
||||
elif 'title' in super(InfoLabels, self).keys() and super(InfoLabels, self).__getitem__('title') != "":
|
||||
return 'movie'
|
||||
|
||||
else:
|
||||
return 'list'
|
||||
|
||||
else:
|
||||
# El resto de claves devuelven cadenas vacias por defecto
|
||||
return ""
|
||||
|
||||
def tostring(self, separador=', '):
|
||||
ls = []
|
||||
dic = dict(super(InfoLabels, self).items())
|
||||
|
||||
for i in sorted(dic.items()):
|
||||
i_str = str(i)[1:-1]
|
||||
if isinstance(i[0], str):
|
||||
old = i[0] + "',"
|
||||
new = i[0] + "':"
|
||||
else:
|
||||
old = str(i[0]) + ","
|
||||
new = str(i[0]) + ":"
|
||||
ls.append(i_str.replace(old, new, 1))
|
||||
|
||||
return "{%s}" % separador.join(ls)
|
||||
|
||||
|
||||
class Item(object):
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Inicializacion del item
|
||||
"""
|
||||
|
||||
# Creamos el atributo infoLabels
|
||||
self.__dict__["infoLabels"] = InfoLabels()
|
||||
if "infoLabels" in kwargs:
|
||||
if isinstance(kwargs["infoLabels"], dict):
|
||||
self.__dict__["infoLabels"].update(kwargs["infoLabels"])
|
||||
del kwargs["infoLabels"]
|
||||
|
||||
if "parentContent" in kwargs:
|
||||
self.set_parent_content(kwargs["parentContent"])
|
||||
del kwargs["parentContent"]
|
||||
|
||||
kw = copy.copy(kwargs)
|
||||
for k in kw:
|
||||
if k in ["contentTitle", "contentPlot", "contentSerieName", "show", "contentType", "contentEpisodeTitle",
|
||||
"contentSeason", "contentEpisodeNumber", "contentThumbnail", "plot", "duration", "contentQuality",
|
||||
"quality"]:
|
||||
self.__setattr__(k, kw[k])
|
||||
del kwargs[k]
|
||||
|
||||
self.__dict__.update(kwargs)
|
||||
self.__dict__ = self.toutf8(self.__dict__)
|
||||
|
||||
def __contains__(self, m):
|
||||
"""
|
||||
Comprueba si un atributo existe en el item
|
||||
"""
|
||||
return m in self.__dict__
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""
|
||||
Función llamada al modificar cualquier atributo del item, modifica algunos atributos en función de los datos
|
||||
modificados.
|
||||
"""
|
||||
value = self.toutf8(value)
|
||||
if name == "__dict__":
|
||||
for key in value:
|
||||
self.__setattr__(key, value[key])
|
||||
return
|
||||
|
||||
# Descodificamos los HTML entities
|
||||
if name in ["title", "plot", "fulltitle", "contentPlot", "contentTitle"]:
|
||||
value = self.decode_html(value)
|
||||
|
||||
# Al modificar cualquiera de estos atributos content...
|
||||
if name in ["contentTitle", "contentPlot", "plot", "contentSerieName", "contentType", "contentEpisodeTitle",
|
||||
"contentSeason", "contentEpisodeNumber", "contentThumbnail", "show", "contentQuality", "quality"]:
|
||||
# ... marcamos hasContentDetails como "true"...
|
||||
self.__dict__["hasContentDetails"] = True
|
||||
# ...y actualizamos infoLables
|
||||
if name == "contentTitle":
|
||||
self.__dict__["infoLabels"]["title"] = value
|
||||
elif name == "contentPlot" or name == "plot":
|
||||
self.__dict__["infoLabels"]["plot"] = value
|
||||
elif name == "contentSerieName" or name == "show":
|
||||
self.__dict__["infoLabels"]["tvshowtitle"] = value
|
||||
elif name == "contentType":
|
||||
self.__dict__["infoLabels"]["mediatype"] = value
|
||||
elif name == "contentEpisodeTitle":
|
||||
self.__dict__["infoLabels"]["episodeName"] = value
|
||||
elif name == "contentSeason":
|
||||
self.__dict__["infoLabels"]["season"] = value
|
||||
elif name == "contentEpisodeNumber":
|
||||
self.__dict__["infoLabels"]["episode"] = value
|
||||
elif name == "contentThumbnail":
|
||||
self.__dict__["infoLabels"]["thumbnail"] = value
|
||||
elif name == "contentQuality" or name == "quality":
|
||||
self.__dict__["infoLabels"]["quality"] = value
|
||||
|
||||
elif name == "duration":
|
||||
# String q representa la duracion del video en segundos
|
||||
self.__dict__["infoLabels"]["duration"] = str(value)
|
||||
|
||||
elif name == "viewcontent" and value not in ["files", "movies", "tvshows", "seasons", "episodes"]:
|
||||
super(Item, self).__setattr__("viewcontent", "files")
|
||||
|
||||
# Al asignar un valor a infoLables
|
||||
elif name == "infoLabels":
|
||||
if isinstance(value, dict):
|
||||
value_defaultdict = InfoLabels(value)
|
||||
self.__dict__["infoLabels"] = value_defaultdict
|
||||
|
||||
else:
|
||||
super(Item, self).__setattr__(name, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Devuelve los valores por defecto en caso de que el atributo solicitado no exista en el item
|
||||
"""
|
||||
if name.startswith("__"):
|
||||
return super(Item, self).__getattribute__(name)
|
||||
|
||||
# valor por defecto para folder
|
||||
if name == "folder":
|
||||
return True
|
||||
|
||||
# valor por defecto para contentChannel
|
||||
elif name == "contentChannel":
|
||||
return "list"
|
||||
|
||||
# valor por defecto para viewcontent
|
||||
elif name == "viewcontent":
|
||||
# intentamos fijarlo segun el tipo de contenido...
|
||||
if self.__dict__["infoLabels"]["mediatype"] == 'movie':
|
||||
viewcontent = 'movies'
|
||||
elif self.__dict__["infoLabels"]["mediatype"] in ["tvshow", "season", "episode"]:
|
||||
viewcontent = "episodes"
|
||||
else:
|
||||
viewcontent = "files"
|
||||
|
||||
self.__dict__["viewcontent"] = viewcontent
|
||||
return viewcontent
|
||||
|
||||
# Valor por defecto para hasContentDetails
|
||||
elif name == "hasContentDetails":
|
||||
return False
|
||||
|
||||
# valores guardados en infoLabels
|
||||
elif name in ["contentTitle", "contentPlot", "contentSerieName", "show", "contentType", "contentEpisodeTitle",
|
||||
"contentSeason", "contentEpisodeNumber", "contentThumbnail", "plot", "duration",
|
||||
"contentQuality", "quality"]:
|
||||
if name == "contentTitle":
|
||||
return self.__dict__["infoLabels"]["title"]
|
||||
elif name == "contentPlot" or name == "plot":
|
||||
return self.__dict__["infoLabels"]["plot"]
|
||||
elif name == "contentSerieName" or name == "show":
|
||||
return self.__dict__["infoLabels"]["tvshowtitle"]
|
||||
elif name == "contentType":
|
||||
ret = self.__dict__["infoLabels"]["mediatype"]
|
||||
if ret == 'list' and self.__dict__.get("fulltitle", None): # retrocompatibilidad
|
||||
ret = 'movie'
|
||||
self.__dict__["infoLabels"]["mediatype"] = ret
|
||||
return ret
|
||||
elif name == "contentEpisodeTitle":
|
||||
return self.__dict__["infoLabels"]["episodeName"]
|
||||
elif name == "contentSeason":
|
||||
return self.__dict__["infoLabels"]["season"]
|
||||
elif name == "contentEpisodeNumber":
|
||||
return self.__dict__["infoLabels"]["episode"]
|
||||
elif name == "contentThumbnail":
|
||||
return self.__dict__["infoLabels"]["thumbnail"]
|
||||
elif name == "contentQuality" or name == "quality":
|
||||
return self.__dict__["infoLabels"]["quality"]
|
||||
else:
|
||||
return self.__dict__["infoLabels"][name]
|
||||
|
||||
# valor por defecto para el resto de atributos
|
||||
else:
|
||||
return ""
|
||||
|
||||
def __str__(self):
|
||||
return '\r\t' + self.tostring('\r\t')
|
||||
|
||||
def set_parent_content(self, parentContent):
|
||||
"""
|
||||
Rellena los campos contentDetails con la informacion del item "padre"
|
||||
@param parentContent: item padre
|
||||
@type parentContent: item
|
||||
"""
|
||||
# Comprueba que parentContent sea un Item
|
||||
if not type(parentContent) == type(self):
|
||||
return
|
||||
# Copia todos los atributos que empiecen por "content" y esten declarados y los infoLabels
|
||||
for attr in parentContent.__dict__:
|
||||
if attr.startswith("content") or attr == "infoLabels":
|
||||
self.__setattr__(attr, parentContent.__dict__[attr])
|
||||
|
||||
def tostring(self, separator=", "):
|
||||
"""
|
||||
Genera una cadena de texto con los datos del item para el log
|
||||
Uso: logger.info(item.tostring())
|
||||
@param separator: cadena que se usará como separador
|
||||
@type separator: str
|
||||
'"""
|
||||
dic = self.__dict__.copy()
|
||||
|
||||
# Añadimos los campos content... si tienen algun valor
|
||||
for key in ["contentTitle", "contentPlot", "contentSerieName", "contentEpisodeTitle",
|
||||
"contentSeason", "contentEpisodeNumber", "contentThumbnail"]:
|
||||
value = self.__getattr__(key)
|
||||
if value:
|
||||
dic[key] = value
|
||||
|
||||
if 'mediatype' in self.__dict__["infoLabels"]:
|
||||
dic["contentType"] = self.__dict__["infoLabels"]['mediatype']
|
||||
|
||||
ls = []
|
||||
for var in sorted(dic):
|
||||
if isinstance(dic[var], str):
|
||||
valor = "'%s'" % dic[var]
|
||||
elif isinstance(dic[var], InfoLabels):
|
||||
if separator == '\r\t':
|
||||
valor = dic[var].tostring(',\r\t\t')
|
||||
else:
|
||||
valor = dic[var].tostring()
|
||||
else:
|
||||
valor = str(dic[var])
|
||||
|
||||
ls.append(var + "= " + valor)
|
||||
|
||||
return separator.join(ls)
|
||||
|
||||
def tourl(self):
|
||||
"""
|
||||
Genera una cadena de texto con los datos del item para crear una url, para volver generar el Item usar
|
||||
item.fromurl().
|
||||
|
||||
Uso: url = item.tourl()
|
||||
"""
|
||||
dump = json.dump(self.__dict__)
|
||||
# if empty dict
|
||||
if not dump:
|
||||
# set a str to avoid b64encode fails
|
||||
dump = ""
|
||||
return urllib.quote(base64.b64encode(dump))
|
||||
|
||||
def fromurl(self, url):
|
||||
"""
|
||||
Genera un item a partir de una cadena de texto. La cadena puede ser creada por la funcion tourl() o tener
|
||||
el formato antiguo: plugin://plugin.video.alfa/?channel=... (+ otros parametros)
|
||||
Uso: item.fromurl("cadena")
|
||||
|
||||
@param url: url
|
||||
@type url: str
|
||||
"""
|
||||
if "?" in url:
|
||||
url = url.split("?")[1]
|
||||
decoded = False
|
||||
try:
|
||||
str_item = base64.b64decode(urllib.unquote(url))
|
||||
json_item = json.load(str_item, object_hook=self.toutf8)
|
||||
if json_item is not None and len(json_item) > 0:
|
||||
self.__dict__.update(json_item)
|
||||
decoded = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if not decoded:
|
||||
url = urllib.unquote_plus(url)
|
||||
dct = dict([[param.split("=")[0], param.split("=")[1]] for param in url.split("&") if "=" in param])
|
||||
self.__dict__.update(dct)
|
||||
self.__dict__ = self.toutf8(self.__dict__)
|
||||
|
||||
if 'infoLabels' in self.__dict__ and not isinstance(self.__dict__['infoLabels'], InfoLabels):
|
||||
self.__dict__['infoLabels'] = InfoLabels(self.__dict__['infoLabels'])
|
||||
|
||||
return self
|
||||
|
||||
def tojson(self, path=""):
|
||||
"""
|
||||
Crea un JSON a partir del item, para guardar archivos de favoritos, lista de descargas, etc...
|
||||
Si se especifica un path, te lo guarda en la ruta especificada, si no, devuelve la cadena json
|
||||
Usos: item.tojson(path="ruta\archivo\json.json")
|
||||
file.write(item.tojson())
|
||||
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
"""
|
||||
if path:
|
||||
open(path, "wb").write(json.dump(self.__dict__))
|
||||
else:
|
||||
return json.dump(self.__dict__)
|
||||
|
||||
def fromjson(self, json_item=None, path=""):
|
||||
"""
|
||||
Genera un item a partir de un archivo JSON
|
||||
Si se especifica un path, lee directamente el archivo, si no, lee la cadena de texto pasada.
|
||||
Usos: item = Item().fromjson(path="ruta\archivo\json.json")
|
||||
item = Item().fromjson("Cadena de texto json")
|
||||
|
||||
@param json_item: item
|
||||
@type json_item: json
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
"""
|
||||
if path:
|
||||
if os.path.exists(path):
|
||||
json_item = open(path, "rb").read()
|
||||
else:
|
||||
json_item = {}
|
||||
|
||||
if json_item is None:
|
||||
json_item = {}
|
||||
|
||||
item = json.load(json_item, object_hook=self.toutf8)
|
||||
self.__dict__.update(item)
|
||||
|
||||
if 'infoLabels' in self.__dict__ and not isinstance(self.__dict__['infoLabels'], InfoLabels):
|
||||
self.__dict__['infoLabels'] = InfoLabels(self.__dict__['infoLabels'])
|
||||
|
||||
return self
|
||||
|
||||
def clone(self, **kwargs):
|
||||
"""
|
||||
Genera un nuevo item clonando el item actual
|
||||
Usos: NuevoItem = item.clone()
|
||||
NuevoItem = item.clone(title="Nuevo Titulo", action = "Nueva Accion")
|
||||
"""
|
||||
newitem = copy.deepcopy(self)
|
||||
if "infoLabels" in kwargs:
|
||||
kwargs["infoLabels"] = InfoLabels(kwargs["infoLabels"])
|
||||
for kw in kwargs:
|
||||
newitem.__setattr__(kw, kwargs[kw])
|
||||
newitem.__dict__ = newitem.toutf8(newitem.__dict__)
|
||||
|
||||
return newitem
|
||||
|
||||
@staticmethod
|
||||
def decode_html(value):
|
||||
"""
|
||||
Descodifica las HTML entities
|
||||
@param value: valor a decodificar
|
||||
@type value: str
|
||||
"""
|
||||
try:
|
||||
unicode_title = unicode(value, "utf8", "ignore")
|
||||
return HTMLParser().unescape(unicode_title).encode("utf8")
|
||||
except:
|
||||
return value
|
||||
|
||||
def toutf8(self, *args):
|
||||
"""
|
||||
Pasa el item a utf8
|
||||
"""
|
||||
if len(args) > 0:
|
||||
value = args[0]
|
||||
else:
|
||||
value = self.__dict__
|
||||
|
||||
if type(value) == unicode:
|
||||
return value.encode("utf8")
|
||||
|
||||
elif type(value) == str:
|
||||
return unicode(value, "utf8", "ignore").encode("utf8")
|
||||
|
||||
elif type(value) == list:
|
||||
for x, key in enumerate(value):
|
||||
value[x] = self.toutf8(value[x])
|
||||
return value
|
||||
|
||||
elif isinstance(value, dict):
|
||||
newdct = {}
|
||||
for key in value:
|
||||
v = self.toutf8(value[key])
|
||||
if type(key) == unicode:
|
||||
key = key.encode("utf8")
|
||||
|
||||
newdct[key] = v
|
||||
|
||||
if len(args) > 0:
|
||||
if isinstance(value, InfoLabels):
|
||||
return InfoLabels(newdct)
|
||||
else:
|
||||
return newdct
|
||||
|
||||
else:
|
||||
return value
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# json_tools - JSON load and parse functions with library detection
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import traceback
|
||||
|
||||
import logger
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
logger.info("json incluido en el interprete **NO** disponible")
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except:
|
||||
logger.info("simplejson incluido en el interprete **NO** disponible")
|
||||
try:
|
||||
from lib import simplejson as json
|
||||
except:
|
||||
logger.info("simplejson en el directorio lib **NO** disponible")
|
||||
logger.error("No se ha encontrado un parser de JSON valido")
|
||||
json = None
|
||||
else:
|
||||
logger.info("Usando simplejson en el directorio lib")
|
||||
else:
|
||||
logger.info("Usando simplejson incluido en el interprete")
|
||||
else:
|
||||
logger.info("Usando json incluido en el interprete")
|
||||
|
||||
|
||||
def load(*args, **kwargs):
|
||||
if "object_hook" not in kwargs:
|
||||
kwargs["object_hook"] = to_utf8
|
||||
|
||||
try:
|
||||
value = json.loads(*args, **kwargs)
|
||||
except:
|
||||
logger.error("**NO** se ha podido cargar el JSON")
|
||||
logger.error(traceback.format_exc())
|
||||
value = {}
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def dump(*args, **kwargs):
|
||||
if not kwargs:
|
||||
kwargs = {"indent": 4, "skipkeys": True, "sort_keys": True, "ensure_ascii": False}
|
||||
|
||||
try:
|
||||
value = json.dumps(*args, **kwargs)
|
||||
except:
|
||||
logger.error("**NO** se ha podido cargar el JSON")
|
||||
logger.error(traceback.format_exc())
|
||||
value = ""
|
||||
return value
|
||||
|
||||
|
||||
def to_utf8(dct):
|
||||
if isinstance(dct, dict):
|
||||
return dict((to_utf8(key), to_utf8(value)) for key, value in dct.iteritems())
|
||||
elif isinstance(dct, list):
|
||||
return [to_utf8(element) for element in dct]
|
||||
elif isinstance(dct, unicode):
|
||||
return dct.encode('utf-8')
|
||||
else:
|
||||
return dct
|
||||
|
||||
|
||||
def get_node_from_file(name_file, node, path=None):
|
||||
"""
|
||||
Obtiene el nodo de un fichero JSON
|
||||
|
||||
@param name_file: Puede ser el nombre de un canal o server (sin incluir extension)
|
||||
o bien el nombre de un archivo json (con extension)
|
||||
@type name_file: str
|
||||
@param node: nombre del nodo a obtener
|
||||
@type node: str
|
||||
@param path: Ruta base del archivo json. Por defecto la ruta de settings_channels.
|
||||
@return: dict con el nodo a devolver
|
||||
@rtype: dict
|
||||
"""
|
||||
logger.info()
|
||||
from core import config
|
||||
from core import filetools
|
||||
|
||||
dict_node = {}
|
||||
|
||||
if not name_file.endswith(".json"):
|
||||
name_file += "_data.json"
|
||||
|
||||
if not path:
|
||||
path = filetools.join(config.get_data_path(), "settings_channels")
|
||||
|
||||
fname = filetools.join(path, name_file)
|
||||
|
||||
if filetools.isfile(fname):
|
||||
data = filetools.read(fname)
|
||||
dict_data = load(data)
|
||||
|
||||
check_to_backup(data, fname, dict_data)
|
||||
|
||||
if node in dict_data:
|
||||
dict_node = dict_data[node]
|
||||
|
||||
logger.debug("dict_node: %s" % dict_node)
|
||||
|
||||
return dict_node
|
||||
|
||||
|
||||
def check_to_backup(data, fname, dict_data):
|
||||
"""
|
||||
Comprueba que si dict_data(conversion del fichero JSON a dict) no es un diccionario, se genere un fichero con
|
||||
data de nombre fname.bk.
|
||||
|
||||
@param data: contenido del fichero fname
|
||||
@type data: str
|
||||
@param fname: nombre del fichero leido
|
||||
@type fname: str
|
||||
@param dict_data: nombre del diccionario
|
||||
@type dict_data: dict
|
||||
"""
|
||||
logger.info()
|
||||
|
||||
if not dict_data:
|
||||
logger.error("Error al cargar el json del fichero %s" % fname)
|
||||
|
||||
if data != "":
|
||||
# se crea un nuevo fichero
|
||||
from core import filetools
|
||||
title = filetools.write("%s.bk" % fname, data)
|
||||
if title != "":
|
||||
logger.error("Ha habido un error al guardar el fichero: %s.bk" % fname)
|
||||
else:
|
||||
logger.debug("Se ha guardado una copia con el nombre: %s.bk" % fname)
|
||||
else:
|
||||
logger.debug("Está vacío el fichero: %s" % fname)
|
||||
|
||||
|
||||
def update_node(dict_node, name_file, node, path=None):
|
||||
"""
|
||||
actualiza el json_data de un fichero con el diccionario pasado
|
||||
|
||||
@param dict_node: diccionario con el nodo
|
||||
@type dict_node: dict
|
||||
@param name_file: Puede ser el nombre de un canal o server (sin incluir extension)
|
||||
o bien el nombre de un archivo json (con extension)
|
||||
@type name_file: str
|
||||
@param node: nodo a actualizar
|
||||
@param path: Ruta base del archivo json. Por defecto la ruta de settings_channels.
|
||||
@return result: Devuelve True si se ha escrito correctamente o False si ha dado un error
|
||||
@rtype: bool
|
||||
@return json_data
|
||||
@rtype: dict
|
||||
"""
|
||||
logger.info()
|
||||
|
||||
from core import config
|
||||
from core import filetools
|
||||
json_data = {}
|
||||
result = False
|
||||
|
||||
if not name_file.endswith(".json"):
|
||||
name_file += "_data.json"
|
||||
|
||||
if not path:
|
||||
path = filetools.join(config.get_data_path(), "settings_channels")
|
||||
|
||||
fname = filetools.join(path, name_file)
|
||||
|
||||
try:
|
||||
data = filetools.read(fname)
|
||||
dict_data = load(data)
|
||||
# es un dict
|
||||
if dict_data:
|
||||
if node in dict_data:
|
||||
logger.debug(" existe el key %s" % node)
|
||||
dict_data[node] = dict_node
|
||||
else:
|
||||
logger.debug(" NO existe el key %s" % node)
|
||||
new_dict = {node: dict_node}
|
||||
dict_data.update(new_dict)
|
||||
else:
|
||||
logger.debug(" NO es un dict")
|
||||
dict_data = {node: dict_node}
|
||||
json_data = dump(dict_data)
|
||||
result = filetools.write(fname, json_data)
|
||||
except:
|
||||
logger.error("No se ha podido actualizar %s" % fname)
|
||||
|
||||
return result, json_data
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Logger (kodi)
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
import xbmc
|
||||
from core import config
|
||||
|
||||
loggeractive = (config.get_setting("debug") == True)
|
||||
|
||||
|
||||
def log_enable(active):
|
||||
global loggeractive
|
||||
loggeractive = active
|
||||
|
||||
|
||||
def encode_log(message=""):
|
||||
# Unicode to utf8
|
||||
if type(message) == unicode:
|
||||
message = message.encode("utf8")
|
||||
|
||||
# All encodings to utf8
|
||||
elif type(message) == str:
|
||||
message = unicode(message, "utf8", errors="replace").encode("utf8")
|
||||
|
||||
# Objects to string
|
||||
else:
|
||||
message = str(message)
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def get_caller(message=None):
|
||||
module = inspect.getmodule(inspect.currentframe().f_back.f_back)
|
||||
|
||||
module = module.__name__
|
||||
|
||||
function = inspect.currentframe().f_back.f_back.f_code.co_name
|
||||
|
||||
if module == "__main__":
|
||||
module = "alfa"
|
||||
else:
|
||||
module = "alfa." + module
|
||||
if message:
|
||||
if module not in message:
|
||||
if function == "<module>":
|
||||
return module + " " + message
|
||||
else:
|
||||
return module + " [" + function + "] " + message
|
||||
else:
|
||||
return message
|
||||
else:
|
||||
if function == "<module>":
|
||||
return module
|
||||
else:
|
||||
return module + "." + function
|
||||
|
||||
|
||||
def info(texto=""):
|
||||
if loggeractive:
|
||||
xbmc.log(get_caller(encode_log(texto)), xbmc.LOGNOTICE)
|
||||
|
||||
|
||||
def debug(texto=""):
|
||||
if loggeractive:
|
||||
texto = " [" + get_caller() + "] " + encode_log(texto)
|
||||
|
||||
xbmc.log("######## DEBUG #########", xbmc.LOGNOTICE)
|
||||
xbmc.log(texto, xbmc.LOGNOTICE)
|
||||
|
||||
|
||||
def error(texto=""):
|
||||
texto = " [" + get_caller() + "] " + encode_log(texto)
|
||||
|
||||
xbmc.log("######## ERROR #########", xbmc.LOGERROR)
|
||||
xbmc.log(texto, xbmc.LOGERROR)
|
||||
Executable
+275
@@ -0,0 +1,275 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from core import config
|
||||
from core import logger
|
||||
from core.item import InfoLabels
|
||||
from platformcode import platformtools
|
||||
|
||||
# Este modulo es una interface para poder implementar diferentes scrapers
|
||||
# contendra todos las funciones comunes
|
||||
|
||||
dict_default = None
|
||||
scraper = None
|
||||
|
||||
|
||||
def find_and_set_infoLabels(item):
|
||||
"""
|
||||
función que se llama para buscar y setear los infolabels
|
||||
:param item:
|
||||
:return: boleano que indica si se ha podido encontrar el 'code'
|
||||
"""
|
||||
global scraper
|
||||
scraper = None
|
||||
# logger.debug("item:\n" + item.tostring('\n'))
|
||||
|
||||
list_opciones_cuadro = ["Introducir otro nombre", "Completar información"]
|
||||
# Si se añaden más scrapers hay q declararlos aqui-> "modulo_scraper": "Texto_en_cuadro"
|
||||
scrapers_disponibles = {'tmdb': "Buscar en TheMovieDB.org",
|
||||
'tvdb': "Buscar en TheTvDB.com"}
|
||||
|
||||
# Obtener el Scraper por defecto de la configuracion segun el tipo de contenido
|
||||
if item.contentType == "movie":
|
||||
scraper_actual = ['tmdb'][config.get_setting("scraper_movies", "videolibrary")]
|
||||
tipo_contenido = "película"
|
||||
title = item.contentTitle
|
||||
# Completar lista de opciones para este tipo de contenido
|
||||
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
|
||||
|
||||
else:
|
||||
scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
|
||||
tipo_contenido = "serie"
|
||||
title = item.contentSerieName
|
||||
# Completar lista de opciones para este tipo de contenido
|
||||
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
|
||||
list_opciones_cuadro.append(scrapers_disponibles['tvdb'])
|
||||
|
||||
# Importamos el scraper
|
||||
try:
|
||||
scraper = __import__('core.%s' % scraper_actual, fromlist=["core.%s" % scraper_actual])
|
||||
except ImportError:
|
||||
exec "import core." + scraper_actual + " as scraper"
|
||||
except:
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
while scraper:
|
||||
# Llamamos a la funcion find_and_set_infoLabels del scraper seleccionado
|
||||
scraper_result = scraper.find_and_set_infoLabels(item)
|
||||
|
||||
# Verificar si existe 'code'
|
||||
if scraper_result and item.infoLabels['code']:
|
||||
# code correcto
|
||||
logger.info("Identificador encontrado: %s" % item.infoLabels['code'])
|
||||
scraper.completar_codigos(item)
|
||||
return True
|
||||
elif scraper_result:
|
||||
# Contenido encontrado pero no hay 'code'
|
||||
msg = "Identificador no encontrado para: %s" % title
|
||||
else:
|
||||
# Contenido no encontrado
|
||||
msg = "No se ha encontrado informacion para: %s" % title
|
||||
|
||||
logger.info(msg)
|
||||
# Mostrar cuadro con otras opciones:
|
||||
if scrapers_disponibles[scraper_actual] in list_opciones_cuadro:
|
||||
list_opciones_cuadro.remove(scrapers_disponibles[scraper_actual])
|
||||
index = platformtools.dialog_select(msg, list_opciones_cuadro)
|
||||
|
||||
if index < 0:
|
||||
logger.debug("Se ha pulsado 'cancelar' en la ventana '%s'" % msg)
|
||||
return False
|
||||
|
||||
elif index == 0:
|
||||
# Pregunta el titulo
|
||||
title = platformtools.dialog_input(title, "Introduzca el nombre de la %s a buscar" % tipo_contenido)
|
||||
if title:
|
||||
if item.contentType == "movie":
|
||||
item.contentTitle = title
|
||||
else:
|
||||
item.contentSerieName = title
|
||||
else:
|
||||
logger.debug("he pulsado 'cancelar' en la ventana 'Introduzca el nombre correcto'")
|
||||
return False
|
||||
|
||||
elif index == 1:
|
||||
# Hay q crear un cuadro de dialogo para introducir los datos
|
||||
logger.info("Completar información")
|
||||
if cuadro_completar(item):
|
||||
# code correcto
|
||||
logger.info("Identificador encontrado: %s" % str(item.infoLabels['code']))
|
||||
return True
|
||||
# raise
|
||||
|
||||
elif list_opciones_cuadro[index] in scrapers_disponibles.values():
|
||||
# Obtener el nombre del modulo del scraper
|
||||
for k, v in scrapers_disponibles.items():
|
||||
if list_opciones_cuadro[index] == v:
|
||||
if scrapers_disponibles[scraper_actual] not in list_opciones_cuadro:
|
||||
list_opciones_cuadro.append(scrapers_disponibles[scraper_actual])
|
||||
# Importamos el scraper k
|
||||
scraper_actual = k
|
||||
try:
|
||||
scraper = None
|
||||
scraper = __import__('core.%s' % scraper_actual, fromlist=["core.%s" % scraper_actual])
|
||||
except ImportError:
|
||||
exec "import core." + scraper_actual + " as scraper_module"
|
||||
break
|
||||
|
||||
logger.error("Error al importar el modulo scraper %s" % scraper_actual)
|
||||
|
||||
|
||||
def cuadro_completar(item):
|
||||
logger.info()
|
||||
|
||||
global dict_default
|
||||
dict_default = {}
|
||||
|
||||
COLOR = ["0xFF8A4B08", "0xFFF7BE81"]
|
||||
# Creamos la lista de campos del infoLabel
|
||||
controls = [("title", "text", "Titulo:"),
|
||||
("originaltitle", "text", "Titulo original"),
|
||||
("year", "text", "Año"),
|
||||
("identificadores", "label", "Identificadores:"),
|
||||
("tmdb_id", "text", " The Movie Database ID"),
|
||||
("url_tmdb", "text", " URL Tmdb", "+!eq(-1,'')"),
|
||||
("tvdb_id", "text", " The TVDB ID", "+eq(-7,'Serie')"),
|
||||
("url_tvdb", "text", " URL TVDB", "+!eq(-1,'')+eq(-8,'Serie')"),
|
||||
("imdb_id", "text", " IMDb ID"),
|
||||
("otro_id", "text", " Otro ID", "+eq(-1,'')"),
|
||||
("urls", "label", "Imágenes (urls):"),
|
||||
("fanart", "text", " Fondo"),
|
||||
("thumbnail", "text", " Miniatura")]
|
||||
|
||||
if item.infoLabels["mediatype"] == "movie":
|
||||
mediatype_default = 0
|
||||
else:
|
||||
mediatype_default = 1
|
||||
|
||||
listado_controles = [{'id': "mediatype",
|
||||
'type': "list",
|
||||
'label': "Tipo de contenido",
|
||||
'color': COLOR[1],
|
||||
'default': mediatype_default,
|
||||
'enabled': True,
|
||||
'visible': True,
|
||||
'lvalues': ["Película", "Serie"]
|
||||
}]
|
||||
|
||||
for i, c in enumerate(controls):
|
||||
color = COLOR[0]
|
||||
dict_default[c[0]] = item.infoLabels.get(c[0], '')
|
||||
|
||||
enabled = True
|
||||
|
||||
if i > 0 and c[1] != 'label':
|
||||
color = COLOR[1]
|
||||
enabled = "!eq(-%s,'')" % i
|
||||
if len(c) > 3:
|
||||
enabled += c[3]
|
||||
|
||||
# default para casos especiales
|
||||
if c[0] == "url_tmdb" and item.infoLabels["tmdb_id"] and 'tmdb' in item.infoLabels["url_scraper"]:
|
||||
dict_default[c[0]] = item.infoLabels["url_scraper"]
|
||||
|
||||
elif c[0] == "url_tvdb" and item.infoLabels["tvdb_id"] and 'thetvdb.com' in item.infoLabels["url_scraper"]:
|
||||
dict_default[c[0]] = item.infoLabels["url_scraper"]
|
||||
|
||||
if not dict_default[c[0]] or dict_default[c[0]] == 'None' or dict_default[c[0]] == 0:
|
||||
dict_default[c[0]] = ''
|
||||
elif isinstance(dict_default[c[0]], (int, float, long)):
|
||||
# Si es numerico lo convertimos en str
|
||||
dict_default[c[0]] = str(dict_default[c[0]])
|
||||
|
||||
listado_controles.append({'id': c[0],
|
||||
'type': c[1],
|
||||
'label': c[2],
|
||||
'color': color,
|
||||
'default': dict_default[c[0]],
|
||||
'enabled': enabled,
|
||||
'visible': True})
|
||||
|
||||
# logger.debug(dict_default)
|
||||
if platformtools.show_channel_settings(listado_controles, caption="Completar información", item=item,
|
||||
callback="core.scraper.callback_cuadro_completar",
|
||||
custom_button={"visible": False}):
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def callback_cuadro_completar(item, dict_values):
|
||||
# logger.debug(dict_values)
|
||||
global dict_default
|
||||
|
||||
if dict_values.get("title", None):
|
||||
# Adaptar dict_values a infoLabels validos
|
||||
dict_values['mediatype'] = ['movie', 'tvshow'][dict_values['mediatype']]
|
||||
for k, v in dict_values.items():
|
||||
if k in dict_default and dict_default[k] == dict_values[k]:
|
||||
del dict_values[k]
|
||||
|
||||
if isinstance(item.infoLabels, InfoLabels):
|
||||
infoLabels = item.infoLabels
|
||||
else:
|
||||
infoLabels = InfoLabels()
|
||||
|
||||
infoLabels.update(dict_values)
|
||||
item.infoLabels = infoLabels
|
||||
|
||||
if item.infoLabels['code']:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_nfo(item):
|
||||
"""
|
||||
Devuelve la información necesaria para que se scrapee el resultado en la videoteca de kodi,
|
||||
|
||||
@param item: elemento que contiene los datos necesarios para generar la info
|
||||
@type item: Item
|
||||
@rtype: str
|
||||
@return:
|
||||
"""
|
||||
logger.info()
|
||||
if "infoLabels" in item and "noscrap_id" in item.infoLabels:
|
||||
# Crea el fichero xml con los datos que se obtiene de item ya que no hay ningún scraper activo
|
||||
info_nfo = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'
|
||||
|
||||
if "season" in item.infoLabels and "episode" in item.infoLabels:
|
||||
info_nfo += '<episodedetails><title>%s</title>' % item.infoLabels['title']
|
||||
info_nfo += '<showtitle>%s</showtitle>' % item.infoLabels['tvshowtitle']
|
||||
info_nfo += '<thumb>%s</thumb>' % item.thumbnail
|
||||
|
||||
info_nfo += '</episodedetails>\n'
|
||||
|
||||
elif item.infoLabels["mediatype"] == "tvshow":
|
||||
info_nfo += '<tvshow><title>%s</title>' % item.infoLabels['title']
|
||||
info_nfo += '<thumb aspect="poster">%s</thumb>' % item.thumbnail
|
||||
info_nfo += '<fanart><thumb>%s</thumb></fanart>' % item.fanart
|
||||
|
||||
info_nfo += '</tvshow>\n'
|
||||
|
||||
else:
|
||||
info_nfo += '<movie><title>%s</title>' % item.infoLabels['title']
|
||||
info_nfo += '<thumb aspect="poster">%s</thumb>' % item.thumbnail
|
||||
info_nfo += '<fanart><thumb>%s</thumb></fanart>' % item.fanart
|
||||
|
||||
info_nfo += '</movie>\n'
|
||||
|
||||
return info_nfo
|
||||
else:
|
||||
return scraper.get_nfo(item)
|
||||
|
||||
|
||||
def sort_episode_list(episodelist):
|
||||
scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
|
||||
|
||||
if scraper_actual == "tmdb":
|
||||
episodelist.sort(key=lambda e: (int(e.contentSeason), int(e.contentEpisodeNumber)))
|
||||
|
||||
elif scraper_actual == "tvdb":
|
||||
episodelist.sort(key=lambda e: (int(e.contentEpisodeNumber), int(e.contentSeason)))
|
||||
|
||||
return episodelist
|
||||
Executable
+498
@@ -0,0 +1,498 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Scraper tools for reading and processing web elements
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import logger
|
||||
from core import httptools
|
||||
|
||||
|
||||
def cache_page(url, post=None, headers=None, modo_cache=None, timeout=None):
|
||||
return cachePage(url, post, headers, modo_cache, timeout=timeout)
|
||||
|
||||
|
||||
def cachePage(url, post=None, headers=None, modoCache=None, timeout=None):
|
||||
data = downloadpage(url, post=post, headers=headers, timeout=timeout)
|
||||
return data
|
||||
|
||||
|
||||
def downloadpage(url, post=None, headers=None, follow_redirects=True, timeout=None, header_to_get=None):
|
||||
response = httptools.downloadpage(url, post=post, headers=headers, follow_redirects=follow_redirects,
|
||||
timeout=timeout)
|
||||
|
||||
if header_to_get:
|
||||
return response.headers.get(header_to_get)
|
||||
else:
|
||||
return response.data
|
||||
|
||||
|
||||
def downloadpageWithResult(url, post=None, headers=None, follow_redirects=True, timeout=None, header_to_get=None):
|
||||
response = httptools.downloadpage(url, post=post, headers=headers, follow_redirects=follow_redirects,
|
||||
timeout=timeout)
|
||||
|
||||
if header_to_get:
|
||||
return response.headers.get(header_to_get)
|
||||
else:
|
||||
return response.data, response.code
|
||||
|
||||
|
||||
def downloadpageWithoutCookies(url):
|
||||
response = httptools.downloadpage(url, cookies=False)
|
||||
return response.data
|
||||
|
||||
|
||||
def downloadpageGzip(url):
|
||||
response = httptools.downloadpage(url, add_referer=True)
|
||||
return response.data
|
||||
|
||||
|
||||
def getLocationHeaderFromResponse(url):
|
||||
response = httptools.downloadpage(url, only_headers=True)
|
||||
return response.headers.get("location")
|
||||
|
||||
|
||||
def get_header_from_response(url, header_to_get="", post=None, headers=None):
|
||||
header_to_get = header_to_get.lower()
|
||||
response = httptools.downloadpage(url, post=post, headers=headers, only_headers=True)
|
||||
return response.headers.get(header_to_get)
|
||||
|
||||
|
||||
def get_headers_from_response(url, post=None, headers=None):
|
||||
response = httptools.downloadpage(url, post=post, headers=headers, only_headers=True)
|
||||
return response.headers.items()
|
||||
|
||||
|
||||
def read_body_and_headers(url, post=None, headers=None, follow_redirects=False, timeout=None):
|
||||
response = httptools.downloadpage(url, post=post, headers=headers, follow_redirects=follow_redirects,
|
||||
timeout=timeout)
|
||||
return response.data, response.headers
|
||||
|
||||
|
||||
def anti_cloudflare(url, host="", headers=None, post=None, location=False):
|
||||
# anti_cloudfare ya integrado en httptools por defecto
|
||||
response = httptools.downloadpage(url, post=post, headers=headers)
|
||||
return response.data
|
||||
|
||||
|
||||
def printMatches(matches):
|
||||
i = 0
|
||||
for match in matches:
|
||||
logger.info("%d %s" % (i, match))
|
||||
i = i + 1
|
||||
|
||||
|
||||
def get_match(data, patron, index=0):
|
||||
matches = re.findall(patron, data, flags=re.DOTALL)
|
||||
return matches[index]
|
||||
|
||||
|
||||
def find_single_match(data, patron, index=0):
|
||||
try:
|
||||
matches = re.findall(patron, data, flags=re.DOTALL)
|
||||
return matches[index]
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
# Parse string and extracts multiple matches using regular expressions
|
||||
def find_multiple_matches(text, pattern):
|
||||
return re.findall(pattern, text, re.DOTALL)
|
||||
|
||||
|
||||
def entityunescape(cadena):
|
||||
return unescape(cadena)
|
||||
|
||||
|
||||
def unescape(text):
|
||||
"""Removes HTML or XML character references
|
||||
and entities from a text string.
|
||||
keep &, >, < in the source code.
|
||||
from Fredrik Lundh
|
||||
http://effbot.org/zone/re-sub.htm#unescape-html
|
||||
"""
|
||||
|
||||
def fixup(m):
|
||||
text = m.group(0)
|
||||
if text[:2] == "&#":
|
||||
# character reference
|
||||
try:
|
||||
if text[:3] == "&#x":
|
||||
return unichr(int(text[3:-1], 16)).encode("utf-8")
|
||||
else:
|
||||
return unichr(int(text[2:-1])).encode("utf-8")
|
||||
|
||||
except ValueError:
|
||||
logger.error("error de valor")
|
||||
pass
|
||||
else:
|
||||
# named entity
|
||||
try:
|
||||
'''
|
||||
if text[1:-1] == "amp":
|
||||
text = "&amp;"
|
||||
elif text[1:-1] == "gt":
|
||||
text = "&gt;"
|
||||
elif text[1:-1] == "lt":
|
||||
text = "&lt;"
|
||||
else:
|
||||
print text[1:-1]
|
||||
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]).encode("utf-8")
|
||||
'''
|
||||
import htmlentitydefs
|
||||
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]).encode("utf-8")
|
||||
except KeyError:
|
||||
logger.error("keyerror")
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
return text # leave as is
|
||||
|
||||
return re.sub("&#?\w+;", fixup, text)
|
||||
|
||||
# Convierte los codigos html "ñ" y lo reemplaza por "ñ" caracter unicode utf-8
|
||||
|
||||
|
||||
def decodeHtmlentities(string):
|
||||
string = entitiesfix(string)
|
||||
entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")
|
||||
|
||||
def substitute_entity(match):
|
||||
from htmlentitydefs import name2codepoint as n2cp
|
||||
ent = match.group(2)
|
||||
if match.group(1) == "#":
|
||||
return unichr(int(ent)).encode('utf-8')
|
||||
else:
|
||||
cp = n2cp.get(ent)
|
||||
|
||||
if cp:
|
||||
return unichr(cp).encode('utf-8')
|
||||
else:
|
||||
return match.group()
|
||||
|
||||
return entity_re.subn(substitute_entity, string)[0]
|
||||
|
||||
|
||||
def entitiesfix(string):
|
||||
# Las entidades comienzan siempre con el símbolo & , y terminan con un punto y coma ( ; ).
|
||||
string = string.replace("á", "á")
|
||||
string = string.replace("é", "é")
|
||||
string = string.replace("í", "í")
|
||||
string = string.replace("ó", "ó")
|
||||
string = string.replace("ú", "ú")
|
||||
string = string.replace("Á", "Á")
|
||||
string = string.replace("É", "É")
|
||||
string = string.replace("Í", "Í")
|
||||
string = string.replace("Ó", "Ó")
|
||||
string = string.replace("Ú", "Ú")
|
||||
string = string.replace("ü", "ü")
|
||||
string = string.replace("Ü", "Ü")
|
||||
string = string.replace("ñ", "ñ")
|
||||
string = string.replace("¿", "¿")
|
||||
string = string.replace("¡", "¡")
|
||||
string = string.replace(";;", ";")
|
||||
return string
|
||||
|
||||
|
||||
def htmlclean(cadena):
|
||||
cadena = re.compile("<!--.*?-->", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = cadena.replace("<center>", "")
|
||||
cadena = cadena.replace("</center>", "")
|
||||
cadena = cadena.replace("<cite>", "")
|
||||
cadena = cadena.replace("</cite>", "")
|
||||
cadena = cadena.replace("<em>", "")
|
||||
cadena = cadena.replace("</em>", "")
|
||||
cadena = cadena.replace("<u>", "")
|
||||
cadena = cadena.replace("</u>", "")
|
||||
cadena = cadena.replace("<li>", "")
|
||||
cadena = cadena.replace("</li>", "")
|
||||
cadena = cadena.replace("<turl>", "")
|
||||
cadena = cadena.replace("</tbody>", "")
|
||||
cadena = cadena.replace("<tr>", "")
|
||||
cadena = cadena.replace("</tr>", "")
|
||||
cadena = cadena.replace("<![CDATA[", "")
|
||||
cadena = cadena.replace("<Br />", " ")
|
||||
cadena = cadena.replace("<BR />", " ")
|
||||
cadena = cadena.replace("<Br>", " ")
|
||||
cadena = re.compile("<br[^>]*>", re.DOTALL).sub(" ", cadena)
|
||||
|
||||
cadena = re.compile("<script.*?</script>", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = re.compile("<option[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</option>", "")
|
||||
|
||||
cadena = re.compile("<button[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</button>", "")
|
||||
|
||||
cadena = re.compile("<i[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</iframe>", "")
|
||||
cadena = cadena.replace("</i>", "")
|
||||
|
||||
cadena = re.compile("<table[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</table>", "")
|
||||
|
||||
cadena = re.compile("<td[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</td>", "")
|
||||
|
||||
cadena = re.compile("<div[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</div>", "")
|
||||
|
||||
cadena = re.compile("<dd[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</dd>", "")
|
||||
|
||||
cadena = re.compile("<b[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</b>", "")
|
||||
|
||||
cadena = re.compile("<font[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</font>", "")
|
||||
|
||||
cadena = re.compile("<strong[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</strong>", "")
|
||||
|
||||
cadena = re.compile("<small[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</small>", "")
|
||||
|
||||
cadena = re.compile("<span[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</span>", "")
|
||||
|
||||
cadena = re.compile("<a[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</a>", "")
|
||||
|
||||
cadena = re.compile("<p[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</p>", "")
|
||||
|
||||
cadena = re.compile("<ul[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</ul>", "")
|
||||
|
||||
cadena = re.compile("<h1[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h1>", "")
|
||||
|
||||
cadena = re.compile("<h2[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h2>", "")
|
||||
|
||||
cadena = re.compile("<h3[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h3>", "")
|
||||
|
||||
cadena = re.compile("<h4[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h4>", "")
|
||||
|
||||
cadena = re.compile("<!--[^-]+-->", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = re.compile("<img[^>]*>", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = re.compile("<object[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</object>", "")
|
||||
cadena = re.compile("<param[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</param>", "")
|
||||
cadena = re.compile("<embed[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</embed>", "")
|
||||
|
||||
cadena = re.compile("<title[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</title>", "")
|
||||
|
||||
cadena = re.compile("<link[^>]*>", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = cadena.replace("\t", "")
|
||||
cadena = entityunescape(cadena)
|
||||
return cadena
|
||||
|
||||
|
||||
def slugify(title):
|
||||
# print title
|
||||
|
||||
# Sustituye acentos y eñes
|
||||
title = title.replace("Á", "a")
|
||||
title = title.replace("É", "e")
|
||||
title = title.replace("Í", "i")
|
||||
title = title.replace("Ó", "o")
|
||||
title = title.replace("Ú", "u")
|
||||
title = title.replace("á", "a")
|
||||
title = title.replace("é", "e")
|
||||
title = title.replace("í", "i")
|
||||
title = title.replace("ó", "o")
|
||||
title = title.replace("ú", "u")
|
||||
title = title.replace("À", "a")
|
||||
title = title.replace("È", "e")
|
||||
title = title.replace("Ì", "i")
|
||||
title = title.replace("Ò", "o")
|
||||
title = title.replace("Ù", "u")
|
||||
title = title.replace("à", "a")
|
||||
title = title.replace("è", "e")
|
||||
title = title.replace("ì", "i")
|
||||
title = title.replace("ò", "o")
|
||||
title = title.replace("ù", "u")
|
||||
title = title.replace("ç", "c")
|
||||
title = title.replace("Ç", "C")
|
||||
title = title.replace("Ñ", "n")
|
||||
title = title.replace("ñ", "n")
|
||||
title = title.replace("/", "-")
|
||||
title = title.replace("&", "&")
|
||||
|
||||
# Pasa a minúsculas
|
||||
title = title.lower().strip()
|
||||
|
||||
# Elimina caracteres no válidos
|
||||
validchars = "abcdefghijklmnopqrstuvwxyz1234567890- "
|
||||
title = ''.join(c for c in title if c in validchars)
|
||||
|
||||
# Sustituye espacios en blanco duplicados y saltos de línea
|
||||
title = re.compile("\s+", re.DOTALL).sub(" ", title)
|
||||
|
||||
# Sustituye espacios en blanco por guiones
|
||||
title = re.compile("\s", re.DOTALL).sub("-", title.strip())
|
||||
|
||||
# Sustituye espacios en blanco duplicados y saltos de línea
|
||||
title = re.compile("\-+", re.DOTALL).sub("-", title)
|
||||
|
||||
# Arregla casos especiales
|
||||
if title.startswith("-"):
|
||||
title = title[1:]
|
||||
|
||||
if title == "":
|
||||
title = "-" + str(time.time())
|
||||
|
||||
return title
|
||||
|
||||
|
||||
def remove_htmltags(string):
|
||||
return re.sub('<[^<]+?>', '', string)
|
||||
|
||||
|
||||
def remove_show_from_title(title, show):
|
||||
# print slugify(title)+" == "+slugify(show)
|
||||
# Quita el nombre del programa del título
|
||||
if slugify(title).startswith(slugify(show)):
|
||||
|
||||
# Convierte a unicode primero, o el encoding se pierde
|
||||
title = unicode(title, "utf-8", "replace")
|
||||
show = unicode(show, "utf-8", "replace")
|
||||
title = title[len(show):].strip()
|
||||
|
||||
if title.startswith("-"):
|
||||
title = title[1:].strip()
|
||||
|
||||
if title == "":
|
||||
title = str(time.time())
|
||||
|
||||
# Vuelve a utf-8
|
||||
title = title.encode("utf-8", "ignore")
|
||||
show = show.encode("utf-8", "ignore")
|
||||
|
||||
return title
|
||||
|
||||
|
||||
def getRandom(str):
|
||||
return get_md5(str)
|
||||
|
||||
|
||||
def unseo(cadena):
|
||||
if cadena.upper().startswith("VER GRATIS LA PELICULA "):
|
||||
cadena = cadena[23:]
|
||||
elif cadena.upper().startswith("VER GRATIS PELICULA "):
|
||||
cadena = cadena[20:]
|
||||
elif cadena.upper().startswith("VER ONLINE LA PELICULA "):
|
||||
cadena = cadena[23:]
|
||||
elif cadena.upper().startswith("VER GRATIS "):
|
||||
cadena = cadena[11:]
|
||||
elif cadena.upper().startswith("VER ONLINE "):
|
||||
cadena = cadena[11:]
|
||||
elif cadena.upper().startswith("DESCARGA DIRECTA "):
|
||||
cadena = cadena[17:]
|
||||
return cadena
|
||||
|
||||
|
||||
# scrapertools.get_filename_from_url(media_url)[-4:]
|
||||
def get_filename_from_url(url):
|
||||
import urlparse
|
||||
parsed_url = urlparse.urlparse(url)
|
||||
try:
|
||||
filename = parsed_url.path
|
||||
except:
|
||||
# Si falla es porque la implementación de parsed_url no reconoce los atributos como "path"
|
||||
if len(parsed_url) >= 4:
|
||||
filename = parsed_url[2]
|
||||
else:
|
||||
filename = ""
|
||||
|
||||
if "/" in filename:
|
||||
filename = filename.split("/")[-1]
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_domain_from_url(url):
|
||||
import urlparse
|
||||
parsed_url = urlparse.urlparse(url)
|
||||
try:
|
||||
filename = parsed_url.netloc
|
||||
except:
|
||||
# Si falla es porque la implementación de parsed_url no reconoce los atributos como "path"
|
||||
if len(parsed_url) >= 4:
|
||||
filename = parsed_url[1]
|
||||
else:
|
||||
filename = ""
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_season_and_episode(title):
|
||||
"""
|
||||
Retorna el numero de temporada y de episodio en formato "1x01" obtenido del titulo de un episodio
|
||||
Ejemplos de diferentes valores para title y su valor devuelto:
|
||||
"serie 101x1.strm", "s101e1.avi", "t101e1.avi" -> '101x01'
|
||||
"Name TvShow 1x6.avi" -> '1x06'
|
||||
"Temp 3 episodio 2.avi" -> '3x02'
|
||||
"Alcantara season 13 episodie 12.avi" -> '13x12'
|
||||
"Temp1 capitulo 14" -> '1x14'
|
||||
"Temporada 1: El origen Episodio 9" -> '' (entre el numero de temporada y los episodios no puede haber otro texto)
|
||||
"Episodio 25: titulo episodio" -> '' (no existe el numero de temporada)
|
||||
"Serie X Temporada 1" -> '' (no existe el numero del episodio)
|
||||
@type title: str
|
||||
@param title: titulo del episodio de una serie
|
||||
@rtype: str
|
||||
@return: Numero de temporada y episodio en formato "1x01" o cadena vacia si no se han encontrado
|
||||
"""
|
||||
filename = ""
|
||||
|
||||
patrons = ["(\d+)x(\d+)", "(?:s|t)(\d+)e(\d+)",
|
||||
"(?:season|temp\w*)\s*(\d+)\s*(?:capitulo|epi\w*)\s*(\d+)"]
|
||||
|
||||
for patron in patrons:
|
||||
try:
|
||||
matches = re.compile(patron, re.I).search(title)
|
||||
if matches:
|
||||
filename = matches.group(1) + "x" + matches.group(2).zfill(2)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
logger.info("'" + title + "' -> '" + filename + "'")
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_sha1(cadena):
|
||||
try:
|
||||
import hashlib
|
||||
devuelve = hashlib.sha1(cadena).hexdigest()
|
||||
except:
|
||||
import sha
|
||||
import binascii
|
||||
devuelve = binascii.hexlify(sha.new(cadena).digest())
|
||||
|
||||
return devuelve
|
||||
|
||||
|
||||
def get_md5(cadena):
|
||||
try:
|
||||
import hashlib
|
||||
devuelve = hashlib.md5(cadena).hexdigest()
|
||||
except:
|
||||
import md5
|
||||
import binascii
|
||||
devuelve = binascii.hexlify(md5.new(cadena).digest())
|
||||
|
||||
return devuelve
|
||||
Executable
+343
@@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Scraper tools v2 for reading and processing web elements
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
import logger
|
||||
from core.entities import html5
|
||||
|
||||
|
||||
def printMatches(matches):
|
||||
i = 0
|
||||
for match in matches:
|
||||
logger.info("%d %s" % (i, match))
|
||||
i = i + 1
|
||||
|
||||
|
||||
def get_match(data, patron, index=0):
|
||||
return find_single_match(data, patron, index=0)
|
||||
|
||||
|
||||
def find_single_match(data, patron, index=0):
|
||||
try:
|
||||
matches = re.findall(patron, data, flags=re.DOTALL)
|
||||
return matches[index]
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
# Parse string and extracts multiple matches using regular expressions
|
||||
def find_multiple_matches(text, pattern):
|
||||
return re.findall(pattern, text, re.DOTALL)
|
||||
|
||||
|
||||
# Convierte los codigos html "ñ" y lo reemplaza por "ñ" caracter unicode utf-8
|
||||
def decodeHtmlentities(data):
|
||||
entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8})(;?)")
|
||||
|
||||
def substitute_entity(match):
|
||||
ent = match.group(2) + match.group(3)
|
||||
res = ""
|
||||
while not ent in html5 and not ent.endswith(";") and match.group(1) != "#":
|
||||
# Excepción para cuando '&' se usa como argumento en la urls contenidas en los datos
|
||||
try:
|
||||
res = ent[-1] + res
|
||||
ent = ent[:-1]
|
||||
except:
|
||||
break
|
||||
|
||||
if match.group(1) == "#":
|
||||
ent = unichr(int(ent.replace(";", "")))
|
||||
return ent.encode('utf-8')
|
||||
else:
|
||||
cp = html5.get(ent)
|
||||
if cp:
|
||||
return cp.decode("unicode-escape").encode('utf-8') + res
|
||||
else:
|
||||
return match.group()
|
||||
|
||||
return entity_re.subn(substitute_entity, data)[0]
|
||||
|
||||
|
||||
def htmlclean(cadena):
|
||||
cadena = re.compile("<!--.*?-->", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = cadena.replace("<center>", "")
|
||||
cadena = cadena.replace("</center>", "")
|
||||
cadena = cadena.replace("<cite>", "")
|
||||
cadena = cadena.replace("</cite>", "")
|
||||
cadena = cadena.replace("<em>", "")
|
||||
cadena = cadena.replace("</em>", "")
|
||||
cadena = cadena.replace("<u>", "")
|
||||
cadena = cadena.replace("</u>", "")
|
||||
cadena = cadena.replace("<li>", "")
|
||||
cadena = cadena.replace("</li>", "")
|
||||
cadena = cadena.replace("<turl>", "")
|
||||
cadena = cadena.replace("</tbody>", "")
|
||||
cadena = cadena.replace("<tr>", "")
|
||||
cadena = cadena.replace("</tr>", "")
|
||||
cadena = cadena.replace("<![CDATA[", "")
|
||||
cadena = cadena.replace("<Br />", " ")
|
||||
cadena = cadena.replace("<BR />", " ")
|
||||
cadena = cadena.replace("<Br>", " ")
|
||||
cadena = re.compile("<br[^>]*>", re.DOTALL).sub(" ", cadena)
|
||||
|
||||
cadena = re.compile("<script.*?</script>", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = re.compile("<option[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</option>", "")
|
||||
|
||||
cadena = re.compile("<button[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</button>", "")
|
||||
|
||||
cadena = re.compile("<i[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</iframe>", "")
|
||||
cadena = cadena.replace("</i>", "")
|
||||
|
||||
cadena = re.compile("<table[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</table>", "")
|
||||
|
||||
cadena = re.compile("<td[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</td>", "")
|
||||
|
||||
cadena = re.compile("<div[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</div>", "")
|
||||
|
||||
cadena = re.compile("<dd[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</dd>", "")
|
||||
|
||||
cadena = re.compile("<b[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</b>", "")
|
||||
|
||||
cadena = re.compile("<font[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</font>", "")
|
||||
|
||||
cadena = re.compile("<strong[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</strong>", "")
|
||||
|
||||
cadena = re.compile("<small[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</small>", "")
|
||||
|
||||
cadena = re.compile("<span[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</span>", "")
|
||||
|
||||
cadena = re.compile("<a[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</a>", "")
|
||||
|
||||
cadena = re.compile("<p[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</p>", "")
|
||||
|
||||
cadena = re.compile("<ul[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</ul>", "")
|
||||
|
||||
cadena = re.compile("<h1[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h1>", "")
|
||||
|
||||
cadena = re.compile("<h2[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h2>", "")
|
||||
|
||||
cadena = re.compile("<h3[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h3>", "")
|
||||
|
||||
cadena = re.compile("<h4[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</h4>", "")
|
||||
|
||||
cadena = re.compile("<!--[^-]+-->", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = re.compile("<img[^>]*>", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = re.compile("<object[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</object>", "")
|
||||
cadena = re.compile("<param[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</param>", "")
|
||||
cadena = re.compile("<embed[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</embed>", "")
|
||||
|
||||
cadena = re.compile("<title[^>]*>", re.DOTALL).sub("", cadena)
|
||||
cadena = cadena.replace("</title>", "")
|
||||
|
||||
cadena = re.compile("<link[^>]*>", re.DOTALL).sub("", cadena)
|
||||
|
||||
cadena = cadena.replace("\t", "")
|
||||
# cadena = entityunescape(cadena)
|
||||
return cadena
|
||||
|
||||
|
||||
def slugify(title):
|
||||
# print title
|
||||
|
||||
# Sustituye acentos y eñes
|
||||
title = title.replace("Á", "a")
|
||||
title = title.replace("É", "e")
|
||||
title = title.replace("Í", "i")
|
||||
title = title.replace("Ó", "o")
|
||||
title = title.replace("Ú", "u")
|
||||
title = title.replace("á", "a")
|
||||
title = title.replace("é", "e")
|
||||
title = title.replace("í", "i")
|
||||
title = title.replace("ó", "o")
|
||||
title = title.replace("ú", "u")
|
||||
title = title.replace("À", "a")
|
||||
title = title.replace("È", "e")
|
||||
title = title.replace("Ì", "i")
|
||||
title = title.replace("Ò", "o")
|
||||
title = title.replace("Ù", "u")
|
||||
title = title.replace("à", "a")
|
||||
title = title.replace("è", "e")
|
||||
title = title.replace("ì", "i")
|
||||
title = title.replace("ò", "o")
|
||||
title = title.replace("ù", "u")
|
||||
title = title.replace("ç", "c")
|
||||
title = title.replace("Ç", "C")
|
||||
title = title.replace("Ñ", "n")
|
||||
title = title.replace("ñ", "n")
|
||||
title = title.replace("/", "-")
|
||||
title = title.replace("&", "&")
|
||||
|
||||
# Pasa a minúsculas
|
||||
title = title.lower().strip()
|
||||
|
||||
# Elimina caracteres no válidos
|
||||
validchars = "abcdefghijklmnopqrstuvwxyz1234567890- "
|
||||
title = ''.join(c for c in title if c in validchars)
|
||||
|
||||
# Sustituye espacios en blanco duplicados y saltos de línea
|
||||
title = re.compile("\s+", re.DOTALL).sub(" ", title)
|
||||
|
||||
# Sustituye espacios en blanco por guiones
|
||||
title = re.compile("\s", re.DOTALL).sub("-", title.strip())
|
||||
|
||||
# Sustituye espacios en blanco duplicados y saltos de línea
|
||||
title = re.compile("\-+", re.DOTALL).sub("-", title)
|
||||
|
||||
# Arregla casos especiales
|
||||
if title.startswith("-"):
|
||||
title = title[1:]
|
||||
|
||||
if title == "":
|
||||
title = "-" + str(time.time())
|
||||
|
||||
return title
|
||||
|
||||
|
||||
def remove_htmltags(string):
|
||||
return re.sub('<[^<]+?>', '', string)
|
||||
|
||||
|
||||
def remove_show_from_title(title, show):
|
||||
# print slugify(title)+" == "+slugify(show)
|
||||
# Quita el nombre del programa del título
|
||||
if slugify(title).startswith(slugify(show)):
|
||||
|
||||
# Convierte a unicode primero, o el encoding se pierde
|
||||
title = unicode(title, "utf-8", "replace")
|
||||
show = unicode(show, "utf-8", "replace")
|
||||
title = title[len(show):].strip()
|
||||
|
||||
if title.startswith("-"):
|
||||
title = title[1:].strip()
|
||||
|
||||
if title == "":
|
||||
title = str(time.time())
|
||||
|
||||
# Vuelve a utf-8
|
||||
title = title.encode("utf-8", "ignore")
|
||||
show = show.encode("utf-8", "ignore")
|
||||
|
||||
return title
|
||||
|
||||
|
||||
# scrapertools.get_filename_from_url(media_url)[-4:]
|
||||
def get_filename_from_url(url):
|
||||
parsed_url = urlparse.urlparse(url)
|
||||
try:
|
||||
filename = parsed_url.path
|
||||
except:
|
||||
# Si falla es porque la implementación de parsed_url no reconoce los atributos como "path"
|
||||
if len(parsed_url) >= 4:
|
||||
filename = parsed_url[2]
|
||||
else:
|
||||
filename = ""
|
||||
|
||||
if "/" in filename:
|
||||
filename = filename.split("/")[-1]
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_domain_from_url(url):
|
||||
parsed_url = urlparse.urlparse(url)
|
||||
try:
|
||||
filename = parsed_url.netloc
|
||||
except:
|
||||
# Si falla es porque la implementación de parsed_url no reconoce los atributos como "path"
|
||||
if len(parsed_url) >= 4:
|
||||
filename = parsed_url[1]
|
||||
else:
|
||||
filename = ""
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_season_and_episode(title):
|
||||
"""
|
||||
Retorna el numero de temporada y de episodio en formato "1x01" obtenido del titulo de un episodio
|
||||
Ejemplos de diferentes valores para title y su valor devuelto:
|
||||
"serie 101x1.strm", "s101e1.avi", "t101e1.avi" -> '101x01'
|
||||
"Name TvShow 1x6.avi" -> '1x06'
|
||||
"Temp 3 episodio 2.avi" -> '3x02'
|
||||
"Alcantara season 13 episodie 12.avi" -> '13x12'
|
||||
"Temp1 capitulo 14" -> '1x14'
|
||||
"Temporada 1: El origen Episodio 9" -> '' (entre el numero de temporada y los episodios no puede haber otro texto)
|
||||
"Episodio 25: titulo episodio" -> '' (no existe el numero de temporada)
|
||||
"Serie X Temporada 1" -> '' (no existe el numero del episodio)
|
||||
@type title: str
|
||||
@param title: titulo del episodio de una serie
|
||||
@rtype: str
|
||||
@return: Numero de temporada y episodio en formato "1x01" o cadena vacia si no se han encontrado
|
||||
"""
|
||||
filename = ""
|
||||
|
||||
patrons = ["(\d+)x(\d+)", "(?:s|t)(\d+)e(\d+)",
|
||||
"(?:season|temp\w*)\s*(\d+)\s*(?:capitulo|epi\w*)\s*(\d+)"]
|
||||
|
||||
for patron in patrons:
|
||||
try:
|
||||
matches = re.compile(patron, re.I).search(title)
|
||||
if matches:
|
||||
filename = matches.group(1) + "x" + matches.group(2).zfill(2)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
logger.info("'" + title + "' -> '" + filename + "'")
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_sha1(cadena):
|
||||
try:
|
||||
import hashlib
|
||||
devuelve = hashlib.sha1(cadena).hexdigest()
|
||||
except:
|
||||
import sha
|
||||
import binascii
|
||||
devuelve = binascii.hexlify(sha.new(cadena).digest())
|
||||
|
||||
return devuelve
|
||||
|
||||
|
||||
def get_md5(cadena):
|
||||
try:
|
||||
import hashlib
|
||||
devuelve = hashlib.md5(cadena).hexdigest()
|
||||
except:
|
||||
import md5
|
||||
import binascii
|
||||
devuelve = binascii.hexlify(md5.new(cadena).digest())
|
||||
|
||||
return devuelve
|
||||
Executable
+761
@@ -0,0 +1,761 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Server management
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
from core import config
|
||||
from core import httptools
|
||||
from core import jsontools
|
||||
from core import logger
|
||||
from core.item import Item
|
||||
from platformcode import platformtools
|
||||
|
||||
dict_servers_parameters = {}
|
||||
|
||||
|
||||
def find_video_items(item=None, data=None):
|
||||
"""
|
||||
Función genérica para buscar vídeos en una página, devolviendo un itemlist con los items listos para usar.
|
||||
- Si se pasa un Item como argumento, a los items resultantes mantienen los parametros del item pasado
|
||||
- Si no se pasa un Item, se crea uno nuevo, pero no contendra ningun parametro mas que los propios del servidor.
|
||||
|
||||
@param item: Item al cual se quieren buscar vídeos, este debe contener la url válida
|
||||
@type item: Item
|
||||
@param data: Cadena con el contendio de la página ya descargado (si no se pasa item)
|
||||
@type data: str
|
||||
|
||||
@return: devuelve el itemlist con los resultados
|
||||
@rtype: list
|
||||
"""
|
||||
logger.info()
|
||||
itemlist = []
|
||||
|
||||
# Descarga la página
|
||||
if data is None:
|
||||
data = httptools.downloadpage(item.url).data
|
||||
|
||||
# Crea un item si no hay item
|
||||
if item is None:
|
||||
item = Item()
|
||||
# Pasa los campos thumbnail y title a contentThumbnail y contentTitle
|
||||
else:
|
||||
if not item.contentThumbnail:
|
||||
item.contentThumbnail = item.thumbnail
|
||||
if not item.contentTitle:
|
||||
item.contentTitle = item.title
|
||||
|
||||
# Busca los enlaces a los videos
|
||||
for label, url, server, thumbnail in findvideos(data):
|
||||
title = "Enlace encontrado en %s" % label
|
||||
itemlist.append(
|
||||
item.clone(title=title, action="play", url=url, thumbnail=thumbnail, server=server, folder=False))
|
||||
|
||||
return itemlist
|
||||
|
||||
|
||||
def get_servers_itemlist(itemlist, fnc=None, sort=False):
|
||||
"""
|
||||
Obtiene el servidor para cada uno de los items, en funcion de su url.
|
||||
- Asigna el servidor, la url modificada, el thumbnail (si el item no contiene contentThumbnail se asigna el del thumbnail)
|
||||
- Si se pasa una funcion por el argumento fnc, esta se ejecuta pasando el item como argumento,
|
||||
el resultado de esa funcion se asigna al titulo del item
|
||||
- En esta funcion podemos modificar cualquier cosa del item
|
||||
- Esta funcion siempre tiene que devolver el item.title como resultado
|
||||
- Si no se encuentra servidor para una url, se asigna "directo"
|
||||
|
||||
@param itemlist: listado de items
|
||||
@type itemlist: list
|
||||
@param fnc: función para ejecutar con cada item (para asignar el titulo)
|
||||
@type fnc: function
|
||||
@param sort: indica si el listado resultante se ha de ordenar en funcion de la lista de servidores favoritos
|
||||
@type sort: bool
|
||||
"""
|
||||
server_stats = {}
|
||||
# Recorre los servidores
|
||||
for serverid in get_servers_list().keys():
|
||||
server_parameters = get_server_parameters(serverid)
|
||||
|
||||
# Recorre los patrones
|
||||
for pattern in server_parameters.get("find_videos", {}).get("patterns", []):
|
||||
logger.info(pattern["pattern"])
|
||||
# Recorre los resultados
|
||||
for match in re.compile(pattern["pattern"], re.DOTALL).finditer(
|
||||
"\n".join([item.url.split('|')[0] for item in itemlist if not item.server])):
|
||||
url = pattern["url"]
|
||||
for x in range(len(match.groups())):
|
||||
url = url.replace("\\%s" % (x + 1), match.groups()[x])
|
||||
|
||||
server_stats[serverid] = "found"
|
||||
for item in itemlist:
|
||||
if match.group() in item.url:
|
||||
if not item.contentThumbnail:
|
||||
item.contentThumbnail = item.thumbnail
|
||||
item.thumbnail = server_parameters.get("thumbnail", "")
|
||||
item.server = serverid
|
||||
if '|' in item.url:
|
||||
item.url = url + '|' + item.url.split('|')[1]
|
||||
else:
|
||||
item.url = url
|
||||
|
||||
save_server_stats(server_stats, "find_videos")
|
||||
|
||||
# Eliminamos los servidores desactivados
|
||||
itemlist = filter(lambda i: not i.server or is_server_enabled(i.server), itemlist)
|
||||
|
||||
for item in itemlist:
|
||||
# Asignamos "directo" en caso de que el server no se encuentre en pelisalcarta
|
||||
if not item.server and item.url:
|
||||
item.server = "directo"
|
||||
|
||||
if fnc:
|
||||
item.title = fnc(item)
|
||||
|
||||
# Filtrar si es necesario
|
||||
itemlist = filter_servers(itemlist)
|
||||
|
||||
# Ordenar segun favoriteslist si es necesario
|
||||
if sort:
|
||||
itemlist = sort_servers(itemlist)
|
||||
|
||||
return itemlist
|
||||
|
||||
|
||||
def findvideos(data, skip=False):
|
||||
"""
|
||||
Recorre la lista de servidores disponibles y ejecuta la funcion findvideosbyserver para cada uno de ellos
|
||||
:param data: Texto donde buscar los enlaces
|
||||
:param skip: Indica un limite para dejar de recorrer la lista de servidores. Puede ser un booleano en cuyo caso
|
||||
seria False para recorrer toda la lista (valor por defecto) o True para detenerse tras el primer servidor que
|
||||
retorne algun enlace. Tambien puede ser un entero mayor de 1, que representaria el numero maximo de enlaces a buscar.
|
||||
:return:
|
||||
"""
|
||||
logger.info()
|
||||
devuelve = []
|
||||
skip = int(skip)
|
||||
servers_list = get_servers_list().keys()
|
||||
|
||||
# Ordenar segun favoriteslist si es necesario
|
||||
servers_list = sort_servers(servers_list)
|
||||
is_filter_servers = False
|
||||
|
||||
# Ejecuta el findvideos en cada servidor activo
|
||||
for serverid in servers_list:
|
||||
if not is_server_enabled(serverid):
|
||||
continue
|
||||
if config.get_setting("black_list", server=serverid):
|
||||
is_filter_servers = True
|
||||
continue
|
||||
|
||||
devuelve.extend(findvideosbyserver(data, serverid))
|
||||
if skip and len(devuelve) >= skip:
|
||||
devuelve = devuelve[:skip]
|
||||
break
|
||||
|
||||
if not devuelve and is_filter_servers:
|
||||
platformtools.dialog_ok("Filtrar servidores (Lista Negra)",
|
||||
"No hay enlaces disponibles que cumplan los requisitos de su Lista Negra.",
|
||||
"Pruebe de nuevo modificando el fíltro en 'Configuracíon Servidores")
|
||||
|
||||
return devuelve
|
||||
|
||||
|
||||
def findvideosbyserver(data, serverid):
|
||||
serverid = get_server_name(serverid)
|
||||
if not serverid:
|
||||
return []
|
||||
|
||||
server_parameters = get_server_parameters(serverid)
|
||||
devuelve = []
|
||||
|
||||
if "find_videos" in server_parameters:
|
||||
# Recorre los patrones
|
||||
for pattern in server_parameters["find_videos"].get("patterns", []):
|
||||
msg = "%s\npattern: %s" % (serverid, pattern["pattern"])
|
||||
# Recorre los resultados
|
||||
for match in re.compile(pattern["pattern"], re.DOTALL).finditer(data):
|
||||
url = pattern["url"]
|
||||
# Crea la url con los datos
|
||||
for x in range(len(match.groups())):
|
||||
url = url.replace("\\%s" % (x + 1), match.groups()[x])
|
||||
msg += "\nurl encontrada: %s" % url
|
||||
value = server_parameters["name"], url, serverid, server_parameters.get("thumbnail", "")
|
||||
if value not in devuelve and url not in server_parameters["find_videos"].get("ignore_urls", []):
|
||||
devuelve.append(value)
|
||||
logger.info(msg)
|
||||
|
||||
# Guardar estadisticas
|
||||
if devuelve:
|
||||
save_server_stats({serverid: "found"}, "find_videos")
|
||||
|
||||
return devuelve
|
||||
|
||||
|
||||
def guess_server_thumbnail(serverid):
|
||||
server = get_server_name(serverid)
|
||||
server_parameters = get_server_parameters(server)
|
||||
return server_parameters.get('thumbnail', "")
|
||||
|
||||
|
||||
def get_server_from_url(url):
|
||||
encontrado = findvideos(url, True)
|
||||
if len(encontrado) > 0:
|
||||
devuelve = encontrado[0][2]
|
||||
else:
|
||||
devuelve = "directo"
|
||||
|
||||
return devuelve
|
||||
|
||||
|
||||
def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialogo=False):
|
||||
"""
|
||||
Función para obtener la url real del vídeo
|
||||
@param server: Servidor donde está alojado el vídeo
|
||||
@type server: str
|
||||
@param url: url del vídeo
|
||||
@type url: str
|
||||
@param video_password: Password para el vídeo
|
||||
@type video_password: str
|
||||
@param muestra_dialogo: Muestra el diálogo de progreso
|
||||
@type muestra_dialogo: bool
|
||||
|
||||
@return: devuelve la url del video
|
||||
@rtype: list
|
||||
"""
|
||||
logger.info("Server: %s, Url: %s" % (server, url))
|
||||
|
||||
server = server.lower()
|
||||
|
||||
video_urls = []
|
||||
video_exists = True
|
||||
error_messages = []
|
||||
opciones = []
|
||||
|
||||
# Si el vídeo es "directo" o "local", no hay que buscar más
|
||||
if server == "directo" or server == "local":
|
||||
logger.info("Server: %s, la url es la buena" % server)
|
||||
video_urls.append(["%s [%s]" % (urlparse.urlparse(url)[2][-4:], server), url])
|
||||
|
||||
# Averigua la URL del vídeo
|
||||
else:
|
||||
if server:
|
||||
server_parameters = get_server_parameters(server)
|
||||
else:
|
||||
server_parameters = {}
|
||||
|
||||
if server_parameters:
|
||||
# Muestra un diágo de progreso
|
||||
if muestra_dialogo:
|
||||
progreso = platformtools.dialog_progress("alfa",
|
||||
"Conectando con %s" % server_parameters["name"])
|
||||
|
||||
# Cuenta las opciones disponibles, para calcular el porcentaje
|
||||
|
||||
orden = [
|
||||
["free"] + [server] + [premium for premium in server_parameters["premium"] if not premium == server],
|
||||
[server] + [premium for premium in server_parameters["premium"] if not premium == server] + ["free"],
|
||||
[premium for premium in server_parameters["premium"] if not premium == server] + [server] + ["free"]
|
||||
]
|
||||
|
||||
if server_parameters["free"] == True:
|
||||
opciones.append("free")
|
||||
opciones.extend(
|
||||
[premium for premium in server_parameters["premium"] if config.get_setting("premium", server=premium)])
|
||||
|
||||
priority = int(config.get_setting("resolve_priority"))
|
||||
opciones = sorted(opciones, key=lambda x: orden[priority].index(x))
|
||||
|
||||
logger.info("Opciones disponibles: %s | %s" % (len(opciones), opciones))
|
||||
else:
|
||||
logger.error("No existe conector para el servidor %s" % server)
|
||||
error_messages.append("No existe conector para el servidor %s" % server)
|
||||
muestra_dialogo = False
|
||||
|
||||
# Importa el server
|
||||
try:
|
||||
server_module = __import__('servers.%s' % server, None, None, ["servers.%s" % server])
|
||||
logger.info("Servidor importado: %s" % server_module)
|
||||
except:
|
||||
server_module = None
|
||||
logger.error("No se ha podido importar el servidor: %s" % server)
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# Si tiene una función para ver si el vídeo existe, lo comprueba ahora
|
||||
if hasattr(server_module, 'test_video_exists'):
|
||||
logger.info("Invocando a %s.test_video_exists" % server)
|
||||
try:
|
||||
video_exists, message = server_module.test_video_exists(page_url=url)
|
||||
|
||||
if not video_exists:
|
||||
error_messages.append(message)
|
||||
logger.info("test_video_exists dice que el video no existe")
|
||||
else:
|
||||
logger.info("test_video_exists dice que el video SI existe")
|
||||
except:
|
||||
logger.error("No se ha podido comprobar si el video existe")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# Si el video existe y el modo free está disponible, obtenemos la url
|
||||
if video_exists:
|
||||
for opcion in opciones:
|
||||
# Opcion free y premium propio usa el mismo server
|
||||
if opcion == "free" or opcion == server:
|
||||
serverid = server_module
|
||||
server_name = server_parameters["name"]
|
||||
|
||||
# Resto de opciones premium usa un debrider
|
||||
else:
|
||||
serverid = __import__('servers.debriders.%s' % opcion, None, None,
|
||||
["servers.debriders.%s" % opcion])
|
||||
server_name = get_server_parameters(opcion)["name"]
|
||||
|
||||
# Muestra el progreso
|
||||
if muestra_dialogo:
|
||||
progreso.update((100 / len(opciones)) * opciones.index(opcion), "Conectando con %s" % server_name)
|
||||
|
||||
# Modo free
|
||||
if opcion == "free":
|
||||
try:
|
||||
logger.info("Invocando a %s.get_video_url" % server)
|
||||
response = serverid.get_video_url(page_url=url, video_password=video_password)
|
||||
if response:
|
||||
save_server_stats({server: "sucess"}, "resolve")
|
||||
video_urls.extend(response)
|
||||
except:
|
||||
save_server_stats({server: "error"}, "resolve")
|
||||
logger.error("Error al obrener la url en modo free")
|
||||
error_messages.append("Se ha producido un error en %s" % server_name)
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# Modo premium
|
||||
else:
|
||||
try:
|
||||
logger.info("Invocando a %s.get_video_url" % opcion)
|
||||
response = serverid.get_video_url(page_url=url, premium=True,
|
||||
user=config.get_setting("user", server=opcion),
|
||||
password=config.get_setting("password", server=opcion),
|
||||
video_password=video_password)
|
||||
if response and response[0][1]:
|
||||
if opcion == server:
|
||||
save_server_stats({server: "sucess"}, "resolve")
|
||||
video_urls.extend(response)
|
||||
elif response and response[0][0]:
|
||||
error_messages.append(response[0][0])
|
||||
else:
|
||||
error_messages.append("Se ha producido un error en %s" % server_name)
|
||||
except:
|
||||
if opcion == server:
|
||||
save_server_stats({server: "error"}, "resolve")
|
||||
logger.error("Error en el servidor: %s" % opcion)
|
||||
error_messages.append("Se ha producido un error en %s" % server_name)
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# Si ya tenemos URLS, dejamos de buscar
|
||||
if video_urls and config.get_setting("resolve_stop") == True:
|
||||
break
|
||||
|
||||
# Cerramos el progreso
|
||||
if muestra_dialogo:
|
||||
progreso.update(100, "Proceso finalizado")
|
||||
progreso.close()
|
||||
|
||||
# Si no hay opciones disponibles mostramos el aviso de las cuentas premium
|
||||
if video_exists and not opciones and server_parameters.get("premium"):
|
||||
listapremium = [get_server_parameters(premium)["name"] for premium in server_parameters["premium"]]
|
||||
error_messages.append(
|
||||
"Para ver un vídeo en %s necesitas<br/>una cuenta en: %s" % (server, " o ".join(listapremium)))
|
||||
|
||||
# Si no tenemos urls ni mensaje de error, ponemos uno generico
|
||||
elif not video_urls and not error_messages:
|
||||
error_messages.append("Se ha producido un error en %s" % get_server_parameters(server)["name"])
|
||||
|
||||
return video_urls, len(video_urls) > 0, "<br/>".join(error_messages)
|
||||
|
||||
|
||||
def get_server_name(serverid):
|
||||
"""
|
||||
Función obtener el nombre del servidor real a partir de una cadena.
|
||||
@param serverid: Cadena donde mirar
|
||||
@type serverid: str
|
||||
|
||||
@return: Nombre del servidor
|
||||
@rtype: str
|
||||
"""
|
||||
serverid = serverid.lower().split(".")[0]
|
||||
|
||||
# Obtenemos el listado de servers
|
||||
server_list = get_servers_list().keys()
|
||||
|
||||
# Si el nombre está en la lista
|
||||
if serverid in server_list:
|
||||
return serverid
|
||||
|
||||
# Recorre todos los servers buscando el nombre
|
||||
for server in server_list:
|
||||
params = get_server_parameters(server)
|
||||
# Si la nombre esta en el listado de ids
|
||||
if serverid in params["id"]:
|
||||
return server
|
||||
# Si el nombre es mas de una palabra, comprueba si algun id esta dentro del nombre:
|
||||
elif len(serverid.split()) > 1:
|
||||
for id in params["id"]:
|
||||
if id in serverid:
|
||||
return server
|
||||
|
||||
# Si no se encuentra nada se devuelve una cadena vacia
|
||||
return ""
|
||||
|
||||
|
||||
def is_server_enabled(server):
|
||||
"""
|
||||
Función comprobar si un servidor está segun la configuración establecida
|
||||
@param server: Nombre del servidor
|
||||
@type server: str
|
||||
|
||||
@return: resultado de la comprobación
|
||||
@rtype: bool
|
||||
"""
|
||||
|
||||
server = get_server_name(server)
|
||||
|
||||
# El server no existe
|
||||
if not server:
|
||||
return False
|
||||
|
||||
server_parameters = get_server_parameters(server)
|
||||
if server_parameters["active"] == True:
|
||||
if not config.get_setting("hidepremium"):
|
||||
return True
|
||||
elif server_parameters["free"] == True:
|
||||
return True
|
||||
elif [premium for premium in server_parameters["premium"] if config.get_setting("premium", server=premium)]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_server_parameters(server):
|
||||
"""
|
||||
Obtiene los datos del servidor
|
||||
@param server: Nombre del servidor
|
||||
@type server: str
|
||||
|
||||
@return: datos del servidor
|
||||
@rtype: dict
|
||||
"""
|
||||
# logger.info("server %s" % server)
|
||||
global dict_servers_parameters
|
||||
server = server.split('.')[0]
|
||||
if not server:
|
||||
return {}
|
||||
|
||||
if server not in dict_servers_parameters:
|
||||
try:
|
||||
# Servers
|
||||
if os.path.isfile(os.path.join(config.get_runtime_path(), "servers", server + ".json")):
|
||||
path = os.path.join(config.get_runtime_path(), "servers", server + ".json")
|
||||
|
||||
# Debriders
|
||||
elif os.path.isfile(os.path.join(config.get_runtime_path(), "servers", "debriders", server + ".json")):
|
||||
path = os.path.join(config.get_runtime_path(), "servers", "debriders", server + ".json")
|
||||
|
||||
import filetools
|
||||
data = filetools.read(path)
|
||||
dict_server = jsontools.load(data)
|
||||
|
||||
# Imagenes: se admiten url y archivos locales dentro de "resources/images"
|
||||
if dict_server.get("thumbnail") and "://" not in dict_server["thumbnail"]:
|
||||
dict_server["thumbnail"] = os.path.join(config.get_runtime_path(), "resources", "media",
|
||||
"servers", dict_server["thumbnail"])
|
||||
for k in ['premium', 'id']:
|
||||
dict_server[k] = dict_server.get(k, list())
|
||||
|
||||
if type(dict_server[k]) == str:
|
||||
dict_server[k] = [dict_server[k]]
|
||||
|
||||
# if not dict_server.has_key(k) or dict_server[k] == "":
|
||||
# dict_server[k] = []
|
||||
# elif type(dict_server[k]) == dict:
|
||||
# dict_server[k] = dict_server[k]["value"]
|
||||
# if type(dict_server[k]) == str:
|
||||
# dict_server[k] = [dict_server[k]]
|
||||
|
||||
if "find_videos" in dict_server:
|
||||
dict_server['find_videos']["patterns"] = dict_server['find_videos'].get("patterns", list())
|
||||
dict_server['find_videos']["ignore_urls"] = dict_server['find_videos'].get("ignore_urls", list())
|
||||
|
||||
if "settings" in dict_server:
|
||||
dict_server['has_settings'] = True
|
||||
else:
|
||||
dict_server['has_settings'] = False
|
||||
|
||||
dict_servers_parameters[server] = dict_server
|
||||
|
||||
except:
|
||||
mensaje = "Error al cargar el servidor: %s\n" % server
|
||||
import traceback
|
||||
logger.error(mensaje + traceback.format_exc())
|
||||
return {}
|
||||
|
||||
return dict_servers_parameters[server]
|
||||
|
||||
|
||||
def get_server_json(server_name):
|
||||
# logger.info("server_name=" + server_name)
|
||||
import filetools
|
||||
try:
|
||||
server_path = filetools.join(config.get_runtime_path(), "servers", server_name + ".json")
|
||||
if not filetools.exists(server_path):
|
||||
server_path = filetools.join(config.get_runtime_path(), "servers", "debriders", server_name + ".json")
|
||||
|
||||
# logger.info("server_path=" + server_path)
|
||||
server_json = jsontools.load(filetools.read(server_path))
|
||||
# logger.info("server_json= %s" % server_json)
|
||||
|
||||
except Exception, ex:
|
||||
template = "An exception of type %s occured. Arguments:\n%r"
|
||||
message = template % (type(ex).__name__, ex.args)
|
||||
logger.error(" %s" % message)
|
||||
server_json = None
|
||||
|
||||
return server_json
|
||||
|
||||
|
||||
def get_server_controls_settings(server_name):
|
||||
dict_settings = {}
|
||||
|
||||
list_controls = get_server_json(server_name).get('settings', [])
|
||||
import copy
|
||||
list_controls = copy.deepcopy(list_controls)
|
||||
|
||||
# Conversion de str a bool, etc...
|
||||
for c in list_controls:
|
||||
if 'id' not in c or 'type' not in c or 'default' not in c:
|
||||
# Si algun control de la lista no tiene id, type o default lo ignoramos
|
||||
continue
|
||||
|
||||
# new dict with key(id) and value(default) from settings
|
||||
dict_settings[c['id']] = c['default']
|
||||
|
||||
return list_controls, dict_settings
|
||||
|
||||
|
||||
def get_server_setting(name, server, default=None):
|
||||
"""
|
||||
Retorna el valor de configuracion del parametro solicitado.
|
||||
|
||||
Devuelve el valor del parametro 'name' en la configuracion propia del servidor 'server'.
|
||||
|
||||
Busca en la ruta \addon_data\plugin.video.addon\settings_servers el archivo server_data.json y lee
|
||||
el valor del parametro 'name'. Si el archivo server_data.json no existe busca en la carpeta servers el archivo
|
||||
server.json y crea un archivo server_data.json antes de retornar el valor solicitado. Si el parametro 'name'
|
||||
tampoco existe en el el archivo server.json se devuelve el parametro default.
|
||||
|
||||
|
||||
@param name: nombre del parametro
|
||||
@type name: str
|
||||
@param server: nombre del servidor
|
||||
@type server: str
|
||||
@param default: valor devuelto en caso de que no exista el parametro name
|
||||
@type default: cualquiera
|
||||
|
||||
@return: El valor del parametro 'name'
|
||||
@rtype: El tipo del valor del parametro
|
||||
|
||||
"""
|
||||
# Creamos la carpeta si no existe
|
||||
if not os.path.exists(os.path.join(config.get_data_path(), "settings_servers")):
|
||||
os.mkdir(os.path.join(config.get_data_path(), "settings_servers"))
|
||||
|
||||
file_settings = os.path.join(config.get_data_path(), "settings_servers", server + "_data.json")
|
||||
dict_settings = {}
|
||||
dict_file = {}
|
||||
if os.path.exists(file_settings):
|
||||
# Obtenemos configuracion guardada de ../settings/channel_data.json
|
||||
try:
|
||||
dict_file = jsontools.load(open(file_settings, "rb").read())
|
||||
if isinstance(dict_file, dict) and 'settings' in dict_file:
|
||||
dict_settings = dict_file['settings']
|
||||
except EnvironmentError:
|
||||
logger.info("ERROR al leer el archivo: %s" % file_settings)
|
||||
|
||||
if not dict_settings or name not in dict_settings:
|
||||
# Obtenemos controles del archivo ../servers/server.json
|
||||
try:
|
||||
list_controls, default_settings = get_server_controls_settings(server)
|
||||
except:
|
||||
default_settings = {}
|
||||
if name in default_settings: # Si el parametro existe en el server.json creamos el server_data.json
|
||||
default_settings.update(dict_settings)
|
||||
dict_settings = default_settings
|
||||
dict_file['settings'] = dict_settings
|
||||
# Creamos el archivo ../settings/channel_data.json
|
||||
json_data = jsontools.dump(dict_file)
|
||||
try:
|
||||
open(file_settings, "wb").write(json_data)
|
||||
except EnvironmentError:
|
||||
logger.info("ERROR al salvar el archivo: %s" % file_settings)
|
||||
|
||||
# Devolvemos el valor del parametro local 'name' si existe, si no se devuelve default
|
||||
return dict_settings.get(name, default)
|
||||
|
||||
|
||||
def set_server_setting(name, value, server):
|
||||
# Creamos la carpeta si no existe
|
||||
if not os.path.exists(os.path.join(config.get_data_path(), "settings_servers")):
|
||||
os.mkdir(os.path.join(config.get_data_path(), "settings_servers"))
|
||||
|
||||
file_settings = os.path.join(config.get_data_path(), "settings_servers", server + "_data.json")
|
||||
dict_settings = {}
|
||||
|
||||
dict_file = None
|
||||
|
||||
if os.path.exists(file_settings):
|
||||
# Obtenemos configuracion guardada de ../settings/channel_data.json
|
||||
try:
|
||||
dict_file = jsontools.load(open(file_settings, "r").read())
|
||||
dict_settings = dict_file.get('settings', {})
|
||||
except EnvironmentError:
|
||||
logger.info("ERROR al leer el archivo: %s" % file_settings)
|
||||
|
||||
dict_settings[name] = value
|
||||
|
||||
# comprobamos si existe dict_file y es un diccionario, sino lo creamos
|
||||
if dict_file is None or not dict_file:
|
||||
dict_file = {}
|
||||
|
||||
dict_file['settings'] = dict_settings
|
||||
|
||||
# Creamos el archivo ../settings/channel_data.json
|
||||
try:
|
||||
json_data = jsontools.dump(dict_file)
|
||||
open(file_settings, "w").write(json_data)
|
||||
except EnvironmentError:
|
||||
logger.info("ERROR al salvar el archivo: %s" % file_settings)
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def get_servers_list():
|
||||
"""
|
||||
Obtiene un diccionario con todos los servidores disponibles
|
||||
|
||||
@return: Diccionario cuyas claves son los nombre de los servidores (nombre del json)
|
||||
y como valor un diccionario con los parametros del servidor.
|
||||
@rtype: dict
|
||||
"""
|
||||
server_list = {}
|
||||
for server in os.listdir(os.path.join(config.get_runtime_path(), "servers")):
|
||||
if server.endswith(".json") and not server == "version.json":
|
||||
server_parameters = get_server_parameters(server)
|
||||
if server_parameters["active"] == True:
|
||||
server_list[server.split(".")[0]] = server_parameters
|
||||
|
||||
return server_list
|
||||
|
||||
|
||||
def get_debriders_list():
|
||||
"""
|
||||
Obtiene un diccionario con todos los debriders disponibles
|
||||
|
||||
@return: Diccionario cuyas claves son los nombre de los debriders (nombre del json)
|
||||
y como valor un diccionario con los parametros del servidor.
|
||||
@rtype: dict
|
||||
"""
|
||||
server_list = {}
|
||||
for server in os.listdir(os.path.join(config.get_runtime_path(), "servers", "debriders")):
|
||||
if server.endswith(".json"):
|
||||
server_parameters = get_server_parameters(server)
|
||||
if server_parameters["active"] == True:
|
||||
logger.info(server_parameters)
|
||||
server_list[server.split(".")[0]] = server_parameters
|
||||
return server_list
|
||||
|
||||
|
||||
def sort_servers(servers_list):
|
||||
"""
|
||||
Si esta activada la opcion "Ordenar servidores" en la configuracion de servidores y existe un listado de servidores
|
||||
favoritos en la configuracion lo utiliza para ordenar la lista servers_list
|
||||
:param servers_list: Listado de servidores para ordenar. Los elementos de la lista servers_list pueden ser strings
|
||||
u objetos Item. En cuyo caso es necesario q tengan un atributo item.server del tipo str.
|
||||
:return: Lista del mismo tipo de objetos que servers_list ordenada en funcion de los servidores favoritos.
|
||||
"""
|
||||
if servers_list and config.get_setting('favorites_servers'):
|
||||
if isinstance(servers_list[0], Item):
|
||||
servers_list = sorted(servers_list,
|
||||
key=lambda x: config.get_setting("favorites_servers_list", server=x.server) or 100)
|
||||
else:
|
||||
servers_list = sorted(servers_list,
|
||||
key=lambda x: config.get_setting("favorites_servers_list", server=x) or 100)
|
||||
return servers_list
|
||||
|
||||
|
||||
def filter_servers(servers_list):
|
||||
"""
|
||||
Si esta activada la opcion "Filtrar por servidores" en la configuracion de servidores, elimina de la lista
|
||||
de entrada los servidores incluidos en la Lista Negra.
|
||||
:param servers_list: Listado de servidores para filtrar. Los elementos de la lista servers_list pueden ser strings
|
||||
u objetos Item. En cuyo caso es necesario q tengan un atributo item.server del tipo str.
|
||||
:return: Lista del mismo tipo de objetos que servers_list filtrada en funcion de la Lista Negra.
|
||||
"""
|
||||
if servers_list and config.get_setting('filter_servers'):
|
||||
if isinstance(servers_list[0], Item):
|
||||
servers_list_filter = filter(lambda x: not config.get_setting("black_list", server=x.server), servers_list)
|
||||
else:
|
||||
servers_list_filter = filter(lambda x: not config.get_setting("black_list", server=x), servers_list)
|
||||
|
||||
# Si no hay enlaces despues de filtrarlos
|
||||
if servers_list_filter or not platformtools.dialog_yesno("Filtrar servidores (Lista Negra)",
|
||||
"Todos los enlaces disponibles pertenecen a servidores incluidos en su Lista Negra.",
|
||||
"¿Desea mostrar estos enlaces?"):
|
||||
servers_list = servers_list_filter
|
||||
|
||||
return servers_list
|
||||
|
||||
|
||||
def save_server_stats(stats, type="find_videos"):
|
||||
if not config.get_setting("server_stats"):
|
||||
return
|
||||
|
||||
stats_file = os.path.join(config.get_data_path(), "server_stats.json")
|
||||
today = datetime.datetime.now().strftime("%Y%m%d")
|
||||
|
||||
# Leemos el archivo
|
||||
try:
|
||||
server_stats = jsontools.load(open(stats_file, "rb").read())
|
||||
except:
|
||||
server_stats = {"created": time.time(), "data": {}}
|
||||
|
||||
# Actualizamos los datos
|
||||
for server in stats:
|
||||
if not server in server_stats["data"]:
|
||||
server_stats["data"][server] = {}
|
||||
|
||||
if not today in server_stats["data"][server]:
|
||||
server_stats["data"][server][today] = {"find_videos": {"found": 0}, "resolve": {"sucess": 0, "error": 0}}
|
||||
|
||||
server_stats["data"][server][today][type][stats[server]] += 1
|
||||
|
||||
# Guardamos el archivo
|
||||
open(stats_file, "wb").write(jsontools.dump(server_stats))
|
||||
|
||||
# Enviamos al servidor
|
||||
return
|
||||
if time.time() - server_stats["created"] > 86400: # 86400: #1 Dia
|
||||
from core import httptools
|
||||
if httptools.downloadpage("url servidor", headers={'Content-Type': 'application/json'},
|
||||
post=jsontools.dump(server_stats)).sucess:
|
||||
os.remove(stats_file)
|
||||
logger.info("Datos enviados correctamente")
|
||||
else:
|
||||
logger.info("No se han podido enviar los datos")
|
||||
Executable
+1442
File diff suppressed because it is too large
Load Diff
Executable
+1135
File diff suppressed because it is too large
Load Diff
Executable
+170
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# update_servers.py
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
from core import config
|
||||
from core import scrapertools
|
||||
from core import servertools
|
||||
|
||||
remote_url = ""
|
||||
local_folder = os.path.join(config.get_runtime_path(), "servers")
|
||||
|
||||
|
||||
### Procedures
|
||||
def update_servers():
|
||||
update_servers_files(
|
||||
read_remote_servers_list(
|
||||
dict(read_local_servers_list())
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_servers_files(update_servers_list):
|
||||
# ----------------------------
|
||||
from platformcode import platformtools
|
||||
progress = platformtools.dialog_progress_bg("Update servers list")
|
||||
# ----------------------------
|
||||
|
||||
for index, server in enumerate(update_servers_list):
|
||||
# ----------------------------
|
||||
percentage = index * 100 / len(update_servers_list)
|
||||
# ----------------------------
|
||||
|
||||
data = scrapertools.cache_page(remote_url + server[0] + ".py")
|
||||
|
||||
f = open(os.path.join(local_folder, server[0] + ".py"), 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
# ----------------------------
|
||||
progress.update(percentage, ' Update server: "' + server[0] + '"', 'MD5: "' + server[1] + '"')
|
||||
# ----------------------------
|
||||
|
||||
# ----------------------------
|
||||
progress.close()
|
||||
# ----------------------------
|
||||
|
||||
|
||||
### Functions
|
||||
## init
|
||||
def read_remote_servers_list(local_servers):
|
||||
data = scrapertools.cache_page(remote_url + "servertools.py")
|
||||
|
||||
f = open(os.path.join(local_folder, "servertools.py"), 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
all_servers = sorted(
|
||||
servertools.FREE_SERVERS + \
|
||||
servertools.PREMIUM_SERVERS + \
|
||||
servertools.FILENIUM_SERVERS + \
|
||||
servertools.REALDEBRID_SERVERS + \
|
||||
servertools.ALLDEBRID_SERVERS
|
||||
)
|
||||
|
||||
servers = []
|
||||
for server_id in all_servers:
|
||||
if server_id not in servers:
|
||||
servers.append(server_id)
|
||||
|
||||
# ----------------------------
|
||||
from platformcode import platformtools
|
||||
progress = platformtools.dialog_progress_bg("Remote servers list")
|
||||
# ----------------------------
|
||||
|
||||
remote_servers = []
|
||||
update_servers_list = []
|
||||
for index, server in enumerate(servers):
|
||||
# ----------------------------
|
||||
percentage = index * 100 / len(servers)
|
||||
# ----------------------------
|
||||
server_file = urlparse.urljoin(remote_url, server + ".py")
|
||||
|
||||
data = scrapertools.cache_page(server_file)
|
||||
if data != "Not Found":
|
||||
md5_remote_server = md5_remote(data)
|
||||
remote_servers.append([server, md5_remote_server])
|
||||
|
||||
md5_local_server = local_servers.get(server)
|
||||
if md5_local_server:
|
||||
if md5_local_server != md5_remote_server:
|
||||
update_servers_list.append([server, md5_remote_server, md5_local_server, "Update"])
|
||||
else:
|
||||
update_servers_list.append([server, md5_remote_server, "New", "Update"])
|
||||
|
||||
# ----------------------------
|
||||
progress.update(percentage, ' Remote server: "' + server + '"', 'MD5: "' + md5_remote_server + '"')
|
||||
# ----------------------------
|
||||
|
||||
# ----------------------------
|
||||
progress.close()
|
||||
# ----------------------------
|
||||
|
||||
return update_servers_list
|
||||
|
||||
|
||||
def read_local_servers_list():
|
||||
all_servers = sorted(
|
||||
servertools.FREE_SERVERS + \
|
||||
servertools.PREMIUM_SERVERS + \
|
||||
servertools.FILENIUM_SERVERS + \
|
||||
servertools.REALDEBRID_SERVERS + \
|
||||
servertools.ALLDEBRID_SERVERS
|
||||
)
|
||||
|
||||
servers = []
|
||||
for server_id in all_servers:
|
||||
if server_id not in servers:
|
||||
servers.append(server_id)
|
||||
|
||||
# ----------------------------
|
||||
from platformcode import platformtools
|
||||
progress = platformtools.dialog_progress_bg("Local servers list")
|
||||
# ----------------------------
|
||||
|
||||
local_servers = []
|
||||
for index, server in enumerate(servers):
|
||||
# ----------------------------
|
||||
percentage = index * 100 / len(servers)
|
||||
# ----------------------------
|
||||
server_file = os.path.join(config.get_runtime_path(), "servers", server + ".py")
|
||||
if os.path.exists(server_file):
|
||||
md5_local_server = md5_local(server_file)
|
||||
local_servers.append([server, md5_local_server])
|
||||
# ----------------------------
|
||||
progress.update(percentage, ' Local server: "' + server + '"', 'MD5: "' + md5_local_server + '"')
|
||||
# ----------------------------
|
||||
|
||||
# ----------------------------
|
||||
progress.close()
|
||||
# ----------------------------
|
||||
|
||||
return local_servers
|
||||
|
||||
|
||||
def md5_local(file_server):
|
||||
import hashlib
|
||||
hash = hashlib.md5()
|
||||
with open(file_server) as f:
|
||||
for chunk in iter(lambda: f.read(4096), ""):
|
||||
hash.update(chunk)
|
||||
|
||||
return hash.hexdigest()
|
||||
|
||||
|
||||
def md5_remote(data_server):
|
||||
import hashlib
|
||||
hash = hashlib.md5()
|
||||
hash.update(data_server)
|
||||
|
||||
return hash.hexdigest()
|
||||
|
||||
|
||||
### Run
|
||||
update_servers()
|
||||
# from threading import Thread
|
||||
# Thread( target=update_servers ).start()
|
||||
Executable
+209
@@ -0,0 +1,209 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Updater process
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import config
|
||||
import logger
|
||||
import scrapertools
|
||||
import versiontools
|
||||
|
||||
|
||||
# Método antiguo, muestra un popup con la versión
|
||||
def checkforupdates():
|
||||
logger.info()
|
||||
|
||||
# Valores por defecto
|
||||
numero_version_publicada = 0
|
||||
tag_version_publicada = ""
|
||||
|
||||
# Lee la versión remota
|
||||
from core import api
|
||||
latest_packages = api.plugins_get_latest_packages()
|
||||
for latest_package in latest_packages["body"]:
|
||||
if latest_package["package"] == "plugin":
|
||||
numero_version_publicada = latest_package["version"]
|
||||
tag_version_publicada = latest_package["tag"]
|
||||
break
|
||||
|
||||
logger.info("version remota=" + str(numero_version_publicada))
|
||||
|
||||
# Lee la versión local
|
||||
numero_version_local = versiontools.get_current_plugin_version()
|
||||
logger.info("version local=" + str(numero_version_local))
|
||||
|
||||
hayqueactualizar = numero_version_publicada > numero_version_local
|
||||
logger.info("-> hayqueactualizar=" + repr(hayqueactualizar))
|
||||
|
||||
# Si hay actualización disponible, devuelve la Nueva versión para que cada plataforma se encargue de mostrar los avisos
|
||||
if hayqueactualizar:
|
||||
return tag_version_publicada
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# Método nuevo, devuelve el nº de actualizaciones disponibles además de indicar si hay nueva versión del plugin
|
||||
def get_available_updates():
|
||||
logger.info()
|
||||
|
||||
# Cuantas actualizaciones hay?
|
||||
number_of_updates = 0
|
||||
new_published_version_tag = ""
|
||||
|
||||
# Lee la versión remota
|
||||
from core import api
|
||||
latest_packages = api.plugins_get_latest_packages()
|
||||
|
||||
for latest_package in latest_packages["body"]:
|
||||
|
||||
if latest_package["package"] == "plugin":
|
||||
if latest_package["version"] > versiontools.get_current_plugin_version():
|
||||
number_of_updates = number_of_updates + 1
|
||||
new_published_version_tag = latest_package["tag"]
|
||||
|
||||
elif latest_package["package"] == "channels":
|
||||
if latest_package["version"] > versiontools.get_current_channels_version():
|
||||
number_of_updates = number_of_updates + 1
|
||||
|
||||
elif latest_package["package"] == "servers":
|
||||
if latest_package["version"] > versiontools.get_current_servers_version():
|
||||
number_of_updates = number_of_updates + 1
|
||||
|
||||
return new_published_version_tag, number_of_updates
|
||||
|
||||
|
||||
def update(item):
|
||||
logger.info()
|
||||
|
||||
# Valores por defecto
|
||||
published_version_url = ""
|
||||
published_version_filename = ""
|
||||
|
||||
# Lee la versión remota
|
||||
from core import api
|
||||
latest_packages = api.plugins_get_latest_packages()
|
||||
for latest_package in latest_packages["body"]:
|
||||
if latest_package["package"] == "plugin":
|
||||
published_version_url = latest_package["url"]
|
||||
published_version_filename = latest_package["filename"]
|
||||
published_version_number = latest_package["version"]
|
||||
break
|
||||
|
||||
# La URL viene del API, y lo descarga en "userdata"
|
||||
remotefilename = published_version_url
|
||||
localfilename = os.path.join(config.get_data_path(), published_version_filename)
|
||||
|
||||
download_and_install(remotefilename, localfilename)
|
||||
|
||||
|
||||
def download_and_install(remote_file_name, local_file_name):
|
||||
logger.info("from " + remote_file_name + " to " + local_file_name)
|
||||
|
||||
if os.path.exists(local_file_name):
|
||||
os.remove(local_file_name)
|
||||
|
||||
# Descarga el fichero
|
||||
inicio = time.clock()
|
||||
from core import downloadtools
|
||||
downloadtools.downloadfile(remote_file_name, local_file_name, continuar=False)
|
||||
fin = time.clock()
|
||||
logger.info("Descargado en %d segundos " % (fin - inicio + 1))
|
||||
|
||||
logger.info("descomprime fichero...")
|
||||
import ziptools
|
||||
unzipper = ziptools.ziptools()
|
||||
|
||||
# Lo descomprime en "addons" (un nivel por encima del plugin)
|
||||
installation_target = os.path.join(config.get_runtime_path(), "..")
|
||||
logger.info("installation_target=%s" % installation_target)
|
||||
|
||||
unzipper.extract(local_file_name, installation_target)
|
||||
|
||||
# Borra el zip descargado
|
||||
logger.info("borra fichero...")
|
||||
os.remove(local_file_name)
|
||||
logger.info("...fichero borrado")
|
||||
|
||||
|
||||
def update_channel(channel_name):
|
||||
logger.info(channel_name)
|
||||
|
||||
import channeltools
|
||||
remote_channel_url, remote_version_url = channeltools.get_channel_remote_url(channel_name)
|
||||
local_channel_path, local_version_path, local_compiled_path = channeltools.get_channel_local_path(channel_name)
|
||||
|
||||
# Version remota
|
||||
try:
|
||||
data = scrapertools.cachePage(remote_version_url)
|
||||
logger.info("remote_data=" + data)
|
||||
remote_version = int(scrapertools.find_single_match(data, '<version>([^<]+)</version>'))
|
||||
addon_condition = int(scrapertools.find_single_match(data, "<addon_version>([^<]*)</addon_version>")
|
||||
.replace(".", "").ljust(len(str(versiontools.get_current_plugin_version())), '0'))
|
||||
except:
|
||||
remote_version = 0
|
||||
addon_condition = 0
|
||||
|
||||
logger.info("remote_version=%d" % remote_version)
|
||||
|
||||
# Version local
|
||||
if os.path.exists(local_version_path):
|
||||
infile = open(local_version_path)
|
||||
from core import jsontools
|
||||
data = jsontools.load(infile.read())
|
||||
infile.close()
|
||||
|
||||
local_version = data.get('version', 0)
|
||||
else:
|
||||
local_version = 0
|
||||
|
||||
logger.info("local_version=%d" % local_version)
|
||||
|
||||
# Comprueba si ha cambiado
|
||||
updated = (remote_version > local_version) and (versiontools.get_current_plugin_version() >= addon_condition)
|
||||
|
||||
if updated:
|
||||
logger.info("downloading...")
|
||||
download_channel(channel_name)
|
||||
|
||||
return updated
|
||||
|
||||
|
||||
def download_channel(channel_name):
|
||||
logger.info(channel_name)
|
||||
|
||||
import channeltools
|
||||
remote_channel_url, remote_version_url = channeltools.get_channel_remote_url(channel_name)
|
||||
local_channel_path, local_version_path, local_compiled_path = channeltools.get_channel_local_path(channel_name)
|
||||
|
||||
# Descarga el canal
|
||||
try:
|
||||
updated_channel_data = scrapertools.cachePage(remote_channel_url)
|
||||
outfile = open(local_channel_path, "wb")
|
||||
outfile.write(updated_channel_data)
|
||||
outfile.flush()
|
||||
outfile.close()
|
||||
logger.info("Grabado a " + local_channel_path)
|
||||
except:
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# Descarga la version (puede no estar)
|
||||
try:
|
||||
updated_version_data = scrapertools.cachePage(remote_version_url)
|
||||
outfile = open(local_version_path, "w")
|
||||
outfile.write(updated_version_data)
|
||||
outfile.flush()
|
||||
outfile.close()
|
||||
logger.info("Grabado a " + local_version_path)
|
||||
except:
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
if os.path.exists(local_compiled_path):
|
||||
os.remove(local_compiled_path)
|
||||
|
||||
from platformcode import platformtools
|
||||
platformtools.dialog_notification(channel_name + " actualizado", "Se ha descargado una nueva versión")
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Version Tools
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
import config
|
||||
import scrapertools
|
||||
|
||||
|
||||
def get_current_plugin_version():
|
||||
return 4300
|
||||
|
||||
|
||||
def get_current_plugin_version_tag():
|
||||
return "4.3.0-beta1"
|
||||
|
||||
|
||||
def get_current_plugin_date():
|
||||
return "30/06/2017"
|
||||
|
||||
|
||||
def get_current_channels_version():
|
||||
f = open(os.path.join(config.get_runtime_path(), "channels", "version.xml"))
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
return int(scrapertools.find_single_match(data, "<version>([^<]+)</version>"))
|
||||
|
||||
|
||||
def get_current_servers_version():
|
||||
f = open(os.path.join(config.get_runtime_path(), "servers", "version.xml"))
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
return int(scrapertools.find_single_match(data, "<version>([^<]+)</version>"))
|
||||
Executable
+605
@@ -0,0 +1,605 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# Common Library Tools
|
||||
# ------------------------------------------------------------
|
||||
|
||||
import errno
|
||||
import math
|
||||
import os
|
||||
|
||||
from core import config
|
||||
from core import filetools
|
||||
from core import logger
|
||||
from core import scraper
|
||||
from core import scrapertools
|
||||
from core.item import Item
|
||||
from platformcode import platformtools
|
||||
|
||||
FOLDER_MOVIES = config.get_setting("folder_movies")
|
||||
FOLDER_TVSHOWS = config.get_setting("folder_tvshows")
|
||||
VIDEOLIBRARY_PATH = config.get_videolibrary_path()
|
||||
MOVIES_PATH = filetools.join(VIDEOLIBRARY_PATH, FOLDER_MOVIES)
|
||||
TVSHOWS_PATH = filetools.join(VIDEOLIBRARY_PATH, FOLDER_TVSHOWS)
|
||||
|
||||
if not FOLDER_MOVIES or not FOLDER_TVSHOWS or not VIDEOLIBRARY_PATH \
|
||||
or not filetools.exists(MOVIES_PATH) or not filetools.exists(TVSHOWS_PATH):
|
||||
config.verify_directories_created()
|
||||
|
||||
addon_name = "plugin://plugin.video.%s/" % config.PLUGIN_NAME
|
||||
|
||||
|
||||
def read_nfo(path_nfo, item=None):
|
||||
"""
|
||||
Metodo para leer archivos nfo.
|
||||
Los arcivos nfo tienen la siguiente extructura: url_scraper | xml + item_json
|
||||
[url_scraper] y [xml] son opcionales, pero solo uno de ellos ha de existir siempre.
|
||||
@param path_nfo: ruta absoluta al archivo nfo
|
||||
@type path_nfo: str
|
||||
@param item: Si se pasa este parametro el item devuelto sera una copia de este con
|
||||
los valores de 'infoLabels', 'library_playcounts' y 'path' leidos del nfo
|
||||
@type: Item
|
||||
@return: Una tupla formada por la cabecera (head_nfo ='url_scraper'|'xml') y el objeto 'item_json'
|
||||
@rtype: tuple (str, Item)
|
||||
"""
|
||||
head_nfo = ""
|
||||
it = None
|
||||
|
||||
data = filetools.read(path_nfo)
|
||||
|
||||
if data:
|
||||
head_nfo = data.splitlines()[0] + "\n"
|
||||
data = "\n".join(data.splitlines()[1:])
|
||||
|
||||
it_nfo = Item().fromjson(data)
|
||||
|
||||
if item:
|
||||
it = item.clone()
|
||||
it.infoLabels = it_nfo.infoLabels
|
||||
if 'library_playcounts' in it_nfo:
|
||||
it.library_playcounts = it_nfo.library_playcounts
|
||||
if it_nfo.path:
|
||||
it.path = it_nfo.path
|
||||
else:
|
||||
it = it_nfo
|
||||
|
||||
if 'fanart' in it.infoLabels:
|
||||
it.fanart = it.infoLabels['fanart']
|
||||
|
||||
return head_nfo, it
|
||||
|
||||
|
||||
def save_movie(item):
|
||||
"""
|
||||
guarda en la libreria de peliculas el elemento item, con los valores que contiene.
|
||||
@type item: item
|
||||
@param item: elemento que se va a guardar.
|
||||
@rtype insertados: int
|
||||
@return: el número de elementos insertados
|
||||
@rtype sobreescritos: int
|
||||
@return: el número de elementos sobreescritos
|
||||
@rtype fallidos: int
|
||||
@return: el número de elementos fallidos o -1 si ha fallado todo
|
||||
"""
|
||||
logger.info()
|
||||
# logger.debug(item.tostring('\n'))
|
||||
insertados = 0
|
||||
sobreescritos = 0
|
||||
fallidos = 0
|
||||
path = ""
|
||||
|
||||
# Itentamos obtener el titulo correcto:
|
||||
# 1. contentTitle: Este deberia ser el sitio correcto, ya que title suele contener "Añadir a la videoteca..."
|
||||
# 2. fulltitle
|
||||
# 3. title
|
||||
if not item.contentTitle:
|
||||
# Colocamos el titulo correcto en su sitio para que scraper lo localize
|
||||
if item.fulltitle:
|
||||
item.contentTitle = item.fulltitle
|
||||
else:
|
||||
item.contentTitle = item.title
|
||||
|
||||
# Si llegados a este punto no tenemos titulo, salimos
|
||||
if not item.contentTitle or not item.channel:
|
||||
logger.debug("NO ENCONTRADO contentTitle")
|
||||
return 0, 0, -1 # Salimos sin guardar
|
||||
|
||||
scraper_return = scraper.find_and_set_infoLabels(item)
|
||||
|
||||
# Llegados a este punto podemos tener:
|
||||
# scraper_return = True: Un item con infoLabels con la información actualizada de la peli
|
||||
# scraper_return = False: Un item sin información de la peli (se ha dado a cancelar en la ventana)
|
||||
# item.infoLabels['code'] == "" : No se ha encontrado el identificador de IMDB necesario para continuar, salimos
|
||||
if not scraper_return or not item.infoLabels['code']:
|
||||
# TODO de momento si no hay resultado no añadimos nada,
|
||||
# aunq podriamos abrir un cuadro para introducir el identificador/nombre a mano
|
||||
logger.debug("NO ENCONTRADO EN SCRAPER O NO TIENE code")
|
||||
return 0, 0, -1
|
||||
|
||||
_id = item.infoLabels['code'][0]
|
||||
|
||||
# progress dialog
|
||||
p_dialog = platformtools.dialog_progress('alfa', 'Añadiendo película...')
|
||||
|
||||
if config.get_setting("original_title_folder", "videolibrary") == 1 and item.infoLabels['originaltitle']:
|
||||
base_name = item.infoLabels['originaltitle']
|
||||
else:
|
||||
base_name = item.contentTitle
|
||||
|
||||
base_name = unicode(filetools.validate_path(base_name.replace('/', '-')), "utf8").lower().encode("utf8")
|
||||
|
||||
subcarpetas = os.listdir(MOVIES_PATH)
|
||||
|
||||
for c in subcarpetas:
|
||||
code = scrapertools.find_single_match(c, '\[(.*?)\]')
|
||||
if code and code in item.infoLabels['code']:
|
||||
path = c
|
||||
_id = code
|
||||
break
|
||||
|
||||
if not path:
|
||||
# Crear carpeta
|
||||
path = filetools.join(MOVIES_PATH, ("%s [%s]" % (base_name, _id)).strip())
|
||||
logger.info("Creando directorio pelicula:" + path)
|
||||
if not filetools.mkdir(path):
|
||||
logger.debug("No se ha podido crear el directorio")
|
||||
return 0, 0, -1
|
||||
|
||||
nfo_path = filetools.join(path, "%s [%s].nfo" % (base_name, _id))
|
||||
strm_path = filetools.join(path, "%s.strm" % base_name)
|
||||
json_path = filetools.join(path, ("%s [%s].json" % (base_name, item.channel.lower())))
|
||||
|
||||
nfo_exists = filetools.exists(nfo_path)
|
||||
strm_exists = filetools.exists(strm_path)
|
||||
json_exists = filetools.exists(json_path)
|
||||
|
||||
if not nfo_exists:
|
||||
# Creamos .nfo si no existe
|
||||
logger.info("Creando .nfo: " + nfo_path)
|
||||
head_nfo = scraper.get_nfo(item)
|
||||
|
||||
item_nfo = Item(title=item.contentTitle, channel="videolibrary", action='findvideos',
|
||||
library_playcounts={"%s [%s]" % (base_name, _id): 0}, infoLabels=item.infoLabels,
|
||||
library_urls={})
|
||||
|
||||
else:
|
||||
# Si existe .nfo, pero estamos añadiendo un nuevo canal lo abrimos
|
||||
head_nfo, item_nfo = read_nfo(nfo_path)
|
||||
|
||||
if not strm_exists:
|
||||
# Crear base_name.strm si no existe
|
||||
item_strm = Item(channel='videolibrary', action='play_from_library',
|
||||
strm_path=strm_path.replace(MOVIES_PATH, ""), contentType='movie',
|
||||
contentTitle=item.contentTitle)
|
||||
strm_exists = filetools.write(strm_path, '%s?%s' % (addon_name, item_strm.tourl()))
|
||||
item_nfo.strm_path = strm_path.replace(MOVIES_PATH, "")
|
||||
|
||||
# Solo si existen item_nfo y .strm continuamos
|
||||
if item_nfo and strm_exists:
|
||||
|
||||
if json_exists:
|
||||
logger.info("El fichero existe. Se sobreescribe")
|
||||
sobreescritos += 1
|
||||
else:
|
||||
insertados += 1
|
||||
|
||||
if filetools.write(json_path, item.tojson()):
|
||||
p_dialog.update(100, 'Añadiendo película...', item.contentTitle)
|
||||
item_nfo.library_urls[item.channel] = item.url
|
||||
|
||||
if filetools.write(nfo_path, head_nfo + item_nfo.tojson()):
|
||||
# actualizamos la videoteca de Kodi con la pelicula
|
||||
if config.is_xbmc():
|
||||
from platformcode import xbmc_videolibrary
|
||||
xbmc_videolibrary.update(FOLDER_MOVIES, filetools.basename(path) + "/")
|
||||
|
||||
p_dialog.close()
|
||||
return insertados, sobreescritos, fallidos
|
||||
|
||||
# Si llegamos a este punto es por q algo ha fallado
|
||||
logger.error("No se ha podido guardar %s en la videoteca" % item.contentTitle)
|
||||
p_dialog.update(100, 'Fallo al añadir...', item.contentTitle)
|
||||
p_dialog.close()
|
||||
return 0, 0, -1
|
||||
|
||||
|
||||
def save_tvshow(item, episodelist):
|
||||
"""
|
||||
guarda en la libreria de series la serie con todos los capitulos incluidos en la lista episodelist
|
||||
@type item: item
|
||||
@param item: item que representa la serie a guardar
|
||||
@type episodelist: list
|
||||
@param episodelist: listado de items que representan los episodios que se van a guardar.
|
||||
@rtype insertados: int
|
||||
@return: el número de episodios insertados
|
||||
@rtype sobreescritos: int
|
||||
@return: el número de episodios sobreescritos
|
||||
@rtype fallidos: int
|
||||
@return: el número de episodios fallidos o -1 si ha fallado toda la serie
|
||||
"""
|
||||
logger.info()
|
||||
# logger.debug(item.tostring('\n'))
|
||||
path = ""
|
||||
|
||||
# Si llegados a este punto no tenemos titulo o code, salimos
|
||||
if not (item.contentSerieName or item.infoLabels['code']) or not item.channel:
|
||||
logger.debug("NO ENCONTRADO contentSerieName NI code")
|
||||
return 0, 0, -1 # Salimos sin guardar
|
||||
|
||||
scraper_return = scraper.find_and_set_infoLabels(item)
|
||||
|
||||
# Llegados a este punto podemos tener:
|
||||
# scraper_return = True: Un item con infoLabels con la información actualizada de la serie
|
||||
# scraper_return = False: Un item sin información de la peli (se ha dado a cancelar en la ventana)
|
||||
# item.infoLabels['code'] == "" : No se ha encontrado el identificador de IMDB necesario para continuar, salimos
|
||||
if not scraper_return or not item.infoLabels['code']:
|
||||
# TODO de momento si no hay resultado no añadimos nada,
|
||||
# aunq podriamos abrir un cuadro para introducir el identificador/nombre a mano
|
||||
logger.debug("NO ENCONTRADO EN SCRAPER O NO TIENE code")
|
||||
return 0, 0, -1
|
||||
|
||||
_id = item.infoLabels['code'][0]
|
||||
|
||||
if config.get_setting("original_title_folder", "videolibrary") == 1 and item.infoLabels['originaltitle']:
|
||||
base_name = item.infoLabels['originaltitle']
|
||||
elif item.infoLabels['title']:
|
||||
base_name = item.infoLabels['title']
|
||||
else:
|
||||
base_name = item.contentSerieName
|
||||
|
||||
base_name = unicode(filetools.validate_path(base_name.replace('/', '-')), "utf8").lower().encode("utf8")
|
||||
|
||||
subcarpetas = os.listdir(TVSHOWS_PATH)
|
||||
|
||||
for c in subcarpetas:
|
||||
code = scrapertools.find_single_match(c, '\[(.*?)\]')
|
||||
if code and code in item.infoLabels['code']:
|
||||
path = filetools.join(TVSHOWS_PATH, c)
|
||||
_id = code
|
||||
break
|
||||
|
||||
if not path:
|
||||
path = filetools.join(TVSHOWS_PATH, ("%s [%s]" % (base_name, _id)).strip())
|
||||
logger.info("Creando directorio serie: " + path)
|
||||
try:
|
||||
filetools.mkdir(path)
|
||||
except OSError, exception:
|
||||
if exception.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
tvshow_path = filetools.join(path, "tvshow.nfo")
|
||||
if not filetools.exists(tvshow_path):
|
||||
# Creamos tvshow.nfo, si no existe, con la head_nfo, info de la serie y marcas de episodios vistos
|
||||
logger.info("Creando tvshow.nfo: " + tvshow_path)
|
||||
head_nfo = scraper.get_nfo(item)
|
||||
|
||||
item_tvshow = Item(title=item.contentTitle, channel="videolibrary", action="get_seasons",
|
||||
fanart=item.infoLabels['fanart'], thumbnail=item.infoLabels['thumbnail'],
|
||||
infoLabels=item.infoLabels, path=path.replace(TVSHOWS_PATH, ""))
|
||||
item_tvshow.library_playcounts = {}
|
||||
item_tvshow.library_urls = {item.channel: item.url}
|
||||
|
||||
else:
|
||||
# Si existe tvshow.nfo, pero estamos añadiendo un nuevo canal actualizamos el listado de urls
|
||||
head_nfo, item_tvshow = read_nfo(tvshow_path)
|
||||
item_tvshow.channel = "videolibrary"
|
||||
item_tvshow.action = "get_seasons"
|
||||
item_tvshow.library_urls[item.channel] = item.url
|
||||
|
||||
# FILTERTOOLS
|
||||
# si el canal tiene filtro de idiomas, añadimos el canal y el show
|
||||
if episodelist and "list_language" in episodelist[0]:
|
||||
# si ya hemos añadido un canal previamente con filtro, añadimos o actualizamos el canal y show
|
||||
if "library_filter_show" in item_tvshow:
|
||||
item_tvshow.library_filter_show[item.channel] = item.show
|
||||
# no habia ningún canal con filtro y lo generamos por primera vez
|
||||
else:
|
||||
item_tvshow.library_filter_show = {item.channel: item.show}
|
||||
|
||||
if item.channel != "downloads":
|
||||
item_tvshow.active = 1 # para que se actualice a diario cuando se llame a videolibrary_service
|
||||
|
||||
filetools.write(tvshow_path, head_nfo + item_tvshow.tojson())
|
||||
|
||||
if not episodelist:
|
||||
# La lista de episodios esta vacia
|
||||
return 0, 0, 0
|
||||
|
||||
# Guardar los episodios
|
||||
'''import time
|
||||
start_time = time.time()'''
|
||||
insertados, sobreescritos, fallidos = save_episodes(path, episodelist, item)
|
||||
'''msg = "Insertados: %d | Sobreescritos: %d | Fallidos: %d | Tiempo: %2.2f segundos" % \
|
||||
(insertados, sobreescritos, fallidos, time.time() - start_time)
|
||||
logger.debug(msg)'''
|
||||
|
||||
return insertados, sobreescritos, fallidos, path
|
||||
|
||||
|
||||
def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
|
||||
"""
|
||||
guarda en la ruta indicada todos los capitulos incluidos en la lista episodelist
|
||||
@type path: str
|
||||
@param path: ruta donde guardar los episodios
|
||||
@type episodelist: list
|
||||
@param episodelist: listado de items que representan los episodios que se van a guardar.
|
||||
@type serie: item
|
||||
@param serie: serie de la que se van a guardar los episodios
|
||||
@type silent: bool
|
||||
@param silent: establece si se muestra la notificación
|
||||
@param overwrite: permite sobreescribir los ficheros existentes
|
||||
@type overwrite: bool
|
||||
@rtype insertados: int
|
||||
@return: el número de episodios insertados
|
||||
@rtype sobreescritos: int
|
||||
@return: el número de episodios sobreescritos
|
||||
@rtype fallidos: int
|
||||
@return: el número de episodios fallidos
|
||||
"""
|
||||
logger.info()
|
||||
|
||||
# No hay lista de episodios, no hay nada que guardar
|
||||
if not len(episodelist):
|
||||
logger.info("No hay lista de episodios, salimos sin crear strm")
|
||||
return 0, 0, 0
|
||||
|
||||
insertados = 0
|
||||
sobreescritos = 0
|
||||
fallidos = 0
|
||||
news_in_playcounts = {}
|
||||
|
||||
# Listamos todos los ficheros de la serie, asi evitamos tener que comprobar si existe uno por uno
|
||||
ficheros = os.listdir(path)
|
||||
ficheros = [filetools.join(path, f) for f in ficheros]
|
||||
|
||||
# Silent es para no mostrar progreso (para videolibrary_service)
|
||||
if not silent:
|
||||
# progress dialog
|
||||
p_dialog = platformtools.dialog_progress('alfa', 'Añadiendo episodios...')
|
||||
p_dialog.update(0, 'Añadiendo episodio...')
|
||||
|
||||
new_episodelist = []
|
||||
# Obtenemos el numero de temporada y episodio y descartamos los q no lo sean
|
||||
for e in episodelist:
|
||||
try:
|
||||
season_episode = scrapertools.get_season_and_episode(e.title)
|
||||
|
||||
e.infoLabels = serie.infoLabels
|
||||
e.contentSeason, e.contentEpisodeNumber = season_episode.split("x")
|
||||
new_episodelist.append(e)
|
||||
except:
|
||||
continue
|
||||
|
||||
# No hay lista de episodios, no hay nada que guardar
|
||||
if not len(new_episodelist):
|
||||
logger.info("No hay lista de episodios, salimos sin crear strm")
|
||||
return 0, 0, 0
|
||||
|
||||
# fix float porque la division se hace mal en python 2.x
|
||||
t = float(100) / len(new_episodelist)
|
||||
|
||||
for i, e in enumerate(scraper.sort_episode_list(new_episodelist)):
|
||||
if not silent:
|
||||
p_dialog.update(int(math.ceil((i + 1) * t)), 'Añadiendo episodio...', e.title)
|
||||
|
||||
season_episode = "%sx%s" % (e.contentSeason, str(e.contentEpisodeNumber).zfill(2))
|
||||
strm_path = filetools.join(path, "%s.strm" % season_episode)
|
||||
nfo_path = filetools.join(path, "%s.nfo" % season_episode)
|
||||
json_path = filetools.join(path, ("%s [%s].json" % (season_episode, e.channel)).lower())
|
||||
|
||||
strm_exists = strm_path in ficheros
|
||||
nfo_exists = nfo_path in ficheros
|
||||
json_exists = json_path in ficheros
|
||||
|
||||
if not strm_exists:
|
||||
# Si no existe season_episode.strm añadirlo
|
||||
item_strm = Item(action='play_from_library', channel='videolibrary',
|
||||
strm_path=strm_path.replace(TVSHOWS_PATH, ""), infoLabels={})
|
||||
item_strm.contentSeason = e.contentSeason
|
||||
item_strm.contentEpisodeNumber = e.contentEpisodeNumber
|
||||
item_strm.contentType = e.contentType
|
||||
item_strm.contentTitle = season_episode
|
||||
|
||||
# FILTERTOOLS
|
||||
if item_strm.list_language:
|
||||
# si tvshow.nfo tiene filtro se le pasa al item_strm que se va a generar
|
||||
if "library_filter_show" in serie:
|
||||
item_strm.library_filter_show = serie.library_filter_show
|
||||
|
||||
if item_strm.library_filter_show == "":
|
||||
logger.error("Se ha producido un error al obtener el nombre de la serie a filtrar")
|
||||
|
||||
# logger.debug("item_strm" + item_strm.tostring('\n'))
|
||||
# logger.debug("serie " + serie.tostring('\n'))
|
||||
strm_exists = filetools.write(strm_path, '%s?%s' % (addon_name, item_strm.tourl()))
|
||||
|
||||
item_nfo = None
|
||||
if not nfo_exists and e.infoLabels["code"]:
|
||||
# Si no existe season_episode.nfo añadirlo
|
||||
scraper.find_and_set_infoLabels(e)
|
||||
head_nfo = scraper.get_nfo(e)
|
||||
|
||||
item_nfo = e.clone(channel="videolibrary", url="", action='findvideos',
|
||||
strm_path=strm_path.replace(TVSHOWS_PATH, ""))
|
||||
|
||||
nfo_exists = filetools.write(nfo_path, head_nfo + item_nfo.tojson())
|
||||
|
||||
# Solo si existen season_episode.nfo y season_episode.strm continuamos
|
||||
if nfo_exists and strm_exists:
|
||||
if not json_exists or overwrite:
|
||||
# Obtenemos infoLabel del episodio
|
||||
if not item_nfo:
|
||||
head_nfo, item_nfo = read_nfo(nfo_path)
|
||||
|
||||
e.infoLabels = item_nfo.infoLabels
|
||||
|
||||
if filetools.write(json_path, e.tojson()):
|
||||
if not json_exists:
|
||||
logger.info("Insertado: %s" % json_path)
|
||||
insertados += 1
|
||||
# Marcamos episodio como no visto
|
||||
news_in_playcounts[season_episode] = 0
|
||||
# Marcamos la temporada como no vista
|
||||
news_in_playcounts["season %s" % e.contentSeason] = 0
|
||||
# Marcamos la serie como no vista
|
||||
# logger.debug("serie " + serie.tostring('\n'))
|
||||
news_in_playcounts[serie.contentTitle] = 0
|
||||
|
||||
else:
|
||||
logger.info("Sobreescrito: %s" % json_path)
|
||||
sobreescritos += 1
|
||||
else:
|
||||
logger.info("Fallido: %s" % json_path)
|
||||
fallidos += 1
|
||||
|
||||
else:
|
||||
logger.info("Fallido: %s" % json_path)
|
||||
fallidos += 1
|
||||
|
||||
if not silent and p_dialog.iscanceled():
|
||||
break
|
||||
|
||||
if not silent:
|
||||
p_dialog.close()
|
||||
|
||||
if news_in_playcounts:
|
||||
# Si hay nuevos episodios los marcamos como no vistos en tvshow.nfo ...
|
||||
tvshow_path = filetools.join(path, "tvshow.nfo")
|
||||
try:
|
||||
import datetime
|
||||
head_nfo, tvshow_item = read_nfo(tvshow_path)
|
||||
tvshow_item.library_playcounts.update(news_in_playcounts)
|
||||
|
||||
if tvshow_item.active == 30:
|
||||
tvshow_item.active = 1
|
||||
update_last = datetime.date.today()
|
||||
tvshow_item.update_last = update_last.strftime('%Y-%m-%d')
|
||||
update_next = datetime.date.today() + datetime.timedelta(days=int(tvshow_item.active))
|
||||
tvshow_item.update_next = update_next.strftime('%Y-%m-%d')
|
||||
|
||||
filetools.write(tvshow_path, head_nfo + tvshow_item.tojson())
|
||||
except:
|
||||
logger.error("Error al actualizar tvshow.nfo")
|
||||
fallidos = -1
|
||||
else:
|
||||
# ... si ha sido correcto actualizamos la videoteca de Kodi
|
||||
if config.is_xbmc() and not silent:
|
||||
from platformcode import xbmc_videolibrary
|
||||
xbmc_videolibrary.update(FOLDER_TVSHOWS, filetools.basename(path))
|
||||
|
||||
if fallidos == len(episodelist):
|
||||
fallidos = -1
|
||||
|
||||
logger.debug("%s [%s]: insertados= %s, sobreescritos= %s, fallidos= %s" %
|
||||
(serie.contentSerieName, serie.channel, insertados, sobreescritos, fallidos))
|
||||
return insertados, sobreescritos, fallidos
|
||||
|
||||
|
||||
def add_movie(item):
|
||||
"""
|
||||
guarda una pelicula en la libreria de cine. La pelicula puede ser un enlace dentro de un canal o un video
|
||||
descargado previamente.
|
||||
|
||||
Para añadir episodios descargados en local, el item debe tener exclusivamente:
|
||||
- contentTitle: titulo de la pelicula
|
||||
- title: titulo a mostrar junto al listado de enlaces -findvideos- ("Reproducir video local HD")
|
||||
- infoLabels["tmdb_id"] o infoLabels["imdb_id"]
|
||||
- contentType == "movie"
|
||||
- channel = "downloads"
|
||||
- url : ruta local al video
|
||||
|
||||
@type item: item
|
||||
@param item: elemento que se va a guardar.
|
||||
"""
|
||||
logger.info()
|
||||
|
||||
new_item = item.clone(action="findvideos")
|
||||
insertados, sobreescritos, fallidos = save_movie(new_item)
|
||||
|
||||
if fallidos == 0:
|
||||
platformtools.dialog_ok(config.get_localized_string(30131), new_item.contentTitle,
|
||||
config.get_localized_string(30135)) # 'se ha añadido a la videoteca'
|
||||
else:
|
||||
platformtools.dialog_ok(config.get_localized_string(30131),
|
||||
"ERROR, la pelicula NO se ha añadido a la videoteca")
|
||||
|
||||
|
||||
def add_tvshow(item, channel=None):
|
||||
"""
|
||||
Guarda contenido en la libreria de series. Este contenido puede ser uno de estos dos:
|
||||
- La serie con todos los capitulos incluidos en la lista episodelist.
|
||||
- Un solo capitulo descargado previamente en local.
|
||||
|
||||
Para añadir episodios descargados en local, el item debe tener exclusivamente:
|
||||
- contentSerieName (o show): Titulo de la serie
|
||||
- contentTitle: titulo del episodio para extraer season_and_episode ("1x01 Piloto")
|
||||
- title: titulo a mostrar junto al listado de enlaces -findvideos- ("Reproducir video local")
|
||||
- infoLabels["tmdb_id"] o infoLabels["imdb_id"]
|
||||
- contentType != "movie"
|
||||
- channel = "downloads"
|
||||
- url : ruta local al video
|
||||
|
||||
@type item: item
|
||||
@param item: item que representa la serie a guardar
|
||||
@type channel: modulo
|
||||
@param channel: canal desde el que se guardara la serie.
|
||||
Por defecto se importara item.from_channel o item.channel
|
||||
|
||||
"""
|
||||
logger.info("show=#" + item.show + "#")
|
||||
|
||||
if item.channel == "downloads":
|
||||
itemlist = [item.clone()]
|
||||
|
||||
else:
|
||||
# Esta marca es porque el item tiene algo más aparte en el atributo "extra"
|
||||
item.action = item.extra
|
||||
if isinstance(item.extra, str) and "###" in item.extra:
|
||||
item.action = item.extra.split("###")[0]
|
||||
item.extra = item.extra.split("###")[1]
|
||||
|
||||
if item.from_action:
|
||||
item.__dict__["action"] = item.__dict__.pop("from_action")
|
||||
if item.from_channel:
|
||||
item.__dict__["channel"] = item.__dict__.pop("from_channel")
|
||||
|
||||
if not channel:
|
||||
try:
|
||||
channel = __import__('channels.%s' % item.channel, fromlist=["channels.%s" % item.channel])
|
||||
except ImportError:
|
||||
exec "import channels." + item.channel + " as channel"
|
||||
|
||||
# Obtiene el listado de episodios
|
||||
itemlist = getattr(channel, item.action)(item)
|
||||
|
||||
insertados, sobreescritos, fallidos, path = save_tvshow(item, itemlist)
|
||||
|
||||
if not insertados and not sobreescritos and not fallidos:
|
||||
platformtools.dialog_ok("Videoteca", "ERROR, la serie NO se ha añadido a la videoteca",
|
||||
"No se ha podido obtener ningun episodio")
|
||||
logger.error("La serie %s no se ha podido añadir a la videoteca. No se ha podido obtener ningun episodio"
|
||||
% item.show)
|
||||
|
||||
elif fallidos == -1:
|
||||
platformtools.dialog_ok("Videoteca", "ERROR, la serie NO se ha añadido a la videoteca")
|
||||
logger.error("La serie %s no se ha podido añadir a la videoteca" % item.show)
|
||||
|
||||
elif fallidos > 0:
|
||||
platformtools.dialog_ok("Videoteca", "ERROR, la serie NO se ha añadido completa a la videoteca")
|
||||
logger.error("No se han podido añadir %s episodios de la serie %s a la videoteca" % (fallidos, item.show))
|
||||
|
||||
else:
|
||||
platformtools.dialog_ok("Videoteca", "La serie se ha añadido a la videoteca")
|
||||
logger.info("Se han añadido %s episodios de la serie %s a la videoteca" %
|
||||
(insertados, item.show))
|
||||
if config.is_xbmc():
|
||||
if config.get_setting("sync_trakt_new_tvshow", "videolibrary"):
|
||||
import xbmc
|
||||
from platformcode import xbmc_videolibrary
|
||||
if config.get_setting("sync_trakt_new_tvshow_wait", "videolibrary"):
|
||||
# Comprobar que no se esta buscando contenido en la videoteca de Kodi
|
||||
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
|
||||
xbmc.sleep(1000)
|
||||
# Se lanza la sincronizacion para la videoteca de Kodi
|
||||
xbmc_videolibrary.sync_trakt_kodi()
|
||||
# Se lanza la sincronización para la videoteca del addon
|
||||
xbmc_videolibrary.sync_trakt_addon(path)
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------------
|
||||
# Zip Tools
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
import config
|
||||
import logger
|
||||
|
||||
|
||||
class ziptools:
|
||||
def extract(self, file, dir, folder_to_extract="", overwrite_question=False, backup=False):
|
||||
logger.info("file=%s" % file)
|
||||
logger.info("dir=%s" % dir)
|
||||
|
||||
if not dir.endswith(':') and not os.path.exists(dir):
|
||||
os.mkdir(dir)
|
||||
|
||||
zf = zipfile.ZipFile(file)
|
||||
if not folder_to_extract:
|
||||
self._createstructure(file, dir)
|
||||
num_files = len(zf.namelist())
|
||||
|
||||
for name in zf.namelist():
|
||||
logger.info("name=%s" % name)
|
||||
if not name.endswith('/'):
|
||||
logger.info("no es un directorio")
|
||||
try:
|
||||
(path, filename) = os.path.split(os.path.join(dir, name))
|
||||
logger.info("path=%s" % path)
|
||||
logger.info("name=%s" % name)
|
||||
if folder_to_extract:
|
||||
if path != os.path.join(dir, folder):
|
||||
break
|
||||
else:
|
||||
os.makedirs(path)
|
||||
except:
|
||||
pass
|
||||
if folder_to_extract:
|
||||
outfilename = os.path.join(dir, filename)
|
||||
|
||||
else:
|
||||
outfilename = os.path.join(dir, name)
|
||||
logger.info("outfilename=%s" % outfilename)
|
||||
try:
|
||||
if os.path.exists(outfilename) and overwrite_question:
|
||||
from platformcode import platformtools
|
||||
dyesno = platformtools.dialog_yesno("El archivo ya existe",
|
||||
"El archivo %s a descomprimir ya existe" \
|
||||
", ¿desea sobrescribirlo?" \
|
||||
% os.path.basename(outfilename))
|
||||
if not dyesno:
|
||||
break
|
||||
if backup:
|
||||
import time
|
||||
import shutil
|
||||
hora_folder = "Copia seguridad [%s]" % time.strftime("%d-%m_%H-%M", time.localtime())
|
||||
backup = os.path.join(config.get_data_path(), 'backups', hora_folder, folder_to_extract)
|
||||
if not os.path.exists(backup):
|
||||
os.makedirs(backup)
|
||||
shutil.copy2(outfilename, os.path.join(backup, os.path.basename(outfilename)))
|
||||
|
||||
outfile = open(outfilename, 'wb')
|
||||
outfile.write(zf.read(name))
|
||||
except:
|
||||
logger.error("Error en fichero " + name)
|
||||
|
||||
def _createstructure(self, file, dir):
|
||||
self._makedirs(self._listdirs(file), dir)
|
||||
|
||||
def create_necessary_paths(filename):
|
||||
try:
|
||||
(path, name) = os.path.split(filename)
|
||||
os.makedirs(path)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _makedirs(self, directories, basedir):
|
||||
for dir in directories:
|
||||
curdir = os.path.join(basedir, dir)
|
||||
if not os.path.exists(curdir):
|
||||
os.mkdir(curdir)
|
||||
|
||||
def _listdirs(self, file):
|
||||
zf = zipfile.ZipFile(file)
|
||||
dirs = []
|
||||
for name in zf.namelist():
|
||||
if name.endswith('/'):
|
||||
dirs.append(name)
|
||||
|
||||
dirs.sort()
|
||||
return dirs
|
||||
Reference in New Issue
Block a user