plugin.video.torrenter/Player.py

466 lines
20 KiB
Python
Raw Normal View History

2015-01-09 14:11:21 +03:00
# -*- coding: utf-8 -*-
2015-06-30 18:08:57 +03:00
'''
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 <http://www.gnu.org/licenses/>.
'''
2015-01-09 14:11:21 +03:00
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
2015-07-26 21:44:46 +03:00
from functions import calculate, showMessage, clearStorage, DownloadDB, get_ids_video, log, debug
2015-01-09 14:11:21 +03:00
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 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"])
class TorrentPlayer(xbmc.Player):
__plugin__ = sys.modules["__main__"].__plugin__
__settings__ = sys.modules["__main__"].__settings__
2015-06-23 23:00:27 +03:00
ROOT = sys.modules["__main__"].__root__ # .decode('utf-8').encode(sys.getfilesystemencoding())
2015-01-09 14:11:21 +03:00
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'
2015-07-20 19:31:37 +03:00
seeding = __settings__.getSetting('keep_seeding') == 'true' and __settings__.getSetting('keep_files') == '1'
2015-06-23 23:00:27 +03:00
seeding_status = False
seeding_run = False
2015-01-18 20:29:38 +03:00
ids_video = None
episodeId = None
2015-07-23 18:23:22 +03:00
basename = ''
2015-01-09 14:11:21 +03:00
2015-01-10 18:47:07 +03:00
def __init__(self, userStorageDirectory, torrentUrl, params={}):
self.userStorageDirectory = userStorageDirectory
2015-06-23 23:00:27 +03:00
self.torrentUrl = torrentUrl
2015-01-09 14:11:21 +03:00
xbmc.Player.__init__(self)
2015-07-26 21:44:46 +03:00
log("[TorrentPlayer] Initalized")
2015-01-09 14:11:21 +03:00
self.params = params
self.get = self.params.get
self.contentId = int(self.get("url"))
2015-01-18 20:29:38 +03:00
self.torrent = Downloader.Torrent(self.userStorageDirectory, self.torrentUrl, self.torrentFilesDirectory).player
2015-01-09 14:11:21 +03:00
try:
2015-01-18 20:29:38 +03:00
if self.get("url2"):
self.ids_video = urllib.unquote_plus(self.get("url2")).split(',')
else:
self.ids_video = self.get_ids()
2015-01-09 14:11:21 +03:00
except:
2015-01-18 20:29:38 +03:00
pass
2015-01-09 14:11:21 +03:00
self.init()
self.setup_torrent()
if self.buffer():
while True:
if self.setup_play():
2015-07-26 21:44:46 +03:00
debug('************************************* GOING LOOP')
self.torrent.startSession()
self.torrent.continueSession(self.contentId)
2015-01-09 14:11:21 +03:00
self.loop()
else:
break
2015-07-26 21:44:46 +03:00
debug('************************************* GO NEXT?')
2015-01-19 17:32:24 +03:00
if self.next_dl and self.next_dling and isinstance(self.next_contentId, int) and self.iterator == 100:
2015-01-09 14:11:21 +03:00
self.contentId = self.next_contentId
continue
2015-07-26 21:44:46 +03:00
debug('************************************* NO! break')
2015-01-09 14:11:21 +03:00
break
2015-01-25 01:13:12 +03:00
self.torrent.stopSession()
self.torrent.threadComplete = True
self.torrent.checkThread()
2015-07-20 19:31:37 +03:00
if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.userStorageDirectory:
2015-07-26 21:44:46 +03:00
xbmc.sleep(1000)
2015-01-25 01:13:12 +03:00
clearStorage(self.userStorageDirectory)
else:
if self.seeding_status:
2015-07-26 21:44:46 +03:00
showMessage(self.localize('Information'),
self.localize('Torrent is seeding. To stop it use Download Status.'), forced=True)
2015-01-09 14:11:21 +03:00
else:
2015-01-25 01:13:12 +03:00
if self.seeding: self.db_delete()
2015-07-26 21:44:46 +03:00
showMessage(self.localize('Information'),
self.localize('Torrent downloading is stopped.'), forced=True)
2015-01-09 14:11:21 +03:00
def init(self):
2015-01-18 01:32:32 +03:00
self.next_dl = True if self.__settings__.getSetting('next_dl') == 'true' and self.ids_video else False
2015-07-26 21:44:46 +03:00
log('[TorrentPlayer]: init - ' + str(self.next_dl))
2015-01-18 01:32:32 +03:00
self.next_contentId = False
2015-01-09 14:11:21 +03:00
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 = []
def setup_torrent(self):
2015-06-28 18:04:09 +03:00
self.torrent.initSession()
if self.__settings__.getSetting('encryption') == 'true':
self.torrent.encryptSession()
2015-01-09 14:11:21 +03:00
self.torrent.startSession()
2015-06-23 23:00:27 +03:00
upload_limit = self.__settings__.getSetting("upload_limit") if self.__settings__.getSetting(
"upload_limit") != "" else 0
2015-02-16 19:19:58 +03:00
if 0 < int(upload_limit):
2015-12-17 21:17:40 +03:00
self.torrent.setUploadLimit(int(upload_limit) * 1024 * 1024 / 8) # MBits/second
2015-06-23 23:00:27 +03:00
download_limit = self.__settings__.getSetting("download_limit") if self.__settings__.getSetting(
"download_limit") != "" else 0
2015-02-16 19:19:58 +03:00
if 0 < int(download_limit):
2015-01-09 14:11:21 +03:00
self.torrent.setDownloadLimit(
2015-12-17 21:17:40 +03:00
int(download_limit) * 1024 * 1024 / 8) # MBits/second
2015-01-09 14:11:21 +03:00
self.torrent.status = False
self.fullSize = self.torrent.getFileSize(self.contentId)
Offset = calculate(self.fullSize)
2015-07-26 21:44:46 +03:00
debug('Offset: '+str(Offset))
2015-06-08 21:17:37 +03:00
2015-06-23 23:00:27 +03:00
# mp4 fix
2015-06-08 21:17:37 +03:00
label = os.path.basename(self.torrent.getFilePath(self.contentId))
2015-06-23 23:00:27 +03:00
isMP4 = False
2015-08-20 18:07:03 +03:00
if '.' in label and str(label.split('.')[-1]).lower() == 'mp4':
2015-06-28 20:34:03 +03:00
isMP4 = True
2015-07-26 21:44:46 +03:00
debug('setup_torrent: '+str((self.contentId, Offset, isMP4, label)))
2015-06-08 21:17:37 +03:00
self.torrent.continueSession(self.contentId, Offset=Offset, isMP4=isMP4)
2015-01-09 14:11:21 +03:00
def buffer(self):
iterator = 0
progressBar = xbmcgui.DialogProgress()
2015-07-26 21:44:46 +03:00
progressBar.create(self.localize('Please Wait') + str(' [%s]' % str(self.torrent.lt.version)),
self.localize('Seeds searching.'))
2015-01-09 14:11:21 +03:00
if self.subs_dl:
2015-06-23 23:00:27 +03:00
subs = self.torrent.getSubsIds(os.path.basename(self.torrent.getFilePath(self.contentId)))
if len(subs) > 0:
2015-01-09 14:11:21 +03:00
for ind, title in subs:
self.torrent.continueSession(ind)
2015-06-23 23:00:27 +03:00
num_pieces = int(self.torrent.torrentFileInfo.num_pieces())
2015-08-02 15:26:16 +03:00
xbmc.sleep(1000)
self.torrent.torrentHandle.force_dht_announce()
2015-01-09 14:11:21 +03:00
while iterator < 100:
self.torrent.debug()
downloadedSize = self.torrent.torrentHandle.file_progress()[self.contentId]
status = self.torrent.torrentHandle.status()
iterator = int(status.progress * 100)
if status.state == 0 or (status.progress == 0 and status.num_pieces > 0):
2015-06-23 23:00:27 +03:00
iterator = int(status.num_pieces * 100 / num_pieces)
2015-01-09 14:11:21 +03:00
if iterator > 99: iterator = 99
2015-07-26 21:44:46 +03:00
progressBar.update(iterator, self.localize('Checking preloaded files...'), ' ', ' ')
2015-01-09 14:11:21 +03:00
elif status.state == 3:
2015-07-26 21:44:46 +03:00
dialogText = self.localize('Preloaded: ') + str(downloadedSize / 1024 / 1024) + ' MB / ' + str(
2015-01-09 14:11:21 +03:00
self.fullSize / 1024 / 1024) + ' MB'
peersText = ' [%s: %s; %s: %s]' % (
2015-07-26 21:44:46 +03:00
self.localize('Seeds'), str(self.torrent.getSeeds()), self.localize('Peers'),
2015-06-23 23:00:27 +03:00
str(self.torrent.getPeers()),)
2015-01-09 14:11:21 +03:00
speedsText = '%s: %s Mbit/s; %s: %s Mbit/s' % (
2015-12-17 21:17:40 +03:00
self.localize('Downloading'), str(self.torrent.getDownloadRate() * 8 / 1024 / 1024),
self.localize('Uploading'), str(self.torrent.getUploadRate() * 8 / 1024 / 1024))
2015-07-24 20:22:30 +03:00
if self.debug:
peersText=peersText + ' ' + self.torrent.get_debug_info('dht_state')
2015-07-26 21:44:46 +03:00
dialogText=dialogText.replace(self.localize('Preloaded: '),'') + ' ' + self.torrent.get_debug_info('trackers_sum')
progressBar.update(iterator, self.localize('Seeds searching.') + peersText, dialogText,
2015-01-09 14:11:21 +03:00
speedsText)
else:
2015-07-26 21:44:46 +03:00
progressBar.update(iterator, self.localize('UNKNOWN STATUS'), ' ', ' ')
2015-01-09 14:11:21 +03:00
if progressBar.iscanceled():
progressBar.update(0)
progressBar.close()
self.torrent.threadComplete = True
self.torrent.checkThread()
return
2015-08-02 15:26:16 +03:00
xbmc.sleep(1000)
2015-12-15 17:09:25 +03:00
#self.torrent.torrentHandle.flush_cache()
#self.torrent.session.remove_torrent(self.torrent.torrentHandle)
2015-01-09 14:11:21 +03:00
progressBar.update(0)
progressBar.close()
return True
def setup_play(self):
self.next_dling = False
2015-06-23 23:00:27 +03:00
self.iterator = 0
2015-01-09 14:11:21 +03:00
path = self.torrent.getFilePath(self.contentId)
label = os.path.basename(path)
2015-06-23 23:00:27 +03:00
self.basename = label
self.seeding_run = False
2015-01-09 14:11:21 +03:00
listitem = xbmcgui.ListItem(label, path=path)
if self.subs_dl:
self.setup_subs(label, path)
2015-01-18 20:29:38 +03:00
try:
2015-01-09 14:11:21 +03:00
seasonId = self.get("seasonId")
2015-06-23 23:00:27 +03:00
self.episodeId = self.get("episodeId") if not self.episodeId else int(self.episodeId) + 1
2015-01-18 20:29:38 +03:00
title = urllib.unquote_plus(self.get("title")) if self.get("title") else None
2015-01-09 14:11:21 +03:00
2015-01-18 20:29:38 +03:00
if self.get("label") and self.episodeId == self.get("episodeId"):
2015-01-09 14:11:21 +03:00
label = urllib.unquote_plus(self.get("label"))
2015-01-18 20:29:38 +03:00
elif seasonId and self.episodeId and title:
2015-06-23 23:00:27 +03:00
label = '%s S%02dE%02d.%s (%s)' % (
title, int(seasonId), int(self.episodeId), self.basename.split('.')[-1], self.basename)
2015-01-09 14:11:21 +03:00
2015-01-18 20:29:38 +03:00
if seasonId and self.episodeId and label and title:
2015-01-09 14:11:21 +03:00
listitem = xbmcgui.ListItem(label, path=path)
listitem.setInfo(type='video', infoLabels={'title': label,
2015-01-18 20:29:38 +03:00
'episode': int(self.episodeId),
2015-01-09 14:11:21 +03:00
'season': int(seasonId),
2015-01-18 20:29:38 +03:00
'tvshowtitle': title})
except:
2015-07-26 21:44:46 +03:00
log('[TorrentPlayer] Operation INFO failed!')
2015-01-18 20:29:38 +03:00
2015-01-09 14:11:21 +03:00
thumbnail = self.get("thumbnail")
if thumbnail:
listitem.setThumbnailImage(urllib.unquote_plus(thumbnail))
self.display_name = label
2015-06-23 23:00:27 +03:00
# мегакостыль!
2015-01-09 14:11:21 +03:00
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)
2015-12-15 17:09:25 +03:00
xbmc.sleep(1000)
2015-01-09 14:11:21 +03:00
if response:
2015-06-23 23:00:27 +03:00
# xbmc.Player().play(path, listitem)
2015-05-14 19:20:49 +03:00
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist.add(path, listitem)
xbmc.Player().play(playlist)
2015-08-02 15:26:16 +03:00
xbmc.sleep(2000) # very important, do not edit this, podavan
2015-01-09 14:11:21 +03:00
return True
2015-12-15 17:09:25 +03:00
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)
2015-01-09 14:11:21 +03:00
def onPlayBackStarted(self):
for f in self.on_playback_started:
f()
2015-07-26 21:44:46 +03:00
log('[onPlayBackStarted]: '+(str(("video", "play", self.display_name))))
2015-01-09 14:11:21 +03:00
def onPlayBackResumed(self):
for f in self.on_playback_resumed:
f()
self.onPlayBackStarted()
def onPlayBackPaused(self):
for f in self.on_playback_paused:
f()
2015-07-26 21:44:46 +03:00
log('[onPlayBackPaused]: '+(str(("video", "pause", self.display_name))))
2015-01-09 14:11:21 +03:00
def onPlayBackStopped(self):
for f in self.on_playback_stopped:
f()
2015-07-26 21:44:46 +03:00
log('[onPlayBackStopped]: '+(str(("video", "stop", self.display_name))))
2015-01-09 14:11:21 +03:00
@contextmanager
def attach(self, callback, *events):
for event in events:
event.append(callback)
yield
for event in events:
event.remove(callback)
def loop(self):
2015-06-28 18:04:09 +03:00
debug_counter=0
2015-08-02 15:26:16 +03:00
xbmc.sleep(1000)
self.torrent.torrentHandle.force_dht_announce()
2015-01-09 14:11:21 +03:00
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)):
2015-01-25 01:13:12 +03:00
while not xbmc.abortRequested and self.isPlaying() and not self.torrent.threadComplete:
2015-01-09 14:11:21 +03:00
self.torrent.checkThread()
2015-06-28 18:04:09 +03:00
if self.iterator == 100 and debug_counter < 100:
debug_counter += 1
else:
self.torrent.debug()
debug_counter=0
2015-01-09 14:11:21 +03:00
status = self.torrent.torrentHandle.status()
overlay.text = "\n".join(self._get_status_lines(status))
2015-06-23 23:00:27 +03:00
# downloadedSize = torrent.torrentHandle.file_progress()[contentId]
2015-01-09 14:11:21 +03:00
self.iterator = int(status.progress * 100)
2015-01-18 20:29:38 +03:00
xbmc.sleep(1000)
2015-01-18 01:32:32 +03:00
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
2015-07-26 21:44:46 +03:00
debug('[loop] next_contentId: '+str(self.next_contentId))
2015-01-15 22:53:23 +03:00
if not self.seeding_run and self.iterator == 100 and self.seeding:
2015-06-23 23:00:27 +03:00
self.seeding_run = True
2015-01-14 23:24:01 +03:00
self.seed(self.contentId)
2015-06-23 23:00:27 +03:00
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:
2015-07-26 21:44:46 +03:00
showMessage(self.localize('Torrent Downloading'),
self.localize('Starting download next episode!'), forced=True)
2015-01-09 14:11:21 +03:00
self.torrent.stopSession()
2015-06-23 23:00:27 +03:00
# xbmc.sleep(1000)
2015-01-09 14:11:21 +03:00
path = self.torrent.getFilePath(self.next_contentId)
2015-06-23 23:00:27 +03:00
self.basename = self.display_name = os.path.basename(path)
2015-01-14 23:24:01 +03:00
self.torrent.continueSession(self.next_contentId)
2015-01-09 14:11:21 +03:00
self.next_dling = True
def _get_status_lines(self, s):
return [
2015-07-26 21:44:46 +03:00
self.display_name.decode('utf-8')+'; '+self.torrent.get_debug_info('dht_state'),
"%.2f%% %s; %s" % (s.progress * 100, self.localize(STATE_STRS[s.state]).decode('utf-8'), self.torrent.get_debug_info('trackers_sum')),
2015-12-17 21:17:40 +03:00
"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'),
2015-01-09 14:11:21 +03:00
s.num_seeds, s.num_peers)
]
2015-01-14 23:24:01 +03:00
def db_delete(self):
2015-07-23 18:23:22 +03:00
if self.basename:
db = DownloadDB()
get = db.get(self.basename)
if get:
db.delete(get[0])
2015-01-14 23:24:01 +03:00
def seed(self, contentId):
self.db_delete()
2015-06-23 23:00:27 +03:00
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))
2015-01-18 20:29:38 +03:00
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])
2015-06-23 23:00:27 +03:00
return get_ids_video(contentList)
2015-07-26 21:44:46 +03:00
def localize(self, string):
try:
return Localization.localize(string)
except:
return string