Big Kodi DB overhaul - ensure video metadata updates/deletes correctly
This commit is contained in:
parent
f6336feb72
commit
f4681011b9
3 changed files with 199 additions and 473 deletions
|
@ -53,13 +53,6 @@ LOG = getLogger("PLEX." + __name__)
|
||||||
REGEX_IMDB = re_compile(r'''/(tt\d+)''')
|
REGEX_IMDB = re_compile(r'''/(tt\d+)''')
|
||||||
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
|
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
|
||||||
|
|
||||||
# Key of library: Plex-identifier. Value represents the Kodi/emby side
|
|
||||||
PEOPLE_OF_INTEREST = {
|
|
||||||
'Director': 'Director',
|
|
||||||
'Writer': 'Writer',
|
|
||||||
'Role': 'Actor',
|
|
||||||
'Producer': 'Producer'
|
|
||||||
}
|
|
||||||
# we need to use a little mapping between fanart.tv arttypes and kodi
|
# we need to use a little mapping between fanart.tv arttypes and kodi
|
||||||
# artttypes
|
# artttypes
|
||||||
FANART_TV_TYPES = [
|
FANART_TV_TYPES = [
|
||||||
|
@ -326,25 +319,35 @@ class API(object):
|
||||||
|
|
||||||
def people_list(self):
|
def people_list(self):
|
||||||
"""
|
"""
|
||||||
Returns a list of people from item, with a list item of the form
|
Returns a dict with lists of tuples:
|
||||||
{
|
{
|
||||||
'Name': xxx,
|
'actor': [..., (<name>, <artwork url>, <role>, <cast order>), ...],
|
||||||
'Type': xxx,
|
'director': [..., (<name>, ), ...],
|
||||||
'Id': xxx
|
'writer': [..., (<name>, ), ...]
|
||||||
'imageurl': url to picture, None otherwise
|
|
||||||
('Role': xxx for cast/actors only, None if not found)
|
|
||||||
}
|
}
|
||||||
|
Everything in unicode, except <cast order> which is an int.
|
||||||
|
Only <art-url> and <role> may be None if not found.
|
||||||
|
|
||||||
|
Kodi does not yet support a Producer. People may appear several times
|
||||||
|
per category and overall!
|
||||||
"""
|
"""
|
||||||
people = []
|
people = {
|
||||||
|
'actor': [],
|
||||||
|
'director': [],
|
||||||
|
'writer': []
|
||||||
|
}
|
||||||
|
cast_order = 0
|
||||||
for child in self.item:
|
for child in self.item:
|
||||||
if child.tag in PEOPLE_OF_INTEREST:
|
if child.tag == 'Role':
|
||||||
people.append({
|
people['actor'].append((child.attrib['tag'],
|
||||||
'Name': child.attrib['tag'],
|
child.get('thumb'),
|
||||||
'Type': PEOPLE_OF_INTEREST[child.tag],
|
child.get('role'),
|
||||||
'Id': child.attrib['id'],
|
cast_order))
|
||||||
'imageurl': child.get('thumb'),
|
cast_order += 1
|
||||||
'Role': child.get('role')
|
elif child.tag == 'Writer':
|
||||||
})
|
people['writer'].append((child.attrib['tag'], ))
|
||||||
|
elif child.tag == 'Director':
|
||||||
|
people['director'].append((child.attrib['tag'], ))
|
||||||
return people
|
return people
|
||||||
|
|
||||||
def genre_list(self):
|
def genre_list(self):
|
||||||
|
|
|
@ -439,7 +439,9 @@ class Movies(Items):
|
||||||
# Process countries
|
# Process countries
|
||||||
self.kodi_db.modify_countries(movieid, v.KODI_TYPE_MOVIE, countries)
|
self.kodi_db.modify_countries(movieid, v.KODI_TYPE_MOVIE, countries)
|
||||||
# Process cast
|
# Process cast
|
||||||
self.kodi_db.addPeople(movieid, api.people_list(), "movie")
|
self.kodi_db.modify_people(movieid,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
api.people_list())
|
||||||
# Process genres
|
# Process genres
|
||||||
self.kodi_db.modify_genres(movieid, v.KODI_TYPE_MOVIE, genres)
|
self.kodi_db.modify_genres(movieid, v.KODI_TYPE_MOVIE, genres)
|
||||||
# Process artwork
|
# Process artwork
|
||||||
|
@ -472,8 +474,8 @@ class Movies(Items):
|
||||||
kodi_id = plex_dbitem[0]
|
kodi_id = plex_dbitem[0]
|
||||||
file_id = plex_dbitem[1]
|
file_id = plex_dbitem[1]
|
||||||
kodi_type = plex_dbitem[4]
|
kodi_type = plex_dbitem[4]
|
||||||
LOG.debug("Removing %sid: %s file_id: %s",
|
LOG.debug('Removing %sid: %s file_id: %s',
|
||||||
kodi_type, kodi_id, file_id)
|
kodi_type, kodi_id, file_id)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -484,11 +486,11 @@ class Movies(Items):
|
||||||
|
|
||||||
if kodi_type == v.KODI_TYPE_MOVIE:
|
if kodi_type == v.KODI_TYPE_MOVIE:
|
||||||
set_id = self.kodi_db.get_set_id(kodi_id)
|
set_id = self.kodi_db.get_set_id(kodi_id)
|
||||||
self.kodi_db.delete_countries(kodi_id, kodi_type)
|
self.kodi_db.modify_countries(kodi_id, kodi_type)
|
||||||
self.kodi_db.delete_people(kodi_id, kodi_type)
|
self.kodi_db.modify_people(kodi_id, kodi_type)
|
||||||
self.kodi_db.delete_genre(kodi_id, kodi_type)
|
self.kodi_db.modify_genres(kodi_id, kodi_type)
|
||||||
self.kodi_db.delete_studios(kodi_id, kodi_type)
|
self.kodi_db.modify_studios(kodi_id, kodi_type)
|
||||||
self.kodi_db.delete_tags(kodi_id, kodi_type)
|
self.kodi_db.modify_tags(kodi_id, kodi_type)
|
||||||
self.kodi_db.modify_streams(file_id)
|
self.kodi_db.modify_streams(file_id)
|
||||||
self.kodi_db.delete_playstate(file_id)
|
self.kodi_db.delete_playstate(file_id)
|
||||||
# Delete kodi movie and file
|
# Delete kodi movie and file
|
||||||
|
@ -739,20 +741,15 @@ class TVShows(Items):
|
||||||
'''
|
'''
|
||||||
kodicursor.execute(query, (path, None, None, 1, toppathid, pathid))
|
kodicursor.execute(query, (path, None, None, 1, toppathid, pathid))
|
||||||
|
|
||||||
# Process cast
|
self.kodi_db.modify_people(showid, v.KODI_TYPE_SHOW, api.people_list())
|
||||||
people = api.people_list()
|
self.kodi_db.modify_genres(showid, v.KODI_TYPE_SHOW, genres)
|
||||||
self.kodi_db.addPeople(showid, people, "tvshow")
|
artwork.addArtwork(api.artwork(), showid, v.KODI_TYPE_SHOW, kodicursor)
|
||||||
# Process genres
|
|
||||||
self.kodi_db.addGenres(showid, genres, "tvshow")
|
|
||||||
# Process artwork
|
|
||||||
allartworks = api.artwork()
|
|
||||||
artwork.addArtwork(allartworks, showid, "tvshow", kodicursor)
|
|
||||||
# Process studios
|
# Process studios
|
||||||
self.kodi_db.addStudios(showid, studios, "tvshow")
|
self.kodi_db.modify_studios(showid, v.KODI_TYPE_SHOW, studios)
|
||||||
# Process tags: view, PMS collection tags
|
# Process tags: view, PMS collection tags
|
||||||
tags = [viewtag]
|
tags = [viewtag]
|
||||||
tags.extend(collections)
|
tags.extend(collections)
|
||||||
self.kodi_db.addTags(showid, tags, "tvshow")
|
self.kodi_db.modify_tags(showid, v.KODI_TYPE_SHOW, tags)
|
||||||
|
|
||||||
@catch_exceptions(warnuser=True)
|
@catch_exceptions(warnuser=True)
|
||||||
def add_updateSeason(self, item, viewtag=None, viewid=None):
|
def add_updateSeason(self, item, viewtag=None, viewid=None):
|
||||||
|
@ -1088,8 +1085,9 @@ class TVShows(Items):
|
||||||
))
|
))
|
||||||
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
|
||||||
# Process cast
|
# Process cast
|
||||||
people = api.people_list()
|
self.kodi_db.modify_people(episodeid,
|
||||||
self.kodi_db.addPeople(episodeid, people, "episode")
|
v.KODI_TYPE_EPISODE,
|
||||||
|
api.people_list())
|
||||||
# Process artwork
|
# Process artwork
|
||||||
# Wide "screenshot" of particular episode
|
# Wide "screenshot" of particular episode
|
||||||
poster = item.attrib.get('thumb')
|
poster = item.attrib.get('thumb')
|
||||||
|
@ -1221,9 +1219,9 @@ class TVShows(Items):
|
||||||
Remove a TV show, and only the show, no seasons or episodes
|
Remove a TV show, and only the show, no seasons or episodes
|
||||||
"""
|
"""
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
self.kodi_db.delete_genre(kodi_id, v.KODI_TYPE_SHOW)
|
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
self.kodi_db.delete_studios(kodi_id, v.KODI_TYPE_SHOW)
|
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
self.kodi_db.delete_tags(kodi_id, v.KODI_TYPE_SHOW)
|
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
self.artwork.deleteArtwork(kodi_id, v.KODI_TYPE_SHOW, kodicursor)
|
self.artwork.deleteArtwork(kodi_id, v.KODI_TYPE_SHOW, kodicursor)
|
||||||
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,))
|
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,))
|
||||||
if v.KODIVERSION >= 17:
|
if v.KODIVERSION >= 17:
|
||||||
|
@ -1246,7 +1244,7 @@ class TVShows(Items):
|
||||||
Remove an episode, and episode only
|
Remove an episode, and episode only
|
||||||
"""
|
"""
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
self.kodi_db.delete_people(kodi_id, v.KODI_TYPE_EPISODE)
|
self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE)
|
||||||
self.kodi_db.modify_streams(file_id)
|
self.kodi_db.modify_streams(file_id)
|
||||||
self.kodi_db.delete_playstate(file_id)
|
self.kodi_db.delete_playstate(file_id)
|
||||||
self.artwork.deleteArtwork(kodi_id, "episode", kodicursor)
|
self.artwork.deleteArtwork(kodi_id, "episode", kodicursor)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
###############################################################################
|
###############################################################################
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from ntpath import dirname
|
from ntpath import dirname
|
||||||
|
from sqlite3 import IntegrityError
|
||||||
|
|
||||||
import artwork
|
import artwork
|
||||||
from utils import kodi_sql, try_decode
|
from utils import kodi_sql, try_decode
|
||||||
|
@ -287,14 +288,15 @@ class KodiDBMethods(object):
|
||||||
self.cursor.execute(query_rem, (entry_id,))
|
self.cursor.execute(query_rem, (entry_id,))
|
||||||
if self.cursor.fetchone() is None:
|
if self.cursor.fetchone() is None:
|
||||||
# Delete in the original table because entry is now orphaned
|
# Delete in the original table because entry is now orphaned
|
||||||
LOG.debug('Deleting %s from Kodi DB: %s', table, entry_id)
|
LOG.debug('Removing %s from Kodi DB: %s', table, entry_id)
|
||||||
self.cursor.execute(query_delete, (entry_id,))
|
self.cursor.execute(query_delete, (entry_id,))
|
||||||
|
|
||||||
def modify_countries(self, kodi_id, kodi_type, countries):
|
def modify_countries(self, kodi_id, kodi_type, countries=None):
|
||||||
"""
|
"""
|
||||||
Writes a country (string) in the list countries into the Kodi DB. Will
|
Writes a country (string) in the list countries into the Kodi DB. Will
|
||||||
also delete any orphaned country entries.
|
also delete any orphaned country entries.
|
||||||
"""
|
"""
|
||||||
|
countries = countries if countries else []
|
||||||
self._modify_link_and_table(kodi_id,
|
self._modify_link_and_table(kodi_id,
|
||||||
kodi_type,
|
kodi_type,
|
||||||
countries,
|
countries,
|
||||||
|
@ -302,11 +304,12 @@ class KodiDBMethods(object):
|
||||||
'country',
|
'country',
|
||||||
'country_id')
|
'country_id')
|
||||||
|
|
||||||
def modify_genres(self, kodi_id, kodi_type, genres):
|
def modify_genres(self, kodi_id, kodi_type, genres=None):
|
||||||
"""
|
"""
|
||||||
Writes a country (string) in the list countries into the Kodi DB. Will
|
Writes a country (string) in the list countries into the Kodi DB. Will
|
||||||
also delete any orphaned country entries.
|
also delete any orphaned country entries.
|
||||||
"""
|
"""
|
||||||
|
genres = genres if genres else []
|
||||||
self._modify_link_and_table(kodi_id,
|
self._modify_link_and_table(kodi_id,
|
||||||
kodi_type,
|
kodi_type,
|
||||||
genres,
|
genres,
|
||||||
|
@ -314,11 +317,12 @@ class KodiDBMethods(object):
|
||||||
'genre',
|
'genre',
|
||||||
'genre_id')
|
'genre_id')
|
||||||
|
|
||||||
def modify_studios(self, kodi_id, kodi_type, studios):
|
def modify_studios(self, kodi_id, kodi_type, studios=None):
|
||||||
"""
|
"""
|
||||||
Writes a country (string) in the list countries into the Kodi DB. Will
|
Writes a country (string) in the list countries into the Kodi DB. Will
|
||||||
also delete any orphaned country entries.
|
also delete any orphaned country entries.
|
||||||
"""
|
"""
|
||||||
|
studios = studios if studios else []
|
||||||
self._modify_link_and_table(kodi_id,
|
self._modify_link_and_table(kodi_id,
|
||||||
kodi_type,
|
kodi_type,
|
||||||
studios,
|
studios,
|
||||||
|
@ -326,11 +330,12 @@ class KodiDBMethods(object):
|
||||||
'studio',
|
'studio',
|
||||||
'studio_id')
|
'studio_id')
|
||||||
|
|
||||||
def modify_tags(self, kodi_id, kodi_type, tags):
|
def modify_tags(self, kodi_id, kodi_type, tags=None):
|
||||||
"""
|
"""
|
||||||
Writes a country (string) in the list countries into the Kodi DB. Will
|
Writes a country (string) in the list countries into the Kodi DB. Will
|
||||||
also delete any orphaned country entries.
|
also delete any orphaned country entries.
|
||||||
"""
|
"""
|
||||||
|
tags = tags if tags else []
|
||||||
self._modify_link_and_table(kodi_id,
|
self._modify_link_and_table(kodi_id,
|
||||||
kodi_type,
|
kodi_type,
|
||||||
tags,
|
tags,
|
||||||
|
@ -338,201 +343,123 @@ class KodiDBMethods(object):
|
||||||
'tag',
|
'tag',
|
||||||
'tag_id')
|
'tag_id')
|
||||||
|
|
||||||
def addCountries(self, kodiid, countries, mediatype):
|
def modify_people(self, kodi_id, kodi_type, people=None):
|
||||||
for country in countries:
|
"""
|
||||||
query = ' '.join((
|
Makes sure that actors, directors and writers are recorded correctly
|
||||||
|
for the elmement kodi_id, kodi_type.
|
||||||
"SELECT country_id",
|
Will also delete a freshly orphaned actor entry.
|
||||||
"FROM country",
|
"""
|
||||||
"WHERE name = ?",
|
people = people if people else {'actor': [],
|
||||||
"COLLATE NOCASE"
|
'director': [],
|
||||||
))
|
'writer': []}
|
||||||
self.cursor.execute(query, (country,))
|
for kind, people_list in people.iteritems():
|
||||||
|
self._modify_people_kind(kodi_id, kodi_type, kind, people_list)
|
||||||
|
|
||||||
|
def _modify_people_kind(self, kodi_id, kodi_type, kind, people_list):
|
||||||
|
# Get the people already saved in the DB for this specific item
|
||||||
|
if kind == 'actor':
|
||||||
|
query = '''
|
||||||
|
SELECT actor.actor_id, actor.name, art.url, actor_link.role,
|
||||||
|
actor_link.cast_order
|
||||||
|
FROM actor_link
|
||||||
|
LEFT JOIN actor ON actor.actor_id = actor_link.actor_id
|
||||||
|
LEFT JOIN art ON (art.media_id = actor_link.actor_id AND
|
||||||
|
art.media_type = 'actor')
|
||||||
|
WHERE actor_link.media_id = ? AND actor_link.media_type = ?
|
||||||
|
'''
|
||||||
|
else:
|
||||||
|
query = '''
|
||||||
|
SELECT actor.actor_id, actor.name
|
||||||
|
FROM {0}_link
|
||||||
|
LEFT JOIN actor ON actor.actor_id = {0}_link.actor_id
|
||||||
|
WHERE {0}_link.media_id = ? AND {0}_link.media_type = ?
|
||||||
|
'''.format(kind)
|
||||||
|
self.cursor.execute(query, (kodi_id, kodi_type))
|
||||||
|
old_people = self.cursor.fetchall()
|
||||||
|
# Determine which people we need to save or delete
|
||||||
|
outdated_people = []
|
||||||
|
for person in old_people:
|
||||||
try:
|
try:
|
||||||
country_id = self.cursor.fetchone()[0]
|
people_list.remove(person[1:])
|
||||||
|
except ValueError:
|
||||||
|
outdated_people.append(person)
|
||||||
|
# Get rid of old entries
|
||||||
|
query = '''
|
||||||
|
DELETE FROM %s_link
|
||||||
|
WHERE actor_id = ? AND media_id = ? AND media_type = ?
|
||||||
|
''' % kind
|
||||||
|
query_actor_check = 'SELECT actor_id FROM %s_link WHERE actor_id = ?'
|
||||||
|
query_actor_delete = 'DELETE FROM actor WHERE actor_id = ?'
|
||||||
|
for person in outdated_people:
|
||||||
|
# Delete the outdated entry
|
||||||
|
self.cursor.execute(query, (person[0], kodi_id, kodi_type))
|
||||||
|
# Do we now have orphaned entries?
|
||||||
|
for person_kind in ('actor', 'writer', 'director'):
|
||||||
|
self.cursor.execute(query_actor_check % person_kind,
|
||||||
|
(person[0],))
|
||||||
|
if self.cursor.fetchone() is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# person entry in actor table is now orphaned
|
||||||
|
# Delete the person from actor table
|
||||||
|
LOG.debug('Removing person from Kodi DB: %s', person)
|
||||||
|
self.cursor.execute(query_actor_delete, (person[0],))
|
||||||
|
if kind == 'actor':
|
||||||
|
# Delete any associated artwork
|
||||||
|
self.artwork.deleteArtwork(person[0], 'actor', self.cursor)
|
||||||
|
# Save new people to Kodi DB by iterating over the remaining entries
|
||||||
|
if kind == 'actor':
|
||||||
|
query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)'
|
||||||
|
for person in people_list:
|
||||||
|
LOG.debug('Adding actor to Kodi DB: %s', person)
|
||||||
|
# Make sure the person entry in table actor exists
|
||||||
|
actor_id = self._get_actor_id(person[0], art_url=person[1])
|
||||||
|
# Link the person with the media element
|
||||||
|
try:
|
||||||
|
self.cursor.execute(query, (actor_id, kodi_id, kodi_type,
|
||||||
|
person[2], person[3]))
|
||||||
|
except IntegrityError:
|
||||||
|
# With Kodi, an actor may have only one role, unlike Plex
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
query = 'INSERT INTO %s_link VALUES (?, ?, ?)' % kind
|
||||||
|
for person in people_list:
|
||||||
|
LOG.debug('Adding %s to Kodi DB: %s', kind, person[0])
|
||||||
|
# Make sure the person entry in table actor exists:
|
||||||
|
actor_id = self._get_actor_id(person[0])
|
||||||
|
# Link the person with the media element
|
||||||
|
try:
|
||||||
|
self.cursor.execute(query, (actor_id, kodi_id, kodi_type))
|
||||||
|
except IntegrityError:
|
||||||
|
# Again, Kodi may have only one person assigned to a role
|
||||||
|
pass
|
||||||
|
|
||||||
except TypeError:
|
def _get_actor_id(self, name, art_url=None):
|
||||||
# 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
|
Returns the actor_id [int] for name [unicode] in table actor (without
|
||||||
associated country links in the table country_link and also deletes
|
ensuring that the name matches).
|
||||||
orphaned countries in the table country
|
If not, will create a new record with actor_id, name, art_url
|
||||||
"""
|
|
||||||
self._delete_from_link_and_table(kodi_id,
|
|
||||||
kodi_type,
|
|
||||||
'country_link',
|
|
||||||
'country',
|
|
||||||
'country_id')
|
|
||||||
|
|
||||||
def _getactorid(self, name):
|
Uses Plex ids and thus assumes that Plex person id is unique!
|
||||||
"""
|
"""
|
||||||
Crucial für sync speed!
|
self.cursor.execute('SELECT actor_id FROM actor WHERE name=? LIMIT 1',
|
||||||
"""
|
(name,))
|
||||||
query = ' '.join((
|
|
||||||
"SELECT actor_id",
|
|
||||||
"FROM actor",
|
|
||||||
"WHERE name = ?",
|
|
||||||
"LIMIT 1"
|
|
||||||
))
|
|
||||||
self.cursor.execute(query, (name,))
|
|
||||||
try:
|
try:
|
||||||
actorid = self.cursor.fetchone()[0]
|
actor_id = self.cursor.fetchone()[0]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Cast entry does not exists
|
# Not yet in actor DB, add person
|
||||||
self.cursor.execute("select coalesce(max(actor_id),0) from actor")
|
self.cursor.execute('SELECT COALESCE(MAX(actor_id),-1) FROM actor')
|
||||||
actorid = self.cursor.fetchone()[0] + 1
|
actor_id = self.cursor.fetchone()[0] + 1
|
||||||
query = "INSERT INTO actor(actor_id, name) VALUES (?, ?)"
|
self.cursor.execute('INSERT INTO actor(actor_id, name) '
|
||||||
self.cursor.execute(query, (actorid, name))
|
'VALUES (?, ?)',
|
||||||
return actorid
|
(actor_id, name))
|
||||||
|
if art_url:
|
||||||
def _addPerson(self, role, person_type, actorid, kodiid, mediatype,
|
self.artwork.addOrUpdateArt(art_url,
|
||||||
castorder):
|
actor_id,
|
||||||
if "Actor" == person_type:
|
'actor',
|
||||||
query = '''
|
"thumb",
|
||||||
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)
|
self.cursor)
|
||||||
|
return actor_id
|
||||||
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):
|
def existingArt(self, kodiId, mediaType, refresh=False):
|
||||||
"""
|
"""
|
||||||
|
@ -576,134 +503,46 @@ class KodiDBMethods(object):
|
||||||
result['Backdrop'] = [d[0] for d in data]
|
result['Backdrop'] = [d[0] for d in data]
|
||||||
return result
|
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):
|
def modify_streams(self, fileid, streamdetails=None, runtime=None):
|
||||||
"""
|
"""
|
||||||
Leave streamdetails and runtime empty to delete all stream entries for
|
Leave streamdetails and runtime empty to delete all stream entries for
|
||||||
fileid
|
fileid
|
||||||
"""
|
"""
|
||||||
# First remove any existing entries
|
# First remove any existing entries
|
||||||
self.cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,))
|
self.cursor.execute('DELETE FROM streamdetails WHERE idFile = ?',
|
||||||
if streamdetails:
|
(fileid,))
|
||||||
# Video details
|
if not streamdetails:
|
||||||
for videotrack in streamdetails['video']:
|
return
|
||||||
query = (
|
for videotrack in streamdetails['video']:
|
||||||
'''
|
query = '''
|
||||||
INSERT INTO streamdetails(
|
INSERT INTO streamdetails(
|
||||||
idFile, iStreamType, strVideoCodec, fVideoAspect,
|
idFile, iStreamType, strVideoCodec, fVideoAspect,
|
||||||
iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode)
|
iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
'''
|
||||||
'''
|
self.cursor.execute(query,
|
||||||
)
|
(fileid, 0, videotrack['codec'],
|
||||||
self.cursor.execute(query, (fileid, 0, videotrack['codec'],
|
videotrack['aspect'], videotrack['width'],
|
||||||
videotrack['aspect'], videotrack['width'], videotrack['height'],
|
videotrack['height'], runtime,
|
||||||
runtime ,videotrack['video3DFormat']))
|
videotrack['video3DFormat']))
|
||||||
|
for audiotrack in streamdetails['audio']:
|
||||||
# Audio details
|
query = '''
|
||||||
for audiotrack in streamdetails['audio']:
|
INSERT INTO streamdetails(
|
||||||
query = (
|
idFile, iStreamType, strAudioCodec, iAudioChannels,
|
||||||
'''
|
strAudioLanguage)
|
||||||
INSERT INTO streamdetails(
|
VALUES (?, ?, ?, ?, ?)
|
||||||
idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage)
|
'''
|
||||||
|
self.cursor.execute(query,
|
||||||
VALUES (?, ?, ?, ?, ?)
|
(fileid, 1, audiotrack['codec'],
|
||||||
'''
|
audiotrack['channels'],
|
||||||
)
|
audiotrack['language']))
|
||||||
self.cursor.execute(query, (fileid, 1, audiotrack['codec'],
|
for subtitletrack in streamdetails['subtitle']:
|
||||||
audiotrack['channels'], audiotrack['language']))
|
query = '''
|
||||||
|
INSERT INTO streamdetails(idFile, iStreamType,
|
||||||
# Subtitles details
|
strSubtitleLanguage)
|
||||||
for subtitletrack in streamdetails['subtitle']:
|
VALUES (?, ?, ?)
|
||||||
query = (
|
'''
|
||||||
'''
|
self.cursor.execute(query, (fileid, 2, subtitletrack))
|
||||||
INSERT INTO streamdetails(
|
|
||||||
idFile, iStreamType, strSubtitleLanguage)
|
|
||||||
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
'''
|
|
||||||
)
|
|
||||||
self.cursor.execute(query, (fileid, 2, subtitletrack))
|
|
||||||
|
|
||||||
def resume_points(self):
|
def resume_points(self):
|
||||||
"""
|
"""
|
||||||
|
@ -722,26 +561,6 @@ class KodiDBMethods(object):
|
||||||
ids.append(row[0])
|
ids.append(row[0])
|
||||||
return ids
|
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):
|
def video_id_from_filename(self, filename, path):
|
||||||
"""
|
"""
|
||||||
Returns the tuple (itemId, type) where
|
Returns the tuple (itemId, type) where
|
||||||
|
@ -846,46 +665,6 @@ class KodiDBMethods(object):
|
||||||
return
|
return
|
||||||
return song_id[0]
|
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):
|
def get_resume(self, file_id):
|
||||||
"""
|
"""
|
||||||
Returns the first resume point in seconds (int) if found, else None for
|
Returns the first resume point in seconds (int) if found, else None for
|
||||||
|
@ -951,60 +730,6 @@ class KodiDBMethods(object):
|
||||||
"""
|
"""
|
||||||
self.cursor.execute('DELETE FROM bookmark where idFile = ?', (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):
|
def createTag(self, name):
|
||||||
# This will create and return the tag_id
|
# This will create and return the tag_id
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
Loading…
Add table
Reference in a new issue