initial commit - first version

This commit is contained in:
Marcel van der Veldt 2015-03-13 22:24:59 +01:00
parent fdbfc9455b
commit 860bdfbbd8
24 changed files with 3353 additions and 0 deletions

250
resources/lib/API.py Normal file
View file

@ -0,0 +1,250 @@
# API.py
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
from datetime import datetime
class API():
def getPeople(self, item):
# Process People
director=''
writer=''
cast=[]
people = item.get("People")
if(people != None):
for person in people:
if(person.get("Type") == "Director"):
director = director + person.get("Name") + ' '
if(person.get("Type") == "Writing"):
writer = person.get("Name")
if(person.get("Type") == "Writer"):
writer = person.get("Name")
if(person.get("Type") == "Actor"):
Name = person.get("Name")
Role = person.get("Role")
if Role == None:
Role = ''
cast.append(Name)
return {'Director' : director,
'Writer' : writer,
'Cast' : cast
}
def getTimeInfo(self, item):
resumeTime = ''
userData = item.get("UserData")
PlaybackPositionTicks = '100'
if userData.get("PlaybackPositionTicks") != None:
PlaybackPositionTicks = str(userData.get("PlaybackPositionTicks"))
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
resumeTime = reasonableTicks / 10000
try:
tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60))
except TypeError:
try:
tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60))
except TypeError:
tempDuration = "0"
cappedPercentage = None
resume=0
percentage=0
if (resumeTime != "" and int(resumeTime) > 0):
duration = float(tempDuration)
if(duration > 0):
resume = float(resumeTime) / 60
percentage = int((resume / duration) * 100.0)
return {'Duration' : tempDuration,
'TotalTime' : tempDuration,
'Percent' : str(percentage),
'ResumeTime' : str(resume)
}
def getStudio(self, item):
# Process Studio
studio = ""
if item.get("SeriesStudio") != None and item.get("SeriesStudio") != '':
studio = item.get("SeriesStudio")
if studio == "":
studios = item.get("Studios")
if(studios != None):
for studio_string in studios:
if studio=="": #Just take the first one
temp=studio_string.get("Name")
studio=temp.encode('utf-8')
return studio
def getMediaStreams(self, item, mediaSources=False):
# Process MediaStreams
channels = ''
videocodec = ''
audiocodec = ''
height = ''
width = ''
aspectratio = '1:1'
aspectfloat = 1.85
if mediaSources == True:
mediaSources = item.get("MediaSources")
if(mediaSources != None):
MediaStreams = mediaSources[0].get("MediaStreams")
else:
MediaStreams = None
else:
MediaStreams = item.get("MediaStreams")
if(MediaStreams != None):
#mediaStreams = MediaStreams[0].get("MediaStreams")
if(MediaStreams != None):
for mediaStream in MediaStreams:
if(mediaStream.get("Type") == "Video"):
videocodec = mediaStream.get("Codec")
height = str(mediaStream.get("Height"))
width = str(mediaStream.get("Width"))
aspectratio = mediaStream.get("AspectRatio")
if aspectratio != None and len(aspectratio) >= 3:
try:
aspectwidth,aspectheight = aspectratio.split(':')
aspectfloat = float(aspectwidth) / float(aspectheight)
except:
aspectfloat = 1.85
if(mediaStream.get("Type") == "Audio"):
audiocodec = mediaStream.get("Codec")
channels = mediaStream.get("Channels")
return {'channels' : str(channels),
'videocodec' : videocodec,
'audiocodec' : audiocodec,
'height' : height,
'width' : width,
'aspectratio' : str(aspectfloat)
}
def getUserData(self, item):
userData = item.get("UserData")
resumeTime = 0
if(userData != None):
if userData.get("Played") != True:
watched="True"
else:
watched="False"
if userData.get("IsFavorite") == True:
favorite="True"
else:
favorite="False"
if(userData.get("Played") == True):
playcount="1"
else:
playcount="0"
if userData.get('UnplayedItemCount') != None:
UnplayedItemCount = userData.get('UnplayedItemCount')
else:
UnplayedItemCount = "0"
if userData.get('PlaybackPositionTicks') != None:
PlaybackPositionTicks = userData.get('PlaybackPositionTicks')
else:
PlaybackPositionTicks = ''
return {'Watched' : watched,
'Favorite' : favorite,
'PlayCount': playcount,
'UnplayedItemCount' : UnplayedItemCount,
'PlaybackPositionTicks' : str(PlaybackPositionTicks)
}
def getGenre(self,item):
genre = ""
genres = item.get("Genres")
if genres != None and genres != []:
for genre_string in genres:
if genre == "": #Just take the first genre
genre = genre_string
else:
genre = genre + " / " + genre_string
elif item.get("SeriesGenres") != None and item.get("SeriesGenres") != '':
genres = item.get("SeriesGenres")
if genres != None and genres != []:
for genre_string in genres:
if genre == "": #Just take the first genre
genre = genre_string
else:
genre = genre + " / " + genre_string
return genre
def getName(self, item):
Temp = item.get("Name")
if Temp == None:
Temp = ""
Name=Temp.encode('utf-8')
return Name
def getRecursiveItemCount(self, item):
if item.get("RecursiveItemCount") != None:
return str(item.get("RecursiveItemCount"))
else:
return "0"
def getSeriesName(self, item):
Temp = item.get("SeriesName")
if Temp == None:
Temp = ""
Name=Temp.encode('utf-8')
return Name
def getOverview(self, item):
Temp = item.get("Overview")
if Temp == None:
Temp=''
Overview1=Temp.encode('utf-8')
Overview=str(Overview1)
return Overview
def getPremiereDate(self, item):
if(item.get("PremiereDate") != None):
premieredatelist = (item.get("PremiereDate")).split("T")
premieredate = premieredatelist[0]
else:
premieredate = ""
Temp = premieredate
premieredate = Temp.encode('utf-8')
return premieredate
def getTVInfo(self, item, userData):
TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount")
TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount")
WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-int(userData.get("UnplayedItemCount"))
UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else int(userData.get("UnplayedItemCount"))
NumEpisodes = TotalEpisodes
tempEpisode = ""
if (item.get("IndexNumber") != None):
episodeNum = item.get("IndexNumber")
if episodeNum < 10:
tempEpisode = "0" + str(episodeNum)
else:
tempEpisode = str(episodeNum)
tempSeason = ""
if (str(item.get("ParentIndexNumber")) != None):
tempSeason = str(item.get("ParentIndexNumber"))
if item.get("ParentIndexNumber") < 10:
tempSeason = "0" + tempSeason
if item.get("SeriesName") != None:
temp=item.get("SeriesName")
SeriesName=temp.encode('utf-8')
else:
SeriesName=''
return {'TotalSeasons' : str(TotalSeasons),
'TotalEpisodes' : str(TotalEpisodes),
'WatchedEpisodes' : str(WatchedEpisodes),
'UnWatchedEpisodes': str(UnWatchedEpisodes),
'NumEpisodes' : str(NumEpisodes),
'Season' : tempSeason,
'Episode' : tempEpisode,
'SeriesName' : SeriesName
}
def getDate(self, item):
tempDate = item.get("DateCreated")
if tempDate != None:
tempDate = tempDate.split("T")[0]
date = tempDate.split("-")
tempDate = date[2] + "." + date[1] + "." +date[0]
else:
tempDate = "01.01.2000"
return tempDate

View file

@ -0,0 +1,52 @@
from uuid import uuid4 as uuid4
import xbmc
import xbmcaddon
import xbmcgui
class ClientInformation():
def getMachineId(self):
WINDOW = xbmcgui.Window( 10000 )
clientId = WINDOW.getProperty("client_id")
self.addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
if(clientId == None or clientId == ""):
xbmc.log("CLIENT_ID - > No Client ID in WINDOW")
clientId = self.addonSettings.getSetting('client_id')
if(clientId == None or clientId == ""):
xbmc.log("CLIENT_ID - > No Client ID in SETTINGS")
uuid = uuid4()
clientId = str("%012X" % uuid)
WINDOW.setProperty("client_id", clientId)
self.addonSettings.setSetting('client_id', clientId)
xbmc.log("CLIENT_ID - > New Client ID : " + clientId)
else:
WINDOW.setProperty('client_id', clientId)
xbmc.log("CLIENT_ID - > Client ID saved to WINDOW from Settings : " + clientId)
return clientId
def getVersion(self):
version = xbmcaddon.Addon(id="plugin.video.mb3sync").getAddonInfo("version")
return version
def getPlatform(self):
if xbmc.getCondVisibility('system.platform.osx'):
return "OSX"
elif xbmc.getCondVisibility('system.platform.atv2'):
return "ATV2"
elif xbmc.getCondVisibility('system.platform.ios'):
return "iOS"
elif xbmc.getCondVisibility('system.platform.windows'):
return "Windows"
elif xbmc.getCondVisibility('system.platform.linux'):
return "Linux/RPi"
elif xbmc.getCondVisibility('system.platform.android'):
return "Linux/Android"
return "Unknown"

