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

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
- Fix to extrafanart
- Fix for artists deletion

View file

@ -112,7 +112,9 @@ if __name__ == '__main__':
if newvalue:
newvalue = int(newvalue)
if newvalue > 5: newvalue = "5"
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 = ?" ))

View file

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

View file

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

View file

@ -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,6 +167,17 @@ class Artwork():
# This method will sync all Kodi artwork to textures13.db
# and cache them locally. This takes diskspace!
if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"):
return
self.logMsg("Doing Image Cache Sync", 1)
dialog = xbmcgui.DialogProgress()
dialog.create("Emby for Kodi", "Image Cache Sync")
# ask to rest all existing or not
if xbmcgui.Dialog().yesno("Image Texture Cache", "Reset all existing cache data first?", ""):
self.logMsg("Resetting all cache data first", 1)
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
if xbmcvfs.exists(path):
@ -167,9 +185,13 @@ class Artwork():
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
xbmcvfs.delete(os.path.join(path+dir,file))
if os.path.supports_unicode_filenames:
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
textureconnection = utils.KodiSQL('texture')
# remove all existing data from texture DB
textureconnection = utils.kodiSQL('texture')
texturecursor = textureconnection.cursor()
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = texturecursor.fetchall()
@ -180,31 +202,86 @@ class Artwork():
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)
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(
@ -216,6 +293,10 @@ class Artwork():
# We don't need the result
except: pass
else:
self.addWorkerImageCacheThread(url)
def addArtwork(self, artwork, kodiId, mediaType, cursor):
# Kodi conversion table
kodiart = {
@ -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

View file

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

View file

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

View file

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

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)
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
#if enabled, try to get the rating from file and/or emby
if not self.directstream:
rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
else:
hasEmbeddedCover = False
comment = API.getOverview()
rating = userdata['UserRating']
#the server doesn't support comment on songs so this will always be empty
comment = API.getOverview()
#if enabled, try to get the rating and comment value from the file itself
if not self.directstream:
rating, comment = self.getSongRatingAndComment(itemid, rating, API)
##### GET THE FILE AND PATH #####
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

View file

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

View file

@ -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,10 +486,10 @@ 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":
if mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos']:
vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
totalnodes += 1
# Add view to emby database
@ -525,6 +526,7 @@ class LibrarySync(threading.Thread):
current_viewtype,
delete=True)
# Added new playlist
if mediatype in ['movies', 'tvshows', 'musicvideos']:
utils.playlistXSP(mediatype, foldername, viewtype)
# Add new video node
if mediatype != "musicvideos":
@ -540,6 +542,7 @@ class LibrarySync(threading.Thread):
else:
if mediatype != "music":
# Validate the playlist exists or recreate it
if mediatype in ['movies', 'tvshows', 'musicvideos']:
utils.playlistXSP(mediatype, foldername, viewtype)
# Create the video node if not already exists
if mediatype != "musicvideos":
@ -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)')

View file

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

View file

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

View file

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

View file

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

View file

@ -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]
# 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', "")

View file

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

View file

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

View 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

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

View file

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