This commit is contained in:
Alfa
2017-07-28 19:37:39 -04:00
parent 60e4685ce8
commit 3cc42f282f
1046 changed files with 162392 additions and 9 deletions

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__), "..", "..")))

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 config
from core import downloadtools
from core import 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('Descargando...', 'Cierra esta ventana para empezar la reproducción')
dialog.update(0)
while not cancelled and download_thread.isAlive():
dialog.update(download_thread.get_progress(), "Cancela esta ventana para empezar la reproducción",
"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:
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

View File

@@ -0,0 +1,452 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# XBMC Launcher (xbmc / kodi)
# ------------------------------------------------------------
import os
import sys
import urllib2
from core import channeltools
from core import config
from core import videolibrarytools
from core import logger
from core import scrapertools
from core import servertools
from core.item import Item
from platformcode import platformtools
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()
# 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:
item = Item(channel="channelselector", action="getmainlist", viewmode="movie")
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
if item.action == "getmainlist":
import channelselector
# # Check for updates only on first screen
# if config.get_setting("check_for_plugin_updates") == True:
# logger.info("Check for plugin updates enabled")
# from core import updater
#
# try:
# config.set_setting("plugin_updates_available", 0)
# new_published_version_tag, number_of_updates = updater.get_available_updates()
#
# config.set_setting("plugin_updates_available", number_of_updates)
# itemlist = channelselector.getmainlist()
#
# if new_published_version_tag != "":
# platformtools.dialog_notification(new_published_version_tag + " disponible",
# "Ya puedes descargar la nueva versión del plugin\n"
# "desde el listado principal")
#
# itemlist = channelselector.getmainlist()
# itemlist.insert(0, Item(title="Descargar version " + new_published_version_tag,
# version=new_published_version_tag, channel="updater",
# action="update",
# thumbnail=channelselector.get_thumb("thumb_update.png")))
# except:
# import traceback
# logger.error(traceback.format_exc())
# platformtools.dialog_ok("No se puede conectar", "No ha sido posible comprobar",
# "si hay actualizaciones")
# logger.error("Fallo al verificar la actualización")
# config.set_setting("plugin_updates_available", 0)
# itemlist = channelselector.getmainlist()
#
# else:
# logger.info("Check for plugin updates disabled")
# config.set_setting("plugin_updates_available", 0)
# itemlist = channelselector.getmainlist()
itemlist = channelselector.getmainlist()
platformtools.render_items(itemlist, item)
# # Action for updating plugin
# elif item.action == "update":
#
# from core import updater
# updater.update(item)
# config.set_setting("plugin_updates_available", 0)
#
# import xbmc
# xbmc.executebuiltin("Container.Refresh")
# 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
# 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_pin") != "":
tecleado = platformtools.dialog_input("", "Contraseña para canales de adultos", True)
if tecleado is None or tecleado != config.get_setting("adult_pin"):
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":
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("alfa", "No hay nada para reproducir")
# 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)
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:
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 = "Ruta: " + xbmc.translatePath("special://logpath") + log_name
except:
log_message = ""
if canal:
platformtools.dialog_ok(
"Error inesperado en el canal " + canal,
"Puede deberse a un fallo de conexión, la web del canal "
"ha cambiado su estructura, o un error interno de alfa.",
"Para saber más detalles, consulta el log.", log_message)
else:
platformtools.dialog_ok(
"Se ha producido un error en alfa",
"Comprueba el log para ver mas detalles del error.",
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 = [['Ver en', '[V]'],
['Descargar en', '[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
# 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
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('alfa', 'Cargando...')
p_dialog.update(0, '')
itemlist = videolibrary.findvideos(item)
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)
p_dialog.update(100, '')
xbmc.sleep(500)
p_dialog.close()
if len(itemlist) > 0:
# 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)

View File

@@ -0,0 +1,737 @@
# -*- 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 core import config
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 = scrapertools.downloadpage(url)
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.get_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.get_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

View File

@@ -0,0 +1,964 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# platformtools
# ------------------------------------------------------------
# Herramientas responsables de adaptar los diferentes
# cuadros de dialogo a una plataforma en concreto,
# en este caso Kodi.
# version 2.0
# ------------------------------------------------------------
import os
import sys
import urllib
import xbmc
import xbmcgui
import xbmcplugin
from core import config
from core import logger
from core.item import Item
def dialog_ok(heading, line1, line2="", line3=""):
dialog = xbmcgui.Dialog()
return dialog.ok(heading, line1, line2, line3)
def dialog_notification(heading, message, icon=0, time=5000, sound=True):
dialog = xbmcgui.Dialog()
try:
l_icono = xbmcgui.NOTIFICATION_INFO, xbmcgui.NOTIFICATION_WARNING, xbmcgui.NOTIFICATION_ERROR
dialog.notification(heading, message, l_icono[icon], time, sound)
except:
dialog_ok(heading, message)
def dialog_yesno(heading, line1, line2="", line3="", nolabel="No", yeslabel="Si", autoclose=""):
dialog = xbmcgui.Dialog()
if autoclose:
return dialog.yesno(heading, line1, line2, line3, nolabel, yeslabel, autoclose)
else:
return dialog.yesno(heading, line1, line2, line3, nolabel, yeslabel)
def dialog_select(heading, _list):
return xbmcgui.Dialog().select(heading, _list)
def dialog_progress(heading, line1, line2=" ", line3=" "):
dialog = xbmcgui.DialogProgress()
dialog.create(heading, line1, line2, line3)
return dialog
def dialog_progress_bg(heading, message=""):
try:
dialog = xbmcgui.DialogProgressBG()
dialog.create(heading, message)
return dialog
except:
return dialog_progress(heading, message)
def dialog_input(default="", heading="", hidden=False):
keyboard = xbmc.Keyboard(default, heading, hidden)
keyboard.doModal()
if keyboard.isConfirmed():
return keyboard.getText()
else:
return None
def dialog_numeric(_type, heading, default=""):
dialog = xbmcgui.Dialog()
d = dialog.numeric(_type, heading, default)
return d
def itemlist_refresh():
xbmc.executebuiltin("Container.Refresh")
def itemlist_update(item):
xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ")")
def render_items(itemlist, parent_item):
"""
Función encargada de mostrar el itemlist en kodi, se pasa como parametros el itemlist y el item del que procede
@type itemlist: list
@param itemlist: lista de elementos a mostrar
@type parent_item: item
@param parent_item: elemento padre
"""
# Si el itemlist no es un list salimos
if not type(itemlist) == list:
return
# Si no hay ningun item, mostramos un aviso
if not len(itemlist):
itemlist.append(Item(title="No hay elementos que mostrar"))
# Recorremos el itemlist
for item in itemlist:
# logger.debug(item)
# Si el item no contiene categoria, le ponemos la del item padre
if item.category == "":
item.category = parent_item.category
# Si el item no contiene fanart, le ponemos el del item padre
if item.fanart == "":
item.fanart = parent_item.fanart
# Formatear titulo
if item.text_color:
item.title = '[COLOR %s]%s[/COLOR]' % (item.text_color, item.title)
if item.text_bold:
item.title = '[B]%s[/B]' % item.title
if item.text_italic:
item.title = '[I]%s[/I]' % item.title
# Añade headers a las imagenes si estan en un servidor con cloudflare
from core import httptools
item.thumbnail = httptools.get_url_headers(item.thumbnail)
item.fanart = httptools.get_url_headers(item.fanart)
# IconImage para folder y video
if item.folder:
icon_image = "DefaultFolder.png"
else:
icon_image = "DefaultVideo.png"
# Creamos el listitem
listitem = xbmcgui.ListItem(item.title, iconImage=icon_image, thumbnailImage=item.thumbnail)
# Ponemos el fanart
if item.fanart:
listitem.setProperty('fanart_image', item.fanart)
else:
listitem.setProperty('fanart_image', os.path.join(config.get_runtime_path(), "fanart.jpg"))
# TODO: ¿Se puede eliminar esta linea? yo no he visto que haga ningun efecto.
xbmcplugin.setPluginFanart(int(sys.argv[1]), os.path.join(config.get_runtime_path(), "fanart.jpg"))
# Añadimos los infoLabels
set_infolabels(listitem, item)
# Montamos el menu contextual
context_commands = set_context_commands(item, parent_item)
# Añadimos el item
listitem.addContextMenuItems(context_commands, replaceItems=True)
if not item.totalItems:
item.totalItems = 0
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url='%s?%s' % (sys.argv[0], item.tourl()),
listitem=listitem, isFolder=item.folder,
totalItems=item.totalItems)
# Fijar los tipos de vistas...
if config.get_setting("forceview") == True:
# ...forzamos segun el viewcontent
xbmcplugin.setContent(int(sys.argv[1]), parent_item.viewcontent)
# logger.debug(parent_item)
elif parent_item.channel not in ["channelselector", ""]:
# ... o segun el canal
xbmcplugin.setContent(int(sys.argv[1]), "movies")
# Fijamos el "breadcrumb"
xbmcplugin.setPluginCategory(handle=int(sys.argv[1]), category=parent_item.category.capitalize())
# No ordenar items
xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=xbmcplugin.SORT_METHOD_NONE)
# Cerramos el directorio
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
# Fijar la vista
if config.get_setting("forceview") == True:
viewmode_id = get_viewmode_id(parent_item)
xbmc.executebuiltin("Container.SetViewMode(%s)" % viewmode_id)
def get_viewmode_id(parent_item):
# viewmode_json habria q guardarlo en un archivo y crear un metodo para q el user fije sus preferencias en:
# user_files, user_movies, user_tvshows, user_season y user_episodes.
viewmode_json = {'skin.confluence': {'default_files': 50,
'default_movies': 515,
'default_tvshows': 508,
'default_seasons': 503,
'default_episodes': 504,
'view_list': 50,
'view_thumbnails': 500,
'view_movie_with_plot': 503},
'skin.estuary': {'default_files': 50,
'default_movies': 54,
'default_tvshows': 502,
'default_seasons': 500,
'default_episodes': 53,
'view_list': 50,
'view_thumbnails': 500,
'view_movie_with_plot': 54}}
# Si el parent_item tenia fijado un viewmode usamos esa vista...
if parent_item.viewmode == 'movie':
# Remplazamos el antiguo viewmode 'movie' por 'thumbnails'
parent_item.viewmode = 'thumbnails'
if parent_item.viewmode in ["list", "movie_with_plot", "thumbnails"]:
view_name = "view_" + parent_item.viewmode
'''elif isinstance(parent_item.viewmode, int):
# only for debug
viewName = parent_item.viewmode'''
# ...sino ponemos la vista por defecto en funcion del viewcontent
else:
view_name = "default_" + parent_item.viewcontent
skin_name = xbmc.getSkinDir()
if skin_name not in viewmode_json:
skin_name = 'skin.confluence'
view_skin = viewmode_json[skin_name]
return view_skin.get(view_name, 50)
def set_infolabels(listitem, item, player=False):
"""
Metodo para pasar la informacion al listitem (ver tmdb.set_InfoLabels() )
item.infoLabels es un dicionario con los pares de clave/valor descritos en:
http://mirrors.xbmc.org/docs/python-docs/14.x-helix/xbmcgui.html#ListItem-setInfo
@param listitem: objeto xbmcgui.ListItem
@type listitem: xbmcgui.ListItem
@param item: objeto Item que representa a una pelicula, serie o capitulo
@type item: item
"""
if item.infoLabels:
if 'mediatype' not in item.infoLabels:
item.infoLabels['mediatype'] = item.contentType
listitem.setInfo("video", item.infoLabels)
if player and not item.contentTitle:
if item.fulltitle:
listitem.setInfo("video", {"Title": item.fulltitle})
else:
listitem.setInfo("video", {"Title": item.title})
elif not player:
listitem.setInfo("video", {"Title": item.title})
# Añadido para Kodi Krypton (v17)
if config.get_platform(True)['num_version'] >= 17.0:
listitem.setArt({"poster": item.thumbnail})
def set_context_commands(item, parent_item):
"""
Función para generar los menus contextuales.
1. Partiendo de los datos de item.context
a. Metodo antiguo item.context tipo str separando las opciones por "|" (ejemplo: item.context = "1|2|3")
(solo predefinidos)
b. Metodo list: item.context es un list con las diferentes opciones del menu:
- Predefinidos: Se cargara una opcion predefinida con un nombre.
item.context = ["1","2","3"]
- dict(): Se cargara el item actual modificando los campos que se incluyan en el dict() en caso de
modificar los campos channel y action estos serán guardados en from_channel y from_action.
item.context = [{"title":"Nombre del menu", "action": "action del menu",
"channel":"channel del menu"}, {...}]
2. Añadiendo opciones segun criterios
Se pueden añadir opciones al menu contextual a items que cumplan ciertas condiciones.
3. Añadiendo opciones a todos los items
Se pueden añadir opciones al menu contextual para todos los items
4. Se pueden deshabilitar las opciones del menu contextual añadiendo un comando 'no_context' al item.context.
Las opciones que Kodi, el skin u otro añadido añada al menu contextual no se pueden deshabilitar.
@param item: elemento que contiene los menu contextuales
@type item: item
@param parent_item:
@type parent_item: item
"""
context_commands = []
num_version_xbmc = config.get_platform(True)['num_version']
# Creamos un list con las diferentes opciones incluidas en item.context
if type(item.context) == str:
context = item.context.split("|")
elif type(item.context) == list:
context = item.context
else:
context = []
# Opciones segun item.context
for command in context:
# Predefinidos
if type(command) == str:
if command == "no_context":
return []
# Formato dict
if type(command) == dict:
# Los parametros del dict, se sobreescriben al nuevo context_item en caso de sobreescribir "action" y
# "channel", los datos originales se guardan en "from_action" y "from_channel"
if "action" in command:
command["from_action"] = item.action
if "channel" in command:
command["from_channel"] = item.channel
if "goto" in command:
context_commands.append((command["title"], "XBMC.Container.Refresh (%s?%s)" %
(sys.argv[0], item.clone(**command).tourl())))
else:
context_commands.append(
(command["title"], "XBMC.RunPlugin(%s?%s)" % (sys.argv[0], item.clone(**command).tourl())))
# Opciones segun criterios, solo si el item no es un tag (etiqueta), ni es "Añadir a la videoteca", etc...
if item.action and item.action not in ["add_pelicula_to_library", "add_serie_to_library", "buscartrailer"]:
# Mostrar informacion: si el item tiene plot suponemos q es una serie, temporada, capitulo o pelicula
if item.infoLabels['plot'] and (num_version_xbmc < 17.0 or item.contentType == 'season'):
context_commands.append(("Información", "XBMC.Action(Info)"))
# ExtendedInfo: Si esta instalado el addon y se cumplen una serie de condiciones
if xbmc.getCondVisibility('System.HasAddon(script.extendedinfo)') \
and config.get_setting("extended_info") == True:
if item.contentType == "episode" and item.contentEpisodeNumber and item.contentSeason \
and (item.infoLabels['tmdb_id'] or item.contentSerieName):
param = "tvshow_id =%s, tvshow=%s, season=%s, episode=%s" \
% (item.infoLabels['tmdb_id'], item.contentSerieName, item.contentSeason,
item.contentEpisodeNumber)
context_commands.append(("ExtendedInfo",
"XBMC.RunScript(script.extendedinfo,info=extendedepisodeinfo,%s)" % param))
elif item.contentType == "season" and item.contentSeason \
and (item.infoLabels['tmdb_id'] or item.contentSerieName):
param = "tvshow_id =%s,tvshow=%s, season=%s" \
% (item.infoLabels['tmdb_id'], item.contentSerieName, item.contentSeason)
context_commands.append(("ExtendedInfo",
"XBMC.RunScript(script.extendedinfo,info=seasoninfo,%s)" % param))
elif item.contentType == "tvshow" and (item.infoLabels['tmdb_id'] or item.infoLabels['tvdb_id'] or
item.infoLabels['imdb_id'] or item.contentSerieName):
param = "id =%s,tvdb_id=%s,imdb_id=%s,name=%s" \
% (item.infoLabels['tmdb_id'], item.infoLabels['tvdb_id'], item.infoLabels['imdb_id'],
item.contentSerieName)
context_commands.append(("ExtendedInfo",
"XBMC.RunScript(script.extendedinfo,info=extendedtvinfo,%s)" % param))
elif item.contentType == "movie" and (item.infoLabels['tmdb_id'] or item.infoLabels['imdb_id'] or
item.contentTitle):
param = "id =%s,imdb_id=%s,name=%s" \
% (item.infoLabels['tmdb_id'], item.infoLabels['imdb_id'], item.contentTitle)
context_commands.append(("ExtendedInfo",
"XBMC.RunScript(script.extendedinfo,info=extendedinfo,%s)" % param))
# InfoPlus
if config.get_setting("infoplus") == True:
if item.infoLabels['tmdb_id'] or item.infoLabels['imdb_id'] or item.infoLabels['tvdb_id'] or \
(item.contentTitle and item.infoLabels["year"]) or item.contentSerieName:
context_commands.append(("InfoPlus", "XBMC.RunPlugin(%s?%s)" % (sys.argv[0], item.clone(
channel="infoplus", action="start", from_channel=item.channel).tourl())))
# Ir al Menu Principal (channel.mainlist)
if parent_item.channel not in ["news", "channelselector"] and item.action != "mainlist" \
and parent_item.action != "mainlist":
context_commands.append(("Ir al Menu Principal", "XBMC.Container.Refresh (%s?%s)" %
(sys.argv[0], Item(channel=item.channel, action="mainlist").tourl())))
# Añadir a Favoritos
if num_version_xbmc < 17.0 and \
((item.channel not in ["favorites", "videolibrary", "help", ""]
or item.action in ["update_videolibrary"]) and parent_item.channel != "favorites"):
context_commands.append((config.get_localized_string(30155), "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(channel="favoritos", action="addFavourite",
from_channel=item.channel,
from_action=item.action).tourl())))
if item.channel != "videolibrary":
# Añadir Serie a la videoteca
if item.action in ["episodios", "get_episodios"] and item.contentSerieName:
context_commands.append(("Añadir Serie a Videoteca", "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(action="add_serie_to_library",
from_action=item.action).tourl())))
# Añadir Pelicula a videoteca
elif item.action in ["detail", "findvideos"] and item.contentType == 'movie' and item.contentTitle:
context_commands.append(("Añadir Pelicula a Videoteca", "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(action="add_pelicula_to_library",
from_action=item.action).tourl())))
if item.channel != "downloads":
# Descargar pelicula
if item.contentType == "movie" and item.contentTitle:
context_commands.append(("Descargar Pelicula", "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(channel="downloads", action="save_download",
from_channel=item.channel, from_action=item.action)
.tourl())))
elif item.contentSerieName:
# Descargar serie
if item.contentType == "tvshow":
context_commands.append(("Descargar Serie", "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(channel="downloads", action="save_download",
from_channel=item.channel,
from_action=item.action).tourl())))
# Descargar episodio
if item.contentType == "episode":
context_commands.append(("Descargar Episodio", "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(channel="downloads", action="save_download",
from_channel=item.channel,
from_action=item.action).tourl())))
# Descargar temporada
if item.contentType == "season":
context_commands.append(("Descargar Temporada", "XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(channel="downloads", action="save_download",
from_channel=item.channel,
from_action=item.action).tourl())))
# Abrir configuración
if parent_item.channel not in ["setting", "news", "search"]:
context_commands.append(("Abrir Configuración", "XBMC.Container.Update(%s?%s)" %
(sys.argv[0], Item(channel="setting", action="mainlist").tourl())))
# Buscar Trailer
if item.action == "findvideos" or "buscar_trailer" in context:
context_commands.append(("Buscar Trailer", "XBMC.RunPlugin(%s?%s)" % (sys.argv[0], item.clone(
channel="trailertools", action="buscartrailer", contextual=True).tourl())))
# Añadir SuperFavourites al menu contextual (1.0.53 o superior necesario)
sf_file_path = xbmc.translatePath("special://home/addons/plugin.program.super.favourites/LaunchSFMenu.py")
check_sf = os.path.exists(sf_file_path)
if check_sf and xbmc.getCondVisibility('System.HasAddon("plugin.program.super.favourites")'):
context_commands.append(("Super Favourites Menu",
"XBMC.RunScript(special://home/addons/plugin.program.super.favourites/LaunchSFMenu.py)"))
return sorted(context_commands, key=lambda comand: comand[0])
def is_playing():
return xbmc.Player().isPlaying()
def play_video(item, strm=False):
logger.info()
# logger.debug(item.tostring('\n'))
if item.channel == 'downloads':
logger.info("Reproducir video local: %s [%s]" % (item.title, item.url))
xlistitem = xbmcgui.ListItem(path=item.url, thumbnailImage=item.thumbnail)
set_infolabels(xlistitem, item, True)
xbmc.Player().play(item.url, xlistitem)
return
default_action = config.get_setting("default_action")
logger.info("default_action=%s" % default_action)
# Abre el diálogo de selección para ver las opciones disponibles
opciones, video_urls, seleccion, salir = get_dialogo_opciones(item, default_action, strm)
if salir:
return
# se obtienen la opción predeterminada de la configuración del addon
seleccion = get_seleccion(default_action, opciones, seleccion, video_urls)
if seleccion < 0: # Cuadro cancelado
return
logger.info("seleccion=%d" % seleccion)
logger.info("seleccion=%s" % opciones[seleccion])
# se ejecuta la opcion disponible, jdwonloader, descarga, favoritos, añadir a la videoteca... SI NO ES PLAY
salir = set_opcion(item, seleccion, opciones, video_urls)
if salir:
return
# obtenemos el video seleccionado
mediaurl, view, mpd = get_video_seleccionado(item, seleccion, video_urls)
if mediaurl == "":
return
# se obtiene la información del video.
if not item.contentThumbnail:
xlistitem = xbmcgui.ListItem(path=mediaurl, thumbnailImage=item.thumbnail)
else:
xlistitem = xbmcgui.ListItem(path=mediaurl, thumbnailImage=item.contentThumbnail)
set_infolabels(xlistitem, item, True)
# si se trata de un vídeo en formato mpd, se configura el listitem para reproducirlo
# con el addon inpustreamaddon implementado en Kodi 17
if mpd:
xlistitem.setProperty('inputstreamaddon', 'inputstream.adaptive')
xlistitem.setProperty('inputstream.adaptive.manifest_type', 'mpd')
# se lanza el reproductor
set_player(item, xlistitem, mediaurl, view, strm)
def stop_video():
xbmc.Player().stop()
def get_seleccion(default_action, opciones, seleccion, video_urls):
# preguntar
if default_action == 0:
# "Elige una opción"
seleccion = dialog_select(config.get_localized_string(30163), opciones)
# Ver en calidad baja
elif default_action == 1:
seleccion = 0
# Ver en alta calidad
elif default_action == 2:
seleccion = len(video_urls) - 1
else:
seleccion = 0
return seleccion
def show_channel_settings(**kwargs):
"""
Muestra un cuadro de configuracion personalizado para cada canal y guarda los datos al cerrarlo.
Los parámetros que se le pasan se puede ver en la el método al que se llama
@return: devuelve la ventana con los elementos
@rtype: SettingsWindow
"""
from xbmc_config_menu import SettingsWindow
return SettingsWindow("ChannelSettings.xml", config.get_runtime_path()).start(**kwargs)
def show_video_info(*args, **kwargs):
"""
Muestra una ventana con la info del vídeo.
Los parámetros que se le pasan se puede ver en la el método al que se llama
@return: devuelve la ventana con los elementos
@rtype: InfoWindow
"""
from xbmc_info_window import InfoWindow
return InfoWindow("InfoWindow.xml", config.get_runtime_path()).start(*args, **kwargs)
def show_recaptcha(key, referer):
from recaptcha import Recaptcha
return Recaptcha("Recaptcha.xml", config.get_runtime_path()).Start(key, referer)
def alert_no_disponible_server(server):
# 'El vídeo ya no está en %s' , 'Prueba en otro servidor o en otro canal'
dialog_ok(config.get_localized_string(30055), (config.get_localized_string(30057) % server),
config.get_localized_string(30058))
def alert_unsopported_server():
# 'Servidor no soportado o desconocido' , 'Prueba en otro servidor o en otro canal'
dialog_ok(config.get_localized_string(30065), config.get_localized_string(30058))
def handle_wait(time_to_wait, title, text):
logger.info("handle_wait(time_to_wait=%d)" % time_to_wait)
espera = dialog_progress(' ' + title, "")
secs = 0
increment = int(100 / time_to_wait)
cancelled = False
while secs < time_to_wait:
secs += 1
percent = increment * secs
secs_left = str((time_to_wait - secs))
remaining_display = ' Espera ' + secs_left + ' segundos para que comience el vídeo...'
espera.update(percent, ' ' + text, remaining_display)
xbmc.sleep(1000)
if espera.iscanceled():
cancelled = True
break
if cancelled:
logger.info('Espera cancelada')
return False
else:
logger.info('Espera finalizada')
return True
def get_dialogo_opciones(item, default_action, strm):
logger.info()
# logger.debug(item.tostring('\n'))
from core import servertools
opciones = []
error = False
try:
item.server = item.server.lower()
except AttributeError:
item.server = ""
if item.server == "":
item.server = "directo"
# Si no es el modo normal, no muestra el diálogo porque cuelga XBMC
muestra_dialogo = (config.get_setting("player_mode") == 0 and not strm)
# Extrae las URL de los vídeos, y si no puedes verlo te dice el motivo
# Permitir varias calidades para server "directo"
if item.video_urls:
video_urls, puedes, motivo = item.video_urls, True, ""
else:
video_urls, puedes, motivo = servertools.resolve_video_urls_for_playing(
item.server, item.url, item.password, muestra_dialogo)
seleccion = 0
# Si puedes ver el vídeo, presenta las opciones
if puedes:
for video_url in video_urls:
opciones.append(config.get_localized_string(30151) + " " + video_url[0])
if item.server == "local":
opciones.append(config.get_localized_string(30164))
else:
# "Descargar"
opcion = config.get_localized_string(30153)
opciones.append(opcion)
if item.isFavourite:
# "Quitar de favoritos"
opciones.append(config.get_localized_string(30154))
else:
# "Añadir a favoritos"
opciones.append(config.get_localized_string(30155))
if not strm and item.contentType == 'movie':
# "Añadir a videoteca"
opciones.append(config.get_localized_string(30161))
if default_action == "3":
seleccion = len(opciones) - 1
# Busqueda de trailers en youtube
if item.channel not in ["Trailer", "ecarteleratrailers"]:
# "Buscar Trailer"
opciones.append(config.get_localized_string(30162))
# Si no puedes ver el vídeo te informa
else:
if item.server != "":
if "<br/>" in motivo:
dialog_ok("No puedes ver ese vídeo porque...", motivo.split("<br/>")[0], motivo.split("<br/>")[1],
item.url)
else:
dialog_ok("No puedes ver ese vídeo porque...", motivo, item.url)
else:
dialog_ok("No puedes ver ese vídeo porque...", "El servidor donde está alojado no está",
"soportado en alfa todavía", item.url)
if item.channel == "favoritos":
# "Quitar de favoritos"
opciones.append(config.get_localized_string(30154))
if len(opciones) == 0:
error = True
return opciones, video_urls, seleccion, error
def set_opcion(item, seleccion, opciones, video_urls):
logger.info()
# logger.debug(item.tostring('\n'))
salir = False
# No ha elegido nada, lo más probable porque haya dado al ESC
if seleccion == -1:
# Para evitar el error "Uno o más elementos fallaron" al cancelar la selección desde fichero strm
listitem = xbmcgui.ListItem(item.title, iconImage="DefaultVideo.png", thumbnailImage=item.thumbnail)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# "Descargar"
elif opciones[seleccion] == config.get_localized_string(30153):
from channels import downloads
if item.contentType == "list" or item.contentType == "tvshow":
item.contentType = "video"
item.play_menu = True
downloads.save_download(item)
salir = True
# "Quitar de favoritos"
elif opciones[seleccion] == config.get_localized_string(30154):
from channels import favorites
favorites.delFavourite(item)
salir = True
# "Añadir a favoritos":
elif opciones[seleccion] == config.get_localized_string(30155):
from channels import favorites
item.from_channel = "favoritos"
favorites.addFavourite(item)
salir = True
# "Añadir a videoteca":
elif opciones[seleccion] == config.get_localized_string(30161):
titulo = item.fulltitle
if titulo == "":
titulo = item.title
new_item = item.clone(title=titulo, action="play_from_library", category="Cine",
fulltitle=item.fulltitle, channel=item.channel)
from core import videolibrarytools
videolibrarytools.add_movie(new_item)
salir = True
# "Buscar Trailer":
elif opciones[seleccion] == config.get_localized_string(30162):
config.set_setting("subtitulo", False)
xbmc.executebuiltin("XBMC.RunPlugin(%s?%s)" %
(sys.argv[0], item.clone(channel="trailertools", action="buscartrailer",
contextual=True).tourl()))
salir = True
return salir
def get_video_seleccionado(item, seleccion, video_urls):
logger.info()
mediaurl = ""
view = False
wait_time = 0
mpd = False
# Ha elegido uno de los vídeos
if seleccion < len(video_urls):
mediaurl = video_urls[seleccion][1]
if len(video_urls[seleccion]) > 4:
wait_time = video_urls[seleccion][2]
item.subtitle = video_urls[seleccion][3]
mpd = True
elif len(video_urls[seleccion]) > 3:
wait_time = video_urls[seleccion][2]
item.subtitle = video_urls[seleccion][3]
elif len(video_urls[seleccion]) > 2:
wait_time = video_urls[seleccion][2]
view = True
# Si no hay mediaurl es porque el vídeo no está :)
logger.info("mediaurl=" + mediaurl)
if mediaurl == "":
if item.server == "unknown":
alert_unsopported_server()
else:
alert_no_disponible_server(item.server)
# Si hay un tiempo de espera (como en megaupload), lo impone ahora
if wait_time > 0:
continuar = handle_wait(wait_time, item.server, "Cargando vídeo...")
if not continuar:
mediaurl = ""
return mediaurl, view, mpd
def set_player(item, xlistitem, mediaurl, view, strm):
logger.info()
logger.debug("item:\n" + item.tostring('\n'))
# Movido del conector "torrent" aqui
if item.server == "torrent":
play_torrent(item, xlistitem, mediaurl)
return
# Si es un fichero strm no hace falta el play
elif strm:
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xlistitem)
if item.subtitle != "":
xbmc.sleep(2000)
xbmc.Player().setSubtitles(item.subtitle)
else:
logger.info("player_mode=%s" % config.get_setting("player_mode"))
logger.info("mediaurl=" + mediaurl)
if config.get_setting("player_mode") == 3 or "megacrypter.com" in mediaurl:
import download_and_play
download_and_play.download_and_play(mediaurl, "download_and_play.tmp", config.get_setting("downloadpath"))
return
elif config.get_setting("player_mode") == 0 or \
(config.get_setting("player_mode") == 3 and mediaurl.startswith("rtmp")):
# Añadimos el listitem a una lista de reproducción (playlist)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist.add(mediaurl, xlistitem)
# Reproduce
xbmc_player = xbmc.Player()
xbmc_player.play(playlist, xlistitem)
elif config.get_setting("player_mode") == 1:
logger.info("mediaurl :" + mediaurl)
logger.info("Tras setResolvedUrl")
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xbmcgui.ListItem(path=mediaurl))
elif config.get_setting("player_mode") == 2:
xbmc.executebuiltin("PlayMedia(" + mediaurl + ")")
# TODO MIRAR DE QUITAR VIEW
if item.subtitle != "" and view:
logger.info("Subtítulos externos: " + item.subtitle)
xbmc.sleep(2000)
xbmc.Player().setSubtitles(item.subtitle)
# si es un archivo de la videoteca enviar a marcar como visto
if strm or item.strm_path:
from platformcode import xbmc_videolibrary
xbmc_videolibrary.mark_auto_as_watched(item)
def play_torrent(item, xlistitem, mediaurl):
logger.info()
# Opciones disponibles para Reproducir torrents
torrent_options = list()
torrent_options.append(["Cliente interno (necesario libtorrent)"])
torrent_options.append(["Cliente interno MCT (necesario libtorrent)"])
# Plugins externos se encuentra en servers/torrent.json nodo clients
from core import filetools
from core import jsontools
torrent_clients = jsontools.get_node_from_file("torrent.json", "clients", filetools.join(config.get_runtime_path(),
"servers"))
for client in torrent_clients:
if xbmc.getCondVisibility('System.HasAddon("%s")' % client["id"]):
torrent_options.append(["Plugin externo: %s" % client["name"], client["url"]])
# todo permitir elegir opción por defecto
if len(torrent_options) > 1:
seleccion = dialog_select("Abrir torrent con...", [opcion[0] for opcion in torrent_options])
else:
seleccion = 0
# Plugins externos
if seleccion > 1:
mediaurl = urllib.quote_plus(item.url)
xbmc.executebuiltin("PlayMedia(" + torrent_options[seleccion][1] % mediaurl + ")")
if seleccion == 1:
from platformcode import mct
mct.play(mediaurl, xlistitem, subtitle=item.subtitle, item=item)
# Reproductor propio (libtorrent)
if seleccion == 0:
import time
played = False
debug = (config.get_setting("debug") == True)
# Importamos el cliente
from btserver import Client
client_tmp_path = config.get_setting("downloadpath")
if not client_tmp_path:
client_tmp_path = config.get_data_path()
# Iniciamos el cliente:
c = Client(url=mediaurl, is_playing_fnc=xbmc.Player().isPlaying, wait_time=None, timeout=10,
temp_path=os.path.join(client_tmp_path, "alfa-torrent"), print_status=debug)
# Mostramos el progreso
progreso = dialog_progress("Alfa - Torrent", "Iniciando...")
# Mientras el progreso no sea cancelado ni el cliente cerrado
while not c.closed:
try:
# Obtenemos el estado del torrent
s = c.status
if debug:
# Montamos las tres lineas con la info del torrent
txt = '%.2f%% de %.1fMB %s | %.1f kB/s' % \
(s.progress_file, s.file_size, s.str_state, s._download_rate)
txt2 = 'S: %d(%d) P: %d(%d) | DHT:%s (%d) | Trakers: %d' % \
(s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.dht_state, s.dht_nodes,
s.trackers)
txt3 = 'Origen Peers TRK: %d DHT: %d PEX: %d LSD %d ' % \
(s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers)
else:
txt = '%.2f%% de %.1fMB %s | %.1f kB/s' % \
(s.progress_file, s.file_size, s.str_state, s._download_rate)
txt2 = 'S: %d(%d) P: %d(%d)' % (s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete)
try:
txt3 = 'Deteniendo automaticamente en: %ss' % (int(s.timeout))
except:
txt3 = ''
progreso.update(s.buffer, txt, txt2, txt3)
time.sleep(0.5)
if progreso.iscanceled():
progreso.close()
if s.buffer == 100:
if dialog_yesno("Alfa - Torrent", "¿Deseas iniciar la reproduccion?"):
played = False
progreso = dialog_progress("Alfa - Torrent", "")
progreso.update(s.buffer, txt, txt2, txt3)
else:
progreso = dialog_progress("Alfa - Torrent", "")
break
else:
if dialog_yesno("Alfa - Torrent", "¿Deseas cancelar el proceso?"):
progreso = dialog_progress("Alfa - Torrent", "")
break
else:
progreso = dialog_progress("Alfa - Torrent", "")
progreso.update(s.buffer, txt, txt2, txt3)
# Si el buffer se ha llenado y la reproduccion no ha sido iniciada, se inicia
if s.buffer == 100 and not played:
# Cerramos el progreso
progreso.close()
# Obtenemos el playlist del torrent
videourl = c.get_play_list()
# Iniciamos el reproductor
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist.add(videourl, xlistitem)
xbmc_player = xbmc.Player()
xbmc_player.play(playlist)
# Marcamos como reproducido para que no se vuelva a iniciar
played = True
# 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)
# Y esperamos a que el reproductor se cierre
while xbmc.Player().isPlaying():
time.sleep(1)
# Cuando este cerrado, Volvemos a mostrar el dialogo
progreso = dialog_progress("Alfa - Torrent", "")
progreso.update(s.buffer, txt, txt2, txt3)
except:
import traceback
logger.error(traceback.format_exc())
break
progreso.update(100, "Terminando y eliminando datos", " ", " ")
# Detenemos el cliente
if not c.closed:
c.stop()
# Y cerramos el progreso
progreso.close()

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
import xbmcgui
from core import config
from core import httptools
from core import scrapertools
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):
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,269 @@
# -*- coding: utf-8 -*-
import os
import re
import string
import urllib
from unicodedata import normalize
import xbmc
import xbmcgui
from core import config
from core import 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