View file

@ -0,0 +1,147 @@
#################################################################################################
# connection manager class
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import json
import threading
from datetime import datetime
from DownloadUtils import DownloadUtils
import urllib
import sys
import socket
#define our global download utils
logLevel = 1
###########################################################################
class ConnectionManager():
addonSettings = None
__addon__ = xbmcaddon.Addon(id='plugin.video.mb3sync')
__addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') )
__language__ = __addon__.getLocalizedString
def printDebug(self, msg, level = 1):
if(logLevel >= level):
if(logLevel == 2):
try:
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg))
except UnicodeEncodeError:
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
else:
try:
xbmc.log("mb3sync " + str(level) + " -> " + str(msg))
except UnicodeEncodeError:
xbmc.log("mb3sync " + str(level) + " -> " + str(msg.encode('utf-8')))
def checkServer(self):
WINDOW = xbmcgui.Window( 10000 )
WINDOW.setProperty("Server_Checked", "True")
self.printDebug ("mb3sync Connection Manager Called")
self.addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
port = self.addonSettings.getSetting('port')
host = self.addonSettings.getSetting('ipaddress')
if(len(host) != 0 and host != "<none>"):
self.printDebug ("mb3sync server already set")
return
serverInfo = self.getServerDetails()
if(serverInfo == None):
self.printDebug ("mb3sync getServerDetails failed")
return
index = serverInfo.find(":")
if(index <= 0):
self.printDebug ("mb3sync getServerDetails data not correct : " + serverInfo)
return
server_address = serverInfo[:index]
server_port = serverInfo[index+1:]
self.printDebug ("mb3sync detected server info " + server_address + " : " + server_port)
xbmcgui.Dialog().ok(self.__language__(30167), self.__language__(30168), self.__language__(30169) + server_address, self.__language__(30030) + server_port)
# get a list of users
self.printDebug ("Getting user list")
jsonData = None
downloadUtils = DownloadUtils()
try:
jsonData = downloadUtils.downloadUrl(server_address + ":" + server_port + "/mediabrowser/Users/Public?format=json")
except Exception, msg:
error = "Get User unable to connect to " + server_address + ":" + server_port + " : " + str(msg)
xbmc.log (error)
return ""
if(jsonData == False):
return
self.printDebug("jsonData : " + str(jsonData), level=2)
result = json.loads(jsonData)
names = []
userList = []
for user in result:
name = user.get("Name")
userList.append(name)
if(user.get("HasPassword") == True):
name = name + " (Secure)"
names.append(name)
self.printDebug ("User List : " + str(names))
self.printDebug ("User List : " + str(userList))
return_value = xbmcgui.Dialog().select(self.__language__(30200), names)
if(return_value > -1):
selected_user = userList[return_value]
self.printDebug("Setting Selected User : " + selected_user)
if self.addonSettings.getSetting("port") != server_port:
self.addonSettings.setSetting("port", server_port)
if self.addonSettings.getSetting("ipaddress") != server_address:
self.addonSettings.setSetting("ipaddress", server_address)
if self.addonSettings.getSetting("username") != selected_user:
self.addonSettings.setSetting("username", selected_user)
def getServerDetails(self):
self.printDebug("Getting Server Details from Network")
MESSAGE = "who is MediaBrowserServer?"
#MULTI_GROUP = ("224.3.29.71", 7359)
#MULTI_GROUP = ("127.0.0.1", 7359)
MULTI_GROUP = ("<broadcast>", 7359)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(6.0)
#ttl = struct.pack('b', 20)
#sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
xbmc.log("MutliGroup : " + str(MULTI_GROUP));
xbmc.log("Sending UDP Data : " + MESSAGE);
sock.sendto(MESSAGE, MULTI_GROUP)
try:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
xbmc.log("Received Response : " + data)
if(data[0:18] == "MediaBrowserServer"):
xbmc.log("Found Server : " + data[19:])
return data[19:]
except:
xbmc.log("No UDP Response")
pass
return None

View file

