folder reorganization

This commit is contained in:
cttynul
2019-04-23 14:32:53 +02:00
parent 659751b2f4
commit 8e7ee78a87
1195 changed files with 267003 additions and 2 deletions

12
platformcode/__init__.py Normal file
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__), "..", "..")))

431
platformcode/config.py Normal file
View File

@@ -0,0 +1,431 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# Parámetros de configuración (kodi)
# ------------------------------------------------------------
import os
import re
import xbmc
import xbmcaddon
PLUGIN_NAME = "alfa"
__settings__ = xbmcaddon.Addon(id="plugin.video." + PLUGIN_NAME)
__language__ = __settings__.getLocalizedString
def get_addon_version(with_fix=True):
'''
Devuelve el número de versión del addon, y opcionalmente número de fix si lo hay
'''
if with_fix:
return __settings__.getAddonInfo('version') + get_addon_version_fix()
else:
return __settings__.getAddonInfo('version')
def get_addon_version_fix():
try:
last_fix_json = os.path.join(get_runtime_path(), 'last_fix.json') # información de la versión fixeada del usuario
if os.path.exists(last_fix_json):
with open(last_fix_json, 'r') as f: data=f.read(); f.close()
fix = re.findall('"fix_version"\s*:\s*(\d+)', data)
if fix:
return '.fix%s' % fix[0]
except:
pass
return ''
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': 'MyVideos116.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}
from core 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']:
set_setting('adult_password', settings_post['adult_aux_new_password1'])
else:
platformtools.dialog_ok(get_localized_string(60305),
get_localized_string(60306),
get_localized_string(60307))
else:
platformtools.dialog_ok(get_localized_string(60305), get_localized_string(60309),
get_localized_string(60310))
# 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: any
@return: El valor del parametro 'name'
@rtype: any
"""
# Specific channel setting
if channel:
# logger.info("get_setting reading channel setting '"+name+"' from channel json")
from core import channeltools
value = channeltools.get_channel_setting(name, channel, default)
# logger.info("get_setting -> '"+repr(value)+"'")
return value
# Specific server setting
elif server:
# logger.info("get_setting reading server setting '"+name+"' from server json")
from core import servertools
value = servertools.get_server_setting(name, server, default)
# logger.info("get_setting -> '"+repr(value)+"'")
return value
# Global setting
else:
# logger.info("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
if value == "true":
return True
elif value == "false":
return False
else:
# special case return as str
if name in ["adult_password", "adult_aux_intro_password", "adult_aux_new_password1",
"adult_aux_new_password2"]:
return value
else:
try:
value = int(value)
except ValueError:
pass
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:
if isinstance(value, bool):
if value:
value = "true"
else:
value = "false"
elif isinstance(value, (int, long)):
value = str(value)
__settings__.setSetting(name, value)
except Exception, ex:
from platformcode import logger
logger.error("Error al convertir '%s' no se guarda el valor \n%s" % (name, ex))
return None
return value
def get_localized_string(code):
dev = __language__(code)
try:
dev = dev.encode("utf-8")
except:
pass
return dev
def get_localized_category(categ):
categories = {'movie': get_localized_string(30122), 'tvshow': get_localized_string(30123),
'anime': get_localized_string(30124), 'documentary': get_localized_string(30125),
'vos': get_localized_string(30136), 'vosi': get_localized_string(70566), 'adult': get_localized_string(30126),
'direct': get_localized_string(30137), 'torrent': get_localized_string(70015)}
return categories[categ] if categ in categories else categ
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 platformcode 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"]]
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
filetools.mkdir(content_path)
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')
if not os.path.isdir(skindir): return # No hace falta mostrar error en el log si no existe la carpeta
# 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())

229
platformcode/custom_code.py Normal file
View File

@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------------
# Updater (kodi)
# --------------------------------------------------------------------------------
import os
import json
import traceback
import xbmc
import xbmcaddon
from platformcode import config, logger, platformtools
from core import jsontools
from core import filetools
json_data_file_name = 'custom_code.json'
def init():
logger.info()
"""
Todo el código añadido al add-on se borra con cada actualización. Esta función permite restaurarlo automáticamente con cada actualización.
Esto permite al usuario tener su propio código, bajo su responsabilidad, y restaurarlo al add-on cada vez que se actualiza.
El mecanismo funciona copiando el contenido de la carpeta-arbol ".\userdata\addon_data\plugin.video.alfa\custom_code\..." sobre
las carpetas de código del add-on. No verifica el contenido, solo vuelca(reemplaza) el contenido de "custom_code".
El usuario almacenará en las subcarpetas de "custom_code" su código actualizado y listo para ser copiado en cualquier momento.
Si no se desea que copie algo, simplemente se borra de "custom_code" y ya no se copiará en la próxima actualización.
Los pasos que sigue esta función, son los siguientes:
1.- La función se llama desde videolibrary_service.py, desde la función inicial:
# Copia Custom code a las carpetas de Alfa desde la zona de Userdata
from platformcode import custom_code
custom_code.init()
2.- En el inicio de Kodi, comprueba si existe la carpeta "custom_code" en ".\userdata\addon_data\plugin.video.alfa\".
Si no existe, la crea y sale sin más, dando al ususario la posibilidad de copiar sobre esa estructura su código,
y que la función la vuelque sobre el add-on en el próximo inicio de Kodi.
3.- En el siguiente inicio de Kodi, comprueba si existe el custom_code.json en la carpeta root del add-on.
Si no existe, lo crea con el número de versión del add-on vacío, para permitir que se copien los archivos en esta pasada.
4.- Verifica que el número de versión del add-on es diferente de el de custom_code.json. Si es la misma versión,
se sale porque ya se realizo la copia anteriormente.
Si la versión es distinta, se realiza el volcado de todos los archivos de la carpeta-árbol "custom_code" sobre el add-on.
Si la carpeta de destino no existe, dará un error y se cancelará la copia. Se considera que no tienen sentido nuevas carpetas.
5.- Si la copia ha terminado con éxito, se actualiza el custom_code.json con el número de versión del add-on,
para que en inicios sucesivos de Kodi no se realicen las copias, hasta que el add-on cambie de versión.
En el número de versión del add-on no se considera el número de fix.
Tiempos: Copiando 7 archivos de prueba, el proceso ha tardado una décima de segundo.
"""
try:
#Verifica si Kodi tiene algún achivo de Base de Datos de Vídeo de versiones anteriores, entonces los borra
verify_Kodi_video_DB()
#QUASAR: Preguntamos si se hacen modificaciones a Quasar
if not filetools.exists(os.path.join(config.get_data_path(), "quasar.json")) and not config.get_setting('addon_quasar_update', default=False):
question_update_external_addon("quasar")
#QUASAR: Hacemos las modificaciones a Quasar, si está permitido, y si está instalado
if config.get_setting('addon_quasar_update', default=False):
if not update_external_addon("quasar"):
platformtools.dialog_notification("Actualización Quasar", "Ha fallado. Consulte el log")
#Existe carpeta "custom_code" ? Si no existe se crea y se sale
custom_code_dir = os.path.join(config.get_data_path(), 'custom_code')
if os.path.exists(custom_code_dir) == False:
create_folder_structure(custom_code_dir)
return
else:
#Existe "custom_code.json" ? Si no existe se crea
custom_code_json_path = config.get_runtime_path()
custom_code_json = os.path.join(custom_code_json_path, 'custom_code.json')
if os.path.exists(custom_code_json) == False:
create_json(custom_code_json_path)
#Se verifica si la versión del .json y del add-on son iguales. Si es así se sale. Si no se copia "custom_code" al add-on
verify_copy_folders(custom_code_dir, custom_code_json_path)
except:
logger.error(traceback.format_exc())
def create_folder_structure(custom_code_dir):
logger.info()
#Creamos todas las carpetas. La importante es "custom_code". Las otras sirven meramente de guía para evitar errores de nombres...
os.mkdir(custom_code_dir)
os.mkdir(filetools.join(custom_code_dir, 'channels'))
os.mkdir(filetools.join(custom_code_dir, 'core'))
os.mkdir(filetools.join(custom_code_dir, 'lib'))
os.mkdir(filetools.join(custom_code_dir, 'platformcode'))
os.mkdir(filetools.join(custom_code_dir, 'resources'))
os.mkdir(filetools.join(custom_code_dir, 'servers'))
return
def create_json(custom_code_json_path, json_name=json_data_file_name):
logger.info()
#Guardamaos el json con la versión de Alfa vacía, para permitir hacer la primera copia
json_data_file = filetools.join(custom_code_json_path, json_name)
json_file = open(json_data_file, "a+")
json_file.write(json.dumps({"addon_version": ""}))
json_file.close()
return
def verify_copy_folders(custom_code_dir, custom_code_json_path):
logger.info()
#verificamos si es una nueva versión de Alfa instalada o era la existente. Si es la existente, nos vamos sin hacer nada
json_data_file = filetools.join(custom_code_json_path, json_data_file_name)
json_data = jsontools.load(filetools.read(json_data_file))
current_version = config.get_addon_version(with_fix=False)
if current_version == json_data['addon_version']:
return
#Ahora copiamos los archivos desde el área de Userdata, Custom_code, sobre las carpetas del add-on
for root, folders, files in os.walk(custom_code_dir):
for file in files:
input_file = filetools.join(root, file)
output_file = input_file.replace(custom_code_dir, custom_code_json_path)
if filetools.copy(input_file, output_file, silent=True) == False:
return
#Guardamaos el json con la versión actual de Alfa, para no volver a hacer la copia hasta la nueva versión
json_data['addon_version'] = current_version
filetools.write(json_data_file, jsontools.dump(json_data))
return
def question_update_external_addon(addon_name):
logger.info(addon_name)
#Verificamos que el addon está instalado
stat = False
if xbmc.getCondVisibility('System.HasAddon("plugin.video.%s")' % addon_name):
#Si es la primera vez que se pregunta por la actualización del addon externo, recogemos la respuesta,
# guardaos un .json en userdat/alfa para no volver a preguntar otra vez, y se actualiza el setting en Alfa.
stat = platformtools.dialog_yesno('Actualización de %s' % addon_name.capitalize(), '¿Quiere que actualicemos Quasar para que sea compatible con las últimas versiones de Kodi? (recomendado: SÍ)', '', 'Si actualiza Quasar, reinicie Kodi en un par de minutos')
#Con la respuesta actualizamos la variable en Alfa settings.xml. Se puede cambiar en Ajustes de Alfa, Otros
if stat:
config.set_setting('addon_quasar_update', True)
else:
config.set_setting('addon_quasar_update', False)
#Creamos un .json en userdata para no volver a preguntar otra vez
create_json(config.get_data_path(), "%s.json" % addon_name)
return stat
def update_external_addon(addon_name):
logger.info(addon_name)
#Verificamos que el addon está instalado
if xbmc.getCondVisibility('System.HasAddon("plugin.video.%s")' % addon_name):
#Path de actuali<aciones de Alfa
alfa_addon_updates = filetools.join(config.get_runtime_path(), filetools.join("lib", addon_name))
#Path de destino en addon externo
__settings__ = xbmcaddon.Addon(id="plugin.video." + addon_name)
if addon_name.lower() in ['quasar', 'elementum']:
addon_path = filetools.join(xbmc.translatePath(__settings__.getAddonInfo('Path')), filetools.join("resources", filetools.join("site-packages", addon_name)))
else:
addon_path = ''
#Hay modificaciones en Alfa? Las copiamos al addon
if filetools.exists(alfa_addon_updates) and filetools.exists(addon_path):
for root, folders, files in os.walk(alfa_addon_updates):
for file in files:
input_file = filetools.join(root, file)
output_file = input_file.replace(alfa_addon_updates, addon_path)
if filetools.copy(input_file, output_file, silent=True) == False:
logger.error('Error en la copia: Input: %s o Output: %s' % (input_file, output_file))
return False
return True
else:
logger.error('Alguna carpeta no existe: Alfa: %s o %s: %s' % (alfa_addon_updates, addon_name, addon_path))
return False
def verify_Kodi_video_DB():
logger.info()
import random
platform = {}
path = ''
db_files = []
try:
path = filetools.join(xbmc.translatePath("special://masterprofile/"), "Database")
if filetools.exists(path):
platform = config.get_platform(full_version=True)
if platform:
db_files = filetools.walk(path)
if filetools.exists(filetools.join(path, platform['video_db'])):
for root, folders, files in db_files:
for file in files:
if file != platform['video_db']:
if file.startswith('MyVideos'):
randnum = str(random.randrange(1, 999999))
filetools.rename(filetools.join(path, file), 'OLD_' + randnum +'_' + file)
logger.error('BD obsoleta: ' + file)
else:
logger.error('Video_DB: ' + str(platform['video_db']) + ' para versión Kodi ' + str(platform['num_version']) + ' NO EXISTE. Analizar carpeta: ' + str(db_files))
else:
logger.error('Estructura de get_platform(full_version=True) incorrecta')
else:
logger.error('Path a Userdata/Database (' + path + ') no encontrado')
except:
logger.error('Platform: ' + str(platform) + ' / Path: ' + str(path) + ' / Files: ' + str(db_files))
logger.error(traceback.format_exc())
return

View File

@@ -0,0 +1,351 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# Download and play
# ------------------------------------------------------------
# Based on code from the Mega add-on (xbmchub.com)
# ---------------------------------------------------------------------------
import os
import re
import socket
import threading
import time
import urllib
import urllib2
import xbmc
import xbmcgui
from core import downloadtools
from platformcode import config, logger
# Download a file and start playing while downloading
def download_and_play(url, file_name, download_path):
# Lanza thread
logger.info("Active threads " + str(threading.active_count()))
logger.info("" + repr(threading.enumerate()))
logger.info("Starting download thread...")
download_thread = DownloadThread(url, file_name, download_path)
download_thread.start()
logger.info("Download thread started")
logger.info("Active threads " + str(threading.active_count()))
logger.info("" + repr(threading.enumerate()))
# Espera
logger.info("Waiting...")
while True:
cancelled = False
dialog = xbmcgui.DialogProgress()
dialog.create(config.get_localized_string(60200), config.get_localized_string(60312))
dialog.update(0)
while not cancelled and download_thread.isAlive():
dialog.update(download_thread.get_progress(), config.get_localized_string(60313),
"Velocidad: " + str(int(download_thread.get_speed() / 1024)) + " KB/s " + str(
download_thread.get_actual_size()) + "MB de " + str(
download_thread.get_total_size()) + "MB",
"Tiempo restante: " + str(downloadtools.sec_to_hms(download_thread.get_remaining_time())))
xbmc.sleep(1000)
if dialog.iscanceled():
cancelled = True
break
dialog.close()
logger.info("End of waiting")
# Lanza el reproductor
player = CustomPlayer()
player.set_download_thread(download_thread)
player.PlayStream(download_thread.get_file_name())
# Fin de reproducción
logger.info("Fin de reproducción")
if player.is_stopped():
logger.info("Terminado por el usuario")
break
else:
if not download_thread.isAlive():
logger.info("La descarga ha terminado")
break
else:
logger.info("Continua la descarga")
# Cuando el reproductor acaba, si continúa descargando lo para ahora
logger.info("Download thread alive=" + str(download_thread.isAlive()))
if download_thread.isAlive():
logger.info("Killing download thread")
download_thread.force_stop()
class CustomPlayer(xbmc.Player):
def __init__(self, *args, **kwargs):
logger.info()
self.actualtime = 0
self.totaltime = 0
self.stopped = False
xbmc.Player.__init__(self)
def PlayStream(self, url):
logger.info("url=" + url)
self.play(url)
self.actualtime = 0
self.url = url
while self.isPlaying():
self.actualtime = self.getTime()
self.totaltime = self.getTotalTime()
logger.info("actualtime=" + str(self.actualtime) + " totaltime=" + str(self.totaltime))
xbmc.sleep(3000)
def set_download_thread(self, download_thread):
logger.info()
self.download_thread = download_thread
def force_stop_download_thread(self):
logger.info()
if self.download_thread.isAlive():
logger.info("Killing download thread")
self.download_thread.force_stop()
# while self.download_thread.isAlive():
# xbmc.sleep(1000)
def onPlayBackStarted(self):
logger.info("PLAYBACK STARTED")
def onPlayBackEnded(self):
logger.info("PLAYBACK ENDED")
def onPlayBackStopped(self):
logger.info("PLAYBACK STOPPED")
self.stopped = True
self.force_stop_download_thread()
def is_stopped(self):
return self.stopped
# Download in background
class DownloadThread(threading.Thread):
def __init__(self, url, file_name, download_path):
logger.info(repr(file))
self.url = url
self.download_path = download_path
self.file_name = os.path.join(download_path, file_name)
self.progress = 0
self.force_stop_file_name = os.path.join(self.download_path, "force_stop.tmp")
self.velocidad = 0
self.tiempofalta = 0
self.actual_size = 0
self.total_size = 0
if os.path.exists(self.force_stop_file_name):
os.remove(self.force_stop_file_name)
threading.Thread.__init__(self)
def run(self):
logger.info("Download starts...")
if "megacrypter.com" in self.url:
self.download_file_megacrypter()
else:
self.download_file()
logger.info("Download ends")
def force_stop(self):
logger.info()
force_stop_file = open(self.force_stop_file_name, "w")
force_stop_file.write("0")
force_stop_file.close()
def get_progress(self):
return self.progress
def get_file_name(self):
return self.file_name
def get_speed(self):
return self.velocidad
def get_remaining_time(self):
return self.tiempofalta
def get_actual_size(self):
return self.actual_size
def get_total_size(self):
return self.total_size
def download_file_megacrypter(self):
logger.info()
comando = "./megacrypter.sh"
logger.info("comando=" + comando)
oldcwd = os.getcwd()
logger.info("oldcwd=" + oldcwd)
cwd = os.path.join(config.get_runtime_path(), "tools")
logger.info("cwd=" + cwd)
os.chdir(cwd)
logger.info("directory changed to=" + os.getcwd())
logger.info("destino=" + self.download_path)
os.system(comando + " '" + self.url + "' \"" + self.download_path + "\"")
# p = subprocess.Popen([comando , self.url , self.download_path], cwd=cwd, stdout=subprocess.PIPE , stderr=subprocess.PIPE )
# out, err = p.communicate()
# logger.info("DownloadThread.download_file out="+out)
os.chdir(oldcwd)
def download_file(self):
logger.info("Direct download")
headers = []
# Se asegura de que el fichero se podrá crear
logger.info("nombrefichero=" + self.file_name)
self.file_name = xbmc.makeLegalFilename(self.file_name)
logger.info("nombrefichero=" + self.file_name)
logger.info("url=" + self.url)
# Crea el fichero
existSize = 0
f = open(self.file_name, 'wb')
grabado = 0
# Interpreta las cabeceras en una URL como en XBMC
if "|" in self.url:
additional_headers = self.url.split("|")[1]
if "&" in additional_headers:
additional_headers = additional_headers.split("&")
else:
additional_headers = [additional_headers]
for additional_header in additional_headers:
logger.info("additional_header: " + additional_header)
name = re.findall("(.*?)=.*?", additional_header)[0]
value = urllib.unquote_plus(re.findall(".*?=(.*?)$", additional_header)[0])
headers.append([name, value])
self.url = self.url.split("|")[0]
logger.info("url=" + self.url)
# Timeout del socket a 60 segundos
socket.setdefaulttimeout(60)
# Crea la petición y añade las cabeceras
h = urllib2.HTTPHandler(debuglevel=0)
request = urllib2.Request(self.url)
for header in headers:
logger.info("Header=" + header[0] + ": " + header[1])
request.add_header(header[0], header[1])
# Lanza la petición
opener = urllib2.build_opener(h)
urllib2.install_opener(opener)
try:
connexion = opener.open(request)
except urllib2.HTTPError, e:
logger.error("error %d (%s) al abrir la url %s" % (e.code, e.msg, self.url))
# print e.code
# print e.msg
# print e.hdrs
# print e.fp
f.close()
# El error 416 es que el rango pedido es mayor que el fichero => es que ya está completo
if e.code == 416:
return 0
else:
return -2
try:
totalfichero = int(connexion.headers["Content-Length"])
except:
totalfichero = 1
self.total_size = int(float(totalfichero) / float(1024 * 1024))
logger.info("Content-Length=%s" % totalfichero)
blocksize = 100 * 1024
bloqueleido = connexion.read(blocksize)
logger.info("Iniciando descarga del fichero, bloqueleido=%s" % len(bloqueleido))
maxreintentos = 10
while len(bloqueleido) > 0:
try:
if os.path.exists(self.force_stop_file_name):
logger.info("Detectado fichero force_stop, se interrumpe la descarga")
f.close()
xbmc.executebuiltin((u'XBMC.Notification("Cancelado", "Descarga en segundo plano cancelada", 300)'))
return
# Escribe el bloque leido
# try:
# import xbmcvfs
# f.write( bloqueleido )
# except:
f.write(bloqueleido)
grabado = grabado + len(bloqueleido)
logger.info("grabado=%d de %d" % (grabado, totalfichero))
percent = int(float(grabado) * 100 / float(totalfichero))
self.progress = percent;
totalmb = float(float(totalfichero) / (1024 * 1024))
descargadosmb = float(float(grabado) / (1024 * 1024))
self.actual_size = int(descargadosmb)
# Lee el siguiente bloque, reintentando para no parar todo al primer timeout
reintentos = 0
while reintentos <= maxreintentos:
try:
before = time.time()
bloqueleido = connexion.read(blocksize)
after = time.time()
if (after - before) > 0:
self.velocidad = len(bloqueleido) / ((after - before))
falta = totalfichero - grabado
if self.velocidad > 0:
self.tiempofalta = falta / self.velocidad
else:
self.tiempofalta = 0
break
except:
import sys
reintentos = reintentos + 1
logger.info("ERROR en la descarga del bloque, reintento %d" % reintentos)
for line in sys.exc_info():
logger.error("%s" % line)
# Ha habido un error en la descarga
if reintentos > maxreintentos:
logger.error("ERROR en la descarga del fichero")
f.close()
return -2
except:
import traceback, sys
from pprint import pprint
exc_type, exc_value, exc_tb = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
for line in lines:
line_splits = line.split("\n")
for line_split in line_splits:
logger.error(line_split)
f.close()
return -2
return

161
platformcode/keymaptools.py Normal file
View File

@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
from threading import Timer
import xbmc
import xbmcaddon
import xbmcgui
from channelselector import get_thumb
from platformcode import config
class KeyListener(xbmcgui.WindowXMLDialog):
TIMEOUT = 10
def __new__(cls):
gui_api = tuple(map(int, xbmcaddon.Addon('xbmc.gui').getAddonInfo('version').split('.')))
if gui_api >= (5, 11, 0):
filenname = "DialogNotification.xml"
else:
filenname = "DialogKaiToast.xml"
return super(KeyListener, cls).__new__(cls, filenname, "")
def __init__(self):
self.key = None
def onInit(self):
try:
self.getControl(401).addLabel("Presiona la tecla a usar para abrir la ventana")
self.getControl(402).addLabel("Tienes %s segundos" % self.TIMEOUT)
except AttributeError:
self.getControl(401).setLabel("Presiona la tecla a usar para abrir la ventana")
self.getControl(402).setLabel("Tienes %s segundos" % self.TIMEOUT)
def onAction(self, action):
code = action.getButtonCode()
if code == 0:
self.key = None
else:
self.key = str(code)
self.close()
@staticmethod
def record_key():
dialog = KeyListener()
timeout = Timer(KeyListener.TIMEOUT, dialog.close)
timeout.start()
dialog.doModal()
timeout.cancel()
key = dialog.key
del dialog
return key
def set_key():
saved_key = config.get_setting("shortcut_key")
new_key = KeyListener().record_key()
if new_key and saved_key != new_key:
from core import filetools
from platformcode import platformtools
import xbmc
file_xml = "special://profile/keymaps/alfa.xml"
data = '<keymap><global><keyboard><key id="%s">' % new_key + 'runplugin(plugin://' \
'plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAia2V5bWFwIiwNCiAgICAib3BlbiI6IHRydWUNCn0=)</key></keyboard></global></keymap>'
filetools.write(xbmc.translatePath(file_xml), data)
platformtools.dialog_notification("Tecla guardada", "Reinicia Kodi para que se apliquen los cambios")
config.set_setting("shortcut_key", new_key)
# file_idioma = filetools.join(config.get_runtime_path(), 'resources', 'language', 'Spanish', 'strings.xml')
# data = filetools.read(file_idioma)
# value_xml = scrapertools.find_single_match(data, '<string id="31100">([^<]+)<')
# if "tecla" in value_xml:
# data = data.replace(value_xml, 'Cambiar tecla/botón para abrir la ventana (Guardada: %s)' % new_key)
# elif "key" in value_xml:
# data = data.replace(value_xml, 'Change key/button to open the window (Saved: %s)' % new_key)
# else:
# data = data.replace(value_xml,
# 'Cambiamento di chiave/pulsante per aprire la finestra (Salvato: %s)' % new_key)
# filetools.write(file_idioma, data)
return
MAIN_MENU = {
"news": {"label": "Novedades", "icon": get_thumb("news.png"), "order": 0},
"channels": {"label": "Canales", "icon": get_thumb("channels.png"), "order": 1},
"search": {"label": "Buscador", "icon": get_thumb("search.png"), "order": 2},
"favorites": {"label": "Favoritos", "icon": get_thumb("favorites.png"), "order": 3},
"videolibrary": {"label": "Videoteca", "icon": get_thumb("videolibrary.png"), "order": 4},
"downloads": {"label": "Descargas", "icon": get_thumb("downloads.png"), "order": 5},
"settings": {"label": "Configuración", "icon": get_thumb("setting_0.png"), "order": 6}
}
class Main(xbmcgui.WindowXMLDialog):
def __init__(self, *args, **kwargs):
self.items = []
def onInit(self):
#### Compatibilidad con Kodi 18 ####
if config.get_platform(True)['num_version'] < 18:
self.setCoordinateResolution(2)
for menuentry in MAIN_MENU.keys():
item = xbmcgui.ListItem(MAIN_MENU[menuentry]["label"])
item.setProperty("thumb", str(MAIN_MENU[menuentry]["icon"]))
item.setProperty("identifier", str(menuentry))
item.setProperty("order", str(MAIN_MENU[menuentry]["order"]))
self.items.append(item)
self.items.sort(key=lambda it: it.getProperty("order"))
self.getControl(32500).addItems(self.items)
self.setFocusId(32500)
def onClick(self, control_id):
if control_id == 32500:
identifier = self.getControl(32500).getSelectedItem().getProperty("identifier")
if identifier == "news":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAibWFpbmxpc3QiLCANCiAgICAiY2hhbm5lbCI6ICJuZXdzIg0KfQ==")')
elif identifier == "channels":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAiZ2V0Y2hhbm5lbHR5cGVzIiwgDQogICAgImNoYW5uZWwiOiAiY2hhbm5lbHNlbGVjdG9yIg0KfQ==")')
elif identifier == "search":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAibWFpbmxpc3QiLCANCiAgICAiY2hhbm5lbCI6ICJzZWFyY2giDQp9")')
elif identifier == "favorites":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAibWFpbmxpc3QiLCANCiAgICAiY2hhbm5lbCI6ICJmYXZvcml0ZXMiDQp9")')
elif identifier == "videolibrary":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAibWFpbmxpc3QiLCANCiAgICAiY2hhbm5lbCI6ICJ2aWRlb2xpYnJhcnkiDQp9")')
elif identifier == "downloads":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAibWFpbmxpc3QiLCANCiAgICAiY2hhbm5lbCI6ICJkb3dubG9hZHMiDQp9")')
elif identifier == "settings":
xbmc.executebuiltin('Dialog.Close(all,true)')
xbmc.executebuiltin(
'ActivateWindow(10025, "plugin://plugin.video.alfa/?ew0KICAgICJhY3Rpb24iOiAibWFpbmxpc3QiLCANCiAgICAiY2hhbm5lbCI6ICJzZXR0aW5nIg0KfQ==")')
def onAction(self, action):
# exit
if action.getId() in [xbmcgui.ACTION_PREVIOUS_MENU, xbmcgui.ACTION_NAV_BACK]:
# main.close()
xbmc.executebuiltin('Dialog.Close(all,true)')
if action.getId() == xbmcgui.ACTION_CONTEXT_MENU:
config.open_settings()
def open_shortcut_menu():
main = Main('ShortCutMenu.xml', config.get_runtime_path())
main.doModal()
del main

489
platformcode/launcher.py Normal file
View File

@@ -0,0 +1,489 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# XBMC Launcher (xbmc / kodi)
# ------------------------------------------------------------
import os
import sys
import urllib2
import time
from core import channeltools
from core import scrapertools
from core import servertools
from core import videolibrarytools
from core import trakt_tools
from core.item import Item
from platformcode import config, logger
from platformcode import platformtools
from platformcode.logger import WebErrorException
def start():
""" Primera funcion que se ejecuta al entrar en el plugin.
Dentro de esta funcion deberian ir todas las llamadas a las
funciones que deseamos que se ejecuten nada mas abrir el plugin.
"""
logger.info()
#config.set_setting('show_once', True)
# Test if all the required directories are created
config.verify_directories_created()
def run(item=None):
logger.info()
if not item:
# Extract item from sys.argv
if sys.argv[2]:
item = Item().fromurl(sys.argv[2])
# If no item, this is mainlist
else:
if config.get_setting("start_page"):
if not config.get_setting("custom_start"):
dictCategory = {
config.get_localized_string(70137): 'peliculas',
config.get_localized_string(30123): 'series',
config.get_localized_string(30124): 'anime',
config.get_localized_string(70018): 'infantiles',
config.get_localized_string(60513): 'documentales',
config.get_localized_string(70013): 'terror',
config.get_localized_string(30124): 'castellano',
config.get_localized_string(59976): 'latino',
config.get_localized_string(70171): 'torrent',
}
category = dictCategory[config.get_setting("category")]
item = Item(channel="news", action="novedades", extra=category, mode = 'silent')
else:
from channels import side_menu
item= Item()
item = side_menu.check_user_home(item)
item.start = True;
else:
item = Item(channel="channelselector", action="getmainlist", viewmode="movie")
if not config.get_setting('show_once'):
from platformcode import xbmc_videolibrary
xbmc_videolibrary.ask_set_content(1)
config.set_setting('show_once', True)
logger.info(item.tostring())
try:
# If item has no action, stops here
if item.action == "":
logger.info("Item sin accion")
return
# Action for main menu in channelselector
elif item.action == "getmainlist":
import channelselector
itemlist = channelselector.getmainlist()
platformtools.render_items(itemlist, item)
# Action for channel types on channelselector: movies, series, etc.
elif item.action == "getchanneltypes":
import channelselector
itemlist = channelselector.getchanneltypes()
platformtools.render_items(itemlist, item)
# Action for channel listing on channelselector
elif item.action == "filterchannels":
import channelselector
itemlist = channelselector.filterchannels(item.channel_type)
platformtools.render_items(itemlist, item)
# Special action for playing a video from the library
elif item.action == "play_from_library":
play_from_library(item)
return
elif item.action == "keymap":
from platformcode import keymaptools
if item.open:
return keymaptools.open_shortcut_menu()
else:
return keymaptools.set_key()
elif item.action == "script":
from core import tmdb
if tmdb.drop_bd():
platformtools.dialog_notification(config.get_localized_string(20000), config.get_localized_string(60011), time=2000, sound=False)
# Action in certain channel specified in "action" and "channel" parameters
else:
# Entry point for a channel is the "mainlist" action, so here we check parental control
if item.action == "mainlist":
# Parental control
# If it is an adult channel, and user has configured pin, asks for it
if channeltools.is_adult(item.channel) and config.get_setting("adult_request_password"):
tecleado = platformtools.dialog_input("", config.get_localized_string(60334), True)
if tecleado is None or tecleado != config.get_setting("adult_password"):
return
# # Actualiza el canal individual
# if (item.action == "mainlist" and item.channel != "channelselector" and
# config.get_setting("check_for_channel_updates") == True):
# from core import updater
# updater.update_channel(item.channel)
# Checks if channel exists
channel_file = os.path.join(config.get_runtime_path(),
'channels', item.channel + ".py")
logger.info("channel_file=%s" % channel_file)
channel = None
if os.path.exists(channel_file):
try:
channel = __import__('channels.%s' % item.channel, None,
None, ["channels.%s" % item.channel])
except ImportError:
exec "import channels." + item.channel + " as channel"
logger.info("Running channel %s | %s" % (channel.__name__, channel.__file__))
# Special play action
if item.action == "play":
#define la info para trakt
try:
trakt_tools.set_trakt_info(item)
except:
pass
logger.info("item.action=%s" % item.action.upper())
# logger.debug("item_toPlay: " + "\n" + item.tostring('\n'))
# First checks if channel has a "play" function
if hasattr(channel, 'play'):
logger.info("Executing channel 'play' method")
itemlist = channel.play(item)
b_favourite = item.isFavourite
# Play should return a list of playable URLS
if len(itemlist) > 0 and isinstance(itemlist[0], Item):
item = itemlist[0]
if b_favourite:
item.isFavourite = True
platformtools.play_video(item)
# Permitir varias calidades desde play en el canal
elif len(itemlist) > 0 and isinstance(itemlist[0], list):
item.video_urls = itemlist
platformtools.play_video(item)
# If not, shows user an error message
else:
platformtools.dialog_ok(config.get_localized_string(20000), config.get_localized_string(60339))
# If player don't have a "play" function, not uses the standard play from platformtools
else:
logger.info("Executing core 'play' method")
platformtools.play_video(item)
# Special action for findvideos, where the plugin looks for known urls
elif item.action == "findvideos":
# First checks if channel has a "findvideos" function
if hasattr(channel, 'findvideos'):
itemlist = getattr(channel, item.action)(item)
itemlist = servertools.filter_servers(itemlist)
# If not, uses the generic findvideos function
else:
logger.info("No channel 'findvideos' method, "
"executing core method")
itemlist = servertools.find_video_items(item)
if config.get_setting("max_links", "videolibrary") != 0:
itemlist = limit_itemlist(itemlist)
from platformcode import subtitletools
subtitletools.saveSubtitleName(item)
platformtools.render_items(itemlist, item)
# Special action for adding a movie to the library
elif item.action == "add_pelicula_to_library":
videolibrarytools.add_movie(item)
# Special action for adding a serie to the library
elif item.action == "add_serie_to_library":
videolibrarytools.add_tvshow(item, channel)
# Special action for downloading all episodes from a serie
elif item.action == "download_all_episodes":
from channels import downloads
item.action = item.extra
del item.extra
downloads.save_download(item)
# Special action for searching, first asks for the words then call the "search" function
elif item.action == "search":
logger.info("item.action=%s" % item.action.upper())
last_search = ""
last_search_active = config.get_setting("last_search", "search")
if last_search_active:
try:
current_saved_searches_list = list(config.get_setting("saved_searches_list", "search"))
last_search = current_saved_searches_list[0]
except:
pass
tecleado = platformtools.dialog_input(last_search)
if tecleado is not None:
if last_search_active and not tecleado.startswith("http"):
from channels import search
search.save_search(tecleado)
itemlist = channel.search(item, tecleado)
else:
return
platformtools.render_items(itemlist, item)
# For all other actions
else:
logger.info("Executing channel '%s' method" % item.action)
itemlist = getattr(channel, item.action)(item)
if config.get_setting('trakt_sync'):
token_auth = config.get_setting("token_trakt", "trakt")
if not token_auth:
trakt_tools.auth_trakt()
else:
import xbmc
if not xbmc.getCondVisibility('System.HasAddon(script.trakt)') and config.get_setting(
'install_trakt'):
trakt_tools.ask_install_script()
itemlist = trakt_tools.trakt_check(itemlist)
else:
config.set_setting('install_trakt', True)
platformtools.render_items(itemlist, item)
except urllib2.URLError, e:
import traceback
logger.error(traceback.format_exc())
# Grab inner and third party errors
if hasattr(e, 'reason'):
logger.error("Razon del error, codigo: %s | Razon: %s" % (str(e.reason[0]), str(e.reason[1])))
texto = config.get_localized_string(30050) # "No se puede conectar con el sitio web"
platformtools.dialog_ok("alfa", texto)
# Grab server response errors
elif hasattr(e, 'code'):
logger.error("Codigo de error HTTP : %d" % e.code)
# "El sitio web no funciona correctamente (error http %d)"
platformtools.dialog_ok("alfa", config.get_localized_string(30051) % e.code)
except WebErrorException, e:
import traceback
logger.error(traceback.format_exc())
patron = 'File "' + os.path.join(config.get_runtime_path(), "channels", "").replace("\\",
"\\\\") + '([^.]+)\.py"'
canal = scrapertools.find_single_match(traceback.format_exc(), patron)
platformtools.dialog_ok(
config.get_localized_string(59985) + canal,
config.get_localized_string(60013) %(e))
except:
import traceback
logger.error(traceback.format_exc())
patron = 'File "' + os.path.join(config.get_runtime_path(), "channels", "").replace("\\",
"\\\\") + '([^.]+)\.py"'
canal = scrapertools.find_single_match(traceback.format_exc(), patron)
try:
import xbmc
if config.get_platform(True)['num_version'] < 14:
log_name = "xbmc.log"
else:
log_name = "kodi.log"
log_message = config.get_localized_string(50004) + xbmc.translatePath("special://logpath") + log_name
except:
log_message = ""
if canal:
platformtools.dialog_ok(
config.get_localized_string(60087) %canal,
config.get_localized_string(60014),
log_message)
else:
platformtools.dialog_ok(
config.get_localized_string(60038),
config.get_localized_string(60015),
log_message)
def reorder_itemlist(itemlist):
logger.info()
# logger.debug("Inlet itemlist size: %i" % len(itemlist))
new_list = []
mod_list = []
not_mod_list = []
modified = 0
not_modified = 0
to_change = [[config.get_localized_string(60335), '[V]'],
[config.get_localized_string(60336), '[D]']]
for item in itemlist:
old_title = unicode(item.title, "utf8").lower().encode("utf8")
for before, after in to_change:
if before in item.title:
item.title = item.title.replace(before, after)
break
new_title = unicode(item.title, "utf8").lower().encode("utf8")
if old_title != new_title:
mod_list.append(item)
modified += 1
else:
not_mod_list.append(item)
not_modified += 1
# logger.debug("OLD: %s | NEW: %s" % (old_title, new_title))
new_list.extend(mod_list)
new_list.extend(not_mod_list)
logger.info("Titulos modificados:%i | No modificados:%i" % (modified, not_modified))
if len(new_list) == 0:
new_list = itemlist
# logger.debug("Outlet itemlist size: %i" % len(new_list))
return new_list
def limit_itemlist(itemlist):
logger.info()
# logger.debug("Inlet itemlist size: %i" % len(itemlist))
try:
opt = config.get_setting("max_links", "videolibrary")
if opt == 0:
new_list = itemlist
else:
i_max = 30 * opt
new_list = itemlist[:i_max]
# logger.debug("Outlet itemlist size: %i" % len(new_list))
return new_list
except:
return itemlist
def play_from_library(item):
"""
Los .strm al reproducirlos desde kodi, este espera que sea un archivo "reproducible" asi que no puede contener
más items, como mucho se puede colocar un dialogo de seleccion.
Esto lo solucionamos "engañando a kodi" y haciendole creer que se ha reproducido algo, asi despues mediante
"Container.Update()" cargamos el strm como si un item desde dentro del addon se tratara, quitando todas
las limitaciones y permitiendo reproducir mediante la funcion general sin tener que crear nuevos métodos para
la videoteca.
@type item: item
@param item: elemento con información
"""
logger.info()
#logger.debug("item: \n" + item.tostring('\n'))
import xbmcgui
import xbmcplugin
import xbmc
from time import sleep
# Intentamos reproducir una imagen (esto no hace nada y ademas no da error)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True,
xbmcgui.ListItem(
path=os.path.join(config.get_runtime_path(), "resources", "subtitle.mp4")))
# Por si acaso la imagen hiciera (en futuras versiones) le damos a stop para detener la reproduccion
sleep(0.5) ### Si no se pone esto se bloquea Kodi
xbmc.Player().stop()
# modificamos el action (actualmente la videoteca necesita "findvideos" ya que es donde se buscan las fuentes
item.action = "findvideos"
window_type = config.get_setting("window_type", "videolibrary")
# y volvemos a lanzar kodi
if xbmc.getCondVisibility('Window.IsMedia') and not window_type == 1:
# Ventana convencional
xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ")")
else:
# Ventana emergente
from channels import videolibrary
p_dialog = platformtools.dialog_progress_bg(config.get_localized_string(20000), config.get_localized_string(70004))
p_dialog.update(0, '')
itemlist = videolibrary.findvideos(item)
while platformtools.is_playing():
# Ventana convencional
sleep(5)
p_dialog.update(50, '')
'''# Se filtran los enlaces segun la lista negra
if config.get_setting('filter_servers', "servers"):
itemlist = servertools.filter_servers(itemlist)'''
# Se limita la cantidad de enlaces a mostrar
if config.get_setting("max_links", "videolibrary") != 0:
itemlist = limit_itemlist(itemlist)
# Se "limpia" ligeramente la lista de enlaces
if config.get_setting("replace_VD", "videolibrary") == 1:
itemlist = reorder_itemlist(itemlist)
import time
p_dialog.update(100, '')
time.sleep(0.5)
p_dialog.close()
if len(itemlist) > 0:
while not xbmc.Monitor().abortRequested():
# El usuario elige el mirror
opciones = []
for item in itemlist:
opciones.append(item.title)
# Se abre la ventana de seleccion
if (item.contentSerieName != "" and
item.contentSeason != "" and
item.contentEpisodeNumber != ""):
cabecera = ("%s - %sx%s -- %s" %
(item.contentSerieName,
item.contentSeason,
item.contentEpisodeNumber,
config.get_localized_string(30163)))
else:
cabecera = config.get_localized_string(30163)
seleccion = platformtools.dialog_select(cabecera, opciones)
if seleccion == -1:
return
else:
item = videolibrary.play(itemlist[seleccion])[0]
platformtools.play_video(item)
from channels import autoplay
if (platformtools.is_playing() and item.action) or item.server == 'torrent' or autoplay.is_active(item.contentChannel):
break

86
platformcode/logger.py Normal file
View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------------
# Logger (kodi)
# --------------------------------------------------------------------------------
import inspect
import xbmc
from platformcode 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)
if module == None:
module = "None"
else:
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)
class WebErrorException(Exception):
def __init__(self, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)

738
platformcode/mct.py Normal file
View File

@@ -0,0 +1,738 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# MCT - Mini Cliente Torrent
# ------------------------------------------------------------
import os
import shutil
import tempfile
import urllib
import urllib2
try:
from python_libtorrent import get_libtorrent, get_platform
lt = get_libtorrent()
except Exception, e:
import libtorrent as lt
import xbmc
import xbmcgui
from platformcode import config
from core import httptools
from core import scrapertools
from core import filetools
def play(url, xlistitem={}, is_view=None, subtitle="", item=None):
allocate = True
try:
import platform
xbmc.log("XXX KODI XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
xbmc.log("OS platform: %s %s" % (platform.system(), platform.release()))
xbmc.log("xbmc/kodi version: %s" % xbmc.getInfoLabel("System.BuildVersion"))
xbmc_version = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
xbmc.log("xbmc/kodi version number: %s" % xbmc_version)
xbmc.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX KODI XXXX")
_platform = get_platform()
if str(_platform['system']) in ["android_armv7", "linux_armv6", "linux_armv7"]:
allocate = False
# -- log ------------------------------------------------
xbmc.log("XXX platform XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
xbmc.log("_platform['system']: %s" % _platform['system'])
xbmc.log("allocate: %s" % allocate)
xbmc.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX platform XXXX")
# -- ----------------------------------------------------
except:
pass
DOWNLOAD_PATH = config.get_setting("downloadpath")
# -- adfly: ------------------------------------
if url.startswith("http://adf.ly/"):
try:
data = httptools.downloadpage(url).data
url = decode_adfly(data)
except:
ddd = xbmcgui.Dialog()
ddd.ok("alfa-MCT: Sin soporte adf.ly",
"El script no tiene soporte para el acortador de urls adf.ly.", "", "url: " + url)
return
# -- Necesario para algunas webs ----------------------------
if not url.endswith(".torrent") and not url.startswith("magnet"):
t_file = scrapertools.get_header_from_response(url, header_to_get="location")
if len(t_file) > 0:
url = t_file
t_file = scrapertools.get_header_from_response(url, header_to_get="location")
if len(t_file) > 0:
url = t_file
# -- Crear dos carpetas en descargas para los archivos ------
save_path_videos = os.path.join(DOWNLOAD_PATH, "torrent-videos")
save_path_torrents = os.path.join(DOWNLOAD_PATH, "torrent-torrents")
if not os.path.exists(save_path_torrents): os.mkdir(save_path_torrents)
# -- Usar - archivo torrent desde web, magnet o HD ---------
if not os.path.isfile(url) and not url.startswith("magnet"):
# -- http - crear archivo torrent -----------------------
data = url_get(url)
# -- El nombre del torrent será el que contiene en los --
# -- datos. -
re_name = urllib.unquote(scrapertools.scrapertools.find_single_match(data, ':name\d+:(.*?)\d+:'))
torrent_file = filetools.join(save_path_torrents, filetools.encode(re_name + '.torrent'))
f = open(torrent_file, 'wb')
f.write(data)
f.close()
elif os.path.isfile(url):
# -- file - para usar torrens desde el HD ---------------
torrent_file = url
else:
# -- magnet ---------------------------------------------
torrent_file = url
# -----------------------------------------------------------
# -- MCT - MiniClienteTorrent -------------------------------
ses = lt.session()
# -- log ----------------------------------------------------
xbmc.log("### Init session ########")
xbmc.log(lt.version)
xbmc.log("#########################")
# -- --------------------------------------------------------
ses.add_dht_router("router.bittorrent.com", 6881)
ses.add_dht_router("router.utorrent.com", 6881)
ses.add_dht_router("dht.transmissionbt.com", 6881)
trackers = [
"udp://tracker.openbittorrent.com:80/announce",
"http://tracker.torrentbay.to:6969/announce",
"http://tracker.pow7.com/announce",
"udp://tracker.ccc.de:80/announce",
"udp://open.demonii.com:1337",
"http://9.rarbg.com:2710/announce",
"http://bt.careland.com.cn:6969/announce",
"http://explodie.org:6969/announce",
"http://mgtracker.org:2710/announce",
"http://tracker.best-torrents.net:6969/announce",
"http://tracker.tfile.me/announce",
"http://tracker1.wasabii.com.tw:6969/announce",
"udp://9.rarbg.com:2710/announce",
"udp://9.rarbg.me:2710/announce",
"udp://coppersurfer.tk:6969/announce",
"http://www.spanishtracker.com:2710/announce",
"http://www.todotorrents.com:2710/announce",
]
video_file = ""
# -- magnet2torrent -----------------------------------------
if torrent_file.startswith("magnet"):
try:
import zlib
btih = hex(zlib.crc32(
scrapertools.scrapertools.find_single_match(torrent_file, 'magnet:\?xt=urn:(?:[A-z0-9:]+|)([A-z0-9]{32})')) & 0xffffffff)
files = [f for f in os.listdir(save_path_torrents) if os.path.isfile(os.path.join(save_path_torrents, f))]
for file in files:
if btih in os.path.basename(file):
torrent_file = os.path.join(save_path_torrents, file)
except:
pass
if torrent_file.startswith("magnet"):
try:
tempdir = tempfile.mkdtemp()
except IOError:
tempdir = os.path.join(save_path_torrents, "temp")
if not os.path.exists(tempdir):
os.mkdir(tempdir)
params = {
'save_path': tempdir,
'trackers': trackers,
'storage_mode': lt.storage_mode_t.storage_mode_allocate,
'paused': False,
'auto_managed': True,
'duplicate_is_error': True
}
h = lt.add_magnet_uri(ses, torrent_file, params)
dp = xbmcgui.DialogProgress()
dp.create('alfa-MCT')
while not h.has_metadata():
message, porcent, msg_file, s, download = getProgress(h, "Creando torrent desde magnet")
dp.update(porcent, message, msg_file)
if s.state == 1: download = 1
if dp.iscanceled():
dp.close()
remove_files(download, torrent_file, video_file, ses, h)
return
h.force_dht_announce()
xbmc.sleep(1000)
dp.close()
info = h.get_torrent_info()
data = lt.bencode(lt.create_torrent(info).generate())
torrent_file = os.path.join(save_path_torrents,
unicode(info.name() + "-" + btih, "'utf-8'", errors="replace") + ".torrent")
f = open(torrent_file, 'wb')
f.write(data)
f.close()
ses.remove_torrent(h)
shutil.rmtree(tempdir)
# -----------------------------------------------------------
# -- Archivos torrent ---------------------------------------
e = lt.bdecode(open(torrent_file, 'rb').read())
info = lt.torrent_info(e)
# -- El más gordo o uno de los más gordo se entiende que es -
# -- el vídeo o es el vídeo que se usará como referencia -
# -- para el tipo de archivo -
xbmc.log("##### Archivos ## %s ##" % len(info.files()))
_index_file, _video_file, _size_file = get_video_file(info)
# -- Prioritarizar/Seleccionar archivo-----------------------
_index, video_file, video_size, len_files = get_video_files_sizes(info)
if len_files == 0:
dp = xbmcgui.Dialog().ok("No se puede reproducir", "El torrent no contiene ningún archivo de vídeo")
if _index == -1:
_index = _index_file
video_file = _video_file
video_size = _size_file
_video_file_ext = os.path.splitext(_video_file)[1]
xbmc.log("##### _video_file_ext ## %s ##" % _video_file_ext)
if (_video_file_ext == ".avi" or _video_file_ext == ".mp4") and allocate:
xbmc.log("##### storage_mode_t.storage_mode_allocate (" + _video_file_ext + ") #####")
h = ses.add_torrent({'ti': info, 'save_path': save_path_videos, 'trackers': trackers,
'storage_mode': lt.storage_mode_t.storage_mode_allocate})
else:
xbmc.log("##### storage_mode_t.storage_mode_sparse (" + _video_file_ext + ") #####")
h = ses.add_torrent({'ti': info, 'save_path': save_path_videos, 'trackers': trackers,
'storage_mode': lt.storage_mode_t.storage_mode_sparse})
allocate = True
# -----------------------------------------------------------
# -- Descarga secuencial - trozo 1, trozo 2, ... ------------
h.set_sequential_download(True)
h.force_reannounce()
h.force_dht_announce()
# -- Inicio de variables para 'pause' automático cuando el -
# -- el vídeo se acerca a una pieza sin completar -
is_greater_num_pieces = False
is_greater_num_pieces_plus = False
is_greater_num_pieces_pause = False
porcent4first_pieces = int(video_size * 0.000000005)
if porcent4first_pieces < 10: porcent4first_pieces = 10
if porcent4first_pieces > 100: porcent4first_pieces = 100
porcent4last_pieces = int(porcent4first_pieces / 2)
num_pieces_to_resume = int(video_size * 0.0000000025)
if num_pieces_to_resume < 5: num_pieces_to_resume = 5
if num_pieces_to_resume > 25: num_pieces_to_resume = 25
xbmc.log("##### porcent4first_pieces ## %s ##" % porcent4first_pieces)
xbmc.log("##### porcent4last_pieces ## %s ##" % porcent4last_pieces)
xbmc.log("##### num_pieces_to_resume ## %s ##" % num_pieces_to_resume)
# -- Prioritarizar o seleccionar las piezas del archivo que -
# -- se desea reproducir con 'file_priorities' -
piece_set = set_priority_pieces(h, _index, video_file, video_size,
porcent4first_pieces, porcent4last_pieces, allocate)
# -- Crear diálogo de progreso para el primer bucle ---------
dp = xbmcgui.DialogProgress()
dp.create('alfa-MCT')
_pieces_info = {}
# -- Doble bucle anidado ------------------------------------
# -- Descarga - Primer bucle -
while not h.is_seed():
s = h.status()
xbmc.sleep(100)
# -- Recuperar los datos del progreso -------------------
message, porcent, msg_file, s, download = getProgress(h, video_file, _pf=_pieces_info)
# -- Si hace 'checking' existe descarga -----------------
# -- 'download' Se usará para saber si hay datos -
# -- descargados para el diálogo de 'remove_files' -
if s.state == 1: download = 1
# -- Player - play --------------------------------------
# -- Comprobar si se han completado las piezas para el -
# -- inicio del vídeo -
first_pieces = True
_c = 0
for i in range(piece_set[0], piece_set[porcent4first_pieces]):
first_pieces &= h.have_piece(i)
if h.have_piece(i): _c += 1
_pieces_info = {'current': 0, 'continuous': "%s/%s" % (_c, porcent4first_pieces), 'continuous2': "",
'have': h.status().num_pieces, 'len': len(piece_set)}
last_pieces = True
if not allocate:
_c = len(piece_set) - 1;
_cc = 0
for i in range(len(piece_set) - porcent4last_pieces, len(piece_set)):
last_pieces &= h.have_piece(i)
if h.have_piece(i): _c -= 1; _cc += 1
_pieces_info['continuous2'] = "[%s/%s] " % (_cc, porcent4last_pieces)
if is_view != "Ok" and first_pieces and last_pieces:
_pieces_info['continuous2'] = ""
xbmc.log("##### porcent [%.2f%%]" % (s.progress * 100))
is_view = "Ok"
dp.close()
# -- Player - Ver el vídeo --------------------------
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
ren_video_file = os.path.join(save_path_videos, video_file)
try:
playlist.add(ren_video_file, xlistitem)
except:
playlist.add(ren_video_file)
if xbmc_version < 17:
player = play_video(xbmc.PLAYER_CORE_AUTO)
else:
player = play_video()
player.play(playlist)
# -- Contador de cancelaciones para la ventana de -
# -- 'pause' automático -
is_greater_num_pieces_canceled = 0
continuous_pieces = 0
porcent_time = 0.00
current_piece = 0
set_next_continuous_pieces = porcent4first_pieces
# -- Impedir que kodi haga 'resume' a un archivo ----
# -- que se reprodujo con anterioridad y que se -
# -- eliminó para impedir que intente la reprucción -
# -- en una pieza que aún no se ha completado y se -
# -- active 'pause' automático -
not_resume = True
# -- Bandera subTítulos
_sub = False
# -- Segundo bucle - Player - Control de eventos ----
while player.isPlaying():
xbmc.sleep(100)
# -- Añadir subTítulos
if subtitle != "" and not _sub:
_sub = True
player.setSubtitles(subtitle)
# -- Impedir que kodi haga 'resume' al inicio ---
# -- de la descarga de un archivo conocido -
if not_resume:
player.seekTime(0)
not_resume = False
# -- Control 'pause' automático -
continuous_pieces = count_completed_continuous_pieces(h, piece_set)
if xbmc.Player().isPlaying():
# -- Porcentage del progreso del vídeo ------
player_getTime = player.getTime()
player_getTotalTime = player.getTotalTime()
porcent_time = player_getTime / player_getTotalTime * 100
# -- Pieza que se está reproduciendo --------
current_piece = int(porcent_time / 100 * len(piece_set))
# -- Banderas de control --------------------
is_greater_num_pieces = (current_piece > continuous_pieces - num_pieces_to_resume)
is_greater_num_pieces_plus = (current_piece + porcent4first_pieces > continuous_pieces)
is_greater_num_pieces_finished = (current_piece + porcent4first_pieces >= len(piece_set))
# -- Activa 'pause' automático --------------
if is_greater_num_pieces and not player.paused and not is_greater_num_pieces_finished:
is_greater_num_pieces_pause = True
player.pause()
if continuous_pieces >= set_next_continuous_pieces:
set_next_continuous_pieces = continuous_pieces + num_pieces_to_resume
next_continuous_pieces = str(continuous_pieces - current_piece) + "/" + str(
set_next_continuous_pieces - current_piece)
_pieces_info = {'current': current_piece, 'continuous': next_continuous_pieces,
'continuous2': _pieces_info['continuous2'], 'have': h.status().num_pieces,
'len': len(piece_set)}
# si es un archivo de la videoteca enviar a marcar como visto
if item.strm_path:
from platformcode import xbmc_videolibrary
xbmc_videolibrary.mark_auto_as_watched(item)
# -- Cerrar el diálogo de progreso --------------
if player.resumed:
dp.close()
# -- Mostrar el diálogo de progreso -------------
if player.paused:
# -- Crear diálogo si no existe -------------
if not player.statusDialogoProgress:
dp = xbmcgui.DialogProgress()
dp.create('alfa-MCT')
player.setDialogoProgress()
# -- Diálogos de estado en el visionado -----
if not h.is_seed():
# -- Recuperar los datos del progreso ---
message, porcent, msg_file, s, download = getProgress(h, video_file, _pf=_pieces_info)
dp.update(porcent, message, msg_file)
else:
dp.update(100, "Descarga completa: " + video_file)
# -- Se canceló el progreso en el visionado -
# -- Continuar -
if dp.iscanceled():
dp.close()
player.pause()
# -- Se canceló el progreso en el visionado -
# -- en la ventana de 'pause' automático. -
# -- Parar si el contador llega a 3 -
if dp.iscanceled() and is_greater_num_pieces_pause:
is_greater_num_pieces_canceled += 1
if is_greater_num_pieces_canceled == 3:
player.stop()
# -- Desactiva 'pause' automático y ---------
# -- reinicia el contador de cancelaciones -
if not dp.iscanceled() and not is_greater_num_pieces_plus and is_greater_num_pieces_pause:
dp.close()
player.pause()
is_greater_num_pieces_pause = False
is_greater_num_pieces_canceled = 0
# -- El usuario cancelo el visionado --------
# -- Terminar -
if player.ended:
# -- Diálogo eliminar archivos ----------
remove_files(download, torrent_file, video_file, ses, h)
return
# -- Kodi - Se cerró el visionado -----------------------
# -- Continuar | Terminar -
if is_view == "Ok" and not xbmc.Player().isPlaying():
if info.num_files() == 1:
# -- Diálogo continuar o terminar ---------------
d = xbmcgui.Dialog()
ok = d.yesno('alfa-MCT', 'XBMC-Kodi Cerró el vídeo.', '¿Continuar con la sesión?')
else:
ok = False
# -- SI ---------------------------------------------
if ok:
# -- Continuar: ---------------------------------
is_view = None
else:
# -- Terminar: ----------------------------------
# -- Comprobar si el vídeo pertenece a una ------
# -- lista de archivos -
_index, video_file, video_size, len_files = get_video_files_sizes(info)
if _index == -1 or len_files == 1:
# -- Diálogo eliminar archivos --------------
remove_files(download, torrent_file, video_file, ses, h)
return
else:
# -- Lista de archivos. Diálogo de opciones -
piece_set = set_priority_pieces(h, _index, video_file, video_size,
porcent4first_pieces, porcent4last_pieces, allocate)
is_view = None
dp = xbmcgui.DialogProgress()
dp.create('alfa-MCT')
# -- Mostar progeso antes del visionado -----------------
if is_view != "Ok":
dp.update(porcent, message, msg_file)
# -- Se canceló el progreso antes del visionado ---------
# -- Terminar -
if dp.iscanceled():
dp.close()
# -- Comprobar si el vídeo pertenece a una lista de -
# -- archivos -
_index, video_file, video_size, len_files = get_video_files_sizes(info)
if _index == -1 or len_files == 1:
# -- Diálogo eliminar archivos ------------------
remove_files(download, torrent_file, video_file, ses, h)
return
else:
# -- Lista de archivos. Diálogo de opciones -----
piece_set = set_priority_pieces(h, _index, video_file, video_size,
porcent4first_pieces, porcent4last_pieces, allocate)
is_view = None
dp = xbmcgui.DialogProgress()
dp.create('alfa-MCT')
# -- Kodi - Error? - No debería llegar aquí -----------------
if is_view == "Ok" and not xbmc.Player().isPlaying():
dp.close()
# -- Diálogo eliminar archivos --------------------------
remove_files(download, torrent_file, video_file, ses, h)
return
# -- Progreso de la descarga ------------------------------------
def getProgress(h, video_file, _pf={}):
if len(_pf) > 0:
_pf_msg = "[%s] [%s] %s[%s] [%s]" % (
_pf['current'], _pf['continuous'], _pf['continuous2'], _pf['have'], _pf['len'])
else:
_pf_msg = ""
s = h.status()
state_str = ['queued', 'checking', 'downloading metadata', \
'downloading', 'finished', 'seeding', 'allocating', 'checking fastresume']
message = '%.2f%% d:%.1f kb/s u:%.1f kb/s p:%d s:%d %s' % \
(s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, \
s.num_peers, s.num_seeds, state_str[s.state])
porcent = int(s.progress * 100)
download = (s.progress * 100)
if "/" in video_file: video_file = video_file.split("/")[1]
msg_file = video_file
if len(msg_file) > 50:
msg_file = msg_file.replace(video_file,
os.path.splitext(video_file)[0][:40] + "... " + os.path.splitext(video_file)[1])
msg_file = msg_file + "[CR]" + "%.2f MB" % (s.total_wanted / 1048576.0) + " - " + _pf_msg
return (message, porcent, msg_file, s, download)
# -- Clase play_video - Controlar eventos -----------------------
class play_video(xbmc.Player):
def __init__(self, *args, **kwargs):
self.paused = False
self.resumed = True
self.statusDialogoProgress = False
self.ended = False
def onPlayBackPaused(self):
self.paused = True
self.resumed = False
def onPlayBackResumed(self):
self.paused = False
self.resumed = True
self.statusDialogoProgress = False
def is_paused(self):
return self.paused
def setDialogoProgress(self):
self.statusDialogoProgress = True
def is_started(self):
self.ended = False
def is_ended(self):
self.ended = True
# -- Conseguir el nombre un alchivo de vídeo del metadata -------
# -- El más gordo o uno de los más gordo se entiende que es el -
# -- vídeo o es vídeo que se usará como referencia para el tipo -
# -- de archivo -
def get_video_file(info):
size_file = 0
for i, f in enumerate(info.files()):
if f.size > size_file:
video_file = f.path.replace("\\", "/")
size_file = f.size
index_file = i
return index_file, video_file, size_file
# -- Listado de selección del vídeo a prioritarizar -------------
def get_video_files_sizes(info):
opciones = {}
vfile_name = {}
vfile_size = {}
# -- Eliminar errores con tíldes -----------------------------
for i, f in enumerate(info.files()):
_title = unicode(f.path, "iso-8859-1", errors="replace")
_title = unicode(f.path, "'utf-8'", errors="replace")
extensions_list = ['.aaf', '.3gp', '.asf', '.avi', '.flv', '.mpeg',
'.m1v', '.m2v', '.m4v', '.mkv', '.mov', '.mpg',
'.mpe', '.mp4', '.ogg', '.rar', '.wmv', '.zip']
for i, f in enumerate(info.files()):
_index = int(i)
_title = f.path.replace("\\", "/")
_size = f.size
_file_name = os.path.splitext(_title)[0]
if "/" in _file_name: _file_name = _file_name.split('/')[1]
_file_ext = os.path.splitext(_title)[1]
if _file_ext in extensions_list:
index = len(opciones)
_caption = str(index) + \
" - " + \
_file_name + _file_ext + \
" - %.2f MB" % (_size / 1048576.0)
vfile_name[index] = _title
vfile_size[index] = _size
opciones[i] = _caption
if len(opciones) > 1:
d = xbmcgui.Dialog()
seleccion = d.select("alfa-MCT: Lista de vídeos", opciones.values())
else:
seleccion = 0
index = opciones.keys()[seleccion]
if seleccion == -1:
vfile_name[seleccion] = ""
vfile_size[seleccion] = 0
index = seleccion
return index, vfile_name[seleccion], vfile_size[seleccion], len(opciones)
# -- Preguntar si se desea borrar lo descargado -----------------
def remove_files(download, torrent_file, video_file, ses, h):
dialog_view = False
torrent = False
if os.path.isfile(torrent_file):
dialog_view = True
torrent = True
if download > 0:
dialog_view = True
if "/" in video_file: video_file = video_file.split("/")[0]
if dialog_view:
d = xbmcgui.Dialog()
ok = d.yesno('alfa-MCT', 'Borrar las descargas del video', video_file)
# -- SI -------------------------------------------------
if ok:
# -- Borrar archivo - torrent -----------------------
if torrent:
os.remove(torrent_file)
# -- Borrar carpeta/archivos y sesión - vídeo -------
ses.remove_torrent(h, 1)
xbmc.log("### End session #########")
else:
# -- Borrar sesión ----------------------------------
ses.remove_torrent(h)
xbmc.log("### End session #########")
else:
# -- Borrar sesión --------------------------------------
ses.remove_torrent(h)
xbmc.log("### End session #########")
return
# -- Descargar de la web los datos para crear el torrent --------
# -- Si queremos aligerar el script mct.py se puede importar la -
# -- función del conentor torrent.py -
def url_get(url, params={}, headers={}):
from contextlib import closing
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:20.0) Gecko/20100101 Firefox/20.0"
if params:
import urllib
url = "%s?%s" % (url, urllib.urlencode(params))
req = urllib2.Request(url)
req.add_header("User-Agent", USER_AGENT)
for k, v in headers.items():
req.add_header(k, v)
try:
with closing(urllib2.urlopen(req)) as response:
data = response.read()
if response.headers.get("Content-Encoding", "") == "gzip":
import zlib
return zlib.decompressobj(16 + zlib.MAX_WBITS).decompress(data)
return data
except urllib2.HTTPError:
return None
# -- Contar las piezas contiguas completas del vídeo ------------
def count_completed_continuous_pieces(h, piece_set):
not_zero = 0
for i, _set in enumerate(piece_set):
if not h.have_piece(_set):
break
else:
not_zero = 1
return i + not_zero
# -- Prioritarizar o seleccionar las piezas del archivo que se -
# -- desea reproducir con 'file_priorities' estableciendo a 1 -
# -- el archivo deseado y a 0 el resto de archivos almacenando -
# -- en una lista los índices de de las piezas del archivo -
def set_priority_pieces(h, _index, video_file, video_size,
porcent4first_pieces, porcent4last_pieces, allocate):
for i, _set in enumerate(h.file_priorities()):
if i != _index:
h.file_priority(i, 0)
else:
h.file_priority(i, 1)
piece_set = []
for i, _set in enumerate(h.piece_priorities()):
if _set == 1: piece_set.append(i)
if not allocate:
for i in range(0, porcent4first_pieces):
h.set_piece_deadline(piece_set[i], 10000)
for i in range(len(piece_set) - porcent4last_pieces, len(piece_set)):
h.set_piece_deadline(piece_set[i], 10000)
return piece_set
def decode_adfly(data):
import base64
ysmm = scrapertools.find_single_match(data, "var ysmm = '([^']+)'")
left = ''
right = ''
for c in [ysmm[i:i + 2] for i in range(0, len(ysmm), 2)]:
left += c[0]
right = c[1] + right
decoded_url = base64.b64decode(left.encode() + right.encode())[2:].decode()
return decoded_url

File diff suppressed because it is too large Load Diff

73
platformcode/recaptcha.py Normal file
View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
import xbmcgui
from core import httptools
from core import scrapertools
from platformcode import config
from platformcode import platformtools
class Recaptcha(xbmcgui.WindowXMLDialog):
def Start(self, key, referer):
self.referer = referer
self.key = key
self.headers = {'Referer': self.referer}
api_js = httptools.downloadpage("http://www.google.com/recaptcha/api.js?hl=es").data
version = scrapertools.find_single_match(api_js, 'po.src = \'(.*?)\';').split("/")[5]
self.url = "http://www.google.com/recaptcha/api/fallback?k=%s&hl=es&v=%s&t=2&ff=true" % (self.key, version)
self.doModal()
# Reload
if self.result == {}:
self.result = Recaptcha("Recaptcha.xml", config.get_runtime_path()).Start(self.key, self.referer)
return self.result
def update_window(self):
data = httptools.downloadpage(self.url, headers=self.headers).data
self.message = scrapertools.find_single_match(data,
'<div class="rc-imageselect-desc-no-canonical">(.*?)(?:</label>|</div>)').replace(
"<strong>", "[B]").replace("</strong>", "[/B]")
self.token = scrapertools.find_single_match(data, 'name="c" value="([^"]+)"')
self.image = "http://www.google.com/recaptcha/api2/payload?k=%s&c=%s" % (self.key, self.token)
self.result = {}
self.getControl(10020).setImage(self.image)
self.getControl(10000).setText(self.message)
self.setFocusId(10005)
def __init__(self, *args, **kwargs):
self.mensaje = kwargs.get("mensaje")
self.imagen = kwargs.get("imagen")
def onInit(self):
#### Compatibilidad con Kodi 18 ####
if config.get_platform(True)['num_version'] < 18:
self.setCoordinateResolution(2)
self.update_window()
def onClick(self, control):
if control == 10003:
self.result = None
self.close()
elif control == 10004:
self.result = {}
self.close()
elif control == 10002:
self.result = [int(k) for k in range(9) if self.result.get(k, False) == True]
post = "c=%s" % self.token
for r in self.result:
post += "&response=%s" % r
data = httptools.downloadpage(self.url, post, headers=self.headers).data
self.result = scrapertools.find_single_match(data, '<div class="fbc-verification-token">.*?>([^<]+)<')
if self.result:
platformtools.dialog_notification("Captcha Correcto", "La verificación ha concluido")
self.close()
else:
self.result = {}
self.close()
else:
self.result[control - 10005] = not self.result.get(control - 10005, False)

View File

@@ -0,0 +1,268 @@
# -*- coding: utf-8 -*-
import os
import re
import string
import urllib
from unicodedata import normalize
import xbmc
import xbmcgui
from platformcode import config, logger
allchars = string.maketrans('', '')
deletechars = ',\\/:*"<>|?'
# Extraemos el nombre de la serie, temporada y numero de capitulo ejemplo: 'fringe 1x01'
def regex_tvshow(compare, file, sub=""):
regex_expressions = ['[Ss]([0-9]+)[][._-]*[Ee]([0-9]+)([^\\\\/]*)$',
'[\._ \-]([0-9]+)x([0-9]+)([^\\/]*)', # foo.1x09
'[\._ \-]([0-9]+)([0-9][0-9])([\._ \-][^\\/]*)', # foo.109
'([0-9]+)([0-9][0-9])([\._ \-][^\\/]*)',
'[\\\\/\\._ -]([0-9]+)([0-9][0-9])[^\\/]*',
'Season ([0-9]+) - Episode ([0-9]+)[^\\/]*',
'Season ([0-9]+) Episode ([0-9]+)[^\\/]*',
'[\\\\/\\._ -][0]*([0-9]+)x[0]*([0-9]+)[^\\/]*',
'[[Ss]([0-9]+)\]_\[[Ee]([0-9]+)([^\\/]*)', # foo_[s01]_[e01]
'[\._ \-][Ss]([0-9]+)[\.\-]?[Ee]([0-9]+)([^\\/]*)', # foo, s01e01, foo.s01.e01, foo.s01-e01
's([0-9]+)ep([0-9]+)[^\\/]*', # foo - s01ep03, foo - s1ep03
'[Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+)([^\\\\/]*)$',
'[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+)([^\\\\/]*)$',
'[\\\\/\\._ \\[\\(-]([0-9]+)X([0-9]+)([^\\\\/]*)$'
]
sub_info = ""
tvshow = 0
for regex in regex_expressions:
response_file = re.findall(regex, file)
if len(response_file) > 0:
print "Regex File Se: %s, Ep: %s," % (str(response_file[0][0]), str(response_file[0][1]),)
tvshow = 1
if not compare:
title = re.split(regex, file)[0]
for char in ['[', ']', '_', '(', ')', '.', '-']:
title = title.replace(char, ' ')
if title.endswith(" "): title = title.strip()
print "title: %s" % title
return title, response_file[0][0], response_file[0][1]
else:
break
if (tvshow == 1):
for regex in regex_expressions:
response_sub = re.findall(regex, sub)
if len(response_sub) > 0:
try:
sub_info = "Regex Subtitle Ep: %s," % (str(response_sub[0][1]),)
if (int(response_sub[0][1]) == int(response_file[0][1])):
return True
except:
pass
return False
if compare:
return True
else:
return "", "", ""
# Obtiene el nombre de la pelicula o capitulo de la serie guardado previamente en configuraciones del plugin
# y luego lo busca en el directorio de subtitulos, si los encuentra los activa.
def set_Subtitle():
logger.info()
exts = [".srt", ".sub", ".txt", ".smi", ".ssa", ".ass"]
subtitle_folder_path = os.path.join(config.get_data_path(), "subtitles")
subtitle_type = config.get_setting("subtitle_type")
if subtitle_type == "2":
subtitle_path = config.get_setting("subtitlepath_file")
logger.info("Con subtitulo : " + subtitle_path)
xbmc.Player().setSubtitles(subtitle_path)
else:
if subtitle_type == "0":
subtitle_path = config.get_setting("subtitlepath_folder")
if subtitle_path == "":
subtitle_path = subtitle_folder_path
config.set_setting("subtitlepath_folder", subtitle_path)
else:
subtitle_path = config.get_setting("subtitlepath_keyboard")
long = len(subtitle_path)
if long > 0:
if subtitle_path.startswith("http") or subtitle_path[long - 4, long] in exts:
logger.info("Con subtitulo : " + subtitle_path)
xbmc.Player().setSubtitles(subtitle_path)
return
else:
subtitle_path = subtitle_folder_path
config.set_setting("subtitlepath_keyboard", subtitle_path)
import glob
subtitle_name = config.get_setting("subtitle_name").replace("amp;", "")
tvshow_title, season, episode = regex_tvshow(False, subtitle_name)
try:
if episode != "":
Subnames = glob.glob(os.path.join(subtitle_path, "Tvshows", tvshow_title,
"%s %sx%s" % (tvshow_title, season, episode) + "*.??.???"))
else:
Subnames = glob.glob(os.path.join(subtitle_path, "Movies", subtitle_name + "*.??.???"))
for Subname in Subnames:
if os.path.splitext(Subname)[1] in exts:
logger.info("Con subtitulo : " + os.path.split(Subname)[1])
xbmc.Player().setSubtitles((Subname))
except:
logger.error("error al cargar subtitulos")
# Limpia los caracteres unicode
def _normalize(title, charset='utf-8'):
'''Removes all accents and illegal chars for titles from the String'''
if isinstance(title, unicode):
title = string.translate(title, allchars, deletechars)
try:
title = title.encode("utf-8")
title = normalize('NFKD', title).encode('ASCII', 'ignore')
except UnicodeEncodeError:
logger.error("Error de encoding")
else:
title = string.translate(title, allchars, deletechars)
try:
# iso-8859-1
title = title.decode(charset).encode('utf-8')
title = normalize('NFKD', unicode(title, 'utf-8'))
title = title.encode('ASCII', 'ignore')
except UnicodeEncodeError:
logger.error("Error de encoding")
return title
#
def searchSubtitle(item):
if config.get_setting("subtitle_type") == 0:
subtitlepath = config.get_setting("subtitlepath_folder")
if subtitlepath == "":
subtitlepath = os.path.join(config.get_data_path(), "subtitles")
config.set_setting("subtitlepath_folder", subtitlepath)
elif config.get_setting("subtitle_type") == 1:
subtitlepath = config.get_setting("subtitlepath_keyboard")
if subtitlepath == "":
subtitlepath = os.path.join(config.get_data_path(), "subtitles")
config.set_setting("subtitlepathkeyboard", subtitlepath)
elif subtitlepath.startswith("http"):
subtitlepath = config.get_setting("subtitlepath_folder")
else:
subtitlepath = config.get_setting("subtitlepath_folder")
if subtitlepath == "":
subtitlepath = os.path.join(config.get_data_path(), "subtitles")
config.set_setting("subtitlepath_folder", subtitlepath)
if not os.path.exists(subtitlepath):
try:
os.mkdir(subtitlepath)
except:
logger.error("error no se pudo crear path subtitulos")
return
path_movie_subt = xbmc.translatePath(os.path.join(subtitlepath, "Movies"))
if not os.path.exists(path_movie_subt):
try:
os.mkdir(path_movie_subt)
except:
logger.error("error no se pudo crear el path Movies")
return
full_path_tvshow = ""
path_tvshow_subt = xbmc.translatePath(os.path.join(subtitlepath, "Tvshows"))
if not os.path.exists(path_tvshow_subt):
try:
os.mkdir(path_tvshow_subt)
except:
logger.error("error no pudo crear el path Tvshows")
return
if item.show in item.title:
title_new = title = urllib.unquote_plus(item.title)
else:
title_new = title = urllib.unquote_plus(item.show + " - " + item.title)
path_video_temp = xbmc.translatePath(os.path.join(config.get_runtime_path(), "resources", "subtitle.mp4"))
if not os.path.exists(path_video_temp):
logger.error("error : no existe el video temporal de subtitulos")
return
# path_video_temp = xbmc.translatePath(os.path.join( ,video_temp + ".mp4" ))
title_new = _normalize(title_new)
tvshow_title, season, episode = regex_tvshow(False, title_new)
if episode != "":
full_path_tvshow = xbmc.translatePath(os.path.join(path_tvshow_subt, tvshow_title))
if not os.path.exists(full_path_tvshow):
os.mkdir(full_path_tvshow) # title_new + ".mp4"
full_path_video_new = xbmc.translatePath(
os.path.join(full_path_tvshow, "%s %sx%s.mp4" % (tvshow_title, season, episode)))
logger.info(full_path_video_new)
listitem = xbmcgui.ListItem(title_new, iconImage="DefaultVideo.png", thumbnailImage="")
listitem.setInfo("video",
{"Title": title_new, "Genre": "Tv shows", "episode": int(episode), "season": int(season),
"tvshowtitle": tvshow_title})
else:
full_path_video_new = xbmc.translatePath(os.path.join(path_movie_subt, title_new + ".mp4"))
listitem = xbmcgui.ListItem(title, iconImage="DefaultVideo.png", thumbnailImage="")
listitem.setInfo("video", {"Title": title_new, "Genre": "Movies"})
import shutil, time
try:
shutil.copy(path_video_temp, full_path_video_new)
copy = True
logger.info("nuevo path =" + full_path_video_new)
time.sleep(2)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist.add(full_path_video_new, listitem)
# xbmcPlayer = xbmc.Player( xbmc.PLAYER_CORE_AUTO )
xbmcPlayer = xbmc.Player()
xbmcPlayer.play(playlist)
# xbmctools.launchplayer(full_path_video_new,listitem)
except:
copy = False
logger.error("Error : no se pudo copiar")
time.sleep(1)
if copy:
if xbmc.Player().isPlayingVideo():
xbmc.executebuiltin("RunScript(script.xbmc.subtitles)")
while xbmc.Player().isPlayingVideo():
continue
time.sleep(1)
os.remove(full_path_video_new)
try:
if full_path_tvshow != "":
os.rmdir(full_path_tvshow)
except OSError:
pass
def saveSubtitleName(item):
if item.show in item.title:
title = item.title
else:
title = item.show + " - " + item.title
try:
title = _normalize(title)
except:
pass
tvshow_title, season, episode = regex_tvshow(False, title)
if episode != "":
# title = "% %sx%s" %(tvshow_title,season,episode)
config.set_setting("subtitle_name", title)
else:
config.set_setting("subtitle_name", title)
return

607
platformcode/unify.py Normal file
View File

@@ -0,0 +1,607 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# Unify
# ------------------------------------------------------------
# Herramientas responsables de unificar diferentes tipos de
# datos obtenidos de las paginas
# ----------------------------------------------------------
import os
import sys
import urllib
import unicodedata
import re
import config
from core.item import Item
from core import scrapertools
from platformcode import logger
thumb_dict = {"movies": "https://s10.postimg.cc/fxtqzdog9/peliculas.png",
"tvshows": "https://s10.postimg.cc/kxvslawe1/series.png",
"all": "https://s10.postimg.cc/h1igpgw0p/todas.png",
"genres": "https://s10.postimg.cc/6c4rx3x1l/generos.png",
"search": "https://s10.postimg.cc/v985e2izd/buscar.png",
"quality": "https://s10.postimg.cc/9bbojsbjd/calidad.png",
"audio": "https://s10.postimg.cc/b34nern7d/audio.png",
"newest": "https://s10.postimg.cc/g1s5tf1bt/novedades.png",
"last": "https://s10.postimg.cc/i6ciuk0eh/ultimas.png",
"hot": "https://s10.postimg.cc/yu40x8q2x/destacadas.png",
"year": "https://s10.postimg.cc/atzrqg921/a_o.png",
"alphabet": "https://s10.postimg.cc/4dy3ytmgp/a-z.png",
"recomended": "https://s10.postimg.cc/7xk1oqccp/recomendadas.png",
"more watched": "https://s10.postimg.cc/c6orr5neh/masvistas.png",
"more voted": "https://s10.postimg.cc/lwns2d015/masvotadas.png",
"favorites": "https://s10.postimg.cc/rtg147gih/favoritas.png",
"colections": "https://s10.postimg.cc/ywnwjvytl/colecciones.png",
"categories": "https://s10.postimg.cc/v0ako5lmh/categorias.png",
"premieres": "https://s10.postimg.cc/sk8r9xdq1/estrenos.png",
"documentaries": "https://s10.postimg.cc/68aygmmcp/documentales.png",
"language": "https://s10.postimg.cc/6wci189ft/idioma.png",
"new episodes": "https://s10.postimg.cc/fu4iwpnqh/nuevoscapitulos.png",
"country": "https://s10.postimg.cc/yz0h81j15/pais.png",
"adults": "https://s10.postimg.cc/s8raxc51l/adultos.png",
"recents": "https://s10.postimg.cc/649u24kp5/recents.png",
"updated" : "https://s10.postimg.cc/46m3h6h9l/updated.png",
"actors": "https://i.postimg.cc/tC2HMhVV/actors.png",
"cast": "https://i.postimg.cc/qvfP5Xvt/cast.png",
"lat": "https://i.postimg.cc/Gt8fMH0J/lat.png",
"vose": "https://i.postimg.cc/kgmnbd8h/vose.png",
"accion": "https://s14.postimg.cc/sqy3q2aht/action.png",
"adolescente" : "https://s10.postimg.cc/inq7u4p61/teens.png",
"adultos": "https://s10.postimg.cc/s8raxc51l/adultos.png",
"animacion": "https://s14.postimg.cc/vl193mupd/animation.png",
"anime" : "https://s10.postimg.cc/n9mc2ikzt/anime.png",
"artes marciales" : "https://s10.postimg.cc/4u1v51tzt/martial_arts.png",
"asiaticas" : "https://i.postimg.cc/Xq0HXD5d/asiaticas.png",
"aventura": "https://s14.postimg.cc/ky7fy5he9/adventure.png",
"belico": "https://s14.postimg.cc/5e027lru9/war.png",
"biografia" : "https://s10.postimg.cc/jq0ecjxnt/biographic.png",
"carreras": "https://s14.postimg.cc/yt5qgdr69/races.png",
"ciencia ficcion": "https://s14.postimg.cc/8kulr2jy9/scifi.png",
"cine negro" : "https://s10.postimg.cc/6ym862qgp/noir.png",
"comedia": "https://s14.postimg.cc/9ym8moog1/comedy.png",
"cortometraje" : "https://s10.postimg.cc/qggvlxndl/shortfilm.png",
"crimen": "https://s14.postimg.cc/duzkipjq9/crime.png",
"de la tv": "https://s10.postimg.cc/94gj0iwh5/image.png",
"deporte": "https://s14.postimg.cc/x1crlnnap/sports.png",
"destacadas": "https://s10.postimg.cc/yu40x8q2x/destacadas.png",
"documental": "https://s10.postimg.cc/68aygmmcp/documentales.png",
"doramas":"https://s10.postimg.cc/h4dyr4nfd/doramas.png",
"drama": "https://s14.postimg.cc/fzjxjtnxt/drama.png",
"erotica" : "https://s10.postimg.cc/dcbb9bfx5/erotic.png",
"espanolas" : "https://s10.postimg.cc/x1y6zikx5/spanish.png",
"estrenos" : "https://s10.postimg.cc/sk8r9xdq1/estrenos.png",
"extranjera": "https://s10.postimg.cc/f44a4eerd/foreign.png",
"familiar": "https://s14.postimg.cc/jj5v9ndsx/family.png",
"fantasia": "https://s14.postimg.cc/p7c60ksg1/fantasy.png",
"fantastico" : "https://s10.postimg.cc/tedufx5eh/fantastic.png",
"historica": "https://s10.postimg.cc/p1faxj6yh/historic.png",
"horror" : "https://s10.postimg.cc/8exqo6yih/horror2.png",
"infantil": "https://s14.postimg.cc/4zyq842mp/childish.png",
"intriga": "https://s14.postimg.cc/5qrgdimw1/intrigue.png",
"latino" : "https://s10.postimg.cc/swip0b86h/latin.png",
"mexicanas" : "https://s10.postimg.cc/swip0b86h/latin.png",
"misterio": "https://s14.postimg.cc/3m73cg8ep/mistery.png",
"musical": "https://s10.postimg.cc/hy7fhtecp/musical.png",
"peleas" : "https://s10.postimg.cc/7a3ojbjwp/Fight.png",
"policial" : "https://s10.postimg.cc/wsw0wbgbd/cops.png",
"recomendadas": "https://s10.postimg.cc/7xk1oqccp/recomendadas.png",
"religion" : "https://s10.postimg.cc/44j2skquh/religion.png",
"romance" : "https://s10.postimg.cc/yn8vdll6x/romance.png",
"romantica": "https://s14.postimg.cc/8xlzx7cht/romantic.png",
"suspenso": "https://s10.postimg.cc/7peybxdfd/suspense.png",
"telenovelas": "https://i.postimg.cc/QCXZkyDM/telenovelas.png",
"terror": "https://s14.postimg.cc/thqtvl52p/horror.png",
"thriller": "https://s14.postimg.cc/uwsekl8td/thriller.png",
"western": "https://s10.postimg.cc/5wc1nokjt/western.png"
}
def set_genre(string):
#logger.info()
genres_dict = {'accion':['accion', 'action', 'accion y aventura', 'action & adventure'],
'adultos':['adultos', 'adultos +', 'adulto'],
'animacion':['animacion', 'animacion e infantil', 'dibujos animados'],
'adolescente':['adolescente', 'adolescentes', 'adolescencia', 'adolecentes'],
'aventura':['aventura', 'aventuras'],
'belico':['belico', 'belica', 'belicas', 'guerra', 'belico guerra'],
'biografia':['biografia', 'biografias', 'biografica', 'biograficas', 'biografico'],
'ciencia ficcion':['ciencia ficcion', 'cienciaficcion', 'sci fi', 'c ficcion'],
'cine negro':['film noir', 'negro'],
'comedia':['comedia', 'comedias'],
'cortometraje':['cortometraje', 'corto', 'cortos'],
'de la tv':['de la tv', 'television', 'tv'],
'deporte':['deporte', 'deportes'],
'destacadas':['destacada', 'destacadas'],
'documental':['documental', 'documentales'],
'erotica':['erotica', 'erotica +', 'eroticas', 'eroticas +', 'erotico', 'erotico +'],
'estrenos':['estrenos', 'estrenos'],
'extranjera':['extrajera', 'extrajeras', 'foreign'],
'familiar':['familiar', 'familia'],
'fantastico':['fantastico', 'fantastica', 'fantasticas'],
'historica':['historica', 'historicas', 'historico', 'historia'],
'infantil':['infantil', 'kids'],
'musical':['musical', 'musicales', 'musica'],
'policial':['policial', 'policiaco', 'policiaca'],
'recomendadas':['recomedada', 'recomendadas'],
'religion':['religion', 'religiosa', 'religiosas'],
'romantica':['romantica', 'romanticas', 'romantico'],
'suspenso':['suspenso', 'suspense'],
'thriller':['thriller', 'thrillers'],
'western':['western', 'westerns', 'oeste western']
}
string = re.sub(r'peliculas de |pelicula de la |peli |cine ','', string)
for genre, variants in genres_dict.items():
if string in variants:
string = genre
return string
def remove_format(string):
#logger.info()
#logger.debug('entra en remove: %s' % string)
string = string.rstrip()
string = re.sub(r'(\[|\[\/)(?:color|COLOR|b|B|i|I).*?\]|\[|\]|\(|\)|\:|\.', '', string)
#logger.debug('sale de remove: %s' % string)
return string
def normalize(string):
string = string.decode('utf-8')
normal = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn'))
return normal
def simplify(string):
#logger.info()
#logger.debug('entra en simplify: %s'%string)
string = remove_format(string)
string = string.replace('-',' ').replace('_',' ')
string = re.sub(r'\d+','', string)
string = string.strip()
notilde = normalize(string)
try:
string = notilde.decode()
except:
pass
string = string.lower()
#logger.debug('sale de simplify: %s' % string)
return string
def add_languages(title, languages):
#logger.info()
if isinstance(languages, list):
for language in languages:
title = '%s %s' % (title, set_color(language, language))
else:
title = '%s %s' % (title, set_color(languages, languages))
return title
def set_color(title, category):
#logger.info()
color_scheme = {'otro': 'white', 'dual': 'white'}
#logger.debug('category antes de remove: %s' % category)
category = remove_format(category).lower()
#logger.debug('category despues de remove: %s' % category)
# Lista de elementos posibles en el titulo
color_list = ['movie', 'tvshow', 'year', 'rating_1', 'rating_2', 'rating_3', 'quality', 'cast', 'lat', 'vose',
'vos', 'vo', 'server', 'library', 'update', 'no_update']
# Se verifica el estado de la opcion de colores personalizados
custom_colors = config.get_setting('title_color')
# Se Forma el diccionario de colores para cada elemento, la opcion esta activas utiliza la configuracion del
# usuario, si no pone el titulo en blanco.
if title not in ['', ' ']:
for element in color_list:
if custom_colors:
color_scheme[element] = remove_format(config.get_setting('%s_color' % element))
else:
color_scheme[element] = 'white'
if category in ['update', 'no_update']:
#logger.debug('title antes de updates: %s' % title)
title= re.sub(r'\[COLOR .*?\]','[COLOR %s]' % color_scheme[category],title)
else:
if category not in ['movie', 'tvshow', 'library', 'otro']:
title = "[COLOR %s][%s][/COLOR]"%(color_scheme[category], title)
else:
title = "[COLOR %s]%s[/COLOR]" % (color_scheme[category], title)
return title
def set_lang(language):
#logger.info()
cast =['castellano','espanol','cast','esp','espaol', 'es','zc', 'spa', 'spanish', 'vc']
ita =['italiano','italian','ita','it']
lat=['latino','lat','la', 'espanol latino', 'espaol latino', 'zl', 'mx', 'co', 'vl']
vose=['subtitulado','subtitulada','sub','sub espanol','vose','espsub','su','subs castellano',
'sub: español', 'vs', 'zs', 'vs', 'english-spanish subs', 'ingles sub espanol']
vosi=['sottotitolato','sottotitolata','sub','sub ita','vosi','sub-ita','subs italiano',
'sub: italiano', 'inglese sottotitolato']
vos=['vos', 'sub ingles', 'engsub', 'vosi','ingles subtitulado', 'sub: ingles']
vo=['ingles', 'en','vo', 'ovos', 'eng','v.o', 'english']
dual=['dual']
language = scrapertools.decodeHtmlentities(language)
old_lang = language
language = simplify(language)
#logger.debug('language before simplify: %s' % language)
#logger.debug('old language: %s' % old_lang)
if language in cast:
language = 'cast'
elif language in lat:
language = 'lat'
elif language in ita:
language = 'ita'
elif language in vose:
language = 'vose'
elif language in vos:
language = 'vos'
elif language in vo:
language = 'vo'
elif language in dual:
language = 'dual'
elif language in dual:
language = 'vosi'
else:
language = 'otro'
#logger.debug('language after simplify: %s' % language)
return language
def title_format(item):
#logger.info()
lang = False
valid = True
language_color = 'otro'
#logger.debug('item.title antes de formatear: %s' % item.title.lower())
# TODO se deberia quitar cualquier elemento que no sea un enlace de la lista de findvideos para quitar esto
#Palabras "prohibidas" en los titulos (cualquier titulo que contengas estas no se procesara en unify)
excluded_words = ['online', 'descarga', 'downloads', 'trailer', 'videoteca', 'gb', 'autoplay']
# Actions excluidos, (se define canal y action) los titulos que contengan ambos valores no se procesaran en unify
excluded_actions = [('videolibrary','get_episodes')]
# Verifica si hay marca de visto de trakt
visto = False
#logger.debug('titlo con visto? %s' % item.title)
if '[[I]v[/I]]' in item.title or '[COLOR limegreen][v][/COLOR]' in item.title:
visto = True
# Se elimina cualquier formato previo en el titulo
if item.action != '' and item.action !='mainlist':
item.title = remove_format(item.title)
#logger.debug('visto? %s' % visto)
# Evita que aparezcan los idiomas en los mainlist de cada canal
if item.action == 'mainlist':
item.language =''
info = item.infoLabels
#logger.debug('item antes de formatear: %s'%item)
if hasattr(item,'text_color'):
item.text_color=''
#Verifica el item sea valido para ser formateado por unify
if item.channel == 'trailertools' or (item.channel.lower(), item.action.lower()) in excluded_actions or \
item.action=='':
valid = False
else:
for word in excluded_words:
if word in item.title.lower():
valid = False
break
if valid and item.unify!=False:
# Formamos el titulo para serie, se debe definir contentSerieName
# o show en el item para que esto funcione.
if item.contentSerieName:
# Si se tiene la informacion en infolabels se utiliza
if item.contentType == 'episode' and info['episode'] != '':
if info['title'] == '':
info['title'] = '%s - Episodio %s'% (info['tvshowtitle'], info['episode'])
elif 'Episode' in info['title']:
episode = info['title'].lower().replace('episode', 'episodio')
info['title'] = '%s - %s' % (info['tvshowtitle'], episode.capitalize())
elif info['episodio_titulo']!='':
#logger.debug('info[episode_titulo]: %s' % info['episodio_titulo'])
if 'episode' in info['episodio_titulo'].lower():
episode = info['episodio_titulo'].lower().replace('episode', 'episodio')
item.title = '%sx%s - %s' % (info['season'],info['episode'], episode.capitalize())
else:
item.title = '%sx%s - %s' % (info['season'], info['episode'], info['episodio_titulo'].capitalize())
else:
item.title = '%sx%s - %s' % (info['season'],info['episode'], info['title'])
item.title = set_color(item.title, 'tvshow')
else:
# En caso contrario se utiliza el titulo proporcionado por el canal
#logger.debug ('color_scheme[tvshow]: %s' % color_scheme['tvshow'])
item.title = '%s' % set_color(item.title, 'tvshow')
elif item.contentTitle:
# Si el titulo no tiene contentSerieName entonces se formatea como pelicula
saga = False
if 'saga' in item.title.lower():
item.title = '%s [Saga]' % set_color(item.contentTitle, 'movie')
elif 'miniserie' in item.title.lower():
item.title = '%s [Miniserie]' % set_color(item.contentTitle, 'movie')
elif 'extend' in item.title.lower():
item.title = '%s [V.Extend.]' % set_color(item.contentTitle, 'movie')
else:
item.title = '%s' % set_color(item.contentTitle, 'movie')
if item.contentType=='movie':
if item.context:
if isinstance(item.context, list):
item.context.append('Buscar esta pelicula en otros canales')
if 'Novedades' in item.category and item.from_channel=='news':
#logger.debug('novedades')
item.title = '%s [%s]'%(item.title, item.channel)
# Verificamos si item.language es una lista, si lo es se toma
# cada valor y se normaliza formado una nueva lista
if hasattr(item,'language') and item.language !='':
#logger.debug('tiene language: %s'%item.language)
if isinstance(item.language, list):
language_list =[]
for language in item.language:
if language != '':
lang = True
language_list.append(set_lang(remove_format(language)).upper())
#logger.debug('language_list: %s' % language_list)
simple_language = language_list
else:
# Si item.language es un string se normaliza
if item.language != '':
lang = True
simple_language = set_lang(item.language).upper()
else:
simple_language = ''
#item.language = simple_language
# Damos formato al año si existiera y lo agregamos
# al titulo excepto que sea un episodio
if info and info.get("year", "") not in [""," "] and item.contentType != 'episode' and not info['season']:
try:
year = '%s' % set_color(info['year'], 'year')
item.title = item.title = '%s %s' % (item.title, year)
except:
logger.debug('infoLabels: %s'%info)
# Damos formato al puntaje si existiera y lo agregamos al titulo
if info and info['rating'] and info['rating']!='0.0' and not info['season']:
# Se normaliza el puntaje del rating
rating_value = check_rating(info['rating'])
# Asignamos el color dependiendo el puntaje, malo, bueno, muy bueno, en caso de que exista
if rating_value:
value = float(rating_value)
if value <= 3:
color_rating = 'rating_1'
elif value > 3 and value <= 7:
color_rating = 'rating_2'
else:
color_rating = 'rating_3'
rating = '%s' % rating_value
else:
rating = ''
color_rating = 'otro'
item.title = '%s %s' % (item.title, set_color(rating, color_rating))
# Damos formato a la calidad si existiera y lo agregamos al titulo
if item.quality and isinstance(item.quality, str):
quality = item.quality.strip()
item.title = '%s %s' % (item.title, set_color(quality, 'quality'))
else:
quality = ''
# Damos formato al idioma si existiera y lo agregamos al titulo
if lang:
item.title = add_languages(item.title, simple_language)
# Para las busquedas por canal
if item.from_channel != '':
from core import channeltools
channel_parameters = channeltools.get_channel_parameters(item.from_channel)
logger.debug(channel_parameters)
item.title = '%s [%s]' % (item.title, channel_parameters['title'])
# Formato para actualizaciones de series en la videoteca sobreescribe los colores anteriores
if item.channel=='videolibrary' and item.context!='':
if item.action=='get_seasons':
if 'Desactivar' in item.context[1]['title']:
item.title= '%s' % (set_color(item.title, 'update'))
if 'Activar' in item.context[1]['title']:
item.title= '%s' % (set_color(item.title, 'no_update'))
#logger.debug('Despues del formato: %s' % item)
# Damos formato al servidor si existiera
if item.server:
server = '%s' % set_color(item.server.strip().capitalize(), 'server')
# Compureba si estamos en findvideos, y si hay server, si es asi no se muestra el
# titulo sino el server, en caso contrario se muestra el titulo normalmente.
#logger.debug('item.title antes de server: %s'%item.title)
if item.action != 'play' and item.server:
item.title ='%s %s'%(item.title, server.strip())
elif item.action == 'play' and item.server:
if item.quality == 'default':
quality = ''
#logger.debug('language_color: %s'%language_color)
item.title = '%s %s' % (server, set_color(quality,'quality'))
if lang:
item.title = add_languages(item.title, simple_language)
#logger.debug('item.title: %s' % item.title)
# si hay verificacion de enlaces
if item.alive != '':
if item.alive.lower() == 'no':
item.title = '[[COLOR red][B]X[/B][/COLOR]] %s' % item.title
elif item.alive == '??':
item.title = '[[COLOR yellow][B]?[/B][/COLOR]] %s' % item.title
else:
item.title = '%s' % item.title
#logger.debug('item.title despues de server: %s' % item.title)
elif 'library' in item.action:
item.title = '%s' % set_color(item.title, 'library')
elif item.action == '' and item.title !='':
item.title='**- %s -**'%item.title
else:
item.title = '%s' % set_color(item.title, 'otro')
#logger.debug('antes de salir %s' % item.title)
if visto:
try:
check = u'\u221a'
title = '[B][COLOR limegreen][%s][/COLOR][/B] %s' % (check, item.title.decode('utf-8'))
item.title = title.encode('utf-8')
except:
check = 'v'
title = '[B][COLOR limegreen][%s][/COLOR][/B] %s' % (check, item.title.decode('utf-8'))
item.title = title.encode('utf-8')
return item
def thumbnail_type(item):
#logger.info()
# Se comprueba que tipo de thumbnail se utilizara en findvideos,
# Poster o Logo del servidor
thumb_type = config.get_setting('video_thumbnail_type')
info = item.infoLabels
if not item.contentThumbnail:
item.contentThumbnail = item.thumbnail
if info:
if info['thumbnail'] !='':
item.contentThumbnail = info['thumbnail']
if item.action == 'play':
if thumb_type == 0:
if info['thumbnail'] != '':
item.thumbnail = info['thumbnail']
elif thumb_type == 1:
from core.servertools import get_server_parameters
#logger.debug('item.server: %s'%item.server)
server_parameters = get_server_parameters(item.server.lower())
item.thumbnail = server_parameters.get("thumbnail", item.contentThumbnail)
return item.thumbnail
from decimal import *
def check_rating(rating):
# logger.debug("\n\nrating %s" % rating)
def check_decimal_length(_rating):
"""
Dejamos que el float solo tenga un elemento en su parte decimal, "7.10" --> "7.1"
@param _rating: valor del rating
@type _rating: float
@return: devuelve el valor modificado si es correcto, si no devuelve None
@rtype: float|None
"""
# logger.debug("rating %s" % _rating)
try:
# convertimos los deciamles p.e. 7.1
return "%.1f" % round(_rating, 1)
except Exception, ex_dl:
template = "An exception of type %s occured. Arguments:\n%r"
message = template % (type(ex_dl).__name__, ex_dl.args)
logger.error(message)
return None
def check_range(_rating):
"""
Comprobamos que el rango de rating sea entre 0.0 y 10.0
@param _rating: valor del rating
@type _rating: float
@return: devuelve el valor si está dentro del rango, si no devuelve None
@rtype: float|None
"""
# logger.debug("rating %s" % _rating)
# fix para comparacion float
dec = Decimal(_rating)
if 0.0 <= dec <= 10.0:
# logger.debug("estoy en el rango!")
return _rating
else:
# logger.debug("NOOO estoy en el rango!")
return None
def convert_float(_rating):
try:
return float(_rating)
except ValueError, ex_ve:
template = "An exception of type %s occured. Arguments:\n%r"
message = template % (type(ex_ve).__name__, ex_ve.args)
logger.error(message)
return None
if type(rating) != float:
# logger.debug("no soy float")
if type(rating) == int:
# logger.debug("soy int")
rating = convert_float(rating)
elif type(rating) == str:
# logger.debug("soy str")
rating = rating.replace("<", "")
rating = convert_float(rating)
if rating is None:
# logger.debug("error al convertir str, rating no es un float")
# obtenemos los valores de numericos
new_rating = scrapertools.find_single_match(rating, "(\d+)[,|:](\d+)")
if len(new_rating) > 0:
rating = convert_float("%s.%s" % (new_rating[0], new_rating[1]))
else:
logger.error("no se que soy!!")
# obtenemos un valor desconocido no devolvemos nada
return None
if rating:
rating = check_decimal_length(rating)
rating = check_range(rating)
return rating

160
platformcode/updater.py Normal file
View File

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------------
# Updater (kodi)
# --------------------------------------------------------------------------------
import os
import time
import threading
import traceback
from platformcode import config, logger, platformtools
from core import httptools
from core import jsontools
from core import downloadtools
from core import ziptools
from core import filetools
def check_addon_init():
logger.info()
# Subtarea de monitor. Se activa cada X horas para comprobar si hay FIXES al addon
def check_addon_monitor():
logger.info()
# Obtiene el íntervalo entre actualizaciones y si se quieren mensajes
try:
timer = int(config.get_setting('addon_update_timer')) # Intervalo entre actualizaciones, en Ajustes de Alfa
if timer <= 0:
return # 0. No se quieren actualizaciones
verbose = config.get_setting('addon_update_message')
except:
timer = 12 # Por defecto cada 12 horas
verbose = False # Por defecto, sin mensajes
timer = timer * 3600 # Lo pasamos a segundos
if config.get_platform(True)['num_version'] >= 14: # Si es Kodi, lanzamos el monitor
import xbmc
monitor = xbmc.Monitor()
else: # Lanzamos solo una actualización y salimos
check_addon_updates(verbose) # Lanza la actualización
return
while not monitor.abortRequested(): # Loop infinito hasta cancelar Kodi
check_addon_updates(verbose) # Lanza la actualización
if monitor.waitForAbort(timer): # Espera el tiempo programado o hasta que cancele Kodi
break # Cancelación de Kodi, salimos
return
# Lanzamos en Servicio de actualización de FIXES
try:
threading.Thread(target=check_addon_monitor).start() # Creamos un Thread independiente, hasta el fin de Kodi
time.sleep(5) # Dejamos terminar la primera verificación...
except: # Si hay problemas de threading, se llama una sola vez
try:
timer = int(config.get_setting('addon_update_timer')) # Intervalo entre actualizaciones, en Ajustes de Alfa
if timer <= 0:
return # 0. No se quieren actualizaciones
verbose = config.get_setting('addon_update_message')
except:
verbose = False # Por defecto, sin mensajes
pass
check_addon_updates(verbose) # Lanza la actualización, en Ajustes de Alfa
time.sleep(5) # Dejamos terminar la primera verificación...
return
def check_addon_updates(verbose=False):
logger.info()
ADDON_UPDATES_JSON = 'https://extra.alfa-addon.com/addon_updates/updates.json'
ADDON_UPDATES_ZIP = 'https://extra.alfa-addon.com/addon_updates/updates.zip'
try:
last_fix_json = os.path.join(config.get_runtime_path(), 'last_fix.json') # información de la versión fixeada del usuario
# Se guarda en get_runtime_path en lugar de get_data_path para que se elimine al cambiar de versión
# Descargar json con las posibles actualizaciones
# -----------------------------------------------
data = httptools.downloadpage(ADDON_UPDATES_JSON, timeout=2).data
if data == '':
logger.info('No se encuentran actualizaciones del addon')
if verbose:
platformtools.dialog_notification(config.get_localized_string(70667), config.get_localized_string(70668))
return False
data = jsontools.load(data)
if 'addon_version' not in data or 'fix_version' not in data:
logger.info('No hay actualizaciones del addon')
if verbose:
platformtools.dialog_notification(config.get_localized_string(70667), config.get_localized_string(70668))
return False
# Comprobar versión que tiene instalada el usuario con versión de la actualización
# --------------------------------------------------------------------------------
current_version = config.get_addon_version(with_fix=False)
if current_version != data['addon_version']:
logger.info('No hay actualizaciones para la versión %s del addon' % current_version)
if verbose:
platformtools.dialog_notification(config.get_localized_string(70667), config.get_localized_string(70668))
return False
if os.path.exists(last_fix_json):
try:
lastfix = {}
lastfix = jsontools.load(filetools.read(last_fix_json))
if lastfix['addon_version'] == data['addon_version'] and lastfix['fix_version'] == data['fix_version']:
logger.info(config.get_localized_string(70670) % (data['addon_version'], data['fix_version']))
if verbose:
platformtools.dialog_notification(config.get_localized_string(70667), config.get_localized_string(70671) % (data['addon_version'], data['fix_version']))
return False
except:
if lastfix:
logger.error('last_fix.json: ERROR en: ' + str(lastfix))
else:
logger.error('last_fix.json: ERROR desconocido')
lastfix = {}
# Descargar zip con las actualizaciones
# -------------------------------------
localfilename = os.path.join(config.get_data_path(), 'temp_updates.zip')
if os.path.exists(localfilename): os.remove(localfilename)
downloadtools.downloadfile(ADDON_UPDATES_ZIP, localfilename, silent=True)
# Descomprimir zip dentro del addon
# ---------------------------------
try:
unzipper = ziptools.ziptools()
unzipper.extract(localfilename, config.get_runtime_path())
except:
import xbmc
xbmc.executebuiltin('XBMC.Extract("%s", "%s")' % (localfilename, config.get_runtime_path()))
time.sleep(1)
# Borrar el zip descargado
# ------------------------
os.remove(localfilename)
# Guardar información de la versión fixeada
# -----------------------------------------
if 'files' in data: data.pop('files', None)
filetools.write(last_fix_json, jsontools.dump(data))
logger.info(config.get_localized_string(70672) % (data['addon_version'], data['fix_version']))
if verbose:
platformtools.dialog_notification(config.get_localized_string(70673), config.get_localized_string(70671) % (data['addon_version'], data['fix_version']))
return True
except:
logger.error('Error al comprobar actualizaciones del addon!')
logger.error(traceback.format_exc())
if verbose:
platformtools.dialog_notification(config.get_localized_string(70674), config.get_localized_string(70675))
return False

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,331 @@
# -*- coding: utf-8 -*-
import xbmcgui
from core.tmdb import Tmdb
from platformcode import config, logger
ID_BUTTON_CLOSE = 10003
ID_BUTTON_PREVIOUS = 10025
ID_BUTTON_NEXT = 10026
ID_BUTTON_CANCEL = 10027
ID_BUTTON_OK = 10028
class InfoWindow(xbmcgui.WindowXMLDialog):
otmdb = None
item_title = ""
item_serie = ""
item_temporada = 0
item_episodio = 0
result = {}
# PARA TMDB
@staticmethod
def get_language(lng):
# Cambiamos el formato del Idioma
languages = {
'aa': 'Afar', 'ab': 'Abkhazian', 'af': 'Afrikaans', 'ak': 'Akan', 'sq': 'Albanian', 'am': 'Amharic',
'ar': 'Arabic', 'an': 'Aragonese', 'as': 'Assamese', 'av': 'Avaric', 'ae': 'Avestan', 'ay': 'Aymara',
'az': 'Azerbaijani', 'ba': 'Bashkir', 'bm': 'Bambara', 'eu': 'Basque', 'be': 'Belarusian', 'bn': 'Bengali',
'bh': 'Bihari languages', 'bi': 'Bislama', 'bo': 'Tibetan', 'bs': 'Bosnian', 'br': 'Breton',
'bg': 'Bulgarian', 'my': 'Burmese', 'ca': 'Catalan; Valencian', 'cs': 'Czech', 'ch': 'Chamorro',
'ce': 'Chechen', 'zh': 'Chinese',
'cu': 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic', 'cv': 'Chuvash',
'kw': 'Cornish', 'co': 'Corsican', 'cr': 'Cree', 'cy': 'Welsh', 'da': 'Danish', 'de': 'German',
'dv': 'Divehi; Dhivehi; Maldivian', 'nl': 'Dutch; Flemish', 'dz': 'Dzongkha', 'en': 'English',
'eo': 'Esperanto', 'et': 'Estonian', 'ee': 'Ewe', 'fo': 'Faroese', 'fa': 'Persian', 'fj': 'Fijian',
'fi': 'Finnish', 'fr': 'French', 'fy': 'Western Frisian', 'ff': 'Fulah', 'Ga': 'Georgian',
'gd': 'Gaelic; Scottish Gaelic', 'ga': 'Irish', 'gl': 'Galician', 'gv': 'Manx',
'el': 'Greek, Modern (1453-)', 'gn': 'Guarani', 'gu': 'Gujarati', 'ht': 'Haitian; Haitian Creole',
'ha': 'Hausa', 'he': 'Hebrew', 'hz': 'Herero', 'hi': 'Hindi', 'ho': 'Hiri Motu', 'hr': 'Croatian',
'hu': 'Hungarian', 'hy': 'Armenian', 'ig': 'Igbo', 'is': 'Icelandic', 'io': 'Ido',
'ii': 'Sichuan Yi; Nuosu', 'iu': 'Inuktitut', 'ie': 'Interlingue; Occidental',
'ia': 'Interlingua (International Auxiliary Language Association)', 'id': 'Indonesian', 'ik': 'Inupiaq',
'it': 'Italian', 'jv': 'Javanese', 'ja': 'Japanese', 'kl': 'Kalaallisut; Greenlandic', 'kn': 'Kannada',
'ks': 'Kashmiri', 'ka': 'Georgian', 'kr': 'Kanuri', 'kk': 'Kazakh', 'km': 'Central Khmer',
'ki': 'Kikuyu; Gikuyu', 'rw': 'Kinyarwanda', 'ky': 'Kirghiz; Kyrgyz', 'kv': 'Komi', 'kg': 'Kongo',
'ko': 'Korean', 'kj': 'Kuanyama; Kwanyama', 'ku': 'Kurdish', 'lo': 'Lao', 'la': 'Latin', 'lv': 'Latvian',
'li': 'Limburgan; Limburger; Limburgish', 'ln': 'Lingala', 'lt': 'Lithuanian',
'lb': 'Luxembourgish; Letzeburgesch', 'lu': 'Luba-Katanga', 'lg': 'Ganda', 'mk': 'Macedonian',
'mh': 'Marshallese', 'ml': 'Malayalam', 'mi': 'Maori', 'mr': 'Marathi', 'ms': 'Malay', 'Mi': 'Micmac',
'mg': 'Malagasy', 'mt': 'Maltese', 'mn': 'Mongolian', 'na': 'Nauru', 'nv': 'Navajo; Navaho',
'nr': 'Ndebele, South; South Ndebele', 'nd': 'Ndebele, North; North Ndebele', 'ng': 'Ndonga',
'ne': 'Nepali', 'nn': 'Norwegian Nynorsk; Nynorsk, Norwegian', 'nb': 'Bokmål, Norwegian; Norwegian Bokmål',
'no': 'Norwegian', 'oc': 'Occitan (post 1500)', 'oj': 'Ojibwa', 'or': 'Oriya', 'om': 'Oromo',
'os': 'Ossetian; Ossetic', 'pa': 'Panjabi; Punjabi', 'pi': 'Pali', 'pl': 'Polish', 'pt': 'Portuguese',
'ps': 'Pushto; Pashto', 'qu': 'Quechua', 'ro': 'Romanian; Moldavian; Moldovan', 'rn': 'Rundi',
'ru': 'Russian', 'sg': 'Sango', 'rm': 'Romansh', 'sa': 'Sanskrit', 'si': 'Sinhala; Sinhalese',
'sk': 'Slovak', 'sl': 'Slovenian', 'se': 'Northern Sami', 'sm': 'Samoan', 'sn': 'Shona', 'sd': 'Sindhi',
'so': 'Somali', 'st': 'Sotho, Southern', 'es': 'Spanish', 'sc': 'Sardinian', 'sr': 'Serbian', 'ss': 'Swati',
'su': 'Sundanese', 'sw': 'Swahili', 'sv': 'Swedish', 'ty': 'Tahitian', 'ta': 'Tamil', 'tt': 'Tatar',
'te': 'Telugu', 'tg': 'Tajik', 'tl': 'Tagalog', 'th': 'Thai', 'ti': 'Tigrinya',
'to': 'Tonga (Tonga Islands)', 'tn': 'Tswana', 'ts': 'Tsonga', 'tk': 'Turkmen', 'tr': 'Turkish',
'tw': 'Twi', 'ug': 'Uighur; Uyghur', 'uk': 'Ukrainian', 'ur': 'Urdu', 'uz': 'Uzbek', 've': 'Venda',
'vi': 'Vietnamese', 'vo': 'Volapük', 'wa': 'Walloon', 'wo': 'Wolof', 'xh': 'Xhosa', 'yi': 'Yiddish',
'yo': 'Yoruba', 'za': 'Zhuang; Chuang', 'zu': 'Zulu'}
return languages.get(lng, lng)
def get_scraper_data(self, data_in):
self.otmdb = None
# logger.debug(str(data_in))
if self.listData:
# Datos comunes a todos los listados
infoLabels = self.scraper().get_infoLabels(origen=data_in)
if "original_language" in infoLabels:
infoLabels["language"] = self.get_language(infoLabels["original_language"])
infoLabels["puntuacion"] = "%s/10 (%s)" % (infoLabels.get("rating", "?"), infoLabels.get("votes", "N/A"))
self.result = infoLabels
def start(self, data, caption="Información del vídeo", item=None, scraper=Tmdb):
"""
Muestra una ventana con la info del vídeo. Opcionalmente se puede indicar el titulo de la ventana mendiante
el argumento 'caption'.
Si se pasa un item como argumento 'data' usa el scrapper Tmdb para buscar la info del vídeo
En caso de peliculas:
Coge el titulo de los siguientes campos (en este orden)
1. contentTitle (este tiene prioridad 1)
2. fulltitle (este tiene prioridad 2)
3. title (este tiene prioridad 3)
El primero que contenga "algo" lo interpreta como el titulo (es importante asegurarse que el titulo este en
su sitio)
En caso de series:
1. Busca la temporada y episodio en los campos contentSeason y contentEpisodeNumber
2. Intenta Sacarlo del titulo del video (formato: 1x01)
Aqui hay dos opciones posibles:
1. Tenemos Temporada y episodio
Muestra la información del capitulo concreto
2. NO Tenemos Temporada y episodio
En este caso muestra la informacion generica de la serie
Si se pasa como argumento 'data' un objeto InfoLabels(ver item.py) muestra en la ventana directamente
la información pasada (sin usar el scrapper)
Formato:
En caso de peliculas:
infoLabels({
"type" : "movie",
"title" : "Titulo de la pelicula",
"original_title" : "Titulo original de la pelicula",
"date" : "Fecha de lanzamiento",
"language" : "Idioma original de la pelicula",
"rating" : "Puntuacion de la pelicula",
"votes" : "Numero de votos",
"genres" : "Generos de la pelicula",
"thumbnail" : "Ruta para el thumbnail",
"fanart" : "Ruta para el fanart",
"plot" : "Sinopsis de la pelicula"
}
En caso de series:
infoLabels({
"type" : "tv",
"title" : "Titulo de la serie",
"episode_title" : "Titulo del episodio",
"date" : "Fecha de emision",
"language" : "Idioma original de la serie",
"rating" : "Puntuacion de la serie",
"votes" : "Numero de votos",
"genres" : "Generos de la serie",
"thumbnail" : "Ruta para el thumbnail",
"fanart" : "Ruta para el fanart",
"plot" : "Sinopsis de la del episodio o de la serie",
"seasons" : "Numero de Temporadas",
"season" : "Temporada",
"episodes" : "Numero de episodios de la temporada",
"episode" : "Episodio"
}
Si se pasa como argumento 'data' un listado de InfoLabels() con la estructura anterior, muestra los botones
'Anterior' y 'Siguiente' para ir recorriendo la lista. Ademas muestra los botones 'Aceptar' y 'Cancelar' que
llamaran a la funcion 'callback' del canal desde donde se realiza la llamada pasandole como parametros el elemento
actual (InfoLabels()) o None respectivamente.
@param data: información para obtener datos del scraper.
@type data: item, InfoLabels, list(InfoLabels)
@param caption: titulo de la ventana.
@type caption: str
@param item: elemento del que se va a mostrar la ventana de información
@type item: Item
@param scraper: scraper que tiene los datos de las peliculas o series a mostrar en la ventana.
@type scraper: Scraper
"""
# Capturamos los parametros
self.caption = caption
self.item = item
self.indexList = -1
self.listData = None
self.return_value = None
self.scraper = scraper
logger.debug(data)
if type(data) == list:
self.listData = data
self.indexList = 0
data = self.listData[self.indexList]
self.get_scraper_data(data)
# Muestra la ventana
self.doModal()
return self.return_value
def __init__(self, *args):
self.caption = ""
self.item = None
self.listData = None
self.indexList = 0
self.return_value = None
self.scraper = Tmdb
def onInit(self):
#### Compatibilidad con Kodi 18 ####
if config.get_platform(True)['num_version'] < 18:
if xbmcgui.__version__ == "1.2":
self.setCoordinateResolution(1)
else:
self.setCoordinateResolution(5)
# Ponemos el título y las imagenes
self.getControl(10002).setLabel(self.caption)
self.getControl(10004).setImage(self.result.get("fanart", ""))
self.getControl(10005).setImage(self.result.get("thumbnail", "images/img_no_disponible.png"))
# Cargamos los datos para el formato pelicula
if self.result.get("mediatype", "movie") == "movie":
self.getControl(10006).setLabel(config.get_localized_string(60377))
self.getControl(10007).setLabel(self.result.get("title", "N/A"))
self.getControl(10008).setLabel(config.get_localized_string(60378))
self.getControl(10009).setLabel(self.result.get("originaltitle", "N/A"))
self.getControl(100010).setLabel(config.get_localized_string(60379))
self.getControl(100011).setLabel(self.result.get("language", "N/A"))
self.getControl(100012).setLabel(config.get_localized_string(60380))
self.getControl(100013).setLabel(self.result.get("puntuacion", "N/A"))
self.getControl(100014).setLabel(config.get_localized_string(60381))
self.getControl(100015).setLabel(self.result.get("release_date", "N/A"))
self.getControl(100016).setLabel(config.get_localized_string(60382))
self.getControl(100017).setLabel(self.result.get("genre", "N/A"))
# Cargamos los datos para el formato serie
else:
self.getControl(10006).setLabel(config.get_localized_string(60383))
self.getControl(10007).setLabel(self.result.get("title", "N/A"))
self.getControl(10008).setLabel(config.get_localized_string(60379))
self.getControl(10009).setLabel(self.result.get("language", "N/A"))
self.getControl(100010).setLabel(config.get_localized_string(60380))
self.getControl(100011).setLabel(self.result.get("puntuacion", "N/A"))
self.getControl(100012).setLabel(config.get_localized_string(60382))
self.getControl(100013).setLabel(self.result.get("genre", "N/A"))
if self.result.get("season"):
self.getControl(100014).setLabel(config.get_localized_string(60384))
self.getControl(100015).setLabel(self.result.get("temporada_nombre", "N/A"))
self.getControl(100016).setLabel(config.get_localized_string(60385))
self.getControl(100017).setLabel(self.result.get("season", "N/A") + " de " +
self.result.get("seasons", "N/A"))
if self.result.get("episode"):
self.getControl(100014).setLabel(config.get_localized_string(60377))
self.getControl(100015).setLabel(self.result.get("episode_title", "N/A"))
self.getControl(100018).setLabel(config.get_localized_string(60386))
self.getControl(100019).setLabel(self.result.get("episode", "N/A") + " de " +
self.result.get("episodes", "N/A"))
self.getControl(100020).setLabel(config.get_localized_string(60387))
self.getControl(100021).setLabel(self.result.get("date", "N/A"))
# Sinopsis
if self.result['plot']:
self.getControl(100022).setLabel(config.get_localized_string(60388))
self.getControl(100023).setText(self.result.get("plot", "N/A"))
else:
self.getControl(100022).setLabel("")
self.getControl(100023).setText("")
# Cargamos los botones si es necesario
self.getControl(10024).setVisible(self.indexList > -1) # Grupo de botones
self.getControl(ID_BUTTON_PREVIOUS).setEnabled(self.indexList > 0) # Anterior
if self.listData:
m = len(self.listData)
else:
m = 1
self.getControl(ID_BUTTON_NEXT).setEnabled(self.indexList + 1 != m) # Siguiente
self.getControl(100029).setLabel("(%s/%s)" % (self.indexList + 1, m)) # x/m
# Ponemos el foco en el Grupo de botones, si estuviera desactivado "Anterior" iria el foco al boton "Siguiente"
# si "Siguiente" tb estuviera desactivado pasara el foco al botón "Cancelar"
self.setFocus(self.getControl(10024))
return self.return_value
def onClick(self, _id):
logger.info("onClick id=" + repr(_id))
if _id == ID_BUTTON_PREVIOUS and self.indexList > 0:
self.indexList -= 1
self.get_scraper_data(self.listData[self.indexList])
self.onInit()
elif _id == ID_BUTTON_NEXT and self.indexList < len(self.listData) - 1:
self.indexList += 1
self.get_scraper_data(self.listData[self.indexList])
self.onInit()
elif _id == ID_BUTTON_OK or _id == ID_BUTTON_CLOSE or _id == ID_BUTTON_CANCEL:
self.close()
if _id == ID_BUTTON_OK:
self.return_value = self.listData[self.indexList]
else:
self.return_value = None
def onAction(self, action):
logger.info("action=" + repr(action.getId()))
action = action.getId()
# Obtenemos el foco
focus = self.getFocusId()
# Accion 1: Flecha izquierda
if action == 1:
if focus == ID_BUTTON_OK:
self.setFocus(self.getControl(ID_BUTTON_CANCEL))
elif focus == ID_BUTTON_CANCEL:
if self.indexList + 1 != len(self.listData):
# vamos al botón Siguiente
self.setFocus(self.getControl(ID_BUTTON_NEXT))
elif self.indexList > 0:
# vamos al botón Anterior ya que Siguiente no está activo (estamos al final de la lista)
self.setFocus(self.getControl(ID_BUTTON_PREVIOUS))
elif focus == ID_BUTTON_NEXT:
if self.indexList > 0:
# vamos al botón Anterior
self.setFocus(self.getControl(ID_BUTTON_PREVIOUS))
# Accion 2: Flecha derecha
elif action == 2:
if focus == ID_BUTTON_PREVIOUS:
if self.indexList + 1 != len(self.listData):
# vamos al botón Siguiente
self.setFocus(self.getControl(ID_BUTTON_NEXT))
else:
# vamos al botón Cancelar ya que Siguiente no está activo (estamos al final de la lista)
self.setFocus(self.getControl(ID_BUTTON_CANCEL))
elif focus == ID_BUTTON_NEXT:
self.setFocus(self.getControl(ID_BUTTON_CANCEL))
elif focus == ID_BUTTON_CANCEL:
self.setFocus(self.getControl(ID_BUTTON_OK))
# Pulsa ESC o Atrás, simula click en boton cancelar
if action in [10, 92]:
self.onClick(ID_BUTTON_CANCEL)

View File

@@ -0,0 +1,927 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# XBMC Library Tools
# ------------------------------------------------------------
import os
import threading
import time
import urllib2
import xbmc
from core import filetools
from core import jsontools
from platformcode import config, logger
from platformcode import platformtools
def mark_auto_as_watched(item):
def mark_as_watched_subThread(item):
logger.info()
# logger.debug("item:\n" + item.tostring('\n'))
condicion = config.get_setting("watched_setting", "videolibrary")
time_limit = time.time() + 30
while not platformtools.is_playing() and time.time() < time_limit:
time.sleep(1)
sync_with_trakt = False
while platformtools.is_playing():
tiempo_actual = xbmc.Player().getTime()
totaltime = xbmc.Player().getTotalTime()
mark_time = 0
if condicion == 0: # '5 minutos'
mark_time = 300
elif condicion == 1: # '30%'
mark_time = totaltime * 0.3
elif condicion == 2: # '50%'
mark_time = totaltime * 0.5
elif condicion == 3: # '80%'
mark_time = totaltime * 0.8
elif condicion == 4: # '0 seg'
mark_time = -1
# logger.debug(str(tiempo_actual))
# logger.debug(str(mark_time))
if tiempo_actual > mark_time:
logger.debug("marcado")
item.playcount = 1
sync_with_trakt = True
from channels import videolibrary
videolibrary.mark_content_as_watched2(item)
break
time.sleep(30)
# Sincronizacion silenciosa con Trakt
if sync_with_trakt:
if config.get_setting("sync_trakt_watched", "videolibrary"):
sync_trakt_kodi()
# logger.debug("Fin del hilo")
# Si esta configurado para marcar como visto
if config.get_setting("mark_as_watched", "videolibrary"):
threading.Thread(target=mark_as_watched_subThread, args=[item]).start()
def sync_trakt_addon(path_folder):
"""
Actualiza los valores de episodios vistos si
"""
logger.info()
# si existe el addon hacemos la busqueda
if xbmc.getCondVisibility('System.HasAddon("script.trakt")'):
# importamos dependencias
paths = ["special://home/addons/script.module.dateutil/lib/", "special://home/addons/script.module.six/lib/",
"special://home/addons/script.module.arrow/lib/", "special://home/addons/script.module.trakt/lib/",
"special://home/addons/script.trakt/"]
for path in paths:
import sys
sys.path.append(xbmc.translatePath(path))
# se obtiene las series vistas
try:
from resources.lib.traktapi import traktAPI
traktapi = traktAPI()
except:
return
shows = traktapi.getShowsWatched({})
shows = shows.items()
# obtenemos el id de la serie para comparar
import re
_id = re.findall("\[(.*?)\]", path_folder, flags=re.DOTALL)[0]
logger.debug("el id es %s" % _id)
if "tt" in _id:
type_id = "imdb"
elif "tvdb_" in _id:
_id = _id.strip("tvdb_")
type_id = "tvdb"
elif "tmdb_" in _id:
type_id = "tmdb"
_id = _id.strip("tmdb_")
else:
logger.error("No hay _id de la serie")
return
# obtenemos los valores de la serie
from core import videolibrarytools
tvshow_file = filetools.join(path_folder, "tvshow.nfo")
head_nfo, serie = videolibrarytools.read_nfo(tvshow_file)
# buscamos en las series de trakt
for show in shows:
show_aux = show[1].to_dict()
try:
_id_trakt = show_aux['ids'].get(type_id, None)
# logger.debug("ID ES %s" % _id_trakt)
if _id_trakt:
if _id == _id_trakt:
logger.debug("ENCONTRADO!! %s" % show_aux)
# creamos el diccionario de trakt para la serie encontrada con el valor que tiene "visto"
dict_trakt_show = {}
for idx_season, season in enumerate(show_aux['seasons']):
for idx_episode, episode in enumerate(show_aux['seasons'][idx_season]['episodes']):
sea_epi = "%sx%s" % (show_aux['seasons'][idx_season]['number'],
str(show_aux['seasons'][idx_season]['episodes'][idx_episode][
'number']).zfill(2))
dict_trakt_show[sea_epi] = show_aux['seasons'][idx_season]['episodes'][idx_episode][
'watched']
logger.debug("dict_trakt_show %s " % dict_trakt_show)
# obtenemos las keys que son episodios
regex_epi = re.compile('\d+x\d+')
keys_episodes = [key for key in serie.library_playcounts if regex_epi.match(key)]
# obtenemos las keys que son temporadas
keys_seasons = [key for key in serie.library_playcounts if 'season ' in key]
# obtenemos los numeros de las keys temporadas
seasons = [key.strip('season ') for key in keys_seasons]
# marcamos los episodios vistos
for k in keys_episodes:
serie.library_playcounts[k] = dict_trakt_show.get(k, 0)
for season in seasons:
episodios_temporada = 0
episodios_vistos_temporada = 0
# obtenemos las keys de los episodios de una determinada temporada
keys_season_episodes = [key for key in keys_episodes if key.startswith("%sx" % season)]
for k in keys_season_episodes:
episodios_temporada += 1
if serie.library_playcounts[k] > 0:
episodios_vistos_temporada += 1
# se comprueba que si todos los episodios están vistos, se marque la temporada como vista
if episodios_temporada == episodios_vistos_temporada:
serie.library_playcounts.update({"season %s" % season: 1})
temporada = 0
temporada_vista = 0
for k in keys_seasons:
temporada += 1
if serie.library_playcounts[k] > 0:
temporada_vista += 1
# se comprueba que si todas las temporadas están vistas, se marque la serie como vista
if temporada == temporada_vista:
serie.library_playcounts.update({serie.title: 1})
logger.debug("los valores nuevos %s " % serie.library_playcounts)
filetools.write(tvshow_file, head_nfo + serie.tojson())
break
else:
continue
else:
logger.error("no se ha podido obtener el id, trakt tiene: %s" % show_aux['ids'])
except:
import traceback
logger.error(traceback.format_exc())
def sync_trakt_kodi(silent=True):
# Para que la sincronizacion no sea silenciosa vale con silent=False
if xbmc.getCondVisibility('System.HasAddon("script.trakt")'):
notificacion = True
if (not config.get_setting("sync_trakt_notification", "videolibrary") and
platformtools.is_playing()):
notificacion = False
xbmc.executebuiltin('RunScript(script.trakt,action=sync,silent=%s)' % silent)
logger.info("Sincronizacion con Trakt iniciada")
if notificacion:
platformtools.dialog_notification(config.get_localized_string(20000),
config.get_localized_string(60045),
icon=0,
time=2000)
def mark_content_as_watched_on_kodi(item, value=1):
"""
marca el contenido como visto o no visto en la libreria de Kodi
@type item: item
@param item: elemento a marcar
@type value: int
@param value: >0 para visto, 0 para no visto
"""
logger.info()
# logger.debug("item:\n" + item.tostring('\n'))
payload_f = ''
if item.contentType == "movie":
movieid = 0
payload = {"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies",
"params": {"properties": ["title", "playcount", "originaltitle", "file"]},
"id": 1}
data = get_data(payload)
if 'result' in data and "movies" in data['result']:
if item.strm_path: #Si Item es de un episodio
filename = filetools.basename(item.strm_path)
head, tail = filetools.split(filetools.split(item.strm_path)[0])
else: #Si Item es de la Serie
filename = filetools.basename(item.path)
head, tail = filetools.split(filetools.split(item.path)[0])
path = filetools.join(tail, filename)
for d in data['result']['movies']:
if d['file'].replace("/", "\\").endswith(path.replace("/", "\\")):
# logger.debug("marco la pelicula como vista")
movieid = d['movieid']
break
if movieid != 0:
payload_f = {"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": {
"movieid": movieid, "playcount": value}, "id": 1}
else: # item.contentType != 'movie'
episodeid = 0
payload = {"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes",
"params": {"properties": ["title", "playcount", "showtitle", "file", "tvshowid"]},
"id": 1}
data = get_data(payload)
if 'result' in data and "episodes" in data['result']:
if item.strm_path: #Si Item es de un episodio
filename = filetools.basename(item.strm_path)
head, tail = filetools.split(filetools.split(item.strm_path)[0])
else: #Si Item es de la Serie
filename = filetools.basename(item.path)
head, tail = filetools.split(filetools.split(item.path)[0])
path = filetools.join(tail, filename)
for d in data['result']['episodes']:
if d['file'].replace("/", "\\").endswith(path.replace("/", "\\")):
# logger.debug("marco el episodio como visto")
episodeid = d['episodeid']
break
if episodeid != 0:
payload_f = {"jsonrpc": "2.0", "method": "VideoLibrary.SetEpisodeDetails", "params": {
"episodeid": episodeid, "playcount": value}, "id": 1}
if payload_f:
# Marcar como visto
data = get_data(payload_f)
# logger.debug(str(data))
if data['result'] != 'OK':
logger.error("ERROR al poner el contenido como visto")
def mark_season_as_watched_on_kodi(item, value=1):
"""
marca toda la temporada como vista o no vista en la libreria de Kodi
@type item: item
@param item: elemento a marcar
@type value: int
@param value: >0 para visto, 0 para no visto
"""
logger.info()
# logger.debug("item:\n" + item.tostring('\n'))
# Solo podemos marcar la temporada como vista en la BBDD de Kodi si la BBDD es local,
# en caso de compartir BBDD esta funcionalidad no funcionara
if config.get_setting("db_mode", "videolibrary"):
return
if value == 0:
value = 'Null'
request_season = ''
if item.contentSeason > -1:
request_season = ' and c12= %s' % item.contentSeason
tvshows_path = filetools.join(config.get_videolibrary_path(), config.get_setting("folder_tvshows"))
item_path1 = "%" + item.path.replace("\\\\", "\\").replace(tvshows_path, "")
if item_path1[:-1] != "\\":
item_path1 += "\\"
item_path2 = item_path1.replace("\\", "/")
sql = 'update files set playCount= %s where idFile in ' \
'(select idfile from episode_view where (strPath like "%s" or strPath like "%s")%s)' % \
(value, item_path1, item_path2, request_season)
execute_sql_kodi(sql)
def mark_content_as_watched_on_alfa(path):
from channels import videolibrary
from core import videolibrarytools
from core import scrapertools
from core import filetools
import re
"""
marca toda la serie o película como vista o no vista en la Videoteca de Alfa basado en su estado en la Videoteca de Kodi
@type str: path
@param path: carpeta de contenido a marcar
"""
logger.info()
#logger.debug("path: " + path)
FOLDER_MOVIES = config.get_setting("folder_movies")
FOLDER_TVSHOWS = config.get_setting("folder_tvshows")
VIDEOLIBRARY_PATH = config.get_videolibrary_config_path()
if not VIDEOLIBRARY_PATH:
return
# Solo podemos marcar el contenido como vista en la BBDD de Kodi si la BBDD es local,
# en caso de compartir BBDD esta funcionalidad no funcionara
#if config.get_setting("db_mode", "videolibrary"):
# return
path2 = ''
if "special://" in VIDEOLIBRARY_PATH:
if FOLDER_TVSHOWS in path:
path2 = re. sub(r'.*?%s' % FOLDER_TVSHOWS, VIDEOLIBRARY_PATH + "/" + FOLDER_TVSHOWS, path).replace("\\", "/")
if FOLDER_MOVIES in path:
path2 = re. sub(r'.*?%s' % FOLDER_MOVIES, VIDEOLIBRARY_PATH + "/" + FOLDER_MOVIES, path).replace("\\", "/")
if "\\" in path:
path = path.replace("/", "\\")
head_nfo, item = videolibrarytools.read_nfo(path) #Leo el .nfo del contenido
if FOLDER_TVSHOWS in path: #Compruebo si es CINE o SERIE
contentType = "episode_view" #Marco la tabla de BBDD de Kodi Video
nfo_name = "tvshow.nfo" #Construyo el nombre del .nfo
path1 = path.replace("\\\\", "\\").replace(nfo_name, '') #para la SQL solo necesito la carpeta
if not path2:
path2 = path1.replace("\\", "/") #Formato no Windows
else:
path2 = path2.replace(nfo_name, '')
else:
contentType = "movie_view" #Marco la tabla de BBDD de Kodi Video
path1 = path.replace("\\\\", "\\") #Formato Windows
if not path2:
path2 = path1.replace("\\", "/") #Formato no Windows
nfo_name = scrapertools.find_single_match(path2, '\]\/(.*?)$') #Construyo el nombre del .nfo
path1 = path1.replace(nfo_name, '') #para la SQL solo necesito la carpeta
path2 = path2.replace(nfo_name, '') #para la SQL solo necesito la carpeta
path2 = filetools.remove_smb_credential(path2) #Si el archivo está en un servidor SMB, quiamos las credenciales
#Ejecutmos la sentencia SQL
sql = 'select strFileName, playCount from %s where (strPath like "%s" or strPath like "%s")' % (contentType, path1, path2)
nun_records = 0
records = None
nun_records, records = execute_sql_kodi(sql) #ejecución de la SQL
if nun_records == 0: #hay error?
logger.error("Error en la SQL: " + sql + ": 0 registros")
return #salimos: o no está catalogado en Kodi, o hay un error en la SQL
for title, playCount in records: #Ahora recorremos todos los registros obtenidos
if contentType == "episode_view":
title_plain = title.replace('.strm', '') #Si es Serie, quitamos el sufijo .strm
else:
title_plain = scrapertools.find_single_match(item.strm_path, '.(.*?\s\[.*?\])') #si es peli, quitamos el título
if playCount is None or playCount == 0: #todavía no se ha visto, lo ponemos a 0
playCount_final = 0
elif playCount >= 1:
playCount_final = 1
title_plain = title_plain.decode("utf-8").encode("utf-8") #Hacemos esto porque si no genera esto: u'title_plain'
item.library_playcounts.update({title_plain: playCount_final}) #actualizamos el playCount del .nfo
if item.infoLabels['mediatype'] == "tvshow": #Actualizamos los playCounts de temporadas y Serie
for season in item.library_playcounts:
if "season" in season: #buscamos las etiquetas "season" dentro de playCounts
season_num = int(scrapertools.find_single_match(season, 'season (\d+)')) #salvamos el núm, de Temporada
item = videolibrary.check_season_playcount(item, season_num) #llamamos al método que actualiza Temps. y Series
filetools.write(path, head_nfo + item.tojson())
#logger.debug(item)
def get_data(payload):
"""
obtiene la información de la llamada JSON-RPC con la información pasada en payload
@type payload: dict
@param payload: data
:return:
"""
logger.info("payload: %s" % payload)
# Required header for XBMC JSON-RPC calls, otherwise you'll get a 415 HTTP response code - Unsupported media type
headers = {'content-type': 'application/json'}
if config.get_setting("db_mode", "videolibrary"):
try:
try:
xbmc_port = config.get_setting("xbmc_puerto", "videolibrary")
except:
xbmc_port = 0
xbmc_json_rpc_url = "http://" + config.get_setting("xbmc_host", "videolibrary") + ":" + str(
xbmc_port) + "/jsonrpc"
req = urllib2.Request(xbmc_json_rpc_url, data=jsontools.dump(payload), headers=headers)
f = urllib2.urlopen(req)
response = f.read()
f.close()
logger.info("get_data: response %s" % response)
data = jsontools.load(response)
except Exception, ex:
template = "An exception of type %s occured. Arguments:\n%r"
message = template % (type(ex).__name__, ex.args)
logger.error("error en xbmc_json_rpc_url: %s" % message)
data = ["error"]
else:
try:
data = jsontools.load(xbmc.executeJSONRPC(jsontools.dump(payload)))
except Exception, ex:
template = "An exception of type %s occured. Arguments:\n%r"
message = template % (type(ex).__name__, ex.args)
logger.error("error en xbmc.executeJSONRPC: %s" % message)
data = ["error"]
logger.info("data: %s" % data)
return data
def update(folder_content=config.get_setting("folder_tvshows"), folder=""):
"""
Actualiza la libreria dependiendo del tipo de contenido y la ruta que se le pase.
@type folder_content: str
@param folder_content: tipo de contenido para actualizar, series o peliculas
@type folder: str
@param folder: nombre de la carpeta a escanear.
"""
logger.info(folder)
payload = {
"jsonrpc": "2.0",
"method": "VideoLibrary.Scan",
"id": 1
}
if folder:
videolibrarypath = config.get_videolibrary_config_path()
if folder.endswith('/') or folder.endswith('\\'):
folder = folder[:-1]
update_path = None
if videolibrarypath.startswith("special:"):
if videolibrarypath.endswith('/'):
videolibrarypath = videolibrarypath[:-1]
update_path = videolibrarypath + "/" + folder_content + "/" + folder + "/"
else:
update_path = filetools.join(videolibrarypath, folder_content, folder) + "/"
if not update_path.startswith("smb://"):
payload["params"] = {"directory": update_path}
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
xbmc.sleep(500)
data = get_data(payload)
def clean(mostrar_dialogo=False):
"""
limpia la libreria de elementos que no existen
@param mostrar_dialogo: muestra el cuadro de progreso mientras se limpia la videoteca
@type mostrar_dialogo: bool
"""
logger.info()
payload = {"jsonrpc": "2.0", "method": "VideoLibrary.Clean", "id": 1,
"params": {"showdialogs": mostrar_dialogo}}
data = get_data(payload)
if data.get('result', False) == 'OK':
return True
return False
def search_library_path():
sql = 'SELECT strPath FROM path WHERE strPath LIKE "special://%/plugin.video.alfa/library/" AND idParentPath ISNULL'
nun_records, records = execute_sql_kodi(sql)
if nun_records >= 1:
logger.debug(records[0][0])
return records[0][0]
return None
def set_content(content_type, silent=False):
"""
Procedimiento para auto-configurar la videoteca de kodi con los valores por defecto
@type content_type: str ('movie' o 'tvshow')
@param content_type: tipo de contenido para configurar, series o peliculas
"""
continuar = True
msg_text = ""
videolibrarypath = config.get_setting("videolibrarypath")
if content_type == 'movie':
scraper = [config.get_localized_string(70093), config.get_localized_string(70096)]
seleccion = platformtools.dialog_select(config.get_localized_string(70094), scraper)
# Instalar The Movie Database
if seleccion == -1 or seleccion == 0:
if not xbmc.getCondVisibility('System.HasAddon(metadata.themoviedb.org)'):
if not silent:
# Preguntar si queremos instalar metadata.themoviedb.org
install = platformtools.dialog_yesno(config.get_localized_string(60046))
else:
install = True
if install:
try:
# Instalar metadata.themoviedb.org
xbmc.executebuiltin('xbmc.installaddon(metadata.themoviedb.org)', True)
logger.info("Instalado el Scraper de películas de TheMovieDB")
except:
pass
continuar = (install and xbmc.getCondVisibility('System.HasAddon(metadata.themoviedb.org)'))
if not continuar:
msg_text = config.get_localized_string(60047)
if continuar:
xbmc.executebuiltin('xbmc.addon.opensettings(metadata.themoviedb.org)', True)
# Instalar Universal Movie Scraper
elif seleccion == 1:
if continuar and not xbmc.getCondVisibility('System.HasAddon(metadata.universal)'):
continuar = False
if not silent:
# Preguntar si queremos instalar metadata.universal
install = platformtools.dialog_yesno(config.get_localized_string(70095))
else:
install = True
if install:
try:
xbmc.executebuiltin('xbmc.installaddon(metadata.universal)', True)
if xbmc.getCondVisibility('System.HasAddon(metadata.universal)'):
continuar = True
except:
pass
continuar = (install and continuar)
if not continuar:
msg_text = config.get_localized_string(70097)
if continuar:
xbmc.executebuiltin('xbmc.addon.opensettings(metadata.universal)', True)
else: # SERIES
scraper = [config.get_localized_string(70098), config.get_localized_string(70093)]
seleccion = platformtools.dialog_select(config.get_localized_string(70107), scraper)
# Instalar The TVDB
if seleccion == -1 or seleccion == 0:
if not xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'):
if not silent:
# Preguntar si queremos instalar metadata.tvdb.com
install = platformtools.dialog_yesno(config.get_localized_string(60048))
else:
install = True
if install:
try:
# Instalar metadata.tvdb.com
xbmc.executebuiltin('xbmc.installaddon(metadata.tvdb.com)', True)
logger.info("Instalado el Scraper de series de The TVDB")
except:
pass
continuar = (install and xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'))
if not continuar:
msg_text = config.get_localized_string(70099)
if continuar:
xbmc.executebuiltin('xbmc.addon.opensettings(metadata.tvdb.com)', True)
# Instalar The Movie Database
elif seleccion == 1:
if continuar and not xbmc.getCondVisibility('System.HasAddon(metadata.tvshows.themoviedb.org)'):
continuar = False
if not silent:
# Preguntar si queremos instalar metadata.tvshows.themoviedb.org
install = platformtools.dialog_yesno(config.get_localized_string(70100))
else:
install = True
if install:
try:
# Instalar metadata.tvshows.themoviedb.org
xbmc.executebuiltin('xbmc.installaddon(metadata.tvshows.themoviedb.org)', True)
if xbmc.getCondVisibility('System.HasAddon(metadata.tvshows.themoviedb.org)'):
continuar = True
except:
pass
continuar = (install and continuar)
if not continuar:
msg_text = config.get_localized_string(60047)
if continuar:
xbmc.executebuiltin('xbmc.addon.opensettings(metadata.tvshows.themoviedb.org)', True)
idPath = 0
idParentPath = 0
if continuar:
continuar = False
# Buscamos el idPath
sql = 'SELECT MAX(idPath) FROM path'
nun_records, records = execute_sql_kodi(sql)
if nun_records == 1:
idPath = records[0][0] + 1
sql_videolibrarypath = videolibrarypath
if sql_videolibrarypath.startswith("special://"):
sql_videolibrarypath = sql_videolibrarypath.replace('/profile/', '/%/').replace('/home/userdata/', '/%/')
sep = '/'
elif sql_videolibrarypath.startswith("smb://"):
sep = '/'
else:
sep = os.sep
if not sql_videolibrarypath.endswith(sep):
sql_videolibrarypath += sep
# Buscamos el idParentPath
sql = 'SELECT idPath, strPath FROM path where strPath LIKE "%s"' % sql_videolibrarypath
nun_records, records = execute_sql_kodi(sql)
if nun_records == 1:
idParentPath = records[0][0]
videolibrarypath = records[0][1][:-1]
continuar = True
else:
# No existe videolibrarypath en la BD: la insertamos
sql_videolibrarypath = videolibrarypath
if not sql_videolibrarypath.endswith(sep):
sql_videolibrarypath += sep
sql = 'INSERT INTO path (idPath, strPath, scanRecursive, useFolderNames, noUpdate, exclude) VALUES ' \
'(%s, "%s", 0, 0, 0, 0)' % (idPath, sql_videolibrarypath)
nun_records, records = execute_sql_kodi(sql)
if nun_records == 1:
continuar = True
idParentPath = idPath
idPath += 1
else:
msg_text = config.get_localized_string(70101)
if continuar:
continuar = False
# Fijamos strContent, strScraper, scanRecursive y strSettings
if content_type == 'movie':
strContent = 'movies'
scanRecursive = 2147483647
if seleccion == -1 or seleccion == 0:
strScraper = 'metadata.themoviedb.org'
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.themoviedb.org/settings.xml")
elif seleccion == 1:
strScraper = 'metadata.universal'
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.universal/settings.xml")
settings_data = filetools.read(path_settings)
strSettings = ' '.join(settings_data.split()).replace("> <", "><")
strSettings = strSettings.replace("\"","\'")
strActualizar = "¿Desea configurar este Scraper en español como opción por defecto para películas?"
if not videolibrarypath.endswith(sep):
videolibrarypath += sep
strPath = videolibrarypath + config.get_setting("folder_movies") + sep
else:
strContent = 'tvshows'
scanRecursive = 0
if seleccion == -1 or seleccion == 0:
strScraper = 'metadata.tvdb.com'
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvdb.com/settings.xml")
elif seleccion == 1:
strScraper = 'metadata.tvshows.themoviedb.org'
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
settings_data = filetools.read(path_settings)
strSettings = ' '.join(settings_data.split()).replace("> <", "><")
strSettings = strSettings.replace("\"","\'")
strActualizar = "¿Desea configurar este Scraper en español como opción por defecto para series?"
if not videolibrarypath.endswith(sep):
videolibrarypath += sep
strPath = videolibrarypath + config.get_setting("folder_tvshows") + sep
logger.info("%s: %s" % (content_type, strPath))
# Comprobamos si ya existe strPath en la BD para evitar duplicados
sql = 'SELECT idPath FROM path where strPath="%s"' % strPath
nun_records, records = execute_sql_kodi(sql)
sql = ""
if nun_records == 0:
# Insertamos el scraper
sql = 'INSERT INTO path (idPath, strPath, strContent, strScraper, scanRecursive, useFolderNames, ' \
'strSettings, noUpdate, exclude, idParentPath) VALUES (%s, "%s", "%s", "%s", %s, 0, ' \
'"%s", 0, 0, %s)' % (
idPath, strPath, strContent, strScraper, scanRecursive, strSettings, idParentPath)
else:
if not silent:
# Preguntar si queremos configurar themoviedb.org como opcion por defecto
actualizar = platformtools.dialog_yesno(config.get_localized_string(70098), strActualizar)
else:
actualizar = True
if actualizar:
# Actualizamos el scraper
idPath = records[0][0]
sql = 'UPDATE path SET strContent="%s", strScraper="%s", scanRecursive=%s, strSettings="%s" ' \
'WHERE idPath=%s' % (strContent, strScraper, scanRecursive, strSettings, idPath)
if sql:
nun_records, records = execute_sql_kodi(sql)
if nun_records == 1:
continuar = True
if not continuar:
msg_text = config.get_localized_string(60055)
if not continuar:
heading = config.get_localized_string(70102) % content_type
elif content_type == 'SERIES' and not xbmc.getCondVisibility(
'System.HasAddon(metadata.tvshows.themoviedb.org)'):
heading = config.get_localized_string(70103) % content_type
msg_text = config.get_localized_string(60058)
else:
heading = config.get_localized_string(70103) % content_type
msg_text = config.get_localized_string(70104)
platformtools.dialog_notification(heading, msg_text, icon=1, time=3000)
logger.info("%s: %s" % (heading, msg_text))
def execute_sql_kodi(sql):
"""
Ejecuta la consulta sql contra la base de datos de kodi
@param sql: Consulta sql valida
@type sql: str
@return: Numero de registros modificados o devueltos por la consulta
@rtype nun_records: int
@return: lista con el resultado de la consulta
@rtype records: list of tuples
"""
logger.info()
file_db = ""
nun_records = 0
records = None
# Buscamos el archivo de la BBDD de videos segun la version de kodi
video_db = config.get_platform(True)['video_db']
if video_db:
file_db = filetools.join(xbmc.translatePath("special://userdata/Database"), video_db)
# metodo alternativo para localizar la BBDD
if not file_db or not filetools.exists(file_db):
file_db = ""
for f in filetools.listdir(xbmc.translatePath("special://userdata/Database")):
path_f = filetools.join(xbmc.translatePath("special://userdata/Database"), f)
if filetools.isfile(path_f) and f.lower().startswith('myvideos') and f.lower().endswith('.db'):
file_db = path_f
break
if file_db:
logger.info("Archivo de BD: %s" % file_db)
conn = None
try:
import sqlite3
conn = sqlite3.connect(file_db)
cursor = conn.cursor()
logger.info("Ejecutando sql: %s" % sql)
cursor.execute(sql)
conn.commit()
records = cursor.fetchall()
if sql.lower().startswith("select"):
nun_records = len(records)
if nun_records == 1 and records[0][0] is None:
nun_records = 0
records = []
else:
nun_records = conn.total_changes
conn.close()
logger.info("Consulta ejecutada. Registros: %s" % nun_records)
except:
logger.error("Error al ejecutar la consulta sql")
if conn:
conn.close()
else:
logger.debug("Base de datos no encontrada")
return nun_records, records
def add_sources(path):
logger.info()
from xml.dom import minidom
SOURCES_PATH = xbmc.translatePath("special://userdata/sources.xml")
if os.path.exists(SOURCES_PATH):
xmldoc = minidom.parse(SOURCES_PATH)
else:
# Crear documento
xmldoc = minidom.Document()
nodo_sources = xmldoc.createElement("sources")
for type in ['programs', 'video', 'music', 'picture', 'files']:
nodo_type = xmldoc.createElement(type)
element_default = xmldoc.createElement("default")
element_default.setAttribute("pathversion", "1")
nodo_type.appendChild(element_default)
nodo_sources.appendChild(nodo_type)
xmldoc.appendChild(nodo_sources)
# Buscamos el nodo video
nodo_video = xmldoc.childNodes[0].getElementsByTagName("video")[0]
# Buscamos el path dentro de los nodos_path incluidos en el nodo_video
nodos_paths = nodo_video.getElementsByTagName("path")
list_path = [p.firstChild.data for p in nodos_paths]
logger.debug(list_path)
if path in list_path:
logger.debug("La ruta %s ya esta en sources.xml" % path)
return
logger.debug("La ruta %s NO esta en sources.xml" % path)
# Si llegamos aqui es por q el path no esta en sources.xml, asi q lo incluimos
nodo_source = xmldoc.createElement("source")
# Nodo <name>
nodo_name = xmldoc.createElement("name")
sep = os.sep
if path.startswith("special://") or path.startswith("smb://"):
sep = "/"
name = path
if path.endswith(sep):
name = path[:-1]
nodo_name.appendChild(xmldoc.createTextNode(name.rsplit(sep)[-1]))
nodo_source.appendChild(nodo_name)
# Nodo <path>
nodo_path = xmldoc.createElement("path")
nodo_path.setAttribute("pathversion", "1")
nodo_path.appendChild(xmldoc.createTextNode(path))
nodo_source.appendChild(nodo_path)
# Nodo <allowsharing>
nodo_allowsharing = xmldoc.createElement("allowsharing")
nodo_allowsharing.appendChild(xmldoc.createTextNode('true'))
nodo_source.appendChild(nodo_allowsharing)
# Añadimos <source> a <video>
nodo_video.appendChild(nodo_source)
# Guardamos los cambios
filetools.write(SOURCES_PATH,
'\n'.join([x for x in xmldoc.toprettyxml().encode("utf-8").splitlines() if x.strip()]))
def ask_set_content(flag, silent=False):
logger.info()
logger.debug("videolibrary_kodi_flag %s" % config.get_setting("videolibrary_kodi_flag"))
logger.debug("videolibrary_kodi %s" % config.get_setting("videolibrary_kodi"))
def do_config():
logger.debug("hemos aceptado")
config.set_setting("videolibrary_kodi", True)
set_content("movie", silent=True)
set_content("tvshow", silent=True)
add_sources(config.get_setting("videolibrarypath"))
add_sources(config.get_setting("downloadpath"))
if not silent:
heading = config.get_localized_string(59971)
linea1 = config.get_localized_string(70105)
linea2 = config.get_localized_string(70106)
if platformtools.dialog_yesno(heading, linea1, linea2):
do_config()
else:
# no hemos aceptado
config.set_setting("videolibrary_kodi", False)
else:
do_config()
config.set_setting("videolibrary_kodi_flag", flag)