# -*- coding: utf-8 -*- ################################################################################################## import logging from datetime import datetime import api import embydb_functions as embydb import musicutils import _kodi_music from _common import Items, catch_except from utils import window, settings, language as lang ################################################################################################## log = logging.getLogger("EMBY."+__name__) ################################################################################################## class Music(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 = _kodi_music.KodiMusic(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) 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 views = self.emby_db.getView_byType('music') for view in views: # Process artists self.compare_artists(view) # Process albums self.compare_albums() # Process songs self.compare_songs() return True def compare_artists(self, view): all_embyartistsIds = set() update_list = list() if self.pdialog: self.pdialog.update(heading=lang(29999), message="%s Artists..." % lang(33031)) artists = dict(self.emby_db.get_checksum('MusicArtist')) album_artists = dict(self.emby_db.get_checksum('AlbumArtist')) emby_artists = self.emby.getArtists(view['id'], dialog=self.pdialog) for item in emby_artists['Items']: if self.should_stop(): return False item_id = item['Id'] API = api.API(item) all_embyartistsIds.add(item_id) if item_id in artists: if artists[item_id] != API.get_checksum(): # Only update if artist is not in Kodi or checksum is different update_list.append(item_id) elif album_artists.get(item_id) != API.get_checksum(): # Only update if artist is not in Kodi or checksum is different update_list.append(item_id) #compare_to.pop(item_id, None) log.info("Update for Artist: %s", update_list) emby_items = self.emby.getFullItems(update_list) total = len(update_list) if self.pdialog: self.pdialog.update(heading="Processing Artists / %s items" % total) # Process additions and updates if emby_items: self.process_all("MusicArtist", "update", emby_items, total) # Process removals for artist in artists: if artist not in all_embyartistsIds and artists[artist] is not None: self.remove(artist) def compare_albums(self): if self.pdialog: self.pdialog.update(heading=lang(29999), message="%s Albums..." % lang(33031)) albums = dict(self.emby_db.get_checksum('MusicAlbum')) emby_albums = self.emby.getAlbums(basic=True, dialog=self.pdialog) return self.compare("MusicAlbum", emby_albums['Items'], albums) def compare_songs(self): if self.pdialog: self.pdialog.update(heading=lang(29999), message="%s Songs..." % lang(33031)) songs = dict(self.emby_db.get_checksum('Audio')) emby_songs = self.emby.getSongs(basic=True, dialog=self.pdialog) return self.compare("Audio", emby_songs['Items'], songs) 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(self.title) @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) else: pass ##### 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 = "%s" % thumb if backdrops: fanart = "%s" % 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.get_artist(name, musicBrainzId) # Create the reference in emby table emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum) # Process the artist if self.kodi_version > 15: self.kodi_db.update_artist_16(genres, bio, thumb, fanart, lastScraped, artistid) else: self.kodi_db.update_artist(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 = 0 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 = "%s" % 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.get_album(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 self.kodi_db.update_album_17(artistname, year, genre, bio, thumb, rating, lastScraped, "album", albumid) elif self.kodi_version == 16: # Kodi Jarvis self.kodi_db.update_album(artistname, year, genre, bio, thumb, rating, lastScraped, "album", albumid) elif self.kodi_version == 15: # Kodi Isengard self.kodi_db.update_album_15(artistname, year, genre, bio, thumb, rating, lastScraped, dateadded, "album", albumid) else: # TODO: Remove Helix code when Krypton is RC self.kodi_db.update_album_14(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. self.kodi_db.update_artist_name(artistid, artistname) # Add artist to album self.kodi_db.link_artist(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 self.kodi_db.add_discography(artistid, name, year) # Add genres self.kodi_db.add_genres(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) songid = self.kodi_db.create_entry_song() ##### 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 = 0 #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 self.kodi_db.update_path(pathid, path) # Update the song entry self.kodi_db.update_song(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.add_path(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.get_album(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.") album_id = self.kodi_db.create_entry_album() if self.kodi_version == 16: self.kodi_db.add_single(albumid, genre, year, "single") elif self.kodi_version == 15: self.kodi_db.add_single_15(albumid, genre, year, dateadded, "single") else: # TODO: Remove Helix code when Krypton is RC self.kodi_db.add_single_14(albumid, genre, year, dateadded) # Create the song entry self.kodi_db.add_song(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 self.kodi_db.link_song_album(songid, albumid, track, title, duration) # Create default role if self.kodi_version > 16: self.kodi_db.add_role() # 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] if artist_edb else None except Exception: artistid = None if artistid: # Link song to artist self.kodi_db.link_song_artist(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: # Link artist to album self.kodi_db.link_artist(artistid, albumid, artist_name) # Update discography if item.get('Album'): self.kodi_db.add_discography(artistid, item['Album'], 0) # Artist names album_artists = " / ".join(album_artists) self.kodi_db.get_album_artist(albumid, album_artists) # Add genres self.kodi_db.add_genres(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() rating = 0 # 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) self.kodi_db.rate_song(playcount, dateplayed, rating, kodiid) emby_db.updateReference(itemid, checksum) def remove(self, itemid): # Remove kodiid, fileid, pathid, emby reference emby_db = self.emby_db 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, kodi_id): self.artwork.delete_artwork(kodi_id, "song", self.kodicursor) self.kodi_db.remove_song(kodi_id) def removeAlbum(self, kodi_id): self.artwork.delete_artwork(kodi_id, "album", self.kodicursor) self.kodi_db.remove_album(kodi_id) def removeArtist(self, kodi_id): self.artwork.delete_artwork(kodi_id, "artist", self.kodicursor) self.kodi_db.remove_artist(kodi_id)