@ -0,0 +1,594 @@
import xbmc
import xbmcgui
import xbmcaddon
import urllib
import urllib2
import httplib
import hashlib
import StringIO
import gzip
import sys
import inspect
import json as json
from random import randrange
from uuid import uuid4 as uuid4
from ClientInformation import ClientInformation
import Utils as utils
import encodings
import time
import traceback
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
getString = addonSettings.getLocalizedString
class DownloadUtils():
logLevel = 0
getString = None
LogCalls = False
TrackLog = ""
TotalUrlCalls = 0
def __init__(self, *args):
pass
def getServer(self):
port = addonSettings.getSetting('port')
host = addonSettings.getSetting('ipaddress')
return host + ":" + port
def getUserId(self, suppress=True):
WINDOW = xbmcgui.Window( 10000 )
port = addonSettings.getSetting('port')
host = addonSettings.getSetting('ipaddress')
userName = addonSettings.getSetting('username')
userid = WINDOW.getProperty("userid" + userName)
if(userid != None and userid != ""):
utils.logMsg("MB3 Sync","DownloadUtils -> Returning saved UserID : " + userid + "UserName: " + userName)
return userid
utils.logMsg("MB3 Sync","Looking for user name: " + userName)
authOk = self.authenticate()
if(authOk == ""):
if(suppress == False):
xbmcgui.Dialog().ok(getString(30044), getString(30044))
return ""
userid = WINDOW.getProperty("userid"+ userName)
if(userid == "" and suppress == False):
xbmcgui.Dialog().ok(getString(30045),getString(30045))
utils.logMsg("MB3 Sync","userid : " + userid)
self.postcapabilities()
return userid
def postcapabilities(self):
utils.logMsg("MB3 Sync","postcapabilities called")
# Set Capabilities
mb3Port = addonSettings.getSetting('port')
mb3Host = addonSettings.getSetting('ipaddress')
clientInfo = ClientInformation()
machineId = clientInfo.getMachineId()
# get session id
url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions?DeviceId=" + machineId + "&format=json"
utils.logMsg("MB3 Sync","Session URL : " + url);
jsonData = self.downloadUrl(url)
utils.logMsg("MB3 Sync","Session JsonData : " + jsonData)
result = json.loads(jsonData)
utils.logMsg("MB3 Sync","Session JsonData : " + str(result))
sessionId = result[0].get("Id")
utils.logMsg("MB3 Sync","Session Id : " + str(sessionId))
# post capability data
playableMediaTypes = "Audio,Video,Photo"
supportedCommands = "Play,Playstate,DisplayContent,GoHome,SendString,GoToSettings,DisplayMessage,PlayNext"
url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions/Capabilities?Id=" + sessionId + "&PlayableMediaTypes=" + playableMediaTypes + "&SupportedCommands=" + supportedCommands
postData = {}
#postData["Id"] = sessionId;
#postData["PlayableMediaTypes"] = "Video";
#postData["SupportedCommands"] = "MoveUp";
stringdata = json.dumps(postData)
utils.logMsg("MB3 Sync","Capabilities URL : " + url);
utils.logMsg("MB3 Sync","Capabilities Data : " + stringdata)
self.downloadUrl(url, postBody=stringdata, type="POST")
def authenticate(self):
WINDOW = xbmcgui.Window( 10000 )
token = WINDOW.getProperty("AccessToken"+addonSettings.getSetting('username'))
if(token != None and token != ""):
utils.logMsg("MB3 Sync","DownloadUtils -> Returning saved AccessToken for user : " + addonSettings.getSetting('username') + " token: "+ token)
return token
port = addonSettings.getSetting("port")
host = addonSettings.getSetting("ipaddress")
if(host == None or host == "" or port == None or port == ""):
return ""
url = "http://" + addonSettings.getSetting("ipaddress") + ":" + addonSettings.getSetting("port") + "/mediabrowser/Users/AuthenticateByName?format=json"
clientInfo = ClientInformation()
txt_mac = clientInfo.getMachineId()
version = clientInfo.getVersion()
deviceName = addonSettings.getSetting('deviceName')
deviceName = deviceName.replace("\"", "_")
authString = "Mediabrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
headers = {'Accept-encoding': 'gzip', 'Authorization' : authString}
if addonSettings.getSetting('password') !=None and addonSettings.getSetting('password') !='':
sha1 = hashlib.sha1(addonSettings.getSetting('password'))
sha1 = sha1.hexdigest()
else:
sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
messageData = "username=" + addonSettings.getSetting('username') + "&password=" + sha1
resp = self.downloadUrl(url, postBody=messageData, type="POST", authenticate=False, suppress=True)
result = None
accessToken = None
try:
result = json.loads(resp)
accessToken = result.get("AccessToken")
except:
pass
if(result != None and accessToken != None):
utils.logMsg("MB3 Sync","User Authenticated : " + accessToken)
WINDOW.setProperty("AccessToken"+addonSettings.getSetting('username'), accessToken)
WINDOW.setProperty("userid"+addonSettings.getSetting('username'), result.get("User").get("Id"))
WINDOW.setProperty("mb3_authenticated", "true")
return accessToken
else:
utils.logMsg("MB3 Sync","User NOT Authenticated")
WINDOW.setProperty("AccessToken"+addonSettings.getSetting('username'), "")
WINDOW.setProperty("mb3_authenticated", "false")
return ""
def getArtwork(self, data, type, index = "0", userParentInfo = False):
id = data.get("Id")
getSeriesData = False
userData = data.get("UserData")
if type == "tvshow.poster": # Change the Id to the series to get the overall series poster
if data.get("Type") == "Season" or data.get("Type")== "Episode":
id = data.get("SeriesId")
getSeriesData = True
elif type == "poster" and data.get("Type") == "Episode" and addonSettings.getSetting('useSeasonPoster')=='true': # Change the Id to the Season to get the season poster
id = data.get("SeasonId")
if type == "poster" or type == "tvshow.poster": # Now that the Ids are right, change type to MB3 name
type="Primary"
if data.get("Type") == "Season": # For seasons: primary (poster), thumb and banner get season art, rest series art
if type != "Primary" and type != "Primary2" and type != "Primary3" and type != "Primary4" and type != "Thumb" and type != "Banner" and type!="Thumb3":
id = data.get("SeriesId")
getSeriesData = True
if data.get("Type") == "Episode": # For episodes: primary (episode thumb) gets episode art, rest series art.
if type != "Primary" and type != "Primary2" and type != "Primary3" and type != "Primary4":
id = data.get("SeriesId")
getSeriesData = True
if type =="Primary2" or type=="Primary3" or type=="Primary4":
id = data.get("SeasonId")
getSeriesData = True
if data.get("SeasonUserData") != None:
userData = data.get("SeasonUserData")
if id == None:
id=data.get("Id")
imageTag = "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format
originalType = type
if type == "Primary2" or type == "Primary3" or type == "Primary4" or type=="SeriesPrimary":
type = "Primary"
if type == "Backdrop2" or type=="Backdrop3" or type=="BackdropNoIndicators":
type = "Backdrop"
if type == "Thumb2" or type=="Thumb3":
type = "Thumb"
if(data.get("ImageTags") != None and data.get("ImageTags").get(type) != None):
imageTag = data.get("ImageTags").get(type)
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Logo":
imageTag = data.get("ParentLogoImageTag")
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Art":
imageTag = data.get("ParentArtImageTag")
if (data.get("Type") == "Episode") and originalType=="Thumb3":
imageTag = data.get("SeriesThumbImageTag")
if (data.get("Type") == "Season") and originalType=="Thumb3" and imageTag=="e3ab56fe27d389446754d0fb04910a34" :
imageTag = data.get("ParentThumbImageTag")
id = data.get("SeriesId")
query = ""
height = "10000"
width = "10000"
played = "0"
totalbackdrops = 0
if addonSettings.getSetting('showArtIndicators')=='true': # add watched, unplayedcount and percentage played indicators to posters
if (originalType =="Primary" or originalType =="Backdrop" or originalType =="Banner") and data.get("Type") != "Episode":
if originalType =="Backdrop" and index == "0" and data.get("BackdropImageTags") != None:
totalbackdrops = len(data.get("BackdropImageTags"))
if totalbackdrops != 0:
index = str(randrange(0,totalbackdrops))
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
elif originalType =="Primary2":
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
height = "338"
width = "226"
elif originalType =="Primary3" or originalType == "SeriesPrimary":
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
elif originalType =="Primary4":
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
height = "270"
width = "180"
elif type =="Primary" and data.get("Type") == "Episode":
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
height = "410"
width = "770"
elif originalType =="Backdrop2" or originalType =="Thumb2" and data.get("Type") != "Episode":
if originalType =="Backdrop2" and data.get("BackdropImageTags") != None:
totalbackdrops = len(data.get("BackdropImageTags"))
if totalbackdrops != 0:
index = str(randrange(0,totalbackdrops))
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
height = "370"
width = "660"
elif originalType =="Backdrop3" or originalType =="Thumb3" and data.get("Type") != "Episode":
if originalType =="Backdrop3" and data.get("BackdropImageTags") != None:
totalbackdrops = len(data.get("BackdropImageTags"))
if totalbackdrops != 0:
index = str(randrange(0,totalbackdrops))
if userData != None:
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
query = query + "&UnplayedCount=" + str(UnWatched)
if(userData != None and userData.get("Played") == True and addonSettings.getSetting('showWatchedIndicators')=='true'):
query = query + "&AddPlayedIndicator=true"
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
PlayedPercentage = userData.get("PlayedPercentage")
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
played = str(PlayedPercentage)
height = "910"
width = "1620"
if originalType =="BackdropNoIndicators" and index == "0" and data.get("BackdropImageTags") != None:
totalbackdrops = len(data.get("BackdropImageTags"))
if totalbackdrops != 0:
index = str(randrange(0,totalbackdrops))
# use the local image proxy server that is made available by this addons service
port = addonSettings.getSetting('port')
host = addonSettings.getSetting('ipaddress')
server = host + ":" + port
if addonSettings.getSetting('compressArt')=='true':
query = query + "&Quality=90"
if imageTag == None:
imageTag = "e3ab56fe27d389446754d0fb04910a34"
artwork = "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + index + "/" + imageTag + "/original/" + width + "/" + height + "/" + played + "?" + query
if addonSettings.getSetting('disableCoverArt')=='true':
artwork = artwork + "&EnableImageEnhancers=false"
utils.logMsg("MB3 Sync","getArtwork : " + artwork, level=2)
# do not return non-existing images
if ( (type!="Backdrop" and imageTag=="e3ab56fe27d389446754d0fb04910a34") | #Remember, this is the placeholder tag, meaning we didn't find a valid tag
(type=="Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) |
(type=="Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0)
):
if type != "Backdrop" or (type=="Backdrop" and getSeriesData==True and data.get("ParentBackdropImageTags") == None) or (type=="Backdrop" and getSeriesData!=True):
artwork=''
return artwork
def getUserArtwork(self, data, type, index = "0"):
id = data.get("Id")
port = addonSettings.getSetting('port')
host = addonSettings.getSetting('ipaddress')
server = host + ":" + port
artwork = "http://" + server + "/mediabrowser/Users/" + str(id) + "/Images/" + type + "?Format=original"
return artwork
def imageUrl(self, id, type, index, width, height):
port = addonSettings.getSetting('port')
host = addonSettings.getSetting('ipaddress')
server = host + ":" + port
return "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + str(index) + "/e3ab56fe27d389446754d0fb04910a34/original/" + str(width) + "/" + str(height) + "/0"
def getAuthHeader(self, authenticate=True):
clientInfo = ClientInformation()
txt_mac = clientInfo.getMachineId()
version = clientInfo.getVersion()
deviceName = addonSettings.getSetting('deviceName')
deviceName = deviceName.replace("\"", "_")
if(authenticate == False):
authString = "MediaBrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString}
return headers
else:
userid = self.getUserId()
authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString}
authToken = self.authenticate()
if(authToken != ""):
headers["X-MediaBrowser-Token"] = authToken
utils.logMsg("MB3 Sync","Authentication Header : " + str(headers))
return headers
def downloadUrl(self, url, suppress=False, postBody=None, type="GET", popup=0, authenticate=True ):
utils.logMsg("MB3 Sync","== ENTER: getURL ==")
self.TotalUrlCalls = self.TotalUrlCalls + 1
if(self.LogCalls):
stackString = ""
for f in inspect.stack():
stackString = stackString + "\r - " + str(f)
self.TrackLog = self.TrackLog + "HTTP_API_CALL : " + url + stackString + "\r"
link = ""
try:
if url[0:4] == "http":
serversplit = 2
urlsplit = 3
else:
serversplit = 0
urlsplit = 1
server = url.split('/')[serversplit]
urlPath = "/"+"/".join(url.split('/')[urlsplit:])
utils.logMsg("MB3 Sync","DOWNLOAD_URL = " + url)
utils.logMsg("MB3 Sync","server = "+str(server), level=2)
utils.logMsg("MB3 Sync","urlPath = "+str(urlPath), level=2)
conn = httplib.HTTPConnection(server, timeout=5)
head = self.getAuthHeader(authenticate)
utils.logMsg("MB3 Sync","HEADERS : " + str(head), level=1)
# make the connection and send the request
if(postBody != None):
head["Content-Type"] = "application/x-www-form-urlencoded"
head["Content-Length"] = str(len(postBody))
utils.logMsg("MB3 Sync","POST DATA : " + postBody)
conn.request(method=type, url=urlPath, body=postBody, headers=head)
else:
conn.request(method=type, url=urlPath, headers=head)
# get the response
tries = 0
while tries <= 4:
try:
data = conn.getresponse()
break
except:
# TODO: we need to work out which errors we can just quit trying immediately
if(xbmc.abortRequested == True):
return ""
xbmc.sleep(100)
if(xbmc.abortRequested == True):
return ""
tries += 1
if tries == 5:
data = conn.getresponse()
utils.logMsg("MB3 Sync","GET URL HEADERS : " + str(data.getheaders()), level=2)
# process the response
contentType = "none"
if int(data.status) == 200:
retData = data.read()
contentType = data.getheader('content-encoding')
utils.logMsg("MB3 Sync","Data Len Before : " + str(len(retData)), level=2)
if(contentType == "gzip"):
retData = StringIO.StringIO(retData)
gzipper = gzip.GzipFile(fileobj=retData)
link = gzipper.read()
else:
link = retData
utils.logMsg("MB3 Sync","Data Len After : " + str(len(link)), level=2)
utils.logMsg("MB3 Sync","====== 200 returned =======", level=2)
utils.logMsg("MB3 Sync","Content-Type : " + str(contentType), level=2)
utils.logMsg("MB3 Sync",link, level=2)
utils.logMsg("MB3 Sync","====== 200 finished ======", level=2)
elif ( int(data.status) == 301 ) or ( int(data.status) == 302 ):
try:
conn.close()
except:
pass
return data.getheader('Location')
elif int(data.status) == 401:
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
xbmc.log(error)
WINDOW = xbmcgui.Window(10000)
timeStamp = WINDOW.getProperty("mb3sync_LAST_USER_ERROR")
if(timeStamp == None or timeStamp == ""):
timeStamp = "0"
if((int(timeStamp) + 10) < int(time.time())):
xbmcgui.Dialog().ok(getString(30135), getString(30044))
WINDOW.setProperty("mb3sync_LAST_USER_ERROR", str(int(time.time())))
try:
conn.close()
except:
pass
return ""
elif int(data.status) >= 400:
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
xbmc.log(error)
if suppress is False:
if popup == 0:
xbmc.executebuiltin("XBMC.Notification(URL error: "+ str(data.reason) +",)")
else:
xbmcgui.Dialog().ok(getString(30135),server)
try:
conn.close()
except:
pass
return ""
else:
link = ""
except Exception, msg:
error = "Unable to connect to " + str(server) + " : " + str(msg)
xbmc.log(error)
stack = self.FormatException()
utils.logMsg("MB3 Sync",stack)
if suppress is False:
if popup == 0:
xbmc.executebuiltin("XBMC.Notification(: Connection Error: Error connecting to server,)")
else:
xbmcgui.Dialog().ok(getString(30204), str(msg))
pass
else:
try:
conn.close()
except:
pass
return link
def FormatException(self):
exception_list = traceback.format_stack()
exception_list = exception_list[:-2]
exception_list.extend(traceback.format_tb(sys.exc_info()[2]))
exception_list.extend(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1]))
exception_str = "Traceback (most recent call last):\n"
exception_str += "".join(exception_list)
# Removing the last \n
exception_str = exception_str[:-1]
return exception_str
def __del__(self):
return
# xbmc.log("\rURL_REQUEST_REPORT : Total Calls : " + str(self.TotalUrlCalls) + "\r" + self.TrackLog)

