This commit is contained in:
Pavel Suha
2025-04-25 16:31:48 +02:00
commit 7b54ba45a1
132 changed files with 6984 additions and 0 deletions

560
lib/xbmcutil.py Normal file
View File

@@ -0,0 +1,560 @@
# -*- coding: UTF-8 -*-
#/*
# * Copyright (C) 2011 Libor Zoubek
# *
# *
# * 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 2, 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; see the file COPYING. If not, write to
# * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
# * http://www.gnu.org/copyleft/gpl.html
# *
# */
import os
import re
import sys
import urllib.request, urllib.parse, urllib.error
import urllib.request, urllib.error, urllib.parse
import traceback
import http.cookiejar
import time
import socket
import unicodedata
import xbmcgui
import xbmcplugin
import xbmc
import xbmcaddon
import xbmcvfs
from html.entities import name2codepoint as n2cp
import json
import util
UA = 'Mozilla/6.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.5) Gecko/2008092417 Firefox/3.0.3'
sys.path.append(os.path.join(os.path.dirname(__file__), 'usage'))
import utmain
__addon__ = xbmcaddon.Addon(id='script.module.stream.resolver')
__lang__ = __addon__.getLocalizedString
##
# initializes urllib cookie handler
def init_urllib():
cj = http.cookiejar.LWPCookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
urllib.request.install_opener(opener)
def request(url, headers=None):
if headers is None:
headers = {}
debug('request: %s' % url)
req = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(req)
data = response.read()
response.close()
debug('len(data) %s' % len(data))
return data
def post(url, data):
postdata = urllib.parse.urlencode(data)
req = urllib.request.Request(url, postdata)
req.add_header('User-Agent', UA)
response = urllib.request.urlopen(req)
data = response.read()
response.close()
return data
def icon(name):
return 'https://github.com/lzoubek/xbmc-doplnky/raw/dharma/icons/' + name
def substr(data, start, end):
i1 = data.find(start)
i2 = data.find(end, i1)
return data[i1:i2]
def add_dir(name, params, logo='', infoLabels={}, menuItems={}):
name = decode_html(name)
if not 'title' in infoLabels:
infoLabels['title'] = ''
if logo == None:
logo = ''
liz = xbmcgui.ListItem(name)
liz.setArt({'icon': 'DefaultFolder.png', 'thumb': logo})
try:
liz.setInfo(type='Video', infoLabels=infoLabels)
except:
traceback.print_exc()
items = []
for mi in list(menuItems.keys()):
action = menuItems[mi]
if not type(action) == type({}):
items.append((mi, action))
else:
if 'action-type' in action:
action_type = action['action-type']
del action['action-type']
if action_type == 'list':
items.append((mi, 'Container.Update(%s)' % _create_plugin_url(action)))
elif action_type == 'play':
items.append((mi, 'PlayMedia(%s)' % _create_plugin_url(action)))
else:
items.append((mi, 'RunPlugin(%s)' % _create_plugin_url(action)))
else:
items.append((mi, 'RunPlugin(%s)' % _create_plugin_url(action)))
if len(items) > 0:
liz.addContextMenuItems(items)
return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=_create_plugin_url(params),
listitem=liz, isFolder=True)
def add_local_dir(name, url, logo='', infoLabels={}, menuItems={}):
name = decode_html(name)
infoLabels['Title'] = name
liz = xbmcgui.ListItem(name)
liz.setArt({'icon': 'DefaultFolder.png', 'thumb': logo})
liz.setInfo(type='Video', infoLabels=infoLabels)
items = []
for mi in list(menuItems.keys()):
items.append((mi, 'RunPlugin(%s)' % _create_plugin_url(menuItems[mi])))
if len(items) > 0:
liz.addContextMenuItems(items)
return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz,
isFolder=True)
def add_video(name, params={}, logo='', infoLabels={}, menuItems={}):
name = decode_html(name)
if not 'Title' in infoLabels:
infoLabels['Title'] = name
url = _create_plugin_url(params)
li = xbmcgui.ListItem(name, path=url)
li.setArt({'icon': 'DefaultVideo.png', 'thumb': logo})
li.setInfo(type='Video', infoLabels=infoLabels)
# remove WARNING: XFILE::CFileFactory::CreateLoader - unsupported
# protocol(plugin) in plugin://....
li.addStreamInfo('video', {})
li.setProperty('IsPlayable', 'true')
items = [(xbmc.getLocalizedString(13347), 'Action(Queue)')]
for mi in list(menuItems.keys()):
action = menuItems[mi]
if not type(action) == type({}):
items.append((mi, action))
else:
if 'action-type' in action:
action_type = action['action-type']
del action['action-type']
if action_type == 'list':
items.append((mi, 'Container.Update(%s)' % _create_plugin_url(action)))
elif action_type == 'play':
items.append((mi, 'PlayMedia(%s)' % _create_plugin_url(action)))
else:
items.append((mi, 'RunPlugin(%s)' % _create_plugin_url(action)))
else:
items.append((mi, 'RunPlugin(%s)' % _create_plugin_url(action)))
if len(items) > 0:
li.addContextMenuItems(items)
return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=li,
isFolder=False)
def _create_plugin_url(params, plugin=sys.argv[0]):
url = []
for key in list(params.keys()):
value = decode_html(params[key])
#--value = value.encode('ascii','ignore')
value = value.encode('utf-8').hex()
url.append(key + '=' + value + '&')
return plugin + '?' + ''.join(url)
def reportUsage(addonid, action):
host = 'xbmc-doplnky.googlecode.com'
tc = 'UA-3971432-4'
try:
utmain.main({'id': addonid, 'host': host, 'tc': tc, 'action': action})
except:
pass
def init_usage_reporting(addonid):
utmain.main({'do': 'reg', 'id': addonid})
def save_to_file(url, file, headers=None):
try:
f = open(compat_path(file), 'wb')
f.write(request(url, headers))
f.flush()
os.fsync(f.fileno())
f.close()
return True
except:
traceback.print_exc()
def set_subtitles(listItem, url, headers=None):
if not (url == '' or url == None):
util.info('Downloading subtitles')
local = xbmcvfs.translatePath(__addon__.getAddonInfo('path'))
c_local = compat_path(local)
if not os.path.exists(c_local):
os.makedirs(c_local)
local = os.path.join(local, 'xbmc_subs' + str(int(time.time())) + '.srt')
util.info('Saving subtitles as %s' % local)
if not save_to_file(url, local, headers):
util.error('Failed to store subtitles!')
return
util.info('Setting subtitles to playable item')
listItem.setSubtitles([local.encode('utf-8')])
def load_subtitles(url, headers=None):
util.info('Downloading subtitles and load them to player...')
if not (url == '' or url == None):
local = xbmcvfs.translatePath(__addon__.getAddonInfo('path')).decode('utf-8')
c_local = compat_path(local)
if not os.path.exists(c_local):
os.makedirs(c_local)
local = os.path.join(local, 'xbmc_subs' + str(int(time.time())) + '.srt')
util.info('Saving subtitles as %s' % local)
if not save_to_file(url, local, headers):
util.error('Failed to store subtitles!')
return
player = xbmc.Player()
count = 0
max_count = 99
while not player.isPlaying() and count < max_count:
count += 1
xbmc.sleep(200)
if count > max_count - 2:
util.info("Cannot load subtitles, player timed out")
return
player.setSubtitles(local.encode('utf-8'))
util.info('Subtitles loaded to player')
def _substitute_entity(match):
ent = match.group(3)
if match.group(1) == '#':
# decoding by number
if match.group(2) == '':
# number is in decimal
return chr(int(ent))
elif match.group(2) == 'x':
# number is in hex
return chr(int('0x' + ent, 16))
else:
# they were using a name
cp = n2cp.get(ent)
if cp:
return chr(cp)
else:
return match.group()
def decode_html(data):
try:
if not type(data) == str:
data = str(data, 'utf-8', errors='ignore')
entity_re = re.compile(r'&(#?)(x?)(\w+);')
return entity_re.subn(_substitute_entity, data)[0]
except:
traceback.print_exc()
print([data])
return data
def debug(text):
xbmc.log(str([text]), xbmc.LOGDEBUG)
def info(text):
xbmc.log(str([text]))
def error(text):
xbmc.log(str([text]), xbmc.LOGERROR)
def search_remove(cache, search):
data = cache.get('search')
if data == None or data == '':
data = []
else:
data = eval(data)
data.remove(search)
cache.set('search', repr(data))
def search_replace(cache, search, replacement):
data = cache.get('search')
if data == None or data == '':
data = []
else:
data = eval(data)
index = data.index(search)
if index > -1:
data[index] = replacement
cache.set('search', repr(data))
def search_add(cache, search, maximum):
data = cache.get('search')
if data == None or data == '':
data = []
else:
data = eval(data)
if search in data:
data.remove(search)
data.insert(0, search)
remove = len(data) - maximum
if remove > 0:
for i in range(remove):
data.pop()
cache.set('search', repr(data))
def search_list(cache):
data = cache.get('search')
if data == None or data == '':
data = []
else:
data = eval(data)
return data
# obsolete functions ..
def get_searches(addon, server):
local = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
c_local = compat_path(local)
if not os.path.exists(c_local):
os.makedirs(c_local)
c_local = compat_path(os.path.join(local, server))
if not os.path.exists(c_local):
return []
f = open(c_local, 'r')
data = f.read()
searches = json.loads(str(data.decode('utf-8', 'ignore')))
f.close()
return searches
def add_search(addon, server, search, maximum):
searches = []
local = xbmcvfs.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
c_local = compat_path(local)
if not os.path.exists(c_local):
os.makedirs(c_local)
c_local = compat_path(os.path.join(local, server))
if os.path.exists(c_local):
f = open(c_local, 'r')
data = f.read()
searches = json.loads(str(data.decode('utf-8', 'ignore')))
f.close()
if search in searches:
searches.remove(search)
searches.insert(0, search)
remove = len(searches) - maximum
if remove > 0:
for i in range(remove):
searches.pop()
f = open(c_local, 'w')
f.write(json.dumps(searches, ensure_ascii=True))
f.close()
def delete_search_history(addon, server):
local = xbmcvfs.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
c_local = compat_path(local)
if not os.path.exists(c_local):
return
c_local = compat_path(os.path.join(local, server))
if os.path.exists(c_local):
os.remove(c_local)
def remove_search(addon, server, search):
local = xbmcvfs.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
c_local = compat_path(local)
if not os.path.exists(c_local):
return
c_local = compat_path(os.path.join(local, server))
if os.path.exists(c_local):
f = open(c_local, 'r')
data = f.read()
searches = json.loads(str(data.decode('utf-8', 'ignore')))
f.close()
searches.remove(search)
f = open(c_local, 'w')
f.write(json.dumps(searches, ensure_ascii=True))
f.close()
# obsolete funcionts END
def download(addon, filename, url, local, notifyFinishDialog=True, headers={}):
try:
util.info('Downloading %s to %s' % (url, local))
except:
util.info('Downloading ' + url)
local = xbmc.makeLegalFilename(local)
try:
filename = util.replace_diacritic(util.decode_html(filename))
except:
filename = 'Video soubor'
icon = os.path.join(addon.getAddonInfo('path'), 'icon.png')
notifyEnabled = addon.getSetting('download-notify') == 'true'
notifyEvery = addon.getSetting('download-notify-every')
notifyPercent = 1
if int(notifyEvery) == 0:
notifyPercent = 10
if int(notifyEvery) == 1:
notifyPercent = 5
def encode(string):
return ' '.join(string).encode('utf-8')
def notify(title, message, time=3000):
try:
xbmcgui.Dialog().notification(encode(title), encode(message), time=time, icon=icon,
sound=False)
except:
traceback.print_exc()
error('unable to show notification')
def callback(percent, speed, est, filename):
if percent == 0 and speed == 0:
notify(xbmc.getLocalizedString(13413), filename)
return
if notifyEnabled:
if percent > 0 and percent % notifyPercent == 0:
esTime = '%ss' % est
if est > 60:
esTime = '%sm' % int(est / 60)
message = xbmc.getLocalizedString(
24042) % percent + ' - %s KB/s %s' % (speed, esTime)
notify(message, filename)
downloader = Downloader(callback)
result = downloader.download(url, local, filename, headers)
try:
if result == True:
if xbmc.Player().isPlaying():
notify(xbmc.getLocalizedString(20177), filename)
else:
if notifyFinishDialog:
xbmcgui.Dialog().ok(xbmc.getLocalizedString(20177), filename)
else:
notify(xbmc.getLocalizedString(20177), filename)
else:
notify(xbmc.getLocalizedString(257), filename)
xbmcgui.Dialog().ok(filename, xbmc.getLocalizedString(257) + ' : ' + result)
except:
traceback.print_exc()
class Downloader(object):
def __init__(self, callback=None):
self.init_time = time.time()
self.callback = callback
self.gran = 50
self.percent = -1
def download(self, remote, local, filename=None, headers={}):
class MyURLopener(urllib.request.FancyURLopener):
def http_error_default(self, url, fp, errcode, errmsg, headers):
self.error_msg = 'Downlad failed, error : ' + str(errcode)
if not filename:
filename = os.path.basename(local)
self.filename = filename
if self.callback:
self.callback(0, 0, 0, filename)
socket.setdefaulttimeout(60)
opener = MyURLopener()
for header in headers:
opener.addheader(header, headers[header])
try:
opener.retrieve(remote, local, reporthook=self.dlProgress)
if hasattr(opener, 'error_msg'):
return opener.error_msg
return True
except socket.error:
errno, errstr = sys.exc_info()[:2]
if errno == socket.timeout:
return 'Download failed, connection timeout'
except:
traceback.print_exc()
errno, errstr = sys.exc_info()[:2]
return str(errstr)
def dlProgress(self, count, blockSize, totalSize):
if count % self.gran == 0 and not count == 0:
percent = int(count * blockSize * 100 / totalSize)
newTime = time.time()
diff = newTime - self.init_time
self.init_time = newTime
if diff <= 0:
diff = 1
speed = int(((1 / diff) * blockSize * self.gran) / 1024)
est = int((totalSize - int(count * blockSize)) / 1024 / speed)
if self.callback and not self.percent == percent:
self.callback(percent, speed, est, self.filename)
self.percent = percent
_diacritic_replace = {'\u00f3': 'o',
'\u0213': '-',
'\u00e1': 'a',
'\u010d': 'c',
'\u010c': 'C',
'\u010f': 'd',
'\u010e': 'D',
'\u00e9': 'e',
'\u011b': 'e',
'\u00ed': 'i',
'\u0148': 'n',
'\u0159': 'r',
'\u0161': 's',
'\u0165': 't',
'\u016f': 'u',
'\u00fd': 'y',
'\u017e': 'z'
}
def replace_diacritic(string):
ret = []
for char in string:
if char in _diacritic_replace:
ret.append(_diacritic_replace[char])
else:
ret.append(char)
return ''.join(ret)
def compat_path(path):
return path
#no longer needed in python 3
#if sys.platform.startswith('win'):
# if isinstance(path, str):
# path = path.decode('utf-8')
#else:
# if isinstance(path, str):
# path = path.encode('utf-8')
#return path