# -*- coding: utf-8 -*- #from builtins import str from builtins import range import sys PY3 = False VFS = True if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int; VFS = False 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 else: import urllib import time import threading import os import traceback import re try: import xbmc import xbmcgui import xbmcaddon except: pass from core import filetools from core import httptools from core import scrapertools from core import jsontools from core.item import Item from platformcode import logger from platformcode import config from platformcode import platformtools trackers = [ "udp://tracker.openbittorrent.com:80/announce", "http://tracker.torrentbay.to:6969/announce", "http://tracker.pow7.com/announce", "udp://tracker.ccc.de:80/announce", "udp://open.demonii.com:1337", "http://9.rarbg.com:2710/announce", "http://bt.careland.com.cn:6969/announce", "http://explodie.org:6969/announce", "http://mgtracker.org:2710/announce", "http://tracker.best-torrents.net:6969/announce", "http://tracker.tfile.me/announce", "http://tracker1.wasabii.com.tw:6969/announce", "udp://9.rarbg.com:2710/announce", "udp://9.rarbg.me:2710/announce", "udp://coppersurfer.tk:6969/announce", "http://www.spanishtracker.com:2710/announce", "http://www.todotorrents.com:2710/announce", ] # Returns an array of possible video url's from the page_url def get_video_url(page_url, premium=False, user="", password="", video_password=""): logger.info("server=torrent, la url es la buena") if page_url.startswith("magnet:"): video_urls = [["magnet: [torrent]", page_url]] else: video_urls = [[".torrent [torrent]", page_url]] return video_urls class XBMCPlayer(xbmc.Player): def __init__(self, *args): pass xbmc_player = XBMCPlayer() def caching_torrents(url, referer=None, post=None, torrents_path=None, timeout=10, \ lookup=False, data_torrent=False, headers={}, proxy_retries=1): if torrents_path != None: logger.info("path = " + torrents_path) else: logger.info() if referer and post: logger.info('REFERER: ' + referer) torrent_file = '' t_hash = '' if referer: headers.update({'Content-Type': 'application/x-www-form-urlencoded', 'Referer': referer}) #Necesario para el Post del .Torrent """ Descarga en el path recibido el .torrent de la url recibida, y pasa el decode Devuelve el path real del .torrent, o el path vacío si la operación no ha tenido éxito """ videolibrary_path = config.get_videolibrary_path() #Calculamos el path absoluto a partir de la Videoteca if torrents_path == None: if not videolibrary_path: torrents_path = '' if data_torrent: return (torrents_path, torrent_file) return torrents_path #Si hay un error, devolvemos el "path" vacío torrents_path = filetools.join(videolibrary_path, 'temp_torrents_Alfa', 'cliente_torrent_Alfa.torrent') #path de descarga temporal if '.torrent' not in torrents_path: torrents_path += '.torrent' #path para dejar el .torrent #torrents_path_encode = filetools.encode(torrents_path) #encode utf-8 del path torrents_path_encode = torrents_path #if url.endswith(".rar") or url.startswith("magnet:"): #No es un archivo .torrent if url.endswith(".rar"): #No es un archivo .torrent logger.error('No es un archivo Torrent: ' + url) torrents_path = '' if data_torrent: return (torrents_path, torrent_file) return torrents_path #Si hay un error, devolvemos el "path" vacío try: #Descargamos el .torrent if url.startswith("magnet:"): if config.get_setting("magnet2torrent", server="torrent", default=False): torrent_file = magnet2torrent(url, headers=headers) #Convierte el Magnet en un archivo Torrent else: if data_torrent: return (url, torrent_file) return url if not torrent_file: logger.error('No es un archivo Magnet: ' + url) torrents_path = '' if data_torrent: return (torrents_path, torrent_file) return torrents_path #Si hay un error, devolvemos el "path" vacío else: if lookup: proxy_retries = 0 if post: #Descarga con POST response = httptools.downloadpage(url, headers=headers, post=post, \ follow_redirects=False, timeout=timeout, proxy_retries=proxy_retries) else: #Descarga sin post response = httptools.downloadpage(url, headers=headers, timeout=timeout, \ proxy_retries=proxy_retries) if not response.sucess: logger.error('Archivo .torrent no encontrado: ' + url) torrents_path = '' if data_torrent: return (torrents_path, torrent_file) return torrents_path #Si hay un error, devolvemos el "path" vacío torrent_file = response.data torrent_file_uncoded = response.data if PY3 and isinstance(torrent_file, bytes): torrent_file = "".join(chr(x) for x in bytes(torrent_file_uncoded)) #Si es un archivo .ZIP tratamos de extraer el contenido if torrent_file.startswith("PK"): logger.info('Es un archivo .ZIP: ' + url) torrents_path_zip = filetools.join(videolibrary_path, 'temp_torrents_zip') #Carpeta de trabajo torrents_path_zip = filetools.encode(torrents_path_zip) torrents_path_zip_file = filetools.join(torrents_path_zip, 'temp_torrents_zip.zip') #Nombre del .zip import time filetools.rmdirtree(torrents_path_zip) #Borramos la carpeta temporal time.sleep(1) #Hay que esperar, porque si no da error filetools.mkdir(torrents_path_zip) #La creamos de nuevo if filetools.write(torrents_path_zip_file, torrent_file_uncoded, vfs=VFS): #Salvamos el .zip torrent_file = '' #Borramos el contenido en memoria try: #Extraemos el .zip from core import ziptools unzipper = ziptools.ziptools() unzipper.extract(torrents_path_zip_file, torrents_path_zip) except: import xbmc xbmc.executebuiltin('XBMC.Extract("%s", "%s")' % (torrents_path_zip_file, torrents_path_zip)) time.sleep(1) for root, folders, files in filetools.walk(torrents_path_zip): #Recorremos la carpeta para leer el .torrent for file in files: if file.endswith(".torrent"): input_file = filetools.join(root, file) #nombre del .torrent torrent_file = filetools.read(input_file, vfs=VFS) #leemos el .torrent torrent_file_uncoded = torrent_file if PY3 and isinstance(torrent_file, bytes): torrent_file = "".join(chr(x) for x in bytes(torrent_file_uncoded)) filetools.rmdirtree(torrents_path_zip) #Borramos la carpeta temporal #Si no es un archivo .torrent (RAR, HTML,..., vacío) damos error if not scrapertools.find_single_match(torrent_file, '^d\d+:.*?\d+:'): logger.error('No es un archivo Torrent: ' + url) torrents_path = '' if data_torrent: return (torrents_path, torrent_file) return torrents_path #Si hay un error, devolvemos el "path" vacío #Calculamos el Hash del Torrent y modificamos el path import bencode, hashlib decodedDict = bencode.bdecode(torrent_file_uncoded) if not PY3: t_hash = hashlib.sha1(bencode.bencode(decodedDict[b"info"])).hexdigest() else: t_hash = hashlib.sha1(bencode.bencode(decodedDict["info"])).hexdigest() if t_hash: torrents_path = filetools.join(filetools.dirname(torrents_path), t_hash + '.torrent') torrents_path_encode = filetools.join(filetools.dirname(torrents_path_encode), t_hash + '.torrent') #Salvamos el .torrent if not lookup: if not filetools.write(torrents_path_encode, torrent_file_uncoded, vfs=VFS): logger.error('ERROR: Archivo .torrent no escrito: ' + torrents_path_encode) torrents_path = '' #Si hay un error, devolvemos el "path" vacío torrent_file = '' #... y el buffer del .torrent if data_torrent: return (torrents_path, torrent_file) return torrents_path except: torrents_path = '' #Si hay un error, devolvemos el "path" vacío torrent_file = '' #... y el buffer del .torrent logger.error('Error en el proceso de descarga del .torrent: ' + url + ' / ' + torrents_path_encode) logger.error(traceback.format_exc()) #logger.debug(torrents_path) if data_torrent: return (torrents_path, torrent_file) return torrents_path def magnet2torrent(magnet, headers={}): logger.info() torrent_file = '' info = '' post = '' LIBTORRENT_PATH = config.get_setting("libtorrent_path", server="torrent", default="") LIBTORRENT_MAGNET_PATH = filetools.join(config.get_setting("downloadpath"), 'magnet') MAGNET2TORRENT = config.get_setting("magnet2torrent", server="torrent", default=False) btih = scrapertools.find_single_match(magnet, 'urn:btih:([\w\d]+)\&').upper() if magnet.startswith('magnet') and MAGNET2TORRENT: # Tratamos de convertir el magnet on-line (opción más rápida, pero no se puede convertir más de un magnet a la vez) url_list = [ ('https://itorrents.org/torrent/', 6, '', '.torrent') ] # Lista de servicios on-line testeados for url, timeout, id, sufix in url_list: if id: post = '%s=%s' % (id, magnet) else: url = '%s%s%s' % (url, btih, sufix) response = httptools.downloadpage(url, timeout=timeout, headers=headers, post=post) if not response.sucess: continue if not scrapertools.find_single_match(response.data, '^d\d+:.*?\d+:') and not response.data.startswith("PK"): continue torrent_file = response.data break #Usamos Libtorrent para la conversión del magnet como alternativa (es lento) if not torrent_file: lt, e, e1, e2 = import_libtorrent(LIBTORRENT_PATH) # Importamos Libtorrent if lt: ses = lt.session() # Si se ha importado bien, activamos Libtorrent ses.add_dht_router("router.bittorrent.com",6881) ses.add_dht_router("router.utorrent.com",6881) ses.add_dht_router("dht.transmissionbt.com",6881) if ses: filetools.mkdir(LIBTORRENT_MAGNET_PATH) # Creamos la carpeta temporal params = { 'save_path': LIBTORRENT_MAGNET_PATH, 'trackers': trackers, 'storage_mode': lt.storage_mode_t.storage_mode_allocate } # Creamos los parámetros de la sesión h = lt.add_magnet_uri(ses, magnet, params) # Abrimos la sesión i = 0 while not h.has_metadata() and not xbmc.abortRequested: # Esperamos mientras Libtorrent abre la sesión h.force_dht_announce() time.sleep(1) i += 1 logger.error(i) if i > 5: LIBTORRENT_PATH = '' # No puede convertir el magnet break if LIBTORRENT_PATH: info = h.get_torrent_info() # Obtiene la información del .torrent torrent_file = lt.bencode(lt.create_torrent(info).generate()) # Obtiene los datos del .torrent ses.remove_torrent(h) # Desactiva Libtorrent filetools.rmdirtree(LIBTORRENT_MAGNET_PATH) # Elimina la carpeta temporal return torrent_file def verify_url_torrent(url, timeout=5): """ Verifica si el archivo .torrent al que apunta la url está disponible, descargándolo en un area temporal Entrada: url Salida: True o False dependiendo del resultado de la operación """ if not url or url == 'javascript:;': #Si la url viene vacía... return False #... volvemos con error torrents_path = caching_torrents(url, timeout=timeout, lookup=True) #Descargamos el .torrent if torrents_path: #Si ha tenido éxito... return True else: return False # Reproductor Cliente Torrent propio (libtorrent) def bt_client(mediaurl, xlistitem, rar_files, subtitle=None, password=None, item=None): logger.info() # Importamos el cliente from btserver import Client played = False debug = False try: save_path_videos = '' save_path_videos = filetools.join(config.get_setting("bt_download_path", server="torrent", \ default=config.get_setting("downloadpath")), 'BT-torrents') except: pass if not config.get_setting("bt_download_path", server="torrent") and save_path_videos: config.set_setting("bt_download_path", filetools.join(config.get_data_path(), 'downloads'), server="torrent") if not save_path_videos: save_path_videos = filetools.join(config.get_data_path(), 'downloads', 'BT-torrents') config.set_setting("bt_download_path", filetools.join(config.get_data_path(), 'downloads'), server="torrent") UNRAR = config.get_setting("unrar_path", server="torrent", default="") BACKGROUND = config.get_setting("mct_background_download", server="torrent", default=True) RAR = config.get_setting("mct_rar_unpack", server="torrent", default=True) try: BUFFER = int(config.get_setting("bt_buffer", server="torrent", default="50")) except: BUFFER = 50 DOWNLOAD_LIMIT = config.get_setting("mct_download_limit", server="torrent", default="") if DOWNLOAD_LIMIT: try: DOWNLOAD_LIMIT = int(DOWNLOAD_LIMIT) except: DOWNLOAD_LIMIT = 0 else: DOWNLOAD_LIMIT = 0 UPLOAD_LIMIT = 100 torr_client = 'BT' rar_file = '' rar_names = [] rar = False rar_res = False bkg_user = False video_names = [] video_file = '' video_path = '' videourl = '' msg_header = 'KoD %s Client Torrent' % torr_client extensions_list = ['.aaf', '.3gp', '.asf', '.avi', '.flv', '.mpeg', '.m1v', '.m2v', '.m4v', '.mkv', '.mov', '.mpg', '.mpe', '.mp4', '.ogg', '.rar', '.wmv', '.zip'] for entry in rar_files: for file, path in list(entry.items()): if file == 'path' and '.rar' in str(path): for file_r in path: rar_names += [file_r] rar = True if RAR and BACKGROUND: bkg_user = True elif file == 'path' and not '.rar' in str(path): for file_r in path: if os.path.splitext(file_r)[1] in extensions_list: video_names += [file_r] elif file == '__name': video_path = path video_file = path if rar: rar_file = '%s/%s' % (video_path, rar_names[0]) erase_file_path = filetools.join(save_path_videos, video_path) video_path = erase_file_path if video_names: video_file = video_names[0] if not video_file and mediaurl.startswith('magnet'): video_file = urllib.unquote_plus(scrapertools.find_single_match(mediaurl, '(?:\&|&)dn=([^\&]+)\&')) erase_file_path = filetools.join(save_path_videos, video_file) if rar and RAR and not UNRAR: if not platformtools.dialog_yesno(msg_header, 'Se ha detectado un archivo .RAR en la descarga', \ 'No tiene instalado el extractor UnRAR', '¿Desea descargarlo en cualquier caso?'): return # Iniciamos el cliente: c = Client(url=mediaurl, is_playing_fnc=xbmc_player.isPlaying, wait_time=None, auto_shutdown=False, timeout=10, temp_path=save_path_videos, print_status=debug, auto_delete=False) activo = True finalizado = False dp_cerrado = True # Mostramos el progreso if rar and RAR and BACKGROUND: # Si se descarga un RAR... progreso = platformtools.dialog_progress_bg(msg_header) platformtools.dialog_notification("Descarga de RAR en curso", "Puedes realizar otras tareas en Kodi mientrastanto. " + \ "Te informaremos...", time=10000) else: progreso = platformtools.dialog_progress('Alfa %s Cliente Torrent' % torr_client, '') dp_cerrado = False # Mientras el progreso no sea cancelado ni el cliente cerrado try: while not c.closed and not xbmc.abortRequested: # Obtenemos el estado del torrent s = c.status if debug: # Montamos las tres lineas con la info del torrent txt = '%.2f%% de %.1fMB %s | %.1f kB/s' % \ (s.progress_file, s.file_size, s.str_state, s._download_rate) txt2 = 'S: %d(%d) P: %d(%d) | DHT:%s (%d) | Trakers: %d | Pi: %d(%d)' % \ (s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.dht_state, s.dht_nodes, s.trackers, s.pieces_sum, s.pieces_len) txt3 = 'Origen Peers TRK: %d DHT: %d PEX: %d LSD %d ' % \ (s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers) else: txt = '%.2f%% de %.1fMB %s | %.1f kB/s' % \ (s.progress_file, s.file_size, s.str_state, s._download_rate) txt2 = 'S: %d(%d) P: %d(%d) | DHT:%s (%d) | Trakers: %d | Pi: %d(%d)' % \ (s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.dht_state, s.dht_nodes, s.trackers, s.pieces_sum, s.pieces_len) txt3 = video_file if rar and RAR and BACKGROUND or bkg_user: progreso.update(s.buffer, txt, txt2) else: progreso.update(s.buffer, txt, txt2, txt3) time.sleep(1) if (not bkg_user and progreso.iscanceled()) and (not (rar and RAR and BACKGROUND) and progreso.iscanceled()): if not dp_cerrado: progreso.close() dp_cerrado = True if 'Finalizado' in s.str_state or 'Seeding' in s.str_state: """ if not rar and platformtools.dialog_yesno(msg_header, config.get_localized_string(70198)): played = False dp_cerrado = False progreso = platformtools.dialog_progress(msg_header, '') progreso.update(s.buffer, txt, txt2, txt3) else: """ dp_cerrado = False progreso = platformtools.dialog_progress(msg_header, '') break else: if not platformtools.dialog_yesno(msg_header, config.get_localized_string(30031), config.get_localized_string(30032)): dp_cerrado = False progreso = platformtools.dialog_progress(msg_header, '') break else: bkg_user = True if not dp_cerrado: progreso.close() dp_cerrado = False progreso = platformtools.dialog_progress_bg(msg_header) progreso.update(s.buffer, txt, txt2) if not c.closed: c.set_speed_limits(DOWNLOAD_LIMIT, UPLOAD_LIMIT) # Bajamos la velocidad en background # Si el buffer se ha llenado y la reproduccion no ha sido iniciada, se inicia if ((s.pieces_sum >= BUFFER or 'Finalizado' in s.str_state or 'Seeding' in s.str_state) and not rar and not bkg_user) or \ (s.pieces_sum >= s.pieces_len - 3 and s.pieces_len > 0 and ('Finalizado' in s.str_state or 'Seeding' \ in s.str_state) and (rar or bkg_user)) and not played: if rar and RAR and UNRAR: c.stop() activo = False finalizado = True bkg_user = False dp_cerrado = False video_file, rar_res, video_path, erase_file_path = extract_files(rar_file, \ save_path_videos, password, progreso, item, torr_client) # ... extraemos el vídeo del RAR if rar_res and not xbmc.abortRequested: time.sleep(1) else: break elif (rar and not UNRAR) or (rar and not RAR): break elif bkg_user: finalizado = True break # Cerramos el progreso if not dp_cerrado: progreso.close() dp_cerrado = True # Reproducimos el vídeo extraido, si no hay nada en reproducción if not c.closed: c.set_speed_limits(DOWNLOAD_LIMIT, UPLOAD_LIMIT) # Bajamos la velocidad en background bkg_auto = True while xbmc_player.isPlaying() and not xbmc.abortRequested: time.sleep(3) # Obtenemos el playlist del torrent #videourl = c.get_play_list() if not rar_res: # Es un Magnet ? video_file = filetools.join(save_path_videos, s.file_name) if erase_file_path == save_path_videos: erase_file_path = video_file videourl = video_file else: videourl = filetools.join(video_path, video_file) # Iniciamos el reproductor playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() playlist.add(videourl, xlistitem) # xbmc_player = xbmc_player log("##### videourl: %s" % videourl) xbmc_player.play(playlist) # Marcamos como reproducido para que no se vuelva a iniciar played = True mark_auto_as_watched(item) # Y esperamos a que el reproductor se cierre bkg_auto = True dp_cerrado = True while xbmc_player.isPlaying() and not xbmc.abortRequested: time.sleep(1) if xbmc.getCondVisibility('Player.Playing'): if not dp_cerrado: dp_cerrado = True progreso.close() if xbmc.getCondVisibility('Player.Paused') and not rar_res: if not c.closed: s = c.status txt = '%.2f%% de %.1fMB %s | %.1f kB/s' % \ (s.progress_file, s.file_size, s.str_state, s._download_rate) txt2 = 'S: %d(%d) P: %d(%d) | DHT:%s (%d) | Trakers: %d | Pi: %d(%d)' % \ (s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.dht_state, s.dht_nodes, s.trackers, s.pieces_sum, s.pieces_len) txt3 = video_file[:99] if dp_cerrado: dp_cerrado = False progreso = xbmcgui.DialogProgressBG() progreso.create(msg_header) progreso.update(s.buffer, msg_header, '[CR][CR]' + txt + '[CR]' + txt2) if not dp_cerrado: dp_cerrado = True progreso.close() # Miramos si se ha completado la descarga para borrar o no los archivos if activo: s = c.status if s.pieces_sum == s.pieces_len: finalizado = True break if platformtools.dialog_yesno(msg_header, config.get_localized_string(30031), config.get_localized_string(30032)): progreso = platformtools.dialog_progress(msg_header, '') dp_cerrado = False break else: bkg_user = True played = False if not dp_cerrado: progreso.close() progreso = platformtools.dialog_progress_bg(msg_header) progreso.update(s.buffer, txt, txt2) dp_cerrado = False continue # Cuando este cerrado, Volvemos a mostrar el dialogo if not (rar and bkg_user): progreso = platformtools.dialog_progress(msg_header, '') progreso.update(s.buffer, txt, txt2, txt3) dp_cerrado = False break except: logger.error(traceback.format_exc(1)) return if not dp_cerrado: if rar or bkg_user: progreso.update(100, config.get_localized_string(70200), " ") else: progreso.update(100, config.get_localized_string(70200), " ", " ") # Detenemos el cliente if activo and not c.closed: c.stop() activo = False # Cerramos el progreso if not dp_cerrado: progreso.close() dp_cerrado = True # Y borramos los archivos de descarga restantes time.sleep(1) if filetools.exists(erase_file_path) and not bkg_user: if finalizado and not platformtools.dialog_yesno(msg_header, '¿Borrarmos los archivos descargados? (completos)'): return log("##### erase_file_path: %s" % erase_file_path) for x in range(10): if filetools.isdir(erase_file_path): if erase_file_path != save_path_videos: filetools.rmdirtree(erase_file_path) else: break else: filetools.remove(erase_file_path) time.sleep(5) if not filetools.exists(erase_file_path): break def call_torrent_via_web(mediaurl, torr_client): # Usado para llamar a los clientes externos de Torrents para automatizar la descarga de archivos que contienen .RAR logger.info() post = '' ELEMENTUMD_HOST = "http://localhost:65220" if torr_client == 'elementum': try: ADDON = xbmcaddon.Addon("plugin.video.elementum") except: ADDON = False if ADDON: ELEMENTUMD_HOST = "http://" + ADDON.getSetting("remote_host") + ":" + ADDON.getSetting("remote_port") local_host = {"quasar": ["http://localhost:65251/torrents/", "add?uri"], \ "elementum": ["%s/torrents/" % ELEMENTUMD_HOST, "add"]} if torr_client == "quasar": uri = '%s%s=%s' % (local_host[torr_client][0], local_host[torr_client][1], mediaurl) elif torr_client == "elementum": uri = '%s%s' % (local_host[torr_client][0], local_host[torr_client][1]) post = 'uri=%s&file=null&all=1' % mediaurl if post: response = httptools.downloadpage(uri, post=post, timeout=5, alfa_s=True, ignore_response_code=True) else: response = httptools.downloadpage(uri, timeout=5, alfa_s=True, ignore_response_code=True) return response.sucess def mark_auto_as_watched(item): time_limit = time.time() + 150 #Marcamos el timepo máx. de buffering while not platformtools.is_playing() and time.time() < time_limit: #Esperamos mientra buffera time.sleep(5) #Repetimos cada intervalo #logger.debug(str(time_limit)) if item.subtitle: time.sleep(5) xbmc_player.setSubtitles(item.subtitle) #subt = xbmcgui.ListItem(path=item.url, thumbnailImage=item.thumbnail) #subt.setSubtitles([item.subtitle]) if item.strm_path and platformtools.is_playing(): #Sólo si es de Videoteca from platformcode import xbmc_videolibrary xbmc_videolibrary.mark_auto_as_watched(item) #Marcamos como visto al terminar #logger.debug("Llamado el marcado") def wait_for_download(item, mediaurl, rar_files, torr_client, password='', size='', rar_control={}): logger.info() from subprocess import Popen, PIPE, STDOUT # Analizamos los archivos dentro del .torrent rar = False rar_names = [] rar_names_abs = [] folder = '' if rar_control: for x, entry in enumerate(rar_control['rar_files']): if '__name' in entry: folder = rar_control['rar_files'][x]['__name'] break rar_names = [rar_control['rar_names'][0]] else: for entry in rar_files: for file, path in list(entry.items()): if file == 'path' and '.rar' in str(path): for file_r in path: rar_names += [file_r] rar = True elif file == '__name': folder = path if not folder: # Si no se detecta el folder... return ('', '', '') # ... no podemos hacer nada if not rar_names: return ('', '', folder) rar_file = '%s/%s' % (folder, rar_names[0]) log("##### rar_file: %s" % rar_file) if len(rar_names) > 1: log("##### rar_names: %s" % str(rar_names)) # Localizamos el path de descarga del .torrent save_path_videos = '' __settings__ = xbmcaddon.Addon(id="plugin.video.%s" % torr_client) # Apunta settings del cliente torrent if torr_client == 'torrenter': save_path_videos = str(xbmc.translatePath(__settings__.getSetting('storage'))) if not save_path_videos: save_path_videos = str(filetools.join(xbmc.translatePath("special://home/"), \ "cache", "xbmcup", "plugin.video.torrenter", "Torrenter")) else: save_path_videos = str(xbmc.translatePath(__settings__.getSetting('download_path'))) if __settings__.getSetting('download_storage') == '1': # Descarga en memoria? return ('', '', folder) # volvemos if not save_path_videos: # No hay path de descarga? return ('', '', folder) # Volvemos log("##### save_path_videos: %s" % save_path_videos) # Si es nueva descarga, ponemos un archivo de control para reiniciar el UNRar si ha habido cancelación de Kodi # Si ya existe el archivo (llamada), se reinicia el proceso de UNRar donde se quedó if rar_control: if 'downloading' not in rar_control['status']: log("##### Torrent DESCARGADO Anteriormente: %s" % str(folder)) return (rar_file, save_path_videos, folder) else: rar_control = { 'torr_client': torr_client, 'rar_files': rar_files, 'rar_names': rar_names, 'size': size, 'password': password, 'download_path': filetools.join(save_path_videos, folder), 'status': 'downloading', 'error': 0, 'error_msg': '', 'item': item.tourl(), 'mediaurl': mediaurl } if torr_client == 'quasar': # Quasar no copia en .torrent ret = filetools.copy(item.url, filetools.join(save_path_videos, 'torrents', \ filetools.basename(item.url)), silent=True) # Esperamos mientras el .torrent se descarga. Verificamos si el .RAR está descargado al completo platformtools.dialog_notification("Automatizando la extracción", "Acepta descargar el archivo RAR y te iremos guiando...", time=10000) # Plan A: usar el monitor del cliente torrent para ver el status de la descarga loop = 3600 # Loop de 10 horas hasta crear archivo wait_time = 10 time.sleep(wait_time) fast = False ret = filetools.write(filetools.join(rar_control['download_path'], '_rar_control.json'), jsontools.dump(rar_control)) for x in range(loop): if xbmc.abortRequested: return ('', '', folder) torr_data, deamon_url, index = get_tclient_data(folder, torr_client) if not torr_data or not deamon_url: if len(filetools.listdir(rar_control['download_path'], silent=True)) <= 1: filetools.remove(filetools.join(rar_control['download_path'], '_rar_control.json'), silent=True) filetools.rmdir(rar_control['download_path'], silent=True) return ('', '', folder) # Volvemos if (torr_client in ['quasar'] or torr_client in ['elementum']) and not \ torr_data['label'].startswith('0.00%') and not fast: platformtools.dialog_notification("Descarga en curso", "Puedes realizar otras tareas en Kodi mientrastanto. " + \ "Te informaremos...", time=10000) fast = True if not torr_data['label'].startswith('100.00%'): log("##### Descargado: %s, ID: %s" % (scrapertools.find_single_match(torr_data['label'], '(^.*?\%)'), index)) time.sleep(wait_time) continue update_rar_control(rar_control['download_path'], status='downloaded') log("##### Torrent FINALIZADO: %s" % str(folder)) return (rar_file, save_path_videos, folder) # Plan B: monitorizar con UnRAR si los archivos se han desacargado por completo unrar_path = config.get_setting("unrar_path", server="torrent", default="") if not unrar_path: # Si Unrar no está instalado... return ('', '', folder) # ... no podemos hacer nada cmd = [] for rar_name in rar_names: # Preparamos por si es un archivo multiparte cmd.append(['%s' % unrar_path, 'l', '%s' % filetools.join(save_path_videos, folder, rar_name)]) creationflags = '' if xbmc.getCondVisibility("system.platform.Windows"): creationflags = 0x08000000 loop = 30 # Loop inicial de 5 minutos hasta crear archivo wait_time = 10 loop_change = 0 loop_error = 6 part_name = '' y = 0 returncode = '' fast = False while rar and not xbmc.abortRequested: for x in range(loop): # Loop corto (5 min.) o largo (10 h.) if xbmc.abortRequested: return ('', '', folder) if not rar or loop_change > 0: loop = loop_change # Paso de loop corto a largo loop_change = 0 break try: responses = [] for z, command in enumerate(cmd): # Se prueba por cada parte if xbmc.getCondVisibility("system.platform.Windows"): data_rar = Popen(command, bufsize=0, stdout=PIPE, stdin=PIPE, \ stderr=STDOUT, creationflags=creationflags) else: data_rar = Popen(command, bufsize=0, stdout=PIPE, stdin=PIPE, \ stderr=STDOUT) out_, error_ = data_rar.communicate() responses.append([z, str(data_rar.returncode), out_, error_]) # Se guarda la respuesta de cada parte except: logger.error(traceback.format_exc(1)) # Error de incompatibilidad de UnRAR rar = False break else: dl_files = 0 for z, returncode, out__, error__ in responses: # Analizamos las respuestas if returncode == '0': # Ya se ha descargado... parte ... dl_files += 1 part_name = scrapertools.find_single_match(str(out__), '(\.part\d+.rar)') log("##### Torrent descargando: %s, %s" % (part_name, str(returncode))) if dl_files == len(cmd): # ... o todo fast = True rar = False break # ... o sólo una parte elif returncode == '10': # archivo no existe if loop != 30: # Si el archivo es borrado durante el proceso ... rar = False break #... abortamos elif returncode == '6': # En proceso de descarga y += 1 #if loop == 30 and y == len(responses): # Si es la primera vez en proceso ... if loop == 30 and y == 1: # Si es la primera vez en proceso ... if torr_client in ['quasar']: platformtools.dialog_notification("Descarga en curso", "Puedes realizar otras tareas en Kodi mientrastanto. " + \ "Te informaremos...", time=10000) loop_change = 3600 # ... pasamos a un loop de 10 horas elif loop <= 6: # Recuerado el error desconocido loop_change = 3600 # ... pasamos a un loop de 10 horas loop_error = 6 # Restauramos loop_error por si acaso break elif returncode == '1': # Ha alcanzado el fin de archivo ??? pasamos part_name = scrapertools.find_single_match(str(out__), '(\.part\d+.rar)') log("##### Torrent descargando: %s, %s" % (part_name, str(returncode))) else: # No entendemos el error loop_change = loop_error # ... pasamos a un loop de 1 minutos para reintentar loop_error += -1 break #... abortamos if str(returncode) in ['0', '6', '10']: log("##### Torrent descargando: %s" % str(returncode)) else: log("##### Torrent descargando: %s, %s" % (str(out__), str(returncode))) if not rar or fast: fast = False break time.sleep(wait_time) # Esperamos un poco y volvemos a empezar else: rar = False break if str(returncode) == '0': log("##### Torrent FINALIZADO: %s" % str(returncode)) else: rar_file = '' logger.error('##### Torrent NO DESCARGADO: %s, %s' % (str(out__), str(returncode))) return (rar_file, save_path_videos, folder) def get_tclient_data(folder, torr_client): # Monitoriza el estado de descarga del torrent en Quasar y Elementum ELEMENTUMD_HOST = "http://localhost:65220" if torr_client == 'elementum': try: ADDON = xbmcaddon.Addon("plugin.video.elementum") except: ADDON = False if ADDON: ELEMENTUMD_HOST = "http://" + ADDON.getSetting("remote_host") + ":" + ADDON.getSetting("remote_port") local_host = {"quasar": "http://localhost:65251/torrents/", "elementum": "%s/torrents/" % ELEMENTUMD_HOST} torr = '' torr_id = '' x = 0 y = '' try: data = httptools.downloadpage(local_host[torr_client], timeout=5, alfa_s=True).data if not data: return '', local_host[torr_client], 0 data = jsontools.load(data) data = data['items'] for x, torr in enumerate(data): if not folder in torr['label']: continue if "elementum" in torr_client: torr_id = scrapertools.find_single_match(str(torr), 'torrents\/move\/(.*?)\)') break else: return '', local_host[torr_client], 0 except: log(traceback.format_exc(1)) return '', local_host[torr_client], 0 if torr_id: y = torr_id else: y = x return torr, local_host[torr_client], y def extract_files(rar_file, save_path_videos, password, dp, item=None, \ torr_client=None, rar_control={}, size='RAR', mediaurl=''): logger.info() from platformcode import custom_code if not rar_control: rar_control = { 'torr_client': torr_client, 'rar_files': [{"__name": "%s" % rar_file.split("/")[0]}], 'rar_names': [filetools.basename(rar_file)], 'size': size, 'password': password, 'download_path': save_path_videos, 'status': 'downloaded', 'error': 0, 'error_msg': '', 'item': item.tourl(), 'mediaurl': mediaurl } ret = filetools.write(filetools.join(rar_control['download_path'], '_rar_control.json'), jsontools.dump(rar_control)) #reload(sys) #sys.setdefaultencoding('utf-8') sys.path.insert(0, config.get_setting("unrar_path", server="torrent", default="")\ .replace('/unrar', '').replace('\\unrar,exe', '')) import rarfile # Verificamos si hay path para UnRAR rarfile.UNRAR_TOOL = config.get_setting("unrar_path", server="torrent", default="") if not rarfile.UNRAR_TOOL: if xbmc.getCondVisibility("system.platform.Android"): rarfile.UNRAR_TOOL = xbmc.executebuiltin("StartAndroidActivity(com.rarlab.rar)") return rar_file, False, '', '' log("##### unrar_path: %s" % rarfile.UNRAR_TOOL) rarfile.DEFAULT_CHARSET = 'utf-8' # Preparamos un path alternativo más corto para no sobrepasar la longitud máxima video_path = '' if item: if item.contentType == 'movie': video_path = '%s-%s' % (item.contentTitle, item.infoLabels['tmdb_id']) else: video_path = '%s-%sx%s-%s' % (item.contentSerieName, item.contentSeason, \ item.contentEpisodeNumber, item.infoLabels['tmdb_id']) video_path = video_path.replace("á", "a").replace("é", "e").replace("í", "i").replace("ó", "o")\ .replace("ú", "u").replace("ü", "u").replace("ñ", "n")\ .replace("Á", "A").replace("É", "E").replace("Í", "I").replace("Ó", "O")\ .replace("Ú", "U").replace("Ü", "U").replace("Ñ", "N") # Renombramos el path dejado en la descarga a uno más corto rename_status = False org_rar_file = rar_file org_save_path_videos = save_path_videos if video_path and '/' in rar_file: log("##### rar_file: %s" % rar_file) rename_status, rar_file = rename_rar_dir(org_rar_file, org_save_path_videos, video_path, torr_client) # Calculamos el path para del RAR if "/" in rar_file: folders = rar_file.split("/") erase_file_path = filetools.join(save_path_videos, folders[0]) file_path = save_path_videos for f in folders: file_path = filetools.join(file_path, f) else: file_path = save_path_videos erase_file_path = save_path_videos # Calculamos el path para la extracción if "/" in rar_file: folders = rar_file.split("/") for f in folders: if not '.rar' in f: save_path_videos = filetools.join(save_path_videos, f) save_path_videos = filetools.join(save_path_videos, 'Extracted') if not filetools.exists(save_path_videos): filetools.mkdir(save_path_videos) log("##### save_path_videos: %s" % save_path_videos) rar_control = update_rar_control(erase_file_path, status='UnRARing') # Permite hasta 5 pasadas de extracción de .RARs anidados platformtools.dialog_notification("Empezando extracción...", rar_file, time=5000) for x in range(5): try: if not PY3: archive = rarfile.RarFile(file_path.decode("utf8")) else: archive = rarfile.RarFile(file_path) except: log("##### ERROR en Archivo rar: %s" % rar_file) log("##### ERROR en Carpeta del rar: %s" % file_path) log(traceback.format_exc()) error_msg = "Error al abrir el RAR" error_msg1 = "Comprueba el log para más detalles" platformtools.dialog_notification(error_msg, error_msg1) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg, status='ERROR') return rar_file, False, '', '' # Analizamos si es necesaria una contraseña, que debería estar en item.password if archive.needs_password(): if not password: pass_path = filetools.split(file_path)[0] password = last_password_search(pass_path, erase_file_path) if not password : password = platformtools.dialog_input(heading="Introduce la contraseña (Mira en %s)" % pass_path) if not password: error_msg = "No se ha introducido la contraseña" rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg, status='ERROR') dp.close() return custom_code.reactivate_unrar(init=False, mute=False) archive.setpassword(password) log("##### Password rar: %s" % password) # Miramos el contenido del RAR a extraer files = archive.infolist() info = [] for idx, i in enumerate(files): if i.file_size == 0: files.pop(idx) continue filename = i.filename if "/" in filename: filename = filename.rsplit("/", 1)[1] info.append("%s - %.2f MB" % (filename, i.file_size / 1048576.0)) if info: info.append("Extraer todo sin reproducir") else: error_msg = "El RAR está vacío" error_msg1 = "O no contiene archivos válidos" platformtools.dialog_notification(error_msg, error_msg1) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg, status='ERROR') dp.close() return custom_code.reactivate_unrar(init=False, mute=False) # Seleccionamos extraer TODOS los archivos del RAR #selection = xbmcgui.Dialog().select("Selecciona el fichero a extraer y reproducir", info) selection = len(info) - 1 if selection < 0: error_msg = "El RAR está vacío" platformtools.dialog_notification(error_msg) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg, status='ERROR') return rar_file, False, '', '' else: try: log("##### RAR Extract INI #####") if selection == len(info) - 1: log("##### rar_file 1: %s" % file_path) log("##### save_path_videos 1: %s" % save_path_videos) dp.update(99, "Extrayendo archivos...", "Espera unos minutos....") archive.extractall(save_path_videos) else: log("##### rar_file 2: %s" % file_path) log("##### save_path_videos 2: %s" % save_path_videos) dp.update(99, "Espera unos minutos....", "Extrayendo archivo... %s" % info[selection]) archive.extract(files[selection], save_path_videos) log("##### RAR Extract END #####") except (rarfile.RarWrongPassword, rarfile.RarCRCError): log(traceback.format_exc(1)) error_msg = "Error al extraer" error_msg1 = "Contraseña incorrecta" platformtools.dialog_notification(error_msg, error_msg1) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg1, status='ERROR') dp.close() return custom_code.reactivate_unrar(init=False, mute=False) except rarfile.BadRarFile: log(traceback.format_exc(1)) error_msg = "Error al extraer" error_msg1 = "Archivo rar con errores" platformtools.dialog_notification(error_msg, error_msg1) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg1, status='ERROR') #return rar_file, False, '', erase_file_path dp.close() return custom_code.reactivate_unrar(init=False, mute=False) except: log(traceback.format_exc(1)) error_msg = "Error al extraer" error_msg1 = "Comprueba el log para más detalles" platformtools.dialog_notification(error_msg, error_msg1) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg, status='ERROR') dp.close() return custom_code.reactivate_unrar(init=False, mute=False) extensions_list = ['.aaf', '.3gp', '.asf', '.avi', '.flv', '.mpeg', '.m1v', '.m2v', '.m4v', '.mkv', '.mov', '.mpg', '.mpe', '.mp4', '.ogg', '.wmv'] # Localizamos el path donde se ha dejado la extracción folder = True file_result = filetools.listdir(save_path_videos) while folder: for file_r in file_result: if filetools.isdir(filetools.join(save_path_videos, file_r)): file_result_alt = filetools.listdir(filetools.join(save_path_videos, file_r)) if file_result_alt: file_result = file_result_alt save_path_videos = filetools.join(save_path_videos, file_r) else: folder = False break else: folder = False # Si hay RARs anidados, ajustamos los paths para la siguiente pasada if '.rar' in str(file_result): for file_r in file_result: if '.rar' in file_r: rar_file = file_r file_path = str(filetools.join(save_path_videos, rar_file)) save_path_videos = filetools.join(save_path_videos, 'Extracted') rar_control = update_rar_control(erase_file_path, newextract=(rar_file)) if not filetools.exists(save_path_videos): filetools.mkdir(save_path_videos) platformtools.dialog_notification("Siguiente extracción...", rar_file, time=5000) # Si ya se ha extraido todo, preparamos el retorno else: video_list = [] for file_r in file_result: if os.path.splitext(file_r)[1] in extensions_list: video_list += [file_r] if len(video_list) == 0: error_msg = "El rar está vacío" error_msg1 = "O no contiene archivos válidos" platformtools.dialog_notification(error_msg, error_msg1) rar_control = update_rar_control(erase_file_path, error=True, error_msg=error_msg, status='ERROR') dp.close() return custom_code.reactivate_unrar(init=False, mute=False) else: log("##### Archivo extraído: %s" % video_list[0]) platformtools.dialog_notification("Archivo extraído...", video_list[0], time=10000) log("##### Archivo remove: %s" % file_path) #rar_control = update_rar_control(erase_file_path, status='DONE') ret = filetools.remove(filetools.join(erase_file_path, '_rar_control.json'), silent=True) return str(video_list[0]), True, save_path_videos, erase_file_path def rename_rar_dir(rar_file, save_path_videos, video_path, torr_client): logger.info() rename_status = False folders = rar_file.split("/") if filetools.exists(filetools.join(save_path_videos, folders[0])) and video_path not in folders[0]: if not PY3: src = filetools.join(save_path_videos, folders[0]).decode("utf8") dst = filetools.join(save_path_videos, video_path).decode("utf8") dst_file = video_path.decode("utf8") else: src = filetools.join(save_path_videos, folders[0]) dst = filetools.join(save_path_videos, video_path) dst_file = video_path for x in range(20): if xbmc.abortRequested: return rename_status, rar_file xbmc.sleep(1000) # Se para la actividad para que libere los archivos descargados if torr_client in ['quasar', 'elementum']: torr_data, deamon_url, index = get_tclient_data(folders[0], torr_client) if torr_data and deamon_url: log("##### Client URL: %s" % '%spause/%s' % (deamon_url, index)) data = httptools.downloadpage('%spause/%s' % (deamon_url, index), timeout=5, alfa_s=True).data try: if filetools.exists(src): filetools.rename(src, dst_file, silent=True, strict=True) elif not filetools.exists(dst_file): break except: log("##### Rename ERROR: SRC: %s" % src) log(traceback.format_exc(1)) else: if filetools.exists(dst): log("##### Renamed: SRC: %s" % src) log("##### TO: DST: %s" % dst) rar_file = video_path + '/' + folders[1] rename_status = True update_rar_control(dst, newpath=dst) break return rename_status, rar_file def last_password_search(pass_path, erase_file_path=''): logger.info(pass_path) if not erase_file_path: erase_file_path = pass_path # Busca en el Path de extracción si hay algún archivo que contenga la URL donde pueda estar la CONTRASEÑA password = '' patron_url = '(http.*\:\/\/(?:www.)?\w+\.\w+\/.*?)[\n|\r|$]' patron_pass = ')", "", httptools.downloadpage(url).data) password = scrapertools.find_single_match(data, patron_pass) if password: update_rar_control(erase_file_path, password=password, status='UnRARing: Password update') break except: log(traceback.format_exc(1)) log("##### Contraseña extraída: %s" % password) return password def update_rar_control(path, newpath='', newextract='', password='', error='', error_msg='', status=''): try: rar_control = {} rar_control = jsontools.load(filetools.read(filetools.join(path, '_rar_control.json'))) if rar_control: if newpath: rar_control['download_path'] = newpath for x, entry in enumerate(rar_control['rar_files']): if '__name' in entry: rar_control['rar_files'][x]['__name'] = filetools.basename(newpath) break if newextract: for x, entry in enumerate(rar_control['rar_files']): if '__name' in entry: #rar_control['rar_files'][x]['__name'] = filetools.join(rar_control['rar_files'][x]['__name'], 'Extracted') rar_control['rar_files'][x]['__name'] = rar_control['rar_files'][x]['__name'] + '/Extracted' break rar_control['rar_names'] = [newextract] if password: rar_control['password'] = password if error: rar_control['error'] += 1 if error_msg: rar_control['error_msg'] = error_msg if status and status not in rar_control['status']: rar_control['status'] = status ret = filetools.write(filetools.join(rar_control['download_path'], '_rar_control.json'), \ jsontools.dump(rar_control)) logger.debug('%s, %s, %s, %s, %s, %s' % (rar_control['download_path'], \ rar_control['rar_names'][0], rar_control['password'], \ str(rar_control['error']), rar_control['error_msg'], rar_control['status'])) except: log(traceback.format_exc(1)) return rar_control def import_libtorrent(LIBTORRENT_PATH): logger.info(LIBTORRENT_PATH) e = '' e1 = '' e2 = '' fp = '' pathname = '' description = '' lt = '' try: sys.path.insert(0, LIBTORRENT_PATH) if LIBTORRENT_PATH: try: if not xbmc.getCondVisibility("system.platform.android"): import libtorrent as lt pathname = LIBTORRENT_PATH else: import imp from ctypes import CDLL dll_path = os.path.join(LIBTORRENT_PATH, 'liblibtorrent.so') liblibtorrent = CDLL(dll_path) path_list = [LIBTORRENT_PATH, xbmc.translatePath('special://xbmc')] fp, pathname, description = imp.find_module('libtorrent', path_list) # Esta parte no funciona en Android. Por algún motivo da el error "dlopen failed: library "liblibtorrent.so" not found" # Hay que encontrar un hack para rodear el problema. Lo siguiente ha sido probado sin éxito: #if fp: fp.close() #fp = filetools.file_open(filetools.join(LIBTORRENT_PATH, 'libtorrent.so'), mode='rb') # Usa XbmcVFS #fp = open(os.path.join(LIBTORRENT_PATH, 'libtorrent.so'), 'rb') try: lt = imp.load_module('libtorrent', fp, pathname, description) finally: if fp: fp.close() except Exception as e1: logger.error(traceback.format_exc(1)) log('fp = ' + str(fp)) log('pathname = ' + str(pathname)) log('description = ' + str(description)) if fp: fp.close() from lib.python_libtorrent.python_libtorrent import get_libtorrent lt = get_libtorrent() except Exception as e2: try: logger.error(traceback.format_exc()) if fp: fp.close() e = e1 or e2 ok = platformtools.dialog_ok(config.get_localized_string(30035), config.get_localized_string(30036), config.get_localized_string(60015), str(e2)) except: pass try: if not e1 and e2: e1 = e2 except: try: if e2: e1 = e2 else: e1 = '' e2 = '' except: e1 = '' e2 = '' return lt, e, e1, e2 def log(texto): try: xbmc.log(texto, xbmc.LOGNOTICE) except: pass