PlexKodiConnect/resources/lib/kodidb_functions.py

1324 lines
45 KiB
Python

# -*- coding: utf-8 -*-
###############################################################################
from logging import getLogger
from ntpath import dirname
import artwork
from utils import kodi_sql, try_decode
import variables as v
###############################################################################
LOG = getLogger("PLEX." + __name__)
###############################################################################
class GetKodiDB(object):
"""
Usage: with GetKodiDB(db_type) as kodi_db:
do stuff with kodi_db
Parameters:
db_type: DB to open: 'video', 'music', 'plex', 'texture'
On exiting "with" (no matter what), commits get automatically committed
and the db gets closed
"""
def __init__(self, db_type):
self.kodiconn = None
self.db_type = db_type
def __enter__(self):
self.kodiconn = kodi_sql(self.db_type)
kodi_db = KodiDBMethods(self.kodiconn.cursor())
return kodi_db
def __exit__(self, typus, value, traceback):
self.kodiconn.commit()
self.kodiconn.close()
class KodiDBMethods(object):
"""
Best used indirectly with another Class GetKodiDB:
with GetKodiDB(db_type) as kodi_db:
kodi_db.method()
"""
def __init__(self, cursor):
self.cursor = cursor
self.artwork = artwork.Artwork()
def setup_path_table(self):
"""
Use with Kodi video DB
Sets strContent to e.g. 'movies' and strScraper to metadata.local
For some reason, Kodi ignores this if done via itemtypes while e.g.
adding or updating items. (addPath method does NOT work)
"""
path_id = self.getPath('plugin://%s.movies/' % v.ADDON_ID)
if path_id is None:
self.cursor.execute("select coalesce(max(idPath),0) from path")
path_id = self.cursor.fetchone()[0] + 1
query = '''
INSERT INTO path(idPath,
strPath,
strContent,
strScraper,
noUpdate,
exclude)
VALUES (?, ?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (path_id,
'plugin://%s.movies/' % v.ADDON_ID,
'movies',
'metadata.local',
1,
0))
# And TV shows
path_id = self.getPath('plugin://%s.tvshows/' % v.ADDON_ID)
if path_id is None:
self.cursor.execute("select coalesce(max(idPath),0) from path")
path_id = self.cursor.fetchone()[0] + 1
query = '''
INSERT INTO path(idPath,
strPath,
strContent,
strScraper,
noUpdate,
exclude)
VALUES (?, ?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (path_id,
'plugin://%s.tvshows/' % v.ADDON_ID,
'tvshows',
'metadata.local',
1,
0))
def getParentPathId(self, path):
"""
Video DB: Adds all subdirectories to SQL path while setting a "trail"
of parentPathId
"""
if "\\" in path:
# Local path
parentpath = "%s\\" % dirname(dirname(path))
else:
# Network path
parentpath = "%s/" % dirname(dirname(path))
pathid = self.getPath(parentpath)
if pathid is None:
self.cursor.execute("select coalesce(max(idPath),0) from path")
pathid = self.cursor.fetchone()[0] + 1
query = ' '.join((
"INSERT INTO path(idPath, strPath)",
"VALUES (?, ?)"
))
self.cursor.execute(query, (pathid, parentpath))
parentPathid = self.getParentPathId(parentpath)
query = ' '.join((
"UPDATE path",
"SET idParentPath = ?",
"WHERE idPath = ?"
))
self.cursor.execute(query, (parentPathid, pathid))
return pathid
def addPath(self, path, strHash=None):
# SQL won't return existing paths otherwise
if path is None:
path = ""
query = ' '.join((
"SELECT idPath",
"FROM path",
"WHERE strPath = ?"
))
self.cursor.execute(query, (path,))
try:
pathid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("select coalesce(max(idPath),0) from path")
pathid = self.cursor.fetchone()[0] + 1
if strHash is None:
query = (
'''
INSERT INTO path(
idPath, strPath)
VALUES (?, ?)
'''
)
self.cursor.execute(query, (pathid, path))
else:
query = (
'''
INSERT INTO path(
idPath, strPath, strHash)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (pathid, path, strHash))
return pathid
def getPath(self, path):
query = ' '.join((
"SELECT idPath",
"FROM path",
"WHERE strPath = ?"
))
self.cursor.execute(query, (path,))
try:
pathid = self.cursor.fetchone()[0]
except TypeError:
pathid = None
return pathid
def addFile(self, filename, pathid):
query = ' '.join((
"SELECT idFile",
"FROM files",
"WHERE strFilename = ?",
"AND idPath = ?"
))
self.cursor.execute(query, (filename, pathid,))
try:
fileid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("select coalesce(max(idFile),0) from files")
fileid = self.cursor.fetchone()[0] + 1
query = (
'''
INSERT INTO files(
idFile, strFilename)
VALUES (?, ?)
'''
)
self.cursor.execute(query, (fileid, filename))
return fileid
def getFile(self, fileid):
query = ' '.join((
"SELECT strFilename",
"FROM files",
"WHERE idFile = ?"
))
self.cursor.execute(query, (fileid,))
try:
filename = self.cursor.fetchone()[0]
except TypeError:
filename = ""
return filename
def removeFile(self, path, filename):
pathid = self.getPath(path)
if pathid is not None:
query = ' '.join((
"DELETE FROM files",
"WHERE idPath = ?",
"AND strFilename = ?"
))
self.cursor.execute(query, (pathid, filename,))
def addCountries(self, kodiid, countries, mediatype):
for country in countries:
query = ' '.join((
"SELECT country_id",
"FROM country",
"WHERE name = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (country,))
try:
country_id = self.cursor.fetchone()[0]
except TypeError:
# Country entry does not exists
self.cursor.execute("select coalesce(max(country_id),0) from country")
country_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO country(country_id, name) values(?, ?)"
self.cursor.execute(query, (country_id, country))
LOG.debug("Add country to media, processing: %s", country)
finally: # Assign country to content
query = (
'''
INSERT OR REPLACE INTO country_link(
country_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (country_id, kodiid, mediatype))
def _delete_from_link_and_table(self, kodi_id, kodi_type, link_table,
table, key):
# Get all existing links
query = ('SELECT %s FROM %s WHERE media_id = ? AND media_type = ? '
% (key, link_table))
self.cursor.execute(query, (kodi_id, kodi_type))
key_list = self.cursor.fetchall()
# Delete all links
query = ('DELETE FROM %s WHERE media_id = ? AND media_type = ?'
% link_table)
self.cursor.execute(query, (kodi_id, kodi_type))
# Which countries are now orphaned?
query = 'SELECT %s FROM %s WHERE %s = ?' % (key, link_table, key)
query_delete = 'DELETE FROM %s WHERE %s = ?' % (table, key)
for entry in key_list:
# country_id still in table?
self.cursor.execute(query, (entry[0],))
if self.cursor.fetchone() is None:
self.cursor.execute(query_delete, (entry[0],))
def delete_countries(self, kodi_id, kodi_type):
"""
Assuming that video kodi_id, kodi_type gets deleted, will delete any
associated country links in the table country_link and also deletes
orphaned countries in the table country
"""
self._delete_from_link_and_table(kodi_id,
kodi_type,
'country_link',
'country',
'country_id')
def _getactorid(self, name):
"""
Crucial für sync speed!
"""
query = ' '.join((
"SELECT actor_id",
"FROM actor",
"WHERE name = ?",
"LIMIT 1"
))
self.cursor.execute(query, (name,))
try:
actorid = self.cursor.fetchone()[0]
except TypeError:
# Cast entry does not exists
self.cursor.execute("select coalesce(max(actor_id),0) from actor")
actorid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO actor(actor_id, name) VALUES (?, ?)"
self.cursor.execute(query, (actorid, name))
return actorid
def _addPerson(self, role, person_type, actorid, kodiid, mediatype,
castorder):
if "Actor" == person_type:
query = '''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type, role, cast_order)
VALUES (?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype, role,
castorder))
castorder += 1
elif "Director" == person_type:
query = '''
INSERT OR REPLACE INTO director_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype))
elif person_type == "Writer":
query = '''
INSERT OR REPLACE INTO writer_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype))
elif "Artist" == person_type:
query = '''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (actorid, kodiid, mediatype))
return castorder
def addPeople(self, kodiid, people, mediatype):
castorder = 0
for person in people:
actorid = self._getactorid(person['Name'])
# Link person to content
castorder = self._addPerson(person.get('Role'),
person['Type'],
actorid,
kodiid,
mediatype,
castorder)
# Add person image to art table
if person['imageurl']:
self.artwork.addOrUpdateArt(person['imageurl'], actorid,
person['Type'].lower(), "thumb",
self.cursor)
def delete_people(self, kodi_id, kodi_type):
"""
Assuming that the video kodi_id, kodi_type gets deleted, will delete any
associated actor_, director_, writer_links and also deletes
orphaned actors
"""
# Actors
query = '''
SELECT actor_id FROM actor_link
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
actor_ids = self.cursor.fetchall()
query = 'DELETE FROM actor_link WHERE media_id = ? AND media_type = ?'
self.cursor.execute(query, (kodi_id, kodi_type))
# Directors
query = '''
SELECT actor_id FROM director_link
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
actor_ids.extend(self.cursor.fetchall())
query = '''
DELETE FROM director_link WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
# Writers
query = '''
SELECT actor_id FROM writer_link
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
actor_ids.extend(self.cursor.fetchall())
query = '''
DELETE FROM writer_link WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
# Which people are now orphaned?
query_actor = 'SELECT actor_id FROM actor_link WHERE actor_id = ?'
query_director = 'SELECT actor_id FROM director_link WHERE actor_id = ?'
query_writer = 'SELECT actor_id FROM writer_link WHERE actor_id = ?'
query_delete = 'DELETE FROM actor WHERE actor_id = ?'
# Delete orphaned people
for actor_id in actor_ids:
self.cursor.execute(query_actor, (actor_id[0],))
if self.cursor.fetchone() is None:
self.cursor.execute(query_director, (actor_id[0],))
if self.cursor.fetchone() is None:
self.cursor.execute(query_writer, (actor_id[0],))
if self.cursor.fetchone() is None:
# Delete the person itself from actor table
self.cursor.execute(query_delete, (actor_id[0],))
# Delete any associated artwork
self.artwork.deleteArtwork(actor_id[0],
'actor',
self.cursor)
def existingArt(self, kodiId, mediaType, refresh=False):
"""
For kodiId, returns an artwork dict with already existing art from
the Kodi db
"""
# Only get EITHER poster OR thumb (should have same URL)
kodiToPKC = {
'banner': 'Banner',
'clearart': 'Art',
'clearlogo': 'Logo',
'discart': 'Disc',
'landscape': 'Thumb',
'thumb': 'Primary'
}
# BoxRear yet unused
result = {'BoxRear': ''}
for art in kodiToPKC:
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
self.cursor.execute(query, (kodiId, mediaType, art,))
try:
url = self.cursor.fetchone()[0]
except TypeError:
url = ""
result[kodiToPKC[art]] = url
# There may be several fanart URLs saved
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
data = self.cursor.execute(query, (kodiId, mediaType, "fanart%",))
result['Backdrop'] = [d[0] for d in data]
return result
def addGenres(self, kodi_id, genres, kodi_type):
"""
Adds the genres (list of strings) to the Kodi DB and associates them
with the element kodi_id, kodi_type
"""
# Delete current genres for clean slate
query = 'DELETE FROM genre_link WHERE media_id = ? AND media_type = ?'
self.cursor.execute(query, (kodi_id, kodi_type,))
# Add genres
for genre in genres:
query = ' SELECT genre_id FROM genre WHERE name = ? COLLATE NOCASE'
self.cursor.execute(query, (genre,))
try:
genre_id = self.cursor.fetchone()[0]
except TypeError:
# Create genre in database
self.cursor.execute("select coalesce(max(genre_id),0) from genre")
genre_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(genre_id, name) values(?, ?)"
self.cursor.execute(query, (genre_id, genre))
finally:
# Assign genre to item
query = '''
INSERT OR REPLACE INTO genre_link(
genre_id, media_id, media_type)
VALUES (?, ?, ?)
'''
self.cursor.execute(query, (genre_id, kodi_id, kodi_type))
def delete_genre(self, kodi_id, kodi_type):
"""
Removes the genre links as well as orphaned genres from the Kodi DB
"""
self._delete_from_link_and_table(kodi_id,
kodi_type,
'genre_link',
'genre',
'genre_id')
def addStudios(self, kodiid, studios, mediatype):
for studio in studios:
query = ' '.join((
"SELECT studio_id",
"FROM studio",
"WHERE name = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (studio,))
try:
studioid = self.cursor.fetchone()[0]
except TypeError:
# Studio does not exists.
self.cursor.execute("select coalesce(max(studio_id),0) from studio")
studioid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO studio(studio_id, name) values(?, ?)"
self.cursor.execute(query, (studioid, studio))
LOG.debug("Add Studios to media, processing: %s", studio)
finally: # Assign studio to item
query = (
'''
INSERT OR REPLACE INTO studio_link(
studio_id, media_id, media_type)
VALUES (?, ?, ?)
''')
self.cursor.execute(query, (studioid, kodiid, mediatype))
def delete_studios(self, kodi_id, kodi_type):
"""
Removes the studio links as well as orphaned studios from the Kodi DB
"""
self._delete_from_link_and_table(kodi_id,
kodi_type,
'studio_link',
'studio',
'studio_id')
def modify_streams(self, fileid, streamdetails=None, runtime=None):
"""
Leave streamdetails and runtime empty to delete all stream entries for
fileid
"""
# First remove any existing entries
self.cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,))
if streamdetails:
# Video details
for videotrack in streamdetails['video']:
query = (
'''
INSERT INTO streamdetails(
idFile, iStreamType, strVideoCodec, fVideoAspect,
iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
'''
)
self.cursor.execute(query, (fileid, 0, videotrack['codec'],
videotrack['aspect'], videotrack['width'], videotrack['height'],
runtime ,videotrack['video3DFormat']))
# Audio details
for audiotrack in streamdetails['audio']:
query = (
'''
INSERT INTO streamdetails(
idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage)
VALUES (?, ?, ?, ?, ?)
'''
)
self.cursor.execute(query, (fileid, 1, audiotrack['codec'],
audiotrack['channels'], audiotrack['language']))
# Subtitles details
for subtitletrack in streamdetails['subtitle']:
query = (
'''
INSERT INTO streamdetails(
idFile, iStreamType, strSubtitleLanguage)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (fileid, 2, subtitletrack))
def resume_points(self):
"""
VIDEOS
Returns all Kodi idFile that have a resume point set (not unwatched
ones or items that have already been completely watched)
"""
query = '''
SELECT idFile
FROM bookmark
'''
rows = self.cursor.execute(query)
ids = []
for row in rows:
ids.append(row[0])
return ids
def getUnplayedMusicItems(self):
"""
MUSIC
Returns all Kodi Item idFile that have not yet been completely played
"""
query = ' '.join((
"SELECT idSong",
"FROM song",
"WHERE iTimesPlayed = ?"
))
try:
rows = self.cursor.execute(query, (0, ))
except:
return []
ids = []
for row in rows:
ids.append(row[0])
return ids
def video_id_from_filename(self, filename, path):
"""
Returns the tuple (itemId, type) where
itemId: Kodi DB unique Id for either movie or episode
type: either 'movie' or 'episode'
Returns None if not found OR if too many entries were found
"""
query = ' '.join((
"SELECT idFile, idPath",
"FROM files",
"WHERE strFilename = ?"
))
self.cursor.execute(query, (filename,))
files = self.cursor.fetchall()
if len(files) == 0:
LOG.info('Did not find any file, abort')
return
query = ' '.join((
"SELECT strPath",
"FROM path",
"WHERE idPath = ?"
))
# result will contain a list of all idFile with matching filename and
# matching path
result = []
for file in files:
# Use idPath to get path as a string
self.cursor.execute(query, (file[1],))
try:
strPath = self.cursor.fetchone()[0]
except TypeError:
# idPath not found; skip
continue
# For whatever reason, double might have become triple
strPath = strPath.replace('///', '//')
strPath = strPath.replace('\\\\\\', '\\\\')
if strPath == path:
result.append(file[0])
if len(result) == 0:
LOG.info('Did not find matching paths, abort')
return
# Kodi seems to make ONE temporary entry; we only want the earlier,
# permanent one
if len(result) > 2:
LOG.warn('We found too many items with matching filenames and '
' paths, aborting')
return
idFile = result[0]
# Try movies first
query = ' '.join((
"SELECT idMovie",
"FROM movie",
"WHERE idFile = ?"
))
self.cursor.execute(query, (idFile,))
try:
itemId = self.cursor.fetchone()[0]
typus = v.KODI_TYPE_MOVIE
except TypeError:
# Try tv shows next
query = ' '.join((
"SELECT idEpisode",
"FROM episode",
"WHERE idFile = ?"
))
self.cursor.execute(query, (idFile,))
try:
itemId = self.cursor.fetchone()[0]
typus = v.KODI_TYPE_EPISODE
except TypeError:
LOG.warn('Unexpectantly did not find a match!')
return
return itemId, typus
def music_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.
"""
query = '''
SELECT idPath
FROM path
WHERE strPath = ?
'''
self.cursor.execute(query, (path,))
path_id = self.cursor.fetchall()
if len(path_id) != 1:
LOG.error('Found wrong number of path ids: %s for path %s, abort',
path_id, path)
return
query = '''
SELECT idSong
FROM song
WHERE strFileName = ? AND idPath = ?
'''
self.cursor.execute(query, (filename, path_id[0]))
song_id = self.cursor.fetchall()
if len(song_id) != 1:
LOG.info('Found wrong number of songs %s, abort', song_id)
return
return song_id[0]
def getUnplayedItems(self):
"""
VIDEOS
Returns all Kodi Item idFile that have not yet been completely played
"""
query = ' '.join((
"SELECT idFile",
"FROM files",
"WHERE playCount IS NULL OR playCount = ''"
))
try:
rows = self.cursor.execute(query)
except:
return []
ids = []
for row in rows:
ids.append(row[0])
return ids
def getVideoRuntime(self, kodiid, mediatype):
if mediatype == v.KODI_TYPE_MOVIE:
query = ' '.join((
"SELECT c11",
"FROM movie",
"WHERE idMovie = ?",
))
elif mediatype == v.KODI_TYPE_EPISODE:
query = ' '.join((
"SELECT c09",
"FROM episode",
"WHERE idEpisode = ?",
))
self.cursor.execute(query, (kodiid,))
try:
runtime = self.cursor.fetchone()[0]
except TypeError:
return None
return int(runtime)
def get_resume(self, file_id):
"""
Returns the first resume point in seconds (int) if found, else None for
the Kodi file_id provided
"""
query = '''
SELECT timeInSeconds
FROM bookmark
WHERE idFile = ?
'''
self.cursor.execute(query, (file_id,))
resume = self.cursor.fetchone()
try:
resume = resume[0]
except TypeError:
resume = None
return resume
def delete_all_playstates(self):
"""
Entirely resets the table bookmark and thus all resume points
"""
self.cursor.execute("DELETE FROM bookmark")
def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount,
dateplayed):
# Delete existing resume point
query = '''
DELETE FROM bookmark
WHERE idFile = ?
'''
self.cursor.execute(query, (fileid,))
# Set watched count
query = '''
UPDATE files
SET playCount = ?, lastPlayed = ?
WHERE idFile = ?
'''
self.cursor.execute(query, (playcount, dateplayed, fileid))
# Set the resume bookmark
if resume_seconds:
self.cursor.execute(
'select coalesce(max(idBookmark),0) from bookmark')
bookmark_id = self.cursor.fetchone()[0] + 1
query = '''
INSERT INTO bookmark(
idBookmark, idFile, timeInSeconds, totalTimeInSeconds,
thumbNailImage, player, playerState, type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (bookmark_id,
fileid,
resume_seconds,
total_seconds,
'',
"VideoPlayer",
'',
1))
def delete_playstate(self, file_id):
"""
Removes all playstates/bookmarks for the file with file_id
"""
self.cursor.execute('DELETE FROM bookmark where idFile = ?', (file_id,))
def addTags(self, kodiid, tags, mediatype):
# First, delete any existing tags associated to the id
query = ' '.join((
"DELETE FROM tag_link",
"WHERE media_id = ?",
"AND media_type = ?"
))
self.cursor.execute(query, (kodiid, mediatype))
# Add tags
LOG.debug("Adding Tags: %s", tags)
for tag in tags:
self.addTag(kodiid, tag, mediatype)
def delete_tags(self, kodi_id, kodi_type):
"""
Removes the genre links as well as orphaned genres from the Kodi DB
"""
self._delete_from_link_and_table(kodi_id,
kodi_type,
'tag_link',
'tag',
'tag_id')
def addTag(self, kodiid, tag, mediatype):
query = ' '.join((
"SELECT tag_id",
"FROM tag",
"WHERE name = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (tag,))
try:
tag_id = self.cursor.fetchone()[0]
except TypeError:
# Create the tag, because it does not exist
tag_id = self.createTag(tag)
LOG.debug("Adding tag: %s", tag)
finally:
# Assign tag to item
query = (
'''
INSERT OR REPLACE INTO tag_link(
tag_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (tag_id, kodiid, mediatype))
def createTag(self, name):
# This will create and return the tag_id
query = ' '.join((
"SELECT tag_id",
"FROM tag",
"WHERE name = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (name,))
try:
tag_id = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("select coalesce(max(tag_id),0) from tag")
tag_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO tag(tag_id, name) values(?, ?)"
self.cursor.execute(query, (tag_id, name))
LOG.debug("Create tag_id: %s name: %s", tag_id, name)
return tag_id
def updateTag(self, oldtag, newtag, kodiid, mediatype):
try:
query = ' '.join((
"UPDATE tag_link",
"SET tag_id = ?",
"WHERE media_id = ?",
"AND media_type = ?",
"AND tag_id = ?"
))
self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
except Exception as e:
# The new tag we are going to apply already exists for this item
# delete current tag instead
query = ' '.join((
"DELETE FROM tag_link",
"WHERE media_id = ?",
"AND media_type = ?",
"AND tag_id = ?"
))
self.cursor.execute(query, (kodiid, mediatype, oldtag,))
def addSets(self, movieid, collections, kodicursor):
for setname in collections:
setid = self.createBoxset(setname)
self.assignBoxset(setid, movieid)
def createBoxset(self, boxsetname):
LOG.debug("Adding boxset: %s", boxsetname)
query = ' '.join((
"SELECT idSet",
"FROM sets",
"WHERE strSet = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (boxsetname,))
try:
setid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("select coalesce(max(idSet),0) from sets")
setid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO sets(idSet, strSet) values(?, ?)"
self.cursor.execute(query, (setid, boxsetname))
return setid
def assignBoxset(self, setid, movieid):
query = ' '.join((
"UPDATE movie",
"SET idSet = ?",
"WHERE idMovie = ?"
))
self.cursor.execute(query, (setid, movieid,))
def removefromBoxset(self, movieid):
query = ' '.join((
"UPDATE movie",
"SET idSet = null",
"WHERE idMovie = ?"
))
self.cursor.execute(query, (movieid,))
def get_set_id(self, kodi_id):
"""
Returns the set_id for the movie with kodi_id or None
"""
query = 'SELECT idSet FROM movie WHERE idMovie = ?'
self.cursor.execute(query, (kodi_id,))
try:
answ = self.cursor.fetchone()[0]
except TypeError:
answ = None
return answ
def delete_possibly_empty_set(self, set_id):
"""
Checks whether there are other movies in the set set_id. If not,
deletes the set
"""
query = 'SELECT idSet FROM movie WHERE idSet = ?'
self.cursor.execute(query, (set_id,))
if self.cursor.fetchone() is None:
query = 'DELETE FROM sets WHERE idSet = ?'
self.cursor.execute(query, (set_id,))
def addSeason(self, showid, seasonnumber):
query = ' '.join((
"SELECT idSeason",
"FROM seasons",
"WHERE idShow = ?",
"AND season = ?"
))
self.cursor.execute(query, (showid, seasonnumber,))
try:
seasonid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("select coalesce(max(idSeason),0) from seasons")
seasonid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)"
self.cursor.execute(query, (seasonid, showid, seasonnumber))
return seasonid
def addArtist(self, name, musicbrainz):
query = ' '.join((
"SELECT idArtist, strArtist",
"FROM artist",
"WHERE strMusicBrainzArtistID = ?"
))
self.cursor.execute(query, (musicbrainz,))
try:
result = self.cursor.fetchone()
artistid = result[0]
artistname = result[1]
except TypeError:
query = ' '.join((
"SELECT idArtist",
"FROM artist",
"WHERE strArtist = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (name,))
try:
artistid = self.cursor.fetchone()[0]
except TypeError:
# Krypton has a dummy first entry idArtist: 1 strArtist:
# [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
if v.KODIVERSION >= 17:
self.cursor.execute(
"select coalesce(max(idArtist),1) from artist")
else:
self.cursor.execute(
"select coalesce(max(idArtist),0) from artist")
artistid = self.cursor.fetchone()[0] + 1
query = (
'''
INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (artistid, name, musicbrainz))
else:
if artistname != name:
query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
self.cursor.execute(query, (name, artistid,))
return artistid
def addAlbum(self, name, musicbrainz):
query = ' '.join((
"SELECT idAlbum",
"FROM album",
"WHERE strMusicBrainzAlbumID = ?"
))
self.cursor.execute(query, (musicbrainz,))
try:
albumid = self.cursor.fetchone()[0]
except TypeError:
# Create the album
self.cursor.execute("select coalesce(max(idAlbum),0) from album")
albumid = self.cursor.fetchone()[0] + 1
query = (
'''
INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)
VALUES (?, ?, ?, ?)
'''
)
self.cursor.execute(query, (albumid, name, musicbrainz, "album"))
return albumid
def addMusicGenres(self, kodiid, genres, mediatype):
if mediatype == "album":
# Delete current genres for clean slate
query = ' '.join((
"DELETE FROM album_genre",
"WHERE idAlbum = ?"
))
self.cursor.execute(query, (kodiid,))
for genre in genres:
query = ' '.join((
"SELECT idGenre",
"FROM genre",
"WHERE strGenre = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (genre,))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute("select coalesce(max(idGenre),0) from genre")
genreid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
self.cursor.execute(query, (genreid, genre))
query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)"
self.cursor.execute(query, (genreid, kodiid))
elif mediatype == "song":
# Delete current genres for clean slate
query = ' '.join((
"DELETE FROM song_genre",
"WHERE idSong = ?"
))
self.cursor.execute(query, (kodiid,))
for genre in genres:
query = ' '.join((
"SELECT idGenre",
"FROM genre",
"WHERE strGenre = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (genre,))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute("select coalesce(max(idGenre),0) from genre")
genreid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
self.cursor.execute(query, (genreid, genre))
query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
self.cursor.execute(query, (genreid, kodiid))
# Krypton only stuff ##############################
def update_userrating(self, kodi_id, kodi_type, userrating):
"""
Updates userrating for >=Krypton
"""
if kodi_type == v.KODI_TYPE_MOVIE:
ID = 'idMovie'
elif kodi_type == v.KODI_TYPE_EPISODE:
ID = 'idEpisode'
elif kodi_type == v.KODI_TYPE_SONG:
ID = 'idSong'
query = '''UPDATE %s SET userrating = ? WHERE ? = ?''' % kodi_type
self.cursor.execute(query, (userrating, ID, kodi_id))
def add_uniqueid(self, *args):
"""
Feed with:
uniqueid_id: int
media_id: int
media_type: string
value: string
type: e.g. 'imdb' or 'tvdb'
"""
query = '''
INSERT INTO uniqueid(
uniqueid_id, media_id, media_type, value, type)
VALUES (?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (args))
def get_uniqueid(self, kodi_id, kodi_type):
query = '''
SELECT uniqueid_id FROM uniqueid
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
try:
uniqueid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute(
'SELECT COALESCE(MAX(uniqueid_id),0) FROM uniqueid')
uniqueid = self.cursor.fetchone()[0] + 1
return uniqueid
def update_uniqueid(self, *args):
"""
Pass in media_id, media_type, value, type, uniqueid_id
"""
query = '''
UPDATE uniqueid
SET media_id = ?, media_type = ?, value = ?, type = ?
WHERE uniqueid_id = ?
'''
self.cursor.execute(query, (args))
def remove_uniqueid(self, kodi_id, kodi_type):
query = '''
DELETE FROM uniqueid
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
def get_ratingid(self, kodi_id, kodi_type):
query = '''
SELECT rating_id FROM rating
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
try:
ratingid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute('SELECT COALESCE(MAX(rating_id),0) FROM rating')
ratingid = self.cursor.fetchone()[0] + 1
return ratingid
def update_ratings(self, *args):
"""
Feed with media_id, media_type, rating_type, rating, votes, rating_id
"""
query = '''
UPDATE rating
SET media_id = ?,
media_type = ?,
rating_type = ?,
rating = ?,
votes = ?
WHERE rating_id = ?
'''
self.cursor.execute(query, (args))
def add_ratings(self, *args):
"""
feed with:
rating_id, media_id, media_type, rating_type, rating, votes
rating_type = 'default'
"""
query = '''
INSERT INTO rating(
rating_id, media_id, media_type, rating_type, rating, votes)
VALUES (?, ?, ?, ?, ?, ?)
'''
self.cursor.execute(query, (args))
def remove_ratings(self, kodi_id, kodi_type):
query = '''
DELETE FROM rating
WHERE media_id = ? AND media_type = ?
'''
self.cursor.execute(query, (kodi_id, kodi_type))
def kodiid_from_filename(path, kodi_type):
"""
Returns kodi_id if we have an item in the Kodi video or audio database with
said path. Feed with the Kodi itemtype, e.v. 'movie', 'song'
Returns None if not possible
"""
kodi_id = None
path = try_decode(path)
try:
filename = path.rsplit('/', 1)[1]
path = path.rsplit('/', 1)[0] + '/'
except IndexError:
filename = path.rsplit('\\', 1)[1]
path = path.rsplit('\\', 1)[0] + '\\'
if kodi_type == v.KODI_TYPE_SONG:
with GetKodiDB('music') as kodi_db:
try:
kodi_id, _ = kodi_db.music_id_from_filename(filename, path)
except TypeError:
LOG.debug('No Kodi audio db element found for path %s', path)
else:
with GetKodiDB('video') as kodi_db:
try:
kodi_id, _ = kodi_db.video_id_from_filename(filename, path)
except TypeError:
LOG.debug('No kodi video db element found for path %s', path)
return kodi_id