diff --git a/Anteoloader.py b/Anteoloader.py index 35700ca..999c3c2 100644 --- a/Anteoloader.py +++ b/Anteoloader.py @@ -1,701 +1,701 @@ -# -*- coding: utf-8 -*- -''' - Torrenter v2 plugin for XBMC/Kodi - Copyright (C) 2012-2015 Vadim Skorba v1 - DiMartino v2 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -''' - - -import urllib2 -import hashlib -import re -from StringIO import StringIO -import gzip - -import xbmc -import xbmcgui -import xbmcvfs -import Localization -from functions import file_encode, isSubtitle, DownloadDB, log, debug, is_writable, unquote, file_url - - -import os -import urllib -import sys -from contextlib import contextmanager, closing, nested - - -from functions import foldername, showMessage, clearStorage, WatchedHistoryDB, get_ids_video, log, debug, ensure_str - -if sys.modules["__main__"].__settings__.getSetting("torrent_player") == '2': - from torrent2http import State, Engine, MediaType - author = 'Anteo' -elif sys.modules["__main__"].__settings__.getSetting("torrent_player") == '3': - from pyrrent2http import State, Engine, MediaType - author = 'Inpos' - -ROOT = sys.modules["__main__"].__root__ -RESOURCES_PATH = os.path.join(ROOT, 'resources') -TORRENT2HTTP_TIMEOUT = 20 -TORRENT2HTTP_POLL = 1000 -PLAYING_EVENT_INTERVAL = 60 -MIN_COMPLETED_PIECES = 0.5 - -WINDOW_FULLSCREEN_VIDEO = 12005 - -XBFONT_LEFT = 0x00000000 -XBFONT_RIGHT = 0x00000001 -XBFONT_CENTER_X = 0x00000002 -XBFONT_CENTER_Y = 0x00000004 -XBFONT_TRUNCATED = 0x00000008 -XBFONT_JUSTIFY = 0x00000010 - -STATE_STRS = [ - 'Queued', - 'Checking', - 'Downloading metadata', - 'Downloading', - 'Finished', - 'Seeding', - 'Allocating', - 'Allocating file & Checking resume' -] - -VIEWPORT_WIDTH = 1920.0 -VIEWPORT_HEIGHT = 1088.0 -OVERLAY_WIDTH = int(VIEWPORT_WIDTH * 0.7) # 70% size -OVERLAY_HEIGHT = 150 - -ENCRYPTION_SETTINGS = { - "Forced": 0, - "Enabled": 1, - "Disabled": 2, -} - -class Encryption: - FORCED = 0 - ENABLED = 1 - DISABLED = 2 - -class AnteoLoader: - magnetLink = None - engine = None - torrentFile = None - __plugin__ = sys.modules["__main__"].__plugin__ - __settings__ = sys.modules["__main__"].__settings__ - - def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'): - self.storageDirectory = storageDirectory - self.torrentFilesPath = os.path.join(self.storageDirectory, torrentFilesDirectory) + os.sep - if not is_writable(self.storageDirectory): - xbmcgui.Dialog().ok(self.localize('Torrenter v2'), - self.localize('Your storage path is not writable or not local! Please change it in settings!'), - self.localize(self.storageDirectory)) - - sys.exit(1) - - #pre settings - if re.match("^magnet\:.+$", torrentFile): - self.magnetLink = torrentFile - else: - self.torrentFile = torrentFile - - def __exit__(self): - log('on __exit__') - if self.engine: - self.engine.close() - log('__exit__ worked!') - - def setup_engine(self): - encryption = Encryption.ENABLED if self.__settings__.getSetting('encryption') == 'true' else Encryption.DISABLED - - if self.__settings__.getSetting("connections_limit") not in ["",0,"0"]: - connections_limit = int(self.__settings__.getSetting("connections_limit")) - else: - connections_limit = None - - use_random_port = True if self.__settings__.getSetting('use_random_port') == 'true' else False - - listen_port=int(self.__settings__.getSetting("listen_port")) if self.__settings__.getSetting( - "listen_port") != "" else 6881 - - if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.storageDirectory: - keep_complete = False - keep_incomplete = False - else: - keep_complete = True - keep_incomplete = True - - dht_routers = ["router.bittorrent.com:6881", "router.utorrent.com:6881"] - user_agent = 'uTorrent/2200(24683)' - self.engine = Engine(uri=file_url(self.torrentFile), download_path=self.storageDirectory, - connections_limit=connections_limit, - encryption=encryption, keep_complete=keep_complete, keep_incomplete=keep_incomplete, - dht_routers=dht_routers, use_random_port=use_random_port, listen_port=listen_port, - user_agent=user_agent) - - def localize(self, string): - try: - return Localization.localize(string) - except: - return string - - def getContentList(self): - self.setup_engine() - files = [] - filelist = [] - with closing(self.engine): - self.engine.start() - #media_types=[MediaType.VIDEO, MediaType.AUDIO, MediaType.SUBTITLES, MediaType.UNKNOWN] - - iterator = 0 - text = Localization.localize('Magnet-link is converting') if self.magnetLink\ - else Localization.localize('Opening torrent file') - while not files and not xbmc.abortRequested and iterator < 100: - files = self.engine.list() - self.engine.check_torrent_error() - if iterator==4: - progressBar = xbmcgui.DialogProgress() - progressBar.create(Localization.localize('Please Wait'), - Localization.localize('Magnet-link is converting')) - elif iterator>4: - progressBar.update(iterator, Localization.localize('Please Wait'),text+'.' * (iterator % 4), ' ') - if progressBar.iscanceled(): - progressBar.update(0) - progressBar.close() - return [] - xbmc.sleep(500) - iterator += 1 - - for fs in files: - stringdata = {"title": ensure_str(fs.name), "size": fs.size, "ind": fs.index, - 'offset': fs.offset} - filelist.append(stringdata) - return filelist - - def saveTorrent(self, torrentUrl): - #if not xbmcvfs.exists(torrentUrl) or re.match("^http.+$", torrentUrl): - if re.match("^magnet\:.+$", torrentUrl): - self.magnetLink = torrentUrl - self.magnetToTorrent(torrentUrl) - self.magnetLink = None - return self.torrentFile - else: - if not xbmcvfs.exists(self.torrentFilesPath): xbmcvfs.mkdirs(self.torrentFilesPath) - torrentFile = os.path.join(self.torrentFilesPath, self.md5(torrentUrl) + '.torrent') - try: - if not re.match("^http\:.+$", torrentUrl): - content = xbmcvfs.File(torrentUrl, "rb").read() - else: - request = urllib2.Request(torrentUrl) - request.add_header('Referer', torrentUrl) - request.add_header('Accept-encoding', 'gzip') - result = urllib2.urlopen(request) - if result.info().get('Content-Encoding') == 'gzip': - buf = StringIO(result.read()) - f = gzip.GzipFile(fileobj=buf) - content = f.read() - else: - content = result.read() - - localFile = xbmcvfs.File(torrentFile, "w+b") - localFile.write(content) - localFile.close() - except Exception, e: - log('Unable to rename torrent file from %s to %s in AnteoLoader::saveTorrent. Exception: %s' % - (torrentUrl, torrentFile, str(e))) - return - #else: - #torrentFile = torrentUrl - if xbmcvfs.exists(torrentFile) and not os.path.exists(torrentFile): - if not xbmcvfs.exists(self.torrentFilesPath): xbmcvfs.mkdirs(self.torrentFilesPath) - torrentFile = os.path.join(self.torrentFilesPath, self.md5(torrentUrl) + '.torrent') - xbmcvfs.copy(torrentUrl, torrentFile) - if os.path.exists(torrentFile): - self.torrentFile = torrentFile - return self.torrentFile - - def md5(self, string): - hasher = hashlib.md5() - try: - hasher.update(string) - except: - hasher.update(string.encode('utf-8', 'ignore')) - return hasher.hexdigest() - - def magnetToTorrent(self, magnet): - try: - from Libtorrent import Libtorrent - torrent = Libtorrent(self.storageDirectory, magnet) - torrent.magnetToTorrent(magnet) - self.torrentFile = torrent.torrentFile - except: - self.torrentFile = magnet - log('['+author+'Loader][magnetToTorrent]: self.torrentFile '+str(self.torrentFile)) - -class AnteoPlayer(xbmc.Player): - __plugin__ = sys.modules["__main__"].__plugin__ - __settings__ = sys.modules["__main__"].__settings__ - ROOT = sys.modules["__main__"].__root__ # .decode('utf-8').encode(sys.getfilesystemencoding()) - USERAGENT = "Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0" - torrentFilesDirectory = 'torrents' - debug = __settings__.getSetting('debug') == 'true' - subs_dl = __settings__.getSetting('subs_dl') == 'true' - seeding = __settings__.getSetting('keep_seeding') == 'true' and __settings__.getSetting('keep_files') == '1' - seeding_status = False - seeding_run = False - ids_video = None - episodeId = None - fullSize = 0 - watchedTime = 0 - totalTime = 1 - seek = 0 - basename = '' - - def __init__(self, userStorageDirectory, torrentUrl, params={}): - self.userStorageDirectory = userStorageDirectory - self.torrentUrl = torrentUrl - xbmc.Player.__init__(self) - log("[AnteoPlayer] Initalized") - self.params = params - self.get = self.params.get - self.contentId = int(self.get("url")) - if self.get("seek"): - self.seek = int(self.get("seek")) - #self.torrent = AnteoLoader(self.userStorageDirectory, self.torrentUrl, self.torrentFilesDirectory) - self.init() - self.setup_engine() - with closing(self.engine): - self.engine.start(self.contentId) - self.setup_nextep() - while True: - if self.buffer(): - log('['+author+'Player]: ************************************* GOING LOOP') - if self.setup_play(): - self.setup_subs() - self.loop() - WatchedHistoryDB().add(self.basename, foldername(self.getContentList()[self.contentId]['title']), self.watchedTime, self.totalTime, self.contentId, self.fullSize) - else: - log('['+author+'Player]: ************************************* break') - break - log('['+author+'Player]: ************************************* GO NEXT?') - if self.next_dl and self.next_contentId != False and isinstance(self.next_contentId, int) and self.iterator == 100: - if not self.next_play: - xbmc.sleep(3000) - if not xbmcgui.Dialog().yesno( - self.localize('Torrent2HTTP'), - self.localize('Would you like to play next episode?')): - break - self.contentId = self.next_contentId - continue - log('['+author+'Player]: ************************************* NO! break') - break - - xbmc.Player().stop() - - if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.userStorageDirectory: - xbmc.sleep(1000) - clearStorage(self.userStorageDirectory) - else: - #if self.seeding_status: - #showMessage(self.localize('Information'), - # self.localize('Torrent is seeding. To stop it use Download Status.'), forced=True) - #else: - #if self.seeding: self.db_delete() - showMessage(self.localize('Information'), - self.localize('Torrent downloading is stopped.'), forced=True) - - def __exit__(self): - log('on __exit__') - if self.engine: - self.engine.close() - log('__exit__ worked!') - - def init(self): - self.next_contentId = False - self.display_name = '' - self.downloadedSize = 0 - self.dialog = xbmcgui.Dialog() - self.on_playback_started = [] - self.on_playback_resumed = [] - self.on_playback_paused = [] - self.on_playback_stopped = [] - self.torrentUrl = self.torrentUrl - - def setup_engine(self): - #uri=None, binaries_path=None, platform=None, download_path=".", - #bind_host='127.0.0.1', bind_port=5001, connections_limit=None, download_kbps=None, upload_kbps=None, - #enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False, - #log_stats=False, encryption=Encryption.ENABLED, keep_complete=False, keep_incomplete=False, - #keep_files=False, log_files_progress=False, log_overall_progress=False, log_pieces_progress=False, - #listen_port=6881, use_random_port=False, max_idle_timeout=None, no_sparse=False, resume_file=None, - #user_agent=None, startup_timeout=5, state_file=None, enable_utp=True, enable_tcp=True, - #debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50, - #peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3, - #dht_routers=None, trackers=None) - - encryption = Encryption.ENABLED if self.__settings__.getSetting('encryption') == 'true' else Encryption.DISABLED - upload_limit = int(self.__settings__.getSetting("upload_limit"))*1024/8 if self.__settings__.getSetting( - "upload_limit") != "" else 0 - download_limit = int(self.__settings__.getSetting("download_limit"))*1024/8 if self.__settings__.getSetting( - "download_limit") != "" else 0 - - if self.__settings__.getSetting("connections_limit") not in ["",0,"0"]: - connections_limit = int(self.__settings__.getSetting("connections_limit")) - else: - connections_limit = None - - use_random_port = True if self.__settings__.getSetting('use_random_port') == 'true' else False - - listen_port=int(self.__settings__.getSetting("listen_port")) if self.__settings__.getSetting( - "listen_port") != "" else 6881 - - if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.userStorageDirectory: - keep_complete = False - keep_incomplete = False - keep_files = False - resume_file = None - else: - keep_complete = True - keep_incomplete = True - keep_files = True - resume_file=os.path.join(self.userStorageDirectory, 'torrents', os.path.basename(self.torrentUrl)+'.resume_data') - - dht_routers = ["router.bittorrent.com:6881","router.utorrent.com:6881"] - user_agent = 'uTorrent/2200(24683)' - self.pre_buffer_bytes = int(self.__settings__.getSetting("pre_buffer_bytes"))*1024*1024 - - self.engine = Engine(uri=file_url(self.torrentUrl), download_path=self.userStorageDirectory, - connections_limit=connections_limit, download_kbps=download_limit, upload_kbps=upload_limit, - encryption=encryption, keep_complete=keep_complete, keep_incomplete=keep_incomplete, - dht_routers=dht_routers, use_random_port=use_random_port, listen_port=listen_port, - keep_files=keep_files, user_agent=user_agent, resume_file=resume_file) - - def buffer(self): - #self.pre_buffer_bytes = 30*1024*1024 #30 MB - ready = False - progressBar = xbmcgui.DialogProgress() - progressBar.create(self.localize('Please Wait'), - self.localize('Seeds searching.')) - #if self.subs_dl: - # subs = self.torrent.getSubsIds(os.path.basename(self.torrent.getFilePath(self.contentId))) - # if len(subs) > 0: - # for ind, title in subs: - # self.torrent.continueSession(ind) - - while not xbmc.abortRequested and not ready: - xbmc.sleep(500) - status = self.engine.status() - self.print_debug(status) - #self.print_fulldebug() - self.engine.check_torrent_error(status) - file_status = self.engine.file_status(self.contentId) - if not file_status: - continue - self.fullSize = int(file_status.size / 1024 / 1024) - downloadedSize = status.total_download / 1024 / 1024 - getDownloadRate = status.download_rate / 1024 * 8 - getUploadRate = status.upload_rate / 1024 * 8 - getSeeds, getPeers = status.num_seeds, status.num_peers - iterator = int(round(float(file_status.download) / self.pre_buffer_bytes, 2) * 100) - if iterator > 99: iterator = 99 - if status.state == State.CHECKING_FILES: - iterator = int(status.progress*100) - if iterator > 99: iterator = 99 - progressBar.update(iterator, self.localize('Checking preloaded files...'), ' ', ' ') - elif status.state == State.DOWNLOADING: - dialogText = self.localize('Preloaded: ') + "%d MB / %d MB" % \ - (int(downloadedSize), self.fullSize) - peersText = ' [%s: %s; %s: %s]' % ( - self.localize('Seeds'), getSeeds, self.localize('Peers'), getPeers) - speedsText = '%s: %d Mbit/s; %s: %d Mbit/s' % ( - self.localize('Downloading'), int(getDownloadRate), - self.localize('Uploading'), int(getUploadRate)) - progressBar.update(iterator, self.localize('Seeds searching.') + peersText, dialogText, - speedsText) - - if file_status.download >= self.pre_buffer_bytes: - ready = True - break - elif status.state in [State.FINISHED, State.SEEDING]: - ready = True - break - else: - progressBar.update(iterator, self.localize('UNKNOWN STATUS'), ' ', ' ') - if progressBar.iscanceled(): - self.iterator = 0 - ready = False - break - - progressBar.update(0) - progressBar.close() - return ready - - def setup_nextep(self): - try: - if self.get("url2"): - debug("[setup_nextep]: url2") - self.ids_video = urllib.unquote_plus(self.get("url2")).split(',') - else: - debug("[setup_nextep]: not url2") - self.ids_video = self.get_ids() - except: - pass - - if self.__settings__.getSetting('next_dl') == 'true' and self.ids_video and len(self.ids_video)>1: - self.next_dl = True - else: - self.next_dl = False - self.next_play = self.__settings__.getSetting('next_play') == 'true' - log('['+author+'Player]: next_dl - %s, next_play - %s, ids_video - %s' % (str(self.next_dl), str(self.next_play), str(self.ids_video))) - - def setup_play(self): - file_status = self.engine.file_status(self.contentId) - self.iterator = 0 - self.watchedTime = 0 - self.totalTime = 1 - url = file_status.url - label = os.path.basename(file_status.name) - self.basename = label - self.seeding_run = False - listitem = xbmcgui.ListItem(label, path=url) - - if self.next_dl: - next_contentId_index = self.ids_video.index(str(self.contentId)) + 1 - if len(self.ids_video) > next_contentId_index: - self.next_contentId = int(self.ids_video[next_contentId_index]) - else: - self.next_contentId = False - log('['+author+'Player][setup_play]: next_contentId: '+str(self.next_contentId)) - try: - seasonId = self.get("seasonId") - self.episodeId = self.get("episodeId") if not self.episodeId else int(self.episodeId) + 1 - title = urllib.unquote_plus(self.get("title")) if self.get("title") else None - - if self.get("label") and self.episodeId == self.get("episodeId"): - label = urllib.unquote_plus(self.get("label")) - elif seasonId and self.episodeId and title: - label = '%s S%02dE%02d.%s (%s)' % ( - title, int(seasonId), int(self.episodeId), self.basename.split('.')[-1], self.basename) - - if seasonId and self.episodeId and label and title: - listitem = xbmcgui.ListItem(label, path=url) - - listitem.setInfo(type='video', infoLabels={'title': label, - 'episode': int(self.episodeId), - 'season': int(seasonId), - 'tvshowtitle': title}) - except: - log('['+author+'Player]: Operation INFO failed!') - - thumbnail = self.get("thumbnail") - if thumbnail: - listitem.setThumbnailImage(urllib.unquote_plus(thumbnail)) - self.display_name = label - - player = xbmc.Player() - player.play(url, listitem) - - xbmc.sleep(2000) # very important, do not edit this, podavan - i = 0 - while not xbmc.abortRequested and not self.isPlaying() and i < 50: - xbmc.sleep(200) - i += 1 - - log('['+author+'Player]: self.isPlaying() = %s, i = %d, xbmc.abortRequested - %s' % (str(self.isPlaying()), i, str(xbmc.abortRequested))) - if not self.isPlaying() or xbmc.abortRequested: - return False - - if self.seek > 0: - log('['+author+'Player]: seekTime - '+str(self.seek)) - self.seekTime(self.seek) - return True - - def setup_subs(self): - if self.subs_dl: - file_status = self.engine.file_status(self.contentId) - subs = [] - filename = os.path.basename(file_status.name) - sub_files = self.engine.list(media_types=[MediaType.SUBTITLES]) - for i in sub_files: - if isSubtitle(filename, i.name): - subs.append(i) - if subs: - log("[AnteoPlayer][setup_subs]: Detected subtitles: %s" % str(subs)) - for sub in subs: - xbmc.Player().setSubtitles(sub.url) - - def loop(self): - debug_counter = 0 - pause = True - with closing( - OverlayText(w=OVERLAY_WIDTH, h=OVERLAY_HEIGHT, alignment=XBFONT_CENTER_X | XBFONT_CENTER_Y)) as overlay: - with nested(self.attach(overlay.show, self.on_playback_paused), - self.attach(overlay.hide, self.on_playback_resumed, self.on_playback_stopped)): - while not xbmc.abortRequested and self.isPlaying(): - #self.print_fulldebug() - status = self.engine.status() - file_status = self.engine.file_status(self.contentId) - self.watchedTime = xbmc.Player().getTime() - self.totalTime = xbmc.Player().getTotalTime() - if self.iterator == 100 and debug_counter < 100: - debug_counter += 1 - else: - self.print_debug(status) - debug_counter=0 - - overlay.text = "\n".join(self._get_status_lines(status, file_status)) - - self.iterator = int(file_status.progress * 100) - - if pause and self.__settings__.getSetting("pause_onplay") == 'true': - pause = False - xbmc.Player().pause() - xbmc.sleep(1000) - - #if not self.seeding_run and self.iterator == 100 and self.seeding: - #self.seeding_run = True - #self.seed(self.contentId) - #self.seeding_status = True - # xbmc.sleep(7000) - - def onPlayBackStarted(self): - for f in self.on_playback_started: - f() - log('[onPlayBackStarted]: '+(str(("video", "play", self.display_name)))) - - def onPlayBackResumed(self): - for f in self.on_playback_resumed: - f() - self.onPlayBackStarted() - - def onPlayBackPaused(self): - for f in self.on_playback_paused: - f() - log('[onPlayBackPaused]: '+(str(("video", "pause", self.display_name)))) - - def onPlayBackStopped(self): - for f in self.on_playback_stopped: - f() - log('[onPlayBackStopped]: '+(str(("video", "stop", self.display_name)))) - - @contextmanager - def attach(self, callback, *events): - for event in events: - event.append(callback) - yield - for event in events: - event.remove(callback) - - def _get_status_lines(self, s, f): - return [ - self.display_name, - "%.2f%% %s" % (f.progress * 100, self.localize(STATE_STRS[s.state]).decode('utf-8')), - "D:%.2f%s U:%.2f%s S:%d P:%d" % (s.download_rate, self.localize('kb/s').decode('utf-8'), - s.upload_rate, self.localize('kb/s').decode('utf-8'), - s.num_seeds, s.num_peers) - ] - - def localize(self, string): - try: - return Localization.localize(string) - except: - return string - - def print_debug(self, status=None): - #FileStatus = namedtuple('FileStatus', "name, save_path, url, size, offset, download, progress, index, media_type") - - #SessionStatus = namedtuple('SessionStatus', "name, state, state_str, error, progress, download_rate, upload_rate, " - # "total_download, total_upload, num_peers, num_seeds, total_seeds, " - # "total_peers") - - #log('[buffer] file_status:'+str(file_status)) - #log('[buffer] status:'+str(status)) - if not status: - status = self.engine.status() - self.engine.check_torrent_error(status) - log('['+author+'Player]: %.2f%% complete (down: %.1f kb/s up: %.1f kb/s peers: %d) %s' % \ - (status.progress * 100, status.download_rate, - status.upload_rate, status.num_peers, status.state_str)) - - def print_fulldebug(self): - status = self.engine.status() - file_status = self.engine.file_status(self.contentId) - log('[buffer] file_status:'+str(file_status)) - log('[buffer] status:'+str(status)) - - def get_ids(self): - contentList = [] - for fs in self.engine.list(): - contentList.append((fs.name, str(fs.index))) - contentList = sorted(contentList, key=lambda x: x[0]) - return get_ids_video(contentList) - - def getContentList(self): - filelist = [] - for fs in self.engine.list(): - stringdata = {"title": ensure_str(fs.name), "size": fs.size, "ind": fs.index, - 'offset': fs.offset} - filelist.append(stringdata) - return filelist - -class OverlayText(object): - def __init__(self, w, h, *args, **kwargs): - self.window = xbmcgui.Window(WINDOW_FULLSCREEN_VIDEO) - viewport_w, viewport_h = self._get_skin_resolution() - # Adjust size based on viewport, we are using 1080p coordinates - w = int(w * viewport_w / VIEWPORT_WIDTH) - h = int(h * viewport_h / VIEWPORT_HEIGHT) - x = (viewport_w - w) / 2 - y = (viewport_h - h) / 2 - self._shown = False - self._text = "" - self._label = xbmcgui.ControlLabel(x, y, w, h, self._text, *args, **kwargs) - self._background = xbmcgui.ControlImage(x, y, w, h, os.path.join(RESOURCES_PATH, "images", "black.png")) - self._background.setColorDiffuse("0xD0000000") - - def show(self): - if not self._shown: - self.window.addControls([self._background, self._label]) - self._shown = True - self._background.setColorDiffuse("0xD0000000") - - def hide(self): - if self._shown: - self._shown = False - self.window.removeControls([self._background, self._label]) - self._background.setColorDiffuse("0xFF000000") - - def close(self): - self.hide() - - @property - def text(self): - return self._text - - @text.setter - def text(self, text): - self._text = text - if self._shown: - self._label.setLabel(self._text) - - # This is so hackish it hurts. - def _get_skin_resolution(self): - import xml.etree.ElementTree as ET - - skin_path = xbmc.translatePath("special://skin/") - tree = ET.parse(os.path.join(skin_path, "addon.xml")) - res = tree.findall("./extension/res")[0] +# -*- coding: utf-8 -*- +''' + Torrenter v2 plugin for XBMC/Kodi + Copyright (C) 2012-2015 Vadim Skorba v1 - DiMartino v2 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + + +import urllib2 +import hashlib +import re +from StringIO import StringIO +import zlib + +import xbmc +import xbmcgui +import xbmcvfs +import Localization +from functions import file_encode, isSubtitle, DownloadDB, log, debug, is_writable, unquote, file_url + + +import os +import urllib +import sys +from contextlib import contextmanager, closing, nested + + +from functions import foldername, showMessage, clearStorage, WatchedHistoryDB, get_ids_video, log, debug, ensure_str + +if sys.modules["__main__"].__settings__.getSetting("torrent_player") == '2': + from torrent2http import State, Engine, MediaType + author = 'Anteo' +elif sys.modules["__main__"].__settings__.getSetting("torrent_player") == '3': + from pyrrent2http import State, Engine, MediaType + author = 'Inpos' + +ROOT = sys.modules["__main__"].__root__ +RESOURCES_PATH = os.path.join(ROOT, 'resources') +TORRENT2HTTP_TIMEOUT = 20 +TORRENT2HTTP_POLL = 1000 +PLAYING_EVENT_INTERVAL = 60 +MIN_COMPLETED_PIECES = 0.5 + +WINDOW_FULLSCREEN_VIDEO = 12005 + +XBFONT_LEFT = 0x00000000 +XBFONT_RIGHT = 0x00000001 +XBFONT_CENTER_X = 0x00000002 +XBFONT_CENTER_Y = 0x00000004 +XBFONT_TRUNCATED = 0x00000008 +XBFONT_JUSTIFY = 0x00000010 + +STATE_STRS = [ + 'Queued', + 'Checking', + 'Downloading metadata', + 'Downloading', + 'Finished', + 'Seeding', + 'Allocating', + 'Allocating file & Checking resume' +] + +VIEWPORT_WIDTH = 1920.0 +VIEWPORT_HEIGHT = 1088.0 +OVERLAY_WIDTH = int(VIEWPORT_WIDTH * 0.7) # 70% size +OVERLAY_HEIGHT = 150 + +ENCRYPTION_SETTINGS = { + "Forced": 0, + "Enabled": 1, + "Disabled": 2, +} + +class Encryption: + FORCED = 0 + ENABLED = 1 + DISABLED = 2 + +class AnteoLoader: + magnetLink = None + engine = None + torrentFile = None + __plugin__ = sys.modules["__main__"].__plugin__ + __settings__ = sys.modules["__main__"].__settings__ + + def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'): + self.storageDirectory = storageDirectory + self.torrentFilesPath = os.path.join(self.storageDirectory, torrentFilesDirectory) + os.sep + if not is_writable(self.storageDirectory): + xbmcgui.Dialog().ok(self.localize('Torrenter v2'), + self.localize('Your storage path is not writable or not local! Please change it in settings!'), + self.localize(self.storageDirectory)) + + sys.exit(1) + + #pre settings + if re.match("^magnet\:.+$", torrentFile): + self.magnetLink = torrentFile + else: + self.torrentFile = torrentFile + + def __exit__(self): + log('on __exit__') + if self.engine: + self.engine.close() + log('__exit__ worked!') + + def setup_engine(self): + encryption = Encryption.ENABLED if self.__settings__.getSetting('encryption') == 'true' else Encryption.DISABLED + + if self.__settings__.getSetting("connections_limit") not in ["",0,"0"]: + connections_limit = int(self.__settings__.getSetting("connections_limit")) + else: + connections_limit = None + + use_random_port = True if self.__settings__.getSetting('use_random_port') == 'true' else False + + listen_port=int(self.__settings__.getSetting("listen_port")) if self.__settings__.getSetting( + "listen_port") != "" else 6881 + + if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.storageDirectory: + keep_complete = False + keep_incomplete = False + else: + keep_complete = True + keep_incomplete = True + + dht_routers = ["router.bittorrent.com:6881", "router.utorrent.com:6881"] + user_agent = 'uTorrent/2200(24683)' + self.engine = Engine(uri=file_url(self.torrentFile), download_path=self.storageDirectory, + connections_limit=connections_limit, + encryption=encryption, keep_complete=keep_complete, keep_incomplete=keep_incomplete, + dht_routers=dht_routers, use_random_port=use_random_port, listen_port=listen_port, + user_agent=user_agent) + + def localize(self, string): + try: + return Localization.localize(string) + except: + return string + + def getContentList(self): + self.setup_engine() + files = [] + filelist = [] + with closing(self.engine): + self.engine.start() + #media_types=[MediaType.VIDEO, MediaType.AUDIO, MediaType.SUBTITLES, MediaType.UNKNOWN] + + iterator = 0 + text = Localization.localize('Magnet-link is converting') if self.magnetLink\ + else Localization.localize('Opening torrent file') + while not files and not xbmc.abortRequested and iterator < 100: + files = self.engine.list() + self.engine.check_torrent_error() + if iterator==4: + progressBar = xbmcgui.DialogProgress() + progressBar.create(Localization.localize('Please Wait'), + Localization.localize('Magnet-link is converting')) + elif iterator>4: + progressBar.update(iterator, Localization.localize('Please Wait'),text+'.' * (iterator % 4), ' ') + if progressBar.iscanceled(): + progressBar.update(0) + progressBar.close() + return [] + xbmc.sleep(500) + iterator += 1 + + for fs in files: + stringdata = {"title": ensure_str(fs.name), "size": fs.size, "ind": fs.index, + 'offset': fs.offset} + filelist.append(stringdata) + return filelist + + def saveTorrent(self, torrentUrl): + #if not xbmcvfs.exists(torrentUrl) or re.match("^http.+$", torrentUrl): + if re.match("^magnet\:.+$", torrentUrl): + self.magnetLink = torrentUrl + self.magnetToTorrent(torrentUrl) + self.magnetLink = None + return self.torrentFile + else: + if not xbmcvfs.exists(self.torrentFilesPath): xbmcvfs.mkdirs(self.torrentFilesPath) + torrentFile = os.path.join(self.torrentFilesPath, self.md5(torrentUrl) + '.torrent') + try: + if not re.match("^http\:.+$", torrentUrl): + content = xbmcvfs.File(torrentUrl, "rb").read() + else: + request = urllib2.Request(torrentUrl) + request.add_header('Referer', torrentUrl) + request.add_header('Accept-encoding', 'gzip') + result = urllib2.urlopen(request) + if result.info().get('Content-Encoding') == 'gzip': + buf = StringIO(result.read()) + decomp = zlib.decompressobj(16 + zlib.MAX_WBITS) + content = decomp.decompress(buf.getvalue()) + else: + content = result.read() + + localFile = xbmcvfs.File(torrentFile, "w+b") + localFile.write(content) + localFile.close() + except Exception, e: + log('Unable to rename torrent file from %s to %s in AnteoLoader::saveTorrent. Exception: %s' % + (torrentUrl, torrentFile, str(e))) + return + #else: + #torrentFile = torrentUrl + if xbmcvfs.exists(torrentFile) and not os.path.exists(torrentFile): + if not xbmcvfs.exists(self.torrentFilesPath): xbmcvfs.mkdirs(self.torrentFilesPath) + torrentFile = os.path.join(self.torrentFilesPath, self.md5(torrentUrl) + '.torrent') + xbmcvfs.copy(torrentUrl, torrentFile) + if os.path.exists(torrentFile): + self.torrentFile = torrentFile + return self.torrentFile + + def md5(self, string): + hasher = hashlib.md5() + try: + hasher.update(string) + except: + hasher.update(string.encode('utf-8', 'ignore')) + return hasher.hexdigest() + + def magnetToTorrent(self, magnet): + try: + from Libtorrent import Libtorrent + torrent = Libtorrent(self.storageDirectory, magnet) + torrent.magnetToTorrent(magnet) + self.torrentFile = torrent.torrentFile + except: + self.torrentFile = magnet + log('['+author+'Loader][magnetToTorrent]: self.torrentFile '+str(self.torrentFile)) + +class AnteoPlayer(xbmc.Player): + __plugin__ = sys.modules["__main__"].__plugin__ + __settings__ = sys.modules["__main__"].__settings__ + ROOT = sys.modules["__main__"].__root__ # .decode('utf-8').encode(sys.getfilesystemencoding()) + USERAGENT = "Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0" + torrentFilesDirectory = 'torrents' + debug = __settings__.getSetting('debug') == 'true' + subs_dl = __settings__.getSetting('subs_dl') == 'true' + seeding = __settings__.getSetting('keep_seeding') == 'true' and __settings__.getSetting('keep_files') == '1' + seeding_status = False + seeding_run = False + ids_video = None + episodeId = None + fullSize = 0 + watchedTime = 0 + totalTime = 1 + seek = 0 + basename = '' + + def __init__(self, userStorageDirectory, torrentUrl, params={}): + self.userStorageDirectory = userStorageDirectory + self.torrentUrl = torrentUrl + xbmc.Player.__init__(self) + log("[AnteoPlayer] Initalized") + self.params = params + self.get = self.params.get + self.contentId = int(self.get("url")) + if self.get("seek"): + self.seek = int(self.get("seek")) + #self.torrent = AnteoLoader(self.userStorageDirectory, self.torrentUrl, self.torrentFilesDirectory) + self.init() + self.setup_engine() + with closing(self.engine): + self.engine.start(self.contentId) + self.setup_nextep() + while True: + if self.buffer(): + log('['+author+'Player]: ************************************* GOING LOOP') + if self.setup_play(): + self.setup_subs() + self.loop() + WatchedHistoryDB().add(self.basename, foldername(self.getContentList()[self.contentId]['title']), self.watchedTime, self.totalTime, self.contentId, self.fullSize) + else: + log('['+author+'Player]: ************************************* break') + break + log('['+author+'Player]: ************************************* GO NEXT?') + if self.next_dl and self.next_contentId != False and isinstance(self.next_contentId, int) and self.iterator == 100: + if not self.next_play: + xbmc.sleep(3000) + if not xbmcgui.Dialog().yesno( + self.localize('Torrent2HTTP'), + self.localize('Would you like to play next episode?')): + break + self.contentId = self.next_contentId + continue + log('['+author+'Player]: ************************************* NO! break') + break + + xbmc.Player().stop() + + if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.userStorageDirectory: + xbmc.sleep(1000) + clearStorage(self.userStorageDirectory) + else: + #if self.seeding_status: + #showMessage(self.localize('Information'), + # self.localize('Torrent is seeding. To stop it use Download Status.'), forced=True) + #else: + #if self.seeding: self.db_delete() + showMessage(self.localize('Information'), + self.localize('Torrent downloading is stopped.'), forced=True) + + def __exit__(self): + log('on __exit__') + if self.engine: + self.engine.close() + log('__exit__ worked!') + + def init(self): + self.next_contentId = False + self.display_name = '' + self.downloadedSize = 0 + self.dialog = xbmcgui.Dialog() + self.on_playback_started = [] + self.on_playback_resumed = [] + self.on_playback_paused = [] + self.on_playback_stopped = [] + self.torrentUrl = self.torrentUrl + + def setup_engine(self): + #uri=None, binaries_path=None, platform=None, download_path=".", + #bind_host='127.0.0.1', bind_port=5001, connections_limit=None, download_kbps=None, upload_kbps=None, + #enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False, + #log_stats=False, encryption=Encryption.ENABLED, keep_complete=False, keep_incomplete=False, + #keep_files=False, log_files_progress=False, log_overall_progress=False, log_pieces_progress=False, + #listen_port=6881, use_random_port=False, max_idle_timeout=None, no_sparse=False, resume_file=None, + #user_agent=None, startup_timeout=5, state_file=None, enable_utp=True, enable_tcp=True, + #debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50, + #peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3, + #dht_routers=None, trackers=None) + + encryption = Encryption.ENABLED if self.__settings__.getSetting('encryption') == 'true' else Encryption.DISABLED + upload_limit = int(self.__settings__.getSetting("upload_limit"))*1024/8 if self.__settings__.getSetting( + "upload_limit") != "" else 0 + download_limit = int(self.__settings__.getSetting("download_limit"))*1024/8 if self.__settings__.getSetting( + "download_limit") != "" else 0 + + if self.__settings__.getSetting("connections_limit") not in ["",0,"0"]: + connections_limit = int(self.__settings__.getSetting("connections_limit")) + else: + connections_limit = None + + use_random_port = True if self.__settings__.getSetting('use_random_port') == 'true' else False + + listen_port=int(self.__settings__.getSetting("listen_port")) if self.__settings__.getSetting( + "listen_port") != "" else 6881 + + if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.userStorageDirectory: + keep_complete = False + keep_incomplete = False + keep_files = False + resume_file = None + else: + keep_complete = True + keep_incomplete = True + keep_files = True + resume_file=os.path.join(self.userStorageDirectory, 'torrents', os.path.basename(self.torrentUrl)+'.resume_data') + + dht_routers = ["router.bittorrent.com:6881","router.utorrent.com:6881"] + user_agent = 'uTorrent/2200(24683)' + self.pre_buffer_bytes = int(self.__settings__.getSetting("pre_buffer_bytes"))*1024*1024 + + self.engine = Engine(uri=file_url(self.torrentUrl), download_path=self.userStorageDirectory, + connections_limit=connections_limit, download_kbps=download_limit, upload_kbps=upload_limit, + encryption=encryption, keep_complete=keep_complete, keep_incomplete=keep_incomplete, + dht_routers=dht_routers, use_random_port=use_random_port, listen_port=listen_port, + keep_files=keep_files, user_agent=user_agent, resume_file=resume_file) + + def buffer(self): + #self.pre_buffer_bytes = 30*1024*1024 #30 MB + ready = False + progressBar = xbmcgui.DialogProgress() + progressBar.create(self.localize('Please Wait'), + self.localize('Seeds searching.')) + #if self.subs_dl: + # subs = self.torrent.getSubsIds(os.path.basename(self.torrent.getFilePath(self.contentId))) + # if len(subs) > 0: + # for ind, title in subs: + # self.torrent.continueSession(ind) + + while not xbmc.abortRequested and not ready: + xbmc.sleep(500) + status = self.engine.status() + self.print_debug(status) + #self.print_fulldebug() + self.engine.check_torrent_error(status) + file_status = self.engine.file_status(self.contentId) + if not file_status: + continue + self.fullSize = int(file_status.size / 1024 / 1024) + downloadedSize = status.total_download / 1024 / 1024 + getDownloadRate = status.download_rate / 1024 * 8 + getUploadRate = status.upload_rate / 1024 * 8 + getSeeds, getPeers = status.num_seeds, status.num_peers + iterator = int(round(float(file_status.download) / self.pre_buffer_bytes, 2) * 100) + if iterator > 99: iterator = 99 + if status.state == State.CHECKING_FILES: + iterator = int(status.progress*100) + if iterator > 99: iterator = 99 + progressBar.update(iterator, self.localize('Checking preloaded files...'), ' ', ' ') + elif status.state == State.DOWNLOADING: + dialogText = self.localize('Preloaded: ') + "%d MB / %d MB" % \ + (int(downloadedSize), self.fullSize) + peersText = ' [%s: %s; %s: %s]' % ( + self.localize('Seeds'), getSeeds, self.localize('Peers'), getPeers) + speedsText = '%s: %d Mbit/s; %s: %d Mbit/s' % ( + self.localize('Downloading'), int(getDownloadRate), + self.localize('Uploading'), int(getUploadRate)) + progressBar.update(iterator, self.localize('Seeds searching.') + peersText, dialogText, + speedsText) + + if file_status.download >= self.pre_buffer_bytes: + ready = True + break + elif status.state in [State.FINISHED, State.SEEDING]: + ready = True + break + else: + progressBar.update(iterator, self.localize('UNKNOWN STATUS'), ' ', ' ') + if progressBar.iscanceled(): + self.iterator = 0 + ready = False + break + + progressBar.update(0) + progressBar.close() + return ready + + def setup_nextep(self): + try: + if self.get("url2"): + debug("[setup_nextep]: url2") + self.ids_video = urllib.unquote_plus(self.get("url2")).split(',') + else: + debug("[setup_nextep]: not url2") + self.ids_video = self.get_ids() + except: + pass + + if self.__settings__.getSetting('next_dl') == 'true' and self.ids_video and len(self.ids_video)>1: + self.next_dl = True + else: + self.next_dl = False + self.next_play = self.__settings__.getSetting('next_play') == 'true' + log('['+author+'Player]: next_dl - %s, next_play - %s, ids_video - %s' % (str(self.next_dl), str(self.next_play), str(self.ids_video))) + + def setup_play(self): + file_status = self.engine.file_status(self.contentId) + self.iterator = 0 + self.watchedTime = 0 + self.totalTime = 1 + url = file_status.url + label = os.path.basename(file_status.name) + self.basename = label + self.seeding_run = False + listitem = xbmcgui.ListItem(label, path=url) + + if self.next_dl: + next_contentId_index = self.ids_video.index(str(self.contentId)) + 1 + if len(self.ids_video) > next_contentId_index: + self.next_contentId = int(self.ids_video[next_contentId_index]) + else: + self.next_contentId = False + log('['+author+'Player][setup_play]: next_contentId: '+str(self.next_contentId)) + try: + seasonId = self.get("seasonId") + self.episodeId = self.get("episodeId") if not self.episodeId else int(self.episodeId) + 1 + title = urllib.unquote_plus(self.get("title")) if self.get("title") else None + + if self.get("label") and self.episodeId == self.get("episodeId"): + label = urllib.unquote_plus(self.get("label")) + elif seasonId and self.episodeId and title: + label = '%s S%02dE%02d.%s (%s)' % ( + title, int(seasonId), int(self.episodeId), self.basename.split('.')[-1], self.basename) + + if seasonId and self.episodeId and label and title: + listitem = xbmcgui.ListItem(label, path=url) + + listitem.setInfo(type='video', infoLabels={'title': label, + 'episode': int(self.episodeId), + 'season': int(seasonId), + 'tvshowtitle': title}) + except: + log('['+author+'Player]: Operation INFO failed!') + + thumbnail = self.get("thumbnail") + if thumbnail: + listitem.setThumbnailImage(urllib.unquote_plus(thumbnail)) + self.display_name = label + + player = xbmc.Player() + player.play(url, listitem) + + xbmc.sleep(2000) # very important, do not edit this, podavan + i = 0 + while not xbmc.abortRequested and not self.isPlaying() and i < 50: + xbmc.sleep(200) + i += 1 + + log('['+author+'Player]: self.isPlaying() = %s, i = %d, xbmc.abortRequested - %s' % (str(self.isPlaying()), i, str(xbmc.abortRequested))) + if not self.isPlaying() or xbmc.abortRequested: + return False + + if self.seek > 0: + log('['+author+'Player]: seekTime - '+str(self.seek)) + self.seekTime(self.seek) + return True + + def setup_subs(self): + if self.subs_dl: + file_status = self.engine.file_status(self.contentId) + subs = [] + filename = os.path.basename(file_status.name) + sub_files = self.engine.list(media_types=[MediaType.SUBTITLES]) + for i in sub_files: + if isSubtitle(filename, i.name): + subs.append(i) + if subs: + log("[AnteoPlayer][setup_subs]: Detected subtitles: %s" % str(subs)) + for sub in subs: + xbmc.Player().setSubtitles(sub.url) + + def loop(self): + debug_counter = 0 + pause = True + with closing( + OverlayText(w=OVERLAY_WIDTH, h=OVERLAY_HEIGHT, alignment=XBFONT_CENTER_X | XBFONT_CENTER_Y)) as overlay: + with nested(self.attach(overlay.show, self.on_playback_paused), + self.attach(overlay.hide, self.on_playback_resumed, self.on_playback_stopped)): + while not xbmc.abortRequested and self.isPlaying(): + #self.print_fulldebug() + status = self.engine.status() + file_status = self.engine.file_status(self.contentId) + self.watchedTime = xbmc.Player().getTime() + self.totalTime = xbmc.Player().getTotalTime() + if self.iterator == 100 and debug_counter < 100: + debug_counter += 1 + else: + self.print_debug(status) + debug_counter=0 + + overlay.text = "\n".join(self._get_status_lines(status, file_status)) + + self.iterator = int(file_status.progress * 100) + + if pause and self.__settings__.getSetting("pause_onplay") == 'true': + pause = False + xbmc.Player().pause() + xbmc.sleep(1000) + + #if not self.seeding_run and self.iterator == 100 and self.seeding: + #self.seeding_run = True + #self.seed(self.contentId) + #self.seeding_status = True + # xbmc.sleep(7000) + + def onPlayBackStarted(self): + for f in self.on_playback_started: + f() + log('[onPlayBackStarted]: '+(str(("video", "play", self.display_name)))) + + def onPlayBackResumed(self): + for f in self.on_playback_resumed: + f() + self.onPlayBackStarted() + + def onPlayBackPaused(self): + for f in self.on_playback_paused: + f() + log('[onPlayBackPaused]: '+(str(("video", "pause", self.display_name)))) + + def onPlayBackStopped(self): + for f in self.on_playback_stopped: + f() + log('[onPlayBackStopped]: '+(str(("video", "stop", self.display_name)))) + + @contextmanager + def attach(self, callback, *events): + for event in events: + event.append(callback) + yield + for event in events: + event.remove(callback) + + def _get_status_lines(self, s, f): + return [ + self.display_name, + "%.2f%% %s" % (f.progress * 100, self.localize(STATE_STRS[s.state]).decode('utf-8')), + "D:%.2f%s U:%.2f%s S:%d P:%d" % (s.download_rate, self.localize('kb/s').decode('utf-8'), + s.upload_rate, self.localize('kb/s').decode('utf-8'), + s.num_seeds, s.num_peers) + ] + + def localize(self, string): + try: + return Localization.localize(string) + except: + return string + + def print_debug(self, status=None): + #FileStatus = namedtuple('FileStatus', "name, save_path, url, size, offset, download, progress, index, media_type") + + #SessionStatus = namedtuple('SessionStatus', "name, state, state_str, error, progress, download_rate, upload_rate, " + # "total_download, total_upload, num_peers, num_seeds, total_seeds, " + # "total_peers") + + #log('[buffer] file_status:'+str(file_status)) + #log('[buffer] status:'+str(status)) + if not status: + status = self.engine.status() + self.engine.check_torrent_error(status) + log('['+author+'Player]: %.2f%% complete (down: %.1f kb/s up: %.1f kb/s peers: %d) %s' % \ + (status.progress * 100, status.download_rate, + status.upload_rate, status.num_peers, status.state_str)) + + def print_fulldebug(self): + status = self.engine.status() + file_status = self.engine.file_status(self.contentId) + log('[buffer] file_status:'+str(file_status)) + log('[buffer] status:'+str(status)) + + def get_ids(self): + contentList = [] + for fs in self.engine.list(): + contentList.append((fs.name, str(fs.index))) + contentList = sorted(contentList, key=lambda x: x[0]) + return get_ids_video(contentList) + + def getContentList(self): + filelist = [] + for fs in self.engine.list(): + stringdata = {"title": ensure_str(fs.name), "size": fs.size, "ind": fs.index, + 'offset': fs.offset} + filelist.append(stringdata) + return filelist + +class OverlayText(object): + def __init__(self, w, h, *args, **kwargs): + self.window = xbmcgui.Window(WINDOW_FULLSCREEN_VIDEO) + viewport_w, viewport_h = self._get_skin_resolution() + # Adjust size based on viewport, we are using 1080p coordinates + w = int(w * viewport_w / VIEWPORT_WIDTH) + h = int(h * viewport_h / VIEWPORT_HEIGHT) + x = (viewport_w - w) / 2 + y = (viewport_h - h) / 2 + self._shown = False + self._text = "" + self._label = xbmcgui.ControlLabel(x, y, w, h, self._text, *args, **kwargs) + self._background = xbmcgui.ControlImage(x, y, w, h, os.path.join(RESOURCES_PATH, "images", "black.png")) + self._background.setColorDiffuse("0xD0000000") + + def show(self): + if not self._shown: + self.window.addControls([self._background, self._label]) + self._shown = True + self._background.setColorDiffuse("0xD0000000") + + def hide(self): + if self._shown: + self._shown = False + self.window.removeControls([self._background, self._label]) + self._background.setColorDiffuse("0xFF000000") + + def close(self): + self.hide() + + @property + def text(self): + return self._text + + @text.setter + def text(self, text): + self._text = text + if self._shown: + self._label.setLabel(self._text) + + # This is so hackish it hurts. + def _get_skin_resolution(self): + import xml.etree.ElementTree as ET + + skin_path = xbmc.translatePath("special://skin/") + tree = ET.parse(os.path.join(skin_path, "addon.xml")) + res = tree.findall("./extension/res")[0] return int(res.attrib["width"]), int(res.attrib["height"]) \ No newline at end of file diff --git a/Libtorrent.py b/Libtorrent.py index fed73c5..25c863b 100644 --- a/Libtorrent.py +++ b/Libtorrent.py @@ -1,607 +1,607 @@ -# -*- coding: utf-8 -*- -''' - Torrenter v2 plugin for XBMC/Kodi - Copyright (C) 2012-2015 Vadim Skorba v1 - DiMartino v2 - https://forums.tvaddons.ag/addon-releases/29224-torrenter-v2.html - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -''' - -import thread -import os -import urllib2 -import hashlib -import re -from StringIO import StringIO -import gzip -import sys - -import xbmc -import xbmcgui -import xbmcvfs -import Localization -from functions import file_encode, isSubtitle, DownloadDB, log, debug, is_writable, vista_check, windows_check -from platform_pulsar import get_platform - -class Libtorrent: - magnetLink = None - startPart = 0 - endPart = 0 - partOffset = 0 - torrentHandle = None - session = None - downloadThread = None - threadComplete = False - lt = None - save_resume_data = None - __settings__ = sys.modules["__main__"].__settings__ - - def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'): - self.platform = get_platform() - self.storageDirectory = storageDirectory - self.torrentFilesPath = os.path.join(self.storageDirectory, torrentFilesDirectory) + os.sep - if not is_writable(self.storageDirectory): - xbmcgui.Dialog().ok(Localization.localize('Torrenter v2'), - Localization.localize('Your storage path is not writable or not local! Please change it in settings!'), - Localization.localize(self.storageDirectory)) - - sys.exit(1) - - try: - from python_libtorrent import get_libtorrent - libtorrent=get_libtorrent() - log('Imported libtorrent v%s from python_libtorrent/%s' %(libtorrent.version, self.platform['system'])) - module=True - except Exception, e: - module=False - log('Error importing python_libtorrent.%s. Exception: %s' %(self.platform['system'], str(e))) - import libtorrent - - try: - if not module: log('Imported libtorrent v' + libtorrent.version + ' from system') - self.lt = libtorrent - del libtorrent - - except Exception, e: - log('Error importing from system. Exception: ' + str(e)) - xbmcgui.Dialog().ok(Localization.localize('python-libtorrent Not Found'), - Localization.localize(self.platform["message"][0]), - Localization.localize(self.platform["message"][1])) - return - - if xbmcvfs.exists(torrentFile): - self.torrentFile = torrentFile - e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) - self.torrentFileInfo = self.lt.torrent_info(e) - elif re.match("^magnet\:.+$", torrentFile): - self.magnetLink = torrentFile - - def saveTorrent(self, torrentUrl): - if re.match("^magnet\:.+$", torrentUrl): - self.magnetLink = torrentUrl - self.magnetToTorrent(torrentUrl) - self.magnetLink = None - return self.torrentFile - else: - if not xbmcvfs.exists(self.torrentFilesPath): - xbmcvfs.mkdirs(self.torrentFilesPath) - torrentFile = self.torrentFilesPath + self.md5( - torrentUrl) + '.torrent' - try: - if not re.match("^http\:.+$", torrentUrl): - content = xbmcvfs.File(torrentUrl, "rb").read() - else: - request = urllib2.Request(torrentUrl) - request.add_header('Referer', torrentUrl) - request.add_header('Accept-encoding', 'gzip') - result = urllib2.urlopen(request) - if result.info().get('Content-Encoding') == 'gzip': - buf = StringIO(result.read()) - f = gzip.GzipFile(fileobj=buf) - content = f.read() - else: - content = result.read() - - localFile = xbmcvfs.File(torrentFile, "w+b") - localFile.write(content) - localFile.close() - except Exception, e: - log('Unable to save torrent file from "' + torrentUrl + '" to "' + torrentFile + '" in Torrent::saveTorrent' + '. Exception: ' + str(e)) - return - if xbmcvfs.exists(torrentFile): - try: - e=self.lt.bdecode(xbmcvfs.File(torrentFile,'rb').read()) - self.torrentFileInfo = self.lt.torrent_info(e) - except Exception, e: - log('Exception: ' + str(e)) - xbmcvfs.delete(torrentFile) - return - baseName = file_encode(os.path.basename(self.getFilePath())) - if not xbmcvfs.exists(self.torrentFilesPath): - xbmcvfs.mkdirs(self.torrentFilesPath) - newFile = self.torrentFilesPath + self.md5(baseName) + '.' + self.md5( - torrentUrl) + '.torrent' # + '.'+ baseName - if xbmcvfs.exists(newFile): - xbmcvfs.delete(newFile) - if not xbmcvfs.exists(newFile): - try: - xbmcvfs.rename(torrentFile, newFile) - except Exception, e: - log('Unable to rename torrent file from %s to %s in Torrent::renameTorrent. Exception: %s' % - (torrentFile, newFile, str(e))) - return - self.torrentFile = newFile - if not self.torrentFileInfo: - e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) - self.torrentFileInfo = self.lt.torrent_info(e) - return self.torrentFile - - def getMagnetInfo(self): - magnetSettings = { - 'url': self.magnetLink, - 'save_path': self.storageDirectory, - 'storage_mode': self.lt.storage_mode_t(0), - 'paused': True, - #'auto_managed': True, - #'duplicate_is_error': True - } - progressBar = xbmcgui.DialogProgress() - progressBar.create(Localization.localize('Please Wait'), Localization.localize('Magnet-link is converting')) - #try: - self.torrentHandle = self.session.add_torrent(magnetSettings) - #except: - # self.torrentHandle = self.lt.add_magnet_uri(self.session, self.magnetLink, magnetSettings) - iterator = 0 - while iterator < 100: - xbmc.sleep(500) - self.torrentHandle.force_dht_announce() - progressBar.update(iterator, Localization.localize('Please Wait'), Localization.localize('Magnet-link is converting')+'.' * (iterator % 4), ' ') - iterator += 1 - if progressBar.iscanceled(): - progressBar.update(0) - progressBar.close() - return - if self.torrentHandle.status().has_metadata: - iterator = 100 - progressBar.update(0) - progressBar.close() - if self.torrentHandle.status().has_metadata: - try: - info = self.torrentHandle.torrent_file() - except: - info = self.torrentHandle.get_torrent_info() - return info - - def magnetToTorrent(self, magnet): - self.magnetLink = magnet - self.initSession() - torrentInfo = self.getMagnetInfo() - if torrentInfo: - try: - torrentFile = self.lt.create_torrent(torrentInfo) - baseName = os.path.basename(self.storageDirectory + os.sep + torrentInfo.files()[0].path) - if not xbmcvfs.exists(self.torrentFilesPath): - xbmcvfs.mkdirs(self.torrentFilesPath) - self.torrentFile = self.torrentFilesPath + self.md5(baseName) + '.torrent' - torentFileHandler = xbmcvfs.File(self.torrentFile, "w+b") - torentFileHandler.write(self.lt.bencode(torrentFile.generate())) - torentFileHandler.close() - e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) - self.torrentFileInfo = self.lt.torrent_info(e) - except: - xbmc.executebuiltin("Notification(%s, %s, 7500)" % (Localization.localize('Error'), Localization.localize( - 'Can\'t download torrent, probably no seeds available.'))) - self.torrentFileInfo = torrentInfo - finally: - self.session.remove_torrent(self.torrentHandle) - self.torrentHandle = None - - def getUploadRate(self): - if None == self.torrentHandle: - return 0 - else: - return self.torrentHandle.status().upload_payload_rate - - def getDownloadRate(self): - if None == self.torrentHandle: - return 0 - else: - return self.torrentHandle.status().download_payload_rate - - def getPeers(self): - if None == self.torrentHandle: - return 0 - else: - return self.torrentHandle.status().num_peers - - def getSeeds(self): - if None == self.torrentHandle: - return 0 - else: - return self.torrentHandle.status().num_seeds - - def getFileSize(self, contentId=0): - return self.getContentList()[contentId]['size'] - - def getFilePath(self, contentId=0): - return os.path.join(self.storageDirectory, self.getContentList()[contentId]['title']) # .decode('utf8') - - def getContentList(self): - filelist = [] - for contentId, contentFile in enumerate(self.torrentFileInfo.files()): - stringdata = {"title": contentFile.path, "size": contentFile.size, "ind": int(contentId), - 'offset': contentFile.offset} - filelist.append(stringdata) - return filelist - - def getSubsIds(self, filename): - subs = [] - for i in self.getContentList(): - if isSubtitle(filename, i['title']): - subs.append((i['ind'], i['title'])) - return subs - - def setUploadLimit(self, bytesPerSecond): - try: - session_settings = self.session.get_settings() - session_settings['upload_rate_limit'] = int(bytesPerSecond) - self.session.set_settings(session_settings) - except: - #0.16 compatibility - self.session.set_upload_rate_limit(int(bytesPerSecond)) - - def setDownloadLimit(self, bytesPerSecond): - try: - session_settings = self.session.get_settings() - session_settings['download_rate_limit'] = int(bytesPerSecond) - self.session.set_settings(session_settings) - except: - #0.16 compatibility - self.session.set_download_rate_limit(int(bytesPerSecond)) - - def md5(self, string): - hasher = hashlib.md5() - try: - hasher.update(string) - except: - hasher.update(string.encode('utf-8', 'ignore')) - return hasher.hexdigest() - - def downloadProcess(self, contentId, encrytion=True): - self.initSession() - if encrytion: - self.encryptSession() - self.startSession() - self.paused = False - db = DownloadDB() - ContentList = self.getContentList() - if contentId != None: contentId = int(contentId) - if len(ContentList) == 1 or contentId not in [None, -1]: - if not contentId: contentId = 0 - title = os.path.basename(ContentList[contentId]['title']) - path = os.path.join(self.storageDirectory, ContentList[contentId]['title']) - type = 'file' - else: - contentId = -1 - title = ContentList[0]['title'].split('\\')[0] - path = os.path.join(self.storageDirectory, title) - type = 'folder' - - add = db.add(title, path, type, {'progress': 0}, 'downloading', self.torrentFile, contentId, - self.storageDirectory) - get = db.get(title) - if add or get[5] == 'stopped': - if get[5] == 'stopped': - db.update_status(get[0], 'downloading') - if contentId not in [None, -1]: - self.continueSession(int(contentId), Offset=0, seeding=False) - else: - for i in range(self.torrentFileInfo.num_pieces()): - self.torrentHandle.piece_priority(i, 6) - thread.start_new_thread(self.downloadLoop, (title,)) - - def downloadLoop(self, title): - db = DownloadDB() - status = 'downloading' - while db.get(title) and status != 'stopped': - xbmc.sleep(3000) - status = db.get_status(title) - if not self.paused: - if status == 'pause': - self.paused = True - self.session.pause() - else: - if status != 'pause': - self.paused = False - self.session.resume() - s = self.torrentHandle.status() - info = {} - info['upload'] = s.upload_payload_rate - info['download'] = s.download_payload_rate - info['peers'] = s.num_peers - info['seeds'] = s.num_seeds - iterator = int(s.progress * 100) - info['progress'] = iterator - db.update(title, info) - self.debug() - self.session.remove_torrent(self.torrentHandle) - return - - def initSession(self): - self.session = self.lt.session() - self.session.set_alert_mask(self.lt.alert.category_t.error_notification | self.lt.alert.category_t.status_notification | self.lt.alert.category_t.storage_notification) - #self.session.set_alert_mask(self.lt.alert.category_t.all_categories) - self.session.add_dht_router("router.bittorrent.com", 6881) - self.session.add_dht_router("router.utorrent.com", 6881) - self.session.start_dht() - self.session.start_lsd() - self.session.start_upnp() - self.session.start_natpmp() - try: - port = int(self.__settings__.getSetting('listen_port')) - self.session.listen_on(port, port+10) - except: - try: - log('listen_on(%d, %d) error' %(port, port+10)) - except: - log('listen_port %s error' %(self.__settings__.getSetting('listen_port'))) - - pc_config = int(self.__settings__.getSetting('pc_config')) - - # Session settings - try: - session_settings = self.session.get_settings() - # - session_settings['announce_to_all_tiers'] = True - session_settings['announce_to_all_trackers'] = True - session_settings['peer_connect_timeout'] = 2 - session_settings['rate_limit_ip_overhead'] = True - session_settings['request_timeout'] = 1 - session_settings['torrent_connect_boost'] = 50 - session_settings['user_agent'] = 'uTorrent/2200(24683)' - if pc_config == 0: - #good pc - session_settings['connections_limit'] = 200 - session_settings['unchoke_slots_limit'] = 10 - session_settings['connection_speed'] = 200 - session_settings['file_pool_size'] = 40 - elif pc_config == 1: - #bad pc/router - session_settings['connections_limit'] = 100 - session_settings['half_open_limit'] = (lambda: windows_check() and - (lambda: vista_check() and 4 or 8)() or 50)() - session_settings['unchoke_slots_limit'] = 4 - session_settings['connection_speed'] = 100 - session_settings['file_pool_size'] = 40 - - #session_settings['cache_size'] = 0 - #session_settings['use_read_cache'] = False - - except: - #0.15 compatibility - log('[initSession]: Session settings 0.15 compatibility') - session_settings = self.session.settings() - - session_settings.announce_to_all_tiers = True - session_settings.announce_to_all_trackers = True - session_settings.connection_speed = 100 - session_settings.peer_connect_timeout = 2 - session_settings.rate_limit_ip_overhead = True - session_settings.request_timeout = 1 - session_settings.torrent_connect_boost = 100 - session_settings.user_agent = 'uTorrent/2200(24683)' - # - self.session.set_settings(session_settings) - - def encryptSession(self): - # Encryption settings - log('Encryption enabling...') - try: - encryption_settings = self.lt.pe_settings() - encryption_settings.out_enc_policy = self.lt.enc_policy(self.lt.enc_policy.forced) - encryption_settings.in_enc_policy = self.lt.enc_policy(self.lt.enc_policy.forced) - encryption_settings.allowed_enc_level = self.lt.enc_level.both - encryption_settings.prefer_rc4 = True - self.session.set_pe_settings(encryption_settings) - log('Encryption on!') - except Exception, e: - log('Encryption failed! Exception: ' + str(e)) - pass - - def startSession(self): - if self.magnetLink: - self.torrentFileInfo = self.getMagnetInfo() - torrent_info={'ti': self.torrentFileInfo, - 'save_path': self.storageDirectory, - 'flags': 0x300, - #'storage_mode': self.lt.storage_mode_t(1), - 'paused': False, - #'auto_managed': False, - 'duplicate_is_error': False - } - if self.save_resume_data: - log('loading resume data') - torrent_info['resume_data']=self.save_resume_data - else: - resume_file=self.resume_data_path() - if xbmcvfs.exists(resume_file): - log('loading resume data from file '+resume_file) - try: - resumDataFile=xbmcvfs.File(resume_file,'rb') - self.save_resume_data=resumDataFile.read() - resumDataFile.close() - torrent_info['resume_data']=self.save_resume_data - - except: - log('Failed to load resume data from file '+ resume_file) - self.torrentHandle = self.session.add_torrent(torrent_info) - self.torrentHandle.set_sequential_download(True) - self.torrentHandle.set_max_connections(60) - self.torrentHandle.set_max_uploads(-1) - self.stopSession() - - def stopSession(self): - for i in range(self.torrentFileInfo.num_pieces()): - self.torrentHandle.piece_priority(i, 0) - - def continueSession(self, contentId=0, Offset=0, seeding=False, isMP4=False): - self.piece_length = self.torrentFileInfo.piece_length() - selectedFileInfo = self.getContentList()[contentId] - if not Offset: - Offset = selectedFileInfo['size'] / (1024 * 1024) - self.partOffset = (Offset * 1024 * 1024 / self.piece_length) + 1 - self.startPart = selectedFileInfo['offset'] / self.piece_length - self.endPart = int((selectedFileInfo['offset'] + selectedFileInfo['size']) / self.piece_length) - multiplier = self.partOffset / 5 - log('continueSession: multiplier ' + str(multiplier)) - for i in range(self.startPart, self.startPart + self.partOffset): - if i <= self.endPart: - self.torrentHandle.piece_priority(i, 7) - if isMP4 and i % multiplier == 0: - self.torrentHandle.piece_priority(self.endPart - i / multiplier, 7) - if multiplier >= i: - self.torrentHandle.piece_priority(self.endPart - i, 7) - - def checkThread(self): - if self.threadComplete == True: - self.resume_data() - log('checkThread KIIIIIIIIIIILLLLLLLLLLLLLLL') - try: - self.session.remove_torrent(self.torrentHandle) - except: - log('RuntimeError: invalid torrent handle used') - self.session.stop_natpmp() - self.session.stop_upnp() - self.session.stop_lsd() - self.session.stop_dht() - - def resume_data(self): - wasPaused=self.session.is_paused() - self.session.pause() - self.save_resume_data=None - - try: - if not self.torrentHandle.is_valid(): - return - status = self.torrentHandle.status() - if not status.has_metadata: - return - if not status.need_save_resume: - return - - log('[save_resume_data]: waiting for alert...') - self.torrentHandle.save_resume_data(self.lt.save_resume_flags_t.flush_disk_cache) - received=False - while not received: - self.session.wait_for_alert(1000) - a = self.session.pop_alert() - log('[save_resume_data]: ['+str(type(a))+'] the alert '+str(a)+' is received') - if type(a) == self.lt.save_resume_data_alert: - received = True - debug('[save_resume_data]: '+str(dir(a))) - self.save_resume_data=self.lt.bencode(a.resume_data) - log('[save_resume_data]: the torrent resume data are received') - try: - resumeFileHandler = xbmcvfs.File(self.resume_data_path(), "w+b") - resumeFileHandler.write(self.save_resume_data) - resumeFileHandler.close() - log('[save_resume_data]: the torrent resume data to file' + self.resume_data_path()) - except: - log('[save_resume_data]: failed to save the torrent resume data to file') - elif type(a) == self.lt.save_resume_data_failed_alert: - received = True - log('[save_resume_data]: save_resume_data() failed') - log('[save_resume_data]: done.') - - finally: - if not wasPaused: - self.session.resume() - - def debug(self): - #try: - if 1==1: - # log(str(self.getFilePath(0))) - s = self.torrentHandle.status() - #get_cache_status=self.session.get_cache_status() - #log('get_cache_status - %s/%s' % (str(get_cache_status.blocks_written), str(get_cache_status.blocks_read))) - # get_settings=self.torrentHandle.status - # log(s.num_pieces) - #priorities = self.torrentHandle.piece_priorities() - #log(str(priorities)) - - state_str = ['queued', 'checking', 'downloading metadata', - 'downloading', 'finished', 'seeding', 'allocating', 'checking fastresume'] - log('[%s] %.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % \ - (self.lt.version, s.progress * 100, s.download_rate / 1000, - s.upload_rate / 1000, s.num_peers, state_str[s.state])) - #log('%s %s' % (self.get_debug_info('dht_state'), self.get_debug_info('trackers_sum'))) - #debug('TRACKERS:' +str(self.torrentHandle.trackers())) - - #received=self.session.pop_alert() - #while received: - # debug('[debug]: ['+str(type(received))+'] the alert '+str(received)+' is received') - # #if type(received) == self.lt.torrent_finished_alert: - # # self.session.pause() - # received = self.session.pop_alert() - - #log('is_dht_running:' +str(self.session.is_dht_running())) - #log('dht_state:' +str(self.session.dht_state())) - #i = 0 - # for t in s.pieces: - # if t: i=i+1 - #log(str(self.session.pop_alert()) - # log(str(s.pieces[self.startPart:self.endPart])) - # log('True pieces: %d' % i) - # log(s.current_tracker) - # log(str(s.pieces)) - #except: - else: - log('debug error') - pass - - def get_debug_info(self, info): - result='' - if info in ['trackers_full','trackers_sum']: - trackers=[] - for tracker in self.torrentHandle.trackers(): - trackers.append((tracker['url'], tracker['fails'], tracker['verified'])) - if info=='trackers_full': - for url, fails, verified in trackers: - result=result+'%s: f=%d, v=%s' %(url, fails, str(verified)) - if info=='trackers_sum': - fails_sum, verified_sum = 0, 0 - for url, fails, verified in trackers: - fails_sum+=fails - if verified: verified_sum+=1 - result=result+'Trackers: verified %d/%d, fails=%d' %(verified_sum, len(trackers)-1, fails_sum) - if info=='dht_state': - is_dht_running='ON' if self.session.is_dht_running() else 'OFF' - try: - nodes=self.session.dht_state().get('nodes') - except: - nodes=None - nodes=len(nodes) if nodes else 0 - result='DHT: %s (%d)' % (is_dht_running, nodes) - return result - - def dump(self, obj): - for attr in dir(obj): - try: - log("'%s':'%s'," % (attr, getattr(obj, attr))) - except: - pass - - def resume_data_path(self): - path=self.torrentFile + ".resume_data" - return path +# -*- coding: utf-8 -*- +''' + Torrenter v2 plugin for XBMC/Kodi + Copyright (C) 2012-2015 Vadim Skorba v1 - DiMartino v2 + https://forums.tvaddons.ag/addon-releases/29224-torrenter-v2.html + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +import thread +import os +import urllib2 +import hashlib +import re +from StringIO import StringIO +import zlib +import sys + +import xbmc +import xbmcgui +import xbmcvfs +import Localization +from functions import file_encode, isSubtitle, DownloadDB, log, debug, is_writable, vista_check, windows_check +from platform_pulsar import get_platform + +class Libtorrent: + magnetLink = None + startPart = 0 + endPart = 0 + partOffset = 0 + torrentHandle = None + session = None + downloadThread = None + threadComplete = False + lt = None + save_resume_data = None + __settings__ = sys.modules["__main__"].__settings__ + + def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'): + self.platform = get_platform() + self.storageDirectory = storageDirectory + self.torrentFilesPath = os.path.join(self.storageDirectory, torrentFilesDirectory) + os.sep + if not is_writable(self.storageDirectory): + xbmcgui.Dialog().ok(Localization.localize('Torrenter v2'), + Localization.localize('Your storage path is not writable or not local! Please change it in settings!'), + Localization.localize(self.storageDirectory)) + + sys.exit(1) + + try: + from python_libtorrent import get_libtorrent + libtorrent=get_libtorrent() + log('Imported libtorrent v%s from python_libtorrent/%s' %(libtorrent.version, self.platform['system'])) + module=True + except Exception, e: + module=False + log('Error importing python_libtorrent.%s. Exception: %s' %(self.platform['system'], str(e))) + import libtorrent + + try: + if not module: log('Imported libtorrent v' + libtorrent.version + ' from system') + self.lt = libtorrent + del libtorrent + + except Exception, e: + log('Error importing from system. Exception: ' + str(e)) + xbmcgui.Dialog().ok(Localization.localize('python-libtorrent Not Found'), + Localization.localize(self.platform["message"][0]), + Localization.localize(self.platform["message"][1])) + return + + if xbmcvfs.exists(torrentFile): + self.torrentFile = torrentFile + e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) + self.torrentFileInfo = self.lt.torrent_info(e) + elif re.match("^magnet\:.+$", torrentFile): + self.magnetLink = torrentFile + + def saveTorrent(self, torrentUrl): + if re.match("^magnet\:.+$", torrentUrl): + self.magnetLink = torrentUrl + self.magnetToTorrent(torrentUrl) + self.magnetLink = None + return self.torrentFile + else: + if not xbmcvfs.exists(self.torrentFilesPath): + xbmcvfs.mkdirs(self.torrentFilesPath) + torrentFile = self.torrentFilesPath + self.md5( + torrentUrl) + '.torrent' + try: + if not re.match("^http\:.+$", torrentUrl): + content = xbmcvfs.File(torrentUrl, "rb").read() + else: + request = urllib2.Request(torrentUrl) + request.add_header('Referer', torrentUrl) + request.add_header('Accept-encoding', 'gzip') + result = urllib2.urlopen(request) + if result.info().get('Content-Encoding') == 'gzip': + buf = StringIO(result.read()) + decomp = zlib.decompressobj(16 + zlib.MAX_WBITS) + content = decomp.decompress(buf.getvalue()) + else: + content = result.read() + + localFile = xbmcvfs.File(torrentFile, "w+b") + localFile.write(content) + localFile.close() + except Exception, e: + log('Unable to save torrent file from "' + torrentUrl + '" to "' + torrentFile + '" in Torrent::saveTorrent' + '. Exception: ' + str(e)) + return + if xbmcvfs.exists(torrentFile): + try: + e=self.lt.bdecode(xbmcvfs.File(torrentFile,'rb').read()) + self.torrentFileInfo = self.lt.torrent_info(e) + except Exception, e: + log('Exception: ' + str(e)) + xbmcvfs.delete(torrentFile) + return + baseName = file_encode(os.path.basename(self.getFilePath())) + if not xbmcvfs.exists(self.torrentFilesPath): + xbmcvfs.mkdirs(self.torrentFilesPath) + newFile = self.torrentFilesPath + self.md5(baseName) + '.' + self.md5( + torrentUrl) + '.torrent' # + '.'+ baseName + if xbmcvfs.exists(newFile): + xbmcvfs.delete(newFile) + if not xbmcvfs.exists(newFile): + try: + xbmcvfs.rename(torrentFile, newFile) + except Exception, e: + log('Unable to rename torrent file from %s to %s in Torrent::renameTorrent. Exception: %s' % + (torrentFile, newFile, str(e))) + return + self.torrentFile = newFile + if not self.torrentFileInfo: + e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) + self.torrentFileInfo = self.lt.torrent_info(e) + return self.torrentFile + + def getMagnetInfo(self): + magnetSettings = { + 'url': self.magnetLink, + 'save_path': self.storageDirectory, + 'storage_mode': self.lt.storage_mode_t(0), + 'paused': True, + #'auto_managed': True, + #'duplicate_is_error': True + } + progressBar = xbmcgui.DialogProgress() + progressBar.create(Localization.localize('Please Wait'), Localization.localize('Magnet-link is converting')) + #try: + self.torrentHandle = self.session.add_torrent(magnetSettings) + #except: + # self.torrentHandle = self.lt.add_magnet_uri(self.session, self.magnetLink, magnetSettings) + iterator = 0 + while iterator < 100: + xbmc.sleep(500) + self.torrentHandle.force_dht_announce() + progressBar.update(iterator, Localization.localize('Please Wait'), Localization.localize('Magnet-link is converting')+'.' * (iterator % 4), ' ') + iterator += 1 + if progressBar.iscanceled(): + progressBar.update(0) + progressBar.close() + return + if self.torrentHandle.status().has_metadata: + iterator = 100 + progressBar.update(0) + progressBar.close() + if self.torrentHandle.status().has_metadata: + try: + info = self.torrentHandle.torrent_file() + except: + info = self.torrentHandle.get_torrent_info() + return info + + def magnetToTorrent(self, magnet): + self.magnetLink = magnet + self.initSession() + torrentInfo = self.getMagnetInfo() + if torrentInfo: + try: + torrentFile = self.lt.create_torrent(torrentInfo) + baseName = os.path.basename(self.storageDirectory + os.sep + torrentInfo.files()[0].path) + if not xbmcvfs.exists(self.torrentFilesPath): + xbmcvfs.mkdirs(self.torrentFilesPath) + self.torrentFile = self.torrentFilesPath + self.md5(baseName) + '.torrent' + torentFileHandler = xbmcvfs.File(self.torrentFile, "w+b") + torentFileHandler.write(self.lt.bencode(torrentFile.generate())) + torentFileHandler.close() + e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) + self.torrentFileInfo = self.lt.torrent_info(e) + except: + xbmc.executebuiltin("Notification(%s, %s, 7500)" % (Localization.localize('Error'), Localization.localize( + 'Can\'t download torrent, probably no seeds available.'))) + self.torrentFileInfo = torrentInfo + finally: + self.session.remove_torrent(self.torrentHandle) + self.torrentHandle = None + + def getUploadRate(self): + if None == self.torrentHandle: + return 0 + else: + return self.torrentHandle.status().upload_payload_rate + + def getDownloadRate(self): + if None == self.torrentHandle: + return 0 + else: + return self.torrentHandle.status().download_payload_rate + + def getPeers(self): + if None == self.torrentHandle: + return 0 + else: + return self.torrentHandle.status().num_peers + + def getSeeds(self): + if None == self.torrentHandle: + return 0 + else: + return self.torrentHandle.status().num_seeds + + def getFileSize(self, contentId=0): + return self.getContentList()[contentId]['size'] + + def getFilePath(self, contentId=0): + return os.path.join(self.storageDirectory, self.getContentList()[contentId]['title']) # .decode('utf8') + + def getContentList(self): + filelist = [] + for contentId, contentFile in enumerate(self.torrentFileInfo.files()): + stringdata = {"title": contentFile.path, "size": contentFile.size, "ind": int(contentId), + 'offset': contentFile.offset} + filelist.append(stringdata) + return filelist + + def getSubsIds(self, filename): + subs = [] + for i in self.getContentList(): + if isSubtitle(filename, i['title']): + subs.append((i['ind'], i['title'])) + return subs + + def setUploadLimit(self, bytesPerSecond): + try: + session_settings = self.session.get_settings() + session_settings['upload_rate_limit'] = int(bytesPerSecond) + self.session.set_settings(session_settings) + except: + #0.16 compatibility + self.session.set_upload_rate_limit(int(bytesPerSecond)) + + def setDownloadLimit(self, bytesPerSecond): + try: + session_settings = self.session.get_settings() + session_settings['download_rate_limit'] = int(bytesPerSecond) + self.session.set_settings(session_settings) + except: + #0.16 compatibility + self.session.set_download_rate_limit(int(bytesPerSecond)) + + def md5(self, string): + hasher = hashlib.md5() + try: + hasher.update(string) + except: + hasher.update(string.encode('utf-8', 'ignore')) + return hasher.hexdigest() + + def downloadProcess(self, contentId, encrytion=True): + self.initSession() + if encrytion: + self.encryptSession() + self.startSession() + self.paused = False + db = DownloadDB() + ContentList = self.getContentList() + if contentId != None: contentId = int(contentId) + if len(ContentList) == 1 or contentId not in [None, -1]: + if not contentId: contentId = 0 + title = os.path.basename(ContentList[contentId]['title']) + path = os.path.join(self.storageDirectory, ContentList[contentId]['title']) + type = 'file' + else: + contentId = -1 + title = ContentList[0]['title'].split('\\')[0] + path = os.path.join(self.storageDirectory, title) + type = 'folder' + + add = db.add(title, path, type, {'progress': 0}, 'downloading', self.torrentFile, contentId, + self.storageDirectory) + get = db.get(title) + if add or get[5] == 'stopped': + if get[5] == 'stopped': + db.update_status(get[0], 'downloading') + if contentId not in [None, -1]: + self.continueSession(int(contentId), Offset=0, seeding=False) + else: + for i in range(self.torrentFileInfo.num_pieces()): + self.torrentHandle.piece_priority(i, 6) + thread.start_new_thread(self.downloadLoop, (title,)) + + def downloadLoop(self, title): + db = DownloadDB() + status = 'downloading' + while db.get(title) and status != 'stopped': + xbmc.sleep(3000) + status = db.get_status(title) + if not self.paused: + if status == 'pause': + self.paused = True + self.session.pause() + else: + if status != 'pause': + self.paused = False + self.session.resume() + s = self.torrentHandle.status() + info = {} + info['upload'] = s.upload_payload_rate + info['download'] = s.download_payload_rate + info['peers'] = s.num_peers + info['seeds'] = s.num_seeds + iterator = int(s.progress * 100) + info['progress'] = iterator + db.update(title, info) + self.debug() + self.session.remove_torrent(self.torrentHandle) + return + + def initSession(self): + self.session = self.lt.session() + self.session.set_alert_mask(self.lt.alert.category_t.error_notification | self.lt.alert.category_t.status_notification | self.lt.alert.category_t.storage_notification) + #self.session.set_alert_mask(self.lt.alert.category_t.all_categories) + self.session.add_dht_router("router.bittorrent.com", 6881) + self.session.add_dht_router("router.utorrent.com", 6881) + self.session.start_dht() + self.session.start_lsd() + self.session.start_upnp() + self.session.start_natpmp() + try: + port = int(self.__settings__.getSetting('listen_port')) + self.session.listen_on(port, port+10) + except: + try: + log('listen_on(%d, %d) error' %(port, port+10)) + except: + log('listen_port %s error' %(self.__settings__.getSetting('listen_port'))) + + pc_config = int(self.__settings__.getSetting('pc_config')) + + # Session settings + try: + session_settings = self.session.get_settings() + # + session_settings['announce_to_all_tiers'] = True + session_settings['announce_to_all_trackers'] = True + session_settings['peer_connect_timeout'] = 2 + session_settings['rate_limit_ip_overhead'] = True + session_settings['request_timeout'] = 1 + session_settings['torrent_connect_boost'] = 50 + session_settings['user_agent'] = 'uTorrent/2200(24683)' + if pc_config == 0: + #good pc + session_settings['connections_limit'] = 200 + session_settings['unchoke_slots_limit'] = 10 + session_settings['connection_speed'] = 200 + session_settings['file_pool_size'] = 40 + elif pc_config == 1: + #bad pc/router + session_settings['connections_limit'] = 100 + session_settings['half_open_limit'] = (lambda: windows_check() and + (lambda: vista_check() and 4 or 8)() or 50)() + session_settings['unchoke_slots_limit'] = 4 + session_settings['connection_speed'] = 100 + session_settings['file_pool_size'] = 40 + + #session_settings['cache_size'] = 0 + #session_settings['use_read_cache'] = False + + except: + #0.15 compatibility + log('[initSession]: Session settings 0.15 compatibility') + session_settings = self.session.settings() + + session_settings.announce_to_all_tiers = True + session_settings.announce_to_all_trackers = True + session_settings.connection_speed = 100 + session_settings.peer_connect_timeout = 2 + session_settings.rate_limit_ip_overhead = True + session_settings.request_timeout = 1 + session_settings.torrent_connect_boost = 100 + session_settings.user_agent = 'uTorrent/2200(24683)' + # + self.session.set_settings(session_settings) + + def encryptSession(self): + # Encryption settings + log('Encryption enabling...') + try: + encryption_settings = self.lt.pe_settings() + encryption_settings.out_enc_policy = self.lt.enc_policy(self.lt.enc_policy.forced) + encryption_settings.in_enc_policy = self.lt.enc_policy(self.lt.enc_policy.forced) + encryption_settings.allowed_enc_level = self.lt.enc_level.both + encryption_settings.prefer_rc4 = True + self.session.set_pe_settings(encryption_settings) + log('Encryption on!') + except Exception, e: + log('Encryption failed! Exception: ' + str(e)) + pass + + def startSession(self): + if self.magnetLink: + self.torrentFileInfo = self.getMagnetInfo() + torrent_info={'ti': self.torrentFileInfo, + 'save_path': self.storageDirectory, + 'flags': 0x300, + #'storage_mode': self.lt.storage_mode_t(1), + 'paused': False, + #'auto_managed': False, + #'duplicate_is_error': True + } + if self.save_resume_data: + log('loading resume data') + torrent_info['resume_data']=self.save_resume_data + else: + resume_file=self.resume_data_path() + if xbmcvfs.exists(resume_file): + log('loading resume data from file '+resume_file) + try: + resumDataFile=xbmcvfs.File(resume_file,'rb') + self.save_resume_data=resumDataFile.read() + resumDataFile.close() + torrent_info['resume_data']=self.save_resume_data + + except: + log('Failed to load resume data from file '+ resume_file) + self.torrentHandle = self.session.add_torrent(torrent_info) + self.torrentHandle.set_sequential_download(True) + self.torrentHandle.set_max_connections(60) + self.torrentHandle.set_max_uploads(-1) + self.stopSession() + + def stopSession(self): + for i in range(self.torrentFileInfo.num_pieces()): + self.torrentHandle.piece_priority(i, 0) + + def continueSession(self, contentId=0, Offset=0, seeding=False, isMP4=False): + self.piece_length = self.torrentFileInfo.piece_length() + selectedFileInfo = self.getContentList()[contentId] + if not Offset: + Offset = selectedFileInfo['size'] / (1024 * 1024) + self.partOffset = (Offset * 1024 * 1024 / self.piece_length) + 1 + self.startPart = selectedFileInfo['offset'] / self.piece_length + self.endPart = int((selectedFileInfo['offset'] + selectedFileInfo['size']) / self.piece_length) + multiplier = self.partOffset / 5 + log('continueSession: multiplier ' + str(multiplier)) + for i in range(self.startPart, self.startPart + self.partOffset): + if i <= self.endPart: + self.torrentHandle.piece_priority(i, 7) + if isMP4 and i % multiplier == 0: + self.torrentHandle.piece_priority(self.endPart - i / multiplier, 7) + if multiplier >= i: + self.torrentHandle.piece_priority(self.endPart - i, 7) + + def checkThread(self): + if self.threadComplete == True: + self.resume_data() + log('checkThread KIIIIIIIIIIILLLLLLLLLLLLLLL') + try: + self.session.remove_torrent(self.torrentHandle) + except: + log('RuntimeError: invalid torrent handle used') + self.session.stop_natpmp() + self.session.stop_upnp() + self.session.stop_lsd() + self.session.stop_dht() + + def resume_data(self): + wasPaused=self.session.is_paused() + self.session.pause() + self.save_resume_data=None + + try: + if not self.torrentHandle.is_valid(): + return + status = self.torrentHandle.status() + if not status.has_metadata: + return + if not status.need_save_resume: + return + + log('[save_resume_data]: waiting for alert...') + self.torrentHandle.save_resume_data(self.lt.save_resume_flags_t.flush_disk_cache) + received=False + while not received: + self.session.wait_for_alert(1000) + a = self.session.pop_alert() + log('[save_resume_data]: ['+str(type(a))+'] the alert '+str(a)+' is received') + if type(a) == self.lt.save_resume_data_alert: + received = True + debug('[save_resume_data]: '+str(dir(a))) + self.save_resume_data=self.lt.bencode(a.resume_data) + log('[save_resume_data]: the torrent resume data are received') + try: + resumeFileHandler = xbmcvfs.File(self.resume_data_path(), "w+b") + resumeFileHandler.write(self.save_resume_data) + resumeFileHandler.close() + log('[save_resume_data]: the torrent resume data to file' + self.resume_data_path()) + except: + log('[save_resume_data]: failed to save the torrent resume data to file') + elif type(a) == self.lt.save_resume_data_failed_alert: + received = True + log('[save_resume_data]: save_resume_data() failed') + log('[save_resume_data]: done.') + + finally: + if not wasPaused: + self.session.resume() + + def debug(self): + #try: + if 1==1: + # log(str(self.getFilePath(0))) + s = self.torrentHandle.status() + #get_cache_status=self.session.get_cache_status() + #log('get_cache_status - %s/%s' % (str(get_cache_status.blocks_written), str(get_cache_status.blocks_read))) + # get_settings=self.torrentHandle.status + # log(s.num_pieces) + #priorities = self.torrentHandle.piece_priorities() + #log(str(priorities)) + + state_str = ['queued', 'checking', 'downloading metadata', + 'downloading', 'finished', 'seeding', 'allocating', 'checking fastresume'] + log('[%s] %.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % \ + (self.lt.version, s.progress * 100, s.download_rate / 1000, + s.upload_rate / 1000, s.num_peers, state_str[s.state])) + #log('%s %s' % (self.get_debug_info('dht_state'), self.get_debug_info('trackers_sum'))) + #debug('TRACKERS:' +str(self.torrentHandle.trackers())) + + #received=self.session.pop_alert() + #while received: + # debug('[debug]: ['+str(type(received))+'] the alert '+str(received)+' is received') + # #if type(received) == self.lt.torrent_finished_alert: + # # self.session.pause() + # received = self.session.pop_alert() + + #log('is_dht_running:' +str(self.session.is_dht_running())) + #log('dht_state:' +str(self.session.dht_state())) + #i = 0 + # for t in s.pieces: + # if t: i=i+1 + #log(str(self.session.pop_alert()) + # log(str(s.pieces[self.startPart:self.endPart])) + # log('True pieces: %d' % i) + # log(s.current_tracker) + # log(str(s.pieces)) + #except: + else: + log('debug error') + pass + + def get_debug_info(self, info): + result='' + if info in ['trackers_full','trackers_sum']: + trackers=[] + for tracker in self.torrentHandle.trackers(): + trackers.append((tracker['url'], tracker['fails'], tracker['verified'])) + if info=='trackers_full': + for url, fails, verified in trackers: + result=result+'%s: f=%d, v=%s' %(url, fails, str(verified)) + if info=='trackers_sum': + fails_sum, verified_sum = 0, 0 + for url, fails, verified in trackers: + fails_sum+=fails + if verified: verified_sum+=1 + result=result+'Trackers: verified %d/%d, fails=%d' %(verified_sum, len(trackers)-1, fails_sum) + if info=='dht_state': + is_dht_running='ON' if self.session.is_dht_running() else 'OFF' + try: + nodes=self.session.dht_state().get('nodes') + except: + nodes=None + nodes=len(nodes) if nodes else 0 + result='DHT: %s (%d)' % (is_dht_running, nodes) + return result + + def dump(self, obj): + for attr in dir(obj): + try: + log("'%s':'%s'," % (attr, getattr(obj, attr))) + except: + pass + + def resume_data_path(self): + path=self.torrentFile + ".resume_data" + return path