# -*- coding: utf-8 -*- ''' Torrenter v2 plugin for XBMC/Kodi Copyright (C) 2012-2015 Vadim Skorba v1 - DiMartino v2 http://forum.kodi.tv/showthread.php?tid=214366 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 os from contextlib import contextmanager, closing, nested from resources.btclient.argparse import Namespace import xbmc import xbmcgui import Downloader import xbmcgui import xbmcvfs import Localization from platform_pulsar import get_platform from resources.btclient.btclient import * from functions import showMessage, DownloadDB, get_ids_video, log, debug, clearStorage from Player import OverlayText 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 class BTClientPlayer(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 basename = '' def __del__(self): print '!!!!!!!!!!!!!!!!!! DIED !!! '+self.__class__.__name__ def __init__(self, userStorageDirectory, torrentUrl, params={}): print '!!!!!!!!!!!!!!!!!! BORN '+self.__class__.__name__ self.userStorageDirectory = userStorageDirectory self.torrentUrl = torrentUrl xbmc.Player.__init__(self) log("[BTClientPlayer] Initalized") self.params = params self.get = self.params.get self.contentId = int(self.get("url")) self.platform = get_platform() self.init() self.torrent = Downloader.Torrent(self.userStorageDirectory, self.torrentUrl, self.torrentFilesDirectory).player self.lt=self.torrent.lt try: if self.get("url2"): self.ids_video = urllib.unquote_plus(self.get("url2")).split(',') else: self.ids_video = self.get_ids() except: pass args=Namespace(bt_download_limit=self.download_limit,#KB bt_upload_limit=self.upload_limit, choose_subtitles=False, clear_older=0, debug_log='',#os.path.join(self.userStorageDirectory, 'log.txt'), delete_on_finish=False, directory=self.userStorageDirectory, listen_port_max=6891,# listen_port_min=6881, no_resume=False, player='kodi', port=5001, print_pieces=False, quiet=False, stdin=False, stream=True, subtitles=None, trace=False, content_id=self.contentId, url=self.torrentUrl) args=main(args) #config self.free_port = args.port log('BTClientPlayer: args '+str(args)) self.btclient=self.stream(args, BTClient) def stream(self, args, client_class): self.c = client_class(args.directory, args=args, lt=self.lt) try: while True: try: s = socket.socket() res = s.connect_ex(('127.0.0.1', self.free_port)) if res: break finally: s.close() self.free_port += 1 self.server = StreamServer(('127.0.0.1', self.free_port), BTFileHandler, allow_range=True, status_fn=self.c.get_normalized_status) log('Started http server on port %d' % self.free_port) self.server.run() log('Starting btclient - libtorrent version %s' % self.lt.version) self.c.start_url(args.url) self.setup_torrent() if self.buffer(): f = self.c._file self.server.set_file(f) self.setup_play() while True: if self.setup_play(): log('************************************* GOING LOOP') #self.torrent.continueSession(self.contentId) self.loop() else: break log('************************************* GO NEXT?') if self.next_dl and self.next_dling and isinstance(self.next_contentId, int) and self.iterator == 100: self.contentId = self.next_contentId continue log('************************************* NO! break') break 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) log('Play ended') if self.server: self.server.stop() except Exception: traceback.print_exc() finally: self.on_exit() def init(self): self.next_dl = True if self.__settings__.getSetting('next_dl') == 'true' and self.ids_video else False log('[BTClientPlayer]: init next_dl - ' + str(self.next_dl)) 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.fullSize = 0 if self.__settings__.getSetting("upload_limit") == "": self.upload_limit = 0.0 else: self.upload_limit = float(self.__settings__.getSetting("upload_limit")) / 8 * 1024 if self.__settings__.getSetting("download_limit") == "": self.download_limit = 0.0 else: self.download_limit = float(self.__settings__.getSetting("download_limit")) / 8 * 1024 def setup_torrent(self): pass #if self.__settings__.getSetting('encryption') == 'true': # self.c.encrypt() #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) def buffer(self): iterator = 0 progressBar = xbmcgui.DialogProgress() progressBar.create(self.localize('Please Wait') + str(' [%s]' % str(self.lt.version)), self.localize('Seeds searching.')) while iterator < 100 or not self.c.is_file_ready:# not self.c.is_file_ready iterator = 0 ready_list=[] status = self.c.get_normalized_status() conditions=[status['state'] in ['downloading', 'finished', 'seeding'], status['desired_rate'] > 0 or status['progress'] > 0.02, status['progress'] > 0, status['desired_rate'] > 0 or status['progress'] > 0.02 and (status['download_rate'] > status['desired_rate'] or status['download_rate'] * status['progress'] * 100 > status['desired_rate'])] for cond in conditions: if cond: ready_list.append(True) iterator+=100/len(conditions) else: ready_list.append(False) speedsText = '%s: %s Mbit/s %s %s: %s Mbit/s' % (self.localize('Bitrate'), str(int(status['desired_rate'] * 8 / (1024 * 1024))) if status['desired_rate'] else 0, '[COLOR=green]>[/COLOR]' if ready_list[3] else '[COLOR=red]<[/COLOR]', self.localize('Download speed'),str(status['download_rate'] * 8 / 1000000)) if status['state'] in ['queued','checking','checking fastresume'] or (status['progress'] == 0 and status['num_pieces'] > 0): progressBar.update(iterator, self.localize('Checking preloaded files...'), speedsText, ' ') elif status['state'] in ['downloading', 'finished', 'seeding']: dialogText = self.localize('Preloaded: ') + '%s MB / %s MB' % \ (str(status['downloaded'] / 1024 / 1024), str(status['total_size'] / 1024 / 1024)) peersText = '[%s: %s; %s: %s]' % (self.localize('Seeds'), str(status['seeds_connected']), self.localize('Peers'), str(status['peers_connected']),) progressBar.update(iterator, peersText, speedsText, dialogText, ) else: progressBar.update(iterator, self.localize('UNKNOWN STATUS'), ' ', ' ') if progressBar.iscanceled(): self.on_exit() break xbmc.sleep(1000) progressBar.update(0) progressBar.close() return True def setup_subs(self, label, path): iterator = 0 subs = self.torrent.getSubsIds(label) log('[setup_subs] subs: '+str(subs)) if len(subs) > 0: showMessage(self.localize('Information'), self.localize('Downloading and copy subtitles. Please wait.'), forced=True) for ind, title in subs: self.torrent.continueSession(ind) while iterator < 100: xbmc.sleep(1000) #self.torrent.debug() status = self.torrent.torrentHandle.status() iterator = int(status.progress * 100) # xbmc.sleep(2000) for ind, title in subs: folder = title.split(os.sep)[0] temp = os.path.basename(title) addition = os.path.dirname(title).lstrip(folder + os.sep).replace(os.sep, '.').replace(' ', '_').strip() ext = temp.split('.')[-1] temp = temp[:len(temp) - len(ext) - 1] + '.' + addition + '.' + ext newFileName = os.path.join(os.path.dirname(path), temp) log('[setup_subs]: '+str((os.path.join(os.path.dirname(os.path.dirname(path)),title),newFileName))) if not xbmcvfs.exists(newFileName): xbmcvfs.copy(os.path.join(os.path.dirname(os.path.dirname(path)), title), newFileName) def setup_play(self): #self.next_dling = False #self.iterator = 0 path = os.path.join(self.userStorageDirectory, self.c._file.path) label = os.path.basename(self.c._file.path) self.basename = label #self.seeding_run = False listitem = xbmcgui.ListItem(label) info={} #if self.subs_dl: # self.setup_subs(label, path) 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: info={'title': label, 'episode': int(self.episodeId), 'season': int(seasonId), 'tvshowtitle': title} except: log('[BTClientPlayer] Operation INFO failed!') thumbnail = self.get("thumbnail") listitem.setInfo(type='Video', infoLabels=info) if thumbnail: listitem.setThumbnailImage(urllib.unquote_plus(thumbnail)) self.display_name = label base = 'http://127.0.0.1:' + str(self.free_port) + '/' url = urlparse.urljoin(base, urllib.quote(self.c._file.path)) # мегакостыль! rpc = ({'jsonrpc': '2.0', 'method': 'Files.GetDirectory', 'params': { 'media': 'video', 'directory': path}, 'id': 0}) data = json.dumps(rpc) request = xbmc.executeJSONRPC(data) response = json.loads(request) while not response: xbmc.sleep(100) if response: xbmc.sleep(3000) #if 1==1: playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() playlist.add(url, listitem) xbmc.Player().play(playlist, listitem) log("Serving file on %s" % url) return True 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)))) def onPlayBackSeek(self, x ,y): log('[onPlayBackSeek]: '+(str(("video", "seek", self.display_name)))) xbmc.Player().pause() if self.buffer(): xbmc.Player().play() def onPlayBackSeekChapter(self, x): log('[onPlayBackSeek]: '+(str(("video", "seek", self.display_name)))) xbmc.Player().pause() if self.buffer(): xbmc.Player().play() @contextmanager def attach(self, callback, *events): for event in events: event.append(callback) yield for event in events: event.remove(callback) def loop(self): #debug_counter=0 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 True: if xbmc.abortRequested or not self.isPlaying(): break #if self.iterator == 100 and debug_counter < 100: # debug_counter += 1 #else: # self.torrent.debug() # debug_counter=0 status = self.c.status overlay.text = "\n".join(self._get_status_lines(status)) self.iterator = int(status.progress * 100) xbmc.sleep(1000) if self.iterator == 100 and 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('[loop] next_contentId: '+str(self.next_contentId)) 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) if self.iterator == 100 and self.next_dl and not self.next_dling and isinstance(self.next_contentId, int) and self.next_contentId != False: showMessage(self.localize('Torrent Downloading'), self.localize('Starting download next episode!'), forced=True) self.torrent.stopSession() # xbmc.sleep(1000) path = self.torrent.getFilePath(self.next_contentId) self.basename = self.display_name = os.path.basename(path) self.torrent.continueSession(self.next_contentId) self.next_dling = True def _get_status_lines(self, s): return [ self.display_name.decode('utf-8'), "%.2f%% %s" % (s.progress * 100, self.localize(STATE_STRS[s.state]).decode('utf-8')), "D:%.2f%s U:%.2f%s S:%d P:%d" % (s.download_rate / 1024, self.localize('kb/s').decode('utf-8'), s.upload_rate / 1024, self.localize('kb/s').decode('utf-8'), s.num_seeds, s.num_peers) ] def db_delete(self): if self.basename: db = DownloadDB() get = db.get(self.basename) if get: db.delete(get[0]) def seed(self, contentId): self.db_delete() exec_str = 'XBMC.RunPlugin(%s)' % \ ('%s?action=%s&url=%s&storage=%s&ind=%s') % \ (sys.argv[0], 'downloadLibtorrent', urllib.quote_plus(self.torrentUrl), urllib.quote_plus(self.userStorageDirectory), str(contentId)) xbmc.executebuiltin(exec_str) def get_ids(self): contentList = [] for filedict in self.torrent.getContentList(): contentList.append((filedict.get('title'), str(filedict.get('ind')))) contentList = sorted(contentList, key=lambda x: x[0]) return get_ids_video(contentList) def localize(self, string): try: return Localization.localize(string) except: return string def on_exit(self): self.c.close() sys.exit(0)