diff --git a/core/jsontools.py b/core/jsontools.py index 94d3c5b6..57ea27a8 100644 --- a/core/jsontools.py +++ b/core/jsontools.py @@ -44,6 +44,7 @@ def load(*args, **kwargs): except: logger.error("**NOT** able to load the JSON") logger.error(traceback.format_exc()) + logger.error('ERROR STACK ' + str(stack()[1][3])) value = {} return value @@ -184,7 +185,7 @@ def update_node(dict_node, name_file, node, path=None): # es un dict if dict_data: if node in dict_data: - logger.debug(" the key exists %s" % node) + # logger.debug(" the key exists %s" % node) dict_data[node] = dict_node else: logger.debug(" The key does NOT exist %s" % node) diff --git a/lib/torrentool/__init__.py b/lib/torrentool/__init__.py new file mode 100644 index 00000000..f7f572ba --- /dev/null +++ b/lib/torrentool/__init__.py @@ -0,0 +1 @@ +VERSION = (1, 0, 2) \ No newline at end of file diff --git a/lib/torrentool/api.py b/lib/torrentool/api.py new file mode 100644 index 00000000..baac4b3b --- /dev/null +++ b/lib/torrentool/api.py @@ -0,0 +1,7 @@ +""" +Exposes commonly used classes and functions. + +""" +from .bencode import Bencode +from .torrent import Torrent +from .utils import upload_to_cache_server, get_open_trackers_from_local, get_open_trackers_from_remote diff --git a/lib/torrentool/bencode.py b/lib/torrentool/bencode.py new file mode 100644 index 00000000..6d431d13 --- /dev/null +++ b/lib/torrentool/bencode.py @@ -0,0 +1,204 @@ +from collections import OrderedDict +from operator import itemgetter +from codecs import encode +from sys import version_info + +from .exceptions import BencodeDecodingError, BencodeEncodingError + + +PY3 = version_info >= (3, 0) + +if PY3: + str_type = str + byte_types = (bytes, bytearray) + chr_ = chr + int_types = int +else: + str_type = basestring + byte_types = bytes + chr_ = lambda ch: ch + int_types = (int, long) + + +class Bencode(object): + """Exposes utilities for bencoding.""" + + @classmethod + def encode(cls, value): + """Encodes a value into bencoded bytes. + + :param value: Python object to be encoded (str, int, list, dict). + :param str val_encoding: Encoding used by strings in a given object. + :rtype: bytes + """ + val_encoding = 'utf-8' + + def encode_str(v): + try: + v_enc = encode(v, val_encoding) + + except UnicodeDecodeError: + if PY3: + raise + else: + # Suppose bytestring + v_enc = v + + prefix = encode('%s:' % len(v_enc), val_encoding) + return prefix + v_enc + + def encode_(val): + if isinstance(val, str_type): + result = encode_str(val) + + elif isinstance(val, int_types): + result = encode(('i%se' % val), val_encoding) + + elif isinstance(val, (list, set, tuple)): + result = encode('l', val_encoding) + for item in val: + result += encode_(item) + result += encode('e', val_encoding) + + elif isinstance(val, dict): + result = encode('d', val_encoding) + + # Dictionaries are expected to be sorted by key. + for k, v in OrderedDict(sorted(val.items(), key=itemgetter(0))).items(): + result += (encode_str(k) + encode_(v)) + + result += encode('e', val_encoding) + + elif isinstance(val, byte_types): + result = encode('%s:' % len(val), val_encoding) + result += val + + else: + raise BencodeEncodingError('Unable to encode `%s` %s' % (type(val), val)) + + return result + + return encode_(value) + + @classmethod + def decode(cls, encoded): + """Decodes bencoded data introduced as bytes. + + Returns decoded structure(s). + + :param bytes encoded: + """ + def create_dict(items): + # Let's guarantee that dictionaries are sorted. + k_v_pair = zip(*[iter(items)] * 2) + return OrderedDict(sorted(k_v_pair, key=itemgetter(0))) + + def create_list(items): + return list(items) + + stack_items = [] + stack_containers = [] + + def compress_stack(): + target_container = stack_containers.pop() + subitems = [] + + while True: + subitem = stack_items.pop() + subitems.append(subitem) + if subitem is target_container: + break + + container_creator = subitems.pop() + container = container_creator(reversed(subitems)) + stack_items.append(container) + + def parse_forward(till_char, sequence): + number = '' + char_sub_idx = 0 + + for char_sub_idx, char_sub in enumerate(sequence): + char_sub = chr_(char_sub) + if char_sub == till_char: + break + + number += char_sub + + number = int(number or 0) + char_sub_idx += 1 + + return number, char_sub_idx + + while encoded: + char = encoded[0] + char = chr_(char) + + if char == 'd': # Dictionary + stack_items.append(create_dict) + stack_containers.append(create_dict) + encoded = encoded[1:] + + elif char == 'l': # List + stack_items.append(create_list) + stack_containers.append(create_list) + encoded = encoded[1:] + + elif char == 'i': # Integer + number, char_sub_idx = parse_forward('e', encoded[1:]) + char_sub_idx += 1 + + stack_items.append(number) + encoded = encoded[char_sub_idx:] + + elif char.isdigit(): # String + str_len, char_sub_idx = parse_forward(':', encoded) + last_char_idx = char_sub_idx + str_len + + string = encoded[char_sub_idx:last_char_idx] + try: + string = string.decode('utf-8') + except UnicodeDecodeError: + # Considered bytestring (e.g. `pieces` hashes concatenation). + pass + + stack_items.append(string) + encoded = encoded[last_char_idx:] + + elif char == 'e': # End of a dictionary or a list. + compress_stack() + encoded = encoded[1:] + + else: + raise BencodeDecodingError('Unable to interpret `%s` char.' % char) + + if len(stack_items) == 1: + stack_items = stack_items.pop() + + return stack_items + + @classmethod + def read_string(cls, string): + """Decodes a given bencoded string or bytestring. + + Returns decoded structure(s). + + :param str string: + :rtype: list + """ + if PY3 and not isinstance(string, byte_types): + string = string.encode() + + return cls.decode(string) + + @classmethod + def read_file(cls, filepath): + """Decodes bencoded data of a given file. + + Returns decoded structure(s). + + :param str filepath: + :rtype: list + """ + with open(filepath, mode='rb') as f: + contents = f.read() + return cls.decode(contents) diff --git a/lib/torrentool/cli.py b/lib/torrentool/cli.py new file mode 100644 index 00000000..d07a8ee3 --- /dev/null +++ b/lib/torrentool/cli.py @@ -0,0 +1,94 @@ +from __future__ import division +import click +from os import path, getcwd + +from . import VERSION +from .api import Torrent +from .utils import humanize_filesize, upload_to_cache_server, get_open_trackers_from_remote, \ + get_open_trackers_from_local +from .exceptions import RemoteUploadError, RemoteDownloadError + + +@click.group() +@click.version_option(version='.'.join(map(str, VERSION))) +def start(): + """Torrentool command line utilities.""" + + +@start.group() +def torrent(): + """Torrent-related commands.""" + + +@torrent.command() +@click.argument('torrent_path', type=click.Path(exists=True, writable=False, dir_okay=False)) +def info(torrent_path): + """Print out information from .torrent file.""" + + my_torrent = Torrent.from_file(torrent_path) + + size = my_torrent.total_size + + click.secho('Name: %s' % my_torrent.name, fg='blue') + click.secho('Files:') + for file_tuple in my_torrent.files: + click.secho(file_tuple.name) + + click.secho('Hash: %s' % my_torrent.info_hash, fg='blue') + click.secho('Size: %s (%s)' % (humanize_filesize(size), size), fg='blue') + click.secho('Magnet: %s' % my_torrent.get_magnet(), fg='yellow') + + +@torrent.command() +@click.argument('source', type=click.Path(exists=True, writable=False)) +@click.option('--dest', default=getcwd, type=click.Path(file_okay=False), help='Destination path to put .torrent file into. Default: current directory.') +@click.option('--tracker', default=None, help='Tracker announce URL (multiple comma-separated values supported).') +@click.option('--open_trackers', default=False, is_flag=True, help='Add open trackers announce URLs.') +@click.option('--comment', default=None, help='Arbitrary comment.') +@click.option('--cache', default=False, is_flag=True, help='Upload file to torrent cache services.') +def create(source, dest, tracker, open_trackers, comment, cache): + """Create torrent file from a single file or a directory.""" + + source_title = path.basename(source).replace('.', '_').replace(' ', '_') + dest = '%s.torrent' % path.join(dest, source_title) + + click.secho('Creating torrent from %s ...' % source) + + my_torrent = Torrent.create_from(source) + + if comment: + my_torrent.comment = comment + + urls = [] + + if tracker: + urls = tracker.split(',') + + if open_trackers: + click.secho('Fetching an up-to-date open tracker list ...') + try: + urls.extend(get_open_trackers_from_remote()) + except RemoteDownloadError: + click.secho('Failed. Using built-in open tracker list.', fg='red', err=True) + urls.extend(get_open_trackers_from_local()) + + if urls: + my_torrent.announce_urls = urls + + my_torrent.to_file(dest) + + click.secho('Torrent file created: %s' % dest, fg='green') + click.secho('Torrent info hash: %s' % my_torrent.info_hash, fg='blue') + + if cache: + click.secho('Uploading to %s torrent cache service ...') + try: + result = upload_to_cache_server(dest) + click.secho('Cached torrent URL: %s' % result, fg='yellow') + + except RemoteUploadError as e: + click.secho('Failed: %s' % e, fg='red', err=True) + + +def main(): + start(obj={}) diff --git a/lib/torrentool/exceptions.py b/lib/torrentool/exceptions.py new file mode 100644 index 00000000..53118482 --- /dev/null +++ b/lib/torrentool/exceptions.py @@ -0,0 +1,27 @@ + +class TorrentoolException(Exception): + """Base torrentool exception. All others are inherited from it.""" + + +class BencodeError(TorrentoolException): + """Base exception for bencode related errors.""" + + +class BencodeDecodingError(BencodeError): + """Raised when torrentool is unable to decode bencoded data.""" + + +class BencodeEncodingError(BencodeError): + """Raised when torrentool is unable to encode data into bencode.""" + + +class TorrentError(TorrentoolException): + """Base exception for Torrent object related errors.""" + + +class RemoteUploadError(TorrentoolException): + """Base class for upload to remotes related issues.""" + + +class RemoteDownloadError(TorrentoolException): + """Base class for issues related to downloads from remotes.""" diff --git a/lib/torrentool/repo/open_trackers.ini b/lib/torrentool/repo/open_trackers.ini new file mode 100644 index 00000000..c0ca0aa0 --- /dev/null +++ b/lib/torrentool/repo/open_trackers.ini @@ -0,0 +1,8 @@ +udp://tracker.coppersurfer.tk:6969/announce +udp://tracker.internetwarriors.net:1337/announce +udp://tracker.leechers-paradise.org:6969/announce +udp://tracker.opentrackr.org:1337/announce +udp://tracker.openbittorrent.com:80/announce +udp://tracker.sktorrent.net:6969/announce +udp://tracker.zer0day.to:1337/announce +udp://exodus.desync.com:6969/announce diff --git a/lib/torrentool/torrent.py b/lib/torrentool/torrent.py new file mode 100644 index 00000000..8472c8cb --- /dev/null +++ b/lib/torrentool/torrent.py @@ -0,0 +1,436 @@ +from calendar import timegm +from collections import namedtuple +from datetime import datetime +from functools import reduce +from hashlib import sha1 +from os import walk, sep +from os.path import join, isdir, getsize, normpath, basename + +try: + from urllib.parse import urlencode +except ImportError: # Py2 + from urllib import urlencode + +from .bencode import Bencode +from .exceptions import TorrentError +from .utils import get_app_version + + +_ITERABLE_TYPES = (list, tuple, set) + + +TorrentFile = namedtuple('TorrentFile', ['name', 'length']) + + +class Torrent(object): + """Represents a torrent file, and exposes utilities to work with it.""" + + _filepath = None + + def __init__(self, dict_struct=None): + dict_struct = dict_struct or {'info': {}} + self._struct = dict_struct + + def __str__(self): + return 'Torrent: %s' % self.name + + announce_urls = property() + """List of lists of tracker announce URLs.""" + + comment = property() + """Optional. Free-form textual comments of the author.""" + + creation_date = property() + """Optional. The creation time of the torrent, in standard UNIX epoch format. UTC.""" + + created_by = property() + """Optional. Name and version of the program used to create the .torrent""" + + private = property() + """Optional. If True the client MUST publish its presence to get other peers + ONLY via the trackers explicitly described in the metainfo file. If False or is not present, + the client may obtain peer from other means, e.g. PEX peer exchange, dht. + + """ + + name = property() + """Torrent name (title).""" + + webseeds = property() + """A list of URLs where torrent data can be retrieved. + + See also: Torrent.httpseeds + + http://bittorrent.org/beps/bep_0019.html + """ + + httpseeds = property() + """A list of URLs where torrent data can be retrieved. + + See also and prefer Torrent.webseeds + + http://bittorrent.org/beps/bep_0017.html + """ + + def _list_getter(self, key): + return self._struct.get(key, []) + + def _list_setter(self, key, val): + if val is None: + try: + del self._struct[key] + return + except KeyError: + return + + if not isinstance(val, _ITERABLE_TYPES): + val = [val] + + self._struct[key] = val + + @webseeds.getter + def webseeds(self): + return self._list_getter('url-list') + + @webseeds.setter + def webseeds(self, val): + self._list_setter('url-list', val) + + @httpseeds.getter + def httpseeds(self): + return self._list_getter('httpseeds') + + @httpseeds.setter + def httpseeds(self, val): + self._list_setter('httpseeds', val) + + @property + def files(self): + """Files in torrent. + + List of namedtuples (filepath, size). + + :rtype: list[TorrentFile] + """ + files = [] + info = self._struct.get('info') + + if not info: + return files + + if 'files' in info: + base = info['name'] + + for f in info['files']: + files.append(TorrentFile(join(base, *f['path']), f['length'])) + + else: + files.append(TorrentFile(info['name'], info['length'])) + + return files + + @property + def total_size(self): + """Total size of all files in torrent.""" + return reduce(lambda prev, curr: prev + curr[1], self.files, 0) + + @property + def info_hash(self): + """Hash of torrent file info section. Also known as torrent hash.""" + info = self._struct.get('info') + + if not info: + return None + + return sha1(Bencode.encode(info)).hexdigest() + + @property + def magnet_link(self): + """Magnet link using BTIH (BitTorrent Info Hash) URN.""" + return self.get_magnet(detailed=False) + + @announce_urls.getter + def announce_urls(self): + """List of lists of announce (tracker) URLs. + + First inner list is considered as primary announcers list, + the following lists as back-ups. + + http://bittorrent.org/beps/bep_0012.html + + """ + urls = self._struct.get('announce-list') + + if not urls: + urls = self._struct.get('announce') + if not urls: + return [] + urls = [[urls]] + + return urls + + @announce_urls.setter + def announce_urls(self, val): + self._struct['announce'] = '' + self._struct['announce-list'] = [] + + def set_single(val): + del self._struct['announce-list'] + self._struct['announce'] = val + + if isinstance(val, _ITERABLE_TYPES): + length = len(val) + + if length: + if length == 1: + set_single(val[0]) + else: + for item in val: + if not isinstance(item, _ITERABLE_TYPES): + item = [item] + self._struct['announce-list'].append(item) + self._struct['announce'] = val[0] + + else: + set_single(val) + + @comment.getter + def comment(self): + return self._struct.get('comment') + + @comment.setter + def comment(self, val): + self._struct['comment'] = val + + @creation_date.getter + def creation_date(self): + date = self._struct.get('creation date') + if date is not None: + date = datetime.utcfromtimestamp(int(date)) + return date + + @creation_date.setter + def creation_date(self, val): + self._struct['creation date'] = timegm(val.timetuple()) + + @created_by.getter + def created_by(self): + return self._struct.get('created by') + + @created_by.setter + def created_by(self, val): + self._struct['created by'] = val + + @private.getter + def private(self): + return self._struct.get('info', {}).get('private', False) + + @private.setter + def private(self, val): + if not val: + try: + del self._struct['info']['private'] + except KeyError: + pass + else: + self._struct['info']['private'] = 1 + + @name.getter + def name(self): + return self._struct.get('info', {}).get('name', None) + + @name.setter + def name(self, val): + self._struct['info']['name'] = val + + def get_magnet(self, detailed=True): + """Returns torrent magnet link, consisting of BTIH (BitTorrent Info Hash) URN + anr optional other information. + + :param bool|list|tuple|set detailed: + For boolean - whether additional info (such as trackers) should be included. + For iterable - expected allowed parameter names: + tr - trackers + ws - webseeds + + """ + result = 'magnet:?xt=urn:btih:' + self.info_hash + + def add_tr(): + urls = self.announce_urls + if not urls: + return + + trackers = [] + + urls = urls[0] # Only primary announcers are enough. + for url in urls: + trackers.append(('tr', url)) + + if trackers: + return urlencode(trackers) + + def add_ws(): + webseeds = [('ws', url) for url in self.webseeds] + if webseeds: + return urlencode(webseeds) + + params_map = { + 'tr': add_tr, + 'ws': add_ws, + } + + if detailed: + details = [] + + if isinstance(detailed, _ITERABLE_TYPES): + requested_params = detailed + else: + requested_params = params_map.keys() + + for param in requested_params: + param_val = params_map[param]() + param_val and details.append(param_val) + + if details: + result += '&%s' % '&'.join(details) + + return result + + def to_file(self, filepath=None): + """Writes Torrent object into file, either + + :param filepath: + """ + if filepath is None and self._filepath is None: + raise TorrentError('Unable to save torrent to file: no filepath supplied.') + + if filepath is not None: + self._filepath = filepath + + with open(self._filepath, mode='wb') as f: + f.write(self.to_string()) + + def to_string(self): + """Returns bytes representing torrent file. + + :param str encoding: Encoding used by strings in Torrent object. + :rtype: bytearray + """ + return Bencode.encode(self._struct) + + @classmethod + def _get_target_files_info(cls, src_path): + src_path = u'%s' % src_path # Force walk() to return unicode names. + + is_dir = isdir(src_path) + target_files = [] + + if is_dir: + for base, _, files in walk(src_path): + target_files.extend([join(base, fname) for fname in sorted(files)]) + + else: + target_files.append(src_path) + + target_files_ = [] + total_size = 0 + for fpath in target_files: + file_size = getsize(fpath) + if not file_size: + continue + target_files_.append((fpath, file_size, normpath(fpath.replace(src_path, '')).strip(sep).split(sep))) + total_size += file_size + + return target_files_, total_size + + @classmethod + def create_from(cls, src_path): + """Returns Torrent object created from a file or a directory. + + :param str src_path: + :rtype: Torrent + """ + is_dir = isdir(src_path) + target_files, size_data = cls._get_target_files_info(src_path) + + SIZE_MIN = 32768 # 32 KiB + SIZE_DEFAULT = 262144 # 256 KiB + SIZE_MAX = 1048576 # 1 MiB + + CHUNKS_MIN = 1000 # todo use those limits as advised + CHUNKS_MAX = 2200 + + size_piece = SIZE_MIN + if size_data > SIZE_MIN: + size_piece = SIZE_DEFAULT + + if size_piece > SIZE_MAX: + size_piece = SIZE_MAX + + def read(filepath): + with open(filepath, 'rb') as f: + while True: + chunk = f.read(size_piece - len(pieces_buffer)) + chunk_size = len(chunk) + if chunk_size == 0: + break + yield chunk + + pieces = bytearray() + pieces_buffer = bytearray() + + for fpath, _, _ in target_files: + for chunk in read(fpath): + pieces_buffer += chunk + + if len(pieces_buffer) == size_piece: + pieces += sha1(pieces_buffer).digest()[:20] + pieces_buffer = bytearray() + + if len(pieces_buffer): + pieces += sha1(pieces_buffer).digest()[:20] + pieces_buffer = bytearray() + + info = { + 'name': basename(src_path), + 'pieces': bytes(pieces), + 'piece length': size_piece, + } + + if is_dir: + files = [] + + for _, length, path in target_files: + files.append({'length': length, 'path': path}) + + info['files'] = files + + else: + info['length'] = target_files[0][1] + + torrent = cls({'info': info}) + torrent.created_by = get_app_version() + torrent.creation_date = datetime.utcnow() + + return torrent + + @classmethod + def from_string(cls, string): + """Alternative constructor to get Torrent object from string. + + :param str string: + :rtype: Torrent + """ + return cls(Bencode.read_string(string)) + + @classmethod + def from_file(cls, filepath): + """Alternative constructor to get Torrent object from file. + + :param str filepath: + :rtype: Torrent + """ + torrent = cls(Bencode.read_file(filepath)) + torrent._filepath = filepath + return torrent diff --git a/lib/torrentool/utils.py b/lib/torrentool/utils.py new file mode 100644 index 00000000..7d346cd7 --- /dev/null +++ b/lib/torrentool/utils.py @@ -0,0 +1,91 @@ +import math +from os import path + +from .exceptions import RemoteUploadError, RemoteDownloadError + + +OPEN_TRACKERS_FILENAME = 'open_trackers.ini' +REMOTE_TIMEOUT = 4 + + +def get_app_version(): + """Returns full version string including application name + suitable for putting into Torrent.created_by. + + """ + from torrentool import VERSION + return 'torrentool/%s' % '.'.join(map(str, VERSION)) + + +def humanize_filesize(bytes_size): + """Returns human readable filesize. + + :param int bytes_size: + :rtype: str + """ + if not bytes_size: + return '0 B' + + names = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') + + name_idx = int(math.floor(math.log(bytes_size, 1024))) + size = round(bytes_size / math.pow(1024, name_idx), 2) + + return '%s %s' % (size, names[name_idx]) + + +def upload_to_cache_server(fpath): + """Uploads .torrent file to a cache server. + + Returns upload file URL. + + :rtype: str + """ + url_base = 'http://torrage.info' + url_upload = '%s/autoupload.php' % url_base + url_download = '%s/torrent.php?h=' % url_base + file_field = 'torrent' + + try: + import requests + + response = requests.post(url_upload, files={file_field: open(fpath, 'rb')}, timeout=REMOTE_TIMEOUT) + response.raise_for_status() + + info_cache = response.text + return url_download + info_cache + + except (ImportError, requests.RequestException) as e: + + # Now trace is lost. `raise from` to consider. + raise RemoteUploadError('Unable to upload to %s: %s' % (url_upload, e)) + + +def get_open_trackers_from_remote(): + """Returns open trackers announce URLs list from remote repo.""" + + url_base = 'https://raw.githubusercontent.com/idlesign/torrentool/master/torrentool/repo' + url = '%s/%s' % (url_base, OPEN_TRACKERS_FILENAME) + + try: + import requests + + response = requests.get(url, timeout=REMOTE_TIMEOUT) + response.raise_for_status() + + open_trackers = response.text.splitlines() + + except (ImportError, requests.RequestException) as e: + + # Now trace is lost. `raise from` to consider. + raise RemoteDownloadError('Unable to download from %s: %s' % (url, e)) + + return open_trackers + + +def get_open_trackers_from_local(): + """Returns open trackers announce URLs list from local backup.""" + with open(path.join(path.dirname(__file__), 'repo', OPEN_TRACKERS_FILENAME)) as f: + open_trackers = map(str.strip, f.readlines()) + + return list(open_trackers) diff --git a/platformcode/platformtools.py b/platformcode/platformtools.py index b9ce7472..265d2473 100644 --- a/platformcode/platformtools.py +++ b/platformcode/platformtools.py @@ -11,32 +11,22 @@ from __future__ import division from __future__ import absolute_import from past.utils import old_div -#from builtins import str import sys PY3 = False 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 else: - import urllib # Usamos el nativo de PY2 que es más rápido + import urllib -import os - -import xbmc -import xbmcgui -import xbmcplugin -import xbmcaddon +import os, xbmc, xbmcgui, xbmcplugin, xbmcaddon from channelselector import get_thumb from core import channeltools from core import trakt_tools, scrapertools from core.item import Item -from platformcode import logger -from platformcode import config -from platformcode import unify +from platformcode import logger, config, unify addon = xbmcaddon.Addon('plugin.video.kod') addon_icon = os.path.join( addon.getAddonInfo( "path" ), "logo.png" ) @@ -808,7 +798,7 @@ def set_context_commands(item, item_url, parent_item, **kwargs): elif item.contentSerieName: # Descargar serie - if item.contentType == "tvshow": + if item.contentType == "tvshow" and item.action not in ['findvideos']: if item.channel == 'videolibrary': context_commands.append((config.get_localized_string(60003), "XBMC.RunPlugin(%s?%s&%s)" % (sys.argv[0], item_url, @@ -818,7 +808,7 @@ def set_context_commands(item, item_url, parent_item, **kwargs): context_commands.append((config.get_localized_string(60357), "XBMC.RunPlugin(%s?%s&%s)" % (sys.argv[0], item_url, 'channel=downloads&action=save_download&download=season&from_channel=' + item.channel + '&from_action=' + item.action))) # Descargar episodio - elif item.contentType == "episode": + elif item.contentType == "episode" or item.action in ['findvideos']: context_commands.append((config.get_localized_string(60356), "XBMC.RunPlugin(%s?%s&%s)" % (sys.argv[0], item_url, 'channel=downloads&action=save_download&from_channel=' + item.channel + '&from_action=' + item.action))) # Descargar temporada @@ -1353,140 +1343,23 @@ def torrent_client_installed(show_tuple=False): def play_torrent(item, xlistitem, mediaurl): logger.info() import time - import traceback - - from core import filetools - from core import httptools - from lib import generictools from servers import torrent - - # Si Libtorrent ha dado error de inicialización, no se pueden usar los clientes internos - UNRAR = config.get_setting("unrar_path", server="torrent", default="") - LIBTORRENT = config.get_setting("libtorrent_path", server="torrent", default='') - size_rar = 2 - rar_files = [] - if item.password: - size_rar = 3 - - - # Opciones disponibles para Reproducir torrents torrent_options = torrent_client_installed(show_tuple=True) - torrent_client = config.get_setting("torrent_client", server="torrent") - - # Si es Libtorrent y no está soportado, se ofrecen alternativas, si las hay... if len(torrent_options) > 1: selection = dialog_select(config.get_localized_string(70193), [opcion[0] for opcion in torrent_options]) else: selection = 0 - # Si es Torrenter o Elementum con opción de Memoria, se ofrece la posibilidad ee usar Libtorrent temporalemente - if selection >= 0 and LIBTORRENT and UNRAR and 'RAR-' in item.torrent_info and ( - "torrenter" in torrent_options[selection][0] \ - or ("elementum" in torrent_options[selection][0] and xbmcaddon.Addon(id="plugin.video.%s" % torrent_options[selection][0].replace('Plugin externo: ','')).getSetting('download_storage') == '1')): - if dialog_yesno(torrent_options[selection][0], config.get_localized_string(70777), config.get_localized_string(70778), config.get_localized_string(70779) % size_rar): - selection = 1 - else: - return - # Si es Elementum pero con opción de Memoria, se muestras los Ajustes de Elementum y se pide al usuario que cambie a "Usar Archivos" - elif selection >= 0 and not LIBTORRENT and UNRAR and 'RAR-' in item.torrent_info and "elementum" in \ - torrent_options[selection][0] \ - and xbmcaddon.Addon(id="plugin.video.%s" % torrent_options[selection][0].capitalize()) \ - .getSetting('download_storage') == '1': - if dialog_yesno(torrent_options[selection][0], config.get_localized_string(70780) % size_rar, config.get_localized_string(70781)): - __settings__ = xbmcaddon.Addon( - id="plugin.video.%s" % torrent_options[selection][0].capitalize()) - __settings__.openSettings() # Se visulizan los Ajustes de Elementum - elementum_dl = xbmcaddon.Addon( - id="plugin.video.%s" % torrent_options[selection][0].capitalize()) \ - .getSetting('download_storage') - if elementum_dl != '1': - config.set_setting("elementum_dl", "1", server="torrent") # Salvamos el cambio para restaurarlo luego - return # Se sale, porque habrá refresco y cancelaría Kodi si no - - # Descarga de torrents a local if selection >= 0: - #### Compatibilidad con Kodi 18: evita cuelgues/cancelaciones cuando el .torrent se lanza desde pantalla convencional - # if xbmc.getCondVisibility('Window.IsMedia'): - xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xlistitem) # Preparamos el entorno para evitar error Kod1 18 - time.sleep(0.5) # Dejamos tiempo para que se ejecute - - # Nuevo método de descarga previa del .torrent. Si da error, miramos si hay alternatica local. - # Si ya es local, lo usamos - url = '' - url_stat = False - torrents_path = '' - referer = None - post = None - rar = False - size = '' - password = '' - if item.password: - password = item.password - - videolibrary_path = config.get_videolibrary_path() # Calculamos el path absoluto a partir de la Videoteca - if scrapertools.find_single_match(videolibrary_path, '(^\w+:\/\/)'): # Si es una conexión REMOTA, usamos userdata local - videolibrary_path = config.get_data_path() # Calculamos el path absoluto a partir de Userdata - if not filetools.exists(videolibrary_path): # Si no existe el path, pasamos al modo clásico - videolibrary_path = False - else: - torrents_path = filetools.join(videolibrary_path, 'temp_torrents', 'client_torrent.torrent') # path descarga temporal - if not videolibrary_path or not filetools.exists(filetools.join(videolibrary_path, 'temp_torrents')): # Si no existe la carpeta temporal, la creamos - filetools.mkdir(filetools.join(videolibrary_path, 'temp_torrents')) - - # Si hay headers, se pasar a la petición de descarga del .torrent - headers = {} - if item.headers: - headers = item.headers - - # identificamos si es una url o un path de archivo - if not item.url.startswith("\\") and not item.url.startswith("/") and not url_stat: - timeout = 10 - if item.torrent_alt: - timeout = 5 - # Si es una llamada con POST, lo preparamos - if item.referer: referer = item.referer - if item.post: post = item.post - # Descargamos el .torrent - size, url, torrent_f, rar_files = generictools.get_torrent_size(item.url, referer, post, torrents_path=torrents_path, timeout=timeout, lookup=False, headers=headers, short_pad=True) - if url: - url_stat = True - item.url = url - if "torrentin" in torrent_options[selection][0]: - item.url = 'file://' + item.url - - if not url and item.torrent_alt: # Si hay error, se busca un .torrent alternativo - if (item.torrent_alt.startswith("\\") or item.torrent_alt.startswith("/")) and videolibrary_path: - item.url = item.torrent_alt # El .torrent alternativo puede estar en una url o en local - elif not item.url.startswith("\\") and not item.url.startswith("/"): - item.url = item.torrent_alt - - # Si es un archivo .torrent local, actualizamos el path relativo a path absoluto - if (item.url.startswith("\\") or item.url.startswith("/")) and not url_stat and videolibrary_path: # .torrent alternativo local - movies = config.get_setting("folder_movies") - series = config.get_setting("folder_tvshows") - if item.contentType == 'movie': - folder = movies # películas - else: - folder = series # o series - item.url = filetools.join(config.get_videolibrary_path(), folder, item.url) # dirección del .torrent local en la Videoteca - if filetools.copy(item.url, torrents_path, silent=True): # se copia a la carpeta generíca para evitar problemas de encode - item.url = torrents_path - if "torrentin" in torrent_options[selection][0]: # Si es Torrentin, hay que añadir un prefijo - item.url = 'file://' + item.url - size, rar_files = generictools.get_torrent_size('', file_list=True, local_torr=torrents_path,short_pad=True) - - mediaurl = item.url - - if selection >= 0: + xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xlistitem) + time.sleep(1) mediaurl = urllib.quote_plus(item.url) torr_client = torrent_options[selection][0] - torr_setting = xbmcaddon.Addon(id="plugin.video.%s" %torr_client) - # Llamada con más parámetros para completar el título if torr_client in ['quasar', 'elementum'] and item.infoLabels['tmdb_id']: if item.contentType == 'episode' and "elementum" not in torr_client: mediaurl += "&episode=%s&library=&season=%s&show=%s&tmdb=%s&type=episode" % (item.infoLabels['episode'], item.infoLabels['season'], item.infoLabels['tmdb_id'], item.infoLabels['tmdb_id']) @@ -1494,57 +1367,12 @@ def play_torrent(item, xlistitem, mediaurl): mediaurl += "&library=&tmdb=%s&type=movie" % (item.infoLabels['tmdb_id']) if torr_client in ['quasar', 'elementum'] and item.downloadFilename: - torrent.elementum_monitor(item, torr_client) + torrent.elementum_download(item) else: xbmc.executebuiltin("PlayMedia(" + torrent_options[selection][1] % mediaurl + ")") - # Si es un archivo RAR, monitorizamos el cliente Torrent hasta que haya descargado el archivo, - # y después lo extraemos, incluso con RAR's anidados y con contraseña - if 'RAR-' in size and torr_client in ['quasar', 'elementum'] and UNRAR: - rar_file, save_path_videos, folder_torr = torrent.wait_for_download(item, mediaurl, rar_files, torr_client) # Esperamos mientras se descarga el RAR - if rar_file and save_path_videos: # Si se ha descargado el RAR... - dp = dialog_progress_bg('KoD %s' % torr_client) - video_file, rar, video_path, erase_file_path = torrent.extract_files(rar_file, save_path_videos, password, dp, item, torr_client) # ... extraemos el vídeo del RAR - dp.close() - - # Reproducimos el vídeo extraido, si no hay nada en reproducción - while is_playing() and rar and not xbmc.abortRequested: - time.sleep(3) # Repetimos cada intervalo - if rar and not xbmc.abortRequested: - time.sleep(1) - video_play = filetools.join(video_path, video_file) - log("##### video_play: %s" % video_play) - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - playlist.clear() - playlist.add(video_play, xlistitem) - xbmc_player.play(playlist) - - # selectionamos que clientes torrent soportamos para el marcado de vídeos vistos: asumimos que todos funcionan torrent.mark_auto_as_watched(item) - # Si se ha extraido un RAR, se pregunta para borrar los archivos después de reproducir el vídeo (plugins externos) - while is_playing() and rar and not xbmc.abortRequested: - time.sleep(3) # Repetimos cada intervalo - if rar and not xbmc.abortRequested: - if dialog_yesno('KoD %s' % torr_client, config.get_localized_string(30031)): - log("##### erase_file_path: %s" % erase_file_path) - try: - torr_data, deamon_url, index = torrent.get_tclient_data(folder_torr, torr_client) - if torr_data and deamon_url: - data = httptools.downloadpage('%sdelete/%s' % (deamon_url, index), timeout=5, alfa_s=True).data - time.sleep(1) - if filetools.isdir(erase_file_path): - filetools.rmdirtree(erase_file_path) - elif filetools.exists(erase_file_path) and filetools.isfile(erase_file_path): - filetools.remove(erase_file_path) - except: - logger.error(traceback.format_exc(1)) - elementum_dl = config.get_setting("elementum_dl", server="torrent", default='') # Si salvamos el cambio de Elementum - if elementum_dl: - config.set_setting("elementum_dl", "", server="torrent") # lo reseteamos en Alfa - xbmcaddon.Addon(id="plugin.video.%s" % torrent_options[selection][0].replace('Plugin externo: ', '')) \ - .setSetting('download_storage', elementum_dl) # y lo reseteamos en Elementum - def log(texto): xbmc.log(texto, xbmc.LOGNOTICE) \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index df334b5a..e41734f2 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -106,9 +106,7 @@ - - diff --git a/servers/torrent.py b/servers/torrent.py index 8c26a9fd..37fb07f2 100755 --- a/servers/torrent.py +++ b/servers/torrent.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import sys +import os, sys # from builtins import str from builtins import range @@ -10,67 +10,36 @@ 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 + import urllib.parse as urllib else: import urllib -import time -import os -import traceback -import re +import time, requests, xbmc, xbmcgui, xbmcaddon +from core import filetools, jsontools +from core.support import dbg, log, match +# from core import httptools +# from core import scrapertools +from platformcode import config, platformtools +from threading import Thread, currentThread +from torrentool.api import Torrent -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 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", - ] +elementum_setting = xbmcaddon.Addon(id='plugin.video.elementum') +elementum_host = 'http://127.0.0.1:' + elementum_setting.getSetting('remote_port') + '/torrents/' +extensions_list = ['aaf', '3gp', 'asf', 'avi', 'flv', 'mpeg', 'm1v', 'm2v', 'm4v', 'mkv', 'mov', 'mpg', 'mpe', 'mp4', 'ogg', 'wmv'] # Returns an array of possible video url's from the page_url -def get_video_url(page_url, premium=False, user="", password="", video_password=""): +def get_video_url(page_url, premium=False, user='', password='', video_password=''): torrent_options = platformtools.torrent_client_installed(show_tuple=True) if len(torrent_options) == 0: from specials import elementum_download elementum_download.download() - logger.info("server=torrent, the url is the good") + log('server=torrent, the url is the good') - if page_url.startswith("magnet:"): - video_urls = [["magnet: [torrent]", page_url]] + if page_url.startswith('magnet:'): + video_urls = [['magnet: [torrent]', page_url]] else: - video_urls = [[".torrent [torrent]", page_url]] + video_urls = [['.torrent [torrent]', page_url]] return video_urls @@ -82,1366 +51,160 @@ class XBMCPlayer(xbmc.Player): 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("it's a zip archive: " + 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, config.get_localized_string(70791)): - 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(config.get_localized_string(70790), config.get_localized_string(70769), time=10000) - else: - progreso = platformtools.dialog_progress('%s Torrent Client' % 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 not 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, config.get_localized_string(70792)): - 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)) + time_limit = time.time() + 150 + while not platformtools.is_playing() and time.time() < time_limit: + time.sleep(5) 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 + if item.strm_path and platformtools.is_playing(): 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(config.get_localized_string(70803), "", 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(config.get_localized_string(60200), config.get_localized_string(70769), time=10000) - fast = True - if not torr_data['label'].startswith('100.00%'): - log("##### Downloading: %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 FINALIZED: %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 downloading: %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(config.get_localized_string(60200), config.get_localized_string(70769), 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 downloading: %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 downloading: %s" % str(returncode)) - else: - log("##### Torrent downloading: %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 FINALIZED: %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(config.get_localized_string(70793), 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 in rar archive: %s" % rar_file) - log("##### ERROR in rar folder: %s" % file_path) - log(traceback.format_exc()) - error_msg = config.get_localized_string(70796) - error_msg1 = config.get_localized_string(60015) - 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=config.get_localized_string(70794) % pass_path) - if not password: - error_msg = config.get_localized_string(60309) - 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(config.get_localized_string(70801)) - else: - error_msg = config.get_localized_string(70797) - error_msg1 = config.get_localized_string(70798) - 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 = config.get_localized_string(70797) - 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, config.get_localized_string(70803), config.get_localized_string(70802)) - 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, config.get_localized_string(70802), config.get_localized_string(70803) + " %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 = config.get_localized_string(70799) - error_msg1 = config.get_localized_string(60309) - 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 = config.get_localized_string(70799) - error_msg1 = config.get_localized_string(60800) - 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 = config.get_localized_string(70799) - error_msg1 = config.get_localized_string(60015) - 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(config.get_localized_string(70804), 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 = config.get_localized_string(70797) - error_msg1 = config.get_localized_string(70798) - 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("##### Archive extracted: %s" % video_list[0]) - platformtools.dialog_notification(config.get_localized_string(70795), video_list[0], time=10000) - log("##### Archive removes: %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("##### Password Extracted: %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 + xbmc_videolibrary.mark_auto_as_watched(item) ########################## ELEMENTUM MONITOR TEST ########################## -def elementum_monitor(item, torr_client): - host = 'http://127.0.0.1:65220/torrents/' - torr_setting = xbmcaddon.Addon(id="plugin.video.elementum") - # from core.support import dbg;dbg() - data = httptools.downloadpage(host, alfa_s=True).data - items = jsontools.load(data)['items'] - if not items: - set_elementum() - call_torrent_via_web(urllib.quote_plus(item.url), torr_client) +elementumHost = 'http://127.0.0.1:65220/torrents/' - result = 0 - while result < 100: - data = httptools.downloadpage(host, alfa_s=True).data - items = jsontools.load(data)['items'] +def elementum_download(item): + sleep = False + if elementum_setting.getSetting('logger_silent') == False: + elementum_setting.setSetting('logger_silent', 'true') + sleep = True + if elementum_setting.getSetting('download_storage') != 0: + config.set_setting('elementumtype', elementum_setting.getSetting('download_storage')) # Backup Setting + elementum_setting.setSetting('download_storage', '0') # Set Setting + sleep = True + if elementum_setting.getSetting('download_path') != config.get_setting('downloadpath'): + elementum_setting.setSetting('download_path', config.get_setting('downloadpath')) # Backup Setting + config.set_setting('elementumdl', elementum_setting.getSetting('download_path')) # Set Setting + sleep = True + if sleep: time.sleep(3) - partials = [] - files = [] - titles = [] - - for it in items: - p = scrapertools.find_single_match(it['label'], r'(\d+\.\d+)%') - f = scrapertools.find_single_match(it['path'], r'resume=([^&]+)') - t = it['info']['title'] - partials.append(p) - files.append(f) - titles.append(t) - - partial = 0 - for i, p in enumerate(partials): - partial += float(p) - update_download_info(files[i], p, titles[i]) - - result = partial / len(items) - xbmc.sleep(5000) - - log('All Torrent downloaded.') - xbmc.sleep(1000) - unset_elementum() + path = filetools.join(config.get_data_path(),'elementum_torrent.txt') + url = urllib.quote_plus(item.url) + filetools.write(path, url) -def update_download_info(file, partial, title): - extensions_list = ['aaf', '3gp', 'asf', 'avi', 'flv', 'mpeg', 'm1v', 'm2v', 'm4v', 'mkv', 'mov', 'mpg', 'mpe', 'mp4', 'ogg', 'wmv'] +def stop_elementum_monitor(): + config.set_setting('stop_elementum_monitor', True) + time.sleep(2) + +def start_elementum_monitor(): + config.set_setting('stop_elementum_monitor', False) + time.sleep(3) + Thread(target=elementum_monitor).start() + + + +def elementum_monitor(): + log('Start Elementum Monitor') + path = filetools.join(config.get_data_path(),'elementum_torrent.txt') + partials = [] + while True: + if filetools.isfile(path): + log('Add Torrent') + url = filetools.read(path) + TorrentName = match(url, patron=r'btih(?::|%3A)([^&%]+)', string=True).match + uri = elementum_host + 'add' + post = 'uri=%s&file=null&all=1' % url + match(uri, post=post, timeout=5, alfa_s=True, ignore_response_code=True) + filetools.remove(path) + while not filetools.isfile(filetools.join(elementum_setting.getSetting('torrents_path'), TorrentName + '.torrent')): + time.sleep(1) + else: + data = match(elementum_host, alfa_s=True).data + if data: + json = jsontools.load(data)['items'] + + for it in json: + Partial = float(match(it['label'], patron=r'(\d+\.\d+)%').match) + Title = it['info']['title'] + TorrentName = match(it['path'], patron=r'resume=([^&]+)').match + File, Json = find_file(TorrentName) + update_download_info(Partial, Title, TorrentName, File, Json) + partials.append(Partial) + + result = 0 + for p in partials: + result += p + + if result / len(partials) == 100: + unset_elementum() + + time.sleep(1) + + +def find_file(File): path = xbmc.translatePath(config.get_setting('downloadlistpath')) - dlpath = filetools.join(config.get_setting('downloadpath'), title) - log('DL PATH: ' + dlpath) - sep = '/' if dlpath.startswith('smb') else os.sep - if filetools.isdir(dlpath) and partial > 0: - dlfiles = filetools.listdir(dlpath) - for f in dlfiles: - log('FILE TYPE: ' + f.split('.')[-1]) - if f.split('.')[-1] in extensions_list: - dlpath = filetools.join(dlpath.split(sep)[-1], f) - else: - dlpath = dlpath.split(sep)[-1] - files = filetools.listdir(path) + # dbg() for f in files: - json = jsontools.load(filetools.read(filetools.join(path, f))) - if 'downloadServer' in json and file in json['downloadServer']['url'] or \ - 'url' in json and file in json['url']: - jsontools.update_node(dlpath, f, "downloadFilename", path) - jsontools.update_node(float(partial), f, "downloadProgress", path) - if float(partial) == 100: - jsontools.update_node(2, f, "downloadStatus", path) + filepath = filetools.join(path, f) + json = jsontools.load(filetools.read(filepath)) + if ('downloadServer' in json and 'url' in json['downloadServer'] and File in json['downloadServer']['url']) or ('url' in json and File in json['url']): + break + return filetools.split(filepath)[-1], json + + +def update_download_info(Partial, Title, TorrentName, File, Json): + path = xbmc.translatePath(config.get_setting('downloadlistpath')) + dlpath = filetools.join(config.get_setting('downloadpath'), Title) + tpath = filetools.join(xbmc.translatePath(elementum_setting.getSetting('torrents_path')), TorrentName) + if Json['downloadSize'] == 0: + size = Torrent.from_file(tpath + '.torrent').total_size + jsontools.update_node(size, File, 'downloadSize', path) + if Json['downloadFilename'] != dlpath and 'backupFilename' not in Json: + jsontools.update_node(Json['downloadFilename'], File, 'backupFilename', path) + jsontools.update_node(dlpath, File, 'downloadFilename', path) + if Json['downloadProgress'] != Partial and Partial != 0: + jsontools.update_node(Partial, File, 'downloadProgress', path) + jsontools.update_node(4, File, 'downloadStatus', path) + if Partial == 100: + jsontools.update_node(Json['downloadSize'], File, 'downloadCompleted', path) + jsontools.update_node(2, File, 'downloadStatus', path) + requests.get(elementum_host + 'pause/' + TorrentName) + filetools.remove(tpath + '.torrent') + time.sleep(5) + rename(TorrentName, path) + + +def rename(TorrentName, Path): + File, Json = find_file(TorrentName) + path = Json['downloadFilename'] + if filetools.isdir(path): + extension = '' + files = filetools.listdir(path) + sep = '/' if path.lower().startswith("smb://") else os.sep + oldName = path.split(sep)[-1] + newName = Json['backupFilename'] + for f in files: + ext = f.split('.')[-1] + if ext in extensions_list: extension = '.' + ext + filetools.rename(filetools.join(path, f), f.replace(oldName, newName)) + filetools.rename(path, newName) + jsontools.update_node(filetools.join(newName,newName + extension), File, 'downloadFilename', Path) + + else: + oldName = filetools.split(path)[-1] + newName = Json['backupFilename'] + '.' + oldName.split('.')[-1] + filetools.rename(path, newName) + jsontools.update_node(newName, File, 'downloadFilename', Path) -def set_elementum(): - log('SET Elementum') - torr_setting = xbmcaddon.Addon(id="plugin.video.elementum") - config.set_setting('elementumtype', torr_setting.getSetting('download_storage')) - config.set_setting('elementumdl', torr_setting.getSetting('download_path')) - torr_setting.setSetting('download_storage', '0') - torr_setting.setSetting('download_path', config.get_setting('downloadpath')) - xbmc.sleep(3000) def unset_elementum(): log('UNSET Elementum') - torr_setting = xbmcaddon.Addon(id="plugin.video.elementum") Sleep = False if config.get_setting('elementumtype'): - torr_setting.setSetting('download_storage', str(config.get_setting('elementumtype'))) + elementum_setting.setSetting('download_storage', str(config.get_setting('elementumtype'))) Sleep = True if config.get_setting('elementumdl'): - torr_setting.setSetting('download_path', config.get_setting('elementumdl')) + elementum_setting.setSetting('download_path', config.get_setting('elementumdl')) Sleep = True if Sleep: - xbmc.sleep(3000) + time.sleep(3) ########################## ELEMENTUM MONITOR TEST ########################## - -def log(texto): - try: - xbmc.log(texto, xbmc.LOGNOTICE) - except: - pass diff --git a/service.py b/service.py index 3e440373..bb6f03f5 100644 --- a/service.py +++ b/service.py @@ -397,6 +397,8 @@ if __name__ == "__main__": custom_code.init() from threading import Thread Thread(target=viewmodeMonitor).start() + from servers import torrent + Thread(target=torrent.elementum_monitor).start() if not config.get_setting("update", "videolibrary") == 2: check_for_update(overwrite=False) diff --git a/specials/downloads.py b/specials/downloads.py index c45f02f4..637a0c1b 100644 --- a/specials/downloads.py +++ b/specials/downloads.py @@ -22,6 +22,7 @@ from core.downloader import Downloader from core.item import Item from platformcode import config, logger from platformcode import platformtools +from core.support import log, dbg, typo kb = '0xFF65B3DA' kg = '0xFF65DAA8' @@ -920,6 +921,7 @@ def get_episodes(item): def write_json(item): logger.info() + channel = item.channel item.action = "menu" item.channel = "downloads" item.downloadStatus = STATUS_CODES.stoped @@ -933,7 +935,14 @@ def write_json(item): if name in item.__dict__: item.__dict__.pop(name) - path = filetools.join(DOWNLOAD_LIST_PATH, str(time.time()) + ".json") + naming = item.fulltitle + typo(item.infoLabels['IMDBNumber'], '_ []') + typo(channel, '_ []') + naming += typo(item.contentLanguage, '_ []') if item.contentLanguage else '' + naming += typo(item.quality, '_ []') if item.quality else '' + + path = filetools.join(DOWNLOAD_LIST_PATH, naming + ".json") + if filetools.isfile(path): + filetools.remove(path) + item.path = path filetools.write(path, item.tojson()) time.sleep(0.1) @@ -1039,6 +1048,7 @@ def save_download_movie(item): progreso.update(0, config.get_localized_string(60062)) item.downloadFilename = filetools.validate_path("%s [%s]" % (item.contentTitle.strip(), item.infoLabels['IMDBNumber'])) + item.backupFilename = filetools.validate_path("%s [%s]" % (item.contentTitle.strip(), item.infoLabels['IMDBNumber'])) write_json(item) @@ -1059,8 +1069,7 @@ def save_download_movie(item): def save_download_tvshow(item): - logger.info("contentAction: %s | contentChannel: %s | contentType: %s | contentSerieName: %s" % ( - item.contentAction, item.contentChannel, item.contentType, item.contentSerieName)) + logger.info("contentAction: %s | contentChannel: %s | contentType: %s | contentSerieName: %s" % (item.contentAction, item.contentChannel, item.contentType, item.contentSerieName)) progreso = platformtools.dialog_progress_bg(config.get_localized_string(30101), config.get_localized_string(70188)) try: @@ -1088,9 +1097,8 @@ def save_download_tvshow(item): progreso.close() if not platformtools.dialog_yesno(config.get_localized_string(30101), config.get_localized_string(70189)): - platformtools.dialog_ok(config.get_localized_string(30101), - str(len(episodes)) + config.get_localized_string(30110) + item.contentSerieName, - config.get_localized_string(30109)) + platformtools.dialog_ok(config.get_localized_string(30101), str(len(episodes)) + config.get_localized_string(30110) + item.contentSerieName, config.get_localized_string(30109)) + else: if len(episodes) == 1: play_item = select_server(episodes[0])