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
11
addon.xml
11
addon.xml
|
@ -1,11 +1,14 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.emby"
|
<addon id="plugin.video.emby"
|
||||||
name="Emby"
|
name="Emby"
|
||||||
version="1.1.62"
|
version="1.1.63"
|
||||||
provider-name="Emby.media">
|
provider-name="Emby.media">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.3.0"/>
|
<import addon="script.module.requests" version="2.3.0" />
|
||||||
|
<import addon="plugin.video.emby.movies" version="0.01" />
|
||||||
|
<import addon="plugin.video.emby.tvshows" version="0.01" />
|
||||||
|
<import addon="plugin.video.emby.musicvideos" version="0.01" />
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource"
|
<extension point="xbmc.python.pluginsource"
|
||||||
library="default.py">
|
library="default.py">
|
||||||
|
@ -23,4 +26,4 @@
|
||||||
<summary lang="en"></summary>
|
<summary lang="en"></summary>
|
||||||
<description lang="en">Welcome to Emby for Kodi A whole new way to manage and view your media library. The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Emby - the most powerful fully open source multi-client media metadata indexer and server. Emby for Kodi is the absolute best way to enjoy the incredible Kodi playback engine combined with the power of Emby's centralized database. Features: Direct integration with the Kodi library for native Kodi speed Instant synchronization with the Emby server Full support for Movie, TV and Music collections Emby Server direct stream and transcoding support - use Kodi when you are away from home!</description>
|
<description lang="en">Welcome to Emby for Kodi A whole new way to manage and view your media library. The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Emby - the most powerful fully open source multi-client media metadata indexer and server. Emby for Kodi is the absolute best way to enjoy the incredible Kodi playback engine combined with the power of Emby's centralized database. Features: Direct integration with the Kodi library for native Kodi speed Instant synchronization with the Emby server Full support for Movie, TV and Music collections Emby Server direct stream and transcoding support - use Kodi when you are away from home!</description>
|
||||||
</extension>
|
</extension>
|
||||||
</addon>
|
</addon>
|
140
default.py
140
default.py
|
@ -1,18 +1,33 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import xbmcaddon, xbmc
|
|
||||||
import os, sys
|
#################################################################################################
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
||||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||||
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||||
sys.path.append(base_resource_path)
|
sys.path.append(base_resource)
|
||||||
import Entrypoint as entrypoint
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import entrypoint
|
||||||
|
import utils
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
enableProfiling = False
|
enableProfiling = False
|
||||||
|
|
||||||
class Main:
|
class Main:
|
||||||
|
|
||||||
|
|
||||||
# MAIN ENTRY POINT
|
# MAIN ENTRY POINT
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
@ -24,83 +39,66 @@ class Main:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mode = params['mode'][0]
|
mode = params['mode'][0]
|
||||||
id = params.get('id', None)
|
itemid = params.get('id')
|
||||||
if id:
|
if itemid:
|
||||||
id = id[0]
|
itemid = itemid[0]
|
||||||
except:
|
except:
|
||||||
params = {}
|
params = {}
|
||||||
mode = ""
|
mode = ""
|
||||||
|
|
||||||
##### PLAY ITEM VIA plugin://plugin.video.emby/ #####
|
|
||||||
if "play" in mode or "playnow" in mode:
|
|
||||||
entrypoint.doPlayback(id)
|
|
||||||
|
|
||||||
#### DO RESET AUTH #####
|
modes = {
|
||||||
elif "resetauth" in mode:
|
|
||||||
entrypoint.resetAuth()
|
|
||||||
|
|
||||||
##### DO DATABASE RESET #####
|
|
||||||
elif "reset" in mode:
|
|
||||||
import Utils as utils
|
|
||||||
utils.reset()
|
|
||||||
|
|
||||||
##### ADD/REMOVE USER FROM SESSION #####
|
'reset': utils.reset,
|
||||||
elif "adduser" in mode:
|
'resetauth': entrypoint.resetAuth,
|
||||||
entrypoint.addUser()
|
'play': entrypoint.doPlayback,
|
||||||
|
'passwords': utils.passwordsXML,
|
||||||
|
'adduser': entrypoint.addUser,
|
||||||
|
'thememedia': entrypoint.getThemeMedia,
|
||||||
|
'channels': entrypoint.BrowseChannels,
|
||||||
|
'channelsfolder': entrypoint.BrowseChannels,
|
||||||
|
'nextup': entrypoint.getNextUpEpisodes,
|
||||||
|
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||||
|
'recentepisodes': entrypoint.getRecentEpisodes
|
||||||
|
}
|
||||||
|
|
||||||
##### SYNC THEME MEDIA #####
|
if modes.get(mode):
|
||||||
elif "thememedia" in mode:
|
# Simple functions
|
||||||
entrypoint.getThemeMedia()
|
if mode == "play":
|
||||||
|
dbid = params.get('dbid')
|
||||||
|
modes[mode](itemid, dbid)
|
||||||
|
|
||||||
##### LAUNCH EMBY USER PREFS #####
|
elif mode in ("nextup", "inprogressepisodes", "recentepisodes"):
|
||||||
elif "userprefs" in mode:
|
limit = int(params['limit'][0])
|
||||||
entrypoint.userPreferences()
|
modes[mode](itemid, limit)
|
||||||
|
|
||||||
##### OPEN ADDON SETTINGS #####
|
|
||||||
elif "settings" in mode:
|
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
|
|
||||||
|
|
||||||
##### MANUALLY SYNC LIBRARY #####
|
|
||||||
elif "manualsync" in mode:
|
|
||||||
from LibrarySync import LibrarySync
|
|
||||||
LibrarySync().FullLibrarySync(True)
|
|
||||||
|
|
||||||
##### CACHE ARTWORK #####
|
|
||||||
elif "texturecache" in mode:
|
|
||||||
from TextureCache import TextureCache
|
|
||||||
TextureCache().FullTextureCacheSync()
|
|
||||||
|
|
||||||
##### BROWSE EMBY CHANNELS FOLDER #####
|
|
||||||
elif "channelsfolder" in mode:
|
|
||||||
folderid = params['folderid'][0]
|
|
||||||
entrypoint.BrowseChannels(id,folderid)
|
|
||||||
|
|
||||||
##### BROWSE EMBY CHANNELS ROOT #####
|
elif mode == "channels":
|
||||||
elif "channels" in mode:
|
modes[mode](itemid)
|
||||||
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 mode == "channelsfolder":
|
||||||
elif "inprogressepisodes" in mode:
|
folderid = params['folderid'][0]
|
||||||
limit = int(params['limit'][0])
|
modes[mode](itemid, folderid)
|
||||||
entrypoint.getInProgressEpisodes(id, limit)
|
|
||||||
|
else:
|
||||||
|
modes[mode]()
|
||||||
|
else:
|
||||||
|
# Other functions
|
||||||
|
if mode == "settings":
|
||||||
|
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
|
||||||
|
elif mode in ("manualsync", "repair"):
|
||||||
|
import librarysync
|
||||||
|
if mode == "manualsync":
|
||||||
|
librarysync.LibrarySync().fullSync(manualrun=True)
|
||||||
|
else:
|
||||||
|
librarysync.LibrarySync().fullSync(repair=True)
|
||||||
|
elif mode == "texturecache":
|
||||||
|
import artwork
|
||||||
|
artwork.Artwork().FullTextureCacheSync()
|
||||||
|
elif "extrafanart" in sys.argv[0]:
|
||||||
|
entrypoint.getExtraFanArt()
|
||||||
|
else:
|
||||||
|
entrypoint.doMainListing()
|
||||||
|
|
||||||
##### GET RECENT EPISODES FOR TAGNAME #####
|
|
||||||
elif "recentepisodes" in mode:
|
|
||||||
limit = int(params['limit'][0])
|
|
||||||
entrypoint.getRecentEpisodes(id, limit)
|
|
||||||
|
|
||||||
##### GET EXTRAFANART FOR LISTITEM #####
|
|
||||||
elif "extrafanart" in sys.argv[0]:
|
|
||||||
entrypoint.getExtraFanArt()
|
|
||||||
|
|
||||||
##### SHOW ADDON NODES LISTING #####
|
|
||||||
if not mode:
|
|
||||||
entrypoint.doMainListing()
|
|
||||||
|
|
||||||
if ( __name__ == "__main__" ):
|
if ( __name__ == "__main__" ):
|
||||||
xbmc.log('plugin.video.emby started')
|
xbmc.log('plugin.video.emby started')
|
||||||
|
|
|
@ -239,7 +239,7 @@
|
||||||
<string id="30246">Enable Netflix style next up notification</string>
|
<string id="30246">Enable Netflix style next up notification</string>
|
||||||
<string id="30247"> - The number of seconds before the end to show the notification</string>
|
<string id="30247"> - The number of seconds before the end to show the notification</string>
|
||||||
<string id="30248">Show Emby Info dialog on play/select action</string>
|
<string id="30248">Show Emby Info dialog on play/select action</string>
|
||||||
<string id="30249">Suppress server connection message on start-up</string>
|
<string id="30249">Enable server connection message on start-up</string>
|
||||||
<string id="30250">Use local paths instead of addon redirect for playback</string>
|
<string id="30250">Use local paths instead of addon redirect for playback</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,94 +1,245 @@
|
||||||
# -- coding: utf-8 --
|
# -*- coding: utf-8 -*-
|
||||||
# API.py
|
|
||||||
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
|
|
||||||
|
|
||||||
from datetime import datetime
|
##################################################################################################
|
||||||
from random import randrange
|
|
||||||
import xbmc
|
import clientinfo
|
||||||
import xbmcgui
|
import utils
|
||||||
|
|
||||||
|
##################################################################################################
|
||||||
|
|
||||||
import Utils as utils
|
|
||||||
|
|
||||||
class API():
|
class API():
|
||||||
|
|
||||||
def getPeople(self, item):
|
def __init__(self, item):
|
||||||
|
|
||||||
|
self.item = item
|
||||||
|
self.clientinfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientinfo.getAddonName()
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def getUserData(self):
|
||||||
|
# Default
|
||||||
|
favorite = False
|
||||||
|
playcount = None
|
||||||
|
played = False
|
||||||
|
lastPlayedDate = None
|
||||||
|
resume = 0
|
||||||
|
rating = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
userdata = self.item['UserData']
|
||||||
|
|
||||||
|
except KeyError: # No userdata found.
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
favorite = userdata['IsFavorite']
|
||||||
|
likes = userdata.get('Likes')
|
||||||
|
# Rating for album and songs
|
||||||
|
if favorite:
|
||||||
|
rating = 5
|
||||||
|
elif likes:
|
||||||
|
rating = 3
|
||||||
|
elif likes == False:
|
||||||
|
rating = 1
|
||||||
|
else:
|
||||||
|
rating = 0
|
||||||
|
|
||||||
|
lastPlayedDate = userdata.get('LastPlayedDate')
|
||||||
|
if lastPlayedDate:
|
||||||
|
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
|
||||||
|
|
||||||
|
if userdata['Played']:
|
||||||
|
# Playcount is tied to the watch status
|
||||||
|
played = True
|
||||||
|
playcount = userdata['PlayCount']
|
||||||
|
if playcount == 0:
|
||||||
|
playcount = 1
|
||||||
|
|
||||||
|
if lastPlayedDate is None:
|
||||||
|
lastPlayedDate = self.getDateCreated()
|
||||||
|
|
||||||
|
playbackPosition = userdata.get('PlaybackPositionTicks')
|
||||||
|
if playbackPosition:
|
||||||
|
resume = playbackPosition / 10000000.0
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
'Favorite': favorite,
|
||||||
|
'PlayCount': playcount,
|
||||||
|
'Played': played,
|
||||||
|
'LastPlayedDate': lastPlayedDate,
|
||||||
|
'Resume': resume,
|
||||||
|
'Rating': rating
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPeople(self):
|
||||||
# Process People
|
# Process People
|
||||||
director = []
|
director = []
|
||||||
writer = []
|
writer = []
|
||||||
cast = []
|
cast = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
people = item['People']
|
people = self.item['People']
|
||||||
except: pass
|
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
for person in people:
|
for person in people:
|
||||||
|
|
||||||
type = person['Type']
|
type = person['Type']
|
||||||
Name = person['Name']
|
name = person['Name']
|
||||||
|
|
||||||
if "Director" in type:
|
if "Director" in type:
|
||||||
director.append(Name)
|
director.append(name)
|
||||||
elif "Writing" in type:
|
|
||||||
writer.append(Name)
|
|
||||||
elif "Writer" in type:
|
|
||||||
writer.append(Name)
|
|
||||||
elif "Actor" in type:
|
elif "Actor" in type:
|
||||||
cast.append(Name)
|
cast.append(name)
|
||||||
|
elif type in ("Writing", "Writer"):
|
||||||
|
writer.append(name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
'Director': director,
|
'Director': director,
|
||||||
'Writer': writer,
|
'Writer': writer,
|
||||||
'Cast': cast
|
'Cast': cast
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTimeInfo(self, item):
|
def getMediaStreams(self):
|
||||||
# Runtime and Resume point
|
item = self.item
|
||||||
tempRuntime = 0
|
videotracks = []
|
||||||
runtime = 0
|
audiotracks = []
|
||||||
resume = 0
|
subtitlelanguages = []
|
||||||
|
|
||||||
try: # Get resume point
|
try:
|
||||||
userdata = item['UserData']
|
media_streams = item['MediaSources'][0]['MediaStreams']
|
||||||
playbackPosition = userdata['PlaybackPositionTicks']
|
|
||||||
resume = playbackPosition / 10000000.0
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
try: # Get total runtime
|
|
||||||
tempRuntime = item['RunTimeTicks']
|
|
||||||
|
|
||||||
except:
|
|
||||||
try: tempRuntime = item['CumulativeRunTimeTicks']
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
finally:
|
except KeyError:
|
||||||
runtime = tempRuntime / 10000000.0
|
media_streams = item['MediaStreams']
|
||||||
|
|
||||||
|
for media_stream in media_streams:
|
||||||
|
# Sort through Video, Audio, Subtitle
|
||||||
|
stream_type = media_stream['Type']
|
||||||
|
codec = media_stream.get('Codec', "").lower()
|
||||||
|
profile = media_stream.get('Profile', "").lower()
|
||||||
|
|
||||||
|
if stream_type == "Video":
|
||||||
|
# Height, Width, Codec, AspectRatio, AspectFloat, 3D
|
||||||
|
track = {
|
||||||
|
|
||||||
|
'videocodec': codec,
|
||||||
|
'height': media_stream.get('Height'),
|
||||||
|
'width': media_stream.get('Width'),
|
||||||
|
'video3DFormat': item.get('Video3DFormat'),
|
||||||
|
'aspectratio': 1.85
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
container = item['MediaSources'][0]['Container'].lower()
|
||||||
|
except:
|
||||||
|
container = ""
|
||||||
|
|
||||||
|
# Sort codec vs container/profile
|
||||||
|
if "msmpeg4" in codec:
|
||||||
|
track['videocodec'] = "divx"
|
||||||
|
elif "mpeg4" in codec:
|
||||||
|
if "simple profile" in profile or not profile:
|
||||||
|
track['videocodec'] = "xvid"
|
||||||
|
elif "h264" in codec:
|
||||||
|
if container in ("mp4", "mov", "m4v"):
|
||||||
|
track['videocodec'] = "avc1"
|
||||||
|
|
||||||
|
# Aspect ratio
|
||||||
|
if item.get('AspectRatio'):
|
||||||
|
# Metadata AR
|
||||||
|
aspectratio = item['AspectRatio']
|
||||||
|
else: # File AR
|
||||||
|
aspectratio = media_stream.get('AspectRatio', "0")
|
||||||
|
|
||||||
|
try:
|
||||||
|
aspectwidth, aspectheight = aspectratio.split(':')
|
||||||
|
track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
width = track['width']
|
||||||
|
height = track['height']
|
||||||
|
|
||||||
|
if width and height:
|
||||||
|
track['aspectratio'] = round(float(width / height), 6)
|
||||||
|
|
||||||
|
videotracks.append(track)
|
||||||
|
|
||||||
|
elif stream_type == "Audio":
|
||||||
|
# Codec, Channels, language
|
||||||
|
track = {
|
||||||
|
|
||||||
|
'audiocodec': codec,
|
||||||
|
'channels': media_stream.get('Channels'),
|
||||||
|
'audiolanguage': media_stream.get('Language')
|
||||||
|
}
|
||||||
|
|
||||||
|
if "dca" in codec and "dts-hd ma" in profile:
|
||||||
|
track['audiocodec'] = "dtshd_ma"
|
||||||
|
|
||||||
|
audiotracks.append(track)
|
||||||
|
|
||||||
|
elif stream_type == "Subtitle":
|
||||||
|
# Language
|
||||||
|
subtitlelanguages.append(media_stream.get('Language', "Unknown"))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
'ResumeTime': resume,
|
'video': videotracks,
|
||||||
'TotalTime': runtime
|
'audio': audiotracks,
|
||||||
|
'subtitle': subtitlelanguages
|
||||||
}
|
}
|
||||||
|
|
||||||
def getStudios(self, item):
|
def getRuntime(self):
|
||||||
# Process Studio
|
item = self.item
|
||||||
|
try:
|
||||||
|
runtime = item['RunTimeTicks'] / 10000000.0
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
|
||||||
|
|
||||||
|
return runtime
|
||||||
|
|
||||||
|
def adjustResume(self, resume_seconds):
|
||||||
|
|
||||||
|
resume = 0
|
||||||
|
if resume_seconds:
|
||||||
|
resume = round(float(resume_seconds), 6)
|
||||||
|
jumpback = int(utils.settings('resumeJumpBack'))
|
||||||
|
if resume > jumpback:
|
||||||
|
# To avoid negative bookmark
|
||||||
|
resume = resume - jumpback
|
||||||
|
|
||||||
|
return resume
|
||||||
|
|
||||||
|
def getStudios(self):
|
||||||
|
# Process Studios
|
||||||
|
item = self.item
|
||||||
studios = []
|
studios = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
studio = item['SeriesStudio']
|
studio = item['SeriesStudio']
|
||||||
studios.append(self.getStudio(studio))
|
studios.append(self.verifyStudio(studio))
|
||||||
except:
|
|
||||||
try:
|
except KeyError:
|
||||||
studioArray = item['Studios']
|
studioList = item['Studios']
|
||||||
for studio in studioArray:
|
for studio in studioList:
|
||||||
studios.append(self.getStudio(studio['Name']))
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
|
name = studio['Name']
|
||||||
|
studios.append(self.verifyStudio(name))
|
||||||
|
|
||||||
return studios
|
return studios
|
||||||
|
|
||||||
def getStudio(self, studioName):
|
def verifyStudio(self, studioName):
|
||||||
# Convert studio for Kodi to properly detect them
|
# Convert studio for Kodi to properly detect them
|
||||||
studios = {
|
studios = {
|
||||||
|
|
||||||
|
@ -101,503 +252,127 @@ class API():
|
||||||
|
|
||||||
return studios.get(studioName.lower(), studioName)
|
return studios.get(studioName.lower(), studioName)
|
||||||
|
|
||||||
def getGenre(self,item):
|
def getChecksum(self):
|
||||||
genre = ""
|
# Use the etags checksum and userdata
|
||||||
genres = item.get("Genres")
|
item = self.item
|
||||||
if genres != None and genres != []:
|
userdata = item['UserData']
|
||||||
for genre_string in genres:
|
|
||||||
if genre == "": #Just take the first genre
|
|
||||||
genre = genre_string
|
|
||||||
else:
|
|
||||||
genre = genre + " / " + genre_string
|
|
||||||
elif item.get("SeriesGenres") != None and item.get("SeriesGenres") != '':
|
|
||||||
genres = item.get("SeriesGenres")
|
|
||||||
if genres != None and genres != []:
|
|
||||||
for genre_string in genres:
|
|
||||||
if genre == "": #Just take the first genre
|
|
||||||
genre = genre_string
|
|
||||||
else:
|
|
||||||
genre = genre + " / " + genre_string
|
|
||||||
return genre
|
|
||||||
|
|
||||||
def getMediaStreams(self, item, mediaSources = False):
|
checksum = "%s%s%s%s%s%s" % (
|
||||||
|
|
||||||
videotracks = [] # Height, Width, Codec, AspectRatio, AspectFloat, 3D
|
|
||||||
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
|
return checksum
|
||||||
|
|
||||||
def getUserData(self, item):
|
def getGenres(self):
|
||||||
# Default
|
item = self.item
|
||||||
favorite = False
|
all_genres = ""
|
||||||
playcount = None
|
genres = item.get('Genres', item.get('SeriesGenres'))
|
||||||
lastPlayedDate = None
|
|
||||||
userKey = ""
|
if genres:
|
||||||
|
all_genres = " / ".join(genres)
|
||||||
|
|
||||||
|
return all_genres
|
||||||
|
|
||||||
|
def getDateCreated(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
userdata = item['UserData']
|
dateadded = self.item['DateCreated']
|
||||||
|
|
||||||
except: # No userdata found.
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
favorite = userdata['IsFavorite']
|
|
||||||
userKey = userdata.get('Key', "")
|
|
||||||
|
|
||||||
watched = userdata['Played']
|
|
||||||
if watched:
|
|
||||||
# Playcount is tied to the watch status
|
|
||||||
playcount = userdata['PlayCount']
|
|
||||||
if playcount == 0:
|
|
||||||
playcount = 1
|
|
||||||
else:
|
|
||||||
playcount = None
|
|
||||||
|
|
||||||
lastPlayedDate = userdata.get('LastPlayedDate', None)
|
|
||||||
if lastPlayedDate:
|
|
||||||
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
'Favorite': favorite,
|
|
||||||
'PlayCount': playcount,
|
|
||||||
'LastPlayedDate': lastPlayedDate,
|
|
||||||
'Key': userKey
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def getRecursiveItemCount(self, item):
|
|
||||||
if item.get("RecursiveItemCount") != None:
|
|
||||||
return str(item.get("RecursiveItemCount"))
|
|
||||||
else:
|
|
||||||
return "0"
|
|
||||||
|
|
||||||
def getOverview(self, item):
|
|
||||||
|
|
||||||
overview = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
overview = item['Overview']
|
|
||||||
overview = overview.replace("\"", "\'")
|
|
||||||
overview = overview.replace("\n", " ")
|
|
||||||
overview = overview.replace("\r", " ")
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
return overview
|
|
||||||
|
|
||||||
def getTVInfo(self, item, userData):
|
|
||||||
TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount")
|
|
||||||
TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount")
|
|
||||||
WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-int(userData.get("UnplayedItemCount"))
|
|
||||||
UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else int(userData.get("UnplayedItemCount"))
|
|
||||||
NumEpisodes = TotalEpisodes
|
|
||||||
tempEpisode = ""
|
|
||||||
if (item.get("IndexNumber") != None):
|
|
||||||
episodeNum = item.get("IndexNumber")
|
|
||||||
if episodeNum < 10:
|
|
||||||
tempEpisode = "0" + str(episodeNum)
|
|
||||||
else:
|
|
||||||
tempEpisode = str(episodeNum)
|
|
||||||
|
|
||||||
tempSeason = ""
|
|
||||||
if (str(item.get("ParentIndexNumber")) != None):
|
|
||||||
tempSeason = str(item.get("ParentIndexNumber"))
|
|
||||||
if item.get("ParentIndexNumber") < 10:
|
|
||||||
tempSeason = "0" + tempSeason
|
|
||||||
if item.get("SeriesName") != None:
|
|
||||||
temp=item.get("SeriesName")
|
|
||||||
SeriesName=temp.encode('utf-8')
|
|
||||||
else:
|
|
||||||
SeriesName=''
|
|
||||||
return {'TotalSeasons' : str(TotalSeasons),
|
|
||||||
'TotalEpisodes' : str(TotalEpisodes),
|
|
||||||
'WatchedEpisodes' : str(WatchedEpisodes),
|
|
||||||
'UnWatchedEpisodes': str(UnWatchedEpisodes),
|
|
||||||
'NumEpisodes' : str(NumEpisodes),
|
|
||||||
'Season' : tempSeason,
|
|
||||||
'Episode' : tempEpisode,
|
|
||||||
'SeriesName' : SeriesName
|
|
||||||
}
|
|
||||||
|
|
||||||
def getDateCreated(self, item):
|
|
||||||
|
|
||||||
try:
|
|
||||||
dateadded = item['DateCreated']
|
|
||||||
dateadded = dateadded.split('.')[0].replace('T', " ")
|
dateadded = dateadded.split('.')[0].replace('T', " ")
|
||||||
except:
|
except KeyError:
|
||||||
dateadded = None
|
dateadded = None
|
||||||
|
|
||||||
return dateadded
|
return dateadded
|
||||||
|
|
||||||
def getPremiereDate(self, item):
|
def getPremiereDate(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
premiere = item['PremiereDate']
|
premiere = self.item['PremiereDate']
|
||||||
premiere = premiere.split('.')[0].replace('T', " ")
|
premiere = premiere.split('.')[0].replace('T', " ")
|
||||||
except:
|
except KeyError:
|
||||||
premiere = None
|
premiere = None
|
||||||
|
|
||||||
return premiere
|
return premiere
|
||||||
|
|
||||||
def getTagline(self, item):
|
def getOverview(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tagline = item['Taglines'][0]
|
overview = self.item['Overview']
|
||||||
except:
|
overview = overview.replace("\"", "\'")
|
||||||
|
overview = overview.replace("\n", " ")
|
||||||
|
overview = overview.replace("\r", " ")
|
||||||
|
except KeyError:
|
||||||
|
overview = ""
|
||||||
|
|
||||||
|
return overview
|
||||||
|
|
||||||
|
def getTagline(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
tagline = self.item['Taglines'][0]
|
||||||
|
except IndexError:
|
||||||
tagline = None
|
tagline = None
|
||||||
|
|
||||||
return tagline
|
return tagline
|
||||||
|
|
||||||
def getProvider(self, item, providername):
|
def getProvider(self, providername):
|
||||||
# Provider Name: imdb or tvdb
|
|
||||||
try:
|
try:
|
||||||
if "imdb" in providername:
|
provider = self.item['ProviderIds'][providername]
|
||||||
provider = item['ProviderIds']['Imdb']
|
except KeyError:
|
||||||
elif "tvdb" in providername:
|
|
||||||
provider = item['ProviderIds']['Tvdb']
|
|
||||||
elif "musicBrainzArtist" in providername:
|
|
||||||
provider = item['ProviderIds']['MusicBrainzArtist']
|
|
||||||
elif "musicBrainzAlbum" in providername:
|
|
||||||
provider = item['ProviderIds']['MusicBrainzAlbum']
|
|
||||||
elif "musicBrainzTrackId" in providername:
|
|
||||||
provider = item['ProviderIds']['MusicBrainzTrackId']
|
|
||||||
except:
|
|
||||||
provider = None
|
provider = None
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
|
|
||||||
def getCountry(self, item):
|
def getMpaa(self):
|
||||||
|
# Convert more complex cases
|
||||||
|
mpaa = self.item.get('OfficialRating', "")
|
||||||
|
|
||||||
|
if mpaa in ("NR", "UR"):
|
||||||
|
# Kodi seems to not like NR, but will accept Not Rated
|
||||||
|
mpaa = "Not Rated"
|
||||||
|
|
||||||
|
return mpaa
|
||||||
|
|
||||||
|
def getCountry(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
country = item['ProductionLocations'][0]
|
country = self.item['ProductionLocations'][0]
|
||||||
except:
|
except IndexError:
|
||||||
country = None
|
country = None
|
||||||
|
|
||||||
return country
|
return country
|
||||||
|
|
||||||
def getMpaa(self, item):
|
def getFilePath(self):
|
||||||
# Convert more complex cases
|
|
||||||
mpaa = item.get('OfficialRating', "")
|
|
||||||
if mpaa in ("NR", "UR"):
|
|
||||||
# Kodi seems to not like NR, but will accept Rated Not Rated
|
|
||||||
mpaa = "Rated Not Rated"
|
|
||||||
|
|
||||||
return mpaa
|
item = self.item
|
||||||
|
try:
|
||||||
|
filepath = item['Path']
|
||||||
|
|
||||||
def getAllArtwork(self, item, parentInfo = False):
|
except KeyError:
|
||||||
|
filepath = ""
|
||||||
|
|
||||||
"""
|
else:
|
||||||
Get all artwork, it will return an empty string
|
if "\\\\" in filepath:
|
||||||
for the artwork type not found.
|
# append smb protocol
|
||||||
|
filepath = filepath.replace("\\\\", "smb://")
|
||||||
|
filepath = filepath.replace("\\", "/")
|
||||||
|
|
||||||
Artwork type: Primary, Art, Banner, Logo, Thumb,
|
if item.get('VideoType'):
|
||||||
Disc, Backdrop
|
videotype = item['VideoType']
|
||||||
"""
|
# Specific format modification
|
||||||
|
if 'Dvd'in videotype:
|
||||||
username = utils.window('currUser')
|
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
|
||||||
server = utils.window('server%s' % username)
|
elif 'Bluray' in videotype:
|
||||||
|
filepath = "%s/BDMV/index.bdmv" % filepath
|
||||||
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 "\\" in filepath:
|
||||||
if not allartworks['Backdrop']:
|
# Local path scenario, with special videotype
|
||||||
|
filepath = filepath.replace("/", "\\")
|
||||||
parentId = item.get('ParentBackdropItemId')
|
|
||||||
if parentId:
|
|
||||||
# If there is a parentId, go through the parent backdrop list
|
|
||||||
parentbackdrops = item['ParentBackdropImageTags']
|
|
||||||
|
|
||||||
backdropIndex = 0
|
return filepath
|
||||||
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
|
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
from uuid import uuid4 as uuid4
|
|
||||||
from Lock import Lock
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmcgui
|
|
||||||
|
|
||||||
import Utils as utils
|
|
||||||
|
|
||||||
|
|
||||||
class ClientInformation():
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.addon = xbmcaddon.Addon()
|
|
||||||
self.addonName = self.getAddonName()
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
|
||||||
|
|
||||||
className = self.__class__.__name__
|
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
|
||||||
|
|
||||||
def getAddonId(self):
|
|
||||||
# To use when declaring xbmcaddon.Addon(id=addonId)
|
|
||||||
return "plugin.video.emby"
|
|
||||||
|
|
||||||
def getAddonName(self):
|
|
||||||
# Useful for logging
|
|
||||||
return self.addon.getAddonInfo('name').upper()
|
|
||||||
|
|
||||||
def getVersion(self):
|
|
||||||
|
|
||||||
return self.addon.getAddonInfo('version')
|
|
||||||
|
|
||||||
def getDeviceName(self):
|
|
||||||
|
|
||||||
if utils.settings('deviceNameOpt') == "false":
|
|
||||||
# Use Kodi's deviceName
|
|
||||||
deviceName = xbmc.getInfoLabel('System.FriendlyName')
|
|
||||||
else:
|
|
||||||
deviceName = utils.settings('deviceName')
|
|
||||||
deviceName = deviceName.replace("\"", "_")
|
|
||||||
deviceName = deviceName.replace("/", "_")
|
|
||||||
|
|
||||||
return deviceName
|
|
||||||
|
|
||||||
def getMachineId(self):
|
|
||||||
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
|
|
||||||
clientId = WINDOW.getProperty("client_id")
|
|
||||||
if clientId:
|
|
||||||
return clientId
|
|
||||||
|
|
||||||
# we need to load and or generate a client machine id
|
|
||||||
addon = self.addon
|
|
||||||
addondir = addon.getAddonInfo('path').decode('utf-8')
|
|
||||||
machine_guid_lock_path = xbmc.translatePath(os.path.join(addondir, "machine_guid.lock")).decode('utf-8')
|
|
||||||
machine_guid_path = xbmc.translatePath(os.path.join(addondir, "machine_guid")).decode('utf-8')
|
|
||||||
clientId = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
lock = Lock(machine_guid_lock_path)
|
|
||||||
locked = lock.acquire()
|
|
||||||
|
|
||||||
if locked:
|
|
||||||
|
|
||||||
fd = os.open(machine_guid_path, os.O_CREAT|os.O_RDWR)
|
|
||||||
clientId = os.read(fd, 256)
|
|
||||||
|
|
||||||
if len(clientId) == 0:
|
|
||||||
uuid = uuid4()
|
|
||||||
clientId = str("%012X" % uuid)
|
|
||||||
self.logMsg("ClientId saved to FILE: %s" % clientId, 2)
|
|
||||||
os.write(fd, clientId)
|
|
||||||
os.fsync(fd)
|
|
||||||
|
|
||||||
os.close(fd)
|
|
||||||
|
|
||||||
self.logMsg("ClientId saved to WINDOW: %s" % clientId, 1)
|
|
||||||
WINDOW.setProperty("client_id", clientId)
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
return clientId
|
|
||||||
|
|
||||||
def getPlatform(self):
|
|
||||||
|
|
||||||
if xbmc.getCondVisibility('system.platform.osx'):
|
|
||||||
return "OSX"
|
|
||||||
elif xbmc.getCondVisibility('system.platform.atv2'):
|
|
||||||
return "ATV2"
|
|
||||||
elif xbmc.getCondVisibility('system.platform.ios'):
|
|
||||||
return "iOS"
|
|
||||||
elif xbmc.getCondVisibility('system.platform.windows'):
|
|
||||||
return "Windows"
|
|
||||||
elif xbmc.getCondVisibility('system.platform.linux'):
|
|
||||||
return "Linux/RPi"
|
|
||||||
elif xbmc.getCondVisibility('system.platform.android'):
|
|
||||||
return "Linux/Android"
|
|
||||||
|
|
||||||
return "Unknown"
|
|
|
@ -1,161 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# connection manager class
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import json
|
|
||||||
import socket
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
|
||||||
|
|
||||||
import Utils as utils
|
|
||||||
from ClientInformation import ClientInformation
|
|
||||||
from DownloadUtils import DownloadUtils
|
|
||||||
from UserClient import UserClient
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionManager():
|
|
||||||
|
|
||||||
clientInfo = ClientInformation()
|
|
||||||
user = UserClient()
|
|
||||||
doUtils = DownloadUtils()
|
|
||||||
|
|
||||||
addonName = clientInfo.getAddonName()
|
|
||||||
addonId = clientInfo.getAddonId()
|
|
||||||
addon = xbmcaddon.Addon()
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.__language__ = self.addon.getLocalizedString
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
|
||||||
|
|
||||||
className = self.__class__.__name__
|
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
|
||||||
|
|
||||||
def checkServer(self):
|
|
||||||
|
|
||||||
self.WINDOW.setProperty("Server_Checked", "True")
|
|
||||||
self.logMsg("Connection Manager Called", 2)
|
|
||||||
|
|
||||||
server = self.user.getServer()
|
|
||||||
|
|
||||||
if server != "":
|
|
||||||
self.logMsg("Server already set", 2)
|
|
||||||
return
|
|
||||||
|
|
||||||
serverInfo = self.getServerDetails()
|
|
||||||
|
|
||||||
try:
|
|
||||||
prefix,ip,port = serverInfo.split(":")
|
|
||||||
setServer = xbmcgui.Dialog().yesno(self.__language__(30167), "Proceed with the following server?", self.__language__(30169) + serverInfo)
|
|
||||||
except: # serverInfo is None
|
|
||||||
self.logMsg("getServerDetails failed", 1)
|
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
|
||||||
return
|
|
||||||
|
|
||||||
if setServer == 1:
|
|
||||||
self.logMsg("Server selected. Saving information.", 1)
|
|
||||||
utils.settings("ipaddress", ip.replace("/", ""))
|
|
||||||
utils.settings("port", port)
|
|
||||||
# If https, enable the setting
|
|
||||||
if (prefix == 'https'):
|
|
||||||
utils.settings('https', "true")
|
|
||||||
else:
|
|
||||||
self.logMsg("No server selected.", 1)
|
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get List of public users
|
|
||||||
self.logMsg("Getting user list", 1)
|
|
||||||
server = "%s:%s" % (ip.replace("/", ""), port)
|
|
||||||
url = "%s/mediabrowser/Users/Public?format=json" % serverInfo
|
|
||||||
|
|
||||||
result = self.doUtils.downloadUrl(url, authenticate=False)
|
|
||||||
if result == "":
|
|
||||||
self.logMsg("Unable to connect to %s." % server, 1)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.logMsg("Result: %s" % result, 2)
|
|
||||||
|
|
||||||
# Process the list returned
|
|
||||||
names = []
|
|
||||||
userList = []
|
|
||||||
for user in result:
|
|
||||||
name = user['Name']
|
|
||||||
userList.append(name)
|
|
||||||
|
|
||||||
if user['HasPassword']:
|
|
||||||
name = "%s (Secure)" % name
|
|
||||||
names.append(name)
|
|
||||||
|
|
||||||
self.logMsg("User list: %s" % names, 1)
|
|
||||||
resp = xbmcgui.Dialog().select(self.__language__(30200), names)
|
|
||||||
if resp > -1:
|
|
||||||
selected_user = userList[resp]
|
|
||||||
self.logMsg("Selected User: %s" % selected_user, 1)
|
|
||||||
utils.settings("username", selected_user)
|
|
||||||
else:
|
|
||||||
self.logMsg("No user selected.", 1)
|
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
|
||||||
return
|
|
||||||
|
|
||||||
musicDisabled = xbmcgui.Dialog().yesno("Music Setting", "Disable music library?")
|
|
||||||
if musicDisabled:
|
|
||||||
self.logMsg("User opted to disable music library.", 1)
|
|
||||||
utils.settings('enableMusicSync', "false")
|
|
||||||
else:
|
|
||||||
# Music is enabled, prompt for direct stream
|
|
||||||
musicPath = xbmcgui.Dialog().yesno("Music Setting", "Direct stream the music library?", "Select this option only if you plan on listening to music outside your network.")
|
|
||||||
if musicPath:
|
|
||||||
self.logMsg("User option to direct stream music library.", 1)
|
|
||||||
utils.settings('directstreammusic', "true")
|
|
||||||
|
|
||||||
directPaths = xbmcgui.Dialog().yesno(
|
|
||||||
heading="Direct paths",
|
|
||||||
line1=(
|
|
||||||
"Use direct paths? Caution! If you choose yes, you "
|
|
||||||
"will lose access to certain Emby features such as: "
|
|
||||||
"Emby cinema mode, direct stream/transcode options, "
|
|
||||||
"parental access schedule."))
|
|
||||||
if directPaths:
|
|
||||||
self.logMsg("User opted to use direct paths.", 1)
|
|
||||||
utils.settings('useDirectPaths', "true")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def getServerDetails(self):
|
|
||||||
|
|
||||||
self.logMsg("Getting Server Details from Network")
|
|
||||||
|
|
||||||
MULTI_GROUP = ("<broadcast>", 7359)
|
|
||||||
MESSAGE = "who is EmbyServer?"
|
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
sock.settimeout(6.0)
|
|
||||||
|
|
||||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
|
|
||||||
|
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
||||||
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
|
|
||||||
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
|
|
||||||
|
|
||||||
self.logMsg("MutliGroup : %s" % str(MULTI_GROUP), 2);
|
|
||||||
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
|
|
||||||
sock.sendto(MESSAGE, MULTI_GROUP)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
|
|
||||||
self.logMsg("Received Response: %s" % data)
|
|
||||||
# Get the address
|
|
||||||
data = json.loads(data)
|
|
||||||
return data['Address']
|
|
||||||
except:
|
|
||||||
self.logMsg("No UDP Response")
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
|
|
@ -1,40 +0,0 @@
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import errno
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
class Lock:
|
|
||||||
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.filename = filename
|
|
||||||
self.delay = 0.5
|
|
||||||
self.timeout = 10
|
|
||||||
self.is_locked = False
|
|
||||||
self.fd = None
|
|
||||||
|
|
||||||
def acquire(self):
|
|
||||||
start_time = time.time()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self.fd = os.open(self.filename, os.O_CREAT|os.O_RDWR|os.O_EXCL)
|
|
||||||
break;
|
|
||||||
except OSError as e:
|
|
||||||
if (time.time() - start_time) >= self.timeout:
|
|
||||||
xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " timed out")
|
|
||||||
return False
|
|
||||||
#xbmc.log("File_Lock_On " + self.filename + " error " + str(e))
|
|
||||||
time.sleep(self.delay)
|
|
||||||
self.is_locked = True
|
|
||||||
xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " obtained")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if self.is_locked:
|
|
||||||
os.close(self.fd)
|
|
||||||
os.unlink(self.filename)
|
|
||||||
self.is_locked = False
|
|
||||||
xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " released")
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.release()
|
|
||||||
|
|
|
@ -1,386 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# ReadEmbyDB
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
from DownloadUtils import DownloadUtils
|
|
||||||
|
|
||||||
|
|
||||||
class ReadEmbyDB():
|
|
||||||
|
|
||||||
doUtils = DownloadUtils()
|
|
||||||
urllimit = 50
|
|
||||||
|
|
||||||
def filterbyId(self, result, itemList = []):
|
|
||||||
|
|
||||||
newResult = []
|
|
||||||
for item in result:
|
|
||||||
if item['Id'] in itemList:
|
|
||||||
newResult.append(item)
|
|
||||||
|
|
||||||
return newResult
|
|
||||||
|
|
||||||
def getMovies(self, parentId, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
# Only get basic info for our sync-compares
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Movie&CollapseBoxSetItems=false&ImageTypeLimit=1&format=json" % parentId
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicVideos(self, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
# Only get basic info for our sync-compares
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/items?&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=MusicVideo&CollapseBoxSetItems=false&ImageTypeLimit=1&format=json"
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicArtists(self, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
# Only get basic info for our sync-compares
|
|
||||||
url = "{server}/Artists?Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json"
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicArtistsTotal(self):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
url = "{server}/Artists?Limit=1&Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json"
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
|
|
||||||
total = jsondata['TotalRecordCount']
|
|
||||||
index = 1
|
|
||||||
jump = 200
|
|
||||||
|
|
||||||
while index < total:
|
|
||||||
url = "{server}/Artists?StartIndex=%s&Limit=%s&Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json" % (index, jump)
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
result.extend(jsondata['Items'])
|
|
||||||
index += jump
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicSongs(self, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
# Only get basic info for our sync-compares
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json"
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicSongsTotal(self):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?Index=1&Limit=1&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json"
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
|
|
||||||
total = jsondata['TotalRecordCount']
|
|
||||||
index = 1
|
|
||||||
jump = 200
|
|
||||||
|
|
||||||
while index < total:
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json" % (index, jump)
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
result.extend(jsondata['Items'])
|
|
||||||
index += jump
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicAlbums(self, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
# Only get basic info for our sync-compares
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=MusicAlbum&format=json"
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
tempresult = []
|
|
||||||
# Only return valid albums - which have artists
|
|
||||||
for item in result:
|
|
||||||
if item['AlbumArtists']:
|
|
||||||
tempresult.append(item)
|
|
||||||
result = tempresult
|
|
||||||
# If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMusicAlbumsTotal(self):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?Limit=1&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=MusicAlbum&format=json"
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
|
|
||||||
total = jsondata['TotalRecordCount']
|
|
||||||
index = 1
|
|
||||||
jump = 200
|
|
||||||
|
|
||||||
while index < total:
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&SortBy=DateCreated&Recursive=true&IncludeItemTypes=MusicAlbum&format=json" % (index, jump)
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
|
|
||||||
#tempresult = []
|
|
||||||
# Only return valid albums - which have artists
|
|
||||||
'''for item in jsondata['Items']:
|
|
||||||
if item['AlbumArtists']:
|
|
||||||
tempresult.append(item)
|
|
||||||
|
|
||||||
result.extend(tempresult)'''
|
|
||||||
result.extend(jsondata['Items'])
|
|
||||||
index += jump
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getTvShows(self, parentId, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
# Only get basic info for our sync-compares
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Series&format=json&ImageTypeLimit=1" % parentId
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getTVShowSeasons(self, tvShowId):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
url = "{server}/Shows/%s/Seasons?UserId={UserId}&format=json&ImageTypeLimit=1" % tvShowId
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
if jsondata:
|
|
||||||
result = jsondata['Items']
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getEpisodes(self, showId, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
lenlist = len(itemList) < self.urllimit
|
|
||||||
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&IsVirtualUnaired=false&IsMissing=False&SortBy=SortName&Fields=Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % showId
|
|
||||||
if itemList and lenlist:
|
|
||||||
url = "%s&Ids=%s" % (url, ",".join(itemList))
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else: # If list was longer than 49 items, we pulled the entire list so we need to sort
|
|
||||||
if not lenlist:
|
|
||||||
result = self.filterbyId(result, itemList)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getLatestEpisodes(self, fullinfo = False, itemList = []):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
limitString = "Limit=20&SortBy=DateCreated&"
|
|
||||||
if itemList: # if we want a certain list specify it
|
|
||||||
limitString = "Ids=%s&" % ",".join(itemList)
|
|
||||||
|
|
||||||
if fullinfo:
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?%sIsVirtualUnaired=false&IsMissing=False&Fields=ParentId,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % limitString
|
|
||||||
else:
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?%sIsVirtualUnaired=false&IsMissing=False&Fields=ParentId,Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % limitString
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
if jsondata:
|
|
||||||
result = jsondata['Items']
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getItem(self, id):
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1&Fields=Etag" % id
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
if jsondata:
|
|
||||||
result = jsondata
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getFullItem(self, id):
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&Fields=Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview,CriticRating,CriticRatingSummary" % id
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
if jsondata:
|
|
||||||
result = jsondata
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getCollections(self, type):
|
|
||||||
# Build a list of the user views
|
|
||||||
collections = []
|
|
||||||
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Views?format=json" #Items?Sortby=SortName&format=json"
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = jsondata['Items']
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
for item in result:
|
|
||||||
|
|
||||||
name = item['Name']
|
|
||||||
contentType = item['Type']
|
|
||||||
itemtype = item.get('CollectionType')
|
|
||||||
content = itemtype
|
|
||||||
|
|
||||||
if contentType == "Channel":
|
|
||||||
# Ignore channel type otherwise, they get processed as mixed content
|
|
||||||
continue
|
|
||||||
|
|
||||||
if itemtype is None and type in ("movies", "tvshows"):
|
|
||||||
# Mixed content or rich presentation is disabled
|
|
||||||
itemtype = type
|
|
||||||
content = "mixed"
|
|
||||||
|
|
||||||
if itemtype == type and name not in ("Collections", "Trailers"):
|
|
||||||
collections.append({
|
|
||||||
|
|
||||||
'title': name,
|
|
||||||
'type': itemtype,
|
|
||||||
'id': item['Id'],
|
|
||||||
'content': content
|
|
||||||
})
|
|
||||||
|
|
||||||
return collections
|
|
||||||
|
|
||||||
def getBoxSets(self):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?SortBy=SortName&IsVirtualUnaired=false&IsMissing=False&Fields=Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Ascending&IncludeItemTypes=BoxSet&format=json&ImageTypeLimit=1"
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
if jsondata:
|
|
||||||
result = jsondata['Items']
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getMoviesInBoxSet(self, boxsetId):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&Fields=ItemCounts,Etag&format=json&ImageTypeLimit=1" % boxsetId
|
|
||||||
|
|
||||||
jsondata = self.doUtils.downloadUrl(url)
|
|
||||||
if jsondata:
|
|
||||||
result = jsondata['Items']
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# This is not being used.
|
|
||||||
# To be removed?
|
|
||||||
|
|
||||||
def getViewCollections(self, type):
|
|
||||||
#Build a list of the user views
|
|
||||||
doUtils = DownloadUtils()
|
|
||||||
|
|
||||||
viewsUrl = "{server}/mediabrowser/Users/{UserId}/Views?format=json&ImageTypeLimit=1"
|
|
||||||
result = doUtils.downloadUrl(viewsUrl)
|
|
||||||
collections=[]
|
|
||||||
|
|
||||||
if (result == ""):
|
|
||||||
return []
|
|
||||||
|
|
||||||
result = result[u'Items']
|
|
||||||
|
|
||||||
for view in result:
|
|
||||||
if (view[u'Type'] == 'UserView'): # Need to grab the real main node
|
|
||||||
newViewsUrl = "{server}/mediabrowser/Users/{UserId}/items?ParentId=%s&SortBy=SortName&SortOrder=Ascending&format=json&ImageTypeLimit=1" % view[u'Id']
|
|
||||||
newViews = doUtils.downloadUrl(newViewsUrl)
|
|
||||||
if (result == ""):
|
|
||||||
return []
|
|
||||||
newViews = newViews[u'Items']
|
|
||||||
for newView in newViews:
|
|
||||||
# There are multiple nodes in here like 'Latest', 'NextUp' - below we grab the full node.
|
|
||||||
if newView[u'CollectionType'] != None:
|
|
||||||
if newView[u'CollectionType'] == "MovieMovies" or newView[u'CollectionType'] == "TvShowSeries":
|
|
||||||
view=newView
|
|
||||||
if (view[u'ChildCount'] != 0):
|
|
||||||
Name = view[u'Name']
|
|
||||||
|
|
||||||
total = str(view[u'ChildCount'])
|
|
||||||
try:
|
|
||||||
itemtype = view[u'CollectionType']
|
|
||||||
except:
|
|
||||||
itemtype = "movies"
|
|
||||||
if itemtype == "MovieMovies":
|
|
||||||
itemtype = "movies"
|
|
||||||
if itemtype == "TvShowSeries":
|
|
||||||
itemtype = "tvshows"
|
|
||||||
if itemtype == type:
|
|
||||||
collections.append( {'title' : Name,
|
|
||||||
'type' : type,
|
|
||||||
'id' : view[u'Id']})
|
|
||||||
return collections
|
|
|
@ -1,132 +0,0 @@
|
||||||
#################################################################################################
|
|
||||||
# ReadKodiDB
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
import Utils as utils
|
|
||||||
|
|
||||||
class ReadKodiDB():
|
|
||||||
|
|
||||||
|
|
||||||
def getKodiMovies(self, connection, cursor):
|
|
||||||
#returns all movies in Kodi db
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='movie'")
|
|
||||||
allmovies = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allmovies
|
|
||||||
|
|
||||||
def getKodiMusicVideos(self, connection, cursor):
|
|
||||||
#returns all musicvideos in Kodi db
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='musicvideo'")
|
|
||||||
allvideos = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allvideos
|
|
||||||
|
|
||||||
def getKodiTvShows(self, connection, cursor):
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='tvshow'")
|
|
||||||
allshows = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allshows
|
|
||||||
|
|
||||||
def getKodiEpisodes(self, connection, cursor, showid=None):
|
|
||||||
|
|
||||||
if showid == None:
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type=?",("episode",))
|
|
||||||
else:
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type=? AND parent_id=?",("episode", showid))
|
|
||||||
|
|
||||||
allepisodes = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allepisodes
|
|
||||||
|
|
||||||
def getEmbyIdByKodiId(self, id, type, connection=None, cursor=None):
|
|
||||||
if not connection:
|
|
||||||
connection = utils.KodiSQL()
|
|
||||||
cursor = connection.cursor()
|
|
||||||
closeCon = True
|
|
||||||
else:
|
|
||||||
closeCon = False
|
|
||||||
cursor.execute("SELECT emby_id FROM emby WHERE media_type=? AND kodi_id=?",(type,id))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
if closeCon:
|
|
||||||
connection.close()
|
|
||||||
if result:
|
|
||||||
return result[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getShowIdByEmbyId(self, id, connection=None, cursor=None):
|
|
||||||
if not connection:
|
|
||||||
connection = utils.KodiSQL()
|
|
||||||
cursor = connection.cursor()
|
|
||||||
closeCon = True
|
|
||||||
else:
|
|
||||||
closeCon = False
|
|
||||||
cursor.execute("SELECT parent_id FROM emby WHERE emby_id=?",(id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
if closeCon:
|
|
||||||
connection.close()
|
|
||||||
if result:
|
|
||||||
return result[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getTypeByEmbyId(self, id, connection=None, cursor=None):
|
|
||||||
if not connection:
|
|
||||||
connection = utils.KodiSQL()
|
|
||||||
cursor = connection.cursor()
|
|
||||||
closeCon = True
|
|
||||||
else:
|
|
||||||
closeCon = False
|
|
||||||
cursor.execute("SELECT media_type FROM emby WHERE emby_id=?",(id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
if closeCon:
|
|
||||||
connection.close()
|
|
||||||
if result:
|
|
||||||
return result[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getShowTotalCount(self, id, connection=None, cursor=None):
|
|
||||||
if not connection:
|
|
||||||
connection = utils.KodiSQL()
|
|
||||||
cursor = connection.cursor()
|
|
||||||
closeCon = True
|
|
||||||
else:
|
|
||||||
closeCon = False
|
|
||||||
command = "SELECT totalCount FROM tvshowcounts WHERE idShow=%s" % str(id)
|
|
||||||
cursor.execute(command)
|
|
||||||
result = cursor.fetchone()
|
|
||||||
if closeCon:
|
|
||||||
connection.close()
|
|
||||||
if result:
|
|
||||||
return result[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getKodiMusicArtists(self, connection, cursor):
|
|
||||||
#returns all artists in Kodi db
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='artist'")
|
|
||||||
allartists = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allartists
|
|
||||||
|
|
||||||
def getKodiMusicAlbums(self, connection, cursor):
|
|
||||||
#returns all artists in Kodi db
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='album'")
|
|
||||||
allalbums = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allalbums
|
|
||||||
|
|
||||||
def getKodiMusicSongs(self, connection, cursor):
|
|
||||||
#returns all songs in Kodi db
|
|
||||||
cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='song'")
|
|
||||||
allsongs = cursor.fetchall()
|
|
||||||
#this will return a list with tuples of all items returned from the database
|
|
||||||
return allsongs
|
|
|
@ -1,236 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
import urllib
|
|
||||||
import os
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
import Utils as utils
|
|
||||||
from ClientInformation import ClientInformation
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
class TextureCache():
|
|
||||||
|
|
||||||
addonName = ClientInformation().getAddonName()
|
|
||||||
|
|
||||||
xbmc_host = 'localhost'
|
|
||||||
xbmc_port = None
|
|
||||||
xbmc_username = None
|
|
||||||
xbmc_password = None
|
|
||||||
enableTextureCache = utils.settings('enableTextureCache') == "true"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
if not self.xbmc_port and self.enableTextureCache:
|
|
||||||
self.setKodiWebServerDetails()
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
|
||||||
|
|
||||||
className = self.__class__.__name__
|
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
|
||||||
|
|
||||||
def double_urlencode(self, text):
|
|
||||||
text = self.single_urlencode(text)
|
|
||||||
text = self.single_urlencode(text)
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def single_urlencode(self, text):
|
|
||||||
blah = urllib.urlencode({'blahblahblah':text})
|
|
||||||
blah = blah[13:]
|
|
||||||
|
|
||||||
return blah
|
|
||||||
|
|
||||||
def setKodiWebServerDetails(self):
|
|
||||||
# Get the Kodi webserver details - used to set the texture cache
|
|
||||||
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserver"}, "id":1}')
|
|
||||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
|
||||||
if(jsonobject.has_key('result')):
|
|
||||||
xbmc_webserver_enabled = jsonobject["result"]["value"]
|
|
||||||
|
|
||||||
if not xbmc_webserver_enabled:
|
|
||||||
#enable the webserver if not enabled
|
|
||||||
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserverport","value":8080}, "id":1}')
|
|
||||||
self.xbmc_port = 8080
|
|
||||||
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserver","value":true}, "id":1}')
|
|
||||||
self.xbmc_port = "kodi"
|
|
||||||
|
|
||||||
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverport"}, "id":1}')
|
|
||||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
|
||||||
if(jsonobject.has_key('result')):
|
|
||||||
self.xbmc_port = jsonobject["result"]["value"]
|
|
||||||
|
|
||||||
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverusername"}, "id":1}')
|
|
||||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
|
||||||
if(jsonobject.has_key('result')):
|
|
||||||
self.xbmc_username = jsonobject["result"]["value"]
|
|
||||||
|
|
||||||
json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverpassword"}, "id":1}')
|
|
||||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
|
||||||
if(jsonobject.has_key('result')):
|
|
||||||
self.xbmc_password = jsonobject["result"]["value"]
|
|
||||||
|
|
||||||
def FullTextureCacheSync(self):
|
|
||||||
#this method can be called from the plugin to sync all Kodi textures to the texture cache.
|
|
||||||
#Warning: this means that every image will be cached locally, this takes diskspace!
|
|
||||||
|
|
||||||
# Remove all existing textures first
|
|
||||||
path = "special://thumbnails/"
|
|
||||||
if xbmcvfs.exists(path):
|
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
|
||||||
for dir in allDirs:
|
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
|
||||||
for file in allFiles:
|
|
||||||
xbmcvfs.delete(os.path.join(path+dir,file))
|
|
||||||
|
|
||||||
textureconnection = utils.KodiSQL('texture')
|
|
||||||
texturecursor = textureconnection.cursor()
|
|
||||||
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
|
||||||
rows = texturecursor.fetchall()
|
|
||||||
for row in rows:
|
|
||||||
tableName = row[0]
|
|
||||||
if(tableName != "version"):
|
|
||||||
texturecursor.execute("DELETE FROM " + tableName)
|
|
||||||
textureconnection.commit()
|
|
||||||
texturecursor.close()
|
|
||||||
|
|
||||||
|
|
||||||
# Cache all entries in video DB
|
|
||||||
connection = utils.KodiSQL('video')
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT url FROM art")
|
|
||||||
result = cursor.fetchall()
|
|
||||||
for url in result:
|
|
||||||
self.CacheTexture(url[0])
|
|
||||||
cursor.close()
|
|
||||||
|
|
||||||
# Cache all entries in music DB
|
|
||||||
connection = utils.KodiSQL('music')
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT url FROM art")
|
|
||||||
result = cursor.fetchall()
|
|
||||||
for url in result:
|
|
||||||
self.CacheTexture(url[0])
|
|
||||||
cursor.close()
|
|
||||||
|
|
||||||
|
|
||||||
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
|
||||||
# Kodi conversion table
|
|
||||||
kodiart = {
|
|
||||||
|
|
||||||
'Primary': ["thumb", "poster"],
|
|
||||||
'Banner': "banner",
|
|
||||||
'Logo': "clearlogo",
|
|
||||||
'Art': "clearart",
|
|
||||||
'Thumb': "landscape",
|
|
||||||
'Disc': "discart",
|
|
||||||
'Backdrop': "fanart",
|
|
||||||
'BoxRear': "poster"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Artwork is a dictionary
|
|
||||||
for art in artwork:
|
|
||||||
|
|
||||||
if art == "Backdrop":
|
|
||||||
# Backdrop entry is a list, process extra fanart for artwork downloader (fanart, fanart1, fanart2, etc.)
|
|
||||||
backdrops = artwork[art]
|
|
||||||
backdropsNumber = len(backdrops)
|
|
||||||
|
|
||||||
cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", (kodiId, mediaType, "fanart%",))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
|
|
||||||
if len(rows) > backdropsNumber:
|
|
||||||
# More backdrops in database than what we are going to process. Delete extra fanart.
|
|
||||||
cursor.execute("DELETE FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", (kodiId, mediaType, "fanart_",))
|
|
||||||
|
|
||||||
index = ""
|
|
||||||
for backdrop in backdrops:
|
|
||||||
self.addOrUpdateArt(backdrop, kodiId, mediaType, "%s%s" % ("fanart", index), cursor)
|
|
||||||
if backdropsNumber > 1:
|
|
||||||
try: # Will only fail on the first try, str to int.
|
|
||||||
index += 1
|
|
||||||
except TypeError:
|
|
||||||
index = 1
|
|
||||||
|
|
||||||
elif art == "Primary":
|
|
||||||
# Primary art is processed as thumb and poster for Kodi.
|
|
||||||
for artType in kodiart[art]:
|
|
||||||
self.addOrUpdateArt(artwork[art], kodiId, mediaType, artType, cursor)
|
|
||||||
|
|
||||||
elif kodiart.get(art): # For banner, logo, art, thumb, disc
|
|
||||||
# Only process artwork type that Kodi can use
|
|
||||||
self.addOrUpdateArt(artwork[art], kodiId, mediaType, kodiart[art], cursor)
|
|
||||||
|
|
||||||
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
|
|
||||||
# Possible that the imageurl is an empty string
|
|
||||||
if imageUrl:
|
|
||||||
cacheimage = False
|
|
||||||
|
|
||||||
cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,))
|
|
||||||
try: # Update the artwork
|
|
||||||
url = cursor.fetchone()[0]
|
|
||||||
|
|
||||||
except: # Add the artwork
|
|
||||||
cacheimage = True
|
|
||||||
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
|
|
||||||
query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
|
||||||
|
|
||||||
else: # Only cache artwork if it changed
|
|
||||||
if url != imageUrl:
|
|
||||||
cacheimage = True
|
|
||||||
|
|
||||||
# Only for the main backdrop, poster
|
|
||||||
if imageType in ("fanart", "poster"):
|
|
||||||
# Delete current entry before updating with the new one
|
|
||||||
self.deleteCachedArtwork(url)
|
|
||||||
|
|
||||||
self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1)
|
|
||||||
query = "UPDATE art set url = ? WHERE media_id = ? AND media_type = ? AND type = ?"
|
|
||||||
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
|
||||||
|
|
||||||
# Cache fanart and poster in Kodi texture cache
|
|
||||||
if cacheimage and imageType in ("fanart", "poster"):
|
|
||||||
self.CacheTexture(imageUrl)
|
|
||||||
|
|
||||||
def CacheTexture(self, url):
|
|
||||||
# Cache a single image url to the texture cache
|
|
||||||
if url and self.enableTextureCache:
|
|
||||||
self.logMsg("Processing: %s" % url, 2)
|
|
||||||
|
|
||||||
# Add image to texture cache by simply calling it at the http endpoint
|
|
||||||
url = self.double_urlencode(url)
|
|
||||||
try: # Extreme short timeouts so we will have a exception, but we don't need the result so pass
|
|
||||||
response = requests.head('http://%s:%s/image/image://%s' % (self.xbmc_host, self.xbmc_port, url), auth=(self.xbmc_username, self.xbmc_password), timeout=(0.01, 0.01))
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
def deleteCachedArtwork(self, url):
|
|
||||||
# Only necessary to remove and apply a new backdrop or poster
|
|
||||||
connection = utils.KodiSQL('texture')
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
|
|
||||||
try:
|
|
||||||
cachedurl = cursor.fetchone()[0]
|
|
||||||
|
|
||||||
except:
|
|
||||||
self.logMsg("Could not find cached url.", 1)
|
|
||||||
|
|
||||||
else: # Delete thumbnail as well as the entry
|
|
||||||
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl)
|
|
||||||
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
|
|
||||||
xbmcvfs.delete(thumbnails)
|
|
||||||
try:
|
|
||||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
|
||||||
connection.commit()
|
|
||||||
except:
|
|
||||||
self.logMsg("Issue deleting url from cache. Skipping.", 2)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
cursor.close()
|
|
|
@ -1,78 +0,0 @@
|
||||||
|
|
||||||
import sys
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
|
||||||
import json as json
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
ACTION_BACK = 92
|
|
||||||
|
|
||||||
class UserPreferences(xbmcgui.WindowXMLDialog):
|
|
||||||
|
|
||||||
configuration = None
|
|
||||||
save = False
|
|
||||||
name = None
|
|
||||||
image = None
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def onInit(self):
|
|
||||||
self.action_exitkeys_id = [10, 13]
|
|
||||||
# set the dialog data
|
|
||||||
self.save = False
|
|
||||||
cinemaMode = self.configuration.get(u'EnableCinemaMode')
|
|
||||||
self.getControl(8011).setSelected(cinemaMode)
|
|
||||||
self.getControl(8001).setLabel(self.name)
|
|
||||||
self.getControl(8002).setImage(self.image)
|
|
||||||
def save(self):
|
|
||||||
self.save = True
|
|
||||||
|
|
||||||
def isSave(self):
|
|
||||||
return self.save
|
|
||||||
|
|
||||||
def setConfiguration(self, configuration):
|
|
||||||
self.configuration = configuration
|
|
||||||
|
|
||||||
def getConfiguration(self):
|
|
||||||
return self.configuration
|
|
||||||
|
|
||||||
def setName(self, name):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def setImage(self, image):
|
|
||||||
self.image = image
|
|
||||||
|
|
||||||
def onFocus(self, controlId):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def doAction(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def closeDialog(self):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def onClick(self, controlID):
|
|
||||||
|
|
||||||
if(controlID == 8012):
|
|
||||||
# save now
|
|
||||||
self.save = True
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
elif(controlID == 8013):
|
|
||||||
#cancel
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
if(controlID == 8011):
|
|
||||||
# cinema mode
|
|
||||||
cinemamode = self.getControl(8011).isSelected()
|
|
||||||
self.configuration['EnableCinemaMode'] = cinemamode
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onAction(self, action):
|
|
||||||
if action == ACTION_BACK:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
|
@ -1,305 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import json
|
|
||||||
import threading
|
|
||||||
import websocket
|
|
||||||
import logging
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# WebSocket Client thread
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
|
||||||
|
|
||||||
import KodiMonitor
|
|
||||||
import Utils as utils
|
|
||||||
from ClientInformation import ClientInformation
|
|
||||||
from DownloadUtils import DownloadUtils
|
|
||||||
from PlaybackUtils import PlaybackUtils
|
|
||||||
from LibrarySync import LibrarySync
|
|
||||||
from WriteKodiVideoDB import WriteKodiVideoDB
|
|
||||||
from WriteKodiMusicDB import WriteKodiMusicDB
|
|
||||||
|
|
||||||
logging.basicConfig()
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketThread(threading.Thread):
|
|
||||||
|
|
||||||
_shared_state = {}
|
|
||||||
|
|
||||||
doUtils = DownloadUtils()
|
|
||||||
clientInfo = ClientInformation()
|
|
||||||
KodiMonitor = KodiMonitor.Kodi_Monitor()
|
|
||||||
|
|
||||||
addonName = clientInfo.getAddonName()
|
|
||||||
|
|
||||||
client = None
|
|
||||||
keepRunning = True
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
|
||||||
threading.Thread.__init__(self, *args)
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
|
||||||
|
|
||||||
self.className = self.__class__.__name__
|
|
||||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
|
||||||
|
|
||||||
def sendProgressUpdate(self, data):
|
|
||||||
self.logMsg("sendProgressUpdate", 1)
|
|
||||||
if self.client:
|
|
||||||
try:
|
|
||||||
# Send progress update
|
|
||||||
messageData = {
|
|
||||||
'MessageType': "ReportPlaybackProgress",
|
|
||||||
'Data': data
|
|
||||||
}
|
|
||||||
messageString = json.dumps(messageData)
|
|
||||||
self.client.send(messageString)
|
|
||||||
self.logMsg("Message data: %s" % messageString, 2)
|
|
||||||
except Exception as e:
|
|
||||||
self.logMsg("Exception: %s" % e, 1)
|
|
||||||
|
|
||||||
def stopClient(self):
|
|
||||||
# stopping the client is tricky, first set keep_running to false and then trigger one
|
|
||||||
# more message by requesting one SessionsStart message, this causes the
|
|
||||||
# client to receive the message and then exit
|
|
||||||
if self.client:
|
|
||||||
self.logMsg("Stopping Client")
|
|
||||||
self.keepRunning = False
|
|
||||||
self.client.keep_running = False
|
|
||||||
self.client.close()
|
|
||||||
self.logMsg("Stopping Client : KeepRunning set to False")
|
|
||||||
else:
|
|
||||||
self.logMsg("Stopping Client NO Object ERROR")
|
|
||||||
|
|
||||||
def on_message(self, ws, message):
|
|
||||||
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
self.logMsg("Message: %s" % message, 1)
|
|
||||||
|
|
||||||
result = json.loads(message)
|
|
||||||
messageType = result['MessageType']
|
|
||||||
data = result.get("Data")
|
|
||||||
|
|
||||||
if messageType == "Play":
|
|
||||||
# A remote control play command has been sent from the server.
|
|
||||||
itemIds = data['ItemIds']
|
|
||||||
playCommand = data['PlayCommand']
|
|
||||||
|
|
||||||
if "PlayNow" in playCommand:
|
|
||||||
startPositionTicks = data.get('StartPositionTicks', 0)
|
|
||||||
xbmc.executebuiltin("Dialog.Close(all,true)")
|
|
||||||
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
|
|
||||||
PlaybackUtils().PLAYAllItems(itemIds, startPositionTicks)
|
|
||||||
|
|
||||||
elif "PlayNext" in playCommand:
|
|
||||||
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
|
|
||||||
playlist = PlaybackUtils().AddToPlaylist(itemIds)
|
|
||||||
if not xbmc.Player().isPlaying():
|
|
||||||
xbmc.Player().play(playlist)
|
|
||||||
|
|
||||||
elif messageType == "Playstate":
|
|
||||||
# A remote control update playstate command has been sent from the server.
|
|
||||||
command = data['Command']
|
|
||||||
|
|
||||||
if "Stop" in command:
|
|
||||||
self.logMsg("Playback Stopped.", 1)
|
|
||||||
xbmc.Player().stop()
|
|
||||||
elif "Unpause" in command:
|
|
||||||
self.logMsg("Playback unpaused.", 1)
|
|
||||||
xbmc.Player().pause()
|
|
||||||
elif "Pause" in command:
|
|
||||||
self.logMsg("Playback paused.", 1)
|
|
||||||
xbmc.Player().pause()
|
|
||||||
elif "NextTrack" in command:
|
|
||||||
self.logMsg("Playback next track.", 1)
|
|
||||||
xbmc.Player().playnext()
|
|
||||||
elif "PreviousTrack" in command:
|
|
||||||
self.logMsg("Playback previous track.", 1)
|
|
||||||
xbmc.Player().playprevious()
|
|
||||||
elif "Seek" in command:
|
|
||||||
seekPositionTicks = data['SeekPositionTicks']
|
|
||||||
seekTime = seekPositionTicks / 10000000.0
|
|
||||||
self.logMsg("Seek to %s" % seekTime, 1)
|
|
||||||
xbmc.Player().seekTime(seekTime)
|
|
||||||
# Report playback
|
|
||||||
WINDOW.setProperty('commandUpdate', "true")
|
|
||||||
|
|
||||||
elif messageType == "UserDataChanged":
|
|
||||||
# A user changed their personal rating for an item, or their playstate was updated
|
|
||||||
userdataList = data['UserDataList']
|
|
||||||
self.logMsg("Message: Doing UserDataChanged: UserDataList: %s" % userdataList, 1)
|
|
||||||
LibrarySync().user_data_update(userdataList)
|
|
||||||
|
|
||||||
elif messageType == "LibraryChanged":
|
|
||||||
# Library items
|
|
||||||
itemsRemoved = data.get("ItemsRemoved")
|
|
||||||
itemsAdded = data.get("ItemsAdded")
|
|
||||||
itemsUpdated = data.get("ItemsUpdated")
|
|
||||||
itemsToUpdate = itemsAdded + itemsUpdated
|
|
||||||
|
|
||||||
self.logMsg("Message: WebSocket LibraryChanged: Items Added: %s" % itemsAdded, 1)
|
|
||||||
self.logMsg("Message: WebSocket LibraryChanged: Items Updated: %s" % itemsUpdated, 1)
|
|
||||||
self.logMsg("Message: WebSocket LibraryChanged: Items Removed: %s" % itemsRemoved, 1)
|
|
||||||
|
|
||||||
LibrarySync().remove_items(itemsRemoved)
|
|
||||||
LibrarySync().update_items(itemsToUpdate)
|
|
||||||
|
|
||||||
elif messageType == "GeneralCommand":
|
|
||||||
|
|
||||||
command = data['Name']
|
|
||||||
arguments = data.get("Arguments")
|
|
||||||
|
|
||||||
if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
|
|
||||||
# These commands need to be reported back
|
|
||||||
if command == "Mute":
|
|
||||||
xbmc.executebuiltin('Mute')
|
|
||||||
elif command == "Unmute":
|
|
||||||
xbmc.executebuiltin('Mute')
|
|
||||||
elif command == "SetVolume":
|
|
||||||
volume = arguments['Volume']
|
|
||||||
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
|
|
||||||
elif command == "SetSubtitleStreamIndex":
|
|
||||||
# Emby merges audio and subtitle index together
|
|
||||||
xbmcplayer = xbmc.Player()
|
|
||||||
currentFile = xbmcplayer.getPlayingFile()
|
|
||||||
embyIndex = int(arguments['Index'])
|
|
||||||
|
|
||||||
mapping = WINDOW.getProperty("%sIndexMapping" % currentFile)
|
|
||||||
externalIndex = json.loads(mapping)
|
|
||||||
|
|
||||||
if externalIndex:
|
|
||||||
# If there's external subtitles added via PlaybackUtils
|
|
||||||
for index in externalIndex:
|
|
||||||
if externalIndex[index] == embyIndex:
|
|
||||||
xbmcplayer.setSubtitleStream(int(index))
|
|
||||||
else:
|
|
||||||
# User selected internal subtitles
|
|
||||||
external = len(externalIndex)
|
|
||||||
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
|
|
||||||
xbmcplayer.setSubtitleStream(external + embyIndex - audioTracks - 1)
|
|
||||||
else:
|
|
||||||
# Emby merges audio and subtitle index together
|
|
||||||
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
|
|
||||||
xbmcplayer.setSubtitleStream(index - audioTracks - 1)
|
|
||||||
|
|
||||||
elif command == "SetAudioStreamIndex":
|
|
||||||
index = int(arguments['Index'])
|
|
||||||
xbmc.Player().setAudioStream(index - 1)
|
|
||||||
# Report playback
|
|
||||||
WINDOW.setProperty('commandUpdate', "true")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# GUI commands
|
|
||||||
if command == "ToggleFullscreen":
|
|
||||||
xbmc.executebuiltin('Action(FullScreen)')
|
|
||||||
elif command == "ToggleOsdMenu":
|
|
||||||
xbmc.executebuiltin('Action(OSD)')
|
|
||||||
elif command == "MoveUp":
|
|
||||||
xbmc.executebuiltin('Action(Up)')
|
|
||||||
elif command == "MoveDown":
|
|
||||||
xbmc.executebuiltin('Action(Down)')
|
|
||||||
elif command == "MoveLeft":
|
|
||||||
xbmc.executebuiltin('Action(Left)')
|
|
||||||
elif command == "MoveRight":
|
|
||||||
xbmc.executebuiltin('Action(Right)')
|
|
||||||
elif command == "Select":
|
|
||||||
xbmc.executebuiltin('Action(Select)')
|
|
||||||
elif command == "Back":
|
|
||||||
xbmc.executebuiltin('Action(back)')
|
|
||||||
elif command == "ToggleContextMenu":
|
|
||||||
xbmc.executebuiltin('Action(ContextMenu)')
|
|
||||||
elif command == "GoHome":
|
|
||||||
xbmc.executebuiltin('ActivateWindow(Home)')
|
|
||||||
elif command == "PageUp":
|
|
||||||
xbmc.executebuiltin('Action(PageUp)')
|
|
||||||
elif command == "NextLetter":
|
|
||||||
xbmc.executebuiltin('Action(NextLetter)')
|
|
||||||
elif command == "GoToSearch":
|
|
||||||
xbmc.executebuiltin('VideoLibrary.Search')
|
|
||||||
elif command == "GoToSettings":
|
|
||||||
xbmc.executebuiltin('ActivateWindow(Settings)')
|
|
||||||
elif command == "PageDown":
|
|
||||||
xbmc.executebuiltin('Action(PageDown)')
|
|
||||||
elif command == "PreviousLetter":
|
|
||||||
xbmc.executebuiltin('Action(PrevLetter)')
|
|
||||||
elif command == "TakeScreenshot":
|
|
||||||
xbmc.executebuiltin('TakeScreenshot')
|
|
||||||
elif command == "ToggleMute":
|
|
||||||
xbmc.executebuiltin('Mute')
|
|
||||||
elif command == "VolumeUp":
|
|
||||||
xbmc.executebuiltin('Action(VolumeUp)')
|
|
||||||
elif command == "VolumeDown":
|
|
||||||
xbmc.executebuiltin('Action(VolumeDown)')
|
|
||||||
elif command == "DisplayMessage":
|
|
||||||
header = arguments['Header']
|
|
||||||
text = arguments['Text']
|
|
||||||
xbmcgui.Dialog().notification(header, text, icon="special://home/addons/plugin.video.emby/icon.png", time=4000)
|
|
||||||
elif command == "SendString":
|
|
||||||
string = arguments['String']
|
|
||||||
text = '{"jsonrpc": "2.0", "method": "Input.SendText", "params": { "text": "%s", "done": false }, "id": 0}' % string
|
|
||||||
result = xbmc.executeJSONRPC(text)
|
|
||||||
else:
|
|
||||||
self.logMsg("Unknown command.", 1)
|
|
||||||
|
|
||||||
elif messageType == "ServerRestarting":
|
|
||||||
if utils.settings('supressRestartMsg') == "true":
|
|
||||||
xbmcgui.Dialog().notification("Emby server", "Server is restarting.", icon="special://home/addons/plugin.video.emby/icon.png")
|
|
||||||
|
|
||||||
def on_error(self, ws, error):
|
|
||||||
if "10061" in str(error):
|
|
||||||
# Server is offline
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.logMsg("Error: %s" % error, 1)
|
|
||||||
#raise
|
|
||||||
|
|
||||||
def on_close(self, ws):
|
|
||||||
self.logMsg("Closed", 2)
|
|
||||||
|
|
||||||
def on_open(self, ws):
|
|
||||||
deviceId = ClientInformation().getMachineId()
|
|
||||||
self.doUtils.postCapabilities(deviceId)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
logLevel = int(WINDOW.getProperty('getLogLevel'))
|
|
||||||
username = WINDOW.getProperty('currUser')
|
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
|
||||||
token = WINDOW.getProperty('accessToken%s' % username)
|
|
||||||
deviceId = ClientInformation().getMachineId()
|
|
||||||
|
|
||||||
'''if (logLevel == 2):
|
|
||||||
websocket.enableTrace(True)'''
|
|
||||||
|
|
||||||
# Get the appropriate prefix for websocket
|
|
||||||
if "https" in server:
|
|
||||||
server = server.replace('https', 'wss')
|
|
||||||
else:
|
|
||||||
server = server.replace('http', 'ws')
|
|
||||||
|
|
||||||
websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
|
|
||||||
self.logMsg("websocket URL: %s" % websocketUrl)
|
|
||||||
|
|
||||||
self.client = websocket.WebSocketApp(websocketUrl,
|
|
||||||
on_message = self.on_message,
|
|
||||||
on_error = self.on_error,
|
|
||||||
on_close = self.on_close)
|
|
||||||
|
|
||||||
self.client.on_open = self.on_open
|
|
||||||
|
|
||||||
while self.keepRunning:
|
|
||||||
|
|
||||||
self.client.run_forever()
|
|
||||||
|
|
||||||
if self.keepRunning:
|
|
||||||
self.logMsg("Client Needs To Restart", 2)
|
|
||||||
if self.KodiMonitor.waitForAbort(5):
|
|
||||||
break
|
|
||||||
|
|
||||||
self.logMsg("Thread Exited", 1)
|
|
|
@ -1,518 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# WriteKodiMusicDB
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
from datetime import datetime
|
|
||||||
from ntpath import split as ntsplit
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
|
||||||
|
|
||||||
from ClientInformation import ClientInformation
|
|
||||||
import Utils as utils
|
|
||||||
from API import API
|
|
||||||
from PlayUtils import PlayUtils
|
|
||||||
from ReadKodiDB import ReadKodiDB
|
|
||||||
from ReadEmbyDB import ReadEmbyDB
|
|
||||||
from TextureCache import TextureCache
|
|
||||||
|
|
||||||
|
|
||||||
class WriteKodiMusicDB():
|
|
||||||
|
|
||||||
textureCache = TextureCache()
|
|
||||||
kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
|
||||||
|
|
||||||
addonName = ClientInformation().getAddonName()
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
username = utils.window('currUser')
|
|
||||||
self.userid = utils.window('userId%s' % username)
|
|
||||||
self.server = utils.window('server%s' % username)
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl = 1):
|
|
||||||
|
|
||||||
className = self.__class__.__name__
|
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
|
||||||
|
|
||||||
def addOrUpdateArtistToKodiLibrary(self, MBitem, connection, cursor):
|
|
||||||
|
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
|
||||||
# If the item doesn't exist, we'll add it to the database
|
|
||||||
kodiVersion = self.kodiversion
|
|
||||||
embyId = MBitem["Id"]
|
|
||||||
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,))
|
|
||||||
try:
|
|
||||||
artistid = cursor.fetchone()[0]
|
|
||||||
except:
|
|
||||||
artistid = None
|
|
||||||
|
|
||||||
##### The artist details #####
|
|
||||||
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
dateadded = API().getDateCreated(MBitem)
|
|
||||||
checksum = API().getChecksum(MBitem)
|
|
||||||
|
|
||||||
name = MBitem['Name']
|
|
||||||
musicBrainzId = API().getProvider(MBitem, "musicBrainzArtist")
|
|
||||||
genres = " / ".join(MBitem.get('Genres'))
|
|
||||||
bio = API().getOverview(MBitem)
|
|
||||||
|
|
||||||
# Associate artwork
|
|
||||||
artworks = API().getAllArtwork(MBitem, parentInfo=True)
|
|
||||||
thumb = artworks['Primary']
|
|
||||||
backdrops = artworks['Backdrop'] # List
|
|
||||||
|
|
||||||
if thumb:
|
|
||||||
thumb = "<thumb>%s</thumb>" % thumb
|
|
||||||
if backdrops:
|
|
||||||
fanart = "<fanart>%s</fanart>" % backdrops[0]
|
|
||||||
else:
|
|
||||||
fanart = ""
|
|
||||||
|
|
||||||
##### UPDATE THE ARTIST #####
|
|
||||||
if artistid:
|
|
||||||
self.logMsg("UPDATE artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1)
|
|
||||||
|
|
||||||
if kodiVersion == 16:
|
|
||||||
query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ? WHERE idArtist = ?"
|
|
||||||
cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, artistid))
|
|
||||||
else:
|
|
||||||
query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ?, dateadded = ? WHERE idArtist = ?"
|
|
||||||
cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded, artistid))
|
|
||||||
|
|
||||||
# Update the checksum in emby table
|
|
||||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
|
||||||
cursor.execute(query, (checksum, embyId))
|
|
||||||
|
|
||||||
##### OR ADD THE ARTIST #####
|
|
||||||
else:
|
|
||||||
self.logMsg("ADD artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1)
|
|
||||||
|
|
||||||
#safety checks: It looks like Emby supports the same artist multiple times in the database while Kodi doesn't allow that. In case that happens we just merge the artist in the Kodi database.
|
|
||||||
|
|
||||||
# Safety check 1: does the artist already exist?
|
|
||||||
cursor.execute("SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE", (name,))
|
|
||||||
try:
|
|
||||||
artistid = cursor.fetchone()[0]
|
|
||||||
self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1)
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
# Safety check 2: does the MusicBrainzArtistId already exist?
|
|
||||||
cursor.execute("SELECT idArtist FROM artist WHERE strMusicBrainzArtistID = ?", (musicBrainzId,))
|
|
||||||
try:
|
|
||||||
artistid = cursor.fetchone()[0]
|
|
||||||
self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1)
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
if not artistid:
|
|
||||||
# Create the artist
|
|
||||||
cursor.execute("select coalesce(max(idArtist),0) as artistid from artist")
|
|
||||||
artistid = cursor.fetchone()[0] + 1
|
|
||||||
if kodiVersion == 16:
|
|
||||||
query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped) values(?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped))
|
|
||||||
else:
|
|
||||||
query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded))
|
|
||||||
|
|
||||||
# Create the reference in emby table
|
|
||||||
query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (embyId, artistid, "artist", checksum))
|
|
||||||
|
|
||||||
# Update artwork
|
|
||||||
self.textureCache.addArtwork(artworks, artistid, "artist", cursor)
|
|
||||||
|
|
||||||
def addOrUpdateAlbumToKodiLibrary(self, MBitem, connection, cursor):
|
|
||||||
|
|
||||||
kodiVersion = self.kodiversion
|
|
||||||
|
|
||||||
embyId = MBitem["Id"]
|
|
||||||
|
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
|
||||||
# If the item doesn't exist, we'll add it to the database
|
|
||||||
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,))
|
|
||||||
try:
|
|
||||||
albumid = cursor.fetchone()[0]
|
|
||||||
except:
|
|
||||||
albumid = None
|
|
||||||
|
|
||||||
genres = MBitem.get('Genres')
|
|
||||||
|
|
||||||
##### The album details #####
|
|
||||||
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
dateadded = API().getDateCreated(MBitem)
|
|
||||||
checksum = API().getChecksum(MBitem)
|
|
||||||
|
|
||||||
name = MBitem['Name']
|
|
||||||
musicBrainzId = API().getProvider(MBitem, "musicBrainzAlbum")
|
|
||||||
year = MBitem.get('ProductionYear')
|
|
||||||
genre = " / ".join(genres)
|
|
||||||
bio = API().getOverview(MBitem)
|
|
||||||
|
|
||||||
MBartists = []
|
|
||||||
for item in MBitem['AlbumArtists']:
|
|
||||||
MBartists.append(item['Name'])
|
|
||||||
artists = " / ".join(MBartists)
|
|
||||||
|
|
||||||
# Associate the artwork
|
|
||||||
artworks = API().getAllArtwork(MBitem, parentInfo=True)
|
|
||||||
thumb = artworks['Primary']
|
|
||||||
if thumb:
|
|
||||||
thumb = "<thumb>%s</thumb>" % thumb
|
|
||||||
|
|
||||||
|
|
||||||
##### UPDATE THE ALBUM #####
|
|
||||||
if albumid:
|
|
||||||
self.logMsg("UPDATE album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
|
|
||||||
|
|
||||||
if kodiVersion == 15:
|
|
||||||
# Kodi Isengard
|
|
||||||
query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ? WHERE idAlbum = ?"
|
|
||||||
cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album", albumid))
|
|
||||||
elif kodiVersion == 16:
|
|
||||||
query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, strReleaseType = ? WHERE idAlbum = ?"
|
|
||||||
cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album", albumid))
|
|
||||||
else:
|
|
||||||
# Kodi Gotham and Helix
|
|
||||||
query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ? WHERE idAlbum = ?"
|
|
||||||
cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, albumid))
|
|
||||||
|
|
||||||
# Update the checksum in emby table
|
|
||||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
|
||||||
cursor.execute(query, (checksum, embyId))
|
|
||||||
|
|
||||||
##### OR ADD THE ALBUM #####
|
|
||||||
else:
|
|
||||||
self.logMsg("ADD album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
|
|
||||||
|
|
||||||
# Safety check: does the strMusicBrainzAlbumID already exist?
|
|
||||||
cursor.execute("SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?", (musicBrainzId,))
|
|
||||||
try:
|
|
||||||
albumid = cursor.fetchone()[0]
|
|
||||||
except:
|
|
||||||
# Create the album
|
|
||||||
cursor.execute("select coalesce(max(idAlbum),0) as albumid from album")
|
|
||||||
albumid = cursor.fetchone()[0] + 1
|
|
||||||
if kodiVersion == 15:
|
|
||||||
# Kodi Isengard
|
|
||||||
query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album"))
|
|
||||||
elif kodiVersion == 16:
|
|
||||||
query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album"))
|
|
||||||
else:
|
|
||||||
# Kodi Gotham and Helix
|
|
||||||
query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded))
|
|
||||||
|
|
||||||
# Create the reference in emby table
|
|
||||||
query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (embyId, albumid, "album", checksum))
|
|
||||||
|
|
||||||
|
|
||||||
# Add genres
|
|
||||||
self.AddGenresToMedia(albumid, genres, "album", cursor)
|
|
||||||
|
|
||||||
# Update artwork
|
|
||||||
if artworks['Primary']:
|
|
||||||
self.textureCache.addOrUpdateArt(artworks['Primary'], albumid, "album", "thumb", cursor)
|
|
||||||
artworks['Primary'] = ""
|
|
||||||
|
|
||||||
if artworks.get('BoxRear'):
|
|
||||||
self.textureCache.addOrUpdateArt(artworks['BoxRear'], albumid, "album", "poster", cursor)
|
|
||||||
artworks['BoxRear'] = ""
|
|
||||||
|
|
||||||
self.textureCache.addArtwork(artworks, albumid, "album", cursor)
|
|
||||||
|
|
||||||
# Link album to artists
|
|
||||||
if MBartists:
|
|
||||||
album_artists = MBitem['AlbumArtists']
|
|
||||||
else:
|
|
||||||
album_artists = MBitem.get('ArtistItems', [])
|
|
||||||
|
|
||||||
for artist in album_artists:
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],))
|
|
||||||
try:
|
|
||||||
artistid = cursor.fetchone()[0]
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)"
|
|
||||||
cursor.execute(query, (artistid, albumid, artist['Name']))
|
|
||||||
# Update discography
|
|
||||||
query = "INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) values(?, ?, ?)"
|
|
||||||
cursor.execute(query, (artistid, name, str(year)))
|
|
||||||
|
|
||||||
def addOrUpdateSongToKodiLibrary(self, MBitem, connection, cursor):
|
|
||||||
|
|
||||||
kodiVersion = self.kodiversion
|
|
||||||
|
|
||||||
embyId = MBitem["Id"]
|
|
||||||
|
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
|
||||||
# If the item doesn't exist, we'll add it to the database
|
|
||||||
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,))
|
|
||||||
try:
|
|
||||||
songid = cursor.fetchone()[0]
|
|
||||||
except:
|
|
||||||
songid = None
|
|
||||||
|
|
||||||
timeInfo = API().getTimeInfo(MBitem)
|
|
||||||
userData = API().getUserData(MBitem)
|
|
||||||
genres = MBitem.get('Genres')
|
|
||||||
|
|
||||||
##### The song details #####
|
|
||||||
playcount = userData.get('PlayCount')
|
|
||||||
lastplayed = userData.get('LastPlayedDate')
|
|
||||||
dateadded = API().getDateCreated(MBitem)
|
|
||||||
checksum = API().getChecksum(MBitem)
|
|
||||||
|
|
||||||
name = MBitem['Name']
|
|
||||||
musicBrainzId = API().getProvider(MBitem, "musicBrainzTrackId")
|
|
||||||
genre = " / ".join(genres)
|
|
||||||
artists = " / ".join(MBitem.get('Artists'))
|
|
||||||
tracknumber = MBitem.get('IndexNumber', 0)
|
|
||||||
disc = MBitem.get('ParentIndexNumber', 1)
|
|
||||||
track = disc*2**16 + tracknumber
|
|
||||||
year = MBitem.get('ProductionYear')
|
|
||||||
bio = API().getOverview(MBitem)
|
|
||||||
duration = timeInfo.get('TotalTime')
|
|
||||||
|
|
||||||
|
|
||||||
if utils.settings('directstreammusic') == "true":
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
username = WINDOW.getProperty('currUser')
|
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
|
||||||
|
|
||||||
playurl = PlayUtils().directStream(MBitem, server, embyId, "Audio")
|
|
||||||
filename = "stream.mp3"
|
|
||||||
path = playurl.replace(filename, "")
|
|
||||||
else:
|
|
||||||
# Get the path and filename
|
|
||||||
playurl = PlayUtils().directPlay(MBitem)
|
|
||||||
path, filename = ntsplit(playurl)
|
|
||||||
if "/" in playurl:
|
|
||||||
path = "%s/" % path
|
|
||||||
elif "\\" in playurl:
|
|
||||||
path = "%s\\" % path
|
|
||||||
|
|
||||||
|
|
||||||
# Validate the path in database
|
|
||||||
cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (path,))
|
|
||||||
try:
|
|
||||||
pathid = cursor.fetchone()[0]
|
|
||||||
except:
|
|
||||||
cursor.execute("select coalesce(max(idPath),0) as pathid from path")
|
|
||||||
pathid = cursor.fetchone()[0] + 1
|
|
||||||
query = "INSERT INTO path(idPath, strPath) values(?, ?)"
|
|
||||||
cursor.execute(query, (pathid, path))
|
|
||||||
|
|
||||||
# Get the album
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (MBitem.get("AlbumId"),))
|
|
||||||
try:
|
|
||||||
albumid = cursor.fetchone()[0]
|
|
||||||
except:
|
|
||||||
# No album found, create a single's album
|
|
||||||
cursor.execute("select coalesce(max(idAlbum),0) as albumid from album")
|
|
||||||
albumid = cursor.fetchone()[0] + 1
|
|
||||||
if kodiVersion == 15:
|
|
||||||
# Kodi Isengard
|
|
||||||
query = "INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) values(?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (albumid, genre, year, dateadded, "single"))
|
|
||||||
elif kodiVersion == 16:
|
|
||||||
query = "INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (albumid, genre, year, "single"))
|
|
||||||
else:
|
|
||||||
# Kodi Gotham and Helix
|
|
||||||
query = "INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (albumid, genre, year, dateadded))
|
|
||||||
finally:
|
|
||||||
cursor.execute("SELECT strArtists FROM album WHERE idAlbum = ?", (albumid,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
if result and result[0] == "":
|
|
||||||
# Link album to artists
|
|
||||||
if MBitem['AlbumArtists']:
|
|
||||||
album_artists = MBitem['AlbumArtists']
|
|
||||||
else:
|
|
||||||
album_artists = MBitem['ArtistItems']
|
|
||||||
|
|
||||||
MBartists = []
|
|
||||||
for artist in album_artists:
|
|
||||||
MBartists.append(artist['Name'])
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],))
|
|
||||||
try:
|
|
||||||
artistid = cursor.fetchone()[0]
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)"
|
|
||||||
cursor.execute(query, (artistid, albumid, artist['Name']))
|
|
||||||
|
|
||||||
artists_onalbum = " / ".join(MBartists)
|
|
||||||
if kodiVersion == 15:
|
|
||||||
# Kodi Isengard
|
|
||||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
|
||||||
cursor.execute(query, (artists_onalbum, albumid))
|
|
||||||
elif kodiVersion == 16:
|
|
||||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
|
||||||
cursor.execute(query, (artists_onalbum, albumid))
|
|
||||||
else:
|
|
||||||
# Kodi Gotham and Helix
|
|
||||||
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
|
|
||||||
cursor.execute(query, (artists_onalbum, albumid))
|
|
||||||
|
|
||||||
|
|
||||||
##### UPDATE THE SONG #####
|
|
||||||
if songid:
|
|
||||||
self.logMsg("UPDATE song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
|
|
||||||
|
|
||||||
query = "UPDATE song SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?, iDuration = ?, iYear = ?, strFilename = ?, strMusicBrainzTrackID = ?, iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?"
|
|
||||||
cursor.execute(query, (albumid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed, songid))
|
|
||||||
|
|
||||||
# Update the checksum in emby table
|
|
||||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
|
||||||
cursor.execute(query, (checksum, embyId))
|
|
||||||
|
|
||||||
##### OR ADD THE SONG #####
|
|
||||||
else:
|
|
||||||
self.logMsg("ADD song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1)
|
|
||||||
|
|
||||||
# Create the song
|
|
||||||
cursor.execute("select coalesce(max(idSong),0) as songid from song")
|
|
||||||
songid = cursor.fetchone()[0] + 1
|
|
||||||
query = "INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (songid, albumid, pathid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed))
|
|
||||||
|
|
||||||
# Create the reference in emby table
|
|
||||||
query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (embyId, songid, "song", checksum))
|
|
||||||
|
|
||||||
|
|
||||||
# Add genres
|
|
||||||
self.AddGenresToMedia(songid, genres, "song", cursor)
|
|
||||||
|
|
||||||
# Link song to album
|
|
||||||
if albumid:
|
|
||||||
query = "INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) values(?, ?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (songid, albumid, track, name, duration))
|
|
||||||
|
|
||||||
# Link song to artist
|
|
||||||
for artist in MBitem.get('ArtistItems'):
|
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],))
|
|
||||||
try:
|
|
||||||
artistid = cursor.fetchone()[0]
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
query = "INSERT OR REPLACE INTO song_artist(idArtist, idSong, strArtist) values(?, ?, ?)"
|
|
||||||
cursor.execute(query, (artistid, songid, artist['Name']))
|
|
||||||
|
|
||||||
# Update artwork
|
|
||||||
self.textureCache.addArtwork(API().getAllArtwork(MBitem, parentInfo=True), songid, "song", cursor)
|
|
||||||
|
|
||||||
def deleteItemFromKodiLibrary(self, id, connection, cursor):
|
|
||||||
|
|
||||||
cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id=?", (id,))
|
|
||||||
try:
|
|
||||||
result = cursor.fetchone()
|
|
||||||
kodi_id = result[0]
|
|
||||||
media_type = result[1]
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
if "artist" in media_type:
|
|
||||||
self.logMsg("Deleting artist from Kodi library, Id: %s" % id, 1)
|
|
||||||
cursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodi_id,))
|
|
||||||
elif "song" in media_type:
|
|
||||||
self.logMsg("Deleting song from Kodi library, Id: %s" % id, 1)
|
|
||||||
cursor.execute("DELETE FROM song WHERE idSong = ?", (kodi_id,))
|
|
||||||
elif "album" in media_type:
|
|
||||||
self.logMsg("Deleting album from Kodi library, Id: %s" % id, 1)
|
|
||||||
cursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodi_id,))
|
|
||||||
|
|
||||||
# Delete the record in emby table
|
|
||||||
cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,))
|
|
||||||
|
|
||||||
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
|
|
||||||
|
|
||||||
if imageUrl:
|
|
||||||
|
|
||||||
cacheimage = False
|
|
||||||
|
|
||||||
cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,))
|
|
||||||
try:
|
|
||||||
url = cursor.fetchone()[0]
|
|
||||||
except: # Image does not exists
|
|
||||||
cacheimage = True
|
|
||||||
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 1)
|
|
||||||
query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)"
|
|
||||||
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
|
||||||
else:
|
|
||||||
if url != imageUrl:
|
|
||||||
cacheimage = True
|
|
||||||
self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1)
|
|
||||||
query = "UPDATE art SET url = ? WHERE media_id = ? and media_type = ? and type = ?"
|
|
||||||
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
|
||||||
|
|
||||||
# Cache fanart textures in Kodi texture cache
|
|
||||||
if cacheimage and "fanart" in imageType:
|
|
||||||
self.textureCache.CacheTexture(imageUrl)
|
|
||||||
|
|
||||||
def AddGenresToMedia(self, id, genres, mediatype, cursor):
|
|
||||||
|
|
||||||
if genres:
|
|
||||||
|
|
||||||
for genre in genres:
|
|
||||||
|
|
||||||
cursor.execute("SELECT idGenre as idGenre FROM genre WHERE strGenre = ? COLLATE NOCASE", (genre,))
|
|
||||||
try:
|
|
||||||
idGenre = cursor.fetchone()[0]
|
|
||||||
except: # Create the genre
|
|
||||||
cursor.execute("select coalesce(max(idGenre),0) as idGenre from genre")
|
|
||||||
idGenre = cursor.fetchone()[0] + 1
|
|
||||||
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
|
|
||||||
cursor.execute(query, (idGenre, genre))
|
|
||||||
finally: # Assign the genre to item
|
|
||||||
if "album" in mediatype:
|
|
||||||
query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)"
|
|
||||||
cursor.execute(query, (idGenre, id))
|
|
||||||
elif "song" in mediatype:
|
|
||||||
query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
|
|
||||||
cursor.execute(query, (idGenre, id))
|
|
||||||
|
|
||||||
def updateUserdata(self, userdata, connection, cursor):
|
|
||||||
# This updates: LastPlayedDate, Playcount
|
|
||||||
embyId = userdata['ItemId']
|
|
||||||
MBitem = ReadEmbyDB().getItem(embyId)
|
|
||||||
|
|
||||||
if not MBitem:
|
|
||||||
self.logMsg("UPDATE userdata to Kodi library FAILED, Item %s not found on server!" % embyId, 1)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get details
|
|
||||||
checksum = API().getChecksum(MBitem)
|
|
||||||
userdata = API().getUserData(MBitem)
|
|
||||||
|
|
||||||
# Find the Kodi Id
|
|
||||||
cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id = ?", (embyId,))
|
|
||||||
try:
|
|
||||||
result = cursor.fetchone()
|
|
||||||
kodiid = result[0]
|
|
||||||
mediatype = result[1]
|
|
||||||
self.logMsg("Found embyId: %s in database - kodiId: %s type: %s" % (embyId, kodiid, mediatype), 1)
|
|
||||||
except:
|
|
||||||
self.logMsg("Id: %s not found in the emby database table." % embyId, 1)
|
|
||||||
else:
|
|
||||||
if mediatype in ("song"):
|
|
||||||
playcount = userdata['PlayCount']
|
|
||||||
dateplayed = userdata['LastPlayedDate']
|
|
||||||
|
|
||||||
query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?"
|
|
||||||
cursor.execute(query, (playcount, dateplayed, kodiid))
|
|
||||||
|
|
||||||
#update the checksum in emby table
|
|
||||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
|
||||||
cursor.execute(query, (checksum, embyId))
|
|
File diff suppressed because it is too large
Load diff
524
resources/lib/artwork.py
Normal file
524
resources/lib/artwork.py
Normal file
|
@ -0,0 +1,524 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcvfs
|
||||||
|
|
||||||
|
import utils
|
||||||
|
import clientinfo
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Artwork():
|
||||||
|
|
||||||
|
xbmc_host = 'localhost'
|
||||||
|
xbmc_port = None
|
||||||
|
xbmc_username = None
|
||||||
|
xbmc_password = None
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.clientinfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientinfo.getAddonName()
|
||||||
|
|
||||||
|
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
|
||||||
|
if not self.xbmc_port and self.enableTextureCache:
|
||||||
|
self.setKodiWebServerDetails()
|
||||||
|
|
||||||
|
self.userId = utils.window('emby_currUser')
|
||||||
|
self.server = utils.window('emby_server%s' % self.userId)
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def double_urlencode(self, text):
|
||||||
|
text = self.single_urlencode(text)
|
||||||
|
text = self.single_urlencode(text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def single_urlencode(self, text):
|
||||||
|
text = urllib.urlencode({'blahblahblah':text})
|
||||||
|
text = text[13:]
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def setKodiWebServerDetails(self):
|
||||||
|
# Get the Kodi webserver details - used to set the texture cache
|
||||||
|
web_query = {
|
||||||
|
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "Settings.GetSettingValue",
|
||||||
|
"params": {
|
||||||
|
|
||||||
|
"setting": "services.webserver"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(web_query))
|
||||||
|
result = json.loads(result)
|
||||||
|
try:
|
||||||
|
xbmc_webserver_enabled = result['result']['value']
|
||||||
|
except TypeError:
|
||||||
|
xbmc_webserver_enabled = False
|
||||||
|
|
||||||
|
if not xbmc_webserver_enabled:
|
||||||
|
# Enable the webserver, it is disabled
|
||||||
|
web_port = {
|
||||||
|
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "Settings.SetSettingValue",
|
||||||
|
"params": {
|
||||||
|
|
||||||
|
"setting": "services.webserverport",
|
||||||
|
"value": 8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(web_port))
|
||||||
|
self.xbmc_port = 8080
|
||||||
|
|
||||||
|
web_user = {
|
||||||
|
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "Settings.SetSettingValue",
|
||||||
|
"params": {
|
||||||
|
|
||||||
|
"setting": "services.webserver",
|
||||||
|
"value": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(web_user))
|
||||||
|
self.xbmc_username = "kodi"
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Webserver already enabled
|
||||||
|
web_port = {
|
||||||
|
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "Settings.GetSettingValue",
|
||||||
|
"params": {
|
||||||
|
|
||||||
|
"setting": "services.webserverport"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(web_port))
|
||||||
|
try:
|
||||||
|
self.xbmc_port = result['result']['value']
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
web_user = {
|
||||||
|
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "Settings.GetSettingValue",
|
||||||
|
"params": {
|
||||||
|
|
||||||
|
"setting": "services.webserverusername"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(web_user))
|
||||||
|
try:
|
||||||
|
self.xbmc_username = result['result']['value']
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
web_pass = {
|
||||||
|
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "Settings.GetSettingValue",
|
||||||
|
"params": {
|
||||||
|
|
||||||
|
"setting": "services.webserverpassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(web_pass))
|
||||||
|
try:
|
||||||
|
self.xbmc_password = result['result']['value']
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def FullTextureCacheSync(self):
|
||||||
|
# This method will sync all Kodi artwork to textures13.db
|
||||||
|
# and cache them locally. This takes diskspace!
|
||||||
|
|
||||||
|
# Remove all existing textures first
|
||||||
|
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
||||||
|
if xbmcvfs.exists(path):
|
||||||
|
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||||
|
for dir in allDirs:
|
||||||
|
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||||
|
for file in allFiles:
|
||||||
|
xbmcvfs.delete(os.path.join(path+dir,file))
|
||||||
|
|
||||||
|
textureconnection = utils.KodiSQL('texture')
|
||||||
|
texturecursor = textureconnection.cursor()
|
||||||
|
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
rows = texturecursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
tableName = row[0]
|
||||||
|
if(tableName != "version"):
|
||||||
|
texturecursor.execute("DELETE FROM " + tableName)
|
||||||
|
textureconnection.commit()
|
||||||
|
texturecursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Cache all entries in video DB
|
||||||
|
connection = utils.KodiSQL('video')
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("SELECT url FROM art")
|
||||||
|
result = cursor.fetchall()
|
||||||
|
for url in result:
|
||||||
|
self.CacheTexture(url[0])
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
# Cache all entries in music DB
|
||||||
|
connection = utils.KodiSQL('music')
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("SELECT url FROM art")
|
||||||
|
result = cursor.fetchall()
|
||||||
|
for url in result:
|
||||||
|
self.CacheTexture(url[0])
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
def CacheTexture(self, url):
|
||||||
|
# Cache a single image url to the texture cache
|
||||||
|
if url and self.enableTextureCache:
|
||||||
|
self.logMsg("Processing: %s" % url, 2)
|
||||||
|
|
||||||
|
# Add image to texture cache by simply calling it at the http endpoint
|
||||||
|
url = self.double_urlencode(url)
|
||||||
|
try: # Extreme short timeouts so we will have a exception.
|
||||||
|
response = requests.head(
|
||||||
|
url=(
|
||||||
|
"http://%s:%s/image/image://%s"
|
||||||
|
% (self.xbmc_host, self.xbmc_port, url)),
|
||||||
|
auth=(self.xbmc_username, self.xbmc_password),
|
||||||
|
timeout=(0.01, 0.01))
|
||||||
|
# We don't need the result
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
|
||||||
|
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
||||||
|
# Kodi conversion table
|
||||||
|
kodiart = {
|
||||||
|
|
||||||
|
'Primary': ["thumb", "poster"],
|
||||||
|
'Banner': "banner",
|
||||||
|
'Logo': "clearlogo",
|
||||||
|
'Art': "clearart",
|
||||||
|
'Thumb': "landscape",
|
||||||
|
'Disc': "discart",
|
||||||
|
'Backdrop': "fanart",
|
||||||
|
'BoxRear': "poster"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Artwork is a dictionary
|
||||||
|
for art in artwork:
|
||||||
|
|
||||||
|
if art == "Backdrop":
|
||||||
|
# Backdrop entry is a list
|
||||||
|
# Process extra fanart for artwork downloader (fanart, fanart1, fanart2...)
|
||||||
|
backdrops = artwork[art]
|
||||||
|
backdropsNumber = len(backdrops)
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT url",
|
||||||
|
"FROM art",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?",
|
||||||
|
"AND type LIKE ?"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (kodiId, mediaType, "fanart%",))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
if len(rows) > backdropsNumber:
|
||||||
|
# More backdrops in database. Delete extra fanart.
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"DELETE FROM art",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?",
|
||||||
|
"AND type LIKE ?"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (kodiId, mediaType, "fanart_",))
|
||||||
|
|
||||||
|
# Process backdrops and extra fanart
|
||||||
|
index = ""
|
||||||
|
for backdrop in backdrops:
|
||||||
|
self.addOrUpdateArt(
|
||||||
|
imageUrl=backdrop,
|
||||||
|
kodiId=kodiId,
|
||||||
|
mediaType=mediaType,
|
||||||
|
imageType="%s%s" % ("fanart", index),
|
||||||
|
cursor=cursor)
|
||||||
|
|
||||||
|
if backdropsNumber > 1:
|
||||||
|
try: # Will only fail on the first try, str to int.
|
||||||
|
index += 1
|
||||||
|
except TypeError:
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
elif art == "Primary":
|
||||||
|
# Primary art is processed as thumb and poster for Kodi.
|
||||||
|
for artType in kodiart[art]:
|
||||||
|
self.addOrUpdateArt(
|
||||||
|
imageUrl=artwork[art],
|
||||||
|
kodiId=kodiId,
|
||||||
|
mediaType=mediaType,
|
||||||
|
imageType=artType,
|
||||||
|
cursor=cursor)
|
||||||
|
|
||||||
|
elif kodiart.get(art):
|
||||||
|
# Process the rest artwork type that Kodi can use
|
||||||
|
self.addOrUpdateArt(
|
||||||
|
imageUrl=artwork[art],
|
||||||
|
kodiId=kodiId,
|
||||||
|
mediaType=mediaType,
|
||||||
|
imageType=kodiart[art],
|
||||||
|
cursor=cursor)
|
||||||
|
|
||||||
|
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
|
||||||
|
# Possible that the imageurl is an empty string
|
||||||
|
if imageUrl:
|
||||||
|
cacheimage = False
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT url",
|
||||||
|
"FROM art",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?",
|
||||||
|
"AND type = ?"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (kodiId, mediaType, imageType,))
|
||||||
|
try: # Update the artwork
|
||||||
|
url = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
except TypeError: # Add the artwork
|
||||||
|
cacheimage = True
|
||||||
|
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"INSERT INTO art(",
|
||||||
|
"media_id, media_type, type, url)",
|
||||||
|
|
||||||
|
"VALUES (?, ?, ?, ?)"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
|
||||||
|
|
||||||
|
else: # Only cache artwork if it changed
|
||||||
|
if url != imageUrl:
|
||||||
|
cacheimage = True
|
||||||
|
|
||||||
|
# Only for the main backdrop, poster
|
||||||
|
if imageType in ("fanart", "poster"):
|
||||||
|
# Delete current entry before updating with the new one
|
||||||
|
self.deleteCachedArtwork(url)
|
||||||
|
|
||||||
|
self.logMsg(
|
||||||
|
"Updating Art url for %s kodiId: %s (%s) -> (%s)"
|
||||||
|
% (imageType, kodiId, url, imageUrl), 1)
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"UPDATE art",
|
||||||
|
"SET url = ?",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?",
|
||||||
|
"AND type = ?"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
|
||||||
|
|
||||||
|
# Cache fanart and poster in Kodi texture cache
|
||||||
|
if cacheimage and imageType in ("fanart", "poster"):
|
||||||
|
self.CacheTexture(imageUrl)
|
||||||
|
|
||||||
|
def deleteArtwork(self, kodiid, mediatype, cursor):
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT url, type",
|
||||||
|
"FROM art",
|
||||||
|
"WHERE media_id = ?",
|
||||||
|
"AND media_type = ?"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (kodiid, mediatype,))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
|
||||||
|
url = row[0]
|
||||||
|
imagetype = row[1]
|
||||||
|
if imagetype in ("poster", "fanart"):
|
||||||
|
self.deleteCachedArtwork(url)
|
||||||
|
|
||||||
|
def deleteCachedArtwork(self, url):
|
||||||
|
# Only necessary to remove and apply a new backdrop or poster
|
||||||
|
connection = utils.kodiSQL('texture')
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
|
||||||
|
try:
|
||||||
|
cachedurl = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
self.logMsg("Could not find cached url.", 1)
|
||||||
|
|
||||||
|
else: # Delete thumbnail as well as the entry
|
||||||
|
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
|
||||||
|
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
|
||||||
|
xbmcvfs.delete(thumbnails)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||||
|
connection.commit()
|
||||||
|
except:
|
||||||
|
self.logMsg("Issue deleting url from cache. Skipping.", 2)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
def getPeopleArtwork(self, people):
|
||||||
|
# append imageurl if existing
|
||||||
|
for person in people:
|
||||||
|
|
||||||
|
personId = person['Id']
|
||||||
|
tag = person.get('PrimaryImageTag')
|
||||||
|
|
||||||
|
image = ""
|
||||||
|
if tag:
|
||||||
|
image = (
|
||||||
|
"%s/emby/Items/%s/Images/Primary?"
|
||||||
|
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
|
||||||
|
% (self.server, personId, tag))
|
||||||
|
|
||||||
|
person['imageurl'] = image
|
||||||
|
|
||||||
|
return people
|
||||||
|
|
||||||
|
def getUserArtwork(self, itemid, itemtype):
|
||||||
|
# Load user information set by UserClient
|
||||||
|
image = ("%s/emby/Users/%s/Images/%s?Format=original"
|
||||||
|
% (self.server, itemid, itemtype))
|
||||||
|
return image
|
||||||
|
|
||||||
|
def getAllArtwork(self, item, parentInfo=False):
|
||||||
|
|
||||||
|
server = self.server
|
||||||
|
|
||||||
|
id = item['Id']
|
||||||
|
artworks = item['ImageTags']
|
||||||
|
backdrops = item['BackdropImageTags']
|
||||||
|
|
||||||
|
maxHeight = 10000
|
||||||
|
maxWidth = 10000
|
||||||
|
customquery = ""
|
||||||
|
|
||||||
|
if utils.settings('compressArt') == "true":
|
||||||
|
customquery = "&Quality=90"
|
||||||
|
|
||||||
|
if utils.settings('enableCoverArt') == "false":
|
||||||
|
customquery += "&EnableImageEnhancers=false"
|
||||||
|
|
||||||
|
allartworks = {
|
||||||
|
|
||||||
|
'Primary': "",
|
||||||
|
'Art': "",
|
||||||
|
'Banner': "",
|
||||||
|
'Logo': "",
|
||||||
|
'Thumb': "",
|
||||||
|
'Disc': "",
|
||||||
|
'Backdrop': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process backdrops
|
||||||
|
backdropIndex = 0
|
||||||
|
for backdroptag in backdrops:
|
||||||
|
artwork = (
|
||||||
|
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
||||||
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
|
% (server, id, backdropIndex,
|
||||||
|
maxWidth, maxHeight, backdroptag, customquery))
|
||||||
|
allartworks['Backdrop'].append(artwork)
|
||||||
|
backdropIndex += 1
|
||||||
|
|
||||||
|
# Process the rest of the artwork
|
||||||
|
for art in artworks:
|
||||||
|
# Filter backcover
|
||||||
|
if art != "BoxRear":
|
||||||
|
tag = artworks[art]
|
||||||
|
artwork = (
|
||||||
|
"%s/emby/Items/%s/Images/%s/0?"
|
||||||
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
|
% (server, id, art, maxWidth, maxHeight, tag, customquery))
|
||||||
|
allartworks[art] = artwork
|
||||||
|
|
||||||
|
# Process parent items if the main item is missing artwork
|
||||||
|
if parentInfo:
|
||||||
|
|
||||||
|
# Process parent backdrops
|
||||||
|
if not allartworks['Backdrop']:
|
||||||
|
|
||||||
|
parentId = item.get('ParentBackdropItemId')
|
||||||
|
if parentId:
|
||||||
|
# If there is a parentId, go through the parent backdrop list
|
||||||
|
parentbackdrops = item['ParentBackdropImageTags']
|
||||||
|
|
||||||
|
backdropIndex = 0
|
||||||
|
for parentbackdroptag in parentbackdrops:
|
||||||
|
artwork = (
|
||||||
|
"%s/emby/Items/%s/Images/Backdrop/%s?"
|
||||||
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
|
% (server, parentId, backdropIndex,
|
||||||
|
maxWidth, maxHeight, parentbackdroptag, customquery))
|
||||||
|
allartworks['Backdrop'].append(artwork)
|
||||||
|
backdropIndex += 1
|
||||||
|
|
||||||
|
# Process the rest of the artwork
|
||||||
|
parentartwork = ['Logo', 'Art', 'Thumb']
|
||||||
|
for parentart in parentartwork:
|
||||||
|
|
||||||
|
if not allartworks[parentart]:
|
||||||
|
|
||||||
|
parentId = item.get('Parent%sItemId' % parentart)
|
||||||
|
if parentId:
|
||||||
|
|
||||||
|
parentTag = item['Parent%sImageTag' % parentart]
|
||||||
|
artwork = (
|
||||||
|
"%s/emby/Items/%s/Images/%s/0?"
|
||||||
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
|
% (server, parentId, parentart,
|
||||||
|
maxWidth, maxHeight, parentTag, customquery))
|
||||||
|
allartworks[parentart] = artwork
|
||||||
|
|
||||||
|
# Parent album works a bit differently
|
||||||
|
if not allartworks['Primary']:
|
||||||
|
|
||||||
|
parentId = item.get('AlbumId')
|
||||||
|
if parentId and item.get('AlbumPrimaryImageTag'):
|
||||||
|
|
||||||
|
parentTag = item['AlbumPrimaryImageTag']
|
||||||
|
artwork = (
|
||||||
|
"%s/emby/Items/%s/Images/Primary/0?"
|
||||||
|
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
|
||||||
|
% (server, parentId, maxWidth, maxHeight, parentTag, customquery))
|
||||||
|
allartworks['Primary'] = artwork
|
||||||
|
|
||||||
|
return allartworks
|
96
resources/lib/clientinfo.py
Normal file
96
resources/lib/clientinfo.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
import utils
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class ClientInfo():
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.addon = xbmcaddon.Addon()
|
||||||
|
self.addonName = self.getAddonName()
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def getAddonName(self):
|
||||||
|
# Used for logging
|
||||||
|
return self.addon.getAddonInfo('name').upper()
|
||||||
|
|
||||||
|
def getAddonId(self):
|
||||||
|
|
||||||
|
return "plugin.video.emby"
|
||||||
|
|
||||||
|
def getVersion(self):
|
||||||
|
|
||||||
|
return self.addon.getAddonInfo('version')
|
||||||
|
|
||||||
|
def getDeviceName(self):
|
||||||
|
|
||||||
|
if utils.settings('deviceNameOpt') == "false":
|
||||||
|
# Use Kodi's deviceName
|
||||||
|
deviceName = xbmc.getInfoLabel('System.FriendlyName')
|
||||||
|
else:
|
||||||
|
deviceName = utils.settings('deviceName')
|
||||||
|
deviceName = deviceName.replace("\"", "_")
|
||||||
|
deviceName = deviceName.replace("/", "_")
|
||||||
|
|
||||||
|
return deviceName
|
||||||
|
|
||||||
|
def getPlatform(self):
|
||||||
|
|
||||||
|
if xbmc.getCondVisibility('system.platform.osx'):
|
||||||
|
return "OSX"
|
||||||
|
elif xbmc.getCondVisibility('system.platform.atv2'):
|
||||||
|
return "ATV2"
|
||||||
|
elif xbmc.getCondVisibility('system.platform.ios'):
|
||||||
|
return "iOS"
|
||||||
|
elif xbmc.getCondVisibility('system.platform.windows'):
|
||||||
|
return "Windows"
|
||||||
|
elif xbmc.getCondVisibility('system.platform.linux'):
|
||||||
|
return "Linux/RPi"
|
||||||
|
elif xbmc.getCondVisibility('system.platform.android'):
|
||||||
|
return "Linux/Android"
|
||||||
|
else:
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def getDeviceId(self):
|
||||||
|
|
||||||
|
clientId = utils.window('emby_deviceId')
|
||||||
|
if clientId:
|
||||||
|
return clientId
|
||||||
|
|
||||||
|
addon_path = self.addon.getAddonInfo('path').decode('utf-8')
|
||||||
|
GUID_file = xbmc.translatePath("%s\machine_guid" % addon_path).decode('utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
GUID = open(GUID_file)
|
||||||
|
|
||||||
|
except IOError: # machine_guid does not exists.
|
||||||
|
clientId = str("%012X" % uuid4())
|
||||||
|
GUID = open(GUID_file, 'w')
|
||||||
|
GUID.write(clientId)
|
||||||
|
|
||||||
|
else: # machine_guid already exists. Get guid.
|
||||||
|
clientId = GUID.read()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
GUID.close()
|
||||||
|
|
||||||
|
self.logMsg("DeviceId loaded: %s" % clientId, 1)
|
||||||
|
utils.window('emby_deviceId', value=clientId)
|
||||||
|
|
||||||
|
return clientId
|
294
resources/lib/embydb_functions.py
Normal file
294
resources/lib/embydb_functions.py
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import utils
|
||||||
|
import clientinfo
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Embydb_Functions():
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, embycursor):
|
||||||
|
|
||||||
|
self.embycursor = embycursor
|
||||||
|
|
||||||
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
def getViews(self):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
views = []
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT view_id",
|
||||||
|
"FROM view"
|
||||||
|
))
|
||||||
|
embycursor.execute(query)
|
||||||
|
rows = embycursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
views.append(row[0])
|
||||||
|
|
||||||
|
return views
|
||||||
|
|
||||||
|
def getView_byId(self, viewid):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT view_name, media_type, kodi_tagid",
|
||||||
|
"FROM view",
|
||||||
|
"WHERE view_id = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (viewid,))
|
||||||
|
view = embycursor.fetchone()
|
||||||
|
|
||||||
|
return view
|
||||||
|
|
||||||
|
def getView_byType(self, mediatype):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
views = []
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT view_id, view_name",
|
||||||
|
"FROM view",
|
||||||
|
"WHERE media_type = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (mediatype,))
|
||||||
|
rows = embycursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
views.append({
|
||||||
|
|
||||||
|
'id': row[0],
|
||||||
|
'name': row[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
return views
|
||||||
|
|
||||||
|
def getView_byName(self, tagname):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT view_id",
|
||||||
|
"FROM view",
|
||||||
|
"WHERE view_name = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (tagname,))
|
||||||
|
try:
|
||||||
|
view = embycursor.fetchone()[0]
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
view = None
|
||||||
|
|
||||||
|
return view
|
||||||
|
|
||||||
|
def addView(self, embyid, name, mediatype, tagid):
|
||||||
|
|
||||||
|
query = (
|
||||||
|
'''
|
||||||
|
INSERT INTO view(
|
||||||
|
view_id, view_name, media_type, kodi_tagid)
|
||||||
|
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
|
||||||
|
|
||||||
|
def updateView(self, name, tagid, mediafolderid):
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"UPDATE view",
|
||||||
|
"SET view_name = ?, kodi_tagid = ?",
|
||||||
|
"WHERE view_id = ?"
|
||||||
|
))
|
||||||
|
self.embycursor.execute(query, (name, tagid, mediafolderid))
|
||||||
|
|
||||||
|
def getItem_byId(self, embyid):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE emby_id = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (embyid,))
|
||||||
|
item = embycursor.fetchone()
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def getItem_byView(self, mediafolderid):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT kodi_id",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE media_folder = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (mediafolderid,))
|
||||||
|
items = embycursor.fetchall()
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getItem_byKodiId(self, kodiid, mediatype):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT emby_id, parent_id",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE kodi_id = ?",
|
||||||
|
"AND media_type = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (kodiid, mediatype,))
|
||||||
|
item = embycursor.fetchone()
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def getItem_byParentId(self, parentid, mediatype):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT emby_id, kodi_id, kodi_fileid",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE parent_id = ?",
|
||||||
|
"AND media_type = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (parentid, mediatype,))
|
||||||
|
items = embycursor.fetchall()
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getItemId_byParentId(self, parentid, mediatype):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT emby_id, kodi_id",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE parent_id = ?",
|
||||||
|
"AND media_type = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (parentid, mediatype,))
|
||||||
|
items = embycursor.fetchall()
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getChecksum(self, mediatype):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT emby_id, checksum",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE emby_type = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (mediatype,))
|
||||||
|
items = embycursor.fetchall()
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getMediaType_byId(self, embyid):
|
||||||
|
|
||||||
|
embycursor = self.embycursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT emby_type",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE emby_id = ?"
|
||||||
|
))
|
||||||
|
embycursor.execute(query, (embyid,))
|
||||||
|
try:
|
||||||
|
itemtype = embycursor.fetchone()[0]
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
itemtype = None
|
||||||
|
|
||||||
|
return itemtype
|
||||||
|
|
||||||
|
def sortby_mediaType(self, itemids, unsorted=True):
|
||||||
|
|
||||||
|
sorted_items = {}
|
||||||
|
|
||||||
|
for itemid in itemids:
|
||||||
|
|
||||||
|
mediatype = self.getMediaType_byId(itemid)
|
||||||
|
if mediatype:
|
||||||
|
sorted_items.setdefault(mediatype, []).append(itemid)
|
||||||
|
elif unsorted:
|
||||||
|
sorted_items.setdefault('Unsorted', []).append(itemid)
|
||||||
|
|
||||||
|
return sorted_items
|
||||||
|
|
||||||
|
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
|
||||||
|
parentid=None, checksum=None, mediafolderid=None):
|
||||||
|
query = (
|
||||||
|
'''
|
||||||
|
INSERT OR REPLACE INTO emby(
|
||||||
|
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
|
||||||
|
checksum, media_folder)
|
||||||
|
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
|
||||||
|
parentid, checksum, mediafolderid))
|
||||||
|
|
||||||
|
def updateReference(self, embyid, checksum):
|
||||||
|
|
||||||
|
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
||||||
|
self.embycursor.execute(query, (checksum, embyid))
|
||||||
|
|
||||||
|
def updateParentId(self, embyid, parent_kodiid):
|
||||||
|
|
||||||
|
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
|
||||||
|
self.embycursor.execute(query, (parent_kodiid, embyid))
|
||||||
|
|
||||||
|
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"DELETE FROM emby",
|
||||||
|
"WHERE parent_id = ?",
|
||||||
|
"AND media_type = ?"
|
||||||
|
))
|
||||||
|
self.embycursor.execute(query, (parent_kodiid, mediatype,))
|
||||||
|
|
||||||
|
def removeItem_byKodiId(self, kodiid, mediatype):
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"DELETE FROM emby",
|
||||||
|
"WHERE kodi_id = ?",
|
||||||
|
"AND media_type = ?"
|
||||||
|
))
|
||||||
|
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||||
|
|
||||||
|
def removeItem(self, embyid):
|
||||||
|
|
||||||
|
query = "DELETE FROM emby WHERE emby_id = ?"
|
||||||
|
self.embycursor.execute(query, (embyid,))
|
192
resources/lib/initialsetup.py
Normal file
192
resources/lib/initialsetup.py
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
import utils
|
||||||
|
import clientinfo
|
||||||
|
import downloadutils
|
||||||
|
import userclient
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class InitialSetup():
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.addon = xbmcaddon.Addon()
|
||||||
|
self.__language__ = self.addon.getLocalizedString
|
||||||
|
|
||||||
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
|
self.addonId = self.clientInfo.getAddonId()
|
||||||
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
|
self.userClient = userclient.UserClient()
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
# Check server, user, direct paths, music, direct stream if not direct path.
|
||||||
|
string = self.__language__
|
||||||
|
addonId = self.addonId
|
||||||
|
|
||||||
|
##### SERVER INFO #####
|
||||||
|
|
||||||
|
self.logMsg("Initial setup called.", 2)
|
||||||
|
server = self.userClient.getServer()
|
||||||
|
|
||||||
|
if server:
|
||||||
|
self.logMsg("Server is already set.", 2)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logMsg("Looking for server...", 2)
|
||||||
|
server = self.getServerDetails()
|
||||||
|
self.logMsg("Found: %s" % server, 2)
|
||||||
|
try:
|
||||||
|
prefix, ip, port = server.replace("/", "").split(":")
|
||||||
|
except: # Failed to retrieve server information
|
||||||
|
self.logMsg("getServerDetails failed.", 1)
|
||||||
|
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
server_confirm = xbmcgui.Dialog().yesno(
|
||||||
|
heading="Emby for Kodi",
|
||||||
|
line1="Proceed with the following server?",
|
||||||
|
line2="%s %s" % (string(30169), server))
|
||||||
|
if server_confirm:
|
||||||
|
# Correct server found
|
||||||
|
self.logMsg("Server is selected. Saving the information.", 1)
|
||||||
|
utils.settings('ipaddress', value=ip)
|
||||||
|
utils.settings('port', value=port)
|
||||||
|
|
||||||
|
if prefix == "https":
|
||||||
|
utils.settings('https', value="true")
|
||||||
|
else:
|
||||||
|
# User selected no or cancelled the dialog
|
||||||
|
self.logMsg("No server selected.", 1)
|
||||||
|
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
|
||||||
|
return
|
||||||
|
|
||||||
|
##### USER INFO #####
|
||||||
|
|
||||||
|
self.logMsg("Getting user list.", 1)
|
||||||
|
|
||||||
|
url = "%s/emby/Users/Public?format=json" % server
|
||||||
|
result = self.doUtils.downloadUrl(url, authenticate=False)
|
||||||
|
if result == "":
|
||||||
|
self.logMsg("Unable to connect to %s" % server, 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logMsg("Response: %s" % result, 2)
|
||||||
|
# Process the list of users
|
||||||
|
usernames = []
|
||||||
|
users_hasPassword = []
|
||||||
|
|
||||||
|
for user in result:
|
||||||
|
# Username
|
||||||
|
name = user['Name']
|
||||||
|
usernames.append(name)
|
||||||
|
# Password
|
||||||
|
if user['HasPassword']:
|
||||||
|
name = "%s (secure)" % name
|
||||||
|
users_hasPassword.append(name)
|
||||||
|
|
||||||
|
self.logMsg("Presenting user list: %s" % users_hasPassword, 1)
|
||||||
|
user_select = xbmcgui.Dialog().select(string(30200), users_hasPassword)
|
||||||
|
if user_select > -1:
|
||||||
|
selected_user = usernames[user_select]
|
||||||
|
self.logMsg("Selected user: %s" % selected_user, 1)
|
||||||
|
utils.settings('username', value=selected_user)
|
||||||
|
else:
|
||||||
|
self.logMsg("No user selected.", 1)
|
||||||
|
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
|
||||||
|
|
||||||
|
##### ADDITIONAL PROMPTS #####
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
|
directPaths = dialog.yesno(
|
||||||
|
heading="Playback Mode",
|
||||||
|
line1=(
|
||||||
|
"Caution! If you choose Native mode, you "
|
||||||
|
"will lose access to certain Emby features such as: "
|
||||||
|
"Emby cinema mode, direct stream/transcode options, "
|
||||||
|
"parental access schedule."),
|
||||||
|
nolabel="Addon (Default)",
|
||||||
|
yeslabel="Native (Direct Paths)")
|
||||||
|
if directPaths:
|
||||||
|
self.logMsg("User opted to use direct paths.", 1)
|
||||||
|
utils.settings('useDirectPaths', value="1")
|
||||||
|
|
||||||
|
# ask for credentials
|
||||||
|
credentials = dialog.yesno(
|
||||||
|
heading="Network credentials",
|
||||||
|
line1= (
|
||||||
|
"Add network credentials to allow Kodi access to your "
|
||||||
|
"content? Note: Skipping this step may generate a message "
|
||||||
|
"during the initial scan of your content if Kodi can't "
|
||||||
|
"locate your content."))
|
||||||
|
if credentials:
|
||||||
|
self.logMsg("Presenting network credentials dialog.", 1)
|
||||||
|
utils.passwordsXML()
|
||||||
|
|
||||||
|
musicDisabled = dialog.yesno(
|
||||||
|
heading="Music Library",
|
||||||
|
line1="Disable Emby music library?")
|
||||||
|
if musicDisabled:
|
||||||
|
self.logMsg("User opted to disable Emby music library.", 1)
|
||||||
|
utils.settings('enableMusic', value="false")
|
||||||
|
else:
|
||||||
|
# Only prompt if the user didn't select direct paths for videos
|
||||||
|
if not directPaths:
|
||||||
|
musicAccess = dialog.yesno(
|
||||||
|
heading="Music Library",
|
||||||
|
line1=(
|
||||||
|
"Direct stream the music library? Select "
|
||||||
|
"this option only if you plan on listening "
|
||||||
|
"to music outside of your network."))
|
||||||
|
if musicAccess:
|
||||||
|
self.logMsg("User opted to direct stream music.", 1)
|
||||||
|
utils.settings('streamMusic', value="true")
|
||||||
|
|
||||||
|
def getServerDetails(self):
|
||||||
|
|
||||||
|
self.logMsg("Getting Server Details from Network", 1)
|
||||||
|
|
||||||
|
MULTI_GROUP = ("<broadcast>", 7359)
|
||||||
|
MESSAGE = "who is EmbyServer?"
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.settimeout(6.0)
|
||||||
|
|
||||||
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
|
||||||
|
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
|
||||||
|
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2);
|
||||||
|
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
|
||||||
|
sock.sendto(MESSAGE, MULTI_GROUP)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
|
||||||
|
self.logMsg("Received Response: %s" % data)
|
||||||
|
except:
|
||||||
|
self.logMsg("No UDP Response")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# Get the address
|
||||||
|
data = json.loads(data)
|
||||||
|
return data['Address']
|
2528
resources/lib/itemtypes.py
Normal file
2528
resources/lib/itemtypes.py
Normal file
File diff suppressed because it is too large
Load diff
1143
resources/lib/kodidb_functions.py
Normal file
1143
resources/lib/kodidb_functions.py
Normal file
File diff suppressed because it is too large
Load diff
201
resources/lib/playlist.py
Normal file
201
resources/lib/playlist.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcplugin
|
||||||
|
|
||||||
|
import clientinfo
|
||||||
|
import playutils
|
||||||
|
import playbackutils
|
||||||
|
import embydb_functions as embydb
|
||||||
|
import read_embyserver as embyserver
|
||||||
|
import utils
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist():
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
|
|
||||||
|
self.userid = utils.window('emby_currUser')
|
||||||
|
self.server = utils.window('emby_server%s' % self.userid)
|
||||||
|
|
||||||
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
self.className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def playAll(self, itemids, startat):
|
||||||
|
|
||||||
|
embyconn = utils.kodiSQL('emby')
|
||||||
|
embycursor = embyconn.cursor()
|
||||||
|
emby_db = embydb.Embydb_Functions(embycursor)
|
||||||
|
|
||||||
|
self.logMsg("---*** PLAY ALL ***---", 1)
|
||||||
|
self.logMsg("Items: %s" % itemids)
|
||||||
|
|
||||||
|
player = xbmc.Player()
|
||||||
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
playlist.clear()
|
||||||
|
started = False
|
||||||
|
|
||||||
|
utils.window('emby_customplaylist', value="true", windowid=10101)
|
||||||
|
|
||||||
|
position = 0
|
||||||
|
|
||||||
|
for itemid in itemids:
|
||||||
|
embydb_item = emby_db.getItem_byId(itemid)
|
||||||
|
try:
|
||||||
|
dbid = embydb_item[0]
|
||||||
|
mediatype = embydb_item[4]
|
||||||
|
except TypeError:
|
||||||
|
# Item is not found in our database, add item manually
|
||||||
|
item = self.emby.getItem(itemid)
|
||||||
|
self.addtoPlaylist_xbmc(playlist, item)
|
||||||
|
else:
|
||||||
|
# Add to playlist
|
||||||
|
self.addtoPlaylist(dbid, mediatype)
|
||||||
|
|
||||||
|
self.logMsg("Adding %s to playlist." % itemid, 1)
|
||||||
|
|
||||||
|
if not started:
|
||||||
|
started = True
|
||||||
|
player.play(playlist)
|
||||||
|
|
||||||
|
if startat:
|
||||||
|
# Seek to the starting position
|
||||||
|
seektime = startat / 10000000.0
|
||||||
|
player.seekTime(seektime)
|
||||||
|
|
||||||
|
self.verifyPlaylist()
|
||||||
|
embycursor.close()
|
||||||
|
|
||||||
|
def modifyPlaylist(self, itemids):
|
||||||
|
|
||||||
|
embyconn = utils.kodiSQL('emby')
|
||||||
|
embycursor = embyconn.cursor()
|
||||||
|
emby_db = embydb.Embydb_Functions(embycursor)
|
||||||
|
|
||||||
|
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
|
||||||
|
self.logMsg("Items: %s" % itemids, 1)
|
||||||
|
|
||||||
|
player = xbmc.Player()
|
||||||
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
|
||||||
|
for itemid in itemids:
|
||||||
|
embydb_item = emby_db.getItem_byId(itemid)
|
||||||
|
try:
|
||||||
|
dbid = embydb_item[0]
|
||||||
|
mediatype = embydb_item[4]
|
||||||
|
except TypeError:
|
||||||
|
# Item is not found in our database, add item manually
|
||||||
|
item = self.emby.getItem(itemid)
|
||||||
|
self.addtoPlaylist_xbmc(playlist, item)
|
||||||
|
else:
|
||||||
|
# Add to playlist
|
||||||
|
self.addtoPlaylist(dbid, mediatype)
|
||||||
|
|
||||||
|
self.logMsg("Adding %s to playlist." % itemid, 1)
|
||||||
|
|
||||||
|
self.verifyPlaylist()
|
||||||
|
embycursor.close()
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||||
|
|
||||||
|
pl = {
|
||||||
|
|
||||||
|
'jsonrpc': "2.0",
|
||||||
|
'id': 1,
|
||||||
|
'method': "Playlist.Add",
|
||||||
|
'params': {
|
||||||
|
|
||||||
|
'playlistid': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dbid is not None:
|
||||||
|
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||||
|
else:
|
||||||
|
pl['params']['item'] = {'file': url}
|
||||||
|
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||||
|
self.logMsg(result, 2)
|
||||||
|
|
||||||
|
def addtoPlaylist_xbmc(self, playlist, item):
|
||||||
|
|
||||||
|
itemid = item['Id']
|
||||||
|
playurl = playutils.PlayUtils(item).getPlayUrl()
|
||||||
|
if not playurl:
|
||||||
|
# Playurl failed
|
||||||
|
self.logMsg("Failed to retrieve playurl.", 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logMsg("Playurl: %s" % playurl)
|
||||||
|
listitem = xbmcgui.ListItem()
|
||||||
|
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
|
||||||
|
|
||||||
|
playlist.add(playurl, listitem)
|
||||||
|
|
||||||
|
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
|
||||||
|
|
||||||
|
pl = {
|
||||||
|
|
||||||
|
'jsonrpc': "2.0",
|
||||||
|
'id': 1,
|
||||||
|
'method': "Playlist.Insert",
|
||||||
|
'params': {
|
||||||
|
|
||||||
|
'playlistid': 1,
|
||||||
|
'position': position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dbid is not None:
|
||||||
|
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||||
|
else:
|
||||||
|
pl['params']['item'] = {'file': url}
|
||||||
|
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||||
|
self.logMsg(result, 2)
|
||||||
|
|
||||||
|
def verifyPlaylist(self):
|
||||||
|
|
||||||
|
pl = {
|
||||||
|
|
||||||
|
'jsonrpc': "2.0",
|
||||||
|
'id': 1,
|
||||||
|
'method': "Playlist.GetItems",
|
||||||
|
'params': {
|
||||||
|
|
||||||
|
'playlistid': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||||
|
self.logMsg(result, 2)
|
||||||
|
|
||||||
|
def removefromPlaylist(self, position):
|
||||||
|
|
||||||
|
pl = {
|
||||||
|
|
||||||
|
'jsonrpc': "2.0",
|
||||||
|
'id': 1,
|
||||||
|
'method': "Playlist.Remove",
|
||||||
|
'params': {
|
||||||
|
|
||||||
|
'playlistid': 1,
|
||||||
|
'position': position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||||
|
self.logMsg(result, 2)
|
426
resources/lib/read_embyserver.py
Normal file
426
resources/lib/read_embyserver.py
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import utils
|
||||||
|
import clientinfo
|
||||||
|
import downloadutils
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Read_EmbyServer():
|
||||||
|
|
||||||
|
limitIndex = 200
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
|
|
||||||
|
self.userId = utils.window('emby_currUser')
|
||||||
|
self.server = utils.window('emby_server%s' % self.userId)
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def split_list(self, itemlist, size):
|
||||||
|
# Split up list in pieces of size. Will generate a list of lists
|
||||||
|
return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]
|
||||||
|
|
||||||
|
|
||||||
|
def getItem(self, itemid):
|
||||||
|
# This will return the full item
|
||||||
|
item = {}
|
||||||
|
|
||||||
|
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
|
||||||
|
result = self.doUtils.downloadUrl(url)
|
||||||
|
if result:
|
||||||
|
item = result
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def getItems(self, itemlist):
|
||||||
|
|
||||||
|
items = []
|
||||||
|
|
||||||
|
itemlists = self.split_list(itemlist, 50)
|
||||||
|
for itemlist in itemlists:
|
||||||
|
# Will return basic information
|
||||||
|
url = "{server}/emby/Users/{UserId}/Items?&format=json"
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'Ids': ",".join(itemlist),
|
||||||
|
'Fields': "Etag"
|
||||||
|
}
|
||||||
|
result = self.doUtils.downloadUrl(url, parameters=params)
|
||||||
|
if result:
|
||||||
|
items.extend(result['Items'])
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getFullItems(self, itemlist):
|
||||||
|
|
||||||
|
items = []
|
||||||
|
|
||||||
|
itemlists = self.split_list(itemlist, 50)
|
||||||
|
for itemlist in itemlists:
|
||||||
|
|
||||||
|
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
||||||
|
params = {
|
||||||
|
|
||||||
|
"Ids": ",".join(itemlist),
|
||||||
|
"Fields": (
|
||||||
|
|
||||||
|
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||||
|
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||||
|
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||||
|
"CriticRating,CriticRatingSummary,Etag,ProductionLocations,"
|
||||||
|
"OfficialRating,Tags,ProviderIds,RemoteTrailers"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result = self.doUtils.downloadUrl(url, parameters=params)
|
||||||
|
if result:
|
||||||
|
items.extend(result['Items'])
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getView_embyId(self, itemid):
|
||||||
|
# Returns ancestors using embyId
|
||||||
|
viewId = None
|
||||||
|
url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
|
||||||
|
result = self.doUtils.downloadUrl(url)
|
||||||
|
|
||||||
|
for view in result:
|
||||||
|
|
||||||
|
viewtype = view['Type']
|
||||||
|
if viewtype == "CollectionFolder":
|
||||||
|
# Found view
|
||||||
|
viewId = view['Id']
|
||||||
|
|
||||||
|
# Compare to view table in emby database
|
||||||
|
emby = utils.kodiSQL('emby')
|
||||||
|
cursor_emby = emby.cursor()
|
||||||
|
query = ' '.join((
|
||||||
|
|
||||||
|
"SELECT view_name, media_type",
|
||||||
|
"FROM view",
|
||||||
|
"WHERE view_id = ?"
|
||||||
|
))
|
||||||
|
cursor_emby.execute(query, (viewId,))
|
||||||
|
result = cursor_emby.fetchone()
|
||||||
|
try:
|
||||||
|
viewName = result[0]
|
||||||
|
mediatype = result[1]
|
||||||
|
except TypeError:
|
||||||
|
viewName = None
|
||||||
|
mediatype = None
|
||||||
|
|
||||||
|
cursor_emby.close()
|
||||||
|
|
||||||
|
return [viewName, viewId, mediatype]
|
||||||
|
|
||||||
|
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False):
|
||||||
|
|
||||||
|
doUtils = self.doUtils
|
||||||
|
items = {
|
||||||
|
|
||||||
|
'Items': [],
|
||||||
|
'TotalRecordCount': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get total number of items
|
||||||
|
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'ParentId': parentid,
|
||||||
|
'IncludeItemTypes': itemtype,
|
||||||
|
'CollapseBoxSetItems': False,
|
||||||
|
'IsVirtualUnaired': False,
|
||||||
|
'IsMissing': False,
|
||||||
|
'Recursive': True,
|
||||||
|
'Limit': 1
|
||||||
|
}
|
||||||
|
result = doUtils.downloadUrl(url, parameters=params)
|
||||||
|
try:
|
||||||
|
total = result['TotalRecordCount']
|
||||||
|
items['TotalRecordCount'] = total
|
||||||
|
|
||||||
|
except TypeError: # Failed to retrieve
|
||||||
|
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = 0
|
||||||
|
jump = self.limitIndex
|
||||||
|
|
||||||
|
while index < total:
|
||||||
|
# Get items by chunk to increase retrieval speed at scale
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'ParentId': parentid,
|
||||||
|
'IncludeItemTypes': itemtype,
|
||||||
|
'CollapseBoxSetItems': False,
|
||||||
|
'IsVirtualUnaired': False,
|
||||||
|
'IsMissing': False,
|
||||||
|
'Recursive': True,
|
||||||
|
'StartIndex': index,
|
||||||
|
'Limit': jump,
|
||||||
|
'SortBy': sortby,
|
||||||
|
'SortOrder': "Ascending",
|
||||||
|
}
|
||||||
|
if basic:
|
||||||
|
params['Fields'] = "Etag"
|
||||||
|
else:
|
||||||
|
params['Fields'] = (
|
||||||
|
|
||||||
|
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||||
|
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||||
|
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||||
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
|
"OfficialRating,Tags,ProviderIds,ParentId,RemoteTrailers"
|
||||||
|
)
|
||||||
|
result = doUtils.downloadUrl(url, parameters=params)
|
||||||
|
items['Items'].extend(result['Items'])
|
||||||
|
|
||||||
|
index += jump
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getViews(self, type, root=False):
|
||||||
|
# Build a list of user views
|
||||||
|
doUtils = self.doUtils
|
||||||
|
views = []
|
||||||
|
type = type.lower()
|
||||||
|
|
||||||
|
if not root:
|
||||||
|
url = "{server}/emby/Users/{UserId}/Views?format=json"
|
||||||
|
else: # Views ungrouped
|
||||||
|
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
|
||||||
|
|
||||||
|
result = doUtils.downloadUrl(url)
|
||||||
|
try:
|
||||||
|
items = result['Items']
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
self.logMsg("Error retrieving views for type: %s" % type, 2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
name = item['Name']
|
||||||
|
itemId = item['Id']
|
||||||
|
viewtype = item['Type']
|
||||||
|
|
||||||
|
if viewtype == "Channel":
|
||||||
|
# Filter view types
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 11/10/2015 Review key, when it's added to server. Currently unavailable.
|
||||||
|
itemtype = item.get('OriginalCollectionType', item.get('CollectionType'))
|
||||||
|
|
||||||
|
# 11/29/2015 Remove this once OriginalCollectionType is added to stable server.
|
||||||
|
# Assumed missing is mixed then.
|
||||||
|
if itemtype is None:
|
||||||
|
url = "{server}/emby/Library/MediaFolders?format=json"
|
||||||
|
result = doUtils.downloadUrl(url)
|
||||||
|
|
||||||
|
for folder in result['Items']:
|
||||||
|
if itemId == folder['Id']:
|
||||||
|
itemtype = folder.get('CollectionType', "mixed")
|
||||||
|
|
||||||
|
if (itemtype == type or
|
||||||
|
(itemtype == "mixed" and type in ("movies", "tvshows"))):
|
||||||
|
|
||||||
|
views.append({
|
||||||
|
|
||||||
|
'name': name,
|
||||||
|
'type': itemtype,
|
||||||
|
'id': itemId
|
||||||
|
})
|
||||||
|
|
||||||
|
return views
|
||||||
|
|
||||||
|
def getMovies(self, parentId, basic=False):
|
||||||
|
|
||||||
|
items = self.getSection(parentId, "Movie", basic=basic)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getBoxset(self):
|
||||||
|
|
||||||
|
items = self.getSection(None, "BoxSet")
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getMovies_byBoxset(self, boxsetid):
|
||||||
|
|
||||||
|
items = self.getSection(boxsetid)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getMusicVideos(self, parentId, basic=False):
|
||||||
|
|
||||||
|
items = self.getSection(parentId, "MusicVideo", basic=basic)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getHomeVideos(self, parentId):
|
||||||
|
|
||||||
|
items = self.getSection(parentId, "Video")
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getShows(self, parentId, basic=False):
|
||||||
|
|
||||||
|
items = self.getSection(parentId, "Series", basic=basic)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getSeasons(self, showId):
|
||||||
|
|
||||||
|
items = {
|
||||||
|
|
||||||
|
'Items': [],
|
||||||
|
'TotalRecordCount': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'IsVirtualUnaired': False,
|
||||||
|
'Fields': "Etag"
|
||||||
|
}
|
||||||
|
result = self.doUtils.downloadUrl(url, parameters=params)
|
||||||
|
if result:
|
||||||
|
items = result
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getEpisodes(self, parentId, basic=False):
|
||||||
|
|
||||||
|
items = self.getSection(parentId, "Episode", basic=basic)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getEpisodesbyShow(self, showId):
|
||||||
|
|
||||||
|
items = self.getSection(showId, "Episode")
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getEpisodesbySeason(self, seasonId):
|
||||||
|
|
||||||
|
items = self.getSection(seasonId, "Episode")
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getArtists(self):
|
||||||
|
|
||||||
|
doUtils = self.doUtils
|
||||||
|
items = {
|
||||||
|
|
||||||
|
'Items': [],
|
||||||
|
'TotalRecordCount': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get total number of items
|
||||||
|
url = "{server}/emby/Artists?UserId={UserId}&format=json"
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'Recursive': True,
|
||||||
|
'Limit': 1
|
||||||
|
}
|
||||||
|
result = doUtils.downloadUrl(url, parameters=params)
|
||||||
|
try:
|
||||||
|
total = result['TotalRecordCount']
|
||||||
|
items['TotalRecordCount'] = total
|
||||||
|
|
||||||
|
except TypeError: # Failed to retrieve
|
||||||
|
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = 1
|
||||||
|
jump = self.limitIndex
|
||||||
|
|
||||||
|
while index < total:
|
||||||
|
# Get items by chunk to increase retrieval speed at scale
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'Recursive': True,
|
||||||
|
'IsVirtualUnaired': False,
|
||||||
|
'IsMissing': False,
|
||||||
|
'StartIndex': index,
|
||||||
|
'Limit': jump,
|
||||||
|
'SortBy': "SortName",
|
||||||
|
'SortOrder': "Ascending",
|
||||||
|
'Fields': (
|
||||||
|
|
||||||
|
"Etag,Genres,SortName,Studios,Writer,ProductionYear,"
|
||||||
|
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,"
|
||||||
|
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result = doUtils.downloadUrl(url, parameters=params)
|
||||||
|
items['Items'].extend(result['Items'])
|
||||||
|
|
||||||
|
index += jump
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getAlbums(self, basic=False):
|
||||||
|
|
||||||
|
items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getAlbumsbyArtist(self, artistId):
|
||||||
|
|
||||||
|
items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getSongs(self, basic=False):
|
||||||
|
|
||||||
|
items = self.getSection(None, "Audio", basic=basic)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getSongsbyAlbum(self, albumId):
|
||||||
|
|
||||||
|
items = self.getSection(albumId, "Audio")
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getAdditionalParts(self, itemId):
|
||||||
|
|
||||||
|
items = {
|
||||||
|
|
||||||
|
'Items': [],
|
||||||
|
'TotalRecordCount': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
|
||||||
|
result = self.doUtils.downloadUrl(url)
|
||||||
|
if result:
|
||||||
|
items = result
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def sortby_mediatype(self, itemids):
|
||||||
|
|
||||||
|
sorted_items = {}
|
||||||
|
|
||||||
|
# Sort items
|
||||||
|
items = self.getFullItems(itemids)
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
mediatype = item.get('Type')
|
||||||
|
if mediatype:
|
||||||
|
sorted_items.setdefault(mediatype, []).append(item)
|
||||||
|
|
||||||
|
return sorted_items
|
319
resources/lib/websocket_client.py
Normal file
319
resources/lib/websocket_client.py
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import websocket
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
|
||||||
|
import clientinfo
|
||||||
|
import downloadutils
|
||||||
|
import librarysync
|
||||||
|
import playlist
|
||||||
|
import userclient
|
||||||
|
import utils
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.basicConfig()
|
||||||
|
|
||||||
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocket_Client(threading.Thread):
|
||||||
|
|
||||||
|
_shared_state = {}
|
||||||
|
|
||||||
|
client = None
|
||||||
|
stopClient = False
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.__dict__ = self._shared_state
|
||||||
|
self.monitor = xbmc.Monitor()
|
||||||
|
|
||||||
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
|
self.deviceId = self.clientInfo.getDeviceId()
|
||||||
|
self.librarySync = librarysync.LibrarySync()
|
||||||
|
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
self.className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
|
def sendProgressUpdate(self, data):
|
||||||
|
self.logMsg("sendProgressUpdate", 2)
|
||||||
|
try:
|
||||||
|
messageData = {
|
||||||
|
|
||||||
|
'MessageType': "ReportPlaybackProgress",
|
||||||
|
'Data': data
|
||||||
|
}
|
||||||
|
messageString = json.dumps(messageData)
|
||||||
|
self.client.send(messageString)
|
||||||
|
self.logMsg("Message data: %s" % messageString, 2)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logMsg("Exception: %s" % e, 1)
|
||||||
|
|
||||||
|
def on_message(self, ws, message):
|
||||||
|
|
||||||
|
result = json.loads(message)
|
||||||
|
messageType = result['MessageType']
|
||||||
|
data = result['Data']
|
||||||
|
|
||||||
|
if messageType not in ('SessionEnded'):
|
||||||
|
# Mute certain events
|
||||||
|
self.logMsg("Message: %s" % message, 1)
|
||||||
|
|
||||||
|
if messageType == "Play":
|
||||||
|
# A remote control play command has been sent from the server.
|
||||||
|
itemIds = data['ItemIds']
|
||||||
|
command = data['PlayCommand']
|
||||||
|
|
||||||
|
pl = playlist.Playlist()
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
dialog.notification("Emby for Kodi", "Adding %s items to playlist." % len(itemIds))
|
||||||
|
|
||||||
|
if command == "PlayNow":
|
||||||
|
dialog.notification(
|
||||||
|
heading="Emby for Kodi",
|
||||||
|
message="Adding %s items to playlist." % len(itemIds),
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||||
|
sound=False)
|
||||||
|
startat = data.get('StartPositionTicks', 0)
|
||||||
|
pl.playAll(itemIds, startat)
|
||||||
|
|
||||||
|
elif command == "PlayNext":
|
||||||
|
dialog.notification(
|
||||||
|
heading="Emby for Kodi",
|
||||||
|
message="Queueing %s items to playlist." % len(itemIds),
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||||
|
sound=False)
|
||||||
|
newplaylist = pl.modifyPlaylist(itemIds)
|
||||||
|
player = xbmc.Player()
|
||||||
|
if not player.isPlaying():
|
||||||
|
# Only start the playlist if nothing is playing
|
||||||
|
player.play(newplaylist)
|
||||||
|
|
||||||
|
elif messageType == "Playstate":
|
||||||
|
# A remote control update playstate command has been sent from the server.
|
||||||
|
command = data['Command']
|
||||||
|
player = xbmc.Player()
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
|
||||||
|
'Stop': player.stop,
|
||||||
|
'Unpause': player.pause,
|
||||||
|
'Pause': player.pause,
|
||||||
|
'NextTrack': player.playnext,
|
||||||
|
'PreviousTrack': player.playprevious,
|
||||||
|
'Seek': player.seekTime
|
||||||
|
}
|
||||||
|
action = actions[command]
|
||||||
|
if command == "Seek":
|
||||||
|
seekto = data['SeekPositionTicks']
|
||||||
|
seektime = seekto / 10000000.0
|
||||||
|
action(seektime)
|
||||||
|
self.logMsg("Seek to %s." % seektime, 1)
|
||||||
|
else:
|
||||||
|
action()
|
||||||
|
self.logMsg("Command: %s completed." % command, 1)
|
||||||
|
|
||||||
|
utils.window('emby_command', value="true")
|
||||||
|
|
||||||
|
elif messageType == "UserDataChanged":
|
||||||
|
# A user changed their personal rating for an item, or their playstate was updated
|
||||||
|
userdata_list = data['UserDataList']
|
||||||
|
self.librarySync.triage_items("userdata", userdata_list)
|
||||||
|
|
||||||
|
elif messageType == "LibraryChanged":
|
||||||
|
|
||||||
|
librarySync = self.librarySync
|
||||||
|
processlist = {
|
||||||
|
|
||||||
|
'added': data['ItemsAdded'],
|
||||||
|
'update': data['ItemsUpdated'],
|
||||||
|
'remove': data['ItemsRemoved']
|
||||||
|
}
|
||||||
|
for action in processlist:
|
||||||
|
librarySync.triage_items(action, processlist[action])
|
||||||
|
|
||||||
|
elif messageType == "GeneralCommand":
|
||||||
|
|
||||||
|
command = data['Name']
|
||||||
|
arguments = data['Arguments']
|
||||||
|
|
||||||
|
if command in ('Mute', 'Unmute', 'SetVolume',
|
||||||
|
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
|
||||||
|
|
||||||
|
player = xbmc.Player()
|
||||||
|
# These commands need to be reported back
|
||||||
|
if command == "Mute":
|
||||||
|
xbmc.executebuiltin('Mute')
|
||||||
|
elif command == "Unmute":
|
||||||
|
xbmc.executebuiltin('Mute')
|
||||||
|
elif command == "SetVolume":
|
||||||
|
volume = arguments['Volume']
|
||||||
|
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
|
||||||
|
elif command == "SetAudioStreamIndex":
|
||||||
|
index = int(arguments['Index'])
|
||||||
|
player.setAudioStream(index - 1)
|
||||||
|
elif command == "SetSubtitleStreamIndex":
|
||||||
|
embyindex = int(arguments['Index'])
|
||||||
|
currentFile = player.getPlayingFile()
|
||||||
|
|
||||||
|
mapping = utils.window('emby_%s.indexMapping' % currentFile)
|
||||||
|
if mapping:
|
||||||
|
externalIndex = json.loads(mapping)
|
||||||
|
# If there's external subtitles added via playbackutils
|
||||||
|
for index in externalIndex:
|
||||||
|
if externalIndex[index] == embyindex:
|
||||||
|
player.setSubtitleStream(int(index))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# User selected internal subtitles
|
||||||
|
external = len(externalIndex)
|
||||||
|
audioTracks = len(player.getAvailableAudioStreams())
|
||||||
|
player.setSubtitleStream(external + embyindex - audioTracks - 1)
|
||||||
|
else:
|
||||||
|
# Emby merges audio and subtitle index together
|
||||||
|
audioTracks = len(player.getAvailableAudioStreams())
|
||||||
|
player.setSubtitleStream(index - audioTracks - 1)
|
||||||
|
|
||||||
|
# Let service know
|
||||||
|
utils.window('emby_command', value="true")
|
||||||
|
|
||||||
|
elif command == "DisplayMessage":
|
||||||
|
|
||||||
|
header = arguments['Header']
|
||||||
|
text = arguments['Text']
|
||||||
|
xbmcgui.Dialog().notification(
|
||||||
|
heading=header,
|
||||||
|
message=text,
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||||
|
time=4000)
|
||||||
|
|
||||||
|
elif command == "SendString":
|
||||||
|
|
||||||
|
string = arguments['String']
|
||||||
|
text = {
|
||||||
|
|
||||||
|
'jsonrpc': "2.0",
|
||||||
|
'id': 0,
|
||||||
|
'method': "Input.SendText",
|
||||||
|
'params': {
|
||||||
|
|
||||||
|
'text': "%s" % string,
|
||||||
|
'done': False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(text))
|
||||||
|
|
||||||
|
else:
|
||||||
|
builtin = {
|
||||||
|
|
||||||
|
'ToggleFullscreen': 'Action(FullScreen)',
|
||||||
|
'ToggleOsdMenu': 'Action(OSD)',
|
||||||
|
'ToggleContextMenu': 'Action(ContextMenu)',
|
||||||
|
'MoveUp': 'Action(Up)',
|
||||||
|
'MoveDown': 'Action(Down)',
|
||||||
|
'MoveLeft': 'Action(Left)',
|
||||||
|
'MoveRight': 'Action(Right)',
|
||||||
|
'Select': 'Action(Select)',
|
||||||
|
'Back': 'Action(back)',
|
||||||
|
'GoHome': 'ActivateWindow(Home)',
|
||||||
|
'PageUp': 'Action(PageUp)',
|
||||||
|
'NextLetter': 'Action(NextLetter)',
|
||||||
|
'GoToSearch': 'VideoLibrary.Search',
|
||||||
|
'GoToSettings': 'ActivateWindow(Settings)',
|
||||||
|
'PageDown': 'Action(PageDown)',
|
||||||
|
'PreviousLetter': 'Action(PrevLetter)',
|
||||||
|
'TakeScreenshot': 'TakeScreenshot',
|
||||||
|
'ToggleMute': 'Mute',
|
||||||
|
'VolumeUp': 'Action(VolumeUp)',
|
||||||
|
'VolumeDown': 'Action(VolumeDown)',
|
||||||
|
}
|
||||||
|
action = builtin.get(command)
|
||||||
|
if action:
|
||||||
|
xbmc.executebuiltin(action)
|
||||||
|
|
||||||
|
elif messageType == "ServerRestarting":
|
||||||
|
if utils.settings('supressRestartMsg') == "true":
|
||||||
|
xbmcgui.Dialog().notification(
|
||||||
|
heading="Emby server",
|
||||||
|
message="Server is restarting.",
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png")
|
||||||
|
|
||||||
|
elif messageType == "UserConfigurationUpdated":
|
||||||
|
# Update user data set in userclient
|
||||||
|
userclient.UserClient().userSettings = data
|
||||||
|
self.librarySync.refresh_views = True
|
||||||
|
|
||||||
|
def on_close(self, ws):
|
||||||
|
self.logMsg("Closed.", 2)
|
||||||
|
|
||||||
|
def on_open(self, ws):
|
||||||
|
self.doUtils.postCapabilities(self.deviceId)
|
||||||
|
|
||||||
|
def on_error(self, ws, error):
|
||||||
|
if "10061" in str(error):
|
||||||
|
# Server is offline
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.logMsg("Error: %s" % error, 2)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
monitor = self.monitor
|
||||||
|
loglevel = int(utils.window('emby_logLevel'))
|
||||||
|
# websocket.enableTrace(True)
|
||||||
|
|
||||||
|
userId = utils.window('emby_currUser')
|
||||||
|
server = utils.window('emby_server%s' % userId)
|
||||||
|
token = utils.window('emby_accessToken%s' % userId)
|
||||||
|
deviceId = self.deviceId
|
||||||
|
|
||||||
|
# Get the appropriate prefix for the websocket
|
||||||
|
if "https" in server:
|
||||||
|
server = server.replace('https', "wss")
|
||||||
|
else:
|
||||||
|
server = server.replace('http', "ws")
|
||||||
|
|
||||||
|
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
|
||||||
|
self.logMsg("websocket url: %s" % websocket_url, 1)
|
||||||
|
|
||||||
|
self.client = websocket.WebSocketApp(websocket_url,
|
||||||
|
on_message=self.on_message,
|
||||||
|
on_error=self.on_error,
|
||||||
|
on_close=self.on_close)
|
||||||
|
|
||||||
|
self.client.on_open = self.on_open
|
||||||
|
self.logMsg("----===## Starting WebSocketClient ##===----", 0)
|
||||||
|
|
||||||
|
while not monitor.abortRequested():
|
||||||
|
|
||||||
|
self.client.run_forever()
|
||||||
|
|
||||||
|
if self.stopClient:
|
||||||
|
break
|
||||||
|
|
||||||
|
if monitor.waitForAbort(5):
|
||||||
|
# Abort was requested, exit
|
||||||
|
break
|
||||||
|
|
||||||
|
self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
|
||||||
|
|
||||||
|
def stopClient(self):
|
||||||
|
|
||||||
|
self.stopClient = True
|
||||||
|
self.client.close()
|
||||||
|
self.logMsg("Stopping thread.")
|
|
@ -1,63 +1,69 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<category label="30014"> <!-- Emby -->
|
<category label="30014"><!-- Emby -->
|
||||||
<setting id="ipaddress" type="text" label="30000" default="" visible="true" enable="true" />
|
<!-- Primary address -->
|
||||||
<setting id="port" type="number" label="30030" default="8096" visible="true" enable="true" />
|
<setting id="ipaddress" label="30000" type="text" default="" />
|
||||||
<setting id="https" type="bool" label="30243" visible="true" enable="true" default="false" />
|
<setting id="port" label="30030" type="number" default="8096" />
|
||||||
<setting id="sslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
|
<setting id="https" label="30243" type="bool" default="false" />
|
||||||
<setting id="sslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
|
<setting id="sslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
|
||||||
<setting id="altip" type="bool" label="Use alternate address" default="false" visible="true" enable="true" />
|
<setting id="sslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
|
||||||
<setting id="secondipaddress" type="text" label="Secondary Server Address" default="" visible="eq(-1,true)" enable="true" />
|
<!-- Secondary address -->
|
||||||
<setting id="secondport" type="number" label="30030" default="8096" visible="eq(-2,true)" enable="true" />
|
<setting id="altip" label="Use alternate address" type="bool" default="false" />
|
||||||
<setting id="secondhttps" type="bool" label="30243" visible="true" enable="eq(-3,true)" default="false" />
|
<setting id="secondipaddress" label="Secondary Server Address" type="text" default="" visible="eq(-1,true)" />
|
||||||
<setting id="secondsslverify" type="bool" label="Verify Host SSL Certificate" visible="eq(-1,true)" enable="true" default="false" />
|
<setting id="secondport" label="30030" type="number" default="8096" visible="eq(-2,true)" />
|
||||||
<setting id="secondsslcert" type="file" label="Client SSL certificate" visible="eq(-2,true)" enable="true" default="None" />
|
<setting id="secondhttps" label="30243" type="bool" default="false" visible="eq(-3,true)" />
|
||||||
<setting id="pathsub" type="bool" visible="false" default="false" />
|
<setting id="secondsslverify" subsetting="true" label="Verify Host SSL Certificate" type="bool" default="false" visible="eq(-1,true)" />
|
||||||
<setting id="username" type="text" label="30024" default="" />
|
<setting id="secondsslcert" subsetting="true" label="Client SSL certificate" type="file" default="None" visible="eq(-2,true)" />
|
||||||
|
<!-- User settings -->
|
||||||
|
<setting id="username" label="30024" type="text" default="" />
|
||||||
<setting type="sep" />
|
<setting type="sep" />
|
||||||
<setting id="deviceNameOpt" type="bool" label="Use altername Device Name" visible="true" enable="true" default="true" />
|
<setting id="deviceNameOpt" label="Use altername Device Name" type="bool" default="false" />
|
||||||
<setting id="deviceName" type="text" label="30016" visible="eq(-1,true)" enable="true" default="Kodi" />
|
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
|
||||||
|
<setting label="[COLOR yellow]Reset login attempts[/COLOR]" type="action" visible="eq(1,) + !eq(-15,)" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
|
||||||
<setting id="accessToken" type="text" visible="false" default="" />
|
<setting id="accessToken" type="text" visible="false" default="" />
|
||||||
<setting label="[COLOR yellow]Reset login attempts[/COLOR]" type="action" visible="eq(-1,) + !eq(-9,)" enable="true" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
|
<setting id="pathsub" type="bool" visible="false" default="false" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="Sync Options">
|
<category label="Sync Options">
|
||||||
<setting id="dbSyncIndication" type="bool" label="Show sync progress on screen" default="false" visible="true" enable="true" />
|
<setting id="dbSyncIndicator" label="Show sync progress" type="bool" default="false" />
|
||||||
<setting id="syncSpecialsOrder" type="bool" label="Sync specials ordering" default="false" visible="true" enable="true" />
|
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" />
|
||||||
<setting id="syncEmptyShows" type="bool" label="Sync empty TV Shows" default="false" visible="true" enable="true" />
|
<setting id="enableMusic" type="bool" label="Enable Music Library Sync" default="true" />
|
||||||
<setting id="enableMusicSync" type="bool" label="Enable Music Library Sync" default="true" visible="true" enable="true" />
|
<setting id="streamMusic" type="bool" label="Direct stream music library" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||||
<setting id="directstreammusic" type="bool" label="- Direct stream music library" default="false" visible="eq(-1,true)" enable="true" />
|
<setting id="useDirectPaths" type="enum" label="Playback Mode" values="Addon(Default)|Native(Direct paths)" default="0" />
|
||||||
<setting id="useDirectPaths" type="bool" label="30250" default="false" visible="true" enable="true" />
|
<setting id="enableTextureCache" label="Auto add images to the Kodi texture cache" type="bool" default="true" />
|
||||||
<setting id="enableTextureCache" type="bool" label="Auto add images to the Kodi texture cache" default="true" visible="true" enable="true" />
|
<setting id="serverSync" type="bool" label="Enable fast starting sync" default="true" />
|
||||||
<setting id="useIncSync" type="bool" label="Use incremental sync at startup (Requires Server Plugin)" default="false" visible="true" enable="true" />
|
|
||||||
<setting id="incSyncMaxItems" type="number" label=" - Max change set size" default="1000" visible="eq(-1,true)" enable="true" />
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="Playback"> <!-- Extra Sync options -->
|
<category label="Playback"> <!-- Extra Sync options -->
|
||||||
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
|
<setting label="Network credentials" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=passwords)" option="close" />
|
||||||
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
|
|
||||||
<setting type="sep" />
|
<setting type="sep" />
|
||||||
<setting id="disableCinema" type="bool" label="Disable Emby cinema mode" default="false" visible="true" enable="true" />
|
<setting id="enableCinema" type="bool" label="Enable Emby cinema mode" default="true" />
|
||||||
<setting id="askCinema" type="bool" label="Ask to play trailers" default="false" visible="eq(-1,false)" enable="true" subsetting="true" />
|
<setting id="askCinema" type="bool" label="Ask to play trailers" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||||
<setting id="offerDelete" type="bool" label="30114" visible="true" enable="true" default="false" />
|
<setting id="offerDelete" type="bool" label="30114" default="false" />
|
||||||
<setting id="offerDeleteTV" type="bool" label="30115" visible="eq(-1,true)" enable="true" default="false" subsetting="true" />
|
<setting id="deleteTV" type="bool" label="30115" visible="eq(-1,true)" default="false" subsetting="true" />
|
||||||
<setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" subsetting="true" />
|
<setting id="deleteMovies" type="bool" label="30116" visible="eq(-2,true)" default="false" subsetting="true" />
|
||||||
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" visible="true" enable="true" />
|
<setting id="skipConfirmDelete" type="bool" label="Skip delete confirmation" visible="eq(-3,true)" default="false" subsetting="true" />
|
||||||
<setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" />
|
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" />
|
||||||
<setting id="videoBitRate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" default="17" />
|
<setting id="playFromStream" type="bool" label="30002" default="false" />
|
||||||
|
<setting id="videoBitrate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="eq(-1,true)" default="17" />
|
||||||
<setting id="transcodeH265" type="bool" label="Force transcode 1080p/H265" default="false" />
|
<setting id="transcodeH265" type="bool" label="Force transcode 1080p/H265" default="false" />
|
||||||
<setting id="markPlayed" type="number" visible="false" default="90" />
|
<setting id="markPlayed" type="number" visible="false" default="90" />
|
||||||
<setting id="directSteamFailedCount" type="number" visible="false" default="0" />
|
<setting id="failedCount" type="number" visible="false" default="0" />
|
||||||
|
<setting id="networkCreds" type="text" visible="false" default="" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="Extras">
|
<category label="Extras">
|
||||||
<setting id="disableCoverArt" type="bool" label="30157" default="false" visible="true" enable="true" />
|
<setting id="enableCoverArt" type="bool" label="30157" default="true" />
|
||||||
<setting id="coverArtratio" type="bool" label="Force CoverArt Ratio" visible="eq(-1,false)" default="false" enable="true" />
|
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
|
||||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" visible="true" enable="true" default="false" />
|
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
||||||
<setting id="showSpecialInfoDialog" type="bool" label="Show special Emby info dialog on play" default="false" visible="false" />
|
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
||||||
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" visible="true" enable="true" />
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30022">
|
<category label="30022">
|
||||||
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="0" />
|
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="1" />
|
||||||
<setting id="supressConnectMsg" type="bool" label="30249" default="false" visible="true" enable="true" />
|
<setting id="connectMsg" type="bool" label="30249" default="true" />
|
||||||
<setting id="supressRestartMsg" type="bool" label="Enable server message when it's restarting" default="false" visible="true" enable="true" />
|
<setting id="restartMsg" type="bool" label="Enable server message when it's restarting" default="false" />
|
||||||
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" />
|
<setting id="newContent" type="bool" label="Enable new content notification" default="false" />
|
||||||
|
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" option="close" />
|
||||||
</category>
|
</category>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
273
service.py
273
service.py
|
@ -7,8 +7,8 @@ import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmc
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
|
@ -16,152 +16,142 @@ import xbmcvfs
|
||||||
|
|
||||||
_addon = xbmcaddon.Addon(id='plugin.video.emby')
|
_addon = xbmcaddon.Addon(id='plugin.video.emby')
|
||||||
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||||
base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||||
sys.path.append(base_resource_path)
|
sys.path.append(base_resource)
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
import KodiMonitor
|
import userclient
|
||||||
import Utils as utils
|
import clientinfo
|
||||||
from ClientInformation import ClientInformation
|
import initialsetup
|
||||||
from ConnectionManager import ConnectionManager
|
import kodimonitor
|
||||||
from UserClient import UserClient
|
import librarysync
|
||||||
from Player import Player
|
import player
|
||||||
from WebSocketClient import WebSocketThread
|
import utils
|
||||||
from LibrarySync import LibrarySync
|
import videonodes
|
||||||
|
import websocket_client as wsc
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
|
||||||
class Service():
|
class Service():
|
||||||
|
|
||||||
KodiMonitor = KodiMonitor.Kodi_Monitor()
|
|
||||||
clientInfo = ClientInformation()
|
|
||||||
|
|
||||||
addonName = clientInfo.getAddonName()
|
|
||||||
logLevel = UserClient().getLogLevel()
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
|
|
||||||
newWebSocketThread = None
|
|
||||||
newUserClient = None
|
|
||||||
newLibraryThread = None
|
|
||||||
warn_auth = True
|
|
||||||
welcome_msg = True
|
welcome_msg = True
|
||||||
server_online = True
|
server_online = True
|
||||||
|
warn_auth = True
|
||||||
|
|
||||||
def __init__(self, *args):
|
userclient_running = False
|
||||||
|
websocket_running = False
|
||||||
|
library_running = False
|
||||||
|
kodimonitor_running = False
|
||||||
|
|
||||||
addonName = self.addonName
|
|
||||||
clientInfo = self.clientInfo
|
|
||||||
logLevel = self.logLevel
|
|
||||||
|
|
||||||
utils.window('getLogLevel', value=str(logLevel))
|
def __init__(self):
|
||||||
utils.window('kodiProfile_emby', value=xbmc.translatePath("special://profile"))
|
|
||||||
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
|
logLevel = userclient.UserClient().getLogLevel()
|
||||||
|
self.monitor = xbmc.Monitor()
|
||||||
|
|
||||||
|
utils.window('emby_logLevel', value=str(logLevel))
|
||||||
|
utils.window('emby_kodiProfile', value=xbmc.translatePath("special://profile"))
|
||||||
|
|
||||||
# Initial logging
|
# Initial logging
|
||||||
self.logMsg("Starting Monitor", 0)
|
self.logMsg("======== START %s ========" % self.addonName, 0)
|
||||||
self.logMsg("======== START %s ========" % addonName, 0)
|
self.logMsg("Platform: %s" % (self.clientInfo.getPlatform()), 0)
|
||||||
self.logMsg("Platform: %s" % (clientInfo.getPlatform()), 0)
|
|
||||||
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0)
|
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0)
|
||||||
self.logMsg("%s Version: %s" % (addonName, clientInfo.getVersion()), 0)
|
self.logMsg("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0)
|
||||||
self.logMsg("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0)
|
self.logMsg("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0)
|
||||||
self.logMsg("Log Level: %s" % logLevel, 0)
|
self.logMsg("Log Level: %s" % logLevel, 0)
|
||||||
|
|
||||||
# Reset window props for profile switch
|
# Reset window props for profile switch
|
||||||
utils.window('Server_online', clear=True)
|
properties = [
|
||||||
utils.window('Server_status', clear=True)
|
|
||||||
utils.window('startup', clear=True)
|
|
||||||
utils.window('OnWakeSync', clear=True)
|
|
||||||
utils.window('kodiScan', clear=True)
|
|
||||||
utils.window('minDBVersionCheck', clear=True)
|
|
||||||
|
|
||||||
# Set min DB version
|
|
||||||
utils.window('minDBVersion', value="1.1.52")
|
|
||||||
|
|
||||||
embyProperty = utils.window('Emby.nodes.total')
|
"emby_online", "emby_serverStatus", "emby_onWake",
|
||||||
propNames = [
|
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
||||||
|
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId"
|
||||||
"index","path","title","content",
|
|
||||||
"inprogress.content","inprogress.title",
|
|
||||||
"inprogress.content","inprogress.path",
|
|
||||||
"nextepisodes.title","nextepisodes.content",
|
|
||||||
"nextepisodes.path","unwatched.title",
|
|
||||||
"unwatched.content","unwatched.path",
|
|
||||||
"recent.title","recent.content","recent.path",
|
|
||||||
"recentepisodes.title","recentepisodes.content",
|
|
||||||
"recentepisodes.path","inprogressepisodes.title",
|
|
||||||
"inprogressepisodes.content","inprogressepisodes.path"
|
|
||||||
]
|
]
|
||||||
|
for prop in properties:
|
||||||
|
utils.window(prop, clear=True)
|
||||||
|
|
||||||
if embyProperty:
|
# Clear playlist properties
|
||||||
totalNodes = int(embyProperty)
|
xbmcgui.Window(10101).clearProperties()
|
||||||
for i in range(totalNodes):
|
# Clear video nodes properties
|
||||||
for prop in propNames:
|
videonodes.VideoNodes().clearProperties()
|
||||||
utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
|
||||||
|
# Set the minimum database version
|
||||||
|
utils.window('emby_minDBVersion', value="1.1.63")
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
className = self.__class__.__name__
|
className = self.__class__.__name__
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
|
||||||
def ServiceEntryPoint(self):
|
def ServiceEntryPoint(self):
|
||||||
|
# Important: Threads depending on abortRequest will not trigger
|
||||||
|
# if profile switch happens more than once.
|
||||||
|
monitor = self.monitor
|
||||||
kodiProfile = xbmc.translatePath("special://profile")
|
kodiProfile = xbmc.translatePath("special://profile")
|
||||||
|
|
||||||
# Server auto-detect
|
# Server auto-detect
|
||||||
ConnectionManager().checkServer()
|
initialsetup.InitialSetup().setup()
|
||||||
|
|
||||||
# Initialize important threads
|
# Initialize important threads
|
||||||
user = UserClient()
|
user = userclient.UserClient()
|
||||||
player = Player()
|
ws = wsc.WebSocket_Client()
|
||||||
ws = WebSocketThread()
|
library = librarysync.LibrarySync()
|
||||||
library = LibrarySync()
|
kplayer = player.Player()
|
||||||
# Sync and progress report
|
# Sync and progress report
|
||||||
lastProgressUpdate = datetime.today()
|
lastProgressUpdate = datetime.today()
|
||||||
|
|
||||||
while not self.KodiMonitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
|
|
||||||
|
if utils.window('emby_kodiProfile') != kodiProfile:
|
||||||
|
# Profile change happened, terminate this thread and others
|
||||||
|
self.logMsg(
|
||||||
|
"Kodi profile was: %s and changed to: %s. Terminating old Emby thread."
|
||||||
|
% (kodiProfile, utils.window('emby_kodiProfile')), 1)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
# Before proceeding, need to make sure:
|
# Before proceeding, need to make sure:
|
||||||
# 1. Server is online
|
# 1. Server is online
|
||||||
# 2. User is set
|
# 2. User is set
|
||||||
# 3. User has access to the server
|
# 3. User has access to the server
|
||||||
|
|
||||||
if utils.window("kodiProfile_emby") != kodiProfile:
|
if utils.window('emby_online') == "true":
|
||||||
# Profile change happened, terminate this thread
|
|
||||||
self.logMsg("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1)
|
|
||||||
break
|
|
||||||
|
|
||||||
if utils.window('Server_online') == "true":
|
|
||||||
|
|
||||||
# Emby server is online
|
# Emby server is online
|
||||||
# Verify if user is set and has access to the server
|
# Verify if user is set and has access to the server
|
||||||
if (user.currUser is not None) and user.HasAccess:
|
if (user.currUser is not None) and user.HasAccess:
|
||||||
|
|
||||||
# If an item is playing
|
# If an item is playing
|
||||||
if xbmc.Player().isPlaying():
|
if xbmc.Player().isPlaying():
|
||||||
try:
|
try:
|
||||||
# Update and report progress
|
# Update and report progress
|
||||||
playTime = xbmc.Player().getTime()
|
playtime = xbmc.Player().getTime()
|
||||||
totalTime = xbmc.Player().getTotalTime()
|
totalTime = xbmc.Player().getTotalTime()
|
||||||
currentFile = player.currentFile
|
currentFile = kplayer.currentFile
|
||||||
|
|
||||||
# Update positionticks
|
# Update positionticks
|
||||||
if player.played_information.get(currentFile) is not None:
|
if kplayer.played_info.get(currentFile) is not None:
|
||||||
player.played_information[currentFile]['currentPosition'] = playTime
|
kplayer.played_info[currentFile]['currentPosition'] = playtime
|
||||||
|
|
||||||
td = datetime.today() - lastProgressUpdate
|
td = datetime.today() - lastProgressUpdate
|
||||||
secDiff = td.seconds
|
secDiff = td.seconds
|
||||||
|
|
||||||
# Report progress to Emby server
|
# Report progress to Emby server
|
||||||
if (secDiff > 3):
|
if (secDiff > 3):
|
||||||
player.reportPlayback()
|
kplayer.reportPlayback()
|
||||||
lastProgressUpdate = datetime.today()
|
lastProgressUpdate = datetime.today()
|
||||||
|
|
||||||
elif utils.window('commandUpdate') == "true":
|
elif utils.window('emby_command') == "true":
|
||||||
# Received a remote control command that
|
# Received a remote control command that
|
||||||
# requires updating immediately
|
# requires updating immediately
|
||||||
utils.window('commandUpdate', clear=True)
|
utils.window('emby_command', clear=True)
|
||||||
player.reportPlayback()
|
kplayer.reportPlayback()
|
||||||
lastProgressUpdate = da4tetime.today()
|
lastProgressUpdate = datetime.today()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logMsg("Exception in Playback Monitor Service: %s" % e, 1)
|
self.logMsg("Exception in Playback Monitor Service: %s" % e, 1)
|
||||||
|
@ -169,27 +159,34 @@ class Service():
|
||||||
else:
|
else:
|
||||||
# Start up events
|
# Start up events
|
||||||
self.warn_auth = True
|
self.warn_auth = True
|
||||||
if utils.settings('supressConnectMsg') == "false":
|
if utils.settings('connectMsg') == "true" and self.welcome_msg:
|
||||||
if self.welcome_msg:
|
# Reset authentication warnings
|
||||||
# Reset authentication warnings
|
self.welcome_msg = False
|
||||||
self.welcome_msg = False
|
# Get additional users
|
||||||
# Get additional users
|
additionalUsers = user.AdditionalUser
|
||||||
additionalUsers = user.AdditionalUser
|
if additionalUsers:
|
||||||
if additionalUsers:
|
add = ", %s" % ", ".join(additionalUsers)
|
||||||
add = ", %s" % ", ".join(additionalUsers)
|
else:
|
||||||
else:
|
add = ""
|
||||||
add = ""
|
xbmcgui.Dialog().notification(
|
||||||
xbmcgui.Dialog().notification("Emby server", "Welcome %s%s!" % (user.currUser, add), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False)
|
heading="Emby server",
|
||||||
|
message="Welcome %s%s!" % (user.currUser, add),
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||||
|
time=2000,
|
||||||
|
sound=False)
|
||||||
|
|
||||||
|
# Start monitoring kodi events
|
||||||
|
if not self.kodimonitor_running:
|
||||||
|
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||||
|
|
||||||
# Start the Websocket Client
|
# Start the Websocket Client
|
||||||
if (self.newWebSocketThread is None):
|
if not self.websocket_running:
|
||||||
self.newWebSocketThread = "Started"
|
self.websocket_running = True
|
||||||
ws.start()
|
ws.start()
|
||||||
# Start the Library Sync Thread
|
# Start the syncing thread
|
||||||
if (self.newLibraryThread is None):
|
if not self.library_running:
|
||||||
self.newLibraryThread = "Started"
|
self.library_running = True
|
||||||
library.start()
|
library.start()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if (user.currUser is None) and self.warn_auth:
|
if (user.currUser is None) and self.warn_auth:
|
||||||
|
@ -204,20 +201,19 @@ class Service():
|
||||||
# Verify access with an API call
|
# Verify access with an API call
|
||||||
user.hasAccess()
|
user.hasAccess()
|
||||||
|
|
||||||
if utils.window('Server_online') != "true":
|
if utils.window('emby_online') != "true":
|
||||||
# Server went offline
|
# Server went offline
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.KodiMonitor.waitForAbort(5):
|
if monitor.waitForAbort(5):
|
||||||
# Abort was requested while waiting. We should exit
|
# Abort was requested while waiting. We should exit
|
||||||
break
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Wait until Emby server is online
|
# Wait until Emby server is online
|
||||||
# or Kodi is shut down.
|
# or Kodi is shut down.
|
||||||
while not self.KodiMonitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
|
|
||||||
if user.getServer() == "":
|
if user.getServer() == False:
|
||||||
# No server info set in add-on settings
|
# No server info set in add-on settings
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -226,8 +222,14 @@ class Service():
|
||||||
# Alert the user and suppress future warning
|
# Alert the user and suppress future warning
|
||||||
if self.server_online:
|
if self.server_online:
|
||||||
self.logMsg("Server is offline.", 1)
|
self.logMsg("Server is offline.", 1)
|
||||||
utils.window('Server_online', value="false")
|
utils.window('emby_online', value="false")
|
||||||
xbmcgui.Dialog().notification("Error connecting", "%s Server is unreachable." % self.addonName, icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
|
|
||||||
|
xbmcgui.Dialog().notification(
|
||||||
|
heading="Error connecting",
|
||||||
|
message="%s Server is unreachable." % self.addonName,
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||||
|
sound=False)
|
||||||
|
|
||||||
self.server_online = False
|
self.server_online = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -235,54 +237,55 @@ class Service():
|
||||||
if not self.server_online:
|
if not self.server_online:
|
||||||
# Server was offline when Kodi started.
|
# Server was offline when Kodi started.
|
||||||
# Wait for server to be fully established.
|
# Wait for server to be fully established.
|
||||||
if self.KodiMonitor.waitForAbort(5):
|
if monitor.waitForAbort(5):
|
||||||
# Abort was requested while waiting.
|
# Abort was requested while waiting.
|
||||||
break
|
break
|
||||||
# Alert the user that server is online.
|
# Alert the user that server is online.
|
||||||
xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % user.currUser, icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False)
|
xbmcgui.Dialog().notification(
|
||||||
|
heading="Emby server",
|
||||||
|
message="Server is online.",
|
||||||
|
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||||
|
time=2000,
|
||||||
|
sound=False)
|
||||||
|
|
||||||
self.server_online = True
|
self.server_online = True
|
||||||
self.logMsg("Server is online and ready.", 1)
|
self.logMsg("Server is online and ready.", 1)
|
||||||
utils.window('Server_online', value="true")
|
utils.window('emby_online', value="true")
|
||||||
|
|
||||||
# Start the User client
|
# Start the userclient thread
|
||||||
if self.newUserClient is None:
|
if not self.userclient_running:
|
||||||
self.newUserClient = "Started"
|
self.userclient_running = True
|
||||||
user.start()
|
user.start()
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.KodiMonitor.waitForAbort(1):
|
if monitor.waitForAbort(1):
|
||||||
# Abort was requested while waiting.
|
# Abort was requested while waiting.
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.KodiMonitor.waitForAbort(1):
|
if monitor.waitForAbort(1):
|
||||||
# Abort was requested while waiting. We should exit
|
# Abort was requested while waiting. We should exit
|
||||||
break
|
break
|
||||||
|
|
||||||
##### Emby thread is terminating. #####
|
##### Emby thread is terminating. #####
|
||||||
|
|
||||||
# If music is enabled and direct stream for music is enabled
|
if self.library_running:
|
||||||
# We use Kodi pathsubstitution to allow for music to play outside network
|
library.stopThread()
|
||||||
# The setting needs to be set before Kodi starts.
|
|
||||||
if utils.settings('enableMusicSync') == "true" and utils.settings('directstreammusic') == "true":
|
|
||||||
# We need to keep track of the settings
|
|
||||||
alternate = utils.settings('altip') == "true"
|
|
||||||
pathsub = utils.settings('pathsub') == "true"
|
|
||||||
|
|
||||||
if pathsub and not alternate:
|
|
||||||
# Path sub in place, but primary address in use, remove it
|
|
||||||
utils.pathsubstitution(False)
|
|
||||||
elif not pathsub and alternate:
|
|
||||||
# Path sub not in place, but secondary address in use, add it
|
|
||||||
utils.pathsubstitution()
|
|
||||||
|
|
||||||
if (self.newWebSocketThread is not None):
|
|
||||||
ws.stopClient()
|
|
||||||
|
|
||||||
if (self.newUserClient is not None):
|
if self.websocket_running:
|
||||||
|
ws.stopClient()
|
||||||
|
|
||||||
|
if self.userclient_running:
|
||||||
user.stopClient()
|
user.stopClient()
|
||||||
|
|
||||||
self.logMsg("======== STOP %s ========" % self.addonName, 0)
|
self.logMsg("======== STOP %s ========" % self.addonName, 0)
|
||||||
|
|
||||||
# Start the service
|
# Delay option
|
||||||
Service().ServiceEntryPoint()
|
delay = int(utils.settings('startupDelay'))
|
||||||
|
|
||||||
|
xbmc.log("Delaying emby startup by: %s sec..." % delay)
|
||||||
|
if delay and xbmc.Monitor().waitForAbort(delay):
|
||||||
|
# Start the service
|
||||||
|
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
||||||
|
else:
|
||||||
|
Service().ServiceEntryPoint()
|
Loading…
Reference in a new issue