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"?>
|
||||
<addon id="plugin.video.plexkodiconnect"
|
||||
name="PlexKodiConnect"
|
||||
version="1.1.72"
|
||||
version="1.1.79"
|
||||
provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
|
@ -11,7 +11,7 @@
|
|||
</requires>
|
||||
<extension point="xbmc.python.pluginsource"
|
||||
library="default.py">
|
||||
<provides>executable video audio image</provides>
|
||||
<provides>video audio image</provides>
|
||||
</extension>
|
||||
<extension point="xbmc.service" library="service.py" start="login">
|
||||
</extension>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<item>
|
||||
<label>30401</label>
|
||||
<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>
|
||||
</extension>
|
||||
<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
|
||||
- Fix to extrafanart
|
||||
- Fix for artists deletion
|
||||
|
|
|
@ -112,9 +112,11 @@ if __name__ == '__main__':
|
|||
if newvalue:
|
||||
newvalue = int(newvalue)
|
||||
if newvalue > 5: newvalue = "5"
|
||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||
API.updateUserRating(embyid, like, favourite, deletelike)
|
||||
if utils.settings('enableUpdateSongRating') == "true":
|
||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||
if utils.settings('enableExportSongRating') == "true":
|
||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||
API.updateUserRating(embyid, like, favourite, deletelike)
|
||||
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
||||
kodicursor.execute(query, (newvalue,itemid,))
|
||||
kodiconn.commit()
|
||||
|
|
|
@ -33,7 +33,6 @@ class Main:
|
|||
# Parse parameters
|
||||
xbmc.log("Full sys.argv received: %s" % sys.argv)
|
||||
base_url = sys.argv[0]
|
||||
addon_handle = int(sys.argv[1])
|
||||
params = urlparse.parse_qs(sys.argv[2][1:])
|
||||
xbmc.log("Parameter string: %s" % sys.argv[2])
|
||||
try:
|
||||
|
@ -62,6 +61,7 @@ class Main:
|
|||
'channels': entrypoint.BrowseChannels,
|
||||
'channelsfolder': entrypoint.BrowseChannels,
|
||||
'browsecontent': entrypoint.BrowseContent,
|
||||
'getsubfolders': entrypoint.GetSubFolders,
|
||||
'nextup': entrypoint.getNextUpEpisodes,
|
||||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
||||
'recentepisodes': entrypoint.getRecentEpisodes,
|
||||
|
@ -82,11 +82,11 @@ class Main:
|
|||
limit = int(params['limit'][0])
|
||||
modes[mode](itemid, limit)
|
||||
|
||||
elif mode == "channels":
|
||||
elif mode in ["channels","getsubfolders"]:
|
||||
modes[mode](itemid)
|
||||
|
||||
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":
|
||||
folderid = params['folderid'][0]
|
||||
|
|
|
@ -248,6 +248,10 @@
|
|||
<string id="30254">Favourite Photos</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 -->
|
||||
<string id="30300">Active</string>
|
||||
<string id="30301">Clear Settings</string>
|
||||
|
@ -274,5 +278,4 @@
|
|||
<string id="30408">Emby addon settings</string>
|
||||
<string id="30409">Delete item from the server</string>
|
||||
|
||||
|
||||
</strings>
|
||||
|
|
|
@ -8,10 +8,12 @@ import os
|
|||
import urllib
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
import utils
|
||||
import clientinfo
|
||||
import image_cache_thread
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
@ -23,13 +25,18 @@ class Artwork():
|
|||
xbmc_username = None
|
||||
xbmc_password = None
|
||||
|
||||
imageCacheThreads = []
|
||||
imageCacheLimitThreads = 0
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.clientinfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientinfo.getAddonName()
|
||||
|
||||
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:
|
||||
self.setKodiWebServerDetails()
|
||||
|
||||
|
@ -37,7 +44,6 @@ class Artwork():
|
|||
self.server = utils.window('emby_server%s' % self.userId)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
@ -49,10 +55,11 @@ class Artwork():
|
|||
return 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:]
|
||||
|
||||
return text
|
||||
return text.decode("utf-8") #return the result again as unicode
|
||||
|
||||
def setKodiWebServerDetails(self):
|
||||
# Get the Kodi webserver details - used to set the texture cache
|
||||
|
@ -160,61 +167,135 @@ class Artwork():
|
|||
# This method will sync all Kodi artwork to textures13.db
|
||||
# and cache them locally. This takes diskspace!
|
||||
|
||||
# Remove all existing textures first
|
||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
||||
if xbmcvfs.exists(path):
|
||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||
for dir in allDirs:
|
||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||
for file in allFiles:
|
||||
xbmcvfs.delete(os.path.join(path+dir,file))
|
||||
if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"):
|
||||
return
|
||||
|
||||
textureconnection = utils.KodiSQL('texture')
|
||||
texturecursor = textureconnection.cursor()
|
||||
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
rows = texturecursor.fetchall()
|
||||
for row in rows:
|
||||
tableName = row[0]
|
||||
if(tableName != "version"):
|
||||
texturecursor.execute("DELETE FROM " + tableName)
|
||||
textureconnection.commit()
|
||||
texturecursor.close()
|
||||
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
|
||||
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
|
||||
if xbmcvfs.exists(path):
|
||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||
for dir in allDirs:
|
||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||
for file in allFiles:
|
||||
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))
|
||||
|
||||
# remove all existing data from texture DB
|
||||
textureconnection = utils.kodiSQL('texture')
|
||||
texturecursor = textureconnection.cursor()
|
||||
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
rows = texturecursor.fetchall()
|
||||
for row in rows:
|
||||
tableName = row[0]
|
||||
if(tableName != "version"):
|
||||
texturecursor.execute("DELETE FROM " + tableName)
|
||||
textureconnection.commit()
|
||||
texturecursor.close()
|
||||
|
||||
# Cache all entries in video DB
|
||||
connection = utils.KodiSQL('video')
|
||||
connection = utils.kodiSQL('video')
|
||||
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()
|
||||
total = len(result)
|
||||
count = 1
|
||||
percentage = 0
|
||||
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
|
||||
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])
|
||||
count += 1
|
||||
cursor.close()
|
||||
|
||||
# Cache all entries in music DB
|
||||
connection = utils.KodiSQL('music')
|
||||
connection = utils.kodiSQL('music')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT url FROM art")
|
||||
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:
|
||||
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])
|
||||
count += 1
|
||||
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):
|
||||
# Cache a single image url to the texture cache
|
||||
if url and self.enableTextureCache:
|
||||
self.logMsg("Processing: %s" % url, 2)
|
||||
|
||||
# Add image to texture cache by simply calling it at the http endpoint
|
||||
url = self.double_urlencode(url)
|
||||
try: # Extreme short timeouts so we will have a exception.
|
||||
response = requests.head(
|
||||
url=(
|
||||
"http://%s:%s/image/image://%s"
|
||||
% (self.xbmc_host, self.xbmc_port, url)),
|
||||
auth=(self.xbmc_username, self.xbmc_password),
|
||||
timeout=(0.01, 0.01))
|
||||
# We don't need the result
|
||||
except: pass
|
||||
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
|
||||
#Add image to texture cache by simply calling it at the http endpoint
|
||||
|
||||
url = self.double_urlencode(url)
|
||||
try: # Extreme short timeouts so we will have a exception.
|
||||
response = requests.head(
|
||||
url=(
|
||||
"http://%s:%s/image/image://%s"
|
||||
% (self.xbmc_host, self.xbmc_port, url)),
|
||||
auth=(self.xbmc_username, self.xbmc_password),
|
||||
timeout=(0.01, 0.01))
|
||||
# We don't need the result
|
||||
except: pass
|
||||
|
||||
else:
|
||||
self.addWorkerImageCacheThread(url)
|
||||
|
||||
|
||||
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
||||
# Kodi conversion table
|
||||
|
@ -264,7 +345,6 @@ class Artwork():
|
|||
# Process backdrops and extra fanart
|
||||
index = ""
|
||||
for backdrop in backdrops:
|
||||
self.logMsg("imageURL: %s, kodiId: %s, mediatype: %s, imagetype: %s, cursor: %s" % (backdrop, kodiId, mediaType, index, cursor), 2)
|
||||
self.addOrUpdateArt(
|
||||
imageUrl=backdrop,
|
||||
kodiId=kodiId,
|
||||
|
@ -429,7 +509,7 @@ def getAllArtwork(self, item, parentInfo=False):
|
|||
|
||||
id = item['Id']
|
||||
artworks = item['ImageTags']
|
||||
backdrops = item['BackdropImageTags']
|
||||
backdrops = item.get('BackdropImageTags',[])
|
||||
|
||||
maxHeight = 10000
|
||||
maxWidth = 10000
|
||||
|
|
|
@ -7,6 +7,7 @@ from uuid import uuid4
|
|||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
import utils
|
||||
|
||||
|
@ -43,7 +44,7 @@ class ClientInfo():
|
|||
|
||||
if utils.settings('deviceNameOpt') == "false":
|
||||
# Use Kodi's deviceName
|
||||
deviceName = xbmc.getInfoLabel('System.FriendlyName')
|
||||
deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8')
|
||||
else:
|
||||
deviceName = utils.settings('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_")
|
||||
|
|
|
@ -316,7 +316,7 @@ class DownloadUtils():
|
|||
elif r.status_code == requests.codes.ok:
|
||||
|
||||
try:
|
||||
# UTF-8 - JSON object
|
||||
# UNICODE - JSON object
|
||||
r = r.json()
|
||||
self.logMsg("====== 200 Success ======", 2)
|
||||
self.logMsg("Response: %s" % r, 2)
|
||||
|
|
|
@ -98,31 +98,29 @@ def doMainListing():
|
|||
path = utils.window('Emby.nodes.%s.content' % i)
|
||||
label = utils.window('Emby.nodes.%s.title' % 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)
|
||||
|
||||
#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
|
||||
addDirectoryItem("Network credentials", "plugin://plugin.video.plexkodiconnect/?mode=passwords", False)
|
||||
addDirectoryItem("Settings", "plugin://plugin.video.plexkodiconnect/?mode=settings", False)
|
||||
addDirectoryItem("Switch Plex user", "plugin://plugin.video.plexkodiconnect/?mode=switchuser", False)
|
||||
#addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.plexkodiconnect/?mode=texturecache")
|
||||
addDirectoryItem(
|
||||
label="Refresh Emby playlists",
|
||||
path="plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist",
|
||||
folder=False)
|
||||
addDirectoryItem("Perform manual sync", "plugin://plugin.video.plexkodiconnect/?mode=manualsync", False)
|
||||
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)
|
||||
addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords")
|
||||
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings")
|
||||
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
|
||||
addDirectoryItem("Refresh Emby playlists", "plugin://plugin.video.emby/?mode=refreshplaylist")
|
||||
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync")
|
||||
addDirectoryItem("Repair local database (force update all content)", "plugin://plugin.video.emby/?mode=repair")
|
||||
addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset")
|
||||
addDirectoryItem("Cache all images to Kodi texture cache", "plugin://plugin.video.emby/?mode=texturecache")
|
||||
addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia")
|
||||
|
||||
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||
|
||||
|
@ -321,7 +319,7 @@ def getThemeMedia():
|
|||
result = doUtils.downloadUrl(url)
|
||||
|
||||
# Create nfo and write themes to it
|
||||
nfo_file = open(nfo_path, 'w')
|
||||
nfo_file = xbmcvfs.File(nfo_path, 'w')
|
||||
pathstowrite = ""
|
||||
# May be more than one theme
|
||||
for theme in result['Items']:
|
||||
|
@ -382,7 +380,7 @@ def getThemeMedia():
|
|||
result = doUtils.downloadUrl(url)
|
||||
|
||||
# Create nfo and write themes to it
|
||||
nfo_file = open(nfo_path, 'w')
|
||||
nfo_file = xbmcvfs.File(nfo_path, 'w')
|
||||
pathstowrite = ""
|
||||
# May be more than one theme
|
||||
for theme in result['Items']:
|
||||
|
@ -431,11 +429,31 @@ def refreshPlaylist():
|
|||
time=1000,
|
||||
sound=False)
|
||||
|
||||
##### BROWSE EMBY HOMEVIDEOS AND PICTURES #####
|
||||
def BrowseContent(viewname, type="", folderid=None, filter=""):
|
||||
#### SHOW SUBFOLDERS FOR NODE #####
|
||||
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()
|
||||
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)
|
||||
#get views for root level
|
||||
if not folderid:
|
||||
|
@ -444,6 +462,7 @@ def BrowseContent(viewname, type="", folderid=None, filter=""):
|
|||
if view.get("name") == viewname:
|
||||
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
|
||||
#only proceed if we have a folderid
|
||||
if folderid:
|
||||
|
@ -457,21 +476,25 @@ def BrowseContent(viewname, type="", folderid=None, filter=""):
|
|||
itemtype = ""
|
||||
|
||||
#get the actual listing
|
||||
if filter == "recent":
|
||||
listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending")
|
||||
if type == "recordings":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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:
|
||||
listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False)
|
||||
|
||||
#process the listing
|
||||
if listing:
|
||||
for item in listing.get("Items"):
|
||||
li = createListItemFromEmbyItem(item)
|
||||
li = createListItemFromEmbyItem(item,art,doUtils)
|
||||
if item.get("IsFolder") == True:
|
||||
#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"))
|
||||
|
@ -491,10 +514,8 @@ def BrowseContent(viewname, type="", folderid=None, filter=""):
|
|||
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
|
||||
|
||||
##### CREATE LISTITEM FROM EMBY METADATA #####
|
||||
def createListItemFromEmbyItem(item):
|
||||
def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()):
|
||||
API = api.API(item)
|
||||
art = artwork.Artwork()
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
itemid = item['Id']
|
||||
|
||||
title = item.get('Name')
|
||||
|
@ -531,10 +552,11 @@ def createListItemFromEmbyItem(item):
|
|||
genre = API.getGenres()
|
||||
overlay = 0
|
||||
userdata = API.getUserData()
|
||||
runtime = item.get("RunTimeTicks",0)/ 10000000.0
|
||||
seektime = userdata['Resume']
|
||||
if seektime:
|
||||
li.setProperty("resumetime", seektime)
|
||||
li.setProperty("totaltime", item.get("RunTimeTicks")/ 10000000.0)
|
||||
li.setProperty("totaltime", str(runtime))
|
||||
|
||||
played = userdata['Played']
|
||||
if played: overlay = 7
|
||||
|
@ -551,16 +573,20 @@ def createListItemFromEmbyItem(item):
|
|||
'id': itemid,
|
||||
'rating': rating,
|
||||
'year': item.get('ProductionYear'),
|
||||
'premieredate': premieredate,
|
||||
'date': premieredate,
|
||||
'genre': genre,
|
||||
'playcount': str(playcount),
|
||||
'title': title,
|
||||
'plot': API.getOverview(),
|
||||
'Overlay': str(overlay),
|
||||
'duration': runtime
|
||||
}
|
||||
if premieredate:
|
||||
extradata["premieredate"] = premieredate
|
||||
extradata["date"] = premieredate
|
||||
li.setInfo('video', infoLabels=extradata)
|
||||
li.setThumbnailImage(allart.get('Primary'))
|
||||
if allart.get('Primary'):
|
||||
li.setThumbnailImage(allart.get('Primary'))
|
||||
else: li.setThumbnailImage('DefaultTVShows.png')
|
||||
li.setIconImage('DefaultTVShows.png')
|
||||
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
|
||||
li.setArt( {"fanart": allart.get('Primary') } )
|
||||
|
@ -568,9 +594,14 @@ def createListItemFromEmbyItem(item):
|
|||
pbutils.PlaybackUtils(item).setArtwork(li)
|
||||
|
||||
mediastreams = API.getMediaStreams()
|
||||
videostreamFound = False
|
||||
if mediastreams:
|
||||
for key, value in mediastreams.iteritems():
|
||||
if key == "video" and value: videostreamFound = True
|
||||
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
|
||||
|
||||
|
@ -594,84 +625,20 @@ def BrowseChannels(itemid, folderid=None):
|
|||
url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
|
||||
|
||||
result = doUtils.downloadUrl(url)
|
||||
try:
|
||||
channels = result['Items']
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
for item in channels:
|
||||
|
||||
API = api.API(item)
|
||||
if result and result.get("Items"):
|
||||
for item in result.get("Items"):
|
||||
itemid = item['Id']
|
||||
itemtype = item['Type']
|
||||
title = item.get('Name', "Missing Title")
|
||||
li = xbmcgui.ListItem(title)
|
||||
|
||||
li = createListItemFromEmbyItem(item,art,doUtils)
|
||||
if itemtype == "ChannelFolderItem":
|
||||
isFolder = True
|
||||
else:
|
||||
isFolder = False
|
||||
|
||||
channelId = item.get('ChannelId', "")
|
||||
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":
|
||||
path = "%s?id=%s&mode=channels" % (_addon_url, itemid)
|
||||
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
|
||||
|
||||
elif isFolder:
|
||||
path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid)
|
||||
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
|
||||
|
@ -998,12 +965,12 @@ def getExtraFanArt():
|
|||
try:
|
||||
# for tvshows we get the embyid just from the path
|
||||
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:
|
||||
embyId = itemPath.split("/")[-2]
|
||||
else:
|
||||
#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:
|
||||
params = urlparse.parse_qs(itemPath)
|
||||
embyId = params.get('id')
|
||||
|
@ -1028,7 +995,10 @@ def getExtraFanArt():
|
|||
for backdrop in backdrops:
|
||||
# Same ordering as in artwork
|
||||
tag = tags[count]
|
||||
fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag)
|
||||
if os.path.supports_unicode_filenames:
|
||||
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)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=int(sys.argv[1]),
|
||||
|
@ -1041,7 +1011,7 @@ def getExtraFanArt():
|
|||
# Use existing cached images
|
||||
dirs, files = xbmcvfs.listdir(fanartDir)
|
||||
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)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
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)
|
||||
|
||||
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.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
|
@ -1831,15 +1834,14 @@ class Music(Items):
|
|||
year = item.get('ProductionYear')
|
||||
duration = API.getRuntime()
|
||||
|
||||
#the server only returns the rating based on like/love and not the actual rating from the song
|
||||
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 enabled, try to get the rating from file and/or emby
|
||||
if not self.directstream:
|
||||
rating, comment = self.getSongRatingAndComment(itemid, rating, API)
|
||||
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']
|
||||
|
||||
|
||||
##### GET THE FILE AND PATH #####
|
||||
if self.directstream:
|
||||
|
@ -2043,7 +2045,10 @@ class Music(Items):
|
|||
# Add genres
|
||||
kodi_db.addMusicGenres(songid, genres, "song")
|
||||
# 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):
|
||||
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
|
||||
|
@ -2070,10 +2075,19 @@ class Music(Items):
|
|||
return
|
||||
|
||||
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
|
||||
playcount = userdata['PlayCount']
|
||||
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 = ?"
|
||||
kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
|
||||
|
@ -2085,86 +2099,6 @@ class Music(Items):
|
|||
|
||||
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):
|
||||
# Remove kodiid, fileid, pathid, emby reference
|
||||
emby_db = self.emby_db
|
||||
|
|
|
@ -74,7 +74,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||
|
||||
if data:
|
||||
data = json.loads(data)
|
||||
data = json.loads(data,'utf-8')
|
||||
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
|
@ -205,6 +205,5 @@ class KodiMonitor(xbmc.Monitor):
|
|||
utils.window('emby_onWake', value="true")
|
||||
|
||||
elif method == "Playlist.OnClear":
|
||||
utils.window('emby_customPlaylist', clear=True, windowid=10101)
|
||||
#xbmcgui.Window(10101).clearProperties()
|
||||
utils.window('emby_customPlaylist', clear=True)
|
||||
self.logMsg("Clear playlist properties.")
|
|
@ -395,7 +395,10 @@ class LibrarySync(threading.Thread):
|
|||
elapsedTime = datetime.now() - startTime
|
||||
self.logMsg(
|
||||
"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
|
||||
# if music_enabled:
|
||||
|
@ -424,8 +427,6 @@ class LibrarySync(threading.Thread):
|
|||
utils.settings('SyncInstallRunDone', value="true")
|
||||
utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
|
||||
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)')
|
||||
elapsedtotal = datetime.now() - starttotal
|
||||
|
||||
|
@ -485,12 +486,12 @@ class LibrarySync(threading.Thread):
|
|||
self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
|
||||
tagid = kodi_db.createTag(foldername)
|
||||
# Create playlist for the video library
|
||||
if mediatype != "music":
|
||||
if mediatype in ['movies', 'tvshows', 'musicvideos']:
|
||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||
# Create the video node
|
||||
if mediatype != "musicvideos":
|
||||
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
|
||||
totalnodes += 1
|
||||
# Create the video node
|
||||
if mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos']:
|
||||
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
|
||||
totalnodes += 1
|
||||
# Add view to emby database
|
||||
emby_db.addView(folderid, foldername, viewtype, tagid)
|
||||
|
||||
|
@ -525,7 +526,8 @@ class LibrarySync(threading.Thread):
|
|||
current_viewtype,
|
||||
delete=True)
|
||||
# Added new playlist
|
||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||
if mediatype in ['movies', 'tvshows', 'musicvideos']:
|
||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||
# Add new video node
|
||||
if mediatype != "musicvideos":
|
||||
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
|
||||
|
@ -540,7 +542,8 @@ class LibrarySync(threading.Thread):
|
|||
else:
|
||||
if mediatype != "music":
|
||||
# Validate the playlist exists or recreate it
|
||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||
if mediatype in ['movies', 'tvshows', 'musicvideos']:
|
||||
utils.playlistXSP(mediatype, foldername, viewtype)
|
||||
# Create the video node if not already exists
|
||||
if mediatype != "musicvideos":
|
||||
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
|
||||
|
@ -835,7 +838,7 @@ class LibrarySync(threading.Thread):
|
|||
heading="Emby for Kodi",
|
||||
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']:
|
||||
|
||||
if self.shouldStop():
|
||||
|
@ -856,7 +859,7 @@ class LibrarySync(threading.Thread):
|
|||
del updatelist[:]
|
||||
else:
|
||||
# Initial or repair sync
|
||||
all_embymvideos = emby.getMusicVideos(viewId)
|
||||
all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog)
|
||||
total = all_embymvideos['TotalRecordCount']
|
||||
embymvideos = all_embymvideos['Items']
|
||||
|
||||
|
@ -1051,7 +1054,7 @@ class LibrarySync(threading.Thread):
|
|||
heading="Emby for Kodi",
|
||||
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']:
|
||||
|
||||
if self.shouldStop():
|
||||
|
@ -1071,7 +1074,7 @@ class LibrarySync(threading.Thread):
|
|||
total = len(updatelist)
|
||||
del updatelist[:]
|
||||
else:
|
||||
all_embytvshows = emby.getShows(viewId)
|
||||
all_embytvshows = emby.getShows(viewId, dialog=pdialog)
|
||||
total = all_embytvshows['TotalRecordCount']
|
||||
embytvshows = all_embytvshows['Items']
|
||||
|
||||
|
@ -1114,7 +1117,7 @@ class LibrarySync(threading.Thread):
|
|||
heading="Emby for Kodi",
|
||||
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']:
|
||||
|
||||
if self.shouldStop():
|
||||
|
@ -1212,9 +1215,9 @@ class LibrarySync(threading.Thread):
|
|||
pass
|
||||
|
||||
if type != "artists":
|
||||
all_embyitems = process[type][0](basic=True)
|
||||
all_embyitems = process[type][0](basic=True, dialog=pdialog)
|
||||
else:
|
||||
all_embyitems = process[type][0]()
|
||||
all_embyitems = process[type][0](dialog=pdialog)
|
||||
for embyitem in all_embyitems['Items']:
|
||||
|
||||
if self.shouldStop():
|
||||
|
@ -1243,7 +1246,7 @@ class LibrarySync(threading.Thread):
|
|||
total = len(updatelist)
|
||||
del updatelist[:]
|
||||
else:
|
||||
all_embyitems = process[type][0]()
|
||||
all_embyitems = process[type][0](dialog=pdialog)
|
||||
total = all_embyitems['TotalRecordCount']
|
||||
embyitems = all_embyitems['Items']
|
||||
|
||||
|
@ -1379,9 +1382,6 @@ class LibrarySync(threading.Thread):
|
|||
embyconn.commit()
|
||||
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)
|
||||
utils.window('emby_kodiScan', value="true")
|
||||
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
import os
|
||||
import xbmc, xbmcaddon, xbmcvfs
|
||||
import utils
|
||||
from mutagen.flac import FLAC
|
||||
from mutagen.flac import FLAC, Picture
|
||||
from mutagen.id3 import ID3
|
||||
from mutagen import id3
|
||||
import base64
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
@ -16,18 +17,23 @@ from mutagen import id3
|
|||
def logMsg(msg, lvl=1):
|
||||
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...
|
||||
isTemp = False
|
||||
|
||||
if not xbmcvfs.exists(filename):
|
||||
logMsg( "File does not exist! %s" %(filename), 0)
|
||||
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...
|
||||
if os.path.exists(filename):
|
||||
if os.path.exists(checkfile):
|
||||
filename = filename
|
||||
elif os.path.exists(filename.replace("smb://","\\\\").replace("/","\\")):
|
||||
elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")):
|
||||
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
||||
else:
|
||||
#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
|
||||
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):
|
||||
# Get the actual ID3 tags for music songs as the server is lacking that info
|
||||
rating = None
|
||||
rating = 0
|
||||
comment = ""
|
||||
hasEmbeddedCover = False
|
||||
|
||||
isTemp,filename = getRealFileName(file)
|
||||
logMsg( "getting song ID3 tags for " + filename)
|
||||
|
||||
try:
|
||||
###### FLAC FILES #############
|
||||
if filename.lower().endswith(".flac"):
|
||||
audio = FLAC(filename)
|
||||
if audio.get("comment"):
|
||||
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"):
|
||||
rating = float(audio.get("rating")[0])
|
||||
#flac rating is 0-100 and needs to be converted to 0-5 range
|
||||
if rating > 5: rating = (rating / 100) * 5
|
||||
|
||||
###### MP3 FILES #############
|
||||
elif filename.lower().endswith(".mp3"):
|
||||
audio = ID3(filename)
|
||||
|
||||
if audio.get("APIC:Front Cover"):
|
||||
if audio.get("APIC:Front Cover").data:
|
||||
hasEmbeddedCover = True
|
||||
|
||||
if audio.get("comment"):
|
||||
comment = audio.get("comment")[0]
|
||||
if audio.get("POPM:Windows Media Player 9 Series"):
|
||||
|
@ -83,45 +208,66 @@ def getSongTags(file):
|
|||
if rating > 5: rating = (rating / 255) * 5
|
||||
else:
|
||||
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
|
||||
|
||||
#the rating must be a round value
|
||||
rating = int(round(rating,0))
|
||||
|
||||
except Exception as e:
|
||||
#file in use ?
|
||||
logMsg("Exception in getSongTags %s" %e,0)
|
||||
rating = None
|
||||
|
||||
#remove tempfile if needed....
|
||||
if isTemp: xbmcvfs.delete(filename)
|
||||
|
||||
return (rating, comment)
|
||||
return (rating, comment, hasEmbeddedCover)
|
||||
|
||||
def updateRatingToFile(rating, file):
|
||||
#update the rating from Emby to the file
|
||||
|
||||
isTemp,filename = getRealFileName(file)
|
||||
logMsg( "setting song rating: %s for filename: %s" %(rating,filename))
|
||||
f = xbmcvfs.File(file)
|
||||
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
|
||||
|
||||
try:
|
||||
if filename.lower().endswith(".flac"):
|
||||
audio = FLAC(filename)
|
||||
if tempfile.lower().endswith(".flac"):
|
||||
audio = FLAC(tempfile)
|
||||
calcrating = int(round((float(rating) / 5) * 100, 0))
|
||||
audio["rating"] = str(calcrating)
|
||||
audio.save()
|
||||
elif filename.lower().endswith(".mp3"):
|
||||
audio = ID3(filename)
|
||||
elif tempfile.lower().endswith(".mp3"):
|
||||
audio = ID3(tempfile)
|
||||
calcrating = int(round((float(rating) / 5) * 255, 0))
|
||||
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
||||
audio.save()
|
||||
else:
|
||||
logMsg( "Not supported fileformat: %s" %(filename))
|
||||
logMsg( "Not supported fileformat: %s" %(tempfile))
|
||||
|
||||
#remove tempfile if needed....
|
||||
if isTemp:
|
||||
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
|
||||
#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.copy(filename,file)
|
||||
xbmcvfs.delete(filename)
|
||||
xbmcvfs.copy(tempfile,file)
|
||||
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:
|
||||
#file in use ?
|
||||
|
|
|
@ -78,7 +78,7 @@ class PlaybackUtils():
|
|||
sizePlaylist = playlist.size()
|
||||
currentPosition = startPos
|
||||
|
||||
propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
|
||||
propertiesPlayback = utils.window('emby_playbackProps') == "true"
|
||||
introsPlaylist = False
|
||||
partsPlaylist = False
|
||||
dummyPlaylist = False
|
||||
|
@ -96,11 +96,11 @@ class PlaybackUtils():
|
|||
# Otherwise we get a loop.
|
||||
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)
|
||||
|
||||
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)
|
||||
dummyPlaylist = True
|
||||
|
@ -190,21 +190,21 @@ class PlaybackUtils():
|
|||
# We just skipped adding properties. Reset flag for next time.
|
||||
elif propertiesPlayback:
|
||||
self.logMsg("Resetting properties playback flag.", 2)
|
||||
utils.window('emby_playbackProps', clear=True, windowid=10101)
|
||||
utils.window('emby_playbackProps', clear=True)
|
||||
|
||||
#self.pl.verifyPlaylist()
|
||||
########## SETUP MAIN ITEM ##########
|
||||
|
||||
# For transcoding only, ask for audio/subs pref
|
||||
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")
|
||||
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
|
||||
############### PLAYBACK ################
|
||||
customPlaylist = utils.window('emby_customPlaylist', windowid=10101)
|
||||
customPlaylist = utils.window('emby_customPlaylist')
|
||||
if homeScreen and seektime:
|
||||
self.logMsg("Play as a widget item.", 1)
|
||||
self.setListItem(listitem)
|
||||
|
@ -245,7 +245,7 @@ class PlaybackUtils():
|
|||
# Only for direct play and direct stream
|
||||
# subtitles = self.externalSubs(playurl)
|
||||
subtitles = self.API.externalSubs(playurl)
|
||||
if playmethod in ("DirectStream", "Transcode"):
|
||||
if playmethod != "Transcode":
|
||||
# Direct play automatically appends external
|
||||
listitem.setSubtitles(subtitles)
|
||||
|
||||
|
|
|
@ -416,14 +416,10 @@ class Player(xbmc.Player):
|
|||
|
||||
def onPlayBackStopped( self ):
|
||||
# Will be called when user stops xbmc playing a file
|
||||
currentFile = self.currentFile
|
||||
self.logMsg("ONPLAYBACK_STOPPED", 2)
|
||||
if self.played_info.get(currentFile):
|
||||
self.played_info[currentFile]['paused'] = 'stopped'
|
||||
self.reportPlayback()
|
||||
|
||||
xbmcgui.Window(10101).clearProperties()
|
||||
self.logMsg("Clear playlist properties.")
|
||||
utils.window('emby_customPlaylist', clear=True)
|
||||
utils.window('emby_playbackProps', clear=True)
|
||||
self.logMsg("Clear playlist properties.", 1)
|
||||
self.stopAll()
|
||||
|
||||
def onPlayBackEnded( self ):
|
||||
|
|
|
@ -51,7 +51,7 @@ class Playlist():
|
|||
playlist.clear()
|
||||
started = False
|
||||
|
||||
utils.window('emby_customplaylist', value="true", windowid=10101)
|
||||
utils.window('emby_customplaylist', value="true")
|
||||
|
||||
position = 0
|
||||
|
||||
|
|
|
@ -44,6 +44,12 @@ class PlayUtils():
|
|||
self.API.setPartNumber(partIndex)
|
||||
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":
|
||||
# # Only play as http
|
||||
# self.logMsg("File protocol is http.", 1)
|
||||
|
@ -168,6 +174,12 @@ class PlayUtils():
|
|||
if not self.h265enabled():
|
||||
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
|
||||
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
|
||||
# Plex: always able?!?
|
||||
|
@ -273,7 +285,7 @@ class PlayUtils():
|
|||
# max bit rate supported by server (max signed 32bit integer)
|
||||
return bitrate.get(videoQuality, 2147483)
|
||||
|
||||
def audioSubsPref(self, url, child=0):
|
||||
def audioSubsPref(self, url, listitem, child=0):
|
||||
self.API.setChildNumber(child)
|
||||
# For transcoding only
|
||||
# Present the list of audio to select from
|
||||
|
@ -282,6 +294,7 @@ class PlayUtils():
|
|||
audioStreamsChannelsList = {}
|
||||
subtitleStreamsList = {}
|
||||
subtitleStreams = ['No subtitles']
|
||||
downloadableStreams = []
|
||||
selectAudioIndex = ""
|
||||
selectSubsIndex = ""
|
||||
playurlprefs = "%s" % url
|
||||
|
@ -312,8 +325,8 @@ class PlayUtils():
|
|||
audioStreams.append(track)
|
||||
|
||||
elif 'Subtitle' in type:
|
||||
if stream['IsExternal']:
|
||||
continue
|
||||
'''if stream['IsExternal']:
|
||||
continue'''
|
||||
try:
|
||||
track = "%s - %s" % (index, stream['Language'])
|
||||
except:
|
||||
|
@ -321,10 +334,14 @@ class PlayUtils():
|
|||
|
||||
default = stream['IsDefault']
|
||||
forced = stream['IsForced']
|
||||
downloadable = stream['IsTextSubtitleStream']
|
||||
|
||||
if default:
|
||||
track = "%s - Default" % track
|
||||
if forced:
|
||||
track = "%s - Forced" % track
|
||||
if downloadable:
|
||||
downloadableStreams.append(index)
|
||||
|
||||
subtitleStreamsList[track] = index
|
||||
subtitleStreams.append(track)
|
||||
|
@ -352,7 +369,19 @@ class PlayUtils():
|
|||
# User selected subtitles
|
||||
selected = subtitleStreams[resp]
|
||||
selectSubsIndex = subtitleStreamsList[selected]
|
||||
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
|
||||
|
||||
# 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
|
||||
|
||||
else: # User backed out of selection
|
||||
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
|
||||
|
||||
|
|
|
@ -149,7 +149,37 @@ class Read_EmbyServer():
|
|||
}
|
||||
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
|
||||
items = {
|
||||
|
@ -210,16 +240,12 @@ class Read_EmbyServer():
|
|||
"MediaSources"
|
||||
)
|
||||
result = doUtils.downloadUrl(url, parameters=params)
|
||||
try:
|
||||
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
|
||||
items['Items'].extend(result['Items'])
|
||||
|
||||
index += jump
|
||||
if dialog:
|
||||
percentage = int((float(index) / float(total))*100)
|
||||
dialog.update(percentage)
|
||||
return items
|
||||
|
||||
def getViews(self, type, root=False):
|
||||
|
@ -276,15 +302,15 @@ class Read_EmbyServer():
|
|||
|
||||
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
|
||||
|
||||
def getBoxset(self):
|
||||
def getBoxset(self, dialog=None):
|
||||
|
||||
items = self.getSection(None, "BoxSet")
|
||||
items = self.getSection(None, "BoxSet", dialog=dialog)
|
||||
|
||||
return items
|
||||
|
||||
|
@ -294,9 +320,9 @@ class Read_EmbyServer():
|
|||
|
||||
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
|
||||
|
||||
|
@ -306,9 +332,9 @@ class Read_EmbyServer():
|
|||
|
||||
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
|
||||
|
||||
|
@ -332,9 +358,9 @@ class Read_EmbyServer():
|
|||
|
||||
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
|
||||
|
||||
|
@ -350,7 +376,7 @@ class Read_EmbyServer():
|
|||
|
||||
return items
|
||||
|
||||
def getArtists(self):
|
||||
def getArtists(self, dialog=None):
|
||||
|
||||
doUtils = self.doUtils
|
||||
items = {
|
||||
|
@ -397,21 +423,17 @@ class Read_EmbyServer():
|
|||
)
|
||||
}
|
||||
result = doUtils.downloadUrl(url, parameters=params)
|
||||
try:
|
||||
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
|
||||
items['Items'].extend(result['Items'])
|
||||
|
||||
index += jump
|
||||
if dialog:
|
||||
percentage = int((float(index) / float(total))*100)
|
||||
dialog.update(percentage)
|
||||
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
|
||||
|
||||
|
@ -421,9 +443,9 @@ class Read_EmbyServer():
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -73,12 +73,18 @@ def window(property, value=None, clear=False, windowid=10000):
|
|||
# Get or set window property
|
||||
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:
|
||||
WINDOW.clearProperty(property)
|
||||
elif value is not None:
|
||||
WINDOW.setProperty(property, value)
|
||||
else:
|
||||
return WINDOW.getProperty(property)
|
||||
else: #getproperty returns string so convert to unicode
|
||||
return WINDOW.getProperty(property).decode("utf-8")
|
||||
|
||||
def settings(setting, value=None):
|
||||
# Get or add addon setting
|
||||
|
@ -87,13 +93,12 @@ def settings(setting, value=None):
|
|||
if value is not None:
|
||||
addon.setSetting(setting, value)
|
||||
else:
|
||||
return addon.getSetting(setting)
|
||||
return addon.getSetting(setting) #returns unicode object
|
||||
|
||||
def language(stringid):
|
||||
# Central string retrieval
|
||||
addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
string = addon.getLocalizedString(stringid).decode("utf-8")
|
||||
|
||||
string = addon.getLocalizedString(stringid) #returns unicode object
|
||||
return string
|
||||
|
||||
def kodiSQL(type="video"):
|
||||
|
@ -169,11 +174,11 @@ def reset():
|
|||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
dirs, files = xbmcvfs.listdir(path)
|
||||
for dir in dirs:
|
||||
if dir.startswith('Emby'):
|
||||
shutil.rmtree("%s%s" % (path, dir))
|
||||
if dir.decode('utf-8').startswith('Emby'):
|
||||
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
|
||||
for file in files:
|
||||
if file.startswith('emby'):
|
||||
xbmcvfs.delete("%s%s" % (path, file))
|
||||
if file.decode('utf-8').startswith('emby'):
|
||||
xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
|
||||
|
||||
# Wipe the kodi databases
|
||||
logMsg("EMBY", "Resetting the Kodi video database.")
|
||||
|
@ -254,7 +259,7 @@ def stopProfiling(pr, profileName):
|
|||
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
|
||||
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")
|
||||
for (key, value) in ps.stats.items():
|
||||
(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)
|
||||
try:
|
||||
f = open(xsppath, 'w')
|
||||
f = xbmcvfs.File(xsppath, 'w')
|
||||
except:
|
||||
logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1)
|
||||
return
|
||||
|
@ -526,5 +531,5 @@ def deletePlaylists():
|
|||
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
||||
dirs, files = xbmcvfs.listdir(path)
|
||||
for file in files:
|
||||
if file.startswith('Emby'):
|
||||
if file.decode('utf-8').startswith('Emby'):
|
||||
xbmcvfs.delete("%s%s" % (path, file))
|
|
@ -96,7 +96,7 @@ class VideoNodes(object):
|
|||
return
|
||||
|
||||
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)
|
||||
|
||||
|
@ -163,6 +163,13 @@ class VideoNodes(object):
|
|||
'8': 30255,
|
||||
'11': 30254
|
||||
},
|
||||
'musicvideos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30256,
|
||||
'4': 30257,
|
||||
'6': 30258
|
||||
},
|
||||
}
|
||||
|
||||
nodes = mediatypes[mediatype]
|
||||
|
@ -185,7 +192,7 @@ class VideoNodes(object):
|
|||
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browsecontent&type=%s" %(tagname,mediatype)
|
||||
elif (mediatype == "homevideos" or mediatype == "photos"):
|
||||
# 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":
|
||||
# Custom query
|
||||
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="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="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="limitindex" type="number" label="Maximum items to request at once from server" default="200" option="int" />
|
||||
</category>
|
||||
|
@ -55,6 +56,7 @@
|
|||
<setting id="playFromStream" type="bool" label="30002" default="false" />
|
||||
<setting id="videoBitrate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="eq(-1,true)" default="17" />
|
||||
<setting id="transcodeH265" type="bool" label="Force transcode 1080p/H265" default="false" />
|
||||
<setting id="transcode720H265" type="bool" label="Force transcode 720p/H265" default="false" />
|
||||
<setting id="markPlayed" type="number" visible="false" default="90" />
|
||||
<setting id="failedCount" type="number" visible="false" default="0" />
|
||||
<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="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 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 label="30022">
|
||||
|
|
|
@ -75,13 +75,11 @@ class Service():
|
|||
"emby_online", "emby_serverStatus", "emby_onWake",
|
||||
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
|
||||
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId",
|
||||
"emby_initialScan"
|
||||
"emby_initialScan", "emby_customplaylist", "emby_playbackProps"
|
||||
]
|
||||
for prop in properties:
|
||||
utils.window(prop, clear=True)
|
||||
|
||||
# Clear playlist properties
|
||||
xbmcgui.Window(10101).clearProperties()
|
||||
# Clear video nodes properties
|
||||
videonodes.VideoNodes().clearProperties()
|
||||
|
||||
|
|
Loading…
Reference in a new issue