View file

@ -0,0 +1,40 @@
#################################################################################################
# Kodi Monitor
# Watched events that occur in Kodi, like setting media watched
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import json
import Utils as utils
from LibrarySync import LibrarySync
librarySync = LibrarySync()
WINDOW = xbmcgui.Window( 10000 )
class Kodi_Monitor(xbmc.Monitor):
def __init__(self, *args, **kwargs):
xbmc.Monitor.__init__(self)
def onDatabaseUpdated(self, database):
pass
#this library monitor is used to detect a watchedstate change by the user through the library
def onNotification (self,sender,method,data):
if method == "VideoLibrary.OnUpdate":
#check windowprop if the sync is busy to prevent any false updates
if WINDOW.getProperty("librarysync") != "busy":
jsondata = json.loads(data)
if jsondata != None:
playcount = None
playcount = jsondata.get("playcount")
item = jsondata.get("item").get("id")
if playcount != None:
librarySync.updatePlayCountFromKodi(item, playcount)

View file

@ -0,0 +1,270 @@
#################################################################################################
# LibrarySync
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import json
import threading
import urllib
from datetime import datetime, timedelta, time
import urllib2
import os
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
from xml.etree import ElementTree
from xml.dom import minidom
import xml.etree.cElementTree as ET
from API import API
import Utils as utils
from DownloadUtils import DownloadUtils
downloadUtils = DownloadUtils()
addon = xbmcaddon.Addon(id='plugin.video.mb3sync')
addondir = xbmc.translatePath( addon.getAddonInfo('profile') )
dataPath = os.path.join(addondir,"library")
movieLibrary = os.path.join(dataPath,'movies')
tvLibrary = os.path.join(dataPath,'tvshows')
WINDOW = xbmcgui.Window( 10000 )
port = addon.getSetting('port')
host = addon.getSetting('ipaddress')
server = host + ":" + port
userid = downloadUtils.getUserId()
class LibrarySync():
def syncDatabase(self):
WINDOW.setProperty("librarysync", "busy")
updateNeeded = False
allMovies = list()
for item in self.getMovies(True):
if not item.get('IsFolder'):
kodiItem = self.getKodiMovie(item["Id"])
allMovies.append(item["Id"])
if kodiItem == None:
self.addMovieToKodiLibrary(item)
updateNeeded = True
else:
self.updateMovieToKodiLibrary(item, kodiItem)
cleanNeeded = False
# process deletes
allLocaldirs, filesMovies = xbmcvfs.listdir(movieLibrary)
allMB3Movies = set(allMovies)
for dir in allLocaldirs:
if not dir in allMB3Movies:
self.deleteMovieFromKodiLibrary(dir)
cleanneeded = True
if cleanNeeded:
xbmc.executebuiltin("CleanLibrary(video)")
if updateNeeded:
xbmc.executebuiltin("UpdateLibrary(video)")
WINDOW.clearProperty("librarysync")
def updatePlayCounts(self):
#update all playcounts from MB3 to Kodi library
WINDOW.setProperty("librarysync", "busy")
for item in self.getMovies(False):
if not item.get('IsFolder'):
kodiItem = self.getKodiMovie(item["Id"])
userData=API().getUserData(item)
timeInfo = API().getTimeInfo(item)
if kodiItem != None:
if kodiItem['playcount'] != int(userData.get("PlayCount")):
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "playcount": %i}, "id": 1 }' %(kodiItem['movieid'], int(userData.get("PlayCount"))))
if kodiItem['playcount'] != int(userData.get("PlayCount")):
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "playcount": %i}, "id": 1 }' %(kodiItem['movieid'], int(userData.get("PlayCount"))))
WINDOW.clearProperty("librarysync")
def getMovies(self, fullinfo = False):
result = None
if fullinfo:
url = server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=Path,Genres,Studios,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Movie&format=json&ImageTypeLimit=1'
else:
url = server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=CumulativeRunTimeTicks&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Movie&format=json&ImageTypeLimit=1'
jsonData = downloadUtils.downloadUrl(url, suppress=True, popup=0)
if jsonData != None:
result = json.loads(jsonData)
if(result.has_key('Items')):
result = result['Items']
return result
def updatePlayCountFromKodi(self, id, playcount=0):
#when user marks item watched from kodi interface update this to MB3
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovieDetails", "params": { "movieid": ' + str(id) + ', "properties" : ["playcount", "file"] }, "id": "1"}')
if json_response != None:
jsonobject = json.loads(json_response.decode('utf-8','replace'))
movie = None
if(jsonobject.has_key('result')):
result = jsonobject['result']
if(result.has_key('moviedetails')):
moviedetails = result['moviedetails']
filename = moviedetails.get("file").rpartition('\\')[2]
mb3Id = filename.replace(".strm","")
watchedurl = 'http://' + host + ':' + port + '/mediabrowser/Users/' + userid + '/PlayedItems/' + mb3Id
print "watchedurl -->" + watchedurl
if playcount != 0:
downloadUtils.downloadUrl(watchedurl, postBody="", type="POST")
else:
downloadUtils.downloadUrl(watchedurl, type="DELETE")
def updateMovieToKodiLibrary( self, MBitem, KodiItem ):
#TODO: only update the info if something is actually changed
timeInfo = API().getTimeInfo(MBitem)
userData=API().getUserData(MBitem)
people = API().getPeople(MBitem)
mediaStreams=API().getMediaStreams(MBitem)
thumbPath = downloadUtils.getArtwork(MBitem, "Primary")
utils.logMsg("Updating item to Kodi Library", MBitem["Id"] + " - " + MBitem["Name"])
#update artwork
self.updateArtWork(KodiItem,"poster", downloadUtils.getArtwork(MBitem, "poster"))
self.updateArtWork(KodiItem,"clearlogo", downloadUtils.getArtwork(MBitem, "Logo"))
self.updateArtWork(KodiItem,"banner", downloadUtils.getArtwork(MBitem, "Banner"))
self.updateArtWork(KodiItem,"landscape", downloadUtils.getArtwork(MBitem, "Thumb"))
self.updateArtWork(KodiItem,"discart", downloadUtils.getArtwork(MBitem, "Disc"))
self.updateArtWork(KodiItem,"fanart", downloadUtils.getArtwork(MBitem, "Backdrop"))
#update duration
duration = (int(timeInfo.get('Duration'))*60)
if KodiItem['runtime'] != duration:
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "runtime": %s}, "id": 1 }' %(KodiItem['movieid'], duration))
#update year
if KodiItem['year'] != MBitem.get("ProductionYear") and MBitem.get("ProductionYear") != None:
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "year": %s}, "id": 1 }' %(KodiItem['movieid'], MBitem.get("ProductionYear")))
#update strm file - TODO: only update strm when path has changed
self.createSTRM(MBitem["Id"])
#update nfo file - needed for testing
nfoFile = os.path.join(movieLibrary,MBitem["Id"],MBitem["Id"] + ".nfo")
if not xbmcvfs.exists(nfoFile):
self.createNFO(MBitem)
#update playcounts
if KodiItem['playcount'] != int(userData.get("PlayCount")):
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "playcount": %i}, "id": 1 }' %(KodiItem['movieid'], int(userData.get("PlayCount"))))
def updateArtWork(self,KodiItem,artWorkName,artworkValue):
if KodiItem['art'].has_key(artWorkName):
if KodiItem['art'][artWorkName] != artworkValue and artworkValue != None:
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "art": { "%s": "%s" }}, "id": 1 }' %(KodiItem['movieid'], artWorkName, artworkValue))
def createSTRM(self,id):
itemPath = os.path.join(movieLibrary,id)
if not xbmcvfs.exists(itemPath):
xbmcvfs.mkdir(itemPath)
strmFile = os.path.join(itemPath,id + ".strm")
text_file = open(strmFile, "w")
playUrl = "plugin://plugin.video.mb3sync/?id=" + id + '&mode=play'
text_file.writelines(playUrl)
text_file.close()
def createNFO(self,item):
timeInfo = API().getTimeInfo(item)
userData=API().getUserData(item)
people = API().getPeople(item)
mediaStreams=API().getMediaStreams(item)
#todo: change path if type is not movie
itemPath = os.path.join(movieLibrary,item["Id"])
nfoFile = os.path.join(itemPath,item["Id"] + ".nfo")
root = Element("movie")
SubElement(root, "id").text = item["Id"]
SubElement(root, "tag").text = item["Id"]
SubElement(root, "thumb").text = downloadUtils.getArtwork(item, "poster")
SubElement(root, "fanart").text = timeInfo.get('Backdrop')
SubElement(root, "title").text = item["Name"].encode('utf-8').decode('utf-8')
SubElement(root, "originaltitle").text = item["Id"]
SubElement(root, "year").text = str(item.get("ProductionYear"))
SubElement(root, "runtime").text = str(timeInfo.get('Duration'))
fileinfo = SubElement(root, "fileinfo")
streamdetails = SubElement(fileinfo, "streamdetails")
video = SubElement(streamdetails, "video")
SubElement(video, "duration").text = str(timeInfo.get('totaltime'))
SubElement(video, "aspect").text = timeInfo.get('aspectratio')
SubElement(video, "codec").text = timeInfo.get('videocodec')
SubElement(video, "width").text = str(timeInfo.get('width'))
SubElement(video, "height").text = str(timeInfo.get('height'))
audio = SubElement(streamdetails, "audio")
SubElement(audio, "codec").text = timeInfo.get('audiocodec')
SubElement(audio, "channels").text = timeInfo.get('channels')
SubElement(root, "plot").text = API().getOverview(item).decode('utf-8')
art = SubElement(root, "art")
SubElement(art, "poster").text = downloadUtils.getArtwork(item, "poster")
SubElement(art, "fanart").text = downloadUtils.getArtwork(item, "Backdrop")
SubElement(art, "landscape").text = downloadUtils.getArtwork(item, "Thumb")
SubElement(art, "clearlogo").text = downloadUtils.getArtwork(item, "Logo")
SubElement(art, "discart").text = downloadUtils.getArtwork(item, "Disc")
SubElement(art, "banner").text = downloadUtils.getArtwork(item, "Banner")
ET.ElementTree(root).write(nfoFile, encoding="utf-8", xml_declaration=True)
def addMovieToKodiLibrary( self, item ):
itemPath = os.path.join(movieLibrary,item["Id"])
strmFile = os.path.join(itemPath,item["Id"] + ".strm")
utils.logMsg("Adding item to Kodi Library",item["Id"] + " - " + item["Name"])
#create path if not exists
if not xbmcvfs.exists(itemPath):
xbmcvfs.mkdir(itemPath)
#create nfo file
self.createNFO(item)
# create strm file
self.createSTRM(item["Id"])
def deleteMovieFromKodiLibrary(self, id ):
kodiItem = self.getKodiMovie(id)
utils.logMsg("deleting movie from Kodi library",id)
if kodiItem != None:
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "params": { "movieid": %i}, "id": 1 }' %(kodiItem["movieid"]))
path = os.path.join(movieLibrary,id)
xbmcvfs.rmdir(path)
def getKodiMovie(self, id):
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": { "filter": {"operator": "contains", "field": "path", "value": "' + id + '"}, "properties" : ["art", "rating", "thumbnail", "runtime", "year", "plot", "playcount", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
movie = None
if(jsonobject.has_key('result')):
result = jsonobject['result']
if(result.has_key('movies')):
movies = result['movies']
movie = movies[0]
return movie

