diff --git a/README.md b/README.md index d87d4049..59b8de90 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ We're still in beta stage of development. Currently these features are working: **Important note about MySQL database in kodi** -The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, Emby takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library. Offcourse you can still use MySQL for your music while music is not supported by the addon currently. +The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, Emby takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library. **Important note about user collections/nodes** diff --git a/addon.xml b/addon.xml index fe13d361..49ee7dd1 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 56d6b63a..4285f804 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +version 1.1.52 +- Report playback for music +- Support Emby tags for music videos +- Fix studio icon for movies +- FORCE RESET LOCAL DATABASE IN PLACE + version 1.1.50 - Ignore channels from syncing process - Date added can now be updated diff --git a/resources/lib/API.py b/resources/lib/API.py index acb692cd..52212311 100644 --- a/resources/lib/API.py +++ b/resources/lib/API.py @@ -394,10 +394,13 @@ class API(): maxHeight = 10000 maxWidth = 10000 - quality = "" + customquery = "" if utils.settings('compressArt') == "true": - quality = "&Quality=90" + customquery = "&Quality=90" + + if utils.settings('disableCoverArt') == "true": + customquery += "&EnableImageEnhancers=false" allartworks = { @@ -413,14 +416,14 @@ class API(): # Process backdrops backdropIndex = 0 for backdroptag in backdrops: - artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, backdropIndex, maxWidth, maxHeight, backdroptag, quality) + artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, backdropIndex, maxWidth, maxHeight, backdroptag, customquery) allartworks['Backdrop'].append(artwork) backdropIndex += 1 # Process the rest of the artwork for art in artworks: tag = artworks[art] - artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art, maxWidth, maxHeight, tag, quality) + artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art, maxWidth, maxHeight, tag, customquery) allartworks[art] = artwork # Process parent items if the main item is missing artwork @@ -436,7 +439,7 @@ class API(): backdropIndex = 0 for parentbackdroptag in parentbackdrops: - artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, backdropIndex, maxWidth, maxHeight, parentbackdroptag, quality) + artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, backdropIndex, maxWidth, maxHeight, parentbackdroptag, customquery) allartworks['Backdrop'].append(artwork) backdropIndex += 1 @@ -450,7 +453,7 @@ class API(): if parentId: parentTag = item['Parent%sImageTag' % parentart] - artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, parentart, maxWidth, maxHeight, parentTag, quality) + artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, parentart, maxWidth, maxHeight, parentTag, customquery) allartworks[parentart] = artwork # Parent album works a bit differently @@ -460,7 +463,7 @@ class API(): if parentId and item.get('AlbumPrimaryImageTag'): parentTag = item['AlbumPrimaryImageTag'] - artwork = "%s/mediabrowser/Items/%s/Images/Primary/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, maxWidth, maxHeight, parentTag, quality) + artwork = "%s/mediabrowser/Items/%s/Images/Primary/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, maxWidth, maxHeight, parentTag, customquery) allartworks['Primary'] = artwork diff --git a/resources/lib/KodiMonitor.py b/resources/lib/KodiMonitor.py index 5426a237..a2216eb0 100644 --- a/resources/lib/KodiMonitor.py +++ b/resources/lib/KodiMonitor.py @@ -28,9 +28,14 @@ class Kodi_Monitor( xbmc.Monitor ): className = self.__class__.__name__ utils.logMsg("%s %s" % ("EMBY", className), msg, int(lvl)) - def onDatabaseUpdated(self, database): - pass - + def onScanStarted(self, library): + utils.window('kodiScan', value="true") + self.logMsg("Kodi library scan running.", 2) + + def onScanFinished(self, library): + utils.window('kodiScan', clear=True) + self.logMsg("Kodi library scan finished.", 2) + #this library monitor is used to detect a watchedstate change by the user through the library #as well as detect when a library item has been deleted to pass the delete to the Emby server def onNotification (self, sender, method, data): @@ -38,72 +43,48 @@ class Kodi_Monitor( xbmc.Monitor ): WINDOW = self.WINDOW downloadUtils = DownloadUtils() #player started playing an item - - if ("Playlist.OnAdd" in method or "Player.OnPlay" in method) and utils.settings('useDirectPaths')=='true': + if ("Playlist.OnAdd" in method or "Player.OnPlay" in method): jsondata = json.loads(data) - if jsondata != None: + if jsondata: if jsondata.has_key("item"): if jsondata.get("item").has_key("id") and jsondata.get("item").has_key("type"): id = jsondata.get("item").get("id") type = jsondata.get("item").get("type") - embyid = ReadKodiDB().getEmbyIdByKodiId(id,type) - - if embyid != None: - - playurl = xbmc.Player().getPlayingFile() - - WINDOW = xbmcgui.Window( 10000 ) - username = WINDOW.getProperty('currUser') - userid = WINDOW.getProperty('userId%s' % username) - server = WINDOW.getProperty('server%s' % username) + + if utils.settings('useDirectPaths')=='true' or (type == "song" and utils.settings('enableMusicSync') == "true"): - url = "{server}/mediabrowser/Users/{UserId}/Items/" + embyid + "?format=json&ImageTypeLimit=1" - result = downloadUtils.downloadUrl(url) - print "Here: %s" % result - userData = result['UserData'] - - playurl = PlayUtils().getPlayUrl(server, embyid, result) - - watchedurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + embyid - positionurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayingItems/' + embyid - deleteurl = 'http://' + server + '/mediabrowser/Items/' + embyid + if type == "song": + connection = utils.KodiSQL('music') + cursor = connection.cursor() + embyid = ReadKodiDB().getEmbyIdByKodiId(id, type, connection, cursor) + cursor.close() + else: + embyid = ReadKodiDB().getEmbyIdByKodiId(id,type) - # set the current playing info - WINDOW.setProperty(playurl+"watchedurl", watchedurl) - WINDOW.setProperty(playurl+"positionurl", positionurl) - WINDOW.setProperty(playurl+"deleteurl", "") - WINDOW.setProperty(playurl+"deleteurl", deleteurl) - if result.get("Type")=="Episode": - WINDOW.setProperty(playurl+"refresh_id", result.get("SeriesId")) - else: - WINDOW.setProperty(playurl+"refresh_id", embyid) + if embyid: + + url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % embyid + result = downloadUtils.downloadUrl(url) + self.logMsg("Result: %s" % result, 2) - WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks"))) - WINDOW.setProperty(playurl+"type", result.get("Type")) - WINDOW.setProperty(playurl+"item_id", embyid) + playurl = None + count = 0 + while not playurl and count < 2: + try: + playurl = xbmc.Player().getPlayingFile() + except RuntimeError: + xbmc.sleep(200) + else: + listItem = xbmcgui.ListItem() + PlaybackUtils().setProperties(playurl, result, listItem) - if PlayUtils().isDirectPlay(result) == True: - playMethod = "DirectPlay" - else: - playMethod = "Transcode" - - WINDOW.setProperty(playurl+"playmethod", playMethod) - - mediaSources = result.get("MediaSources") - if(mediaSources != None): - mediaStream = mediaSources[0].get('MediaStreams') - defaultsubs = "" - for stream in mediaStream: - if u'Subtitle' in stream[u'Type'] and stream[u'IsDefault']: - if u'Language' in stream: - defaultsubs = stream[u'Language'] + if type == "song" and utils.settings('directstreammusic') == "true": + utils.window('%splaymethod' % playurl, value="DirectStream") else: - defaultsubs = stream[u'Codec'] - WINDOW.setProperty("%ssubs" % playurl, defaultsubs.encode('utf-8')) - if mediaSources[0].get('DefaultAudioStreamIndex') != None: - WINDOW.setProperty(playurl+"AudioStreamIndex", str(mediaSources[0].get('DefaultAudioStreamIndex'))) - if mediaSources[0].get('DefaultSubtitleStreamIndex') != None: - WINDOW.setProperty(playurl+"SubtitleStreamIndex", str(mediaSources[0].get('DefaultSubtitleStreamIndex'))) + utils.window('%splaymethod' % playurl, value="DirectPlay") + + count += 1 if method == "VideoLibrary.OnUpdate": # Triggers 4 times, the following is only for manually marking as watched/unwatched diff --git a/resources/lib/LibrarySync.py b/resources/lib/LibrarySync.py index 26acab11..c8ee381d 100644 --- a/resources/lib/LibrarySync.py +++ b/resources/lib/LibrarySync.py @@ -1078,6 +1078,15 @@ class LibrarySync(threading.Thread): # Library sync if not startupComplete: + + # Verify the database for videos + videodb = utils.getKodiVideoDBPath() + if not xbmcvfs.exists(videodb): + # Database does not exists. + self.logMsg("The current Kodi version is incompatible with the Emby for Kodi add-on. Please visit here, to see currently supported Kodi versions: https://github.com/MediaBrowser/Emby.Kodi/wiki", 0) + xbmcgui.Dialog().ok("Emby Warning", "Cancelling the database syncing process. Current Kodi version: %s is unsupported. Please verify your logs for more info." % xbmc.getInfoLabel('System.BuildVersion')) + break + # Run full sync self.logMsg("DB Version: " + utils.settings("dbCreatedWithVersion"), 0) self.logMsg("Doing_Db_Sync: syncDatabase (Started)", 1) @@ -1099,21 +1108,21 @@ class LibrarySync(threading.Thread): - if len(self.updateItems) > 0: + if len(self.updateItems) > 0 and utils.window('kodiScan') != "true": # Add or update items self.logMsg("Processing items: %s" % (str(self.updateItems)), 1) listItems = self.updateItems self.updateItems = [] self.IncrementalSync(listItems) - if len(self.userdataItems) > 0: + if len(self.userdataItems) > 0 and utils.window('kodiScan') != "true": # Process userdata changes only self.logMsg("Processing items: %s" % (str(self.userdataItems)), 1) listItems = self.userdataItems self.userdataItems = [] self.setUserdata(listItems) - if len(self.removeItems) > 0: + if len(self.removeItems) > 0 and utils.window('kodiScan') != "true": # Remove item from Kodi library self.logMsg("Removing items: %s" % self.removeItems, 1) listItems = self.removeItems diff --git a/resources/lib/Lock.py b/resources/lib/Lock.py index 169f1ab1..59103d9c 100644 --- a/resources/lib/Lock.py +++ b/resources/lib/Lock.py @@ -20,12 +20,12 @@ class Lock: break; except OSError as e: if (time.time() - start_time) >= self.timeout: - xbmc.log("File_Lock_On " + self.filename + " timed out") + xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " timed out") return False #xbmc.log("File_Lock_On " + self.filename + " error " + str(e)) time.sleep(self.delay) self.is_locked = True - xbmc.log("File_Lock_On " + self.filename + " obtained") + xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " obtained") return True def release(self): @@ -33,7 +33,7 @@ class Lock: os.close(self.fd) os.unlink(self.filename) self.is_locked = False - xbmc.log("File_Lock_On " + self.filename + " released") + xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " released") def __del__(self): self.release() diff --git a/resources/lib/PlaybackUtils.py b/resources/lib/PlaybackUtils.py index 9c50fa1d..bbbf0c1e 100644 --- a/resources/lib/PlaybackUtils.py +++ b/resources/lib/PlaybackUtils.py @@ -125,25 +125,35 @@ class PlaybackUtils(): if utils.settings('disableCinema') == "false" and not seekTime: # if we have any play them when the movie/show is not being resumed - url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id - intros = doUtils.downloadUrl(url) + getTrailers = True - if intros['TotalRecordCount'] != 0: - for intro in intros['Items']: - # The server randomly returns intros, process them. - introId = intro['Id'] - - introPlayurl = PlayUtils().getPlayUrl(server, introId, intro) - introListItem = xbmcgui.ListItem() - self.logMsg("Adding Intro: %s" % introPlayurl, 1) + if utils.settings('askCinema') == "true": + resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?") + if not resp: + # User selected to not play trailers + getTrailers = False + self.logMsg("Skip trailers.", 1) - # Set listitem and properties for intros - self.setProperties(introPlayurl, intro, introListItem) - self.setListItemProps(server, introId, introListItem, intro) - - playlist.add(introPlayurl, introListItem, index=currentPosition) - introsPlaylist = True - currentPosition += 1 + if getTrailers: + url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id + intros = doUtils.downloadUrl(url) + + if intros['TotalRecordCount'] != 0: + for intro in intros['Items']: + # The server randomly returns intros, process them. + introId = intro['Id'] + + introPlayurl = PlayUtils().getPlayUrl(server, introId, intro) + introListItem = xbmcgui.ListItem() + self.logMsg("Adding Intro: %s" % introPlayurl, 1) + + # Set listitem and properties for intros + self.setProperties(introPlayurl, intro, introListItem) + self.setListItemProps(server, introId, introListItem, intro) + + playlist.add(introPlayurl, introListItem, index=currentPosition) + introsPlaylist = True + currentPosition += 1 ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############### diff --git a/resources/lib/ReadEmbyDB.py b/resources/lib/ReadEmbyDB.py index 86bd6b68..3ab12bfd 100644 --- a/resources/lib/ReadEmbyDB.py +++ b/resources/lib/ReadEmbyDB.py @@ -176,13 +176,14 @@ class ReadEmbyDB(): url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&SortBy=DateCreated&Recursive=true&IncludeItemTypes=MusicAlbum&format=json" % (index, jump) jsondata = self.doUtils.downloadUrl(url) - tempresult = [] + #tempresult = [] # Only return valid albums - which have artists - for item in jsondata['Items']: + '''for item in jsondata['Items']: if item['AlbumArtists']: tempresult.append(item) - result.extend(tempresult) + result.extend(tempresult)''' + result.extend(jsondata['Items']) index += jump return result diff --git a/resources/lib/Utils.py b/resources/lib/Utils.py index 37d55267..67abfcff 100644 --- a/resources/lib/Utils.py +++ b/resources/lib/Utils.py @@ -56,59 +56,44 @@ def KodiSQL(type="video"): if type == "music": dbPath = getKodiMusicDBPath() elif type == "texture": - dbPath = xbmc.translatePath("special://database/Textures13.db") + dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8') else: dbPath = getKodiVideoDBPath() connection = sqlite3.connect(dbPath) - return connection def getKodiVideoDBPath(): - kodibuild = xbmc.getInfoLabel("System.BuildVersion") + kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2] + dbVersion = { - if kodibuild.startswith("13"): - # Gotham - dbVersion = "78" - elif kodibuild.startswith("14"): - # Helix - dbVersion = "90" - elif kodibuild.startswith("15"): - # Isengard - dbVersion = "93" - elif kodibuild.startswith("16"): - # Jarvis - dbVersion = "99" - else: - # Not a compatible build - xbmc.log("This Kodi version is incompatible. Current version: %s" % kodibuild) + "13": 78, # Gotham + "14": 90, # Helix + "15": 93, # Isengard + "16": 99 # Jarvis + } - dbPath = xbmc.translatePath("special://profile/Database/MyVideos" + dbVersion + ".db") - - return dbPath + dbPath = xbmc.translatePath( + "special://database/MyVideos%s.db" + % dbVersion.get(kodibuild, "")).decode('utf-8') + return dbPath def getKodiMusicDBPath(): - if xbmc.getInfoLabel("System.BuildVersion").startswith("13"): - #gotham - dbVersion = "46" - elif xbmc.getInfoLabel("System.BuildVersion").startswith("14"): - #helix - dbVersion = "48" - elif xbmc.getInfoLabel("System.BuildVersion").startswith("15"): - #isengard - dbVersion = "52" - elif xbmc.getInfoLabel("System.BuildVersion").startswith("16"): - #jarvis - dbVersion = "55" - else: - # Not a compatible build - xbmc.log("This Kodi version is incompatible. Current version: %s" % kodibuild) - - dbPath = xbmc.translatePath("special://profile/Database/MyMusic" + dbVersion + ".db") - - return dbPath + kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2] + dbVersion = { + + "13": 46, # Gotham + "14": 48, # Helix + "15": 52, # Isengard + "16": 55 # Jarvis + } + + dbPath = xbmc.translatePath( + "special://database/MyMusic%s.db" + % dbVersion.get(kodibuild, "")).decode('utf-8') + return dbPath def prettifyXml(elem): rough_string = etree.tostring(elem, "utf-8") diff --git a/resources/lib/WriteKodiMusicDB.py b/resources/lib/WriteKodiMusicDB.py index 2d98b34f..c8570341 100644 --- a/resources/lib/WriteKodiMusicDB.py +++ b/resources/lib/WriteKodiMusicDB.py @@ -27,13 +27,12 @@ class WriteKodiMusicDB(): kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) addonName = ClientInformation().getAddonName() - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - userid = WINDOW.getProperty('userId%s' % username) - server = WINDOW.getProperty('server%s' % username) - - directpath = utils.settings('useDirectPaths') == "true" + def __init__(self): + + username = utils.window('currUser') + self.userid = utils.window('userId%s' % username) + self.server = utils.window('server%s' % username) def logMsg(self, msg, lvl = 1): @@ -221,8 +220,16 @@ class WriteKodiMusicDB(): self.AddGenresToMedia(albumid, genres, "album", cursor) # Update artwork - self.textureCache.addArtwork(artworks, albumid, "album", cursor) + if artworks['Primary']: + self.textureCache.addOrUpdateArt(artworks['Primary'], albumid, "album", "thumb", cursor) + artworks['Primary'] = "" + + if artworks.get('BoxRear'): + self.textureCache.addOrUpdateArt(artworks['BoxRear'], albumid, "album", "poster", cursor) + artworks['BoxRear'] = "" + self.textureCache.addArtwork(artworks, albumid, "album", cursor) + # Link album to artists if MBartists: album_artists = MBitem['AlbumArtists'] @@ -323,7 +330,7 @@ class WriteKodiMusicDB(): # Kodi Gotham and Helix query = "INSERT INTO album(idAlbum, strArtists, strGenres, iYear, dateAdded) values(?, ?, ?, ?, ?)" cursor.execute(query, (albumid, artists, genre, year, dateadded)) - + finally: # Link album to artists for artist in MBitem['ArtistItems']: cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) diff --git a/resources/lib/WriteKodiVideoDB.py b/resources/lib/WriteKodiVideoDB.py index a2d6e081..e0bb557b 100644 --- a/resources/lib/WriteKodiVideoDB.py +++ b/resources/lib/WriteKodiVideoDB.py @@ -27,13 +27,14 @@ class WriteKodiVideoDB(): kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) addonName = ClientInformation().getAddonName() - WINDOW = xbmcgui.Window(10000) + + def __init__(self): - username = WINDOW.getProperty('currUser') - userid = WINDOW.getProperty('userId%s' % username) - server = WINDOW.getProperty('server%s' % username) - - directpath = utils.settings('useDirectPaths') == "true" + username = utils.window('currUser') + self.userid = utils.window('userId%s' % username) + self.server = utils.window('server%s' % username) + + self.directpath = utils.settings('useDirectPaths') == "true" def logMsg(self, msg, lvl = 1): diff --git a/resources/settings.xml b/resources/settings.xml index a0afbfb0..125b4de7 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -36,6 +36,7 @@ + diff --git a/service.py b/service.py index cf6834f1..e845829d 100644 --- a/service.py +++ b/service.py @@ -73,7 +73,7 @@ class Service(): utils.window('minDBVersionCheck', clear=True) # Set min DB version - utils.window('minDBVersion', value="1.1.40") + utils.window('minDBVersion', value="1.1.52") embyProperty = utils.window('Emby.nodes.total') propNames = [