# -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import datetime from ..database import jellyfin_db, queries as QUEM from ..helper import api, stop, validate, jellyfin_item, values, Local, LazyLogger from ..helper.utils import find_library from ..helper.exceptions import PathValidationException from .obj import Objects from .kodi import Music as KodiDb, queries_music as QU ################################################################################################## LOG = LazyLogger(__name__) ################################################################################################## class Music(KodiDb): def __init__(self, server, jellyfindb, musicdb, direct_path, library=None): self.server = server self.jellyfin = jellyfindb self.music = musicdb self.direct_path = direct_path self.jellyfin_db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) self.objects = Objects() self.item_ids = [] self.library = library KodiDb.__init__(self, musicdb.cursor) @stop @jellyfin_item def artist(self, item, e_item): """If item does not exist, entry will be added. If item exists, entry will be updated. """ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[ "address" ] API = api.API(item, server_address) obj = self.objects.map(item, "Artist") update = True try: obj["ArtistId"] = e_item[0] obj["LibraryId"] = e_item[6] obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"]) except TypeError: update = False library = self.library or find_library(self.server, item) if not library: # This item doesn't belong to a whitelisted library return obj["ArtistId"] = None obj["LibraryId"] = library["Id"] obj["LibraryName"] = library["Name"] LOG.debug("ArtistId %s not found", obj["Id"]) else: if self.validate_artist(*values(obj, QU.get_artist_by_id_obj)) is None: update = False LOG.info( "ArtistId %s missing from kodi. repairing the entry.", obj["ArtistId"], ) obj["LastScraped"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") obj["ArtistType"] = "MusicArtist" obj["Genre"] = " / ".join(obj["Genres"] or []) obj["Bio"] = API.get_overview(obj["Bio"]) obj["Artwork"] = API.get_all_artwork( self.objects.map(item, "ArtworkMusic"), True ) obj["Thumb"] = obj["Artwork"]["Primary"] obj["Backdrops"] = obj["Artwork"]["Backdrop"] or "" if obj["Thumb"]: obj["Thumb"] = "%s" % obj["Thumb"] if obj["Backdrops"]: obj["Backdrops"] = "%s" % obj["Backdrops"][0] if update: self.artist_update(obj) else: self.artist_add(obj) self.update( obj["Genre"], obj["Bio"], obj["Thumb"], obj["Backdrops"], obj["LastScraped"], obj["ArtistId"], ) self.artwork.add(obj["Artwork"], obj["ArtistId"], "artist") self.item_ids.append(obj["Id"]) def artist_add(self, obj): """Add object to kodi. safety checks: It looks like Jellyfin supports the same artist multiple times. Kodi doesn't allow that. In case that happens we just merge the artist entries. """ obj["ArtistId"] = self.get(*values(obj, QU.get_artist_obj)) self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_artist_obj)) LOG.debug("ADD artist [%s] %s: %s", obj["ArtistId"], obj["Name"], obj["Id"]) def artist_update(self, obj): """Update object to kodi.""" self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) LOG.debug("UPDATE artist [%s] %s: %s", obj["ArtistId"], obj["Name"], obj["Id"]) @stop @jellyfin_item def album(self, item, e_item): """Update object to kodi.""" server_address = self.server.auth.get_server_info(self.server.auth.server_id)[ "address" ] API = api.API(item, server_address) obj = self.objects.map(item, "Album") update = True try: obj["AlbumId"] = e_item[0] obj["LibraryId"] = e_item[6] obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"]) except TypeError: update = False library = self.library or find_library(self.server, item) if not library: # This item doesn't belong to a whitelisted library return obj["AlbumId"] = None obj["LibraryId"] = library["Id"] obj["LibraryName"] = library["Name"] LOG.debug("AlbumId %s not found", obj["Id"]) else: if self.validate_album(*values(obj, QU.get_album_by_id_obj)) is None: update = False LOG.info( "AlbumId %s missing from kodi. repairing the entry.", obj["AlbumId"] ) obj["Rating"] = 0 obj["LastScraped"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") obj["Genres"] = obj["Genres"] or [] obj["Genre"] = " / ".join(obj["Genres"]) obj["Bio"] = API.get_overview(obj["Bio"]) obj["Artists"] = " / ".join(obj["Artists"] or []) obj["Artwork"] = API.get_all_artwork( self.objects.map(item, "ArtworkMusic"), True ) obj["Thumb"] = obj["Artwork"]["Primary"] obj["DateAdded"] = item.get("DateCreated") if obj["DateAdded"]: obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ") if obj["Thumb"]: obj["Thumb"] = "%s" % obj["Thumb"] if update: self.album_update(obj) else: self.album_add(obj) self.artist_link(obj) self.artist_discography(obj) self.update_album(*values(obj, QU.update_album_obj)) self.add_genres(*values(obj, QU.add_genres_obj)) self.artwork.add(obj["Artwork"], obj["AlbumId"], "album") self.item_ids.append(obj["Id"]) def album_add(self, obj): """Add object to kodi.""" if self.version_id >= 82: obj_values = values(obj, QU.get_album_obj82) else: obj_values = values(obj, QU.get_album_obj) obj["AlbumId"] = self.get_album(*obj_values) self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_album_obj)) LOG.debug("ADD album [%s] %s: %s", obj["AlbumId"], obj["Title"], obj["Id"]) def album_update(self, obj): """Update object to kodi.""" self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) LOG.debug("UPDATE album [%s] %s: %s", obj["AlbumId"], obj["Title"], obj["Id"]) def artist_discography(self, obj): """Update the artist's discography.""" for artist in obj["ArtistItems"] or []: temp_obj = dict(obj) temp_obj["Id"] = artist["Id"] temp_obj["AlbumId"] = obj["Id"] try: temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except TypeError: continue self.add_discography(*values(temp_obj, QU.update_discography_obj)) self.jellyfin_db.update_parent_id( *values(temp_obj, QUEM.update_parent_album_obj) ) def artist_link(self, obj): """Assign main artists to album. Artist does not exist in jellyfin database, create the reference. """ for artist in obj["AlbumArtists"] or []: temp_obj = dict(obj) temp_obj["Name"] = artist["Name"] temp_obj["Id"] = artist["Id"] try: temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except TypeError: try: self.artist(self.server.jellyfin.get_item(temp_obj["Id"])) temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except Exception as error: LOG.exception(error) continue self.update_artist_name(*values(temp_obj, QU.update_artist_name_obj)) self.link(*values(temp_obj, QU.update_link_obj)) self.item_ids.append(temp_obj["Id"]) @stop @jellyfin_item def song(self, item, e_item): """Update object to kodi.""" server_address = self.server.auth.get_server_info(self.server.auth.server_id)[ "address" ] API = api.API(item, server_address) obj = self.objects.map(item, "Song") update = True try: obj["SongId"] = e_item[0] obj["PathId"] = e_item[2] obj["AlbumId"] = e_item[3] obj["LibraryId"] = e_item[6] obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"]) except TypeError: update = False library = self.library or find_library(self.server, item) if not library: # This item doesn't belong to a whitelisted library return obj["SongId"] = self.create_entry_song() obj["LibraryId"] = library["Id"] obj["LibraryName"] = library["Name"] LOG.debug("SongId %s not found", obj["Id"]) else: if self.validate_song(*values(obj, QU.get_song_by_id_obj)) is None: update = False LOG.info( "SongId %s missing from kodi. repairing the entry.", obj["SongId"] ) self.get_song_path_filename(obj, API) obj["Rating"] = 0 obj["Genres"] = obj["Genres"] or [] obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"]) obj["Runtime"] = (obj["Runtime"] or 0) / 10000000.0 obj["Genre"] = " / ".join(obj["Genres"]) obj["Artists"] = " / ".join(obj["Artists"] or []) obj["AlbumArtists"] = obj["AlbumArtists"] or [] obj["Index"] = obj["Index"] or 0 obj["Disc"] = obj["Disc"] or 1 obj["EmbedCover"] = False obj["Comment"] = API.get_overview(obj["Comment"]) obj["Artwork"] = API.get_all_artwork( self.objects.map(item, "ArtworkMusic"), True ) if obj["DateAdded"]: obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ") if obj["DatePlayed"]: obj["DatePlayed"] = Local(obj["DatePlayed"]).split(".")[0].replace("T", " ") obj["Index"] = obj["Disc"] * 2**16 + obj["Index"] if update: self.song_update(obj) else: self.song_add(obj) self.link_song_album(*values(obj, QU.update_song_album_obj)) self.add_role(*values(obj, QU.update_role_obj)) # defaultt role self.song_artist_link(obj) self.song_artist_discography(obj) obj["strAlbumArtists"] = " / ".join(obj["AlbumArtists"]) self.get_album_artist(*values(obj, QU.get_album_artist_obj)) self.add_genres(*values(obj, QU.update_genre_song_obj)) self.artwork.add(obj["Artwork"], obj["SongId"], "song") self.item_ids.append(obj["Id"]) if obj["SongAlbumId"] is None: self.artwork.add(obj["Artwork"], obj["AlbumId"], "album") return not update def song_add(self, obj): """Add object to kodi. Verify if there's an album associated. If no album found, create a single's album """ obj["PathId"] = self.add_path(obj["Path"]) try: obj["AlbumId"] = self.jellyfin_db.get_item_by_id( *values(obj, QUEM.get_item_song_obj) )[0] except TypeError: try: if obj["SongAlbumId"] is None: raise TypeError("No album id found associated?") self.album(self.server.jellyfin.get_item(obj["SongAlbumId"])) obj["AlbumId"] = self.jellyfin_db.get_item_by_id( *values(obj, QUEM.get_item_song_obj) )[0] except TypeError: self.single(obj) self.add_song(*values(obj, QU.add_song_obj)) self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_song_obj)) LOG.debug( "ADD song [%s/%s/%s] %s: %s", obj["PathId"], obj["AlbumId"], obj["SongId"], obj["Id"], obj["Title"], ) def song_update(self, obj): """Update object to kodi.""" self.update_path(*values(obj, QU.update_path_obj)) self.update_song(*values(obj, QU.update_song_obj)) self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) LOG.debug( "UPDATE song [%s/%s/%s] %s: %s", obj["PathId"], obj["AlbumId"], obj["SongId"], obj["Id"], obj["Title"], ) def get_song_path_filename(self, obj, api): """Get the path and filename and build it into protocol://path""" obj["Path"] = api.get_file_path(obj["Path"]) obj["Filename"] = ( obj["Path"].rsplit("\\", 1)[1] if "\\" in obj["Path"] else obj["Path"].rsplit("/", 1)[1] ) if self.direct_path: if not validate(obj["Path"]): raise PathValidationException("Failed to validate path. User stopped.") obj["Path"] = obj["Path"].replace(obj["Filename"], "") else: server_address = self.server.auth.get_server_info( self.server.auth.server_id )["address"] obj["Path"] = "%s/Audio/%s/" % (server_address, obj["Id"]) obj["Filename"] = "stream.%s?static=true" % obj["Container"] def song_artist_discography(self, obj): """Update the artist's discography.""" artists = [] for artist in obj["AlbumArtists"] or []: temp_obj = dict(obj) temp_obj["Name"] = artist["Name"] temp_obj["Id"] = artist["Id"] artists.append(temp_obj["Name"]) try: temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except TypeError: try: self.artist(self.server.jellyfin.get_item(temp_obj["Id"])) temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except Exception as error: LOG.exception(error) continue self.link(*values(temp_obj, QU.update_link_obj)) self.item_ids.append(temp_obj["Id"]) if obj["Album"]: temp_obj["Title"] = obj["Album"] temp_obj["Year"] = 0 self.add_discography(*values(temp_obj, QU.update_discography_obj)) obj["AlbumArtists"] = artists def song_artist_link(self, obj): """Assign main artists to song. Artist does not exist in jellyfin database, create the reference. """ for index, artist in enumerate(obj["ArtistItems"] or []): temp_obj = dict(obj) temp_obj["Name"] = artist["Name"] temp_obj["Id"] = artist["Id"] temp_obj["Index"] = index try: temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except TypeError: try: self.artist(self.server.jellyfin.get_item(temp_obj["Id"])) temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id( *values(temp_obj, QUEM.get_item_obj) )[0] except Exception as error: LOG.exception(error) continue self.link_song_artist(*values(temp_obj, QU.update_song_artist_obj)) self.item_ids.append(temp_obj["Id"]) def single(self, obj): obj["AlbumId"] = self.create_entry_album() self.add_single(*values(obj, QU.add_single_obj)) @stop @jellyfin_item def userdata(self, item, e_item): """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks Poster with progress bar """ obj = self.objects.map(item, "SongUserData") try: obj["KodiId"] = e_item[0] obj["Media"] = e_item[4] except TypeError: return obj["Rating"] = 0 if obj["Media"] == "song": if obj["DatePlayed"]: obj["DatePlayed"] = ( Local(obj["DatePlayed"]).split(".")[0].replace("T", " ") ) self.rate_song(*values(obj, QU.update_song_rating_obj)) self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) LOG.debug( "USERDATA %s [%s] %s: %s", obj["Media"], obj["KodiId"], obj["Id"], obj["Title"], ) @stop @jellyfin_item def remove(self, item_id, e_item): """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks Poster with progress bar This should address single song scenario, where server doesn't actually create an album for the song. """ obj = {"Id": item_id} try: obj["KodiId"] = e_item[0] obj["Media"] = e_item[4] except TypeError: return if obj["Media"] == "song": self.remove_song(obj["KodiId"], obj["Id"]) self.jellyfin_db.remove_wild_item(obj["Id"]) for item in self.jellyfin_db.get_item_by_wild_id( *values(obj, QUEM.get_item_by_wild_obj) ): if item[1] == "album": temp_obj = dict(obj) temp_obj["ParentId"] = item[0] if not self.jellyfin_db.get_item_by_parent_id( *values(temp_obj, QUEM.get_item_by_parent_song_obj) ): self.remove_album(temp_obj["ParentId"], obj["Id"]) elif obj["Media"] == "album": obj["ParentId"] = obj["KodiId"] for song in self.jellyfin_db.get_item_by_parent_id( *values(obj, QUEM.get_item_by_parent_song_obj) ): self.remove_song(song[1], obj["Id"]) else: self.jellyfin_db.remove_items_by_parent_id( *values(obj, QUEM.delete_item_by_parent_song_obj) ) self.remove_album(obj["KodiId"], obj["Id"]) elif obj["Media"] == "artist": obj["ParentId"] = obj["KodiId"] for album in self.jellyfin_db.get_item_by_parent_id( *values(obj, QUEM.get_item_by_parent_album_obj) ): temp_obj = dict(obj) temp_obj["ParentId"] = album[1] for song in self.jellyfin_db.get_item_by_parent_id( *values(temp_obj, QUEM.get_item_by_parent_song_obj) ): self.remove_song(song[1], obj["Id"]) else: self.jellyfin_db.remove_items_by_parent_id( *values(temp_obj, QUEM.delete_item_by_parent_song_obj) ) self.jellyfin_db.remove_items_by_parent_id( *values(temp_obj, QUEM.delete_item_by_parent_artist_obj) ) self.remove_album(temp_obj["ParentId"], obj["Id"]) else: self.jellyfin_db.remove_items_by_parent_id( *values(obj, QUEM.delete_item_by_parent_album_obj) ) self.remove_artist(obj["KodiId"], obj["Id"]) self.jellyfin_db.remove_item(*values(obj, QUEM.delete_item_obj)) def remove_artist(self, kodi_id, item_id): self.artwork.delete(kodi_id, "artist") self.delete(kodi_id) LOG.debug("DELETE artist [%s] %s", kodi_id, item_id) def remove_album(self, kodi_id, item_id): self.artwork.delete(kodi_id, "album") self.delete_album(kodi_id) LOG.debug("DELETE album [%s] %s", kodi_id, item_id) def remove_song(self, kodi_id, item_id): self.artwork.delete(kodi_id, "song") self.delete_song(kodi_id) LOG.debug("DELETE song [%s] %s", kodi_id, item_id) @jellyfin_item def get_child(self, item_id, e_item): """Get all child elements from tv show jellyfin id.""" obj = {"Id": item_id} child = [] try: obj["KodiId"] = e_item[0] obj["FileId"] = e_item[1] obj["ParentId"] = e_item[3] obj["Media"] = e_item[4] except TypeError: return child obj["ParentId"] = obj["KodiId"] for album in self.jellyfin_db.get_item_by_parent_id( *values(obj, QUEM.get_item_by_parent_album_obj) ): temp_obj = dict(obj) temp_obj["ParentId"] = album[1] child.append((album[0],)) for song in self.jellyfin_db.get_item_by_parent_id( *values(temp_obj, QUEM.get_item_by_parent_song_obj) ): child.append((song[0],)) return child