mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-25 10:16:11 +00:00
Merge remote-tracking branch 'origin/master' into clean-up
This commit is contained in:
commit
dc3dbb202a
6 changed files with 193 additions and 61 deletions
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.emby"
|
||||
name="Emby"
|
||||
version="1.0.08"
|
||||
version="1.0.15"
|
||||
provider-name="Emby.media">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
|
|
|
@ -144,6 +144,20 @@ def getThemeMedia():
|
|||
playback = "DirectStream"
|
||||
else:return
|
||||
|
||||
# Set custom path for user
|
||||
tvtunes_path = xbmc.translatePath("special://profile/addon_data/script.tvtunes/")
|
||||
if xbmcvfs.exists(tvtunes_path):
|
||||
tvtunes = xbmcaddon.Addon(id="script.tvtunes")
|
||||
tvtunes.setSetting('custom_path_enable', "true")
|
||||
tvtunes.setSetting('custom_path', library)
|
||||
xbmc.log("TV Tunes custom path is enabled and set.")
|
||||
else:
|
||||
# if it does not exist this will not work so warn user, often they need to edit the settings first for it to be created.
|
||||
dialog = xbmcgui.Dialog()
|
||||
dialog.ok('Warning', 'The settings file does not exist in tvtunes. Go to the tvtunes addon and change a setting, then come back and re-run')
|
||||
return
|
||||
|
||||
|
||||
# Create library directory
|
||||
if not xbmcvfs.exists(library):
|
||||
xbmcvfs.mkdir(library)
|
||||
|
@ -157,18 +171,20 @@ def getThemeMedia():
|
|||
userviewId = view[u'Id']
|
||||
userViews.append(userviewId)
|
||||
|
||||
# Get Ids with Theme songs
|
||||
|
||||
# Get Ids with Theme Videos
|
||||
itemIds = {}
|
||||
for view in userViews:
|
||||
url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
|
||||
url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
|
||||
result = doUtils.downloadUrl(url)
|
||||
if result[u'TotalRecordCount'] != 0:
|
||||
for item in result[u'Items']:
|
||||
itemId = item[u'Id']
|
||||
folderName = item[u'Name']
|
||||
folderName = utils.normalize_string(folderName.encode('utf-8'))
|
||||
itemIds[itemId] = folderName
|
||||
|
||||
# Get paths
|
||||
# Get paths for theme videos
|
||||
for itemId in itemIds:
|
||||
nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
|
||||
# Create folders for each content
|
||||
|
@ -177,6 +193,64 @@ def getThemeMedia():
|
|||
# Where to put the nfos
|
||||
nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
|
||||
|
||||
url = "{server}/mediabrowser/Items/%s/ThemeVideos?format=json" % itemId
|
||||
result = doUtils.downloadUrl(url)
|
||||
|
||||
# Create nfo and write themes to it
|
||||
nfo_file = open(nfo_path, 'w')
|
||||
pathstowrite = ""
|
||||
# May be more than one theme
|
||||
for theme in result[u'Items']:
|
||||
if playback == "DirectPlay":
|
||||
playurl = playUtils.directPlay(theme)
|
||||
else:
|
||||
playurl = playUtils.directStream(result, server, theme[u'Id'])
|
||||
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
|
||||
|
||||
# Check if the item has theme songs and add them
|
||||
url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId
|
||||
result = doUtils.downloadUrl(url)
|
||||
|
||||
# May be more than one theme
|
||||
for theme in result[u'Items']:
|
||||
if playback == "DirectPlay":
|
||||
playurl = playUtils.directPlay(theme)
|
||||
else:
|
||||
playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio")
|
||||
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
|
||||
|
||||
nfo_file.write(
|
||||
'<tvtunes>%s</tvtunes>' % pathstowrite
|
||||
)
|
||||
# Close nfo file
|
||||
nfo_file.close()
|
||||
|
||||
# Get Ids with Theme songs
|
||||
musicitemIds = {}
|
||||
for view in userViews:
|
||||
url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
|
||||
result = doUtils.downloadUrl(url)
|
||||
if result[u'TotalRecordCount'] != 0:
|
||||
for item in result[u'Items']:
|
||||
itemId = item[u'Id']
|
||||
folderName = item[u'Name']
|
||||
folderName = utils.normalize_string(folderName.encode('utf-8'))
|
||||
musicitemIds[itemId] = folderName
|
||||
|
||||
# Get paths
|
||||
for itemId in musicitemIds:
|
||||
|
||||
# if the item was already processed with video themes back out
|
||||
if itemId in itemIds:
|
||||
continue
|
||||
|
||||
nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
|
||||
# Create folders for each content
|
||||
if not xbmcvfs.exists(nfo_path):
|
||||
xbmcvfs.mkdir(nfo_path)
|
||||
# Where to put the nfos
|
||||
nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
|
||||
|
||||
url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId
|
||||
result = doUtils.downloadUrl(url)
|
||||
|
||||
|
@ -189,7 +263,7 @@ def getThemeMedia():
|
|||
playurl = playUtils.directPlay(theme)
|
||||
else:
|
||||
playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio")
|
||||
pathstowrite += ('<file>%s</file>' % playurl)
|
||||
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
|
||||
|
||||
nfo_file.write(
|
||||
'<tvtunes>%s</tvtunes>' % pathstowrite
|
||||
|
@ -576,12 +650,12 @@ def doMainListing():
|
|||
addDirectoryItem(label, path)
|
||||
|
||||
# some extra entries for settings and stuff. TODO --> localize the labels
|
||||
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings")
|
||||
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync")
|
||||
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
|
||||
addDirectoryItem("Configure user preferences", "plugin://plugin.video.emby/?mode=userprefs")
|
||||
addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset")
|
||||
addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache")
|
||||
addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia")
|
||||
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings", False)
|
||||
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync", False)
|
||||
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser", False)
|
||||
addDirectoryItem("Configure user preferences", "plugin://plugin.video.emby/?mode=userprefs", False)
|
||||
addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset", False)
|
||||
addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache", False)
|
||||
addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia", False)
|
||||
|
||||
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
||||
|
|
|
@ -131,6 +131,9 @@ class LibrarySync(threading.Thread):
|
|||
# set prop to show we have run for the first time
|
||||
WINDOW.setProperty("startup", "done")
|
||||
|
||||
# tell any widgets to refresh because the content has changed
|
||||
WINDOW.setProperty("widgetreload", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
finally:
|
||||
WINDOW.setProperty("SyncDatabaseRunning", "false")
|
||||
utils.logMsg("Sync DB", "syncDatabase Exiting", 0)
|
||||
|
@ -596,6 +599,16 @@ class LibrarySync(threading.Thread):
|
|||
#tv show doesn't exist
|
||||
#perform full tvshow sync instead so both the show and episodes get added
|
||||
self.TvShowsFullSync(connection,cursor,None)
|
||||
|
||||
elif u"Season" in MBitem['Type']:
|
||||
|
||||
#get the tv show
|
||||
cursor.execute("SELECT kodi_id FROM emby WHERE media_type='tvshow' AND emby_id=?", (MBitem["SeriesId"],))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
kodi_show_id = result[0]
|
||||
# update season
|
||||
WriteKodiVideoDB().updateSeasons(MBitem["SeriesId"], kodi_show_id, connection, cursor)
|
||||
|
||||
#### PROCESS BOXSETS ######
|
||||
elif MBitem["Type"] == "BoxSet":
|
||||
|
@ -630,9 +643,10 @@ class LibrarySync(threading.Thread):
|
|||
cursor.close()
|
||||
|
||||
finally:
|
||||
|
||||
xbmc.executebuiltin("UpdateLibrary(video)")
|
||||
WINDOW.setProperty("SyncDatabaseRunning", "false")
|
||||
# tell any widgets to refresh because the content has changed
|
||||
WINDOW.setProperty("widgetreload", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
def ShouldStop(self):
|
||||
|
||||
|
@ -673,4 +687,4 @@ class LibrarySync(threading.Thread):
|
|||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
self.logMsg("--- Library Sync Thread stopped ---", 0)
|
||||
self.logMsg("--- Library Sync Thread stopped ---", 0)
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
#################################################################################################
|
||||
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmc, xbmcaddon, xbmcvfs
|
||||
import json
|
||||
import requests
|
||||
import urllib
|
||||
import os
|
||||
|
||||
import Utils as utils
|
||||
|
||||
|
@ -42,15 +42,38 @@ class TextureCache():
|
|||
def FullTextureCacheSync(self):
|
||||
#this method can be called from the plugin to sync all Kodi textures to the texture cache.
|
||||
#Warning: this means that every image will be cached locally, this takes diskspace!
|
||||
|
||||
#remove all existing textures first
|
||||
path = "special://thumbnails/"
|
||||
if xbmcvfs.exists(path):
|
||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||
for dir in allDirs:
|
||||
allDirs, allFiles = xbmcvfs.listdir(path+dir)
|
||||
for file in allFiles:
|
||||
xbmcvfs.delete(os.path.join(path+dir,file))
|
||||
|
||||
textureconnection = utils.KodiSQL("texture")
|
||||
texturecursor = textureconnection.cursor()
|
||||
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||
rows = texturecursor.fetchall()
|
||||
for row in rows:
|
||||
tableName = row[0]
|
||||
if(tableName != "version"):
|
||||
texturecursor.execute("DELETE FROM " + tableName)
|
||||
textureconnection.commit()
|
||||
texturecursor.close()
|
||||
|
||||
|
||||
#cache all entries in video DB
|
||||
connection = utils.KodiSQL("video")
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT url FROM art")
|
||||
result = cursor.fetchall()
|
||||
for url in result:
|
||||
self.CacheTexture(url[0])
|
||||
|
||||
cursor.close()
|
||||
|
||||
#cache all entries in music DB
|
||||
connection = utils.KodiSQL("music")
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT url FROM art")
|
||||
|
@ -73,7 +96,7 @@ class TextureCache():
|
|||
except:
|
||||
#extreme short timeouts so we will have a exception, but we don't need the result so pass
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def setKodiWebServerDetails(self):
|
||||
# Get the Kodi webserver details - used to set the texture cache
|
||||
|
|
|
@ -54,6 +54,8 @@ def KodiSQL(type="video"):
|
|||
|
||||
if type == "music":
|
||||
dbPath = getKodiMusicDBPath()
|
||||
elif type == "texture":
|
||||
dbPath = xbmc.translatePath("special://database/Textures13.db")
|
||||
else:
|
||||
dbPath = getKodiVideoDBPath()
|
||||
|
||||
|
@ -73,12 +75,7 @@ def getKodiVideoDBPath():
|
|||
dbVersion = "90"
|
||||
elif kodibuild.startswith("15"):
|
||||
# Isengard
|
||||
if "BETA1" in kodibuild:
|
||||
# Beta 1
|
||||
dbVersion = "92"
|
||||
elif "BETA2" in kodibuild:
|
||||
# Beta 2
|
||||
dbVersion = "93"
|
||||
dbVersion = "93"
|
||||
else:
|
||||
# Not a compatible build
|
||||
xbmc.log("This Kodi version is incompatible. Current version: %s" % kodibuild)
|
||||
|
@ -100,17 +97,7 @@ def getKodiMusicDBPath():
|
|||
|
||||
dbPath = xbmc.translatePath("special://profile/Database/MyMusic" + dbVersion + ".db")
|
||||
|
||||
return dbPath
|
||||
|
||||
|
||||
def checkAuthentication():
|
||||
#check authentication
|
||||
if addonSettings.getSetting('username') != "" and addonSettings.getSetting('ipaddress') != "":
|
||||
try:
|
||||
downloadUtils.authenticate()
|
||||
except Exception, e:
|
||||
logMsg("Emby authentication failed",e)
|
||||
pass
|
||||
return dbPath
|
||||
|
||||
def prettifyXml(elem):
|
||||
rough_string = etree.tostring(elem, "utf-8")
|
||||
|
@ -174,6 +161,25 @@ def CleanName(filename):
|
|||
validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
|
||||
cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
|
||||
return ''.join(c for c in cleanedFilename if c in validFilenameChars)
|
||||
|
||||
def normalize_string(text):
|
||||
try:
|
||||
text = text.replace(":", "")
|
||||
text = text.replace("/", "-")
|
||||
text = text.replace("\\", "-")
|
||||
text = text.replace("<", "")
|
||||
text = text.replace(">", "")
|
||||
text = text.replace("*", "")
|
||||
text = text.replace("?", "")
|
||||
text = text.replace('|', "")
|
||||
text = text.strip()
|
||||
# Remove dots from the last character as windows can not have directories
|
||||
# with dots at the end
|
||||
text = text.rstrip('.')
|
||||
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
||||
except:
|
||||
pass
|
||||
return text
|
||||
|
||||
|
||||
def reset():
|
||||
|
|
|
@ -39,18 +39,11 @@ class WriteKodiVideoDB():
|
|||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT emby_id FROM emby WHERE media_type=? AND kodi_id=?",(type,id))
|
||||
|
||||
emby_id = cursor.fetchone()[0]
|
||||
cursor.close
|
||||
emby_id = cursor.fetchone()
|
||||
|
||||
if(emby_id != None):
|
||||
emby_id = emby_id[0]
|
||||
# Erase resume point when user marks watched/unwatched to follow Emby behavior
|
||||
# Also force sets the playcount to instantly reflect the appropriate playstate.
|
||||
if type == "episode":
|
||||
resume = '{"jsonrpc": "2.0", "method": "VideoLibrary.SetEpisodeDetails", "params": {"episodeid": %d, "playcount": %d, "resume": {"position": 0}}, "id": "setResumePoint"}' % (id, playcount)
|
||||
elif type == "movie":
|
||||
resume = '{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": {"movieid": %d, "playcount": %d, "resume": {"position": 0}}, "id": "setResumePoint"}' % (id, playcount)
|
||||
xbmc.executeJSONRPC(resume)
|
||||
|
||||
addon = xbmcaddon.Addon(id='plugin.video.emby')
|
||||
downloadUtils = DownloadUtils()
|
||||
watchedurl = "{server}/mediabrowser/Users/{UserId}/PlayedItems/%s" % emby_id
|
||||
|
@ -58,6 +51,9 @@ class WriteKodiVideoDB():
|
|||
downloadUtils.downloadUrl(watchedurl, type="POST")
|
||||
else:
|
||||
downloadUtils.downloadUrl(watchedurl, type="DELETE")
|
||||
|
||||
self.setKodiResumePoint(id, 0, 0, cursor)
|
||||
cursor.close
|
||||
|
||||
def addOrUpdateMovieToKodiLibrary( self, embyId ,connection, cursor, viewTag):
|
||||
|
||||
|
@ -123,6 +119,13 @@ class WriteKodiVideoDB():
|
|||
if(jsonData != ""):
|
||||
trailerItem = jsonData
|
||||
trailerUrl = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % trailerItem[0][u'Id']
|
||||
elif MBitem.get("RemoteTrailers"):
|
||||
try:
|
||||
trailerUrl = MBitem.get("RemoteTrailers")[0].get("Url")
|
||||
trailerId = trailerUrl.split('=')[1]
|
||||
trailerUrl = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId
|
||||
except:
|
||||
trailerUrl = MBitem.get("RemoteTrailers")[0].get("Url")
|
||||
|
||||
if MBitem.get("DateCreated") != None:
|
||||
dateadded = MBitem["DateCreated"].split('.')[0].replace('T', " ")
|
||||
|
@ -142,24 +145,20 @@ class WriteKodiVideoDB():
|
|||
|
||||
#### ADD OR UPDATE THE FILE AND PATH ###########
|
||||
#### NOTE THAT LASTPLAYED AND PLAYCOUNT ARE STORED AT THE FILE ENTRY
|
||||
if addon.getSetting('useDirectPaths')=='true':
|
||||
if PlayUtils().isDirectPlay(MBitem):
|
||||
playurl = PlayUtils().directPlay(MBitem)
|
||||
#use the direct file path
|
||||
if "\\" in playurl:
|
||||
filename = playurl.rsplit("\\",1)[-1]
|
||||
path = playurl.replace(filename,"")
|
||||
elif "/" in playurl:
|
||||
filename = playurl.rsplit("/",1)[-1]
|
||||
path = playurl.replace(filename,"")
|
||||
else:
|
||||
#for transcoding we just use the server's streaming path because I couldn't figure out how to set the plugin path in the music DB
|
||||
path = server + "/Video/%s/" %MBitem["Id"]
|
||||
filename = "stream.mp4"
|
||||
if addon.getSetting('useDirectPaths') == 'true':
|
||||
playurl = PlayUtils().directPlay(MBitem)
|
||||
if playurl == False:
|
||||
return
|
||||
elif "\\" in playurl:
|
||||
filename = playurl.rsplit("\\",1)[-1]
|
||||
path = playurl.replace(filename, "")
|
||||
elif "/" in playurl:
|
||||
filename = playurl.rsplit("/",1)[-1]
|
||||
path = playurl.replace(filename, "")
|
||||
else:
|
||||
path = "plugin://plugin.video.emby/movies/%s/" % MBitem["Id"]
|
||||
filename = "plugin://plugin.video.emby/movies/%s/?id=%s&mode=play" % (MBitem["Id"],MBitem["Id"])
|
||||
|
||||
|
||||
#create the path
|
||||
cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?",(path,))
|
||||
result = cursor.fetchone()
|
||||
|
@ -757,7 +756,22 @@ class WriteKodiVideoDB():
|
|||
self.addOrUpdateArt(imageUrl, seasonid, "season", "poster", cursor)
|
||||
|
||||
imageUrl = API().getArtwork(season, "Banner")
|
||||
self.addOrUpdateArt(imageUrl, seasonid, "season", "banner", cursor)
|
||||
self.addOrUpdateArt(imageUrl, seasonid, "season", "banner", cursor)
|
||||
# Set All season poster
|
||||
MBitem = ReadEmbyDB().getFullItem(embyTvShowId)
|
||||
seasonNum = -1
|
||||
cursor.execute("SELECT idSeason as seasonid FROM seasons WHERE idShow = ? and season = ?", (kodiTvShowId, seasonNum))
|
||||
result = cursor.fetchone()
|
||||
if result == None:
|
||||
# Create the all-season
|
||||
cursor.execute("select coalesce(max(idSeason),0) as seasonid from seasons")
|
||||
seasonid = cursor.fetchone()[0]
|
||||
seasonid = seasonid + 1
|
||||
cursor.execute("INSERT into seasons(idSeason, idShow, season) values(?, ?, ?)", (seasonid, kodiTvShowId, seasonNum))
|
||||
else:
|
||||
seasonid = result[0]
|
||||
imageUrl = API().getArtwork(MBitem, "Primary")
|
||||
self.addOrUpdateArt(imageUrl, seasonid, "season", "poster", cursor)
|
||||
|
||||
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
|
||||
updateDone = False
|
||||
|
@ -774,7 +788,8 @@ class WriteKodiVideoDB():
|
|||
cursor.execute("UPDATE art set url = ? WHERE media_id = ? AND media_type = ? AND type = ?", (imageUrl, kodiId, mediaType, imageType))
|
||||
|
||||
#cache fanart and poster in Kodi texture cache
|
||||
if imageType == "fanart" or imageType == "poster":
|
||||
if "fanart" in imageType or "poster" in imageType:
|
||||
utils.logMsg("ArtworkSync", "Adding or Updating Fanart: %s" % imageUrl)
|
||||
self.textureCache.CacheTexture(imageUrl)
|
||||
|
||||
def setKodiResumePoint(self, fileid, resume_seconds, total_seconds, cursor):
|
||||
|
|
Loading…
Reference in a new issue