View File

@@ -0,0 +1,997 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# XBMC Config Menu
# ------------------------------------------------------------
import inspect
import os
import xbmcgui
from core import channeltools
from core import config
from core import logger
from core import servertools
class SettingsWindow(xbmcgui.WindowXMLDialog):
""" Clase derivada que permite utilizar cuadros de configuracion personalizados.
Esta clase deriva de xbmcgui.WindowXMLDialog y permite crear un cuadro de dialogo con controles del tipo:
Radio Button (bool), Cuadro de texto (text), Lista (list) y Etiquetas informativas (label).
Tambien podemos personalizar el cuadro añadiendole un titulo (title).
Metodo constructor:
SettingWindow(listado_controles, dict_values, title, callback, item)
Parametros:
listado_controles: (list) Lista de controles a incluir en la ventana, segun el siguiente esquema:
(opcional)list_controls= [
{'id': "nameControl1",
'type': "bool", # bool, text, list, label
'label': "Control 1: tipo RadioButton",
'color': '0xFFee66CC', # color del texto en formato ARGB hexadecimal
'default': True,
'enabled': True,
'visible': True
},
{'id': "nameControl2",
'type': "text", # bool, text, list, label
'label': "Control 2: tipo Cuadro de texto",
'color': '0xFFee66CC',
'default': "Valor por defecto",
'hidden': False, # only for type = text Indica si hay que ocultar
el texto (para passwords)
'enabled': True,
'visible': True
},
{'id': "nameControl3",
'type': "list", # bool, text, list, label
'label': "Control 3: tipo Lista",
'color': '0xFFee66CC',
'default': 0, # Indice del valor por defecto en lvalues
'enabled': True,
'visible': True,
'lvalues':["item1", "item2", "item3", "item4"], # only for type = list
},
{'id': "nameControl4",
'type': "label", # bool, text, list, label
'label': "Control 4: tipo Etiqueta",
'color': '0xFFee66CC',
'enabled': True,
'visible': True
}]
Si no se incluye el listado_controles, se intenta obtener del json del canal desde donde se hace la
llamada.
El formato de los controles en el json es:
{
...
...
"settings": [
{
"id": "name_control_1",
"type": "bool",
"label": "Control 1: tipo RadioButton",
"default": false,
"enabled": true,
"visible": true,
"color": "0xFFee66CC"
},
{
"id": "name_control_2",
"type": "text",
"label": "Control 2: tipo Cuadro de texto",
"default": "Valor por defecto",
"hidden": true,
"enabled": true,
"visible": true,
"color": "0xFFee66CC"
},
{
"id": "name_control_3",
"type": "list",
"label": "Control 3: tipo Lista",
"default": 0,
"enabled": true,
"visible": true,
"color": "0xFFee66CC",
"lvalues": [
"item1",
"item2",
"item3",
"item4"
]
},
{
"id": "name_control_4",
"type": "label",
"label": "Control 4: tipo Etiqueta",
"enabled": true,
"visible": true,
"color": "0xFFee66CC"
},
]
}
Los campos 'label', 'default' y 'lvalues' pueden ser un numero precedido de '@'. En cuyo caso se
buscara el literal en el archivo string.xml del idioma seleccionado.
Los campos 'enabled' y 'visible' admiten los comparadores eq(), gt() e it() y su funcionamiento se
describe en: http://kodi.wiki/view/Add-on_settings#Different_types
(opcional)dict_values: (dict) Diccionario que representa el par (id: valor) de los controles de la
lista.
Si algun control de la lista no esta incluido en este diccionario se le asignara el valor por
defecto.
dict_values={"nameControl1": False,
"nameControl2": "Esto es un ejemplo"}
(opcional) caption: (str) Titulo de la ventana de configuracion. Se puede localizar mediante un numero
precedido de '@'
(opcional) callback (str) Nombre de la funcion, del canal desde el que se realiza la llamada, que sera
invocada al pulsar el boton aceptar de la ventana. A esta funcion se le pasara como parametros el
objeto 'item' y el dicionario 'dict_values'. Si este parametro no existe, se busca en el canal una
funcion llamada 'cb_validate_config' y si existe se utiliza como callback.
Retorno: Si se especifica 'callback' o el canal incluye 'cb_validate_config' se devolvera lo que devuelva
esa funcion. Si no devolvera None
Ejemplos de uso:
platformtools.show_channel_settings(): Así tal cual, sin pasar ningún argumento, la ventana detecta de que canal
se ha hecho la llamada,
y lee los ajustes del json y carga los controles, cuando le das a Aceptar los vuelve a guardar.
return platformtools.show_channel_settings(list_controls=list_controls, dict_values=dict_values, callback='cb',
item=item):
Así abre la ventana con los controles pasados y los valores de dict_values, si no se pasa dict_values, carga
los valores por defecto de los controles,
cuando le das a aceptar, llama a la función 'callback' del canal desde donde se ha llamado, pasando como
parámetros, el item y el dict_values
"""
def start(self, list_controls=None, dict_values=None, caption="", callback=None, item=None,
custom_button=None, channelpath=None):
logger.info()
# Ruta para las imagenes de la ventana
self.mediapath = os.path.join(config.get_runtime_path(), 'resources', 'skins', 'Default', 'media')
# Capturamos los parametros
self.list_controls = list_controls
self.values = dict_values
self.caption = caption
self.callback = callback
self.item = item
if type(custom_button) == dict:
self.custom_button = {}
self.custom_button["label"] = custom_button.get("label", "")
self.custom_button["function"] = custom_button.get("function", "")
self.custom_button["visible"] = bool(custom_button.get("visible", True))
self.custom_button["close"] = bool(custom_button.get("close", False))
else:
self.custom_button = None
# Obtenemos el canal desde donde se ha echo la llamada y cargamos los settings disponibles para ese canal
if not channelpath:
channelpath = inspect.currentframe().f_back.f_back.f_code.co_filename
self.channel = os.path.basename(channelpath).replace(".py", "")
self.ch_type = os.path.basename(os.path.dirname(channelpath))
# Si no tenemos list_controls, hay que sacarlos del json del canal
if not self.list_controls:
# Si la ruta del canal esta en la carpeta "channels", obtenemos los controles y valores mediante chaneltools
if os.path.join(config.get_runtime_path(), "channels") in channelpath:
# La llamada se hace desde un canal
self.list_controls, default_values = channeltools.get_channel_controls_settings(self.channel)
self.kwargs = {"channel": self.channel}
# Si la ruta del canal esta en la carpeta "servers", obtenemos los controles y valores mediante servertools
elif os.path.join(config.get_runtime_path(), "servers") in channelpath:
# La llamada se hace desde un canal
self.list_controls, default_values = servertools.get_server_controls_settings(self.channel)
self.kwargs = {"server": self.channel}
# En caso contrario salimos
else:
return None
# Si no se pasan dict_values, creamos un dict en blanco
if self.values is None:
self.values = {}
# Ponemos el titulo
if self.caption == "":
self.caption = str(config.get_localized_string(30100)) + " -- " + self.channel.capitalize()
elif self.caption.startswith('@') and unicode(self.caption[1:]).isnumeric():
self.caption = config.get_localized_string(int(self.caption[1:]))
# Muestra la ventana
self.return_value = None
self.doModal()
return self.return_value
@staticmethod
def set_enabled(c, val):
if c["type"] == "list":
c["control"].setEnabled(val)
c["downBtn"].setEnabled(val)
c["upBtn"].setEnabled(val)
c["label"].setEnabled(val)
else:
c["control"].setEnabled(val)
@staticmethod
def set_visible(c, val):
if c["type"] == "list":
c["control"].setVisible(val)
c["downBtn"].setVisible(val)
c["upBtn"].setVisible(val)
c["label"].setVisible(val)
else:
c["control"].setVisible(val)
def evaluate_conditions(self):
for c in self.list_controls:
c["active"] = self.evaluate(self.list_controls.index(c), c["enabled"])
self.set_enabled(c, c["active"])
c["show"] = self.evaluate(self.list_controls.index(c), c["visible"])
if not c["show"]:
self.set_visible(c, c["show"])
self.visible_controls = [c for c in self.list_controls if c["show"]]
def evaluate(self, index, cond):
import re
# Si la condicion es True o False, no hay mas que evaluar, ese es el valor
if type(cond) == bool:
return cond
# Obtenemos las condiciones
conditions = re.compile("(!?eq|!?gt|!?lt)?\(([^,]+),[\"|']?([^)|'|\"]*)['|\"]?\)[ ]*([+||])?").findall(cond)
for operator, id, value, next in conditions:
# El id tiene que ser un numero, sino, no es valido y devuelve False
try:
id = int(id)
except:
return False
# El control sobre el que evaluar, tiene que estar dentro del rango, sino devuelve False
if index + id < 0 or index + id >= len(self.list_controls):
return False
else:
# Obtenemos el valor del control sobre el que se compara
c = self.list_controls[index + id]
if c["type"] == "bool":
control_value = bool(c["control"].isSelected())
if c["type"] == "text":
control_value = c["control"].getText()
if c["type"] == "list":
control_value = c["label"].getLabel()
if c["type"] == "label":
control_value = c["control"].getLabel()
# Operaciones lt "menor que" y gt "mayor que", requieren que las comparaciones sean numeros, sino devuelve
# False
if operator in ["lt", "!lt", "gt", "!gt"]:
try:
value = int(value)
except ValueError:
return False
# Operacion eq "igual a"
if operator in ["eq", "!eq"]:
# valor int
try:
value = int(value)
except ValueError:
pass
# valor bool
if value.lower() == "true":
value = True
elif value.lower() == "false":
value = False
# operacion "eq" "igual a"
if operator == "eq":
if control_value == value:
ok = True
else:
ok = False
# operacion "!eq" "no igual a"
if operator == "!eq":
if not control_value == value:
ok = True
else:
ok = False
# operacion "gt" "mayor que"
if operator == "gt":
if control_value > value:
ok = True
else:
ok = False
# operacion "!gt" "no mayor que"
if operator == "!gt":
if not control_value > value:
ok = True
else:
ok = False
# operacion "lt" "menor que"
if operator == "lt":
if control_value < value:
ok = True
else:
ok = False
# operacion "!lt" "no menor que"
if operator == "!lt":
if not control_value < value:
ok = True
else:
ok = False
# Siguiente operación, si es "|" (or) y el resultado es True, no tiene sentido seguir, es True
if next == "|" and ok is True:
break
# Siguiente operación, si es "+" (and) y el resultado es False, no tiene sentido seguir, es False
if next == "+" and ok is False:
break
# Siguiente operación, si es "+" (and) y el resultado es True, Seguira, para comprobar el siguiente valor
# Siguiente operación, si es "|" (or) y el resultado es False, Seguira, para comprobar el siguiente valor
return ok
def add_control_label(self, c):
control = xbmcgui.ControlLabel(0, -100, self.controls_width, 30, "", alignment=4, font=self.font,
textColor=c["color"])
self.addControl(control)
control.setVisible(False)
control.setLabel(c["label"])
# Lo añadimos al listado
c["control"] = control
def add_control_list(self, c):
control = xbmcgui.ControlButton(0, -100, self.controls_width, self.height_control,
c["label"], os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'),
os.path.join(self.mediapath, 'Controls', 'MenuItemNF.png'),
0, textColor=c["color"],
font=self.font)
label = xbmcgui.ControlLabel(0, -100, self.controls_width - 30, self.height_control,
"", font=self.font, textColor=c["color"], alignment=4 | 1)
upBtn = xbmcgui.ControlButton(0, -100, 20, 15, "",
focusTexture=os.path.join(self.mediapath, 'Controls', 'spinUp-Focus.png'),
noFocusTexture=os.path.join(self.mediapath, 'Controls', 'spinUp-noFocus.png'))
downBtn = xbmcgui.ControlButton(0, -100 + 15, 20, 15, "",
focusTexture=os.path.join(self.mediapath, 'Controls', 'spinDown-Focus.png'),
noFocusTexture=os.path.join(self.mediapath, 'Controls', 'spinDown-noFocus.png'))
self.addControl(control)
self.addControl(label)
self.addControl(upBtn)
self.addControl(downBtn)
control.setVisible(False)
label.setVisible(False)
upBtn.setVisible(False)
downBtn.setVisible(False)
label.setLabel(c["lvalues"][self.values[c["id"]]])
c["control"] = control
c["label"] = label
c["downBtn"] = downBtn
c["upBtn"] = upBtn
def add_control_text(self, c):
if xbmcgui.ControlEdit == ControlEdit:
control = xbmcgui.ControlEdit(0, -100, self.controls_width, self.height_control,
c["label"], os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'),
os.path.join(self.mediapath, 'Controls', 'MenuItemNF.png'),
0, textColor=c["color"],
font=self.font, isPassword=c["hidden"], window=self)
else:
control = xbmcgui.ControlEdit(0, -100, self.controls_width - 5, self.height_control,
c["label"], self.font, c["color"], '', 4, isPassword=c["hidden"],
focusTexture=os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'),
noFocusTexture=os.path.join(self.mediapath, 'Controls', 'MenuItemNF.png'))
self.addControl(control)
control.setVisible(False)
control.setLabel(c["label"])
control.setText(self.values[c["id"]])
control.setWidth(self.controls_width - 5)
control.setHeight(self.height_control)
c["control"] = control
def add_control_bool(self, c):
# Versiones antiguas no admite algunas texturas
if xbmcgui.__version__ in ["1.2", "2.0"]:
control = xbmcgui.ControlRadioButton(0 - 10, -100, self.controls_width + 10, self.height_control,
label=c["label"], font=self.font, textColor=c["color"],
focusTexture=os.path.join(self.mediapath, 'Controls',
'MenuItemFO.png'),
noFocusTexture=os.path.join(self.mediapath, 'Controls',
'MenuItemNF.png'))
else:
control = xbmcgui.ControlRadioButton(0 - 10, -100, self.controls_width + 10,
self.height_control, label=c["label"], font=self.font,
textColor=c["color"],
focusTexture=os.path.join(self.mediapath, 'Controls',
'MenuItemFO.png'),
noFocusTexture=os.path.join(self.mediapath, 'Controls',
'MenuItemNF.png'),
focusOnTexture=os.path.join(self.mediapath, 'Controls',
'radiobutton-focus.png'),
noFocusOnTexture=os.path.join(self.mediapath, 'Controls',
'radiobutton-focus.png'),
focusOffTexture=os.path.join(self.mediapath, 'Controls',
'radiobutton-nofocus.png'),
noFocusOffTexture=os.path.join(self.mediapath, 'Controls',
'radiobutton-nofocus.png'))
self.addControl(control)
control.setVisible(False)
control.setRadioDimension(x=self.controls_width + 10 - (self.height_control - 5), y=0,
width=self.height_control - 5, height=self.height_control - 5)
control.setSelected(self.values[c["id"]])
c["control"] = control
def onInit(self):
self.getControl(10004).setEnabled(False)
self.getControl(10005).setEnabled(False)
self.getControl(10006).setEnabled(False)
self.ok_enabled = False
self.default_enabled = False
if xbmcgui.__version__ == "1.2":
self.setCoordinateResolution(1)
else:
self.setCoordinateResolution(5)
# Ponemos el título
self.getControl(10002).setLabel(self.caption)
if self.custom_button is not None:
if self.custom_button['visible']:
self.getControl(10006).setLabel(self.custom_button['label'])
else:
self.getControl(10006).setVisible(False)
self.getControl(10004).setPosition(self.getControl(10004).getPosition()[0] + 80,
self.getControl(10004).getPosition()[1])
self.getControl(10005).setPosition(self.getControl(10005).getPosition()[0] + 80,
self.getControl(10005).getPosition()[1])
# Obtenemos las dimensiones del area de controles
self.controls_width = self.getControl(10007).getWidth() - 20
self.controls_height = self.getControl(10007).getHeight()
self.controls_pos_x = self.getControl(10007).getPosition()[0] + self.getControl(10001).getPosition()[0] + 10
self.controls_pos_y = self.getControl(10007).getPosition()[1] + self.getControl(10001).getPosition()[1]
self.height_control = 35
self.font = "font12"
# En versiones antiguas: creamos 5 controles, de lo conrtario al hacer click al segundo control,
# automaticamente cambia el label del tercero a "Short By: Name" no se porque...
if xbmcgui.ControlEdit == ControlEdit:
for x in range(5):
control = xbmcgui.ControlRadioButton(-500, 0, 0, 0, "")
self.addControl(control)
for c in self.list_controls:
# Saltamos controles que no tengan los valores adecuados
if "type" not in c:
continue
if "label" not in c:
continue
if c["type"] != "label" and "id" not in c:
continue
if c["type"] == "list" and "lvalues" not in c:
continue
if c["type"] == "list" and not type(c["lvalues"]) == list:
continue
if c["type"] == "list" and not len(c["lvalues"]) > 0:
continue
if c["type"] != "label" and len(
[control.get("id") for control in self.list_controls if c["id"] == control.get("id")]) > 1:
continue
# Translation label y lvalues
if c['label'].startswith('@') and unicode(c['label'][1:]).isnumeric():
c['label'] = config.get_localized_string(int(c['label'][1:]))
if c['type'] == 'list':
lvalues = []
for li in c['lvalues']:
if li.startswith('@') and unicode(li[1:]).isnumeric():
lvalues.append(config.get_localized_string(int(li[1:])))
else:
lvalues.append(li)
c['lvalues'] = lvalues
# Valores por defecto en caso de que el control no disponga de ellos
if c["type"] == "bool":
default = False
elif c["type"] == "list":
default = 0
else:
# label or text
default = ""
c["default"] = c.get("default", default)
c["color"] = c.get("color", "0xFF0066CC")
c["visible"] = c.get("visible", True)
c["enabled"] = c.get("enabled", True)
if c["type"] == "label" and "id" not in c:
c["id"] = None
if c["type"] == "text":
c["hidden"] = c.get("hidden", False)
# Decidimos si usar el valor por defecto o el valor guardado
if c["type"] in ["bool", "text", "list"]:
if c["id"] not in self.values:
if not self.callback:
self.values[c["id"]] = config.get_setting(c["id"], **self.kwargs)
else:
self.values[c["id"]] = c["default"]
if c["type"] == "bool":
self.add_control_bool(c)
elif c["type"] == 'text':
self.add_control_text(c)
elif c["type"] == 'list':
self.add_control_list(c)
elif c["type"] == 'label':
self.add_control_label(c)
self.list_controls = [c for c in self.list_controls if "control" in c]
self.evaluate_conditions()
self.index = -1
self.dispose_controls(0)
self.getControl(100010).setVisible(False)
self.getControl(10004).setEnabled(True)
self.getControl(10005).setEnabled(True)
self.getControl(10006).setEnabled(True)
self.ok_enabled = True
self.default_enabled = True
self.check_default()
self.check_ok(self.values)
def dispose_controls(self, index, focus=False, force=False):
show_controls = self.controls_height / self.height_control - 1
visible_count = 0
if focus:
if not index >= self.index or not index <= self.index + show_controls:
if index < self.index:
new_index = index
else:
new_index = index - show_controls
else:
new_index = self.index
else:
if index + show_controls >= len(self.visible_controls): index = len(
self.visible_controls) - show_controls - 1
if index < 0: index = 0
new_index = index
if self.index <> new_index or force:
for x, c in enumerate(self.visible_controls):
if x < new_index or visible_count > show_controls or not c["show"]:
self.set_visible(c, False)
else:
c["y"] = self.controls_pos_y + visible_count * self.height_control
visible_count += 1
if c["type"] != "list":
if c["type"] == "bool":
c["control"].setPosition(self.controls_pos_x - 10, c["y"])
else:
c["control"].setPosition(self.controls_pos_x, c["y"])
else:
c["control"].setPosition(self.controls_pos_x, c["y"])
if xbmcgui.__version__ == "1.2":
c["label"].setPosition(self.controls_pos_x + self.controls_width - 30, c["y"])
else:
c["label"].setPosition(self.controls_pos_x, c["y"])
c["upBtn"].setPosition(self.controls_pos_x + c["control"].getWidth() - 25, c["y"] + 3)
c["downBtn"].setPosition(self.controls_pos_x + c["control"].getWidth() - 25, c["y"] + 18)
self.set_visible(c, True)
# Calculamos la posicion y tamaño del ScrollBar
hidden_controls = len(self.visible_controls) - show_controls - 1
if hidden_controls < 0: hidden_controls = 0
scrollbar_height = self.getControl(10008).getHeight() - (hidden_controls * 3)
scrollbar_y = self.getControl(10008).getPosition()[1] + (new_index * 3)
self.getControl(10009).setPosition(self.getControl(10008).getPosition()[0], scrollbar_y)
self.getControl(10009).setHeight(scrollbar_height)
self.index = new_index
if focus:
self.setFocus(self.visible_controls[index]["control"])
def check_ok(self, dict_values=None):
if not self.callback:
if dict_values:
self.init_values = dict_values.copy()
self.getControl(10004).setEnabled(False)
self.ok_enabled = False
else:
if self.init_values == self.values:
self.getControl(10004).setEnabled(False)
self.ok_enabled = False
else:
self.getControl(10004).setEnabled(True)
self.ok_enabled = True
def check_default(self):
if self.custom_button is None:
def_values = dict([[c["id"], c.get("default")] for c in self.list_controls if not c["type"] == "label"])
if def_values == self.values:
self.getControl(10006).setEnabled(False)
self.default_enabled = False
else:
self.getControl(10006).setEnabled(True)
self.default_enabled = True
def onClick(self, id):
# Valores por defecto
if id == 10006:
if self.custom_button is not None:
if self.custom_button["close"]:
self.close()
if '.' in self.callback:
package, self.callback = self.callback.rsplit('.', 1)
else:
package = '%s.%s' % (self.ch_type, self.channel)
try:
cb_channel = __import__(package, None, None, [package])
except ImportError:
logger.error('Imposible importar %s' % package)
else:
self.return_value = getattr(cb_channel, self.custom_button['function'])(self.item, self.values)
if not self.custom_button["close"]:
if isinstance(self.return_value, dict) and self.return_value.has_key("label"):
self.getControl(10006).setLabel(self.return_value['label'])
for c in self.list_controls:
if c["type"] == "text":
c["control"].setText(self.values[c["id"]])
if c["type"] == "bool":
c["control"].setSelected(self.values[c["id"]])
if c["type"] == "list":
c["label"].setLabel(c["lvalues"][self.values[c["id"]]])
self.evaluate_conditions()
self.dispose_controls(self.index, force=True)
else:
for c in self.list_controls:
if c["type"] == "text":
c["control"].setText(c["default"])
self.values[c["id"]] = c["default"]
if c["type"] == "bool":
c["control"].setSelected(c["default"])
self.values[c["id"]] = c["default"]
if c["type"] == "list":
c["label"].setLabel(c["lvalues"][c["default"]])
self.values[c["id"]] = c["default"]
self.evaluate_conditions()
self.dispose_controls(self.index, force=True)
self.check_default()
self.check_ok()
# Boton Cancelar y [X]
if id == 10003 or id == 10005:
self.close()
# Boton Aceptar
if id == 10004:
self.close()
if self.callback and '.' in self.callback:
package, self.callback = self.callback.rsplit('.', 1)
else:
package = '%s.%s' % (self.ch_type, self.channel)
cb_channel = None
try:
cb_channel = __import__(package, None, None, [package])
except ImportError:
logger.error('Imposible importar %s' % package)
if self.callback:
# Si existe una funcion callback la invocamos ...
self.return_value = getattr(cb_channel, self.callback)(self.item, self.values)
else:
# si no, probamos si en el canal existe una funcion 'cb_validate_config' ...
try:
self.return_value = getattr(cb_channel, 'cb_validate_config')(self.item, self.values)
except AttributeError:
# ... si tampoco existe 'cb_validate_config'...
for v in self.values:
config.set_setting(v, self.values[v], **self.kwargs)
# Controles de ajustes, si se cambia el valor de un ajuste, cambiamos el valor guardado en el diccionario de
# valores
# Obtenemos el control sobre el que se ha echo click
control = self.getControl(id)
# Lo buscamos en el listado de controles
for cont in self.list_controls:
# Si el control es un "downBtn" o "upBtn" son los botones del "list"
# en este caso cambiamos el valor del list
if cont["type"] == "list" and (cont["downBtn"] == control or cont["upBtn"] == control):
# Para bajar una posicion
if cont["downBtn"] == control:
index = cont["lvalues"].index(cont["label"].getLabel())
if index > 0:
cont["label"].setLabel(cont["lvalues"][index - 1])
# Para subir una posicion
elif cont["upBtn"] == control:
index = cont["lvalues"].index(cont["label"].getLabel())
if index < len(cont["lvalues"]) - 1:
cont["label"].setLabel(cont["lvalues"][index + 1])
# Guardamos el nuevo valor en el diccionario de valores
self.values[cont["id"]] = cont["lvalues"].index(cont["label"].getLabel())
# Si esl control es un "bool", guardamos el nuevo valor True/False
if cont["type"] == "bool" and cont["control"] == control:
self.values[cont["id"]] = bool(cont["control"].isSelected())
# Si esl control es un "text", guardamos el nuevo valor
if cont["type"] == "text" and cont["control"] == control:
# Versiones antiguas requieren abrir el teclado manualmente
if xbmcgui.ControlEdit == ControlEdit:
import xbmc
keyboard = xbmc.Keyboard(cont["control"].getText(), cont["control"].getLabel(),
cont["control"].isPassword)
keyboard.setHiddenInput(cont["control"].isPassword)
keyboard.doModal()
if keyboard.isConfirmed():
cont["control"].setText(keyboard.getText())
self.values[cont["id"]] = cont["control"].getText()
self.evaluate_conditions()
self.dispose_controls(self.index, force=True)
self.check_default()
self.check_ok()
# Versiones antiguas requieren esta funcion
def onFocus(self, a):
pass
def onAction(self, raw_action):
# Obtenemos el foco
focus = self.getFocusId()
action = raw_action.getId()
# Accion 1: Flecha izquierda
if action == 1:
# Si el foco no está en ninguno de los tres botones inferiores, y esta en un "list" cambiamos el valor
if focus not in [10004, 10005, 10006]:
control = self.getFocus()
for cont in self.list_controls:
if cont["type"] == "list" and cont["control"] == control:
index = cont["lvalues"].index(cont["label"].getLabel())
if index > 0:
cont["label"].setLabel(cont["lvalues"][index - 1])
# Guardamos el nuevo valor en el listado de controles
self.values[cont["id"]] = cont["lvalues"].index(cont["label"].getLabel())
self.evaluate_conditions()
self.dispose_controls(self.index, force=True)
self.check_default()
self.check_ok()
# Si el foco está en alguno de los tres botones inferiores, movemos al siguiente
else:
if focus == 10006:
self.setFocusId(10005)
if focus == 10005 and self.ok_enabled:
self.setFocusId(10004)
# Accion 1: Flecha derecha
elif action == 2:
# Si el foco no está en ninguno de los tres botones inferiores, y esta en un "list" cambiamos el valor
if focus not in [10004, 10005, 10006]:
control = self.getFocus()
for cont in self.list_controls:
if cont["type"] == "list" and cont["control"] == control:
index = cont["lvalues"].index(cont["label"].getLabel())
if index < len(cont["lvalues"]) - 1:
cont["label"].setLabel(cont["lvalues"][index + 1])
# Guardamos el nuevo valor en el listado de controles
self.values[cont["id"]] = cont["lvalues"].index(cont["label"].getLabel())
self.evaluate_conditions()
self.dispose_controls(self.index, force=True)
self.check_default()
self.check_ok()
# Si el foco está en alguno de los tres botones inferiores, movemos al siguiente
else:
if focus == 10004:
self.setFocusId(10005)
if focus == 10005 and self.default_enabled:
self.setFocusId(10006)
# Accion 4: Flecha abajo
elif action == 4:
# Si el foco no está en ninguno de los tres botones inferiores, bajamos el foco en los controles de ajustes
if focus not in [10004, 10005, 10006]:
try:
focus_control = \
[self.visible_controls.index(c) for c in self.visible_controls if c["control"] == self.getFocus()][
0]
focus_control += 1
except:
focus_control = 0
while not focus_control == len(self.visible_controls) and (
self.visible_controls[focus_control]["type"] == "label" or not
self.visible_controls[focus_control]["active"]):
focus_control += 1
if focus_control >= len(self.visible_controls):
self.setFocusId(10005)
return
self.dispose_controls(focus_control, True)
# Accion 4: Flecha arriba
elif action == 3:
# Si el foco no está en ninguno de los tres botones inferiores, subimos el foco en los controles de ajustes
if focus not in [10003, 10004, 10005, 10006]:
try:
focus_control = \
[self.visible_controls.index(c) for c in self.visible_controls if c["control"] == self.getFocus()][
0]
focus_control -= 1
while not focus_control == -1 and (self.visible_controls[focus_control]["type"] == "label" or not
self.visible_controls[focus_control]["active"]):
focus_control -= 1
if focus_control < 0: focus_control = 0
except:
focus_control = 0
self.dispose_controls(focus_control, True)
# Si el foco está en alguno de los tres botones inferiores, ponemos el foco en el ultimo ajuste.
else:
focus_control = len(self.visible_controls) - 1
while not focus_control == -1 and (self.visible_controls[focus_control]["type"] == "label" or not
self.visible_controls[focus_control]["active"]):
focus_control -= 1
if focus_control < 0: focus_control = 0
self.setFocus(self.visible_controls[focus_control]["control"])
# Accion 104: Scroll arriba
elif action == 104:
self.dispose_controls(self.index - 1)
# Accion 105: Scroll abajo
elif action == 105:
self.dispose_controls(self.index + 1)
# ACTION_PREVIOUS_MENU 10
# ACTION_NAV_BACK 92
elif action in [10, 92]:
self.close()
elif action == 504:
if self.xx > raw_action.getAmount2():
if (self.xx - int(raw_action.getAmount2())) / self.height_control:
self.xx -= self.height_control
self.dispose_controls(self.index + 1)
else:
if (int(raw_action.getAmount2()) - self.xx) / self.height_control:
self.xx += self.height_control
self.dispose_controls(self.index - 1)
return
elif action == 501:
self.xx = int(raw_action.getAmount2())
class ControlEdit(xbmcgui.ControlButton):
def __new__(cls, *args, **kwargs):
del kwargs["isPassword"]
del kwargs["window"]
args = list(args)
return xbmcgui.ControlButton.__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
self.isPassword = kwargs["isPassword"]
self.window = kwargs["window"]
self.label = ""
self.text = ""
self.textControl = xbmcgui.ControlLabel(self.getX(), self.getY(), self.getWidth(), self.getHeight(), self.text,
font=kwargs["font"], textColor=kwargs["textColor"], alignment=4 | 1)
self.window.addControl(self.textControl)
def setLabel(self, val):
self.label = val
xbmcgui.ControlButton.setLabel(self, val)
def getX(self):
return xbmcgui.ControlButton.getPosition(self)[0]
def getY(self):
return xbmcgui.ControlButton.getPosition(self)[1]
def setEnabled(self, e):
xbmcgui.ControlButton.setEnabled(self, e)
self.textControl.setEnabled(e)
def setWidth(self, w):
xbmcgui.ControlButton.setWidth(self, w)
self.textControl.setWidth(w / 2)
def setHeight(self, w):
xbmcgui.ControlButton.setHeight(self, w)
self.textControl.setHeight(w)
def setPosition(self, x, y):
xbmcgui.ControlButton.setPosition(self, x, y)
if xbmcgui.__version__ == "1.2":
self.textControl.setPosition(x + self.getWidth(), y)
else:
self.textControl.setPosition(x + self.getWidth() / 2, y)
def setText(self, text):
self.text = text
if self.isPassword:
self.textControl.setLabel("*" * len(self.text))
else:
self.textControl.setLabel(self.text)
def getText(self):
return self.text
if not hasattr(xbmcgui, "ControlEdit"):
xbmcgui.ControlEdit = ControlEdit

