Torrent Download:
- Supporta solo Film - Monitor di Download - Rinominazione dei file alla fine del download
This commit is contained in:
@@ -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)
|
||||
|
||||
1
lib/torrentool/__init__.py
Normal file
1
lib/torrentool/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = (1, 0, 2)
|
||||
7
lib/torrentool/api.py
Normal file
7
lib/torrentool/api.py
Normal file
@@ -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
|
||||
204
lib/torrentool/bencode.py
Normal file
204
lib/torrentool/bencode.py
Normal file
@@ -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)
|
||||
94
lib/torrentool/cli.py
Normal file
94
lib/torrentool/cli.py
Normal file
@@ -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={})
|
||||
27
lib/torrentool/exceptions.py
Normal file
27
lib/torrentool/exceptions.py
Normal file
@@ -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."""
|
||||
8
lib/torrentool/repo/open_trackers.ini
Normal file
8
lib/torrentool/repo/open_trackers.ini
Normal file
@@ -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
|
||||
436
lib/torrentool/torrent.py
Normal file
436
lib/torrentool/torrent.py
Normal file
@@ -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
|
||||
91
lib/torrentool/utils.py
Normal file
91
lib/torrentool/utils.py
Normal file
@@ -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)
|
||||
@@ -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)
|
||||
@@ -106,9 +106,7 @@
|
||||
<setting id="download_adv" type="action" label="30030" visible="eq(-8,true)" action="RunPlugin(plugin://plugin.video.kod/?ew0KCSJhY3Rpb24iOiJjaGFubmVsX2NvbmZpZyIsDQoJImNvbmZpZyI6ImRvd25sb2FkcyIsDQogICAgImNoYW5uZWwiOiJzZXR0aW5nIg0KfQ==)"/>
|
||||
|
||||
<setting id="elementumtype" type="text" label="Elementum Download Type" visible="false"/>
|
||||
<setting id="quasartype" type="text" label="Quasar Download Type" visible="false"/>
|
||||
<setting id="elementumdl" type="folder" label="Elementum Download Backup" visible="false"/>
|
||||
<setting id="quasardl" type="folder" label="Quasar Download Backup" visible="false"/>
|
||||
</category>
|
||||
|
||||
<!-- News -->
|
||||
|
||||
1527
servers/torrent.py
1527
servers/torrent.py
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user