Version 1.1.63

alpha ready for beta testing
This commit is contained in:
angelblue05 2015-12-24 13:51:47 -06:00
parent 0200df3225
commit 3f6fe0a9e7
25 changed files with 6278 additions and 4069 deletions

View file

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.emby"
<addon id="plugin.video.emby"
name="Emby"
version="1.1.62"
version="1.1.63"
provider-name="Emby.media">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.3.0"/>
<import addon="script.module.requests" version="2.3.0" />
<import addon="plugin.video.emby.movies" version="0.01" />
<import addon="plugin.video.emby.tvshows" version="0.01" />
<import addon="plugin.video.emby.musicvideos" version="0.01" />
</requires>
<extension point="xbmc.python.pluginsource"
library="default.py">
@ -23,4 +26,4 @@
<summary lang="en"></summary>
<description lang="en">Welcome to Emby for Kodi A whole new way to manage and view your media library. The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Emby - the most powerful fully open source multi-client media metadata indexer and server.&#10;&#10;Emby for Kodi is the absolute best way to enjoy the incredible Kodi playback engine combined with the power of Emby's centralized database. Features: Direct integration with the Kodi library for native Kodi speed Instant synchronization with the Emby server Full support for Movie, TV and Music collections Emby Server direct stream and transcoding support - use Kodi when you are away from home!</description>
</extension>
</addon>
</addon>

View file

@ -1,18 +1,33 @@
# -*- coding: utf-8 -*-
import xbmcaddon, xbmc
import os, sys
#################################################################################################
import os
import sys
import urlparse
import xbmc
import xbmcaddon
#################################################################################################
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = addon_.getAddonInfo('path').decode('utf-8')
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource_path)
import Entrypoint as entrypoint
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
#################################################################################################
import entrypoint
import utils
#################################################################################################
enableProfiling = False
class Main:
# MAIN ENTRY POINT
def __init__(self):
@ -24,83 +39,66 @@ class Main:
try:
mode = params['mode'][0]
id = params.get('id', None)
if id:
id = id[0]
itemid = params.get('id')
if itemid:
itemid = itemid[0]
except:
params = {}
mode = ""
##### PLAY ITEM VIA plugin://plugin.video.emby/ #####
if "play" in mode or "playnow" in mode:
entrypoint.doPlayback(id)
#### DO RESET AUTH #####
elif "resetauth" in mode:
entrypoint.resetAuth()
##### DO DATABASE RESET #####
elif "reset" in mode:
import Utils as utils
utils.reset()
modes = {
##### ADD/REMOVE USER FROM SESSION #####
elif "adduser" in mode:
entrypoint.addUser()
'reset': utils.reset,
'resetauth': entrypoint.resetAuth,
'play': entrypoint.doPlayback,
'passwords': utils.passwordsXML,
'adduser': entrypoint.addUser,
'thememedia': entrypoint.getThemeMedia,
'channels': entrypoint.BrowseChannels,
'channelsfolder': entrypoint.BrowseChannels,
'nextup': entrypoint.getNextUpEpisodes,
'inprogressepisodes': entrypoint.getInProgressEpisodes,
'recentepisodes': entrypoint.getRecentEpisodes
}
##### SYNC THEME MEDIA #####
elif "thememedia" in mode:
entrypoint.getThemeMedia()
if modes.get(mode):
# Simple functions
if mode == "play":
dbid = params.get('dbid')
modes[mode](itemid, dbid)
##### LAUNCH EMBY USER PREFS #####
elif "userprefs" in mode:
entrypoint.userPreferences()
##### OPEN ADDON SETTINGS #####
elif "settings" in mode:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
##### MANUALLY SYNC LIBRARY #####
elif "manualsync" in mode:
from LibrarySync import LibrarySync
LibrarySync().FullLibrarySync(True)
##### CACHE ARTWORK #####
elif "texturecache" in mode:
from TextureCache import TextureCache
TextureCache().FullTextureCacheSync()
##### BROWSE EMBY CHANNELS FOLDER #####
elif "channelsfolder" in mode:
folderid = params['folderid'][0]
entrypoint.BrowseChannels(id,folderid)
elif mode in ("nextup", "inprogressepisodes", "recentepisodes"):
limit = int(params['limit'][0])
modes[mode](itemid, limit)
##### BROWSE EMBY CHANNELS ROOT #####
elif "channels" in mode:
entrypoint.BrowseChannels(id)
##### GET NEXTUP EPISODES FOR TAGNAME #####
elif "nextup" in mode:
limit = int(params['limit'][0])
entrypoint.getNextUpEpisodes(id, limit)
elif mode == "channels":
modes[mode](itemid)
##### GET INPROGRESS EPISODES FOR TAGNAME #####
elif "inprogressepisodes" in mode:
limit = int(params['limit'][0])
entrypoint.getInProgressEpisodes(id, limit)
elif mode == "channelsfolder":
folderid = params['folderid'][0]
modes[mode](itemid, folderid)
else:
modes[mode]()
else:
# Other functions
if mode == "settings":
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
elif mode in ("manualsync", "repair"):
import librarysync
if mode == "manualsync":
librarysync.LibrarySync().fullSync(manualrun=True)
else:
librarysync.LibrarySync().fullSync(repair=True)
elif mode == "texturecache":
import artwork
artwork.Artwork().FullTextureCacheSync()
elif "extrafanart" in sys.argv[0]:
entrypoint.getExtraFanArt()
else:
entrypoint.doMainListing()
##### GET RECENT EPISODES FOR TAGNAME #####
elif "recentepisodes" in mode:
limit = int(params['limit'][0])
entrypoint.getRecentEpisodes(id, limit)
##### GET EXTRAFANART FOR LISTITEM #####
elif "extrafanart" in sys.argv[0]:
entrypoint.getExtraFanArt()
##### SHOW ADDON NODES LISTING #####
if not mode:
entrypoint.doMainListing()
if ( __name__ == "__main__" ):
xbmc.log('plugin.video.emby started')

View file

@ -239,7 +239,7 @@
<string id="30246">Enable Netflix style next up notification</string>
<string id="30247"> - The number of seconds before the end to show the notification</string>
<string id="30248">Show Emby Info dialog on play/select action</string>
<string id="30249">Suppress server connection message on start-up</string>
<string id="30249">Enable server connection message on start-up</string>
<string id="30250">Use local paths instead of addon redirect for playback</string>

View file

