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"?>
|
||||
<addon id="plugin.video.emby"
|
||||
name="Emby"
|
||||
version="1.1.62"
|
||||
version="1.1.63"
|
||||
provider-name="Emby.media">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.3.0" />
|
||||
<import addon="plugin.video.emby.movies" version="0.01" />
|
||||
<import addon="plugin.video.emby.tvshows" version="0.01" />
|
||||
<import addon="plugin.video.emby.musicvideos" version="0.01" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource"
|
||||
library="default.py">
|
||||
|
|
132
default.py
132
default.py
|
@ -1,18 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import xbmcaddon, xbmc
|
||||
import os, sys
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
|
||||
#################################################################################################
|
||||
|
||||
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
sys.path.append(base_resource_path)
|
||||
import Entrypoint as entrypoint
|
||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
sys.path.append(base_resource)
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import entrypoint
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
enableProfiling = False
|
||||
|
||||
class Main:
|
||||
|
||||
|
||||
# MAIN ENTRY POINT
|
||||
def __init__(self):
|
||||
|
||||
|
@ -24,84 +39,67 @@ class Main:
|
|||
|
||||
try:
|
||||
mode = params['mode'][0]
|
||||
id = params.get('id', None)
|
||||
if id:
|
||||
id = id[0]
|
||||
itemid = params.get('id')
|
||||
if itemid:
|
||||
itemid = itemid[0]
|
||||
except:
|
||||
params = {}
|
||||
mode = ""
|
||||
|
||||
##### PLAY ITEM VIA plugin://plugin.video.emby/ #####
|
||||
if "play" in mode or "playnow" in mode:
|
||||
entrypoint.doPlayback(id)
|
||||
|
||||
#### DO RESET AUTH #####
|
||||
elif "resetauth" in mode:
|
||||
entrypoint.resetAuth()
|
||||
modes = {
|
||||
|
||||
##### DO DATABASE RESET #####
|
||||
elif "reset" in mode:
|
||||
import Utils as utils
|
||||
utils.reset()
|
||||
'reset': utils.reset,
|
||||
'resetauth': entrypoint.resetAuth,
|
||||
'play': entrypoint.doPlayback,
|
||||
'passwords': utils.passwordsXML,
|
||||
'adduser': entrypoint.addUser,
|
||||
'thememedia': entrypoint.getThemeMedia,
|
||||
'channels': entrypoint.BrowseChannels,
|
||||
'channelsfolder': entrypoint.BrowseChannels,
|
||||
'nextup': entrypoint.getNextUpEpisodes,
|
||||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||
'recentepisodes': entrypoint.getRecentEpisodes
|
||||
}
|
||||
|
||||
##### ADD/REMOVE USER FROM SESSION #####
|
||||
elif "adduser" in mode:
|
||||
entrypoint.addUser()
|
||||
if modes.get(mode):
|
||||
# Simple functions
|
||||
if mode == "play":
|
||||
dbid = params.get('dbid')
|
||||
modes[mode](itemid, dbid)
|
||||
|
||||
##### SYNC THEME MEDIA #####
|
||||
elif "thememedia" in mode:
|
||||
entrypoint.getThemeMedia()
|
||||
elif mode in ("nextup", "inprogressepisodes", "recentepisodes"):
|
||||
limit = int(params['limit'][0])
|
||||
modes[mode](itemid, limit)
|
||||
|
||||
##### LAUNCH EMBY USER PREFS #####
|
||||
elif "userprefs" in mode:
|
||||
entrypoint.userPreferences()
|
||||
elif mode == "channels":
|
||||
modes[mode](itemid)
|
||||
|
||||
##### OPEN ADDON SETTINGS #####
|
||||
elif "settings" in mode:
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
|
||||
|
||||
##### MANUALLY SYNC LIBRARY #####
|
||||
elif "manualsync" in mode:
|
||||
from LibrarySync import LibrarySync
|
||||
LibrarySync().FullLibrarySync(True)
|
||||
|
||||
##### CACHE ARTWORK #####
|
||||
elif "texturecache" in mode:
|
||||
from TextureCache import TextureCache
|
||||
TextureCache().FullTextureCacheSync()
|
||||
|
||||
##### BROWSE EMBY CHANNELS FOLDER #####
|
||||
elif "channelsfolder" in mode:
|
||||
elif mode == "channelsfolder":
|
||||
folderid = params['folderid'][0]
|
||||
entrypoint.BrowseChannels(id,folderid)
|
||||
modes[mode](itemid, folderid)
|
||||
|
||||
##### BROWSE EMBY CHANNELS ROOT #####
|
||||
elif "channels" in mode:
|
||||
entrypoint.BrowseChannels(id)
|
||||
|
||||
##### GET NEXTUP EPISODES FOR TAGNAME #####
|
||||
elif "nextup" in mode:
|
||||
limit = int(params['limit'][0])
|
||||
entrypoint.getNextUpEpisodes(id, limit)
|
||||
|
||||
##### GET INPROGRESS EPISODES FOR TAGNAME #####
|
||||
elif "inprogressepisodes" in mode:
|
||||
limit = int(params['limit'][0])
|
||||
entrypoint.getInProgressEpisodes(id, limit)
|
||||
|
||||
##### GET RECENT EPISODES FOR TAGNAME #####
|
||||
elif "recentepisodes" in mode:
|
||||
limit = int(params['limit'][0])
|
||||
entrypoint.getRecentEpisodes(id, limit)
|
||||
|
||||
##### GET EXTRAFANART FOR LISTITEM #####
|
||||
else:
|
||||
modes[mode]()
|
||||
else:
|
||||
# Other functions
|
||||
if mode == "settings":
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
|
||||
elif mode in ("manualsync", "repair"):
|
||||
import librarysync
|
||||
if mode == "manualsync":
|
||||
librarysync.LibrarySync().fullSync(manualrun=True)
|
||||
else:
|
||||
librarysync.LibrarySync().fullSync(repair=True)
|
||||
elif mode == "texturecache":
|
||||
import artwork
|
||||
artwork.Artwork().FullTextureCacheSync()
|
||||
elif "extrafanart" in sys.argv[0]:
|
||||
entrypoint.getExtraFanArt()
|
||||
|
||||
##### SHOW ADDON NODES LISTING #####
|
||||
if not mode:
|
||||
else:
|
||||
entrypoint.doMainListing()
|
||||
|
||||
|
||||
if ( __name__ == "__main__" ):
|
||||
xbmc.log('plugin.video.emby started')
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@
|
|||
<string id="30246">Enable Netflix style next up notification</string>
|
||||
<string id="30247"> - The number of seconds before the end to show the notification</string>
|
||||
<string id="30248">Show Emby Info dialog on play/select action</string>
|
||||
<string id="30249">Suppress server connection message on start-up</string>
|
||||
<string id="30249">Enable server connection message on start-up</string>
|
||||
<string id="30250">Use local paths instead of addon redirect for playback</string>
|
||||
|
||||
|
||||
|
|
|
@ -1,40 +1,107 @@
|
|||
# -- coding: utf-8 --
|
||||
# API.py
|
||||
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime
|
||||
from random import randrange
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
##################################################################################################
|
||||
|
||||
import clientinfo
|
||||
import utils
|
||||
|
||||
##################################################################################################
|
||||
|
||||
import Utils as utils
|
||||
|
||||
class API():
|
||||
|
||||
def getPeople(self, item):
|
||||
def __init__(self, item):
|
||||
|
||||
self.item = item
|
||||
self.clientinfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientinfo.getAddonName()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
||||
def getUserData(self):
|
||||
# Default
|
||||
favorite = False
|
||||
playcount = None
|
||||
played = False
|
||||
lastPlayedDate = None
|
||||
resume = 0
|
||||
rating = 0
|
||||
|
||||
try:
|
||||
userdata = self.item['UserData']
|
||||
|
||||
except KeyError: # No userdata found.
|
||||
pass
|
||||
|
||||
else:
|
||||
favorite = userdata['IsFavorite']
|
||||
likes = userdata.get('Likes')
|
||||
# Rating for album and songs
|
||||
if favorite:
|
||||
rating = 5
|
||||
elif likes:
|
||||
rating = 3
|
||||
elif likes == False:
|
||||
rating = 1
|
||||
else:
|
||||
rating = 0
|
||||
|
||||
lastPlayedDate = userdata.get('LastPlayedDate')
|
||||
if lastPlayedDate:
|
||||
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
|
||||
|
||||
if userdata['Played']:
|
||||
# Playcount is tied to the watch status
|
||||
played = True
|
||||
playcount = userdata['PlayCount']
|
||||
if playcount == 0:
|
||||
playcount = 1
|
||||
|
||||
if lastPlayedDate is None:
|
||||
lastPlayedDate = self.getDateCreated()
|
||||
|
||||
playbackPosition = userdata.get('PlaybackPositionTicks')
|
||||
if playbackPosition:
|
||||
resume = playbackPosition / 10000000.0
|
||||
|
||||
return {
|
||||
|
||||
'Favorite': favorite,
|
||||
'PlayCount': playcount,
|
||||
'Played': played,
|
||||
'LastPlayedDate': lastPlayedDate,
|
||||
'Resume': resume,
|
||||
'Rating': rating
|
||||
}
|
||||
|
||||
def getPeople(self):
|
||||
# Process People
|
||||
director = []
|
||||
writer = []
|
||||
cast = []
|
||||
|
||||
try:
|
||||
people = item['People']
|
||||
except: pass
|
||||
else:
|
||||
people = self.item['People']
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
else:
|
||||
for person in people:
|
||||
|
||||
type = person['Type']
|
||||
Name = person['Name']
|
||||
name = person['Name']
|
||||
|
||||
if "Director" in type:
|
||||
director.append(Name)
|
||||
elif "Writing" in type:
|
||||
writer.append(Name)
|
||||
elif "Writer" in type:
|
||||
writer.append(Name)
|
||||
director.append(name)
|
||||
elif "Actor" in type:
|
||||
cast.append(Name)
|
||||
cast.append(name)
|
||||
elif type in ("Writing", "Writer"):
|
||||
writer.append(name)
|
||||
|
||||
return {
|
||||
|
||||
|
@ -43,52 +110,136 @@ class API():
|
|||
'Cast': cast
|
||||
}
|
||||
|
||||
def getTimeInfo(self, item):
|
||||
# Runtime and Resume point
|
||||
tempRuntime = 0
|
||||
runtime = 0
|
||||
resume = 0
|
||||
def getMediaStreams(self):
|
||||
item = self.item
|
||||
videotracks = []
|
||||
audiotracks = []
|
||||
subtitlelanguages = []
|
||||
|
||||
try: # Get resume point
|
||||
userdata = item['UserData']
|
||||
playbackPosition = userdata['PlaybackPositionTicks']
|
||||
resume = playbackPosition / 10000000.0
|
||||
except: pass
|
||||
try:
|
||||
media_streams = item['MediaSources'][0]['MediaStreams']
|
||||
|
||||
try: # Get total runtime
|
||||
tempRuntime = item['RunTimeTicks']
|
||||
except KeyError:
|
||||
media_streams = item['MediaStreams']
|
||||
|
||||
for media_stream in media_streams:
|
||||
# Sort through Video, Audio, Subtitle
|
||||
stream_type = media_stream['Type']
|
||||
codec = media_stream.get('Codec', "").lower()
|
||||
profile = media_stream.get('Profile', "").lower()
|
||||
|
||||
if stream_type == "Video":
|
||||
# Height, Width, Codec, AspectRatio, AspectFloat, 3D
|
||||
track = {
|
||||
|
||||
'videocodec': codec,
|
||||
'height': media_stream.get('Height'),
|
||||
'width': media_stream.get('Width'),
|
||||
'video3DFormat': item.get('Video3DFormat'),
|
||||
'aspectratio': 1.85
|
||||
}
|
||||
|
||||
try:
|
||||
container = item['MediaSources'][0]['Container'].lower()
|
||||
except:
|
||||
try: tempRuntime = item['CumulativeRunTimeTicks']
|
||||
except: pass
|
||||
container = ""
|
||||
|
||||
finally:
|
||||
runtime = tempRuntime / 10000000.0
|
||||
# Sort codec vs container/profile
|
||||
if "msmpeg4" in codec:
|
||||
track['videocodec'] = "divx"
|
||||
elif "mpeg4" in codec:
|
||||
if "simple profile" in profile or not profile:
|
||||
track['videocodec'] = "xvid"
|
||||
elif "h264" in codec:
|
||||
if container in ("mp4", "mov", "m4v"):
|
||||
track['videocodec'] = "avc1"
|
||||
|
||||
# Aspect ratio
|
||||
if item.get('AspectRatio'):
|
||||
# Metadata AR
|
||||
aspectratio = item['AspectRatio']
|
||||
else: # File AR
|
||||
aspectratio = media_stream.get('AspectRatio', "0")
|
||||
|
||||
try:
|
||||
aspectwidth, aspectheight = aspectratio.split(':')
|
||||
track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
|
||||
|
||||
except ValueError:
|
||||
width = track['width']
|
||||
height = track['height']
|
||||
|
||||
if width and height:
|
||||
track['aspectratio'] = round(float(width / height), 6)
|
||||
|
||||
videotracks.append(track)
|
||||
|
||||
elif stream_type == "Audio":
|
||||
# Codec, Channels, language
|
||||
track = {
|
||||
|
||||
'audiocodec': codec,
|
||||
'channels': media_stream.get('Channels'),
|
||||
'audiolanguage': media_stream.get('Language')
|
||||
}
|
||||
|
||||
if "dca" in codec and "dts-hd ma" in profile:
|
||||
track['audiocodec'] = "dtshd_ma"
|
||||
|
||||
audiotracks.append(track)
|
||||
|
||||
elif stream_type == "Subtitle":
|
||||
# Language
|
||||
subtitlelanguages.append(media_stream.get('Language', "Unknown"))
|
||||
|
||||
return {
|
||||
|
||||
'ResumeTime': resume,
|
||||
'TotalTime': runtime
|
||||
'video': videotracks,
|
||||
'audio': audiotracks,
|
||||
'subtitle': subtitlelanguages
|
||||
}
|
||||
|
||||
def getStudios(self, item):
|
||||
# Process Studio
|
||||
def getRuntime(self):
|
||||
item = self.item
|
||||
try:
|
||||
runtime = item['RunTimeTicks'] / 10000000.0
|
||||
|
||||
except KeyError:
|
||||
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
|
||||
|
||||
return runtime
|
||||
|
||||
def adjustResume(self, resume_seconds):
|
||||
|
||||
resume = 0
|
||||
if resume_seconds:
|
||||
resume = round(float(resume_seconds), 6)
|
||||
jumpback = int(utils.settings('resumeJumpBack'))
|
||||
if resume > jumpback:
|
||||
# To avoid negative bookmark
|
||||
resume = resume - jumpback
|
||||
|
||||
return resume
|
||||
|
||||
def getStudios(self):
|
||||
# Process Studios
|
||||
item = self.item
|
||||
studios = []
|
||||
|
||||
try:
|
||||
studio = item['SeriesStudio']
|
||||
studios.append(self.getStudio(studio))
|
||||
except:
|
||||
try:
|
||||
studioArray = item['Studios']
|
||||
for studio in studioArray:
|
||||
studios.append(self.getStudio(studio['Name']))
|
||||
except: pass
|
||||
studios.append(self.verifyStudio(studio))
|
||||
|
||||
except KeyError:
|
||||
studioList = item['Studios']
|
||||
for studio in studioList:
|
||||
|
||||
name = studio['Name']
|
||||
studios.append(self.verifyStudio(name))
|
||||
|
||||
return studios
|
||||
|
||||
def getStudio(self, studioName):
|
||||
def verifyStudio(self, studioName):
|
||||
# Convert studio for Kodi to properly detect them
|
||||
studios = {
|
||||
|
||||
|
@ -101,503 +252,127 @@ class API():
|
|||
|
||||
return studios.get(studioName.lower(), studioName)
|
||||
|
||||
def getGenre(self,item):
|
||||
genre = ""
|
||||
genres = item.get("Genres")
|
||||
if genres != None and genres != []:
|
||||
for genre_string in genres:
|
||||
if genre == "": #Just take the first genre
|
||||
genre = genre_string
|
||||
else:
|
||||
genre = genre + " / " + genre_string
|
||||
elif item.get("SeriesGenres") != None and item.get("SeriesGenres") != '':
|
||||
genres = item.get("SeriesGenres")
|
||||
if genres != None and genres != []:
|
||||
for genre_string in genres:
|
||||
if genre == "": #Just take the first genre
|
||||
genre = genre_string
|
||||
else:
|
||||
genre = genre + " / " + genre_string
|
||||
return genre
|
||||
def getChecksum(self):
|
||||
# Use the etags checksum and userdata
|
||||
item = self.item
|
||||
userdata = item['UserData']
|
||||
|
||||
def getMediaStreams(self, item, mediaSources = False):
|
||||
checksum = "%s%s%s%s%s%s" % (
|
||||
|
||||
videotracks = [] # Height, Width, Codec, AspectRatio, AspectFloat, 3D
|
||||
audiotracks = [] # Codec, Channels, language
|
||||
subtitlelanguages = [] # Language
|
||||
|
||||
if mediaSources:
|
||||
try:
|
||||
MediaStreams = item['MediaSources'][0]['MediaStreams']
|
||||
except:
|
||||
MediaStreams = None
|
||||
else:
|
||||
MediaStreams = item.get('MediaStreams')
|
||||
|
||||
if MediaStreams:
|
||||
# Sort through the Video, Audio, Subtitle tracks
|
||||
for mediaStream in MediaStreams:
|
||||
|
||||
type = mediaStream.get('Type', "")
|
||||
profile = mediaStream.get('Profile', "").lower()
|
||||
codec = mediaStream.get('Codec', "").lower()
|
||||
|
||||
if "Video" in type:
|
||||
videotrack = {}
|
||||
videotrack['videocodec'] = codec
|
||||
container = item['MediaSources'][0].get('Container', "").lower()
|
||||
if "msmpeg4" in videotrack['videocodec']:
|
||||
videotrack['videocodec'] = "divx"
|
||||
elif "mpeg4" in videotrack['videocodec']:
|
||||
if "simple profile" in profile or profile == "":
|
||||
videotrack['videocodec'] = "xvid"
|
||||
elif "h264" in videotrack['videocodec']:
|
||||
if container in ("mp4", "mov", "m4v"):
|
||||
videotrack['videocodec'] = "avc1"
|
||||
videotrack['height'] = mediaStream.get('Height')
|
||||
videotrack['width'] = mediaStream.get('Width')
|
||||
videotrack['Video3DFormat'] = item.get('Video3DFormat')
|
||||
if item.get('AspectRatio'):
|
||||
# Metadata aspect ratio
|
||||
videotrack['aspectratio'] = item['AspectRatio']
|
||||
else: # File aspect ratio
|
||||
videotrack['aspectratio'] = mediaStream.get('AspectRatio', "0")
|
||||
if len(videotrack['aspectratio']) >= 3:
|
||||
try:
|
||||
aspectwidth, aspectheight = videotrack['aspectratio'].split(':')
|
||||
videotrack['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
|
||||
except:
|
||||
videotrack['aspectratio'] = 1.85
|
||||
else:
|
||||
try:
|
||||
videotrack['aspectratio'] = round(float(videotrack['width'] / videotrack['height']), 6)
|
||||
except: # In the event the aspect ratio is missing and the width and height are missing as well.
|
||||
videotrack['aspectratio'] = 1.85
|
||||
videotracks.append(videotrack)
|
||||
|
||||
elif "Audio" in type:
|
||||
audiotrack = {}
|
||||
audiotrack['audiocodec'] = codec
|
||||
if "dca" in audiotrack['audiocodec'] and "dts-hd ma" in profile:
|
||||
audiotrack['audiocodec'] = "dtshd_ma"
|
||||
audiotrack['channels'] = mediaStream.get('Channels')
|
||||
audiotrack['audiolanguage'] = mediaStream.get('Language')
|
||||
audiotracks.append(audiotrack)
|
||||
|
||||
elif "Subtitle" in type:
|
||||
try:
|
||||
subtitlelanguages.append(mediaStream['Language'])
|
||||
except:
|
||||
subtitlelanguages.append("Unknown")
|
||||
|
||||
return {
|
||||
|
||||
'videocodec' : videotracks,
|
||||
'audiocodec' : audiotracks,
|
||||
'subtitlelanguage' : subtitlelanguages
|
||||
}
|
||||
|
||||
|
||||
def getChecksum(self, item):
|
||||
# use the etags checksum for this if available
|
||||
# AND the userdata
|
||||
checksum = ""
|
||||
|
||||
if item.get("Etag") != None:
|
||||
checksum = item.get("Etag")
|
||||
userData = item.get("UserData")
|
||||
if(userData != None):
|
||||
checksum += str(userData.get("Played"))
|
||||
checksum += str(userData.get("IsFavorite"))
|
||||
if userData.get('UnplayedItemCount') != None:
|
||||
checksum += str(userData.get("UnplayedItemCount"))
|
||||
if userData.get('LastPlayedDate') != None:
|
||||
checksum += str(userData.get("LastPlayedDate"))
|
||||
if userData.get('PlaybackPositionTicks') != None:
|
||||
checksum += str(userData.get("PlaybackPositionTicks"))
|
||||
item['Etag'],
|
||||
userdata['Played'],
|
||||
userdata['IsFavorite'],
|
||||
userdata['PlaybackPositionTicks'],
|
||||
userdata.get('UnplayedItemCount', ""),
|
||||
userdata.get('LastPlayedDate', "")
|
||||
)
|
||||
|
||||
return checksum
|
||||
|
||||
def getUserData(self, item):
|
||||
# Default
|
||||
favorite = False
|
||||
playcount = None
|
||||
lastPlayedDate = None
|
||||
userKey = ""
|
||||
def getGenres(self):
|
||||
item = self.item
|
||||
all_genres = ""
|
||||
genres = item.get('Genres', item.get('SeriesGenres'))
|
||||
|
||||
if genres:
|
||||
all_genres = " / ".join(genres)
|
||||
|
||||
return all_genres
|
||||
|
||||
def getDateCreated(self):
|
||||
|
||||
try:
|
||||
userdata = item['UserData']
|
||||
|
||||
except: # No userdata found.
|
||||
pass
|
||||
|
||||
else:
|
||||
favorite = userdata['IsFavorite']
|
||||
userKey = userdata.get('Key', "")
|
||||
|
||||
watched = userdata['Played']
|
||||
if watched:
|
||||
# Playcount is tied to the watch status
|
||||
playcount = userdata['PlayCount']
|
||||
if playcount == 0:
|
||||
playcount = 1
|
||||
else:
|
||||
playcount = None
|
||||
|
||||
lastPlayedDate = userdata.get('LastPlayedDate', None)
|
||||
if lastPlayedDate:
|
||||
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
|
||||
|
||||
return {
|
||||
|
||||
'Favorite': favorite,
|
||||
'PlayCount': playcount,
|
||||
'LastPlayedDate': lastPlayedDate,
|
||||
'Key': userKey
|
||||
}
|
||||
|
||||
|
||||
def getRecursiveItemCount(self, item):
|
||||
if item.get("RecursiveItemCount") != None:
|
||||
return str(item.get("RecursiveItemCount"))
|
||||
else:
|
||||
return "0"
|
||||
|
||||
def getOverview(self, item):
|
||||
|
||||
overview = ""
|
||||
|
||||
try:
|
||||
overview = item['Overview']
|
||||
overview = overview.replace("\"", "\'")
|
||||
overview = overview.replace("\n", " ")
|
||||
overview = overview.replace("\r", " ")
|
||||
except: pass
|
||||
|
||||
return overview
|
||||
|
||||
def getTVInfo(self, item, userData):
|
||||
TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount")
|
||||
TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount")
|
||||
WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-int(userData.get("UnplayedItemCount"))
|
||||
UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else int(userData.get("UnplayedItemCount"))
|
||||
NumEpisodes = TotalEpisodes
|
||||
tempEpisode = ""
|
||||
if (item.get("IndexNumber") != None):
|
||||
episodeNum = item.get("IndexNumber")
|
||||
if episodeNum < 10:
|
||||
tempEpisode = "0" + str(episodeNum)
|
||||
else:
|
||||
tempEpisode = str(episodeNum)
|
||||
|
||||
tempSeason = ""
|
||||
if (str(item.get("ParentIndexNumber")) != None):
|
||||
tempSeason = str(item.get("ParentIndexNumber"))
|
||||
if item.get("ParentIndexNumber") < 10:
|
||||
tempSeason = "0" + tempSeason
|
||||
if item.get("SeriesName") != None:
|
||||
temp=item.get("SeriesName")
|
||||
SeriesName=temp.encode('utf-8')
|
||||
else:
|
||||
SeriesName=''
|
||||
return {'TotalSeasons' : str(TotalSeasons),
|
||||
'TotalEpisodes' : str(TotalEpisodes),
|
||||
'WatchedEpisodes' : str(WatchedEpisodes),
|
||||
'UnWatchedEpisodes': str(UnWatchedEpisodes),
|
||||
'NumEpisodes' : str(NumEpisodes),
|
||||
'Season' : tempSeason,
|
||||
'Episode' : tempEpisode,
|
||||
'SeriesName' : SeriesName
|
||||
}
|
||||
|
||||
def getDateCreated(self, item):
|
||||
|
||||
try:
|
||||
dateadded = item['DateCreated']
|
||||
dateadded = self.item['DateCreated']
|
||||
dateadded = dateadded.split('.')[0].replace('T', " ")
|
||||
except:
|
||||
except KeyError:
|
||||
dateadded = None
|
||||
|
||||
return dateadded
|
||||
|
||||
def getPremiereDate(self, item):
|
||||
def getPremiereDate(self):
|
||||
|
||||
try:
|
||||
premiere = item['PremiereDate']
|
||||
premiere = self.item['PremiereDate']
|
||||
premiere = premiere.split('.')[0].replace('T', " ")
|
||||
except:
|
||||
except KeyError:
|
||||
premiere = None
|
||||
|
||||
return premiere
|
||||
|
||||
def getTagline(self, item):
|
||||
def getOverview(self):
|
||||
|
||||
try:
|
||||
tagline = item['Taglines'][0]
|
||||
except:
|
||||
overview = self.item['Overview']
|
||||
overview = overview.replace("\"", "\'")
|
||||
overview = overview.replace("\n", " ")
|
||||
overview = overview.replace("\r", " ")
|
||||
except KeyError:
|
||||
overview = ""
|
||||
|
||||
return overview
|
||||
|
||||
def getTagline(self):
|
||||
|
||||
try:
|
||||
tagline = self.item['Taglines'][0]
|
||||
except IndexError:
|
||||
tagline = None
|
||||
|
||||
return tagline
|
||||
|
||||
def getProvider(self, item, providername):
|
||||
# Provider Name: imdb or tvdb
|
||||
def getProvider(self, providername):
|
||||
|
||||
try:
|
||||
if "imdb" in providername:
|
||||
provider = item['ProviderIds']['Imdb']
|
||||
elif "tvdb" in providername:
|
||||
provider = item['ProviderIds']['Tvdb']
|
||||
elif "musicBrainzArtist" in providername:
|
||||
provider = item['ProviderIds']['MusicBrainzArtist']
|
||||
elif "musicBrainzAlbum" in providername:
|
||||
provider = item['ProviderIds']['MusicBrainzAlbum']
|
||||
elif "musicBrainzTrackId" in providername:
|
||||
provider = item['ProviderIds']['MusicBrainzTrackId']
|
||||
except:
|
||||
provider = self.item['ProviderIds'][providername]
|
||||
except KeyError:
|
||||
provider = None
|
||||
|
||||
return provider
|
||||
|
||||
def getCountry(self, item):
|
||||
def getMpaa(self):
|
||||
# Convert more complex cases
|
||||
mpaa = self.item.get('OfficialRating', "")
|
||||
|
||||
if mpaa in ("NR", "UR"):
|
||||
# Kodi seems to not like NR, but will accept Not Rated
|
||||
mpaa = "Not Rated"
|
||||
|
||||
return mpaa
|
||||
|
||||
def getCountry(self):
|
||||
|
||||
try:
|
||||
country = item['ProductionLocations'][0]
|
||||
except:
|
||||
country = self.item['ProductionLocations'][0]
|
||||
except IndexError:
|
||||
country = None
|
||||
|
||||
return country
|
||||
|
||||
def getMpaa(self, item):
|
||||
# Convert more complex cases
|
||||
mpaa = item.get('OfficialRating', "")
|
||||
if mpaa in ("NR", "UR"):
|
||||
# Kodi seems to not like NR, but will accept Rated Not Rated
|
||||
mpaa = "Rated Not Rated"
|
||||
def getFilePath(self):
|
||||
|
||||
return mpaa
|
||||
item = self.item
|
||||
try:
|
||||
filepath = item['Path']
|
||||
|
||||
def getAllArtwork(self, item, parentInfo = False):
|
||||
except KeyError:
|
||||
filepath = ""
|
||||
|
||||
"""
|
||||
Get all artwork, it will return an empty string
|
||||
for the artwork type not found.
|
||||
else:
|
||||
if "\\\\" in filepath:
|
||||
# append smb protocol
|
||||
filepath = filepath.replace("\\\\", "smb://")
|
||||
filepath = filepath.replace("\\", "/")
|
||||
|
||||
Artwork type: Primary, Art, Banner, Logo, Thumb,
|
||||
Disc, Backdrop
|
||||
"""
|
||||
if item.get('VideoType'):
|
||||
videotype = item['VideoType']
|
||||
# Specific format modification
|
||||
if 'Dvd'in videotype:
|
||||
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
|
||||
elif 'Bluray' in videotype:
|
||||
filepath = "%s/BDMV/index.bdmv" % filepath
|
||||
|
||||
username = utils.window('currUser')
|
||||
server = utils.window('server%s' % username)
|
||||
|
||||
id = item['Id']
|
||||
artworks = item['ImageTags']
|
||||
backdrops = item['BackdropImageTags']
|
||||
|
||||
maxHeight = 10000
|
||||
maxWidth = 10000
|
||||
customquery = ""
|
||||
|
||||
if utils.settings('compressArt') == "true":
|
||||
customquery = "&Quality=90"
|
||||
|
||||
if utils.settings('disableCoverArt') == "true":
|
||||
customquery += "&EnableImageEnhancers=false"
|
||||
|
||||
allartworks = {
|
||||
|
||||
'Primary': "",
|
||||
'Art': "",
|
||||
'Banner': "",
|
||||
'Logo': "",
|
||||
'Thumb': "",
|
||||
'Disc': "",
|
||||
'Backdrop': []
|
||||
}
|
||||
|
||||
# Process backdrops
|
||||
backdropIndex = 0
|
||||
for backdroptag in backdrops:
|
||||
artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, backdropIndex, maxWidth, maxHeight, backdroptag, customquery)
|
||||
allartworks['Backdrop'].append(artwork)
|
||||
backdropIndex += 1
|
||||
|
||||
# Process the rest of the artwork
|
||||
for art in artworks:
|
||||
tag = artworks[art]
|
||||
artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art, maxWidth, maxHeight, tag, customquery)
|
||||
allartworks[art] = artwork
|
||||
|
||||
# Process parent items if the main item is missing artwork
|
||||
if parentInfo:
|
||||
|
||||
# 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
|
||||
if "\\" in filepath:
|
||||
# Local path scenario, with special videotype
|
||||
filepath = filepath.replace("/", "\\")
|
||||
|
||||
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"?>
|
||||
<settings>
|
||||
<category label="30014"><!-- Emby -->
|
||||
<setting id="ipaddress" type="text" label="30000" default="" visible="true" enable="true" />
|
||||
<setting id="port" type="number" label="30030" default="8096" visible="true" enable="true" />
|
||||
<setting id="https" type="bool" label="30243" visible="true" enable="true" default="false" />
|
||||
<setting id="sslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
|
||||
<setting id="sslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
|
||||
<setting id="altip" type="bool" label="Use alternate address" default="false" visible="true" enable="true" />
|
||||
<setting id="secondipaddress" type="text" label="Secondary Server Address" default="" visible="eq(-1,true)" enable="true" />
|
||||
<setting id="secondport" type="number" label="30030" default="8096" visible="eq(-2,true)" enable="true" />
|
||||
<setting id="secondhttps" type="bool" label="30243" visible="true" enable="eq(-3,true)" default="false" />
|
||||
<setting id="secondsslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
|
||||
<setting id="secondsslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
|
||||
<setting id="pathsub" type="bool" visible="false" default="false" />
|
||||
<setting id="username" type="text" label="30024" default="" />
|
||||
<!-- Primary address -->
|
||||
<setting id="ipaddress" label="30000" type="text" default="" />
|
||||
<setting id="port" label="30030" type="number" default="8096" />
|
||||
<setting id="https" label="30243" type="bool" default="false" />
|
||||
<setting id="sslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
|
||||
<setting id="sslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
|
||||
<!-- Secondary address -->
|
||||
<setting id="altip" label="Use alternate address" type="bool" default="false" />
|
||||
<setting id="secondipaddress" label="Secondary Server Address" type="text" default="" visible="eq(-1,true)" />
|
||||
<setting id="secondport" label="30030" type="number" default="8096" visible="eq(-2,true)" />
|
||||
<setting id="secondhttps" label="30243" type="bool" default="false" visible="eq(-3,true)" />
|
||||
<setting id="secondsslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
|
||||
<setting id="secondsslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
|
||||
<!-- User settings -->
|
||||
<setting id="username" label="30024" type="text" default="" />
|
||||
<setting type="sep" />
|
||||
<setting id="deviceNameOpt" type="bool" label="Use altername Device Name" visible="true" enable="true" default="true" />
|
||||
<setting id="deviceName" type="text" label="30016" visible="eq(-1,true)" enable="true" default="Kodi" />
|
||||
<setting id="deviceNameOpt" label="Use altername Device Name" type="bool" default="false" />
|
||||
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
|
||||
<setting label="[COLOR yellow]Reset login attempts[/COLOR]" type="action" visible="eq(1,) + !eq(-15,)" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
|
||||
<setting id="accessToken" type="text" visible="false" default="" />
|
||||
<setting label="[COLOR yellow]Reset login attempts[/COLOR]" type="action" visible="eq(-1,) + !eq(-9,)" enable="true" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
|
||||
<setting id="pathsub" type="bool" visible="false" default="false" />
|
||||
</category>
|
||||
|
||||
<category label="Sync Options">
|
||||
<setting id="dbSyncIndication" type="bool" label="Show sync progress on screen" default="false" visible="true" enable="true" />
|
||||
<setting id="syncSpecialsOrder" type="bool" label="Sync specials ordering" default="false" visible="true" enable="true" />
|
||||
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" visible="true" enable="true" />
|
||||
<setting id="enableMusicSync" type="bool" label="Enable Music Library Sync" default="true" visible="true" enable="true" />
|
||||
<setting id="directstreammusic" type="bool" label="- Direct stream music library" default="false" visible="eq(-1,true)" enable="true" />
|
||||
<setting id="useDirectPaths" type="bool" label="30250" default="false" visible="true" enable="true" />
|
||||
<setting id="enableTextureCache" type="bool" label="Auto add images to the Kodi texture cache" default="true" visible="true" enable="true" />
|
||||
<setting id="useIncSync" type="bool" label="Use incremental sync at startup (Requires Server Plugin)" default="false" visible="true" enable="true" />
|
||||
<setting id="incSyncMaxItems" type="number" label=" - Max change set size" default="1000" visible="eq(-1,true)" enable="true" />
|
||||
<setting id="dbSyncIndicator" label="Show sync progress" type="bool" default="false" />
|
||||
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" />
|
||||
<setting id="enableMusic" type="bool" label="Enable Music Library Sync" default="true" />
|
||||
<setting id="streamMusic" type="bool" label="Direct stream music library" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||
<setting id="useDirectPaths" type="enum" label="Playback Mode" values="Addon(Default)|Native(Direct paths)" default="0" />
|
||||
<setting id="enableTextureCache" label="Auto add images to the Kodi texture cache" type="bool" default="true" />
|
||||
<setting id="serverSync" type="bool" label="Enable fast starting sync" default="true" />
|
||||
</category>
|
||||
|
||||
<category label="Playback"> <!-- Extra Sync options -->
|
||||
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
|
||||
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
|
||||
<setting label="Network credentials" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=passwords)" option="close" />
|
||||
<setting type="sep" />
|
||||
<setting id="disableCinema" type="bool" label="Disable Emby cinema mode" default="false" visible="true" enable="true" />
|
||||
<setting id="askCinema" type="bool" label="Ask to play trailers" default="false" visible="eq(-1,false)" enable="true" subsetting="true" />
|
||||
<setting id="offerDelete" type="bool" label="30114" visible="true" enable="true" default="false" />
|
||||
<setting id="offerDeleteTV" type="bool" label="30115" visible="eq(-1,true)" enable="true" default="false" subsetting="true" />
|
||||
<setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" subsetting="true" />
|
||||
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" visible="true" enable="true" />
|
||||
<setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" />
|
||||
<setting id="videoBitRate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" default="17" />
|
||||
<setting id="enableCinema" type="bool" label="Enable Emby cinema mode" default="true" />
|
||||
<setting id="askCinema" type="bool" label="Ask to play trailers" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||
<setting id="offerDelete" type="bool" label="30114" default="false" />
|
||||
<setting id="deleteTV" type="bool" label="30115" visible="eq(-1,true)" default="false" subsetting="true" />
|
||||
<setting id="deleteMovies" type="bool" label="30116" visible="eq(-2,true)" default="false" subsetting="true" />
|
||||
<setting id="skipConfirmDelete" type="bool" label="Skip delete confirmation" visible="eq(-3,true)" default="false" subsetting="true" />
|
||||
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" />
|
||||
<setting id="playFromStream" type="bool" label="30002" default="false" />
|
||||
<setting id="videoBitrate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="eq(-1,true)" default="17" />
|
||||
<setting id="transcodeH265" type="bool" label="Force transcode 1080p/H265" default="false" />
|
||||
<setting id="markPlayed" type="number" visible="false" default="90" />
|
||||
<setting id="directSteamFailedCount" type="number" visible="false" default="0" />
|
||||
<setting id="failedCount" type="number" visible="false" default="0" />
|
||||
<setting id="networkCreds" type="text" visible="false" default="" />
|
||||
</category>
|
||||
|
||||
<category label="Extras">
|
||||
<setting id="disableCoverArt" type="bool" label="30157" default="false" visible="true" enable="true" />
|
||||
<setting id="coverArtratio" type="bool" label="Force CoverArt Ratio" visible="eq(-1,false)" default="false" enable="true" />
|
||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" visible="true" enable="true" default="false" />
|
||||
<setting id="showSpecialInfoDialog" type="bool" label="Show special Emby info dialog on play" default="false" visible="false" />
|
||||
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" visible="true" enable="true" />
|
||||
<setting id="enableCoverArt" type="bool" label="30157" default="true" />
|
||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
|
||||
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
||||
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
||||
</category>
|
||||
|
||||
<category label="30022">
|
||||
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="0" />
|
||||
<setting id="supressConnectMsg" type="bool" label="30249" default="false" visible="true" enable="true" />
|
||||
<setting id="supressRestartMsg" type="bool" label="Enable server message when it's restarting" default="false" visible="true" enable="true" />
|
||||
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" />
|
||||
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="1" />
|
||||
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
||||
<setting id="restartMsg" type="bool" label="Enable server message when it's restarting" default="false" />
|
||||
<setting id="newContent" type="bool" label="Enable new content notification" default="false" />
|
||||
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" option="close" />
|
||||
</category>
|
||||
</settings>
|
||||
|
|
247
service.py
247
service.py
|
@ -7,8 +7,8 @@ import sys
|
|||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import xbmcaddon
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
|
@ -16,121 +16,111 @@ import xbmcvfs
|
|||
|
||||
_addon = xbmcaddon.Addon(id='plugin.video.emby')
|
||||
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
sys.path.append(base_resource_path)
|
||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
sys.path.append(base_resource)
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import KodiMonitor
|
||||
import Utils as utils
|
||||
from ClientInformation import ClientInformation
|
||||
from ConnectionManager import ConnectionManager
|
||||
from UserClient import UserClient
|
||||
from Player import Player
|
||||
from WebSocketClient import WebSocketThread
|
||||
from LibrarySync import LibrarySync
|
||||
import userclient
|
||||
import clientinfo
|
||||
import initialsetup
|
||||
import kodimonitor
|
||||
import librarysync
|
||||
import player
|
||||
import utils
|
||||
import videonodes
|
||||
import websocket_client as wsc
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class Service():
|
||||
|
||||
KodiMonitor = KodiMonitor.Kodi_Monitor()
|
||||
clientInfo = ClientInformation()
|
||||
|
||||
addonName = clientInfo.getAddonName()
|
||||
logLevel = UserClient().getLogLevel()
|
||||
WINDOW = xbmcgui.Window(10000)
|
||||
|
||||
newWebSocketThread = None
|
||||
newUserClient = None
|
||||
newLibraryThread = None
|
||||
warn_auth = True
|
||||
welcome_msg = True
|
||||
server_online = True
|
||||
warn_auth = True
|
||||
|
||||
def __init__(self, *args):
|
||||
userclient_running = False
|
||||
websocket_running = False
|
||||
library_running = False
|
||||
kodimonitor_running = False
|
||||
|
||||
addonName = self.addonName
|
||||
clientInfo = self.clientInfo
|
||||
logLevel = self.logLevel
|
||||
|
||||
utils.window('getLogLevel', value=str(logLevel))
|
||||
utils.window('kodiProfile_emby', value=xbmc.translatePath("special://profile"))
|
||||
def __init__(self):
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
logLevel = userclient.UserClient().getLogLevel()
|
||||
self.monitor = xbmc.Monitor()
|
||||
|
||||
utils.window('emby_logLevel', value=str(logLevel))
|
||||
utils.window('emby_kodiProfile', value=xbmc.translatePath("special://profile"))
|
||||
|
||||
# Initial logging
|
||||
self.logMsg("Starting Monitor", 0)
|
||||
self.logMsg("======== START %s ========" % addonName, 0)
|
||||
self.logMsg("Platform: %s" % (clientInfo.getPlatform()), 0)
|
||||
self.logMsg("======== START %s ========" % self.addonName, 0)
|
||||
self.logMsg("Platform: %s" % (self.clientInfo.getPlatform()), 0)
|
||||
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0)
|
||||
self.logMsg("%s Version: %s" % (addonName, clientInfo.getVersion()), 0)
|
||||
self.logMsg("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0)
|
||||
self.logMsg("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0)
|
||||
self.logMsg("Log Level: %s" % logLevel, 0)
|
||||
|
||||
# Reset window props for profile switch
|
||||
utils.window('Server_online', clear=True)
|
||||
utils.window('Server_status', clear=True)
|
||||
utils.window('startup', clear=True)
|
||||
utils.window('OnWakeSync', clear=True)
|
||||
utils.window('kodiScan', clear=True)
|
||||
utils.window('minDBVersionCheck', clear=True)
|
||||
properties = [
|
||||
|
||||
# Set min DB version
|
||||
utils.window('minDBVersion', value="1.1.52")
|
||||
|
||||
embyProperty = utils.window('Emby.nodes.total')
|
||||
propNames = [
|
||||
|
||||
"index","path","title","content",
|
||||
"inprogress.content","inprogress.title",
|
||||
"inprogress.content","inprogress.path",
|
||||
"nextepisodes.title","nextepisodes.content",
|
||||
"nextepisodes.path","unwatched.title",
|
||||
"unwatched.content","unwatched.path",
|
||||
"recent.title","recent.content","recent.path",
|
||||
"recentepisodes.title","recentepisodes.content",
|
||||
"recentepisodes.path","inprogressepisodes.title",
|
||||
"inprogressepisodes.content","inprogressepisodes.path"
|
||||
"emby_online", "emby_serverStatus", "emby_onWake",
|
||||
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
||||
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId"
|
||||
]
|
||||
for prop in properties:
|
||||
utils.window(prop, clear=True)
|
||||
|
||||
if embyProperty:
|
||||
totalNodes = int(embyProperty)
|
||||
for i in range(totalNodes):
|
||||
for prop in propNames:
|
||||
utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
||||
# Clear playlist properties
|
||||
xbmcgui.Window(10101).clearProperties()
|
||||
# Clear video nodes properties
|
||||
videonodes.VideoNodes().clearProperties()
|
||||
|
||||
# Set the minimum database version
|
||||
utils.window('emby_minDBVersion', value="1.1.63")
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
||||
def ServiceEntryPoint(self):
|
||||
|
||||
# Important: Threads depending on abortRequest will not trigger
|
||||
# if profile switch happens more than once.
|
||||
monitor = self.monitor
|
||||
kodiProfile = xbmc.translatePath("special://profile")
|
||||
|
||||
# Server auto-detect
|
||||
ConnectionManager().checkServer()
|
||||
initialsetup.InitialSetup().setup()
|
||||
|
||||
# Initialize important threads
|
||||
user = UserClient()
|
||||
player = Player()
|
||||
ws = WebSocketThread()
|
||||
library = LibrarySync()
|
||||
user = userclient.UserClient()
|
||||
ws = wsc.WebSocket_Client()
|
||||
library = librarysync.LibrarySync()
|
||||
kplayer = player.Player()
|
||||
# Sync and progress report
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
while not self.KodiMonitor.abortRequested():
|
||||
while not monitor.abortRequested():
|
||||
|
||||
if utils.window('emby_kodiProfile') != kodiProfile:
|
||||
# Profile change happened, terminate this thread and others
|
||||
self.logMsg(
|
||||
"Kodi profile was: %s and changed to: %s. Terminating old Emby thread."
|
||||
% (kodiProfile, utils.window('emby_kodiProfile')), 1)
|
||||
|
||||
break
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
# 1. Server is online
|
||||
# 2. User is set
|
||||
# 3. User has access to the server
|
||||
|
||||
if utils.window("kodiProfile_emby") != kodiProfile:
|
||||
# Profile change happened, terminate this thread
|
||||
self.logMsg("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1)
|
||||
break
|
||||
|
||||
if utils.window('Server_online') == "true":
|
||||
if utils.window('emby_online') == "true":
|
||||
|
||||
# Emby server is online
|
||||
# Verify if user is set and has access to the server
|
||||
|
@ -140,28 +130,28 @@ class Service():
|
|||
if xbmc.Player().isPlaying():
|
||||
try:
|
||||
# Update and report progress
|
||||
playTime = xbmc.Player().getTime()
|
||||
playtime = xbmc.Player().getTime()
|
||||
totalTime = xbmc.Player().getTotalTime()
|
||||
currentFile = player.currentFile
|
||||
currentFile = kplayer.currentFile
|
||||
|
||||
# Update positionticks
|
||||
if player.played_information.get(currentFile) is not None:
|
||||
player.played_information[currentFile]['currentPosition'] = playTime
|
||||
if kplayer.played_info.get(currentFile) is not None:
|
||||
kplayer.played_info[currentFile]['currentPosition'] = playtime
|
||||
|
||||
td = datetime.today() - lastProgressUpdate
|
||||
secDiff = td.seconds
|
||||
|
||||
# Report progress to Emby server
|
||||
if (secDiff > 3):
|
||||
player.reportPlayback()
|
||||
kplayer.reportPlayback()
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
elif utils.window('commandUpdate') == "true":
|
||||
elif utils.window('emby_command') == "true":
|
||||
# Received a remote control command that
|
||||
# requires updating immediately
|
||||
utils.window('commandUpdate', clear=True)
|
||||
player.reportPlayback()
|
||||
lastProgressUpdate = da4tetime.today()
|
||||
utils.window('emby_command', clear=True)
|
||||
kplayer.reportPlayback()
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
except Exception as e:
|
||||
self.logMsg("Exception in Playback Monitor Service: %s" % e, 1)
|
||||
|
@ -169,8 +159,7 @@ class Service():
|
|||
else:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
if utils.settings('supressConnectMsg') == "false":
|
||||
if self.welcome_msg:
|
||||
if utils.settings('connectMsg') == "true" and self.welcome_msg:
|
||||
# Reset authentication warnings
|
||||
self.welcome_msg = False
|
||||
# Get additional users
|
||||
|
@ -179,17 +168,25 @@ class Service():
|
|||
add = ", %s" % ", ".join(additionalUsers)
|
||||
else:
|
||||
add = ""
|
||||
xbmcgui.Dialog().notification("Emby server", "Welcome %s%s!" % (user.currUser, add), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False)
|
||||
xbmcgui.Dialog().notification(
|
||||
heading="Emby server",
|
||||
message="Welcome %s%s!" % (user.currUser, add),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
time=2000,
|
||||
sound=False)
|
||||
|
||||
# Start monitoring kodi events
|
||||
if not self.kodimonitor_running:
|
||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||
|
||||
# Start the Websocket Client
|
||||
if (self.newWebSocketThread is None):
|
||||
self.newWebSocketThread = "Started"
|
||||
if not self.websocket_running:
|
||||
self.websocket_running = True
|
||||
ws.start()
|
||||
# Start the Library Sync Thread
|
||||
if (self.newLibraryThread is None):
|
||||
self.newLibraryThread = "Started"
|
||||
# Start the syncing thread
|
||||
if not self.library_running:
|
||||
self.library_running = True
|
||||
library.start()
|
||||
|
||||
else:
|
||||
|
||||
if (user.currUser is None) and self.warn_auth:
|
||||
|
@ -204,20 +201,19 @@ class Service():
|
|||
# Verify access with an API call
|
||||
user.hasAccess()
|
||||
|
||||
if utils.window('Server_online') != "true":
|
||||
if utils.window('emby_online') != "true":
|
||||
# Server went offline
|
||||
break
|
||||
|
||||
if self.KodiMonitor.waitForAbort(5):
|
||||
if monitor.waitForAbort(5):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
else:
|
||||
# Wait until Emby server is online
|
||||
# or Kodi is shut down.
|
||||
while not self.KodiMonitor.abortRequested():
|
||||
while not monitor.abortRequested():
|
||||
|
||||
if user.getServer() == "":
|
||||
if user.getServer() == False:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
|
||||
|
@ -226,8 +222,14 @@ class Service():
|
|||
# Alert the user and suppress future warning
|
||||
if self.server_online:
|
||||
self.logMsg("Server is offline.", 1)
|
||||
utils.window('Server_online', value="false")
|
||||
xbmcgui.Dialog().notification("Error connecting", "%s Server is unreachable." % self.addonName, icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
|
||||
utils.window('emby_online', value="false")
|
||||
|
||||
xbmcgui.Dialog().notification(
|
||||
heading="Error connecting",
|
||||
message="%s Server is unreachable." % self.addonName,
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
|
||||
self.server_online = False
|
||||
|
||||
else:
|
||||
|
@ -235,54 +237,55 @@ class Service():
|
|||
if not self.server_online:
|
||||
# Server was offline when Kodi started.
|
||||
# Wait for server to be fully established.
|
||||
if self.KodiMonitor.waitForAbort(5):
|
||||
if monitor.waitForAbort(5):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
# Alert the user that server is online.
|
||||
xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % user.currUser, icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False)
|
||||
xbmcgui.Dialog().notification(
|
||||
heading="Emby server",
|
||||
message="Server is online.",
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
time=2000,
|
||||
sound=False)
|
||||
|
||||
self.server_online = True
|
||||
self.logMsg("Server is online and ready.", 1)
|
||||
utils.window('Server_online', value="true")
|
||||
utils.window('emby_online', value="true")
|
||||
|
||||
# Start the User client
|
||||
if self.newUserClient is None:
|
||||
self.newUserClient = "Started"
|
||||
# Start the userclient thread
|
||||
if not self.userclient_running:
|
||||
self.userclient_running = True
|
||||
user.start()
|
||||
|
||||
break
|
||||
|
||||
if self.KodiMonitor.waitForAbort(1):
|
||||
if monitor.waitForAbort(1):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
|
||||
if self.KodiMonitor.waitForAbort(1):
|
||||
if monitor.waitForAbort(1):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
##### Emby thread is terminating. #####
|
||||
|
||||
# If music is enabled and direct stream for music is enabled
|
||||
# We use Kodi pathsubstitution to allow for music to play outside network
|
||||
# The setting needs to be set before Kodi starts.
|
||||
if utils.settings('enableMusicSync') == "true" and utils.settings('directstreammusic') == "true":
|
||||
# We need to keep track of the settings
|
||||
alternate = utils.settings('altip') == "true"
|
||||
pathsub = utils.settings('pathsub') == "true"
|
||||
if self.library_running:
|
||||
library.stopThread()
|
||||
|
||||
if pathsub and not alternate:
|
||||
# Path sub in place, but primary address in use, remove it
|
||||
utils.pathsubstitution(False)
|
||||
elif not pathsub and alternate:
|
||||
# Path sub not in place, but secondary address in use, add it
|
||||
utils.pathsubstitution()
|
||||
|
||||
if (self.newWebSocketThread is not None):
|
||||
if self.websocket_running:
|
||||
ws.stopClient()
|
||||
|
||||
if (self.newUserClient is not None):
|
||||
if self.userclient_running:
|
||||
user.stopClient()
|
||||
|
||||
self.logMsg("======== STOP %s ========" % self.addonName, 0)
|
||||
|
||||
# Delay option
|
||||
delay = int(utils.settings('startupDelay'))
|
||||
|
||||
xbmc.log("Delaying emby startup by: %s sec..." % delay)
|
||||
if delay and xbmc.Monitor().waitForAbort(delay):
|
||||
# Start the service
|
||||
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
||||
else:
|
||||
Service().ServiceEntryPoint()
|
Loading…
Reference in a new issue