diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 72b660c1..6748debd 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -18,6 +18,7 @@ import embydb_functions as embydb import kodidb_functions as kodidb import read_embyserver as embyserver import musicutils +from objects import Movies, MusicVideos, TVShows, Music from utils import window, settings, language as lang, kodiSQL ################################################################################################# @@ -61,6 +62,7 @@ class Items(object): 'Movie': Movies, 'BoxSet': Movies, + 'MusicVideo': MusicVideos, 'Series': TVShows, 'Season': TVShows, 'Episode': TVShows, @@ -101,102 +103,28 @@ class Items(object): if music_enabled: musicconn = kodiSQL('music') musiccursor = musicconn.cursor() - items_process = itemtypes[itemtype](embycursor, musiccursor) + items_process = itemtypes[itemtype](embycursor, musiccursor, pdialog) else: # Music is not enabled, do not proceed with itemtype continue else: update_videolibrary = True - items_process = itemtypes[itemtype](embycursor, kodicursor) + items_process = itemtypes[itemtype](embycursor, kodicursor, pdialog) - if itemtype == "Movie": - actions = { - 'added': items_process.added, - 'update': items_process.add_update, - 'userdata': items_process.updateUserdata, - 'remove': items_process.remove - } - elif itemtype == "BoxSet": - actions = { - 'added': items_process.added_boxset, - 'update': items_process.add_updateBoxset, - 'remove': items_process.remove - } - elif itemtype == "MusicVideo": - actions = { - 'added': items_process.added, - 'update': items_process.add_update, - 'userdata': items_process.updateUserdata, - 'remove': items_process.remove - } - elif itemtype == "Series": - actions = { - 'added': items_process.added, - 'update': items_process.add_update, - 'userdata': items_process.updateUserdata, - 'remove': items_process.remove - } - elif itemtype == "Season": - actions = { - 'added': items_process.added_season, - 'update': items_process.add_updateSeason, - 'remove': items_process.remove - } - elif itemtype == "Episode": - actions = { - 'added': items_process.added_episode, - 'update': items_process.add_updateEpisode, - 'userdata': items_process.updateUserdata, - 'remove': items_process.remove - } - elif itemtype == "MusicAlbum": - actions = { - 'added': items_process.added_album, - 'update': items_process.add_updateAlbum, - 'userdata': items_process.updateUserdata, - 'remove': items_process.remove - } - elif itemtype in ("MusicArtist", "AlbumArtist"): - actions = { - 'added': items_process.added, - 'update': items_process.add_updateArtist, - 'remove': items_process.remove - } - elif itemtype == "Audio": - actions = { - 'added': items_process.added_song, - 'update': items_process.add_updateSong, - 'userdata': items_process.updateUserdata, - 'remove': items_process.remove - } - else: - log.info("Unsupported itemtype: %s." % itemtype) - actions = {} + ''' if actions.get(process): - if process == "remove": - for item in itemlist: - actions[process](item) + if process == "remove": + for item in itemlist: + actions[process](item)''' - elif process == "added": - actions[process](itemlist, pdialog) - - else: - processItems = emby.getFullItems(itemlist) - for item in processItems: - - title = item['Name'] - - if itemtype == "Episode": - title = "%s - %s" % (item.get('SeriesName', "Unknown"), title) - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - - actions[process](item) + if process == "added": + processItems = itemlist + items_process.add_all(itemtype, itemlist) + else: + processItems = emby.getFullItems(itemlist) + items_process.process_all(itemtype, process, processItems, total) if musicconn is not None: @@ -205,2266 +133,4 @@ class Items(object): musicconn.commit() musiccursor.close() - return (True, update_videolibrary) - - def pathValidation(self, path): - # Verify if direct path is accessible or not - if window('emby_pathverified') != "true" and not xbmcvfs.exists(path): - resp = xbmcgui.Dialog().yesno( - heading=lang(29999), - line1="%s %s. %s" % (lang(33047), path, lang(33048))) - if resp: - window('emby_shouldStop', value="true") - return False - - return True - - def contentPop(self, name, time=5000): - - if time: - # It's possible for the time to be 0. It should be considered disabled in this case. - xbmcgui.Dialog().notification( - heading=lang(29999), - message="%s %s" % (lang(33049), name), - icon="special://home/addons/plugin.video.emby/icon.png", - time=time, - sound=False) - - -class Movies(Items): - - - def __init__(self, embycursor, kodicursor): - Items.__init__(self, embycursor, kodicursor) - - def added(self, items, pdialog): - - total = len(items) - count = 0 - for movie in items: - - title = movie['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - self.add_update(movie) - if not pdialog and self.contentmsg: - self.contentPop(title, self.newvideo_time) - - def added_boxset(self, items, pdialog): - - total = len(items) - count = 0 - for boxset in items: - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=boxset['Name']) - count += 1 - self.add_updateBoxset(boxset) - - - def add_update(self, item, viewtag=None, viewid=None): - # Process single movie - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - movieid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid)) - - except TypeError: - update_item = False - log.debug("movieid: %s not found." % itemid) - # movieid - kodicursor.execute("select coalesce(max(idMovie),0) from movie") - movieid = kodicursor.fetchone()[0] + 1 - - else: - # Verification the item is still in Kodi - query = "SELECT * FROM movie WHERE idMovie = ?" - kodicursor.execute(query, (movieid,)) - try: - kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - log.info("movieid: %s missing from Kodi, repairing the entry." % movieid) - - if not viewtag or not viewid: - # Get view tag from emby - viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - log.debug("View tag found: %s" % viewtag) - - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - people = API.get_people() - writer = " / ".join(people['Writer']) - director = " / ".join(people['Director']) - genres = item['Genres'] - title = item['Name'] - plot = API.get_overview() - shortplot = item.get('ShortOverview') - tagline = API.get_tagline() - votecount = item.get('VoteCount') - rating = item.get('CommunityRating') - year = item.get('ProductionYear') - imdb = API.get_provider('Imdb') - sorttitle = item['SortName'] - runtime = API.get_runtime() - mpaa = API.get_mpaa() - genre = " / ".join(genres) - country = API.get_country() - studios = API.get_studios() - try: - studio = studios[0] - except IndexError: - studio = None - - if item.get('LocalTrailerCount'): - # There's a local trailer - url = ( - "{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json" - % itemid - ) - result = self.doUtils.downloadUrl(url) - try: - trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] - except IndexError: - log.info("Failed to process local trailer.") - trailer = None - else: - # Try to get the youtube trailer - try: - trailer = item['RemoteTrailers'][0]['Url'] - except (KeyError, IndexError): - trailer = None - else: - try: - trailerId = trailer.rsplit('=', 1)[1] - except IndexError: - log.info("Failed to process trailer: %s" % trailer) - trailer = None - else: - trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId - - - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() - - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] - - if self.directpath: - # Direct paths is set the Kodi way - if not self.pathValidation(playurl): - return False - - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") - else: - # Set plugin path and media flags using real filename - path = "plugin://plugin.video.emby.movies/" - params = { - - 'filename': filename.encode('utf-8'), - 'id': itemid, - 'dbid': movieid, - 'mode': "play" - } - filename = "%s?%s" % (path, urllib.urlencode(params)) - - - ##### UPDATE THE MOVIE ##### - if update_item: - log.info("UPDATE movie itemid: %s - Title: %s" % (itemid, title)) - - # Update the movie entry - if self.kodiversion > 16: - query = ' '.join(( - - "UPDATE movie", - "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,", - "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,", - "c16 = ?, c18 = ?, c19 = ?, c21 = ?, premiered = ?", - "WHERE idMovie = ?" - )) - kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, - writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, - trailer, country, year, movieid)) - else: - query = ' '.join(( - - "UPDATE movie", - "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,", - "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,", - "c16 = ?, c18 = ?, c19 = ?, c21 = ?", - "WHERE idMovie = ?" - )) - kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, - writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, - trailer, country, movieid)) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE MOVIE ##### - else: - log.info("ADD movie itemid: %s - Title: %s" % (itemid, title)) - - # Add path - pathid = self.kodi_db.addPath(path) - # Add the file - fileid = self.kodi_db.addFile(filename, pathid) - - # Create the movie entry - if self.kodiversion > 16: - query = ( - ''' - INSERT INTO movie( - idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, - c09, c10, c11, c12, c14, c15, c16, c18, c19, c21, premiered) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, - votecount, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, - director, title, studio, trailer, country, year)) - else: - query = ( - ''' - INSERT INTO movie( - idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, - c09, c10, c11, c12, c14, c15, c16, c18, c19, c21) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, - votecount, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, - director, title, studio, trailer, country)) - - # Create the reference in emby table - emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, checksum, viewid) - - # Update the path - query = ' '.join(( - - "UPDATE path", - "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", - "WHERE idPath = ?" - )) - kodicursor.execute(query, (path, "movies", "metadata.local", 1, pathid)) - - # Update the file - query = ' '.join(( - - "UPDATE files", - "SET idPath = ?, strFilename = ?, dateAdded = ?", - "WHERE idFile = ?" - )) - kodicursor.execute(query, (pathid, filename, dateadded, fileid)) - - # Process countries - if 'ProductionLocations' in item: - self.kodi_db.addCountries(movieid, item['ProductionLocations'], "movie") - # Process cast - people = artwork.get_people_artwork(item['People']) - self.kodi_db.addPeople(movieid, people, "movie") - # Process genres - self.kodi_db.addGenres(movieid, genres, "movie") - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), movieid, "movie", kodicursor) - # Process stream details - streams = API.get_media_streams() - self.kodi_db.addStreams(fileid, streams, runtime) - # Process studios - self.kodi_db.addStudios(movieid, studios, "movie") - # Process tags: view, emby tags - tags = [viewtag] - tags.extend(item['Tags']) - if userdata['Favorite']: - tags.append("Favorite movies") - log.info("Applied tags: %s", tags) - self.kodi_db.addTags(movieid, tags, "movie") - # Process playstates - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - - def add_updateBoxset(self, boxset): - - emby = self.emby - emby_db = self.emby_db - artwork = self.artwork - - boxsetid = boxset['Id'] - title = boxset['Name'] - checksum = boxset['Etag'] - emby_dbitem = emby_db.getItem_byId(boxsetid) - try: - setid = emby_dbitem[0] - - except TypeError: - setid = self.kodi_db.createBoxset(title) - - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(boxset), setid, "set", self.kodicursor) - - # Process movies inside boxset - current_movies = emby_db.getItemId_byParentId(setid, "movie") - process = [] - try: - # Try to convert tuple to dictionary - current = dict(current_movies) - except ValueError: - current = {} - - # Sort current titles - for current_movie in current: - process.append(current_movie) - - # New list to compare - for movie in emby.getMovies_byBoxset(boxsetid)['Items']: - - itemid = movie['Id'] - - if not current.get(itemid): - # Assign boxset to movie - emby_dbitem = emby_db.getItem_byId(itemid) - try: - movieid = emby_dbitem[0] - except TypeError: - log.info("Failed to add: %s to boxset." % movie['Name']) - continue - - log.info("New addition to boxset %s: %s" % (title, movie['Name'])) - self.kodi_db.assignBoxset(setid, movieid) - # Update emby reference - emby_db.updateParentId(itemid, setid) - else: - # Remove from process, because the item still belongs - process.remove(itemid) - - # Process removals from boxset - for movie in process: - movieid = current[movie] - log.info("Remove from boxset %s: %s" % (title, movieid)) - self.kodi_db.removefromBoxset(movieid) - # Update emby reference - emby_db.updateParentId(movie, None) - - # Update the reference in the emby table - emby_db.addReference(boxsetid, setid, "BoxSet", mediatype="set", checksum=checksum) - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - movieid = emby_dbitem[0] - fileid = emby_dbitem[1] - log.info("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid)) - except TypeError: - return - - # Process favorite tags - if userdata['Favorite']: - self.kodi_db.addTag(movieid, "Favorite movies", "movie") - else: - self.kodi_db.removeTag(movieid, "Favorite movies", "movie") - - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - - log.debug("%s New resume point: %s" % (itemid, resume)) - - self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - emby_db.updateReference(itemid, checksum) - - def remove(self, itemid): - # Remove movieid, fileid, emby reference - emby_db = self.emby_db - kodicursor = self.kodicursor - artwork = self.artwork - - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - mediatype = emby_dbitem[4] - log.info("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid)) - except TypeError: - return - - # Remove the emby reference - emby_db.removeItem(itemid) - # Remove artwork - artwork.delete_artwork(kodiid, mediatype, kodicursor) - - if mediatype == "movie": - # Delete kodi movie and file - kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodiid,)) - kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) - - elif mediatype == "set": - # Delete kodi boxset - boxset_movies = emby_db.getItem_byParentId(kodiid, "movie") - for movie in boxset_movies: - embyid = movie[0] - movieid = movie[1] - self.kodi_db.removefromBoxset(movieid) - # Update emby reference - emby_db.updateParentId(embyid, None) - - kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) - - log.info("Deleted %s %s from kodi database" % (mediatype, itemid)) - -class MusicVideos(Items): - - - def __init__(self, embycursor, kodicursor): - Items.__init__(self, embycursor, kodicursor) - - def added(self, items, pdialog): - - total = len(items) - count = 0 - for mvideo in items: - - title = mvideo['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - self.add_update(mvideo) - if not pdialog and self.contentmsg: - self.contentPop(title, self.newvideo_time) - - - def add_update(self, item, viewtag=None, viewid=None): - # Process single music video - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - if item.get('LocationType') == "Virtual": # TODO: Filter via api instead - log.info("Skipping virtual episode: %s", item['Name']) - return - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - mvideoid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid)) - - except TypeError: - update_item = False - log.debug("mvideoid: %s not found." % itemid) - # mvideoid - kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") - mvideoid = kodicursor.fetchone()[0] + 1 - - else: - # Verification the item is still in Kodi - query = "SELECT * FROM musicvideo WHERE idMVideo = ?" - kodicursor.execute(query, (mvideoid,)) - try: - kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - log.info("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid) - - if not viewtag or not viewid: - # Get view tag from emby - viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - log.debug("View tag found: %s" % viewtag) - - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - runtime = API.get_runtime() - plot = API.get_overview() - title = item['Name'] - year = item.get('ProductionYear') - genres = item['Genres'] - genre = " / ".join(genres) - studios = API.get_studios() - studio = " / ".join(studios) - artist = " / ".join(item.get('Artists')) - album = item.get('Album') - track = item.get('Track') - people = API.get_people() - director = " / ".join(people['Director']) - - - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() - - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] - - if self.directpath: - # Direct paths is set the Kodi way - if not self.pathValidation(playurl): - return False - - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") - else: - # Set plugin path and media flags using real filename - path = "plugin://plugin.video.emby.musicvideos/" - params = { - - 'filename': filename.encode('utf-8'), - 'id': itemid, - 'dbid': mvideoid, - 'mode': "play" - } - filename = "%s?%s" % (path, urllib.urlencode(params)) - - - ##### UPDATE THE MUSIC VIDEO ##### - if update_item: - log.info("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title)) - - # Update path - query = "UPDATE path SET strPath = ? WHERE idPath = ?" - kodicursor.execute(query, (path, pathid)) - - # Update the filename - query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?" - kodicursor.execute(query, (filename, dateadded, fileid)) - - # Update the music video entry - query = ' '.join(( - - "UPDATE musicvideo", - "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,", - "c11 = ?, c12 = ?" - "WHERE idMVideo = ?" - )) - kodicursor.execute(query, (title, runtime, director, studio, year, plot, album, - artist, genre, track, mvideoid)) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE MUSIC VIDEO ##### - else: - log.info("ADD mvideo itemid: %s - Title: %s" % (itemid, title)) - - # Add path - query = ' '.join(( - - "SELECT idPath", - "FROM path", - "WHERE strPath = ?" - )) - kodicursor.execute(query, (path,)) - try: - pathid = kodicursor.fetchone()[0] - except TypeError: - kodicursor.execute("select coalesce(max(idPath),0) from path") - pathid = kodicursor.fetchone()[0] + 1 - query = ( - ''' - INSERT OR REPLACE INTO path( - idPath, strPath, strContent, strScraper, noUpdate) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (pathid, path, "musicvideos", "metadata.local", 1)) - - # Add the file - kodicursor.execute("select coalesce(max(idFile),0) from files") - fileid = kodicursor.fetchone()[0] + 1 - query = ( - ''' - INSERT INTO files( - idFile, idPath, strFilename, dateAdded) - - VALUES (?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (fileid, pathid, filename, dateadded)) - - # Create the musicvideo entry - query = ( - ''' - INSERT INTO musicvideo( - idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (mvideoid, fileid, title, runtime, director, studio, - year, plot, album, artist, genre, track)) - - # Create the reference in emby table - emby_db.addReference(itemid, mvideoid, "MusicVideo", "musicvideo", fileid, pathid, - checksum=checksum, mediafolderid=viewid) - - - # Process cast - people = item['People'] - artists = item['ArtistItems'] - for artist in artists: - artist['Type'] = "Artist" - people.extend(artists) - people = artwork.get_people_artwork(people) - self.kodi_db.addPeople(mvideoid, people, "musicvideo") - # Process genres - self.kodi_db.addGenres(mvideoid, genres, "musicvideo") - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), mvideoid, "musicvideo", kodicursor) - # Process stream details - streams = API.get_media_streams() - self.kodi_db.addStreams(fileid, streams, runtime) - # Process studios - self.kodi_db.addStudios(mvideoid, studios, "musicvideo") - # Process tags: view, emby tags - tags = [viewtag] - tags.extend(item['Tags']) - if userdata['Favorite']: - tags.append("Favorite musicvideos") - self.kodi_db.addTags(mvideoid, tags, "musicvideo") - # Process playstates - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - mvideoid = emby_dbitem[0] - fileid = emby_dbitem[1] - log.info( - "Update playstate for musicvideo: %s fileid: %s" - % (item['Name'], fileid)) - except TypeError: - return - - # Process favorite tags - if userdata['Favorite']: - self.kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo") - else: - self.kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo") - - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - - self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - emby_db.updateReference(itemid, checksum) - - def remove(self, itemid): - # Remove mvideoid, fileid, pathid, emby reference - emby_db = self.emby_db - kodicursor = self.kodicursor - artwork = self.artwork - - emby_dbitem = emby_db.getItem_byId(itemid) - try: - mvideoid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid)) - except TypeError: - return - - # Remove artwork - query = ' '.join(( - - "SELECT url, type", - "FROM art", - "WHERE media_id = ?", - "AND media_type = 'musicvideo'" - )) - kodicursor.execute(query, (mvideoid,)) - for row in kodicursor.fetchall(): - - url = row[0] - imagetype = row[1] - if imagetype in ("poster", "fanart"): - artwork.delete_cached_artwork(url) - - kodicursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (mvideoid,)) - kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) - if self.directpath: - kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,)) - self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,)) - - log.info("Deleted musicvideo %s from kodi database" % itemid) - -class TVShows(Items): - - - def __init__(self, embycursor, kodicursor): - Items.__init__(self, embycursor, kodicursor) - - def added(self, items, pdialog): - - total = len(items) - count = 0 - for tvshow in items: - - title = tvshow['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - self.add_update(tvshow) - # Add episodes - all_episodes = self.emby.getEpisodesbyShow(tvshow['Id']) - self.added_episode(all_episodes['Items'], pdialog) - - def added_season(self, items, pdialog): - - total = len(items) - count = 0 - for season in items: - - title = "%s - %s" % (season.get('SeriesName', "Unknown"), season['Name']) - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - self.add_updateSeason(season) - # Add episodes - all_episodes = self.emby.getEpisodesbySeason(season['Id']) - self.added_episode(all_episodes['Items'], pdialog) - - def added_episode(self, items, pdialog): - - total = len(items) - count = 0 - for episode in items: - title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - self.add_updateEpisode(episode) - if not pdialog and self.contentmsg: - self.contentPop(title, self.newvideo_time) - - - def add_update(self, item, viewtag=None, viewid=None): - # Process single tvshow - kodicursor = self.kodicursor - emby = self.emby - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): - log.info("Skipping empty show: %s" % item['Name']) - return - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - force_episodes = False - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - showid = emby_dbitem[0] - pathid = emby_dbitem[2] - log.info("showid: %s pathid: %s" % (showid, pathid)) - - except TypeError: - update_item = False - log.debug("showid: %s not found." % itemid) - kodicursor.execute("select coalesce(max(idShow),0) from tvshow") - showid = kodicursor.fetchone()[0] + 1 - - else: - # Verification the item is still in Kodi - query = "SELECT * FROM tvshow WHERE idShow = ?" - kodicursor.execute(query, (showid,)) - try: - kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - log.info("showid: %s missing from Kodi, repairing the entry." % showid) - # Force re-add episodes after the show is re-created. - force_episodes = True - - - if viewtag is None or viewid is None: - # Get view tag from emby - viewtag, viewid, mediatype = emby.getView_embyId(itemid) - log.debug("View tag found: %s" % viewtag) - - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - genres = item['Genres'] - title = item['Name'] - plot = API.get_overview() - rating = item.get('CommunityRating') - premieredate = API.get_premiere_date() - tvdb = API.get_provider('Tvdb') - sorttitle = item['SortName'] - mpaa = API.get_mpaa() - genre = " / ".join(genres) - studios = API.get_studios() - studio = " / ".join(studios) - - # Verify series pooling - if not update_item and tvdb: - query = "SELECT idShow FROM tvshow WHERE C12 = ?" - kodicursor.execute(query, (tvdb,)) - try: - temp_showid = kodicursor.fetchone()[0] - except TypeError: - pass - else: - emby_other = emby_db.getItem_byKodiId(temp_showid, "tvshow") - if emby_other and viewid == emby_other[2]: - log.info("Applying series pooling for %s", title) - emby_other_item = emby_db.getItem_byId(emby_other[0]) - showid = emby_other_item[0] - pathid = emby_other_item[2] - log.info("showid: %s pathid: %s" % (showid, pathid)) - # Create the reference in emby table - emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, - checksum=checksum, mediafolderid=viewid) - update_item = True - - - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() - - if self.directpath: - # Direct paths is set the Kodi way - if "\\" in playurl: - # Local path - path = "%s\\" % playurl - toplevelpath = "%s\\" % dirname(dirname(path)) - else: - # Network path - path = "%s/" % playurl - toplevelpath = "%s/" % dirname(dirname(path)) - - if not self.pathValidation(path): - return False - - window('emby_pathverified', value="true") - else: - # Set plugin path - toplevelpath = "plugin://plugin.video.emby.tvshows/" - path = "%s%s/" % (toplevelpath, itemid) - - - ##### UPDATE THE TVSHOW ##### - if update_item: - log.info("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title)) - - # Update the tvshow entry - query = ' '.join(( - - "UPDATE tvshow", - "SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,", - "c12 = ?, c13 = ?, c14 = ?, c15 = ?", - "WHERE idShow = ?" - )) - kodicursor.execute(query, (title, plot, rating, premieredate, genre, title, - tvdb, mpaa, studio, sorttitle, showid)) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE TVSHOW ##### - else: - log.info("ADD tvshow itemid: %s - Title: %s" % (itemid, title)) - - # Add top path - toppathid = self.kodi_db.addPath(toplevelpath) - query = ' '.join(( - - "UPDATE path", - "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", - "WHERE idPath = ?" - )) - kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid)) - - # Add path - pathid = self.kodi_db.addPath(path) - - # Create the tvshow entry - query = ( - ''' - INSERT INTO tvshow( - idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (showid, title, plot, rating, premieredate, genre, - title, tvdb, mpaa, studio, sorttitle)) - - # Link the path - query = "INSERT INTO tvshowlinkpath(idShow, idPath) values(?, ?)" - kodicursor.execute(query, (showid, pathid)) - - # Create the reference in emby table - emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, - checksum=checksum, mediafolderid=viewid) - - # Update the path - query = ' '.join(( - - "UPDATE path", - "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", - "WHERE idPath = ?" - )) - kodicursor.execute(query, (path, None, None, 1, pathid)) - - # Process cast - people = artwork.get_people_artwork(item['People']) - self.kodi_db.addPeople(showid, people, "tvshow") - # Process genres - self.kodi_db.addGenres(showid, genres, "tvshow") - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), showid, "tvshow", kodicursor) - # Process studios - self.kodi_db.addStudios(showid, studios, "tvshow") - # Process tags: view, emby tags - tags = [viewtag] - tags.extend(item['Tags']) - if userdata['Favorite']: - tags.append("Favorite tvshows") - self.kodi_db.addTags(showid, tags, "tvshow") - # Process seasons - all_seasons = emby.getSeasons(itemid) - for season in all_seasons['Items']: - self.add_updateSeason(season, showid=showid) - else: - # Finally, refresh the all season entry - seasonid = self.kodi_db.addSeason(showid, -1) - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), seasonid, "season", kodicursor) - - if force_episodes: - # We needed to recreate the show entry. Re-add episodes now. - log.info("Repairing episodes for showid: %s %s" % (showid, title)) - all_episodes = emby.getEpisodesbyShow(itemid) - self.added_episode(all_episodes['Items'], None) - - def add_updateSeason(self, item, showid=None): - - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - - seasonnum = item.get('IndexNumber', 1) - - if showid is None: - try: - seriesId = item['SeriesId'] - showid = emby_db.getItem_byId(seriesId)[0] - except KeyError: - return - except TypeError: - # Show is missing, update show instead. - show = self.emby.getItem(seriesId) - self.add_update(show) - return - - seasonid = self.kodi_db.addSeason(showid, seasonnum, item['Name']) - - if item['LocationType'] != "Virtual": - # Create the reference in emby table - emby_db.addReference(item['Id'], seasonid, "Season", "season", parentid=showid) - - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), seasonid, "season", kodicursor) - - def add_updateEpisode(self, item): - # Process single episode - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - if item.get('LocationType') == "Virtual": # TODO: Filter via api instead - log.info("Skipping virtual episode: %s", item['Name']) - return - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - episodeid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid)) - - except TypeError: - update_item = False - log.debug("episodeid: %s not found." % itemid) - # episodeid - kodicursor.execute("select coalesce(max(idEpisode),0) from episode") - episodeid = kodicursor.fetchone()[0] + 1 - - else: - # Verification the item is still in Kodi - query = "SELECT * FROM episode WHERE idEpisode = ?" - kodicursor.execute(query, (episodeid,)) - try: - kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - log.info("episodeid: %s missing from Kodi, repairing the entry." % episodeid) - - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - people = API.get_people() - writer = " / ".join(people['Writer']) - director = " / ".join(people['Director']) - title = item['Name'] - plot = API.get_overview() - rating = item.get('CommunityRating') - runtime = API.get_runtime() - premieredate = API.get_premiere_date() - - # episode details - try: - seriesId = item['SeriesId'] - except KeyError: - # Missing seriesId, skip - log.error("Skipping: %s. SeriesId is missing." % itemid) - return False - - season = item.get('ParentIndexNumber') - episode = item.get('IndexNumber', -1) - - if season is None: - if item.get('AbsoluteEpisodeNumber'): - # Anime scenario - season = 1 - episode = item['AbsoluteEpisodeNumber'] - else: - season = -1 if "Specials" not in item['Path'] else 0 - - # Specials ordering within season - if item.get('AirsAfterSeasonNumber'): - airsBeforeSeason = item['AirsAfterSeasonNumber'] - airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering - else: - airsBeforeSeason = item.get('AirsBeforeSeasonNumber') - airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber') - - # Append multi episodes to title - if item.get('IndexNumberEnd'): - title = "| %02d | %s" % (item['IndexNumberEnd'], title) - - # Get season id - show = emby_db.getItem_byId(seriesId) - try: - showid = show[0] - except TypeError: - # Show is missing from database - show = self.emby.getItem(seriesId) - self.add_update(show) - show = emby_db.getItem_byId(seriesId) - try: - showid = show[0] - except TypeError: - log.error("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) - return False - - seasonid = self.kodi_db.addSeason(showid, season) - - - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() - - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] - - if self.directpath: - # Direct paths is set the Kodi way - if not self.pathValidation(playurl): - return False - - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") - else: - # Set plugin path and media flags using real filename - path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId - params = { - - 'filename': filename.encode('utf-8'), - 'id': itemid, - 'dbid': episodeid, - 'mode': "play" - } - filename = "%s?%s" % (path, urllib.urlencode(params)) - - - ##### UPDATE THE EPISODE ##### - if update_item: - log.info("UPDATE episode itemid: %s - Title: %s" % (itemid, title)) - - # Update the movie entry - if self.kodiversion in (16, 17): - # Kodi Jarvis, Krypton - query = ' '.join(( - - "UPDATE episode", - "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", - "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?, idShow = ?", - "WHERE idEpisode = ?" - )) - kodicursor.execute(query, (title, plot, rating, writer, premieredate, - runtime, director, season, episode, title, airsBeforeSeason, - airsBeforeEpisode, seasonid, showid, episodeid)) - else: - query = ' '.join(( - - "UPDATE episode", - "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", - "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idShow = ?", - "WHERE idEpisode = ?" - )) - kodicursor.execute(query, (title, plot, rating, writer, premieredate, - runtime, director, season, episode, title, airsBeforeSeason, - airsBeforeEpisode, showid, episodeid)) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - # Update parentid reference - emby_db.updateParentId(itemid, seasonid) - - ##### OR ADD THE EPISODE ##### - else: - log.info("ADD episode itemid: %s - Title: %s" % (itemid, title)) - - # Add path - pathid = self.kodi_db.addPath(path) - # Add the file - fileid = self.kodi_db.addFile(filename, pathid) - - # Create the episode entry - if self.kodiversion in (16, 17): - # Kodi Jarvis, Krypton - query = ( - ''' - INSERT INTO episode( - idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, - idShow, c15, c16, idSeason) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer, - premieredate, runtime, director, season, episode, title, showid, - airsBeforeSeason, airsBeforeEpisode, seasonid)) - else: - query = ( - ''' - INSERT INTO episode( - idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, - idShow, c15, c16) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer, - premieredate, runtime, director, season, episode, title, showid, - airsBeforeSeason, airsBeforeEpisode)) - - # Create the reference in emby table - emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid, - seasonid, checksum) - - # Update the path - query = ' '.join(( - - "UPDATE path", - "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", - "WHERE idPath = ?" - )) - kodicursor.execute(query, (path, None, None, 1, pathid)) - - # Update the file - query = ' '.join(( - - "UPDATE files", - "SET idPath = ?, strFilename = ?, dateAdded = ?", - "WHERE idFile = ?" - )) - kodicursor.execute(query, (pathid, filename, dateadded, fileid)) - - # Process cast - people = artwork.get_people_artwork(item['People']) - self.kodi_db.addPeople(episodeid, people, "episode") - # Process artwork - artworks = artwork.get_all_artwork(item) - artwork.add_update_art(artworks['Primary'], episodeid, "episode", "thumb", kodicursor) - # Process stream details - streams = API.get_media_streams() - self.kodi_db.addStreams(fileid, streams, runtime) - # Process playstates - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - if not self.directpath and resume: - # Create additional entry for widgets. This is only required for plugin/episode. - temppathid = self.kodi_db.getPath("plugin://plugin.video.emby.tvshows/") - tempfileid = self.kodi_db.addFile(filename, temppathid) - query = ' '.join(( - - "UPDATE files", - "SET idPath = ?, strFilename = ?, dateAdded = ?", - "WHERE idFile = ?" - )) - kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) - self.kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed) - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() - dateadded = API.get_date_created() - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - mediatype = emby_dbitem[4] - log.info( - "Update playstate for %s: %s fileid: %s" - % (mediatype, item['Name'], fileid)) - except TypeError: - return - - # Process favorite tags - if mediatype == "tvshow": - if userdata['Favorite']: - self.kodi_db.addTag(kodiid, "Favorite tvshows", "tvshow") - else: - self.kodi_db.removeTag(kodiid, "Favorite tvshows", "tvshow") - elif mediatype == "episode": - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - - log.debug("%s New resume point: %s" % (itemid, resume)) - - self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - if not self.directpath and not resume: - # Make sure there's no other bookmarks created by widget. - filename = self.kodi_db.getFile(fileid) - self.kodi_db.removeFile("plugin://plugin.video.emby.tvshows/", filename) - - if not self.directpath and resume: - # Create additional entry for widgets. This is only required for plugin/episode. - filename = self.kodi_db.getFile(fileid) - temppathid = self.kodi_db.getPath("plugin://plugin.video.emby.tvshows/") - tempfileid = self.kodi_db.addFile(filename, temppathid) - query = ' '.join(( - - "UPDATE files", - "SET idPath = ?, strFilename = ?, dateAdded = ?", - "WHERE idFile = ?" - )) - self.kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) - self.kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed) - - emby_db.updateReference(itemid, checksum) - - def remove(self, itemid): - # Remove showid, fileid, pathid, emby reference - emby_db = self.emby_db - embycursor = self.embycursor - kodicursor = self.kodicursor - artwork = self.artwork - - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - parentid = emby_dbitem[3] - mediatype = emby_dbitem[4] - log.info("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid)) - except TypeError: - return - - ##### PROCESS ITEM ##### - - # Remove the emby reference - emby_db.removeItem(itemid) - - - ##### IF EPISODE ##### - - if mediatype == "episode": - # Delete kodi episode and file, verify season and tvshow - self.removeEpisode(kodiid, fileid) - - # Season verification - season = emby_db.getItem_byKodiId(parentid, "season") - try: - showid = season[1] - except TypeError: - return - - season_episodes = emby_db.getItem_byParentId(parentid, "episode") - if not season_episodes: - self.removeSeason(parentid) - emby_db.removeItem(season[0]) - - # Show verification - show = emby_db.getItem_byKodiId(showid, "tvshow") - query = ' '.join(( - - "SELECT totalCount", - "FROM tvshowcounts", - "WHERE idShow = ?" - )) - kodicursor.execute(query, (showid,)) - result = kodicursor.fetchone() - if result and result[0] is None: - # There's no episodes left, delete show and any possible remaining seasons - seasons = emby_db.getItem_byParentId(showid, "season") - for season in seasons: - self.removeSeason(season[1]) - else: - # Delete emby season entries - emby_db.removeItems_byParentId(showid, "season") - self.removeShow(showid) - emby_db.removeItem(show[0]) - - ##### IF TVSHOW ##### - - elif mediatype == "tvshow": - # Remove episodes, seasons, tvshow - seasons = emby_db.getItem_byParentId(kodiid, "season") - for season in seasons: - seasonid = season[1] - season_episodes = emby_db.getItem_byParentId(seasonid, "episode") - for episode in season_episodes: - self.removeEpisode(episode[1], episode[2]) - else: - # Remove emby episodes - emby_db.removeItems_byParentId(seasonid, "episode") - else: - # Remove emby seasons - emby_db.removeItems_byParentId(kodiid, "season") - - # Remove tvshow - self.removeShow(kodiid) - - ##### IF SEASON ##### - - elif mediatype == "season": - # Remove episodes, season, verify tvshow - season_episodes = emby_db.getItem_byParentId(kodiid, "episode") - for episode in season_episodes: - self.removeEpisode(episode[1], episode[2]) - else: - # Remove emby episodes - emby_db.removeItems_byParentId(kodiid, "episode") - - # Remove season - self.removeSeason(kodiid) - - # Show verification - seasons = emby_db.getItem_byParentId(parentid, "season") - if not seasons: - # There's no seasons, delete the show - self.removeShow(parentid) - emby_db.removeItem_byKodiId(parentid, "tvshow") - - log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) - - def removeShow(self, kodiid): - - kodicursor = self.kodicursor - self.artwork.delete_artwork(kodiid, "tvshow", kodicursor) - kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,)) - log.debug("Removed tvshow: %s." % kodiid) - - def removeSeason(self, kodiid): - - kodicursor = self.kodicursor - - self.artwork.delete_artwork(kodiid, "season", kodicursor) - kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) - log.debug("Removed season: %s." % kodiid) - - def removeEpisode(self, kodiid, fileid): - - kodicursor = self.kodicursor - - self.artwork.delete_artwork(kodiid, "episode", kodicursor) - kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,)) - kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) - log.debug("Removed episode: %s." % kodiid) - -class Music(Items): - - - def __init__(self, embycursor, musiccursor): - - Items.__init__(self, embycursor, musiccursor) - - self.directstream = settings('streamMusic') == "true" - self.enableimportsongrating = settings('enableImportSongRating') == "true" - self.enableexportsongrating = settings('enableExportSongRating') == "true" - self.enableupdatesongrating = settings('enableUpdateSongRating') == "true" - self.userid = window('emby_currUser') - self.server = window('emby_server%s' % self.userid) - - def added(self, items, pdialog): - - total = len(items) - count = 0 - for artist in items: - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=artist['Name']) - count += 1 - self.add_updateArtist(artist) - # Add albums - all_albums = self.emby.getAlbumsbyArtist(artist['Id']) - self.added_album(all_albums['Items'], pdialog) - - def added_album(self, items, pdialog): - - total = len(items) - count = 0 - for album in items: - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=album['Name']) - count += 1 - self.add_updateAlbum(album) - # Add songs - all_songs = self.emby.getSongsbyAlbum(album['Id']) - self.added_song(all_songs['Items'], pdialog) - - def added_song(self, items, pdialog): - - total = len(items) - count = 0 - for song in items: - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=song['Name']) - count += 1 - self.add_updateSong(song) - if not pdialog and self.contentmsg: - self.contentPop(song['Name'], self.newmusic_time) - - def add_updateArtist(self, item, artisttype="MusicArtist"): - # Process a single artist - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - artistid = emby_dbitem[0] - except TypeError: - update_item = False - log.debug("artistid: %s not found." % itemid) - - ##### The artist details ##### - lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = API.get_date_created() - checksum = API.get_checksum() - - name = item['Name'] - musicBrainzId = API.get_provider('MusicBrainzArtist') - genres = " / ".join(item.get('Genres')) - bio = API.get_overview() - - # Associate artwork - artworks = artwork.get_all_artwork(item, parent_info=True) - thumb = artworks['Primary'] - backdrops = artworks['Backdrop'] # List - - if thumb: - thumb = "<thumb>%s</thumb>" % thumb - if backdrops: - fanart = "<fanart>%s</fanart>" % backdrops[0] - else: - fanart = "" - - - ##### UPDATE THE ARTIST ##### - if update_item: - log.info("UPDATE artist itemid: %s - Name: %s" % (itemid, name)) - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE ARTIST ##### - else: - log.info("ADD artist itemid: %s - Name: %s" % (itemid, name)) - # safety checks: It looks like Emby supports the same artist multiple times. - # Kodi doesn't allow that. In case that happens we just merge the artist entries. - artistid = self.kodi_db.addArtist(name, musicBrainzId) - # Create the reference in emby table - emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum) - - - # Process the artist - if self.kodiversion in (16, 17): - query = ' '.join(( - - "UPDATE artist", - "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", - "lastScraped = ?", - "WHERE idArtist = ?" - )) - kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, artistid)) - else: - query = ' '.join(( - - "UPDATE artist", - "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", - "lastScraped = ?, dateAdded = ?", - "WHERE idArtist = ?" - )) - kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, - dateadded, artistid)) - - - # Update artwork - artwork.add_artwork(artworks, artistid, "artist", kodicursor) - - def add_updateAlbum(self, item): - # Process a single artist - emby = self.emby - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - albumid = emby_dbitem[0] - except TypeError: - update_item = False - log.debug("albumid: %s not found." % itemid) - - ##### The album details ##### - lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = API.get_date_created() - userdata = API.get_userdata() - checksum = API.get_checksum() - - name = item['Name'] - musicBrainzId = API.get_provider('MusicBrainzAlbum') - year = item.get('ProductionYear') - genres = item.get('Genres') - genre = " / ".join(genres) - bio = API.get_overview() - rating = userdata['UserRating'] - artists = item['AlbumArtists'] - if not artists: - artists = item['ArtistItems'] - artistname = [] - for artist in artists: - artistname.append(artist['Name']) - artistname = " / ".join(artistname) - - # Associate artwork - artworks = artwork.get_all_artwork(item, parent_info=True) - thumb = artworks['Primary'] - if thumb: - thumb = "<thumb>%s</thumb>" % thumb - - ##### UPDATE THE ALBUM ##### - if update_item: - log.info("UPDATE album itemid: %s - Name: %s" % (itemid, name)) - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE ALBUM ##### - else: - log.info("ADD album itemid: %s - Name: %s" % (itemid, name)) - # safety checks: It looks like Emby supports the same artist multiple times. - # Kodi doesn't allow that. In case that happens we just merge the artist entries. - albumid = self.kodi_db.addAlbum(name, musicBrainzId) - # Create the reference in emby table - emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum) - - - # Process the album info - if self.kodiversion == 17: - # Kodi Krypton - query = ' '.join(( - - "UPDATE album", - "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", - "iUserrating = ?, lastScraped = ?, strReleaseType = ?", - "WHERE idAlbum = ?" - )) - kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, - "album", albumid)) - elif self.kodiversion == 16: - # Kodi Jarvis - query = ' '.join(( - - "UPDATE album", - "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", - "iRating = ?, lastScraped = ?, strReleaseType = ?", - "WHERE idAlbum = ?" - )) - kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, - "album", albumid)) - elif self.kodiversion == 15: - # Kodi Isengard - query = ' '.join(( - - "UPDATE album", - "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", - "iRating = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ?", - "WHERE idAlbum = ?" - )) - kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, - dateadded, "album", albumid)) - else: - # Kodi Helix - query = ' '.join(( - - "UPDATE album", - "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", - "iRating = ?, lastScraped = ?, dateAdded = ?", - "WHERE idAlbum = ?" - )) - kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, - dateadded, albumid)) - - # Associate the parentid for emby reference - parentId = item.get('ParentId') - if parentId is not None: - emby_dbartist = emby_db.getItem_byId(parentId) - try: - artistid = emby_dbartist[0] - except TypeError: - # Artist does not exist in emby database. - artist = emby.getItem(parentId) - # Item may not be an artist, verification necessary. - if artist.get('Type') == "MusicArtist": - # Update with the parentId, for remove reference - emby_db.addReference(parentId, parentId, "MusicArtist", "artist") - emby_db.updateParentId(itemid, parentId) - else: - # Update emby reference with the artistid - emby_db.updateParentId(itemid, artistid) - - # Assign main artists to album - for artist in artists: - artistname = artist['Name'] - artistId = artist['Id'] - emby_dbartist = emby_db.getItem_byId(artistId) - try: - artistid = emby_dbartist[0] - except TypeError: - # Artist does not exist in emby database, create the reference - artist = emby.getItem(artistId) - self.add_updateArtist(artist, artisttype="AlbumArtist") - emby_dbartist = emby_db.getItem_byId(artistId) - artistid = emby_dbartist[0] - else: - # Best take this name over anything else. - query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?" - kodicursor.execute(query, (artistname, artistid,)) - - # Add artist to album - query = ( - ''' - INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) - - VALUES (?, ?, ?) - ''' - ) - kodicursor.execute(query, (artistid, albumid, artistname)) - # Update discography - query = ( - ''' - INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) - - VALUES (?, ?, ?) - ''' - ) - kodicursor.execute(query, (artistid, name, year)) - # Update emby reference with parentid - emby_db.updateParentId(artistId, albumid) - - # Add genres - self.kodi_db.addMusicGenres(albumid, genres, "album") - # Update artwork - artwork.add_artwork(artworks, albumid, "album", kodicursor) - - def add_updateSong(self, item): - # Process single song - kodicursor = self.kodicursor - emby = self.emby - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - songid = emby_dbitem[0] - pathid = emby_dbitem[2] - albumid = emby_dbitem[3] - except TypeError: - update_item = False - log.debug("songid: %s not found." % itemid) - - ##### The song details ##### - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - title = item['Name'] - musicBrainzId = API.get_provider('MusicBrainzTrackId') - genres = item.get('Genres') - genre = " / ".join(genres) - artists = " / ".join(item['Artists']) - tracknumber = item.get('IndexNumber', 0) - disc = item.get('ParentIndexNumber', 1) - if disc == 1: - track = tracknumber - else: - track = disc*2**16 + tracknumber - year = item.get('ProductionYear') - duration = API.get_runtime() - rating = userdata['UserRating'] - - #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.get_overview() - - - ##### GET THE FILE AND PATH ##### - if self.directstream: - path = "%s/emby/Audio/%s/" % (self.server, itemid) - extensions = ['mp3', 'aac', 'ogg', 'oga', 'webma', 'wma', 'flac'] - - if 'Container' in item and item['Container'].lower() in extensions: - filename = "stream.%s?static=true" % item['Container'] - else: - filename = "stream.mp3?static=true" - else: - playurl = API.get_file_path() - - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] - - # Direct paths is set the Kodi way - if not self.pathValidation(playurl): - return False - - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") - - ##### UPDATE THE SONG ##### - if update_item: - log.info("UPDATE song itemid: %s - Title: %s" % (itemid, title)) - - # Update path - query = "UPDATE path SET strPath = ? WHERE idPath = ?" - kodicursor.execute(query, (path, pathid)) - - # Update the song entry - query = ' '.join(( - - "UPDATE song", - "SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,", - "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,", - "rating = ?, comment = ?", - "WHERE idSong = ?" - )) - kodicursor.execute(query, (albumid, artists, genre, title, track, duration, year, - filename, playcount, dateplayed, rating, comment, songid)) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE SONG ##### - else: - log.info("ADD song itemid: %s - Title: %s" % (itemid, title)) - - # Add path - pathid = self.kodi_db.addPath(path) - - try: - # Get the album - emby_dbalbum = emby_db.getItem_byId(item['AlbumId']) - albumid = emby_dbalbum[0] - except KeyError: - # Verify if there's an album associated. - album_name = item.get('Album') - if album_name: - log.info("Creating virtual music album for song: %s." % itemid) - albumid = self.kodi_db.addAlbum(album_name, API.get_provider('MusicBrainzAlbum')) - emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album") - else: - # No album Id associated to the song. - log.error("Song itemid: %s has no albumId associated." % itemid) - return False - - except TypeError: - # No album found. Let's create it - log.info("Album database entry missing.") - emby_albumId = item['AlbumId'] - album = emby.getItem(emby_albumId) - self.add_updateAlbum(album) - emby_dbalbum = emby_db.getItem_byId(emby_albumId) - try: - albumid = emby_dbalbum[0] - log.info("Found albumid: %s" % albumid) - except TypeError: - # No album found, create a single's album - log.info("Failed to add album. Creating singles.") - kodicursor.execute("select coalesce(max(idAlbum),0) from album") - albumid = kodicursor.fetchone()[0] + 1 - if self.kodiversion == 16: - # Kodi Jarvis - query = ( - ''' - INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) - - VALUES (?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (albumid, genre, year, "single")) - elif self.kodiversion == 15: - # Kodi Isengard - query = ( - ''' - INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (albumid, genre, year, dateadded, "single")) - else: - # Kodi Helix - query = ( - ''' - INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) - - VALUES (?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (albumid, genre, year, dateadded)) - - # Create the song entry - kodicursor.execute("select coalesce(max(idSong),0) from song") - songid = kodicursor.fetchone()[0] + 1 - query = ( - ''' - INSERT INTO song( - idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, - iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, - rating) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (songid, albumid, pathid, artists, genre, title, track, - duration, year, filename, musicBrainzId, playcount, dateplayed, rating)) - - # Create the reference in emby table - emby_db.addReference(itemid, songid, "Audio", "song", pathid=pathid, parentid=albumid, - checksum=checksum) - - - # Link song to album - query = ( - ''' - INSERT OR REPLACE INTO albuminfosong( - idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (songid, albumid, track, title, duration)) - - # Link song to artists - for index, artist in enumerate(item['ArtistItems']): - - artist_name = artist['Name'] - artist_eid = artist['Id'] - artist_edb = emby_db.getItem_byId(artist_eid) - try: - artistid = artist_edb[0] - except TypeError: - # Artist is missing from emby database, add it. - artist_full = emby.getItem(artist_eid) - self.add_updateArtist(artist_full) - artist_edb = emby_db.getItem_byId(artist_eid) - artistid = artist_edb[0] - finally: - if self.kodiversion >= 17: - # Kodi Krypton - query = ( - ''' - INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (artistid, songid, 1, index, artist_name)) - - # May want to look into only doing this once? - query = ( - ''' - INSERT OR REPLACE INTO role(idRole, strRole) - - VALUES (?, ?) - ''' - ) - kodicursor.execute(query, (1, 'Composer')) - else: - query = ( - ''' - INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist) - - VALUES (?, ?, ?, ?) - ''' - ) - kodicursor.execute(query, (artistid, songid, index, artist_name)) - - # Verify if album artist exists - album_artists = [] - for artist in item['AlbumArtists']: - - artist_name = artist['Name'] - album_artists.append(artist_name) - artist_eid = artist['Id'] - artist_edb = emby_db.getItem_byId(artist_eid) - try: - artistid = artist_edb[0] - except TypeError: - # Artist is missing from emby database, add it. - artist_full = emby.getItem(artist_eid) - self.add_updateArtist(artist_full) - artist_edb = emby_db.getItem_byId(artist_eid) - artistid = artist_edb[0] - finally: - query = ( - ''' - INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) - - VALUES (?, ?, ?) - ''' - ) - kodicursor.execute(query, (artistid, albumid, artist_name)) - # Update discography - if item.get('Album'): - query = ( - ''' - INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) - - VALUES (?, ?, ?) - ''' - ) - kodicursor.execute(query, (artistid, item['Album'], 0)) - else: - album_artists = " / ".join(album_artists) - query = ' '.join(( - - "SELECT strArtists", - "FROM album", - "WHERE idAlbum = ?" - )) - kodicursor.execute(query, (albumid,)) - result = kodicursor.fetchone() - if result and result[0] != album_artists: - # Field is empty - if self.kodiversion in (16, 17): - # Kodi Jarvis, Krypton - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - kodicursor.execute(query, (album_artists, albumid)) - elif self.kodiversion == 15: - # Kodi Isengard - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - kodicursor.execute(query, (album_artists, albumid)) - else: - # Kodi Helix - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - kodicursor.execute(query, (album_artists, albumid)) - - # Add genres - self.kodi_db.addMusicGenres(songid, genres, "song") - - # Update artwork - allart = artwork.get_all_artwork(item, parent_info=True) - if hasEmbeddedCover: - allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl ) - artwork.add_artwork(allart, songid, "song", kodicursor) - - if item.get('AlbumId') is None: - # Update album artwork - artwork.add_artwork(allart, albumid, "album", kodicursor) - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - kodicursor = self.kodicursor - emby_db = self.emby_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() - rating = userdata['UserRating'] - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - mediatype = emby_dbitem[4] - log.info("Update playstate for %s: %s" % (mediatype, item['Name'])) - except TypeError: - return - - if mediatype == "song": - - #should we ignore this item ? - #happens when userdata updated by ratings method - if window("ignore-update-%s" %itemid): - window("ignore-update-%s" %itemid,clear=True) - return - - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - #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)) - - elif mediatype == "album": - # Process playstates - if self.kodiversion >= 17: - query = "UPDATE album SET fRating = ? WHERE idAlbum = ?" - else: - query = "UPDATE album SET iRating = ? WHERE idAlbum = ?" - kodicursor.execute(query, (rating, kodiid)) - - emby_db.updateReference(itemid, checksum) - - def remove(self, itemid): - # Remove kodiid, fileid, pathid, emby reference - emby_db = self.emby_db - kodicursor = self.kodicursor - artwork = self.artwork - - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - mediatype = emby_dbitem[4] - log.info("Removing %s kodiid: %s" % (mediatype, kodiid)) - except TypeError: - return - - ##### PROCESS ITEM ##### - - # Remove the emby reference - emby_db.removeItem(itemid) - - - ##### IF SONG ##### - - if mediatype == "song": - # Delete song - self.removeSong(kodiid) - # This should only address single song scenario, where server doesn't actually - # create an album for the song. - emby_db.removeWildItem(itemid) - - for item in emby_db.getItem_byWildId(itemid): - - item_kid = item[0] - item_mediatype = item[1] - - if item_mediatype == "album": - childs = emby_db.getItem_byParentId(item_kid, "song") - if not childs: - # Delete album - self.removeAlbum(item_kid) - - ##### IF ALBUM ##### - - elif mediatype == "album": - # Delete songs, album - album_songs = emby_db.getItem_byParentId(kodiid, "song") - for song in album_songs: - self.removeSong(song[1]) - else: - # Remove emby songs - emby_db.removeItems_byParentId(kodiid, "song") - - # Remove the album - self.removeAlbum(kodiid) - - ##### IF ARTIST ##### - - elif mediatype == "artist": - # Delete songs, album, artist - albums = emby_db.getItem_byParentId(kodiid, "album") - for album in albums: - albumid = album[1] - album_songs = emby_db.getItem_byParentId(albumid, "song") - for song in album_songs: - self.removeSong(song[1]) - else: - # Remove emby song - emby_db.removeItems_byParentId(albumid, "song") - # Remove emby artist - emby_db.removeItems_byParentId(albumid, "artist") - # Remove kodi album - self.removeAlbum(albumid) - else: - # Remove emby albums - emby_db.removeItems_byParentId(kodiid, "album") - - # Remove artist - self.removeArtist(kodiid) - - log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) - - def removeSong(self, kodiId): - - kodicursor = self.kodicursor - - self.artwork.delete_artwork(kodiId, "song", self.kodicursor) - self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiId,)) - - def removeAlbum(self, kodiId): - - self.artwork.delete_artwork(kodiId, "album", self.kodicursor) - self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiId,)) - - def removeArtist(self, kodiId): - - self.artwork.delete_artwork(kodiId, "artist", self.kodicursor) - self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiId,)) \ No newline at end of file + return (True, update_videolibrary) \ No newline at end of file diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 5d8857f1..19aaec77 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -21,7 +21,8 @@ import kodidb_functions as kodidb import read_embyserver as embyserver import userclient import videonodes -from utils import window, settings, language as lang +from objects import Movies, MusicVideos, TVShows, Music +from utils import window, settings, language as lang, should_stop from ga_client import GoogleAnalytics ################################################################################################## @@ -186,15 +187,6 @@ class LibrarySync(threading.Thread): finally: settings('LastIncrementalSync', value=lastSync) - def shouldStop(self): - # Checkpoint during the syncing process - if self.monitor.abortRequested(): - return True - elif window('emby_shouldStop') == "true": - return True - else: # Keep going - return False - def dbCommit(self, connection): # Central commit, verifies if Kodi database update is running kodidb_scan = window('emby_kodiScan') == "true" @@ -209,7 +201,7 @@ class LibrarySync(threading.Thread): log.info("Flag still active, but will try to commit") window('emby_kodiScan', clear=True) - if self.shouldStop(): + if should_stop(): log.info("Commit unsuccessful. Sync terminated.") break @@ -618,7 +610,7 @@ class LibrarySync(threading.Thread): # Get movies from emby emby_db = embydb.Embydb_Functions(embycursor) - movies = itemtypes.Movies(embycursor, kodicursor) + movies = Movies(embycursor, kodicursor, pdialog) views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') @@ -628,39 +620,18 @@ class LibrarySync(threading.Thread): for view in views: log.info("Processing: %s", view) - - if self.shouldStop(): - return False + view_name = view['name'] # Get items per view if pdialog: pdialog.update( heading=lang(29999), - message="%s %s..." % (lang(33017), view['name'])) + message="%s %s..." % (lang(33017), view_name)) - # Initial or repair sync - all_embymovies = self.emby.getMovies(view['id'], dialog=pdialog) - total = all_embymovies['TotalRecordCount'] - embymovies = all_embymovies['Items'] - - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (view['name'], total)) - - count = 0 - for embymovie in embymovies: - # Process individual movies - if self.shouldStop(): - return False - - title = embymovie['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - movies.add_update(embymovie, view['name'], view['id']) - else: - log.debug("Movies finished.") + all_movies = self.emby.getMovies(view['id'], dialog=pdialog) + movies.add_all("Movie", all_movies, view) + log.debug("Movies finished.") ##### PROCESS BOXSETS ##### if pdialog: @@ -668,25 +639,9 @@ class LibrarySync(threading.Thread): boxsets = self.emby.getBoxset(dialog=pdialog) total = boxsets['TotalRecordCount'] - embyboxsets = boxsets['Items'] - if pdialog: - pdialog.update(heading="Processing Boxsets / %s items" % total) - - count = 0 - for boxset in embyboxsets: - # Process individual boxset - if self.shouldStop(): - return False - - title = boxset['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - movies.add_updateBoxset(boxset) - else: - log.debug("Boxsets finished.") + movies.process_all("BoxSet", "added", boxsets['Items'], total) + log.debug("Boxsets finished.") return True @@ -694,15 +649,13 @@ class LibrarySync(threading.Thread): # Get musicvideos from emby emby_db = embydb.Embydb_Functions(embycursor) - mvideos = itemtypes.MusicVideos(embycursor, kodicursor) + mvideos = MusicVideos(embycursor, kodicursor, pdialog) views = emby_db.getView_byType('musicvideos') log.info("Media folders: %s" % views) for view in views: - - if self.shouldStop(): - return False + log.info("Processing: %s", view) # Get items per view viewId = view['id'] @@ -714,25 +667,9 @@ class LibrarySync(threading.Thread): message="%s %s..." % (lang(33019), viewName)) # Initial or repair sync - all_embymvideos = self.emby.getMusicVideos(viewId, dialog=pdialog) - total = all_embymvideos['TotalRecordCount'] - embymvideos = all_embymvideos['Items'] + all_mvideos = self.emby.getMusicVideos(viewId, dialog=pdialog) + mvideos.add_all("MusicVideo", all_mvideos, view) - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (viewName, total)) - - count = 0 - for embymvideo in embymvideos: - # Process individual musicvideo - if self.shouldStop(): - return False - - title = embymvideo['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - mvideos.add_update(embymvideo, viewName, viewId) else: log.debug("MusicVideos finished.") @@ -742,7 +679,7 @@ class LibrarySync(threading.Thread): # Get shows from emby emby_db = embydb.Embydb_Functions(embycursor) - tvshows = itemtypes.TVShows(embycursor, kodicursor) + tvshows = TVShows(embycursor, kodicursor, pdialog) views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') @@ -750,47 +687,15 @@ class LibrarySync(threading.Thread): for view in views: - if self.shouldStop(): - return False - # Get items per view if pdialog: pdialog.update( heading=lang(29999), message="%s %s..." % (lang(33020), view['name'])) - all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog) - total = all_embytvshows['TotalRecordCount'] - embytvshows = all_embytvshows['Items'] + all_tvshows = self.emby.getShows(view['id'], dialog=pdialog) + tvshows.add_all("Series", all_tvshows, view) - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (view['name'], total)) - - count = 0 - for embytvshow in embytvshows: - # Process individual show - if self.shouldStop(): - return False - - title = embytvshow['Name'] - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - tvshows.add_update(embytvshow, view['name'], view['id']) - - # Process episodes - all_episodes = self.emby.getEpisodesbyShow(embytvshow['Id']) - for episode in all_episodes['Items']: - - # Process individual show - if self.shouldStop(): - return False - - episodetitle = episode['Name'] - if pdialog: - pdialog.update(percentage, message="%s - %s" % (title, episodetitle)) - tvshows.add_updateEpisode(episode) else: log.debug("TVShows finished.") @@ -799,41 +704,21 @@ class LibrarySync(threading.Thread): def music(self, embycursor, kodicursor, pdialog): # Get music from emby emby_db = embydb.Embydb_Functions(embycursor) - music = itemtypes.Music(embycursor, kodicursor) + music = Music(embycursor, kodicursor, pdialog) - process = { + views = emby_db.getView_byType('music') + log.info("Media folders: %s", views) - 'artists': [self.emby.getArtists, music.add_updateArtist], - 'albums': [self.emby.getAlbums, music.add_updateAlbum], - 'songs': [self.emby.getSongs, music.add_updateSong] - } - for itemtype in ['artists', 'albums', 'songs']: + # Add music artists and everything will fall into place + if pdialog: + pdialog.update(heading=lang(29999), + message="%s Music..." % lang(33021)) - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33021), itemtype)) + for view in views: + all_artists = self.emby.getArtists(view['id'], dialog=pdialog) + music.add_all("MusicArtist", all_artists) - all_embyitems = process[itemtype][0](dialog=pdialog) - total = all_embyitems['TotalRecordCount'] - embyitems = all_embyitems['Items'] - - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (itemtype, total)) - - count = 0 - for embyitem in embyitems: - # Process individual item - if self.shouldStop(): - return False - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=embyitem['Name']) - count += 1 - - process[itemtype][1](embyitem) - else: - log.debug("%s finished." % itemtype) + log.debug("Finished syncing music") return True @@ -1123,447 +1008,13 @@ class ManualSync(LibrarySync): def movies(self, embycursor, kodicursor, pdialog): - - # Get movies from emby - emby_db = embydb.Embydb_Functions(embycursor) - movies = itemtypes.Movies(embycursor, kodicursor) - - views = emby_db.getView_byType('movies') - views += emby_db.getView_byType('mixed') - log.info("Media folders: %s" % views) - - # Pull the list of movies and boxsets in Kodi - try: - all_kodimovies = dict(emby_db.get_checksum('Movie')) - except ValueError: - all_kodimovies = {} - - try: - all_kodisets = dict(emby_db.get_checksum('BoxSet')) - except ValueError: - all_kodisets = {} - - all_embymoviesIds = set() - all_embyboxsetsIds = set() - updatelist = [] - - ##### PROCESS MOVIES ##### - for view in views: - - if self.shouldStop(): - return False - - # Get items per view - viewId = view['id'] - viewName = view['name'] - - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33026), viewName)) - - all_embymovies = self.emby.getMovies(viewId, basic=True, dialog=pdialog) - for embymovie in all_embymovies['Items']: - - if self.shouldStop(): - return False - - API = api.API(embymovie) - itemid = embymovie['Id'] - all_embymoviesIds.add(itemid) - - - if all_kodimovies.get(itemid) != API.get_checksum(): - # Only update if movie is not in Kodi or checksum is different - updatelist.append(itemid) - - log.info("Movies to update for %s: %s" % (viewName, updatelist)) - embymovies = self.emby.getFullItems(updatelist) - total = len(updatelist) - del updatelist[:] - - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (viewName, total)) - - count = 0 - for embymovie in embymovies: - # Process individual movies - if self.shouldStop(): - return False - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=embymovie['Name']) - count += 1 - movies.add_update(embymovie, viewName, viewId) - - ##### PROCESS BOXSETS ##### - - boxsets = self.emby.getBoxset(dialog=pdialog) - embyboxsets = [] - - if pdialog: - pdialog.update(heading=lang(29999), message=lang(33027)) - - for boxset in boxsets['Items']: - - if self.shouldStop(): - return False - - # Boxset has no real userdata, so using etag to compare - itemid = boxset['Id'] - all_embyboxsetsIds.add(itemid) - - if all_kodisets.get(itemid) != boxset['Etag']: - # Only update if boxset is not in Kodi or boxset['Etag'] is different - updatelist.append(itemid) - embyboxsets.append(boxset) - - log.info("Boxsets to update: %s" % updatelist) - total = len(updatelist) - - if pdialog: - pdialog.update(heading="Processing Boxsets / %s items" % total) - - count = 0 - for boxset in embyboxsets: - # Process individual boxset - if self.shouldStop(): - return False - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=boxset['Name']) - count += 1 - movies.add_updateBoxset(boxset) - - ##### PROCESS DELETES ##### - - for kodimovie in all_kodimovies: - if kodimovie not in all_embymoviesIds: - movies.remove(kodimovie) - else: - log.info("Movies compare finished.") - - for boxset in all_kodisets: - if boxset not in all_embyboxsetsIds: - movies.remove(boxset) - else: - log.info("Boxsets compare finished.") - - return True + return Movies(embycursor, kodicursor, pdialog).compare_all() def musicvideos(self, embycursor, kodicursor, pdialog): - - # Get musicvideos from emby - emby_db = embydb.Embydb_Functions(embycursor) - mvideos = itemtypes.MusicVideos(embycursor, kodicursor) - - views = emby_db.getView_byType('musicvideos') - log.info("Media folders: %s" % views) - - # Pull the list of musicvideos in Kodi - try: - all_kodimvideos = dict(emby_db.get_checksum('MusicVideo')) - except ValueError: - all_kodimvideos = {} - - all_embymvideosIds = set() - updatelist = [] - - for view in views: - - if self.shouldStop(): - return False - - # Get items per view - viewId = view['id'] - viewName = view['name'] - - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33028), viewName)) - - all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog) - for embymvideo in all_embymvideos['Items']: - - if self.shouldStop(): - return False - - API = api.API(embymvideo) - itemid = embymvideo['Id'] - all_embymvideosIds.add(itemid) - - - if all_kodimvideos.get(itemid) != API.get_checksum(): - # Only update if musicvideo is not in Kodi or checksum is different - updatelist.append(itemid) - - log.info("MusicVideos to update for %s: %s" % (viewName, updatelist)) - embymvideos = self.emby.getFullItems(updatelist) - total = len(updatelist) - del updatelist[:] - - - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (viewName, total)) - - count = 0 - for embymvideo in embymvideos: - # Process individual musicvideo - if self.shouldStop(): - return False - - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=embymvideo['Name']) - count += 1 - mvideos.add_update(embymvideo, viewName, viewId) - - ##### PROCESS DELETES ##### - - for kodimvideo in all_kodimvideos: - if kodimvideo not in all_embymvideosIds: - mvideos.remove(kodimvideo) - else: - log.info("MusicVideos compare finished.") - - return True + return MusicVideos(embycursor, kodicursor, pdialog).compare_all() def tvshows(self, embycursor, kodicursor, pdialog): - - # Get shows from emby - emby_db = embydb.Embydb_Functions(embycursor) - tvshows = itemtypes.TVShows(embycursor, kodicursor) - - views = emby_db.getView_byType('tvshows') - views += emby_db.getView_byType('mixed') - log.info("Media folders: %s" % views) - - # Pull the list of tvshows and episodes in Kodi - try: - all_koditvshows = dict(emby_db.get_checksum('Series')) - except ValueError: - all_koditvshows = {} - - log.info("all_koditvshows = %s", all_koditvshows) - - try: - all_kodiepisodes = dict(emby_db.get_checksum('Episode')) - except ValueError: - all_kodiepisodes = {} - - all_embytvshowsIds = set() - all_embyepisodesIds = set() - updatelist = [] - - - for view in views: - - if self.shouldStop(): - return False - - # Get items per view - viewId = view['id'] - viewName = view['name'] - - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33029), viewName)) - - all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog) - for embytvshow in all_embytvshows['Items']: - - if self.shouldStop(): - return False - - API = api.API(embytvshow) - itemid = embytvshow['Id'] - all_embytvshowsIds.add(itemid) - - - if all_koditvshows.get(itemid) != API.get_checksum(): - # Only update if movie is not in Kodi or checksum is different - updatelist.append(itemid) - - log.info("TVShows to update for %s: %s" % (viewName, updatelist)) - embytvshows = self.emby.getFullItems(updatelist) - total = len(updatelist) - del updatelist[:] - - - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (viewName, total)) - - count = 0 - for embytvshow in embytvshows: - # Process individual show - if self.shouldStop(): - return False - - itemid = embytvshow['Id'] - title = embytvshow['Name'] - all_embytvshowsIds.add(itemid) - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=title) - count += 1 - tvshows.add_update(embytvshow, viewName, viewId) - - else: - # Get all episodes in view - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33030), viewName)) - - all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog) - for embyepisode in all_embyepisodes['Items']: - - if self.shouldStop(): - return False - - API = api.API(embyepisode) - itemid = embyepisode['Id'] - all_embyepisodesIds.add(itemid) - if "SeriesId" in embyepisode: - all_embytvshowsIds.add(embyepisode['SeriesId']) - - if all_kodiepisodes.get(itemid) != API.get_checksum(): - # Only update if movie is not in Kodi or checksum is different - updatelist.append(itemid) - - log.info("Episodes to update for %s: %s" % (viewName, updatelist)) - embyepisodes = self.emby.getFullItems(updatelist) - total = len(updatelist) - del updatelist[:] - - count = 0 - for episode in embyepisodes: - - # Process individual episode - if self.shouldStop(): - return False - - if pdialog: - percentage = int((float(count) / float(total))*100) - title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) - pdialog.update(percentage, message=title) - count += 1 - tvshows.add_updateEpisode(episode) - - ##### PROCESS DELETES ##### - - log.info("all_embytvshowsIds = %s " % all_embytvshowsIds) - - for koditvshow in all_koditvshows: - if koditvshow not in all_embytvshowsIds: - tvshows.remove(koditvshow) - else: - log.info("TVShows compare finished.") - - for kodiepisode in all_kodiepisodes: - if kodiepisode not in all_embyepisodesIds: - tvshows.remove(kodiepisode) - else: - log.info("Episodes compare finished.") - - return True + return TVShows(embycursor, kodicursor, pdialog).compare_all() def music(self, embycursor, kodicursor, pdialog): - - # Get music from emby - emby_db = embydb.Embydb_Functions(embycursor) - music = itemtypes.Music(embycursor, kodicursor) - - # Pull the list of artists, albums, songs - try: - all_kodiartists = dict(emby_db.get_checksum('MusicArtist')) - except ValueError: - all_kodiartists = {} - - try: - all_kodialbums = dict(emby_db.get_checksum('MusicAlbum')) - except ValueError: - all_kodialbums = {} - - try: - all_kodisongs = dict(emby_db.get_checksum('Audio')) - except ValueError: - all_kodisongs = {} - - all_embyartistsIds = set() - all_embyalbumsIds = set() - all_embysongsIds = set() - updatelist = [] - - process = { - - 'artists': [self.emby.getArtists, music.add_updateArtist], - 'albums': [self.emby.getAlbums, music.add_updateAlbum], - 'songs': [self.emby.getSongs, music.add_updateSong] - } - for data_type in ['artists', 'albums', 'songs']: - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33031), data_type)) - if data_type != "artists": - all_embyitems = process[data_type][0](basic=True, dialog=pdialog) - else: - all_embyitems = process[data_type][0](dialog=pdialog) - for embyitem in all_embyitems['Items']: - if self.shouldStop(): - return False - API = api.API(embyitem) - itemid = embyitem['Id'] - if data_type == "artists": - all_embyartistsIds.add(itemid) - if all_kodiartists.get(itemid) != API.get_checksum(): - # Only update if artist is not in Kodi or checksum is different - updatelist.append(itemid) - elif data_type == "albums": - all_embyalbumsIds.add(itemid) - if all_kodialbums.get(itemid) != API.get_checksum(): - # Only update if album is not in Kodi or checksum is different - updatelist.append(itemid) - else: - all_embysongsIds.add(itemid) - if all_kodisongs.get(itemid) != API.get_checksum(): - # Only update if songs is not in Kodi or checksum is different - updatelist.append(itemid) - log.info("%s to update: %s" % (data_type, updatelist)) - embyitems = self.emby.getFullItems(updatelist) - total = len(updatelist) - del updatelist[:] - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (data_type, total)) - count = 0 - for embyitem in embyitems: - # Process individual item - if self.shouldStop(): - return False - if pdialog: - percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message=embyitem['Name']) - count += 1 - process[data_type][1](embyitem) - ##### PROCESS DELETES ##### - for kodiartist in all_kodiartists: - if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: - music.remove(kodiartist) - else: - log.info("Artist compare finished.") - for kodialbum in all_kodialbums: - if kodialbum not in all_embyalbumsIds: - music.remove(kodialbum) - else: - log.info("Albums compare finished.") - for kodisong in all_kodisongs: - if kodisong not in all_embysongsIds: - music.remove(kodisong) - else: - log.info("Songs compare finished.") - return True \ No newline at end of file + return Music(embycursor, kodicursor).compare_all() diff --git a/resources/lib/objects/__init__.py b/resources/lib/objects/__init__.py new file mode 100644 index 00000000..5219542a --- /dev/null +++ b/resources/lib/objects/__init__.py @@ -0,0 +1,5 @@ +# Dummy file to make this directory a package. +from movies import Movies +from musicvideos import MusicVideos +from tvshows import TVShows +from music import Music diff --git a/resources/lib/objects/common.py b/resources/lib/objects/common.py new file mode 100644 index 00000000..797c0607 --- /dev/null +++ b/resources/lib/objects/common.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import xbmc +import xbmcgui +import xbmcvfs + +import artwork +import downloadutils +import read_embyserver as embyserver +from utils import window, settings, dialog, language as lang, should_stop + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Items(object): + + pdialog = None + title = None + count = 0 + total = 0 + + + def __init__(self, **kwargs): + + self.artwork = artwork.Artwork() + self.emby = embyserver.Read_EmbyServer() + self.do_url = downloadutils.DownloadUtils().downloadUrl + self.should_stop = should_stop + + self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) + self.direct_path = settings('useDirectPaths') == "1" + self.content_msg = settings('newContent') == "true" + + def path_validation(self, path): + # Verify if direct path is accessible or not + if window('emby_pathverified') != "true" and not xbmcvfs.exists(path): + if dialog(type_="yesno", + heading="{emby}", + line1="%s %s. %s" % (lang(33047), path, lang(33048))): + + window('emby_shouldStop', value="true") + return False + + return True + + def content_pop(self): + # It's possible for the time to be 0. It should be considered disabled in this case. + if not self.pdialog and self.content_msg and self.new_time: + dialog(type_="notification", + heading="{emby}", + message="%s %s" % (lang(33049), name), + icon="{emby}", + time=time, + sound=False) + + def update_pdialog(self): + + if self.pdialog: + percentage = int((float(self.count) / float(self.total))*100) + self.pdialog.update(percentage, message=self.title) + + def add_all(self, item_type, items, view=None): + + if self.should_stop(): + return False + + total = items['TotalRecordCount'] if 'TotalRecordCount' in items else len(items) + items = items['Items'] if 'Items' in items else items + + if self.pdialog and view: + self.pdialog.update(heading="Processing %s / %s items" % (view['name'], total)) + + action = self._get_func(item_type, "added") + if view: + action(items, total, view) + else: + action(items, total) + + def process_all(self, item_type, action, items, total=None, view=None): + + log.debug("Processing %s: %s", action, items) + + process = self._get_func(item_type, action) + self.total = total or len(items) + self.count = 0 + + for item in items: + + if self.should_stop(): + return False + + if not process: + continue + + self.title = item.get('Name', "unknown") + self.update_pdialog() + + process(item) + self.count += 1 + + def added(self, items, total=None, update=True): + # Generator for newly added content + if update: + self.total = total or len(items) + self.count = 0 + + for item in items: + + if self.should_stop(): + break + + self.title = item.get('Name', "unknown") + + yield item + self.update_pdialog() + + if update: + self.count += 1 diff --git a/resources/lib/objects/movies.py b/resources/lib/objects/movies.py new file mode 100644 index 00000000..c051272b --- /dev/null +++ b/resources/lib/objects/movies.py @@ -0,0 +1,572 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import urllib +from ntpath import dirname +from datetime import datetime + +import api +import common +import downloadutils +import embydb_functions as embydb +import kodidb_functions as kodidb +from utils import window, settings, language as lang, catch_except + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Movies(common.Items): + + + def __init__(self, embycursor, kodicursor, pdialog=None): + + self.embycursor = embycursor + self.emby_db = embydb.Embydb_Functions(self.embycursor) + self.kodicursor = kodicursor + self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor) + self.pdialog = pdialog + + self.new_time = int(settings('newvideotime'))*1000 + + common.Items.__init__(self) + + def _get_func(self, item_type, action): + + if item_type == "Movie": + actions = { + 'added': self.added, + 'update': self.add_update, + 'userdata': self.updateUserdata, + 'remove': self.remove + } + elif item_type == "BoxSet": + actions = { + 'added': self.add_updateBoxset, + 'update': self.add_updateBoxset, + 'remove': self.remove + } + else: + log.info("Unsupported item_type: %s", item_type) + actions = {} + + return actions.get(action) + + def compare_all(self): + # Pull the list of movies and boxsets in Kodi + pdialog = self.pdialog + views = self.emby_db.getView_byType('movies') + views += self.emby_db.getView_byType('mixed') + log.info("Media folders: %s" % views) + + try: + all_kodisets = dict(self.emby_db.get_checksum('BoxSet')) + except ValueError: + all_kodisets = {} + + try: + all_kodimovies = dict(self.emby_db.get_checksum('Movie')) + except ValueError: + all_kodimovies = {} + + all_embymoviesIds = set() + all_embyboxsetsIds = set() + updatelist = [] + + for view in views: + + if self.should_stop(): + return False + + # Get items per view + viewId = view['id'] + viewName = view['name'] + + if pdialog: + pdialog.update( + heading=lang(29999), + message="%s %s..." % (lang(33026), viewName)) + + all_embymovies = self.emby.getMovies(viewId, basic=True, dialog=pdialog) + for embymovie in all_embymovies['Items']: + + if self.should_stop(): + return False + + API = api.API(embymovie) + itemid = embymovie['Id'] + all_embymoviesIds.add(itemid) + + + if all_kodimovies.get(itemid) != API.get_checksum(): + # Only update if movie is not in Kodi or checksum is different + updatelist.append(itemid) + + log.info("Movies to update for %s: %s" % (viewName, updatelist)) + embymovies = self.emby.getFullItems(updatelist) + total = len(updatelist) + del updatelist[:] + + if pdialog: + pdialog.update(heading="Processing %s / %s items" % (viewName, total)) + + self.added(embymovies, total, view) + + boxsets = self.emby.getBoxset(dialog=pdialog) + embyboxsets = [] + + if pdialog: + pdialog.update(heading=lang(29999), message=lang(33027)) + + for boxset in boxsets['Items']: + + if self.should_stop(): + return False + + # Boxset has no real userdata, so using etag to compare + itemid = boxset['Id'] + all_embyboxsetsIds.add(itemid) + + if all_kodisets.get(itemid) != boxset['Etag']: + # Only update if boxset is not in Kodi or boxset['Etag'] is different + updatelist.append(itemid) + embyboxsets.append(boxset) + + log.info("Boxsets to update: %s" % updatelist) + self.total = len(updatelist) + + if pdialog: + pdialog.update(heading="Processing Boxsets / %s items" % total) + + self.count = 0 + for boxset in embyboxsets: + # Process individual boxset + if self.should_stop(): + return False + self.title = boxset['Name'] + self.update_pdialog() + self.add_updateBoxset(boxset) + self.count += 1 + + ##### PROCESS DELETES ##### + + for kodimovie in all_kodimovies: + if kodimovie not in all_embymoviesIds: + self.remove(kodimovie) + else: + log.info("Movies compare finished.") + + for boxset in all_kodisets: + if boxset not in all_embyboxsetsIds: + self.remove(boxset) + else: + log.info("Boxsets compare finished.") + + return True + + + def added(self, items, total=None, view=None): + + for item in super(Movies, self).added(items, total): + if self.add_update(item, view): + self.content_pop() + + @catch_except() + def add_update(self, item, view=None): + # Process single movie + kodicursor = self.kodicursor + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + movieid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + log.info("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid)) + + except TypeError: + update_item = False + log.debug("movieid: %s not found." % itemid) + # movieid + kodicursor.execute("select coalesce(max(idMovie),0) from movie") + movieid = kodicursor.fetchone()[0] + 1 + + else: + # Verification the item is still in Kodi + query = "SELECT * FROM movie WHERE idMovie = ?" + kodicursor.execute(query, (movieid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + log.info("movieid: %s missing from Kodi, repairing the entry." % movieid) + + if not view: + # Get view tag from emby + viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) + log.debug("View tag found: %s" % viewtag) + else: + viewtag = view['name'] + viewid = view['id'] + + # fileId information + checksum = API.get_checksum() + dateadded = API.get_date_created() + userdata = API.get_userdata() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + people = API.get_people() + writer = " / ".join(people['Writer']) + director = " / ".join(people['Director']) + genres = item['Genres'] + title = item['Name'] + plot = API.get_overview() + shortplot = item.get('ShortOverview') + tagline = API.get_tagline() + votecount = item.get('VoteCount') + rating = item.get('CommunityRating') + year = item.get('ProductionYear') + imdb = API.get_provider('Imdb') + sorttitle = item['SortName'] + runtime = API.get_runtime() + mpaa = API.get_mpaa() + genre = " / ".join(genres) + country = API.get_country() + studios = API.get_studios() + try: + studio = studios[0] + except IndexError: + studio = None + + if item.get('LocalTrailerCount'): + # There's a local trailer + url = ( + "{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json" + % itemid + ) + result = self.do_url(url) + try: + trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] + except IndexError: + log.info("Failed to process local trailer.") + trailer = None + else: + # Try to get the youtube trailer + try: + trailer = item['RemoteTrailers'][0]['Url'] + except (KeyError, IndexError): + trailer = None + else: + try: + trailerId = trailer.rsplit('=', 1)[1] + except IndexError: + log.info("Failed to process trailer: %s" % trailer) + trailer = None + else: + trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId + + + ##### GET THE FILE AND PATH ##### + playurl = API.get_file_path() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.direct_path: + # Direct paths is set the Kodi way + if not self.path_validation(playurl): + return False + + path = playurl.replace(filename, "") + window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.movies/" + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': movieid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE MOVIE ##### + if update_item: + log.info("UPDATE movie itemid: %s - Title: %s" % (itemid, title)) + + # Update the movie entry + if self.kodi_version > 16: + query = ' '.join(( + + "UPDATE movie", + "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,", + "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,", + "c16 = ?, c18 = ?, c19 = ?, c21 = ?, premiered = ?", + "WHERE idMovie = ?" + )) + kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, + writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, + trailer, country, year, movieid)) + else: + query = ' '.join(( + + "UPDATE movie", + "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,", + "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,", + "c16 = ?, c18 = ?, c19 = ?, c21 = ?", + "WHERE idMovie = ?" + )) + kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, + writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, + trailer, country, movieid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE MOVIE ##### + else: + log.info("ADD movie itemid: %s - Title: %s" % (itemid, title)) + + # Add path + pathid = self.kodi_db.addPath(path) + # Add the file + fileid = self.kodi_db.addFile(filename, pathid) + + # Create the movie entry + if self.kodi_version > 16: + query = ( + ''' + INSERT INTO movie( + idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, + c09, c10, c11, c12, c14, c15, c16, c18, c19, c21, premiered) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, + votecount, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, + director, title, studio, trailer, country, year)) + else: + query = ( + ''' + INSERT INTO movie( + idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, + c09, c10, c11, c12, c14, c15, c16, c18, c19, c21) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, + votecount, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, + director, title, studio, trailer, country)) + + # Create the reference in emby table + emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, checksum, viewid) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, "movies", "metadata.local", 1, pathid)) + + # Update the file + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (pathid, filename, dateadded, fileid)) + + # Process countries + if 'ProductionLocations' in item: + self.kodi_db.addCountries(movieid, item['ProductionLocations'], "movie") + # Process cast + people = artwork.get_people_artwork(item['People']) + self.kodi_db.addPeople(movieid, people, "movie") + # Process genres + self.kodi_db.addGenres(movieid, genres, "movie") + # Process artwork + artwork.add_artwork(artwork.get_all_artwork(item), movieid, "movie", kodicursor) + # Process stream details + streams = API.get_media_streams() + self.kodi_db.addStreams(fileid, streams, runtime) + # Process studios + self.kodi_db.addStudios(movieid, studios, "movie") + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite movies") + log.info("Applied tags: %s", tags) + self.kodi_db.addTags(movieid, tags, "movie") + # Process playstates + resume = API.adjust_resume(userdata['Resume']) + total = round(float(runtime), 6) + self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + + return True + + def add_updateBoxset(self, boxset): + + emby = self.emby + emby_db = self.emby_db + artwork = self.artwork + + boxsetid = boxset['Id'] + title = boxset['Name'] + checksum = boxset['Etag'] + emby_dbitem = emby_db.getItem_byId(boxsetid) + try: + setid = emby_dbitem[0] + + except TypeError: + setid = self.kodi_db.createBoxset(title) + + # Process artwork + artwork.add_artwork(artwork.get_all_artwork(boxset), setid, "set", self.kodicursor) + + # Process movies inside boxset + current_movies = emby_db.getItemId_byParentId(setid, "movie") + process = [] + try: + # Try to convert tuple to dictionary + current = dict(current_movies) + except ValueError: + current = {} + + # Sort current titles + for current_movie in current: + process.append(current_movie) + + # New list to compare + for movie in emby.getMovies_byBoxset(boxsetid)['Items']: + + itemid = movie['Id'] + + if not current.get(itemid): + # Assign boxset to movie + emby_dbitem = emby_db.getItem_byId(itemid) + try: + movieid = emby_dbitem[0] + except TypeError: + log.info("Failed to add: %s to boxset." % movie['Name']) + continue + + log.info("New addition to boxset %s: %s" % (title, movie['Name'])) + self.kodi_db.assignBoxset(setid, movieid) + # Update emby reference + emby_db.updateParentId(itemid, setid) + else: + # Remove from process, because the item still belongs + process.remove(itemid) + + # Process removals from boxset + for movie in process: + movieid = current[movie] + log.info("Remove from boxset %s: %s" % (title, movieid)) + self.kodi_db.removefromBoxset(movieid) + # Update emby reference + emby_db.updateParentId(movie, None) + + # Update the reference in the emby table + emby_db.addReference(boxsetid, setid, "BoxSet", mediatype="set", checksum=checksum) + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.get_checksum() + userdata = API.get_userdata() + runtime = API.get_runtime() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + movieid = emby_dbitem[0] + fileid = emby_dbitem[1] + log.info("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid)) + except TypeError: + return + + # Process favorite tags + if userdata['Favorite']: + self.kodi_db.addTag(movieid, "Favorite movies", "movie") + else: + self.kodi_db.removeTag(movieid, "Favorite movies", "movie") + + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjust_resume(userdata['Resume']) + total = round(float(runtime), 6) + + log.debug("%s New resume point: %s" % (itemid, resume)) + + self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove movieid, fileid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + fileid = emby_dbitem[1] + mediatype = emby_dbitem[4] + log.info("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid)) + except TypeError: + return + + # Remove the emby reference + emby_db.removeItem(itemid) + # Remove artwork + artwork.delete_artwork(kodiid, mediatype, kodicursor) + + if mediatype == "movie": + # Delete kodi movie and file + kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodiid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + + elif mediatype == "set": + # Delete kodi boxset + boxset_movies = emby_db.getItem_byParentId(kodiid, "movie") + for movie in boxset_movies: + embyid = movie[0] + movieid = movie[1] + self.kodi_db.removefromBoxset(movieid) + # Update emby reference + emby_db.updateParentId(embyid, None) + + kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) + + log.info("Deleted %s %s from kodi database" % (mediatype, itemid)) diff --git a/resources/lib/objects/music.py b/resources/lib/objects/music.py new file mode 100644 index 00000000..a47467f5 --- /dev/null +++ b/resources/lib/objects/music.py @@ -0,0 +1,909 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import urllib +from ntpath import dirname +from datetime import datetime + +import api +import common +import downloadutils +import embydb_functions as embydb +import kodidb_functions as kodidb +import musicutils +from utils import window, settings, language as lang, catch_except + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Music(common.Items): + + + def __init__(self, embycursor, kodicursor, pdialog=None): + + self.embycursor = embycursor + self.emby_db = embydb.Embydb_Functions(self.embycursor) + self.kodicursor = kodicursor + self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor) + self.pdialog = pdialog + + self.new_time = int(settings('newmusictime'))*1000 + self.directstream = settings('streamMusic') == "true" + self.enableimportsongrating = settings('enableImportSongRating') == "true" + self.enableexportsongrating = settings('enableExportSongRating') == "true" + self.enableupdatesongrating = settings('enableUpdateSongRating') == "true" + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) + + common.Items.__init__(self) + + def _get_func(self, item_type, action): + + if item_type == "MusicAlbum": + actions = { + 'added': self.added_album, + 'update': self.add_updateAlbum, + 'userdata': self.updateUserdata, + 'remove': self.remove + } + elif item_type in ("MusicArtist", "AlbumArtist"): + actions = { + 'added': self.added, + 'update': self.add_updateArtist, + 'remove': self.remove + } + elif item_type == "Audio": + actions = { + 'added': self.added_song, + 'update': self.add_updateSong, + 'userdata': self.updateUserdata, + 'remove': self.remove + } + else: + log.info("Unsupported item_type: %s", item_type) + actions = {} + + return actions.get(action) + + def compare_all(self): + # Pull the list of artists, albums, songs + pdialog = self.pdialog + + views = self.emby_db.getView_byType('music') + try: + all_kodiartists = dict(self.emby_db.get_checksum('MusicArtist')) + except ValueError: + all_kodiartists = {} + + try: + all_kodialbums = dict(self.emby_db.get_checksum('MusicAlbum')) + except ValueError: + all_kodialbums = {} + + try: + all_kodisongs = dict(self.emby_db.get_checksum('Audio')) + except ValueError: + all_kodisongs = {} + + all_embyartistsIds = set() + all_embyalbumsIds = set() + all_embysongsIds = set() + updatelist = [] + + process = { + + 'artists': [self.emby.getArtists, self.add_updateArtist], + 'albums': [self.emby.getAlbums, self.add_updateAlbum], + 'songs': [self.emby.getSongs, self.add_updateSong] + } + for view in views: + for data_type in ['artists', 'albums', 'songs']: + if pdialog: + pdialog.update( + heading=lang(29999), + message="%s %s..." % (lang(33031), data_type)) + if data_type != "artists": + all_embyitems = process[data_type][0](basic=True, dialog=pdialog) + else: + all_embyitems = process[data_type][0](view['id'], dialog=pdialog) + + for embyitem in all_embyitems['Items']: + if self.should_stop(): + return False + API = api.API(embyitem) + itemid = embyitem['Id'] + if data_type == "artists": + all_embyartistsIds.add(itemid) + if all_kodiartists.get(itemid) != API.get_checksum(): + # Only update if artist is not in Kodi or checksum is different + updatelist.append(itemid) + elif data_type == "albums": + all_embyalbumsIds.add(itemid) + if all_kodialbums.get(itemid) != API.get_checksum(): + # Only update if album is not in Kodi or checksum is different + updatelist.append(itemid) + else: + all_embysongsIds.add(itemid) + if all_kodisongs.get(itemid) != API.get_checksum(): + # Only update if songs is not in Kodi or checksum is different + updatelist.append(itemid) + log.info("%s to update: %s" % (data_type, updatelist)) + embyitems = self.emby.getFullItems(updatelist) + self.total = len(updatelist) + del updatelist[:] + if pdialog: + pdialog.update(heading="Processing %s / %s items" % (data_type, self.total)) + self.count = 0 + for embyitem in embyitems: + # Process individual item + if self.should_stop(): + return False + self.title = embyitem['Name'] + self.update_pdialog() + process[data_type][1](embyitem) + self.count += 1 + ##### PROCESS DELETES ##### + for kodiartist in all_kodiartists: + if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: + self.remove(kodiartist) + else: + log.info("Artist compare finished.") + for kodialbum in all_kodialbums: + if kodialbum not in all_embyalbumsIds: + self.remove(kodialbum) + else: + log.info("Albums compare finished.") + for kodisong in all_kodisongs: + if kodisong not in all_embysongsIds: + self.remove(kodisong) + else: + log.info("Songs compare finished.") + return True + + + def added(self, items, total=None): + + for item in super(Music, self).added(items, total): + if self.add_updateArtist(item): + # Add albums + all_albums = self.emby.getAlbumsbyArtist(item['Id']) + self.added_album(all_albums['Items']) + + def added_album(self, items, total=None): + + update = True if not self.total else False + + for item in super(Music, self).added(items, total, update): + self.title = "%s - %s" % (item.get('AlbumArtist', "unknown"), self.title) + + if self.add_updateAlbum(item): + # Add songs + all_songs = self.emby.getSongsbyAlbum(item['Id']) + self.added_song(all_songs['Items']) + + def added_song(self, items, total=None): + + update = True if not self.total else False + + for item in super(Music, self).added(items, total, update): + self.title = "%s - %s" % (item.get('AlbumArtist', "unknown"), self.title) + + if self.add_updateSong(item): + self.content_pop() + + @catch_except() + def add_updateArtist(self, item, artisttype="MusicArtist"): + # Process a single artist + kodicursor = self.kodicursor + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + artistid = emby_dbitem[0] + except TypeError: + update_item = False + log.debug("artistid: %s not found." % itemid) + + ##### The artist details ##### + lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + dateadded = API.get_date_created() + checksum = API.get_checksum() + + name = item['Name'] + musicBrainzId = API.get_provider('MusicBrainzArtist') + genres = " / ".join(item.get('Genres')) + bio = API.get_overview() + + # Associate artwork + artworks = artwork.get_all_artwork(item, parent_info=True) + thumb = artworks['Primary'] + backdrops = artworks['Backdrop'] # List + + if thumb: + thumb = "<thumb>%s</thumb>" % thumb + if backdrops: + fanart = "<fanart>%s</fanart>" % backdrops[0] + else: + fanart = "" + + + ##### UPDATE THE ARTIST ##### + if update_item: + log.info("UPDATE artist itemid: %s - Name: %s" % (itemid, name)) + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE ARTIST ##### + else: + log.info("ADD artist itemid: %s - Name: %s" % (itemid, name)) + # safety checks: It looks like Emby supports the same artist multiple times. + # Kodi doesn't allow that. In case that happens we just merge the artist entries. + artistid = self.kodi_db.addArtist(name, musicBrainzId) + # Create the reference in emby table + emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum) + + + # Process the artist + if self.kodi_version in (16, 17): + query = ' '.join(( + + "UPDATE artist", + "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", + "lastScraped = ?", + "WHERE idArtist = ?" + )) + kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, artistid)) + else: + query = ' '.join(( + + "UPDATE artist", + "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", + "lastScraped = ?, dateAdded = ?", + "WHERE idArtist = ?" + )) + kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, + dateadded, artistid)) + + + # Update artwork + artwork.add_artwork(artworks, artistid, "artist", kodicursor) + + return True + + @catch_except() + def add_updateAlbum(self, item): + # Process a single artist + emby = self.emby + kodicursor = self.kodicursor + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + albumid = emby_dbitem[0] + except TypeError: + update_item = False + log.debug("albumid: %s not found." % itemid) + + ##### The album details ##### + lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + dateadded = API.get_date_created() + userdata = API.get_userdata() + checksum = API.get_checksum() + + name = item['Name'] + musicBrainzId = API.get_provider('MusicBrainzAlbum') + year = item.get('ProductionYear') + genres = item.get('Genres') + genre = " / ".join(genres) + bio = API.get_overview() + rating = userdata['UserRating'] + artists = item['AlbumArtists'] + artistname = [] + for artist in artists: + artistname.append(artist['Name']) + artistname = " / ".join(artistname) + + # Associate artwork + artworks = artwork.get_all_artwork(item, parent_info=True) + thumb = artworks['Primary'] + if thumb: + thumb = "<thumb>%s</thumb>" % thumb + + ##### UPDATE THE ALBUM ##### + if update_item: + log.info("UPDATE album itemid: %s - Name: %s" % (itemid, name)) + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE ALBUM ##### + else: + log.info("ADD album itemid: %s - Name: %s" % (itemid, name)) + # safety checks: It looks like Emby supports the same artist multiple times. + # Kodi doesn't allow that. In case that happens we just merge the artist entries. + albumid = self.kodi_db.addAlbum(name, musicBrainzId) + # Create the reference in emby table + emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum) + + + # Process the album info + if self.kodi_version == 17: + # Kodi Krypton + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iUserrating = ?, lastScraped = ?, strReleaseType = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + "album", albumid)) + elif self.kodi_version == 16: + # Kodi Jarvis + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iRating = ?, lastScraped = ?, strReleaseType = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + "album", albumid)) + elif self.kodi_version == 15: + # Kodi Isengard + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iRating = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + dateadded, "album", albumid)) + else: + # Kodi Helix + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iRating = ?, lastScraped = ?, dateAdded = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + dateadded, albumid)) + + # Assign main artists to album + for artist in item['AlbumArtists']: + artistname = artist['Name'] + artistId = artist['Id'] + emby_dbartist = emby_db.getItem_byId(artistId) + try: + artistid = emby_dbartist[0] + except TypeError: + # Artist does not exist in emby database, create the reference + artist = emby.getItem(artistId) + self.add_updateArtist(artist, artisttype="AlbumArtist") + emby_dbartist = emby_db.getItem_byId(artistId) + artistid = emby_dbartist[0] + else: + # Best take this name over anything else. + query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?" + kodicursor.execute(query, (artistname, artistid,)) + + # Add artist to album + query = ( + ''' + INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, albumid, artistname)) + # Update emby reference with parentid + emby_db.updateParentId(artistId, albumid) + + for artist in item['ArtistItems']: + artistId = artist['Id'] + emby_dbartist = emby_db.getItem_byId(artistId) + try: + artistid = emby_dbartist[0] + except TypeError: + pass + else: + # Update discography + query = ( + ''' + INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, name, year)) + + # Add genres + self.kodi_db.addMusicGenres(albumid, genres, "album") + # Update artwork + artwork.add_artwork(artworks, albumid, "album", kodicursor) + + return True + + @catch_except() + def add_updateSong(self, item): + # Process single song + kodicursor = self.kodicursor + emby = self.emby + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + songid = emby_dbitem[0] + pathid = emby_dbitem[2] + albumid = emby_dbitem[3] + except TypeError: + update_item = False + log.debug("songid: %s not found." % itemid) + + ##### The song details ##### + checksum = API.get_checksum() + dateadded = API.get_date_created() + userdata = API.get_userdata() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + title = item['Name'] + musicBrainzId = API.get_provider('MusicBrainzTrackId') + genres = item.get('Genres') + genre = " / ".join(genres) + artists = " / ".join(item['Artists']) + tracknumber = item.get('IndexNumber', 0) + disc = item.get('ParentIndexNumber', 1) + if disc == 1: + track = tracknumber + else: + track = disc*2**16 + tracknumber + year = item.get('ProductionYear') + duration = API.get_runtime() + rating = userdata['UserRating'] + + #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.get_overview() + + + ##### GET THE FILE AND PATH ##### + if self.directstream: + path = "%s/emby/Audio/%s/" % (self.server, itemid) + extensions = ['mp3', 'aac', 'ogg', 'oga', 'webma', 'wma', 'flac'] + + if 'Container' in item and item['Container'].lower() in extensions: + filename = "stream.%s?static=true" % item['Container'] + else: + filename = "stream.mp3?static=true" + else: + playurl = API.get_file_path() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + # Direct paths is set the Kodi way + if not self.path_validation(playurl): + return False + + path = playurl.replace(filename, "") + window('emby_pathverified', value="true") + + ##### UPDATE THE SONG ##### + if update_item: + log.info("UPDATE song itemid: %s - Title: %s" % (itemid, title)) + + # Update path + query = "UPDATE path SET strPath = ? WHERE idPath = ?" + kodicursor.execute(query, (path, pathid)) + + # Update the song entry + query = ' '.join(( + + "UPDATE song", + "SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,", + "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,", + "rating = ?, comment = ?", + "WHERE idSong = ?" + )) + kodicursor.execute(query, (albumid, artists, genre, title, track, duration, year, + filename, playcount, dateplayed, rating, comment, songid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE SONG ##### + else: + log.info("ADD song itemid: %s - Title: %s" % (itemid, title)) + + # Add path + pathid = self.kodi_db.addPath(path) + + try: + # Get the album + emby_dbalbum = emby_db.getItem_byId(item['AlbumId']) + albumid = emby_dbalbum[0] + except KeyError: + # Verify if there's an album associated. + album_name = item.get('Album') + if album_name: + log.info("Creating virtual music album for song: %s." % itemid) + albumid = self.kodi_db.addAlbum(album_name, API.get_provider('MusicBrainzAlbum')) + emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album") + else: + # No album Id associated to the song. + log.error("Song itemid: %s has no albumId associated." % itemid) + return False + + except TypeError: + # No album found. Let's create it + log.info("Album database entry missing.") + emby_albumId = item['AlbumId'] + album = emby.getItem(emby_albumId) + self.add_updateAlbum(album) + emby_dbalbum = emby_db.getItem_byId(emby_albumId) + try: + albumid = emby_dbalbum[0] + log.info("Found albumid: %s" % albumid) + except TypeError: + # No album found, create a single's album + log.info("Failed to add album. Creating singles.") + kodicursor.execute("select coalesce(max(idAlbum),0) from album") + albumid = kodicursor.fetchone()[0] + 1 + if self.kodi_version == 16: + # Kodi Jarvis + query = ( + ''' + INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (albumid, genre, year, "single")) + elif self.kodi_version == 15: + # Kodi Isengard + query = ( + ''' + INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (albumid, genre, year, dateadded, "single")) + else: + # Kodi Helix + query = ( + ''' + INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (albumid, genre, year, dateadded)) + + # Create the song entry + kodicursor.execute("select coalesce(max(idSong),0) from song") + songid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO song( + idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, + iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, + rating) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (songid, albumid, pathid, artists, genre, title, track, + duration, year, filename, musicBrainzId, playcount, dateplayed, rating)) + + # Create the reference in emby table + emby_db.addReference(itemid, songid, "Audio", "song", pathid=pathid, parentid=albumid, + checksum=checksum) + + + # Link song to album + query = ( + ''' + INSERT OR REPLACE INTO albuminfosong( + idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (songid, albumid, track, title, duration)) + + # Link song to artists + for index, artist in enumerate(item['ArtistItems']): + + artist_name = artist['Name'] + artist_eid = artist['Id'] + artist_edb = emby_db.getItem_byId(artist_eid) + try: + artistid = artist_edb[0] + except TypeError: + # Artist is missing from emby database, add it. + artist_full = emby.getItem(artist_eid) + self.add_updateArtist(artist_full) + artist_edb = emby_db.getItem_byId(artist_eid) + artistid = artist_edb[0] + finally: + if self.kodi_version >= 17: + # Kodi Krypton + query = ( + ''' + INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, songid, 1, index, artist_name)) + + # May want to look into only doing this once? + query = ( + ''' + INSERT OR REPLACE INTO role(idRole, strRole) + + VALUES (?, ?) + ''' + ) + kodicursor.execute(query, (1, 'Composer')) + else: + query = ( + ''' + INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, songid, index, artist_name)) + + # Verify if album artist exists + album_artists = [] + for artist in item['AlbumArtists']: + + artist_name = artist['Name'] + album_artists.append(artist_name) + artist_eid = artist['Id'] + artist_edb = emby_db.getItem_byId(artist_eid) + try: + artistid = artist_edb[0] + except TypeError: + # Artist is missing from emby database, add it. + artist_full = emby.getItem(artist_eid) + self.add_updateArtist(artist_full) + artist_edb = emby_db.getItem_byId(artist_eid) + artistid = artist_edb[0] + finally: + query = ( + ''' + INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, albumid, artist_name)) + # Update discography + if item.get('Album'): + query = ( + ''' + INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, item['Album'], 0)) + else: + album_artists = " / ".join(album_artists) + query = ' '.join(( + + "SELECT strArtists", + "FROM album", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (albumid,)) + result = kodicursor.fetchone() + if result and result[0] != album_artists: + # Field is empty + if self.kodi_version in (16, 17): + # Kodi Jarvis, Krypton + query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" + kodicursor.execute(query, (album_artists, albumid)) + elif self.kodi_version == 15: + # Kodi Isengard + query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" + kodicursor.execute(query, (album_artists, albumid)) + else: + # Kodi Helix + query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" + kodicursor.execute(query, (album_artists, albumid)) + + # Add genres + self.kodi_db.addMusicGenres(songid, genres, "song") + + # Update artwork + allart = artwork.get_all_artwork(item, parent_info=True) + if hasEmbeddedCover: + allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl ) + artwork.add_artwork(allart, songid, "song", kodicursor) + + if item.get('AlbumId') is None: + # Update album artwork + artwork.add_artwork(allart, albumid, "album", kodicursor) + + return True + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + kodicursor = self.kodicursor + emby_db = self.emby_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.get_checksum() + userdata = API.get_userdata() + runtime = API.get_runtime() + rating = userdata['UserRating'] + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + mediatype = emby_dbitem[4] + log.info("Update playstate for %s: %s" % (mediatype, item['Name'])) + except TypeError: + return + + if mediatype == "song": + + #should we ignore this item ? + #happens when userdata updated by ratings method + if window("ignore-update-%s" %itemid): + window("ignore-update-%s" %itemid,clear=True) + return + + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + #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)) + + elif mediatype == "album": + # Process playstates + if self.kodi_version >= 17: + query = "UPDATE album SET fRating = ? WHERE idAlbum = ?" + else: + query = "UPDATE album SET iRating = ? WHERE idAlbum = ?" + kodicursor.execute(query, (rating, kodiid)) + + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove kodiid, fileid, pathid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + mediatype = emby_dbitem[4] + log.info("Removing %s kodiid: %s" % (mediatype, kodiid)) + except TypeError: + return + + ##### PROCESS ITEM ##### + + # Remove the emby reference + emby_db.removeItem(itemid) + + + ##### IF SONG ##### + + if mediatype == "song": + # Delete song + self.removeSong(kodiid) + # This should only address single song scenario, where server doesn't actually + # create an album for the song. + emby_db.removeWildItem(itemid) + + for item in emby_db.getItem_byWildId(itemid): + + item_kid = item[0] + item_mediatype = item[1] + + if item_mediatype == "album": + childs = emby_db.getItem_byParentId(item_kid, "song") + if not childs: + # Delete album + self.removeAlbum(item_kid) + + ##### IF ALBUM ##### + + elif mediatype == "album": + # Delete songs, album + album_songs = emby_db.getItem_byParentId(kodiid, "song") + for song in album_songs: + self.removeSong(song[1]) + else: + # Remove emby songs + emby_db.removeItems_byParentId(kodiid, "song") + + # Remove the album + self.removeAlbum(kodiid) + + ##### IF ARTIST ##### + + elif mediatype == "artist": + # Delete songs, album, artist + albums = emby_db.getItem_byParentId(kodiid, "album") + for album in albums: + albumid = album[1] + album_songs = emby_db.getItem_byParentId(albumid, "song") + for song in album_songs: + self.removeSong(song[1]) + else: + # Remove emby song + emby_db.removeItems_byParentId(albumid, "song") + # Remove emby artist + emby_db.removeItems_byParentId(albumid, "artist") + # Remove kodi album + self.removeAlbum(albumid) + else: + # Remove emby albums + emby_db.removeItems_byParentId(kodiid, "album") + + # Remove artist + self.removeArtist(kodiid) + + log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) + + def removeSong(self, kodiId): + + kodicursor = self.kodicursor + + self.artwork.delete_artwork(kodiId, "song", self.kodicursor) + self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiId,)) + + def removeAlbum(self, kodiId): + + self.artwork.delete_artwork(kodiId, "album", self.kodicursor) + self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiId,)) + + def removeArtist(self, kodiId): + + self.artwork.delete_artwork(kodiId, "artist", self.kodicursor) + self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiId,)) diff --git a/resources/lib/objects/musicvideos.py b/resources/lib/objects/musicvideos.py new file mode 100644 index 00000000..03da09b5 --- /dev/null +++ b/resources/lib/objects/musicvideos.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import urllib +from ntpath import dirname +from datetime import datetime + +import api +import common +import downloadutils +import embydb_functions as embydb +import kodidb_functions as kodidb +from utils import window, settings, language as lang, catch_except + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class MusicVideos(common.Items): + + + def __init__(self, embycursor, kodicursor, pdialog=None): + + self.embycursor = embycursor + self.emby_db = embydb.Embydb_Functions(self.embycursor) + self.kodicursor = kodicursor + self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor) + self.pdialog = pdialog + + common.Items.__init__(self) + + def _get_func(self, item_type, action): + + if item_type == "MusicVideo": + actions = { + 'added': self.added, + 'update': self.add_update, + 'userdata': self.updateUserdata, + 'remove': self.remove + } + else: + log.info("Unsupported item_type: %s", item_type) + actions = {} + + return actions.get(action) + + def compare_all(self): + pdialog = self.pdialog + # Pull the list of musicvideos in Kodi + views = self.emby_db.getView_byType('musicvideos') + log.info("Media folders: %s" % views) + + try: + all_kodimvideos = dict(self.emby_db.get_checksum('MusicVideo')) + except ValueError: + all_kodimvideos = {} + + all_embymvideosIds = set() + updatelist = [] + + for view in views: + + if self.should_stop(): + return False + + # Get items per view + viewId = view['id'] + viewName = view['name'] + + if pdialog: + pdialog.update( + heading=lang(29999), + message="%s %s..." % (lang(33028), viewName)) + + all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog) + for embymvideo in all_embymvideos['Items']: + + if self.should_stop(): + return False + + API = api.API(embymvideo) + itemid = embymvideo['Id'] + all_embymvideosIds.add(itemid) + + + if all_kodimvideos.get(itemid) != API.get_checksum(): + # Only update if musicvideo is not in Kodi or checksum is different + updatelist.append(itemid) + + log.info("MusicVideos to update for %s: %s" % (viewName, updatelist)) + embymvideos = self.emby.getFullItems(updatelist) + self.total = len(updatelist) + del updatelist[:] + + + if pdialog: + pdialog.update(heading="Processing %s / %s items" % (viewName, self.total)) + + self.count = 0 + for embymvideo in embymvideos: + # Process individual musicvideo + if self.should_stop(): + return False + self.title = embymvideo['Name'] + self.update_pdialog() + self.add_update(embymvideo, view) + self.count += 1 + + ##### PROCESS DELETES ##### + + for kodimvideo in all_kodimvideos: + if kodimvideo not in all_embymvideosIds: + self.remove(kodimvideo) + else: + log.info("MusicVideos compare finished.") + + return True + + + def added(self, items, total=None, view=None): + for item in super(MusicVideos, self).added(items, total, True): + if self.add_update(item, view): + self.content_pop() + + @catch_except + def add_update(self, item, view=None): + # Process single music video + kodicursor = self.kodicursor + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + mvideoid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + log.info("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid)) + + except TypeError: + update_item = False + log.debug("mvideoid: %s not found." % itemid) + # mvideoid + kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") + mvideoid = kodicursor.fetchone()[0] + 1 + + else: + # Verification the item is still in Kodi + query = "SELECT * FROM musicvideo WHERE idMVideo = ?" + kodicursor.execute(query, (mvideoid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + log.info("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid) + + if not view: + # Get view tag from emby + viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) + log.debug("View tag found: %s" % viewtag) + else: + viewtag = view['name'] + viewid = view['id'] + + # fileId information + checksum = API.get_checksum() + dateadded = API.get_date_created() + userdata = API.get_userdata() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + runtime = API.get_runtime() + plot = API.get_overview() + title = item['Name'] + year = item.get('ProductionYear') + genres = item['Genres'] + genre = " / ".join(genres) + studios = API.get_studios() + studio = " / ".join(studios) + artist = " / ".join(item.get('Artists')) + album = item.get('Album') + track = item.get('Track') + people = API.get_people() + director = " / ".join(people['Director']) + + + ##### GET THE FILE AND PATH ##### + playurl = API.get_file_path() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.direct_path: + # Direct paths is set the Kodi way + if not self.path_validation(playurl): + return False + + path = playurl.replace(filename, "") + window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.musicvideos/" + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': mvideoid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE MUSIC VIDEO ##### + if update_item: + log.info("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title)) + + # Update path + query = "UPDATE path SET strPath = ? WHERE idPath = ?" + kodicursor.execute(query, (path, pathid)) + + # Update the filename + query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?" + kodicursor.execute(query, (filename, dateadded, fileid)) + + # Update the music video entry + query = ' '.join(( + + "UPDATE musicvideo", + "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,", + "c11 = ?, c12 = ?" + "WHERE idMVideo = ?" + )) + kodicursor.execute(query, (title, runtime, director, studio, year, plot, album, + artist, genre, track, mvideoid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE MUSIC VIDEO ##### + else: + log.info("ADD mvideo itemid: %s - Title: %s" % (itemid, title)) + + # Add path + query = ' '.join(( + + "SELECT idPath", + "FROM path", + "WHERE strPath = ?" + )) + kodicursor.execute(query, (path,)) + try: + pathid = kodicursor.fetchone()[0] + except TypeError: + kodicursor.execute("select coalesce(max(idPath),0) from path") + pathid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT OR REPLACE INTO path( + idPath, strPath, strContent, strScraper, noUpdate) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (pathid, path, "musicvideos", "metadata.local", 1)) + + # Add the file + kodicursor.execute("select coalesce(max(idFile),0) from files") + fileid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO files( + idFile, idPath, strFilename, dateAdded) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (fileid, pathid, filename, dateadded)) + + # Create the musicvideo entry + query = ( + ''' + INSERT INTO musicvideo( + idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (mvideoid, fileid, title, runtime, director, studio, + year, plot, album, artist, genre, track)) + + # Create the reference in emby table + emby_db.addReference(itemid, mvideoid, "MusicVideo", "musicvideo", fileid, pathid, + checksum=checksum, mediafolderid=viewid) + + + # Process cast + people = item['People'] + artists = item['ArtistItems'] + for artist in artists: + artist['Type'] = "Artist" + people.extend(artists) + people = artwork.get_people_artwork(people) + self.kodi_db.addPeople(mvideoid, people, "musicvideo") + # Process genres + self.kodi_db.addGenres(mvideoid, genres, "musicvideo") + # Process artwork + artwork.add_artwork(artwork.get_all_artwork(item), mvideoid, "musicvideo", kodicursor) + # Process stream details + streams = API.get_media_streams() + self.kodi_db.addStreams(fileid, streams, runtime) + # Process studios + self.kodi_db.addStudios(mvideoid, studios, "musicvideo") + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite musicvideos") + self.kodi_db.addTags(mvideoid, tags, "musicvideo") + # Process playstates + resume = API.adjust_resume(userdata['Resume']) + total = round(float(runtime), 6) + self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + + return True + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.get_checksum() + userdata = API.get_userdata() + runtime = API.get_runtime() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + mvideoid = emby_dbitem[0] + fileid = emby_dbitem[1] + log.info( + "Update playstate for musicvideo: %s fileid: %s" + % (item['Name'], fileid)) + except TypeError: + return + + # Process favorite tags + if userdata['Favorite']: + self.kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo") + else: + self.kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo") + + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjust_resume(userdata['Resume']) + total = round(float(runtime), 6) + + self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove mvideoid, fileid, pathid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + mvideoid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + log.info("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid)) + except TypeError: + return + + # Remove artwork + query = ' '.join(( + + "SELECT url, type", + "FROM art", + "WHERE media_id = ?", + "AND media_type = 'musicvideo'" + )) + kodicursor.execute(query, (mvideoid,)) + for row in kodicursor.fetchall(): + + url = row[0] + imagetype = row[1] + if imagetype in ("poster", "fanart"): + artwork.delete_cached_artwork(url) + + kodicursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (mvideoid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + if self.direct_path: + kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,)) + self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,)) + + log.info("Deleted musicvideo %s from kodi database" % itemid) diff --git a/resources/lib/objects/tvshows.py b/resources/lib/objects/tvshows.py new file mode 100644 index 00000000..bbf22701 --- /dev/null +++ b/resources/lib/objects/tvshows.py @@ -0,0 +1,924 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import urllib +from ntpath import dirname +from datetime import datetime + +import api +import common +import downloadutils +import embydb_functions as embydb +import kodidb_functions as kodidb +from utils import window, settings, language as lang, catch_except + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class TVShows(common.Items): + + + def __init__(self, embycursor, kodicursor, pdialog=None): + + self.embycursor = embycursor + self.emby_db = embydb.Embydb_Functions(self.embycursor) + self.kodicursor = kodicursor + self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor) + self.pdialog = pdialog + + self.new_time = int(settings('newvideotime'))*1000 + + common.Items.__init__(self) + + def _get_func(self, item_type, action): + + if item_type == "Series": + actions = { + 'added': self.added, + 'update': self.add_update, + 'userdata': self.updateUserdata, + 'remove': self.remove + } + elif item_type == "Season": + actions = { + 'added': self.added_season, + 'update': self.add_updateSeason, + 'remove': self.remove + } + elif item_type == "Episode": + actions = { + 'added': self.added_episode, + 'update': self.add_updateEpisode, + 'userdata': self.updateUserdata, + 'remove': self.remove + } + else: + log.info("Unsupported item_type: %s", item_type) + actions = {} + + return actions.get(action) + + def compare_all(self): + # Pull the list of movies and boxsets in Kodi + pdialog = self.pdialog + views = self.emby_db.getView_byType('tvshows') + views += self.emby_db.getView_byType('mixed') + log.info("Media folders: %s" % views) + + # Pull the list of tvshows and episodes in Kodi + try: + all_koditvshows = dict(self.emby_db.get_checksum('Series')) + except ValueError: + all_koditvshows = {} + + log.info("all_koditvshows = %s", all_koditvshows) + + try: + all_kodiepisodes = dict(self.emby_db.get_checksum('Episode')) + except ValueError: + all_kodiepisodes = {} + + all_embytvshowsIds = set() + all_embyepisodesIds = set() + updatelist = [] + + + for view in views: + + if self.should_stop(): + return False + + # Get items per view + viewId = view['id'] + viewName = view['name'] + + if pdialog: + pdialog.update( + heading=lang(29999), + message="%s %s..." % (lang(33029), viewName)) + + all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog) + for embytvshow in all_embytvshows['Items']: + + if self.should_stop(): + return False + + API = api.API(embytvshow) + itemid = embytvshow['Id'] + all_embytvshowsIds.add(itemid) + + + if all_koditvshows.get(itemid) != API.get_checksum(): + # Only update if movie is not in Kodi or checksum is different + updatelist.append(itemid) + + log.info("TVShows to update for %s: %s" % (viewName, updatelist)) + embytvshows = self.emby.getFullItems(updatelist) + self.total = len(updatelist) + del updatelist[:] + + + if pdialog: + pdialog.update(heading="Processing %s / %s items" % (viewName, self.total)) + + self.count = 0 + for embytvshow in embytvshows: + # Process individual show + if self.should_stop(): + return False + + itemid = embytvshow['Id'] + title = embytvshow['Name'] + all_embytvshowsIds.add(itemid) + self.update_pdialog() + + self.add_update(embytvshow, view) + self.count += 1 + + else: + # Get all episodes in view + if pdialog: + pdialog.update( + heading=lang(29999), + message="%s %s..." % (lang(33030), viewName)) + + all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog) + for embyepisode in all_embyepisodes['Items']: + + if self.should_stop(): + return False + + API = api.API(embyepisode) + itemid = embyepisode['Id'] + all_embyepisodesIds.add(itemid) + if "SeriesId" in embyepisode: + all_embytvshowsIds.add(embyepisode['SeriesId']) + + if all_kodiepisodes.get(itemid) != API.get_checksum(): + # Only update if movie is not in Kodi or checksum is different + updatelist.append(itemid) + + log.info("Episodes to update for %s: %s" % (viewName, updatelist)) + embyepisodes = self.emby.getFullItems(updatelist) + self.total = len(updatelist) + del updatelist[:] + + self.count = 0 + for episode in embyepisodes: + + # Process individual episode + if self.should_stop(): + return False + self.title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) + self.add_updateEpisode(episode) + self.count += 1 + + ##### PROCESS DELETES ##### + + log.info("all_embytvshowsIds = %s " % all_embytvshowsIds) + + for koditvshow in all_koditvshows: + if koditvshow not in all_embytvshowsIds: + self.remove(koditvshow) + else: + log.info("TVShows compare finished.") + + for kodiepisode in all_kodiepisodes: + if kodiepisode not in all_embyepisodesIds: + self.remove(kodiepisode) + else: + log.info("Episodes compare finished.") + + return True + + + def added(self, items, total=None, view=None): + + for item in super(TVShows, self).added(items, total): + if self.add_update(item, view): + # Add episodes + all_episodes = self.emby.getEpisodesbyShow(item['Id']) + self.added_episode(all_episodes['Items']) + + def added_season(self, items, total=None, view=None): + + update = True if not self.total else False + + for item in super(TVShows, self).added(items, total, update): + self.title = "%s - %s" % (item.get('SeriesName', "Unknown"), self.title) + + if self.add_updateSeason(item): + # Add episodes + all_episodes = self.emby.getEpisodesbySeason(item['Id']) + self.added_episode(all_episodes['Items']) + + def added_episode(self, items, total=None, view=None): + + update = True if not self.total else False + + for item in super(TVShows, self).added(items, total, update): + self.title = "%s - %s" % (item.get('SeriesName', "Unknown"), self.title) + + if self.add_updateEpisode(item): + self.content_pop() + + @catch_except() + def add_update(self, item, view=None): + # Process single tvshow + kodicursor = self.kodicursor + emby = self.emby + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): + log.info("Skipping empty show: %s" % item['Name']) + return + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + force_episodes = False + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + showid = emby_dbitem[0] + pathid = emby_dbitem[2] + log.info("showid: %s pathid: %s" % (showid, pathid)) + + except TypeError: + update_item = False + log.debug("showid: %s not found." % itemid) + kodicursor.execute("select coalesce(max(idShow),0) from tvshow") + showid = kodicursor.fetchone()[0] + 1 + + else: + # Verification the item is still in Kodi + query = "SELECT * FROM tvshow WHERE idShow = ?" + kodicursor.execute(query, (showid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + log.info("showid: %s missing from Kodi, repairing the entry." % showid) + # Force re-add episodes after the show is re-created. + force_episodes = True + + + if view is None: + # Get view tag from emby + viewtag, viewid, mediatype = emby.getView_embyId(itemid) + log.debug("View tag found: %s" % viewtag) + else: + viewtag = view['name'] + viewid = view['id'] + + # fileId information + checksum = API.get_checksum() + dateadded = API.get_date_created() + userdata = API.get_userdata() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + genres = item['Genres'] + title = item['Name'] + plot = API.get_overview() + rating = item.get('CommunityRating') + premieredate = API.get_premiere_date() + tvdb = API.get_provider('Tvdb') + sorttitle = item['SortName'] + mpaa = API.get_mpaa() + genre = " / ".join(genres) + studios = API.get_studios() + studio = " / ".join(studios) + + # Verify series pooling + if not update_item and tvdb: + query = "SELECT idShow FROM tvshow WHERE C12 = ?" + kodicursor.execute(query, (tvdb,)) + try: + temp_showid = kodicursor.fetchone()[0] + except TypeError: + pass + else: + emby_other = emby_db.getItem_byKodiId(temp_showid, "tvshow") + if emby_other and viewid == emby_other[2]: + log.info("Applying series pooling for %s", title) + emby_other_item = emby_db.getItem_byId(emby_other[0]) + showid = emby_other_item[0] + pathid = emby_other_item[2] + log.info("showid: %s pathid: %s" % (showid, pathid)) + # Create the reference in emby table + emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, + checksum=checksum, mediafolderid=viewid) + update_item = True + + + ##### GET THE FILE AND PATH ##### + playurl = API.get_file_path() + + if self.direct_path: + # Direct paths is set the Kodi way + if "\\" in playurl: + # Local path + path = "%s\\" % playurl + toplevelpath = "%s\\" % dirname(dirname(path)) + else: + # Network path + path = "%s/" % playurl + toplevelpath = "%s/" % dirname(dirname(path)) + + if not self.path_validation(path): + return False + + window('emby_pathverified', value="true") + else: + # Set plugin path + toplevelpath = "plugin://plugin.video.emby.tvshows/" + path = "%s%s/" % (toplevelpath, itemid) + + + ##### UPDATE THE TVSHOW ##### + if update_item: + log.info("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title)) + + # Update the tvshow entry + query = ' '.join(( + + "UPDATE tvshow", + "SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,", + "c12 = ?, c13 = ?, c14 = ?, c15 = ?", + "WHERE idShow = ?" + )) + kodicursor.execute(query, (title, plot, rating, premieredate, genre, title, + tvdb, mpaa, studio, sorttitle, showid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE TVSHOW ##### + else: + log.info("ADD tvshow itemid: %s - Title: %s" % (itemid, title)) + + # Add top path + toppathid = self.kodi_db.addPath(toplevelpath) + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid)) + + # Add path + pathid = self.kodi_db.addPath(path) + + # Create the tvshow entry + query = ( + ''' + INSERT INTO tvshow( + idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (showid, title, plot, rating, premieredate, genre, + title, tvdb, mpaa, studio, sorttitle)) + + # Link the path + query = "INSERT INTO tvshowlinkpath(idShow, idPath) values(?, ?)" + kodicursor.execute(query, (showid, pathid)) + + # Create the reference in emby table + emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, + checksum=checksum, mediafolderid=viewid) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, None, None, 1, pathid)) + + # Process cast + people = artwork.get_people_artwork(item['People']) + self.kodi_db.addPeople(showid, people, "tvshow") + # Process genres + self.kodi_db.addGenres(showid, genres, "tvshow") + # Process artwork + artwork.add_artwork(artwork.get_all_artwork(item), showid, "tvshow", kodicursor) + # Process studios + self.kodi_db.addStudios(showid, studios, "tvshow") + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite tvshows") + self.kodi_db.addTags(showid, tags, "tvshow") + # Process seasons + all_seasons = emby.getSeasons(itemid) + for season in all_seasons['Items']: + self.add_updateSeason(season, showid=showid) + else: + # Finally, refresh the all season entry + seasonid = self.kodi_db.addSeason(showid, -1) + # Process artwork + artwork.add_artwork(artwork.get_all_artwork(item), seasonid, "season", kodicursor) + + if force_episodes: + # We needed to recreate the show entry. Re-add episodes now. + log.info("Repairing episodes for showid: %s %s" % (showid, title)) + all_episodes = emby.getEpisodesbyShow(itemid) + self.added_episode(all_episodes['Items'], None) + + return True + + def add_updateSeason(self, item, showid=None): + + kodicursor = self.kodicursor + emby_db = self.emby_db + artwork = self.artwork + + seasonnum = item.get('IndexNumber', 1) + + if showid is None: + try: + seriesId = item['SeriesId'] + showid = emby_db.getItem_byId(seriesId)[0] + except KeyError: + return + except TypeError: + # Show is missing, update show instead. + show = self.emby.getItem(seriesId) + self.add_update(show) + return + + seasonid = self.kodi_db.addSeason(showid, seasonnum, item['Name']) + + if item['LocationType'] != "Virtual": + # Create the reference in emby table + emby_db.addReference(item['Id'], seasonid, "Season", "season", parentid=showid) + + # Process artwork + artwork.add_artwork(artwork.get_all_artwork(item), seasonid, "season", kodicursor) + + return True + + @catch_except() + def add_updateEpisode(self, item): + # Process single episode + kodicursor = self.kodicursor + emby_db = self.emby_db + artwork = self.artwork + API = api.API(item) + + if item.get('LocationType') == "Virtual": # TODO: Filter via api instead + log.info("Skipping virtual episode: %s", item['Name']) + return + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + episodeid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + log.info("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid)) + + except TypeError: + update_item = False + log.debug("episodeid: %s not found." % itemid) + # episodeid + kodicursor.execute("select coalesce(max(idEpisode),0) from episode") + episodeid = kodicursor.fetchone()[0] + 1 + + else: + # Verification the item is still in Kodi + query = "SELECT * FROM episode WHERE idEpisode = ?" + kodicursor.execute(query, (episodeid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + log.info("episodeid: %s missing from Kodi, repairing the entry." % episodeid) + + # fileId information + checksum = API.get_checksum() + dateadded = API.get_date_created() + userdata = API.get_userdata() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + people = API.get_people() + writer = " / ".join(people['Writer']) + director = " / ".join(people['Director']) + title = item['Name'] + plot = API.get_overview() + rating = item.get('CommunityRating') + runtime = API.get_runtime() + premieredate = API.get_premiere_date() + + # episode details + try: + seriesId = item['SeriesId'] + except KeyError: + # Missing seriesId, skip + log.error("Skipping: %s. SeriesId is missing." % itemid) + return False + + season = item.get('ParentIndexNumber') + episode = item.get('IndexNumber', -1) + + if season is None: + if item.get('AbsoluteEpisodeNumber'): + # Anime scenario + season = 1 + episode = item['AbsoluteEpisodeNumber'] + else: + season = -1 if "Specials" not in item['Path'] else 0 + + # Specials ordering within season + if item.get('AirsAfterSeasonNumber'): + airsBeforeSeason = item['AirsAfterSeasonNumber'] + airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering + else: + airsBeforeSeason = item.get('AirsBeforeSeasonNumber') + airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber') + + # Append multi episodes to title + if item.get('IndexNumberEnd'): + title = "| %02d | %s" % (item['IndexNumberEnd'], title) + + # Get season id + show = emby_db.getItem_byId(seriesId) + try: + showid = show[0] + except TypeError: + # Show is missing from database + show = self.emby.getItem(seriesId) + self.add_update(show) + show = emby_db.getItem_byId(seriesId) + try: + showid = show[0] + except TypeError: + log.error("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) + return False + + seasonid = self.kodi_db.addSeason(showid, season) + + + ##### GET THE FILE AND PATH ##### + playurl = API.get_file_path() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.direct_path: + # Direct paths is set the Kodi way + if not self.path_validation(playurl): + return False + + path = playurl.replace(filename, "") + window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': episodeid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE EPISODE ##### + if update_item: + log.info("UPDATE episode itemid: %s - Title: %s" % (itemid, title)) + + # Update the movie entry + if self.kodi_version in (16, 17): + # Kodi Jarvis, Krypton + query = ' '.join(( + + "UPDATE episode", + "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", + "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?, idShow = ?", + "WHERE idEpisode = ?" + )) + kodicursor.execute(query, (title, plot, rating, writer, premieredate, + runtime, director, season, episode, title, airsBeforeSeason, + airsBeforeEpisode, seasonid, showid, episodeid)) + else: + query = ' '.join(( + + "UPDATE episode", + "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", + "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idShow = ?", + "WHERE idEpisode = ?" + )) + kodicursor.execute(query, (title, plot, rating, writer, premieredate, + runtime, director, season, episode, title, airsBeforeSeason, + airsBeforeEpisode, showid, episodeid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + # Update parentid reference + emby_db.updateParentId(itemid, seasonid) + + ##### OR ADD THE EPISODE ##### + else: + log.info("ADD episode itemid: %s - Title: %s" % (itemid, title)) + + # Add path + pathid = self.kodi_db.addPath(path) + # Add the file + fileid = self.kodi_db.addFile(filename, pathid) + + # Create the episode entry + if self.kodi_version in (16, 17): + # Kodi Jarvis, Krypton + query = ( + ''' + INSERT INTO episode( + idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, + idShow, c15, c16, idSeason) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer, + premieredate, runtime, director, season, episode, title, showid, + airsBeforeSeason, airsBeforeEpisode, seasonid)) + else: + query = ( + ''' + INSERT INTO episode( + idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, + idShow, c15, c16) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer, + premieredate, runtime, director, season, episode, title, showid, + airsBeforeSeason, airsBeforeEpisode)) + + # Create the reference in emby table + emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid, + seasonid, checksum) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, None, None, 1, pathid)) + + # Update the file + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (pathid, filename, dateadded, fileid)) + + # Process cast + people = artwork.get_people_artwork(item['People']) + self.kodi_db.addPeople(episodeid, people, "episode") + # Process artwork + artworks = artwork.get_all_artwork(item) + artwork.add_update_art(artworks['Primary'], episodeid, "episode", "thumb", kodicursor) + # Process stream details + streams = API.get_media_streams() + self.kodi_db.addStreams(fileid, streams, runtime) + # Process playstates + resume = API.adjust_resume(userdata['Resume']) + total = round(float(runtime), 6) + self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + if not self.direct_path and resume: + # Create additional entry for widgets. This is only required for plugin/episode. + temppathid = self.kodi_db.getPath("plugin://plugin.video.emby.tvshows/") + tempfileid = self.kodi_db.addFile(filename, temppathid) + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) + self.kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed) + + return True + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.get_checksum() + userdata = API.get_userdata() + runtime = API.get_runtime() + dateadded = API.get_date_created() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + fileid = emby_dbitem[1] + mediatype = emby_dbitem[4] + log.info( + "Update playstate for %s: %s fileid: %s" + % (mediatype, item['Name'], fileid)) + except TypeError: + return + + # Process favorite tags + if mediatype == "tvshow": + if userdata['Favorite']: + self.kodi_db.addTag(kodiid, "Favorite tvshows", "tvshow") + else: + self.kodi_db.removeTag(kodiid, "Favorite tvshows", "tvshow") + elif mediatype == "episode": + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjust_resume(userdata['Resume']) + total = round(float(runtime), 6) + + log.debug("%s New resume point: %s" % (itemid, resume)) + + self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + if not self.direct_path and not resume: + # Make sure there's no other bookmarks created by widget. + filename = self.kodi_db.getFile(fileid) + self.kodi_db.removeFile("plugin://plugin.video.emby.tvshows/", filename) + + if not self.direct_path and resume: + # Create additional entry for widgets. This is only required for plugin/episode. + filename = self.kodi_db.getFile(fileid) + temppathid = self.kodi_db.getPath("plugin://plugin.video.emby.tvshows/") + tempfileid = self.kodi_db.addFile(filename, temppathid) + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + self.kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) + self.kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed) + + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove showid, fileid, pathid, emby reference + emby_db = self.emby_db + embycursor = self.embycursor + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + parentid = emby_dbitem[3] + mediatype = emby_dbitem[4] + log.info("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid)) + except TypeError: + return + + ##### PROCESS ITEM ##### + + # Remove the emby reference + emby_db.removeItem(itemid) + + + ##### IF EPISODE ##### + + if mediatype == "episode": + # Delete kodi episode and file, verify season and tvshow + self.removeEpisode(kodiid, fileid) + + # Season verification + season = emby_db.getItem_byKodiId(parentid, "season") + try: + showid = season[1] + except TypeError: + return + + season_episodes = emby_db.getItem_byParentId(parentid, "episode") + if not season_episodes: + self.removeSeason(parentid) + emby_db.removeItem(season[0]) + + # Show verification + show = emby_db.getItem_byKodiId(showid, "tvshow") + query = ' '.join(( + + "SELECT totalCount", + "FROM tvshowcounts", + "WHERE idShow = ?" + )) + kodicursor.execute(query, (showid,)) + result = kodicursor.fetchone() + if result and result[0] is None: + # There's no episodes left, delete show and any possible remaining seasons + seasons = emby_db.getItem_byParentId(showid, "season") + for season in seasons: + self.removeSeason(season[1]) + else: + # Delete emby season entries + emby_db.removeItems_byParentId(showid, "season") + self.removeShow(showid) + emby_db.removeItem(show[0]) + + ##### IF TVSHOW ##### + + elif mediatype == "tvshow": + # Remove episodes, seasons, tvshow + seasons = emby_db.getItem_byParentId(kodiid, "season") + for season in seasons: + seasonid = season[1] + season_episodes = emby_db.getItem_byParentId(seasonid, "episode") + for episode in season_episodes: + self.removeEpisode(episode[1], episode[2]) + else: + # Remove emby episodes + emby_db.removeItems_byParentId(seasonid, "episode") + else: + # Remove emby seasons + emby_db.removeItems_byParentId(kodiid, "season") + + # Remove tvshow + self.removeShow(kodiid) + + ##### IF SEASON ##### + + elif mediatype == "season": + # Remove episodes, season, verify tvshow + season_episodes = emby_db.getItem_byParentId(kodiid, "episode") + for episode in season_episodes: + self.removeEpisode(episode[1], episode[2]) + else: + # Remove emby episodes + emby_db.removeItems_byParentId(kodiid, "episode") + + # Remove season + self.removeSeason(kodiid) + + # Show verification + seasons = emby_db.getItem_byParentId(parentid, "season") + if not seasons: + # There's no seasons, delete the show + self.removeShow(parentid) + emby_db.removeItem_byKodiId(parentid, "tvshow") + + log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) + + def removeShow(self, kodiid): + + kodicursor = self.kodicursor + self.artwork.delete_artwork(kodiid, "tvshow", kodicursor) + kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,)) + log.debug("Removed tvshow: %s." % kodiid) + + def removeSeason(self, kodiid): + + kodicursor = self.kodicursor + + self.artwork.delete_artwork(kodiid, "season", kodicursor) + kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) + log.debug("Removed season: %s." % kodiid) + + def removeEpisode(self, kodiid, fileid): + + kodicursor = self.kodicursor + + self.artwork.delete_artwork(kodiid, "episode", kodicursor) + kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + log.debug("Removed episode: %s." % kodiid) diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 867be38c..3fe6c6c2 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -186,7 +186,7 @@ class Read_EmbyServer(): url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json" return self.doUtils(url, parameters=params) - def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): + def getSection(self, parentid, itemtype=None, sortby="SortName", artist_id=None, basic=False, dialog=None): items = { @@ -199,6 +199,7 @@ class Read_EmbyServer(): params = { 'ParentId': parentid, + 'ArtistIds': artist_id, 'IncludeItemTypes': itemtype, 'CollapseBoxSetItems': False, 'IsVirtualUnaired': False, @@ -225,6 +226,7 @@ class Read_EmbyServer(): params = { 'ParentId': parentid, + 'ArtistIds': artist_id, 'IncludeItemTypes': itemtype, 'CollapseBoxSetItems': False, 'IsVirtualUnaired': False, @@ -435,10 +437,11 @@ class Read_EmbyServer(): return self.getSection(seasonId, "Episode") - def getArtists(self, dialog=None): + def getArtists(self, parent_id=None, dialog=None): items = { + 'ParentId': parent_id, 'Items': [], 'TotalRecordCount': 0 } @@ -459,13 +462,14 @@ class Read_EmbyServer(): log.debug("%s:%s Failed to retrieve the server response." % (url, params)) else: - index = 1 + index = 0 jump = self.limitIndex while index < total: # Get items by chunk to increase retrieval speed at scale params = { + 'ParentId': parent_id, 'Recursive': True, 'IsVirtualUnaired': False, 'IsMissing': False, @@ -495,7 +499,7 @@ class Read_EmbyServer(): def getAlbumsbyArtist(self, artistId): - return self.getSection(artistId, "MusicAlbum", sortby="DateCreated") + return self.getSection(None, "MusicAlbum", sortby="DateCreated", artist_id=artistId) def getSongs(self, basic=False, dialog=None): diff --git a/resources/lib/utils.py b/resources/lib/utils.py index f24941db..01413d38 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -109,6 +109,15 @@ class JSONRPC(object): ################################################################################################# # Database related methods +def should_stop(): + # Checkpoint during the syncing process + if xbmc.Monitor().abortRequested(): + return True + elif window('emby_shouldStop') == "true": + return True + else: # Keep going + return False + def kodiSQL(media_type="video"): if media_type == "emby": @@ -309,6 +318,21 @@ def indent(elem, level=0): if level and (not elem.tail or not elem.tail.strip()): elem.tail = i +def catch_except(errors=(Exception, ), default_value=False): + # Will wrap method with try/except and print parameters for easier debugging + def decorator(func): + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except errors as error: + log.exception(error) + log.error("function: %s \n args: %s \n kwargs: %s", + func.__name__, args, kwargs) + return default_value + + return wrapper + return decorator + def profiling(sortby="cumulative"): # Will print results to Kodi log def decorator(func):