@ -1,94 +1,245 @@
# -- coding: utf-8 --
# API.py
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
# -*- coding: utf-8 -*-
from datetime import datetime
from random import randrange
import xbmc
import xbmcgui
##################################################################################################
import clientinfo
import utils
##################################################################################################
import Utils as utils
class API():
def getPeople(self, item):
def __init__(self, item):
self.item = item
self.clientinfo = clientinfo.ClientInfo()
self.addonName = self.clientinfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getUserData(self):
# Default
favorite = False
playcount = None
played = False
lastPlayedDate = None
resume = 0
rating = 0
try:
userdata = self.item['UserData']
except KeyError: # No userdata found.
pass
else:
favorite = userdata['IsFavorite']
likes = userdata.get('Likes')
# Rating for album and songs
if favorite:
rating = 5
elif likes:
rating = 3
elif likes == False:
rating = 1
else:
rating = 0
lastPlayedDate = userdata.get('LastPlayedDate')
if lastPlayedDate:
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
if userdata['Played']:
# Playcount is tied to the watch status
played = True
playcount = userdata['PlayCount']
if playcount == 0:
playcount = 1
if lastPlayedDate is None:
lastPlayedDate = self.getDateCreated()
playbackPosition = userdata.get('PlaybackPositionTicks')
if playbackPosition:
resume = playbackPosition / 10000000.0
return {
'Favorite': favorite,
'PlayCount': playcount,
'Played': played,
'LastPlayedDate': lastPlayedDate,
'Resume': resume,
'Rating': rating
}
def getPeople(self):
# Process People
director = []
writer = []
cast = []
try:
people = item['People']
except: pass
people = self.item['People']
except KeyError:
pass
else:
for person in people:
type = person['Type']
Name = person['Name']
name = person['Name']
if "Director" in type:
director.append(Name)
elif "Writing" in type:
writer.append(Name)
elif "Writer" in type:
writer.append(Name)
director.append(name)
elif "Actor" in type:
cast.append(Name)
cast.append(name)
elif type in ("Writing", "Writer"):
writer.append(name)
return {
'Director': director,
'Writer': writer,
'Cast': cast
'Writer': writer,
'Cast': cast
}
def getTimeInfo(self, item):
# Runtime and Resume point
tempRuntime = 0
runtime = 0
resume = 0
def getMediaStreams(self):
item = self.item
videotracks = []
audiotracks = []
subtitlelanguages = []
try: # Get resume point
userdata = item['UserData']
playbackPosition = userdata['PlaybackPositionTicks']
resume = playbackPosition / 10000000.0
except: pass
try: # Get total runtime
tempRuntime = item['RunTimeTicks']
except:
try: tempRuntime = item['CumulativeRunTimeTicks']
except: pass
try:
media_streams = item['MediaSources'][0]['MediaStreams']
finally:
runtime = tempRuntime / 10000000.0
except KeyError:
media_streams = item['MediaStreams']
for media_stream in media_streams:
# Sort through Video, Audio, Subtitle
stream_type = media_stream['Type']
codec = media_stream.get('Codec', "").lower()
profile = media_stream.get('Profile', "").lower()
if stream_type == "Video":
# Height, Width, Codec, AspectRatio, AspectFloat, 3D
track = {
'videocodec': codec,
'height': media_stream.get('Height'),
'width': media_stream.get('Width'),
'video3DFormat': item.get('Video3DFormat'),
'aspectratio': 1.85
}
try:
container = item['MediaSources'][0]['Container'].lower()
except:
container = ""
# Sort codec vs container/profile
if "msmpeg4" in codec:
track['videocodec'] = "divx"
elif "mpeg4" in codec:
if "simple profile" in profile or not profile:
track['videocodec'] = "xvid"
elif "h264" in codec:
if container in ("mp4", "mov", "m4v"):
track['videocodec'] = "avc1"
# Aspect ratio
if item.get('AspectRatio'):
# Metadata AR
aspectratio = item['AspectRatio']
else: # File AR
aspectratio = media_stream.get('AspectRatio', "0")
try:
aspectwidth, aspectheight = aspectratio.split(':')
track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
except ValueError:
width = track['width']
height = track['height']
if width and height:
track['aspectratio'] = round(float(width / height), 6)
videotracks.append(track)
elif stream_type == "Audio":
# Codec, Channels, language
track = {
'audiocodec': codec,
'channels': media_stream.get('Channels'),
'audiolanguage': media_stream.get('Language')
}
if "dca" in codec and "dts-hd ma" in profile:
track['audiocodec'] = "dtshd_ma"
audiotracks.append(track)
elif stream_type == "Subtitle":
# Language
subtitlelanguages.append(media_stream.get('Language', "Unknown"))
return {
'ResumeTime': resume,
'TotalTime': runtime
'video': videotracks,
'audio': audiotracks,
'subtitle': subtitlelanguages
}
def getStudios(self, item):
# Process Studio
def getRuntime(self):
item = self.item
try:
runtime = item['RunTimeTicks'] / 10000000.0
except KeyError:
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
return runtime
def adjustResume(self, resume_seconds):
resume = 0
if resume_seconds:
resume = round(float(resume_seconds), 6)
jumpback = int(utils.settings('resumeJumpBack'))
if resume > jumpback:
# To avoid negative bookmark
resume = resume - jumpback
return resume
def getStudios(self):
# Process Studios
item = self.item
studios = []
try:
studio = item['SeriesStudio']
studios.append(self.getStudio(studio))
except:
try:
studioArray = item['Studios']
for studio in studioArray:
studios.append(self.getStudio(studio['Name']))
except: pass
studios.append(self.verifyStudio(studio))
except KeyError:
studioList = item['Studios']
for studio in studioList:
name = studio['Name']
studios.append(self.verifyStudio(name))
return studios
def getStudio(self, studioName):
def verifyStudio(self, studioName):
# Convert studio for Kodi to properly detect them
studios = {
@ -101,503 +252,127 @@ class API():
return studios.get(studioName.lower(), studioName)
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 getChecksum(self):
# Use the etags checksum and userdata
item = self.item
userdata = item['UserData']
def getMediaStreams(self, item, mediaSources = False):
videotracks = [] # Height, Width, Codec, AspectRatio, AspectFloat, 3D
audiotracks = [] # Codec, Channels, language
subtitlelanguages = [] # Language
if mediaSources:
try:
MediaStreams = item['MediaSources'][0]['MediaStreams']
except:
MediaStreams = None
else:
MediaStreams = item.get('MediaStreams')
if MediaStreams:
# Sort through the Video, Audio, Subtitle tracks
for mediaStream in MediaStreams:
type = mediaStream.get('Type', "")
profile = mediaStream.get('Profile', "").lower()
codec = mediaStream.get('Codec', "").lower()
if "Video" in type:
videotrack = {}
videotrack['videocodec'] = codec
container = item['MediaSources'][0].get('Container', "").lower()
if "msmpeg4" in videotrack['videocodec']:
videotrack['videocodec'] = "divx"
elif "mpeg4" in videotrack['videocodec']:
if "simple profile" in profile or profile == "":
videotrack['videocodec'] = "xvid"
elif "h264" in videotrack['videocodec']:
if container in ("mp4", "mov", "m4v"):
videotrack['videocodec'] = "avc1"
videotrack['height'] = mediaStream.get('Height')
videotrack['width'] = mediaStream.get('Width')
videotrack['Video3DFormat'] = item.get('Video3DFormat')
if item.get('AspectRatio'):
# Metadata aspect ratio
videotrack['aspectratio'] = item['AspectRatio']
else: # File aspect ratio
videotrack['aspectratio'] = mediaStream.get('AspectRatio', "0")
if len(videotrack['aspectratio']) >= 3:
try:
aspectwidth, aspectheight = videotrack['aspectratio'].split(':')
videotrack['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
except:
videotrack['aspectratio'] = 1.85
else:
try:
videotrack['aspectratio'] = round(float(videotrack['width'] / videotrack['height']), 6)
except: # In the event the aspect ratio is missing and the width and height are missing as well.
videotrack['aspectratio'] = 1.85
videotracks.append(videotrack)
elif "Audio" in type:
audiotrack = {}
audiotrack['audiocodec'] = codec
if "dca" in audiotrack['audiocodec'] and "dts-hd ma" in profile:
audiotrack['audiocodec'] = "dtshd_ma"
audiotrack['channels'] = mediaStream.get('Channels')
audiotrack['audiolanguage'] = mediaStream.get('Language')
audiotracks.append(audiotrack)
elif "Subtitle" in type:
try:
subtitlelanguages.append(mediaStream['Language'])
except:
subtitlelanguages.append("Unknown")
return {
'videocodec' : videotracks,
'audiocodec' : audiotracks,
'subtitlelanguage' : subtitlelanguages
}
def getChecksum(self, item):
# use the etags checksum for this if available
# AND the userdata
checksum = ""
if item.get("Etag") != None:
checksum = item.get("Etag")
userData = item.get("UserData")
if(userData != None):
checksum += str(userData.get("Played"))
checksum += str(userData.get("IsFavorite"))
if userData.get('UnplayedItemCount') != None:
checksum += str(userData.get("UnplayedItemCount"))
if userData.get('LastPlayedDate') != None:
checksum += str(userData.get("LastPlayedDate"))
if userData.get('PlaybackPositionTicks') != None:
checksum += str(userData.get("PlaybackPositionTicks"))
checksum = "%s%s%s%s%s%s" % (
item['Etag'],
userdata['Played'],
userdata['IsFavorite'],
userdata['PlaybackPositionTicks'],
userdata.get('UnplayedItemCount', ""),
userdata.get('LastPlayedDate', "")
)
return checksum
def getUserData(self, item):
# Default
favorite = False
playcount = None
lastPlayedDate = None
userKey = ""
def getGenres(self):
item = self.item
all_genres = ""
genres = item.get('Genres', item.get('SeriesGenres'))
if genres:
all_genres = " / ".join(genres)
return all_genres
def getDateCreated(self):
try:
userdata = item['UserData']
except: # No userdata found.
pass
else:
favorite = userdata['IsFavorite']
userKey = userdata.get('Key', "")
watched = userdata['Played']
if watched:
# Playcount is tied to the watch status
playcount = userdata['PlayCount']
if playcount == 0:
playcount = 1
else:
playcount = None
lastPlayedDate = userdata.get('LastPlayedDate', None)
if lastPlayedDate:
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
return {
'Favorite': favorite,
'PlayCount': playcount,
'LastPlayedDate': lastPlayedDate,
'Key': userKey
}
def getRecursiveItemCount(self, item):
if item.get("RecursiveItemCount") != None:
return str(item.get("RecursiveItemCount"))
else:
return "0"
def getOverview(self, item):
overview = ""
try:
overview = item['Overview']
overview = overview.replace("\"", "\'")
overview = overview.replace("\n", " ")
overview = overview.replace("\r", " ")
except: pass
return overview
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 getDateCreated(self, item):
try:
dateadded = item['DateCreated']
dateadded = self.item['DateCreated']
dateadded = dateadded.split('.')[0].replace('T', " ")
except:
except KeyError:
dateadded = None
return dateadded
def getPremiereDate(self, item):
def getPremiereDate(self):
try:
premiere = item['PremiereDate']
premiere = self.item['PremiereDate']
premiere = premiere.split('.')[0].replace('T', " ")
except:
except KeyError:
premiere = None
return premiere
def getTagline(self, item):
def getOverview(self):
try:
tagline = item['Taglines'][0]
except:
overview = self.item['Overview']
overview = overview.replace("\"", "\'")
overview = overview.replace("\n", " ")
overview = overview.replace("\r", " ")
except KeyError:
overview = ""
return overview
def getTagline(self):
try:
tagline = self.item['Taglines'][0]
except IndexError:
tagline = None
return tagline
def getProvider(self, item, providername):
# Provider Name: imdb or tvdb
def getProvider(self, providername):
try:
if "imdb" in providername:
provider = item['ProviderIds']['Imdb']
elif "tvdb" in providername:
provider = item['ProviderIds']['Tvdb']
elif "musicBrainzArtist" in providername:
provider = item['ProviderIds']['MusicBrainzArtist']
elif "musicBrainzAlbum" in providername:
provider = item['ProviderIds']['MusicBrainzAlbum']
elif "musicBrainzTrackId" in providername:
provider = item['ProviderIds']['MusicBrainzTrackId']
except:
provider = self.item['ProviderIds'][providername]
except KeyError:
provider = None
return provider
def getCountry(self, item):
def getMpaa(self):
# Convert more complex cases
mpaa = self.item.get('OfficialRating', "")
if mpaa in ("NR", "UR"):
# Kodi seems to not like NR, but will accept Not Rated
mpaa = "Not Rated"
return mpaa
def getCountry(self):
try:
country = item['ProductionLocations'][0]
except:
country = self.item['ProductionLocations'][0]
except IndexError:
country = None
return country
def getMpaa(self, item):
# Convert more complex cases
mpaa = item.get('OfficialRating', "")
if mpaa in ("NR", "UR"):
# Kodi seems to not like NR, but will accept Rated Not Rated
mpaa = "Rated Not Rated"
def getFilePath(self):
return mpaa
item = self.item
try:
filepath = item['Path']
def getAllArtwork(self, item, parentInfo = False):
except KeyError:
filepath = ""
"""
Get all artwork, it will return an empty string
for the artwork type not found.
else:
if "\\\\" in filepath:
# append smb protocol
filepath = filepath.replace("\\\\", "smb://")
filepath = filepath.replace("\\", "/")
Artwork type: Primary, Art, Banner, Logo, Thumb,
Disc, Backdrop
"""
username = utils.window('currUser')
server = utils.window('server%s' % username)
id = item['Id']
artworks = item['ImageTags']
backdrops = item['BackdropImageTags']
maxHeight = 10000
maxWidth = 10000
customquery = ""
if utils.settings('compressArt') == "true":
customquery = "&Quality=90"
if utils.settings('disableCoverArt') == "true":
customquery += "&EnableImageEnhancers=false"
allartworks = {
'Primary': "",
'Art': "",
'Banner': "",
'Logo': "",
'Thumb': "",
'Disc': "",
'Backdrop': []
}
# Process backdrops
backdropIndex = 0
for backdroptag in backdrops:
artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, backdropIndex, maxWidth, maxHeight, backdroptag, customquery)
allartworks['Backdrop'].append(artwork)
backdropIndex += 1
# Process the rest of the artwork
for art in artworks:
tag = artworks[art]
artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art, maxWidth, maxHeight, tag, customquery)
allartworks[art] = artwork
# Process parent items if the main item is missing artwork
if parentInfo:
if item.get('VideoType'):
videotype = item['VideoType']
# Specific format modification
if 'Dvd'in videotype:
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
elif 'Bluray' in videotype:
filepath = "%s/BDMV/index.bdmv" % filepath
# Process backdrops
if not allartworks['Backdrop']:
parentId = item.get('ParentBackdropItemId')
if parentId:
# If there is a parentId, go through the parent backdrop list
parentbackdrops = item['ParentBackdropImageTags']
if "\\" in filepath:
# Local path scenario, with special videotype
filepath = filepath.replace("/", "\\")
backdropIndex = 0
for parentbackdroptag in parentbackdrops:
artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, backdropIndex, maxWidth, maxHeight, parentbackdroptag, customquery)
allartworks['Backdrop'].append(artwork)
backdropIndex += 1
# Process the rest of the artwork
parentartwork = ['Logo', 'Art', 'Thumb']
for parentart in parentartwork:
if not allartworks[parentart]:
parentId = item.get('Parent%sItemId' % parentart)
if parentId:
parentTag = item['Parent%sImageTag' % parentart]
artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, parentart, maxWidth, maxHeight, parentTag, customquery)
allartworks[parentart] = artwork
# Parent album works a bit differently
if not allartworks['Primary']:
parentId = item.get('AlbumId')
if parentId and item.get('AlbumPrimaryImageTag'):
parentTag = item['AlbumPrimaryImageTag']
artwork = "%s/mediabrowser/Items/%s/Images/Primary/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, maxWidth, maxHeight, parentTag, customquery)
allartworks['Primary'] = artwork
return allartworks
def getArtwork(self, data, type, mediaType = "", 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 utils.settings('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" and type!="Backdrop":
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" or data.get("Type") == "Season") and type=="Backdrop":
if data.get("BackdropImageTags"):
imageTag = data['BackdropImageTags'][0]
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")
# for music we return the parent art if no image exists
if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Backdrop" and not data.get("BackdropImageTags"):
data["BackdropImageTags"] = data.get("ParentBackdropImageTags")
id = data.get("ParentBackdropItemId")
if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Logo" and (not imageTag or imageTag == "e3ab56fe27d389446754d0fb04910a34"):
imageTag = data.get("ParentLogoImageTag")
id = data.get("ParentLogoItemId")
if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Art" and (not imageTag or imageTag == "e3ab56fe27d389446754d0fb04910a34"):
imageTag = data.get("ParentArtImageTag")
id = data.get("ParentArtItemId")
query = ""
maxHeight = "10000"
maxWidth = "10000"
height = ""
width = ""
played = "0"
totalbackdrops = 0
if utils.settings('coverArtratio') == "true":
if mediaType in ("movie","boxset","tvshow"):
if "Primary" in type:
# Only force ratio for cover art for main covers
aspectratio = data.get("PrimaryImageAspectRatio")
width = "&Width=1000"
height = "&Height=1480"
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
# Load user information set by UserClient
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
if utils.settings('compressArt')=='true':
query = query + "&Quality=90"
if imageTag == None:
imageTag = "e3ab56fe27d389446754d0fb04910a34"
artwork = "%s/mediabrowser/Items/%s/Images/%s/%s?MaxWidth=%s&MaxHeight=%s%s%s&Format=original&Tag=%s%s" % (server, id, type, index, maxWidth, maxHeight, height, width, imageTag, query)
#artwork = "%s/mediabrowser/Items/%s/Images/%s/%s/%s/original/%s/%s/%s?%s" % (server, id, type, index, imageTag, width, height, played, query) <- broken
if utils.settings('disableCoverArt')=='true':
artwork = artwork + "&EnableImageEnhancers=false"
# 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 imageUrl(self, id, type, index, width, height):
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
# For people image - actors, directors, writers
return "%s/mediabrowser/Items/%s/Images/%s?MaxWidth=%s&MaxHeight=%s&Index=%s" % (server, id, type, width, height, index)
def getUserArtwork(self, data, type, index = "0"):
# Load user information set by UserClient
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
id = data.get("Id")
artwork = "%s/mediabrowser/Users/%s/Images/%s?Format=original" % (server, id, type)
return artwork
return filepath

View file

@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
import os
from uuid import uuid4 as uuid4
from Lock import Lock
import xbmc
import xbmcaddon
import xbmcgui
import Utils as utils
class ClientInformation():
def __init__(self):
self.addon = xbmcaddon.Addon()
self.addonName = self.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def getAddonId(self):
# To use when declaring xbmcaddon.Addon(id=addonId)
return "plugin.video.emby"
def getAddonName(self):
# Useful for logging
return self.addon.getAddonInfo('name').upper()
def getVersion(self):
return self.addon.getAddonInfo('version')
def getDeviceName(self):
if utils.settings('deviceNameOpt') == "false":
# Use Kodi's deviceName
deviceName = xbmc.getInfoLabel('System.FriendlyName')
else:
deviceName = utils.settings('deviceName')
deviceName = deviceName.replace("\"", "_")
deviceName = deviceName.replace("/", "_")
return deviceName
def getMachineId(self):
WINDOW = xbmcgui.Window(10000)
clientId = WINDOW.getProperty("client_id")
if clientId:
return clientId
# we need to load and or generate a client machine id
addon = self.addon
addondir = addon.getAddonInfo('path').decode('utf-8')
machine_guid_lock_path = xbmc.translatePath(os.path.join(addondir, "machine_guid.lock")).decode('utf-8')
machine_guid_path = xbmc.translatePath(os.path.join(addondir, "machine_guid")).decode('utf-8')
clientId = ""
try:
lock = Lock(machine_guid_lock_path)
locked = lock.acquire()
if locked:
fd = os.open(machine_guid_path, os.O_CREAT|os.O_RDWR)
clientId = os.read(fd, 256)
if len(clientId) == 0:
uuid = uuid4()
clientId = str("%012X" % uuid)
self.logMsg("ClientId saved to FILE: %s" % clientId, 2)
os.write(fd, clientId)
os.fsync(fd)
os.close(fd)
self.logMsg("ClientId saved to WINDOW: %s" % clientId, 1)
WINDOW.setProperty("client_id", clientId)
finally:
lock.release()
return clientId
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

@ -1,161 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
# connection manager class
#################################################################################################
import json
import socket
import xbmc
import xbmcgui
import xbmcaddon
import Utils as utils
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
from UserClient import UserClient
class ConnectionManager():
clientInfo = ClientInformation()
user = UserClient()
doUtils = DownloadUtils()
addonName = clientInfo.getAddonName()
addonId = clientInfo.getAddonId()
addon = xbmcaddon.Addon()
WINDOW = xbmcgui.Window(10000)
def __init__(self):
self.__language__ = self.addon.getLocalizedString
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def checkServer(self):
self.WINDOW.setProperty("Server_Checked", "True")
self.logMsg("Connection Manager Called", 2)
server = self.user.getServer()
if server != "":
self.logMsg("Server already set", 2)
return
serverInfo = self.getServerDetails()
try:
prefix,ip,port = serverInfo.split(":")
setServer = xbmcgui.Dialog().yesno(self.__language__(30167), "Proceed with the following server?", self.__language__(30169) + serverInfo)
except: # serverInfo is None
self.logMsg("getServerDetails failed", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
return
if setServer == 1:
self.logMsg("Server selected. Saving information.", 1)
utils.settings("ipaddress", ip.replace("/", ""))
utils.settings("port", port)
# If https, enable the setting
if (prefix == 'https'):
utils.settings('https', "true")
else:
self.logMsg("No server selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
return
# Get List of public users
self.logMsg("Getting user list", 1)
server = "%s:%s" % (ip.replace("/", ""), port)
url = "%s/mediabrowser/Users/Public?format=json" % serverInfo
result = self.doUtils.downloadUrl(url, authenticate=False)
if result == "":
self.logMsg("Unable to connect to %s." % server, 1)
return
self.logMsg("Result: %s" % result, 2)
# Process the list returned
names = []
userList = []
for user in result:
name = user['Name']
userList.append(name)
if user['HasPassword']:
name = "%s (Secure)" % name
names.append(name)
self.logMsg("User list: %s" % names, 1)
resp = xbmcgui.Dialog().select(self.__language__(30200), names)
if resp > -1:
selected_user = userList[resp]
self.logMsg("Selected User: %s" % selected_user, 1)
utils.settings("username", selected_user)
else:
self.logMsg("No user selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
return
musicDisabled = xbmcgui.Dialog().yesno("Music Setting", "Disable music library?")
if musicDisabled:
self.logMsg("User opted to disable music library.", 1)
utils.settings('enableMusicSync', "false")
else:
# Music is enabled, prompt for direct stream
musicPath = xbmcgui.Dialog().yesno("Music Setting", "Direct stream the music library?", "Select this option only if you plan on listening to music outside your network.")
if musicPath:
self.logMsg("User option to direct stream music library.", 1)
utils.settings('directstreammusic', "true")
directPaths = xbmcgui.Dialog().yesno(
heading="Direct paths",
line1=(
"Use direct paths? Caution! If you choose yes, you "
"will lose access to certain Emby features such as: "
"Emby cinema mode, direct stream/transcode options, "
"parental access schedule."))
if directPaths:
self.logMsg("User opted to use direct paths.", 1)
utils.settings('useDirectPaths', "true")
return
def getServerDetails(self):
self.logMsg("Getting Server Details from Network")
MULTI_GROUP = ("<broadcast>", 7359)
MESSAGE = "who is EmbyServer?"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(6.0)
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)
self.logMsg("MutliGroup : %s" % str(MULTI_GROUP), 2);
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
sock.sendto(MESSAGE, MULTI_GROUP)
try:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
self.logMsg("Received Response: %s" % data)
# Get the address
data = json.loads(data)
return data['Address']
except:
self.logMsg("No UDP Response")
pass
return None

View file

@ -1,40 +0,0 @@
import os
import time
import errno
import xbmc
class Lock:
def __init__(self, filename):
self.filename = filename
self.delay = 0.5
self.timeout = 10
self.is_locked = False
self.fd = None
def acquire(self):
start_time = time.time()
while True:
try:
self.fd = os.open(self.filename, os.O_CREAT|os.O_RDWR|os.O_EXCL)
break;
except OSError as e:
if (time.time() - start_time) >= self.timeout:
xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " timed out")
return False
#xbmc.log("File_Lock_On " + self.filename + " error " + str(e))
time.sleep(self.delay)
self.is_locked = True
xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " obtained")
return True
def release(self):
if self.is_locked:
os.close(self.fd)
os.unlink(self.filename)
self.is_locked = False
xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " released")
def __del__(self):
self.release()

View file

@ -1,386 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
# ReadEmbyDB
#################################################################################################
from DownloadUtils import DownloadUtils
class ReadEmbyDB():
doUtils = DownloadUtils()
urllimit = 50
def filterbyId(self, result, itemList = []):
newResult = []
for item in result:
if item['Id'] in itemList:
newResult.append(item)
return newResult
def getMovies(self, parentId, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
# Only get basic info for our sync-compares
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Movie&CollapseBoxSetItems=false&ImageTypeLimit=1&format=json" % parentId
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getMusicVideos(self, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
# Only get basic info for our sync-compares
url = "{server}/mediabrowser/Users/{UserId}/items?&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=MusicVideo&CollapseBoxSetItems=false&ImageTypeLimit=1&format=json"
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getMusicArtists(self, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
# Only get basic info for our sync-compares
url = "{server}/Artists?Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json"
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getMusicArtistsTotal(self):
result = []
url = "{server}/Artists?Limit=1&Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json"
jsondata = self.doUtils.downloadUrl(url)
total = jsondata['TotalRecordCount']
index = 1
jump = 200
while index < total:
url = "{server}/Artists?StartIndex=%s&Limit=%s&Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json" % (index, jump)
jsondata = self.doUtils.downloadUrl(url)
result.extend(jsondata['Items'])
index += jump
return result
def getMusicSongs(self, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
# Only get basic info for our sync-compares
url = "{server}/mediabrowser/Users/{UserId}/Items?Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json"
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getMusicSongsTotal(self):
result = []
url = "{server}/mediabrowser/Users/{UserId}/Items?Index=1&Limit=1&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json"
jsondata = self.doUtils.downloadUrl(url)
total = jsondata['TotalRecordCount']
index = 1
jump = 200
while index < total:
url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json" % (index, jump)
jsondata = self.doUtils.downloadUrl(url)
result.extend(jsondata['Items'])
index += jump
return result
def getMusicAlbums(self, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
# Only get basic info for our sync-compares
url = "{server}/mediabrowser/Users/{UserId}/Items?Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=MusicAlbum&format=json"
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else:
tempresult = []
# Only return valid albums - which have artists
for item in result:
if item['AlbumArtists']:
tempresult.append(item)
result = tempresult
# If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getMusicAlbumsTotal(self):
result = []
url = "{server}/mediabrowser/Users/{UserId}/Items?Limit=1&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=MusicAlbum&format=json"
jsondata = self.doUtils.downloadUrl(url)
total = jsondata['TotalRecordCount']
index = 1
jump = 200
while index < total:
url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&SortBy=DateCreated&Recursive=true&IncludeItemTypes=MusicAlbum&format=json" % (index, jump)
jsondata = self.doUtils.downloadUrl(url)
#tempresult = []
# Only return valid albums - which have artists
'''for item in jsondata['Items']:
if item['AlbumArtists']:
tempresult.append(item)
result.extend(tempresult)'''
result.extend(jsondata['Items'])
index += jump
return result
def getTvShows(self, parentId, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
# Only get basic info for our sync-compares
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Series&format=json&ImageTypeLimit=1" % parentId
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getTVShowSeasons(self, tvShowId):
result = []
url = "{server}/Shows/%s/Seasons?UserId={UserId}&format=json&ImageTypeLimit=1" % tvShowId
jsondata = self.doUtils.downloadUrl(url)
if jsondata:
result = jsondata['Items']
return result
def getEpisodes(self, showId, itemList = []):
result = []
lenlist = len(itemList) < self.urllimit
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&IsVirtualUnaired=false&IsMissing=False&SortBy=SortName&Fields=Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % showId
if itemList and lenlist:
url = "%s&Ids=%s" % (url, ",".join(itemList))
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
if not lenlist:
result = self.filterbyId(result, itemList)
return result
def getLatestEpisodes(self, fullinfo = False, itemList = []):
result = []
limitString = "Limit=20&SortBy=DateCreated&"
if itemList: # if we want a certain list specify it
limitString = "Ids=%s&" % ",".join(itemList)
if fullinfo:
url = "{server}/mediabrowser/Users/{UserId}/Items?%sIsVirtualUnaired=false&IsMissing=False&Fields=ParentId,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % limitString
else:
url = "{server}/mediabrowser/Users/{UserId}/Items?%sIsVirtualUnaired=false&IsMissing=False&Fields=ParentId,Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % limitString
jsondata = self.doUtils.downloadUrl(url)
if jsondata:
result = jsondata['Items']
return result
def getItem(self, id):
result = {}
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1&Fields=Etag" % id
jsondata = self.doUtils.downloadUrl(url)
if jsondata:
result = jsondata
return result
def getFullItem(self, id):
result = {}
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&Fields=Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview,CriticRating,CriticRatingSummary" % id
jsondata = self.doUtils.downloadUrl(url)
if jsondata:
result = jsondata
return result
def getCollections(self, type):
# Build a list of the user views
collections = []
url = "{server}/mediabrowser/Users/{UserId}/Views?format=json" #Items?Sortby=SortName&format=json"
jsondata = self.doUtils.downloadUrl(url)
try:
result = jsondata['Items']
except: pass
else:
for item in result:
name = item['Name']
contentType = item['Type']
itemtype = item.get('CollectionType')
content = itemtype
if contentType == "Channel":
# Ignore channel type otherwise, they get processed as mixed content
continue
if itemtype is None and type in ("movies", "tvshows"):
# Mixed content or rich presentation is disabled
itemtype = type
content = "mixed"
if itemtype == type and name not in ("Collections", "Trailers"):
collections.append({
'title': name,
'type': itemtype,
'id': item['Id'],
'content': content
})
return collections
def getBoxSets(self):
result = []
url = "{server}/mediabrowser/Users/{UserId}/Items?SortBy=SortName&IsVirtualUnaired=false&IsMissing=False&Fields=Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Ascending&IncludeItemTypes=BoxSet&format=json&ImageTypeLimit=1"
jsondata = self.doUtils.downloadUrl(url)
if jsondata:
result = jsondata['Items']
return result
def getMoviesInBoxSet(self, boxsetId):
result = []
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&Fields=ItemCounts,Etag&format=json&ImageTypeLimit=1" % boxsetId
jsondata = self.doUtils.downloadUrl(url)
if jsondata:
result = jsondata['Items']
return result
# This is not being used.
# To be removed?
def getViewCollections(self, type):
#Build a list of the user views
doUtils = DownloadUtils()
viewsUrl = "{server}/mediabrowser/Users/{UserId}/Views?format=json&ImageTypeLimit=1"
result = doUtils.downloadUrl(viewsUrl)
collections=[]
if (result == ""):
return []
result = result[u'Items']
for view in result:
if (view[u'Type'] == 'UserView'): # Need to grab the real main node
newViewsUrl = "{server}/mediabrowser/Users/{UserId}/items?ParentId=%s&SortBy=SortName&SortOrder=Ascending&format=json&ImageTypeLimit=1" % view[u'Id']
newViews = doUtils.downloadUrl(newViewsUrl)
if (result == ""):
return []
newViews = newViews[u'Items']
for newView in newViews:
# There are multiple nodes in here like 'Latest', 'NextUp' - below we grab the full node.
if newView[u'CollectionType'] != None:
if newView[u'CollectionType'] == "MovieMovies" or newView[u'CollectionType'] == "TvShowSeries":
view=newView
if (view[u'ChildCount'] != 0):
Name = view[u'Name']
total = str(view[u'ChildCount'])
try:
itemtype = view[u'CollectionType']
except:
itemtype = "movies"
if itemtype == "MovieMovies":
itemtype = "movies"
if itemtype == "TvShowSeries":
itemtype = "tvshows"
if itemtype == type:
collections.append( {'title' : Name,
'type' : type,
'id' : view[u'Id']})
return collections

View file

@ -1,132 +0,0 @@
#################################################################################################
# ReadKodiDB
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import json
import os
import Utils as utils
class ReadKodiDB():
def getKodiMovies(self, connection, cursor):
#returns all movies in Kodi db
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='movie'")
allmovies = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allmovies
def getKodiMusicVideos(self, connection, cursor):
#returns all musicvideos in Kodi db
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='musicvideo'")
allvideos = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allvideos
def getKodiTvShows(self, connection, cursor):
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='tvshow'")
allshows = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allshows
def getKodiEpisodes(self, connection, cursor, showid=None):
if showid == None:
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type=?",("episode",))
else:
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type=? AND parent_id=?",("episode", showid))
allepisodes = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allepisodes
def getEmbyIdByKodiId(self, id, type, connection=None, cursor=None):
if not connection:
connection = utils.KodiSQL()
cursor = connection.cursor()
closeCon = True
else:
closeCon = False
cursor.execute("SELECT emby_id FROM emby WHERE media_type=? AND kodi_id=?",(type,id))
result = cursor.fetchone()
if closeCon:
connection.close()
if result:
return result[0]
else:
return None
def getShowIdByEmbyId(self, id, connection=None, cursor=None):
if not connection:
connection = utils.KodiSQL()
cursor = connection.cursor()
closeCon = True
else:
closeCon = False
cursor.execute("SELECT parent_id FROM emby WHERE emby_id=?",(id,))
result = cursor.fetchone()
if closeCon:
connection.close()
if result:
return result[0]
else:
return None
def getTypeByEmbyId(self, id, connection=None, cursor=None):
if not connection:
connection = utils.KodiSQL()
cursor = connection.cursor()
closeCon = True
else:
closeCon = False
cursor.execute("SELECT media_type FROM emby WHERE emby_id=?",(id,))
result = cursor.fetchone()
if closeCon:
connection.close()
if result:
return result[0]
else:
return None
def getShowTotalCount(self, id, connection=None, cursor=None):
if not connection:
connection = utils.KodiSQL()
cursor = connection.cursor()
closeCon = True
else:
closeCon = False
command = "SELECT totalCount FROM tvshowcounts WHERE idShow=%s" % str(id)
cursor.execute(command)
result = cursor.fetchone()
if closeCon:
connection.close()
if result:
return result[0]
else:
return None
def getKodiMusicArtists(self, connection, cursor):
#returns all artists in Kodi db
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='artist'")
allartists = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allartists
def getKodiMusicAlbums(self, connection, cursor):
#returns all artists in Kodi db
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='album'")
allalbums = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allalbums
def getKodiMusicSongs(self, connection, cursor):
#returns all songs in Kodi db
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='song'")
allsongs = cursor.fetchall()
#this will return a list with tuples of all items returned from the database
return allsongs

View file

@ -1,236 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import requests
import urllib
import os
import xbmc
import xbmcvfs
import Utils as utils
from ClientInformation import ClientInformation
#################################################################################################
class TextureCache():
addonName = ClientInformation().getAddonName()
xbmc_host = 'localhost'
xbmc_port = None
xbmc_username = None
xbmc_password = None
enableTextureCache = utils.settings('enableTextureCache') == "true"
def __init__(self):
if not self.xbmc_port and self.enableTextureCache:
self.setKodiWebServerDetails()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def double_urlencode(self, text):
text = self.single_urlencode(text)
text = self.single_urlencode(text)
return text
def single_urlencode(self, text):
blah = urllib.urlencode({'blahblahblah':text})
blah = blah[13:]
return blah
def setKodiWebServerDetails(self):
# Get the Kodi webserver details - used to set the texture cache
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserver"}, "id":1}')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
if(jsonobject.has_key('result')):
xbmc_webserver_enabled = jsonobject["result"]["value"]
if not xbmc_webserver_enabled:
#enable the webserver if not enabled
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserverport","value":8080}, "id":1}')
self.xbmc_port = 8080
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserver","value":true}, "id":1}')
self.xbmc_port = "kodi"
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverport"}, "id":1}')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
if(jsonobject.has_key('result')):
self.xbmc_port = jsonobject["result"]["value"]
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverusername"}, "id":1}')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
if(jsonobject.has_key('result')):
self.xbmc_username = jsonobject["result"]["value"]
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverpassword"}, "id":1}')
jsonobject = json.loads(json_response.decode('utf-8','replace'))
if(jsonobject.has_key('result')):
self.xbmc_password = jsonobject["result"]["value"]
def FullTextureCacheSync(self):
#this method can be called from the plugin to sync all Kodi textures to the texture cache.
#Warning: this means that every image will be cached locally, this takes diskspace!
# Remove all existing textures first
path = "special://thumbnails/"
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
xbmcvfs.delete(os.path.join(path+dir,file))
textureconnection = utils.KodiSQL('texture')
texturecursor = textureconnection.cursor()
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = texturecursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
texturecursor.execute("DELETE FROM " + tableName)
textureconnection.commit()
texturecursor.close()
# Cache all entries in video DB
connection = utils.KodiSQL('video')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
for url in result:
self.CacheTexture(url[0])
cursor.close()
# Cache all entries in music DB
connection = utils.KodiSQL('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
for url in result:
self.CacheTexture(url[0])
cursor.close()
def addArtwork(self, artwork, kodiId, mediaType, cursor):
# Kodi conversion table
kodiart = {
'Primary': ["thumb", "poster"],
'Banner': "banner",
'Logo': "clearlogo",
'Art': "clearart",
'Thumb': "landscape",
'Disc': "discart",
'Backdrop': "fanart",
'BoxRear': "poster"
}
# Artwork is a dictionary
for art in artwork:
if art == "Backdrop":
# Backdrop entry is a list, process extra fanart for artwork downloader (fanart, fanart1, fanart2, etc.)
backdrops = artwork[art]
backdropsNumber = len(backdrops)
cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", (kodiId, mediaType, "fanart%",))
rows = cursor.fetchall()
if len(rows) > backdropsNumber:
# More backdrops in database than what we are going to process. Delete extra fanart.
cursor.execute("DELETE FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", (kodiId, mediaType, "fanart_",))
index = ""
for backdrop in backdrops:
self.addOrUpdateArt(backdrop, kodiId, mediaType, "%s%s" % ("fanart", index), cursor)
if backdropsNumber > 1:
try: # Will only fail on the first try, str to int.
index += 1
except TypeError:
index = 1
elif art == "Primary":
# Primary art is processed as thumb and poster for Kodi.
for artType in kodiart[art]:
self.addOrUpdateArt(artwork[art], kodiId, mediaType, artType, cursor)
elif kodiart.get(art): # For banner, logo, art, thumb, disc
# Only process artwork type that Kodi can use
self.addOrUpdateArt(artwork[art], kodiId, mediaType, kodiart[art], cursor)
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
# Possible that the imageurl is an empty string
if imageUrl:
cacheimage = False
cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,))
try: # Update the artwork
url = cursor.fetchone()[0]
except: # Add the artwork
cacheimage = True
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)"
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
else: # Only cache artwork if it changed
if url != imageUrl:
cacheimage = True
# Only for the main backdrop, poster
if imageType in ("fanart", "poster"):
# Delete current entry before updating with the new one
self.deleteCachedArtwork(url)
self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1)
query = "UPDATE art set url = ? WHERE media_id = ? AND media_type = ? AND type = ?"
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
# Cache fanart and poster in Kodi texture cache
if cacheimage and imageType in ("fanart", "poster"):
self.CacheTexture(imageUrl)
def CacheTexture(self, url):
# Cache a single image url to the texture cache
if url and self.enableTextureCache:
self.logMsg("Processing: %s" % url, 2)
# Add image to texture cache by simply calling it at the http endpoint
url = self.double_urlencode(url)
try: # Extreme short timeouts so we will have a exception, but we don't need the result so pass
response = requests.head('http://%s:%s/image/image://%s' % (self.xbmc_host, self.xbmc_port, url), auth=(self.xbmc_username, self.xbmc_password), timeout=(0.01, 0.01))
except: pass
def deleteCachedArtwork(self, url):
# Only necessary to remove and apply a new backdrop or poster
connection = utils.KodiSQL('texture')
cursor = connection.cursor()
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
try:
cachedurl = cursor.fetchone()[0]
except:
self.logMsg("Could not find cached url.", 1)
else: # Delete thumbnail as well as the entry
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl)
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
xbmcvfs.delete(thumbnails)
try:
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
except:
self.logMsg("Issue deleting url from cache. Skipping.", 2)
finally:
cursor.close()

View file

@ -1,78 +0,0 @@
import sys
import xbmc
import xbmcgui
import xbmcaddon
import json as json
import urllib
ACTION_BACK = 92
class UserPreferences(xbmcgui.WindowXMLDialog):
configuration = None
save = False
name = None
image = None
def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
def onInit(self):
self.action_exitkeys_id = [10, 13]
# set the dialog data
self.save = False
cinemaMode = self.configuration.get(u'EnableCinemaMode')
self.getControl(8011).setSelected(cinemaMode)
self.getControl(8001).setLabel(self.name)
self.getControl(8002).setImage(self.image)
def save(self):
self.save = True
def isSave(self):
return self.save
def setConfiguration(self, configuration):
self.configuration = configuration
def getConfiguration(self):
return self.configuration
def setName(self, name):
self.name = name
def setImage(self, image):
self.image = image
def onFocus(self, controlId):
pass
def doAction(self):
pass
def closeDialog(self):
self.close()
def onClick(self, controlID):
if(controlID == 8012):
# save now
self.save = True
self.close()
elif(controlID == 8013):
#cancel
self.close()
if(controlID == 8011):
# cinema mode
cinemamode = self.getControl(8011).isSelected()
self.configuration['EnableCinemaMode'] = cinemamode
pass
def onAction(self, action):
if action == ACTION_BACK:
self.close()

View file

@ -1,305 +0,0 @@
# -*- coding: utf-8 -*-
import json
import threading
import websocket
import logging
#################################################################################################
# WebSocket Client thread
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import KodiMonitor
import Utils as utils
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
from PlaybackUtils import PlaybackUtils
from LibrarySync import LibrarySync
from WriteKodiVideoDB import WriteKodiVideoDB
from WriteKodiMusicDB import WriteKodiMusicDB
logging.basicConfig()
class WebSocketThread(threading.Thread):
_shared_state = {}
doUtils = DownloadUtils()
clientInfo = ClientInformation()
KodiMonitor = KodiMonitor.Kodi_Monitor()
addonName = clientInfo.getAddonName()
client = None
keepRunning = True
def __init__(self, *args):
self.__dict__ = self._shared_state
threading.Thread.__init__(self, *args)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
def sendProgressUpdate(self, data):
self.logMsg("sendProgressUpdate", 1)
if self.client:
try:
# Send progress update
messageData = {
'MessageType': "ReportPlaybackProgress",
'Data': data
}
messageString = json.dumps(messageData)
self.client.send(messageString)
self.logMsg("Message data: %s" % messageString, 2)
except Exception as e:
self.logMsg("Exception: %s" % e, 1)
def stopClient(self):
# stopping the client is tricky, first set keep_running to false and then trigger one
# more message by requesting one SessionsStart message, this causes the
# client to receive the message and then exit
if self.client:
self.logMsg("Stopping Client")
self.keepRunning = False
self.client.keep_running = False
self.client.close()
self.logMsg("Stopping Client : KeepRunning set to False")
else:
self.logMsg("Stopping Client NO Object ERROR")
def on_message(self, ws, message):
WINDOW = xbmcgui.Window(10000)
self.logMsg("Message: %s" % message, 1)
result = json.loads(message)
messageType = result['MessageType']
data = result.get("Data")
if messageType == "Play":
# A remote control play command has been sent from the server.
itemIds = data['ItemIds']
playCommand = data['PlayCommand']
if "PlayNow" in playCommand:
startPositionTicks = data.get('StartPositionTicks', 0)
xbmc.executebuiltin("Dialog.Close(all,true)")
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
PlaybackUtils().PLAYAllItems(itemIds, startPositionTicks)
elif "PlayNext" in playCommand:
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
playlist = PlaybackUtils().AddToPlaylist(itemIds)
if not xbmc.Player().isPlaying():
xbmc.Player().play(playlist)
elif messageType == "Playstate":
# A remote control update playstate command has been sent from the server.
command = data['Command']
if "Stop" in command:
self.logMsg("Playback Stopped.", 1)
xbmc.Player().stop()
elif "Unpause" in command:
self.logMsg("Playback unpaused.", 1)
xbmc.Player().pause()
elif "Pause" in command:
self.logMsg("Playback paused.", 1)
xbmc.Player().pause()
elif "NextTrack" in command:
self.logMsg("Playback next track.", 1)
xbmc.Player().playnext()
elif "PreviousTrack" in command:
self.logMsg("Playback previous track.", 1)
xbmc.Player().playprevious()
elif "Seek" in command:
seekPositionTicks = data['SeekPositionTicks']
seekTime = seekPositionTicks / 10000000.0
self.logMsg("Seek to %s" % seekTime, 1)
xbmc.Player().seekTime(seekTime)
# Report playback
WINDOW.setProperty('commandUpdate', "true")
elif messageType == "UserDataChanged":
# A user changed their personal rating for an item, or their playstate was updated
userdataList = data['UserDataList']
self.logMsg("Message: Doing UserDataChanged: UserDataList: %s" % userdataList, 1)
LibrarySync().user_data_update(userdataList)
elif messageType == "LibraryChanged":
# Library items
itemsRemoved = data.get("ItemsRemoved")
itemsAdded = data.get("ItemsAdded")
itemsUpdated = data.get("ItemsUpdated")
itemsToUpdate = itemsAdded + itemsUpdated
self.logMsg("Message: WebSocket LibraryChanged: Items Added: %s" % itemsAdded, 1)
self.logMsg("Message: WebSocket LibraryChanged: Items Updated: %s" % itemsUpdated, 1)
self.logMsg("Message: WebSocket LibraryChanged: Items Removed: %s" % itemsRemoved, 1)
LibrarySync().remove_items(itemsRemoved)
LibrarySync().update_items(itemsToUpdate)
elif messageType == "GeneralCommand":
command = data['Name']
arguments = data.get("Arguments")
if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
# These commands need to be reported back
if command == "Mute":
xbmc.executebuiltin('Mute')
elif command == "Unmute":
xbmc.executebuiltin('Mute')
elif command == "SetVolume":
volume = arguments['Volume']
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
elif command == "SetSubtitleStreamIndex":
# Emby merges audio and subtitle index together
xbmcplayer = xbmc.Player()
currentFile = xbmcplayer.getPlayingFile()
embyIndex = int(arguments['Index'])
mapping = WINDOW.getProperty("%sIndexMapping" % currentFile)
externalIndex = json.loads(mapping)
if externalIndex:
# If there's external subtitles added via PlaybackUtils
for index in externalIndex:
if externalIndex[index] == embyIndex:
xbmcplayer.setSubtitleStream(int(index))
else:
# User selected internal subtitles
external = len(externalIndex)
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
xbmcplayer.setSubtitleStream(external + embyIndex - audioTracks - 1)
else:
# Emby merges audio and subtitle index together
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
xbmcplayer.setSubtitleStream(index - audioTracks - 1)
elif command == "SetAudioStreamIndex":
index = int(arguments['Index'])
xbmc.Player().setAudioStream(index - 1)
# Report playback
WINDOW.setProperty('commandUpdate', "true")
else:
# GUI commands
if command == "ToggleFullscreen":
xbmc.executebuiltin('Action(FullScreen)')
elif command == "ToggleOsdMenu":
xbmc.executebuiltin('Action(OSD)')
elif command == "MoveUp":
xbmc.executebuiltin('Action(Up)')
elif command == "MoveDown":
xbmc.executebuiltin('Action(Down)')
elif command == "MoveLeft":
xbmc.executebuiltin('Action(Left)')
elif command == "MoveRight":
xbmc.executebuiltin('Action(Right)')
elif command == "Select":
xbmc.executebuiltin('Action(Select)')
elif command == "Back":
xbmc.executebuiltin('Action(back)')
elif command == "ToggleContextMenu":
xbmc.executebuiltin('Action(ContextMenu)')
elif command == "GoHome":
xbmc.executebuiltin('ActivateWindow(Home)')
elif command == "PageUp":
xbmc.executebuiltin('Action(PageUp)')
elif command == "NextLetter":
xbmc.executebuiltin('Action(NextLetter)')
elif command == "GoToSearch":
xbmc.executebuiltin('VideoLibrary.Search')
elif command == "GoToSettings":
xbmc.executebuiltin('ActivateWindow(Settings)')
elif command == "PageDown":
xbmc.executebuiltin('Action(PageDown)')
elif command == "PreviousLetter":
xbmc.executebuiltin('Action(PrevLetter)')
elif command == "TakeScreenshot":
xbmc.executebuiltin('TakeScreenshot')
elif command == "ToggleMute":
xbmc.executebuiltin('Mute')
elif command == "VolumeUp":
xbmc.executebuiltin('Action(VolumeUp)')
elif command == "VolumeDown":
xbmc.executebuiltin('Action(VolumeDown)')
elif command == "DisplayMessage":
header = arguments['Header']
text = arguments['Text']
xbmcgui.Dialog().notification(header, text, icon="special://home/addons/plugin.video.emby/icon.png", time=4000)
elif command == "SendString":
string = arguments['String']
text = '{"jsonrpc": "2.0", "method": "Input.SendText", "params": { "text": "%s", "done": false }, "id": 0}' % string
result = xbmc.executeJSONRPC(text)
else:
self.logMsg("Unknown command.", 1)
elif messageType == "ServerRestarting":
if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification("Emby server", "Server is restarting.", icon="special://home/addons/plugin.video.emby/icon.png")
def on_error(self, ws, error):
if "10061" in str(error):
# Server is offline
pass
else:
self.logMsg("Error: %s" % error, 1)
#raise
def on_close(self, ws):
self.logMsg("Closed", 2)
def on_open(self, ws):
deviceId = ClientInformation().getMachineId()
self.doUtils.postCapabilities(deviceId)
def run(self):
WINDOW = xbmcgui.Window(10000)
logLevel = int(WINDOW.getProperty('getLogLevel'))
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
token = WINDOW.getProperty('accessToken%s' % username)
deviceId = ClientInformation().getMachineId()
'''if (logLevel == 2):
websocket.enableTrace(True)'''
# Get the appropriate prefix for websocket
if "https" in server:
server = server.replace('https', 'wss')
else:
server = server.replace('http', 'ws')
websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
self.logMsg("websocket URL: %s" % websocketUrl)
self.client = websocket.WebSocketApp(websocketUrl,
on_message = self.on_message,
on_error = self.on_error,
on_close = self.on_close)
self.client.on_open = self.on_open
while self.keepRunning:
self.client.run_forever()
if self.keepRunning:
self.logMsg("Client Needs To Restart", 2)
if self.KodiMonitor.waitForAbort(5):
break
self.logMsg("Thread Exited", 1)

View file

@ -1,518 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
# WriteKodiMusicDB
#################################################################################################
import sqlite3
from datetime import datetime
from ntpath import split as ntsplit
import xbmc
import xbmcgui
import xbmcaddon
from ClientInformation import ClientInformation
import Utils as utils
from API import API
from PlayUtils import PlayUtils
from ReadKodiDB import ReadKodiDB
from ReadEmbyDB import ReadEmbyDB
from TextureCache import TextureCache
class WriteKodiMusicDB():
textureCache = TextureCache()
kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
addonName = ClientInformation().getAddonName()
def __init__(self):
username = utils.window('currUser')
self.userid = utils.window('userId%s' % username)
self.server = utils.window('server%s' % username)
def logMsg(self, msg, lvl = 1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def addOrUpdateArtistToKodiLibrary(self, MBitem, connection, cursor):
# If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database
kodiVersion = self.kodiversion
embyId = MBitem["Id"]
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,))
try:
artistid = cursor.fetchone()[0]
except:
artistid = None
##### The artist details #####
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
dateadded = API().getDateCreated(MBitem)
checksum = API().getChecksum(MBitem)
name = MBitem['Name']
musicBrainzId = API().getProvider(MBitem, "musicBrainzArtist")
genres = " / ".join(MBitem.get('Genres'))
bio = API().getOverview(MBitem)
# Associate artwork
artworks = API().getAllArtwork(MBitem, parentInfo=True)
thumb = artworks['Primary']
backdrops = artworks['Backdrop'] # List
if thumb:
thumb = "<thumb>%s</thumb>" % thumb
if backdrops:
fanart = "<fanart>%s</fanart>" % backdrops[0]
else:
fanart = ""
##### UPDATE THE ARTIST #####
if artistid:
self.logMsg("UPDATE artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1)
if kodiVersion == 16:
query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ? WHERE idArtist = ?"
cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, artistid))
else:
query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ?, dateadded = ? WHERE idArtist = ?"
cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded, artistid))
# Update the checksum in emby table
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
cursor.execute(query, (checksum, embyId))
##### OR ADD THE ARTIST #####
else:
self.logMsg("ADD artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1)
#safety checks: It looks like Emby supports the same artist multiple times in the database while Kodi doesn't allow that. In case that happens we just merge the artist in the Kodi database.
# Safety check 1: does the artist already exist?
cursor.execute("SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE", (name,))
try:
artistid = cursor.fetchone()[0]
self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1)
except: pass
# Safety check 2: does the MusicBrainzArtistId already exist?
cursor.execute("SELECT idArtist FROM artist WHERE strMusicBrainzArtistID = ?", (musicBrainzId,))
try:
artistid = cursor.fetchone()[0]
self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1)
except: pass
if not artistid:
# Create the artist
cursor.execute("select coalesce(max(idArtist),0) as artistid from artist")
artistid = cursor.fetchone()[0] + 1
if kodiVersion == 16:
query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped) values(?, ?, ?, ?, ?, ?, ?, ?)"
cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped))
else:
query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?)"
cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded))
# Create the reference in emby table
query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
cursor.execute(query, (embyId, artistid, "artist", checksum))
# Update artwork
self.textureCache.addArtwork(artworks, artistid, "artist", cursor)
def addOrUpdateAlbumToKodiLibrary(self, MBitem, connection, cursor):
kodiVersion = self.kodiversion
embyId = MBitem["Id"]
# If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,))
try:
albumid = cursor.fetchone()[0]
except:
albumid = None
genres = MBitem.get('Genres')
##### The album details #####
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
dateadded = API().getDateCreated(MBitem)
checksum = API().getChecksum(MBitem)
name = MBitem['Name']
musicBrainzId = API().getProvider(MBitem, "musicBrainzAlbum")
year = MBitem.get('ProductionYear')
genre = " / ".join(genres)
bio = API().getOverview(MBitem)
MBartists = []
for item in MBitem['AlbumArtists']:
MBartists.append(item['Name'])
artists = " / ".join(MBartists)
# Associate the artwork
artworks = API().getAllArtwork(MBitem, parentInfo=True)
thumb = artworks['Primary']
if thumb:
thumb = "<thumb>%s</thumb>" % thumb
##### UPDATE THE ALBUM #####
if albumid:
self.logMsg("UPDATE album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
if kodiVersion == 15:
# Kodi Isengard
query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ? WHERE idAlbum = ?"
cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album", albumid))
elif kodiVersion == 16:
query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, strReleaseType = ? WHERE idAlbum = ?"
cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album", albumid))
else:
# Kodi Gotham and Helix
query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ? WHERE idAlbum = ?"
cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, albumid))
# Update the checksum in emby table
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
cursor.execute(query, (checksum, embyId))
##### OR ADD THE ALBUM #####
else:
self.logMsg("ADD album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
# Safety check: does the strMusicBrainzAlbumID already exist?
cursor.execute("SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?", (musicBrainzId,))
try:
albumid = cursor.fetchone()[0]
except:
# Create the album
cursor.execute("select coalesce(max(idAlbum),0) as albumid from album")
albumid = cursor.fetchone()[0] + 1
if kodiVersion == 15:
# Kodi Isengard
query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album"))
elif kodiVersion == 16:
query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album"))
else:
# Kodi Gotham and Helix
query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded))
# Create the reference in emby table
query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
cursor.execute(query, (embyId, albumid, "album", checksum))
# Add genres
self.AddGenresToMedia(albumid, genres, "album", cursor)
# Update artwork
if artworks['Primary']:
self.textureCache.addOrUpdateArt(artworks['Primary'], albumid, "album", "thumb", cursor)
artworks['Primary'] = ""
if artworks.get('BoxRear'):
self.textureCache.addOrUpdateArt(artworks['BoxRear'], albumid, "album", "poster", cursor)
artworks['BoxRear'] = ""
self.textureCache.addArtwork(artworks, albumid, "album", cursor)
# Link album to artists
if MBartists:
album_artists = MBitem['AlbumArtists']
else:
album_artists = MBitem.get('ArtistItems', [])
for artist in album_artists:
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],))
try:
artistid = cursor.fetchone()[0]
except: pass
else:
query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)"
cursor.execute(query, (artistid, albumid, artist['Name']))
# Update discography
query = "INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) values(?, ?, ?)"
cursor.execute(query, (artistid, name, str(year)))
def addOrUpdateSongToKodiLibrary(self, MBitem, connection, cursor):
kodiVersion = self.kodiversion
embyId = MBitem["Id"]
# If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,))
try:
songid = cursor.fetchone()[0]
except:
songid = None
timeInfo = API().getTimeInfo(MBitem)
userData = API().getUserData(MBitem)
genres = MBitem.get('Genres')
##### The song details #####
playcount = userData.get('PlayCount')
lastplayed = userData.get('LastPlayedDate')
dateadded = API().getDateCreated(MBitem)
checksum = API().getChecksum(MBitem)
name = MBitem['Name']
musicBrainzId = API().getProvider(MBitem, "musicBrainzTrackId")
genre = " / ".join(genres)
artists = " / ".join(MBitem.get('Artists'))
tracknumber = MBitem.get('IndexNumber', 0)
disc = MBitem.get('ParentIndexNumber', 1)
track = disc*2**16 + tracknumber
year = MBitem.get('ProductionYear')
bio = API().getOverview(MBitem)
duration = timeInfo.get('TotalTime')
if utils.settings('directstreammusic') == "true":
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
playurl = PlayUtils().directStream(MBitem, server, embyId, "Audio")
filename = "stream.mp3"
path = playurl.replace(filename, "")
else:
# Get the path and filename
playurl = PlayUtils().directPlay(MBitem)
path, filename = ntsplit(playurl)
if "/" in playurl:
path = "%s/" % path
elif "\\" in playurl:
path = "%s\\" % path
# Validate the path in database
cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (path,))
try:
pathid = cursor.fetchone()[0]
except:
cursor.execute("select coalesce(max(idPath),0) as pathid from path")
pathid = cursor.fetchone()[0] + 1
query = "INSERT INTO path(idPath, strPath) values(?, ?)"
cursor.execute(query, (pathid, path))
# Get the album
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (MBitem.get("AlbumId"),))
try:
albumid = cursor.fetchone()[0]
except:
# No album found, create a single's album
cursor.execute("select coalesce(max(idAlbum),0) as albumid from album")
albumid = cursor.fetchone()[0] + 1
if kodiVersion == 15:
# Kodi Isengard
query = "INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) values(?, ?, ?, ?, ?)"
cursor.execute(query, (albumid, genre, year, dateadded, "single"))
elif kodiVersion == 16:
query = "INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) values(?, ?, ?, ?)"
cursor.execute(query, (albumid, genre, year, "single"))
else:
# Kodi Gotham and Helix
query = "INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) values(?, ?, ?, ?)"
cursor.execute(query, (albumid, genre, year, dateadded))
finally:
cursor.execute("SELECT strArtists FROM album WHERE idAlbum = ?", (albumid,))
result = cursor.fetchone()
if result and result[0] == "":
# Link album to artists
if MBitem['AlbumArtists']:
album_artists = MBitem['AlbumArtists']
else:
album_artists = MBitem['ArtistItems']
MBartists = []
for artist in album_artists:
MBartists.append(artist['Name'])
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],))
try:
artistid = cursor.fetchone()[0]
except: pass
else:
query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)"
cursor.execute(query, (artistid, albumid, artist['Name']))
artists_onalbum = " / ".join(MBartists)
if kodiVersion == 15:
# Kodi Isengard
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
cursor.execute(query, (artists_onalbum, albumid))
elif kodiVersion == 16:
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
cursor.execute(query, (artists_onalbum, albumid))
else:
# Kodi Gotham and Helix
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
cursor.execute(query, (artists_onalbum, albumid))
##### UPDATE THE SONG #####
if songid:
self.logMsg("UPDATE song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
query = "UPDATE song SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?, iDuration = ?, iYear = ?, strFilename = ?, strMusicBrainzTrackID = ?, iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?"
cursor.execute(query, (albumid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed, songid))
# Update the checksum in emby table
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
cursor.execute(query, (checksum, embyId))
##### OR ADD THE SONG #####
else:
self.logMsg("ADD song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
# Create the song
cursor.execute("select coalesce(max(idSong),0) as songid from song")
songid = cursor.fetchone()[0] + 1
query = "INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
cursor.execute(query, (songid, albumid, pathid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed))
# Create the reference in emby table
query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
cursor.execute(query, (embyId, songid, "song", checksum))
# Add genres
self.AddGenresToMedia(songid, genres, "song", cursor)
# Link song to album
if albumid:
query = "INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) values(?, ?, ?, ?, ?)"
cursor.execute(query, (songid, albumid, track, name, duration))
# Link song to artist
for artist in MBitem.get('ArtistItems'):
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],))
try:
artistid = cursor.fetchone()[0]
except: pass
else:
query = "INSERT OR REPLACE INTO song_artist(idArtist, idSong, strArtist) values(?, ?, ?)"
cursor.execute(query, (artistid, songid, artist['Name']))
# Update artwork
self.textureCache.addArtwork(API().getAllArtwork(MBitem, parentInfo=True), songid, "song", cursor)
def deleteItemFromKodiLibrary(self, id, connection, cursor):
cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id=?", (id,))
try:
result = cursor.fetchone()
kodi_id = result[0]
media_type = result[1]
except: pass
else:
if "artist" in media_type:
self.logMsg("Deleting artist from Kodi library, Id: %s" % id, 1)
cursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodi_id,))
elif "song" in media_type:
self.logMsg("Deleting song from Kodi library, Id: %s" % id, 1)
cursor.execute("DELETE FROM song WHERE idSong = ?", (kodi_id,))
elif "album" in media_type:
self.logMsg("Deleting album from Kodi library, Id: %s" % id, 1)
cursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodi_id,))
# Delete the record in emby table
cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,))
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
if imageUrl:
cacheimage = False
cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,))
try:
url = cursor.fetchone()[0]
except: # Image does not exists
cacheimage = True
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 1)
query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)"
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
else:
if url != imageUrl:
cacheimage = True
self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1)
query = "UPDATE art SET url = ? WHERE media_id = ? and media_type = ? and type = ?"
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
# Cache fanart textures in Kodi texture cache
if cacheimage and "fanart" in imageType:
self.textureCache.CacheTexture(imageUrl)
def AddGenresToMedia(self, id, genres, mediatype, cursor):
if genres:
for genre in genres:
cursor.execute("SELECT idGenre as idGenre FROM genre WHERE strGenre = ? COLLATE NOCASE", (genre,))
try:
idGenre = cursor.fetchone()[0]
except: # Create the genre
cursor.execute("select coalesce(max(idGenre),0) as idGenre from genre")
idGenre = cursor.fetchone()[0] + 1
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
cursor.execute(query, (idGenre, genre))
finally: # Assign the genre to item
if "album" in mediatype:
query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)"
cursor.execute(query, (idGenre, id))
elif "song" in mediatype:
query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
cursor.execute(query, (idGenre, id))
def updateUserdata(self, userdata, connection, cursor):
# This updates: LastPlayedDate, Playcount
embyId = userdata['ItemId']
MBitem = ReadEmbyDB().getItem(embyId)
if not MBitem:
self.logMsg("UPDATE userdata to Kodi library FAILED, Item %s not found on server!" % embyId, 1)
return
# Get details
checksum = API().getChecksum(MBitem)
userdata = API().getUserData(MBitem)
# Find the Kodi Id
cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id = ?", (embyId,))
try:
result = cursor.fetchone()
kodiid = result[0]
mediatype = result[1]
self.logMsg("Found embyId: %s in database - kodiId: %s type: %s" % (embyId, kodiid, mediatype), 1)
except:
self.logMsg("Id: %s not found in the emby database table." % embyId, 1)
else:
if mediatype in ("song"):
playcount = userdata['PlayCount']
dateplayed = userdata['LastPlayedDate']
query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?"
cursor.execute(query, (playcount, dateplayed, kodiid))
#update the checksum in emby table
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
cursor.execute(query, (checksum, embyId))