170
resources/lib/PlayUtils.py Normal file
View file

@ -0,0 +1,170 @@
#################################################################################################
# utils class
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import json
import threading
from datetime import datetime
from DownloadUtils import DownloadUtils
from ClientInformation import ClientInformation
import urllib
import sys
import os
#define our global download utils
downloadUtils = DownloadUtils()
clientInfo = ClientInformation()
###########################################################################
class PlayUtils():
def getPlayUrl(self, server, id, result):
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
# if the path is local and depending on the video quality play we can direct play it do so-
if self.isDirectPlay(result) == True:
xbmc.log("mb3sync getPlayUrl -> Direct Play")
playurl = result.get("Path")
if playurl != None:
#We have a path to play so play it
USER_AGENT = 'QuickTime/7.7.4'
# If the file it is not a media stub
if (result.get("IsPlaceHolder") != True):
if (result.get("VideoType") == "Dvd"):
playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO"
elif (result.get("VideoType") == "BluRay"):
playurl = playurl + "/BDMV/index.bdmv"
if addonSettings.getSetting('smbusername') == '':
playurl = playurl.replace("\\\\", "smb://")
else:
playurl = playurl.replace("\\\\", "smb://" + addonSettings.getSetting('smbusername') + ':' + addonSettings.getSetting('smbpassword') + '@')
playurl = playurl.replace("\\", "/")
if ("apple.com" in playurl):
playurl += '?|User-Agent=%s' % USER_AGENT
if addonSettings.getSetting('playFromStream') == "true":
playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true'
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex'))
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex'))
else:
#No path or has a path but not sufficient network so transcode
xbmc.log("mb3sync getPlayUrl -> Transcode")
if result.get("Type") == "Audio":
playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream.mp3'
else:
txt_mac = clientInfo.getMachineId()
playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/master.m3u8?mediaSourceId=' + id
playurl = playurl + '&videoCodec=h264'
playurl = playurl + '&AudioCodec=aac,ac3'
playurl = playurl + '&deviceId=' + txt_mac
playurl = playurl + '&VideoBitrate=' + str(int(self.getVideoBitRate()) * 1000)
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex'))
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultSubtitleStreamIndex'))
return playurl.encode('utf-8')
# Works out if we are direct playing or not
def isDirectPlay(self, result):
if (self.fileExists(result) or (result.get("LocationType") == "FileSystem" and self.isNetworkQualitySufficient(result) == True and self.isLocalPath(result) == False)):
return True
else:
return False
# Works out if the network quality can play directly or if transcoding is needed
def isNetworkQualitySufficient(self, result):
settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = int(settingsVideoBitRate) * 1000
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('Bitrate') != None:
if settingsVideoBitRate < int(mediaSources[0].get('Bitrate')):
xbmc.log("mb3sync isNetworkQualitySufficient -> FALSE bit rate - settingsVideoBitRate: " + str(settingsVideoBitRate) + " mediasource bitrate: " + str(mediaSources[0].get('Bitrate')))
return False
else:
xbmc.log("mb3sync isNetworkQualitySufficient -> TRUE bit rate")
return True
# Any thing else is ok
xbmc.log("mb3sync isNetworkQualitySufficient -> TRUE default")
return True
# get the addon video quality
def getVideoBitRate(self):
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
videoQuality = addonSettings.getSetting('videoBitRate')
if (videoQuality == "0"):
return '664'
elif (videoQuality == "1"):
return '996'
elif (videoQuality == "2"):
return '1320'
elif (videoQuality == "3"):
return '2000'
elif (videoQuality == "4"):
return '3200'
elif (videoQuality == "5"):
return '4700'
elif (videoQuality == "6"):
return '6200'
elif (videoQuality == "7"):
return '7700'
elif (videoQuality == "8"):
return '9200'
elif (videoQuality == "9"):
return '10700'
elif (videoQuality == "10"):
return '12200'
elif (videoQuality == "11"):
return '13700'
elif (videoQuality == "12"):
return '15200'
elif (videoQuality == "13"):
return '16700'
elif (videoQuality == "14"):
return '18200'
elif (videoQuality == "15"):
return '20000'
elif (videoQuality == "16"):
return '40000'
elif (videoQuality == "17"):
return '100000'
elif (videoQuality == "18"):
return '1000000'
def fileExists(self, result):
path=result.get("Path").encode('utf-8')
if os.path.exists(path) == True:
return True
else:
return False
# Works out if the network quality can play directly or if transcoding is needed
def isLocalPath(self, result):
path=result.get("Path").encode('utf-8')
playurl = path
if playurl != None:
#We have a path to play so play it
if ":\\" in playurl:
return True
else:
return False
# default to not local
return False

