PlexKodiConnect/resources/lib/kodi_db/music.py

510 lines
18 KiB
Python
Raw Normal View History

2018-11-09 07:22:16 +11:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from logging import getLogger
from . import common
from .. import db, variables as v, app, timing
2018-11-09 07:22:16 +11:00
LOG = getLogger('PLEX.kodi_db.music')
class KodiMusicDB(common.KodiDBBase):
db_kind = 'music'
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def add_path(self, path):
2018-11-09 07:22:16 +11:00
"""
Add the path (unicode) to the music DB, if it does not exist already.
Returns the path id
"""
# SQL won't return existing paths otherwise
path = '' if path is None else path
self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?',
(path,))
try:
pathid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute('INSERT INTO path(strPath, strHash) VALUES (?, ?)',
(path, '123'))
pathid = self.cursor.lastrowid
2018-11-09 07:22:16 +11:00
return pathid
@db.catch_operationalerrors
def setup_kodi_default_entries(self):
"""
Makes sure that we retain the Kodi standard databases. E.g. that there
is a dummy artist with ID 1
"""
self.cursor.execute('''
INSERT OR REPLACE INTO artist(
idArtist,
strArtist,
strMusicBrainzArtistID)
VALUES (?, ?, ?)
''', (1, '[Missing Tag]', 'Artist Tag Missing'))
self.cursor.execute('''
INSERT OR REPLACE INTO role(
idRole,
strRole)
VALUES (?, ?)
''', (1, 'Artist'))
if v.KODIVERSION >= 18:
self.cursor.execute('DELETE FROM versiontagscan')
self.cursor.execute('''
INSERT INTO versiontagscan(
idVersion,
iNeedsScan,
lastscanned)
VALUES (?, ?, ?)
''', (v.DB_MUSIC_VERSION,
0,
timing.kodi_now()))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
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):
2018-11-09 07:22:16 +11:00
"""
Returns the Kodi song_id from the Kodi music database or None if not
found OR something went wrong.
"""
self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?',
2018-11-13 19:02:34 +11:00
(path, ))
2018-11-09 07:22:16 +11:00
path_ids = self.cursor.fetchall()
if len(path_ids) != 1:
LOG.debug('Found wrong number of path ids: %s for path %s, abort',
path_ids, path)
return
self.cursor.execute('SELECT idSong FROM song WHERE strFileName = ? AND idPath = ?',
(filename, path_ids[0][0]))
song_ids = self.cursor.fetchall()
if len(song_ids) != 1:
LOG.info('Found wrong number of songs %s, abort', song_ids)
return
return song_ids[0][0]
@db.catch_operationalerrors
2018-11-09 07:22:16 +11:00
def delete_song_from_song_artist(self, song_id):
"""
Deletes son from song_artist table and possibly orphaned roles
"""
2018-11-13 19:02:34 +11:00
self.cursor.execute('''
SELECT idArtist, idRole FROM song_artist
WHERE idSong = ? LIMIT 1
''', (song_id, ))
2018-11-09 07:22:16 +11:00
artist = self.cursor.fetchone()
2018-11-13 19:02:34 +11:00
if not artist:
2018-11-09 07:22:16 +11:00
# No entry to begin with
return
# Delete the entry
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
(song_id, ))
@db.catch_operationalerrors
2018-11-09 07:22:16 +11:00
def delete_album_from_discography(self, album_id):
"""
Removes the album with id album_id from the table discography
"""
# Need to get the album name as a string first!
self.cursor.execute('SELECT strAlbum, iYear FROM album WHERE idAlbum = ? LIMIT 1',
(album_id, ))
try:
name, year = self.cursor.fetchone()
except TypeError:
return
self.cursor.execute('SELECT idArtist FROM album_artist WHERE idAlbum = ? LIMIT 1',
(album_id, ))
artist = self.cursor.fetchone()
if not artist:
return
self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?',
(artist[0], name, year))
@db.catch_operationalerrors
2018-11-09 07:22:16 +11:00
def delete_song_from_song_genre(self, song_id):
"""
Deletes the one entry with id song_id from the song_genre table.
Will also delete orphaned genres from genre table
"""
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idSong = ?',
(song_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(song_id, ))
# Check for orphaned genres in both song_genre and album_genre tables
for genre in genres:
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.delete_genre(genre[0])
2018-11-09 07:22:16 +11:00
@db.catch_operationalerrors
def delete_genre(self, genre_id):
"""
Dedicated method in order to catch OperationalErrors correctly
"""
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre_id, ))
@db.catch_operationalerrors
2018-11-09 07:22:16 +11:00
def delete_album_from_album_genre(self, album_id):
"""
Deletes the one entry with id album_id from the album_genre table.
Will also delete orphaned genres from genre table
"""
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idAlbum = ?',
(album_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(album_id, ))
# Check for orphaned genres in both album_genre and song_genre tables
for genre in genres:
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.delete_genre(genre[0])
2018-11-09 07:22:16 +11:00
2018-11-13 19:02:34 +11:00
def new_album_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idAlbum), 0) FROM album')
return self.cursor.fetchone()[0] + 1
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def add_album(self, *args):
"""
strReleaseType: 'album' or 'single'
"""
2019-01-05 06:38:45 +11:00
if app.SYNC.artwork:
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
strReleaseDate,
strOrigReleaseDate,
2019-01-05 06:38:45 +11:00
bCompilation,
strReview,
strImage,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2019-01-05 06:38:45 +11:00
''', (args))
else:
args = list(args)
del args[8]
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strArtistDisp,
strGenres,
strReleaseDate,
strOrigReleaseDate,
2019-01-05 06:38:45 +11:00
bCompilation,
strReview,
strLabel,
iUserrating,
lastScraped,
strReleaseType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2019-01-05 06:38:45 +11:00
''', (args))
2018-11-13 19:02:34 +11:00
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def update_album(self, *args):
2019-01-05 06:38:45 +11:00
if app.SYNC.artwork:
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
strReleaseDate = ?,
strOrigReleaseDate = ?,
2019-01-05 06:38:45 +11:00
bCompilation = ?,
strReview = ?,
strImage = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
WHERE idAlbum = ?
''', (args))
else:
args = list(args)
del args[7]
self.cursor.execute('''
UPDATE album
SET strAlbum = ?,
strMusicBrainzAlbumID = ?,
strArtistDisp = ?,
strGenres = ?,
strReleaseDate = ?,
strOrigReleaseDate = ?,
2019-01-05 06:38:45 +11:00
bCompilation = ?,
strReview = ?,
strLabel = ?,
iUserrating = ?,
lastScraped = ?,
strReleaseType = ?
WHERE idAlbum = ?
''', (args))
2018-11-13 19:02:34 +11:00
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
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))
@db.catch_operationalerrors
2018-11-09 07:22:16 +11:00
def add_music_genres(self, kodiid, genres, mediatype):
"""
Adds a list of genres (list of unicode) for a certain Kodi item
"""
if mediatype == "album":
# Delete current genres for clean slate
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(kodiid, ))
for genre in genres:
self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?',
(genre, ))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute('INSERT INTO genre(strGenre) VALUES(?)',
(genre, ))
genreid = self.cursor.lastrowid
2018-11-09 07:22:16 +11:00
self.cursor.execute('''
INSERT OR REPLACE INTO album_genre(
idGenre,
idAlbum)
VALUES (?, ?)
''', (genreid, kodiid))
elif mediatype == "song":
# Delete current genres for clean slate
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(kodiid, ))
for genre in genres:
self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?',
(genre, ))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute('INSERT INTO genre(strGenre) VALUES (?)',
(genre, ))
genreid = self.cursor.lastrowid
2018-11-09 07:22:16 +11:00
self.cursor.execute('''
INSERT OR REPLACE INTO song_genre(
idGenre,
idSong,
iOrder)
VALUES (?, ?, ?)
''', (genreid, kodiid, 0))
2018-11-09 07:22:16 +11:00
2018-11-13 19:02:34 +11:00
def add_song_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song')
return self.cursor.fetchone()[0] + 1
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
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,
2018-12-03 02:12:17 +11:00
mood,
dateAdded)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2018-11-13 19:02:34 +11:00
''', (args))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def update_song(self, *args):
self.cursor.execute('''
UPDATE song
SET idAlbum = ?,
strArtistDisp = ?,
strGenres = ?,
strTitle = ?,
iTrack = ?,
iDuration = ?,
iYear = ?,
strFilename = ?,
iTimesPlayed = ?,
lastplayed = ?,
rating = ?,
comment = ?,
2018-12-03 02:12:17 +11:00
mood = ?,
dateAdded = ?
2018-11-13 19:02:34 +11:00
WHERE idSong = ?
''', (args))
@db.catch_operationalerrors
def set_playcount(self, *args):
self.cursor.execute('''
UPDATE song
SET iTimesPlayed = ?,
lastplayed = ?
WHERE idSong = ?
''', (args))
2018-11-13 19:02:34 +11:00
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
@db.catch_operationalerrors
2018-11-09 07:22:16 +11:00
def add_artist(self, name, musicbrainz):
"""
Adds a single artist's name to the db
"""
self.cursor.execute('''
SELECT idArtist, strArtist
FROM artist
WHERE strMusicBrainzArtistID = ?
''', (musicbrainz, ))
try:
result = self.cursor.fetchone()
artistid = result[0]
artistname = result[1]
except TypeError:
self.cursor.execute('SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE',
(name, ))
try:
artistid = self.cursor.fetchone()[0]
except TypeError:
# Krypton has a dummy first entry idArtist: 1 strArtist:
# [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
self.cursor.execute('''
INSERT INTO artist(strArtist, strMusicBrainzArtistID)
VALUES (?, ?)
''', (name, musicbrainz))
artistid = self.cursor.lastrowid
2018-11-09 07:22:16 +11:00
else:
if artistname != name:
self.cursor.execute('UPDATE artist SET strArtist = ? WHERE idArtist = ?',
(name, artistid,))
return artistid
2018-11-13 19:02:34 +11:00
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def update_artist(self, *args):
2019-01-05 06:38:45 +11:00
if app.SYNC.artwork:
self.cursor.execute('''
UPDATE artist
SET strGenres = ?,
strBiography = ?,
strImage = ?,
lastScraped = ?
WHERE idArtist = ?
''', (args))
else:
args = list(args)
del args[3], args[2]
self.cursor.execute('''
UPDATE artist
SET strGenres = ?,
strBiography = ?,
lastScraped = ?
WHERE idArtist = ?
''', (args))
2018-11-13 19:02:34 +11:00
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def remove_song(self, kodi_id):
self.cursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, ))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def remove_path(self, path_id):
self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, ))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
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))
@db.catch_operationalerrors
2018-11-22 06:20:06 +11:00
def update_userrating(self, kodi_id, kodi_type, userrating):
"""
Updates userrating for songs and albums
"""
if kodi_type == v.KODI_TYPE_SONG:
2018-11-22 17:54:54 +11:00
column = 'userrating'
2018-11-22 06:20:06 +11:00
identifier = 'idSong'
elif kodi_type == v.KODI_TYPE_ALBUM:
2018-11-22 17:54:54 +11:00
column = 'iUserrating'
2018-11-22 06:20:06 +11:00
identifier = 'idAlbum'
2018-12-09 23:02:08 +11:00
else:
return
2018-11-22 17:54:54 +11:00
self.cursor.execute('''UPDATE %s SET %s = ? WHERE ? = ?'''
% (kodi_type, column),
2018-11-22 06:20:06 +11:00
(userrating, identifier, kodi_id))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
def remove_albuminfosong(self, kodi_id):
"""
Kodi 17 only
"""
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?',
(kodi_id, ))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
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, ))
@db.catch_operationalerrors
2018-11-13 19:02:34 +11:00
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, ))