Open bensisco opened 3 years ago
#
# import xbmc import xbmcaddon
import subprocess import os
import time import calendar import _strptime import sys import getopt
import json import urllib.request import urllib.parse from contextlib import closing
import codecs
addon = xbmcaddon.Addon() setting = addon.getSetting __addon_id = addon.getAddonInfo('id') localize = addon__.getLocalizedString
kodi = 'kodi'
busy_notification = 'Notification({})'.format(localize(30008).encode('utf-8'))
#
# def mixed_decoder(unicode_error): err_str = unicode_error[1] err_len = unicode_error.end - unicode_error.start next_position = unicode_error.start + err_len replacement = err_str[unicode_error.start:unicode_error.end].decode('cp1252')
return u'%s' % replacement, next_position
codecs.register_error('mixed', mixed_decoder)
def get_opts(): idle_action = '' busy_action = ''
try:
opts, args = getopt.getopt(sys.argv[1:], "i:b:", ["idle-action=", "busy-action="])
except getopt.GetoptError as err:
return
for opt, arg in opts:
if opt in ("-i", "--idle-action"):
idle_action = arg
elif opt in ("-b", "--busy-action"):
busy_action = arg
if len(args) == 1 and not idle_action:
idle_action = args[0]
if idle_action:
idle_action = idle_action.strip('\'\"')
if busy_action:
busy_action = busy_action.strip('\'\"')
return idle_action, busy_action
def kodi_is_running(): if active_proc(kodi): return True
return False
def is_number(s): try: float(s) return True
except ValueError:
return False
def port_trans(plist): PORT_DICT = {'ftp-data': 20, 'ftp':21, 'ssh':22, 'telnet':23, 'tftp':69, 'http':80, 'https':443, 'smb':445, 'lpd':515, 'rtps':554, 'ipsec':1293, 'l2tp':1701, 'lt2p-ipsec':1707, 'pptp':1723, 'nfs':2049, 'upnp':5000, 'rtp':5004, 'rtcp':5005, 'kodi-web':8080, 'hts':9981, 'tvh':9983, 'vnsi-server':34890}
ret = set()
for p in plist:
if is_number(p):
ret.add(int(p))
else:
try:
ret.add(int(PORT_DICT[p.lower()]))
except:
continue
return ret
def read_set(item, default): ret = set() for element in str(read_val(item, default)).split(','): try: item = int(element) except ValueError: item = element.strip() ret.add(item) return ret
def read_val(item, default): try: value = int(setting(item)) except ValueError: try: if setting(item).lower() == 'true' or setting(item).lower() == 'false': value = bool(setting(item).lower() == 'true') else: value = setting(item) except ValueError: value = default
return value
def load_addon_settings(): global sleep_time, watched_local, watched_remote, watched_procs, pvr_local, pvr_port, pvr_minsecs
sleep_time = read_val('sleep', 60) # 60 secs.
pvr_minsecs = read_val('pvrwaketime', 5) * 60 # 5 mins.
pvr_port = read_val('pvrport', 34890) # VDR-VNSI
pvr_local = read_val('pvrlocal', True) # PVR backend on local system
watched_local = read_set('localports', '445, 2049') # smb, nfs, or 'set()' for empty set
watched_remote = read_set('remoteports', '22, 445') # ssh, smb
watched_procs = read_set('procs', 'HandBrakeCLI, ffmpeg, makemkv, makemkvcon')
watched_local = port_trans(watched_local)
watched_remote = port_trans(watched_remote)
if __name__ != '__main__':
xbmc.log(msg='[{}] Settings loaded.'.format(__addon_id__), level=xbmc.LOGINFO)
return
def get_sleep_time(): return sleep_time
def jsonrpc_request(method, params=None, host='localhost', port=8080, username=None, password=None):
url = 'http://{}:{}/jsonrpc'.format(host, port)
header = {'Content-Type': 'application/json'}
jsondata = {
'jsonrpc': '2.0',
'method': method,
'id': method}
if params:
jsondata['params'] = params
if username and password:
base64str = base64.encodestring('{}:{}'.format(username, password))[:-1]
header['Authorization'] = 'Basic {}'.format(base64str)
try:
if host == 'localhost':
response = xbmc.executeJSONRPC(json.dumps(jsondata))
data = json.loads(response.decode('utf-8','mixed'))
if data['id'] == method and data.has_key('result'):
return data['result']
else:
request = urllib.request.Request(url, json.dumps(jsondata), header)
with closing(urllib.request.urlopen(request)) as response:
data = json.loads(response.read().decode('utf8', 'mixed'))
if data['id'] == method and data.has_key('result'):
return data['result']
except:
pass
return False
def find_clients(port, include_localhost): clients = set()
for conn in active_conns():
#if conn.status != psutil.CONN_ESTABLISHED or not conn.raddr:
if conn.status != 'ESTABLISHED' or not conn.raddr:
continue
if int(conn.laddr[1]) == port:
if conn.raddr[0] != conn.laddr[0] or include_localhost:
clients.add(conn.raddr[0])
return clients
def check_pvrclients(): global busy_notification
if not pvr_local:
return False
for client in find_clients(pvr_port, False): # enumerate PVR clients, exclude localhost
try:
player = jsonrpc_request('Player.GetActivePlayers', host=client)
if player and player[0]['type'] == 'video':
data = jsonrpc_request('Player.GetItem', params={'properties': ['title', 'file'],'playerid': 1}, host=client)
if data and data['item']['type'] == 'channel':
busy_notification = busy_notification.format(__localize__(30009).encode('utf-8'))
if __name__ == '__main__':
xbmc_log('Found client {} watching live tv.'.format(client))
return True # a client is watching live-tv
elif data and 'pvr://' == urllib.parse.unquote(data['item']['file'].encode('utf-8'))[:6]:
busy_notification = busy_notification.format(__localize__(30010).encode('utf-8'))
if __name__ == '__main__':
xbmc_log('Found client {} watching a recording.'.format(client))
return True # a client is watching a recording
except KeyError:
pass
return False
def check_timers(): global busy_notification
if not pvr_local:
return False
localhost = '127.0.0.1'
if localhost not in find_clients(pvr_port, True):
return False
data = jsonrpc_request('PVR.GetTimers', params={'properties': ['title', 'starttime', 'endtime']})
try:
if data:
timers = data['timers']
for i in range(len(timers)):
starttime = int(calendar.timegm(time.strptime(timers[i]['starttime'], '%Y-%m-%d %H:%M:%S')))
endtime = int(calendar.timegm(time.strptime(timers[i]['endtime'], '%Y-%m-%d %H:%M:%S')))
now = int(calendar.timegm(time.gmtime()))
if starttime <= 0 or endtime <= now:
continue
else:
secs_before_recording = starttime - now
if secs_before_recording > 0 and secs_before_recording < pvr_minsecs:
busy_notification = busy_notification.format(__localize__(30011).encode('utf-8'))
if __name__ == '__main__':
xbmc_log('Recording about to start in less than {} seconds.'.format(pvr_minsecs))
return True
if secs_before_recording < 0:
busy_notification = busy_notification.format(__localize__(30012).encode('utf-8'))
if __name__ == '__main__':
xbmc_log('Found active recording.')
return True
# Sometimes we get a key error, maybe beacause pvr backend
# is not responding or busy.
except KeyError:
pass
return False
from collections import namedtuple
def active_conns(): my_env = os.environ.copy() my_env['LC_ALL'] = 'en_EN' netstat = subprocess.check_output(['netstat', '-tn'], universal_newlines=True, env=my_env)
connections = []
connection = namedtuple('connection', 'laddr raddr status')
for line in netstat.split('\n')[2:]:
if len(line) > 5:
conn = connection(line.split()[3].rsplit(':', 1), line.split()[4].rsplit(':', 1), line.split()[5])
connections.append(conn)
return connections
#
def active_proc(list): response = subprocess.check_output(['ps', 'cax'], universal_newlines=True) procs = [line.split()[-1].lower() for line in response.split('\n')[1:] if line] for proc in procs: if proc in list: return proc
return None
def check_procs(): global busy_notification
plist = [element.lower() for element in watched_procs]
pname = active_proc(plist)
if pname:
busy_notification = busy_notification.format(__localize__(30013).encode('utf-8'))
busy_notification = busy_notification.format(pname)
if __name__ == '__main__':
xbmc_log('Found active process of {}.'.format(pname))
return True
return False
def check_services(): global busy_notification
for conn in active_conns():
#if conn.status != psutil.CONN_ESTABLISHED or not conn.raddr:
if conn.status != 'ESTABLISHED' or not conn.raddr:
continue
if ((conn.laddr[0] != conn.raddr[0]) and (int(conn.laddr[1]) in watched_remote)) or \
((conn.laddr[0] == conn.raddr[0]) and (int(conn.laddr[1]) in watched_local)):
busy_notification = busy_notification.format(__localize__(30014).encode('utf-8'))
busy_notification = busy_notification.format(conn.laddr[1])
if __name__ == '__main__':
xbmc_log('Found active connection on port {}.'.format(conn.laddr[1]))
return True
return False
def check_idle(arg_idle_action, arg_busy_action): if check_pvrclients() or check_timers() or check_services() or check_procs(): if arg_busy_action: xbmc.executebuiltin(arg_busy_action) elif arg_idle_action: xbmc.executebuiltin(busy_notification) if name == 'main': xbmc_log('Action \'{}\' cancelled. Background activities detected.'.format(arg_idle_action)) else: if arg_idle_action: xbmc_log('System is idle. Executing requested action: \'{}\'.'.format(arg_idle_action)) xbmc.executebuiltin(arg_idle_action) return
def xbmc_log(text): xbmc.log(msg='[{}] {}'.format(__addon_id__, text), level=xbmc.LOGINFO)
if name == 'main': if not kodi_is_running(): sys.exit() xbmc_log('RunScript action started.') load_addon_settings() check_idle(*get_opts()) else: xbmc_log('Addon started.') load_addon_settings()
With the above check_idle.py the addon works in Matrix.
Sorry I for not noticing your request earlier. What problem were you trying to address and what exactly did you change? I've been using this code with matrix without any issues.
I think this will be in reference to later versions of Kodi requiring plugins to target python 3.
I have managed to get @bensisco changes to start up under Kodi 19.2 (not tested they actually work though). I had to update the addon.xml
to target <import addon="xbmc.python" version="3.0.0"/>
.
It was a little difficult to spot the above changes, here they are represented as a diff:
21,22c21
< import urllib.request
< import urllib.parse
---
> import urllib2
59c58
< except getopt.GetoptError as err:
---
> except getopt.GetoptError, err:
153c152
< xbmc.log(msg='[{}] Settings loaded.'.format(__addon_id__), level=xbmc.LOGINFO)
---
> xbmc.log(msg='[{}] Settings loaded.'.format(__addon_id__), level=xbmc.LOGNOTICE)
188,189c187,188
< request = urllib.request.Request(url, json.dumps(jsondata), header)
< with closing(urllib.request.urlopen(request)) as response:
---
> request = urllib2.Request(url, json.dumps(jsondata), header)
> with closing(urllib2.urlopen(request)) as response:
234c233
< elif data and 'pvr://' == urllib.parse.unquote(data['item']['file'].encode('utf-8'))[:6]:
---
> elif data and 'pvr://' == urllib2.unquote(data['item']['file'].encode('utf-8'))[:6]:
382c381
< xbmc.log(msg='[{}] {}'.format(__addon_id__, text), level=xbmc.LOGINFO)
---
> xbmc.log(msg='[{}] {}'.format(__addon_id__, text), level=xbmc.LOGNOTICE)
394d392
<```
Thanks for the diff. And, my bad, I confused Leia and Matrix. You're absolutely right. The code needs to get adapted for Matrix/Python 3. Unfortunately, I haven't upgraded to Matrix yet. So it will probably take some time before I can validate all changes. In the meantime I hope Matrix users can apply the above changes to make it work.
Code has been updated to run under both Python2 (kodi 18 matrix and earlier) and Python 3 (kodi 19 leia and newer). However, for kodi 19 leia, addon.xml needs to be updated manually according to earlier comment by chrisjohnson1988 .
I have been using this addon in Leia and it worked perfect. Since Kodi now is in Matrix this addon is no longer working. Could you please fix your code so that this addon works with Kodi Matrix?