diff --git a/resources/lib/itemtypes/music.py b/resources/lib/itemtypes/music.py index 5971775f..f1bccd00 100644 --- a/resources/lib/itemtypes/music.py +++ b/resources/lib/itemtypes/music.py @@ -3,9 +3,9 @@ from __future__ import absolute_import, division, unicode_literals from logging import getLogger -from .common import ItemBase, process_path +from .common import ItemBase from ..plex_api import API -from .. import state, variables as v +from .. import plex_functions as PF, utils, state, variables as v LOG = getLogger('PLEX.music') @@ -13,131 +13,117 @@ LOG = getLogger('PLEX.music') class MusicMixin(object): def remove(self, plex_id, plex_type=None): """ - Remove the entire TV shows object (show, season or episode) including - all associated entries from the Kodi DB. + Remove the entire music object, including all associated entries from + both Plex and Kodi DBs """ - if plex_type is None: - entry = self.plex_db.episode(plex_id) - kodi_type = v.KODI_TYPE_EPISODE - if not entry: - entry = self.plex_db.season(plex_id) - kodi_type = v.KODI_TYPE_SEASON - if not entry: - entry = self.plex_db.show(plex_id) - kodi_type = v.KODI_TYPE_SHOW - else: - pass - if not entry: + db_item = self.plex_db.item_by_id(plex_id, plex_type) + if not db_item: LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id) return - kodi_id = entry[0] - file_id = entry[1] - parent_id = entry[3] - kodi_type = entry[4] - LOG.debug("Removing %s with kodi_id: %s file_id: %s parent_id: %s", - kodi_type, kodi_id, file_id, parent_id) + LOG.debug('Removing %s %s with kodi_id: %s', + db_item['plex_type'], plex_id, db_item['kodi_id']) # Remove the plex reference - self.plex_db.removeItem(plex_id) + self.plex_db.remove(plex_id, db_item['plex_type']) - # EPISODE ##### - if kodi_type == v.KODI_TYPE_EPISODE: + # SONG ##### + if db_item['plex_type'] == v.PLEX_TYPE_SONG: # Delete episode, verify season and tvshow - self.remove_episode(kodi_id, file_id) - # Season verification - season = self.plex_db.getItem_byKodiId(parent_id, - v.KODI_TYPE_SEASON) - if season is not None: - if not self.plex_db.getItem_byParentId(parent_id, - v.KODI_TYPE_EPISODE): - # No episode left for season - so delete the season - self.remove_season(parent_id) - self.plex_db.removeItem(season[0]) - show = self.plex_db.getItem_byKodiId(season[1], - v.KODI_TYPE_SHOW) - if show is not None: - if not self.plex_db.getItem_byParentId(season[1], - v.KODI_TYPE_SEASON): - # No seasons for show left - so delete entire show - self.remove_show(season[1]) - self.plex_db.removeItem(show[0]) - else: - LOG.error('No show found in Plex DB for season %s', season) - else: - LOG.error('No season found in Plex DB!') - # SEASON ##### - elif kodi_type == v.KODI_TYPE_SEASON: + self.remove_song(db_item['kodi_id'], db_item['kodi_pathid']) + # Album verification + if not self.plex_db.album_has_songs(db_item['album_id']): + # No episode left for this season - so delete the season + self.remove_album(db_item['parent_id']) + self.plex_db.remove(db_item['album_id'], v.PLEX_TYPE_ALBUM) + # Artist verification + if (not self.plex_db.artist_has_albums(db_item['artist_id']) and + not self.plex_db.artist_has_songs(db_item['artist_id'])): + self.remove_artist(db_item['grandparent_id']) + self.plex_db.remove(db_item['artist_id'], v.PLEX_TYPE_ARTIST) + # ALBUM ##### + elif db_item['plex_type'] == v.PLEX_TYPE_ALBUM: # Remove episodes, season, verify tvshow - for episode in self.plex_db.getItem_byParentId( - kodi_id, v.KODI_TYPE_EPISODE): - self.remove_episode(episode[1], episode[2]) - self.plex_db.removeItem(episode[0]) - # Remove season - self.remove_season(kodi_id) + for song in self.plex_db.song_by_album(db_item['plex_id']): + self.remove_song(song['kodi_id'], song['kodi_pathid']) + self.plex_db.remove(song['plex_id'], v.PLEX_TYPE_SONG) + # Remove the album + self.remove_album(db_item['kodi_id']) # Show verification - if not self.plex_db.getItem_byParentId(parent_id, - v.KODI_TYPE_SEASON): - # There's no other season left, delete the show - self.remove_show(parent_id) - self.plex_db.removeItem_byKodiId(parent_id, v.KODI_TYPE_SHOW) - # TVSHOW ##### - elif kodi_type == v.KODI_TYPE_SHOW: - # Remove episodes, seasons and the tvshow itself - for season in self.plex_db.getItem_byParentId(kodi_id, - v.KODI_TYPE_SEASON): - for episode in self.plex_db.getItem_byParentId( - season[1], v.KODI_TYPE_EPISODE): - self.remove_episode(episode[1], episode[2]) - self.plex_db.removeItem(episode[0]) - self.remove_season(season[1]) - self.plex_db.removeItem(season[0]) - self.remove_show(kodi_id) + if (not self.plex_db.artist_has_albums(db_item['album_id']) and + not self.plex_db.artist_has_songs(db_item['album_id'])): + # There's no other season or episode left, delete the show + self.remove_artist(db_item['parent_id']) + self.plex_db.remove(db_item['artist_id'], v.KODI_TYPE_ARTIST) + # ARTIST ##### + elif db_item['plex_type'] == v.PLEX_TYPE_ARTIST: + # Remove songs, albums and the artist himself + for song in self.plex_db.song_by_artist(db_item['plex_id']): + self.remove_song(song['kodi_id'], song['kodi_pathid']) + self.plex_db.remove(song['plex_id'], v.PLEX_TYPE_SONG) + for album in self.plex_db.album_by_artist(db_item['plex_id']): + self.remove_album(album['kodi_id']) + self.plex_db.remove(album['plex_id'], v.PLEX_TYPE_ALBUM) + self.remove_artist(db_item['kodi_id']) - LOG.debug("Deleted %s %s from Kodi database", kodi_type, plex_id) + LOG.debug('Deleted %s %s from all databases', + db_item['plex_type'], db_item['plex_id']) - def remove_show(self, kodi_id): + def remove_song(self, kodi_id, path_id=None): """ - Remove a TV show, and only the show, no seasons or episodes + Remove song, orphaned artists and orphaned paths """ - self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW) + if not path_id: + query = 'SELECT idPath FROM song WHERE idSong = ? LIMIT 1' + self.kodicursor.execute(query, (kodi_id, )) + try: + path_id = self.kodicursor.fetchone()[0] + except TypeError: + pass + self.kodi_db.delete_song_from_song_artist(kodi_id) + self.kodicursor.execute('DELETE FROM song WHERE idSong = ?', + (kodi_id, )) + # Check whether we have orphaned path entries + query = 'SELECT idPath FROM song WHERE idPath = ? LIMIT 1' + self.kodicursor.execute(query, (path_id, )) + if not self.kodicursor.fetchone(): + self.kodicursor.execute('DELETE FROM path WHERE idPath = ?', + (path_id, )) + if v.KODIVERSION < 18: + self.kodi_db.delete_song_from_song_genre(kodi_id) + query = 'DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?' + self.kodicursor.execute(query, (kodi_id, )) + self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_SONG, self.kodicursor) + + def remove_album(self, kodi_id): + ''' + Remove an album + ''' + self.kodi_db.delete_album_from_discography(kodi_id) + if v.KODIVERSION < 18: + self.kodi_db.delete_album_from_album_genre(kodi_id) + query = 'DELETE FROM albuminfosong WHERE idAlbumInfo = ?' + self.kodicursor.execute(query, (kodi_id, )) + self.kodicursor.execute('DELETE FROM album_artist WHERE idAlbum = ?', + (kodi_id, )) + self.kodicursor.execute('DELETE FROM album WHERE idAlbum = ?', + (kodi_id, )) + self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_ALBUM, self.kodicursor) + + def remove_artist(self, kodi_id): + ''' + Remove an artist and associated songs and albums + ''' + self.kodicursor.execute('DELETE FROM album_artist WHERE idArtist = ?', + (kodi_id, )) + self.kodicursor.execute('DELETE FROM artist WHERE idArtist = ?', + (kodi_id, )) + self.kodicursor.execute('DELETE FROM song_artist WHERE idArtist = ?', + (kodi_id, )) + self.kodicursor.execute('DELETE FROM discography WHERE idArtist = ?', + (kodi_id, )) self.artwork.delete_artwork(kodi_id, - v.KODI_TYPE_SHOW, + v.KODI_TYPE_ARTIST, self.kodicursor) - self.kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", - (kodi_id,)) - if v.KODIVERSION >= 17: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW) - LOG.debug("Removed tvshow: %s", kodi_id) - - def remove_season(self, kodi_id): - """ - Remove a season, and only a season, not the show or episodes - """ - self.artwork.delete_artwork(kodi_id, - v.KODI_TYPE_SEASON, - self.kodicursor) - self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", - (kodi_id,)) - LOG.debug("Removed season: %s", kodi_id) - - def remove_episode(self, kodi_id, file_id): - """ - Remove an episode, and episode only from the Kodi DB (not Plex DB) - """ - self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE) - self.kodi_db.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE) - self.artwork.delete_artwork(kodi_id, - v.KODI_TYPE_EPISODE, - self.kodicursor) - self.kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", - (kodi_id,)) - if v.KODIVERSION >= 17: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE) - self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE) - LOG.debug("Removed episode: %s", kodi_id) class Artist(ItemBase, MusicMixin): @@ -150,255 +136,336 @@ class Artist(ItemBase, MusicMixin): Process a single artist """ api = API(xml) - update_item = True plex_id = api.plex_id() - LOG.debug('Adding show with plex_id %s', plex_id) if not plex_id: - LOG.error("Cannot parse XML data for TV show: %s", xml.attrib) + LOG.error('Cannot process artist %s', xml.attrib) return - show = self.plex_db.show(plex_id) - try: - kodi_id = show[3] - kodi_pathid = show[4] - except TypeError: + LOG.debug('Adding artist with plex_id %s', plex_id) + db_item = self.plex_db.artist(plex_id) + if not db_item: update_item = False - query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow' - self.kodicursor.execute(query) - kodi_id = self.kodicursor.fetchone()[0] + 1 else: - # Verification the item is still in Kodi - self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?', - (kodi_id,)) - try: - self.kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - LOG.info("idShow: %s missing from Kodi, repairing the entry.", - kodi_id) + update_item = True + kodi_id = db_item['kodi_id'] - genres = api.genre_list() - genre = api.list_to_string(genres) - studios = api.music_studio_list() - studio = api.list_to_string(studios) + # Not yet implemented by Plex + musicBrainzId = None - # GET THE FILE AND PATH ##### - if state.DIRECT_PATHS: - # Direct paths is set the Kodi way - playurl = api.validate_playurl(api.tv_show_path(), - api.plex_type(), - folder=True) - if playurl is None: - return - path, toplevelpath = process_path(playurl) - toppathid = self.kodi_db.add_video_path( - toplevelpath, - content='tvshows', - scraper='metadata.local') + # Associate artwork + artworks = api.artwork() + if 'poster' in artworks: + thumb = "%s" % artworks['poster'] else: - # Set plugin path - toplevelpath = "plugin://%s.tvshows/" % v.ADDON_ID - path = "%s%s/" % (toplevelpath, plex_id) - # Do NOT set a parent id because addon-path cannot be "stacked" - toppathid = None + thumb = None + if 'fanart' in artworks: + fanart = "%s" % artworks['fanart'] + else: + fanart = None - kodi_pathid = self.kodi_db.add_video_path(path, - date_added=api.date_created(), - id_parent_path=toppathid) - # UPDATE THE TVSHOW ##### + # UPDATE THE ARTIST ##### if update_item: - LOG.info("UPDATE tvshow plex_id: %s - Title: %s", - plex_id, api.title()) - # update new ratings Kodi 17 - rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.update_ratings(kodi_id, - v.KODI_TYPE_SHOW, - "default", - api.audience_rating(), - api.votecount(), - rating_id) - # update new uniqueid Kodi 17 - if api.provider('tvdb') is not None: - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_SHOW) - self.kodi_db.update_uniqueid(kodi_id, - v.KODI_TYPE_SHOW, - api.provider('tvdb'), - "unknown", - uniqueid) - else: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) - uniqueid = -1 - # Update the tvshow entry - query = ''' - UPDATE tvshow - SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, - c12 = ?, c13 = ?, c14 = ?, c15 = ? - WHERE idShow = ? - ''' - self.kodicursor.execute( - query, (api.title(), api.plot(), rating_id, - api.premiere_date(), genre, api.title(), uniqueid, - api.content_rating(), studio, api.sorttitle(), - kodi_id)) - # OR ADD THE TVSHOW ##### + LOG.info("UPDATE artist plex_id: %s - Name: %s", plex_id, api.title()) + # OR ADD THE ARTIST ##### else: - LOG.info("ADD tvshow plex_id: %s - Title: %s", - plex_id, api.title()) - # Link the path - query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)" - self.kodicursor.execute(query, (kodi_id, kodi_pathid)) - rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.add_ratings(rating_id, - kodi_id, - v.KODI_TYPE_SHOW, - "default", - api.audience_rating(), - api.votecount()) - if api.provider('tvdb') is not None: - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_SHOW) - self.kodi_db.add_uniqueid(uniqueid, - kodi_id, - v.KODI_TYPE_SHOW, - api.provider('tvdb'), - "unknown") - else: - uniqueid = -1 - # Create the tvshow entry - query = ''' - INSERT INTO tvshow( - idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, - c15) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - self.kodicursor.execute( - query, (kodi_id, api.title(), api.plot(), rating_id, - api.premiere_date(), genre, api.title(), uniqueid, - api.content_rating(), studio, api.sorttitle())) - - self.kodi_db.modify_people(kodi_id, - v.KODI_TYPE_SHOW, - api.people_list()) - self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres) - self.artwork.modify_artwork(api.artwork(), + LOG.info("ADD artist plex_id: %s - Name: %s", plex_id, api.title()) + # safety checks: It looks like plex supports the same artist + # multiple times. + # Kodi doesn't allow that. In case that happens we just merge the + # artist entries. + kodi_id = self.kodi_db.add_artist(api.title(), musicBrainzId) + # Create the reference in plex table + query = ''' + UPDATE artist + SET strGenres = ?, + strBiography = ?, + strImage = ?, + strFanart = ?, + lastScraped = ? + WHERE idArtist = ? + ''' + self.kodicursor.execute( + query, + (api.list_to_string(api.genre_list()), + api.plot(), + thumb, + fanart, + utils.unix_date_to_kodi(self.last_sync), + kodi_id)) + # Update artwork + self.artwork.modify_artwork(artworks, kodi_id, - v.KODI_TYPE_SHOW, + v.KODI_TYPE_ARTIST, self.kodicursor) - # Process studios - self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios) - # Process tags: view, PMS collection tags - tags = [section_name] - tags.extend([i for _, i in api.collection_list()]) - self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags) - self.plex_db.add_show(plex_id=plex_id, - checksum=api.checksum(), - section_id=section_id, - kodi_id=kodi_id, - kodi_pathid=kodi_pathid, - last_sync=self.last_sync) + self.plex_db.add_artist(plex_id, + api.checksum(), + section_id, + kodi_id, + self.last_sync) class Album(ItemBase, MusicMixin): def add_update(self, xml, section_name=None, section_id=None, - children=None): + children=None, scan_children=True): """ Process a single album - """ - api = API(xml) - plex_id = api.plex_id() - LOG.debug('Adding season with plex_id %s', plex_id) - if not plex_id: - LOG.error('Error getting plex_id for season, skipping: %s', - xml.attrib) - return - show_id = api.parent_plex_id() - show = self.plex_db.show(show_id) - try: - parent_id = show[3] - except TypeError: - LOG.error('Could not find parent tv show for season %s. ' - 'Skipping season for now.', plex_id) - return - kodi_id = self.kodi_db.add_season(parent_id, api.season_number()) - self.artwork.modify_artwork(api.artwork(), - kodi_id, - v.KODI_TYPE_SEASON, - self.kodicursor) - self.plex_db.add_season(plex_id=plex_id, - checksum=api.checksum(), - section_id=section_id, - show_id=show_id, - parent_id=parent_id, - kodi_id=kodi_id, - last_sync=self.last_sync) - - -class Song(ItemBase, MusicMixin): - def add_update(self, xml, section_name=None, section_id=None, - children=None): - """ - Process single song/track + scan_children: set to False if you don't want to add children, e.g. to + avoid infinite loops """ api = API(xml) update_item = True plex_id = api.plex_id() - LOG.debug('Adding episode with plex_id %s', plex_id) if not plex_id: - LOG.error('Error getting plex_id for episode, skipping: %s', - xml.attrib) + LOG.error('Error processing album: %s', xml.attrib) return - entry = self.plex_db.item_by_id(plex_id) - try: - kodi_id = entry[0] - old_kodi_fileid = entry[1] - kodi_pathid = entry[2] - except TypeError: - update_item = False - query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode' - self.kodicursor.execute(query) - kodi_id = self.kodicursor.fetchone()[0] + 1 + LOG.debug('Adding album with plex_id %s', plex_id) + db_item = self.plex_db.album(plex_id) + if db_item: + update_item = True + kodi_id = db_item['kodi_id'] else: - # Verification the item is still in Kodi - query = 'SELECT * FROM episode WHERE idEpisode = ? LIMIT 1' - self.kodicursor.execute(query, (kodi_id, )) - try: - self.kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - LOG.info('idEpisode %s missing from Kodi, repairing entry.', - kodi_id) + update_item = False - peoples = api.people() - director = api.list_to_string(peoples['Director']) - writer = api.list_to_string(peoples['Writer']) + # See if we have a compilation - Plex does NOT feature a compilation + # flag for albums + compilation = 0 + for song in children: + if song.get('originalTitle') is not None: + compilation = 1 + break + name = api.title() userdata = api.userdata() - show_id, season_id, _, season_no, episode_no = api.episode_data() + # Not yet implemented by Plex + musicBrainzId = None + genres = api.genre_list() + genre = api.list_to_string(genres) + # Associate artwork + artworks = api.artwork() + if 'poster' in artworks: + thumb = "%s" % artworks['poster'] + else: + thumb = None - if season_no is None: - season_no = -1 - if episode_no is None: - episode_no = -1 - airs_before_season = "-1" - airs_before_episode = "-1" + # UPDATE THE ALBUM ##### + if update_item: + LOG.info("UPDATE album plex_id: %s - Name: %s", plex_id, name) + # Update the checksum in plex table + # OR ADD THE ALBUM ##### + else: + LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name) + # safety checks: It looks like plex supports the same artist + # multiple times. + # Kodi doesn't allow that. In case that happens we just merge the + # artist entries. + kodi_id = self.kodi_db.add_album(name, musicBrainzId) + # Create the reference in plex table - show = self.plex_db.show(show_id) - try: - grandparent_id = show[3] - except TypeError: - LOG.error("Parent tvshow now found, skip item") - return False - parent_id = self.kodi_db.add_season(grandparent_id, season_no) + # Process the album info + if v.KODIVERSION >= 18: + # Kodi Leia + query = ''' + UPDATE album + SET strArtistDisp = ?, + iYear = ?, + strGenres = ?, + strReview = ?, + strImage = ?, + iUserrating = ?, + lastScraped = ?, + strReleaseType = ?, + strLabel = ?, + bCompilation = ? + WHERE idAlbum = ? + ''' + self.kodicursor.execute( + query, + (api.artist_name(), + api.year(), + genre, + api.plot(), + thumb, + userdata['UserRating'], + utils.unix_date_to_kodi(self.last_sync), + v.KODI_TYPE_ALBUM, + api.music_studio(), + compilation, + kodi_id)) + else: + # Kodi Krypton + query = ''' + UPDATE album + SET strArtists = ?, + iYear = ?, + strGenres = ?, + strReview = ?, + strImage = ?, + Userrating = ?, + lastScraped = ?, + strReleaseType = ?, + strLabel = ?, + bCompilation = ? + WHERE idAlbum = ? + ''' + self.kodicursor.execute( + query, + (api.artist_name(), + api.year(), + genre, + api.plot(), + thumb, + userdata['UserRating'], + utils.unix_date_to_kodi(self.last_sync), + v.KODI_TYPE_ALBUM, + api.music_studio(), + compilation, + kodi_id)) + + # Associate the parentid for plex reference + parent_id = api.parent_plex_id() + artist_id = None + if parent_id is not None: + try: + artist_id = self.plex_db.getItem_byId(parent_id)[0] + except TypeError: + LOG.info('Artist %s does not yet exist in Plex DB', parent_id) + artist = PF.GetPlexMetadata(parent_id) + try: + artist[0].attrib + except (TypeError, IndexError, AttributeError): + LOG.error('Could not get artist xml for %s', parent_id) + else: + self.add_updateArtist(artist[0]) + plex_dbartist = self.plex_db.getItem_byId(parent_id) + try: + artist_id = plex_dbartist[0] + except TypeError: + LOG.error('Adding artist failed for %s', parent_id) + # Update plex reference with the artist_id + self.plex_db.updateParentId(plex_id, artist_id) + # Add artist to album + query = ''' + INSERT OR REPLACE INTO album_artist( + idArtist, + idAlbum, + strArtist) + VALUES (?, ?, ?) + ''' + self.kodicursor.execute(query, + (artist_id, kodi_id, api.artist_name())) + # Update discography + query = ''' + INSERT OR REPLACE INTO discography( + idArtist, + strAlbum, + strYear) + VALUES (?, ?, ?) + ''' + self.kodicursor.execute(query, + (artist_id, name, api.year())) + if v.KODIVERSION < 18: + self.kodi_db.add_music_genres(kodi_id, + genres, + v.KODI_TYPE_ALBUM) + # Update artwork + self.artwork.modify_artwork(artworks, + kodi_id, + v.KODI_TYPE_ALBUM, + self.kodicursor) + # Add all children - all tracks + if scan_children: + context = Song(self.last_sync, + plex_db=self.plex_db, + kodi_db=self.kodi_db) + for song in children: + context.add_update(song, + section_name=section_name, + section_id=section_id, + album_xml=xml, + genres=genres, + genre=genre, + compilation=compilation) + self.plex_db.add_album(plex_id, + api.checksum(), + section_id, + artist_id, + parent_id, + kodi_id, + self.last_sync) + + +class Song(ItemBase, MusicMixin): + def add_update(self, xml, section_name=None, section_id=None, + children=None, album_xml=None, genres=None, genre=None, + compilation=None): + """ + Process single song/track + """ + api = API(xml) + plex_id = api.plex_id() + if not plex_id: + LOG.error('Error processing song: %s', xml.attrib) + return + LOG.debug('Adding song with plex_id %s', plex_id) + db_item = self.plex_db.song(plex_id) + if db_item: + update_item = True + kodi_id = db_item['kodi_id'] + kodi_pathid = db_item['kodi_pathid'] + album_id = db_item['album_id'] + parent_id = db_item['parent_id'] + grandparent_id = db_item['grandparent_id'] + artist_id = db_item['artist_id'] + else: + update_item = False + self.kodicursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song') + kodi_id = self.kodicursor.fetchone()[0] + 1 + + title = api.title() + # Not yet implemented by Plex + musicBrainzId = None + comment = None + userdata = api.userdata() + playcount = userdata['PlayCount'] + if playcount is None: + # This is different to Video DB! + playcount = 0 + # Getting artists name is complicated + if compilation is not None: + if compilation == 0: + artists = api.grandparent_title() + else: + artists = xml.get('originalTitle') + else: + # compilation not set + artists = xml.get('originalTitle', api.grandparent_title()) + tracknumber = api.track_number() or 0 + disc = api.disc_number() or 1 + if disc == 1: + track = tracknumber + else: + track = disc * 2 ** 16 + tracknumber + year = api.year() + if not year and album_xml: + # Plex did not pass year info - get it from the parent album + album_api = API(album_xml) + year = album_api.year() + moods = [] + for entry in xml: + if entry.tag == 'Mood': + moods.append(entry.attrib['tag']) + mood = api.list_to_string(moods) # GET THE FILE AND PATH ##### do_indirect = not state.DIRECT_PATHS if state.DIRECT_PATHS: + # Direct paths is set the Kodi way playurl = api.file_path(force_first_media=True) if playurl is None: + # Something went wrong, trying to use non-direct paths do_indirect = True else: - playurl = api.validate_playurl(playurl, v.PLEX_TYPE_EPISODE) + playurl = api.validate_playurl(playurl, api.plex_type()) + if playurl is None: + return False if "\\" in playurl: # Local path filename = playurl.rsplit("\\", 1)[1] @@ -406,139 +473,298 @@ class Song(ItemBase, MusicMixin): # Network share filename = playurl.rsplit("/", 1)[1] path = playurl.replace(filename, "") - parent_path_id = self.kodi_db.parent_path_id(path) - kodi_pathid = self.kodi_db.add_video_path( - path, id_parent_path=parent_path_id) if do_indirect: - # Set plugin path - do NOT use "intermediate" paths for the show - # as with direct paths! - filename = api.file_name(force_first_media=True) - path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, show_id) - filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s' - % (path, plex_id, v.PLEX_TYPE_EPISODE, filename)) - playurl = filename - # Root path tvshows/ already saved in Kodi DB - kodi_pathid = self.kodi_db.add_video_path(path) + # Plex works a bit differently + path = "%s%s" % (utils.window('pms_server'), + xml[0][0].get('key')) + path = api.attach_plex_token_to_url(path) + filename = path.rsplit('/', 1)[1] + path = path.replace(filename, '') - # add/retrieve kodi_pathid and fileid - # if the path or file already exists, the calls return current value - kodi_fileid = self.kodi_db.add_file(filename, - kodi_pathid, - api.date_created()) - - # UPDATE THE EPISODE ##### + # UPDATE THE SONG ##### if update_item: - LOG.info("UPDATE episode plex_id: %s, Title: %s", - plex_id, api.title()) - if kodi_fileid != old_kodi_fileid: - self.kodi_db.remove_file(old_kodi_fileid) - ratingid = self.kodi_db.get_ratingid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.update_ratings(kodi_id, - v.KODI_TYPE_EPISODE, - "default", - userdata['Rating'], - api.votecount(), - ratingid) - # update new uniqueid Kodi 17 - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.update_uniqueid(kodi_id, - v.KODI_TYPE_EPISODE, - api.provider('tvdb'), - "tvdb", - uniqueid) - query = ''' - UPDATE episode - SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, - c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, - c18 = ?, c19 = ?, idFile=?, idSeason = ?, - userrating = ? - WHERE idEpisode = ? - ''' - self.kodicursor.execute( - query, (api.title(), api.plot(), ratingid, writer, - api.premiere_date(), api.runtime(), director, season_no, - episode_no, api.title(), airs_before_season, - airs_before_episode, playurl, kodi_pathid, kodi_fileid, - parent_id, userdata['UserRating'], kodi_id)) + LOG.info("UPDATE song plex_id: %s - Title: %s", plex_id, title) + # Use dummy strHash '123' for Kodi + query = "UPDATE path SET strPath = ?, strHash = ? WHERE idPath = ?" + self.kodicursor.execute(query, (path, '123', kodi_pathid)) + # Update the song entry + if v.KODIVERSION >= 18: + # Kodi Leia + query = ''' + UPDATE song + SET idAlbum = ?, + strArtistDisp = ?, + strGenres = ?, + strTitle = ?, + iTrack = ?, + iDuration = ?, + iYear = ?, + strFilename = ?, + iTimesPlayed = ?, + lastplayed = ?, + rating = ?, + comment = ?, + mood = ? + WHERE idSong = ? + ''' + self.kodicursor.execute( + query, + (parent_id, + artists, + genre, + title, + track, + userdata['Runtime'], + year, + filename, + playcount, + userdata['LastPlayedDate'], + userdata['UserRating'], + comment, + mood, + kodi_id)) + else: + query = ''' + UPDATE song + SET idAlbum = ?, + strArtists = ?, + strGenres = ?, + strTitle = ?, + iTrack = ?, + iDuration = ?, + iYear = ?, + strFilename = ?, + iTimesPlayed = ?, + lastplayed = ?, + rating = ?, + comment = ?, + mood = ? + WHERE idSong = ? + ''' + self.kodicursor.execute( + query, + (parent_id, + artists, + genre, + title, + track, + userdata['Runtime'], + year, + filename, + playcount, + userdata['LastPlayedDate'], + userdata['UserRating'], + comment, + mood, + kodi_id)) - # OR ADD THE EPISODE ##### + # OR ADD THE SONG ##### else: - LOG.info("ADD episode plex_id: %s - Title: %s", - plex_id, api.title()) - # Create the episode entry - rating_id = self.kodi_db.get_ratingid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.add_ratings(rating_id, - kodi_id, - v.KODI_TYPE_EPISODE, - "default", - userdata['Rating'], - api.votecount()) - # add new uniqueid Kodi 17 - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.add_uniqueid(uniqueid, - kodi_id, - v.KODI_TYPE_EPISODE, - api.provider('tvdb'), - "tvdb") + LOG.info("ADD song plex_id: %s - Title: %s", plex_id, title) + # Add path + kodi_pathid = self.kodi_db.add_music_path(path, hash_string="123") + # Get the album + album = self.plex_db.album(api.parent_plex_id()) + if album: + parent_id = album['kodi_id'] + else: + # No album found. Let's create it + LOG.info('Album database entry missing and album_xml=%s', + album_xml) + album_id = api.parent_plex_id() + album = None + if album_id: + album_xml = PF.GetPlexMetadata(album_id) + if album_xml is None or album_xml == 401: + LOG.error('Could not download album %s,', album_id) + return + context = Album(self.last_sync, + plex_db=self.plex_db, + kodi_db=self.kodi_db) + context.add_update(album_xml[0], + section_name=section_name, + section_id=section_id, + children=[xml], + scan_children=False) + album = self.plex_db.album(album_id) + if album: + parent_id = album['kodi_id'] + LOG.debug("Found parent_id for album: %s", parent_id) + else: + # No album found, create a single's album + LOG.info("Failed to add album. Creating singles.") + self.kodicursor.execute( + 'SELECT COALESCE(MAX(idAlbum),0) FROM album') + parent_id = self.kodicursor.fetchone()[0] + 1 + query = ''' + INSERT INTO album( + idAlbum, + strGenres, + iYear, + strReleaseType) + VALUES (?, ?, ?, ?) + ''' + self.kodicursor.execute(query, + (parent_id, genre, year, "single")) + + # Create the song entry + if v.KODIVERSION >= 18: + # Kodi Leia + query = ''' + INSERT INTO song( + idSong, + idAlbum, + idPath, + strArtistDisp, + strGenres, + strTitle, + iTrack, + iDuration, + iYear, + strFileName, + strMusicBrainzTrackID, + iTimesPlayed, + lastplayed, + rating, + iStartOffset, + iEndOffset, + mood) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + self.kodicursor.execute( + query, + (kodi_id, + parent_id, + kodi_pathid, + artists, + genre, + title, + track, + userdata['Runtime'], + year, + filename, + musicBrainzId, + playcount, + userdata['LastPlayedDate'], + userdata['UserRating'], + 0, + 0, + mood)) + else: + query = ''' + INSERT INTO song( + idSong, + idAlbum, + idPath, + strArtists, + strGenres, + strTitle, + iTrack, + iDuration, + iYear, + strFileName, + strMusicBrainzTrackID, + iTimesPlayed, + lastplayed, + rating, + iStartOffset, + iEndOffset, + mood) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + self.kodicursor.execute( + query, + (kodi_id, + parent_id, + kodi_pathid, + artists, + genre, + title, + track, + userdata['Runtime'], + year, + filename, + musicBrainzId, + playcount, + userdata['LastPlayedDate'], + userdata['UserRating'], + 0, + 0, + mood)) + if v.KODIVERSION < 18: + # Link song to album query = ''' - INSERT INTO episode( idEpisode, idFile, c00, c01, c03, c04, - c05, c09, c10, c12, c13, c14, idShow, c15, c16, c18, - c19, idSeason, userrating) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?) + INSERT OR REPLACE INTO albuminfosong( + idAlbumInfoSong, + idAlbumInfo, + iTrack, + strTitle, + iDuration) + VALUES (?, ?, ?, ?, ?) ''' self.kodicursor.execute( - query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id, - writer, api.premiere_date(), api.runtime(), director, - season_no, episode_no, api.title(), grandparent_id, - airs_before_season, airs_before_episode, playurl, - kodi_pathid, parent_id, userdata['UserRating'])) - - self.kodi_db.modify_people(kodi_id, - v.KODI_TYPE_EPISODE, - api.people_list()) - self.artwork.modify_artwork(api.artwork(), + query, + (kodi_id, parent_id, track, title, userdata['Runtime'])) + # Link song to artists + artist_name = api.grandparent_title() + artist_id = api.grandparent_id() + artist = self.plex_db.artist(artist_id) + if artist: + grandparent_id = artist['kodi_id'] + else: + LOG.info('Missing artist for song %s. Adding...', plex_id) + artist_xml = PF.GetPlexMetadata(artist_id) + if artist_xml is None or artist_xml == 401: + LOG.error('Error getting artist, abort') + return + context = Artist(self.last_sync, + plex_db=self.plex_db, + kodi_db=self.kodi_db) + context.add_update(artist_xml[0], + section_name=section_name, + section_id=section_id, + children=None) + artist = self.plex_db.artist(artist_id) + if not artist: + LOG.error('Could not add artist %s for song %s', + artist_name, plex_id) + return + grandparent_id = artist['kodi_id'] + # Do the actual linking + query = ''' + INSERT OR REPLACE INTO song_artist( + idArtist, + idSong, + idRole, + iOrder, + strArtist) + VALUES (?, ?, ?, ?, ?) + ''' + self.kodicursor.execute( + query, + (grandparent_id, kodi_id, 1, 0, artist_name)) + # Add genres + if genres: + self.kodi_db.add_music_genres(kodi_id, genres, v.KODI_TYPE_SONG) + artworks = api.artwork() + self.artwork.modify_artwork(artworks, kodi_id, - v.KODI_TYPE_EPISODE, + v.KODI_TYPE_SONG, self.kodicursor) - streams = api.mediastreams() - self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime()) - self.kodi_db.set_resume(kodi_fileid, - api.resume_point(), - api.runtime(), - userdata['PlayCount'], - userdata['LastPlayedDate'], - None) # Do send None, we check here - if not state.DIRECT_PATHS: - # need to set a SECOND file entry for a path without plex show id - filename = api.file_name(force_first_media=True) - path = 'plugin://%s.tvshows/' % v.ADDON_ID - # Filename is exactly the same, WITH plex show id! - filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s' - % (path, show_id, plex_id, v.PLEX_TYPE_EPISODE, - filename)) - kodi_pathid = self.kodi_db.add_video_path(path) - kodi_fileid = self.kodi_db.add_file(filename, - kodi_pathid, - api.date_created()) - self.kodi_db.set_resume(kodi_fileid, - api.resume_point(), - api.runtime(), - userdata['PlayCount'], - userdata['LastPlayedDate'], - None) # Do send None - 2nd entry - self.plex_db.add_episode(plex_id=plex_id, - checksum=api.checksum(), - section_id=section_id, - show_id=show_id, - grandparent_id=grandparent_id, - season_id=season_id, - parent_id=parent_id, - kodi_id=kodi_id, - kodi_fileid=kodi_fileid, - kodi_pathid=kodi_pathid, - last_sync=self.last_sync) + if xml.get('parentKey') is None: + # Update album artwork + self.artwork.modify_artwork(artworks, + parent_id, + v.KODI_TYPE_ALBUM, + self.kodicursor) + # Create the reference in plex table + self.plex_db.add_song(plex_id, + api.checksum(), + section_id, + artist_id, + grandparent_id, + album_id, + parent_id, + kodi_id, + kodi_pathid, + self.last_sync) diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 3d48674a..fc464262 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -926,7 +926,6 @@ class KodiDBMethods(object): def delete_song_from_song_artist(self, song_id): """ Deletes son from song_artist table and possibly orphaned roles - Will returned an orphaned idArtist or None if not orphaned """ query = ''' SELECT idArtist, idRole FROM song_artist WHERE idSong = ? LIMIT 1 @@ -946,13 +945,6 @@ class KodiDBMethods(object): # Delete orphaned role self.cursor.execute('DELETE FROM role WHERE idRole = ?', (artist[1], )) - # Check whether we need to delete orphaned artists - query = 'SELECT idArtist FROM song_artist WHERE idArtist = ? LIMIT 1' - self.cursor.execute(query, (artist[0], )) - if self.cursor.fetchone(): - return - else: - return artist[0] def delete_album_from_discography(self, album_id): """ diff --git a/resources/lib/plex_api.py b/resources/lib/plex_api.py index 58785b33..e83a4f03 100644 --- a/resources/lib/plex_api.py +++ b/resources/lib/plex_api.py @@ -264,6 +264,12 @@ class API(object): """ return cast(int, self.item.get('index')) + def track_number(self): + """ + Returns the 'index' of an XML reply as int. Depicts track number. + """ + return cast(int, self.item.get('index')) + def date_created(self): """ Returns the date when this library item was created. @@ -486,6 +492,13 @@ class API(object): return cast(unicode, self.item.get('titleSort', self.item.get('title','Missing Title'))) + def artist_name(self): + """ + Returns the artist name for an album: first it attempts to return + 'parentTitle', if that failes 'originalTitle' + """ + return self.item.get('parentTitle', self.item.get('originalTitle')) + def plot(self): """ Returns the plot or None. @@ -605,18 +618,16 @@ class API(object): """ Returns the 'studio' or None """ - return self.item.get('studio') + return self.replace_studio(cast(unicode, self.item.get('studio'))) def music_studio_list(self): """ Returns a list with a single entry for the studio, or an empty list """ - studio = [] - try: - studio.append(self.replace_studio(self.item.attrib['studio'])) - except KeyError: - pass - return studio + studio = self.music_studio() + if studio: + return [studio] + return [] @staticmethod def replace_studio(studio_name): @@ -1657,23 +1668,11 @@ class API(object): pass return listitem - def track_number(self): - """ - Returns the song's track number as an int or None if not found - """ - try: - return int(self.item.get('index')) - except TypeError: - pass - def disc_number(self): """ Returns the song's disc number as an int or None if not found """ - try: - return int(self.item.get('parentIndex')) - except TypeError: - pass + return cast(int, self.item.get('parentIndex')) def _create_audio_listitem(self, listitem=None): """ diff --git a/resources/lib/plex_db/__init__.py b/resources/lib/plex_db/__init__.py index 0acefd7d..4236b898 100644 --- a/resources/lib/plex_db/__init__.py +++ b/resources/lib/plex_db/__init__.py @@ -5,7 +5,8 @@ from __future__ import absolute_import, division, unicode_literals from .common import PlexDBBase, initialize, wipe from .tvshows import TVShows from .movies import Movies +from .music import Music -class PlexDB(PlexDBBase, TVShows, Movies): +class PlexDB(PlexDBBase, TVShows, Movies, Music): pass diff --git a/resources/lib/plex_db/common.py b/resources/lib/plex_db/common.py index d3cadb01..0d9a84ef 100644 --- a/resources/lib/plex_db/common.py +++ b/resources/lib/plex_db/common.py @@ -64,69 +64,6 @@ class PlexDBBase(object): break return answ - @staticmethod - def entry_to_movie(entry): - return { - 'plex_type': v.PLEX_TYPE_MOVIE, - 'kodi_type': v.KODI_TYPE_MOVIE, - 'plex_id': entry[0], - 'checksum': entry[1], - 'section_id': entry[2], - 'kodi_id': entry[3], - 'kodi_fileid': entry[4], - 'kodi_pathid': entry[5], - 'fanart_synced': entry[6], - 'last_sync': entry[7] - } - - @staticmethod - def entry_to_episode(entry): - return { - 'plex_type': v.PLEX_TYPE_EPISODE, - 'kodi_type': v.KODI_TYPE_EPISODE, - 'plex_id': entry[0], - 'checksum': entry[1], - 'section_id': entry[2], - 'show_id': entry[3], - 'grandparent_id': entry[4], - 'season_id': entry[5], - 'parent_id': entry[6], - 'kodi_id': entry[7], - 'kodi_fileid': entry[8], - 'kodi_pathid': entry[9], - 'fanart_synced': entry[10], - 'last_sync': entry[11] - } - - @staticmethod - def entry_to_show(entry): - return { - 'plex_type': v.PLEX_TYPE_SHOW, - 'kodi_type': v.KODI_TYPE_SHOW, - 'plex_id': entry[0], - 'checksum': entry[1], - 'section_id': entry[2], - 'kodi_id': entry[3], - 'kodi_pathid': entry[4], - 'fanart_synced': entry[5], - 'last_sync': entry[6] - } - - @staticmethod - def entry_to_season(entry): - return { - 'plex_type': v.PLEX_TYPE_SEASON, - 'kodi_type': v.KODI_TYPE_SEASON, - 'plex_id': entry[0], - 'checksum': entry[1], - 'section_id': entry[2], - 'show_id': entry[3], - 'parent_id': entry[4], - 'kodi_id': entry[5], - 'fanart_synced': entry[6], - 'last_sync': entry[7] - } - def section_ids(self): """ Returns an iterator for section Plex ids for all sections @@ -307,7 +244,6 @@ def initialize(): checksum INTEGER UNIQUE, section_id INTEGER, kodi_id INTEGER, - fanart_synced INTEGER, last_sync INTEGER) ''') plexdb.cursor.execute(''' @@ -318,7 +254,6 @@ def initialize(): artist_id INTEGER, # plex_id of the parent artist parent_id INTEGER, # kodi_id of the parent artist kodi_id INTEGER, - fanart_synced INTEGER, last_sync INTEGER) ''') plexdb.cursor.execute(''' @@ -331,9 +266,7 @@ def initialize(): album_id INTEGER, # plex_id of the parent album parent_id INTEGER, # kodi_id of the parent album kodi_id INTEGER, - kodi_fileid INTEGER, kodi_pathid INTEGER, - fanart_synced INTEGER, last_sync INTEGER) ''') plexdb.cursor.execute(''' diff --git a/resources/lib/plex_db/movies.py b/resources/lib/plex_db/movies.py index 0a742a58..74ed360b 100644 --- a/resources/lib/plex_db/movies.py +++ b/resources/lib/plex_db/movies.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, unicode_literals +from .. import variables as v class Movies(object): - def add_movie(self, plex_id=None, checksum=None, section_id=None, - kodi_id=None, kodi_fileid=None, kodi_pathid=None, - last_sync=None): + def add_movie(self, plex_id, checksum, section_id, kodi_id, kodi_fileid, + kodi_pathid, last_sync): """ Appends or replaces an entry into the plex table for movies """ @@ -22,7 +22,7 @@ class Movies(object): last_sync) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''' - self.plexcursor.execute( + self.cursor.execute( query, (plex_id, checksum, @@ -45,6 +45,25 @@ class Movies(object): fanart_synced INTEGER, last_sync INTEGER """ - self.cursor.execute('SELECT * FROM movie WHERE plex_id = ?', + if plex_id is None: + return + self.cursor.execute('SELECT * FROM movie WHERE plex_id = ? LIMIT 1', (plex_id, )) - return self.cursor.fetchone() + return self.entry_to_movie(self.cursor.fetchone()) + + @staticmethod + def entry_to_movie(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_MOVIE, + 'kodi_type': v.KODI_TYPE_MOVIE, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'kodi_id': entry[3], + 'kodi_fileid': entry[4], + 'kodi_pathid': entry[5], + 'fanart_synced': entry[6], + 'last_sync': entry[7] + } diff --git a/resources/lib/plex_db/music.py b/resources/lib/plex_db/music.py new file mode 100644 index 00000000..3f939f65 --- /dev/null +++ b/resources/lib/plex_db/music.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, unicode_literals +from .. import variables as v + + +class Music(object): + def add_artist(self, plex_id, checksum, section_id, kodi_id, last_sync): + """ + Appends or replaces music artist entry into the plex table + """ + query = ''' + INSERT OR REPLACE INTO artist( + plex_id, + checksum, + section_id, + kodi_id, + last_sync) + VALUES (?, ?, ?, ?, ?) + ''' + self.cursor.execute( + query, + (plex_id, + checksum, + section_id, + kodi_id, + last_sync)) + + def add_album(self, plex_id, checksum, section_id, artist_id, parent_id, + kodi_id, last_sync): + """ + Appends or replaces an entry into the plex table + """ + query = ''' + INSERT OR REPLACE INTO album( + plex_id, + checksum, + section_id, + artist_id, + parent_id, + kodi_id, + last_sync) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''' + self.cursor.execute( + query, + (plex_id, + checksum, + section_id, + artist_id, + parent_id, + kodi_id, + last_sync)) + + def add_song(self, plex_id, checksum, section_id, artist_id, grandparent_id, + album_id, parent_id, kodi_id, kodi_fileid, kodi_pathid, + last_sync): + """ + Appends or replaces an entry into the plex table + """ + query = ''' + INSERT OR REPLACE INTO track( + plex_id, + checksum, + section_id, + artist_id, + grandparent_id, + album_id, + parent_id, + kodi_id, + kodi_fileid, + kodi_pathid, + last_sync) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + self.cursor.execute( + query, + (plex_id, + checksum, + section_id, + artist_id, + grandparent_id, + album_id, + parent_id, + kodi_id, + kodi_fileid, + kodi_pathid, + last_sync)) + + def artist(self, plex_id): + """ + Returns the show info as a tuple for the TV show with plex_id: + plex_id INTEGER PRIMARY KEY, + checksum INTEGER UNIQUE, + section_id INTEGER, + kodi_id INTEGER, + last_sync INTEGER + """ + if plex_id is None: + return + self.cursor.execute('SELECT * FROM artist WHERE plex_id = ? LIMIT 1', + (plex_id, )) + return self.entry_to_artist(self.cursor.fetchone()) + + def album(self, plex_id): + """ + Returns the show info as a tuple for the TV show with plex_id: + plex_id INTEGER PRIMARY KEY, + checksum INTEGER UNIQUE, + section_id INTEGER, + artist_id INTEGER, # plex_id of the parent artist + parent_id INTEGER, # kodi_id of the parent artist + kodi_id INTEGER, + last_sync INTEGER + """ + if plex_id is None: + return + self.cursor.execute('SELECT * FROM album WHERE plex_id = ? LIMIT 1', + (plex_id, )) + return self.entry_to_album(self.cursor.fetchone()) + + def song(self, plex_id): + """ + Returns the show info as a tuple for the TV show with plex_id: + plex_id INTEGER PRIMARY KEY, + checksum INTEGER UNIQUE, + section_id INTEGER, + artist_id INTEGER, # plex_id of the parent artist + grandparent_id INTEGER, # kodi_id of the parent artist + album_id INTEGER, # plex_id of the parent album + parent_id INTEGER, # kodi_id of the parent album + kodi_id INTEGER, + kodi_pathid INTEGER, + last_sync INTEGER + """ + if plex_id is None: + return + self.cursor.execute('SELECT * FROM track WHERE plex_id = ? LIMIT 1', + (plex_id, )) + return self.entry_to_song(self.cursor.fetchone()) + + @staticmethod + def entry_to_song(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_SONG, + 'kodi_type': v.KODI_TYPE_SONG, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'artist_id': entry[3], + 'grandparent_id': entry[4], + 'album_id': entry[5], + 'parent_id': entry[6], + 'kodi_id': entry[7], + 'kodi_pathid': entry[8], + 'last_sync': entry[9] + } + + @staticmethod + def entry_to_album(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_ALBUM, + 'kodi_type': v.KODI_TYPE_ALBUM, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'artist_id': entry[3], + 'parent_id': entry[4], + 'kodi_id': entry[5], + 'last_sync': entry[6] + } + + @staticmethod + def entry_to_artist(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_ARTIST, + 'kodi_type': v.KODI_TYPE_ARTIST, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'kodi_id': entry[3], + 'last_sync': entry[4] + } + + def album_has_songs(self, plex_id): + """ + Returns True if there are songs left for the album with plex_id + """ + self.cursor.execute('SELECT plex_id FROM track WHERE album_id = ? LIMIT 1', + (plex_id, )) + return self.cursor.fetchone() is not None + + def artist_has_albums(self, plex_id): + """ + Returns True if there are albums left for the artist with plex_id + """ + self.cursor.execute('SELECT plex_id FROM album WHERE artist_id = ? LIMIT 1', + (plex_id, )) + return self.cursor.fetchone() is not None + + def artist_has_songs(self, plex_id): + """ + Returns True if there are episodes left for the show with plex_id + """ + self.cursor.execute('SELECT plex_id FROM track WHERE artist_id = ? LIMIT 1', + (plex_id, )) + return self.cursor.fetchone() is not None + + def song_by_album(self, plex_id): + """ + Returns an iterator for all songs that have a parent album_id with + a value of plex_id + """ + self.cursor.execute('SELECT * FROM track WHERE album_id = ?', + (plex_id, )) + return (self.entry_to_song(x) for x in self.cursor) + + def song_by_artist(self, plex_id): + """ + Returns an iterator for all songs that have a grandparent artist_id + with a value of plex_id + """ + self.cursor.execute('SELECT * FROM track WHERE artist_id = ?', + (plex_id, )) + return (self.entry_to_song(x) for x in self.cursor) + + def album_by_artist(self, plex_id): + """ + Returns an iterator for all albums that have a parent artist_id + with a value of plex_id + """ + self.cursor.execute('SELECT * FROM album WHERE artist_id = ?', + (plex_id, )) + return (self.entry_to_album(x) for x in self.cursor) diff --git a/resources/lib/plex_db/tvshows.py b/resources/lib/plex_db/tvshows.py index bb923531..d6ae23d4 100644 --- a/resources/lib/plex_db/tvshows.py +++ b/resources/lib/plex_db/tvshows.py @@ -1,80 +1,100 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, unicode_literals +from .. import variables as v class TVShows(object): - def add_show(self, plex_id=None, checksum=None, section_id=None, - kodi_id=None, kodi_pathid=None, last_sync=None): + def add_show(self, plex_id, checksum, section_id, kodi_id, kodi_pathid, + last_sync): """ Appends or replaces tv show entry into the plex table """ query = ''' INSERT OR REPLACE INTO show( - plex_id - checksum - section_id - kodi_id - kodi_pathid - fanart_synced + plex_id, + checksum, + section_id, + kodi_id, + kodi_pathid, + fanart_synced, last_sync) VALUES (?, ?, ?, ?, ?, ?, ?) ''' - self.plexcursor.execute( + self.cursor.execute( query, - (plex_id, checksum, section_id, kodi_id, kodi_pathid, 0, + (plex_id, + checksum, + section_id, + kodi_id, + kodi_pathid, + 0, last_sync)) - def add_season(self, plex_id=None, checksum=None, section_id=None, - show_id=None, parent_id=None, kodi_id=None, last_sync=None): + def add_season(self, plex_id, checksum, section_id, show_id, parent_id, + kodi_id, last_sync): """ Appends or replaces an entry into the plex table """ query = ''' INSERT OR REPLACE INTO season( - plex_id - checksum - section_id - show_id - parent_id - kodi_id - fanart_synced + plex_id, + checksum, + section_id, + show_id, + parent_id, + kodi_id, + fanart_synced, last_sync) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''' - self.plexcursor.execute( + self.cursor.execute( query, - (plex_id, checksum, section_id, show_id, parent_id, - kodi_id, 0, last_sync)) + (plex_id, + checksum, + section_id, + show_id, + parent_id, + kodi_id, + 0, + last_sync)) - def add_episode(self, plex_id=None, checksum=None, section_id=None, - show_id=None, grandparent_id=None, season_id=None, - parent_id=None, kodi_id=None, kodi_fileid=None, - kodi_pathid=None, last_sync=None): + def add_episode(self, plex_id, checksum, section_id, show_id, + grandparent_id, season_id, parent_id, kodi_id, kodi_fileid, + kodi_pathid, last_sync): """ Appends or replaces an entry into the plex table """ query = ''' INSERT OR REPLACE INTO episode( - plex_id - checksum - section_id - show_id - grandparent_id - season_id - parent_id - kodi_id - kodi_fileid - kodi_pathid - fanart_synced + plex_id, + checksum, + section_id, + show_id, + grandparent_id, + season_id, + parent_id, + kodi_id, + kodi_fileid, + kodi_pathid, + fanart_synced, last_sync) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''' - self.plexcursor.execute( + self.cursor.execute( query, - (plex_id, checksum, section_id, show_id, grandparent_id, - season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid, - 0, last_sync)) + (plex_id, + checksum, + section_id, + show_id, + grandparent_id, + season_id, + parent_id, + kodi_id, + kodi_fileid, + kodi_pathid, + 0, + last_sync)) def show(self, plex_id): """ @@ -87,9 +107,11 @@ class TVShows(object): fanart_synced INTEGER, last_sync INTEGER """ + if plex_id is None: + return self.cursor.execute('SELECT * FROM show WHERE plex_id = ? LIMIT 1', (plex_id, )) - return self.cursor.fetchone() + return self.entry_to_show(self.cursor.fetchone()) def season(self, plex_id): """ @@ -103,9 +125,11 @@ class TVShows(object): fanart_synced INTEGER, last_sync INTEGER """ + if plex_id is None: + return self.cursor.execute('SELECT * FROM season WHERE plex_id = ? LIMIT 1', (plex_id, )) - return self.cursor.fetchone() + return self.entry_to_season(self.cursor.fetchone()) def episode(self, plex_id): """ @@ -123,9 +147,65 @@ class TVShows(object): fanart_synced INTEGER, last_sync INTEGER """ + if plex_id is None: + return self.cursor.execute('SELECT * FROM episode WHERE plex_id = ? LIMIT 1', (plex_id, )) - return self.cursor.fetchone() + return self.entry_to_episode(self.cursor.fetchone()) + + @staticmethod + def entry_to_episode(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_EPISODE, + 'kodi_type': v.KODI_TYPE_EPISODE, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'show_id': entry[3], + 'grandparent_id': entry[4], + 'season_id': entry[5], + 'parent_id': entry[6], + 'kodi_id': entry[7], + 'kodi_fileid': entry[8], + 'kodi_pathid': entry[9], + 'fanart_synced': entry[10], + 'last_sync': entry[11] + } + + @staticmethod + def entry_to_show(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_SHOW, + 'kodi_type': v.KODI_TYPE_SHOW, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'kodi_id': entry[3], + 'kodi_pathid': entry[4], + 'fanart_synced': entry[5], + 'last_sync': entry[6] + } + + @staticmethod + def entry_to_season(entry): + if not entry: + return + return { + 'plex_type': v.PLEX_TYPE_SEASON, + 'kodi_type': v.KODI_TYPE_SEASON, + 'plex_id': entry[0], + 'checksum': entry[1], + 'section_id': entry[2], + 'show_id': entry[3], + 'parent_id': entry[4], + 'kodi_id': entry[5], + 'fanart_synced': entry[6], + 'last_sync': entry[7] + } def season_has_episodes(self, plex_id): """