From b239e74bff81d91d6d32509483437a71374a068a Mon Sep 17 00:00:00 2001 From: DiMartinoXBMC Date: Sun, 2 Aug 2015 19:01:57 +0300 Subject: [PATCH] btclient --- BTClientPlayer.py | 488 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 BTClientPlayer.py diff --git a/BTClientPlayer.py b/BTClientPlayer.py new file mode 100644 index 0000000..eed45ae --- /dev/null +++ b/BTClientPlayer.py @@ -0,0 +1,488 @@ +# -*- 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 +import urllib +import json +import sys +from contextlib import contextmanager, closing, nested + +import xbmc +import xbmcgui +import Downloader +import xbmcgui +import xbmcvfs +import Localization +from platform_pulsar import get_platform +import traceback +from btclient import * +from functions import calculate, showMessage, clearStorage, DownloadDB, get_ids_video, log, debug, is_writable +from argparse import Namespace +from Player import OverlayText +from Libtorrent import Libtorrent + + + +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 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 __init__(self, userStorageDirectory, torrentUrl, params={}): + 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=0,#KB + bt_upload_limit=0, + 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 + + self.btclient=self.stream(args, BTClient) + + #self.init() + #self.setup_torrent() + #if self.buffer(): + # while True: + # if self.setup_play(): + # debug('************************************* GOING LOOP') + # #self.torrent.continueSession(self.contentId) + # self.loop() + # else: + # break + # debug('************************************* 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 + # debug('************************************* NO! break') + # break + #self.torrent.stopSession() + #self.torrent.threadComplete = True + #self.torrent.checkThread() + + #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 on_exit(self): + self.c.close() + sys.exit(0) + + 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) + + if self.buffer(): + f = self.c._file + self.server.set_file(f) + self.setup_play() + + 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 + + status = self.c.status + overlay.text = "\n".join(self._get_status_lines(status)) + xbmc.sleep(1000) + + 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 + + def setup_torrent(self): + self.torrent.initSession() + if self.__settings__.getSetting('encryption') == 'true': + self.torrent.encryptSession() + self.torrent.startSession() + upload_limit = self.__settings__.getSetting("upload_limit") if self.__settings__.getSetting( + "upload_limit") != "" else 0 + if 0 < int(upload_limit): + self.torrent.setUploadLimit(int(upload_limit) * 1000000 / 8) # MBits/second + download_limit = self.__settings__.getSetting("download_limit") if self.__settings__.getSetting( + "download_limit") != "" else 0 + if 0 < int(download_limit): + self.torrent.setDownloadLimit( + int(download_limit) * 1000000 / 8) # MBits/second + self.torrent.status = False + self.fullSize = self.torrent.getFileSize(self.contentId) + Offset = calculate(self.fullSize) + debug('Offset: '+str(Offset)) + 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) + + # mp4 fix + label = os.path.basename(self.torrent.getFilePath(self.contentId)) + isMP4 = False + if '.' in label and str(label.split('.')[-1]).lower() == 'mp4': + isMP4 = True + debug('setup_torrent: '+str((self.contentId, Offset, isMP4, label))) + self.torrent.continueSession(self.contentId, Offset=Offset, isMP4=isMP4) + + 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 not self.c.is_file_ready: #iterator < 100:#or not self.torrent.is_playble() + status = self.c.get_normalized_status() + iterator = int(status['progress'] * 10000) + if iterator > 99: iterator = 99 + 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...'), ' ', ' ') + elif status['state'] == 'downloading': + dialogText = self.localize('Preloaded: ') + str(status['downloaded'] / 1024 / 1024) + ' MB / ' + str( + status['total_size'] / 1024 / 1024) + ' MB' + peersText = ' [%s: %s; %s: %s]' % ( + self.localize('Seeds'), str(status['seeds_connected']), self.localize('Peers'), + str(status['peers_connected']),) + speedsText = '%s: %s Mbit/s; %s: %s Mbit/s' % ( + self.localize('Downloading'), str(status['download_rate'] * 8 / 1000000), + self.localize('Uploading'), str(status['upload_rate'] * 8 / 1000000)) + #if self.debug: + # peersText=peersText + ' ' + self.torrent.get_debug_info('dht_state') + # dialogText=dialogText.replace(self.localize('Preloaded: '),'') + ' ' + self.torrent.get_debug_info('trackers_sum') + progressBar.update(iterator, self.localize('Seeds searching.') + peersText, dialogText, + speedsText) + else: + progressBar.update(iterator, self.localize('UNKNOWN STATUS'), ' ', ' ') + if progressBar.iscanceled(): + self.c.close() + break + xbmc.sleep(1000) + progressBar.update(0) + progressBar.close() + return True + + def setup_subs(self, label, path): + iterator = 0 + subs = self.torrent.getSubsIds(label) + debug('[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) + debug('[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) + + #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: + listitem = xbmcgui.ListItem(label) + + listitem.setInfo(type='video', infoLabels={'title': label, + 'episode': int(self.episodeId), + 'season': int(seasonId), + 'tvshowtitle': title}) + except: + log('[BTClientPlayer] Operation INFO failed!') + + thumbnail = self.get("thumbnail") + 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(path)) + # мегакостыль! + rpc = ({'jsonrpc': '2.0', 'method': 'Files.GetDirectory', 'params': { + 'media': 'video', 'directory': os.path.dirname(path)}, 'id': 0}) + data = json.dumps(rpc) + request = xbmc.executeJSONRPC(data) + response = json.loads(request) + xbmc.sleep(300) + if response: + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + playlist.clear() + playlist.add(url, listitem) + xbmc.Player().play(playlist) + print "\nServing 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)))) + + @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 not xbmc.abortRequested and self.isPlaying() and not self.torrent.threadComplete: + self.torrent.checkThread() + if self.iterator == 100 and debug_counter < 100: + debug_counter += 1 + else: + self.torrent.debug() + debug_counter=0 + status = self.torrent.torrentHandle.status() + overlay.text = "\n".join(self._get_status_lines(status)) + # downloadedSize = torrent.torrentHandle.file_progress()[contentId] + 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 + debug('[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 / 1000, self.localize('kb/s').decode('utf-8'), + s.upload_rate / 1000, 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