Version 1.1.63
alpha ready for beta testing
This commit is contained in:
parent
0200df3225
commit
3f6fe0a9e7
25 changed files with 6278 additions and 4069 deletions
|
@ -1,11 +1,14 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.emby"
|
<addon id="plugin.video.emby"
|
||||||
name="Emby"
|
name="Emby"
|
||||||
version="1.1.62"
|
version="1.1.63"
|
||||||
provider-name="Emby.media">
|
provider-name="Emby.media">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<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>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource"
|
<extension point="xbmc.python.pluginsource"
|
||||||
library="default.py">
|
library="default.py">
|
||||||
|
|
132
default.py
132
default.py
|
@ -1,18 +1,33 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import xbmcaddon, xbmc
|
|
||||||
import os, sys
|
#################################################################################################
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
||||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||||
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||||
sys.path.append(base_resource_path)
|
sys.path.append(base_resource)
|
||||||
import Entrypoint as entrypoint
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import entrypoint
|
||||||
|
import utils
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
enableProfiling = False
|
enableProfiling = False
|
||||||
|
|
||||||
class Main:
|
class Main:
|
||||||
|
|
||||||
|
|
||||||
# MAIN ENTRY POINT
|
# MAIN ENTRY POINT
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
@ -24,84 +39,67 @@ class Main:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mode = params['mode'][0]
|
mode = params['mode'][0]
|
||||||
id = params.get('id', None)
|
itemid = params.get('id')
|
||||||
if id:
|
if itemid:
|
||||||
id = id[0]
|
itemid = itemid[0]
|
||||||
except:
|
except:
|
||||||
params = {}
|
params = {}
|
||||||
mode = ""
|
mode = ""
|
||||||
|
|
||||||
##### PLAY ITEM VIA plugin://plugin.video.emby/ #####
|
|
||||||
if "play" in mode or "playnow" in mode:
|
|
||||||
entrypoint.doPlayback(id)
|
|
||||||
|
|
||||||
#### DO RESET AUTH #####
|
modes = {
|
||||||
elif "resetauth" in mode:
|
|
||||||
entrypoint.resetAuth()
|
|
||||||
|
|
||||||
##### DO DATABASE RESET #####
|
'reset': utils.reset,
|
||||||
elif "reset" in mode:
|
'resetauth': entrypoint.resetAuth,
|
||||||
import Utils as utils
|
'play': entrypoint.doPlayback,
|
||||||
utils.reset()
|
'passwords': utils.passwordsXML,
|
||||||
|
'adduser': entrypoint.addUser,
|
||||||
|
'thememedia': entrypoint.getThemeMedia,
|
||||||
|
'channels': entrypoint.BrowseChannels,
|
||||||
|
'channelsfolder': entrypoint.BrowseChannels,
|
||||||
|
'nextup': entrypoint.getNextUpEpisodes,
|
||||||
|
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||||
|
'recentepisodes': entrypoint.getRecentEpisodes
|
||||||
|
}
|
||||||
|
|
||||||
##### ADD/REMOVE USER FROM SESSION #####
|
if modes.get(mode):
|
||||||
elif "adduser" in mode:
|
# Simple functions
|
||||||
entrypoint.addUser()
|
if mode == "play":
|
||||||
|
dbid = params.get('dbid')
|
||||||
|
modes[mode](itemid, dbid)
|
||||||
|
|
||||||
##### SYNC THEME MEDIA #####
|
elif mode in ("nextup", "inprogressepisodes", "recentepisodes"):
|
||||||
elif "thememedia" in mode:
|
limit = int(params['limit'][0])
|
||||||
entrypoint.getThemeMedia()
|
modes[mode](itemid, limit)
|
||||||
|
|
||||||
##### LAUNCH EMBY USER PREFS #####
|
elif mode == "channels":
|
||||||
elif "userprefs" in mode:
|
modes[mode](itemid)
|
||||||
entrypoint.userPreferences()
|
|
||||||
|
|
||||||
##### OPEN ADDON SETTINGS #####
|
elif mode == "channelsfolder":
|
||||||
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]
|
folderid = params['folderid'][0]
|
||||||
entrypoint.BrowseChannels(id,folderid)
|
modes[mode](itemid, folderid)
|
||||||
|
|
||||||
##### BROWSE EMBY CHANNELS ROOT #####
|
else:
|
||||||
elif "channels" in mode:
|
modes[mode]()
|
||||||
entrypoint.BrowseChannels(id)
|
else:
|
||||||
|
# Other functions
|
||||||
##### GET NEXTUP EPISODES FOR TAGNAME #####
|
if mode == "settings":
|
||||||
elif "nextup" in mode:
|
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
|
||||||
limit = int(params['limit'][0])
|
elif mode in ("manualsync", "repair"):
|
||||||
entrypoint.getNextUpEpisodes(id, limit)
|
import librarysync
|
||||||
|
if mode == "manualsync":
|
||||||
##### GET INPROGRESS EPISODES FOR TAGNAME #####
|
librarysync.LibrarySync().fullSync(manualrun=True)
|
||||||
elif "inprogressepisodes" in mode:
|
else:
|
||||||
limit = int(params['limit'][0])
|
librarysync.LibrarySync().fullSync(repair=True)
|
||||||
entrypoint.getInProgressEpisodes(id, limit)
|
elif mode == "texturecache":
|
||||||
|
import artwork
|
||||||
##### GET RECENT EPISODES FOR TAGNAME #####
|
artwork.Artwork().FullTextureCacheSync()
|
||||||
elif "recentepisodes" in mode:
|
|
||||||
limit = int(params['limit'][0])
|
|
||||||
entrypoint.getRecentEpisodes(id, limit)
|
|
||||||
|
|
||||||
##### GET EXTRAFANART FOR LISTITEM #####
|
|
||||||
elif "extrafanart" in sys.argv[0]:
|
elif "extrafanart" in sys.argv[0]:
|
||||||
entrypoint.getExtraFanArt()
|
entrypoint.getExtraFanArt()
|
||||||
|
else:
|
||||||
##### SHOW ADDON NODES LISTING #####
|
|
||||||
if not mode:
|
|
||||||
entrypoint.doMainListing()
|
entrypoint.doMainListing()
|
||||||
|
|
||||||
|
|
||||||
if ( __name__ == "__main__" ):
|
if ( __name__ == "__main__" ):
|
||||||
xbmc.log('plugin.video.emby started')
|
xbmc.log('plugin.video.emby started')
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@
|
||||||
<string id="30246">Enable Netflix style next up notification</string>
|
<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="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="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>
|
<string id="30250">Use local paths instead of addon redirect for playback</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,107 @@
|
||||||
# -- coding: utf-8 --
|
# -*- coding: utf-8 -*-
|
||||||
# API.py
|
|
||||||
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
|
|
||||||
|
|
||||||
from datetime import datetime
|
##################################################################################################
|
||||||
from random import randrange
|
|
||||||
import xbmc
|
import clientinfo
|
||||||
import xbmcgui
|
import utils
|
||||||
|
|
||||||
|
##################################################################################################
|
||||||
|
|
||||||
import Utils as utils
|
|
||||||
|
|
||||||
class API():
|
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
|
# Process People
|
||||||
director = []
|
director = []
|
||||||
writer = []
|
writer = []
|
||||||
cast = []
|
cast = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
people = item['People']
|
people = self.item['People']
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
for person in people:
|
for person in people:
|
||||||
|
|
||||||
type = person['Type']
|
type = person['Type']
|
||||||
Name = person['Name']
|
name = person['Name']
|
||||||
|
|
||||||
if "Director" in type:
|
if "Director" in type:
|
||||||
director.append(Name)
|
director.append(name)
|
||||||
elif "Writing" in type:
|
|
||||||
writer.append(Name)
|
|
||||||
elif "Writer" in type:
|
|
||||||
writer.append(Name)
|
|
||||||
elif "Actor" in type:
|
elif "Actor" in type:
|
||||||
cast.append(Name)
|
cast.append(name)
|
||||||
|
elif type in ("Writing", "Writer"):
|
||||||
|
writer.append(name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
|
@ -43,52 +110,136 @@ class API():
|
||||||
'Cast': cast
|
'Cast': cast
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTimeInfo(self, item):
|
def getMediaStreams(self):
|
||||||
# Runtime and Resume point
|
item = self.item
|
||||||
tempRuntime = 0
|
videotracks = []
|
||||||
runtime = 0
|
audiotracks = []
|
||||||
resume = 0
|
subtitlelanguages = []
|
||||||
|
|
||||||
try: # Get resume point
|
try:
|
||||||
userdata = item['UserData']
|
media_streams = item['MediaSources'][0]['MediaStreams']
|
||||||
playbackPosition = userdata['PlaybackPositionTicks']
|
|
||||||
resume = playbackPosition / 10000000.0
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
try: # Get total runtime
|
except KeyError:
|
||||||
tempRuntime = item['RunTimeTicks']
|
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:
|
except:
|
||||||
try: tempRuntime = item['CumulativeRunTimeTicks']
|
container = ""
|
||||||
except: pass
|
|
||||||
|
|
||||||
finally:
|
# Sort codec vs container/profile
|
||||||
runtime = tempRuntime / 10000000.0
|
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 {
|
return {
|
||||||
|
|
||||||
'ResumeTime': resume,
|
'video': videotracks,
|
||||||
'TotalTime': runtime
|
'audio': audiotracks,
|
||||||
|
'subtitle': subtitlelanguages
|
||||||
}
|
}
|
||||||
|
|
||||||
def getStudios(self, item):
|
def getRuntime(self):
|
||||||
# Process Studio
|
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 = []
|
studios = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
studio = item['SeriesStudio']
|
studio = item['SeriesStudio']
|
||||||
studios.append(self.getStudio(studio))
|
studios.append(self.verifyStudio(studio))
|
||||||
except:
|
|
||||||
try:
|
except KeyError:
|
||||||
studioArray = item['Studios']
|
studioList = item['Studios']
|
||||||
for studio in studioArray:
|
for studio in studioList:
|
||||||
studios.append(self.getStudio(studio['Name']))
|
|
||||||
except: pass
|
name = studio['Name']
|
||||||
|
studios.append(self.verifyStudio(name))
|
||||||
|
|
||||||
return studios
|
return studios
|
||||||
|
|
||||||
def getStudio(self, studioName):
|
def verifyStudio(self, studioName):
|
||||||
# Convert studio for Kodi to properly detect them
|
# Convert studio for Kodi to properly detect them
|
||||||
studios = {
|
studios = {
|
||||||
|
|
||||||
|
@ -101,503 +252,127 @@ class API():
|
||||||
|
|
||||||
return studios.get(studioName.lower(), studioName)
|
return studios.get(studioName.lower(), studioName)
|
||||||
|
|
||||||
def getGenre(self,item):
|
def getChecksum(self):
|
||||||
genre = ""
|
# Use the etags checksum and userdata
|
||||||
genres = item.get("Genres")
|
item = self.item
|
||||||
if genres != None and genres != []:
|
userdata = item['UserData']
|
||||||
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 getMediaStreams(self, item, mediaSources = False):
|
checksum = "%s%s%s%s%s%s" % (
|
||||||
|
|
||||||
videotracks = [] # Height, Width, Codec, AspectRatio, AspectFloat, 3D
|
item['Etag'],
|
||||||
audiotracks = [] # Codec, Channels, language
|
userdata['Played'],
|
||||||
subtitlelanguages = [] # Language
|
userdata['IsFavorite'],
|
||||||
|
userdata['PlaybackPositionTicks'],
|
||||||
if mediaSources:
|
userdata.get('UnplayedItemCount', ""),
|
||||||
try:
|
userdata.get('LastPlayedDate', "")
|
||||||
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"))
|
|
||||||
|
|
||||||
return checksum
|
return checksum
|
||||||
|
|
||||||
def getUserData(self, item):
|
def getGenres(self):
|
||||||
# Default
|
item = self.item
|
||||||
favorite = False
|
all_genres = ""
|
||||||
playcount = None
|
genres = item.get('Genres', item.get('SeriesGenres'))
|
||||||
lastPlayedDate = None
|
|
||||||
userKey = ""
|
if genres:
|
||||||
|
all_genres = " / ".join(genres)
|
||||||
|
|
||||||
|
return all_genres
|
||||||
|
|
||||||
|
def getDateCreated(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
userdata = item['UserData']
|
dateadded = self.item['DateCreated']
|
||||||
|
|
||||||
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 = dateadded.split('.')[0].replace('T', " ")
|
dateadded = dateadded.split('.')[0].replace('T', " ")
|
||||||
except:
|
except KeyError:
|
||||||
dateadded = None
|
dateadded = None
|
||||||
|
|
||||||
return dateadded
|
return dateadded
|
||||||
|
|
||||||
def getPremiereDate(self, item):
|
def getPremiereDate(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
premiere = item['PremiereDate']
|
premiere = self.item['PremiereDate']
|
||||||
premiere = premiere.split('.')[0].replace('T', " ")
|
premiere = premiere.split('.')[0].replace('T', " ")
|
||||||
except:
|
except KeyError:
|
||||||
premiere = None
|
premiere = None
|
||||||
|
|
||||||
return premiere
|
return premiere
|
||||||
|
|
||||||
def getTagline(self, item):
|
def getOverview(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tagline = item['Taglines'][0]
|
overview = self.item['Overview']
|
||||||
except:
|
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
|
tagline = None
|
||||||
|
|
||||||
return tagline
|
return tagline
|
||||||
|
|
||||||
def getProvider(self, item, providername):
|
def getProvider(self, providername):
|
||||||
# Provider Name: imdb or tvdb
|
|
||||||
try:
|
try:
|
||||||
if "imdb" in providername:
|
provider = self.item['ProviderIds'][providername]
|
||||||
provider = item['ProviderIds']['Imdb']
|
except KeyError:
|
||||||
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 = None
|
provider = None
|
||||||
|
|
||||||
return provider
|
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:
|
try:
|
||||||
country = item['ProductionLocations'][0]
|
country = self.item['ProductionLocations'][0]
|
||||||
except:
|
except IndexError:
|
||||||
country = None
|
country = None
|
||||||
|
|
||||||
return country
|
return country
|
||||||
|
|
||||||
def getMpaa(self, item):
|
def getFilePath(self):
|
||||||
# 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"
|
|
||||||
|
|
||||||
return mpaa
|
item = self.item
|
||||||
|
try:
|
||||||
|
filepath = item['Path']
|
||||||
|
|
||||||
def getAllArtwork(self, item, parentInfo = False):
|
except KeyError:
|
||||||
|
filepath = ""
|
||||||
|
|
||||||
"""
|
else:
|
||||||
Get all artwork, it will return an empty string
|
if "\\\\" in filepath:
|
||||||
for the artwork type not found.
|
# append smb protocol
|
||||||
|
filepath = filepath.replace("\\\\", "smb://")
|
||||||
|
filepath = filepath.replace("\\", "/")
|
||||||
|
|
||||||
Artwork type: Primary, Art, Banner, Logo, Thumb,
|
if item.get('VideoType'):
|
||||||
Disc, Backdrop
|
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
|
||||||
|
|
||||||
username = utils.window('currUser')
|
if "\\" in filepath:
|
||||||
server = utils.window('server%s' % username)
|
# Local path scenario, with special videotype
|
||||||
|
filepath = filepath.replace("/", "\\")
|
||||||
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:
|
|
||||||
|
|
||||||
# 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']
|
|
||||||
|
|
||||||
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
|
|
@ -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"
|
|
|
@ -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
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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()
|
|
|
@ -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()
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
524
resources/lib/artwork.py
Normal 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
|
96
resources/lib/clientinfo.py
Normal file
96
resources/lib/clientinfo.py
Normal 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
|
294
resources/lib/embydb_functions.py
Normal file
294
resources/lib/embydb_functions.py
Normal 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,))
|
192
resources/lib/initialsetup.py
Normal file
192
resources/lib/initialsetup.py
Normal 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
2528
resources/lib/itemtypes.py
Normal file
File diff suppressed because it is too large
Load diff
1143
resources/lib/kodidb_functions.py
Normal file
1143
resources/lib/kodidb_functions.py
Normal file
File diff suppressed because it is too large
Load diff
201
resources/lib/playlist.py
Normal file
201
resources/lib/playlist.py
Normal 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)
|
426
resources/lib/read_embyserver.py
Normal file
426
resources/lib/read_embyserver.py
Normal 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
|
319
resources/lib/websocket_client.py
Normal file
319
resources/lib/websocket_client.py
Normal 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.")
|
|
@ -1,63 +1,69 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<category label="30014"> <!-- Emby -->
|
<category label="30014"><!-- Emby -->
|
||||||
<setting id="ipaddress" type="text" label="30000" default="" visible="true" enable="true" />
|
<!-- Primary address -->
|
||||||
<setting id="port" type="number" label="30030" default="8096" visible="true" enable="true" />
|
<setting id="ipaddress" label="30000" type="text" default="" />
|
||||||
<setting id="https" type="bool" label="30243" visible="true" enable="true" default="false" />
|
<setting id="port" label="30030" type="number" default="8096" />
|
||||||
<setting id="sslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
|
<setting id="https" label="30243" type="bool" default="false" />
|
||||||
<setting id="sslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
|
<setting id="sslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
|
||||||
<setting id="altip" type="bool" label="Use alternate address" default="false" visible="true" enable="true" />
|
<setting id="sslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
|
||||||
<setting id="secondipaddress" type="text" label="Secondary Server Address" default="" visible="eq(-1,true)" enable="true" />
|
<!-- Secondary address -->
|
||||||
<setting id="secondport" type="number" label="30030" default="8096" visible="eq(-2,true)" enable="true" />
|
<setting id="altip" label="Use alternate address" type="bool" default="false" />
|
||||||
<setting id="secondhttps" type="bool" label="30243" visible="true" enable="eq(-3,true)" default="false" />
|
<setting id="secondipaddress" label="Secondary Server Address" type="text" default="" visible="eq(-1,true)" />
|
||||||
<setting id="secondsslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
|
<setting id="secondport" label="30030" type="number" default="8096" visible="eq(-2,true)" />
|
||||||
<setting id="secondsslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
|
<setting id="secondhttps" label="30243" type="bool" default="false" visible="eq(-3,true)" />
|
||||||
<setting id="pathsub" type="bool" visible="false" default="false" />
|
<setting id="secondsslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
|
||||||
<setting id="username" type="text" label="30024" default="" />
|
<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 type="sep" />
|
||||||
<setting id="deviceNameOpt" type="bool" label="Use altername Device Name" visible="true" enable="true" default="true" />
|
<setting id="deviceNameOpt" label="Use altername Device Name" type="bool" default="false" />
|
||||||
<setting id="deviceName" type="text" label="30016" visible="eq(-1,true)" enable="true" default="Kodi" />
|
<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 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>
|
||||||
|
|
||||||
<category label="Sync Options">
|
<category label="Sync Options">
|
||||||
<setting id="dbSyncIndication" type="bool" label="Show sync progress on screen" default="false" visible="true" enable="true" />
|
<setting id="dbSyncIndicator" label="Show sync progress" type="bool" default="false" />
|
||||||
<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" />
|
||||||
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" visible="true" enable="true" />
|
<setting id="enableMusic" type="bool" label="Enable Music Library Sync" default="true" />
|
||||||
<setting id="enableMusicSync" type="bool" label="Enable Music Library Sync" default="true" visible="true" enable="true" />
|
<setting id="streamMusic" type="bool" label="Direct stream music library" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||||
<setting id="directstreammusic" type="bool" label="- Direct stream music library" default="false" visible="eq(-1,true)" enable="true" />
|
<setting id="useDirectPaths" type="enum" label="Playback Mode" values="Addon(Default)|Native(Direct paths)" default="0" />
|
||||||
<setting id="useDirectPaths" type="bool" label="30250" default="false" visible="true" enable="true" />
|
<setting id="enableTextureCache" label="Auto add images to the Kodi texture cache" type="bool" default="true" />
|
||||||
<setting id="enableTextureCache" type="bool" label="Auto add images to the Kodi texture cache" default="true" visible="true" enable="true" />
|
<setting id="serverSync" type="bool" label="Enable fast starting sync" default="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" />
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="Playback"> <!-- Extra Sync options -->
|
<category label="Playback"> <!-- Extra Sync options -->
|
||||||
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
|
<setting label="Network credentials" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=passwords)" option="close" />
|
||||||
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
|
|
||||||
<setting type="sep" />
|
<setting type="sep" />
|
||||||
<setting id="disableCinema" type="bool" label="Disable Emby cinema mode" default="false" visible="true" enable="true" />
|
<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,false)" enable="true" subsetting="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" visible="true" enable="true" default="false" />
|
<setting id="offerDelete" type="bool" label="30114" default="false" />
|
||||||
<setting id="offerDeleteTV" type="bool" label="30115" visible="eq(-1,true)" enable="true" default="false" subsetting="true" />
|
<setting id="deleteTV" type="bool" label="30115" visible="eq(-1,true)" default="false" subsetting="true" />
|
||||||
<setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" subsetting="true" />
|
<setting id="deleteMovies" type="bool" label="30116" visible="eq(-2,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="skipConfirmDelete" type="bool" label="Skip delete confirmation" visible="eq(-3,true)" default="false" subsetting="true" />
|
||||||
<setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" />
|
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" />
|
||||||
<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="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="transcodeH265" type="bool" label="Force transcode 1080p/H265" default="false" />
|
||||||
<setting id="markPlayed" type="number" visible="false" default="90" />
|
<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>
|
||||||
|
|
||||||
<category label="Extras">
|
<category label="Extras">
|
||||||
<setting id="disableCoverArt" type="bool" label="30157" default="false" visible="true" enable="true" />
|
<setting id="enableCoverArt" type="bool" label="30157" default="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" default="false" />
|
||||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" visible="true" enable="true" default="false" />
|
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
||||||
<setting id="showSpecialInfoDialog" type="bool" label="Show special Emby info dialog on play" default="false" visible="false" />
|
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
||||||
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" visible="true" enable="true" />
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30022">
|
<category label="30022">
|
||||||
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="0" />
|
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="1" />
|
||||||
<setting id="supressConnectMsg" type="bool" label="30249" default="false" visible="true" enable="true" />
|
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
||||||
<setting id="supressRestartMsg" type="bool" label="Enable server message when it's restarting" default="false" visible="true" enable="true" />
|
<setting id="restartMsg" type="bool" label="Enable server message when it's restarting" default="false" />
|
||||||
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" />
|
<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>
|
</category>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
251
service.py
251
service.py
|
@ -7,8 +7,8 @@ import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmc
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
|
@ -16,121 +16,111 @@ import xbmcvfs
|
||||||
|
|
||||||
_addon = xbmcaddon.Addon(id='plugin.video.emby')
|
_addon = xbmcaddon.Addon(id='plugin.video.emby')
|
||||||
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||||
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||||
sys.path.append(base_resource_path)
|
sys.path.append(base_resource)
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
import KodiMonitor
|
import userclient
|
||||||
import Utils as utils
|
import clientinfo
|
||||||
from ClientInformation import ClientInformation
|
import initialsetup
|
||||||
from ConnectionManager import ConnectionManager
|
import kodimonitor
|
||||||
from UserClient import UserClient
|
import librarysync
|
||||||
from Player import Player
|
import player
|
||||||
from WebSocketClient import WebSocketThread
|
import utils
|
||||||
from LibrarySync import LibrarySync
|
import videonodes
|
||||||
|
import websocket_client as wsc
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
class Service():
|
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
|
welcome_msg = True
|
||||||
server_online = 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))
|
def __init__(self):
|
||||||
utils.window('kodiProfile_emby', value=xbmc.translatePath("special://profile"))
|
|
||||||
|
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
|
# Initial logging
|
||||||
self.logMsg("Starting Monitor", 0)
|
self.logMsg("======== START %s ========" % self.addonName, 0)
|
||||||
self.logMsg("======== START %s ========" % addonName, 0)
|
self.logMsg("Platform: %s" % (self.clientInfo.getPlatform()), 0)
|
||||||
self.logMsg("Platform: %s" % (clientInfo.getPlatform()), 0)
|
|
||||||
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 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("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0)
|
||||||
self.logMsg("Log Level: %s" % logLevel, 0)
|
self.logMsg("Log Level: %s" % logLevel, 0)
|
||||||
|
|
||||||
# Reset window props for profile switch
|
# Reset window props for profile switch
|
||||||
utils.window('Server_online', clear=True)
|
properties = [
|
||||||
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
|
"emby_online", "emby_serverStatus", "emby_onWake",
|
||||||
utils.window('minDBVersion', value="1.1.52")
|
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
||||||
|
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId"
|
||||||
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"
|
|
||||||
]
|
]
|
||||||
|
for prop in properties:
|
||||||
|
utils.window(prop, clear=True)
|
||||||
|
|
||||||
if embyProperty:
|
# Clear playlist properties
|
||||||
totalNodes = int(embyProperty)
|
xbmcgui.Window(10101).clearProperties()
|
||||||
for i in range(totalNodes):
|
# Clear video nodes properties
|
||||||
for prop in propNames:
|
videonodes.VideoNodes().clearProperties()
|
||||||
utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
|
||||||
|
# Set the minimum database version
|
||||||
|
utils.window('emby_minDBVersion', value="1.1.63")
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
className = self.__class__.__name__
|
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):
|
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")
|
kodiProfile = xbmc.translatePath("special://profile")
|
||||||
|
|
||||||
# Server auto-detect
|
# Server auto-detect
|
||||||
ConnectionManager().checkServer()
|
initialsetup.InitialSetup().setup()
|
||||||
|
|
||||||
# Initialize important threads
|
# Initialize important threads
|
||||||
user = UserClient()
|
user = userclient.UserClient()
|
||||||
player = Player()
|
ws = wsc.WebSocket_Client()
|
||||||
ws = WebSocketThread()
|
library = librarysync.LibrarySync()
|
||||||
library = LibrarySync()
|
kplayer = player.Player()
|
||||||
# Sync and progress report
|
# Sync and progress report
|
||||||
lastProgressUpdate = datetime.today()
|
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:
|
# Before proceeding, need to make sure:
|
||||||
# 1. Server is online
|
# 1. Server is online
|
||||||
# 2. User is set
|
# 2. User is set
|
||||||
# 3. User has access to the server
|
# 3. User has access to the server
|
||||||
|
|
||||||
if utils.window("kodiProfile_emby") != kodiProfile:
|
if utils.window('emby_online') == "true":
|
||||||
# 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":
|
|
||||||
|
|
||||||
# Emby server is online
|
# Emby server is online
|
||||||
# Verify if user is set and has access to the server
|
# Verify if user is set and has access to the server
|
||||||
|
@ -140,28 +130,28 @@ class Service():
|
||||||
if xbmc.Player().isPlaying():
|
if xbmc.Player().isPlaying():
|
||||||
try:
|
try:
|
||||||
# Update and report progress
|
# Update and report progress
|
||||||
playTime = xbmc.Player().getTime()
|
playtime = xbmc.Player().getTime()
|
||||||
totalTime = xbmc.Player().getTotalTime()
|
totalTime = xbmc.Player().getTotalTime()
|
||||||
currentFile = player.currentFile
|
currentFile = kplayer.currentFile
|
||||||
|
|
||||||
# Update positionticks
|
# Update positionticks
|
||||||
if player.played_information.get(currentFile) is not None:
|
if kplayer.played_info.get(currentFile) is not None:
|
||||||
player.played_information[currentFile]['currentPosition'] = playTime
|
kplayer.played_info[currentFile]['currentPosition'] = playtime
|
||||||
|
|
||||||
td = datetime.today() - lastProgressUpdate
|
td = datetime.today() - lastProgressUpdate
|
||||||
secDiff = td.seconds
|
secDiff = td.seconds
|
||||||
|
|
||||||
# Report progress to Emby server
|
# Report progress to Emby server
|
||||||
if (secDiff > 3):
|
if (secDiff > 3):
|
||||||
player.reportPlayback()
|
kplayer.reportPlayback()
|
||||||
lastProgressUpdate = datetime.today()
|
lastProgressUpdate = datetime.today()
|
||||||
|
|
||||||
elif utils.window('commandUpdate') == "true":
|
elif utils.window('emby_command') == "true":
|
||||||
# Received a remote control command that
|
# Received a remote control command that
|
||||||
# requires updating immediately
|
# requires updating immediately
|
||||||
utils.window('commandUpdate', clear=True)
|
utils.window('emby_command', clear=True)
|
||||||
player.reportPlayback()
|
kplayer.reportPlayback()
|
||||||
lastProgressUpdate = da4tetime.today()
|
lastProgressUpdate = datetime.today()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logMsg("Exception in Playback Monitor Service: %s" % e, 1)
|
self.logMsg("Exception in Playback Monitor Service: %s" % e, 1)
|
||||||
|
@ -169,8 +159,7 @@ class Service():
|
||||||
else:
|
else:
|
||||||
# Start up events
|
# Start up events
|
||||||
self.warn_auth = True
|
self.warn_auth = True
|
||||||
if utils.settings('supressConnectMsg') == "false":
|
if utils.settings('connectMsg') == "true" and self.welcome_msg:
|
||||||
if self.welcome_msg:
|
|
||||||
# Reset authentication warnings
|
# Reset authentication warnings
|
||||||
self.welcome_msg = False
|
self.welcome_msg = False
|
||||||
# Get additional users
|
# Get additional users
|
||||||
|
@ -179,17 +168,25 @@ class Service():
|
||||||
add = ", %s" % ", ".join(additionalUsers)
|
add = ", %s" % ", ".join(additionalUsers)
|
||||||
else:
|
else:
|
||||||
add = ""
|
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)
|
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
|
# Start the Websocket Client
|
||||||
if (self.newWebSocketThread is None):
|
if not self.websocket_running:
|
||||||
self.newWebSocketThread = "Started"
|
self.websocket_running = True
|
||||||
ws.start()
|
ws.start()
|
||||||
# Start the Library Sync Thread
|
# Start the syncing thread
|
||||||
if (self.newLibraryThread is None):
|
if not self.library_running:
|
||||||
self.newLibraryThread = "Started"
|
self.library_running = True
|
||||||
library.start()
|
library.start()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if (user.currUser is None) and self.warn_auth:
|
if (user.currUser is None) and self.warn_auth:
|
||||||
|
@ -204,20 +201,19 @@ class Service():
|
||||||
# Verify access with an API call
|
# Verify access with an API call
|
||||||
user.hasAccess()
|
user.hasAccess()
|
||||||
|
|
||||||
if utils.window('Server_online') != "true":
|
if utils.window('emby_online') != "true":
|
||||||
# Server went offline
|
# Server went offline
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.KodiMonitor.waitForAbort(5):
|
if monitor.waitForAbort(5):
|
||||||
# Abort was requested while waiting. We should exit
|
# Abort was requested while waiting. We should exit
|
||||||
break
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Wait until Emby server is online
|
# Wait until Emby server is online
|
||||||
# or Kodi is shut down.
|
# 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
|
# No server info set in add-on settings
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -226,8 +222,14 @@ class Service():
|
||||||
# Alert the user and suppress future warning
|
# Alert the user and suppress future warning
|
||||||
if self.server_online:
|
if self.server_online:
|
||||||
self.logMsg("Server is offline.", 1)
|
self.logMsg("Server is offline.", 1)
|
||||||
utils.window('Server_online', value="false")
|
utils.window('emby_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)
|
|
||||||
|
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
|
self.server_online = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -235,54 +237,55 @@ class Service():
|
||||||
if not self.server_online:
|
if not self.server_online:
|
||||||
# Server was offline when Kodi started.
|
# Server was offline when Kodi started.
|
||||||
# Wait for server to be fully established.
|
# Wait for server to be fully established.
|
||||||
if self.KodiMonitor.waitForAbort(5):
|
if monitor.waitForAbort(5):
|
||||||
# Abort was requested while waiting.
|
# Abort was requested while waiting.
|
||||||
break
|
break
|
||||||
# Alert the user that server is online.
|
# 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.server_online = True
|
||||||
self.logMsg("Server is online and ready.", 1)
|
self.logMsg("Server is online and ready.", 1)
|
||||||
utils.window('Server_online', value="true")
|
utils.window('emby_online', value="true")
|
||||||
|
|
||||||
# Start the User client
|
# Start the userclient thread
|
||||||
if self.newUserClient is None:
|
if not self.userclient_running:
|
||||||
self.newUserClient = "Started"
|
self.userclient_running = True
|
||||||
user.start()
|
user.start()
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.KodiMonitor.waitForAbort(1):
|
if monitor.waitForAbort(1):
|
||||||
# Abort was requested while waiting.
|
# Abort was requested while waiting.
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.KodiMonitor.waitForAbort(1):
|
if monitor.waitForAbort(1):
|
||||||
# Abort was requested while waiting. We should exit
|
# Abort was requested while waiting. We should exit
|
||||||
break
|
break
|
||||||
|
|
||||||
##### Emby thread is terminating. #####
|
##### Emby thread is terminating. #####
|
||||||
|
|
||||||
# If music is enabled and direct stream for music is enabled
|
if self.library_running:
|
||||||
# We use Kodi pathsubstitution to allow for music to play outside network
|
library.stopThread()
|
||||||
# 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:
|
if self.websocket_running:
|
||||||
# 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()
|
ws.stopClient()
|
||||||
|
|
||||||
if (self.newUserClient is not None):
|
if self.userclient_running:
|
||||||
user.stopClient()
|
user.stopClient()
|
||||||
|
|
||||||
self.logMsg("======== STOP %s ========" % self.addonName, 0)
|
self.logMsg("======== STOP %s ========" % self.addonName, 0)
|
||||||
|
|
||||||
# Start the service
|
# Delay option
|
||||||
Service().ServiceEntryPoint()
|
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()
|
Loading…
Reference in a new issue