diff --git a/channels.json b/channels.json
index 8219d96d..82faaae9 100644
--- a/channels.json
+++ b/channels.json
@@ -1,7 +1,7 @@
{
"altadefinizione01": "https://altadefinizione01.photo",
"altadefinizione01_link": "https://altadefinizione01.baby",
- "altadefinizioneclick": "https://altadefinizione.family",
+ "altadefinizioneclick": "https://altadefinizione.productions",
"animeforce": "https://ww1.animeforce.org",
"animeleggendari": "https://animepertutti.org",
"animesaturn": "https://www.animesaturn.com",
@@ -32,12 +32,12 @@
"polpotv": "https://polpotv.club",
"pufimovies": "https://pufimovies.com",
"raiplay": "https://www.raiplay.it",
- "seriehd": "https://seriehd.net",
- "serietvonline": "https://serietvonline.host",
+ "seriehd": "https://seriehd.link",
+ "serietvonline": "https://serietvonline.work",
"serietvsubita": "http://serietvsubita.xyz",
"serietvu": "https://www.serietvu.link",
"streamtime": "https://t.me/s/StreamTime",
- "tantifilm": "https://www.tantifilm.pizza",
+ "tantifilm": "https://www.tantifilm.red",
"toonitalia": "https://toonitalia.org",
"vedohd": "https://vedohd.uno",
"vvvvid": "https://www.vvvvid.it"
diff --git a/core/channeltools.py b/core/channeltools.py
index 1ab338ef..86d3757c 100644
--- a/core/channeltools.py
+++ b/core/channeltools.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
-# channeltools - Herramientas para trabajar con canales
+# channeltools - Tools for working with channels
# ------------------------------------------------------------
from __future__ import absolute_import
@@ -29,11 +29,11 @@ def get_channel_parameters(channel_name):
channel_parameters = get_channel_json(channel_name)
# logger.debug(channel_parameters)
if channel_parameters:
- # cambios de nombres y valores por defecto
+ # name and default changes
channel_parameters["title"] = channel_parameters.pop("name") + (' [DEPRECATED]' if 'deprecated' in channel_parameters and channel_parameters['deprecated'] else '')
channel_parameters["channel"] = channel_parameters.pop("id")
- # si no existe el key se declaran valor por defecto para que no de fallos en las funciones que lo llaman
+ # if the key does not exist, they are declared a default value so that there are no failures in the functions that call it
channel_parameters["update_url"] = channel_parameters.get("update_url", DEFAULT_UPDATE_URL)
channel_parameters["language"] = channel_parameters.get("language", ["all"])
channel_parameters["active"] = channel_parameters.get("active", False)
@@ -45,7 +45,7 @@ def get_channel_parameters(channel_name):
channel_parameters["banner"] = channel_parameters.get("banner", "")
channel_parameters["fanart"] = channel_parameters.get("fanart", "")
- # Imagenes: se admiten url y archivos locales dentro de "resources/images"
+ # Images: url and local files are allowed inside "resources / images"
if channel_parameters.get("thumbnail") and "://" not in channel_parameters["thumbnail"]:
channel_parameters["thumbnail"] = filetools.join(remote_path, "resources", "thumb", channel_parameters["thumbnail"])
if channel_parameters.get("banner") and "://" not in channel_parameters["banner"]:
@@ -53,7 +53,7 @@ def get_channel_parameters(channel_name):
if channel_parameters.get("fanart") and "://" not in channel_parameters["fanart"]:
channel_parameters["fanart"] = filetools.join(remote_path, "resources", channel_parameters["fanart"])
- # Obtenemos si el canal tiene opciones de configuración
+ # We obtain if the channel has configuration options
channel_parameters["has_settings"] = False
if 'settings' in channel_parameters:
channel_parameters['settings'] = get_default_settings(channel_name)
@@ -71,8 +71,7 @@ def get_channel_parameters(channel_name):
dict_channels_parameters[channel_name] = channel_parameters
else:
- # para evitar casos donde canales no están definidos como configuración
- # lanzamos la excepcion y asi tenemos los valores básicos
+ # To avoid cases where channels are not defined as configuration, we throw the exception and thus we have the basic values
raise Exception
except Exception as ex:
@@ -123,7 +122,7 @@ def get_channel_controls_settings(channel_name):
for c in list_controls:
if 'id' not in c or 'type' not in c or 'default' not in c:
- # Si algun control de la lista no tiene id, type o default lo ignoramos
+ # If any control in the list does not have id, type or default, we ignore it
continue
# new dict with key(id) and value(default) from settings
@@ -173,7 +172,7 @@ def get_default_settings(channel_name):
default_controls_renumber = default_file['renumber']
channel_json = get_channel_json(channel_name)
- # Collects configurations
+ # Collects configurations
channel_language = channel_json['language']
channel_controls = channel_json['settings']
categories = channel_json['categories']
@@ -189,28 +188,22 @@ def get_default_settings(channel_name):
label = label[-1]
if label == 'peliculas':
if 'movie' in categories:
- control['label'] = config.get_localized_string(70727) + ' - ' + config.get_localized_string(
- 30122)
- control['default'] = False if ('include_in_newest' in default_off) or (
- 'include_in_newest_peliculas' in default_off) else True
+ control['label'] = config.get_localized_string(70727) + ' - ' + config.get_localized_string( 30122)
+ control['default'] = False if ('include_in_newest' in default_off) or ( ' include_in_newest_peliculas' in default_off) else True
channel_controls.append(control)
else:
pass
elif label == 'series':
if 'tvshow' in categories:
- control['label'] = config.get_localized_string(70727) + ' - ' + config.get_localized_string(
- 30123)
- control['default'] = False if ('include_in_newest' in default_off) or (
- 'include_in_newest_series' in default_off) else True
+ control['label'] = config.get_localized_string(70727) + ' - ' + config.get_localized_string( 30123)
+ control['default'] = False if ('include_in_newest' in default_off) or ( 'include_in_newest_series' in default_off) else True
channel_controls.append(control)
else:
pass
elif label == 'anime':
if 'anime' in categories:
- control['label'] = config.get_localized_string(70727) + ' - ' + config.get_localized_string(
- 30124)
- control['default'] = False if ('include_in_newest' in default_off) or (
- 'include_in_newest_anime' in default_off) else True
+ control['label'] = config.get_localized_string(70727) + ' - ' + config.get_localized_string( 30124)
+ control['default'] = False if ('include_in_newest' in default_off) or ( 'include_in_newest_anime' in default_off) else True
channel_controls.append(control)
else:
pass
@@ -239,24 +232,24 @@ def get_default_settings(channel_name):
def get_channel_setting(name, channel, default=None):
from core import filetools
"""
- Retorna el valor de configuracion del parametro solicitado.
+ Returns the configuration value of the requested parameter.
- Devuelve el valor del parametro 'name' en la configuracion propia del canal 'channel'.
+ Returns the value of the parameter 'name' in the own configuration of the channel 'channel'.
- Busca en la ruta \addon_data\plugin.video.kod\settings_channels el archivo channel_data.json y lee
- el valor del parametro 'name'. Si el archivo channel_data.json no existe busca en la carpeta channels el archivo
- channel.json y crea un archivo channel_data.json antes de retornar el valor solicitado. Si el parametro 'name'
- tampoco existe en el el archivo channel.json se devuelve el parametro default.
+ Look in the path \addon_data\plugin.video.kod\settings_channels for the file channel_data.json and read
+ the value of the parameter 'name'. If the file channel_data.json does not exist look in the channels folder for the file
+ channel.json and create a channel_data.json file before returning the requested value. If the parameter 'name'
+ also does not exist in the channel.json file the default parameter is returned.
- @param name: nombre del parametro
+ @param name: parameter name
@type name: str
- @param channel: nombre del canal
+ @param channel: channel name
@type channel: str
- @param default: valor devuelto en caso de que no exista el parametro name
+ @param default: return value in case the name parameter does not exist
@type default: any
- @return: El valor del parametro 'name'
+ @return: The value of the parameter 'name'
@rtype: any
"""
@@ -266,58 +259,58 @@ def get_channel_setting(name, channel, default=None):
if channel not in ['trakt']: def_settings = get_default_settings(channel)
if filetools.exists(file_settings):
- # Obtenemos configuracion guardada de ../settings/channel_data.json
+ # We get saved configuration from ../settings/channel_data.json
try:
dict_file = jsontools.load(filetools.read(file_settings))
if isinstance(dict_file, dict) and 'settings' in dict_file:
dict_settings = dict_file['settings']
except EnvironmentError:
- logger.error("ERROR al leer el archivo: %s" % file_settings)
+ logger.error("ERROR when reading the file: %s" % file_settings)
if not dict_settings or name not in dict_settings:
- # Obtenemos controles del archivo ../channels/channel.json
+ # We get controls from the file ../channels/channel.json
try:
list_controls, default_settings = get_channel_controls_settings(channel)
except:
default_settings = {}
- if name in default_settings: # Si el parametro existe en el channel.json creamos el channel_data.json
+ if name in default_settings: #If the parameter exists in the channel.json we create the channel_data.json
default_settings.update(dict_settings)
dict_settings = default_settings
dict_file['settings'] = dict_settings
- # Creamos el archivo ../settings/channel_data.json
+ # We create the file ../settings/channel_data.json
json_data = jsontools.dump(dict_file)
if not filetools.write(file_settings, json_data, silent=True):
- logger.error("ERROR al salvar el archivo: %s" % file_settings)
+ logger.error("ERROR saving file: %s" % file_settings)
- # Devolvemos el valor del parametro local 'name' si existe, si no se devuelve default
+ # We return the value of the local parameter 'name' if it exists, if default is not returned
return dict_settings.get(name, default)
def set_channel_setting(name, value, channel):
from core import filetools
"""
- Fija el valor de configuracion del parametro indicado.
+ Sets the configuration value of the indicated parameter.
- Establece 'value' como el valor del parametro 'name' en la configuracion propia del canal 'channel'.
- Devuelve el valor cambiado o None si la asignacion no se ha podido completar.
+ Set 'value' as the value of the parameter 'name' in the own configuration of the channel 'channel'.
+ Returns the changed value or None if the assignment could not be completed.
- Si se especifica el nombre del canal busca en la ruta \addon_data\plugin.video.kod\settings_channels el
- archivo channel_data.json y establece el parametro 'name' al valor indicado por 'value'.
- Si el parametro 'name' no existe lo añade, con su valor, al archivo correspondiente.
+ If the name of the channel is specified, search in the path \addon_data\plugin.video.kod\settings_channels the
+ channel_data.json file and set the parameter 'name' to the value indicated by 'value'.
+ If the parameter 'name' does not exist, it adds it, with its value, to the corresponding file.
- @param name: nombre del parametro
+ @param name: parameter name
@type name: str
- @param value: valor del parametro
+ @param value: parameter value
@type value: str
- @param channel: nombre del canal
+ @param channel: channel name
@type channel: str
- @return: 'value' en caso de que se haya podido fijar el valor y None en caso contrario
+ @return: 'value' if the value could be set and None otherwise
@rtype: str, None
"""
- # Creamos la carpeta si no existe
+ # We create the folder if it does not exist
if not filetools.exists(filetools.join(config.get_data_path(), "settings_channels")):
filetools.mkdir(filetools.join(config.get_data_path(), "settings_channels"))
@@ -327,16 +320,16 @@ def set_channel_setting(name, value, channel):
dict_file = None
if filetools.exists(file_settings):
- # Obtenemos configuracion guardada de ../settings/channel_data.json
+ # We get saved settings from ../settings/channel_data.json
try:
dict_file = jsontools.load(filetools.read(file_settings))
dict_settings = dict_file.get('settings', {})
except EnvironmentError:
- logger.error("ERROR al leer el archivo: %s" % file_settings)
+ logger.error("ERROR when reading the file: %s" % file_settings)
dict_settings[name] = value
- # comprobamos si existe dict_file y es un diccionario, sino lo creamos
+ # we check if dict_file exists and it is a dictionary, if not we create it
if dict_file is None or not dict_file:
dict_file = {}
@@ -345,7 +338,7 @@ def set_channel_setting(name, value, channel):
# Creamos el archivo ../settings/channel_data.json
json_data = jsontools.dump(dict_file)
if not filetools.write(file_settings, json_data, silent=True):
- logger.error("ERROR al salvar el archivo: %s" % file_settings)
+ logger.error("ERROR saving file: %s" % file_settings)
return None
return value
diff --git a/core/downloader.py b/core/downloader.py
index 39f60d39..3cb52c33 100644
--- a/core/downloader.py
+++ b/core/downloader.py
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
"""
-Clase Downloader
+Downloader class
Downloader(url, path [, filename, headers, resume])
- url : string - url para descargar
- path : string - Directorio donde se guarda la descarga
- filename : [opt] string - Nombre de archivo para guardar
- headers : [opt] dict - Headers para usar en la descarga
- resume : [opt] bool - continuar una descarga previa en caso de existir, por defecto True
+ url : string - url to download
+ path : string - Directory where the download is saved
+ filename : [opt] string - File name to save
+ headers : [opt] dict - Headers to use for download
+ resume : [opt] bool - continue a previous download if it exists, by default True
metodos:
- start_dialog() Inicia la descarga mostrando el progreso
- start() Inicia la descarga en segundo plano
- stop(erase = False) Detiene la descarga, con erase = True elimina los datos descargados
+ start_dialog() Start the download showing the progress
+ start() Download starts in the background
+ stop(erase = False) Stop the download, with erase = True it deletes the downloaded data
"""
from __future__ import division
@@ -26,7 +26,7 @@ standard_library.install_aliases()
from builtins import range
from builtins import object
from past.utils import old_div
-#from builtins import str
+# from builtins import str
import sys
PY3 = False
VFS = True
@@ -53,8 +53,7 @@ class Downloader(object):
@property
def connections(self):
- return len([c for c in self._download_info["parts"] if
- c["status"] in [self.states.downloading, self.states.connecting]]), self._max_connections
+ return len([c for c in self._download_info["parts"] if c["status"] in [self.states.downloading, self.states.connecting]]), self._max_connections
@property
def downloaded(self):
@@ -102,7 +101,7 @@ class Downloader(object):
def fullpath(self):
return os.path.abspath(filetools.join(self._path, self._filename))
- # Funciones
+ # Features
def start_dialog(self, title=config.get_localized_string(60200)):
from platformcode import platformtools
progreso = platformtools.dialog_progress_bg(title, config.get_localized_string(60201))
@@ -111,9 +110,7 @@ class Downloader(object):
while self.state == self.states.downloading:
time.sleep(0.2)
line1 = "%s" % (self.filename)
- line2 = config.get_localized_string(59983) % (
- self.downloaded[1], self.downloaded[2], self.size[1], self.size[2],
- self.speed[1], self.speed[2], self.connections[0], self.connections[1])
+ line2 = config.get_localized_string(59983) % ( self.downloaded[1], self.downloaded[2], self.size[1], self.size[2], self.speed[1], self.speed[2], self.connections[0], self.connections[1])
line3 = config.get_localized_string(60202) % (self.remaining_time)
progreso.update(int(self.progress), line1, line2 + " " + line3)
@@ -130,9 +127,7 @@ class Downloader(object):
conns.append(self.__open_connection__("0", ""))
except:
self._max_connections = x
- self._threads = [
- Thread(target=self.__start_part__, name="Downloader %s/%s" % (x + 1, self._max_connections)) for x
- in range(self._max_connections)]
+ self._threads = [ Thread(target=self.__start_part__, name="Downloader %s/%s" % (x + 1, self._max_connections)) for x in range(self._max_connections)]
break
del conns
self._start_time = time.time() - 1
@@ -144,7 +139,7 @@ class Downloader(object):
def stop(self, erase=False):
if self._state == self.states.downloading:
- # Detenemos la descarga
+ # We stop downloading
self._state = self.states.stopped
for t in self._threads:
if t.isAlive(): t.join()
@@ -193,10 +188,10 @@ class Downloader(object):
time.sleep(0.5)
- # Funciones internas
+ # Internal functions
def __init__(self, url, path, filename=None, headers=[], resume=True, max_connections=10, block_size=2 ** 17,
part_size=2 ** 24, max_buffer=10, json_path=None):
- # Parametros
+ # Parameters
self._resume = resume
self._path = path
self._filename = filename
@@ -214,29 +209,26 @@ class Downloader(object):
except:
self.tmp_path = os.getenv("TEMP") or os.getenv("TMP") or os.getenv("TMPDIR")
- self.states = type('states', (),
- {"stopped": 0, "connecting": 1, "downloading": 2, "completed": 3, "error": 4, "saving": 5})
+ self.states = type('states', (), {"stopped": 0, "connecting": 1, "downloading": 2, "completed": 3, "error": 4, "saving": 5})
self._state = self.states.stopped
self._download_lock = Lock()
- self._headers = {
- "User-Agent": "Kodi/15.2 (Windows NT 10.0; WOW64) App_Bitness/32 Version/15.2-Git:20151019-02e7013"}
+ self._headers = {"User-Agent": "Kodi/15.2 (Windows NT 10.0; WOW64) App_Bitness/32 Version/15.2-Git:20151019-02e7013"}
self._speed = 0
self._buffer = {}
self._seekable = True
- self._threads = [Thread(target=self.__start_part__, name="Downloader %s/%s" % (x + 1, self._max_connections))
- for x in range(self._max_connections)]
+ self._threads = [Thread(target=self.__start_part__, name="Downloader %s/%s" % (x + 1, self._max_connections)) for x in range(self._max_connections)]
self._speed_thread = Thread(target=self.__speed_metter__, name="Speed Meter")
self._save_thread = Thread(target=self.__save_file__, name="File Writer")
- # Actualizamos los headers
+ # We update the headers
self._headers.update(dict(headers))
- # Separamos los headers de la url
+ # We separate the headers from the url
self.__url_to_headers__(url)
- # Obtenemos la info del servidor
+ # We get the server info
self.__get_download_headers__()
self._file_size = int(self.response_headers.get("content-length", "0"))
@@ -246,10 +238,10 @@ class Downloader(object):
self._part_size = 0
self._resume = False
- # Obtenemos el nombre del archivo
+ # We get the file name
self.__get_download_filename__()
- # Abrimos en modo "a+" para que cree el archivo si no existe, luego en modo "r+b" para poder hacer seek()
+ # We open in "a+" mode to create the file if it does not exist, then in "r + b" mode to be able to do seek ()
self.file = filetools.file_open(filetools.join(self._path, self._filename), "a+", vfs=VFS)
if self.file: self.file.close()
self.file = filetools.file_open(filetools.join(self._path, self._filename), "r+b", vfs=VFS)
@@ -266,20 +258,17 @@ class Downloader(object):
self.__get_download_info__()
try:
- logger.info("Download started: Parts: %s | Path: %s | File: %s | Size: %s" % \
- (str(len(self._download_info["parts"])), self._pathencode('utf-8'), \
- self._filenameencode('utf-8'), str(self._download_info["size"])))
+ logger.info("Download started: Parts: %s | Path: %s | File: %s | Size: %s" % (str(len(self._download_info["parts"])), self._pathencode('utf-8'), self._filenameencode('utf-8'), str(self._download_info["size"])))
except:
pass
def __url_to_headers__(self, url):
- # Separamos la url de los headers adicionales
+ # We separate the url from the additional headers
self.url = url.split("|")[0]
- # headers adicionales
+ # additional headers
if "|" in url:
- self._headers.update(dict([[header.split("=")[0], urllib.parse.unquote_plus(header.split("=")[1])] for header in
- url.split("|")[1].split("&")]))
+ self._headers.update(dict([[header.split("=")[0], urllib.parse.unquote_plus(header.split("=")[1])] for header in url.split("|")[1].split("&")]))
def __get_download_headers__(self):
if self.url.startswith("https"):
@@ -307,29 +296,21 @@ class Downloader(object):
break
def __get_download_filename__(self):
- # Obtenemos nombre de archivo y extension
- if "filename" in self.response_headers.get("content-disposition",
- "") and "attachment" in self.response_headers.get(
- "content-disposition", ""):
- cd_filename, cd_ext = os.path.splitext(urllib.parse.unquote_plus(
- re.compile("attachment; filename ?= ?[\"|']?([^\"']+)[\"|']?").match(
- self.response_headers.get("content-disposition")).group(1)))
- elif "filename" in self.response_headers.get("content-disposition", "") and "inline" in self.response_headers.get(
- "content-disposition", ""):
- cd_filename, cd_ext = os.path.splitext(urllib.parse.unquote_plus(
- re.compile("inline; filename ?= ?[\"|']?([^\"']+)[\"|']?").match(
- self.response_headers.get("content-disposition")).group(1)))
+ # We get file name and extension
+ if "filename" in self.response_headers.get("content-disposition", "") and "attachment" in self.response_headers.get("content-disposition", ""):
+ cd_filename, cd_ext = os.path.splitext(urllib.parse.unquote_plus( re.compile("attachment; filename ?= ?[\"|']?([^\"']+)[\"|']?").match(self.response_headers.get("content-disposition")).group(1)))
+ elif "filename" in self.response_headers.get("content-disposition", "") and "inline" in self.response_headers.get("content-disposition", ""):
+ cd_filename, cd_ext = os.path.splitext(urllib.parse.unquote_plus(re.compile("inline; filename ?= ?[\"|']?([^\"']+)[\"|']?").match(self.response_headers.get("content-disposition")).group(1)))
else:
cd_filename, cd_ext = "", ""
- url_filename, url_ext = os.path.splitext(
- urllib.parse.unquote_plus(filetools.basename(urllib.parse.urlparse(self.url)[2])))
+ url_filename, url_ext = os.path.splitext(urllib.parse.unquote_plus(filetools.basename(urllib.parse.urlparse(self.url)[2])))
if self.response_headers.get("content-type", "application/octet-stream") != "application/octet-stream":
mime_ext = mimetypes.guess_extension(self.response_headers.get("content-type"))
else:
mime_ext = ""
- # Seleccionamos el nombre mas adecuado
+ # We select the most suitable name
if cd_filename:
self.remote_filename = cd_filename
if not self._filename:
@@ -340,7 +321,7 @@ class Downloader(object):
if not self._filename:
self._filename = url_filename
- # Seleccionamos la extension mas adecuada
+ # We select the most suitable extension
if cd_ext:
if not cd_ext in self._filename: self._filename += cd_ext
if self.remote_filename: self.remote_filename += cd_ext
@@ -360,7 +341,7 @@ class Downloader(object):
return value, old_div(value, 1024.0 ** int(math.log(value, 1024))), units[int(math.log(value, 1024))]
def __get_download_info__(self):
- # Continuamos con una descarga que contiene la info al final del archivo
+ # We continue with a download that contains the info at the end of the file
self._download_info = {}
try:
@@ -386,25 +367,21 @@ class Downloader(object):
part["current"] == part["start"]
self._start_downloaded = sum([c["current"] - c["start"] for c in self._download_info["parts"]])
- self.pending_parts = set(
- [x for x, a in enumerate(self._download_info["parts"]) if not a["status"] == self.states.completed])
- self.completed_parts = set(
- [x for x, a in enumerate(self._download_info["parts"]) if a["status"] == self.states.completed])
+ self.pending_parts = set([x for x, a in enumerate(self._download_info["parts"]) if not a["status"] == self.states.completed])
+ self.completed_parts = set([x for x, a in enumerate(self._download_info["parts"]) if a["status"] == self.states.completed])
self.save_parts = set()
self.download_parts = set()
- # La info no existe o no es correcta, comenzamos de 0
+ # The info does not exist or is not correct, we start from 0
except:
self._download_info["parts"] = []
if self._file_size and self._part_size:
for x in range(0, self._file_size, self._part_size):
end = x + self._part_size - 1
if end >= self._file_size: end = self._file_size - 1
- self._download_info["parts"].append(
- {"start": x, "end": end, "current": x, "status": self.states.stopped})
+ self._download_info["parts"].append({"start": x, "end": end, "current": x, "status": self.states.stopped})
else:
- self._download_info["parts"].append(
- {"start": 0, "end": self._file_size - 1, "current": 0, "status": self.states.stopped})
+ self._download_info["parts"].append({"start": 0, "end": self._file_size - 1, "current": 0, "status": self.states.stopped})
self._download_info["size"] = self._file_size
self._start_downloaded = 0
@@ -436,7 +413,7 @@ class Downloader(object):
logger.info("Thread started: %s" % threading.current_thread().name)
while self._state == self.states.downloading:
- if not self.pending_parts and not self.download_parts and not self.save_parts: # Descarga finalizada
+ if not self.pending_parts and not self.download_parts and not self.save_parts: # Download finished
self._state = self.states.completed
self.file.close()
continue
@@ -446,8 +423,7 @@ class Downloader(object):
save_id = min(self.save_parts)
- if not self._seekable and self._download_info["parts"][save_id][
- "start"] >= 2 ** 31 and not self.__check_consecutive__(save_id):
+ if not self._seekable and self._download_info["parts"][save_id]["start"] >= 2 ** 31 and not self.__check_consecutive__(save_id):
continue
if self._seekable or self._download_info["parts"][save_id]["start"] < 2 ** 31:
@@ -533,8 +509,7 @@ class Downloader(object):
self.__set_part_connecting__(id)
try:
- connection = self.__open_connection__(self._download_info["parts"][id]["current"],
- self._download_info["parts"][id]["end"])
+ connection = self.__open_connection__(self._download_info["parts"][id]["current"], self._download_info["parts"][id]["end"])
except:
self.__set_part__error__(id)
time.sleep(5)
@@ -559,8 +534,7 @@ class Downloader(object):
self.download_parts.remove(id)
break
else:
- if len(buffer) and self._download_info["parts"][id]["current"] < self._download_info["parts"][id][
- "end"]:
+ if len(buffer) and self._download_info["parts"][id]["current"] < self._download_info["parts"][id]["end"]:
# file.write(buffer)
self._buffer[id].append(buffer)
self._download_info["parts"][id]["current"] += len(buffer)
@@ -570,13 +544,9 @@ class Downloader(object):
vm = self.__change_units__(velocidad_minima)
v = self.__change_units__(velocidad)
- if velocidad_minima > speed[-1] and velocidad_minima > speed[-2] and \
- self._download_info["parts"][id]["current"] < \
- self._download_info["parts"][id]["end"]:
+ if velocidad_minima > speed[-1] and velocidad_minima > speed[-2] and self._download_info["parts"][id]["current"] < self._download_info["parts"][id]["end"]:
if connection.fp: connection.fp._sock.close()
- logger.info(
- "ID: %s Restarting connection! | Minimum Speed: %.2f %s/s | Speed: %.2f %s/s" % \
- (id, vm[1], vm[2], v[1], v[2]))
+ logger.info("ID: %s Restarting connection! | Minimum Speed: %.2f %s/s | Speed: %.2f %s/s" % (id, vm[1], vm[2], v[1], v[2]))
# file.close()
break
else:
diff --git a/core/downloadtools.py b/core/downloadtools.py
index 57b020aa..313c1baa 100644
--- a/core/downloadtools.py
+++ b/core/downloadtools.py
@@ -92,8 +92,8 @@ def limpia_nombre_sin_acentos(s):
def limpia_nombre_excepto_1(s):
if not s:
return ''
- # Titulo de entrada
- # Convierte a unicode
+ # Entrance title
+ # Convert to unicode
try:
s = unicode(s, "utf-8")
except UnicodeError:
@@ -103,12 +103,12 @@ def limpia_nombre_excepto_1(s):
except UnicodeError:
# logger.info("no es iso-8859-1")
pass
- # Elimina acentos
+ # Remove accents
s = limpia_nombre_sin_acentos(s)
- # Elimina caracteres prohibidos
+ # Remove prohibited characters
validchars = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!#$%&'()-@[]^_`{}~."
stripped = ''.join(c for c in s if c in validchars)
- # Convierte a iso
+ # Convert to iso
s = stripped.encode("iso-8859-1")
if PY3:
s = s.decode('utf-8')
@@ -124,30 +124,30 @@ def limpia_nombre_excepto_2(s):
def getfilefromtitle(url, title):
- # Imprime en el log lo que va a descartar
+ # Print in the log what you will discard
logger.info("title=" + title)
logger.info("url=" + url)
plataforma = config.get_system_platform()
logger.info("platform=" + plataforma)
- # nombrefichero = xbmc.makeLegalFilename(title + url[-4:])
+ # filename = xbmc.makeLegalFilename(title + url[-4:])
from core import scrapertools
nombrefichero = title + scrapertools.get_filename_from_url(url)[-4:]
- logger.info("filename=%s" % nombrefichero)
+ logger.info("filename= %s" % nombrefichero)
if "videobb" in url or "videozer" in url or "putlocker" in url:
nombrefichero = title + ".flv"
if "videobam" in url:
nombrefichero = title + "." + url.rsplit(".", 1)[1][0:3]
- logger.info("filename=%s" % nombrefichero)
+ logger.info("filename= %s" % nombrefichero)
nombrefichero = limpia_nombre_caracteres_especiales(nombrefichero)
- logger.info("filename=%s" % nombrefichero)
+ logger.info("filename= %s" % nombrefichero)
fullpath = filetools.join(config.get_setting("downloadpath"), nombrefichero)
- logger.info("fullpath=%s" % fullpath)
+ logger.info("fullpath= %s" % fullpath)
if config.is_xbmc() and fullpath.startswith("special://"):
import xbmc
@@ -164,7 +164,7 @@ def downloadtitle(url, title):
def downloadbest(video_urls, title, continuar=False):
logger.info()
- # Le da la vuelta, para poner el de más calidad primero ( list() es para que haga una copia )
+ # Flip it over, to put the highest quality one first (list () is for you to make a copy of)
invertida = list(video_urls)
invertida.reverse()
@@ -176,10 +176,10 @@ def downloadbest(video_urls, title, continuar=False):
else:
logger.info("Downloading option " + title + " " + url.encode('ascii', 'ignore').decode('utf-8'))
- # Calcula el fichero donde debe grabar
+ # Calculate the file where you should record
try:
fullpath = getfilefromtitle(url, title.strip())
- # Si falla, es porque la URL no vale para nada
+ # If it fails, it is because the URL is useless
except:
import traceback
logger.error(traceback.format_exc())
@@ -188,24 +188,24 @@ def downloadbest(video_urls, title, continuar=False):
# Descarga
try:
ret = downloadfile(url, fullpath, continuar=continuar)
- # Llegados a este punto, normalmente es un timeout
+ # At this point, it is usually a timeout.
except urllib.error.URLError as e:
import traceback
logger.error(traceback.format_exc())
ret = -2
- # El usuario ha cancelado la descarga
+ # The user has canceled the download
if ret == -1:
return -1
else:
- # El fichero ni siquiera existe
+ # EThe file doesn't even exist
if not filetools.exists(fullpath):
logger.info("-> You have not downloaded anything, testing with the following option if there is")
- # El fichero existe
+ # The file exists
else:
tamanyo = filetools.getsize(fullpath)
- # Tiene tamaño 0
+ # It has size 0
if tamanyo == 0:
logger.info("-> Download a file with size 0, testing with the following option if it exists")
os.remove(fullpath)
@@ -217,8 +217,8 @@ def downloadbest(video_urls, title, continuar=False):
def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False, resumir=True):
- logger.info("url=" + url)
- logger.info("filename=" + nombrefichero)
+ logger.info("url= " + url)
+ logger.info("filename= " + nombrefichero)
if headers is None:
headers = []
@@ -230,36 +230,36 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
nombrefichero = xbmc.translatePath(nombrefichero)
try:
- # Si no es XBMC, siempre a "Silent"
+ # If it is not XBMC, always "Silent"
from platformcode import platformtools
- # antes
+ # before
# f=open(nombrefichero,"wb")
try:
import xbmc
nombrefichero = xbmc.makeLegalFilename(nombrefichero)
except:
pass
- logger.info("filename=" + nombrefichero)
+ logger.info("filename= " + nombrefichero)
- # El fichero existe y se quiere continuar
+ # The file exists and you want to continue
if filetools.exists(nombrefichero) and continuar:
f = filetools.file_open(nombrefichero, 'r+b', vfs=VFS)
if resumir:
exist_size = filetools.getsize(nombrefichero)
- logger.info("the file exists, size=%d" % exist_size)
+ logger.info("the file exists, size= %d" % exist_size)
grabado = exist_size
f.seek(exist_size)
else:
exist_size = 0
grabado = 0
- # el fichero ya existe y no se quiere continuar, se aborta
+ # the file already exists and you don't want to continue, it aborts
elif filetools.exists(nombrefichero) and not continuar:
logger.info("the file exists, it does not download again")
return -3
- # el fichero no existe
+ # the file does not exist
else:
exist_size = 0
logger.info("the file does not exist")
@@ -267,11 +267,11 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
f = filetools.file_open(nombrefichero, 'wb', vfs=VFS)
grabado = 0
- # Crea el diálogo de progreso
+ # Create the progress dialog
if not silent:
progreso = platformtools.dialog_progress("plugin", "Downloading...", url, nombrefichero)
- # Si la plataforma no devuelve un cuadro de diálogo válido, asume modo silencio
+ # If the platform does not return a valid dialog box, it assumes silent mode
if progreso is None:
silent = True
@@ -291,29 +291,28 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
url = url.split("|")[0]
logger.info("url=" + url)
- # Timeout del socket a 60 segundos
+ # Socket timeout at 60 seconds
socket.setdefaulttimeout(60)
h = urllib.request.HTTPHandler(debuglevel=0)
request = urllib.request.Request(url)
for header in headers:
- logger.info("Header=" + header[0] + ": " + header[1])
+ logger.info("Header= " + header[0] + ": " + header[1])
request.add_header(header[0], header[1])
if exist_size > 0:
- request.add_header('Range', 'bytes=%d-' % (exist_size,))
+ request.add_header('Range', 'bytes= %d-' % (exist_size,))
opener = urllib.request.build_opener(h)
urllib.request.install_opener(opener)
try:
connexion = opener.open(request)
except urllib.error.HTTPError as e:
- logger.error("error %d (%s) al abrir la url %s" %
- (e.code, e.msg, url))
+ logger.error("error %d (%s) opening url %s" % (e.code, e.msg, url))
f.close()
if not silent:
progreso.close()
- # El error 416 es que el rango pedido es mayor que el fichero => es que ya está completo
+ # Error 416 is that the requested range is greater than the file => is that it is already complete
if e.code == 416:
return 0
else:
@@ -327,25 +326,25 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
if exist_size > 0:
totalfichero = totalfichero + exist_size
- logger.info("Content-Length=%s" % totalfichero)
+ logger.info("Content-Length= %s" % totalfichero)
blocksize = 100 * 1024
bloqueleido = connexion.read(blocksize)
- logger.info("Starting downloading the file, blocked=%s" % len(bloqueleido))
+ logger.info("Starting downloading the file, blocked= %s" % len(bloqueleido))
maxreintentos = 10
while len(bloqueleido) > 0:
try:
- # Escribe el bloque leido
+ # Write the block read
f.write(bloqueleido)
grabado += len(bloqueleido)
percent = int(float(grabado) * 100 / float(totalfichero))
totalmb = float(float(totalfichero) / (1024 * 1024))
descargadosmb = float(float(grabado) / (1024 * 1024))
- # Lee el siguiente bloque, reintentando para no parar todo al primer timeout
+ # Read the next block, retrying not to stop everything at the first timeout
reintentos = 0
while reintentos <= maxreintentos:
try:
@@ -371,7 +370,7 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
import traceback
logger.error(traceback.print_exc())
- # El usuario cancelo la descarga
+ # The user cancels the download
try:
if progreso.iscanceled():
logger.info("Download of file canceled")
@@ -381,7 +380,7 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
except:
pass
- # Ha habido un error en la descarga
+ # There was an error in the download
if reintentos > maxreintentos:
logger.info("ERROR in the file download")
f.close()
@@ -407,7 +406,7 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
error = downloadfileRTMP(url, nombrefichero, silent)
if error and not silent:
from platformcode import platformtools
- platformtools.dialog_ok("No puedes descargar ese vídeo", "Las descargas en RTMP aún no", "están soportadas")
+ platformtools.dialog_ok("You cannot download that video "," RTMP downloads not yet "," are supported")
else:
import traceback
from pprint import pprint
@@ -433,21 +432,21 @@ def downloadfile(url, nombrefichero, headers=None, silent=False, continuar=False
def downloadfileRTMP(url, nombrefichero, silent):
- ''' No usa librtmp ya que no siempre está disponible.
- Lanza un subproceso con rtmpdump. En Windows es necesario instalarlo.
- No usa threads así que no muestra ninguna barra de progreso ni tampoco
- se marca el final real de la descarga en el log info.
+ '''
+ Do not use librtmp as it is not always available.
+ Launch a thread with rtmpdump. In Windows it is necessary to install it.
+ It doesn't use threads so it doesn't show any progress bar nor the actual end of the download is marked in the log info.
'''
Programfiles = os.getenv('Programfiles')
if Programfiles: # Windows
rtmpdump_cmd = Programfiles + "/rtmpdump/rtmpdump.exe"
- nombrefichero = '"' + nombrefichero + '"' # Windows necesita las comillas en el nombre
+ nombrefichero = '"' + nombrefichero + '"' # Windows needs the quotes in the name
else:
rtmpdump_cmd = "/usr/bin/rtmpdump"
if not filetools.isfile(rtmpdump_cmd) and not silent:
from platformcode import platformtools
- advertencia = platformtools.dialog_ok("Falta " + rtmpdump_cmd, "Comprueba que rtmpdump está instalado")
+ advertencia = platformtools.dialog_ok("Lack " + rtmpdump_cmd, "Check that rtmpdump is installed")
return True
valid_rtmpdump_options = ["help", "url", "rtmp", "host", "port", "socks", "protocol", "playpath", "playlist",
@@ -475,13 +474,11 @@ def downloadfileRTMP(url, nombrefichero, silent):
try:
rtmpdump_args = [rtmpdump_cmd] + rtmpdump_args + ["-o", nombrefichero]
from os import spawnv, P_NOWAIT
- logger.info("Iniciando descarga del fichero: %s" % " ".join(rtmpdump_args))
+ logger.info("Initiating file download: %s" % " ".join(rtmpdump_args))
rtmpdump_exit = spawnv(P_NOWAIT, rtmpdump_cmd, rtmpdump_args)
if not silent:
from platformcode import platformtools
- advertencia = platformtools.dialog_ok("La opción de descarga RTMP es experimental",
- "y el vídeo se descargará en segundo plano.",
- "No se mostrará ninguna barra de progreso.")
+ advertencia = platformtools.dialog_ok("RTMP download option is experimental", "and the video will download in the background.", "No progress bar will be displayed.")
except:
return True
@@ -489,13 +486,13 @@ def downloadfileRTMP(url, nombrefichero, silent):
def downloadfileGzipped(url, pathfichero):
- logger.info("url=" + url)
+ logger.info("url= " + url)
nombrefichero = pathfichero
- logger.info("filename=" + nombrefichero)
+ logger.info("filename= " + nombrefichero)
import xbmc
nombrefichero = xbmc.makeLegalFilename(nombrefichero)
- logger.info("filename=" + nombrefichero)
+ logger.info("filename= " + nombrefichero)
patron = "(http://[^/]+)/.+"
matches = re.compile(patron, re.DOTALL).findall(url)
@@ -519,11 +516,11 @@ def downloadfileGzipped(url, pathfichero):
txdata = ""
- # Crea el diálogo de progreso
+ # Create the progress dialog
from platformcode import platformtools
progreso = platformtools.dialog_progress("addon", config.get_localized_string(60200), url.split("|")[0], nombrefichero)
- # Timeout del socket a 60 segundos
+ # Socket timeout at 60 seconds
socket.setdefaulttimeout(10)
h = urllib.request.HTTPHandler(debuglevel=0)
@@ -536,10 +533,10 @@ def downloadfileGzipped(url, pathfichero):
try:
connexion = opener.open(request)
except urllib.error.HTTPError as e:
- logger.error("error %d (%s) al abrir la url %s" %
+ logger.error("error %d (%s) when opening the url %s" %
(e.code, e.msg, url))
progreso.close()
- # El error 416 es que el rango pedido es mayor que el fichero => es que ya está completo
+ # Error 416 is that the requested range is greater than the file => is that it is already complete
if e.code == 416:
return 0
else:
@@ -562,13 +559,13 @@ def downloadfileGzipped(url, pathfichero):
nombrefichero = filetools.join(pathfichero, titulo)
totalfichero = int(connexion.headers["Content-Length"])
- # despues
+ # then
f = filetools.file_open(nombrefichero, 'w', vfs=VFS)
logger.info("new file open")
grabado = 0
- logger.info("Content-Length=%s" % totalfichero)
+ logger.info("Content-Length= %s" % totalfichero)
blocksize = 100 * 1024
@@ -581,7 +578,7 @@ def downloadfileGzipped(url, pathfichero):
gzipper = gzip.GzipFile(fileobj=compressedstream)
bloquedata = gzipper.read()
gzipper.close()
- logger.info("Starting downloading the file, blocked=%s" % len(bloqueleido))
+ logger.info("Starting downloading the file, blocked= %s" % len(bloqueleido))
except:
logger.error("ERROR: The file to be downloaded is not compressed with Gzip")
f.close()
@@ -592,14 +589,14 @@ def downloadfileGzipped(url, pathfichero):
while len(bloqueleido) > 0:
try:
- # Escribe el bloque leido
+ # Write the block read
f.write(bloquedata)
grabado += len(bloqueleido)
percent = int(float(grabado) * 100 / float(totalfichero))
totalmb = float(float(totalfichero) / (1024 * 1024))
descargadosmb = float(float(grabado) / (1024 * 1024))
- # Lee el siguiente bloque, reintentando para no parar todo al primer timeout
+ # Read the next block, retrying not to stop everything at the first timeout
reintentos = 0
while reintentos <= maxreintentos:
try:
@@ -621,8 +618,7 @@ def downloadfileGzipped(url, pathfichero):
else:
tiempofalta = 0
logger.info(sec_to_hms(tiempofalta))
- progreso.update(percent, "%.2fMB/%.2fMB (%d%%) %.2f Kb/s %s mancanti " %
- (descargadosmb, totalmb, percent, old_div(velocidad, 1024), sec_to_hms(tiempofalta)))
+ progreso.update(percent, "%.2fMB/%.2fMB (%d%%) %.2f Kb/s %s left " % (descargadosmb, totalmb, percent, old_div(velocidad, 1024), sec_to_hms(tiempofalta)))
break
except:
reintentos += 1
@@ -630,14 +626,14 @@ def downloadfileGzipped(url, pathfichero):
for line in sys.exc_info():
logger.error("%s" % line)
- # El usuario cancelo la descarga
+ # The user cancels the download
if progreso.iscanceled():
logger.info("Download of file canceled")
f.close()
progreso.close()
return -1
- # Ha habido un error en la descarga
+ # There was an error in the download
if reintentos > maxreintentos:
logger.info("ERROR in the file download")
f.close()
@@ -662,10 +658,10 @@ def downloadfileGzipped(url, pathfichero):
def GetTitleFromFile(title):
- # Imprime en el log lo que va a descartar
- logger.info("title=" + title)
+ # Print in the log what you will discard
+ logger.info("title= " + title)
plataforma = config.get_system_platform()
- logger.info("plataform=" + plataforma)
+ logger.info("plataform= " + plataforma)
# nombrefichero = xbmc.makeLegalFilename(title + url[-4:])
nombrefichero = title
@@ -681,16 +677,15 @@ def sec_to_hms(seconds):
def downloadIfNotModifiedSince(url, timestamp):
logger.info("(" + url + "," + time.ctime(timestamp) + ")")
- # Convierte la fecha a GMT
+ # Convert date to GMT
fecha_formateada = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(timestamp))
- logger.info("fechaFormateada=%s" % fecha_formateada)
+ logger.info("Formatted date= %s" % fecha_formateada)
- # Comprueba si ha cambiado
+ # Check if it has changed
inicio = time.clock()
req = urllib.request.Request(url)
req.add_header('If-Modified-Since', fecha_formateada)
- req.add_header('User-Agent',
- 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; es-ES; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12')
+ req.add_header('User-Agent', 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; es-ES; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12')
updated = False
@@ -698,18 +693,18 @@ def downloadIfNotModifiedSince(url, timestamp):
response = urllib.request.urlopen(req)
data = response.read()
- # Si llega hasta aquí, es que ha cambiado
+ # If it gets this far, it has changed
updated = True
response.close()
except urllib.error.URLError as e:
- # Si devuelve 304 es que no ha cambiado
+ # If it returns 304 it is that it has not changed
if hasattr(e, 'code'):
logger.info("HTTP response code : %d" % e.code)
if e.code == 304:
logger.info("It has not changed")
updated = False
- # Agarra los errores con codigo de respuesta del servidor externo solicitado
+ # Grab errors with response code from requested external server
else:
for line in sys.exc_info():
logger.error("%s" % line)
@@ -722,20 +717,20 @@ def downloadIfNotModifiedSince(url, timestamp):
def download_all_episodes(item, channel, first_episode="", preferred_server="vidspot", filter_language=""):
- logger.info("show=" + item.show)
+ logger.info("show= " + item.show)
show_title = item.show
- # Obtiene el listado desde el que se llamó
+ # Gets the listing from which it was called
action = item.extra
- # Esta marca es porque el item tiene algo más aparte en el atributo "extra"
+ # This mark is because the item has something else apart in the "extra" attribute
if "###" in item.extra:
action = item.extra.split("###")[0]
item.extra = item.extra.split("###")[1]
episode_itemlist = getattr(channel, action)(item)
- # Ordena los episodios para que funcione el filtro de first_episode
+ # Sort episodes for the first_episode filter to work
episode_itemlist = sorted(episode_itemlist, key=lambda it: it.title)
from core import servertools
@@ -744,7 +739,7 @@ def download_all_episodes(item, channel, first_episode="", preferred_server="vid
best_server = preferred_server
# worst_server = "moevideos"
- # Para cada episodio
+ # For each episode
if first_episode == "":
empezar = True
else:
@@ -752,9 +747,9 @@ def download_all_episodes(item, channel, first_episode="", preferred_server="vid
for episode_item in episode_itemlist:
try:
- logger.info("episode=" + episode_item.title)
- episode_title = scrapertools.find_single_match(episode_item.title, "(\d+x\d+)")
- logger.info("episode=" + episode_title)
+ logger.info("episode= " + episode_item.title)
+ episode_title = scrapertools.find_single_match(episode_item.title, r"(\d+x\d+)")
+ logger.info("episode= " + episode_title)
except:
import traceback
logger.error(traceback.format_exc())
@@ -769,7 +764,7 @@ def download_all_episodes(item, channel, first_episode="", preferred_server="vid
if not empezar:
continue
- # Extrae los mirrors
+ # Extract the mirrors
try:
mirrors_itemlist = channel.findvideos(episode_item)
except:
@@ -787,7 +782,7 @@ def download_all_episodes(item, channel, first_episode="", preferred_server="vid
for mirror_item in mirrors_itemlist:
- # Si está en español va al principio, si no va al final
+ # If it is in Spanish it goes to the beginning, if it does not go to the end
if "(Italiano)" in mirror_item.title:
if best_server in mirror_item.title.lower():
new_mirror_itemlist_1.append(mirror_item)
@@ -818,7 +813,7 @@ def download_all_episodes(item, channel, first_episode="", preferred_server="vid
new_mirror_itemlist_4 + new_mirror_itemlist_5 + new_mirror_itemlist_6)
for mirror_item in mirrors_itemlist:
- logger.info("mirror=" + mirror_item.title)
+ logger.info("mirror= " + mirror_item.title)
if "(Italiano)" in mirror_item.title:
idioma = "(Italiano)"
@@ -854,16 +849,13 @@ def download_all_episodes(item, channel, first_episode="", preferred_server="vid
if len(video_items) > 0:
video_item = video_items[0]
- # Comprueba que está disponible
- video_urls, puedes, motivo = servertools.resolve_video_urls_for_playing(video_item.server,
- video_item.url,
- video_password="",
- muestra_dialogo=False)
+ # Check that it is available
+ video_urls, puedes, motivo = servertools.resolve_video_urls_for_playing(video_item.server, video_item.url, video_password="", muestra_dialogo=False)
- # Lo añade a la lista de descargas
+ # Adds it to the download list
if puedes:
logger.info("downloading mirror started...")
- # El vídeo de más calidad es el último
+ # The highest quality video is the latest
# mediaurl = video_urls[len(video_urls) - 1][1]
devuelve = downloadbest(video_urls, show_title + " " + episode_title + " " + idioma +
" [" + video_item.server + "]", continuar=False)
@@ -896,9 +888,8 @@ def episodio_ya_descargado(show_title, episode_title):
for fichero in ficheros:
# logger.info("fichero="+fichero)
- if fichero.lower().startswith(show_title.lower()) and \
- scrapertools.find_single_match(fichero, "(\d+x\d+)") == episode_title:
- logger.info("encontrado!")
+ if fichero.lower().startswith(show_title.lower()) and scrapertools.find_single_match(fichero, "(\d+x\d+)") == episode_title:
+ logger.info("found!")
return True
return False
diff --git a/core/filetools.py b/core/filetools.py
index 3b97063c..ec29993b 100644
--- a/core/filetools.py
+++ b/core/filetools.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# filetools
-# Gestion de archivos con discriminación xbmcvfs/samba/local
+# File management with xbmcvfs / samba / local discrimination
# ------------------------------------------------------------
from __future__ import division
-#from builtins import str
+# from builtins import str
from future.builtins import range
from past.utils import old_div
import sys
@@ -18,13 +18,13 @@ import traceback
from core import scrapertools
from platformcode import platformtools, logger
-xbmc_vfs = True # False para desactivar XbmcVFS, True para activar
+xbmc_vfs = True # False to disable XbmcVFS, True to enable
if xbmc_vfs:
try:
import xbmcvfs
if not PY3:
- reload(sys) ### Workoround. Revisar en la migración a Python 3
- sys.setdefaultencoding('utf-8') # xbmcvfs degrada el valor de defaultencoding. Se reestablece
+ reload(sys) # Workoround. Review on migration to Python 3
+ sys.setdefaultencoding('utf-8') # xbmcvfs demeans the value of defaultencoding. It is reestablished
xbmc_vfs = True
except:
xbmc_vfs = False
@@ -35,9 +35,9 @@ if not xbmc_vfs:
from lib.sambatools import libsmb as samba
except:
samba = None
- # Python 2.4 No compatible con modulo samba, hay que revisar
+ # Python 2.4 Not compatible with samba module, you have to check
-# Windows es "mbcs" linux, osx, android es "utf8"
+# Windows is "mbcs" linux, osx, android is "utf8"
if os.name == "nt":
fs_encoding = ""
else:
@@ -47,15 +47,15 @@ else:
def validate_path(path):
"""
- Elimina cáracteres no permitidos
- @param path: cadena a validar
+ Eliminate illegal characters
+ @param path: string to validate
@type path: str
@rtype: str
- @return: devuelve la cadena sin los caracteres no permitidos
+ @return: returns the string without the characters not allowed
"""
chars = ":*?<>|"
- if scrapertools.find_single_match(path, '(^\w+:\/\/)'):
- protocolo = scrapertools.find_single_match(path, '(^\w+:\/\/)')
+ if scrapertools.find_single_match(path, r'(^\w+:\/\/)'):
+ protocolo = scrapertools.find_single_match(path, r'(^\w+:\/\/)')
import re
parts = re.split(r'^\w+:\/\/(.+?)/(.+)', path)[1:3]
return protocolo + parts[0] + "/" + ''.join([c for c in parts[1] if c not in chars])
@@ -72,19 +72,19 @@ def validate_path(path):
def encode(path, _samba=False):
"""
- Codifica una ruta según el sistema operativo que estemos utilizando.
- El argumento path tiene que estar codificado en utf-8
- @type path unicode o str con codificación utf-8
- @param path parámetro a codificar
+ It encodes a path according to the operating system we are using.
+ The path argument has to be encoded in utf-8
+ @type unicode or str path with utf-8 encoding
+ @param path parameter to encode
@type _samba bool
- @para _samba si la ruta es samba o no
+ @para _samba if the path is samba or not
@rtype: str
- @return ruta codificada en juego de caracteres del sistema o utf-8 si samba
+ @return path encoded in system character set or utf-8 if samba
"""
if not isinstance(path, unicode):
path = unicode(path, "utf-8", "ignore")
- if scrapertools.find_single_match(path, '(^\w+:\/\/)') or _samba:
+ if scrapertools.find_single_match(path, r'(^\w+:\/\/)') or _samba:
path = path.encode("utf-8", "ignore")
else:
if fs_encoding and not PY3:
@@ -95,12 +95,12 @@ def encode(path, _samba=False):
def decode(path):
"""
- Convierte una cadena de texto al juego de caracteres utf-8
- eliminando los caracteres que no estén permitidos en utf-8
- @type: str, unicode, list de str o unicode
- @param path: puede ser una ruta o un list() con varias rutas
+ Converts a text string to the utf-8 character set
+ removing characters that are not allowed in utf-8
+ @type: str, unicode, list of str o unicode
+ @param path:can be a path or a list () with multiple paths
@rtype: str
- @return: ruta codificado en UTF-8
+ @return: ruta encoded in UTF-8
"""
if isinstance(path, list):
for x in range(len(path)):
@@ -116,16 +116,15 @@ def decode(path):
def read(path, linea_inicio=0, total_lineas=None, whence=0, silent=False, vfs=True):
"""
- Lee el contenido de un archivo y devuelve los datos
- @param path: ruta del fichero
+ Read the contents of a file and return the data
+ @param path: file path
@type path: str
- @param linea_inicio: primera linea a leer del fichero
- @type linea_inicio: int positivo
- @param total_lineas: numero maximo de lineas a leer. Si es None o superior al total de lineas se leera el
- fichero hasta el final.
- @type total_lineas: int positivo
+ @param linea_inicio: first line to read from the file
+ @type linea_inicio: positive int
+ @param total_lineas: maximum number of lines to read. If it is None or greater than the total lines, the file will be read until the end.
+ @type total_lineas: positive int
@rtype: str
- @return: datos que contiene el fichero
+ @return: data contained in the file
"""
path = encode(path)
try:
@@ -182,13 +181,13 @@ def read(path, linea_inicio=0, total_lineas=None, whence=0, silent=False, vfs=Tr
def write(path, data, mode="wb", silent=False, vfs=True):
"""
- Guarda los datos en un archivo
- @param path: ruta del archivo a guardar
+ Save the data to a file
+ @param path: file path to save
@type path: str
- @param data: datos a guardar
+ @param data: data to save
@type data: str
@rtype: bool
- @return: devuelve True si se ha escrito correctamente o False si ha dado un error
+ @return: returns True if it was written correctly or False if it gave an error
"""
path = encode(path)
try:
@@ -205,7 +204,7 @@ def write(path, data, mode="wb", silent=False, vfs=True):
f.write(data)
f.close()
except:
- logger.error("ERROR al guardar el archivo: %s" % path)
+ logger.error("ERROR saving file: %s" % path)
if not silent:
logger.error(traceback.format_exc())
return False
@@ -215,11 +214,11 @@ def write(path, data, mode="wb", silent=False, vfs=True):
def file_open(path, mode="r", silent=False, vfs=True):
"""
- Abre un archivo
- @param path: ruta
+ Open a file
+ @param path: path
@type path: str
@rtype: str
- @return: objeto file
+ @return: file object
"""
path = encode(path)
try:
@@ -245,11 +244,11 @@ def file_open(path, mode="r", silent=False, vfs=True):
def file_stat(path, silent=False, vfs=True):
"""
- Stat de un archivo
- @param path: ruta
+ Stat of a file
+ @param path: path
@type path: str
@rtype: str
- @return: objeto file
+ @return: file object
"""
path = encode(path)
try:
@@ -266,13 +265,13 @@ def file_stat(path, silent=False, vfs=True):
def rename(path, new_name, silent=False, strict=False, vfs=True):
"""
- Renombra un archivo o carpeta
- @param path: ruta del fichero o carpeta a renombrar
+ Rename a file or folder
+ @param path: path of the file or folder to rename
@type path: str
- @param new_name: nuevo nombre
+ @param new_name: new name
@type new_name: str
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
path = encode(path)
try:
@@ -309,13 +308,13 @@ def rename(path, new_name, silent=False, strict=False, vfs=True):
def move(path, dest, silent=False, strict=False, vfs=True):
"""
- Mueve un archivo
- @param path: ruta del fichero a mover
+ Move a file
+ @param path: path of the file to move
@type path: str
- @param dest: ruta donde mover
+ @param dest: path where to move
@type dest: str
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
try:
if xbmc_vfs and vfs:
@@ -343,10 +342,10 @@ def move(path, dest, silent=False, strict=False, vfs=True):
dest = encode(dest)
path = encode(path)
os.rename(path, dest)
- # mixto En este caso se copia el archivo y luego se elimina el de origen
+ # mixed In this case the file is copied and then the source file is deleted
else:
if not silent:
- dialogo = platformtools.dialog_progress("Copiando archivo", "")
+ dialogo = platformtools.dialog_progress("Copying file", "")
return copy(path, dest) == True and remove(path) == True
except:
logger.error("ERROR when moving file: %s to %s" % (path, dest))
@@ -359,29 +358,29 @@ def move(path, dest, silent=False, strict=False, vfs=True):
def copy(path, dest, silent=False, vfs=True):
"""
- Copia un archivo
- @param path: ruta del fichero a copiar
+ Copy a file
+ @param path: path of the file to copy
@type path: str
- @param dest: ruta donde copiar
+ @param dest: path to copy
@type dest: str
- @param silent: se muestra o no el cuadro de dialogo
+ @param silent: the dialog box is displayed or not
@type silent: bool
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
try:
if xbmc_vfs and vfs:
path = encode(path)
dest = encode(dest)
if not silent:
- dialogo = platformtools.dialog_progress("Copiando archivo", "")
+ dialogo = platformtools.dialog_progress("Copying file", "")
return bool(xbmcvfs.copy(path, dest))
fo = file_open(path, "rb")
fd = file_open(dest, "wb")
if fo and fd:
if not silent:
- dialogo = platformtools.dialog_progress("Copiando archivo", "")
+ dialogo = platformtools.dialog_progress("Copying file", "")
size = getsize(path)
copiado = 0
while True:
@@ -408,11 +407,11 @@ def copy(path, dest, silent=False, vfs=True):
def exists(path, silent=False, vfs=True):
"""
- Comprueba si existe una carpeta o fichero
- @param path: ruta
+ Check if there is a folder or file
+ @param path: path
@type path: str
@rtype: bool
- @return: Retorna True si la ruta existe, tanto si es una carpeta como un archivo
+ @return: Returns True if the path exists, whether it is a folder or a file
"""
path = encode(path)
try:
@@ -434,16 +433,16 @@ def exists(path, silent=False, vfs=True):
def isfile(path, silent=False, vfs=True):
"""
- Comprueba si la ruta es un fichero
- @param path: ruta
+ Check if the path is a file
+ @param path: path
@type path: str
@rtype: bool
- @return: Retorna True si la ruta existe y es un archivo
+ @return: Returns True if the path exists and is a file
"""
path = encode(path)
try:
if xbmc_vfs and vfs:
- if not scrapertools.find_single_match(path, '(^\w+:\/\/)'):
+ if not scrapertools.find_single_match(path, r'(^\w+:\/\/)'):
return os.path.isfile(path)
if path.endswith('/') or path.endswith('\\'):
path = path[:-1]
@@ -466,16 +465,16 @@ def isfile(path, silent=False, vfs=True):
def isdir(path, silent=False, vfs=True):
"""
- Comprueba si la ruta es un directorio
- @param path: ruta
+ Check if the path is a directory
+ @param path: path
@type path: str
@rtype: bool
- @return: Retorna True si la ruta existe y es un directorio
+ @return: Returns True if the path exists and is a directory
"""
path = encode(path)
try:
if xbmc_vfs and vfs:
- if not scrapertools.find_single_match(path, '(^\w+:\/\/)'):
+ if not scrapertools.find_single_match(path, r'(^\w+:\/\/)'):
return os.path.isdir(path)
if path.endswith('/') or path.endswith('\\'):
path = path[:-1]
@@ -498,11 +497,11 @@ def isdir(path, silent=False, vfs=True):
def getsize(path, silent=False, vfs=True):
"""
- Obtiene el tamaño de un archivo
- @param path: ruta del fichero
+ Gets the size of a file
+ @param path: file path
@type path: str
@rtype: str
- @return: tamaño del fichero
+ @return: file size
"""
path = encode(path)
try:
@@ -525,11 +524,11 @@ def getsize(path, silent=False, vfs=True):
def remove(path, silent=False, vfs=True):
"""
- Elimina un archivo
- @param path: ruta del fichero a eliminar
+ Delete a file
+ @param path: path of the file to delete
@type path: str
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
path = encode(path)
try:
@@ -551,11 +550,11 @@ def remove(path, silent=False, vfs=True):
def rmdirtree(path, silent=False, vfs=True):
"""
- Elimina un directorio y su contenido
- @param path: ruta a eliminar
+ Delete a directory and its contents
+ @param path: path to remove
@type path: str
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
path = encode(path)
try:
@@ -591,11 +590,11 @@ def rmdirtree(path, silent=False, vfs=True):
def rmdir(path, silent=False, vfs=True):
"""
- Elimina un directorio
- @param path: ruta a eliminar
+ Delete a directory
+ @param path: path to remove
@type path: str
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
path = encode(path)
try:
@@ -619,11 +618,11 @@ def rmdir(path, silent=False, vfs=True):
def mkdir(path, silent=False, vfs=True):
"""
- Crea un directorio
- @param path: ruta a crear
+ Create a directory
+ @param path: path to create
@type path: str
@rtype: bool
- @return: devuelve False en caso de error
+ @return: returns False on error
"""
path = encode(path)
try:
@@ -652,37 +651,37 @@ def mkdir(path, silent=False, vfs=True):
def walk(top, topdown=True, onerror=None, vfs=True):
"""
- Lista un directorio de manera recursiva
- @param top: Directorio a listar, debe ser un str "UTF-8"
+ List a directory recursively
+ @param top: Directory to list, must be a str "UTF-8"
@type top: str
- @param topdown: se escanea de arriba a abajo
+ @param topdown: scanned from top to bottom
@type topdown: bool
- @param onerror: muestra error para continuar con el listado si tiene algo seteado sino levanta una excepción
+ @param onerror: show error to continue listing if you have something set but raise an exception
@type onerror: bool
- ***El parametro followlinks que por defecto es True, no se usa aqui, ya que en samba no discrimina los links
+ ***The followlinks parameter, which by default is True, is not used here, since in samba it does not discriminate links
"""
top = encode(top)
if xbmc_vfs and vfs:
for a, b, c in walk_vfs(top, topdown, onerror):
- # list(b) es para que haga una copia del listado de directorios
- # si no da error cuando tiene que entrar recursivamente en directorios con caracteres especiales
+ # list (b) is for you to make a copy of the directory listing
+ # if it doesn't give error when you have to recursively enter directories with special characters
yield a, list(b), c
elif top.lower().startswith("smb://"):
for a, b, c in samba.walk(top, topdown, onerror):
- # list(b) es para que haga una copia del listado de directorios
- # si no da error cuando tiene que entrar recursivamente en directorios con caracteres especiales
+ # list (b) is for you to make a copy of the directory listing
+ # if it doesn't give error when you have to recursively enter directories with special characters
yield decode(a), decode(list(b)), decode(c)
else:
for a, b, c in os.walk(top, topdown, onerror):
- # list(b) es para que haga una copia del listado de directorios
- # si no da error cuando tiene que entrar recursivamente en directorios con caracteres especiales
+ # list (b) is for you to make a copy of the directory listing
+ # if it doesn't give error when you have to recursively enter directories with special characters
yield decode(a), decode(list(b)), decode(c)
def walk_vfs(top, topdown=True, onerror=None):
"""
- Lista un directorio de manera recursiva
- Como xmbcvfs no tiene esta función, se copia la lógica de libsmb(samba) para realizar la previa al Walk
+ List a directory recursively
+ Since xmbcvfs does not have this function, the logic of libsmb (samba) is copied to carry out the pre-Walk
"""
top = encode(top)
dirs, nondirs = xbmcvfs.listdir(top)
@@ -707,11 +706,11 @@ def walk_vfs(top, topdown=True, onerror=None):
def listdir(path, silent=False, vfs=True):
"""
- Lista un directorio
- @param path: Directorio a listar, debe ser un str "UTF-8"
+ List a directory
+ @param path: Directory to list, must be a str "UTF-8"
@type path: str
@rtype: str
- @return: contenido de un directorio
+ @return: content of a directory
"""
path = encode(path)
@@ -732,10 +731,10 @@ def listdir(path, silent=False, vfs=True):
def join(*paths):
"""
- Junta varios directorios
- Corrige las barras "/" o "\" segun el sistema operativo y si es o no smaba
+ Join several directories
+ Correct the bars "/" or "\" according to the operating system and whether or not it is smaba
@rytpe: str
- @return: la ruta concatenada
+ @return: the concatenated path
"""
list_path = []
if paths[0].startswith("/"):
@@ -754,14 +753,14 @@ def join(*paths):
def split(path, vfs=True):
"""
- Devuelve una tupla formada por el directorio y el nombre del fichero de una ruta
+ Returns a tuple consisting of the directory and filename of a path
@param path: ruta
@type path: str
@return: (dirname, basename)
@rtype: tuple
"""
- if scrapertools.find_single_match(path, '(^\w+:\/\/)'):
- protocol = scrapertools.find_single_match(path, '(^\w+:\/\/)')
+ if scrapertools.find_single_match(path, r'(^\w+:\/\/)'):
+ protocol = scrapertools.find_single_match(path, r'(^\w+:\/\/)')
if '/' not in path[6:]:
path = path.replace(protocol, protocol + "/", 1)
return path.rsplit('/', 1)
@@ -771,10 +770,10 @@ def split(path, vfs=True):
def basename(path, vfs=True):
"""
- Devuelve el nombre del fichero de una ruta
- @param path: ruta
+ Returns the file name of a path
+ @param path: path
@type path: str
- @return: fichero de la ruta
+ @return: path file
@rtype: str
"""
return split(path)[1]
@@ -782,10 +781,10 @@ def basename(path, vfs=True):
def dirname(path, vfs=True):
"""
- Devuelve el directorio de una ruta
- @param path: ruta
+ Returns the directory of a path
+ @param path: path
@type path: str
- @return: directorio de la ruta
+ @return: path directory
@rtype: str
"""
return split(path)[0]
@@ -797,15 +796,15 @@ def is_relative(path):
def remove_tags(title):
"""
- devuelve el titulo sin tags como color
+ returns the title without tags as color
@type title: str
@param title: title
@rtype: str
- @return: cadena sin tags
+ @return: string without tags
"""
logger.info()
- title_without_tags = scrapertools.find_single_match(title, '\[color .+?\](.+)\[\/color\]')
+ title_without_tags = scrapertools.find_single_match(title, r'\[color .+?\](.+)\[\/color\]')
if title_without_tags:
return title_without_tags
@@ -815,19 +814,19 @@ def remove_tags(title):
def remove_smb_credential(path):
"""
- devuelve el path sin contraseña/usuario para paths de SMB
- @param path: ruta
+ returns the path without password / user for SMB paths
+ @param path: path
@type path: str
- @return: cadena sin credenciales
+ @return: chain without credentials
@rtype: str
"""
logger.info()
- if not scrapertools.find_single_match(path, '(^\w+:\/\/)'):
+ if not scrapertools.find_single_match(path, r'(^\w+:\/\/)'):
return path
- protocol = scrapertools.find_single_match(path, '(^\w+:\/\/)')
- path_without_credentials = scrapertools.find_single_match(path, '^\w+:\/\/(?:[^;\n]+;)?(?:[^:@\n]+[:|@])?(?:[^@\n]+@)?(.*?$)')
+ protocol = scrapertools.find_single_match(path, r'(^\w+:\/\/)')
+ path_without_credentials = scrapertools.find_single_match(path, r'^\w+:\/\/(?:[^;\n]+;)?(?:[^:@\n]+[:|@])?(?:[^@\n]+@)?(.*?$)')
if path_without_credentials:
return (protocol + path_without_credentials)
diff --git a/core/httptools.py b/core/httptools.py
index 9cf736ed..1b005e0c 100755
--- a/core/httptools.py
+++ b/core/httptools.py
@@ -384,7 +384,7 @@ def downloadpage(url, **opt):
info_dict.append(('Success', 'False'))
response['code'] = str(e)
info_dict.append(('Response code', str(e)))
- info_dict.append(('Finalizado en', time.time() - inicio))
+ info_dict.append(('Finished in', time.time() - inicio))
if not opt.get('alfa_s', False):
show_infobox(info_dict)
return type('HTTPResponse', (), response)
diff --git a/core/item.py b/core/item.py
index f3d33b9a..5f17dadf 100644
--- a/core/item.py
+++ b/core/item.py
@@ -12,9 +12,9 @@ if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
if PY3:
#from future import standard_library
#standard_library.install_aliases()
- import urllib.parse as urllib # Es muy lento en PY2. En PY3 es nativo
+ import urllib.parse as urllib # It is very slow in PY2. In PY3 it is native
else:
- import urllib # Usamos el nativo de PY2 que es más rápido
+ import urllib # We use the native of PY2 which is faster
from core.scrapertools import unescape
import base64
@@ -29,14 +29,14 @@ class InfoLabels(dict):
def __setitem__(self, name, value):
if name in ["season", "episode"]:
- # forzamos int() en season y episode
+ # we force int () in season and episode
try:
super(InfoLabels, self).__setitem__(name, int(value))
except:
pass
elif name in ['IMDBNumber', 'imdb_id']:
- # Por compatibilidad hemos de guardar el valor en los tres campos
+ # For compatibility we have to save the value in the three fields
super(InfoLabels, self).__setitem__('IMDBNumber', str(value))
# super(InfoLabels, self).__setitem__('code', value)
super(InfoLabels, self).__setitem__('imdb_id', str(value))
@@ -62,22 +62,22 @@ class InfoLabels(dict):
El parametro 'default' en la funcion obj_infoLabels.get(key,default) tiene preferencia sobre los aqui definidos.
"""
if key in ['rating']:
- # Ejemplo de clave q devuelve un str formateado como float por defecto
+ # Key example q returns a str formatted as float by default
return '0.0'
elif key == 'code':
code = []
- # Añadir imdb_id al listado de codigos
+ # Add imdb_id to the code list
if 'imdb_id' in list(super(InfoLabels, self).keys()) and super(InfoLabels, self).__getitem__('imdb_id'):
code.append(super(InfoLabels, self).__getitem__('imdb_id'))
- # Completar con el resto de codigos
+ # Complete with the rest of the codes
for scr in ['tmdb_id', 'tvdb_id', 'noscrap_id']:
if scr in list(super(InfoLabels, self).keys()) and super(InfoLabels, self).__getitem__(scr):
value = "%s%s" % (scr[:-2], super(InfoLabels, self).__getitem__(scr))
code.append(value)
- # Opcion añadir un code del tipo aleatorio
+ # Option to add a code of the random type
if not code:
import time
value = time.strftime("%Y%m%d%H%M%S", time.gmtime())
@@ -109,7 +109,7 @@ class InfoLabels(dict):
return 'list'
else:
- # El resto de claves devuelven cadenas vacias por defecto
+ # The rest of the keys return empty strings by default
return ""
def tostring(self, separador=', '):
@@ -132,7 +132,7 @@ class InfoLabels(dict):
class Item(object):
def __init__(self, **kwargs):
"""
- Inicializacion del item
+ Item initialization
"""
# Creamos el atributo infoLabels
@@ -159,14 +159,13 @@ class Item(object):
def __contains__(self, m):
"""
- Comprueba si un atributo existe en el item
+ Check if an attribute exists in the item
"""
return m in self.__dict__
def __setattr__(self, name, value):
"""
- Función llamada al modificar cualquier atributo del item, modifica algunos atributos en función de los datos
- modificados.
+ Function called when modifying any attribute of the item, modifies some attributes based on the modified data.
"""
if PY3: name = self.toutf8(name)
value = self.toutf8(value)
@@ -175,14 +174,14 @@ class Item(object):
self.__setattr__(key, value[key])
return
- # Descodificamos los HTML entities
+ # We decode the HTML entities
if name in ["title", "plot", "fulltitle", "contentPlot", "contentTitle"]:
value = self.decode_html(value)
- # Al modificar cualquiera de estos atributos content...
+ # By modifying any of these attributes content...
if name in ["contentTitle", "contentPlot", "plot", "contentSerieName", "contentType", "contentEpisodeTitle",
"contentSeason", "contentEpisodeNumber", "contentThumbnail", "show", "contentQuality", "quality"]:
- # ...y actualizamos infoLables
+ # ...and update infoLables
if name == "contentTitle":
self.__dict__["infoLabels"]["title"] = value
elif name == "contentPlot" or name == "plot":
@@ -203,13 +202,13 @@ class Item(object):
self.__dict__["infoLabels"]["quality"] = value
elif name == "duration":
- # String q representa la duracion del video en segundos
+ # String q represents the duration of the video in seconds
self.__dict__["infoLabels"]["duration"] = str(value)
elif name == "viewcontent" and value not in ["files", "movies", "tvshows", "seasons", "episodes"]:
super(Item, self).__setattr__("viewcontent", "files")
- # Al asignar un valor a infoLables
+ # When assigning a value to infoLables
elif name == "infoLabels":
if isinstance(value, dict):
value_defaultdict = InfoLabels(value)
@@ -220,22 +219,22 @@ class Item(object):
def __getattr__(self, name):
"""
- Devuelve los valores por defecto en caso de que el atributo solicitado no exista en el item
+ Returns the default values in case the requested attribute does not exist in the item
"""
if name.startswith("__"):
return super(Item, self).__getattribute__(name)
- # valor por defecto para folder
+ # default value for folder
if name == "folder":
return True
- # valor por defecto para contentChannel
+ # default value for contentChannel
elif name == "contentChannel":
return "list"
- # valor por defecto para viewcontent
+ # default value for viewcontent
elif name == "viewcontent":
- # intentamos fijarlo segun el tipo de contenido...
+ # we try to fix it according to the type of content...
if self.__dict__["infoLabels"]["mediatype"] == 'movie':
viewcontent = 'movies'
elif self.__dict__["infoLabels"]["mediatype"] in ["tvshow", "season", "episode"]:
@@ -246,7 +245,7 @@ class Item(object):
self.__dict__["viewcontent"] = viewcontent
return viewcontent
- # valores guardados en infoLabels
+ # values saved in infoLabels
elif name in ["contentTitle", "contentPlot", "contentSerieName", "show", "contentType", "contentEpisodeTitle",
"contentSeason", "contentEpisodeNumber", "contentThumbnail", "plot", "duration",
"contentQuality", "quality"]:
@@ -258,7 +257,7 @@ class Item(object):
return self.__dict__["infoLabels"]["tvshowtitle"]
elif name == "contentType":
ret = self.__dict__["infoLabels"]["mediatype"]
- if ret == 'list' and self.__dict__.get("fulltitle", None): # retrocompatibilidad
+ if ret == 'list' and self.__dict__.get("fulltitle", None): # backward compatibility
ret = 'movie'
self.__dict__["infoLabels"]["mediatype"] = ret
return ret
@@ -275,7 +274,7 @@ class Item(object):
else:
return self.__dict__["infoLabels"][name]
- # valor por defecto para el resto de atributos
+ # default value for all other attributes
else:
return ""
@@ -284,28 +283,28 @@ class Item(object):
def set_parent_content(self, parentContent):
"""
- Rellena los campos contentDetails con la informacion del item "padre"
- @param parentContent: item padre
+ Fill the contentDetails fields with the information of the item "parent"
+ @param parentContent: item father
@type parentContent: item
"""
- # Comprueba que parentContent sea un Item
+ # Check that parentContent is an Item
if not type(parentContent) == type(self):
return
- # Copia todos los atributos que empiecen por "content" y esten declarados y los infoLabels
+ # Copy all the attributes that start with "content" and are declared and the infoLabels
for attr in parentContent.__dict__:
if attr.startswith("content") or attr == "infoLabels":
self.__setattr__(attr, parentContent.__dict__[attr])
def tostring(self, separator=", "):
"""
- Genera una cadena de texto con los datos del item para el log
- Uso: logger.info(item.tostring())
- @param separator: cadena que se usará como separador
+ Generate a text string with the item's data for the log
+ Use: logger.info(item.tostring())
+ @param separator: string to be used as a separator
@type separator: str
'"""
dic = self.__dict__.copy()
- # Añadimos los campos content... si tienen algun valor
+ # We add the content fields... if they have any value
for key in ["contentTitle", "contentPlot", "contentSerieName", "contentEpisodeTitle",
"contentSeason", "contentEpisodeNumber", "contentThumbnail"]:
value = self.__getattr__(key)
@@ -337,10 +336,9 @@ class Item(object):
def tourl(self):
"""
- Genera una cadena de texto con los datos del item para crear una url, para volver generar el Item usar
- item.fromurl().
+ Generate a text string with the item data to create a url, to re-generate the Item use item.fromurl ().
- Uso: url = item.tourl()
+ Use: url = item.tourl()
"""
dump = json.dump(self.__dict__).encode("utf8")
# if empty dict
@@ -351,9 +349,9 @@ class Item(object):
def fromurl(self, url):
"""
- Genera un item a partir de una cadena de texto. La cadena puede ser creada por la funcion tourl() o tener
- el formato antiguo: plugin://plugin.video.kod/?channel=... (+ otros parametros)
- Uso: item.fromurl("cadena")
+ Generate an item from a text string. The string can be created by the tourl () function or have
+ the old format: plugin: //plugin.video.kod/? channel = ... (+ other parameters)
+ Use: item.fromurl("string")
@param url: url
@type url: str
@@ -384,12 +382,12 @@ class Item(object):
def tojson(self, path=""):
from core import filetools
"""
- Crea un JSON a partir del item, para guardar archivos de favoritos, lista de descargas, etc...
- Si se especifica un path, te lo guarda en la ruta especificada, si no, devuelve la cadena json
- Usos: item.tojson(path="ruta\archivo\json.json")
- file.write(item.tojson())
+ Create a JSON from the item, to save favorite files, download list, etc....
+ If a path is specified, it saves it in the specified path, if not, it returns the string json
+ Applications: item.tojson(path="path\archivo\json.json")
+ file.write(item.tojson())
- @param path: ruta
+ @param path: path
@type path: str
"""
if path:
@@ -401,14 +399,14 @@ class Item(object):
def fromjson(self, json_item=None, path=""):
from core import filetools
"""
- Genera un item a partir de un archivo JSON
- Si se especifica un path, lee directamente el archivo, si no, lee la cadena de texto pasada.
- Usos: item = Item().fromjson(path="ruta\archivo\json.json")
- item = Item().fromjson("Cadena de texto json")
+ Generate an item from a JSON file
+ If a path is specified, it directly reads the file, if not, it reads the passed text string.
+ Applications: item = Item().fromjson(path="path\archivo\json.json")
+ item = Item().fromjson("Cadena de texto json")
@param json_item: item
@type json_item: json
- @param path: ruta
+ @param path: path
@type path: str
"""
if path:
@@ -431,9 +429,9 @@ class Item(object):
def clone(self, **kwargs):
"""
- Genera un nuevo item clonando el item actual
- Usos: NuevoItem = item.clone()
- NuevoItem = item.clone(title="Nuevo Titulo", action = "Nueva Accion")
+ Generate a new item by cloning the current item
+ Applications: NewItem = item.clone()
+ NuewItem = item.clone(title="New Title", action = "New Action")
"""
newitem = copy.deepcopy(self)
if "infoLabels" in kwargs:
@@ -447,8 +445,8 @@ class Item(object):
@staticmethod
def decode_html(value):
"""
- Descodifica las HTML entities
- @param value: valor a decodificar
+ Decode the HTML entities
+ @param value: value to decode
@type value: str
"""
try:
@@ -461,7 +459,7 @@ class Item(object):
def toutf8(self, *args):
"""
- Pasa el item a utf8
+ Pass the item to utf8
"""
if len(args) > 0:
value = args[0]
diff --git a/core/jsontools.py b/core/jsontools.py
index d12a5213..6ebc799b 100644
--- a/core/jsontools.py
+++ b/core/jsontools.py
@@ -80,15 +80,14 @@ def to_utf8(dct):
def get_node_from_file(name_file, node, path=None):
"""
- Obtiene el nodo de un fichero JSON
+ Gets the node of a JSON file
- @param name_file: Puede ser el nombre de un canal o server (sin incluir extension)
- o bien el nombre de un archivo json (con extension)
+ @param name_file: It can be the name of a channel or server (not including extension) or the name of a json file (with extension)
@type name_file: str
- @param node: nombre del nodo a obtener
+ @param node: name of the node to obtain
@type node: str
- @param path: Ruta base del archivo json. Por defecto la ruta de settings_channels.
- @return: dict con el nodo a devolver
+ @param path: Base path of the json file. By default the path of settings_channels.
+ @return: dict with the node to return
@rtype: dict
"""
logger.info()
@@ -121,14 +120,13 @@ def get_node_from_file(name_file, node, path=None):
def check_to_backup(data, fname, dict_data):
"""
- Comprueba que si dict_data(conversion del fichero JSON a dict) no es un diccionario, se genere un fichero con
- data de nombre fname.bk.
+ Check that if dict_data (conversion of the JSON file to dict) is not a dictionary, a file with data name fname.bk will be generated.
- @param data: contenido del fichero fname
+ @param data: fname file content
@type data: str
- @param fname: nombre del fichero leido
+ @param fname: name of the read file
@type fname: str
- @param dict_data: nombre del diccionario
+ @param dict_data: dictionary name
@type dict_data: dict
"""
logger.info()
@@ -137,7 +135,7 @@ def check_to_backup(data, fname, dict_data):
logger.error("Error loading json from file %s" % fname)
if data != "":
- # se crea un nuevo fichero
+ # a new file is created
from core import filetools
title = filetools.write("%s.bk" % fname, data)
if title != "":
@@ -150,16 +148,15 @@ def check_to_backup(data, fname, dict_data):
def update_node(dict_node, name_file, node, path=None, silent=False):
"""
- actualiza el json_data de un fichero con el diccionario pasado
+ update the json_data of a file with the last dictionary
- @param dict_node: diccionario con el nodo
+ @param dict_node: dictionary with node
@type dict_node: dict
- @param name_file: Puede ser el nombre de un canal o server (sin incluir extension)
- o bien el nombre de un archivo json (con extension)
+ @param name_file: It can be the name of a channel or server (not including extension) or the name of a json file (with extension)
@type name_file: str
- @param node: nodo a actualizar
- @param path: Ruta base del archivo json. Por defecto la ruta de settings_channels.
- @return result: Devuelve True si se ha escrito correctamente o False si ha dado un error
+ @param node: node to update
+ @param path: Base path of the json file. By default the path of settings_channels.
+ @return result: Returns True if it was written correctly or False if it gave an error
@rtype: bool
@return json_data
@rtype: dict
@@ -182,7 +179,7 @@ def update_node(dict_node, name_file, node, path=None, silent=False):
try:
data = filetools.read(fname)
dict_data = load(data)
- # es un dict
+ # it's a dict
if dict_data:
if node in dict_data:
if not silent: logger.debug(" the key exists %s" % node)
diff --git a/core/scraper.py b/core/scraper.py
index 44b945fb..2515a77d 100644
--- a/core/scraper.py
+++ b/core/scraper.py
@@ -9,8 +9,7 @@ from core.item import InfoLabels
from platformcode import config, logger
from platformcode import platformtools
-# Este modulo es una interface para poder implementar diferentes scrapers
-# contendra todos las funciones comunes
+# This module is an interface to implement different scrapers, it will contain all the common functions
dict_default = None
scraper = None
@@ -18,36 +17,35 @@ scraper = None
def find_and_set_infoLabels(item):
"""
- función que se llama para buscar y setear los infolabels
+ function called to search and set infolabels
:param item:
- :return: boleano que indica si se ha podido encontrar el 'code'
+ :return: Boolean indicating if the 'code' could be found
"""
global scraper
scraper = None
# logger.debug("item:\n" + item.tostring('\n'))
list_opciones_cuadro = [config.get_localized_string(60223), config.get_localized_string(60224)]
- # Si se añaden más scrapers hay q declararlos aqui-> "modulo_scraper": "Texto_en_cuadro"
- scrapers_disponibles = {'tmdb': config.get_localized_string(60225),
- 'tvdb': config.get_localized_string(60226)}
+ # If more scrapers are added, they must be declared here-> "modulo_scraper": "Text_in_box"
+ scrapers_disponibles = {'tmdb': config.get_localized_string(60225), 'tvdb': config.get_localized_string(60226)}
- # Obtener el Scraper por defecto de la configuracion segun el tipo de contenido
+ # Get the default Scraper of the configuration according to the content type
if item.contentType == "movie":
scraper_actual = ['tmdb'][config.get_setting("scraper_movies", "videolibrary")]
tipo_contenido = config.get_localized_string(70283)
title = item.contentTitle
- # Completar lista de opciones para este tipo de contenido
+ # Complete list of options for this type of content
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
else:
scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
tipo_contenido = "serie"
title = item.contentSerieName
- # Completar lista de opciones para este tipo de contenido
+ # Complete list of options for this type of content
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
list_opciones_cuadro.append(scrapers_disponibles['tvdb'])
- # Importamos el scraper
+ # We import the scraper
try:
scraper = __import__('core.%s' % scraper_actual, fromlist=["core.%s" % scraper_actual])
except ImportError:
@@ -57,34 +55,34 @@ def find_and_set_infoLabels(item):
logger.error(traceback.format_exc())
while scraper:
- # Llamamos a la funcion find_and_set_infoLabels del scraper seleccionado
+ # We call the find_and_set_infoLabels function of the selected scraper
scraper_result = scraper.find_and_set_infoLabels(item)
- # Verificar si existe 'code'
+ # Check if there is a 'code'
if scraper_result and item.infoLabels['code']:
- # code correcto
+ # correct code
logger.info("Identificador encontrado: %s" % item.infoLabels['code'])
scraper.completar_codigos(item)
return True
elif scraper_result:
- # Contenido encontrado pero no hay 'code'
+ # Content found but no 'code'
msg = config.get_localized_string(60227) % title
else:
- # Contenido no encontrado
+ # Content not found
msg = config.get_localized_string(60228) % title
logger.info(msg)
- # Mostrar cuadro con otras opciones:
+ # Show box with other options:
if scrapers_disponibles[scraper_actual] in list_opciones_cuadro:
list_opciones_cuadro.remove(scrapers_disponibles[scraper_actual])
index = platformtools.dialog_select(msg, list_opciones_cuadro)
if index < 0:
- logger.debug("Se ha pulsado 'cancelar' en la ventana '%s'" % msg)
+ logger.debug("You have clicked 'cancel' in the window '%s'" % msg)
return False
elif index == 0:
- # Pregunta el titulo
+ # Ask the title
title = platformtools.dialog_input(title, config.get_localized_string(60229) % tipo_contenido)
if title:
if item.contentType == "movie":
@@ -92,25 +90,25 @@ def find_and_set_infoLabels(item):
else:
item.contentSerieName = title
else:
- logger.debug("he pulsado 'cancelar' en la ventana 'Introduzca el nombre correcto'")
+ logger.debug("I clicked 'cancel' in the window 'Enter the correct name'")
return False
elif index == 1:
- # Hay q crear un cuadro de dialogo para introducir los datos
- logger.info("Completar información")
+ # You have to create a dialog box to enter the data
+ logger.info("Complete information")
if cuadro_completar(item):
- # code correcto
- logger.info("Identificador encontrado: %s" % str(item.infoLabels['code']))
+ # correct code
+ logger.info("Identifier found: %s" % str(item.infoLabels['code']))
return True
# raise
elif list_opciones_cuadro[index] in list(scrapers_disponibles.values()):
- # Obtener el nombre del modulo del scraper
+ # Get the name of the scraper module
for k, v in list(scrapers_disponibles.items()):
if list_opciones_cuadro[index] == v:
if scrapers_disponibles[scraper_actual] not in list_opciones_cuadro:
list_opciones_cuadro.append(scrapers_disponibles[scraper_actual])
- # Importamos el scraper k
+ # We import the scraper k
scraper_actual = k
try:
scraper = None
@@ -119,7 +117,7 @@ def find_and_set_infoLabels(item):
exec("import core." + scraper_actual + " as scraper_module")
break
- logger.error("Error al importar el modulo scraper %s" % scraper_actual)
+ logger.error("Error importing the scraper module %s" % scraper_actual)
def cuadro_completar(item):
@@ -129,7 +127,7 @@ def cuadro_completar(item):
dict_default = {}
COLOR = ["0xFF65B3DA", "0xFFFFFFFF"]
- # Creamos la lista de campos del infoLabel
+ # We create the list of infoLabel fields
controls = [("title", "text", config.get_localized_string(60230)),
("originaltitle", "text", config.get_localized_string(60231)),
("year", "text", config.get_localized_string(60232)),
@@ -171,7 +169,7 @@ def cuadro_completar(item):
if len(c) > 3:
enabled += c[3]
- # default para casos especiales
+ # default for special cases
if c[0] == "url_tmdb" and item.infoLabels["tmdb_id"] and 'tmdb' in item.infoLabels["url_scraper"]:
dict_default[c[0]] = item.infoLabels["url_scraper"]
@@ -181,7 +179,7 @@ def cuadro_completar(item):
if not dict_default[c[0]] or dict_default[c[0]] == 'None' or dict_default[c[0]] == 0:
dict_default[c[0]] = ''
elif isinstance(dict_default[c[0]], (int, float)) or (not PY3 and isinstance(dict_default[c[0]], (int, float, long))):
- # Si es numerico lo convertimos en str
+ # If it is numerical we convert it into str
dict_default[c[0]] = str(dict_default[c[0]])
listado_controles.append({'id': c[0],
@@ -207,7 +205,7 @@ def callback_cuadro_completar(item, dict_values):
global dict_default
if dict_values.get("title", None):
- # Adaptar dict_values a infoLabels validos
+ # Adapt dict_values to valid infoLabels
dict_values['mediatype'] = ['movie', 'tvshow'][dict_values['mediatype']]
for k, v in list(dict_values.items()):
if k in dict_default and dict_default[k] == dict_values[k]:
@@ -229,16 +227,16 @@ def callback_cuadro_completar(item, dict_values):
def get_nfo(item):
"""
- Devuelve la información necesaria para que se scrapee el resultado en la videoteca de kodi,
+ Returns the information necessary for the result to be scraped into the kodi video library,
- @param item: elemento que contiene los datos necesarios para generar la info
+ @param item: element that contains the data necessary to generate the info
@type item: Item
@rtype: str
@return:
"""
logger.info()
if "infoLabels" in item and "noscrap_id" in item.infoLabels:
- # Crea el fichero xml con los datos que se obtiene de item ya que no hay ningún scraper activo
+ # Create the xml file with the data obtained from the item since there is no active scraper
info_nfo = ''
if "season" in item.infoLabels and "episode" in item.infoLabels:
diff --git a/core/scrapertools.py b/core/scrapertools.py
index 0067a9f1..0ab6440c 100644
--- a/core/scrapertools.py
+++ b/core/scrapertools.py
@@ -56,15 +56,15 @@ def find_multiple_matches_groups(text, pattern):
return [m.groupdict() for m in r.finditer(text)]
-# Convierte los codigos html "ñ" y lo reemplaza por "ñ" caracter unicode utf-8
+# Convert html codes "ñ" and replace it with "ñ" unicode utf-8 character
def decodeHtmlentities(data):
- entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8})(;?)")
+ entity_re = re.compile(r"&(#?)(\d{1,5}|\w{1,8})(;?)")
def substitute_entity(match):
ent = match.group(2) + match.group(3)
res = ""
while not ent in html5 and not ent.endswith(";") and match.group(1) != "#":
- # Excepción para cuando '&' se usa como argumento en la urls contenidas en los datos
+ # Exception for when '&' is used as an argument in the urls contained in the data
try:
res = ent[-1] + res
ent = ent[:-1]
@@ -85,9 +85,9 @@ def decodeHtmlentities(data):
def unescape(text):
- """Removes HTML or XML character references
- and entities from a text string.
- keep &, >, < in the source code.
+ """
+ Removes HTML or XML character references and entities from a text string.
+ keep &, >, < in the source code.
from Fredrik Lundh
http://effbot.org/zone/re-sub.htm#unescape-html
"""
@@ -122,7 +122,7 @@ def unescape(text):
return re.sub("?\w+;", fixup, str(text))
- # Convierte los codigos html "ñ" y lo reemplaza por "ñ" caracter unicode utf-8
+ # Convert html codes "ñ" and replace it with "ñ" unicode utf-8 character
# def decodeHtmlentities(string):
@@ -277,7 +277,7 @@ def htmlclean(cadena):
def slugify(title):
# print title
- # Sustituye acentos y eñes
+ # Substitutes accents and eñes
title = title.replace("Á", "a")
title = title.replace("É", "e")
title = title.replace("Í", "i")
@@ -305,23 +305,23 @@ def slugify(title):
title = title.replace("/", "-")
title = title.replace("&", "&")
- # Pasa a minúsculas
+ # Lowercase
title = title.lower().strip()
- # Elimina caracteres no válidos
+ # Remove invalid characters
validchars = "abcdefghijklmnopqrstuvwxyz1234567890- "
title = ''.join(c for c in title if c in validchars)
- # Sustituye espacios en blanco duplicados y saltos de línea
- title = re.compile("\s+", re.DOTALL).sub(" ", title)
+ # Replace duplicate blanks and line breaks
+ title = re.compile(r"\s+", re.DOTALL).sub(" ", title)
- # Sustituye espacios en blanco por guiones
- title = re.compile("\s", re.DOTALL).sub("-", title.strip())
+ # Replace blanks with hyphens
+ title = re.compile(r"\s", re.DOTALL).sub("-", title.strip())
- # Sustituye espacios en blanco duplicados y saltos de línea
- title = re.compile("\-+", re.DOTALL).sub("-", title)
+ # Replace duplicate blanks and line breaks
+ title = re.compile(r"\-+", re.DOTALL).sub("-", title)
- # Arregla casos especiales
+ # Fix special cases
if title.startswith("-"):
title = title[1:]
@@ -337,10 +337,10 @@ def remove_htmltags(string):
def remove_show_from_title(title, show):
# print slugify(title)+" == "+slugify(show)
- # Quita el nombre del programa del título
+ # Remove program name from title
if slugify(title).startswith(slugify(show)):
- # Convierte a unicode primero, o el encoding se pierde
+ # Convert to unicode first, or encoding is lost
title = unicode(title, "utf-8", "replace")
show = unicode(show, "utf-8", "replace")
title = title[len(show):].strip()
@@ -351,7 +351,7 @@ def remove_show_from_title(title, show):
if title == "":
title = str(time.time())
- # Vuelve a utf-8
+ # Return to utf-8
title = title.encode("utf-8", "ignore")
show = show.encode("utf-8", "ignore")
@@ -360,15 +360,15 @@ def remove_show_from_title(title, show):
def get_filename_from_url(url):
if PY3:
- import urllib.parse as urlparse # Es muy lento en PY2. En PY3 es nativo
+ import urllib.parse as urlparse # It is very slow in PY2. In PY3 it is native
else:
- import urlparse # Usamos el nativo de PY2 que es más rápido
+ import urlparse # We use the native of PY2 which is faster
parsed_url = urlparse.urlparse(url)
try:
filename = parsed_url.path
except:
- # Si falla es porque la implementación de parsed_url no reconoce los atributos como "path"
+ # If it fails it is because the implementation of parsed_url does not recognize the attributes as "path"
if len(parsed_url) >= 4:
filename = parsed_url[2]
else:
@@ -382,15 +382,15 @@ def get_filename_from_url(url):
def get_domain_from_url(url):
if PY3:
- import urllib.parse as urlparse # Es muy lento en PY2. En PY3 es nativo
+ import urllib.parse as urlparse # It is very slow in PY2. In PY3 it is native
else:
- import urlparse # Usamos el nativo de PY2 que es más rápido
+ import urlparse # We use the native of PY2 which is faster
parsed_url = urlparse.urlparse(url)
try:
filename = parsed_url.netloc
except:
- # Si falla es porque la implementación de parsed_url no reconoce los atributos como "path"
+ # If it fails it is because the implementation of parsed_url does not recognize the attributes as "path"
if len(parsed_url) >= 4:
filename = parsed_url[1]
else:
@@ -401,8 +401,8 @@ def get_domain_from_url(url):
def get_season_and_episode(title):
"""
- Retorna el numero de temporada y de episodio en formato "1x01" obtenido del titulo de un episodio
- Ejemplos de diferentes valores para title y su valor devuelto:
+ Returns the season and episode number in "1x01" format obtained from the title of an episode
+ Examples of different values for title and its return value:
"serie 101x1.strm", "s101e1.avi", "t101e1.avi" -> '101x01'
"Name TvShow 1x6.avi" -> '1x06'
"Temp 3 episodio 2.avi" -> '3x02'
@@ -412,9 +412,9 @@ def get_season_and_episode(title):
"Episodio 25: titulo episodio" -> '' (no existe el numero de temporada)
"Serie X Temporada 1" -> '' (no existe el numero del episodio)
@type title: str
- @param title: titulo del episodio de una serie
+ @param title: title of a series episode
@rtype: str
- @return: Numero de temporada y episodio en formato "1x01" o cadena vacia si no se han encontrado
+ @return: Nseason and episode number in "1x01" format or empty string if not found
"""
filename = ""
diff --git a/core/servertools.py b/core/servertools.py
index b370a088..afb846a0 100644
--- a/core/servertools.py
+++ b/core/servertools.py
@@ -12,9 +12,9 @@ if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
if PY3:
#from future import standard_library
#standard_library.install_aliases()
- import urllib.parse as urlparse # Es muy lento en PY2. En PY3 es nativo
+ import urllib.parse as urlparse #It is very slow in PY2. In PY3 it is native
else:
- import urlparse # Usamos el nativo de PY2 que es más rápido
+ import urlparse # We use the native of PY2 which is faster
from future.builtins import range
from past.utils import old_div
@@ -35,38 +35,38 @@ server_list = {}
def find_video_items(item=None, data=None):
"""
- Función genérica para buscar vídeos en una página, devolviendo un itemlist con los items listos para usar.
- - Si se pasa un Item como argumento, a los items resultantes mantienen los parametros del item pasado
- - Si no se pasa un Item, se crea uno nuevo, pero no contendra ningun parametro mas que los propios del servidor.
+ Generic function to search for videos on a page, returning an itemlist with the ready-to-use items.
+ - If an Item is passed as an argument, the resulting items keep the parameters of the last item
+ - If an Item is not passed, a new one is created, but it will not contain any parameters other than those of the server.
- @param item: Item al cual se quieren buscar vídeos, este debe contener la url válida
+ @param item: Item to which you want to search for videos, this must contain the valid url
@type item: Item
- @param data: Cadena con el contendio de la página ya descargado (si no se pasa item)
+ @param data: String with the page content already downloaded (if item is not passed)
@type data: str
- @return: devuelve el itemlist con los resultados
+ @return: returns the itemlist with the results
@rtype: list
"""
logger.info()
itemlist = []
- # Descarga la página
+ # Download the page
if data is None:
data = httptools.downloadpage(item.url).data
data = unshortenit.findlinks(data)
- # Crea un item si no hay item
+ # Create an item if there is no item
if item is None:
item = Item()
- # Pasa los campos thumbnail y title a contentThumbnail y contentTitle
+ # Pass the thumbnail and title fields to contentThumbnail and contentTitle
else:
if not item.contentThumbnail:
item.contentThumbnail = item.thumbnail
if not item.contentTitle:
item.contentTitle = item.title
- # Busca los enlaces a los videos
+ # Find the links to the videos
for label, url, server, thumbnail in findvideos(data):
title = config.get_localized_string(70206) % label
itemlist.append(
@@ -77,29 +77,28 @@ def find_video_items(item=None, data=None):
def get_servers_itemlist(itemlist, fnc=None, sort=False):
"""
- Obtiene el servidor para cada uno de los items, en funcion de su url.
- - Asigna el servidor, la url modificada, el thumbnail (si el item no contiene contentThumbnail se asigna el del thumbnail)
- - Si se pasa una funcion por el argumento fnc, esta se ejecuta pasando el item como argumento,
- el resultado de esa funcion se asigna al titulo del item
- - En esta funcion podemos modificar cualquier cosa del item
- - Esta funcion siempre tiene que devolver el item.title como resultado
- - Si no se encuentra servidor para una url, se asigna "directo"
-
- @param itemlist: listado de items
+ Get the server for each of the items, based on their url.
+ - Assign the server, the modified url, the thumbnail (if the item does not contain contentThumbnail the thumbnail is assigned)
+ - If a function is passed through the fnc argument, it is executed by passing the item as an argument, the result of that function is assigned to the title of the item
+ - In this function we can modify anything of the item
+ - This function always has to return the item.title as a result
+ - If no server is found for a url, it is assigned "direct"
+
+ @param itemlist: item list
@type itemlist: list
- @param fnc: función para ejecutar con cada item (para asignar el titulo)
+ @param fnc: function to execute with each item (to assign the title)
@type fnc: function
- @param sort: indica si el listado resultante se ha de ordenar en funcion de la lista de servidores favoritos
+ @param sort: indicates whether the resulting list should be ordered based on the list of favorite servers
@type sort: bool
"""
- # Recorre los servidores
+ # Roam the servers
for serverid in list(get_servers_list().keys()):
server_parameters = get_server_parameters(serverid)
- # Recorre los patrones
+ # Walk the patterns
for pattern in server_parameters.get("find_videos", {}).get("patterns", []):
logger.info(pattern["pattern"])
- # Recorre los resultados
+ # Scroll through the results
for match in re.compile(pattern["pattern"], re.DOTALL).finditer(
"\n".join([item.url.split('|')[0] for item in itemlist if not item.server])):
url = pattern["url"]
@@ -117,13 +116,13 @@ def get_servers_itemlist(itemlist, fnc=None, sort=False):
else:
item.url = url
- # Eliminamos los servidores desactivados
- #itemlist = filter(lambda i: not i.server or is_server_enabled(i.server), itemlist)
- # Filtrar si es necesario
+ # We remove the deactivated servers
+ # itemlist = filter(lambda i: not i.server or is_server_enabled(i.server), itemlist)
+ # Filter if necessary
itemlist = filter_servers(itemlist)
for item in itemlist:
- # Asignamos "directo" en caso de que el server no se encuentre en Alfa
+ # We assign "direct" in case the server is not in KoD
if not item.server and item.url:
item.server = "directo"
@@ -131,7 +130,7 @@ def get_servers_itemlist(itemlist, fnc=None, sort=False):
item.title = fnc(item)
- # Ordenar segun favoriteslist si es necesario
+ # Sort according to favoriteslist if necessary
if sort:
itemlist = sort_servers(itemlist)
@@ -140,11 +139,11 @@ def get_servers_itemlist(itemlist, fnc=None, sort=False):
def findvideos(data, skip=False):
"""
- Recorre la lista de servidores disponibles y ejecuta la funcion findvideosbyserver para cada uno de ellos
- :param data: Texto donde buscar los enlaces
- :param skip: Indica un limite para dejar de recorrer la lista de servidores. Puede ser un booleano en cuyo caso
- seria False para recorrer toda la lista (valor por defecto) o True para detenerse tras el primer servidor que
- retorne algun enlace. Tambien puede ser un entero mayor de 1, que representaria el numero maximo de enlaces a buscar.
+ Scroll through the list of available servers and run the findvideosbyserver function for each of them
+ :param data: Text where to look for the links
+ :param skip: Indicates a limit to stop scrolling through the list of servers. It can be a boolean in which case
+ It would be False to go through the whole list (default value) or True to stop after the first server that
+ return some link. It can also be an integer greater than 1, which would represent the maximum number of links to search.
:return:
"""
logger.info()
@@ -155,7 +154,7 @@ def findvideos(data, skip=False):
is_filter_servers = False
- # Ejecuta el findvideos en cada servidor activo
+ # Run findvideos on each active server
for serverid in servers_list:
'''if not is_server_enabled(serverid):
continue'''
@@ -183,16 +182,16 @@ def findvideosbyserver(data, serverid):
return []
devuelve = []
if "find_videos" in server_parameters:
- # Recorre los patrones
+ # Walk the patterns
for pattern in server_parameters["find_videos"].get("patterns", []):
msg = "%s\npattern: %s" % (serverid, pattern["pattern"])
- # Recorre los resultados
+ # Scroll through the results
for match in re.compile(pattern["pattern"], re.DOTALL).finditer(data):
url = pattern["url"]
- # Crea la url con los datos
+ # Create the url with the data
for x in range(len(match.groups())):
url = url.replace("\\%s" % (x + 1), match.groups()[x])
- msg += "\nurl encontrada: %s" % url
+ msg += "\nfound url: %s" % url
value = server_parameters["name"], url, serverid, server_parameters.get("thumbnail", "")
if value not in devuelve and url not in server_parameters["find_videos"].get("ignore_urls", []):
devuelve.append(value)
@@ -211,7 +210,7 @@ def get_server_from_url(url):
logger.info()
servers_list = list(get_servers_list().keys())
- # Ejecuta el findvideos en cada servidor activo
+ # Run findvideos on each active server
for serverid in servers_list:
'''if not is_server_enabled(serverid):
continue'''
@@ -224,18 +223,18 @@ def get_server_from_url(url):
if not server_parameters["active"]:
continue
if "find_videos" in server_parameters:
- # Recorre los patrones
+ # Walk the patterns
for n, pattern in enumerate(server_parameters["find_videos"].get("patterns", [])):
msg = "%s\npattern: %s" % (serverid, pattern["pattern"])
if not "pattern_compiled" in pattern:
# logger.info('compiled ' + serverid)
pattern["pattern_compiled"] = re.compile(pattern["pattern"])
dict_servers_parameters[serverid]["find_videos"]["patterns"][n]["pattern_compiled"] = pattern["pattern_compiled"]
- # Recorre los resultados
+ # Scroll through the results
match = re.search(pattern["pattern_compiled"], url)
if match:
url = pattern["url"]
- # Crea la url con los datos
+ # Create the url with the data
for x in range(len(match.groups())):
url = url.replace("\\%s" % (x + 1), match.groups()[x])
msg += "\nurl encontrada: %s" % url
@@ -249,19 +248,19 @@ def get_server_from_url(url):
def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialogo=False, background_dialog=False):
"""
- Función para obtener la url real del vídeo
- @param server: Servidor donde está alojado el vídeo
+ Function to get the real url of the video
+ @param server: Server where the video is hosted
@type server: str
- @param url: url del vídeo
+ @param url: video url
@type url: str
- @param video_password: Password para el vídeo
+ @param video_password: Password for the video
@type video_password: str
- @param muestra_dialogo: Muestra el diálogo de progreso
+ @param muestra_dialogo: Show progress dialog
@type muestra_dialogo: bool
@type background_dialog: bool
@param background_dialog: if progress dialog should be in background
- @return: devuelve la url del video
+ @return: returns the url of the video
@rtype: list
"""
logger.info("Server: %s, Url: %s" % (server, url))
@@ -273,14 +272,14 @@ def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialo
error_messages = []
opciones = []
- # Si el vídeo es "directo" o "local", no hay que buscar más
+ # If the video is "direct" or "local", look no further
if server == "directo" or server == "local":
if isinstance(video_password, list):
return video_password, len(video_password) > 0, "
".join(error_messages)
- logger.info("Server: %s, la url es la buena" % server)
+ logger.info("Server: %s, url is good" % server)
video_urls.append(["%s [%s]" % (urlparse.urlparse(url)[2][-4:], server), url])
- # Averigua la URL del vídeo
+ # Find out the video URL
else:
if server:
server_parameters = get_server_parameters(server)
@@ -288,12 +287,11 @@ def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialo
server_parameters = {}
if server_parameters:
- # Muestra un diágo de progreso
+ # Show a progress dialog
if muestra_dialogo:
- progreso = (platformtools.dialog_progress_bg if background_dialog else platformtools.dialog_progress)(config.get_localized_string(20000),
- config.get_localized_string(70180) % server_parameters["name"])
+ progreso = (platformtools.dialog_progress_bg if background_dialog else platformtools.dialog_progress)(config.get_localized_string(20000), config.get_localized_string(70180) % server_parameters["name"])
- # Cuenta las opciones disponibles, para calcular el porcentaje
+ # Count the available options, to calculate the percentage
orden = [
["free"] + [server] + [premium for premium in server_parameters["premium"] if not premium == server],
@@ -309,76 +307,76 @@ def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialo
priority = int(config.get_setting("resolve_priority"))
opciones = sorted(opciones, key=lambda x: orden[priority].index(x))
- logger.info("Opciones disponibles: %s | %s" % (len(opciones), opciones))
+ logger.info("Available options: %s | %s" % (len(opciones), opciones))
else:
- logger.error("No existe conector para el servidor %s" % server)
+ logger.error("There is no connector for the server %s" % server)
error_messages.append(config.get_localized_string(60004) % server)
muestra_dialogo = False
- # Importa el server
+ # Import the server
try:
server_module = __import__('servers.%s' % server, None, None, ["servers.%s" % server])
- logger.info("Servidor importado: %s" % server_module)
+ logger.info("Imported server: %s" % server_module)
except:
server_module = None
if muestra_dialogo:
progreso.close()
- logger.error("No se ha podido importar el servidor: %s" % server)
+ logger.error("Could not import server: %s" % server)
import traceback
logger.error(traceback.format_exc())
- # Si tiene una función para ver si el vídeo existe, lo comprueba ahora
+ # If it has a function to see if the video exists, check it now
if hasattr(server_module, 'test_video_exists'):
- logger.info("Invocando a %s.test_video_exists" % server)
+ logger.info("Invoking a %s.test_video_exists" % server)
try:
video_exists, message = server_module.test_video_exists(page_url=url)
if not video_exists:
error_messages.append(message)
- logger.info("test_video_exists dice que el video no existe")
+ logger.info("test_video_exists says video doesn't exist")
if muestra_dialogo:
progreso.close()
else:
- logger.info("test_video_exists dice que el video SI existe")
+ logger.info("test_video_exists says the video DOES exist")
except:
- logger.error("No se ha podido comprobar si el video existe")
+ logger.error("Could not verify if the video exists")
import traceback
logger.error(traceback.format_exc())
- # Si el video existe y el modo free está disponible, obtenemos la url
+ # If the video exists and the free mode is available, we get the url
if video_exists:
for opcion in opciones:
- # Opcion free y premium propio usa el mismo server
+ # Own free and premium option uses the same server
if opcion == "free" or opcion == server:
serverid = server_module
server_name = server_parameters["name"]
- # Resto de opciones premium usa un debrider
+ # Rest of premium options use a debrider
else:
serverid = __import__('servers.debriders.%s' % opcion, None, None,
["servers.debriders.%s" % opcion])
server_name = get_server_parameters(opcion)["name"]
- # Muestra el progreso
+ # Show progress
if muestra_dialogo:
progreso.update((old_div(100, len(opciones))) * opciones.index(opcion), config.get_localized_string(70180) % server_name)
- # Modo free
+ # Free mode
if opcion == "free":
try:
- logger.info("Invocando a %s.get_video_url" % server)
+ logger.info("Invoking a %s.get_video_url" % server)
response = serverid.get_video_url(page_url=url, video_password=video_password)
video_urls.extend(response)
except:
- logger.error("Error al obtener la url en modo free")
+ logger.error("Error getting url in free mode")
error_messages.append(config.get_localized_string(60006) % server_name)
import traceback
logger.error(traceback.format_exc())
- # Modo premium
+ # Premium mode
else:
try:
- logger.info("Invocando a %s.get_video_url" % opcion)
+ logger.info("Invoking a %s.get_video_url" % opcion)
response = serverid.get_video_url(page_url=url, premium=True,
user=config.get_setting("user", server=opcion),
password=config.get_setting("password", server=opcion),
@@ -390,27 +388,27 @@ def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialo
else:
error_messages.append(config.get_localized_string(60006) % server_name)
except:
- logger.error("Error en el servidor: %s" % opcion)
+ logger.error("Server errorr: %s" % opcion)
error_messages.append(config.get_localized_string(60006) % server_name)
import traceback
logger.error(traceback.format_exc())
- # Si ya tenemos URLS, dejamos de buscar
+ # If we already have URLS, we stop searching
if video_urls and config.get_setting("resolve_stop") == True:
break
- # Cerramos el progreso
+ # We close progress
if muestra_dialogo:
progreso.update(100, config.get_localized_string(60008))
progreso.close()
- # Si no hay opciones disponibles mostramos el aviso de las cuentas premium
+ # If there are no options available, we show the notice of premium accounts
if video_exists and not opciones and server_parameters.get("premium"):
listapremium = [get_server_parameters(premium)["name"] for premium in server_parameters["premium"]]
error_messages.append(
config.get_localized_string(60009) % (server, " o ".join(listapremium)))
- # Si no tenemos urls ni mensaje de error, ponemos uno generico
+ # If we do not have urls or error messages, we put a generic one
elif not video_urls and not error_messages:
error_messages.append(config.get_localized_string(60006) % get_server_parameters(server)["name"])
@@ -419,51 +417,51 @@ def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialo
def get_server_name(serverid):
"""
- Función obtener el nombre del servidor real a partir de una cadena.
- @param serverid: Cadena donde mirar
+ Function get real server name from string.
+ @param serverid: Chain where to look
@type serverid: str
- @return: Nombre del servidor
+ @return: Server name
@rtype: str
"""
serverid = serverid.lower().split(".")[0]
- # Obtenemos el listado de servers
+ # We get the list of servers
server_list = list(get_servers_list().keys())
- # Si el nombre está en la lista
+ # If the name is in the list
if serverid in server_list:
return serverid
- # Recorre todos los servers buscando el nombre
+ # Browse all servers looking for the name
for server in server_list:
params = get_server_parameters(server)
- # Si la nombre esta en el listado de ids
+ # If the name is in the list of ids
if serverid in params["id"]:
return server
- # Si el nombre es mas de una palabra, comprueba si algun id esta dentro del nombre:
+ # If the name is more than one word, check if any id is inside the name:
elif len(serverid.split()) > 1:
for id in params["id"]:
if id in serverid:
return server
- # Si no se encuentra nada se devuelve una cadena vacia
+ # If nothing is found an empty string is returned
return ""
def is_server_enabled(server):
"""
- Función comprobar si un servidor está segun la configuración establecida
- @param server: Nombre del servidor
+ Function check if a server is according to the established configuration
+ @param server: Server name
@type server: str
- @return: resultado de la comprobación
+ @return: check result
@rtype: bool
"""
server = get_server_name(server)
- # El server no existe
+ # The server does not exist
if not server:
return False
@@ -481,11 +479,11 @@ def is_server_enabled(server):
def get_server_parameters(server):
"""
- Obtiene los datos del servidor
- @param server: Nombre del servidor
+ Get data from server
+ @param server: Server name
@type server: str
- @return: datos del servidor
+ @return: server data
@rtype: dict
"""
# logger.info("server %s" % server)
@@ -503,12 +501,11 @@ def get_server_parameters(server):
# Debriders
elif filetools.isfile(filetools.join(config.get_runtime_path(), "servers", "debriders", server + ".json")):
path = filetools.join(config.get_runtime_path(), "servers", "debriders", server + ".json")
- #
- #Cuando no está bien definido el server en el canal (no existe conector), muestra error por no haber "path" y se tiene que revisar el canal
- #
+
+ # When the server is not well defined in the channel (there is no connector), it shows an error because there is no "path" and the channel has to be checked
dict_server = jsontools.load(filetools.read(path))
- # Imagenes: se admiten url y archivos locales dentro de "resources/images"
+ # Images: url and local files are allowed inside "resources / images"
if dict_server.get("thumbnail") and "://" not in dict_server["thumbnail"]:
dict_server["thumbnail"] = filetools.join(config.get_runtime_path(), "resources", "media",
"servers", dict_server["thumbnail"])
@@ -573,7 +570,7 @@ def get_server_controls_settings(server_name):
# Conversion de str a bool, etc...
for c in list_controls:
if 'id' not in c or 'type' not in c or 'default' not in c:
- # Si algun control de la lista no tiene id, type o default lo ignoramos
+ # If any control in the list does not have id, type or default, we ignore it
continue
# new dict with key(id) and value(default) from settings
@@ -584,28 +581,28 @@ def get_server_controls_settings(server_name):
def get_server_setting(name, server, default=None):
"""
- Retorna el valor de configuracion del parametro solicitado.
+ Returns the configuration value of the requested parameter.
- Devuelve el valor del parametro 'name' en la configuracion propia del servidor 'server'.
+ Returns the value of the parameter 'name' in the own configuration of the server 'server'.
- Busca en la ruta \addon_data\plugin.video.addon\settings_servers el archivo server_data.json y lee
- el valor del parametro 'name'. Si el archivo server_data.json no existe busca en la carpeta servers el archivo
- server.json y crea un archivo server_data.json antes de retornar el valor solicitado. Si el parametro 'name'
- tampoco existe en el el archivo server.json se devuelve el parametro default.
+ Look in the path \addon_data\plugin.video.addon\settings_servers for the file server_data.json and read
+ the value of the parameter 'name'. If the server_data.json file does not exist look in the servers folder for the file
+ server.json and create a server_data.json file before returning the requested value. If the parameter 'name'
+ also does not exist in the server.json file the default parameter is returned.
- @param name: nombre del parametro
+ @param name: parameter name
@type name: str
- @param server: nombre del servidor
+ @param server: server name
@type server: str
- @param default: valor devuelto en caso de que no exista el parametro name
+ @param default: return value in case the name parameter does not exist
@type default: any
- @return: El valor del parametro 'name'
+ @return: The parameter value 'name'
@rtype: any
"""
- # Creamos la carpeta si no existe
+ # We create the folder if it does not exist
if not filetools.exists(filetools.join(config.get_data_path(), "settings_servers")):
filetools.mkdir(filetools.join(config.get_data_path(), "settings_servers"))
@@ -613,34 +610,34 @@ def get_server_setting(name, server, default=None):
dict_settings = {}
dict_file = {}
if filetools.exists(file_settings):
- # Obtenemos configuracion guardada de ../settings/channel_data.json
+ # We get saved configuration from ../settings/channel_data.json
try:
dict_file = jsontools.load(filetools.read(file_settings))
if isinstance(dict_file, dict) and 'settings' in dict_file:
dict_settings = dict_file['settings']
except EnvironmentError:
- logger.info("ERROR al leer el archivo: %s" % file_settings)
+ logger.info("ERROR when reading the file: %s" % file_settings)
if not dict_settings or name not in dict_settings:
- # Obtenemos controles del archivo ../servers/server.json
+ # We get controls from the file ../servers/server.json
try:
list_controls, default_settings = get_server_controls_settings(server)
except:
default_settings = {}
- if name in default_settings: # Si el parametro existe en el server.json creamos el server_data.json
+ if name in default_settings: # If the parameter exists in the server.json we create the server_data.json
default_settings.update(dict_settings)
dict_settings = default_settings
dict_file['settings'] = dict_settings
- # Creamos el archivo ../settings/channel_data.json
+ # We create the file ../settings/channel_data.json
if not filetools.write(file_settings, jsontools.dump(dict_file)):
- logger.info("ERROR al salvar el archivo: %s" % file_settings)
+ logger.info("ERROR saving file: %s" % file_settings)
- # Devolvemos el valor del parametro local 'name' si existe, si no se devuelve default
+ # We return the value of the local parameter 'name' if it exists, if default is not returned
return dict_settings.get(name, default)
def set_server_setting(name, value, server):
- # Creamos la carpeta si no existe
+ # We create the folder if it does not exist
if not filetools.exists(filetools.join(config.get_data_path(), "settings_servers")):
filetools.mkdir(filetools.join(config.get_data_path(), "settings_servers"))
@@ -650,24 +647,24 @@ def set_server_setting(name, value, server):
dict_file = None
if filetools.exists(file_settings):
- # Obtenemos configuracion guardada de ../settings/channel_data.json
+ # We get saved configuration from ../settings/channel_data.json
try:
dict_file = jsontools.load(filetools.read(file_settings))
dict_settings = dict_file.get('settings', {})
except EnvironmentError:
- logger.info("ERROR al leer el archivo: %s" % file_settings)
+ logger.info("ERROR when reading the file: %s" % file_settings)
dict_settings[name] = value
- # comprobamos si existe dict_file y es un diccionario, sino lo creamos
+ # we check if dict_file exists and it is a dictionary, if not we create it
if dict_file is None or not dict_file:
dict_file = {}
dict_file['settings'] = dict_settings
- # Creamos el archivo ../settings/channel_data.json
+ # We create the file ../settings/channel_data.json
if not filetools.write(file_settings, jsontools.dump(dict_file)):
- logger.info("ERROR al salvar el archivo: %s" % file_settings)
+ logger.info("ERROR saving file: %s" % file_settings)
return None
return value
@@ -675,10 +672,9 @@ def set_server_setting(name, value, server):
def get_servers_list():
"""
- Obtiene un diccionario con todos los servidores disponibles
+ Get a dictionary with all available servers
- @return: Diccionario cuyas claves son los nombre de los servidores (nombre del json)
- y como valor un diccionario con los parametros del servidor.
+ @return: Diccionario cuyas claves son los nombre de los servidores (nombre del json) and as a value a dictionary with the server parameters.
@rtype: dict
"""
global server_list
@@ -693,10 +689,9 @@ def get_servers_list():
def get_debriders_list():
"""
- Obtiene un diccionario con todos los debriders disponibles
+ Get a dictionary with all available debriders
- @return: Diccionario cuyas claves son los nombre de los debriders (nombre del json)
- y como valor un diccionario con los parametros del servidor.
+ @return: Dictionary whose keys are the names of the debriders (name of the json) and as a value a dictionary with the server parameters.
@rtype: dict
"""
server_list = {}
@@ -711,60 +706,52 @@ def get_debriders_list():
def sort_servers(servers_list):
"""
- Si esta activada la opcion "Ordenar servidores" en la configuracion de servidores y existe un listado de servidores
- favoritos en la configuracion lo utiliza para ordenar la lista servers_list
- :param servers_list: Listado de servidores para ordenar. Los elementos de la lista servers_list pueden ser strings
- u objetos Item. En cuyo caso es necesario q tengan un atributo item.server del tipo str.
- :return: Lista del mismo tipo de objetos que servers_list ordenada en funcion de los servidores favoritos.
+ If the option "Order servers" is activated in the server configuration and there is a list of servers
+ favorites in settings use it to sort the servers_list list
+ :param servers_list: List of servers to order. The items in the servers_list can be strings or Item objects. In which case it is necessary that they have an item.server attribute of type str.
+ :return: List of the same type of objects as servers_list ordered according to the favorite servers.
"""
if servers_list and config.get_setting('favorites_servers'):
if isinstance(servers_list[0], Item):
- servers_list = sorted(servers_list,
- key=lambda x: config.get_setting("favorites_servers_list", server=x.server) or 100)
+ servers_list = sorted(servers_list, key=lambda x: config.get_setting("favorites_servers_list", server=x.server) or 100)
else:
- servers_list = sorted(servers_list,
- key=lambda x: config.get_setting("favorites_servers_list", server=x) or 100)
+ servers_list = sorted(servers_list, key=lambda x: config.get_setting("favorites_servers_list", server=x) or 100)
return servers_list
def filter_servers(servers_list):
"""
- Si esta activada la opcion "Filtrar por servidores" en la configuracion de servidores, elimina de la lista
- de entrada los servidores incluidos en la Lista Negra.
- :param servers_list: Listado de servidores para filtrar. Los elementos de la lista servers_list pueden ser strings
- u objetos Item. En cuyo caso es necesario q tengan un atributo item.server del tipo str.
- :return: Lista del mismo tipo de objetos que servers_list filtrada en funcion de la Lista Negra.
+ If the option "Filter by servers" is activated in the server configuration, removes the servers included in the Black List from the entry list.
+ :param servers_list: List of servers to filter. The items in the servers_list can be strings or Item objects. In which case it is necessary that they have an item.server attribute of type str.
+ :return: List of the same type of objects as servers_list filtered based on the Black List.
"""
- #Eliminamos los inactivos
+ # We eliminate the inactive
if servers_list:
servers_list = [i for i in servers_list if not i.server or is_server_enabled(i.server)]
-
+
if servers_list and config.get_setting('filter_servers'):
if isinstance(servers_list[0], Item):
servers_list_filter = [x for x in servers_list if not config.get_setting("black_list", server=x.server)]
else:
servers_list_filter = [x for x in servers_list if not config.get_setting("black_list", server=x)]
- # Si no hay enlaces despues de filtrarlos
- if servers_list_filter or not platformtools.dialog_yesno(config.get_localized_string(60000),
- config.get_localized_string(60010),
- config.get_localized_string(70281)):
+ # If there are no links after filtering
+ if servers_list_filter or not platformtools.dialog_yesno(config.get_localized_string(60000), config.get_localized_string(60010), config.get_localized_string(70281)):
servers_list = servers_list_filter
-
+
return servers_list
-# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-# Comprobación de enlaces
-# -----------------------
+
+# Checking links
def check_list_links(itemlist, numero='', timeout=3):
"""
- Comprueba una lista de enlaces a videos y la devuelve modificando el titulo con la verificacion.
- El parámetro numero indica cuantos enlaces hay que verificar (0:5, 1:10, 2:15, 3:20)
- El parámetro timeout indica un tope de espera para descargar la página
+ Check a list of video links and return it by modifying the title with verification.
+ The number parameter indicates how many links to check (0:5, 1:10, 2:15, 3:20)
+ The timeout parameter indicates a waiting limit to download the page
"""
numero = numero if numero > 4 else ((int(numero) + 1) * 5) if numero != '' else 5
import sys
@@ -790,47 +777,43 @@ def check_list_links(itemlist, numero='', timeout=3):
def check_video_link(item, timeout=3):
"""
- Comprueba si el enlace a un video es valido y devuelve un string de 2 posiciones con la verificacion.
- :param url, server: Link y servidor
- :return: str(2) '??':No se ha podido comprobar. 'Ok':Parece que el link funciona. 'NO':Parece que no funciona.
- """
+ Check if the link to a video is valid and return a 2-position string with verification.
+ :param url, server: Link and server
+ :return: str(2) '??':Could not be verified. 'Ok': The link seems to work. 'NO': It doesn't seem to work.
+ """
url = item.url
server = item.server
-
+
NK = "[COLOR 0xFFF9B613][B]" + u"\u2022".encode('utf-8') + "[/B][/COLOR]"
OK = "[COLOR 0xFF00C289][B]" + u"\u2022".encode('utf-8') + "[/B][/COLOR]"
KO = "[COLOR 0xFFC20000][B]" + u"\u2022".encode('utf-8') + "[/B][/COLOR]"
- # NK = "[COLOR 0xFFF9B613][B]♥[/B][/COLOR]"
- # OK = "[COLOR 0xFF00C289][B]♥[/B][/COLOR]"
- # KO = "[COLOR 0xFFC20000][B]♥[/B][/COLOR]"
-
try:
server_module = __import__('servers.%s' % server, None, None, ["servers.%s" % server])
except:
server_module = None
- logger.info("[check_video_link] No se puede importar el servidor! %s" % server)
+ logger.info("[check_video_link] Cannot import server! %s" % server)
return item, NK
-
+
if hasattr(server_module, 'test_video_exists'):
ant_timeout = httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT
- httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = timeout # Limitar tiempo de descarga
-
+ httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = timeout # Limit download time
+
try:
video_exists, message = server_module.test_video_exists(page_url=url)
if not video_exists:
- logger.info("[check_video_link] No existe! %s %s %s" % (message, server, url))
+ logger.info("[check_video_link] Does not exist! %s %s %s" % (message, server, url))
resultado = KO
else:
- logger.info("[check_video_link] comprobacion OK %s %s" % (server, url))
+ logger.info("[check_video_link] check ok %s %s" % (server, url))
resultado = OK
except:
- logger.info("[check_video_link] No se puede comprobar ahora! %s %s" % (server, url))
+ logger.info("[check_video_link] Can't check now! %s %s" % (server, url))
resultado = NK
finally:
- httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = ant_timeout # Restaurar tiempo de descarga
+ httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = ant_timeout # Restore download time
return item, resultado
- logger.info("[check_video_link] No hay test_video_exists para servidor: %s" % server)
+ logger.info("[check_video_link] There is no test_video_exists for server: %s" % server)
return item, NK
diff --git a/core/tmdb.py b/core/tmdb.py
index 9fa9e655..13dd22cd 100644
--- a/core/tmdb.py
+++ b/core/tmdb.py
@@ -1,90 +1,65 @@
# -*- coding: utf-8 -*-
-#from future import standard_library
-#standard_library.install_aliases()
-#from builtins import str
+# from future import standard_library
+# standard_library.install_aliases()
+# from builtins import str
import sys
PY3 = False
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
if PY3:
- import urllib.parse as urllib # Es muy lento en PY2. En PY3 es nativo
+ import urllib.parse as urllib # It is very slow in PY2. In PY3 it is native
else:
- import urllib # Usamos el nativo de PY2 que es más rápido
+ import urllib # We use the native of PY2 which is faster
from future.builtins import range
from future.builtins import object
-import ast
+import ast, copy, re, sqlite3, time, xbmcaddon
-import copy
-import re
-import sqlite3
-import time
-
-import xbmcaddon
-
-from core import filetools
-from core import httptools
-from core import jsontools
-from core import scrapertools
+from core import filetools, httptools, jsontools, scrapertools
from core.item import InfoLabels
-from platformcode import config
-from platformcode import logger
+from platformcode import config, logger
info_language = ["de", "en", "es", "fr", "it", "pt"] # from videolibrary.json
def_lang = info_language[config.get_setting("info_language", "videolibrary")]
-# -----------------------------------------------------------------------------------------------------------
-# Conjunto de funciones relacionadas con las infoLabels.
-# version 1.0:
-# Version inicial
+# ------------------------------------------------- -------------------------------------------------- --------
+# Set of functions related to infoLabels.
+# version 1.0:
+# Initial version
#
-# Incluyen:
-# set_infoLabels(source, seekTmdb, idioma_busqueda): Obtiene y fija (item.infoLabels) los datos extras de una o
-# varias series, capitulos o peliculas.
-# set_infoLabels_item(item, seekTmdb, idioma_busqueda): Obtiene y fija (item.infoLabels) los datos extras de una
-# serie, capitulo o pelicula.
-# set_infoLabels_itemlist(item_list, seekTmdb, idioma_busqueda): Obtiene y fija (item.infoLabels) los datos
-# extras de una lista de series, capitulos o peliculas.
-# infoLabels_tostring(item): Retorna un str con la lista ordenada con los infoLabels del item
+# Include:
+# - set_infoLabels (source, seekTmdb, search_language): Gets and sets (item.infoLabels) the extra data of one or several series, chapters or movies.
+# - set_infoLabels_item (item, seekTmdb, search_language): Gets and sets (item.infoLabels) the extra data of a series, chapter or movie.
+# - set_infoLabels_itemlist (item_list, seekTmdb, search_language): Gets and sets (item.infoLabels) the data extras from a list of series, chapters or movies.
+# - infoLabels_tostring (item): Returns a str with the list ordered with the item's infoLabels
#
-# Uso:
-# tmdb.set_infoLabels(item, seekTmdb = True)
+# Usage:
+# - tmdb.set_infoLabels (item, seekTmdb = True)
#
-# Obtener datos basicos de una pelicula:
-# Antes de llamar al metodo set_infoLabels el titulo a buscar debe estar en item.contentTitle
-# y el año en item.infoLabels['year'].
+# Get basic data from a movie:
+# Before calling the set_infoLabels method the title to search for must be in item.contentTitle and the year in item.infoLabels ['year'].
#
-# Obtener datos basicos de una serie:
-# Antes de llamar al metodo set_infoLabels el titulo a buscar debe estar en item.show o en
-# item.contentSerieName.
+# Obtain basic data from a series:
+# Before calling the set_infoLabels method the title to search for must be in item.show or in item.contentSerieName.
#
-# Obtener mas datos de una pelicula o serie:
-# Despues de obtener los datos basicos en item.infoLabels['tmdb'] tendremos el codigo de la serie o pelicula.
-# Tambien podriamos directamente fijar este codigo, si se conoce, o utilizar los codigo correspondientes de:
-# IMDB (en item.infoLabels['IMDBNumber'] o item.infoLabels['code'] o item.infoLabels['imdb_id']), TVDB
-# (solo series, en item.infoLabels['tvdb_id']),
-# Freebase (solo series, en item.infoLabels['freebase_mid']),TVRage (solo series, en
-# item.infoLabels['tvrage_id'])
+# Get more data from a movie or series:
+# After obtaining the basic data in item.infoLabels ['tmdb'] we will have the code of the series or movie.
+# We could also directly set this code, if known, or use the corresponding code of:
+# IMDB (in item.infoLabels ['IMDBNumber'] or item.infoLabels ['code'] or item.infoLabels ['imdb_id']), TVDB (only series, in item.infoLabels ['tvdb_id']),
+# Freebase (series only, on item.infoLabels ['freebase_mid']), TVRage (series only, on item.infoLabels ['tvrage_id'])
#
-# Obtener datos de una temporada:
-# Antes de llamar al metodo set_infoLabels el titulo de la serie debe estar en item.show o en
-# item.contentSerieName,
-# el codigo TMDB de la serie debe estar en item.infoLabels['tmdb'] (puede fijarse automaticamente mediante
-# la consulta de datos basica)
-# y el numero de temporada debe estar en item.infoLabels['season'].
+# Get data from a season:
+# Before calling the set_infoLabels method the series title must be in item.show or in item.contentSerieName,
+# the series TMDB code must be in item.infoLabels ['tmdb'] (it can be set automatically by the basic data query)
+# and the season number must be in item.infoLabels ['season'].
#
-# Obtener datos de un episodio:
-# Antes de llamar al metodo set_infoLabels el titulo de la serie debe estar en item.show o en
-# item.contentSerieName,
-# el codigo TMDB de la serie debe estar en item.infoLabels['tmdb'] (puede fijarse automaticamente mediante la
-# consulta de datos basica),
-# el numero de temporada debe estar en item.infoLabels['season'] y el numero de episodio debe estar en
-# item.infoLabels['episode'].
-#
-#
-# --------------------------------------------------------------------------------------------------------------
+# Get data from an episode:
+# Before calling the set_infoLabels method the series title must be in item.show or in item.contentSerieName,
+# the TMDB code of the series must be in item.infoLabels ['tmdb'] (it can be set automatically using the basic data query),
+# the season number must be in item.infoLabels ['season'] and the episode number must be in item.infoLabels ['episode'].
+# ------------------------------------------------- -------------------------------------------------- -----------
otmdb_global = None
fname = filetools.join(config.get_data_path(), "kod_db.sqlite")
@@ -110,7 +85,7 @@ def drop_bd():
create_bd()
-# El nombre de la funcion es el nombre del decorador y recibe la funcion que decora.
+# The function name is the name of the decorator and receives the function that decorates.
def cache_response(fn):
logger.info()
@@ -153,7 +128,7 @@ def cache_response(fn):
# 1 month - 30 days
elif cache_expire == 3:
- # no tenemos en cuenta febrero o meses con 31 días
+ # we do not take into account February or months with 31 days
if elapsed > datetime.timedelta(days=30):
valided = False
else:
@@ -167,7 +142,7 @@ def cache_response(fn):
result = {}
try:
- # no está activa la cache
+ # cache is not active
if not config.get_setting("tmdb_cache", default=False):
result = fn(*args)
else:
@@ -199,7 +174,7 @@ def cache_response(fn):
# elapsed_time = time.time() - start_time
# logger.debug("TARDADO %s" % elapsed_time)
- # error al obtener los datos
+ # error getting data
except Exception as ex:
message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args))
logger.error("error in: %s" % message)
@@ -211,17 +186,15 @@ def cache_response(fn):
def set_infoLabels(source, seekTmdb=True, idioma_busqueda=def_lang, forced=False):
"""
- Dependiendo del tipo de dato de source obtiene y fija (item.infoLabels) los datos extras de una o varias series,
- capitulos o peliculas.
+ Depending on the data type of source, it obtains and sets (item.infoLabels) the extra data of one or more series, chapters or movies.
- @param source: variable que contiene la información para establecer infoLabels
+ @param source: variable that contains the information to set infoLabels
@type source: list, item
- @param seekTmdb: si es True hace una busqueda en www.themoviedb.org para obtener los datos, en caso contrario
- obtiene los datos del propio Item.
+ @param seekTmdb: if it is True, it searches www.themoviedb.org to obtain the data, otherwise it obtains the data of the Item itself.
@type seekTmdb: bool
- @param idioma_busqueda: fija el valor de idioma en caso de busqueda en www.themoviedb.org
+ @param idioma_busqueda: set the language value in case of search at www.themoviedb.org
@type idioma_busqueda: str
- @return: un numero o lista de numeros con el resultado de las llamadas a set_infoLabels_item
+ @return: a number or list of numbers with the result of the calls to set_infoLabels_item
@rtype: int, list
"""
@@ -240,23 +213,18 @@ def set_infoLabels(source, seekTmdb=True, idioma_busqueda=def_lang, forced=False
def set_infoLabels_itemlist(item_list, seekTmdb=False, idioma_busqueda=def_lang, forced=False):
"""
- De manera concurrente, obtiene los datos de los items incluidos en la lista item_list.
+ Concurrently, it gets the data of the items included in the item_list.
- La API tiene un limite de 40 peticiones por IP cada 10'' y por eso la lista no deberia tener mas de 30 items
- para asegurar un buen funcionamiento de esta funcion.
+ The API has a limit of 40 requests per IP every 10 '' and that is why the list should not have more than 30 items to ensure the proper functioning of this function.
- @param item_list: listado de objetos Item que representan peliculas, series o capitulos. El atributo
- infoLabels de cada objeto Item sera modificado incluyendo los datos extras localizados.
+ @param item_list: list of Item objects that represent movies, series or chapters. The infoLabels attribute of each Item object will be modified including the extra localized data.
@type item_list: list
- @param seekTmdb: Si es True hace una busqueda en www.themoviedb.org para obtener los datos, en caso contrario
- obtiene los datos del propio Item si existen.
+ @param seekTmdb: If it is True, it searches www.themoviedb.org to obtain the data, otherwise it obtains the data of the Item itself if they exist.
@type seekTmdb: bool
- @param idioma_busqueda: Codigo del idioma segun ISO 639-1, en caso de busqueda en www.themoviedb.org.
+ @param idioma_busqueda: Language code according to ISO 639-1, in case of search at www.themoviedb.org.
@type idioma_busqueda: str
- @return: Una lista de numeros cuyo valor absoluto representa la cantidad de elementos incluidos en el atributo
- infoLabels de cada Item. Este numero sera positivo si los datos se han obtenido de www.themoviedb.org y
- negativo en caso contrario.
+ @return: A list of numbers whose absolute value represents the number of elements included in the infoLabels attribute of each Item. This number will be positive if the data has been obtained from www.themoviedb.org and negative otherwise.
@rtype: list
"""
@@ -284,32 +252,29 @@ def set_infoLabels_itemlist(item_list, seekTmdb=False, idioma_busqueda=def_lang,
i += 1
l_hilo.append(t)
- # esperar q todos los hilos terminen
+ # wait for all the threads to end
for x in l_hilo:
x.join()
- # Ordenar lista de resultados por orden de llamada para mantener el mismo orden q item_list
+ # Sort results list by call order to keep the same order q item_list
r_list.sort(key=lambda i: i[0])
- # Reconstruir y devolver la lista solo con los resultados de las llamadas individuales
+ # Rebuild and return list only with results of individual calls
return [ii[2] for ii in r_list]
def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None):
"""
- Obtiene y fija (item.infoLabels) los datos extras de una serie, capitulo o pelicula.
+ Gets and sets (item.infoLabels) the extra data of a series, chapter or movie.
- @param item: Objeto Item que representa un pelicula, serie o capitulo. El atributo infoLabels sera modificado
- incluyendo los datos extras localizados.
+ @param item: Item object that represents a movie, series or chapter. The infoLabels attribute will be modified including the extra localized data.
@type item: Item
- @param seekTmdb: Si es True hace una busqueda en www.themoviedb.org para obtener los datos, en caso contrario
- obtiene los datos del propio Item si existen.
+ @param seekTmdb: If it is True, it searches www.themoviedb.org to obtain the data, otherwise it obtains the data of the Item itself if they exist.
@type seekTmdb: bool
- @param idioma_busqueda: Codigo del idioma segun ISO 639-1, en caso de busqueda en www.themoviedb.org.
+ @param idioma_busqueda: Language code according to ISO 639-1, in case of search at www.themoviedb.org.
@type idioma_busqueda: str
- @param lock: para uso de threads cuando es llamado del metodo 'set_infoLabels_itemlist'
- @return: Un numero cuyo valor absoluto representa la cantidad de elementos incluidos en el atributo item.infoLabels.
- Este numero sera positivo si los datos se han obtenido de www.themoviedb.org y negativo en caso contrario.
+ @param lock: For use of threads when calling the 'set_infoLabels_itemlist' method
+ @return: A number whose absolute value represents the number of elements included in the item.infoLabels attribute. This number will be positive if the data has been obtained from www.themoviedb.org and negative otherwise.
@rtype: int
"""
global otmdb_global
@@ -322,7 +287,7 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None
item.fanart = item.infoLabels['fanart']
if seekTmdb:
- # Comprobamos q tipo de contenido es...
+ # We check what type of content it is...
if item.contentType == 'movie':
tipo_busqueda = 'movie'
else:
@@ -338,15 +303,12 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None
if lock:
lock.acquire()
- if not otmdb_global or (item.infoLabels['tmdb_id']
- and str(otmdb_global.result.get("id")) != item.infoLabels['tmdb_id']) \
+ if not otmdb_global or (item.infoLabels['tmdb_id'] and str(otmdb_global.result.get("id")) != item.infoLabels['tmdb_id']) \
or (otmdb_global.texto_buscado and otmdb_global.texto_buscado != item.infoLabels['tvshowtitle']):
if item.infoLabels['tmdb_id']:
- otmdb_global = Tmdb(id_Tmdb=item.infoLabels['tmdb_id'], tipo=tipo_busqueda,
- idioma_busqueda=idioma_busqueda)
+ otmdb_global = Tmdb(id_Tmdb=item.infoLabels['tmdb_id'], tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
else:
- otmdb_global = Tmdb(texto_buscado=item.infoLabels['tvshowtitle'], tipo=tipo_busqueda,
- idioma_busqueda=idioma_busqueda, year=item.infoLabels['year'])
+ otmdb_global = Tmdb(texto_buscado=item.infoLabels['tvshowtitle'], tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda, year=item.infoLabels['year'])
__leer_datos(otmdb_global)
@@ -361,13 +323,13 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None
logger.debug("The episode number (%s) is not valid" % repr(item.infoLabels['episode']))
return -1 * len(item.infoLabels)
- # Tenemos numero de temporada y numero de episodio validos...
- # ... buscar datos episodio
+ # We have valid season number and episode number...
+ # ... search episode data
item.infoLabels['mediatype'] = 'episode'
episodio = otmdb_global.get_episodio(numtemporada, episode)
if episodio:
- # Actualizar datos
+ # Update data
__leer_datos(otmdb_global)
item.infoLabels['title'] = episodio['episodio_titulo']
if episodio['episodio_sinopsis']:
@@ -388,15 +350,15 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None
return len(item.infoLabels)
else:
- # Tenemos numero de temporada valido pero no numero de episodio...
- # ... buscar datos temporada
+ # We have a valid season number but no episode number...
+ # ... search season data
item.infoLabels['mediatype'] = 'season'
temporada = otmdb_global.get_temporada(numtemporada)
if not isinstance(temporada, dict):
temporada = ast.literal_eval(temporada.decode('utf-8'))
if temporada:
- # Actualizar datos
+ # Update data
__leer_datos(otmdb_global)
item.infoLabels['title'] = temporada['name'] if 'name' in temporada else ''
if 'overview' in temporada and temporada['overview']:
@@ -418,69 +380,62 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None
if lock and lock.locked():
lock.release()
- # Buscar...
+ # Search...
else:
otmdb = copy.copy(otmdb_global)
- # Busquedas por ID...
+ # Search by ID...
if item.infoLabels['tmdb_id']:
- # ...Busqueda por tmdb_id
- otmdb = Tmdb(id_Tmdb=item.infoLabels['tmdb_id'], tipo=tipo_busqueda,
- idioma_busqueda=idioma_busqueda)
+ # ...Search for tmdb_id
+ otmdb = Tmdb(id_Tmdb=item.infoLabels['tmdb_id'], tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
elif item.infoLabels['imdb_id']:
- # ...Busqueda por imdb code
- otmdb = Tmdb(external_id=item.infoLabels['imdb_id'], external_source="imdb_id",
- tipo=tipo_busqueda,
- idioma_busqueda=idioma_busqueda)
+ # ...Search by imdb code
+ otmdb = Tmdb(external_id=item.infoLabels['imdb_id'], external_source="imdb_id", tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
- elif tipo_busqueda == 'tv': # buscar con otros codigos
+ elif tipo_busqueda == 'tv': # bsearch with other codes
if item.infoLabels['tvdb_id']:
- # ...Busqueda por tvdb_id
- otmdb = Tmdb(external_id=item.infoLabels['tvdb_id'], external_source="tvdb_id", tipo=tipo_busqueda,
- idioma_busqueda=idioma_busqueda)
+ # ...Search for tvdb_id
+ otmdb = Tmdb(external_id=item.infoLabels['tvdb_id'], external_source="tvdb_id", tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
elif item.infoLabels['freebase_mid']:
- # ...Busqueda por freebase_mid
- otmdb = Tmdb(external_id=item.infoLabels['freebase_mid'], external_source="freebase_mid",
- tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
+ # ...Search for freebase_mid
+ otmdb = Tmdb(external_id=item.infoLabels['freebase_mid'], external_source="freebase_mid", tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
elif item.infoLabels['freebase_id']:
- # ...Busqueda por freebase_id
- otmdb = Tmdb(external_id=item.infoLabels['freebase_id'], external_source="freebase_id",
- tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
+ # ...Search by freebase_id
+ otmdb = Tmdb(external_id=item.infoLabels['freebase_id'], external_source="freebase_id", tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
elif item.infoLabels['tvrage_id']:
- # ...Busqueda por tvrage_id
- otmdb = Tmdb(external_id=item.infoLabels['tvrage_id'], external_source="tvrage_id",
- tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
+ # ...Search by tvrage_id
+ otmdb = Tmdb(external_id=item.infoLabels['tvrage_id'], external_source="tvrage_id", tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
- #if otmdb is None:
+ # if otmdb is None:
if not item.infoLabels['tmdb_id'] and not item.infoLabels['imdb_id'] and not item.infoLabels['tvdb_id'] and not item.infoLabels['freebase_mid'] and not item.infoLabels['freebase_id'] and not item.infoLabels['tvrage_id']:
- # No se ha podido buscar por ID...
- # hacerlo por titulo
+ # Could not search by ID ...
+ # do it by title
if tipo_busqueda == 'tv':
- # Busqueda de serie por titulo y filtrando sus resultados si es necesario
+ # Serial search by title and filtering your results if necessary
otmdb = Tmdb(texto_buscado=item.infoLabels['tvshowtitle'], tipo=tipo_busqueda,
idioma_busqueda=idioma_busqueda, filtro=item.infoLabels.get('filtro', {}),
year=item.infoLabels['year'])
else:
- # Busqueda de pelicula por titulo...
+ # Movie search by title ...
# if item.infoLabels['year'] or item.infoLabels['filtro']:
- # ...y año o filtro
+ # ...and year or filter
searched_title = item.contentTitle if item.contentTitle else item.fulltitle
otmdb = Tmdb(texto_buscado=searched_title, tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda,
filtro=item.infoLabels.get('filtro', {}), year=item.infoLabels['year'])
if otmdb is not None:
if otmdb.get_id() and config.get_setting("tmdb_plus_info", default=False):
- # Si la busqueda ha dado resultado y no se esta buscando una lista de items,
- # realizar otra busqueda para ampliar la informacion
+ # If the search has been successful and you are not looking for a list of items,
+ # carry out another search to expand the information
otmdb = Tmdb(id_Tmdb=otmdb.result.get("id"), tipo=tipo_busqueda, idioma_busqueda=idioma_busqueda)
if lock and lock.locked():
lock.release()
if otmdb is not None and otmdb.get_id():
- # La busqueda ha encontrado un resultado valido
+ # The search has found a valid result
__leer_datos(otmdb)
return len(item.infoLabels)
- # La busqueda en tmdb esta desactivada o no ha dado resultado
+ # Search in tmdb is deactivated or has not given result
# item.contentType = item.infoLabels['mediatype']
return -1 * len(item.infoLabels)
@@ -500,7 +455,7 @@ def find_and_set_infoLabels(item):
tipo_contenido = config.get_localized_string(60298)
title = item.contentSerieName
- # Si el titulo incluye el (año) se lo quitamos
+ # If the title includes the (year) we will remove it
year = scrapertools.find_single_match(title, "^.+?\s*(\(\d{4}\))$")
if year:
title = title.replace(year, "").strip()
@@ -510,8 +465,7 @@ def find_and_set_infoLabels(item):
if not item.infoLabels.get("imdb_id"):
otmdb_global = Tmdb(texto_buscado=title, tipo=tipo_busqueda, year=item.infoLabels['year'])
else:
- otmdb_global = Tmdb(external_id=item.infoLabels.get("imdb_id"), external_source="imdb_id",
- tipo=tipo_busqueda)
+ otmdb_global = Tmdb(external_id=item.infoLabels.get("imdb_id"), external_source="imdb_id", tipo=tipo_busqueda)
elif not otmdb_global or str(otmdb_global.result.get("id")) != item.infoLabels['tmdb_id']:
otmdb_global = Tmdb(id_Tmdb=item.infoLabels['tmdb_id'], tipo=tipo_busqueda, idioma_busqueda=def_lang)
@@ -519,8 +473,7 @@ def find_and_set_infoLabels(item):
if len(results) > 1:
from platformcode import platformtools
- tmdb_result = platformtools.show_video_info(results, item=item,
- caption= tipo_contenido % title)
+ tmdb_result = platformtools.show_video_info(results, item=item, caption= tipo_contenido % title)
elif len(results) > 0:
tmdb_result = results[0]
@@ -531,7 +484,7 @@ def find_and_set_infoLabels(item):
if tmdb_result:
infoLabels['tmdb_id'] = tmdb_result['id']
- # todo mirar si se puede eliminar y obtener solo desde get_nfo()
+ # all look if it can be removed and get only from get_nfo ()
infoLabels['url_scraper'] = ["https://www.themoviedb.org/%s/%s" % (tipo_busqueda, infoLabels['tmdb_id'])]
if infoLabels['tvdb_id']:
infoLabels['url_scraper'].append("http://thetvdb.com/index.php?tab=series&id=%s" % infoLabels['tvdb_id'])
@@ -547,16 +500,14 @@ def find_and_set_infoLabels(item):
def get_nfo(item):
"""
- Devuelve la información necesaria para que se scrapee el resultado en la videoteca de kodi, para tmdb funciona
- solo pasandole la url.
- @param item: elemento que contiene los datos necesarios para generar la info
+ Returns the information necessary for the result to be scraped into the kodi video library, for tmdb it works only by passing it the url.
+ @param item: element that contains the data necessary to generate the info
@type item: Item
@rtype: str
@return:
"""
if "season" in item.infoLabels and "episode" in item.infoLabels:
- info_nfo = "https://www.themoviedb.org/tv/%s/season/%s/episode/%s\n" % \
- (item.infoLabels['tmdb_id'], item.contentSeason, item.contentEpisodeNumber)
+ info_nfo = "https://www.themoviedb.org/tv/%s/season/%s/episode/%s\n" % (item.infoLabels['tmdb_id'], item.contentSeason, item.contentEpisodeNumber)
else:
info_nfo = ', '.join(item.infoLabels['url_scraper']) + "\n"
@@ -565,10 +516,10 @@ def get_nfo(item):
def completar_codigos(item):
"""
- Si es necesario comprueba si existe el identificador de tvdb y sino existe trata de buscarlo
+ If necessary, check if the tvdb identifier exists and if it does not exist try to find it
"""
if item.contentType != "movie" and not item.infoLabels['tvdb_id']:
- # Lanzar busqueda por imdb_id en tvdb
+ # Launch search for imdb_id on tvdb
from core.tvdb import Tvdb
ob = Tvdb(imdb_id=item.infoLabels['imdb_id'])
item.infoLabels['tvdb_id'] = ob.get_id()
@@ -585,8 +536,7 @@ def discovery(item, dict_=False, cast=False):
listado = Tmdb(discover = dict_, cast=cast)
elif item.search_type == 'discover':
- listado = Tmdb(discover={'url':'discover/%s' % item.type, 'with_genres':item.list_type, 'language':def_lang,
- 'page':item.page})
+ listado = Tmdb(discover={'url':'discover/%s' % item.type, 'with_genres':item.list_type, 'language':def_lang, 'page':item.page})
elif item.search_type == 'list':
if item.page == '':
@@ -602,7 +552,7 @@ def get_genres(type):
return genres.dic_generos[lang]
-# Clase auxiliar
+# Auxiliary class
class ResultDictDefault(dict):
# Python 2.4
def __getitem__(self, key):
@@ -613,14 +563,13 @@ class ResultDictDefault(dict):
def __missing__(self, key):
"""
- valores por defecto en caso de que la clave solicitada no exista
+ default values in case the requested key does not exist
"""
if key in ['genre_ids', 'genre', 'genres']:
return list()
elif key == 'images_posters':
posters = dict()
- if 'images' in list(super(ResultDictDefault, self).keys()) and \
- 'posters' in super(ResultDictDefault, self).__getitem__('images'):
+ if 'images' in list(super(ResultDictDefault, self).keys()) and 'posters' in super(ResultDictDefault, self).__getitem__('images'):
posters = super(ResultDictDefault, self).__getitem__('images')['posters']
super(ResultDictDefault, self).__setattr__("images_posters", posters)
@@ -628,8 +577,7 @@ class ResultDictDefault(dict):
elif key == "images_backdrops":
backdrops = dict()
- if 'images' in list(super(ResultDictDefault, self).keys()) and \
- 'backdrops' in super(ResultDictDefault, self).__getitem__('images'):
+ if 'images' in list(super(ResultDictDefault, self).keys()) and 'backdrops' in super(ResultDictDefault, self).__getitem__('images'):
backdrops = super(ResultDictDefault, self).__getitem__('images')['backdrops']
super(ResultDictDefault, self).__setattr__("images_backdrops", backdrops)
@@ -637,15 +585,14 @@ class ResultDictDefault(dict):
elif key == "images_profiles":
profiles = dict()
- if 'images' in list(super(ResultDictDefault, self).keys()) and \
- 'profiles' in super(ResultDictDefault, self).__getitem__('images'):
+ if 'images' in list(super(ResultDictDefault, self).keys()) and 'profiles' in super(ResultDictDefault, self).__getitem__('images'):
profiles = super(ResultDictDefault, self).__getitem__('images')['profiles']
super(ResultDictDefault, self).__setattr__("images_profiles", profiles)
return profiles
else:
- # El resto de claves devuelven cadenas vacias por defecto
+ # The rest of the keys return empty strings by default
return ""
def __str__(self):
@@ -668,78 +615,78 @@ class ResultDictDefault(dict):
# ---------------------------------------------------------------------------------------------------------------
# class Tmdb:
-# Scraper para el addon basado en el Api de https://www.themoviedb.org/
-# version 1.4:
-# - Documentada limitacion de uso de la API (ver mas abajo).
-# - Añadido metodo get_temporada()
-# version 1.3:
-# - Corregido error al devolver None el path_poster y el backdrop_path
-# - Corregido error que hacia que en el listado de generos se fueran acumulando de una llamada a otra
-# - Añadido metodo get_generos()
-# - Añadido parametros opcional idioma_alternativo al metodo get_sinopsis()
+# Scraper for the API based addon from https://www.themoviedb.org/
+# version 1.4:
+# - Documented limitation of API use (see below).
+# - Added get_temporada () method
+# version 1.3:
+# - Fixed error when returning None the path_poster and backdrop_path
+# - Fixed a bug that caused the list of genres to accumulate from one call to another
+# - Added get_generos () method
+# - Added optional parameters alternative_language to the get_sinopsis () method
#
#
-# Uso:
-# Metodos constructores:
-# Tmdb(texto_buscado, tipo)
-# Parametros:
-# texto_buscado:(str) Texto o parte del texto a buscar
-# tipo: ("movie" o "tv") Tipo de resultado buscado peliculas o series. Por defecto "movie"
-# (opcional) idioma_busqueda: (str) codigo del idioma segun ISO 639-1
-# (opcional) include_adult: (bool) Se incluyen contenidos para adultos en la busqueda o no. Por defecto
+# Usage:
+# Construction methods:
+# Tmdb (search_text, type)
+# Parameters:
+# searched_text: (str) Text or part of the text to search
+# type: ("movie" or "tv") Type of result searched for movies or series. Default "movie"
+# (optional) language_search: (str) language code according to ISO 639-1
+# (optional) include_adult: (bool) Adult content is included in the search or not. Default
# 'False'
-# (opcional) year: (str) Año de lanzamiento.
-# (opcional) page: (int) Cuando hay muchos resultados para una busqueda estos se organizan por paginas.
-# Podemos cargar la pagina que deseemos aunque por defecto siempre es la primera.
-# Return:
-# Esta llamada devuelve un objeto Tmdb que contiene la primera pagina del resultado de buscar 'texto_buscado'
-# en la web themoviedb.org. Cuantos mas parametros opcionales se incluyan mas precisa sera la busqueda.
-# Ademas el objeto esta inicializado con el primer resultado de la primera pagina de resultados.
-# Tmdb(id_Tmdb,tipo)
-# Parametros:
-# id_Tmdb: (str) Codigo identificador de una determinada pelicula o serie en themoviedb.org
-# tipo: ("movie" o "tv") Tipo de resultado buscado peliculas o series. Por defecto "movie"
-# (opcional) idioma_busqueda: (str) codigo del idioma segun ISO 639-1
-# Return:
-# Esta llamada devuelve un objeto Tmdb que contiene el resultado de buscar una pelicula o serie con el
-# identificador id_Tmd
-# en la web themoviedb.org.
-# Tmdb(external_id, external_source, tipo)
-# Parametros:
-# external_id: (str) Codigo identificador de una determinada pelicula o serie en la web referenciada por
+# (optional) year: (str) Release year.
+# (optional) page: (int) When there are many results for a search these are organized by pages.
+# We can load the page we want, although by default it is always the first page.
+# Return:
+# This call returns a Tmdb object containing the first page of the search result 'search_text'
+# on the themoviedb.org website. The more optional parameters are included, the more precise the search will be.
+# Also the object is initialized with the first result of the first page of results.
+# Tmdb (id_Tmdb, type)
+# Parameters:
+# id_Tmdb: (str) Identifier code of a certain movie or series at themoviedb.org
+# type: ("movie" or "tv") Type of result searched for movies or series. Default "movie"
+# (optional) language_search: (str) language code according to ISO 639-1
+# Return:
+# This call returns a Tmdb object that contains the result of searching for a movie or series with the
+# identifier id_Tmd
+# on the themoviedb.org website.
+# Tmdb (external_id, external_source, type)
+# Parameters:
+# external_id: (str) Identifier code of a certain movie or series on the web referenced by
# 'external_source'.
-# external_source: (Para series:"imdb_id","freebase_mid","freebase_id","tvdb_id","tvrage_id"; Para
-# peliculas:"imdb_id")
-# tipo: ("movie" o "tv") Tipo de resultado buscado peliculas o series. Por defecto "movie"
-# (opcional) idioma_busqueda: (str) codigo del idioma segun ISO 639-1
-# Return:
-# Esta llamada devuelve un objeto Tmdb que contiene el resultado de buscar una pelicula o serie con el
-# identificador 'external_id' de
-# la web referenciada por 'external_source' en la web themoviedb.org.
+# external_source: (For series: "imdb_id", "freebase_mid", "freebase_id", "tvdb_id", "tvrage_id"; For
+# movies: "imdb_id")
+# type: ("movie" or "tv") Type of result searched for movies or series. Default "movie"
+# (optional) language_search: (str) language code according to ISO 639-1
+# Return:
+# This call returns a Tmdb object that contains the result of searching for a movie or series with the
+# identifier 'external_id' of
+# the website referenced by 'external_source' on the themoviedb.org website.
#
-# Metodos principales:
-# get_id(): Retorna un str con el identificador Tmdb de la pelicula o serie cargada o una cadena vacia si no hubiese
-# nada cargado.
-# get_sinopsis(idioma_alternativo): Retorna un str con la sinopsis de la serie o pelicula cargada.
-# get_poster (tipo_respuesta,size): Obtiene el poster o un listado de posters.
-# get_backdrop (tipo_respuesta,size): Obtiene una imagen de fondo o un listado de imagenes de fondo.
-# get_temporada(temporada): Obtiene un diccionario con datos especificos de la temporada.
-# get_episodio (temporada, capitulo): Obtiene un diccionario con datos especificos del episodio.
-# get_generos(): Retorna un str con la lista de generos a los que pertenece la pelicula o serie.
+# Main methods:
+# get_id (): Returns a str with the Tmdb identifier of the loaded movie or series or an empty string if there were no
+# nothing loaded.
+# get_sinopsis (alternate_language): Returns a str with the synopsis of the series or movie loaded.
+# get_poster (response_type, size): Get the poster or a list of posters.
+# get_backdrop (response_type, size): Get a background image or a list of background images.
+# get_temporada (season): Get a dictionary with season-specific data.
+# get_episodio (season, episode): Get a dictionary with specific data of the episode.
+# get_generos (): Returns a str with the list of genres to which the movie or series belongs.
#
#
-# Otros metodos:
-# load_resultado(resultado, page): Cuando la busqueda devuelve varios resultados podemos seleccionar que resultado
-# concreto y de que pagina cargar los datos.
+# Other methods:
+# load_resultado (result, page): When the search returns several results we can select which result
+# concrete and from which page to load the data.
#
-# Limitaciones:
-# El uso de la API impone un limite de 20 conexiones simultaneas (concurrencia) o 30 peticiones en 10 segundos por IP
-# Informacion sobre la api : http://docs.themoviedb.apiary.io
+# Limitations:
+# The use of the API imposes a limit of 20 simultaneous connections (concurrency) or 30 requests in 10 seconds per IP
+# Information about the api: http://docs.themoviedb.apiary.io
# -------------------------------------------------------------------------------------------------------------------
class Tmdb(object):
- # Atributo de clase
+ # Class attribute
dic_generos = {}
'''
dic_generos={"id_idioma1": {"tv": {"id1": "name1",
@@ -830,17 +777,15 @@ class Tmdb(object):
self.busqueda_filtro = kwargs.get('filtro', {})
self.discover = kwargs.get('discover', {})
- # Reellenar diccionario de generos si es necesario
- if (self.busqueda_tipo == 'movie' or self.busqueda_tipo == "tv") and \
- (self.busqueda_idioma not in Tmdb.dic_generos or
- self.busqueda_tipo not in Tmdb.dic_generos[self.busqueda_idioma]):
+ # Refill gender dictionary if necessary
+ if (self.busqueda_tipo == 'movie' or self.busqueda_tipo == "tv") and (self.busqueda_idioma not in Tmdb.dic_generos or self.busqueda_tipo not in Tmdb.dic_generos[self.busqueda_idioma]):
self.rellenar_dic_generos(self.busqueda_tipo, self.busqueda_idioma)
if not self.busqueda_tipo:
self.busqueda_tipo = 'movie'
if self.busqueda_id:
- # Busqueda por identificador tmdb
+ #Search by tmdb identifier
self.__by_id()
elif self.busqueda_texto:
@@ -848,12 +793,10 @@ class Tmdb(object):
self.__search(page=self.page)
elif 'external_source' in kwargs and 'external_id' in kwargs:
- # Busqueda por identificador externo segun el tipo.
+ # Search by external identifier according to type.
# TV Series: imdb_id, freebase_mid, freebase_id, tvdb_id, tvrage_id
# Movies: imdb_id
- if (self.busqueda_tipo == 'movie' and kwargs.get('external_source') == "imdb_id") or \
- (self.busqueda_tipo == 'tv' and kwargs.get('external_source') in (
- "imdb_id", "freebase_mid", "freebase_id", "tvdb_id", "tvrage_id")):
+ if (self.busqueda_tipo == 'movie' and kwargs.get('external_source') == "imdb_id") or (self.busqueda_tipo == 'tv' and kwargs.get('external_source') in ("imdb_id", "freebase_mid", "freebase_id", "tvdb_id", "tvrage_id")):
self.busqueda_id = kwargs.get('external_id')
self.__by_id(source=kwargs.get('external_source'))
@@ -880,9 +823,9 @@ class Tmdb(object):
if dict_data["status_code"] == 25:
while "status_code" in dict_data and dict_data["status_code"] == 25:
wait = int(res_headers['retry-after'])
- #logger.error("Limite alcanzado, esperamos para volver a llamar en ...%s" % wait)
+ #logger.error("Limit reached, we wait to call back on ...%s" % wait)
time.sleep(wait)
- # logger.debug("RE Llamada #%s" % d)
+ # logger.debug("RE Call #%s" % d)
result = httptools.downloadpage(url, cookies=False)
res_headers = result.headers
@@ -890,7 +833,7 @@ class Tmdb(object):
dict_data = jsontools.load(result.data)
# logger.debug("result_data es %s" % dict_data)
- # error al obtener los datos
+ # error getting data
except Exception as ex:
message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args))
logger.error("error in: %s" % message)
@@ -900,14 +843,13 @@ class Tmdb(object):
@classmethod
def rellenar_dic_generos(cls, tipo='movie', idioma=def_lang):
- # Rellenar diccionario de generos del tipo e idioma pasados como parametros
+ # Fill dictionary of genres of the type and language passed as parameters
if idioma not in cls.dic_generos:
cls.dic_generos[idioma] = {}
if tipo not in cls.dic_generos[idioma]:
cls.dic_generos[idioma][tipo] = {}
- url = ('http://api.themoviedb.org/3/genre/%s/list?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s'
- % (tipo, idioma))
+ url = ('http://api.themoviedb.org/3/genre/%s/list?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s' % (tipo, idioma))
try:
logger.info("[Tmdb.py] Filling in dictionary of genres")
@@ -959,7 +901,7 @@ class Tmdb(object):
self.result = ResultDictDefault(resultado)
else:
- # No hay resultados de la busqueda
+ # No search results
msg = "The search of %s gave no results" % buscando
logger.debug(msg)
@@ -995,7 +937,6 @@ class Tmdb(object):
results = resultado["results"]
if self.busqueda_filtro and total_results > 1:
- # TODO documentar esta parte
for key, value in list(dict(self.busqueda_filtro).items()):
for r in results[:]:
if not r[key]:
@@ -1006,13 +947,13 @@ class Tmdb(object):
if results:
if index_results >= len(results):
- # Se ha solicitado un numero de resultado mayor de los obtenidos
+ # A higher number of results has been requested than those obtained
logger.error(
"The search for '%s' gave %s results for the page %s \n It is impossible to show the result number %s"
% (buscando, len(results), page, index_results))
return 0
- # Retornamos el numero de resultados de esta pagina
+ # We return the number of results of this page
self.results = results
self.total_results = total_results
self.total_pages = total_pages
@@ -1020,7 +961,7 @@ class Tmdb(object):
return len(self.results)
else:
- # No hay resultados de la busqueda
+ # No search results
msg = "The search for '%s' gave no results for page %s" % (buscando, page)
logger.error(msg)
return 0
@@ -1033,8 +974,8 @@ class Tmdb(object):
total_pages = 0
# Ejemplo self.discover: {'url': 'discover/movie', 'with_cast': '1'}
- # url: Método de la api a ejecutar
- # resto de claves: Parámetros de la búsqueda concatenados a la url
+ # url: API method to run
+ # rest of keys: Search parameters concatenated to the url
type_search = self.discover.get('url', '')
if type_search:
params = []
@@ -1074,7 +1015,7 @@ class Tmdb(object):
"The search for '%s' did not give %s results" % (type_search, index_results))
return 0
- # Retornamos el numero de resultados de esta pagina
+ # We return the number of results of this page
if results:
self.results = results
self.total_results = total_results
@@ -1086,13 +1027,12 @@ class Tmdb(object):
self.result = results
return len(self.results)
else:
- # No hay resultados de la busqueda
+ # No search results
logger.error("The search for '%s' gave no results" % type_search)
return 0
def load_resultado(self, index_results=0, page=1):
- # Si no hay resultados, solo hay uno o
- # si el numero de resultados de esta pagina es menor al indice buscado salir
+ # If there are no results, there is only one or if the number of results on this page is less than the index sought to exit
self.result = ResultDictDefault()
num_result_page = len(self.results)
@@ -1112,7 +1052,6 @@ class Tmdb(object):
def get_list_resultados(self, num_result=20):
# logger.info("self %s" % str(self))
- # TODO documentar
res = []
if num_result <= 0:
@@ -1141,9 +1080,9 @@ class Tmdb(object):
def get_generos(self, origen=None):
"""
- :param origen: Diccionario origen de donde se obtiene los infoLabels, por omision self.result
+ :param origen: Source dictionary where the infoLabels are obtained, by default self.result
:type origen: Dict
- :return: Devuelve la lista de generos a los que pertenece la pelicula o serie.
+ :return: Returns the list of genres to which the movie or series belongs.
:rtype: str
"""
genre_list = []
@@ -1152,7 +1091,7 @@ class Tmdb(object):
origen = self.result
if "genre_ids" in origen:
- # Buscar lista de generos por IDs
+ # Search list of genres by IDs
for i in origen.get("genre_ids"):
try:
genre_list.append(Tmdb.dic_generos[self.busqueda_idioma][self.busqueda_tipo][str(i)])
@@ -1160,7 +1099,7 @@ class Tmdb(object):
pass
elif "genre" in origen or "genres" in origen:
- # Buscar lista de generos (lista de objetos {id,nombre})
+ # Search genre list (object list {id, name})
v = origen["genre"]
v.extend(origen["genres"])
for i in v:
@@ -1175,9 +1114,7 @@ class Tmdb(object):
def get_id(self):
"""
-
- :return: Devuelve el identificador Tmdb de la pelicula o serie cargada o una cadena vacia en caso de que no
- hubiese nada cargado. Se puede utilizar este metodo para saber si una busqueda ha dado resultado o no.
+ :return: Returns the Tmdb identifier of the loaded movie or series or an empty string in case nothing was loaded. You can use this method to find out if a search has been successful or not.
:rtype: str
"""
return str(self.result.get('id', ""))
@@ -1185,12 +1122,10 @@ class Tmdb(object):
def get_sinopsis(self, idioma_alternativo=""):
"""
- :param idioma_alternativo: codigo del idioma, segun ISO 639-1, en el caso de que en el idioma fijado para la
- busqueda no exista sinopsis.
- Por defecto, se utiliza el idioma original. Si se utiliza None como idioma_alternativo, solo se buscara en
- el idioma fijado.
+ :param idioma_alternativo: Language code, according to ISO 639-1, if there is no synopsis in the language set for the search.
+ By default, the original language is used. If None is used as the alternative_language, it will only search in the set language.
:type idioma_alternativo: str
- :return: Devuelve la sinopsis de una pelicula o serie
+ :return: Returns the synopsis of a movie or series
:rtype: str
"""
ret = ""
@@ -1198,15 +1133,14 @@ class Tmdb(object):
if 'id' in self.result:
ret = self.result.get('overview')
if ret == "" and str(idioma_alternativo).lower() != 'none':
- # Vamos a lanzar una busqueda por id y releer de nuevo la sinopsis
+ # Let's launch a search for id and reread the synopsis again
self.busqueda_id = str(self.result["id"])
if idioma_alternativo:
self.busqueda_idioma = idioma_alternativo
else:
self.busqueda_idioma = self.result['original_language']
- url = ('http://api.themoviedb.org/3/%s/%s?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s' %
- (self.busqueda_tipo, self.busqueda_id, self.busqueda_idioma))
+ url = ('http://api.themoviedb.org/3/%s/%s?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s' % (self.busqueda_tipo, self.busqueda_id, self.busqueda_idioma))
resultado = self.get_json(url)
if not isinstance(resultado, dict):
@@ -1221,15 +1155,13 @@ class Tmdb(object):
def get_poster(self, tipo_respuesta="str", size="original"):
"""
- @param tipo_respuesta: Tipo de dato devuelto por este metodo. Por defecto "str"
+ @param tipo_respuesta: Data type returned by this method. Default "str"
@type tipo_respuesta: list, str
@param size: ("w45", "w92", "w154", "w185", "w300", "w342", "w500", "w600", "h632", "w780", "w1280", "original")
- Indica la anchura(w) o altura(h) de la imagen a descargar. Por defecto "original"
- @return: Si el tipo_respuesta es "list" devuelve un listado con todas las urls de las imagenes tipo poster del
- tamaño especificado.
- Si el tipo_respuesta es "str" devuelve la url de la imagen tipo poster, mas valorada, del tamaño
- especificado.
- Si el tamaño especificado no existe se retornan las imagenes al tamaño original.
+ Indicates the width (w) or height (h) of the image to download. Default "original"
+ @return: If the response_type is "list" it returns a list with all the urls of the poster images of the specified size.
+ If the response_type is "str" it returns the url of the poster image, most valued, of the specified size.
+ If the specified size does not exist, the images are returned to the original size.
@rtype: list, str
"""
ret = []
@@ -1247,7 +1179,7 @@ class Tmdb(object):
return []
if len(self.result['images_posters']) == 0:
- # Vamos a lanzar una busqueda por id y releer de nuevo
+ # We are going to launch a search by id and reread again
self.busqueda_id = str(self.result["id"])
self.__by_id()
@@ -1255,7 +1187,7 @@ class Tmdb(object):
for i in self.result['images_posters']:
imagen_path = i['file_path']
if size != "original":
- # No podemos pedir tamaños mayores que el original
+ # We cannot order sizes larger than the original
if size[1] == 'w' and int(imagen_path['width']) < int(size[1:]):
size = "original"
elif size[1] == 'h' and int(imagen_path['height']) < int(size[1:]):
@@ -1268,16 +1200,15 @@ class Tmdb(object):
def get_backdrop(self, tipo_respuesta="str", size="original"):
"""
- Devuelve las imagenes de tipo backdrop
- @param tipo_respuesta: Tipo de dato devuelto por este metodo. Por defecto "str"
+ Returns the images of type backdrop
+ @param tipo_respuesta: Data type returned by this method. Default "str"
@type tipo_respuesta: list, str
@param size: ("w45", "w92", "w154", "w185", "w300", "w342", "w500", "w600", "h632", "w780", "w1280", "original")
- Indica la anchura(w) o altura(h) de la imagen a descargar. Por defecto "original"
+ Indicates the width (w) or height (h) of the image to download. Default "original"
@type size: str
- @return: Si el tipo_respuesta es "list" devuelve un listado con todas las urls de las imagenes tipo backdrop del
- tamaño especificado.
- Si el tipo_respuesta es "str" devuelve la url de la imagen tipo backdrop, mas valorada, del tamaño especificado.
- Si el tamaño especificado no existe se retornan las imagenes al tamaño original.
+ @return: If the response_type is "list" it returns a list with all the urls of the backdrop images of the specified size.
+ If the response_type is "str" it returns the url of the backdrop type image, most valued, of the specified size.
+ If the specified size does not exist, the images are returned to the original size.
@rtype: list, str
"""
ret = []
@@ -1295,7 +1226,7 @@ class Tmdb(object):
return []
if len(self.result['images_backdrops']) == 0:
- # Vamos a lanzar una busqueda por id y releer de nuevo todo
+ # Let's launch a search by id and reread everything
self.busqueda_id = str(self.result["id"])
self.__by_id()
@@ -1303,7 +1234,7 @@ class Tmdb(object):
for i in self.result['images_backdrops']:
imagen_path = i['file_path']
if size != "original":
- # No podemos pedir tamaños mayores que el original
+ # We cannot order sizes larger than the original
if size[1] == 'w' and int(imagen_path['width']) < int(size[1:]):
size = "original"
elif size[1] == 'h' and int(imagen_path['height']) < int(size[1:]):
@@ -1316,13 +1247,13 @@ class Tmdb(object):
def get_temporada(self, numtemporada=1):
# --------------------------------------------------------------------------------------------------------------------------------------------
- # Parametros:
- # numtemporada: (int) Numero de temporada. Por defecto 1.
- # Return: (dic)
- # Devuelve un dicionario con datos sobre la temporada.
- # Puede obtener mas informacion sobre los datos devueltos en:
- # http://docs.themoviedb.apiary.io/#reference/tv-seasons/tvidseasonseasonnumber/get
- # http://docs.themoviedb.apiary.io/#reference/tv-seasons/tvidseasonseasonnumbercredits/get
+ # Parameters:
+ # season number: (int) Season number. Default 1.
+ # Return: (Dec)
+ # Returns a dictionary with data about the season.
+ # You can get more information about the returned data at:
+ # http://docs.themoviedb.apiary.io/#reference/tv-seasons/tvidseasonseasonnumber/get
+ # http://docs.themoviedb.apiary.io/#reference/tv-seasons/tvidseasonseasonnumbercredits/get
# --------------------------------------------------------------------------------------------------------------------------------------------
if not self.result["id"] or self.busqueda_tipo != "tv":
return {}
@@ -1332,7 +1263,7 @@ class Tmdb(object):
numtemporada = 1
if not self.temporada.get(numtemporada, {}):
- # Si no hay datos sobre la temporada solicitada, consultar en la web
+ # If there is no information about the requested season, check the website
# http://api.themoviedb.org/3/tv/1407/season/1?api_key=a1ab8b8669da03637a4b98fa39c39228&language=es&
# append_to_response=credits
@@ -1352,7 +1283,7 @@ class Tmdb(object):
self.temporada[numtemporada] = {"episodes": {}}
if "status_code" in self.temporada[numtemporada]:
- #Se ha producido un error
+ # An error has occurred
msg = config.get_localized_string(70496) + buscando + config.get_localized_string(70497)
msg += "\nTmdb error: %s %s" % (
self.temporada[numtemporada]["status_code"], self.temporada[numtemporada]["status_message"])
@@ -1363,16 +1294,16 @@ class Tmdb(object):
def get_episodio(self, numtemporada=1, capitulo=1):
# --------------------------------------------------------------------------------------------------------------------------------------------
- # Parametros:
- # numtemporada(opcional): (int) Numero de temporada. Por defecto 1.
- # capitulo: (int) Numero de capitulo. Por defecto 1.
- # Return: (dic)
- # Devuelve un dicionario con los siguientes elementos:
- # "temporada_nombre", "temporada_sinopsis", "temporada_poster", "temporada_num_episodios"(int),
- # "temporada_air_date", "episodio_vote_count", "episodio_vote_average",
- # "episodio_titulo", "episodio_sinopsis", "episodio_imagen", "episodio_air_date",
- # "episodio_crew" y "episodio_guest_stars",
- # Con capitulo == -1 el diccionario solo tendra los elementos referentes a la temporada
+ # Parameters:
+ # season number (optional): (int) Season number. Default 1.
+ # chapter: (int) Chapter number. Default 1.
+ # Return: (Dec)
+ # Returns a dictionary with the following elements:
+ # "season_name", "season_synopsis", "season_poster", "season_num_ episodes" (int),
+ # "season_air_date", "episode_vote_count", "episode_vote_average",
+ # "episode_title", "episode_synopsis", "episode_image", "episode_air_date",
+ # "episode_crew" and "episode_guest_stars",
+ # With chapter == -1 the dictionary will only have the elements referring to the season
# --------------------------------------------------------------------------------------------------------------------------------------------
if not self.result["id"] or self.busqueda_tipo != "tv":
return {}
@@ -1388,16 +1319,16 @@ class Tmdb(object):
if not isinstance(temporada, dict):
temporada = ast.literal_eval(temporada.decode('utf-8'))
if not temporada:
- # Se ha producido un error
+ # An error has occurred
return {}
if len(temporada["episodes"]) == 0 or len(temporada["episodes"]) < capitulo:
- # Se ha producido un error
+ # An error has occurred
logger.error("Episode %d of the season %d not found." % (capitulo, numtemporada))
return {}
ret_dic = dict()
- # Obtener datos para esta temporada
+ # Get data for this season
ret_dic["temporada_nombre"] = temporada["name"]
ret_dic["temporada_sinopsis"] = temporada["overview"]
ret_dic["temporada_num_episodios"] = len(temporada["episodes"])
@@ -1414,8 +1345,7 @@ class Tmdb(object):
ret_dic["temporada_cast"] = dic_aux.get('cast', [])
ret_dic["temporada_crew"] = dic_aux.get('crew', [])
if capitulo == -1:
- # Si solo buscamos datos de la temporada,
- # incluir el equipo tecnico que ha intervenido en algun capitulo
+ # If we only look for season data, include the technical team that has intervened in any chapter
dic_aux = dict((i['id'], i) for i in ret_dic["temporada_crew"])
for e in temporada["episodes"]:
for crew in e['crew']:
@@ -1423,7 +1353,7 @@ class Tmdb(object):
dic_aux[crew['id']] = crew
ret_dic["temporada_crew"] = list(dic_aux.values())
- # Obtener datos del capitulo si procede
+ # Obtain chapter data if applicable
if capitulo != -1:
episodio = temporada["episodes"][capitulo - 1]
ret_dic["episodio_titulo"] = episodio["name"]
@@ -1446,8 +1376,7 @@ class Tmdb(object):
def get_videos(self):
"""
- :return: Devuelve una lista ordenada (idioma/resolucion/tipo) de objetos Dict en la que cada uno de
- sus elementos corresponde con un trailer, teaser o clip de youtube.
+ :return: Returns an ordered list (language / resolution / type) of Dict objects in which each of its elements corresponds to a trailer, teaser or clip from youtube.
:rtype: list of Dict
"""
ret = []
@@ -1455,7 +1384,7 @@ class Tmdb(object):
if self.result['videos']:
self.result["videos"] = self.result["videos"]['results']
else:
- # Primera búsqueda de videos en el idioma de busqueda
+ # First video search in the search language
url = "http://api.themoviedb.org/3/%s/%s/videos?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s" \
% (self.busqueda_tipo, self.result['id'], self.busqueda_idioma)
@@ -1467,10 +1396,9 @@ class Tmdb(object):
dict_videos['results'] = sorted(dict_videos['results'], key=lambda x: (x['type'], x['size']))
self.result["videos"] = dict_videos['results']
- # Si el idioma de busqueda no es ingles, hacer una segunda búsqueda de videos en inglés
+ # If the search language is not English, do a second video search in English
if self.busqueda_idioma != 'en':
- url = "http://api.themoviedb.org/3/%s/%s/videos?api_key=a1ab8b8669da03637a4b98fa39c39228" \
- % (self.busqueda_tipo, self.result['id'])
+ url = "http://api.themoviedb.org/3/%s/%s/videos?api_key=a1ab8b8669da03637a4b98fa39c39228" % (self.busqueda_tipo, self.result['id'])
dict_videos = self.get_json(url)
if not isinstance(dict_videos, dict):
@@ -1480,7 +1408,7 @@ class Tmdb(object):
dict_videos['results'] = sorted(dict_videos['results'], key=lambda x: (x['type'], x['size']))
self.result["videos"].extend(dict_videos['results'])
- # Si las busqueda han obtenido resultados devolver un listado de objetos
+ # If the searches have obtained results, return a list of objects
for i in self.result['videos']:
if i['site'] == "YouTube":
ret.append({'name': i['name'],
@@ -1493,12 +1421,11 @@ class Tmdb(object):
def get_infoLabels(self, infoLabels=None, origen=None):
"""
- :param infoLabels: Informacion extra de la pelicula, serie, temporada o capitulo.
+ :param infoLabels: Extra information about the movie, series, season or chapter.
:type infoLabels: Dict
- :param origen: Diccionario origen de donde se obtiene los infoLabels, por omision self.result
+ :param origen: Source dictionary where the infoLabels are obtained, by default self.result
:type origen: Dict
- :return: Devuelve la informacion extra obtenida del objeto actual. Si se paso el parametro infoLables, el valor
- devuelto sera el leido como parametro debidamente actualizado.
+ :return: Returns the extra information obtained from the current object. If the infoLables parameter was passed, the returned value will be read as a duly updated parameter.
:rtype: Dict
"""
@@ -1507,7 +1434,7 @@ class Tmdb(object):
else:
ret_infoLabels = InfoLabels()
- # Iniciar listados
+ # Start Listings
l_country = [i.strip() for i in ret_infoLabels['country'].split(',') if ret_infoLabels['country']]
l_director = [i.strip() for i in ret_infoLabels['director'].split(',') if ret_infoLabels['director']]
l_writer = [i.strip() for i in ret_infoLabels['writer'].split(',') if ret_infoLabels['writer']]
@@ -1524,9 +1451,9 @@ class Tmdb(object):
items = list(origen.items())
- # Informacion Temporada/episodio
+ # Season / episode information
if ret_infoLabels['season'] and self.temporada.get(ret_infoLabels['season']):
- # Si hay datos cargados de la temporada indicada
+ # If there is data loaded for the indicated season
episodio = -1
if ret_infoLabels['episode']:
episodio = ret_infoLabels['episode']
@@ -1550,12 +1477,12 @@ class Tmdb(object):
else:
ret_infoLabels['plot'] = self.get_sinopsis()
- elif k == 'runtime': #Duration for movies
+ elif k == 'runtime': # Duration for movies
ret_infoLabels['duration'] = int(v) * 60
- elif k == 'episode_run_time': #Duration for episodes
+ elif k == 'episode_run_time': # Duration for episodes
try:
- for v_alt in v: #It comes as a list (?!)
+ for v_alt in v: # It comes as a list (?!)
ret_infoLabels['duration'] = int(v_alt) * 60
except:
pass
@@ -1650,7 +1577,7 @@ class Tmdb(object):
# logger.debug("Atributos no añadidos: " + k +'= '+ str(v))
pass
- # Ordenar las listas y convertirlas en str si es necesario
+ # Sort the lists and convert them to str if necessary
if l_castandrole:
ret_infoLabels['castandrole'] = sorted(l_castandrole, key=lambda tup: tup[0])
if l_country:
diff --git a/core/videolibrarytools.py b/core/videolibrarytools.py
index 006f631b..736ced36 100644
--- a/core/videolibrarytools.py
+++ b/core/videolibrarytools.py
@@ -8,19 +8,12 @@ import sys
PY3 = False
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
-import errno
-import math
-import traceback
-import re
-import os
+import errno, math, traceback, re, os
-from core import filetools
-from core import scraper
-from core import scrapertools
+from core import filetools, scraper, scrapertools
from core.item import Item
from lib import generictools
-from platformcode import config, logger
-from platformcode import platformtools
+from platformcode import config, logger, platformtools
FOLDER_MOVIES = config.get_setting("folder_movies")
FOLDER_TVSHOWS = config.get_setting("folder_tvshows")
@@ -37,15 +30,13 @@ addon_name = "plugin://plugin.video.%s/" % config.PLUGIN_NAME
def read_nfo(path_nfo, item=None):
"""
- Metodo para leer archivos nfo.
- Los arcivos nfo tienen la siguiente extructura: url_scraper | xml + item_json
- [url_scraper] y [xml] son opcionales, pero solo uno de ellos ha de existir siempre.
- @param path_nfo: ruta absoluta al archivo nfo
+ Method to read nfo files.
+ Nfo files have the following structure: url_scraper | xml + item_json [url_scraper] and [xml] are optional, but only one of them must always exist.
+ @param path_nfo: absolute path to nfo file
@type path_nfo: str
- @param item: Si se pasa este parametro el item devuelto sera una copia de este con
- los valores de 'infoLabels', 'library_playcounts' y 'path' leidos del nfo
+ @param item: If this parameter is passed the returned item will be a copy of it with the values of 'infoLabels', 'library_playcounts' and 'path' read from the nfo
@type: Item
- @return: Una tupla formada por la cabecera (head_nfo ='url_scraper'|'xml') y el objeto 'item_json'
+ @return: A tuple consisting of the header (head_nfo = 'url_scraper' | 'xml') and the object 'item_json'
@rtype: tuple (str, Item)
"""
head_nfo = ""
@@ -77,15 +68,15 @@ def read_nfo(path_nfo, item=None):
def save_movie(item, silent=False):
"""
- guarda en la libreria de peliculas el elemento item, con los valores que contiene.
+ saves the item element in the movie library, with the values it contains.
@type item: item
- @param item: elemento que se va a guardar.
+ @param item: item to be saved.
@rtype insertados: int
- @return: el número de elementos insertados
+ @return: the number of elements inserted
@rtype sobreescritos: int
- @return: el número de elementos sobreescritos
+ @return: the number of overwritten elements
@rtype fallidos: int
- @return: el número de elementos fallidos o -1 si ha fallado todo
+ @return: the number of failed items or -1 if all failed
"""
logger.info()
# logger.debug(item.tostring('\n'))
@@ -94,34 +85,32 @@ def save_movie(item, silent=False):
fallidos = 0
path = ""
- # Itentamos obtener el titulo correcto:
- # 1. contentTitle: Este deberia ser el sitio correcto, ya que title suele contener "Añadir a la videoteca..."
+ # We try to obtain the correct title:
+ # 1. contentTitle: This should be the correct site, since the title usually contains "Add to the video library..."
# 2. fulltitle
# 3. title
# if item.contentTitle: item.title = item.contentTitle
# elif item.fulltitle: item.title = item.fulltitle
if not item.contentTitle:
- # Colocamos el titulo correcto en su sitio para que scraper lo localize
+ # We put the correct title on your site so that scraper can locate it
if item.fulltitle:
item.contentTitle = item.fulltitle
else:
item.contentTitle = item.title
- # Si llegados a este punto no tenemos titulo, salimos
+ # If at this point we do not have a title, we leave
if not item.contentTitle or not item.channel:
logger.debug("contentTitle NOT FOUND")
return 0, 0, -1, path # Salimos sin guardar
scraper_return = scraper.find_and_set_infoLabels(item)
- # Llegados a este punto podemos tener:
- # scraper_return = True: Un item con infoLabels con la información actualizada de la peli
- # scraper_return = False: Un item sin información de la peli (se ha dado a cancelar en la ventana)
- # item.infoLabels['code'] == "" : No se ha encontrado el identificador de IMDB necesario para continuar, salimos
+ # At this point we can have:
+ # scraper_return = True: An item with infoLabels with the updated information of the movie
+ # scraper_return = False: An item without movie information (it has been canceled in the window)
+ # item.infoLabels['code'] == "" : The required IMDB identifier was not found to continue, we quit
if not scraper_return or not item.infoLabels['code']:
- # TODO de momento si no hay resultado no añadimos nada,
- # aunq podriamos abrir un cuadro para introducir el identificador/nombre a mano
logger.debug("NOT FOUND IN SCRAPER OR DO NOT HAVE code")
return 0, 0, -1, path
@@ -153,7 +142,7 @@ def save_movie(item, silent=False):
break
if not path:
- # Crear carpeta
+ # Create folder
path = filetools.join(MOVIES_PATH, ("%s [%s]" % (base_name, _id)).strip())
logger.info("Creating movie directory:" + path)
if not filetools.mkdir(path):
@@ -169,7 +158,7 @@ def save_movie(item, silent=False):
json_exists = filetools.exists(json_path)
if not nfo_exists:
- # Creamos .nfo si no existe
+ # We create .nfo if it doesn't exist
logger.info("Creating .nfo: " + nfo_path)
head_nfo = scraper.get_nfo(item)
@@ -178,18 +167,18 @@ def save_movie(item, silent=False):
library_urls={})
else:
- # Si existe .nfo, pero estamos añadiendo un nuevo canal lo abrimos
+ # If .nfo exists, but we are adding a new channel we open it
head_nfo, item_nfo = read_nfo(nfo_path)
if not strm_exists:
- # Crear base_name.strm si no existe
+ # Create base_name.strm if you do not exist
item_strm = Item(channel='videolibrary', action='play_from_library',
strm_path=strm_path.replace(MOVIES_PATH, ""), contentType='movie',
contentTitle=item.contentTitle)
strm_exists = filetools.write(strm_path, '%s?%s' % (addon_name, item_strm.tourl()))
item_nfo.strm_path = strm_path.replace(MOVIES_PATH, "")
- # Solo si existen item_nfo y .strm continuamos
+ # Only if item_nfo and .strm exist we continue
if item_nfo and strm_exists:
if json_exists:
@@ -198,7 +187,7 @@ def save_movie(item, silent=False):
else:
insertados += 1
- # Si se ha marcado la opción de url de emergencia, se añade ésta a la película después de haber ejecutado Findvideos del canal
+ # If the emergency url option has been checked, it is added to the movie after running Findvideos from the channel
try:
headers = {}
if item.headers:
@@ -221,7 +210,7 @@ def save_movie(item, silent=False):
if filetools.write(nfo_path, head_nfo + item_nfo.tojson()):
#logger.info("FOLDER_MOVIES : %s" % FOLDER_MOVIES)
- # actualizamos la videoteca de Kodi con la pelicula
+ # We update the Kodi video library with the movie
if config.is_xbmc() and config.get_setting("videolibrary_kodi") and not silent:
from platformcode import xbmc_videolibrary
xbmc_videolibrary.update()
@@ -229,7 +218,7 @@ def save_movie(item, silent=False):
if not silent: p_dialog.close()
return insertados, sobreescritos, fallidos, path
- # Si llegamos a este punto es por q algo ha fallado
+ # If we get to this point it is because something has gone wrong
logger.error("Could not save %s in the video library" % item.contentTitle)
if not silent:
p_dialog.update(100, config.get_localized_string(60063), item.contentTitle)
@@ -423,37 +412,35 @@ def filter_list(episodelist, action=None, path=None):
def save_tvshow(item, episodelist, silent=False):
"""
- guarda en la libreria de series la serie con todos los capitulos incluidos en la lista episodelist
+ stores in the series library the series with all the chapters included in the episodelist
@type item: item
- @param item: item que representa la serie a guardar
+ @param item: item that represents the series to save
@type episodelist: list
- @param episodelist: listado de items que representan los episodios que se van a guardar.
+ @param episodelist: list of items that represent the episodes to be saved.
@rtype insertados: int
- @return: el número de episodios insertados
+ @return: the number of episodes inserted
@rtype sobreescritos: int
- @return: el número de episodios sobreescritos
+ @return: the number of overwritten episodes
@rtype fallidos: int
- @return: el número de episodios fallidos o -1 si ha fallado toda la serie
+ @return: the number of failed episodes or -1 if the entire series has failed
@rtype path: str
- @return: directorio serie
+ @return: serial directory
"""
logger.info()
# logger.debug(item.tostring('\n'))
path = ""
- # Si llegados a este punto no tenemos titulo o code, salimos
+ # If at this point we do not have a title or code, we leave
if not (item.contentSerieName or item.infoLabels['code']) or not item.channel:
logger.debug("NOT FOUND contentSerieName or code")
return 0, 0, -1, path # Salimos sin guardar
scraper_return = scraper.find_and_set_infoLabels(item)
- # Llegados a este punto podemos tener:
- # scraper_return = True: Un item con infoLabels con la información actualizada de la serie
- # scraper_return = False: Un item sin información de la peli (se ha dado a cancelar en la ventana)
- # item.infoLabels['code'] == "" : No se ha encontrado el identificador de IMDB necesario para continuar, salimos
+ # At this point we can have:
+ # scraper_return = True: An item with infoLabels with the updated information of the series
+ # scraper_return = False: An item without movie information (it has been canceled in the window)
+ # item.infoLabels['code'] == "" :T he required IMDB identifier was not found to continue, we quit
if not scraper_return or not item.infoLabels['code']:
- # TODO de momento si no hay resultado no añadimos nada,
- # aunq podriamos abrir un cuadro para introducir el identificador/nombre a mano
logger.debug("NOT FOUND IN SCRAPER OR DO NOT HAVE code")
return 0, 0, -1, path
@@ -464,8 +451,7 @@ def save_tvshow(item, episodelist, silent=False):
elif item.infoLabels['code'][2] and item.infoLabels['code'][2] != 'None':
_id = item.infoLabels['code'][2]
else:
- logger.error("NO ENCONTRADO EN SCRAPER O NO TIENE code: " + item.url
- + ' / ' + item.infoLabels['code'])
+ logger.error("NOT FOUND IN SCRAPER OR HAS NO CODE: " + item.url + ' / ' + item.infoLabels['code'])
return 0, 0, -1, path
if config.get_setting("original_title_folder", "videolibrary") and item.infoLabels['originaltitle']:
@@ -504,7 +490,7 @@ def save_tvshow(item, episodelist, silent=False):
tvshow_path = filetools.join(path, "tvshow.nfo")
if not filetools.exists(tvshow_path):
- # Creamos tvshow.nfo, si no existe, con la head_nfo, info de la serie y marcas de episodios vistos
+ # We create tvshow.nfo, if it does not exist, with the head_nfo, series info and watched episode marks
logger.info("Creating tvshow.nfo: " + tvshow_path)
head_nfo = scraper.get_nfo(item)
item.infoLabels['mediatype'] = "tvshow"
@@ -516,7 +502,7 @@ def save_tvshow(item, episodelist, silent=False):
item_tvshow.library_urls = {item.channel: item.url}
else:
- # Si existe tvshow.nfo, pero estamos añadiendo un nuevo canal actualizamos el listado de urls
+ # If tvshow.nfo exists, but we are adding a new channel we update the list of urls
head_nfo, item_tvshow = read_nfo(tvshow_path)
item_tvshow.fulltitle = item.fulltitle
item_tvshow.channel = "videolibrary"
@@ -524,15 +510,15 @@ def save_tvshow(item, episodelist, silent=False):
item_tvshow.library_urls[item.channel] = item.url
# FILTERTOOLS
- # si el canal tiene filtro de idiomas, añadimos el canal y el show
+ # if the channel has a language filter, we add the channel and the show
if episodelist and "list_language" in episodelist[0]:
- # si ya hemos añadido un canal previamente con filtro, añadimos o actualizamos el canal y show
+ # if we have already added a previously filtered channel, we add or update the channel and show
if "library_filter_show" in item_tvshow:
if item.title_from_channel:
item_tvshow.library_filter_show[item.channel] = item.title_from_channel
else:
item_tvshow.library_filter_show[item.channel] = item.show
- # no habia ningún canal con filtro y lo generamos por primera vez
+ # there was no filter channel and we generated it for the first time
else:
if item.title_from_channel:
item_tvshow.library_filter_show = {item.channel: item.title_from_channel}
@@ -540,15 +526,15 @@ def save_tvshow(item, episodelist, silent=False):
item_tvshow.library_filter_show = {item.channel: item.show}
if item.channel != "downloads":
- item_tvshow.active = 1 # para que se actualice a diario cuando se llame a service
+ item_tvshow.active = 1 # to be updated daily when service is called
filetools.write(tvshow_path, head_nfo + item_tvshow.tojson())
if not episodelist:
- # La lista de episodios esta vacia
+ # The episode list is empty
return 0, 0, 0, path
- # Guardar los episodios
+ # Save the episodes
'''import time
start_time = time.time()'''
insertados, sobreescritos, fallidos = save_episodes(path, episodelist, item, silent=silent)
@@ -561,27 +547,27 @@ def save_tvshow(item, episodelist, silent=False):
def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
"""
- guarda en la ruta indicada todos los capitulos incluidos en la lista episodelist
+ saves in the indicated path all the chapters included in the episodelist
@type path: str
- @param path: ruta donde guardar los episodios
+ @param path: path to save the episodes
@type episodelist: list
- @param episodelist: listado de items que representan los episodios que se van a guardar.
+ @param episodelist: list of items that represent the episodes to be saved.
@type serie: item
- @param serie: serie de la que se van a guardar los episodios
+ @param serie: series from which to save the episodes
@type silent: bool
- @param silent: establece si se muestra la notificación
- @param overwrite: permite sobreescribir los ficheros existentes
+ @param silent: sets whether notification is displayed
+ @param overwrite: allows to overwrite existing files
@type overwrite: bool
@rtype insertados: int
- @return: el número de episodios insertados
+ @return: the number of episodes inserted
@rtype sobreescritos: int
- @return: el número de episodios sobreescritos
+ @return: the number of overwritten episodes
@rtype fallidos: int
- @return: el número de episodios fallidos
+ @return: the number of failed episodes
"""
logger.info()
episodelist = filter_list(episodelist, serie.action, path)
- # No hay lista de episodios, no hay nada que guardar
+ # No episode list, nothing to save
if not len(episodelist):
logger.info("There is no episode list, we go out without creating strm")
return 0, 0, 0
@@ -606,27 +592,27 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
sobreescritos = 0
fallidos = 0
news_in_playcounts = {}
- # Listamos todos los ficheros de la serie, asi evitamos tener que comprobar si existe uno por uno
+ # We list all the files in the series, so we avoid having to check if they exist one by one
raiz, carpetas_series, ficheros = next(filetools.walk(path))
ficheros = [filetools.join(path, f) for f in ficheros]
- # Silent es para no mostrar progreso (para service)
+ # Silent is to show no progress (for service)
if not silent:
# progress dialog
p_dialog = platformtools.dialog_progress(config.get_localized_string(20000), config.get_localized_string(60064))
p_dialog.update(0, config.get_localized_string(60065))
- channel_alt = generictools.verify_channel(serie.channel) #Preparamos para añadir las urls de emergencia
- emergency_urls_stat = config.get_setting("emergency_urls", channel_alt) #El canal quiere urls de emergencia?
+ channel_alt = generictools.verify_channel(serie.channel) # We prepare to add the emergency urls
+ emergency_urls_stat = config.get_setting("emergency_urls", channel_alt) # Does the channel want emergency urls?
emergency_urls_succ = False
try: channel = __import__('specials.%s' % channel_alt, fromlist=["specials.%s" % channel_alt])
except: channel = __import__('channels.%s' % channel_alt, fromlist=["channels.%s" % channel_alt])
- if serie.torrent_caching_fail: #Si el proceso de conversión ha fallado, no se cachean
+ if serie.torrent_caching_fail: # If the conversion process has failed, they are not cached
emergency_urls_stat = 0
del serie.torrent_caching_fail
new_episodelist = []
- # Obtenemos el numero de temporada y episodio y descartamos los q no lo sean
+ # We obtain the season and episode number and discard those that are not
for e in episodelist:
headers = {}
@@ -636,52 +622,52 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
try:
season_episode = scrapertools.get_season_and_episode(e.title)
- # Si se ha marcado la opción de url de emergencia, se añade ésta a cada episodio después de haber ejecutado Findvideos del canal
- if e.emergency_urls and isinstance(e.emergency_urls, dict): del e.emergency_urls #Borramos trazas anteriores
- json_path = filetools.join(path, ("%s [%s].json" % (season_episode, e.channel)).lower()) #Path del .json del episodio
- if emergency_urls_stat == 1 and not e.emergency_urls and e.contentType == 'episode': #Guardamos urls de emergencia?
+ # If the emergency url option has been checked, it is added to each episode after running Findvideos from the channel
+ if e.emergency_urls and isinstance(e.emergency_urls, dict): del e.emergency_urls # We erase previous traces
+ json_path = filetools.join(path, ("%s [%s].json" % (season_episode, e.channel)).lower()) # Path of the episode .json
+ if emergency_urls_stat == 1 and not e.emergency_urls and e.contentType == 'episode': # Do we keep emergency urls?
if not silent:
- p_dialog.update(0, 'Cacheando enlaces y archivos .torrent...', e.title) #progress dialog
- if json_path in ficheros: #Si existe el .json sacamos de ahí las urls
- if overwrite: #pero solo si se se sobrescriben los .json
- json_epi = Item().fromjson(filetools.read(json_path)) #Leemos el .json
- if json_epi.emergency_urls: #si existen las urls de emergencia...
- e.emergency_urls = json_epi.emergency_urls #... las copiamos
- else: #y si no...
- e = emergency_urls(e, channel, json_path, headers=headers) #... las generamos
+ p_dialog.update(0, 'Caching links and .torren filest...', e.title) # progress dialog
+ if json_path in ficheros: # If there is the .json we get the urls from there
+ if overwrite: # but only if .json are overwritten
+ json_epi = Item().fromjson(filetools.read(json_path)) #We read the .json
+ if json_epi.emergency_urls: # if there are emergency urls ...
+ e.emergency_urls = json_epi.emergency_urls # ... we copy them
+ else: # if not...
+ e = emergency_urls(e, channel, json_path, headers=headers) # ... we generate them
else:
- e = emergency_urls(e, channel, json_path, headers=headers) #Si el episodio no existe, generamos las urls
- if e.emergency_urls: #Si ya tenemos urls...
- emergency_urls_succ = True #... es un éxito y vamos a marcar el .nfo
- elif emergency_urls_stat == 2 and e.contentType == 'episode': #Borramos urls de emergencia?
+ e = emergency_urls(e, channel, json_path, headers=headers) # If the episode does not exist, we generate the urls
+ if e.emergency_urls: #If we already have urls...
+ emergency_urls_succ = True # ... is a success and we are going to mark the .nfo
+ elif emergency_urls_stat == 2 and e.contentType == 'episode': # Do we delete emergency urls?
if e.emergency_urls: del e.emergency_urls
- emergency_urls_succ = True #... es un éxito y vamos a marcar el .nfo
- elif emergency_urls_stat == 3 and e.contentType == 'episode': #Actualizamos urls de emergencia?
+ emergency_urls_succ = True # ... is a success and we are going to mark the .nfo
+ elif emergency_urls_stat == 3 and e.contentType == 'episode': # Do we update emergency urls?
if not silent:
- p_dialog.update(0, 'Cacheando enlaces y archivos .torrent...', e.title) #progress dialog
- e = emergency_urls(e, channel, json_path, headers=headers) #generamos las urls
- if e.emergency_urls: #Si ya tenemos urls...
- emergency_urls_succ = True #... es un éxito y vamos a marcar el .nfo
+ p_dialog.update(0, 'Caching links and .torrent files...', e.title) # progress dialog
+ e = emergency_urls(e, channel, json_path, headers=headers) # we generate the urls
+ if e.emergency_urls: # If we already have urls...
+ emergency_urls_succ = True # ... is a success and we are going to mark the .nfo
if not e.infoLabels["tmdb_id"] or (serie.infoLabels["tmdb_id"] and e.infoLabels["tmdb_id"] != serie.infoLabels["tmdb_id"]): #en series multicanal, prevalece el infolabels...
- e.infoLabels = serie.infoLabels #... del canal actual y no el del original
+ e.infoLabels = serie.infoLabels # ... dthe current channel and not the original one
e.contentSeason, e.contentEpisodeNumber = season_episode.split("x")
if e.videolibray_emergency_urls:
del e.videolibray_emergency_urls
if e.channel_redir:
- del e.channel_redir #... y se borran las marcas de redirecciones
+ del e.channel_redir # ... and redirect marks are erased
new_episodelist.append(e)
except:
if e.contentType == 'episode':
logger.error("Unable to save %s emergency urls in the video library" % e.contentTitle)
continue
- # No hay lista de episodios, no hay nada que guardar
+ # No episode list, nothing to save
if not len(new_episodelist):
logger.info("There is no episode list, we go out without creating strm")
return 0, 0, 0
- # fix float porque la division se hace mal en python 2.x
+ # fix float because division is done poorly in python 2.x
try:
t = float(100) / len(new_episodelist)
except:
@@ -718,9 +704,8 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
json_exists = json_path in ficheros
if not strm_exists:
- # Si no existe season_episode.strm añadirlo
- item_strm = Item(action='play_from_library', channel='videolibrary',
- strm_path=strm_path.replace(TVSHOWS_PATH, ""), infoLabels={})
+ # If there is no season_episode.strm add it
+ item_strm = Item(action='play_from_library', channel='videolibrary', strm_path=strm_path.replace(TVSHOWS_PATH, ""), infoLabels={})
item_strm.contentSeason = e.contentSeason
item_strm.contentEpisodeNumber = e.contentEpisodeNumber
item_strm.contentType = e.contentType
@@ -728,7 +713,7 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
# FILTERTOOLS
if item_strm.list_language:
- # si tvshow.nfo tiene filtro se le pasa al item_strm que se va a generar
+ # if tvshow.nfo has a filter it is passed to the item_strm to be generated
if "library_filter_show" in serie:
item_strm.library_filter_show = serie.library_filter_show
@@ -741,38 +726,36 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
item_nfo = None
if not nfo_exists and e.infoLabels["code"]:
- # Si no existe season_episode.nfo añadirlo
+ # If there is no season_episode.nfo add it
scraper.find_and_set_infoLabels(e)
head_nfo = scraper.get_nfo(e)
- item_nfo = e.clone(channel="videolibrary", url="", action='findvideos',
- strm_path=strm_path.replace(TVSHOWS_PATH, ""))
+ item_nfo = e.clone(channel="videolibrary", url="", action='findvideos', strm_path=strm_path.replace(TVSHOWS_PATH, ""))
if item_nfo.emergency_urls:
- del item_nfo.emergency_urls #Solo se mantiene en el .json del episodio
+ del item_nfo.emergency_urls # It only stays in the episode's .json
nfo_exists = filetools.write(nfo_path, head_nfo + item_nfo.tojson())
- # Solo si existen season_episode.nfo y season_episode.strm continuamos
+ # Only if there are season_episode.nfo and season_episode.strm we continue
if nfo_exists and strm_exists:
if not json_exists or overwrite:
- # Obtenemos infoLabel del episodio
+ # We get infoLabel from the episode
if not item_nfo:
head_nfo, item_nfo = read_nfo(nfo_path)
- # En series multicanal, prevalece el infolabels del canal actual y no el del original
- if not e.infoLabels["tmdb_id"] or (item_nfo.infoLabels["tmdb_id"] \
- and e.infoLabels["tmdb_id"] != item_nfo.infoLabels["tmdb_id"]):
+ # In multichannel series, the infolabels of the current channel prevail and not that of the original
+ if not e.infoLabels["tmdb_id"] or (item_nfo.infoLabels["tmdb_id"] and e.infoLabels["tmdb_id"] != item_nfo.infoLabels["tmdb_id"]):
e.infoLabels = item_nfo.infoLabels
if filetools.write(json_path, e.tojson()):
if not json_exists:
logger.info("Inserted: %s" % json_path)
insertados += 1
- # Marcamos episodio como no visto
+ # We mark episode as unseen
news_in_playcounts[season_episode] = 0
- # Marcamos la temporada como no vista
+ # We mark the season as unseen
news_in_playcounts["season %s" % e.contentSeason] = 0
- # Marcamos la serie como no vista
+ # We mark the series as unseen
# logger.debug("serie " + serie.tostring('\n'))
news_in_playcounts[serie.contentSerieName] = 0
@@ -796,25 +779,25 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
p_dialog.close()
if news_in_playcounts or emergency_urls_succ or serie.infoLabels["status"] == "Ended" or serie.infoLabels["status"] == "Canceled":
- # Si hay nuevos episodios los marcamos como no vistos en tvshow.nfo ...
+ # If there are new episodes we mark them as unseen on tvshow.nfo ...
tvshow_path = filetools.join(path, "tvshow.nfo")
try:
import datetime
head_nfo, tvshow_item = read_nfo(tvshow_path)
tvshow_item.library_playcounts.update(news_in_playcounts)
- #Si la operación de insertar/borrar urls de emergencia en los .jsons de los episodios ha tenido éxito, se marca el .nfo
+ # If the emergency url insert / delete operation in the .jsons of the episodes was successful, the .nfo is checked
if emergency_urls_succ:
if tvshow_item.emergency_urls and not isinstance(tvshow_item.emergency_urls, dict):
del tvshow_item.emergency_urls
- if emergency_urls_stat in [1, 3]: #Operación de guardar/actualizar enlaces
+ if emergency_urls_stat in [1, 3]: # Save / update links operation
if not tvshow_item.emergency_urls:
tvshow_item.emergency_urls = dict()
if tvshow_item.library_urls.get(serie.channel, False):
tvshow_item.emergency_urls.update({serie.channel: True})
- elif emergency_urls_stat == 2: #Operación de Borrar enlaces
+ elif emergency_urls_stat == 2: # Delete links operation
if tvshow_item.emergency_urls and tvshow_item.emergency_urls.get(serie.channel, False):
- tvshow_item.emergency_urls.pop(serie.channel, None) #borramos la entrada del .nfo
+ tvshow_item.emergency_urls.pop(serie.channel, None) # delete the entry of the .nfo
if tvshow_item.active == 30:
tvshow_item.active = 1
@@ -822,12 +805,9 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
tvshow_item.infoLabels = serie.infoLabels
tvshow_item.infoLabels["title"] = tvshow_item.infoLabels["tvshowtitle"]
- if max_sea == high_sea and max_epi == high_epi and (tvshow_item.infoLabels["status"] == "Ended"
- or tvshow_item.infoLabels["status"] == "Canceled") and insertados == 0 and fallidos == 0 \
- and not tvshow_item.local_episodes_path:
- tvshow_item.active = 0 # ... no la actualizaremos más
- logger.debug("%s [%s]: serie 'Terminada' o 'Cancelada'. Se desactiva la actualización periódica" % \
- (serie.contentSerieName, serie.channel))
+ if max_sea == high_sea and max_epi == high_epi and (tvshow_item.infoLabels["status"] == "Ended" or tvshow_item.infoLabels["status"] == "Canceled") and insertados == 0 and fallidos == 0 and not tvshow_item.local_episodes_path:
+ tvshow_item.active = 0 # ... nor we will update it more
+ logger.debug("%s [%s]: 'Finished' or 'Canceled' series. Periodic update is disabled" % (serie.contentSerieName, serie.channel))
update_last = datetime.date.today()
tvshow_item.update_last = update_last.strftime('%Y-%m-%d')
@@ -841,7 +821,7 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
logger.error(traceback.format_exc())
fallidos = -1
else:
- # ... si ha sido correcto actualizamos la videoteca de Kodi
+ # ... if it was correct we update the Kodi video library
if config.is_xbmc() and config.get_setting("videolibrary_kodi") and not silent:
from platformcode import xbmc_videolibrary
xbmc_videolibrary.update()
@@ -849,8 +829,7 @@ def save_episodes(path, episodelist, serie, silent=False, overwrite=True):
if fallidos == len(episodelist):
fallidos = -1
- logger.debug("%s [%s]: inserted= %s, overwritten= %s, failed= %s" %
- (serie.contentSerieName, serie.channel, insertados, sobreescritos, fallidos))
+ logger.debug("%s [%s]: inserted= %s, overwritten= %s, failed= %s" % (serie.contentSerieName, serie.channel, insertados, sobreescritos, fallidos))
return insertados, sobreescritos, fallidos
@@ -924,65 +903,63 @@ def process_local_episodes(local_episodes_path, path):
def add_movie(item):
"""
- guarda una pelicula en la libreria de cine. La pelicula puede ser un enlace dentro de un canal o un video
- descargado previamente.
+ Keep a movie at the movie library. The movie can be a link within a channel or a previously downloaded video.
- Para añadir episodios descargados en local, el item debe tener exclusivamente:
- - contentTitle: titulo de la pelicula
- - title: titulo a mostrar junto al listado de enlaces -findvideos- ("Reproducir video local HD")
- - infoLabels["tmdb_id"] o infoLabels["imdb_id"]
+ To add locally downloaded episodes, the item must have exclusively:
+ - contentTitle: title of the movie
+ - title: title to show next to the list of links -findvideos- ("Play local HD video")
+ - infoLabels ["tmdb_id"] o infoLabels ["imdb_id"]
- contentType == "movie"
- channel = "downloads"
- - url : ruta local al video
+ - url: local path to the video
@type item: item
- @param item: elemento que se va a guardar.
+ @param item: item to be saved.
"""
logger.info()
- #Para desambiguar títulos, se provoca que TMDB pregunte por el título realmente deseado
- #El usuario puede seleccionar el título entre los ofrecidos en la primera pantalla
- #o puede cancelar e introducir un nuevo título en la segunda pantalla
- #Si lo hace en "Introducir otro nombre", TMDB buscará automáticamente el nuevo título
- #Si lo hace en "Completar Información", cambia parcialmente al nuevo título, pero no busca en TMDB. Hay que hacerlo
- #Si se cancela la segunda pantalla, la variable "scraper_return" estará en False. El usuario no quiere seguir
+ # To disambiguate titles, TMDB is caused to ask for the really desired title
+ # The user can select the title among those offered on the first screen
+ # or you can cancel and enter a new title on the second screen
+ # If you do it in "Enter another name", TMDB will automatically search for the new title
+ # If you do it in "Complete Information", it partially changes to the new title, but does not search TMDB. We have to do it
+ # If the second screen is canceled, the variable "scraper_return" will be False. The user does not want to continue
- item = generictools.update_title(item) #Llamamos al método que actualiza el título con tmdb.find_and_set_infoLabels
+ item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels
#if item.tmdb_stat:
- # del item.tmdb_stat #Limpiamos el status para que no se grabe en la Videoteca
+ # del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library
new_item = item.clone(action="findvideos")
insertados, sobreescritos, fallidos, path = save_movie(new_item)
if fallidos == 0:
platformtools.dialog_ok(config.get_localized_string(30131),
- config.get_localized_string(30135) % new_item.contentTitle) # 'se ha añadido a la videoteca'
+ config.get_localized_string(30135) % new_item.contentTitle) # 'has been added to the video library'
else:
filetools.rmdirtree(path)
platformtools.dialog_ok(config.get_localized_string(30131),
- config.get_localized_string(60066) % new_item.contentTitle) #"ERROR, la pelicula NO se ha añadido a la videoteca")
+ config.get_localized_string(60066) % new_item.contentTitle) # "ERROR, the movie has NOT been added to the video library")
def add_tvshow(item, channel=None):
"""
- Guarda contenido en la libreria de series. Este contenido puede ser uno de estos dos:
- - La serie con todos los capitulos incluidos en la lista episodelist.
- - Un solo capitulo descargado previamente en local.
+ Save content in the series library. This content can be one of these two:
+ - The series with all the chapters included in the episodelist.
+ - A single chapter previously downloaded locally.
- Para añadir episodios descargados en local, el item debe tener exclusivamente:
- - contentSerieName (o show): Titulo de la serie
- - contentTitle: titulo del episodio para extraer season_and_episode ("1x01 Piloto")
- - title: titulo a mostrar junto al listado de enlaces -findvideos- ("Reproducir video local")
- - infoLabels["tmdb_id"] o infoLabels["imdb_id"]
+ To add locally downloaded episodes, the item must have exclusively:
+ - contentSerieName (or show): Title of the series
+ - contentTitle: title of the episode to extract season_and_episode ("1x01 Pilot")
+ - title: title to show next to the list of links -findvideos- ("Play local video")
+ - infoLabels ["tmdb_id"] o infoLabels ["imdb_id"]
- contentType != "movie"
- channel = "downloads"
- - url : ruta local al video
+ - url: local path to the video
@type item: item
- @param item: item que representa la serie a guardar
+ @param item: item that represents the series to save
@type channel: modulo
- @param channel: canal desde el que se guardara la serie.
- Por defecto se importara item.from_channel o item.channel
+ @param channel: channel from which the series will be saved. By default, item.from_channel or item.channel will be imported.
"""
logger.info("show=#" + item.show + "#")
@@ -991,7 +968,7 @@ def add_tvshow(item, channel=None):
itemlist = [item.clone()]
else:
- # Esta marca es porque el item tiene algo más aparte en el atributo "extra"
+ # This mark is because the item has something else apart in the "extra" attribute
item.action = item.extra if item.extra else item.action
if isinstance(item.extra, str) and "###" in item.extra:
item.action = item.extra.split("###")[0]
@@ -1009,18 +986,18 @@ def add_tvshow(item, channel=None):
except ImportError:
exec("import channels." + item.channel + " as channel")
- #Para desambiguar títulos, se provoca que TMDB pregunte por el título realmente deseado
- #El usuario puede seleccionar el título entre los ofrecidos en la primera pantalla
- #o puede cancelar e introducir un nuevo título en la segunda pantalla
- #Si lo hace en "Introducir otro nombre", TMDB buscará automáticamente el nuevo título
- #Si lo hace en "Completar Información", cambia parcialmente al nuevo título, pero no busca en TMDB. Hay que hacerlo
- #Si se cancela la segunda pantalla, la variable "scraper_return" estará en False. El usuario no quiere seguir
+ # To disambiguate titles, TMDB is caused to ask for the really desired title
+ # The user can select the title among those offered on the first screen
+ # or you can cancel and enter a new title on the second screen
+ # If you do it in "Enter another name", TMDB will automatically search for the new title
+ # If you do it in "Complete Information", it partially changes to the new title, but does not search TMDB. We have to do it
+ # If the second screen is canceled, the variable "scraper_return" will be False. The user does not want to continue
- item = generictools.update_title(item) #Llamamos al método que actualiza el título con tmdb.find_and_set_infoLabels
+ item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels
#if item.tmdb_stat:
- # del item.tmdb_stat #Limpiamos el status para que no se grabe en la Videoteca
+ # del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library
- # Obtiene el listado de episodios
+ # Get the episode list
itemlist = getattr(channel, item.action)(item)
if itemlist and not scrapertools.find_single_match(itemlist[0].title, r'(\d+.\d+)'):
@@ -1040,34 +1017,34 @@ def add_tvshow(item, channel=None):
if not insertados and not sobreescritos and not fallidos:
filetools.rmdirtree(path)
platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60067) % item.show)
- logger.error("La serie %s no se ha podido añadir a la videoteca. No se ha podido obtener ningun episodio" % item.show)
+ logger.error("The string %s could not be added to the video library. Could not get any episode" % item.show)
elif fallidos == -1:
filetools.rmdirtree(path)
platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60068) % item.show)
- logger.error("La serie %s no se ha podido añadir a la videoteca" % item.show)
+ logger.error("The string %s could not be added to the video library" % item.show)
elif fallidos == -2:
filetools.rmdirtree(path)
elif fallidos > 0:
platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60069) % item.show)
- logger.error("No se han podido añadir %s episodios de la serie %s a la videoteca" % (fallidos, item.show))
+ logger.error("Could not add %s episodes of series %s to the video library" % (fallidos, item.show))
else:
platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60070) % item.show)
- logger.info("Se han añadido %s episodios de la serie %s a la videoteca" % (insertados, item.show))
+ logger.info("%s episodes of series %s have been added to the video library" % (insertados, item.show))
if config.is_xbmc():
if config.get_setting("sync_trakt_new_tvshow", "videolibrary"):
import xbmc
from platformcode import xbmc_videolibrary
if config.get_setting("sync_trakt_new_tvshow_wait", "videolibrary"):
- # Comprobar que no se esta buscando contenido en la videoteca de Kodi
+ # Check that you are not looking for content in the Kodi video library
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
xbmc.sleep(1000)
- # Se lanza la sincronizacion para la videoteca de Kodi
+ # Synchronization for Kodi video library launched
xbmc_videolibrary.sync_trakt_kodi()
- # Se lanza la sincronización para la videoteca del addon
+ # Synchronization for the addon video library is launched
xbmc_videolibrary.sync_trakt_addon(path)
@@ -1079,52 +1056,52 @@ def emergency_urls(item, channel=None, path=None, headers={}):
magnet_caching_e = magnet_caching
except:
magnet_caching_e = True
-
- """
- Llamamos a Findvideos del canal con la variable "item.videolibray_emergency_urls = True" para obtener la variable
- "item.emergency_urls" con la lista de listas de tuplas de los enlaces torrent y de servidores directos para ese episodio o película
- En la lista [0] siempre deben ir los enlaces torrents, si los hay. Si se desea cachear los .torrents, la búsqueda va contra esa lista.
- En la lista dos irán los enlaces de servidores directos, pero también pueden ir enlaces magnet (que no son cacheables)
+
"""
- #lanazamos un "lookup" en el "findvideos" del canal para obtener los enlaces de emergencia
+ We call Findvideos of the channel with the variable "item.videolibray_emergency_urls = True" to get the variable
+ "item.emergency_urls" with the list of tuple lists of torrent links and direct servers for that episode or movie
+ Torrents should always go in list [0], if any. If you want to cache the .torrents, the search goes against that list.
+ List two will include direct server links, but also magnet links (which are not cacheable).
+ """
+ # we launched a "lookup" in the "findvideos" of the channel to obtain the emergency links
try:
- if channel == None: #Si el llamador no ha aportado la estructura de channel, se crea
- channel = generictools.verify_channel(item.channel) #Se verifica si es un clon, que devuelva "newpct1"
+ if channel == None: # If the caller has not provided the channel structure, it is created
+ channel = generictools.verify_channel(item.channel) # It is verified if it is a clone, which returns "newpct1"
#channel = __import__('channels.%s' % channel, fromlist=["channels.%s" % channel])
channel = __import__('specials.%s' % channel_alt, fromlist=["specials.%s" % channel_alt])
- if hasattr(channel, 'findvideos'): #Si el canal tiene "findvideos"...
- item.videolibray_emergency_urls = True #... se marca como "lookup"
- channel_save = item.channel #... guarda el canal original por si hay fail-over en Newpct1
- category_save = item.category #... guarda la categoría original por si hay fail-over o redirección en Newpct1
- if item.channel_redir: #... si hay un redir, se restaura temporamente el canal alternativo
- item.channel = scrapertools.find_single_match(item.url, 'http.?\:\/\/(?:www.)?(\w+)\.\w+\/').lower()
- item.category = scrapertools.find_single_match(item.url, 'http.?\:\/\/(?:www.)?(\w+)\.\w+\/').capitalize()
- item_res = getattr(channel, 'findvideos')(item) #... se procesa Findvideos
- item_res.channel = channel_save #... restaura el canal original por si hay fail-over en Newpct1
- item_res.category = category_save #... restaura la categoría original por si hay fail-over o redirección en Newpct1
- item.category = category_save #... restaura la categoría original por si hay fail-over o redirección en Newpct1
- del item_res.videolibray_emergency_urls #... y se borra la marca de lookup
+ if hasattr(channel, 'findvideos'): # If the channel has "findvideos" ...
+ item.videolibray_emergency_urls = True # ... marks itself as "lookup"
+ channel_save = item.channel # ... save the original channel in case of fail-over in Newpct1
+ category_save = item.category # ... save the original category in case of fail-over or redirection in Newpct1
+ if item.channel_redir: # ... if there is a redir, the alternate channel is temporarily restored
+ item.channel = scrapertools.find_single_match(item.url, r'http.?\:\/\/(?:www.)?(\w+)\.\w+\/').lower()
+ item.category = scrapertools.find_single_match(item.url, r'http.?\:\/\/(?:www.)?(\w+)\.\w+\/').capitalize()
+ item_res = getattr(channel, 'findvideos')(item) # ... the process of Findvideos
+ item_res.channel = channel_save # ... restore the original channel in case there is a fail-over in Newpct1
+ item_res.category = category_save # ... restore the original category in case there is a fail-over or redirection in Newpct1
+ item.category = category_save # ... restore the original category in case there is a fail-over or redirection in Newpct1
+ del item_res.videolibray_emergency_urls # ... and the lookup mark is erased
if item.videolibray_emergency_urls:
- del item.videolibray_emergency_urls #... y se borra la marca de lookup original
+ del item.videolibray_emergency_urls # ... and the original lookup mark is erased
except:
logger.error('ERROR when processing the title in Findvideos del Canal: ' + item.channel + ' / ' + item.title)
logger.error(traceback.format_exc())
- item.channel = channel_save #... restaura el canal original por si hay fail-over o redirección en Newpct1
- item.category = category_save #... restaura la categoría original por si hay fail-over o redirección en Newpct1
- item_res = item.clone() #Si ha habido un error, se devuelve el Item original
+ item.channel = channel_save # ... restore the original channel in case of fail-over or redirection in Newpct1
+ item.category = category_save # ... restore the original category in case there is a fail-over or redirection in Newpct1
+ item_res = item.clone() # If there has been an error, the original Item is returned
if item_res.videolibray_emergency_urls:
- del item_res.videolibray_emergency_urls #... y se borra la marca de lookup
+ del item_res.videolibray_emergency_urls # ... and the lookup mark is erased
if item.videolibray_emergency_urls:
- del item.videolibray_emergency_urls #... y se borra la marca de lookup original
+ del item.videolibray_emergency_urls # ... and the original lookup mark is erased
- #Si el usuario ha activado la opción "emergency_urls_torrents", se descargarán los archivos .torrent de cada título
- else: #Si se han cacheado con éxito los enlaces...
+ # If the user has activated the option "emergency_urls_torrents", the .torrent files of each title will be downloaded
+ else: # If the links have been successfully cached ...
try:
referer = None
post = None
channel_bis = generictools.verify_channel(item.channel)
if config.get_setting("emergency_urls_torrents", channel_bis) and item_res.emergency_urls and path != None:
- videolibrary_path = config.get_videolibrary_path() #detectamos el path absoluto del título
+ videolibrary_path = config.get_videolibrary_path() # we detect the absolute path of the title
movies = config.get_setting("folder_movies")
series = config.get_setting("folder_tvshows")
if movies in path:
@@ -1135,16 +1112,16 @@ def emergency_urls(item, channel=None, path=None, headers={}):
i = 1
if item_res.referer: referer = item_res.referer
if item_res.post: post = item_res.post
- for url in item_res.emergency_urls[0]: #Recorremos las urls de emergencia...
+ for url in item_res.emergency_urls[0]: # We go through the emergency urls ...
torrents_path = re.sub(r'(?:\.\w+$)', '_%s.torrent' % str(i).zfill(2), path)
path_real = ''
if magnet_caching_e or not url.startswith('magnet'):
- path_real = torrent.caching_torrents(url, referer, post, torrents_path=torrents_path, headers=headers) #... para descargar los .torrents
- if path_real: #Si ha tenido éxito...
- item_res.emergency_urls[0][i-1] = path_real.replace(videolibrary_path, '') #se guarda el "path" relativo
+ path_real = torrent.caching_torrents(url, referer, post, torrents_path=torrents_path, headers=headers) # ... to download the .torrents
+ if path_real: # If you have been successful ...
+ item_res.emergency_urls[0][i-1] = path_real.replace(videolibrary_path, '') # if it looks at the relative "path"
i += 1
- #Restauramos variables originales
+ # We restore original variables
if item.referer:
item_res.referer = item.referer
elif item_res.referer:
@@ -1158,7 +1135,7 @@ def emergency_urls(item, channel=None, path=None, headers={}):
except:
logger.error('ERROR when caching the .torrent of: ' + item.channel + ' / ' + item.title)
logger.error(traceback.format_exc())
- item_res = item.clone() #Si ha habido un error, se devuelve el Item original
+ item_res = item.clone() # If there has been an error, the original Item is returned
#logger.debug(item_res.emergency_urls)
- return item_res #Devolvemos el Item actualizado con los enlaces de emergencia
+ return item_res # We return the updated Item with the emergency links
diff --git a/core/ziptools.py b/core/ziptools.py
index 1154e8f7..f95750b7 100644
--- a/core/ziptools.py
+++ b/core/ziptools.py
@@ -17,8 +17,8 @@ from core import filetools
class ziptools(object):
def extract(self, file, dir, folder_to_extract="", overwrite_question=False, backup=False):
- logger.info("file=%s" % file)
- logger.info("dir=%s" % dir)
+ logger.info("file= %s" % file)
+ logger.info("dir= %s" % dir)
if not dir.endswith(':') and not filetools.exists(dir):
filetools.mkdir(dir)
@@ -32,7 +32,7 @@ class ziptools(object):
name = nameo.replace(':', '_').replace('<', '_').replace('>', '_').replace('|', '_').replace('"', '_').replace('?', '_').replace('*', '_')
logger.info("name=%s" % nameo)
if not name.endswith('/'):
- logger.info("no es un directorio")
+ logger.info("it's not a directory")
try:
(path, filename) = filetools.split(filetools.join(dir, name))
logger.info("path=%s" % path)
@@ -53,31 +53,28 @@ class ziptools(object):
try:
if filetools.exists(outfilename) and overwrite_question:
from platformcode import platformtools
- dyesno = platformtools.dialog_yesno("El archivo ya existe",
- "El archivo %s a descomprimir ya existe" \
- ", ¿desea sobrescribirlo?" \
- % filetools.basename(outfilename))
+ dyesno = platformtools.dialog_yesno("File already exists "," File %s to unzip already exists, do you want to overwrite it?" % filetools.basename(outfilename))
if not dyesno:
break
if backup:
import time
- hora_folder = "Copia seguridad [%s]" % time.strftime("%d-%m_%H-%M", time.localtime())
+ hora_folder = "Backup [%s]" % time.strftime("%d-%m_%H-%M", time.localtime())
backup = filetools.join(config.get_data_path(), 'backups', hora_folder, folder_to_extract)
if not filetools.exists(backup):
filetools.mkdir(backup)
filetools.copy(outfilename, filetools.join(backup, filetools.basename(outfilename)))
if not filetools.write(outfilename, zf.read(nameo), silent=True, vfs=VFS): #TRUNCA en FINAL en Kodi 19 con VFS
- logger.error("Error en fichero " + nameo)
+ logger.error("File error " + nameo)
except:
import traceback
logger.error(traceback.format_exc())
- logger.error("Error en fichero " + nameo)
+ logger.error("File error " + nameo)
try:
zf.close()
except:
- logger.info("Error cerrando .zip " + file)
+ logger.info("Error closing .zip " + file)
def _createstructure(self, file, dir):
self._makedirs(self._listdirs(file), dir)
diff --git a/lib/cloudscraper/__init__.py b/lib/cloudscraper/__init__.py
index eb957a63..d10d2f73 100644
--- a/lib/cloudscraper/__init__.py
+++ b/lib/cloudscraper/__init__.py
@@ -1,8 +1,26 @@
+# ------------------------------------------------------------------------------- #
+
import logging
import re
+import requests
import sys
import ssl
-import requests
+
+from collections import OrderedDict
+from copy import deepcopy
+
+from requests.adapters import HTTPAdapter
+from requests.sessions import Session
+from requests_toolbelt.utils import dump
+
+from time import sleep
+
+# ------------------------------------------------------------------------------- #
+
+try:
+ import brotli
+except ImportError:
+ pass
try:
import copyreg
@@ -17,12 +35,12 @@ except ImportError:
else:
from html.parser import HTMLParser
-from copy import deepcopy
-from time import sleep
-from collections import OrderedDict
+try:
+ from urlparse import urlparse, urljoin
+except ImportError:
+ from urllib.parse import urlparse, urljoin
-from requests.sessions import Session
-from requests.adapters import HTTPAdapter
+# ------------------------------------------------------------------------------- #
from .exceptions import (
CloudflareLoopProtection,
@@ -37,25 +55,9 @@ from .interpreters import JavaScriptInterpreter
from .reCaptcha import reCaptcha
from .user_agent import User_Agent
-try:
- from requests_toolbelt.utils import dump
-except ImportError:
- pass
-
-try:
- import brotli
-except ImportError:
- pass
-
-try:
- from urlparse import urlparse, urljoin
-except ImportError:
- from urllib.parse import urlparse, urljoin
-
-
# ------------------------------------------------------------------------------- #
-__version__ = '1.2.36'
+__version__ = '1.2.40'
# ------------------------------------------------------------------------------- #
@@ -107,6 +109,9 @@ class CloudScraper(Session):
self.ssl_context = kwargs.pop('ssl_context', None)
self.interpreter = kwargs.pop('interpreter', 'native')
self.recaptcha = kwargs.pop('recaptcha', {})
+ self.requestPreHook = kwargs.pop('requestPreHook', None)
+ self.requestPostHook = kwargs.pop('requestPostHook', None)
+
self.allow_brotli = kwargs.pop(
'allow_brotli',
True if 'brotli' in sys.modules.keys() else False
@@ -213,19 +218,46 @@ class CloudScraper(Session):
if kwargs.get('proxies') and kwargs.get('proxies') != self.proxies:
self.proxies = kwargs.get('proxies')
- resp = self.decodeBrotli(
+ # ------------------------------------------------------------------------------- #
+ # Pre-Hook the request via user defined function.
+ # ------------------------------------------------------------------------------- #
+
+ if self.requestPreHook:
+ (method, url, args, kwargs) = self.requestPreHook(
+ self,
+ method,
+ url,
+ *args,
+ **kwargs
+ )
+
+ # ------------------------------------------------------------------------------- #
+ # Make the request via requests.
+ # ------------------------------------------------------------------------------- #
+
+ response = self.decodeBrotli(
super(CloudScraper, self).request(method, url, *args, **kwargs)
)
# ------------------------------------------------------------------------------- #
- # Debug request
+ # Debug the request via the Response object.
# ------------------------------------------------------------------------------- #
if self.debug:
- self.debugRequest(resp)
+ self.debugRequest(response)
+
+ # ------------------------------------------------------------------------------- #
+ # Post-Hook the request aka Post-Hook the response via user defined function.
+ # ------------------------------------------------------------------------------- #
+
+ if self.requestPostHook:
+ response = self.requestPostHook(self, response)
+
+ if self.debug:
+ self.debugRequest(response)
# Check if Cloudflare anti-bot is on
- if self.is_Challenge_Request(resp):
+ if self.is_Challenge_Request(response):
# ------------------------------------------------------------------------------- #
# Try to solve the challenge and send it back
# ------------------------------------------------------------------------------- #
@@ -239,12 +271,12 @@ class CloudScraper(Session):
self._solveDepthCnt += 1
- resp = self.Challenge_Response(resp, **kwargs)
+ response = self.Challenge_Response(response, **kwargs)
else:
- if not resp.is_redirect and resp.status_code not in [429, 503]:
+ if not response.is_redirect and response.status_code not in [429, 503]:
self._solveDepthCnt = 0
- return resp
+ return response
# ------------------------------------------------------------------------------- #
# check if the response contains a valid Cloudflare challenge
@@ -259,7 +291,7 @@ class CloudScraper(Session):
and re.search(
r'