PlexKodiConnect/resources/lib/plex.py
2018-09-30 17:35:23 +02:00

390 lines
12 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import logging
import sys
import platform
import uuid
import json
import threading
import time
import requests
import xbmc
from .plexnet import plexapp, myplex
from . import util
LOG = logging.getLogger('PLEX.plex')
class PlexTimer(plexapp.Timer):
def shouldAbort(self):
return xbmc.abortRequested
def abortFlag():
return util.MONITOR.abortRequested()
plexapp.setTimer(PlexTimer)
plexapp.setAbortFlagFunction(abortFlag)
maxVideoRes = plexapp.Res((3840, 2160)) # INTERFACE.globals["supports4k"] and plexapp.Res((3840, 2160)) or plexapp.Res((1920, 1080))
CLIENT_ID = util.getSetting('client.ID')
if not CLIENT_ID:
CLIENT_ID = str(uuid.uuid4())
util.setSetting('client.ID', CLIENT_ID)
def defaultUserAgent():
"""Return a string representing the default user agent."""
_implementation = platform.python_implementation()
if _implementation == 'CPython':
_implementation_version = platform.python_version()
elif _implementation == 'PyPy':
_implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
sys.pypy_version_info.minor,
sys.pypy_version_info.micro)
if sys.pypy_version_info.releaselevel != 'final':
_implementation_version = ''.join([_implementation_version, sys.pypy_version_info.releaselevel])
elif _implementation == 'Jython':
_implementation_version = platform.python_version() # Complete Guess
elif _implementation == 'IronPython':
_implementation_version = platform.python_version() # Complete Guess
else:
_implementation_version = 'Unknown'
try:
p_system = platform.system()
p_release = platform.release()
except IOError:
p_system = 'Unknown'
p_release = 'Unknown'
return " ".join(['%s/%s' % ('Plex-for-Kodi', util.ADDON.getAddonInfo('version')),
'%s/%s' % ('Kodi', xbmc.getInfoLabel('System.BuildVersion').replace(' ', '-')),
'%s/%s' % (_implementation, _implementation_version),
'%s/%s' % (p_system, p_release)])
class PlexInterface(plexapp.AppInterface):
_regs = {
None: {},
}
_globals = {
'platform': 'Kodi',
'appVersionStr': util.ADDON.getAddonInfo('version'),
'clientIdentifier': CLIENT_ID,
'platformVersion': xbmc.getInfoLabel('System.BuildVersion'),
'product': 'Plex for Kodi',
'provides': 'player',
'device': util.getPlatform() or plexapp.PLATFORM,
'model': 'Unknown',
'friendlyName': 'Kodi Add-on ({0})'.format(platform.node()),
'supports1080p60': True,
'vp9Support': True,
'transcodeVideoQualities': [
"10", "20", "30", "30", "40", "60", "60", "75", "100", "60", "75", "90", "100", "100"
],
'transcodeVideoResolutions': [
plexapp.Res((220, 180)),
plexapp.Res((220, 128)),
plexapp.Res((284, 160)),
plexapp.Res((420, 240)),
plexapp.Res((576, 320)),
plexapp.Res((720, 480)),
plexapp.Res((1024, 768)),
plexapp.Res((1280, 720)),
plexapp.Res((1280, 720)),
maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes
],
'transcodeVideoBitrates': [
"64", "96", "208", "320", "720", "1500", "2000", "3000", "4000", "8000", "10000", "12000", "20000", "200000"
],
'deviceInfo': plexapp.DeviceInfo()
}
def getPreference(self, pref, default=None):
if pref == 'manual_connections':
return self.getManualConnections()
else:
return util.getSetting(pref, default)
def getManualConnections(self):
conns = []
for i in range(2):
ip = util.getSetting('manual_ip_{0}'.format(i))
if not ip:
continue
port = util.getSetting('manual_port_{0}'.format(i), 32400)
conns.append({'connection': ip, 'port': port})
return json.dumps(conns)
def setPreference(self, pref, value):
util.setSetting(pref, value)
def getRegistry(self, reg, default=None, sec=None):
if sec == 'myplex' and reg == 'MyPlexAccount':
ret = util.getSetting('{0}.{1}'.format(sec, reg), default)
if ret:
return ret
return json.dumps({'authToken': util.getSetting('auth.token')})
else:
return util.getSetting('{0}.{1}'.format(sec, reg), default)
def setRegistry(self, reg, value, sec=None):
util.setSetting('{0}.{1}'.format(sec, reg), value)
def clearRegistry(self, reg, sec=None):
util.setSetting('{0}.{1}'.format(sec, reg), '')
def addInitializer(self, sec):
pass
def clearInitializer(self, sec):
pass
def getGlobal(self, glbl, default=None):
if glbl == 'transcodeVideoResolutions':
maxres = self.getPreference('allow_4k', True) and plexapp.Res((3840, 2160)) or plexapp.Res((1920, 1080))
self._globals['transcodeVideoResolutions'][-5:] = [maxres] * 5
return self._globals.get(glbl, default)
def getCapabilities(self):
return ''
def LOG(self, msg):
LOG.debug('API: %s', msg)
def DEBUG_LOG(self, msg):
LOG.debug('API: %s', msg)
def WARN_LOG(self, msg):
LOG.warn('API: %s', msg)
def ERROR_LOG(self, msg):
LOG.error('API: %s', msg)
def ERROR(self, msg=None, err=None):
if err:
LOG.error('%s - %s', msg, err.message)
else:
util.ERROR()
def supportsAudioStream(self, codec, channels):
return True
# if codec = invalid then return true
# canDownmix = (m.globals["audioDownmix"][codec] <> invalid)
# supportsSurroundSound = m.SupportsSurroundSound()
# if not supportsSurroundSound and canDownmix then
# maxChannels = m.globals["audioDownmix"][codec]
# else
# maxChannels = firstOf(m.globals["audioDecoders"][codec], 0)
# end if
# if maxChannels > 2 and not canDownmix and not supportsSurroundSound then
# ' It's a surround sound codec and we can't do surround sound
# supported = false
# else if maxChannels = 0 or maxChannels < channels then
# ' The codec is either unsupported or can't handle the requested channels
# supported = false
# else
# supported = true
# return supported
def supportsSurroundSound(self):
return True
def getQualityIndex(self, qualityType):
if qualityType == self.QUALITY_LOCAL:
return self.getPreference("local_quality", 13)
elif qualityType == self.QUALITY_ONLINE:
return self.getPreference("online_quality", 8)
else:
return self.getPreference("remote_quality", 13)
def getMaxResolution(self, quality_type, allow4k=False):
qualityIndex = self.getQualityIndex(quality_type)
if qualityIndex >= 9:
if self.getPreference('allow_4k', True):
return allow4k and 2160 or 1088
else:
return 1088
elif qualityIndex >= 6:
return 720
elif qualityIndex >= 5:
return 480
else:
return 360
plexapp.setInterface(PlexInterface())
plexapp.setUserAgent(defaultUserAgent())
class CallbackEvent(plexapp.CompatEvent):
def __init__(self, context, signal, timeout=15, *args, **kwargs):
threading._Event.__init__(self, *args, **kwargs)
self.start = time.time()
self.context = context
self.signal = signal
self.timeout = timeout
self.context.on(self.signal, self.set)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.wait()
def __repr__(self):
return '<{0}:{1}>'.format(self.__class__.__name__, self.signal)
def set(self, **kwargs):
threading._Event.set(self)
def wait(self):
if not threading._Event.wait(self, self.timeout):
LOG.debug('%s: TIMED-OUT', self)
self.close()
def triggeredOrTimedOut(self, timeout=None):
try:
if time.time() - self.start() > self.timeout:
LOG.debug('%s: TIMED-OUT', self)
return True
if timeout:
threading._Event.wait(self, timeout)
finally:
return self.isSet()
def close(self):
self.set()
self.context.off(self.signal, self.set)
def init():
LOG.info('Initializing')
with CallbackEvent(plexapp.APP, 'init'):
plexapp.init()
LOG.info('Waiting for account initialization...')
retry = True
while retry:
retry = False
if not plexapp.ACCOUNT.authToken:
token = authorize()
if not token:
LOG.info('FAILED TO AUTHORIZE')
return False
with CallbackEvent(plexapp.APP, 'account:response'):
plexapp.ACCOUNT.validateToken(token)
LOG.info('Waiting for account initialization')
# if not PLEX:
# util.messageDialog('Connection Error', u'Unable to connect to any servers')
# util.DEBUG_LOG('SIGN IN: Failed to connect to any servers')
# return False
# util.DEBUG_LOG('SIGN IN: Connected to server: {0} - {1}'.format(PLEX.friendlyName, PLEX.baseuri))
success = requirePlexPass()
if success == 'RETRY':
retry = True
continue
return success
def requirePlexPass():
return True
# if not plexapp.ACCOUNT.hasPlexPass():
# from windows import signin, background
# background.setSplash(False)
# w = signin.SignInPlexPass.open()
# retry = w.retry
# del w
# util.DEBUG_LOG('PlexPass required. Signing out...')
# plexapp.ACCOUNT.signOut()
# plexapp.SERVERMANAGER.clearState()
# if retry:
# return 'RETRY'
# else:
# return False
# return True
def authorize():
from .windows import background
with background.BackgroundContext(function=_authorize) as win:
return win.result
def _authorize():
from .windows import signin, background
background.setSplash(False)
back = signin.Background.create()
pre = signin.PreSignInWindow.open()
try:
if not pre.doSignin:
return None
finally:
del pre
try:
while True:
pinLoginWindow = signin.PinLoginWindow.create()
try:
pl = myplex.PinLogin()
except requests.ConnectionError:
util.ERROR()
util.messageDialog(util.T(32427, 'Failed'), util.T(32449, 'Sign-in failed. Cound not connect to plex.tv'))
return
pinLoginWindow.setPin(pl.pin)
try:
pl.startTokenPolling()
while not pl.finished():
if pinLoginWindow.abort:
util.DEBUG_LOG('SIGN IN: Pin login aborted')
pl.abort()
return None
xbmc.sleep(100)
else:
if not pl.expired():
if pl.authenticationToken:
pinLoginWindow.setLinking()
return pl.authenticationToken
else:
return None
finally:
pinLoginWindow.doClose()
del pinLoginWindow
if pl.expired():
util.DEBUG_LOG('SIGN IN: Pin expired')
expiredWindow = signin.ExpiredWindow.open()
try:
if not expiredWindow.refresh:
util.DEBUG_LOG('SIGN IN: Pin refresh aborted')
return None
finally:
del expiredWindow
finally:
back.doClose()
del back