View File

@@ -0,0 +1,329 @@
# -*- coding: utf-8 -*-
import xbmcgui
from core import logger
from core.tmdb import Tmdb
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):
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("Título:")
self.getControl(10007).setLabel(self.result.get("title", "N/A"))
self.getControl(10008).setLabel("Título original:")
self.getControl(10009).setLabel(self.result.get("originaltitle", "N/A"))
self.getControl(100010).setLabel("Idioma original:")
self.getControl(100011).setLabel(self.result.get("language", "N/A"))
self.getControl(100012).setLabel("Puntuación:")
self.getControl(100013).setLabel(self.result.get("puntuacion", "N/A"))
self.getControl(100014).setLabel("Lanzamiento:")
self.getControl(100015).setLabel(self.result.get("release_date", "N/A"))
self.getControl(100016).setLabel("Géneros:")
self.getControl(100017).setLabel(self.result.get("genre", "N/A"))
# Cargamos los datos para el formato serie
else:
self.getControl(10006).setLabel("Serie:")
self.getControl(10007).setLabel(self.result.get("title", "N/A"))
self.getControl(10008).setLabel("Idioma original:")
self.getControl(10009).setLabel(self.result.get("language", "N/A"))
self.getControl(100010).setLabel("Puntuación:")
self.getControl(100011).setLabel(self.result.get("puntuacion", "N/A"))
self.getControl(100012).setLabel("Géneros:")
self.getControl(100013).setLabel(self.result.get("genre", "N/A"))
if self.result.get("season"):
self.getControl(100014).setLabel("Título temporada:")
self.getControl(100015).setLabel(self.result.get("temporada_nombre", "N/A"))
self.getControl(100016).setLabel("Temporada:")
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("Título:")
self.getControl(100015).setLabel(self.result.get("episode_title", "N/A"))
self.getControl(100018).setLabel("Episodio:")
self.getControl(100019).setLabel(self.result.get("episode", "N/A") + " de " +
self.result.get("episodes", "N/A"))
self.getControl(100020).setLabel("Emisión:")
self.getControl(100021).setLabel(self.result.get("date", "N/A"))
# Sinopsis
if self.result['plot']:
self.getControl(100022).setLabel("Sinopsis:")
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,828 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# XBMC Library Tools
# ------------------------------------------------------------
import os
import threading
import time
import urllib2
import xbmc
from core import config
from core import filetools
from core import jsontools
from core import 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_watched(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("Alfa",
"Sincronizacion con Trakt iniciada",
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']:
filename = filetools.basename(item.strm_path)
head, tail = filetools.split(filetools.split(item.strm_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']:
filename = filetools.basename(item.strm_path)
head, tail = filetools.split(filetools.split(item.strm_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 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)
if not folder:
# Actualizar toda la coleccion
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
xbmc.sleep(500)
xbmc.executebuiltin('UpdateLibrary(video)')
else:
# Actualizar una sola carpeta en un hilo independiente
def update_multi_threads(update_path, lock):
lock.acquire()
# logger.debug("%s\nINICIO" % update_path)
payload = {"jsonrpc": "2.0",
"method": "VideoLibrary.Scan",
"params": {"directory": update_path}, "id": 1}
data = get_data(payload)
lock.release()
# logger.debug("%s\nFIN data: %s" % (update_path, data))
videolibrarypath = config.get_videolibrary_config_path()
if folder.endswith('/') or folder.endswith('\\'):
folder = folder[:-1]
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) + "/"
t = threading.Thread(target=update_multi_threads, args=[update_path, threading.Lock()])
t.setDaemon(True)
t.start()
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':
if not xbmc.getCondVisibility('System.HasAddon(metadata.themoviedb.org)'):
if not silent:
# Preguntar si queremos instalar metadata.themoviedb.org
install = platformtools.dialog_yesno("The Movie Database",
"No se ha encontrado el Scraper de películas de TheMovieDB.",
"¿Desea instalarlo ahora?")
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 = "The Movie Database no instalado."
else: # SERIES
# Instalar The TVDB
if not xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'):
if not silent:
# Preguntar si queremos instalar metadata.tvdb.com
install = platformtools.dialog_yesno("The TVDB",
"No se ha encontrado el Scraper de series de The TVDB.",
"¿Desea instalarlo ahora?")
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 = "The TVDB no instalado."
# Instalar TheMovieDB
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("The Movie Database",
"No se ha encontrado el Scraper de series de TheMovieDB.",
"¿Desea instalarlo ahora?")
else:
install = True
if install:
try:
# Instalar metadata.tvshows.themoviedb.org
# 1º Probar desde el repositorio ...
xbmc.executebuiltin('xbmc.installaddon(metadata.tvshows.themoviedb.org)', True)
if not xbmc.getCondVisibility('System.HasAddon(metadata.tvshows.themoviedb.org)'):
# ...si no funciona descargar e instalar desde la web
url = "http://mirrors.kodi.tv/addons/jarvis/metadata.tvshows.themoviedb.org/metadata.tvshows.themoviedb.org-1.3.1.zip"
path_down = xbmc.translatePath(
"special://home/addons/packages/metadata.tvshows.themoviedb.org-1.3.1.zip")
path_unzip = xbmc.translatePath("special://home/addons/")
header = ("User-Agent",
"Kodi/15.2 (Windows NT 10.0; WOW64) App_Bitness/32 Version/15.2-Git:20151019-02e7013")
from core import downloadtools
from core import ziptools
downloadtools.downloadfile(url, path_down, continuar=True, headers=[header])
unzipper = ziptools.ziptools()
unzipper.extract(path_down, path_unzip)
xbmc.executebuiltin('UpdateLocalAddons')
strSettings = '<settings>\n' \
' <setting id="fanart" value="true" />\n' \
' <setting id="keeporiginaltitle" value="false" />\n' \
' <setting id="language" value="es" />\n' \
'</settings>'
path_settings = xbmc.translatePath(
"special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
tv_themoviedb_addon_path = filetools.dirname(path_settings)
if not filetools.exists(tv_themoviedb_addon_path):
filetools.mkdir(tv_themoviedb_addon_path)
if filetools.write(path_settings, strSettings):
continuar = True
except:
pass
continuar = (install and continuar)
if not continuar:
msg_text = "The Movie Database no instalado."
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 = "Error al fijar videolibrarypath en BD"
if continuar:
continuar = False
# Fijamos strContent, strScraper, scanRecursive y strSettings
if content_type == 'movie':
strContent = 'movies'
strScraper = 'metadata.themoviedb.org'
scanRecursive = 2147483647
strSettings = "<settings><setting id='RatingS' value='TMDb' /><setting id='certprefix' value='Rated ' />" \
"<setting id='fanart' value='true' /><setting id='keeporiginaltitle' value='false' />" \
"<setting id='language' value='es' /><setting id='tmdbcertcountry' value='us' />" \
"<setting id='trailer' value='true' /></settings>"
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'
strScraper = 'metadata.tvdb.com'
scanRecursive = 0
strSettings = "<settings><setting id='RatingS' value='TheTVDB' />" \
"<setting id='absolutenumber' value='false' />" \
"<setting id='dvdorder' value='false' />" \
"<setting id='fallback' value='true' />" \
"<setting id='fanart' value='true' />" \
"<setting id='language' value='es' /></settings>"
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("The TVDB", 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 = "Error al configurar el scraper en la BD."
if not continuar:
heading = "Videoteca %s no configurada" % content_type
elif content_type == 'SERIES' and not xbmc.getCondVisibility(
'System.HasAddon(metadata.tvshows.themoviedb.org)'):
heading = "Videoteca %s configurada" % content_type
msg_text = "Es necesario reiniciar Kodi para que los cambios surtan efecto."
else:
heading = "Videoteca %s configurada" % content_type
msg_text = "Felicidades la videoteca de Kodi ha sido configurada correctamente."
platformtools.dialog_notification(heading, msg_text, icon=1, time=10000)
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 = "Alfa Auto-configuración"
linea1 = "¿Desea que Alfa auto-configure la videteoca de Kodi?"
linea2 = "Si pulsa 'No' podra hacerlo desde 'Configuración > Preferencia > Rutas'."
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)