File diff suppressed because it is too large Load diff

524
resources/lib/artwork.py Normal file
View file

@ -0,0 +1,524 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import requests
import os
import urllib
import xbmc
import xbmcvfs
import utils
import clientinfo
#################################################################################################
class Artwork():
xbmc_host = 'localhost'
xbmc_port = None
xbmc_username = None
xbmc_password = None
def __init__(self):
self.clientinfo = clientinfo.ClientInfo()
self.addonName = self.clientinfo.getAddonName()
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
if not self.xbmc_port and self.enableTextureCache:
self.setKodiWebServerDetails()
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def double_urlencode(self, text):
text = self.single_urlencode(text)
text = self.single_urlencode(text)
return text
def single_urlencode(self, text):
text = urllib.urlencode({'blahblahblah':text})
text = text[13:]
return text
def setKodiWebServerDetails(self):
# Get the Kodi webserver details - used to set the texture cache
web_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Settings.GetSettingValue",
"params": {
"setting": "services.webserver"
}
}
result = xbmc.executeJSONRPC(json.dumps(web_query))
result = json.loads(result)
try:
xbmc_webserver_enabled = result['result']['value']
except TypeError:
xbmc_webserver_enabled = False
if not xbmc_webserver_enabled:
# Enable the webserver, it is disabled
web_port = {
"jsonrpc": "2.0",
"id": 1,
"method": "Settings.SetSettingValue",
"params": {
"setting": "services.webserverport",
"value": 8080
}
}
result = xbmc.executeJSONRPC(json.dumps(web_port))
self.xbmc_port = 8080
web_user = {
"jsonrpc": "2.0",
"id": 1,
"method": "Settings.SetSettingValue",
"params": {
"setting": "services.webserver",
"value": True
}
}
result = xbmc.executeJSONRPC(json.dumps(web_user))
self.xbmc_username = "kodi"
else:
# Webserver already enabled
web_port = {
"jsonrpc": "2.0",
"id": 1,
"method": "Settings.GetSettingValue",
"params": {
"setting": "services.webserverport"
}
}
result = xbmc.executeJSONRPC(json.dumps(web_port))
try:
self.xbmc_port = result['result']['value']
except TypeError:
pass
web_user = {
"jsonrpc": "2.0",
"id": 1,
"method": "Settings.GetSettingValue",
"params": {
"setting": "services.webserverusername"
}
}
result = xbmc.executeJSONRPC(json.dumps(web_user))
try:
self.xbmc_username = result['result']['value']
except TypeError:
pass
web_pass = {
"jsonrpc": "2.0",
"id": 1,
"method": "Settings.GetSettingValue",
"params": {
"setting": "services.webserverpassword"
}
}
result = xbmc.executeJSONRPC(json.dumps(web_pass))
try:
self.xbmc_password = result['result']['value']
except TypeError:
pass
def FullTextureCacheSync(self):
# This method will sync all Kodi artwork to textures13.db
# and cache them locally. This takes diskspace!
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
xbmcvfs.delete(os.path.join(path+dir,file))
textureconnection = utils.KodiSQL('texture')
texturecursor = textureconnection.cursor()
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = texturecursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
texturecursor.execute("DELETE FROM " + tableName)
textureconnection.commit()
texturecursor.close()
# Cache all entries in video DB
connection = utils.KodiSQL('video')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
for url in result:
self.CacheTexture(url[0])
cursor.close()
# Cache all entries in music DB
connection = utils.KodiSQL('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
for url in result:
self.CacheTexture(url[0])
cursor.close()
def CacheTexture(self, url):
# Cache a single image url to the texture cache
if url and self.enableTextureCache:
self.logMsg("Processing: %s" % url, 2)
# Add image to texture cache by simply calling it at the http endpoint
url = self.double_urlencode(url)
try: # Extreme short timeouts so we will have a exception.
response = requests.head(
url=(
"http://%s:%s/image/image://%s"
% (self.xbmc_host, self.xbmc_port, url)),
auth=(self.xbmc_username, self.xbmc_password),
timeout=(0.01, 0.01))
# We don't need the result
except: pass
def addArtwork(self, artwork, kodiId, mediaType, cursor):
# Kodi conversion table
kodiart = {
'Primary': ["thumb", "poster"],
'Banner': "banner",
'Logo': "clearlogo",
'Art': "clearart",
'Thumb': "landscape",
'Disc': "discart",
'Backdrop': "fanart",
'BoxRear': "poster"
}
# Artwork is a dictionary
for art in artwork:
if art == "Backdrop":
# Backdrop entry is a list
# Process extra fanart for artwork downloader (fanart, fanart1, fanart2...)
backdrops = artwork[art]
backdropsNumber = len(backdrops)
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
cursor.execute(query, (kodiId, mediaType, "fanart%",))
rows = cursor.fetchall()
if len(rows) > backdropsNumber:
# More backdrops in database. Delete extra fanart.
query = ' '.join((
"DELETE FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
cursor.execute(query, (kodiId, mediaType, "fanart_",))
# Process backdrops and extra fanart
index = ""
for backdrop in backdrops:
self.addOrUpdateArt(
imageUrl=backdrop,
kodiId=kodiId,
mediaType=mediaType,
imageType="%s%s" % ("fanart", index),
cursor=cursor)
if backdropsNumber > 1:
try: # Will only fail on the first try, str to int.
index += 1
except TypeError:
index = 1
elif art == "Primary":
# Primary art is processed as thumb and poster for Kodi.
for artType in kodiart[art]:
self.addOrUpdateArt(
imageUrl=artwork[art],
kodiId=kodiId,
mediaType=mediaType,
imageType=artType,
cursor=cursor)
elif kodiart.get(art):
# Process the rest artwork type that Kodi can use
self.addOrUpdateArt(
imageUrl=artwork[art],
kodiId=kodiId,
mediaType=mediaType,
imageType=kodiart[art],
cursor=cursor)
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
# Possible that the imageurl is an empty string
if imageUrl:
cacheimage = False
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
cursor.execute(query, (kodiId, mediaType, imageType,))
try: # Update the artwork
url = cursor.fetchone()[0]
except TypeError: # Add the artwork
cacheimage = True
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
query = ' '.join((
"INSERT INTO art(",
"media_id, media_type, type, url)",
"VALUES (?, ?, ?, ?)"
))
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
else: # Only cache artwork if it changed
if url != imageUrl:
cacheimage = True
# Only for the main backdrop, poster
if imageType in ("fanart", "poster"):
# Delete current entry before updating with the new one
self.deleteCachedArtwork(url)
self.logMsg(
"Updating Art url for %s kodiId: %s (%s) -> (%s)"
% (imageType, kodiId, url, imageUrl), 1)
query = ' '.join((
"UPDATE art",
"SET url = ?",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
# Cache fanart and poster in Kodi texture cache
if cacheimage and imageType in ("fanart", "poster"):
self.CacheTexture(imageUrl)
def deleteArtwork(self, kodiid, mediatype, cursor):
query = ' '.join((
"SELECT url, type",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?"
))
cursor.execute(query, (kodiid, mediatype,))
rows = cursor.fetchall()
for row in rows:
url = row[0]
imagetype = row[1]
if imagetype in ("poster", "fanart"):
self.deleteCachedArtwork(url)
def deleteCachedArtwork(self, url):
# Only necessary to remove and apply a new backdrop or poster
connection = utils.kodiSQL('texture')
cursor = connection.cursor()
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
try:
cachedurl = cursor.fetchone()[0]
except TypeError:
self.logMsg("Could not find cached url.", 1)
else: # Delete thumbnail as well as the entry
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
xbmcvfs.delete(thumbnails)
try:
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
except:
self.logMsg("Issue deleting url from cache. Skipping.", 2)
finally:
cursor.close()
def getPeopleArtwork(self, people):
# append imageurl if existing
for person in people:
personId = person['Id']
tag = person.get('PrimaryImageTag')
image = ""
if tag:
image = (
"%s/emby/Items/%s/Images/Primary?"
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
% (self.server, personId, tag))
person['imageurl'] = image
return people
def getUserArtwork(self, itemid, itemtype):
# Load user information set by UserClient
image = ("%s/emby/Users/%s/Images/%s?Format=original"
% (self.server, itemid, itemtype))
return image
def getAllArtwork(self, item, parentInfo=False):
server = self.server
id = item['Id']
artworks = item['ImageTags']
backdrops = item['BackdropImageTags']
maxHeight = 10000
maxWidth = 10000
customquery = ""
if utils.settings('compressArt') == "true":
customquery = "&Quality=90"
if utils.settings('enableCoverArt') == "false":
customquery += "&EnableImageEnhancers=false"
allartworks = {
'Primary': "",
'Art': "",
'Banner': "",
'Logo': "",
'Thumb': "",
'Disc': "",
'Backdrop': []
}
# Process backdrops
backdropIndex = 0
for backdroptag in backdrops:
artwork = (
"%s/emby/Items/%s/Images/Backdrop/%s?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, id, backdropIndex,
maxWidth, maxHeight, backdroptag, customquery))
allartworks['Backdrop'].append(artwork)
backdropIndex += 1
# Process the rest of the artwork
for art in artworks:
# Filter backcover
if art != "BoxRear":
tag = artworks[art]
artwork = (
"%s/emby/Items/%s/Images/%s/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, id, art, maxWidth, maxHeight, tag, customquery))
allartworks[art] = artwork
# Process parent items if the main item is missing artwork
if parentInfo:
# Process parent backdrops
if not allartworks['Backdrop']:
parentId = item.get('ParentBackdropItemId')
if parentId:
# If there is a parentId, go through the parent backdrop list
parentbackdrops = item['ParentBackdropImageTags']
backdropIndex = 0
for parentbackdroptag in parentbackdrops:
artwork = (
"%s/emby/Items/%s/Images/Backdrop/%s?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, backdropIndex,
maxWidth, maxHeight, parentbackdroptag, customquery))
allartworks['Backdrop'].append(artwork)
backdropIndex += 1
# Process the rest of the artwork
parentartwork = ['Logo', 'Art', 'Thumb']
for parentart in parentartwork:
if not allartworks[parentart]:
parentId = item.get('Parent%sItemId' % parentart)
if parentId:
parentTag = item['Parent%sImageTag' % parentart]
artwork = (
"%s/emby/Items/%s/Images/%s/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, parentart,
maxWidth, maxHeight, parentTag, customquery))
allartworks[parentart] = artwork
# Parent album works a bit differently
if not allartworks['Primary']:
parentId = item.get('AlbumId')
if parentId and item.get('AlbumPrimaryImageTag'):
parentTag = item['AlbumPrimaryImageTag']
artwork = (
"%s/emby/Items/%s/Images/Primary/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, maxWidth, maxHeight, parentTag, customquery))
allartworks['Primary'] = artwork
return allartworks

View file

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
#################################################################################################
from uuid import uuid4
import xbmc
import xbmcaddon
import utils
#################################################################################################
class ClientInfo():
def __init__(self):
self.addon = xbmcaddon.Addon()
self.addonName = self.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getAddonName(self):
# Used for logging
return self.addon.getAddonInfo('name').upper()
def getAddonId(self):
return "plugin.video.emby"
def getVersion(self):
return self.addon.getAddonInfo('version')
def getDeviceName(self):
if utils.settings('deviceNameOpt') == "false":
# Use Kodi's deviceName
deviceName = xbmc.getInfoLabel('System.FriendlyName')
else:
deviceName = utils.settings('deviceName')
deviceName = deviceName.replace("\"", "_")
deviceName = deviceName.replace("/", "_")
return deviceName
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"
else:
return "Unknown"
def getDeviceId(self):
clientId = utils.window('emby_deviceId')
if clientId:
return clientId
addon_path = self.addon.getAddonInfo('path').decode('utf-8')
GUID_file = xbmc.translatePath("%s\machine_guid" % addon_path).decode('utf-8')
try:
GUID = open(GUID_file)
except IOError: # machine_guid does not exists.
clientId = str("%012X" % uuid4())
GUID = open(GUID_file, 'w')
GUID.write(clientId)
else: # machine_guid already exists. Get guid.
clientId = GUID.read()
finally:
GUID.close()
self.logMsg("DeviceId loaded: %s" % clientId, 1)
utils.window('emby_deviceId', value=clientId)
return clientId

View file

@ -0,0 +1,294 @@
# -*- coding: utf-8 -*-
#################################################################################################
import utils
import clientinfo
#################################################################################################
class Embydb_Functions():
def __init__(self, embycursor):
self.embycursor = embycursor
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getViews(self):
embycursor = self.embycursor
views = []
query = ' '.join((
"SELECT view_id",
"FROM view"
))
embycursor.execute(query)
rows = embycursor.fetchall()
for row in rows:
views.append(row[0])
return views
def getView_byId(self, viewid):
embycursor = self.embycursor
query = ' '.join((
"SELECT view_name, media_type, kodi_tagid",
"FROM view",
"WHERE view_id = ?"
))
embycursor.execute(query, (viewid,))
view = embycursor.fetchone()
return view
def getView_byType(self, mediatype):
embycursor = self.embycursor
views = []
query = ' '.join((
"SELECT view_id, view_name",
"FROM view",
"WHERE media_type = ?"
))
embycursor.execute(query, (mediatype,))
rows = embycursor.fetchall()
for row in rows:
views.append({
'id': row[0],
'name': row[1]
})
return views
def getView_byName(self, tagname):
embycursor = self.embycursor
query = ' '.join((
"SELECT view_id",
"FROM view",
"WHERE view_name = ?"
))
embycursor.execute(query, (tagname,))
try:
view = embycursor.fetchone()[0]
except TypeError:
view = None
return view
def addView(self, embyid, name, mediatype, tagid):
query = (
'''
INSERT INTO view(
view_id, view_name, media_type, kodi_tagid)
VALUES (?, ?, ?, ?)
'''
)
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
def updateView(self, name, tagid, mediafolderid):
query = ' '.join((
"UPDATE view",
"SET view_name = ?, kodi_tagid = ?",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (name, tagid, mediafolderid))
def getItem_byId(self, embyid):
embycursor = self.embycursor
query = ' '.join((
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
embycursor.execute(query, (embyid,))
item = embycursor.fetchone()
return item
def getItem_byView(self, mediafolderid):
embycursor = self.embycursor
query = ' '.join((
"SELECT kodi_id",
"FROM emby",
"WHERE media_folder = ?"
))
embycursor.execute(query, (mediafolderid,))
items = embycursor.fetchall()
return items
def getItem_byKodiId(self, kodiid, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, parent_id",
"FROM emby",
"WHERE kodi_id = ?",
"AND media_type = ?"
))
embycursor.execute(query, (kodiid, mediatype,))
item = embycursor.fetchone()
return item
def getItem_byParentId(self, parentid, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, kodi_id, kodi_fileid",
"FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
embycursor.execute(query, (parentid, mediatype,))
items = embycursor.fetchall()
return items
def getItemId_byParentId(self, parentid, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, kodi_id",
"FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
embycursor.execute(query, (parentid, mediatype,))
items = embycursor.fetchall()
return items
def getChecksum(self, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, checksum",
"FROM emby",
"WHERE emby_type = ?"
))
embycursor.execute(query, (mediatype,))
items = embycursor.fetchall()
return items
def getMediaType_byId(self, embyid):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
embycursor.execute(query, (embyid,))
try:
itemtype = embycursor.fetchone()[0]
except TypeError:
itemtype = None
return itemtype
def sortby_mediaType(self, itemids, unsorted=True):
sorted_items = {}
for itemid in itemids:
mediatype = self.getMediaType_byId(itemid)
if mediatype:
sorted_items.setdefault(mediatype, []).append(itemid)
elif unsorted:
sorted_items.setdefault('Unsorted', []).append(itemid)
return sorted_items
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
parentid=None, checksum=None, mediafolderid=None):
query = (
'''
INSERT OR REPLACE INTO emby(
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
checksum, media_folder)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
)
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
parentid, checksum, mediafolderid))
def updateReference(self, embyid, checksum):
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
self.embycursor.execute(query, (checksum, embyid))
def updateParentId(self, embyid, parent_kodiid):
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
self.embycursor.execute(query, (parent_kodiid, embyid))
def removeItems_byParentId(self, parent_kodiid, mediatype):
query = ' '.join((
"DELETE FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (parent_kodiid, mediatype,))
def removeItem_byKodiId(self, kodiid, mediatype):
query = ' '.join((
"DELETE FROM emby",
"WHERE kodi_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (kodiid, mediatype,))
def removeItem(self, embyid):
query = "DELETE FROM emby WHERE emby_id = ?"
self.embycursor.execute(query, (embyid,))

View file

@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import socket
import xbmc
import xbmcgui
import xbmcaddon
import utils
import clientinfo
import downloadutils
import userclient
#################################################################################################
class InitialSetup():
def __init__(self):
self.addon = xbmcaddon.Addon()
self.__language__ = self.addon.getLocalizedString
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.addonId = self.clientInfo.getAddonId()
self.doUtils = downloadutils.DownloadUtils()
self.userClient = userclient.UserClient()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def setup(self):
# Check server, user, direct paths, music, direct stream if not direct path.
string = self.__language__
addonId = self.addonId
##### SERVER INFO #####
self.logMsg("Initial setup called.", 2)
server = self.userClient.getServer()
if server:
self.logMsg("Server is already set.", 2)
return
self.logMsg("Looking for server...", 2)
server = self.getServerDetails()
self.logMsg("Found: %s" % server, 2)
try:
prefix, ip, port = server.replace("/", "").split(":")
except: # Failed to retrieve server information
self.logMsg("getServerDetails failed.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
return
else:
server_confirm = xbmcgui.Dialog().yesno(
heading="Emby for Kodi",
line1="Proceed with the following server?",
line2="%s %s" % (string(30169), server))
if server_confirm:
# Correct server found
self.logMsg("Server is selected. Saving the information.", 1)
utils.settings('ipaddress', value=ip)
utils.settings('port', value=port)
if prefix == "https":
utils.settings('https', value="true")
else:
# User selected no or cancelled the dialog
self.logMsg("No server selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
return
##### USER INFO #####
self.logMsg("Getting user list.", 1)
url = "%s/emby/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False)
if result == "":
self.logMsg("Unable to connect to %s" % server, 1)
return
self.logMsg("Response: %s" % result, 2)
# Process the list of users
usernames = []
users_hasPassword = []
for user in result:
# Username
name = user['Name']
usernames.append(name)
# Password
if user['HasPassword']:
name = "%s (secure)" % name
users_hasPassword.append(name)
self.logMsg("Presenting user list: %s" % users_hasPassword, 1)
user_select = xbmcgui.Dialog().select(string(30200), users_hasPassword)
if user_select > -1:
selected_user = usernames[user_select]
self.logMsg("Selected user: %s" % selected_user, 1)
utils.settings('username', value=selected_user)
else:
self.logMsg("No user selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
##### ADDITIONAL PROMPTS #####
dialog = xbmcgui.Dialog()
directPaths = dialog.yesno(
heading="Playback Mode",
line1=(
"Caution! If you choose Native mode, you "
"will lose access to certain Emby features such as: "
"Emby cinema mode, direct stream/transcode options, "
"parental access schedule."),
nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)")
if directPaths:
self.logMsg("User opted to use direct paths.", 1)
utils.settings('useDirectPaths', value="1")
# ask for credentials
credentials = dialog.yesno(
heading="Network credentials",
line1= (
"Add network credentials to allow Kodi access to your "
"content? Note: Skipping this step may generate a message "
"during the initial scan of your content if Kodi can't "
"locate your content."))
if credentials:
self.logMsg("Presenting network credentials dialog.", 1)
utils.passwordsXML()
musicDisabled = dialog.yesno(
heading="Music Library",
line1="Disable Emby music library?")
if musicDisabled:
self.logMsg("User opted to disable Emby music library.", 1)
utils.settings('enableMusic', value="false")
else:
# Only prompt if the user didn't select direct paths for videos
if not directPaths:
musicAccess = dialog.yesno(
heading="Music Library",
line1=(
"Direct stream the music library? Select "
"this option only if you plan on listening "
"to music outside of your network."))
if musicAccess:
self.logMsg("User opted to direct stream music.", 1)
utils.settings('streamMusic', value="true")
def getServerDetails(self):
self.logMsg("Getting Server Details from Network", 1)
MULTI_GROUP = ("<broadcast>", 7359)
MESSAGE = "who is EmbyServer?"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(6.0)
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)
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2);
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
sock.sendto(MESSAGE, MULTI_GROUP)
try:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
self.logMsg("Received Response: %s" % data)
except:
self.logMsg("No UDP Response")
return None
else:
# Get the address
data = json.loads(data)
return data['Address']

2528
resources/lib/itemtypes.py Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

201
resources/lib/playlist.py Normal file
View file

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import xbmc
import xbmcgui
import xbmcplugin
import clientinfo
import playutils
import playbackutils
import embydb_functions as embydb
import read_embyserver as embyserver
import utils
#################################################################################################
class Playlist():
def __init__(self):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.emby = embyserver.Read_EmbyServer()
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def playAll(self, itemids, startat):
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
self.logMsg("---*** PLAY ALL ***---", 1)
self.logMsg("Items: %s" % itemids)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
started = False
utils.window('emby_customplaylist', value="true", windowid=10101)
position = 0
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
try:
dbid = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
if not started:
started = True
player.play(playlist)
if startat:
# Seek to the starting position
seektime = startat / 10000000.0
player.seekTime(seektime)
self.verifyPlaylist()
embycursor.close()
def modifyPlaylist(self, itemids):
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
self.logMsg("Items: %s" % itemids, 1)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
try:
dbid = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
self.verifyPlaylist()
embycursor.close()
return playlist
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Add",
'params': {
'playlistid': 1
}
}
if dbid is not None:
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
else:
pl['params']['item'] = {'file': url}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
def addtoPlaylist_xbmc(self, playlist, item):
itemid = item['Id']
playurl = playutils.PlayUtils(item).getPlayUrl()
if not playurl:
# Playurl failed
self.logMsg("Failed to retrieve playurl.", 1)
return
self.logMsg("Playurl: %s" % playurl)
listitem = xbmcgui.ListItem()
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
playlist.add(playurl, listitem)
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Insert",
'params': {
'playlistid': 1,
'position': position
}
}
if dbid is not None:
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
else:
pl['params']['item'] = {'file': url}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
def verifyPlaylist(self):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.GetItems",
'params': {
'playlistid': 1
}
}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
def removefromPlaylist(self, position):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Remove",
'params': {
'playlistid': 1,
'position': position
}
}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)

View file

@ -0,0 +1,426 @@
# -*- coding: utf-8 -*-
#################################################################################################
import utils
import clientinfo
import downloadutils
#################################################################################################
class Read_EmbyServer():
limitIndex = 200
def __init__(self):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def split_list(self, itemlist, size):
# Split up list in pieces of size. Will generate a list of lists
return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]
def getItem(self, itemid):
# This will return the full item
item = {}
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
result = self.doUtils.downloadUrl(url)
if result:
item = result
return item
def getItems(self, itemlist):
items = []
itemlists = self.split_list(itemlist, 50)
for itemlist in itemlists:
# Will return basic information
url = "{server}/emby/Users/{UserId}/Items?&format=json"
params = {
'Ids': ",".join(itemlist),
'Fields': "Etag"
}
result = self.doUtils.downloadUrl(url, parameters=params)
if result:
items.extend(result['Items'])
return items
def getFullItems(self, itemlist):
items = []
itemlists = self.split_list(itemlist, 50)
for itemlist in itemlists:
url = "{server}/emby/Users/{UserId}/Items?format=json"
params = {
"Ids": ",".join(itemlist),
"Fields": (
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ProductionLocations,"
"OfficialRating,Tags,ProviderIds,RemoteTrailers"
)
}
result = self.doUtils.downloadUrl(url, parameters=params)
if result:
items.extend(result['Items'])
return items
def getView_embyId(self, itemid):
# Returns ancestors using embyId
viewId = None
url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
result = self.doUtils.downloadUrl(url)
for view in result:
viewtype = view['Type']
if viewtype == "CollectionFolder":
# Found view
viewId = view['Id']
# Compare to view table in emby database
emby = utils.kodiSQL('emby')
cursor_emby = emby.cursor()
query = ' '.join((
"SELECT view_name, media_type",
"FROM view",
"WHERE view_id = ?"
))
cursor_emby.execute(query, (viewId,))
result = cursor_emby.fetchone()
try:
viewName = result[0]
mediatype = result[1]
except TypeError:
viewName = None
mediatype = None
cursor_emby.close()
return [viewName, viewId, mediatype]
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False):
doUtils = self.doUtils
items = {
'Items': [],
'TotalRecordCount': 0
}
# Get total number of items
url = "{server}/emby/Users/{UserId}/Items?format=json"
params = {
'ParentId': parentid,
'IncludeItemTypes': itemtype,
'CollapseBoxSetItems': False,
'IsVirtualUnaired': False,
'IsMissing': False,
'Recursive': True,
'Limit': 1
}
result = doUtils.downloadUrl(url, parameters=params)
try:
total = result['TotalRecordCount']
items['TotalRecordCount'] = total
except TypeError: # Failed to retrieve
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
else:
index = 0
jump = self.limitIndex
while index < total:
# Get items by chunk to increase retrieval speed at scale
params = {
'ParentId': parentid,
'IncludeItemTypes': itemtype,
'CollapseBoxSetItems': False,
'IsVirtualUnaired': False,
'IsMissing': False,
'Recursive': True,
'StartIndex': index,
'Limit': jump,
'SortBy': sortby,
'SortOrder': "Ascending",
}
if basic:
params['Fields'] = "Etag"
else:
params['Fields'] = (
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"OfficialRating,Tags,ProviderIds,ParentId,RemoteTrailers"
)
result = doUtils.downloadUrl(url, parameters=params)
items['Items'].extend(result['Items'])
index += jump
return items
def getViews(self, type, root=False):
# Build a list of user views
doUtils = self.doUtils
views = []
type = type.lower()
if not root:
url = "{server}/emby/Users/{UserId}/Views?format=json"
else: # Views ungrouped
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
result = doUtils.downloadUrl(url)
try:
items = result['Items']
except TypeError:
self.logMsg("Error retrieving views for type: %s" % type, 2)
else:
for item in items:
name = item['Name']
itemId = item['Id']
viewtype = item['Type']
if viewtype == "Channel":
# Filter view types
continue
# 11/10/2015 Review key, when it's added to server. Currently unavailable.
itemtype = item.get('OriginalCollectionType', item.get('CollectionType'))
# 11/29/2015 Remove this once OriginalCollectionType is added to stable server.
# Assumed missing is mixed then.
if itemtype is None:
url = "{server}/emby/Library/MediaFolders?format=json"
result = doUtils.downloadUrl(url)
for folder in result['Items']:
if itemId == folder['Id']:
itemtype = folder.get('CollectionType', "mixed")
if (itemtype == type or
(itemtype == "mixed" and type in ("movies", "tvshows"))):
views.append({
'name': name,
'type': itemtype,
'id': itemId
})
return views
def getMovies(self, parentId, basic=False):
items = self.getSection(parentId, "Movie", basic=basic)
return items
def getBoxset(self):
items = self.getSection(None, "BoxSet")
return items
def getMovies_byBoxset(self, boxsetid):
items = self.getSection(boxsetid)
return items
def getMusicVideos(self, parentId, basic=False):
items = self.getSection(parentId, "MusicVideo", basic=basic)
return items
def getHomeVideos(self, parentId):
items = self.getSection(parentId, "Video")
return items
def getShows(self, parentId, basic=False):
items = self.getSection(parentId, "Series", basic=basic)
return items
def getSeasons(self, showId):
items = {
'Items': [],
'TotalRecordCount': 0
}
url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
params = {
'IsVirtualUnaired': False,
'Fields': "Etag"
}
result = self.doUtils.downloadUrl(url, parameters=params)
if result:
items = result
return items
def getEpisodes(self, parentId, basic=False):
items = self.getSection(parentId, "Episode", basic=basic)
return items
def getEpisodesbyShow(self, showId):
items = self.getSection(showId, "Episode")
return items
def getEpisodesbySeason(self, seasonId):
items = self.getSection(seasonId, "Episode")
return items
def getArtists(self):
doUtils = self.doUtils
items = {
'Items': [],
'TotalRecordCount': 0
}
# Get total number of items
url = "{server}/emby/Artists?UserId={UserId}&format=json"
params = {
'Recursive': True,
'Limit': 1
}
result = doUtils.downloadUrl(url, parameters=params)
try:
total = result['TotalRecordCount']
items['TotalRecordCount'] = total
except TypeError: # Failed to retrieve
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
else:
index = 1
jump = self.limitIndex
while index < total:
# Get items by chunk to increase retrieval speed at scale
params = {
'Recursive': True,
'IsVirtualUnaired': False,
'IsMissing': False,
'StartIndex': index,
'Limit': jump,
'SortBy': "SortName",
'SortOrder': "Ascending",
'Fields': (
"Etag,Genres,SortName,Studios,Writer,ProductionYear,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,"
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
)
}
result = doUtils.downloadUrl(url, parameters=params)
items['Items'].extend(result['Items'])
index += jump
return items
def getAlbums(self, basic=False):
items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic)
return items
def getAlbumsbyArtist(self, artistId):
items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
return items
def getSongs(self, basic=False):
items = self.getSection(None, "Audio", basic=basic)
return items
def getSongsbyAlbum(self, albumId):
items = self.getSection(albumId, "Audio")
return items
def getAdditionalParts(self, itemId):
items = {
'Items': [],
'TotalRecordCount': 0
}
url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
result = self.doUtils.downloadUrl(url)
if result:
items = result
return items
def sortby_mediatype(self, itemids):
sorted_items = {}
# Sort items
items = self.getFullItems(itemids)
for item in items:
mediatype = item.get('Type')
if mediatype:
sorted_items.setdefault(mediatype, []).append(item)
return sorted_items

View file

@ -0,0 +1,319 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import threading
import websocket
import xbmc
import xbmcgui
import clientinfo
import downloadutils
import librarysync
import playlist
import userclient
import utils
import logging
logging.basicConfig()
#################################################################################################
class WebSocket_Client(threading.Thread):
_shared_state = {}
client = None
stopClient = False
def __init__(self):
self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor()
self.doUtils = downloadutils.DownloadUtils()
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.deviceId = self.clientInfo.getDeviceId()
self.librarySync = librarysync.LibrarySync()
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def sendProgressUpdate(self, data):
self.logMsg("sendProgressUpdate", 2)
try:
messageData = {
'MessageType': "ReportPlaybackProgress",
'Data': data
}
messageString = json.dumps(messageData)
self.client.send(messageString)
self.logMsg("Message data: %s" % messageString, 2)
except Exception as e:
self.logMsg("Exception: %s" % e, 1)
def on_message(self, ws, message):
result = json.loads(message)
messageType = result['MessageType']
data = result['Data']
if messageType not in ('SessionEnded'):
# Mute certain events
self.logMsg("Message: %s" % message, 1)
if messageType == "Play":
# A remote control play command has been sent from the server.
itemIds = data['ItemIds']
command = data['PlayCommand']
pl = playlist.Playlist()
dialog = xbmcgui.Dialog()
dialog.notification("Emby for Kodi", "Adding %s items to playlist." % len(itemIds))
if command == "PlayNow":
dialog.notification(
heading="Emby for Kodi",
message="Adding %s items to playlist." % len(itemIds),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
startat = data.get('StartPositionTicks', 0)
pl.playAll(itemIds, startat)
elif command == "PlayNext":
dialog.notification(
heading="Emby for Kodi",
message="Queueing %s items to playlist." % len(itemIds),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
newplaylist = pl.modifyPlaylist(itemIds)
player = xbmc.Player()
if not player.isPlaying():
# Only start the playlist if nothing is playing
player.play(newplaylist)
elif messageType == "Playstate":
# A remote control update playstate command has been sent from the server.
command = data['Command']
player = xbmc.Player()
actions = {
'Stop': player.stop,
'Unpause': player.pause,
'Pause': player.pause,
'NextTrack': player.playnext,
'PreviousTrack': player.playprevious,
'Seek': player.seekTime
}
action = actions[command]
if command == "Seek":
seekto = data['SeekPositionTicks']
seektime = seekto / 10000000.0
action(seektime)
self.logMsg("Seek to %s." % seektime, 1)
else:
action()
self.logMsg("Command: %s completed." % command, 1)
utils.window('emby_command', value="true")
elif messageType == "UserDataChanged":
# A user changed their personal rating for an item, or their playstate was updated
userdata_list = data['UserDataList']
self.librarySync.triage_items("userdata", userdata_list)
elif messageType == "LibraryChanged":
librarySync = self.librarySync
processlist = {
'added': data['ItemsAdded'],
'update': data['ItemsUpdated'],
'remove': data['ItemsRemoved']
}
for action in processlist:
librarySync.triage_items(action, processlist[action])
elif messageType == "GeneralCommand":
command = data['Name']
arguments = data['Arguments']
if command in ('Mute', 'Unmute', 'SetVolume',
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
player = xbmc.Player()
# These commands need to be reported back
if command == "Mute":
xbmc.executebuiltin('Mute')
elif command == "Unmute":
xbmc.executebuiltin('Mute')
elif command == "SetVolume":
volume = arguments['Volume']
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
elif command == "SetAudioStreamIndex":
index = int(arguments['Index'])
player.setAudioStream(index - 1)
elif command == "SetSubtitleStreamIndex":
embyindex = int(arguments['Index'])
currentFile = player.getPlayingFile()
mapping = utils.window('emby_%s.indexMapping' % currentFile)
if mapping:
externalIndex = json.loads(mapping)
# If there's external subtitles added via playbackutils
for index in externalIndex:
if externalIndex[index] == embyindex:
player.setSubtitleStream(int(index))
break
else:
# User selected internal subtitles
external = len(externalIndex)
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(external + embyindex - audioTracks - 1)
else:
# Emby merges audio and subtitle index together
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(index - audioTracks - 1)
# Let service know
utils.window('emby_command', value="true")
elif command == "DisplayMessage":
header = arguments['Header']
text = arguments['Text']
xbmcgui.Dialog().notification(
heading=header,
message=text,
icon="special://home/addons/plugin.video.emby/icon.png",
time=4000)
elif command == "SendString":
string = arguments['String']
text = {
'jsonrpc': "2.0",
'id': 0,
'method': "Input.SendText",
'params': {
'text': "%s" % string,
'done': False
}
}
result = xbmc.executeJSONRPC(json.dumps(text))
else:
builtin = {
'ToggleFullscreen': 'Action(FullScreen)',
'ToggleOsdMenu': 'Action(OSD)',
'ToggleContextMenu': 'Action(ContextMenu)',
'MoveUp': 'Action(Up)',
'MoveDown': 'Action(Down)',
'MoveLeft': 'Action(Left)',
'MoveRight': 'Action(Right)',
'Select': 'Action(Select)',
'Back': 'Action(back)',
'GoHome': 'ActivateWindow(Home)',
'PageUp': 'Action(PageUp)',
'NextLetter': 'Action(NextLetter)',
'GoToSearch': 'VideoLibrary.Search',
'GoToSettings': 'ActivateWindow(Settings)',
'PageDown': 'Action(PageDown)',
'PreviousLetter': 'Action(PrevLetter)',
'TakeScreenshot': 'TakeScreenshot',
'ToggleMute': 'Mute',
'VolumeUp': 'Action(VolumeUp)',
'VolumeDown': 'Action(VolumeDown)',
}
action = builtin.get(command)
if action:
xbmc.executebuiltin(action)
elif messageType == "ServerRestarting":
if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification(
heading="Emby server",
message="Server is restarting.",
icon="special://home/addons/plugin.video.emby/icon.png")
elif messageType == "UserConfigurationUpdated":
# Update user data set in userclient
userclient.UserClient().userSettings = data
self.librarySync.refresh_views = True
def on_close(self, ws):
self.logMsg("Closed.", 2)
def on_open(self, ws):
self.doUtils.postCapabilities(self.deviceId)
def on_error(self, ws, error):
if "10061" in str(error):
# Server is offline
pass
else:
self.logMsg("Error: %s" % error, 2)
def run(self):
monitor = self.monitor
loglevel = int(utils.window('emby_logLevel'))
# websocket.enableTrace(True)
userId = utils.window('emby_currUser')
server = utils.window('emby_server%s' % userId)
token = utils.window('emby_accessToken%s' % userId)
deviceId = self.deviceId
# Get the appropriate prefix for the websocket
if "https" in server:
server = server.replace('https', "wss")
else:
server = server.replace('http', "ws")
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
self.logMsg("websocket url: %s" % websocket_url, 1)
self.client = websocket.WebSocketApp(websocket_url,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close)
self.client.on_open = self.on_open
self.logMsg("----===## Starting WebSocketClient ##===----", 0)
while not monitor.abortRequested():
self.client.run_forever()
if self.stopClient:
break
if monitor.waitForAbort(5):
# Abort was requested, exit
break
self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
def stopClient(self):
self.stopClient = True
self.client.close()
self.logMsg("Stopping thread.")

View file

@ -1,63 +1,69 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings>
<category label="30014"> <!-- Emby -->
<setting id="ipaddress" type="text" label="30000" default="" visible="true" enable="true" />
<setting id="port" type="number" label="30030" default="8096" visible="true" enable="true" />
<setting id="https" type="bool" label="30243" visible="true" enable="true" default="false" />
<setting id="sslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
<setting id="sslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
<setting id="altip" type="bool" label="Use alternate address" default="false" visible="true" enable="true" />
<setting id="secondipaddress" type="text" label="Secondary Server Address" default="" visible="eq(-1,true)" enable="true" />
<setting id="secondport" type="number" label="30030" default="8096" visible="eq(-2,true)" enable="true" />
<setting id="secondhttps" type="bool" label="30243" visible="true" enable="eq(-3,true)" default="false" />
<setting id="secondsslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
<setting id="secondsslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
<setting id="pathsub" type="bool" visible="false" default="false" />
<setting id="username" type="text" label="30024" default="" />
<category label="30014"><!-- Emby -->
<!-- Primary address -->
<setting id="ipaddress" label="30000" type="text" default="" />
<setting id="port" label="30030" type="number" default="8096" />
<setting id="https" label="30243" type="bool" default="false" />
<setting id="sslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
<setting id="sslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
<!-- Secondary address -->
<setting id="altip" label="Use alternate address" type="bool" default="false" />
<setting id="secondipaddress" label="Secondary Server Address" type="text" default="" visible="eq(-1,true)" />
<setting id="secondport" label="30030" type="number" default="8096" visible="eq(-2,true)" />
<setting id="secondhttps" label="30243" type="bool" default="false" visible="eq(-3,true)" />
<setting id="secondsslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
<setting id="secondsslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
<!-- User settings -->
<setting id="username" label="30024" type="text" default="" />
<setting type="sep" />
<setting id="deviceNameOpt" type="bool" label="Use altername Device Name" visible="true" enable="true" default="true" />
<setting id="deviceName" type="text" label="30016" visible="eq(-1,true)" enable="true" default="Kodi" />
<setting id="deviceNameOpt" label="Use altername Device Name" type="bool" default="false" />
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
<setting label="[COLOR yellow]Reset login attempts[/COLOR]" type="action" visible="eq(1,) + !eq(-15,)" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
<setting id="accessToken" type="text" visible="false" default="" />
<setting label="[COLOR yellow]Reset login attempts[/COLOR]" type="action" visible="eq(-1,) + !eq(-9,)" enable="true" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
<setting id="pathsub" type="bool" visible="false" default="false" />
</category>
<category label="Sync Options">
<setting id="dbSyncIndication" type="bool" label="Show sync progress on screen" default="false" visible="true" enable="true" />
<setting id="syncSpecialsOrder" type="bool" label="Sync specials ordering" default="false" visible="true" enable="true" />
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" visible="true" enable="true" />
<setting id="enableMusicSync" type="bool" label="Enable Music Library Sync" default="true" visible="true" enable="true" />
<setting id="directstreammusic" type="bool" label="- Direct stream music library" default="false" visible="eq(-1,true)" enable="true" />
<setting id="useDirectPaths" type="bool" label="30250" default="false" visible="true" enable="true" />
<setting id="enableTextureCache" type="bool" label="Auto add images to the Kodi texture cache" default="true" visible="true" enable="true" />
<setting id="useIncSync" type="bool" label="Use incremental sync at startup (Requires Server Plugin)" default="false" visible="true" enable="true" />
<setting id="incSyncMaxItems" type="number" label=" - Max change set size" default="1000" visible="eq(-1,true)" enable="true" />
<setting id="dbSyncIndicator" label="Show sync progress" type="bool" default="false" />
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" />
<setting id="enableMusic" type="bool" label="Enable Music Library Sync" default="true" />
<setting id="streamMusic" type="bool" label="Direct stream music library" default="false" visible="eq(-1,true)" subsetting="true" />
<setting id="useDirectPaths" type="enum" label="Playback Mode" values="Addon(Default)|Native(Direct paths)" default="0" />
<setting id="enableTextureCache" label="Auto add images to the Kodi texture cache" type="bool" default="true" />
<setting id="serverSync" type="bool" label="Enable fast starting sync" default="true" />
</category>
<category label="Playback"> <!-- Extra Sync options -->
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
<setting label="Network credentials" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=passwords)" option="close" />
<setting type="sep" />
<setting id="disableCinema" type="bool" label="Disable Emby cinema mode" default="false" visible="true" enable="true" />
<setting id="askCinema" type="bool" label="Ask to play trailers" default="false" visible="eq(-1,false)" enable="true" subsetting="true" />
<setting id="offerDelete" type="bool" label="30114" visible="true" enable="true" default="false" />
<setting id="offerDeleteTV" type="bool" label="30115" visible="eq(-1,true)" enable="true" default="false" subsetting="true" />
<setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" subsetting="true" />
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" visible="true" enable="true" />
<setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" />
<setting id="videoBitRate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" default="17" />
<setting id="enableCinema" type="bool" label="Enable Emby cinema mode" default="true" />
<setting id="askCinema" type="bool" label="Ask to play trailers" default="false" visible="eq(-1,true)" subsetting="true" />
<setting id="offerDelete" type="bool" label="30114" default="false" />
<setting id="deleteTV" type="bool" label="30115" visible="eq(-1,true)" default="false" subsetting="true" />
<setting id="deleteMovies" type="bool" label="30116" visible="eq(-2,true)" default="false" subsetting="true" />
<setting id="skipConfirmDelete" type="bool" label="Skip delete confirmation" visible="eq(-3,true)" default="false" subsetting="true" />
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" />
<setting id="playFromStream" type="bool" label="30002" default="false" />
<setting id="videoBitrate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="eq(-1,true)" default="17" />
<setting id="transcodeH265" type="bool" label="Force transcode 1080p/H265" default="false" />
<setting id="markPlayed" type="number" visible="false" default="90" />
<setting id="directSteamFailedCount" type="number" visible="false" default="0" />
<setting id="failedCount" type="number" visible="false" default="0" />
<setting id="networkCreds" type="text" visible="false" default="" />
</category>
<category label="Extras">
<setting id="disableCoverArt" type="bool" label="30157" default="false" visible="true" enable="true" />
<setting id="coverArtratio" type="bool" label="Force CoverArt Ratio" visible="eq(-1,false)" default="false" enable="true" />
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" visible="true" enable="true" default="false" />
<setting id="showSpecialInfoDialog" type="bool" label="Show special Emby info dialog on play" default="false" visible="false" />
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" visible="true" enable="true" />
<setting id="enableCoverArt" type="bool" label="30157" default="true" />
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
</category>
<category label="30022">
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="0" />
<setting id="supressConnectMsg" type="bool" label="30249" default="false" visible="true" enable="true" />
<setting id="supressRestartMsg" type="bool" label="Enable server message when it's restarting" default="false" visible="true" enable="true" />
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" />
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="1" />
<setting id="connectMsg" type="bool" label="30249" default="true" />
<setting id="restartMsg" type="bool" label="Enable server message when it's restarting" default="false" />
<setting id="newContent" type="bool" label="Enable new content notification" default="false" />
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" option="close" />
</category>
</settings>

View file

@ -7,8 +7,8 @@ import sys
import time
from datetime import datetime
import xbmcaddon
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
@ -16,152 +16,142 @@ import xbmcvfs
_addon = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = _addon.getAddonInfo('path').decode('utf-8')
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource_path)
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
#################################################################################################
import KodiMonitor
import Utils as utils
from ClientInformation import ClientInformation
from ConnectionManager import ConnectionManager
from UserClient import UserClient
from Player import Player
from WebSocketClient import WebSocketThread
from LibrarySync import LibrarySync
import userclient
import clientinfo
import initialsetup
import kodimonitor
import librarysync
import player
import utils
import videonodes
import websocket_client as wsc
#################################################################################################
class Service():
KodiMonitor = KodiMonitor.Kodi_Monitor()
clientInfo = ClientInformation()
addonName = clientInfo.getAddonName()
logLevel = UserClient().getLogLevel()
WINDOW = xbmcgui.Window(10000)
newWebSocketThread = None
newUserClient = None
newLibraryThread = None
warn_auth = True
welcome_msg = True
server_online = True
warn_auth = True
def __init__(self, *args):
userclient_running = False
websocket_running = False
library_running = False
kodimonitor_running = False
addonName = self.addonName
clientInfo = self.clientInfo
logLevel = self.logLevel
utils.window('getLogLevel', value=str(logLevel))
utils.window('kodiProfile_emby', value=xbmc.translatePath("special://profile"))
def __init__(self):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
logLevel = userclient.UserClient().getLogLevel()
self.monitor = xbmc.Monitor()
utils.window('emby_logLevel', value=str(logLevel))
utils.window('emby_kodiProfile', value=xbmc.translatePath("special://profile"))
# Initial logging
self.logMsg("Starting Monitor", 0)
self.logMsg("======== START %s ========" % addonName, 0)
self.logMsg("Platform: %s" % (clientInfo.getPlatform()), 0)
self.logMsg("======== START %s ========" % self.addonName, 0)
self.logMsg("Platform: %s" % (self.clientInfo.getPlatform()), 0)
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0)
self.logMsg("%s Version: %s" % (addonName, clientInfo.getVersion()), 0)
self.logMsg("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0)
self.logMsg("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0)
self.logMsg("Log Level: %s" % logLevel, 0)
# Reset window props for profile switch
utils.window('Server_online', clear=True)
utils.window('Server_status', clear=True)
utils.window('startup', clear=True)
utils.window('OnWakeSync', clear=True)
utils.window('kodiScan', clear=True)
utils.window('minDBVersionCheck', clear=True)
# Set min DB version
utils.window('minDBVersion', value="1.1.52")
properties = [
embyProperty = utils.window('Emby.nodes.total')
propNames = [
"index","path","title","content",
"inprogress.content","inprogress.title",
"inprogress.content","inprogress.path",
"nextepisodes.title","nextepisodes.content",
"nextepisodes.path","unwatched.title",
"unwatched.content","unwatched.path",
"recent.title","recent.content","recent.path",
"recentepisodes.title","recentepisodes.content",
"recentepisodes.path","inprogressepisodes.title",
"inprogressepisodes.content","inprogressepisodes.path"
"emby_online", "emby_serverStatus", "emby_onWake",
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId"
]
for prop in properties:
utils.window(prop, clear=True)
if embyProperty:
totalNodes = int(embyProperty)
for i in range(totalNodes):
for prop in propNames:
utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
# Clear playlist properties
xbmcgui.Window(10101).clearProperties()
# Clear video nodes properties
videonodes.VideoNodes().clearProperties()
# Set the minimum database version
utils.window('emby_minDBVersion', value="1.1.63")
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def ServiceEntryPoint(self):
# Important: Threads depending on abortRequest will not trigger
# if profile switch happens more than once.
monitor = self.monitor
kodiProfile = xbmc.translatePath("special://profile")
# Server auto-detect
ConnectionManager().checkServer()
initialsetup.InitialSetup().setup()
# Initialize important threads
user = UserClient()
player = Player()
ws = WebSocketThread()
library = LibrarySync()
user = userclient.UserClient()
ws = wsc.WebSocket_Client()
library = librarysync.LibrarySync()
kplayer = player.Player()
# Sync and progress report
lastProgressUpdate = datetime.today()
while not self.KodiMonitor.abortRequested():
while not monitor.abortRequested():
if utils.window('emby_kodiProfile') != kodiProfile:
# Profile change happened, terminate this thread and others
self.logMsg(
"Kodi profile was: %s and changed to: %s. Terminating old Emby thread."
% (kodiProfile, utils.window('emby_kodiProfile')), 1)
break
# Before proceeding, need to make sure:
# 1. Server is online
# 2. User is set
# 3. User has access to the server
if utils.window("kodiProfile_emby") != kodiProfile:
# Profile change happened, terminate this thread
self.logMsg("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1)
break
if utils.window('Server_online') == "true":
if utils.window('emby_online') == "true":
# Emby server is online
# Verify if user is set and has access to the server
if (user.currUser is not None) and user.HasAccess:
# If an item is playing
# If an item is playing
if xbmc.Player().isPlaying():
try:
# Update and report progress
playTime = xbmc.Player().getTime()
playtime = xbmc.Player().getTime()
totalTime = xbmc.Player().getTotalTime()
currentFile = player.currentFile
currentFile = kplayer.currentFile
# Update positionticks
if player.played_information.get(currentFile) is not None:
player.played_information[currentFile]['currentPosition'] = playTime
if kplayer.played_info.get(currentFile) is not None:
kplayer.played_info[currentFile]['currentPosition'] = playtime
td = datetime.today() - lastProgressUpdate
secDiff = td.seconds
# Report progress to Emby server
if (secDiff > 3):
player.reportPlayback()
kplayer.reportPlayback()
lastProgressUpdate = datetime.today()
elif utils.window('commandUpdate') == "true":
elif utils.window('emby_command') == "true":
# Received a remote control command that
# requires updating immediately
utils.window('commandUpdate', clear=True)
player.reportPlayback()
lastProgressUpdate = da4tetime.today()
utils.window('emby_command', clear=True)
kplayer.reportPlayback()
lastProgressUpdate = datetime.today()
except Exception as e:
self.logMsg("Exception in Playback Monitor Service: %s" % e, 1)
@ -169,27 +159,34 @@ class Service():
else:
# Start up events
self.warn_auth = True
if utils.settings('supressConnectMsg') == "false":
if self.welcome_msg:
# Reset authentication warnings
self.welcome_msg = False
# Get additional users
additionalUsers = user.AdditionalUser
if additionalUsers:
add = ", %s" % ", ".join(additionalUsers)
else:
add = ""
xbmcgui.Dialog().notification("Emby server", "Welcome %s%s!" % (user.currUser, add), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False)
if utils.settings('connectMsg') == "true" and self.welcome_msg:
# Reset authentication warnings
self.welcome_msg = False
# Get additional users
additionalUsers = user.AdditionalUser
if additionalUsers:
add = ", %s" % ", ".join(additionalUsers)
else:
add = ""
xbmcgui.Dialog().notification(
heading="Emby server",
message="Welcome %s%s!" % (user.currUser, add),
icon="special://home/addons/plugin.video.emby/icon.png",
time=2000,
sound=False)
# Start monitoring kodi events
if not self.kodimonitor_running:
self.kodimonitor_running = kodimonitor.KodiMonitor()
# Start the Websocket Client
if (self.newWebSocketThread is None):
self.newWebSocketThread = "Started"
if not self.websocket_running:
self.websocket_running = True
ws.start()
# Start the Library Sync Thread
if (self.newLibraryThread is None):
self.newLibraryThread = "Started"
# Start the syncing thread
if not self.library_running:
self.library_running = True
library.start()
else:
if (user.currUser is None) and self.warn_auth:
@ -204,20 +201,19 @@ class Service():
# Verify access with an API call
user.hasAccess()
if utils.window('Server_online') != "true":
if utils.window('emby_online') != "true":
# Server went offline
break
if self.KodiMonitor.waitForAbort(5):
if monitor.waitForAbort(5):
# Abort was requested while waiting. We should exit
break
else:
# Wait until Emby server is online
# or Kodi is shut down.
while not self.KodiMonitor.abortRequested():
while not monitor.abortRequested():
if user.getServer() == "":
if user.getServer() == False:
# No server info set in add-on settings
pass
@ -226,8 +222,14 @@ class Service():
# Alert the user and suppress future warning
if self.server_online:
self.logMsg("Server is offline.", 1)
utils.window('Server_online', value="false")
xbmcgui.Dialog().notification("Error connecting", "%s Server is unreachable." % self.addonName, icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
utils.window('emby_online', value="false")
xbmcgui.Dialog().notification(
heading="Error connecting",
message="%s Server is unreachable." % self.addonName,
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
self.server_online = False
else:
@ -235,54 +237,55 @@ class Service():
if not self.server_online:
# Server was offline when Kodi started.
# Wait for server to be fully established.
if self.KodiMonitor.waitForAbort(5):
if monitor.waitForAbort(5):
# Abort was requested while waiting.
break
# Alert the user that server is online.
xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % user.currUser, icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False)
xbmcgui.Dialog().notification(
heading="Emby server",
message="Server is online.",
icon="special://home/addons/plugin.video.emby/icon.png",
time=2000,
sound=False)
self.server_online = True
self.logMsg("Server is online and ready.", 1)
utils.window('Server_online', value="true")
utils.window('emby_online', value="true")
# Start the User client
if self.newUserClient is None:
self.newUserClient = "Started"
# Start the userclient thread
if not self.userclient_running:
self.userclient_running = True
user.start()
break
if self.KodiMonitor.waitForAbort(1):
if monitor.waitForAbort(1):
# Abort was requested while waiting.
break
if self.KodiMonitor.waitForAbort(1):
if monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
##### Emby thread is terminating. #####
# If music is enabled and direct stream for music is enabled
# We use Kodi pathsubstitution to allow for music to play outside network
# The setting needs to be set before Kodi starts.
if utils.settings('enableMusicSync') == "true" and utils.settings('directstreammusic') == "true":
# We need to keep track of the settings
alternate = utils.settings('altip') == "true"
pathsub = utils.settings('pathsub') == "true"
if pathsub and not alternate:
# Path sub in place, but primary address in use, remove it
utils.pathsubstitution(False)
elif not pathsub and alternate:
# Path sub not in place, but secondary address in use, add it
utils.pathsubstitution()
if (self.newWebSocketThread is not None):
ws.stopClient()
if self.library_running:
library.stopThread()
if (self.newUserClient is not None):
if self.websocket_running:
ws.stopClient()
if self.userclient_running:
user.stopClient()
self.logMsg("======== STOP %s ========" % self.addonName, 0)
# Start the service
Service().ServiceEntryPoint()
# Delay option
delay = int(utils.settings('startupDelay'))
xbmc.log("Delaying emby startup by: %s sec..." % delay)
if delay and xbmc.Monitor().waitForAbort(delay):
# Start the service
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
else:
Service().ServiceEntryPoint()