This commit is contained in:
Alfa
2017-07-28 19:37:39 -04:00
parent 60e4685ce8
commit 3cc42f282f
1046 changed files with 162392 additions and 9 deletions
+12
View File
@@ -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__), "..")))
+59
View File
@@ -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)
+318
View File
@@ -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
+139
View File
@@ -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
+473
View File
@@ -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)
+546
View File
@@ -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)
File diff suppressed because it is too large Load Diff
+2239
View File
File diff suppressed because it is too large Load Diff
+579
View File
@@ -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
+260
View File
@@ -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
+482
View File
@@ -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
+192
View File
@@ -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
+79
View File
@@ -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)
+275
View File
@@ -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
+498
View File
@@ -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 &amp;, &gt;, &lt; 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;amp;"
elif text[1:-1] == "gt":
text = "&amp;gt;"
elif text[1:-1] == "lt":
text = "&amp;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 "&ntilde;" 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("&aacute", "&aacute;")
string = string.replace("&eacute", "&eacute;")
string = string.replace("&iacute", "&iacute;")
string = string.replace("&oacute", "&oacute;")
string = string.replace("&uacute", "&uacute;")
string = string.replace("&Aacute", "&Aacute;")
string = string.replace("&Eacute", "&Eacute;")
string = string.replace("&Iacute", "&Iacute;")
string = string.replace("&Oacute", "&Oacute;")
string = string.replace("&Uacute", "&Uacute;")
string = string.replace("&uuml", "&uuml;")
string = string.replace("&Uuml", "&Uuml;")
string = string.replace("&ntilde", "&ntilde;")
string = string.replace("&#191", "&#191;")
string = string.replace("&#161", "&#161;")
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("&amp;", "&")
# 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
+343
View File
@@ -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 "&ntilde;" 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("&amp;", "&")
# 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
+761
View File
@@ -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")
+1442
View File
File diff suppressed because it is too large Load Diff
+1135
View File
File diff suppressed because it is too large Load Diff
+170
View File
@@ -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()
+209
View File
@@ -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")
+37
View File
@@ -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>"))
+605
View File
@@ -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)
+94
View File
@@ -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