Cleanup music sync code

This commit is contained in:
croneter 2018-11-13 09:02:34 +01:00
parent aafba74ccd
commit e10d92cecb
3 changed files with 456 additions and 352 deletions

View file

@ -15,7 +15,7 @@ LOG = getLogger('PLEX.music')
class MusicMixin(object):
def __enter__(self):
"""
Open DB connections and cursors
Overwrite to use the Kodi music DB instead of the video DB
"""
self.plexconn = utils.kodi_sql('plex')
self.plexcursor = self.plexconn.cursor()
@ -91,25 +91,14 @@ class MusicMixin(object):
Remove song, orphaned artists and orphaned paths
"""
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
path_id = self.kodidb.path_id_from_song(kodi_id)
self.kodidb.delete_song_from_song_artist(kodi_id)
self.kodicursor.execute('DELETE FROM song WHERE idSong = ?',
(kodi_id, ))
self.kodidb.remove_song(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 not self.kodidb.path_id_from_song(kodi_id):
self.kodidb.remove_path(path_id)
if v.KODIVERSION < 18:
self.kodidb.delete_song_from_song_genre(kodi_id)
query = 'DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?'
self.kodicursor.execute(query, (kodi_id, ))
self.kodidb.remove_albuminfosong(kodi_id)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SONG)
def remove_album(self, kodi_id):
@ -119,26 +108,14 @@ class MusicMixin(object):
self.kodidb.delete_album_from_discography(kodi_id)
if v.KODIVERSION < 18:
self.kodidb.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.kodidb.remove_album(kodi_id)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_ALBUM)
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.remove_artist(kodi_id)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_ARTIST)
@ -189,23 +166,12 @@ class Artist(MusicMixin, ItemBase):
# artist entries.
kodi_id = self.kodidb.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()),
self.kodidb.update_artist(api.list_to_string(api.genre_list()),
api.plot(),
thumb,
fanart,
utils.unix_date_to_kodi(self.last_sync),
kodi_id))
kodi_id)
# Update artwork
self.kodidb.modify_artwork(artworks,
kodi_id,
@ -265,8 +231,8 @@ class Album(MusicMixin, ItemBase):
break
name = api.title()
userdata = api.userdata()
# Not yet implemented by Plex
musicBrainzId = None
# Not yet implemented by Plex, let's use unique last.fm or gracenote
musicBrainzId = api.guid_html_escaped()
genres = api.genre_list()
genre = api.list_to_string(genres)
# Associate artwork
@ -279,89 +245,68 @@ class Album(MusicMixin, ItemBase):
# UPDATE THE ALBUM #####
if update_item:
LOG.info("UPDATE album plex_id: %s - Name: %s", plex_id, name)
if v.KODIVERSION >= 18:
self.kodidb.update_album(name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.music_studio(),
userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync),
'album',
kodi_id)
else:
self.kodidb.update_album_17(name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.music_studio(),
userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync),
'album',
kodi_id)
# OR ADD THE ALBUM #####
else:
LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name)
kodi_id = self.kodidb.add_album(name, musicBrainzId)
# Process the album info
kodi_id = self.kodidb.new_album_id()
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(),
self.kodidb.add_album(kodi_id,
name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.music_studio(),
userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync),
v.KODI_TYPE_ALBUM,
api.music_studio(),
compilation,
kodi_id))
'album')
else:
# Kodi Krypton
query = '''
UPDATE album
SET strArtists = ?,
iYear = ?,
strGenres = ?,
strReview = ?,
strImage = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?,
strLabel = ?,
bCompilation = ?
WHERE idAlbum = ?
'''
self.kodicursor.execute(
query,
(api.artist_name(),
api.year(),
self.kodidb.add_album_17(kodi_id,
name,
musicBrainzId,
api.artist_name(),
genre,
api.year(),
compilation,
api.plot(),
thumb,
api.music_studio(),
userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync),
v.KODI_TYPE_ALBUM,
api.music_studio(),
compilation,
kodi_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()))
'album')
self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name())
self.kodidb.add_discography(artist_id, name, api.year())
if v.KODIVERSION < 18:
self.kodidb.add_music_genres(kodi_id,
genres,
@ -410,8 +355,7 @@ class Song(MusicMixin, ItemBase):
kodi_pathid = song['kodi_pathid']
else:
update_item = False
self.kodicursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song')
kodi_id = self.kodicursor.fetchone()[0] + 1
kodi_id = self.kodidb.add_song_id()
artist_id = api.grandparent_id()
album_id = api.parent_id()
@ -440,19 +384,35 @@ class Song(MusicMixin, ItemBase):
if not album_id:
# No album found, create a single's album
LOG.info('Creating singles album')
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, api.year(), 'single'))
parent_id = self.kodidb.new_album_id()
if v.KODIVERSION >= 18:
self.kodidb.add_album(kodi_id,
None,
None,
None,
genre,
api.year(),
None,
None,
None,
None,
None,
utils.unix_date_to_kodi(self.last_sync),
'single')
else:
self.kodidb.add_album_17(kodi_id,
None,
None,
None,
genre,
api.year(),
None,
None,
None,
None,
None,
utils.unix_date_to_kodi(self.last_sync),
'single')
else:
album = self.plexdb.album(album_id)
if not album:
@ -539,31 +499,11 @@ class Song(MusicMixin, ItemBase):
if update_item:
LOG.info("UPDATE song plex_id: %s - %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))
self.kodidb.update_path(path, 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,
self.kodidb.update_song(parent_id,
artists,
genre,
title,
@ -576,28 +516,9 @@ class Song(MusicMixin, ItemBase):
userdata['UserRating'],
comment,
mood,
kodi_id))
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,
self.kodidb.update_song_17(parent_id,
artists,
genre,
title,
@ -610,40 +531,16 @@ class Song(MusicMixin, ItemBase):
userdata['UserRating'],
comment,
mood,
kodi_id))
kodi_id)
# OR ADD THE SONG #####
else:
LOG.info("ADD song plex_id: %s - %s", plex_id, title)
# Add path
kodi_pathid = self.kodidb.add_path(path, hash_string="123")
kodi_pathid = self.kodidb.add_path(path)
# 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,
self.kodidb.add_song(kodi_id,
parent_id,
kodi_pathid,
artists,
@ -659,32 +556,9 @@ class Song(MusicMixin, ItemBase):
userdata['UserRating'],
0,
0,
mood))
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,
self.kodidb.add_song_17(kodi_id,
parent_id,
kodi_pathid,
artists,
@ -700,35 +574,18 @@ class Song(MusicMixin, ItemBase):
userdata['UserRating'],
0,
0,
mood))
mood)
if v.KODIVERSION < 18:
# Link song to album
query = '''
INSERT OR REPLACE INTO albuminfosong(
idAlbumInfoSong,
idAlbumInfo,
iTrack,
strTitle,
iDuration)
VALUES (?, ?, ?, ?, ?)
'''
self.kodicursor.execute(
query,
(kodi_id, parent_id, track, title, userdata['Runtime']))
self.kodidb.add_albuminfosong(kodi_id,
parent_id,
track,
title,
userdata['Runtime'])
# Link song to artists
artist_name = api.grandparent_title()
# 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))
self.kodidb.add_song_artist(grandparent_id, kodi_id, artist_name)
# Add genres
if genres:
self.kodidb.add_music_genres(kodi_id, genres, v.KODI_TYPE_SONG)

View file

@ -30,7 +30,7 @@ def kodiid_from_filename(path, kodi_type=None, db_type=None):
if kodi_type == v.KODI_TYPE_SONG or db_type == 'music':
with KodiMusicDB() as kodidb:
try:
kodi_id = kodidb.music_id_from_filename(filename, path)
kodi_id = kodidb.song_id_from_filename(filename, path)
except TypeError:
LOG.debug('No Kodi audio db element found for path %s', path)
else:

View file

@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from . import common
from .. import utils
from .. import variables as v
LOG = getLogger('PLEX.kodi_db.music')
@ -12,16 +12,10 @@ LOG = getLogger('PLEX.kodi_db.music')
class KodiMusicDB(common.KodiDBBase):
db_kind = 'music'
def __enter__(self):
self.kodiconn = utils.kodi_sql('music')
self.cursor = self.kodiconn.cursor()
return self
def add_path(self, path, hash_string=None):
def add_path(self, path):
"""
Add the path (unicode) to the music DB, if it does not exist already.
Returns the path id
Set hash_string to something unicode to set the strHash attribute
"""
# SQL won't return existing paths otherwise
path = '' if path is None else path
@ -36,10 +30,17 @@ class KodiMusicDB(common.KodiDBBase):
INSERT INTO path(idPath, strPath, strHash)
VALUES (?, ?, ?)
''',
(pathid, path, hash_string))
(pathid, path, '123'))
return pathid
def music_id_from_filename(self, filename, path):
def update_path(self, path, kodi_pathid):
self.cursor.execute('''
UPDATE path
SET strPath = ?, strHash = ?
WHERE idPath = ?
''', (path, '123', kodi_pathid))
def song_id_from_filename(self, filename, path):
"""
Returns the Kodi song_id from the Kodi music database or None if not
found OR something went wrong.
@ -63,10 +64,12 @@ class KodiMusicDB(common.KodiDBBase):
"""
Deletes son from song_artist table and possibly orphaned roles
"""
self.cursor.execute('SELECT idArtist, idRole FROM song_artist WHERE idSong = ? LIMIT 1',
(song_id, ))
self.cursor.execute('''
SELECT idArtist, idRole FROM song_artist
WHERE idSong = ? LIMIT 1
''', (song_id, ))
artist = self.cursor.fetchone()
if artist is None:
if not artist:
# No entry to begin with
return
# Delete the entry
@ -141,27 +144,107 @@ class KodiMusicDB(common.KodiDBBase):
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre[0], ))
def add_album(self, name, musicbrainz):
"""
Adds a single album to the DB
"""
self.cursor.execute('SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?',
(musicbrainz, ))
try:
albumid = self.cursor.fetchone()[0]
except TypeError:
# Create the album
def new_album_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idAlbum), 0) FROM album')
albumid = self.cursor.fetchone()[0] + 1
return self.cursor.fetchone()[0] + 1
def add_album_17(self, *args):
"""
strReleaseType: 'album' or 'single'
"""
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtists,
strGenres,
iYear,
bCompilation,
strReview,
strImage,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?)
''', (albumid, name, musicbrainz, 'album'))
return albumid
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
def update_album_17(self, *args):
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtists = ?,
strGenres = ?,
iYear = ?,
bCompilation = ?
strReview = ?,
strImage = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?,
WHERE idAlbum = ?
''', (args))
def add_album(self, *args):
"""
strReleaseType: 'album' or 'single'
"""
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
iYear,
bCompilation,
strReview,
strImage,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
def update_album(self, *args):
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
iYear = ?,
bCompilation = ?
strReview = ?,
strImage = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?,
WHERE idAlbum = ?
''', (args))
def add_albumartist(self, artist_id, kodi_id, artistname):
self.cursor.execute('''
INSERT OR REPLACE INTO album_artist(
idArtist,
idAlbum,
strArtist)
VALUES (?, ?, ?)
''', (artist_id, kodi_id, artistname))
def add_discography(self, artist_id, albumname, year):
self.cursor.execute('''
INSERT OR REPLACE INTO discography(
idArtist,
strAlbum,
strYear)
VALUES (?, ?, ?)
''', (artist_id, albumname, year))
def add_music_genres(self, kodiid, genres, mediatype):
"""
@ -210,6 +293,102 @@ class KodiMusicDB(common.KodiDBBase):
VALUES (?, ?)
''', (genreid, kodiid))
def add_song_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song')
return self.cursor.fetchone()[0] + 1
def add_song(self, *args):
self.cursor.execute('''
INSERT INTO song(
idSong,
idAlbum,
idPath,
strArtistDisp,
strGenres,
strTitle,
iTrack,
iDuration,
iYear,
strFileName,
strMusicBrainzTrackID,
iTimesPlayed,
lastplayed,
rating,
iStartOffset,
iEndOffset,
mood)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
def add_song_17(self, *args):
self.cursor.execute('''
INSERT INTO song(
idSong,
idAlbum,
idPath,
strArtists,
strGenres,
strTitle,
iTrack,
iDuration,
iYear,
strFileName,
strMusicBrainzTrackID,
iTimesPlayed,
lastplayed,
rating,
iStartOffset,
iEndOffset,
mood)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
def update_song(self, *args):
self.cursor.execute('''
UPDATE song
SET idAlbum = ?,
strArtistDisp = ?,
strGenres = ?,
strTitle = ?,
iTrack = ?,
iDuration = ?,
iYear = ?,
strFilename = ?,
iTimesPlayed = ?,
lastplayed = ?,
rating = ?,
comment = ?,
mood = ?
WHERE idSong = ?
''', (args))
def update_song_17(self, *args):
self.cursor.execute('''
UPDATE song
SET idAlbum = ?,
strArtists = ?,
strGenres = ?,
strTitle = ?,
iTrack = ?,
iDuration = ?,
iYear = ?,
strFilename = ?,
iTimesPlayed = ?,
lastplayed = ?,
rating = ?,
comment = ?,
mood = ?
WHERE idSong = ?
''', (args))
def path_id_from_song(self, kodi_id):
self.cursor.execute('SELECT idPath FROM song WHERE idSong = ? LIMIT 1',
(kodi_id, ))
try:
return self.cursor.fetchone()[0]
except TypeError:
pass
def add_artist(self, name, musicbrainz):
"""
Adds a single artist's name to the db
@ -245,3 +424,71 @@ class KodiMusicDB(common.KodiDBBase):
self.cursor.execute('UPDATE artist SET strArtist = ? WHERE idArtist = ?',
(name, artistid,))
return artistid
def update_artist(self, *args):
self.cursor.execute('''
UPDATE artist
SET strGenres = ?,
strBiography = ?,
strImage = ?,
strFanart = ?,
lastScraped = ?
WHERE idArtist = ?
''', (args))
def remove_song(self, kodi_id):
self.cursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, ))
def remove_path(self, path_id):
self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, ))
def add_song_artist(self, artist_id, song_id, artist_name):
self.cursor.execute('''
INSERT OR REPLACE INTO song_artist(
idArtist,
idSong,
idRole,
iOrder,
strArtist)
VALUES (?, ?, ?, ?, ?)
''', (artist_id, song_id, 1, 0, artist_name))
def add_albuminfosong(self, song_id, album_id, track_no, track_title,
runtime):
"""
Kodi 17 only
"""
self.cursor.execute('''
INSERT OR REPLACE INTO albuminfosong(
idAlbumInfoSong,
idAlbumInfo,
iTrack,
strTitle,
iDuration)
VALUES (?, ?, ?, ?, ?)
''', (song_id, album_id, track_no, track_title, runtime))
def remove_albuminfosong(self, kodi_id):
"""
Kodi 17 only
"""
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?',
(kodi_id, ))
def remove_album(self, kodi_id):
if v.KODIVERSION < 18:
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfo = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM album_artist WHERE idAlbum = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM album WHERE idAlbum = ?', (kodi_id, ))
def remove_artist(self, kodi_id):
self.cursor.execute('DELETE FROM album_artist WHERE idArtist = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM artist WHERE idArtist = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM song_artist WHERE idArtist = ?',
(kodi_id, ))
self.cursor.execute('DELETE FROM discography WHERE idArtist = ?',
(kodi_id, ))