View file

@ -0,0 +1,180 @@
import xbmc
import xbmcplugin
import xbmcgui
import xbmcaddon
import urllib
import datetime
import time
import json as json
import inspect
import sys
from DownloadUtils import DownloadUtils
downloadUtils = DownloadUtils()
from PlayUtils import PlayUtils
from API import API
import Utils as utils
addon = xbmcaddon.Addon(id='plugin.video.mb3sync')
language = addon.getLocalizedString
WINDOW = xbmcgui.Window( 10000 )
port = addon.getSetting('port')
host = addon.getSetting('ipaddress')
server = host + ":" + port
userid = downloadUtils.getUserId()
class PlaybackUtils():
settings = None
language = None
logLevel = 0
def __init__(self, *args):
pass
def PLAY(self, id):
jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json&ImageTypeLimit=1", suppress=False, popup=1 )
result = json.loads(jsonData)
userData = result.get("UserData")
resume_result = 0
seekTime = 0
if userData.get("PlaybackPositionTicks") != 0:
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
seekTime = reasonableTicks / 10000
displayTime = str(datetime.timedelta(seconds=seekTime))
display_list = [ language(30106) + ' ' + displayTime, language(30107)]
resumeScreen = xbmcgui.Dialog()
resume_result = resumeScreen.select(language(30105), display_list)
playurl = PlayUtils().getPlayUrl(server, id, result)
xbmc.log("Play URL: " + playurl)
thumbPath = downloadUtils.getArtwork(result, "Primary")
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath)
self.setListItemProps(server, id, listItem, result)
# Can not play virtual items
if (result.get("LocationType") == "Virtual"):
xbmcgui.Dialog().ok(self.language(30128), language(30129))
watchedurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id
positionurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayingItems/' + id
deleteurl = 'http://' + server + '/mediabrowser/Items/' + id
# set the current playing info
WINDOW.setProperty(playurl+"watchedurl", watchedurl)
WINDOW.setProperty(playurl+"positionurl", positionurl)
WINDOW.setProperty(playurl+"deleteurl", "")
WINDOW.setProperty(playurl+"deleteurl", deleteurl)
if resume_result == 0:
WINDOW.setProperty(playurl+"seektime", str(seekTime))
else:
WINDOW.clearProperty(playurl+"seektime")
if result.get("Type")=="Episode":
WINDOW.setProperty(playurl+"refresh_id", result.get("SeriesId"))
else:
WINDOW.setProperty(playurl+"refresh_id", id)
WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks")))
WINDOW.setProperty(playurl+"type", result.get("Type"))
WINDOW.setProperty(playurl+"item_id", id)
if PlayUtils().isDirectPlay(result) == True:
playMethod = "DirectPlay"
else:
playMethod = "Transcode"
WINDOW.setProperty(playurl+"playmethod", playMethod)
mediaSources = result.get("MediaSources")
if(mediaSources != None):
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
WINDOW.setProperty(playurl+"AudioStreamIndex", str(mediaSources[0].get('DefaultAudioStreamIndex')))
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
WINDOW.setProperty(playurl+"SubtitleStreamIndex", str(mediaSources[0].get('DefaultSubtitleStreamIndex')))
#this launches the playback
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
def setArt(self, list,name,path):
if name=='thumb' or name=='fanart_image' or name=='small_poster' or name=='tiny_poster' or name == "medium_landscape" or name=='medium_poster' or name=='small_fanartimage' or name=='medium_fanartimage' or name=='fanart_noindicators':
list.setProperty(name, path)
else:
list.setArt({name:path})
return list
def setListItemProps(self, server, id, listItem, result):
# set up item and item info
userid = downloadUtils.getUserId()
thumbID = id
eppNum = -1
seasonNum = -1
tvshowTitle = ""
if(result.get("Type") == "Episode"):
thumbID = result.get("SeriesId")
seasonNum = result.get("ParentIndexNumber")
eppNum = result.get("IndexNumber")
tvshowTitle = result.get("SeriesName")
self.setArt(listItem,'poster', downloadUtils.getArtwork(result, "Primary"))
self.setArt(listItem,'tvshow.poster', downloadUtils.getArtwork(result, "SeriesPrimary"))
self.setArt(listItem,'clearart', downloadUtils.getArtwork(result, "Art"))
self.setArt(listItem,'tvshow.clearart', downloadUtils.getArtwork(result, "Art"))
self.setArt(listItem,'clearlogo', downloadUtils.getArtwork(result, "Logo"))
self.setArt(listItem,'tvshow.clearlogo', downloadUtils.getArtwork(result, "Logo"))
self.setArt(listItem,'discart', downloadUtils.getArtwork(result, "Disc"))
self.setArt(listItem,'fanart_image', downloadUtils.getArtwork(result, "Backdrop"))
self.setArt(listItem,'landscape', downloadUtils.getArtwork(result, "Thumb"))
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
# Process Studios
studio = API().getStudio(result)
listItem.setInfo('video', {'studio' : studio})
# play info
playinformation = ''
if PlayUtils().isDirectPlay(result) == True:
playinformation = language(30165)
else:
playinformation = language(30166)
details = {
'title' : result.get("Name", "Missing Name") + ' - ' + playinformation,
'plot' : result.get("Overview")
}
if(eppNum > -1):
details["episode"] = str(eppNum)
if(seasonNum > -1):
details["season"] = str(seasonNum)
if tvshowTitle != None:
details["TVShowTitle"] = tvshowTitle
listItem.setInfo( "Video", infoLabels=details )
people = API().getPeople(result)
# Process Genres
genre = API().getGenre(result)
listItem.setInfo('video', {'director' : people.get('Director')})
listItem.setInfo('video', {'writer' : people.get('Writer')})
listItem.setInfo('video', {'mpaa': result.get("OfficialRating")})
listItem.setInfo('video', {'genre': genre})

