Merge remote-tracking branch 'MediaBrowser/master' into develop

This commit is contained in:
tomkat83 2016-01-22 15:57:22 +01:00
commit 80e1957d25
23 changed files with 634 additions and 375 deletions

View file

@ -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">

View file

@ -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

View file

@ -112,9 +112,11 @@ if __name__ == '__main__':
if newvalue: if newvalue:
newvalue = int(newvalue) newvalue = int(newvalue)
if newvalue > 5: newvalue = "5" if newvalue > 5: newvalue = "5"
musicutils.updateRatingToFile(newvalue, API.getFilePath()) if utils.settings('enableUpdateSongRating') == "true":
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) musicutils.updateRatingToFile(newvalue, API.getFilePath())
API.updateUserRating(embyid, like, favourite, deletelike) 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 = ?" )) query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
kodicursor.execute(query, (newvalue,itemid,)) kodicursor.execute(query, (newvalue,itemid,))
kodiconn.commit() kodiconn.commit()

View file

@ -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]

View file

@ -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>
@ -272,7 +276,6 @@
<string id="30406">Remove from Emby favorites</string> <string id="30406">Remove from Emby favorites</string>
<string id="30407">Set custom song rating</string> <string id="30407">Set custom song rating</string>
<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>

View file

@ -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
@ -159,63 +166,137 @@ class Artwork():
def FullTextureCacheSync(self): def FullTextureCacheSync(self):
# 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!
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
xbmcvfs.delete(os.path.join(path+dir,file))
textureconnection = utils.KodiSQL('texture') if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"):
texturecursor = textureconnection.cursor() return
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = texturecursor.fetchall() self.logMsg("Doing Image Cache Sync", 1)
for row in rows:
tableName = row[0]
if(tableName != "version"):
texturecursor.execute("DELETE FROM " + tableName)
textureconnection.commit()
texturecursor.close()
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 # 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])
cursor.close() count += 1
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
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)
# Add image to texture cache by simply calling it at the http endpoint
url = self.double_urlencode(url)
try: # Extreme short timeouts so we will have a exception.
response = requests.head(
url=(
"http://%s:%s/image/image://%s"
% (self.xbmc_host, self.xbmc_port, url)),
auth=(self.xbmc_username, self.xbmc_password),
timeout=(0.01, 0.01))
# We don't need the result
except: pass
def addArtwork(self, artwork, kodiId, mediaType, cursor): 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

View file

@ -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("\"", "_")

View file

@ -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)

View file

@ -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) 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 # 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,26 +573,35 @@ 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)
li.setThumbnailImage(allart.get('Primary')) if 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') } )
else: else:
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]
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) 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]),

View 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

View file

@ -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)
@ -1830,17 +1833,16 @@ class Music(Items):
track = disc*2**16 + tracknumber track = disc*2**16 + tracknumber
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
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: 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 ##### ##### GET THE FILE AND PATH #####
if self.directstream: if self.directstream:
path = "%s/emby/Audio/%s/" % (self.server, itemid) path = "%s/emby/Audio/%s/" % (self.server, itemid)
@ -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

View file

@ -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.")

View file

@ -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,12 +486,12 @@ 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
emby_db.addView(folderid, foldername, viewtype, tagid) emby_db.addView(folderid, foldername, viewtype, tagid)
@ -525,7 +526,8 @@ class LibrarySync(threading.Thread):
current_viewtype, current_viewtype,
delete=True) delete=True)
# Added new playlist # Added new playlist
utils.playlistXSP(mediatype, foldername, viewtype) if mediatype in ['movies', 'tvshows', 'musicvideos']:
utils.playlistXSP(mediatype, foldername, viewtype)
# Add new video node # Add new video node
if mediatype != "musicvideos": if mediatype != "musicvideos":
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype) vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
@ -540,7 +542,8 @@ 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
utils.playlistXSP(mediatype, foldername, viewtype) if mediatype in ['movies', 'tvshows', 'musicvideos']:
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":
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype) vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
@ -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)')

View file

@ -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...
@ -54,26 +60,145 @@ def getEmbyRatingFromKodiRating(rating):
if (rating == 1 or rating == 2): deletelike = True if (rating == 1 or rating == 2): deletelike = True
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 ?

View file

@ -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)

View file

@ -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 ):

View file

@ -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

View file

@ -43,6 +43,12 @@ class PlayUtils():
if partIndex is not None: if partIndex is not None:
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
@ -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]
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 else: # User backed out of selection
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "") playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")

View file

@ -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
@ -460,4 +482,4 @@ class Read_EmbyServer():
if mediatype: if mediatype:
sorted_items.setdefault(mediatype, []).append(item) sorted_items.setdefault(mediatype, []).append(item)
return sorted_items return sorted_items

View file

@ -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))

View 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

View file

@ -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">

View file

@ -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()