# Copyright (C) 2017 Lunatixz
#
#
# This file is part of Playon Browser
#
# Playon Browser 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.
#
# Playon Browser 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 Playon Browser. If not, see .
import sys, os, re, random, traceback, json, xmltodict, collections
import urllib, urllib2, socket, datetime
import xbmc, xbmcplugin, xbmcaddon, xbmcgui
from simplecache import SimpleCache
# Plugin Info
ADDON_ID = 'plugin.video.playonbrowser'
REAL_SETTINGS = xbmcaddon.Addon(id=ADDON_ID)
ADDON_NAME = REAL_SETTINGS.getAddonInfo('name')
SETTINGS_LOC = REAL_SETTINGS.getAddonInfo('profile')
ADDON_PATH = REAL_SETTINGS.getAddonInfo('path').decode('utf-8')
ADDON_VERSION = REAL_SETTINGS.getAddonInfo('version')
ICON = REAL_SETTINGS.getAddonInfo('icon')
FANART = REAL_SETTINGS.getAddonInfo('fanart')
LANGUAGE = REAL_SETTINGS.getLocalizedString
# Globals
TIMEOUT = 30
PLAYON_DATA = '/data/data.xml'
BASE_VIDEO_URL = "%s/%s/main.m3u8"
BASE_ID_URL = "%s/data/data.xml?id=%s"
BASE_UPNP = REAL_SETTINGS.getSetting("playonUPNPid").rstrip('/')
BASE_URL = REAL_SETTINGS.getSetting("playonserver").rstrip('/')
DEBUG = REAL_SETTINGS.getSetting("debug") == "true"
KODILIBRARY = False #todo strm contextMenu
URLTYPE = {0:'m3u8',1:'upnp',2:'ext'}[int(REAL_SETTINGS.getSetting('playonmedia'))]
PTVL_RUNNING = xbmcgui.Window(10000).getProperty('PseudoTVRunning') == "True"
def log(msg, level=xbmc.LOGDEBUG):
if DEBUG == True or level == xbmc.LOGERROR:
if level == xbmc.LOGERROR:
msg += ' ,' + traceback.format_exc()
xbmc.log(ADDON_ID + '-' + ADDON_VERSION + '-' + (msg), level)
def getParams():
param=[]
if len(sys.argv[2])>=2:
params=sys.argv[2]
cleanedparams=params.replace('?','')
if (params[len(params)-1]=='/'):
params=params[0:len(params)-2]
pairsofparams=cleanedparams.split('&')
param={}
for i in range(len(pairsofparams)):
splitparams={}
splitparams=pairsofparams[i].split('=')
if (len(splitparams))==2:
param[splitparams[0]]=splitparams[1]
return param
def folderIcon(val):
log('folderIcon')
return random.choice(['/images/folders/folder_%s_0.png' %val,'/images/folders/folder_%s_1.png' %val])
def parseSEinfo(label):
season = -1
episode = -1
pattern1 = re.compile(r"""(?:s|season)(?:\s)(?P\d+)(?:e|x|episode|\n)(?:\s)(?P\d+) # s 01e 02""" , re.VERBOSE)
pattern2 = re.compile(r"""(?:s|season)(?P\d+)(?:e|x|episode|\n)(?:\s)(?P\d+) # s01e 02""" , re.VERBOSE)
pattern3 = re.compile(r"""(?:s|season)(?:\s)(?P\d+)(?:e|x|episode|\n)(?P\d+) # s 01e02""" , re.VERBOSE)
pattern4 = re.compile(r"""(?:s|season)(?P\d+)(?:e|x|episode|\n)(?P\d+) # s01e02""" , re.VERBOSE)
pattern5 = re.compile(r"""(?:s|season)(?P\d+)(?:.*)(?:e|x|episode|\n)(?P\d+) # s01 random123 e02""" , re.VERBOSE)
pattern6 = re.compile(r"""(?:s|season)(?:\s)(?P\d+)(?:.*)(?:e|x|episode|\n)(?:\s)(?P\d+) # s 01 random123 e 02""", re.VERBOSE)
pattern7 = re.compile(r"""(?:s|season)(?:\s)(?P\d+)(?:.*)(?:e|x|episode|\n)(?P\d+) # s 01 random123 e02""" , re.VERBOSE)
pattern8 = re.compile(r"""(?:s|season)(?P\d+)(?:.*)(?:e|x|episode|\n)(?:\s)(?P\d+) # s01 random123 e 02""" , re.VERBOSE)
patterns = [pattern1, pattern2, pattern3, pattern4, pattern5, pattern6, pattern7, pattern8 ]
for idx, p in enumerate(patterns):
m = re.search(p, label)
if m:
season = int( m.group('s'))
episode = int( m.group('ep'))
log("parseSEinfo, return S:" + str(season) +',E:'+ str(episode))
return season, episode
socket.setdefaulttimeout(TIMEOUT)
class PlayOn:
def __init__(self):
self.cache = SimpleCache()
random.seed()
if URLTYPE == 'upnp': self.chkUPNP()
def openURL(self, url):
log('openURL, url = ' + str(url))
try:
cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url)
if not cacheResponse:
request = urllib2.Request(url)
request.add_header('User-Agent','Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)')
response = urllib2.urlopen(request, timeout=TIMEOUT).read()
self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, response, expiration=datetime.timedelta(hours=1))
if url == BASE_URL + PLAYON_DATA:
self.chkIP(response)
return self.cache.get(ADDON_NAME + '.openURL, url = %s'%url)
except urllib2.URLError, e:
log("openURL Failed! " + str(e), xbmc.LOGERROR)
except socket.timeout, e:
log("openURL Failed! " + str(e), xbmc.LOGERROR)
except Exception, e:
log("openURL Failed! " + str(e), xbmc.LOGERROR)
xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30010), ICON, 5000)
def chkIP(self, response):
results = xmltodict.parse(response)
if results:
if 'catalog' in results:
try:
ServerName = results['catalog']['@name']
ServerVer = 'PlayOn v.%s'%results['catalog']['@server']
ServerMSG = "Connected to [B]%s %s[/B]"%(ServerName,ServerVer)
log('chkIP, ServerName = ' + ServerName)
log('chkIP, ServerVer = ' + ServerVer)
REAL_SETTINGS.setSetting("playonServerid",ServerMSG)
xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30011)%ServerName, ICON, 5000)
except:
pass
else:
xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30012), ICON, 5000)
def getUPNP(self):
""" Check if upnp id is valid. """
url = BASE_UPNP
json_query = ('{"jsonrpc":"2.0","method":"Files.GetDirectory","params":{"directory":"%s"},"id":1}'%url)
data = json.loads(xbmc.executeJSONRPC(json_query))
try:
if not data['result']['files'][0]['file'].endswith('/playonprovider/'):
url = ''
except Exception,e:
url = ''
log('getUPNP, Failed! ' + str(e))
log('getUPNP, url = ' + url)
return url
def chkUPNP(self):
""" Query json, locate 'playon server' path, else prompt. """
log('chkUPNP')
if len(self.getUPNP()) > 0:
return
else:
json_query = ('{"jsonrpc":"2.0","method":"Files.GetDirectory","params":{"directory":"upnp://"},"id":1}')
data = json.loads(xbmc.executeJSONRPC(json_query))
if data and 'result' in data and 'files' in data['result']:
for item in data['result']['files']:
if (item['label']).lower().startswith('playon'):
REAL_SETTINGS.setSetting("playonUPNPid",item['file'].rstrip('/'))
xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30013), ICON, 5000)
BASE_UPNP = item['file']
REAL_SETTINGS.setSetting("playonUPNPid",BASE_UPNP.rstrip('/'))
elif PTVL_RUNNING == False:
BASE_UPNP = xbmcgui.Dialog().browse(0, LANGUAGE(30014), 'files', '', False, False, 'upnp://')
if BASE_UPNP != -1:
REAL_SETTINGS.setSetting("playonUPNPid",BASE_UPNP.rstrip('/'))
else:
xbmcgui.Dialog().notification(ADDON_NAME, LANGUAGE(30010), ICON, 5000)
def buildItemMenu(self, uri=PLAYON_DATA):
log('buildItemMenu, uri = ' + uri)
try:
genSTRM = False
results = []
ranNum = random.randrange(9)
response = dict(xmltodict.parse(self.openURL(BASE_URL + uri)))
if response and 'catalog' in response and 'group' in response['catalog']:
results = response['catalog']['group']
elif response and 'group' in response and response['group']['@href'] == uri:
results = response['group']['group']
genSTRM = True
if isinstance(results,collections.OrderedDict):
results = [dict(results)]
for item in results:
try:
if isinstance(item,collections.OrderedDict):
item = dict(item)
if item['@type'] == 'folder':
name = item['@name'].replace('PlayOn','[B]- PlayOn[/B]').replace('PlayMark','[B]- PlayMark[/B]')
thumb = BASE_URL + ((item.get('@art','').replace('&size=tiny','&size=large')) or folderIcon(ranNum))
self.addDir(name,item['@href'] ,1,thumb, genSTRM)
elif item['@type'] == 'video':
self.addLink(item['@name'],item['@href'],9,len(results))
except Exception, e:
log("buildItemMenu Failed! " + str(e), xbmc.LOGERROR)
except Exception, e:
log("buildItemMenu Failed! " + str(e), xbmc.LOGERROR)
def playLater(self, name, uri):
log('playLater, uri = ' + uri)
response = dict(xmltodict.parse(self.openURL(BASE_URL + uri)))
if response and 'result' in response:
result = dict(response['result'])
if result['status'] == "true":
msg = result['msg'].replace('The media item',name)
xbmcgui.Dialog().notification(ADDON_NAME, msg, ICON, 5000)
def playVideo(self, name, uri):
log('playVideo, uri = ' + uri)
liz = self.buildListitem(uri)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz)
def buildListitem(self, uri, contextMenu=[]):
result = {}
infoList = {}
response = dict(xmltodict.parse(self.openURL(BASE_URL + uri)))
if response and 'group' in response and response['group']['@href'] == uri:
result = dict(response['group'])
tvshowtitle = (dict(result.get('series','')).get('@name','') or None)
title = (result.get('@name','') or dict(result['media_title'])['@name'] or dict(result['media'])['@name'])
label = title
mType = 'movie'
if tvshowtitle is not None:
if tvshowtitle not in title:
label = '%s - %s'%(tvshowtitle,title)
season, episode = parseSEinfo(title)
infoList['season'] = int(season)
infoList['episode'] = int(episode)
mType = 'episode'
plot = (result.get('@description','') or dict(result.get('description','')).get('@name','') or '')
thumb = BASE_URL + (result.get('@art','') or dict(result.get('media','')).get('@art','') or ICON).replace('&size=tiny','&size=large')
try:
aired = (dict(result.get('date','')).get('@name','') or datetime.datetime.now().strftime('%m/%d/%Y'))
aired = (datetime.datetime.strptime(aired, '%m/%d/%Y')).strftime('%Y-%m-%d')
except:
aired = datetime.datetime.now().strftime('%Y-%m-%d')
timeData = (dict(result.get('time','')).get('@name','') or '')
playLater = dict(result.get('media_playlater','')).get('@src','')
contextMenu = contextMenu + [('Add to PlayLater','XBMC.RunPlugin(%s)'%(sys.argv[0]+"?url="+urllib.quote_plus(playLater)+"&mode="+str(8)+"&name="+urllib.quote_plus(label.encode("utf-8"))))]
if len(timeData) > 0:
timeList = timeData.split(':')
hours = int(timeList[0])
mins = int(timeList[1])
secs = int(timeList[2])
duration = ((hours * 60 * 60) + (mins * 60) + secs)
else:
duration = 0
if URLTYPE == 'm3u8' and 'playlaterrecordings' not in result['@href']:
url = BASE_VIDEO_URL%(BASE_URL,result['@href'].split('?id=')[1])
elif URLTYPE == 'ext' or 'playlaterrecordings' in result['@href']:
url = BASE_URL + '/' + dict(result['media'])['@src']
else:
url = BASE_UPNP + '/' + dict(result['media'])['@src'].split('.')[0].split('/')[0] + '/'
log('playVideo, url = ' + url)
liz=xbmcgui.ListItem(name, path=url)
liz.addContextMenuItems(contextMenu)
infoList = {"mediatype":mType,"label":label,"title":label,"tvshowtitle":tvshowtitle,"plot":plot,"duration":duration,"aired":aired}
infoArt = {"thumb":thumb,"poster":thumb,"icon":ICON,"fanart":FANART}
liz.setInfo(type="Video", infoLabels=infoList)
liz.setArt(infoArt)
liz.setProperty("IsPlayable","true")
liz.setProperty("IsInternetStream","true")
return liz
def addLink(self, name, u, mode, total=0):
name = name.encode("utf-8")
log('addLink, name = ' + name)
contextMenu = [('Add to Library','XBMC.RunPlugin(%s)'%(sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(7)+"&name="+urllib.quote_plus(name)))]
liz = self.buildListitem(u)
u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total)
def addDir(self, name, u, mode, thumb=ICON, strm=False):
name = name.encode("utf-8")
log('addDir, name = ' + name)
liz=xbmcgui.ListItem(name)
# if strm:
# contextMenu = [('Add to Library','XBMC.RunPlugin(%s)'%(sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(7)+"&name="+urllib.quote_plus(name)))]
# liz.addContextMenuItems(contextMenu)
liz.setProperty('IsPlayable', 'false')
liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name})
liz.setArt({'thumb':thumb,'fanart':FANART})
u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
def genSTRMS(self, name, uri):
log('genSTRMS, name = ' + name)
#todo
params=getParams()
try:
url=urllib.unquote_plus(params["url"])
except:
url=None
try:
name=urllib.unquote_plus(params["name"])
except:
name=None
try:
mode=int(params["mode"])
except:
mode=None
log("Mode: "+str(mode))
log("URL : "+str(url))
log("Name: "+str(name))
if mode==None: PlayOn().buildItemMenu()
if mode==1: PlayOn().buildItemMenu(url)
if mode==7: PlayOn().genSTRMS(name, url)
if mode==8: PlayOn().playLater(name, url)
if mode==9: PlayOn().playVideo(name, url)
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_NONE )
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_LABEL )
xbmcplugin.endOfDirectory(int(sys.argv[1]),cacheToDisc=True)