313
resources/lib/Player.py Normal file
View file

@ -0,0 +1,313 @@
import xbmcaddon
import xbmcplugin
import xbmc
import xbmcgui
import os
import threading
import json
import KodiMonitor
import Utils as utils
from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils
from ClientInformation import ClientInformation
from LibrarySync import LibrarySync
librarySync = LibrarySync()
# service class for playback monitoring
class Player( xbmc.Player ):
logLevel = 0
played_information = {}
downloadUtils = None
settings = None
playStats = {}
def __init__( self, *args ):
self.settings = xbmcaddon.Addon(id='plugin.video.mb3sync')
self.downloadUtils = DownloadUtils()
try:
self.logLevel = int(self.settings.getSetting('logLevel'))
except:
pass
self.printDebug("mb3sync Service -> starting playback monitor service")
self.played_information = {}
pass
def printDebug(self, msg, level = 1):
if(self.logLevel >= level):
if(self.logLevel == 2):
try:
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg))
except UnicodeEncodeError:
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
else:
try:
xbmc.log("mb3sync " + str(level) + " -> " + str(msg))
except UnicodeEncodeError:
xbmc.log("mb3sync " + str(level) + " -> " + str(msg.encode('utf-8')))
def deleteItem (self, url):
return_value = xbmcgui.Dialog().yesno(__language__(30091),__language__(30092))
if return_value:
self.printDebug('Deleting via URL: ' + url)
progress = xbmcgui.DialogProgress()
progress.create(__language__(30052), __language__(30053))
self.downloadUtils.downloadUrl(url, type="DELETE")
progress.close()
xbmc.executebuiltin("Container.Refresh")
return 1
else:
return 0
def hasData(self, data):
if(data == None or len(data) == 0 or data == "None"):
return False
else:
return True
def stopAll(self):
if(len(self.played_information) == 0):
return
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
self.printDebug("mb3sync Service -> played_information : " + str(self.played_information))
for item_url in self.played_information:
data = self.played_information.get(item_url)
if(data != None):
self.printDebug("mb3sync Service -> item_url : " + item_url)
self.printDebug("mb3sync Service -> item_data : " + str(data))
deleteurl = data.get("deleteurl")
runtime = data.get("runtime")
currentPosition = data.get("currentPosition")
item_id = data.get("item_id")
refresh_id = data.get("refresh_id")
currentFile = data.get("currentfile")
if(refresh_id != None):
#todo: trigger update of single item from MB3, for now trigger full playcounts update
librarySync.updatePlayCounts()
if(currentPosition != None and self.hasData(runtime)):
runtimeTicks = int(runtime)
self.printDebug("mb3sync Service -> runtimeticks:" + str(runtimeTicks))
percentComplete = (currentPosition * 10000000) / runtimeTicks
markPlayedAt = float(90) / 100
self.printDebug("mb3sync Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt))
self.stopPlayback(data)
if (percentComplete > markPlayedAt):
gotDeleted = 0
if(deleteurl != None and deleteurl != ""):
self.printDebug("mb3sync Service -> Offering Delete:" + str(deleteurl))
gotDeleted = self.deleteItem(deleteurl)
self.played_information.clear()
# stop transcoding - todo check we are actually transcoding?
clientInfo = ClientInformation()
txt_mac = clientInfo.getMachineId()
url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
url = url + '?DeviceId=' + txt_mac
self.downloadUtils.downloadUrl(url, type="DELETE")
def stopPlayback(self, data):
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
item_id = data.get("item_id")
audioindex = data.get("AudioStreamIndex")
subtitleindex = data.get("SubtitleStreamIndex")
playMethod = data.get("playmethod")
currentPosition = data.get("currentPosition")
positionTicks = str(int(currentPosition * 10000000))
url = ("http://%s:%s/mediabrowser/Sessions/Playing/Stopped" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
url = url + "?itemId=" + item_id
url = url + "&canSeek=true"
url = url + "&PlayMethod=" + playMethod
url = url + "&QueueableMediaTypes=Video"
url = url + "&MediaSourceId=" + item_id
url = url + "&PositionTicks=" + positionTicks
if(audioindex != None and audioindex!=""):
url = url + "&AudioStreamIndex=" + audioindex
if(subtitleindex != None and subtitleindex!=""):
url = url + "&SubtitleStreamIndex=" + subtitleindex
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
def reportPlayback(self):
self.printDebug("reportPlayback Called")
currentFile = xbmc.Player().getPlayingFile()
#TODO need to change this to use the one in the data map
playTime = xbmc.Player().getTime()
data = self.played_information.get(currentFile)
# only report playback if mb3sync has initiated the playback (item_id has value)
if(data != None and data.get("item_id") != None):
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
item_id = data.get("item_id")
audioindex = data.get("AudioStreamIndex")
subtitleindex = data.get("SubtitleStreamIndex")
playMethod = data.get("playmethod")
paused = data.get("paused")
url = ("http://%s:%s/mediabrowser/Sessions/Playing/Progress" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
url = url + "?itemId=" + item_id
url = url + "&canSeek=true"
url = url + "&PlayMethod=" + playMethod
url = url + "&QueueableMediaTypes=Video"
url = url + "&MediaSourceId=" + item_id
url = url + "&PositionTicks=" + str(int(playTime * 10000000))
if(audioindex != None and audioindex!=""):
url = url + "&AudioStreamIndex=" + audioindex
if(subtitleindex != None and subtitleindex!=""):
url = url + "&SubtitleStreamIndex=" + subtitleindex
if(paused == None):
paused = "false"
url = url + "&IsPaused=" + paused
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
def onPlayBackPaused( self ):
currentFile = xbmc.Player().getPlayingFile()
self.printDebug("PLAYBACK_PAUSED : " + currentFile)
if(self.played_information.get(currentFile) != None):
self.played_information[currentFile]["paused"] = "true"
self.reportPlayback()
def onPlayBackResumed( self ):
currentFile = xbmc.Player().getPlayingFile()
self.printDebug("PLAYBACK_RESUMED : " + currentFile)
if(self.played_information.get(currentFile) != None):
self.played_information[currentFile]["paused"] = "false"
self.reportPlayback()
def onPlayBackSeek( self, time, seekOffset ):
self.printDebug("PLAYBACK_SEEK")
self.reportPlayback()
def onPlayBackStarted( self ):
# Will be called when xbmc starts playing a file
WINDOW = xbmcgui.Window( 10000 )
self.stopAll()
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
if xbmc.Player().isPlaying():
currentFile = xbmc.Player().getPlayingFile()
self.printDebug("mb3sync Service -> onPlayBackStarted" + currentFile)
# grab all the info about this item from the stored windows props
# only ever use the win props here, use the data map in all other places
deleteurl = WINDOW.getProperty(currentFile + "deleteurl")
runtime = WINDOW.getProperty(currentFile + "runtimeticks")
item_id = WINDOW.getProperty(currentFile + "item_id")
refresh_id = WINDOW.getProperty(currentFile + "refresh_id")
audioindex = WINDOW.getProperty(currentFile + "AudioStreamIndex")
subtitleindex = WINDOW.getProperty(currentFile + "SubtitleStreamIndex")
playMethod = WINDOW.getProperty(currentFile + "playmethod")
itemType = WINDOW.getProperty(currentFile + "type")
seekTime = WINDOW.getProperty(currentFile + "seektime")
if seekTime != "":
self.seekToPosition(int(seekTime))
if(item_id == None or len(item_id) == 0):
return
url = ("http://%s:%s/mediabrowser/Sessions/Playing" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
url = url + "?itemId=" + item_id
url = url + "&canSeek=true"
url = url + "&PlayMethod=" + playMethod
url = url + "&QueueableMediaTypes=Video"
url = url + "&MediaSourceId=" + item_id
if(audioindex != None and audioindex!=""):
url = url + "&AudioStreamIndex=" + audioindex
if(subtitleindex != None and subtitleindex!=""):
url = url + "&SubtitleStreamIndex=" + subtitleindex
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
# save data map for updates and position calls
data = {}
data["deleteurl"] = deleteurl
data["runtime"] = runtime
data["item_id"] = item_id
data["refresh_id"] = refresh_id
data["currentfile"] = currentFile
data["AudioStreamIndex"] = audioindex
data["SubtitleStreamIndex"] = subtitleindex
data["playmethod"] = playMethod
data["Type"] = itemType
self.played_information[currentFile] = data
self.printDebug("mb3sync Service -> ADDING_FILE : " + currentFile)
self.printDebug("mb3sync Service -> ADDING_FILE : " + str(self.played_information))
# log some playback stats
if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
else:
self.playStats[itemType] = 1
if(playMethod != None):
if(self.playStats.get(playMethod) != None):
count = self.playStats.get(playMethod) + 1
self.playStats[playMethod] = count
else:
self.playStats[playMethod] = 1
# reset in progress position
self.reportPlayback()
def GetPlayStats(self):
return self.playStats
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.printDebug("mb3sync Service -> onPlayBackEnded")
self.stopAll()
def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file
self.printDebug("mb3sync Service -> onPlayBackStopped")
self.stopAll()
def seekToPosition(self, seekTo):
#Jump to resume point
jumpBackSec = 10
seekToTime = seekTo - jumpBackSec
count = 0
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
count = count + 1
xbmc.Player().pause
xbmc.sleep(100)
xbmc.Player().seekTime(seekToTime)
xbmc.sleep(100)
xbmc.Player().play()

162
resources/lib/Utils.py Normal file
View file

@ -0,0 +1,162 @@
#################################################################################################
# utils
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import json
import os
import inspect
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
from xml.etree import ElementTree
from xml.dom import minidom
import xml.etree.cElementTree as ET
from API import API
from PlayUtils import PlayUtils
from DownloadUtils import DownloadUtils
downloadUtils = DownloadUtils()
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
language = addonSettings.getLocalizedString
def logMsg(title, msg, level = 1):
#todo --> get this from a setting
logLevel = 0
if(logLevel >= level):
if(logLevel == 1):
try:
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg))
except UnicodeEncodeError:
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
else:
try:
xbmc.log(title + " -> " + str(msg))
except UnicodeEncodeError:
xbmc.log(title + " -> " + str(msg.encode('utf-8')))
def checkKodiSources():
print "All sources in Kodi -->"
addon = xbmcaddon.Addon(id='plugin.video.mb3sync')
addondir = xbmc.translatePath( addon.getAddonInfo('profile') )
dataPath = os.path.join(addondir,"library")
movieLibrary = os.path.join(dataPath,'movies')
tvLibrary = os.path.join(dataPath,'tvshows')
if not xbmcvfs.exists(dataPath):
xbmcvfs.mkdir(dataPath)
if not xbmcvfs.exists(movieLibrary):
xbmcvfs.mkdir(movieLibrary)
if not xbmcvfs.exists(tvLibrary):
xbmcvfs.mkdir(tvLibrary)
allKodiSources = list()
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Files.GetSources", "params": { "media": "video"}, "id": 1 }')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
if(jsonobject.has_key('result')):
result = jsonobject['result']
if(result.has_key('sources')):
for source in result["sources"]:
allKodiSources.append(source["label"])
allKodiSources = set(allKodiSources)
rebootRequired = False
if not "mediabrowser_movies" in allKodiSources:
addKodiSource("mediabrowser_movies",movieLibrary)
rebootRequired = True
if not "mediabrowser_tvshows" in allKodiSources:
addKodiSource("mediabrowser_tvshows",tvLibrary)
rebootRequired = True
if rebootRequired:
ret = xbmcgui.Dialog().yesno(heading="MediaBrowser Sync service", line1="A restart of Kodi is needed to apply changes. Do you want to reboot now ?")
if ret:
xbmc.executebuiltin("RestartApp")
def addKodiSource(name, path):
userDataPath = xbmc.translatePath( "special://profile" )
sourcesFile = os.path.join(userDataPath,'sources.xml')
print "####parsing sources file #####" + sourcesFile
tree = ET.ElementTree(file=sourcesFile)
root = tree.getroot()
videosources = root.find("video")
#remove any existing entries
allsources = videosources.findall("source")
if allsources != None:
for source in allsources:
if source.find("name").text == name:
videosources.remove(source)
# add new source
source = SubElement(videosources,'source')
SubElement(source, "name").text = name
SubElement(source, "path").text = path
tree.write(sourcesFile)
def checkAuthentication():
#check authentication
if addonSettings.getSetting('username') != "" and addonSettings.getSetting('ipaddress') != "":
try:
downloadUtils.authenticate()
except Exception, e:
logMsg("MB3 Syncer authentication failed",e)
pass
def prettifyXml(elem):
rough_string = etree.tostring(elem, "utf-8")
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent="\t")
def doKodiCleanup():
#remove old testdata and remove missing files
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": {"properties" : ["file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
if(jsonobject.has_key('result')):
result = jsonobject['result']
if(result.has_key('movies')):
movies = result['movies']
for movie in movies:
if (xbmcvfs.exists(movie["file"]) == False) or ("plugin.video.xbmb3c" in movie["file"]):
print "deleting --> " + movie["file"]
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "params": { "movieid": %i}, "id": 1 }' %(movie["movieid"]))
def get_params( paramstring ):
xbmc.log("Parameter string: " + paramstring)
param={}
if len(paramstring)>=2:
params=paramstring
if params[0] == "?":
cleanedparams=params[1:]
else:
cleanedparams=params
if (params[len(params)-1]=='/'):
params=params[0:len(params)-2]
pairsofparams=cleanedparams.split('&')
for i in range(len(pairsofparams)):
splitparams={}
splitparams=pairsofparams[i].split('=')
if (len(splitparams))==2:
param[splitparams[0]]=splitparams[1]
elif (len(splitparams))==3:
param[splitparams[0]]=splitparams[1]+"="+splitparams[2]
xbmc.log("XBMB3C -> Detected parameters: " + str(param))
return param

View file

@ -0,0 +1 @@
# Dummy file to make this directory a package.