Merge remote-tracking branch 'MediaBrowser/master' into develop
This commit is contained in:
commit
80e1957d25
23 changed files with 634 additions and 375 deletions
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.plexkodiconnect"
|
<addon id="plugin.video.plexkodiconnect"
|
||||||
name="PlexKodiConnect"
|
name="PlexKodiConnect"
|
||||||
version="1.1.72"
|
version="1.1.79"
|
||||||
provider-name="croneter">
|
provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource"
|
<extension point="xbmc.python.pluginsource"
|
||||||
library="default.py">
|
library="default.py">
|
||||||
<provides>executable video audio image</provides>
|
<provides>video audio image</provides>
|
||||||
</extension>
|
</extension>
|
||||||
<extension point="xbmc.service" library="service.py" start="login">
|
<extension point="xbmc.service" library="service.py" start="login">
|
||||||
</extension>
|
</extension>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<item>
|
<item>
|
||||||
<label>30401</label>
|
<label>30401</label>
|
||||||
<description>Settings for the Emby Server</description>
|
<description>Settings for the Emby Server</description>
|
||||||
<visible>[!IsEmpty(ListItem.DBID) + !IsEmpty(ListItem.DBTYPE)] | !IsEmpty(ListItem.Property(embyid))</visible>
|
<visible>[!IsEmpty(ListItem.DBID) + !StringCompare(ListItem.DBID,-1)] | !IsEmpty(ListItem.Property(embyid))</visible>
|
||||||
</item>
|
</item>
|
||||||
</extension>
|
</extension>
|
||||||
<extension point="xbmc.addon.metadata">
|
<extension point="xbmc.addon.metadata">
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
version 1.1.76
|
||||||
|
- Add music rating system
|
||||||
|
- Add home videos as a dynamic plugin entry (requires a reset)
|
||||||
|
- Add photo library
|
||||||
|
- Add/Fix force transcode setting for 720p-1080p/HEVC-H265 formats
|
||||||
|
- Fix to incremental sync, caused by the server restarting
|
||||||
|
- Fix for image caching during the initial sync on rpi devices
|
||||||
|
- Fix to audio/subtitles tracks (requires a repair, or reset)
|
||||||
|
|
||||||
version 1.1.72
|
version 1.1.72
|
||||||
- Fix to extrafanart
|
- Fix to extrafanart
|
||||||
- Fix for artists deletion
|
- Fix for artists deletion
|
||||||
|
|
|
@ -112,7 +112,9 @@ if __name__ == '__main__':
|
||||||
if newvalue:
|
if newvalue:
|
||||||
newvalue = int(newvalue)
|
newvalue = int(newvalue)
|
||||||
if newvalue > 5: newvalue = "5"
|
if newvalue > 5: newvalue = "5"
|
||||||
|
if utils.settings('enableUpdateSongRating') == "true":
|
||||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||||
|
if utils.settings('enableExportSongRating') == "true":
|
||||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||||
API.updateUserRating(embyid, like, favourite, deletelike)
|
API.updateUserRating(embyid, like, favourite, deletelike)
|
||||||
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
||||||
|
|
|
@ -33,7 +33,6 @@ class Main:
|
||||||
# Parse parameters
|
# Parse parameters
|
||||||
xbmc.log("Full sys.argv received: %s" % sys.argv)
|
xbmc.log("Full sys.argv received: %s" % sys.argv)
|
||||||
base_url = sys.argv[0]
|
base_url = sys.argv[0]
|
||||||
addon_handle = int(sys.argv[1])
|
|
||||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
params = urlparse.parse_qs(sys.argv[2][1:])
|
||||||
xbmc.log("Parameter string: %s" % sys.argv[2])
|
xbmc.log("Parameter string: %s" % sys.argv[2])
|
||||||
try:
|
try:
|
||||||
|
@ -62,6 +61,7 @@ class Main:
|
||||||
'channels': entrypoint.BrowseChannels,
|
'channels': entrypoint.BrowseChannels,
|
||||||
'channelsfolder': entrypoint.BrowseChannels,
|
'channelsfolder': entrypoint.BrowseChannels,
|
||||||
'browsecontent': entrypoint.BrowseContent,
|
'browsecontent': entrypoint.BrowseContent,
|
||||||
|
'getsubfolders': entrypoint.GetSubFolders,
|
||||||
'nextup': entrypoint.getNextUpEpisodes,
|
'nextup': entrypoint.getNextUpEpisodes,
|
||||||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||||
'recentepisodes': entrypoint.getRecentEpisodes,
|
'recentepisodes': entrypoint.getRecentEpisodes,
|
||||||
|
@ -82,11 +82,11 @@ class Main:
|
||||||
limit = int(params['limit'][0])
|
limit = int(params['limit'][0])
|
||||||
modes[mode](itemid, limit)
|
modes[mode](itemid, limit)
|
||||||
|
|
||||||
elif mode == "channels":
|
elif mode in ["channels","getsubfolders"]:
|
||||||
modes[mode](itemid)
|
modes[mode](itemid)
|
||||||
|
|
||||||
elif mode == "browsecontent":
|
elif mode == "browsecontent":
|
||||||
modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0], params.get('filter',[""])[0] )
|
modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0] )
|
||||||
|
|
||||||
elif mode == "channelsfolder":
|
elif mode == "channelsfolder":
|
||||||
folderid = params['folderid'][0]
|
folderid = params['folderid'][0]
|
||||||
|
|
|
@ -248,6 +248,10 @@
|
||||||
<string id="30254">Favourite Photos</string>
|
<string id="30254">Favourite Photos</string>
|
||||||
<string id="30255">Favourite Albums</string>
|
<string id="30255">Favourite Albums</string>
|
||||||
|
|
||||||
|
<string id="30256">Recently added Music videos</string>
|
||||||
|
<string id="30257">In progress Music videos</string>
|
||||||
|
<string id="30258">Unwatched Music videos</string>
|
||||||
|
|
||||||
<!-- Default views -->
|
<!-- Default views -->
|
||||||
<string id="30300">Active</string>
|
<string id="30300">Active</string>
|
||||||
<string id="30301">Clear Settings</string>
|
<string id="30301">Clear Settings</string>
|
||||||
|
@ -274,5 +278,4 @@
|
||||||
<string id="30408">Emby addon settings</string>
|
<string id="30408">Emby addon settings</string>
|
||||||
<string id="30409">Delete item from the server</string>
|
<string id="30409">Delete item from the server</string>
|
||||||
|
|
||||||
|
|
||||||
</strings>
|
</strings>
|
||||||
|
|
|
@ -8,10 +8,12 @@ import os
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
import clientinfo
|
import clientinfo
|
||||||
|
import image_cache_thread
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
@ -23,13 +25,18 @@ class Artwork():
|
||||||
xbmc_username = None
|
xbmc_username = None
|
||||||
xbmc_password = None
|
xbmc_password = None
|
||||||
|
|
||||||
|
imageCacheThreads = []
|
||||||
|
imageCacheLimitThreads = 0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.clientinfo = clientinfo.ClientInfo()
|
self.clientinfo = clientinfo.ClientInfo()
|
||||||
self.addonName = self.clientinfo.getAddonName()
|
self.addonName = self.clientinfo.getAddonName()
|
||||||
|
|
||||||
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
|
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
|
||||||
|
self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit"))
|
||||||
|
self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5);
|
||||||
|
utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1)
|
||||||
|
|
||||||
if not self.xbmc_port and self.enableTextureCache:
|
if not self.xbmc_port and self.enableTextureCache:
|
||||||
self.setKodiWebServerDetails()
|
self.setKodiWebServerDetails()
|
||||||
|
|
||||||
|
@ -37,7 +44,6 @@ class Artwork():
|
||||||
self.server = utils.window('emby_server%s' % self.userId)
|
self.server = utils.window('emby_server%s' % self.userId)
|
||||||
|
|
||||||
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, lvl)
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
@ -49,10 +55,11 @@ class Artwork():
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def single_urlencode(self, text):
|
def single_urlencode(self, text):
|
||||||
text = urllib.urlencode({'blahblahblah':text})
|
|
||||||
|
text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string
|
||||||
text = text[13:]
|
text = text[13:]
|
||||||
|
|
||||||
return text
|
return text.decode("utf-8") #return the result again as unicode
|
||||||
|
|
||||||
def setKodiWebServerDetails(self):
|
def setKodiWebServerDetails(self):
|
||||||
# Get the Kodi webserver details - used to set the texture cache
|
# Get the Kodi webserver details - used to set the texture cache
|
||||||
|
@ -160,6 +167,17 @@ class Artwork():
|
||||||
# This method will sync all Kodi artwork to textures13.db
|
# This method will sync all Kodi artwork to textures13.db
|
||||||
# and cache them locally. This takes diskspace!
|
# and cache them locally. This takes diskspace!
|
||||||
|
|
||||||
|
if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logMsg("Doing Image Cache Sync", 1)
|
||||||
|
|
||||||
|
dialog = xbmcgui.DialogProgress()
|
||||||
|
dialog.create("Emby for Kodi", "Image Cache Sync")
|
||||||
|
|
||||||
|
# ask to rest all existing or not
|
||||||
|
if xbmcgui.Dialog().yesno("Image Texture Cache", "Reset all existing cache data first?", ""):
|
||||||
|
self.logMsg("Resetting all cache data first", 1)
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
||||||
if xbmcvfs.exists(path):
|
if xbmcvfs.exists(path):
|
||||||
|
@ -167,9 +185,13 @@ class Artwork():
|
||||||
for dir in allDirs:
|
for dir in allDirs:
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||||
for file in allFiles:
|
for file in allFiles:
|
||||||
xbmcvfs.delete(os.path.join(path+dir,file))
|
if os.path.supports_unicode_filenames:
|
||||||
|
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
|
||||||
|
else:
|
||||||
|
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
|
||||||
|
|
||||||
textureconnection = utils.KodiSQL('texture')
|
# remove all existing data from texture DB
|
||||||
|
textureconnection = utils.kodiSQL('texture')
|
||||||
texturecursor = textureconnection.cursor()
|
texturecursor = textureconnection.cursor()
|
||||||
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
rows = texturecursor.fetchall()
|
rows = texturecursor.fetchall()
|
||||||
|
@ -180,31 +202,86 @@ class Artwork():
|
||||||
textureconnection.commit()
|
textureconnection.commit()
|
||||||
texturecursor.close()
|
texturecursor.close()
|
||||||
|
|
||||||
|
|
||||||
# Cache all entries in video DB
|
# Cache all entries in video DB
|
||||||
connection = utils.KodiSQL('video')
|
connection = utils.kodiSQL('video')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT url FROM art")
|
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
|
total = len(result)
|
||||||
|
count = 1
|
||||||
|
percentage = 0
|
||||||
|
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||||
for url in result:
|
for url in result:
|
||||||
|
if dialog.iscanceled():
|
||||||
|
break
|
||||||
|
percentage = int((float(count) / float(total))*100)
|
||||||
|
textMessage = str(count) + " of " + str(total) + " (" + str(len(self.imageCacheThreads)) + ")"
|
||||||
|
dialog.update(percentage, "Updating Image Cache: " + textMessage)
|
||||||
self.CacheTexture(url[0])
|
self.CacheTexture(url[0])
|
||||||
|
count += 1
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
# Cache all entries in music DB
|
# Cache all entries in music DB
|
||||||
connection = utils.KodiSQL('music')
|
connection = utils.kodiSQL('music')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("SELECT url FROM art")
|
cursor.execute("SELECT url FROM art")
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
|
total = len(result)
|
||||||
|
count = 1
|
||||||
|
percentage = 0
|
||||||
|
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||||
for url in result:
|
for url in result:
|
||||||
|
if dialog.iscanceled():
|
||||||
|
break
|
||||||
|
percentage = int((float(count) / float(total))*100)
|
||||||
|
textMessage = str(count) + " of " + str(total)
|
||||||
|
dialog.update(percentage, "Updating Image Cache: " + textMessage)
|
||||||
self.CacheTexture(url[0])
|
self.CacheTexture(url[0])
|
||||||
|
count += 1
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
||||||
|
self.logMsg("Waiting for all threads to exit", 1)
|
||||||
|
while len(self.imageCacheThreads) > 0:
|
||||||
|
for thread in self.imageCacheThreads:
|
||||||
|
if thread.isFinished:
|
||||||
|
self.imageCacheThreads.remove(thread)
|
||||||
|
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
|
||||||
|
self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1)
|
||||||
|
xbmc.sleep(500)
|
||||||
|
|
||||||
|
dialog.close()
|
||||||
|
|
||||||
|
def addWorkerImageCacheThread(self, urlToAdd):
|
||||||
|
|
||||||
|
while(True):
|
||||||
|
# removed finished
|
||||||
|
for thread in self.imageCacheThreads:
|
||||||
|
if thread.isFinished:
|
||||||
|
self.imageCacheThreads.remove(thread)
|
||||||
|
|
||||||
|
# add a new thread or wait and retry if we hit our limit
|
||||||
|
if(len(self.imageCacheThreads) < self.imageCacheLimitThreads):
|
||||||
|
newThread = image_cache_thread.image_cache_thread()
|
||||||
|
newThread.setUrl(self.double_urlencode(urlToAdd))
|
||||||
|
newThread.setHost(self.xbmc_host, self.xbmc_port)
|
||||||
|
newThread.setAuth(self.xbmc_username, self.xbmc_password)
|
||||||
|
newThread.start()
|
||||||
|
self.imageCacheThreads.append(newThread)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2)
|
||||||
|
xbmc.sleep(50)
|
||||||
|
|
||||||
|
|
||||||
def CacheTexture(self, url):
|
def CacheTexture(self, url):
|
||||||
# Cache a single image url to the texture cache
|
# Cache a single image url to the texture cache
|
||||||
if url and self.enableTextureCache:
|
if url and self.enableTextureCache:
|
||||||
self.logMsg("Processing: %s" % url, 2)
|
self.logMsg("Processing: %s" % url, 2)
|
||||||
|
|
||||||
|
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
|
||||||
#Add image to texture cache by simply calling it at the http endpoint
|
#Add image to texture cache by simply calling it at the http endpoint
|
||||||
|
|
||||||
url = self.double_urlencode(url)
|
url = self.double_urlencode(url)
|
||||||
try: # Extreme short timeouts so we will have a exception.
|
try: # Extreme short timeouts so we will have a exception.
|
||||||
response = requests.head(
|
response = requests.head(
|
||||||
|
@ -216,6 +293,10 @@ class Artwork():
|
||||||
# We don't need the result
|
# We don't need the result
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.addWorkerImageCacheThread(url)
|
||||||
|
|
||||||
|
|
||||||
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
||||||
# Kodi conversion table
|
# Kodi conversion table
|
||||||
kodiart = {
|
kodiart = {
|
||||||
|
@ -264,7 +345,6 @@ class Artwork():
|
||||||
# Process backdrops and extra fanart
|
# Process backdrops and extra fanart
|
||||||
index = ""
|
index = ""
|
||||||
for backdrop in backdrops:
|
for backdrop in backdrops:
|
||||||
self.logMsg("imageURL: %s, kodiId: %s, mediatype: %s, imagetype: %s, cursor: %s" % (backdrop, kodiId, mediaType, index, cursor), 2)
|
|
||||||
self.addOrUpdateArt(
|
self.addOrUpdateArt(
|
||||||
imageUrl=backdrop,
|
imageUrl=backdrop,
|
||||||
kodiId=kodiId,
|
kodiId=kodiId,
|
||||||
|
@ -429,7 +509,7 @@ def getAllArtwork(self, item, parentInfo=False):
|
||||||
|
|
||||||
id = item['Id']
|
id = item['Id']
|
||||||
artworks = item['ImageTags']
|
artworks = item['ImageTags']
|
||||||
backdrops = item['BackdropImageTags']
|
backdrops = item.get('BackdropImageTags',[])
|
||||||
|
|
||||||
maxHeight = 10000
|
maxHeight = 10000
|
||||||
maxWidth = 10000
|
maxWidth = 10000
|
||||||
|
|
|
@ -7,6 +7,7 @@ from uuid import uuid4
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
|
import xbmcvfs
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ class ClientInfo():
|
||||||
|
|
||||||
if utils.settings('deviceNameOpt') == "false":
|
if utils.settings('deviceNameOpt') == "false":
|
||||||
# Use Kodi's deviceName
|
# Use Kodi's deviceName
|
||||||
deviceName = xbmc.getInfoLabel('System.FriendlyName')
|
deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8')
|
||||||
else:
|
else:
|
||||||
deviceName = utils.settings('deviceName')
|
deviceName = utils.settings('deviceName')
|
||||||
deviceName = deviceName.replace("\"", "_")
|
deviceName = deviceName.replace("\"", "_")
|
||||||
|
|
|
@ -316,7 +316,7 @@ class DownloadUtils():
|
||||||
elif r.status_code == requests.codes.ok:
|
elif r.status_code == requests.codes.ok:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# UTF-8 - JSON object
|
# UNICODE - JSON object
|
||||||
r = r.json()
|
r = r.json()
|
||||||
self.logMsg("====== 200 Success ======", 2)
|
self.logMsg("====== 200 Success ======", 2)
|
||||||
self.logMsg("Response: %s" % r, 2)
|
self.logMsg("Response: %s" % r, 2)
|
||||||
|
|
|
@ -98,31 +98,29 @@ def doMainListing():
|
||||||
path = utils.window('Emby.nodes.%s.content' % i)
|
path = utils.window('Emby.nodes.%s.content' % i)
|
||||||
label = utils.window('Emby.nodes.%s.title' % i)
|
label = utils.window('Emby.nodes.%s.title' % i)
|
||||||
type = utils.window('Emby.nodes.%s.type' % i)
|
type = utils.window('Emby.nodes.%s.type' % i)
|
||||||
if path and ((xbmc.getCondVisibility("Window.IsActive(Pictures)") and type=="photos") or (xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos")):
|
#because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing.
|
||||||
|
#for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window
|
||||||
|
if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and type == "photos":
|
||||||
|
addDirectoryItem(label, path)
|
||||||
|
elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos":
|
||||||
|
addDirectoryItem(label, path)
|
||||||
|
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
|
||||||
addDirectoryItem(label, path)
|
addDirectoryItem(label, path)
|
||||||
|
|
||||||
|
#experimental live tv nodes
|
||||||
|
addDirectoryItem("Live Tv Channels (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root")
|
||||||
|
addDirectoryItem("Live Tv Recordings (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root")
|
||||||
|
|
||||||
# some extra entries for settings and stuff. TODO --> localize the labels
|
# some extra entries for settings and stuff. TODO --> localize the labels
|
||||||
addDirectoryItem("Network credentials", "plugin://plugin.video.plexkodiconnect/?mode=passwords", False)
|
addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords")
|
||||||
addDirectoryItem("Settings", "plugin://plugin.video.plexkodiconnect/?mode=settings", False)
|
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings")
|
||||||
addDirectoryItem("Switch Plex user", "plugin://plugin.video.plexkodiconnect/?mode=switchuser", False)
|
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
|
||||||
#addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.plexkodiconnect/?mode=texturecache")
|
addDirectoryItem("Refresh Emby playlists", "plugin://plugin.video.emby/?mode=refreshplaylist")
|
||||||
addDirectoryItem(
|
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync")
|
||||||
label="Refresh Emby playlists",
|
addDirectoryItem("Repair local database (force update all content)", "plugin://plugin.video.emby/?mode=repair")
|
||||||
path="plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist",
|
addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset")
|
||||||
folder=False)
|
addDirectoryItem("Cache all images to Kodi texture cache", "plugin://plugin.video.emby/?mode=texturecache")
|
||||||
addDirectoryItem("Perform manual sync", "plugin://plugin.video.plexkodiconnect/?mode=manualsync", False)
|
addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia")
|
||||||
addDirectoryItem(
|
|
||||||
label="Repair local database (force update all content)",
|
|
||||||
path="plugin://plugin.video.plexkodiconnect/?mode=repair",
|
|
||||||
folder=False)
|
|
||||||
addDirectoryItem(
|
|
||||||
label="Perform local database reset (full resync)",
|
|
||||||
path="plugin://plugin.video.plexkodiconnect/?mode=reset",
|
|
||||||
folder=False)
|
|
||||||
addDirectoryItem(
|
|
||||||
label="Sync Emby Theme Media to Kodi",
|
|
||||||
path="plugin://plugin.video.plexkodiconnect/?mode=thememedia",
|
|
||||||
folder=False)
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
|
|
||||||
|
@ -321,7 +319,7 @@ def getThemeMedia():
|
||||||
result = doUtils.downloadUrl(url)
|
result = doUtils.downloadUrl(url)
|
||||||
|
|
||||||
# Create nfo and write themes to it
|
# Create nfo and write themes to it
|
||||||
nfo_file = open(nfo_path, 'w')
|
nfo_file = xbmcvfs.File(nfo_path, 'w')
|
||||||
pathstowrite = ""
|
pathstowrite = ""
|
||||||
# May be more than one theme
|
# May be more than one theme
|
||||||
for theme in result['Items']:
|
for theme in result['Items']:
|
||||||
|
@ -382,7 +380,7 @@ def getThemeMedia():
|
||||||
result = doUtils.downloadUrl(url)
|
result = doUtils.downloadUrl(url)
|
||||||
|
|
||||||
# Create nfo and write themes to it
|
# Create nfo and write themes to it
|
||||||
nfo_file = open(nfo_path, 'w')
|
nfo_file = xbmcvfs.File(nfo_path, 'w')
|
||||||
pathstowrite = ""
|
pathstowrite = ""
|
||||||
# May be more than one theme
|
# May be more than one theme
|
||||||
for theme in result['Items']:
|
for theme in result['Items']:
|
||||||
|
@ -431,11 +429,31 @@ def refreshPlaylist():
|
||||||
time=1000,
|
time=1000,
|
||||||
sound=False)
|
sound=False)
|
||||||
|
|
||||||
##### BROWSE EMBY HOMEVIDEOS AND PICTURES #####
|
#### SHOW SUBFOLDERS FOR NODE #####
|
||||||
def BrowseContent(viewname, type="", folderid=None, filter=""):
|
def GetSubFolders(nodeindex):
|
||||||
|
nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"]
|
||||||
|
for node in nodetypes:
|
||||||
|
title = utils.window('Emby.nodes.%s%s.title' %(nodeindex,node))
|
||||||
|
if title:
|
||||||
|
path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node))
|
||||||
|
type = utils.window('Emby.nodes.%s%s.type' %(nodeindex,node))
|
||||||
|
addDirectoryItem(title, path)
|
||||||
|
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||||
|
|
||||||
|
##### BROWSE EMBY NODES DIRECTLY #####
|
||||||
|
def BrowseContent(viewname, type="", folderid=""):
|
||||||
|
|
||||||
emby = embyserver.Read_EmbyServer()
|
emby = embyserver.Read_EmbyServer()
|
||||||
utils.logMsg("BrowseHomeVideos","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname, type, folderid, filter))
|
art = artwork.Artwork()
|
||||||
|
doUtils = downloadutils.DownloadUtils()
|
||||||
|
|
||||||
|
#folderid used as filter ?
|
||||||
|
if folderid in ["recent","recentepisodes","inprogress","inprogressepisodes","unwatched","nextepisodes","sets","genres","random","recommended"]:
|
||||||
|
filter = folderid
|
||||||
|
folderid = ""
|
||||||
|
else:
|
||||||
|
filter = ""
|
||||||
|
|
||||||
xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname)
|
xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname)
|
||||||
#get views for root level
|
#get views for root level
|
||||||
if not folderid:
|
if not folderid:
|
||||||
|
@ -444,6 +462,7 @@ def BrowseContent(viewname, type="", folderid=None, filter=""):
|
||||||
if view.get("name") == viewname:
|
if view.get("name") == viewname:
|
||||||
folderid = view.get("id")
|
folderid = view.get("id")
|
||||||
|
|
||||||
|
utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname, type, folderid, filter))
|
||||||
#set the correct params for the content type
|
#set the correct params for the content type
|
||||||
#only proceed if we have a folderid
|
#only proceed if we have a folderid
|
||||||
if folderid:
|
if folderid:
|
||||||
|
@ -457,21 +476,25 @@ def BrowseContent(viewname, type="", folderid=None, filter=""):
|
||||||
itemtype = ""
|
itemtype = ""
|
||||||
|
|
||||||
#get the actual listing
|
#get the actual listing
|
||||||
if filter == "recent":
|
if type == "recordings":
|
||||||
listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending")
|
listing = emby.getTvRecordings(folderid)
|
||||||
|
elif type == "tvchannels":
|
||||||
|
listing = emby.getTvChannels()
|
||||||
|
elif filter == "recent":
|
||||||
|
listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending")
|
||||||
elif filter == "random":
|
elif filter == "random":
|
||||||
listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending")
|
listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending")
|
||||||
elif filter == "recommended":
|
elif filter == "recommended":
|
||||||
listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
|
listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
|
||||||
elif filter == "sets":
|
elif filter == "sets":
|
||||||
listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
|
listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
|
||||||
else:
|
else:
|
||||||
listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False)
|
listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False)
|
||||||
|
|
||||||
#process the listing
|
#process the listing
|
||||||
if listing:
|
if listing:
|
||||||
for item in listing.get("Items"):
|
for item in listing.get("Items"):
|
||||||
li = createListItemFromEmbyItem(item)
|
li = createListItemFromEmbyItem(item,art,doUtils)
|
||||||
if item.get("IsFolder") == True:
|
if item.get("IsFolder") == True:
|
||||||
#for folders we add an additional browse request, passing the folderId
|
#for folders we add an additional browse request, passing the folderId
|
||||||
path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0], viewname, type, item.get("Id"))
|
path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0], viewname, type, item.get("Id"))
|
||||||
|
@ -491,10 +514,8 @@ def BrowseContent(viewname, type="", folderid=None, filter=""):
|
||||||
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
|
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
|
||||||
|
|
||||||
##### CREATE LISTITEM FROM EMBY METADATA #####
|
##### CREATE LISTITEM FROM EMBY METADATA #####
|
||||||
def createListItemFromEmbyItem(item):
|
def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()):
|
||||||
API = api.API(item)
|
API = api.API(item)
|
||||||
art = artwork.Artwork()
|
|
||||||
doUtils = downloadutils.DownloadUtils()
|
|
||||||
itemid = item['Id']
|
itemid = item['Id']
|
||||||
|
|
||||||
title = item.get('Name')
|
title = item.get('Name')
|
||||||
|
@ -531,10 +552,11 @@ def createListItemFromEmbyItem(item):
|
||||||
genre = API.getGenres()
|
genre = API.getGenres()
|
||||||
overlay = 0
|
overlay = 0
|
||||||
userdata = API.getUserData()
|
userdata = API.getUserData()
|
||||||
|
runtime = item.get("RunTimeTicks",0)/ 10000000.0
|
||||||
seektime = userdata['Resume']
|
seektime = userdata['Resume']
|
||||||
if seektime:
|
if seektime:
|
||||||
li.setProperty("resumetime", seektime)
|
li.setProperty("resumetime", seektime)
|
||||||
li.setProperty("totaltime", item.get("RunTimeTicks")/ 10000000.0)
|
li.setProperty("totaltime", str(runtime))
|
||||||
|
|
||||||
played = userdata['Played']
|
played = userdata['Played']
|
||||||
if played: overlay = 7
|
if played: overlay = 7
|
||||||
|
@ -551,16 +573,20 @@ def createListItemFromEmbyItem(item):
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
'rating': rating,
|
'rating': rating,
|
||||||
'year': item.get('ProductionYear'),
|
'year': item.get('ProductionYear'),
|
||||||
'premieredate': premieredate,
|
|
||||||
'date': premieredate,
|
|
||||||
'genre': genre,
|
'genre': genre,
|
||||||
'playcount': str(playcount),
|
'playcount': str(playcount),
|
||||||
'title': title,
|
'title': title,
|
||||||
'plot': API.getOverview(),
|
'plot': API.getOverview(),
|
||||||
'Overlay': str(overlay),
|
'Overlay': str(overlay),
|
||||||
|
'duration': runtime
|
||||||
}
|
}
|
||||||
|
if premieredate:
|
||||||
|
extradata["premieredate"] = premieredate
|
||||||
|
extradata["date"] = premieredate
|
||||||
li.setInfo('video', infoLabels=extradata)
|
li.setInfo('video', infoLabels=extradata)
|
||||||
|
if allart.get('Primary'):
|
||||||
li.setThumbnailImage(allart.get('Primary'))
|
li.setThumbnailImage(allart.get('Primary'))
|
||||||
|
else: li.setThumbnailImage('DefaultTVShows.png')
|
||||||
li.setIconImage('DefaultTVShows.png')
|
li.setIconImage('DefaultTVShows.png')
|
||||||
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
||||||
li.setArt( {"fanart": allart.get('Primary') } )
|
li.setArt( {"fanart": allart.get('Primary') } )
|
||||||
|
@ -568,9 +594,14 @@ def createListItemFromEmbyItem(item):
|
||||||
pbutils.PlaybackUtils(item).setArtwork(li)
|
pbutils.PlaybackUtils(item).setArtwork(li)
|
||||||
|
|
||||||
mediastreams = API.getMediaStreams()
|
mediastreams = API.getMediaStreams()
|
||||||
|
videostreamFound = False
|
||||||
if mediastreams:
|
if mediastreams:
|
||||||
for key, value in mediastreams.iteritems():
|
for key, value in mediastreams.iteritems():
|
||||||
|
if key == "video" and value: videostreamFound = True
|
||||||
if value: li.addStreamInfo(key, value[0])
|
if value: li.addStreamInfo(key, value[0])
|
||||||
|
if not videostreamFound:
|
||||||
|
#just set empty streamdetails to prevent errors in the logs
|
||||||
|
li.addStreamInfo("video", {'duration': runtime})
|
||||||
|
|
||||||
return li
|
return li
|
||||||
|
|
||||||
|
@ -594,84 +625,20 @@ def BrowseChannels(itemid, folderid=None):
|
||||||
url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
|
url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
|
||||||
|
|
||||||
result = doUtils.downloadUrl(url)
|
result = doUtils.downloadUrl(url)
|
||||||
try:
|
if result and result.get("Items"):
|
||||||
channels = result['Items']
|
for item in result.get("Items"):
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for item in channels:
|
|
||||||
|
|
||||||
API = api.API(item)
|
|
||||||
itemid = item['Id']
|
itemid = item['Id']
|
||||||
itemtype = item['Type']
|
itemtype = item['Type']
|
||||||
title = item.get('Name', "Missing Title")
|
li = createListItemFromEmbyItem(item,art,doUtils)
|
||||||
li = xbmcgui.ListItem(title)
|
|
||||||
|
|
||||||
if itemtype == "ChannelFolderItem":
|
if itemtype == "ChannelFolderItem":
|
||||||
isFolder = True
|
isFolder = True
|
||||||
else:
|
else:
|
||||||
isFolder = False
|
isFolder = False
|
||||||
|
|
||||||
channelId = item.get('ChannelId', "")
|
channelId = item.get('ChannelId', "")
|
||||||
channelName = item.get('ChannelName', "")
|
channelName = item.get('ChannelName', "")
|
||||||
|
|
||||||
premieredate = API.getPremiereDate()
|
|
||||||
# Process Genres
|
|
||||||
genre = API.getGenres()
|
|
||||||
# Process UserData
|
|
||||||
overlay = 0
|
|
||||||
|
|
||||||
userdata = API.getUserData()
|
|
||||||
seektime = userdata['Resume']
|
|
||||||
played = userdata['Played']
|
|
||||||
if played:
|
|
||||||
overlay = 7
|
|
||||||
else:
|
|
||||||
overlay = 6
|
|
||||||
|
|
||||||
favorite = userdata['Favorite']
|
|
||||||
if favorite:
|
|
||||||
overlay = 5
|
|
||||||
|
|
||||||
playcount = userdata['PlayCount']
|
|
||||||
if playcount is None:
|
|
||||||
playcount = 0
|
|
||||||
|
|
||||||
# Populate the details list
|
|
||||||
details = {
|
|
||||||
|
|
||||||
'title': title,
|
|
||||||
'channelname': channelName,
|
|
||||||
'plot': API.getOverview(),
|
|
||||||
'Overlay': str(overlay),
|
|
||||||
'playcount': str(playcount)
|
|
||||||
}
|
|
||||||
|
|
||||||
if itemtype == "ChannelVideoItem":
|
|
||||||
xbmcplugin.setContent(_addon_id, 'movies')
|
|
||||||
elif itemtype == "ChannelAudioItem":
|
|
||||||
xbmcplugin.setContent(_addon_id, 'songs')
|
|
||||||
|
|
||||||
# Populate the extradata list and artwork
|
|
||||||
pbutils.PlaybackUtils(item).setArtwork(li)
|
|
||||||
extradata = {
|
|
||||||
|
|
||||||
'id': itemid,
|
|
||||||
'rating': item.get('CommunityRating'),
|
|
||||||
'year': item.get('ProductionYear'),
|
|
||||||
'premieredate': premieredate,
|
|
||||||
'genre': genre,
|
|
||||||
'playcount': str(playcount),
|
|
||||||
'itemtype': itemtype
|
|
||||||
}
|
|
||||||
li.setInfo('video', infoLabels=extradata)
|
|
||||||
li.setThumbnailImage(art.getAllArtwork(item)['Primary'])
|
|
||||||
li.setIconImage('DefaultTVShows.png')
|
|
||||||
|
|
||||||
if itemtype == "Channel":
|
if itemtype == "Channel":
|
||||||
path = "%s?id=%s&mode=channels" % (_addon_url, itemid)
|
path = "%s?id=%s&mode=channels" % (_addon_url, itemid)
|
||||||
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
|
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
|
||||||
|
|
||||||
elif isFolder:
|
elif isFolder:
|
||||||
path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid)
|
path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid)
|
||||||
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
|
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
|
||||||
|
@ -998,12 +965,12 @@ def getExtraFanArt():
|
||||||
try:
|
try:
|
||||||
# for tvshows we get the embyid just from the path
|
# for tvshows we get the embyid just from the path
|
||||||
if xbmc.getCondVisibility("Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes)"):
|
if xbmc.getCondVisibility("Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes)"):
|
||||||
itemPath = xbmc.getInfoLabel("ListItem.Path")
|
itemPath = xbmc.getInfoLabel("ListItem.Path").decode('utf-8')
|
||||||
if "plugin.video.emby" in itemPath:
|
if "plugin.video.emby" in itemPath:
|
||||||
embyId = itemPath.split("/")[-2]
|
embyId = itemPath.split("/")[-2]
|
||||||
else:
|
else:
|
||||||
#for movies we grab the emby id from the params
|
#for movies we grab the emby id from the params
|
||||||
itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath")
|
itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath").decode('utf-8')
|
||||||
if "plugin.video.emby" in itemPath:
|
if "plugin.video.emby" in itemPath:
|
||||||
params = urlparse.parse_qs(itemPath)
|
params = urlparse.parse_qs(itemPath)
|
||||||
embyId = params.get('id')
|
embyId = params.get('id')
|
||||||
|
@ -1028,7 +995,10 @@ def getExtraFanArt():
|
||||||
for backdrop in backdrops:
|
for backdrop in backdrops:
|
||||||
# Same ordering as in artwork
|
# Same ordering as in artwork
|
||||||
tag = tags[count]
|
tag = tags[count]
|
||||||
|
if os.path.supports_unicode_filenames:
|
||||||
fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag)
|
fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag)
|
||||||
|
else:
|
||||||
|
fanartFile = os.path.join(fanartDir.encode("utf-8"), "fanart%s.jpg" % tag.encode("utf-8"))
|
||||||
li = xbmcgui.ListItem(tag, path=fanartFile)
|
li = xbmcgui.ListItem(tag, path=fanartFile)
|
||||||
xbmcplugin.addDirectoryItem(
|
xbmcplugin.addDirectoryItem(
|
||||||
handle=int(sys.argv[1]),
|
handle=int(sys.argv[1]),
|
||||||
|
@ -1041,7 +1011,7 @@ def getExtraFanArt():
|
||||||
# Use existing cached images
|
# Use existing cached images
|
||||||
dirs, files = xbmcvfs.listdir(fanartDir)
|
dirs, files = xbmcvfs.listdir(fanartDir)
|
||||||
for file in files:
|
for file in files:
|
||||||
fanartFile = os.path.join(fanartDir, file)
|
fanartFile = os.path.join(fanartDir, file.decode('utf-8'))
|
||||||
li = xbmcgui.ListItem(file, path=fanartFile)
|
li = xbmcgui.ListItem(file, path=fanartFile)
|
||||||
xbmcplugin.addDirectoryItem(
|
xbmcplugin.addDirectoryItem(
|
||||||
handle=int(sys.argv[1]),
|
handle=int(sys.argv[1]),
|
||||||
|
|
52
resources/lib/image_cache_thread.py
Normal file
52
resources/lib/image_cache_thread.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import threading
|
||||||
|
import utils
|
||||||
|
import xbmc
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class image_cache_thread(threading.Thread):
|
||||||
|
|
||||||
|
urlToProcess = None
|
||||||
|
isFinished = False
|
||||||
|
|
||||||
|
xbmc_host = ""
|
||||||
|
xbmc_port = ""
|
||||||
|
xbmc_username = ""
|
||||||
|
xbmc_password = ""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.monitor = xbmc.Monitor()
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def logMsg(self, msg, lvl=1):
|
||||||
|
className = self.__class__.__name__
|
||||||
|
utils.logMsg("%s" % className, msg, lvl)
|
||||||
|
|
||||||
|
def setUrl(self, url):
|
||||||
|
self.urlToProcess = url
|
||||||
|
|
||||||
|
def setHost(self, host, port):
|
||||||
|
self.xbmc_host = host
|
||||||
|
self.xbmc_port = port
|
||||||
|
|
||||||
|
def setAuth(self, user, pwd):
|
||||||
|
self.xbmc_username = user
|
||||||
|
self.xbmc_password = pwd
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
self.logMsg("Image Caching Thread Processing : " + self.urlToProcess, 2)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.head(
|
||||||
|
url=(
|
||||||
|
"http://%s:%s/image/image://%s"
|
||||||
|
% (self.xbmc_host, self.xbmc_port, self.urlToProcess)),
|
||||||
|
auth=(self.xbmc_username, self.xbmc_password),
|
||||||
|
timeout=(35.1, 35.1))
|
||||||
|
# We don't need the result
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
self.logMsg("Image Caching Thread Exited", 2)
|
||||||
|
|
||||||
|
self.isFinished = True
|
||||||
|
|
|
@ -1498,6 +1498,9 @@ class Music(Items):
|
||||||
Items.__init__(self, embycursor, musiccursor)
|
Items.__init__(self, embycursor, musiccursor)
|
||||||
|
|
||||||
self.directstream = utils.settings('streamMusic') == "true"
|
self.directstream = utils.settings('streamMusic') == "true"
|
||||||
|
self.enableimportsongrating = utils.settings('enableImportSongRating') == "true"
|
||||||
|
self.enableexportsongrating = utils.settings('enableExportSongRating') == "true"
|
||||||
|
self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true"
|
||||||
self.userid = utils.window('emby_currUser')
|
self.userid = utils.window('emby_currUser')
|
||||||
self.server = utils.window('emby_server%s' % self.userid)
|
self.server = utils.window('emby_server%s' % self.userid)
|
||||||
|
|
||||||
|
@ -1831,15 +1834,14 @@ class Music(Items):
|
||||||
year = item.get('ProductionYear')
|
year = item.get('ProductionYear')
|
||||||
duration = API.getRuntime()
|
duration = API.getRuntime()
|
||||||
|
|
||||||
#the server only returns the rating based on like/love and not the actual rating from the song
|
#if enabled, try to get the rating from file and/or emby
|
||||||
|
if not self.directstream:
|
||||||
|
rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
|
||||||
|
else:
|
||||||
|
hasEmbeddedCover = False
|
||||||
|
comment = API.getOverview()
|
||||||
rating = userdata['UserRating']
|
rating = userdata['UserRating']
|
||||||
|
|
||||||
#the server doesn't support comment on songs so this will always be empty
|
|
||||||
comment = API.getOverview()
|
|
||||||
|
|
||||||
#if enabled, try to get the rating and comment value from the file itself
|
|
||||||
if not self.directstream:
|
|
||||||
rating, comment = self.getSongRatingAndComment(itemid, rating, API)
|
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
if self.directstream:
|
if self.directstream:
|
||||||
|
@ -2043,7 +2045,10 @@ class Music(Items):
|
||||||
# Add genres
|
# Add genres
|
||||||
kodi_db.addMusicGenres(songid, genres, "song")
|
kodi_db.addMusicGenres(songid, genres, "song")
|
||||||
# Update artwork
|
# Update artwork
|
||||||
artwork.addArtwork(artwork.getAllArtwork(item, parentInfo=True), songid, "song", kodicursor)
|
allart = artwork.getAllArtwork(item, parentInfo=True)
|
||||||
|
if hasEmbeddedCover:
|
||||||
|
allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl )
|
||||||
|
artwork.addArtwork(allart, songid, "song", kodicursor)
|
||||||
|
|
||||||
def updateUserdata(self, item):
|
def updateUserdata(self, item):
|
||||||
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
|
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
|
||||||
|
@ -2070,10 +2075,19 @@ class Music(Items):
|
||||||
return
|
return
|
||||||
|
|
||||||
if mediatype == "song":
|
if mediatype == "song":
|
||||||
|
|
||||||
|
#should we ignore this item ?
|
||||||
|
#happens when userdata updated by ratings method
|
||||||
|
if utils.window("ignore-update-%s" %itemid):
|
||||||
|
utils.window("ignore-update-%s" %itemid,clear=True)
|
||||||
|
return
|
||||||
|
|
||||||
# Process playstates
|
# Process playstates
|
||||||
playcount = userdata['PlayCount']
|
playcount = userdata['PlayCount']
|
||||||
dateplayed = userdata['LastPlayedDate']
|
dateplayed = userdata['LastPlayedDate']
|
||||||
rating, comment = self.getSongRatingAndComment(itemid, rating, API)
|
|
||||||
|
#process item ratings
|
||||||
|
rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
|
||||||
|
|
||||||
query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?"
|
query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?"
|
||||||
kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
|
kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
|
||||||
|
@ -2085,86 +2099,6 @@ class Music(Items):
|
||||||
|
|
||||||
emby_db.updateReference(itemid, checksum)
|
emby_db.updateReference(itemid, checksum)
|
||||||
|
|
||||||
def getSongRatingAndComment(self, embyid, emby_rating, API):
|
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
|
||||||
|
|
||||||
previous_values = None
|
|
||||||
filename = API.getFilePath()
|
|
||||||
rating = 0
|
|
||||||
emby_rating = int(round(emby_rating, 0))
|
|
||||||
file_rating, comment = musicutils.getSongTags(filename)
|
|
||||||
|
|
||||||
|
|
||||||
emby_dbitem = self.emby_db.getItem_byId(embyid)
|
|
||||||
try:
|
|
||||||
kodiid = emby_dbitem[0]
|
|
||||||
except TypeError:
|
|
||||||
# Item is not in database.
|
|
||||||
currentvalue = None
|
|
||||||
else:
|
|
||||||
query = "SELECT rating FROM song WHERE idSong = ?"
|
|
||||||
kodicursor.execute(query, (kodiid,))
|
|
||||||
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
|
||||||
|
|
||||||
# Only proceed if we actually have a rating from the file
|
|
||||||
if file_rating is None and currentvalue:
|
|
||||||
return (currentvalue, comment)
|
|
||||||
elif file_rating is None and currentvalue is None:
|
|
||||||
return (emby_rating, comment)
|
|
||||||
|
|
||||||
file_rating = int(round(file_rating,0))
|
|
||||||
self.logMsg("getSongRatingAndComment --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
|
||||||
|
|
||||||
updateFileRating = False
|
|
||||||
updateEmbyRating = False
|
|
||||||
|
|
||||||
if currentvalue != None:
|
|
||||||
# we need to translate the emby values...
|
|
||||||
if emby_rating == 1 and currentvalue == 2:
|
|
||||||
emby_rating = 2
|
|
||||||
if emby_rating == 3 and currentvalue == 4:
|
|
||||||
emby_rating = 4
|
|
||||||
|
|
||||||
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
|
||||||
#the rating has been updated from kodi itself, update change to both emby ands file
|
|
||||||
rating = currentvalue
|
|
||||||
updateFileRating = True
|
|
||||||
updateEmbyRating = True
|
|
||||||
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
|
||||||
#emby rating changed - update the file
|
|
||||||
rating = emby_rating
|
|
||||||
updateFileRating = True
|
|
||||||
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
|
||||||
#file rating was updated, sync change to emby
|
|
||||||
rating = file_rating
|
|
||||||
updateEmbyRating = True
|
|
||||||
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
|
||||||
#both ratings have changed (corner case) - the highest rating wins...
|
|
||||||
if emby_rating > file_rating:
|
|
||||||
rating = emby_rating
|
|
||||||
updateFileRating = True
|
|
||||||
else:
|
|
||||||
rating = file_rating
|
|
||||||
updateEmbyRating = True
|
|
||||||
else:
|
|
||||||
#nothing has changed, just return the current value
|
|
||||||
rating = currentvalue
|
|
||||||
else:
|
|
||||||
# no rating yet in DB, always prefer file details...
|
|
||||||
rating = file_rating
|
|
||||||
updateEmbyRating = True
|
|
||||||
|
|
||||||
if updateFileRating:
|
|
||||||
musicutils.updateRatingToFile(rating, filename)
|
|
||||||
|
|
||||||
if updateEmbyRating:
|
|
||||||
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
|
||||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(rating)
|
|
||||||
API.updateUserRating(embyid, like, favourite, deletelike)
|
|
||||||
|
|
||||||
return (rating, comment)
|
|
||||||
|
|
||||||
def remove(self, itemid):
|
def remove(self, itemid):
|
||||||
# Remove kodiid, fileid, pathid, emby reference
|
# Remove kodiid, fileid, pathid, emby reference
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
|
|
|
@ -74,7 +74,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
data = json.loads(data)
|
data = json.loads(data,'utf-8')
|
||||||
|
|
||||||
|
|
||||||
if method == "Player.OnPlay":
|
if method == "Player.OnPlay":
|
||||||
|
@ -205,6 +205,5 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
utils.window('emby_onWake', value="true")
|
utils.window('emby_onWake', value="true")
|
||||||
|
|
||||||
elif method == "Playlist.OnClear":
|
elif method == "Playlist.OnClear":
|
||||||
utils.window('emby_customPlaylist', clear=True, windowid=10101)
|
utils.window('emby_customPlaylist', clear=True)
|
||||||
#xbmcgui.Window(10101).clearProperties()
|
|
||||||
self.logMsg("Clear playlist properties.")
|
self.logMsg("Clear playlist properties.")
|
|
@ -395,7 +395,10 @@ class LibrarySync(threading.Thread):
|
||||||
elapsedTime = datetime.now() - startTime
|
elapsedTime = datetime.now() - startTime
|
||||||
self.logMsg(
|
self.logMsg(
|
||||||
"SyncDatabase (finished %s in: %s)"
|
"SyncDatabase (finished %s in: %s)"
|
||||||
% (itemtype, str(elapsedTime).split('.')[0]), 0)
|
% (itemtype, str(elapsedTime).split('.')[0]), 1)
|
||||||
|
else:
|
||||||
|
# Close the Kodi cursor
|
||||||
|
kodicursor.close()
|
||||||
|
|
||||||
# # sync music
|
# # sync music
|
||||||
# if music_enabled:
|
# if music_enabled:
|
||||||
|
@ -424,8 +427,6 @@ class LibrarySync(threading.Thread):
|
||||||
utils.settings('SyncInstallRunDone', value="true")
|
utils.settings('SyncInstallRunDone', value="true")
|
||||||
utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
|
utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
|
||||||
self.saveLastSync()
|
self.saveLastSync()
|
||||||
# tell any widgets to refresh because the content has changed
|
|
||||||
utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
|
||||||
xbmc.executebuiltin('UpdateLibrary(video)')
|
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||||
elapsedtotal = datetime.now() - starttotal
|
elapsedtotal = datetime.now() - starttotal
|
||||||
|
|
||||||
|
@ -485,10 +486,10 @@ class LibrarySync(threading.Thread):
|
||||||
self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
|
self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
|
||||||
tagid = kodi_db.createTag(foldername)
|
tagid = kodi_db.createTag(foldername)
|
||||||
# Create playlist for the video library
|
# Create playlist for the video library
|
||||||
if mediatype != "music":
|
if mediatype in ['movies', 'tvshows', 'musicvideos']:
|
||||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||||
# Create the video node
|
# Create the video node
|
||||||
if mediatype != "musicvideos":
|
if mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos']:
|
||||||
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
|
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
|
||||||
totalnodes += 1
|
totalnodes += 1
|
||||||
# Add view to emby database
|
# Add view to emby database
|
||||||
|
@ -525,6 +526,7 @@ class LibrarySync(threading.Thread):
|
||||||
current_viewtype,
|
current_viewtype,
|
||||||
delete=True)
|
delete=True)
|
||||||
# Added new playlist
|
# Added new playlist
|
||||||
|
if mediatype in ['movies', 'tvshows', 'musicvideos']:
|
||||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||||
# Add new video node
|
# Add new video node
|
||||||
if mediatype != "musicvideos":
|
if mediatype != "musicvideos":
|
||||||
|
@ -540,6 +542,7 @@ class LibrarySync(threading.Thread):
|
||||||
else:
|
else:
|
||||||
if mediatype != "music":
|
if mediatype != "music":
|
||||||
# Validate the playlist exists or recreate it
|
# Validate the playlist exists or recreate it
|
||||||
|
if mediatype in ['movies', 'tvshows', 'musicvideos']:
|
||||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||||
# Create the video node if not already exists
|
# Create the video node if not already exists
|
||||||
if mediatype != "musicvideos":
|
if mediatype != "musicvideos":
|
||||||
|
@ -835,7 +838,7 @@ class LibrarySync(threading.Thread):
|
||||||
heading="Emby for Kodi",
|
heading="Emby for Kodi",
|
||||||
message="Comparing musicvideos from view: %s..." % viewName)
|
message="Comparing musicvideos from view: %s..." % viewName)
|
||||||
|
|
||||||
all_embymvideos = emby.getMusicVideos(viewId, basic=True)
|
all_embymvideos = emby.getMusicVideos(viewId, basic=True, dialog=pdialog)
|
||||||
for embymvideo in all_embymvideos['Items']:
|
for embymvideo in all_embymvideos['Items']:
|
||||||
|
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
|
@ -856,7 +859,7 @@ class LibrarySync(threading.Thread):
|
||||||
del updatelist[:]
|
del updatelist[:]
|
||||||
else:
|
else:
|
||||||
# Initial or repair sync
|
# Initial or repair sync
|
||||||
all_embymvideos = emby.getMusicVideos(viewId)
|
all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog)
|
||||||
total = all_embymvideos['TotalRecordCount']
|
total = all_embymvideos['TotalRecordCount']
|
||||||
embymvideos = all_embymvideos['Items']
|
embymvideos = all_embymvideos['Items']
|
||||||
|
|
||||||
|
@ -1051,7 +1054,7 @@ class LibrarySync(threading.Thread):
|
||||||
heading="Emby for Kodi",
|
heading="Emby for Kodi",
|
||||||
message="Comparing tvshows from view: %s..." % viewName)
|
message="Comparing tvshows from view: %s..." % viewName)
|
||||||
|
|
||||||
all_embytvshows = emby.getShows(viewId, basic=True)
|
all_embytvshows = emby.getShows(viewId, basic=True, dialog=pdialog)
|
||||||
for embytvshow in all_embytvshows['Items']:
|
for embytvshow in all_embytvshows['Items']:
|
||||||
|
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
|
@ -1071,7 +1074,7 @@ class LibrarySync(threading.Thread):
|
||||||
total = len(updatelist)
|
total = len(updatelist)
|
||||||
del updatelist[:]
|
del updatelist[:]
|
||||||
else:
|
else:
|
||||||
all_embytvshows = emby.getShows(viewId)
|
all_embytvshows = emby.getShows(viewId, dialog=pdialog)
|
||||||
total = all_embytvshows['TotalRecordCount']
|
total = all_embytvshows['TotalRecordCount']
|
||||||
embytvshows = all_embytvshows['Items']
|
embytvshows = all_embytvshows['Items']
|
||||||
|
|
||||||
|
@ -1114,7 +1117,7 @@ class LibrarySync(threading.Thread):
|
||||||
heading="Emby for Kodi",
|
heading="Emby for Kodi",
|
||||||
message="Comparing episodes from view: %s..." % viewName)
|
message="Comparing episodes from view: %s..." % viewName)
|
||||||
|
|
||||||
all_embyepisodes = emby.getEpisodes(viewId, basic=True)
|
all_embyepisodes = emby.getEpisodes(viewId, basic=True, dialog=pdialog)
|
||||||
for embyepisode in all_embyepisodes['Items']:
|
for embyepisode in all_embyepisodes['Items']:
|
||||||
|
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
|
@ -1212,9 +1215,9 @@ class LibrarySync(threading.Thread):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if type != "artists":
|
if type != "artists":
|
||||||
all_embyitems = process[type][0](basic=True)
|
all_embyitems = process[type][0](basic=True, dialog=pdialog)
|
||||||
else:
|
else:
|
||||||
all_embyitems = process[type][0]()
|
all_embyitems = process[type][0](dialog=pdialog)
|
||||||
for embyitem in all_embyitems['Items']:
|
for embyitem in all_embyitems['Items']:
|
||||||
|
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
|
@ -1243,7 +1246,7 @@ class LibrarySync(threading.Thread):
|
||||||
total = len(updatelist)
|
total = len(updatelist)
|
||||||
del updatelist[:]
|
del updatelist[:]
|
||||||
else:
|
else:
|
||||||
all_embyitems = process[type][0]()
|
all_embyitems = process[type][0](dialog=pdialog)
|
||||||
total = all_embyitems['TotalRecordCount']
|
total = all_embyitems['TotalRecordCount']
|
||||||
embyitems = all_embyitems['Items']
|
embyitems = all_embyitems['Items']
|
||||||
|
|
||||||
|
@ -1379,9 +1382,6 @@ class LibrarySync(threading.Thread):
|
||||||
embyconn.commit()
|
embyconn.commit()
|
||||||
self.saveLastSync()
|
self.saveLastSync()
|
||||||
|
|
||||||
# tell any widgets to refresh because the content has changed
|
|
||||||
utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
|
||||||
|
|
||||||
self.logMsg("Updating video library.", 1)
|
self.logMsg("Updating video library.", 1)
|
||||||
utils.window('emby_kodiScan', value="true")
|
utils.window('emby_kodiScan', value="true")
|
||||||
xbmc.executebuiltin('UpdateLibrary(video)')
|
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
import os
|
import os
|
||||||
import xbmc, xbmcaddon, xbmcvfs
|
import xbmc, xbmcaddon, xbmcvfs
|
||||||
import utils
|
import utils
|
||||||
from mutagen.flac import FLAC
|
from mutagen.flac import FLAC, Picture
|
||||||
from mutagen.id3 import ID3
|
from mutagen.id3 import ID3
|
||||||
from mutagen import id3
|
from mutagen import id3
|
||||||
|
import base64
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
@ -16,18 +17,23 @@ from mutagen import id3
|
||||||
def logMsg(msg, lvl=1):
|
def logMsg(msg, lvl=1):
|
||||||
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
|
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
|
||||||
|
|
||||||
def getRealFileName(filename):
|
def getRealFileName(filename, isTemp=False):
|
||||||
#get the filename path accessible by python if possible...
|
#get the filename path accessible by python if possible...
|
||||||
isTemp = False
|
|
||||||
|
|
||||||
if not xbmcvfs.exists(filename):
|
if not xbmcvfs.exists(filename):
|
||||||
logMsg( "File does not exist! %s" %(filename), 0)
|
logMsg( "File does not exist! %s" %(filename), 0)
|
||||||
return (False, "")
|
return (False, "")
|
||||||
|
|
||||||
|
#if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string
|
||||||
|
if os.path.supports_unicode_filenames:
|
||||||
|
checkfile = filename
|
||||||
|
else:
|
||||||
|
checkfile = filename.encode("utf-8")
|
||||||
|
|
||||||
# determine if our python module is able to access the file directly...
|
# determine if our python module is able to access the file directly...
|
||||||
if os.path.exists(filename):
|
if os.path.exists(checkfile):
|
||||||
filename = filename
|
filename = filename
|
||||||
elif os.path.exists(filename.replace("smb://","\\\\").replace("/","\\")):
|
elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")):
|
||||||
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
||||||
else:
|
else:
|
||||||
#file can not be accessed by python directly, we copy it for processing...
|
#file can not be accessed by python directly, we copy it for processing...
|
||||||
|
@ -55,25 +61,144 @@ def getEmbyRatingFromKodiRating(rating):
|
||||||
if (rating >= 5): favourite = True
|
if (rating >= 5): favourite = True
|
||||||
return(like, favourite, deletelike)
|
return(like, favourite, deletelike)
|
||||||
|
|
||||||
|
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
|
||||||
|
previous_values = None
|
||||||
|
filename = API.getFilePath()
|
||||||
|
rating = 0
|
||||||
|
emby_rating = int(round(emby_rating, 0))
|
||||||
|
|
||||||
|
#get file rating and comment tag from file itself.
|
||||||
|
if enableimportsongrating:
|
||||||
|
file_rating, comment, hasEmbeddedCover = getSongTags(filename)
|
||||||
|
else:
|
||||||
|
file_rating = 0
|
||||||
|
comment = ""
|
||||||
|
hasEmbeddedCover = False
|
||||||
|
|
||||||
|
|
||||||
|
emby_dbitem = emby_db.getItem_byId(embyid)
|
||||||
|
try:
|
||||||
|
kodiid = emby_dbitem[0]
|
||||||
|
except TypeError:
|
||||||
|
# Item is not in database.
|
||||||
|
currentvalue = None
|
||||||
|
else:
|
||||||
|
query = "SELECT rating FROM song WHERE idSong = ?"
|
||||||
|
kodicursor.execute(query, (kodiid,))
|
||||||
|
try:
|
||||||
|
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
||||||
|
except: currentvalue = None
|
||||||
|
|
||||||
|
# Only proceed if we actually have a rating from the file
|
||||||
|
if file_rating is None and currentvalue:
|
||||||
|
return (currentvalue, comment, False)
|
||||||
|
elif file_rating is None and not currentvalue:
|
||||||
|
return (emby_rating, comment, False)
|
||||||
|
|
||||||
|
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
||||||
|
|
||||||
|
updateFileRating = False
|
||||||
|
updateEmbyRating = False
|
||||||
|
|
||||||
|
if currentvalue != None:
|
||||||
|
# we need to translate the emby values...
|
||||||
|
if emby_rating == 1 and currentvalue == 2:
|
||||||
|
emby_rating = 2
|
||||||
|
if emby_rating == 3 and currentvalue == 4:
|
||||||
|
emby_rating = 4
|
||||||
|
|
||||||
|
#if updating rating into file is disabled, we ignore the rating in the file...
|
||||||
|
if not enableupdatesongrating:
|
||||||
|
file_rating = currentvalue
|
||||||
|
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
||||||
|
if not enableexportsongrating:
|
||||||
|
emby_rating = currentvalue
|
||||||
|
|
||||||
|
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
||||||
|
#the rating has been updated from kodi itself, update change to both emby ands file
|
||||||
|
rating = currentvalue
|
||||||
|
updateFileRating = True
|
||||||
|
updateEmbyRating = True
|
||||||
|
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
||||||
|
#emby rating changed - update the file
|
||||||
|
rating = emby_rating
|
||||||
|
updateFileRating = True
|
||||||
|
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
||||||
|
#file rating was updated, sync change to emby
|
||||||
|
rating = file_rating
|
||||||
|
updateEmbyRating = True
|
||||||
|
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
||||||
|
#both ratings have changed (corner case) - the highest rating wins...
|
||||||
|
if emby_rating > file_rating:
|
||||||
|
rating = emby_rating
|
||||||
|
updateFileRating = True
|
||||||
|
else:
|
||||||
|
rating = file_rating
|
||||||
|
updateEmbyRating = True
|
||||||
|
else:
|
||||||
|
#nothing has changed, just return the current value
|
||||||
|
rating = currentvalue
|
||||||
|
else:
|
||||||
|
# no rating yet in DB
|
||||||
|
if enableimportsongrating:
|
||||||
|
#prefer the file rating
|
||||||
|
rating = file_rating
|
||||||
|
#determine if we should also send the rating to emby server
|
||||||
|
if enableexportsongrating:
|
||||||
|
if emby_rating == 1 and file_rating == 2:
|
||||||
|
emby_rating = 2
|
||||||
|
if emby_rating == 3 and file_rating == 4:
|
||||||
|
emby_rating = 4
|
||||||
|
if emby_rating != file_rating:
|
||||||
|
updateEmbyRating = True
|
||||||
|
|
||||||
|
elif enableexportsongrating:
|
||||||
|
#set the initial rating to emby value
|
||||||
|
rating = emby_rating
|
||||||
|
|
||||||
|
if updateFileRating and enableupdatesongrating:
|
||||||
|
updateRatingToFile(rating, filename)
|
||||||
|
|
||||||
|
if updateEmbyRating and enableexportsongrating:
|
||||||
|
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
||||||
|
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
||||||
|
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
|
||||||
|
API.updateUserRating(embyid, like, favourite, deletelike)
|
||||||
|
|
||||||
|
return (rating, comment, hasEmbeddedCover)
|
||||||
|
|
||||||
def getSongTags(file):
|
def getSongTags(file):
|
||||||
# Get the actual ID3 tags for music songs as the server is lacking that info
|
# Get the actual ID3 tags for music songs as the server is lacking that info
|
||||||
rating = None
|
rating = 0
|
||||||
comment = ""
|
comment = ""
|
||||||
|
hasEmbeddedCover = False
|
||||||
|
|
||||||
isTemp,filename = getRealFileName(file)
|
isTemp,filename = getRealFileName(file)
|
||||||
logMsg( "getting song ID3 tags for " + filename)
|
logMsg( "getting song ID3 tags for " + filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
###### FLAC FILES #############
|
||||||
if filename.lower().endswith(".flac"):
|
if filename.lower().endswith(".flac"):
|
||||||
audio = FLAC(filename)
|
audio = FLAC(filename)
|
||||||
if audio.get("comment"):
|
if audio.get("comment"):
|
||||||
comment = audio.get("comment")[0]
|
comment = audio.get("comment")[0]
|
||||||
|
for pic in audio.pictures:
|
||||||
|
if pic.type == 3 and pic.data:
|
||||||
|
#the file has an embedded cover
|
||||||
|
hasEmbeddedCover = True
|
||||||
if audio.get("rating"):
|
if audio.get("rating"):
|
||||||
rating = float(audio.get("rating")[0])
|
rating = float(audio.get("rating")[0])
|
||||||
#flac rating is 0-100 and needs to be converted to 0-5 range
|
#flac rating is 0-100 and needs to be converted to 0-5 range
|
||||||
if rating > 5: rating = (rating / 100) * 5
|
if rating > 5: rating = (rating / 100) * 5
|
||||||
|
|
||||||
|
###### MP3 FILES #############
|
||||||
elif filename.lower().endswith(".mp3"):
|
elif filename.lower().endswith(".mp3"):
|
||||||
audio = ID3(filename)
|
audio = ID3(filename)
|
||||||
|
|
||||||
|
if audio.get("APIC:Front Cover"):
|
||||||
|
if audio.get("APIC:Front Cover").data:
|
||||||
|
hasEmbeddedCover = True
|
||||||
|
|
||||||
if audio.get("comment"):
|
if audio.get("comment"):
|
||||||
comment = audio.get("comment")[0]
|
comment = audio.get("comment")[0]
|
||||||
if audio.get("POPM:Windows Media Player 9 Series"):
|
if audio.get("POPM:Windows Media Player 9 Series"):
|
||||||
|
@ -83,45 +208,66 @@ def getSongTags(file):
|
||||||
if rating > 5: rating = (rating / 255) * 5
|
if rating > 5: rating = (rating / 255) * 5
|
||||||
else:
|
else:
|
||||||
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
|
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
|
||||||
|
|
||||||
|
#the rating must be a round value
|
||||||
rating = int(round(rating,0))
|
rating = int(round(rating,0))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
#file in use ?
|
#file in use ?
|
||||||
logMsg("Exception in getSongTags %s" %e,0)
|
logMsg("Exception in getSongTags %s" %e,0)
|
||||||
|
rating = None
|
||||||
|
|
||||||
#remove tempfile if needed....
|
#remove tempfile if needed....
|
||||||
if isTemp: xbmcvfs.delete(filename)
|
if isTemp: xbmcvfs.delete(filename)
|
||||||
|
|
||||||
return (rating, comment)
|
return (rating, comment, hasEmbeddedCover)
|
||||||
|
|
||||||
def updateRatingToFile(rating, file):
|
def updateRatingToFile(rating, file):
|
||||||
#update the rating from Emby to the file
|
#update the rating from Emby to the file
|
||||||
|
|
||||||
isTemp,filename = getRealFileName(file)
|
f = xbmcvfs.File(file)
|
||||||
logMsg( "setting song rating: %s for filename: %s" %(rating,filename))
|
org_size = f.size()
|
||||||
|
f.close()
|
||||||
|
|
||||||
if not filename:
|
#create tempfile
|
||||||
|
if "/" in file: filepart = file.split("/")[-1]
|
||||||
|
else: filepart = file.split("\\")[-1]
|
||||||
|
tempfile = "special://temp/"+filepart
|
||||||
|
xbmcvfs.copy(file, tempfile)
|
||||||
|
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
|
||||||
|
|
||||||
|
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
|
||||||
|
|
||||||
|
if not tempfile:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if filename.lower().endswith(".flac"):
|
if tempfile.lower().endswith(".flac"):
|
||||||
audio = FLAC(filename)
|
audio = FLAC(tempfile)
|
||||||
calcrating = int(round((float(rating) / 5) * 100, 0))
|
calcrating = int(round((float(rating) / 5) * 100, 0))
|
||||||
audio["rating"] = str(calcrating)
|
audio["rating"] = str(calcrating)
|
||||||
audio.save()
|
audio.save()
|
||||||
elif filename.lower().endswith(".mp3"):
|
elif tempfile.lower().endswith(".mp3"):
|
||||||
audio = ID3(filename)
|
audio = ID3(tempfile)
|
||||||
calcrating = int(round((float(rating) / 5) * 255, 0))
|
calcrating = int(round((float(rating) / 5) * 255, 0))
|
||||||
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
||||||
audio.save()
|
audio.save()
|
||||||
else:
|
else:
|
||||||
logMsg( "Not supported fileformat: %s" %(filename))
|
logMsg( "Not supported fileformat: %s" %(tempfile))
|
||||||
|
|
||||||
#remove tempfile if needed....
|
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
|
||||||
if isTemp:
|
#safety check: we check the file size of the temp file before proceeding with overwite of original file
|
||||||
|
f = xbmcvfs.File(tempfile)
|
||||||
|
checksum_size = f.size()
|
||||||
|
f.close()
|
||||||
|
if checksum_size >= org_size:
|
||||||
xbmcvfs.delete(file)
|
xbmcvfs.delete(file)
|
||||||
xbmcvfs.copy(filename,file)
|
xbmcvfs.copy(tempfile,file)
|
||||||
xbmcvfs.delete(filename)
|
else:
|
||||||
|
logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
|
||||||
|
|
||||||
|
#always delete the tempfile
|
||||||
|
xbmcvfs.delete(tempfile)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
#file in use ?
|
#file in use ?
|
||||||
|
|
|
@ -78,7 +78,7 @@ class PlaybackUtils():
|
||||||
sizePlaylist = playlist.size()
|
sizePlaylist = playlist.size()
|
||||||
currentPosition = startPos
|
currentPosition = startPos
|
||||||
|
|
||||||
propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
|
propertiesPlayback = utils.window('emby_playbackProps') == "true"
|
||||||
introsPlaylist = False
|
introsPlaylist = False
|
||||||
partsPlaylist = False
|
partsPlaylist = False
|
||||||
dummyPlaylist = False
|
dummyPlaylist = False
|
||||||
|
@ -96,11 +96,11 @@ class PlaybackUtils():
|
||||||
# Otherwise we get a loop.
|
# Otherwise we get a loop.
|
||||||
if not propertiesPlayback:
|
if not propertiesPlayback:
|
||||||
|
|
||||||
utils.window('emby_playbackProps', value="true", windowid=10101)
|
utils.window('emby_playbackProps', value="true")
|
||||||
self.logMsg("Setting up properties in playlist.", 1)
|
self.logMsg("Setting up properties in playlist.", 1)
|
||||||
|
|
||||||
if (not homeScreen and not seektime and
|
if (not homeScreen and not seektime and
|
||||||
utils.window('emby_customPlaylist', windowid=10101) != "true"):
|
utils.window('emby_customPlaylist') != "true"):
|
||||||
|
|
||||||
self.logMsg("Adding dummy file to playlist.", 2)
|
self.logMsg("Adding dummy file to playlist.", 2)
|
||||||
dummyPlaylist = True
|
dummyPlaylist = True
|
||||||
|
@ -190,21 +190,21 @@ class PlaybackUtils():
|
||||||
# We just skipped adding properties. Reset flag for next time.
|
# We just skipped adding properties. Reset flag for next time.
|
||||||
elif propertiesPlayback:
|
elif propertiesPlayback:
|
||||||
self.logMsg("Resetting properties playback flag.", 2)
|
self.logMsg("Resetting properties playback flag.", 2)
|
||||||
utils.window('emby_playbackProps', clear=True, windowid=10101)
|
utils.window('emby_playbackProps', clear=True)
|
||||||
|
|
||||||
#self.pl.verifyPlaylist()
|
#self.pl.verifyPlaylist()
|
||||||
########## SETUP MAIN ITEM ##########
|
########## SETUP MAIN ITEM ##########
|
||||||
|
|
||||||
# For transcoding only, ask for audio/subs pref
|
# For transcoding only, ask for audio/subs pref
|
||||||
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
|
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||||
playurl = playutils.audioSubsPref(playurl, child=self.API.getChildNumber())
|
playurl = playutils.audioSubsPref(playurl, listitem, child=self.API.getChildNumber())
|
||||||
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
|
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||||
|
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
self.setProperties(playurl, listitem)
|
self.setProperties(playurl, listitem)
|
||||||
|
|
||||||
############### PLAYBACK ################
|
############### PLAYBACK ################
|
||||||
customPlaylist = utils.window('emby_customPlaylist', windowid=10101)
|
customPlaylist = utils.window('emby_customPlaylist')
|
||||||
if homeScreen and seektime:
|
if homeScreen and seektime:
|
||||||
self.logMsg("Play as a widget item.", 1)
|
self.logMsg("Play as a widget item.", 1)
|
||||||
self.setListItem(listitem)
|
self.setListItem(listitem)
|
||||||
|
@ -245,7 +245,7 @@ class PlaybackUtils():
|
||||||
# Only for direct play and direct stream
|
# Only for direct play and direct stream
|
||||||
# subtitles = self.externalSubs(playurl)
|
# subtitles = self.externalSubs(playurl)
|
||||||
subtitles = self.API.externalSubs(playurl)
|
subtitles = self.API.externalSubs(playurl)
|
||||||
if playmethod in ("DirectStream", "Transcode"):
|
if playmethod != "Transcode":
|
||||||
# Direct play automatically appends external
|
# Direct play automatically appends external
|
||||||
listitem.setSubtitles(subtitles)
|
listitem.setSubtitles(subtitles)
|
||||||
|
|
||||||
|
|
|
@ -416,14 +416,10 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
def onPlayBackStopped( self ):
|
def onPlayBackStopped( self ):
|
||||||
# Will be called when user stops xbmc playing a file
|
# Will be called when user stops xbmc playing a file
|
||||||
currentFile = self.currentFile
|
|
||||||
self.logMsg("ONPLAYBACK_STOPPED", 2)
|
self.logMsg("ONPLAYBACK_STOPPED", 2)
|
||||||
if self.played_info.get(currentFile):
|
utils.window('emby_customPlaylist', clear=True)
|
||||||
self.played_info[currentFile]['paused'] = 'stopped'
|
utils.window('emby_playbackProps', clear=True)
|
||||||
self.reportPlayback()
|
self.logMsg("Clear playlist properties.", 1)
|
||||||
|
|
||||||
xbmcgui.Window(10101).clearProperties()
|
|
||||||
self.logMsg("Clear playlist properties.")
|
|
||||||
self.stopAll()
|
self.stopAll()
|
||||||
|
|
||||||
def onPlayBackEnded( self ):
|
def onPlayBackEnded( self ):
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Playlist():
|
||||||
playlist.clear()
|
playlist.clear()
|
||||||
started = False
|
started = False
|
||||||
|
|
||||||
utils.window('emby_customplaylist', value="true", windowid=10101)
|
utils.window('emby_customplaylist', value="true")
|
||||||
|
|
||||||
position = 0
|
position = 0
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,12 @@ class PlayUtils():
|
||||||
self.API.setPartNumber(partIndex)
|
self.API.setPartNumber(partIndex)
|
||||||
playurl = None
|
playurl = None
|
||||||
|
|
||||||
|
if item.get('Type') in ["Recording","TvChannel"] and item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
|
||||||
|
#Is this the right way to play a Live TV or recordings ?
|
||||||
|
self.logMsg("File protocol is http (livetv).", 1)
|
||||||
|
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id'])
|
||||||
|
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
|
||||||
|
|
||||||
# if item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
|
# if item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
|
||||||
# # Only play as http
|
# # Only play as http
|
||||||
# self.logMsg("File protocol is http.", 1)
|
# self.logMsg("File protocol is http.", 1)
|
||||||
|
@ -168,6 +174,12 @@ class PlayUtils():
|
||||||
if not self.h265enabled():
|
if not self.h265enabled():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
elif (utils.settings('transcode720H265') == "true" and
|
||||||
|
item['MediaSources'][0]['Name'].startswith(("720P/HEVC","720P/H265"))):
|
||||||
|
# Avoid H265 720p
|
||||||
|
self.logMsg("Option to transcode 720P/H265 enabled.", 1)
|
||||||
|
return False
|
||||||
|
|
||||||
# Requirement: BitRate, supported encoding
|
# Requirement: BitRate, supported encoding
|
||||||
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
|
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
|
||||||
# Plex: always able?!?
|
# Plex: always able?!?
|
||||||
|
@ -273,7 +285,7 @@ class PlayUtils():
|
||||||
# max bit rate supported by server (max signed 32bit integer)
|
# max bit rate supported by server (max signed 32bit integer)
|
||||||
return bitrate.get(videoQuality, 2147483)
|
return bitrate.get(videoQuality, 2147483)
|
||||||
|
|
||||||
def audioSubsPref(self, url, child=0):
|
def audioSubsPref(self, url, listitem, child=0):
|
||||||
self.API.setChildNumber(child)
|
self.API.setChildNumber(child)
|
||||||
# For transcoding only
|
# For transcoding only
|
||||||
# Present the list of audio to select from
|
# Present the list of audio to select from
|
||||||
|
@ -282,6 +294,7 @@ class PlayUtils():
|
||||||
audioStreamsChannelsList = {}
|
audioStreamsChannelsList = {}
|
||||||
subtitleStreamsList = {}
|
subtitleStreamsList = {}
|
||||||
subtitleStreams = ['No subtitles']
|
subtitleStreams = ['No subtitles']
|
||||||
|
downloadableStreams = []
|
||||||
selectAudioIndex = ""
|
selectAudioIndex = ""
|
||||||
selectSubsIndex = ""
|
selectSubsIndex = ""
|
||||||
playurlprefs = "%s" % url
|
playurlprefs = "%s" % url
|
||||||
|
@ -312,8 +325,8 @@ class PlayUtils():
|
||||||
audioStreams.append(track)
|
audioStreams.append(track)
|
||||||
|
|
||||||
elif 'Subtitle' in type:
|
elif 'Subtitle' in type:
|
||||||
if stream['IsExternal']:
|
'''if stream['IsExternal']:
|
||||||
continue
|
continue'''
|
||||||
try:
|
try:
|
||||||
track = "%s - %s" % (index, stream['Language'])
|
track = "%s - %s" % (index, stream['Language'])
|
||||||
except:
|
except:
|
||||||
|
@ -321,10 +334,14 @@ class PlayUtils():
|
||||||
|
|
||||||
default = stream['IsDefault']
|
default = stream['IsDefault']
|
||||||
forced = stream['IsForced']
|
forced = stream['IsForced']
|
||||||
|
downloadable = stream['IsTextSubtitleStream']
|
||||||
|
|
||||||
if default:
|
if default:
|
||||||
track = "%s - Default" % track
|
track = "%s - Default" % track
|
||||||
if forced:
|
if forced:
|
||||||
track = "%s - Forced" % track
|
track = "%s - Forced" % track
|
||||||
|
if downloadable:
|
||||||
|
downloadableStreams.append(index)
|
||||||
|
|
||||||
subtitleStreamsList[track] = index
|
subtitleStreamsList[track] = index
|
||||||
subtitleStreams.append(track)
|
subtitleStreams.append(track)
|
||||||
|
@ -352,7 +369,19 @@ class PlayUtils():
|
||||||
# User selected subtitles
|
# User selected subtitles
|
||||||
selected = subtitleStreams[resp]
|
selected = subtitleStreams[resp]
|
||||||
selectSubsIndex = subtitleStreamsList[selected]
|
selectSubsIndex = subtitleStreamsList[selected]
|
||||||
|
|
||||||
|
# Load subtitles in the listitem if downloadable
|
||||||
|
if selectSubsIndex in downloadableStreams:
|
||||||
|
|
||||||
|
itemid = item['Id']
|
||||||
|
url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
|
||||||
|
% (self.server, itemid, itemid, selectSubsIndex))]
|
||||||
|
self.logMsg("Set up subtitles: %s %s" % (selectSubsIndex, url), 1)
|
||||||
|
listitem.setSubtitles(url)
|
||||||
|
else:
|
||||||
|
# Burn subtitles
|
||||||
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
|
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
|
||||||
|
|
||||||
else: # User backed out of selection
|
else: # User backed out of selection
|
||||||
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
|
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,37 @@ class Read_EmbyServer():
|
||||||
}
|
}
|
||||||
return doUtils.downloadUrl(url, parameters=params)
|
return doUtils.downloadUrl(url, parameters=params)
|
||||||
|
|
||||||
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False):
|
def getTvChannels(self):
|
||||||
|
doUtils = self.doUtils
|
||||||
|
url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'EnableImages': True,
|
||||||
|
'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||||
|
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||||
|
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||||
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
||||||
|
}
|
||||||
|
return doUtils.downloadUrl(url, parameters=params)
|
||||||
|
|
||||||
|
def getTvRecordings(self, groupid):
|
||||||
|
doUtils = self.doUtils
|
||||||
|
url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
|
||||||
|
if groupid == "root": groupid = ""
|
||||||
|
params = {
|
||||||
|
|
||||||
|
'GroupId': groupid,
|
||||||
|
'EnableImages': True,
|
||||||
|
'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||||
|
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||||
|
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||||
|
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||||
|
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
|
||||||
|
}
|
||||||
|
return doUtils.downloadUrl(url, parameters=params)
|
||||||
|
|
||||||
|
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
|
||||||
|
|
||||||
doUtils = self.doUtils
|
doUtils = self.doUtils
|
||||||
items = {
|
items = {
|
||||||
|
@ -210,16 +240,12 @@ class Read_EmbyServer():
|
||||||
"MediaSources"
|
"MediaSources"
|
||||||
)
|
)
|
||||||
result = doUtils.downloadUrl(url, parameters=params)
|
result = doUtils.downloadUrl(url, parameters=params)
|
||||||
try:
|
|
||||||
items['Items'].extend(result['Items'])
|
items['Items'].extend(result['Items'])
|
||||||
except TypeError:
|
|
||||||
# Connection timed out, reduce the number
|
|
||||||
jump -= 50
|
|
||||||
self.limitindex = jump
|
|
||||||
self.logMsg("New throttle for items requested: %s" % jump, 1)
|
|
||||||
else:
|
|
||||||
index += jump
|
|
||||||
|
|
||||||
|
index += jump
|
||||||
|
if dialog:
|
||||||
|
percentage = int((float(index) / float(total))*100)
|
||||||
|
dialog.update(percentage)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getViews(self, type, root=False):
|
def getViews(self, type, root=False):
|
||||||
|
@ -276,15 +302,15 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
return views
|
return views
|
||||||
|
|
||||||
def getMovies(self, parentId, basic=False):
|
def getMovies(self, parentId, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(parentId, "Movie", basic=basic)
|
items = self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getBoxset(self):
|
def getBoxset(self, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(None, "BoxSet")
|
items = self.getSection(None, "BoxSet", dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -294,9 +320,9 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getMusicVideos(self, parentId, basic=False):
|
def getMusicVideos(self, parentId, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(parentId, "MusicVideo", basic=basic)
|
items = self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -306,9 +332,9 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getShows(self, parentId, basic=False):
|
def getShows(self, parentId, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(parentId, "Series", basic=basic)
|
items = self.getSection(parentId, "Series", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -332,9 +358,9 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getEpisodes(self, parentId, basic=False):
|
def getEpisodes(self, parentId, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(parentId, "Episode", basic=basic)
|
items = self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -350,7 +376,7 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getArtists(self):
|
def getArtists(self, dialog=None):
|
||||||
|
|
||||||
doUtils = self.doUtils
|
doUtils = self.doUtils
|
||||||
items = {
|
items = {
|
||||||
|
@ -397,21 +423,17 @@ class Read_EmbyServer():
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
result = doUtils.downloadUrl(url, parameters=params)
|
result = doUtils.downloadUrl(url, parameters=params)
|
||||||
try:
|
|
||||||
items['Items'].extend(result['Items'])
|
items['Items'].extend(result['Items'])
|
||||||
except TypeError:
|
|
||||||
# Connection timed out, reduce the number
|
|
||||||
jump -= 50
|
|
||||||
self.limitindex = jump
|
|
||||||
self.logMsg("New throttle for items requested: %s" % jump, 1)
|
|
||||||
else:
|
|
||||||
index += jump
|
|
||||||
|
|
||||||
|
index += jump
|
||||||
|
if dialog:
|
||||||
|
percentage = int((float(index) / float(total))*100)
|
||||||
|
dialog.update(percentage)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getAlbums(self, basic=False):
|
def getAlbums(self, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic)
|
items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -421,9 +443,9 @@ class Read_EmbyServer():
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def getSongs(self, basic=False):
|
def getSongs(self, basic=False, dialog=None):
|
||||||
|
|
||||||
items = self.getSection(None, "Audio", basic=basic)
|
items = self.getSection(None, "Audio", basic=basic, dialog=dialog)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
|
@ -73,12 +73,18 @@ def window(property, value=None, clear=False, windowid=10000):
|
||||||
# Get or set window property
|
# Get or set window property
|
||||||
WINDOW = xbmcgui.Window(windowid)
|
WINDOW = xbmcgui.Window(windowid)
|
||||||
|
|
||||||
|
#setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues
|
||||||
|
if isinstance(property, unicode):
|
||||||
|
property = property.encode("utf-8")
|
||||||
|
if isinstance(value, unicode):
|
||||||
|
value = value.encode("utf-8")
|
||||||
|
|
||||||
if clear:
|
if clear:
|
||||||
WINDOW.clearProperty(property)
|
WINDOW.clearProperty(property)
|
||||||
elif value is not None:
|
elif value is not None:
|
||||||
WINDOW.setProperty(property, value)
|
WINDOW.setProperty(property, value)
|
||||||
else:
|
else: #getproperty returns string so convert to unicode
|
||||||
return WINDOW.getProperty(property)
|
return WINDOW.getProperty(property).decode("utf-8")
|
||||||
|
|
||||||
def settings(setting, value=None):
|
def settings(setting, value=None):
|
||||||
# Get or add addon setting
|
# Get or add addon setting
|
||||||
|
@ -87,13 +93,12 @@ def settings(setting, value=None):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
addon.setSetting(setting, value)
|
addon.setSetting(setting, value)
|
||||||
else:
|
else:
|
||||||
return addon.getSetting(setting)
|
return addon.getSetting(setting) #returns unicode object
|
||||||
|
|
||||||
def language(stringid):
|
def language(stringid):
|
||||||
# Central string retrieval
|
# Central string retrieval
|
||||||
addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||||
string = addon.getLocalizedString(stringid).decode("utf-8")
|
string = addon.getLocalizedString(stringid) #returns unicode object
|
||||||
|
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def kodiSQL(type="video"):
|
def kodiSQL(type="video"):
|
||||||
|
@ -169,11 +174,11 @@ def reset():
|
||||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||||
dirs, files = xbmcvfs.listdir(path)
|
dirs, files = xbmcvfs.listdir(path)
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
if dir.startswith('Emby'):
|
if dir.decode('utf-8').startswith('Emby'):
|
||||||
shutil.rmtree("%s%s" % (path, dir))
|
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.startswith('emby'):
|
if file.decode('utf-8').startswith('emby'):
|
||||||
xbmcvfs.delete("%s%s" % (path, file))
|
xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
|
||||||
|
|
||||||
# Wipe the kodi databases
|
# Wipe the kodi databases
|
||||||
logMsg("EMBY", "Resetting the Kodi video database.")
|
logMsg("EMBY", "Resetting the Kodi video database.")
|
||||||
|
@ -254,7 +259,7 @@ def stopProfiling(pr, profileName):
|
||||||
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
|
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
|
||||||
profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
|
profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
|
||||||
|
|
||||||
f = open(profile, 'wb')
|
f = xbmcvfs.File(profile, 'w')
|
||||||
f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
|
f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
|
||||||
for (key, value) in ps.stats.items():
|
for (key, value) in ps.stats.items():
|
||||||
(filename, count, func_name) = key
|
(filename, count, func_name) = key
|
||||||
|
@ -502,7 +507,7 @@ def playlistXSP(mediatype, tagname, viewtype="", delete=False):
|
||||||
}
|
}
|
||||||
logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1)
|
logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1)
|
||||||
try:
|
try:
|
||||||
f = open(xsppath, 'w')
|
f = xbmcvfs.File(xsppath, 'w')
|
||||||
except:
|
except:
|
||||||
logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1)
|
logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1)
|
||||||
return
|
return
|
||||||
|
@ -526,5 +531,5 @@ def deletePlaylists():
|
||||||
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
||||||
dirs, files = xbmcvfs.listdir(path)
|
dirs, files = xbmcvfs.listdir(path)
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.startswith('Emby'):
|
if file.decode('utf-8').startswith('Emby'):
|
||||||
xbmcvfs.delete("%s%s" % (path, file))
|
xbmcvfs.delete("%s%s" % (path, file))
|
|
@ -96,7 +96,7 @@ class VideoNodes(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
if mediatype=="photos":
|
if mediatype=="photos":
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=photos&filter=index" % tagname
|
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
|
||||||
|
|
||||||
utils.window('Emby.nodes.%s.index' % indexnumber, value=path)
|
utils.window('Emby.nodes.%s.index' % indexnumber, value=path)
|
||||||
|
|
||||||
|
@ -163,6 +163,13 @@ class VideoNodes(object):
|
||||||
'8': 30255,
|
'8': 30255,
|
||||||
'11': 30254
|
'11': 30254
|
||||||
},
|
},
|
||||||
|
'musicvideos':
|
||||||
|
{
|
||||||
|
'1': tagname,
|
||||||
|
'2': 30256,
|
||||||
|
'4': 30257,
|
||||||
|
'6': 30258
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = mediatypes[mediatype]
|
nodes = mediatypes[mediatype]
|
||||||
|
@ -185,7 +192,7 @@ class VideoNodes(object):
|
||||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s" %(tagname,mediatype)
|
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s" %(tagname,mediatype)
|
||||||
elif (mediatype == "homevideos" or mediatype == "photos"):
|
elif (mediatype == "homevideos" or mediatype == "photos"):
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s&filter=%s" %(tagname,mediatype,nodetype)
|
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s&folderid=%s" %(tagname,mediatype,nodetype)
|
||||||
elif nodetype == "nextepisodes":
|
elif nodetype == "nextepisodes":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=nextup&limit=25" % tagname
|
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=nextup&limit=25" % tagname
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<setting id="streamMusic" type="bool" label="Direct stream music library" default="false" visible="eq(-1,true)" subsetting="true" />
|
<setting id="streamMusic" type="bool" label="Direct stream music library" default="false" visible="eq(-1,true)" subsetting="true" />
|
||||||
<setting id="useDirectPaths" type="enum" label="Playback Mode" values="Addon(Default)|Native(Direct paths)" default="0" />
|
<setting id="useDirectPaths" type="enum" label="Playback Mode" values="Addon(Default)|Native(Direct paths)" default="0" />
|
||||||
<setting id="enableTextureCache" label="Auto add images to the Kodi texture cache" type="bool" default="true" />
|
<setting id="enableTextureCache" label="Auto add images to the Kodi texture cache" type="bool" default="true" />
|
||||||
|
<setting id="imageCacheLimit" type="enum" label="Limit image cache import threads" values="None|5|10|15|20|25" default="0" visible="eq(-1,true)"/>
|
||||||
<setting id="serverSync" type="bool" label="Enable fast starting sync" default="true" />
|
<setting id="serverSync" type="bool" label="Enable fast starting sync" default="true" />
|
||||||
<setting id="limitindex" type="number" label="Maximum items to request at once from server" default="200" option="int" />
|
<setting id="limitindex" type="number" label="Maximum items to request at once from server" default="200" option="int" />
|
||||||
</category>
|
</category>
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
<setting id="playFromStream" type="bool" label="30002" default="false" />
|
<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="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="transcode720H265" type="bool" label="Force transcode 720p/H265" default="false" />
|
||||||
<setting id="markPlayed" type="number" visible="false" default="90" />
|
<setting id="markPlayed" type="number" visible="false" default="90" />
|
||||||
<setting id="failedCount" type="number" visible="false" default="0" />
|
<setting id="failedCount" type="number" visible="false" default="0" />
|
||||||
<setting id="networkCreds" type="text" visible="false" default="" />
|
<setting id="networkCreds" type="text" visible="false" default="" />
|
||||||
|
@ -65,6 +67,10 @@
|
||||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
|
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
|
||||||
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
||||||
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
||||||
|
<setting type="lsep" label="Music metadata options (not possible with directstream)" />
|
||||||
|
<setting id="enableImportSongRating" type="bool" label="Import Music song rating directly from files" default="true" />
|
||||||
|
<setting id="enableExportSongRating" type="bool" label="Convert Music song rating to Emby (likes/favourites)" default="false" />
|
||||||
|
<setting id="enableUpdateSongRating" type="bool" label="Allow rating in song files to be updated by Kodi/Emby" default="false" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30022">
|
<category label="30022">
|
||||||
|
|
|
@ -75,13 +75,11 @@ class Service():
|
||||||
"emby_online", "emby_serverStatus", "emby_onWake",
|
"emby_online", "emby_serverStatus", "emby_onWake",
|
||||||
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
||||||
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId",
|
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId",
|
||||||
"emby_initialScan"
|
"emby_initialScan", "emby_customplaylist", "emby_playbackProps"
|
||||||
]
|
]
|
||||||
for prop in properties:
|
for prop in properties:
|
||||||
utils.window(prop, clear=True)
|
utils.window(prop, clear=True)
|
||||||
|
|
||||||
# Clear playlist properties
|
|
||||||
xbmcgui.Window(10101).clearProperties()
|
|
||||||
# Clear video nodes properties
|
# Clear video nodes properties
|
||||||
videonodes.VideoNodes().clearProperties()
|
videonodes.VideoNodes().clearProperties()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue