diff --git a/resources/lib/CreateFiles.py b/resources/lib/CreateFiles.py index 63b5e8d8..f8a51242 100644 --- a/resources/lib/CreateFiles.py +++ b/resources/lib/CreateFiles.py @@ -28,17 +28,17 @@ addondir = xbmc.translatePath(addon.getAddonInfo('profile')) dataPath = os.path.join(addondir,"library") movieLibrary = os.path.join(dataPath,'movies') tvLibrary = os.path.join(dataPath,'tvshows') +musicvideoLibrary = os.path.join(dataPath,'musicvideos') class CreateFiles(): def createSTRM(self,item): - item_type=str(item.get("Type")).encode('utf-8') if item_type == "Movie": itemPath = os.path.join(movieLibrary,item["Id"]) strmFile = os.path.join(itemPath,item["Id"] + ".strm") if item_type == "MusicVideo": - itemPath = os.path.join(musicVideoLibrary,item["Id"]) + itemPath = os.path.join(musicvideoLibrary,item["Id"]) strmFile = os.path.join(itemPath,item["Id"] + ".strm") if item_type == "Episode": @@ -86,6 +86,10 @@ class CreateFiles(): itemPath = os.path.join(movieLibrary,item["Id"]) nfoFile = os.path.join(itemPath,item["Id"] + ".nfo") rootelement = "movie" + if item_type == "MusicVideo": + itemPath = os.path.join(musicvideoLibrary,item["Id"]) + nfoFile = os.path.join(itemPath,item["Id"] + ".nfo") + rootelement = "musicvideo" if item_type == "Series": itemPath = os.path.join(tvLibrary,item["Id"]) nfoFile = os.path.join(itemPath,"tvshow.nfo") @@ -132,6 +136,12 @@ class CreateFiles(): SubElement(root, "originaltitle").text = utils.convertEncoding(item["Name"]) SubElement(root, "sorttitle").text = utils.convertEncoding(item["SortName"]) + if item.has_key("Album"): + SubElement(root, "album").text = item["Album"] + + if item.has_key("Artist"): + SubElement(root, "artist").text = item["Artist"][0] + if item.has_key("OfficialRating"): SubElement(root, "mpaa").text = item["OfficialRating"] diff --git a/resources/lib/LibrarySync.py b/resources/lib/LibrarySync.py index 415d6065..d0651f03 100644 --- a/resources/lib/LibrarySync.py +++ b/resources/lib/LibrarySync.py @@ -59,9 +59,14 @@ class LibrarySync(): self.TvShowsSync(True) #utils.stopProfiling(pr, "TvShowsSync(True)") + #pr = utils.startProfiling() + self.MusicVideosSync(True) + #utils.stopProfiling(pr, "MusicVideosSync(True)") + if syncOption == "Incremental Sync": self.MoviesSync(False) self.TvShowsSync(False) + self.MusicVideosSync(False) WINDOW.setProperty("startup", "done") @@ -194,7 +199,7 @@ class LibrarySync(): allEmbyMovieIds = set(allEmbyMovieIds) for kodiId in allKodiIds: if not kodiId in allEmbyMovieIds: - WriteKodiDB().deleteMovieFromKodiLibrary(dir) + WriteKodiDB().deleteMovieFromKodiLibrary(kodiId) cleanNeeded = True if(self.ShouldStop(pDialog)): @@ -534,6 +539,141 @@ class LibrarySync(): return True + def MusicVideosSync(self, fullsync=True): + + addon = xbmcaddon.Addon(id='plugin.video.mb3sync') + WINDOW = xbmcgui.Window( 10000 ) + pDialog = None + + try: + enableProgress = False + if addon.getSetting("enableProgressFullSync") == 'true': + enableProgress = True + + if(addon.getSetting("SyncFirstMusicVideoRunDone") != 'true'): + pDialog = xbmcgui.DialogProgress() + elif(enableProgress): + pDialog = xbmcgui.DialogProgressBG() + + if(pDialog != None): + pDialog.create('Sync DB', 'Sync DB') + + allEmbyMusicVideoIds = list() + + progressTitle = "" + + updateNeeded = False + + #process new musicvideos + allMB3MusicVideos = ReadEmbyDB().getMusicVideos(True, fullsync) + allKodiIds = set(ReadKodiDB().getKodiMusicVideoIds(True)) + + if(self.ShouldStop(pDialog)): + return True + + if(allMB3MusicVideos == None): + return False + + if(pDialog != None): + progressTitle = "Sync DB : Processing Musicvideos" + pDialog.update(0, progressTitle) + total = len(allMB3MusicVideos) + 1 + count = 1 + + for item in allMB3MusicVideos: + + if not item.get('IsFolder'): + allEmbyMusicVideoIds.append(item["Id"]) + + if item["Id"] not in allKodiIds: + WriteKodiDB().addMusicVideoToKodiLibrary(item) + updateNeeded = True + + if(self.ShouldStop(pDialog)): + return True + + # update progress bar + if(pDialog != None): + percentage = int(((float(count) / float(total)) * 100)) + pDialog.update(percentage, progressTitle, "Adding Musicvideo: " + str(count)) + count += 1 + + #initiate library update and wait for finish before processing any updates + if updateNeeded: + if(pDialog != None): + pDialog.update(0, "Processing New Items", "Importing STRM Files") + + if(pDialog != None and type(pDialog) == xbmcgui.DialogProgressBG): + pDialog.close() + + self.doKodiLibraryUpdate(False, pDialog) + + if(pDialog != None and type(pDialog) == xbmcgui.DialogProgressBG): + pDialog.create('Sync DB', 'Sync DB') + + if(self.ShouldStop(pDialog)): + return True + + if(pDialog != None): + progressTitle = "Sync DB : Processing musicvideos" + pDialog.update(0, progressTitle, "") + total = len(allMB3MusicVideos) + 1 + count = 1 + + #process updates + allKodiMusicVideos = ReadKodiDB().getKodiMusicVideos(True) + for item in allMB3MusicVideos: + + if not item.get('IsFolder'): + + kodimusicvideo = allKodiMusicVideos.get(item["Id"], None) + if(kodimusicvideo != None): + #WriteKodiDB().updateMusicVideoToKodiLibrary(item, kodimusicvideo) + WriteKodiDB().updateMusicVideoToKodiLibrary_Batched(item, kodimusicvideo) + + if(self.ShouldStop(pDialog)): + return True + + # update progress bar + if(pDialog != None): + percentage = int(((float(count) / float(total)) * 100)) + pDialog.update(percentage, progressTitle, "Updating MusicVideo: " + str(count)) + count += 1 + + + if(pDialog != None): + progressTitle = "Removing Deleted Items" + pDialog.update(0, progressTitle, "") + + if(self.ShouldStop(pDialog)): + return True + + cleanNeeded = False + + # process any deletes only at fullsync + if fullsync: + allKodiIds = ReadKodiDB().getKodiMusicVideoIds(True) + allEmbyMusicVideoIds = set(allEmbyMusicVideoIds) + for kodiId in allKodiIds: + if not kodiId in allEmbyMusicVideoIds: + WriteKodiDB().deleteMusicVideoFromKodiLibrary(kodiId) + cleanNeeded = True + + if(self.ShouldStop(pDialog)): + return True + + #initiate library clean and wait for finish before processing any updates + if cleanNeeded: + self.doKodiLibraryUpdate(True, pDialog) + + addon.setSetting("SyncFirstMusicVideoRunDone", "true") + + finally: + if(pDialog != None): + pDialog.close() + + return True + def doKodiLibraryUpdate(self, clean, prog): #initiate library update and wait for finish before processing any updates if clean: diff --git a/resources/lib/ReadEmbyDB.py b/resources/lib/ReadEmbyDB.py index 78479d35..849b6a46 100644 --- a/resources/lib/ReadEmbyDB.py +++ b/resources/lib/ReadEmbyDB.py @@ -40,7 +40,36 @@ class ReadEmbyDB(): result = result['Items'] return result - + + def getMusicVideos(self, fullinfo = False, fullSync = True): + result = None + + addon = xbmcaddon.Addon(id='plugin.video.mb3sync') + port = addon.getSetting('port') + host = addon.getSetting('ipaddress') + server = host + ":" + port + + downloadUtils = DownloadUtils() + userid = downloadUtils.getUserId() + + if not fullSync: + sortstring = "&Limit=20&SortBy=DateCreated" + else: + sortstring = "&SortBy=SortName" + + if fullinfo: + url = server + '/mediabrowser/Users/' + userid + '/items?' + sortstring + '&Fields=Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&SortOrder=Descending&IncludeItemTypes=MusicVideo&format=json&ImageTypeLimit=1' + else: + url = server + '/mediabrowser/Users/' + userid + '/items?' + sortstring + '&Fields=CumulativeRunTimeTicks&Recursive=true&SortOrder=Descending&IncludeItemTypes=MusicVideo&CollapseBoxSetItems=false&format=json&ImageTypeLimit=1' + + jsonData = downloadUtils.downloadUrl(url, suppress=True, popup=0) + if jsonData != None and jsonData != "": + result = json.loads(jsonData) + if(result.has_key('Items')): + result = result['Items'] + + return result + def getItem(self, id): result = None diff --git a/resources/lib/ReadKodiDB.py b/resources/lib/ReadKodiDB.py index aa5b6a65..63f9d1fa 100644 --- a/resources/lib/ReadKodiDB.py +++ b/resources/lib/ReadKodiDB.py @@ -14,6 +14,7 @@ addondir = xbmc.translatePath(addon.getAddonInfo('profile')) dataPath = os.path.join(addondir,"library") movieLibrary = os.path.join(dataPath,'movies') tvLibrary = os.path.join(dataPath,'tvshows') +musicvideoLibrary = os.path.join(dataPath,'musicvideos') #sleepval is used to throttle the calls to the xbmc json API sleepVal = 15 @@ -184,3 +185,61 @@ class ReadKodiDB(): break return episode + + def getKodiMusicVideo(self, id): + #returns a single musicvideo from Kodi db selected on MB item ID + xbmc.sleep(sleepVal) + json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMusicVideos", "params": { "filter": {"operator": "contains", "field": "path", "value": "' + id + '"}, "properties" : ["art", "thumbnail", "resume", "runtime", "year", "genre", "studio", "artist", "album", "track","plot", "director", "playcount", "tag", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMusicVideos"}') + print json_response + jsonobject = json.loads(json_response.decode('utf-8','replace')) + musicvideo = None + + if(jsonobject.has_key('result')): + result = jsonobject['result'] + if(result.has_key('musicvideos')): + musicvideos = result['musicvideos'] + musicvideo = musicvideos[0] + + return musicvideo + + def getKodiMusicVideos(self,fullInfo = False): + #returns all musicvideos in Kodi db inserted by MB + xbmc.sleep(sleepVal) + if fullInfo: + json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMusicVideos", "params": { "filter": {"operator": "contains", "field": "path", "value": "plugin.video.mb3sync"}, "properties" : ["art", "thumbnail", "resume", "runtime", "year", "genre", "studio", "artist", "album", "track","plot", "director", "playcount", "tag", "file"] }, "id": "libMusicVideos"}') + else: + json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMusicVideos", "params": { "filter": {"operator": "contains", "field": "path", "value": "plugin.video.mb3sync"}, "properties" : ["resume", "playcount", "file"] }, "id": "libMusicVideos"}') + jsonobject = json.loads(json_response.decode('utf-8','replace')) + musicvideos = None + if(jsonobject.has_key('result')): + result = jsonobject['result'] + if(result.has_key('musicvideos')): + musicvideos = result['musicvideos'] + + kodiMusicVideoMap = None + if(musicvideos != None and len(musicvideos) > 0): + kodiMusicVideoMap = {} + for kodivideo in musicvideos: + key = kodivideo["file"][-37:-5] #extract the id from the file name + kodiMusicVideoMap[key] = kodivideo + + return kodiMusicVideoMap + + def getKodiMusicVideoIds(self,returnMB3Ids = False): + # returns a list of movieIds or MB3 Id's from all movies currently in the Kodi library + allKodiMusicVideos = self.getKodiMusicVideos(False) + + if(allKodiMusicVideos == None): + return list() + + if(returnMB3Ids): + allKodiMusicVideoIds = list(allKodiMusicVideos.keys()) + return allKodiMusicVideoIds + else: + allKodiMusicVideoIds = list() + for kodivideo in allKodiMusicVideos.values(): + id = str(kodivideo["musicvideoid"]) + allKodiMusicVideoIds.append(id) + + return allKodiMusicVideoIds + \ No newline at end of file diff --git a/resources/lib/Utils.py b/resources/lib/Utils.py index 9bdbacf2..5f92f3e4 100644 --- a/resources/lib/Utils.py +++ b/resources/lib/Utils.py @@ -53,6 +53,7 @@ def checkKodiSources(): dataPath = os.path.join(addondir,"library") movieLibrary = os.path.join(dataPath,'movies') tvLibrary = os.path.join(dataPath,'tvshows') + musicvideoLibrary = os.path.join(dataPath,'musicvideos') rebootRequired = False @@ -66,6 +67,10 @@ def checkKodiSources(): xbmcvfs.mkdir(tvLibrary) rebootRequired = True addKodiSource("mediabrowser_tvshows",tvLibrary,"tvshows") + if not xbmcvfs.exists(musicvideoLibrary + os.sep): + xbmcvfs.mkdir(musicvideoLibrary) + rebootRequired = True + addKodiSource("mediabrowser_musicvideos",musicvideoLibrary,"musicvideos") rebootRequired = KodiAdvancedSettingsCheck() @@ -105,7 +110,7 @@ def addKodiSource(name, path, type): # add it to sources.xml sourcesFile = xbmc.translatePath( "special://profile/sources.xml" ) - # add an emply sources file to work with + # add an empty sources file to work with if xbmcvfs.exists(sourcesFile) == False: sources = Element("sources") video = SubElement(sources, "video") diff --git a/resources/lib/WriteKodiDB.py b/resources/lib/WriteKodiDB.py index 92821d89..3cb8aa49 100644 --- a/resources/lib/WriteKodiDB.py +++ b/resources/lib/WriteKodiDB.py @@ -24,6 +24,7 @@ addondir = xbmc.translatePath(addon.getAddonInfo('profile')) dataPath = os.path.join(addondir,"library") movieLibrary = os.path.join(dataPath,'movies') tvLibrary = os.path.join(dataPath,'tvshows') +musicvideoLibrary = os.path.join(dataPath,'musicvideos') sleepVal = 20 @@ -167,7 +168,65 @@ class WriteKodiDB(): if(changes): utils.logMsg("Updated item to Kodi Library", MBitem["Id"] + " - " + MBitem["Name"], level=0) + def updateMusicVideoToKodiLibrary_Batched(self, MBitem, KodiItem): + addon = xbmcaddon.Addon(id='plugin.video.mb3sync') + port = addon.getSetting('port') + host = addon.getSetting('ipaddress') + server = host + ":" + port + downloadUtils = DownloadUtils() + userid = downloadUtils.getUserId() + timeInfo = API().getTimeInfo(MBitem) + userData=API().getUserData(MBitem) + people = API().getPeople(MBitem) + genre = API().getGenre(MBitem) + studios = API().getStudios(MBitem) + mediaStreams=API().getMediaStreams(MBitem) + + thumbPath = API().getArtwork(MBitem, "Primary") + + params = list() + + self.getArtworkParam_Batched(KodiItem, MBitem, params) + + #update common properties + duration = (int(timeInfo.get('Duration'))*60) + self.getPropertyParam_Batched(KodiItem, "runtime", duration, params) + self.getPropertyParam_Batched(KodiItem, "year", MBitem.get("ProductionYear"), params) + self.getPropertyParamArray_Batched(KodiItem, "director", people.get("Director"), params) + self.getPropertyParamArray_Batched(KodiItem, "genre", MBitem.get("Genres"), params) + self.getPropertyParamArray_Batched(KodiItem, "artist", MBitem.get("Artist"), params) + self.getPropertyParamArray_Batched(KodiItem, "album", MBitem.get("Album"), params) + + if(studios != None): + for x in range(0, len(studios)): + studios[x] = studios[x].replace("/", "&") + self.getPropertyParamArray_Batched(KodiItem, "studio", studios, params) + + changes = False + # if there were movies changes then send the update via JSONRPC + if(len(params) > 0): + changes |= True + utils.logMsg("UpdateMovieParams", str(params), level = 2) + jsoncommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.SetMusicVideoDetails", "params": { "musicvideoid": %i, %s}, "id": 1 }' + paramString = "" + paramLen = len(params) + for x in range(0, paramLen): + param = params[x] + paramString += param + if(x < paramLen-1): + paramString += ", " + jsoncommand = jsoncommand %(KodiItem['musicvideoid'], paramString) + utils.logMsg("executeJSONRPC : ", jsoncommand, level = 2) + xbmc.sleep(sleepVal) + result = xbmc.executeJSONRPC(jsoncommand) + + CreateFiles().createSTRM(MBitem) + CreateFiles().createNFO(MBitem) + + if(changes): + utils.logMsg("Updated musicvideo to Kodi Library", MBitem["Id"] + " - " + MBitem["Name"], level=0) + def updateMovieToKodiLibrary(self, MBitem, KodiItem): addon = xbmcaddon.Addon(id='plugin.video.mb3sync') @@ -615,6 +674,25 @@ class WriteKodiDB(): if changes: utils.logMsg("MB3 Sync","Added movie to Kodi Library",item["Id"] + " - " + item["Name"]) + def addMusicVideoToKodiLibrary( self, item ): + itemPath = os.path.join(musicvideoLibrary,item["Id"]) + strmFile = os.path.join(itemPath,item["Id"] + ".strm") + + changes = False + + #create path if not exists + if not xbmcvfs.exists(itemPath + os.sep): + xbmcvfs.mkdir(itemPath) + + #create nfo file + changes = CreateFiles().createNFO(item) + + # create strm file + changes |= CreateFiles().createSTRM(item) + + if changes: + utils.logMsg("MB3 Sync","Added musicvideo to Kodi Library",item["Id"] + " - " + item["Name"]) + def addEpisodeToKodiLibrary(self, item): changes = False @@ -641,8 +719,21 @@ class WriteKodiDB(): for file in allFiles: xbmcvfs.delete(os.path.join(path,file)) xbmcvfs.rmdir(path) - + + def deleteMusicVideoFromKodiLibrary(self, id ): + utils.logMsg("deleting musicvideo from Kodi library",id) + kodiItem = ReadKodiDB().getKodiMusicVideo(id) + if kodiItem != None: + xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMusicVideo", "params": { "musicvideoid": %i}, "id": 1 }' %(kodiItem["musicvideoid"])) + path = os.path.join(musicvideoLibrary,id) + allDirs, allFiles = xbmcvfs.listdir(path) + for dir in allDirs: + xbmcvfs.rmdir(os.path.join(path,dir)) + for file in allFiles: + xbmcvfs.delete(os.path.join(path,file)) + xbmcvfs.rmdir(path) + def deleteEpisodeFromKodiLibrary(self, episodeid, tvshowid ): utils.logMsg("deleting episode from Kodi library",episodeid) episode = ReadKodiDB().getKodiEpisodeByMbItem(episodeid, tvshowid) @@ -713,9 +804,7 @@ class WriteKodiDB(): cursor.execute(sql, (seasonid,"season","landscape",MB3landscape)) connection.commit() - cursor.close() - - + cursor.close() def setKodiResumePoint(self, id, resume_seconds, total_seconds, fileType): #use sqlite to set the resume point while json api doesn't support this yet