finished the videonodes building, also localized the labels and moved generation to startup
added tvdb field to tv shows for compatability with kodi addons added country field to movies
This commit is contained in:
parent
aed5abf16d
commit
8e8c80980f
6 changed files with 311 additions and 216 deletions
|
@ -159,9 +159,10 @@
|
||||||
<string id="30169">Address : </string>
|
<string id="30169">Address : </string>
|
||||||
|
|
||||||
|
|
||||||
<string id="30170">Alle films</string>
|
<string id="30170">Recent toegevoegde TV-series</string>
|
||||||
<string id="30171">Alle TV</string>
|
<string id="30171">Niet afgekeken TV-series</string>
|
||||||
<string id="30172">Alle Muziek</string>
|
|
||||||
|
<string id="30172">Alle Muziek</string>
|
||||||
<string id="30173">Kanalen</string>
|
<string id="30173">Kanalen</string>
|
||||||
<string id="30174">Recent toegevoegde films</string>
|
<string id="30174">Recent toegevoegde films</string>
|
||||||
<string id="30175">Recent toegevoegde afleveringen</string>
|
<string id="30175">Recent toegevoegde afleveringen</string>
|
||||||
|
@ -179,7 +180,7 @@
|
||||||
<string id="30187">Muziek videos</string>
|
<string id="30187">Muziek videos</string>
|
||||||
<string id="30188">Fotos</string>
|
<string id="30188">Fotos</string>
|
||||||
<string id="30189">Onbekeken films</string>
|
<string id="30189">Onbekeken films</string>
|
||||||
<string id="30190">Film Genres</string>
|
|
||||||
<string id="30191">Film Studios</string>
|
<string id="30191">Film Studios</string>
|
||||||
<string id="30192">Film Acteurs</string>
|
<string id="30192">Film Acteurs</string>
|
||||||
<string id="30193">Onbekeken afleveringen</string>
|
<string id="30193">Onbekeken afleveringen</string>
|
||||||
|
|
|
@ -160,8 +160,8 @@
|
||||||
<string id="30169">Address : </string>
|
<string id="30169">Address : </string>
|
||||||
|
|
||||||
|
|
||||||
<string id="30170">All Movies</string>
|
<string id="30170">Recently Added Tv Shows</string>
|
||||||
<string id="30171">All TV</string>
|
<string id="30171">In Progress Tv Shows</string>
|
||||||
<string id="30172">All Music</string>
|
<string id="30172">All Music</string>
|
||||||
<string id="30173">Channels</string>
|
<string id="30173">Channels</string>
|
||||||
<string id="30174">Recently Added Movies</string>
|
<string id="30174">Recently Added Movies</string>
|
||||||
|
|
|
@ -155,8 +155,8 @@
|
||||||
<string id="30168">Server gefunden</string>
|
<string id="30168">Server gefunden</string>
|
||||||
<string id="30169">Addresse : </string>
|
<string id="30169">Addresse : </string>
|
||||||
|
|
||||||
<string id="30170">Alle Filme</string>
|
<string id="30170">Zuletzt hinzugefügte Serien</string>
|
||||||
<string id="30171">Alle Serien</string>
|
<string id="30171">Begonnene Serien</string>
|
||||||
<string id="30172">Alles an Musik</string>
|
<string id="30172">Alles an Musik</string>
|
||||||
<string id="30173">Kanäle</string>
|
<string id="30173">Kanäle</string>
|
||||||
<string id="30174">Zuletzt hinzugefügte Filme</string>
|
<string id="30174">Zuletzt hinzugefügte Filme</string>
|
||||||
|
|
|
@ -63,6 +63,9 @@ class LibrarySync():
|
||||||
cursor.execute("CREATE TABLE IF NOT EXISTS emby(emby_id TEXT, kodi_id INTEGER, media_type TEXT, checksum TEXT, parent_id INTEGER)")
|
cursor.execute("CREATE TABLE IF NOT EXISTS emby(emby_id TEXT, kodi_id INTEGER, media_type TEXT, checksum TEXT, parent_id INTEGER)")
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
||||||
|
### BUILD VIDEO NODES LISTING ###
|
||||||
|
utils.buildVideoNodesListing()
|
||||||
|
|
||||||
# sync movies
|
# sync movies
|
||||||
self.MoviesFullSync(connection,cursor,pDialog)
|
self.MoviesFullSync(connection,cursor,pDialog)
|
||||||
|
|
||||||
|
@ -245,7 +248,6 @@ class LibrarySync():
|
||||||
for kodishow in allKodiTvShows:
|
for kodishow in allKodiTvShows:
|
||||||
allKodiTvShowIds.append(kodishow[1])
|
allKodiTvShowIds.append(kodishow[1])
|
||||||
|
|
||||||
|
|
||||||
#### TVSHOW: PROCESS ADDS AND UPDATES ###
|
#### TVSHOW: PROCESS ADDS AND UPDATES ###
|
||||||
for item in allEmbyTvShows:
|
for item in allEmbyTvShows:
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,9 @@ def reset():
|
||||||
for dir in allDirs:
|
for dir in allDirs:
|
||||||
if dir.startswith("Emby "):
|
if dir.startswith("Emby "):
|
||||||
shutil.rmtree(xbmc.translatePath("special://userdata/library/video/" + dir))
|
shutil.rmtree(xbmc.translatePath("special://userdata/library/video/" + dir))
|
||||||
|
for file in allFiles:
|
||||||
|
if file.startswith("emby"):
|
||||||
|
xbmcvfs.delete(path + file)
|
||||||
|
|
||||||
# Ask if user information should be deleted too.
|
# Ask if user information should be deleted too.
|
||||||
return_user = xbmcgui.Dialog().yesno("Warning", "Reset all Emby Addon settings?")
|
return_user = xbmcgui.Dialog().yesno("Warning", "Reset all Emby Addon settings?")
|
||||||
|
@ -209,3 +211,244 @@ def reset():
|
||||||
dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.')
|
dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.')
|
||||||
xbmc.executebuiltin("RestartApp")
|
xbmc.executebuiltin("RestartApp")
|
||||||
|
|
||||||
|
|
||||||
|
def buildVideoNodeForView(tagname, type):
|
||||||
|
#this method will build a video node for a particular Emby view (= tag in kodi)
|
||||||
|
|
||||||
|
libraryPath = xbmc.translatePath("special://userdata/library/video/Emby - %s/" %tagname)
|
||||||
|
|
||||||
|
if not xbmcvfs.exists(libraryPath):
|
||||||
|
#create tag node - index
|
||||||
|
xbmcvfs.mkdir(libraryPath)
|
||||||
|
nodefile = os.path.join(libraryPath, "index.xml")
|
||||||
|
root = Element("node", {"order":"1"})
|
||||||
|
SubElement(root, "label").text = "Emby - " + tagname
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - all items
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_all.xml")
|
||||||
|
root = Element("node", {"order":"1", "type":"filter"})
|
||||||
|
SubElement(root, "label").text = tagname
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = type
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = tagname
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - recent items
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_recent.xml")
|
||||||
|
root = Element("node", {"order":"2", "type":"filter"})
|
||||||
|
if type == "tvshows":
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30170)
|
||||||
|
else:
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30174)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = type
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = tagname
|
||||||
|
SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
|
||||||
|
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
SubElement(root, "limit").text = "25"
|
||||||
|
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
Rule2 = SubElement(root, "rule", {"field":"playcount","operator":"is"})
|
||||||
|
SubElement(Rule2, "value").text = "0"
|
||||||
|
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - inprogress items
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_progress.xml")
|
||||||
|
root = Element("node", {"order":"3", "type":"filter"})
|
||||||
|
if type == "tvshows":
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30171)
|
||||||
|
else:
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30177)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = type
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = tagname
|
||||||
|
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
SubElement(root, "limit").text = "25"
|
||||||
|
Rule2 = SubElement(root, "rule", {"field":"inprogress","operator":"true"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - add unwatched movies node for movies
|
||||||
|
if type == "movies":
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_unwatched.xml")
|
||||||
|
root = Element("node", {"order":"4", "type":"filter"})
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30189)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = "movies"
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = tagname
|
||||||
|
Rule = SubElement(root, "rule", {"field":"playcount","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = "0"
|
||||||
|
SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
|
||||||
|
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
SubElement(root, "limit").text = "25"
|
||||||
|
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
Rule2 = SubElement(root, "rule", {"field":"playcount","operator":"is"})
|
||||||
|
SubElement(Rule2, "value").text = "0"
|
||||||
|
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
|
||||||
|
#add some additional nodes for episodes
|
||||||
|
if type == "tvshows":
|
||||||
|
#create tag node - recent episodes
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_recent_episodes.xml")
|
||||||
|
root = Element("node", {"order":"3", "type":"filter"})
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30175)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = "episodes"
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = tagname
|
||||||
|
SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
|
||||||
|
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
SubElement(root, "limit").text = "25"
|
||||||
|
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
Rule2 = SubElement(root, "rule", {"field":"playcount","operator":"is"})
|
||||||
|
SubElement(Rule2, "value").text = "0"
|
||||||
|
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - inprogress items
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_progress_episodes.xml")
|
||||||
|
root = Element("node", {"order":"4", "type":"filter"})
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30178)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = "episodes"
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = tagname
|
||||||
|
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
||||||
|
SubElement(root, "limit").text = "25"
|
||||||
|
Rule2 = SubElement(root, "rule", {"field":"inprogress","operator":"true"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - nextup items
|
||||||
|
nodefile = os.path.join(libraryPath, tagname + "_nextup_episodes.xml")
|
||||||
|
root = Element("node", {"order":"4", "type":"folder"})
|
||||||
|
SubElement(root, "label").text = tagname + " - " + language(30179)
|
||||||
|
SubElement(root, "content").text = "episodes"
|
||||||
|
SubElement(root, "path").text = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" %tagname
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
def buildVideoNodesListing():
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
from ReadEmbyDB import ReadEmbyDB
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# the library path doesn't exist on all systems
|
||||||
|
if not xbmcvfs.exists("special://userdata/library/"):
|
||||||
|
xbmcvfs.mkdir("special://userdata/library")
|
||||||
|
if not xbmcvfs.exists("special://userdata/library/video/"):
|
||||||
|
#we need to copy over the default items
|
||||||
|
import shutil
|
||||||
|
shutil.copytree(xbmc.translatePath("special://xbmc/system/library/video"), xbmc.translatePath("special://userdata/library/video"))
|
||||||
|
|
||||||
|
#always cleanup existing Emby video nodes first because we don't want old stuff to stay in there
|
||||||
|
path = "special://userdata/library/video/"
|
||||||
|
if xbmcvfs.exists(path):
|
||||||
|
allDirs, allFiles = xbmcvfs.listdir(path)
|
||||||
|
for dir in allDirs:
|
||||||
|
if dir.startswith("Emby "):
|
||||||
|
shutil.rmtree(xbmc.translatePath("special://userdata/library/video/" + dir))
|
||||||
|
for file in allFiles:
|
||||||
|
if file.startswith("emby"):
|
||||||
|
xbmcvfs.delete(path + file)
|
||||||
|
|
||||||
|
#create tag node for emby channels
|
||||||
|
nodefile = os.path.join(xbmc.translatePath("special://userdata/library/video"), "emby_channels.xml")
|
||||||
|
if not xbmcvfs.exists(nodefile):
|
||||||
|
root = Element("node", {"order":"1", "type":"folder"})
|
||||||
|
SubElement(root, "label").text = "Emby - " + language(30173)
|
||||||
|
SubElement(root, "content").text = "movies"
|
||||||
|
SubElement(root, "path").text = "plugin://plugin.video.emby/?id=0&mode=channels"
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - favorite shows
|
||||||
|
nodefile = os.path.join(xbmc.translatePath("special://userdata/library/video"),"emby_favorite_shows.xml")
|
||||||
|
if not xbmcvfs.exists(nodefile):
|
||||||
|
root = Element("node", {"order":"1", "type":"filter"})
|
||||||
|
SubElement(root, "label").text = "Emby - " + language(30181)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = "tvshows"
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = "Favorite tvshows" #do not localize the tagname itself
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#create tag node - favorite movies
|
||||||
|
nodefile = os.path.join(xbmc.translatePath("special://userdata/library/video"),"emby_favorite_movies.xml")
|
||||||
|
if not xbmcvfs.exists(nodefile):
|
||||||
|
root = Element("node", {"order":"1", "type":"filter"})
|
||||||
|
SubElement(root, "label").text = "Emby - " + language(30180)
|
||||||
|
SubElement(root, "match").text = "all"
|
||||||
|
SubElement(root, "content").text = "movies"
|
||||||
|
SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
|
||||||
|
SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
|
||||||
|
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
||||||
|
SubElement(Rule, "value").text = "Favorite movies" #do not localize the tagname itself
|
||||||
|
try:
|
||||||
|
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
||||||
|
except:
|
||||||
|
ET.ElementTree(root).write(nodefile)
|
||||||
|
|
||||||
|
#build the listing for all views
|
||||||
|
views_movies = ReadEmbyDB().getCollections("movies")
|
||||||
|
if views_movies:
|
||||||
|
for view in views_movies:
|
||||||
|
buildVideoNodeForView(view.get('title'), "movies")
|
||||||
|
|
||||||
|
views_shows = ReadEmbyDB().getCollections("tvshows")
|
||||||
|
if views_shows:
|
||||||
|
for view in views_shows:
|
||||||
|
buildVideoNodeForView(view.get('title'), "tvshows")
|
||||||
|
|
||||||
|
except:
|
||||||
|
logMsg("Emby addon","Error while creating videonodes listings, restart required ?")
|
||||||
|
|
|
@ -86,6 +86,9 @@ class WriteKodiDB():
|
||||||
studio = " / ".join(studios)
|
studio = " / ".join(studios)
|
||||||
writer = " / ".join(people.get("Writer"))
|
writer = " / ".join(people.get("Writer"))
|
||||||
director = " / ".join(people.get("Director"))
|
director = " / ".join(people.get("Director"))
|
||||||
|
country = None
|
||||||
|
if MBitem.get("ProductionLocations") !=None and MBitem.get("ProductionLocations") != []:
|
||||||
|
country = MBitem.get("ProductionLocations")[0]
|
||||||
|
|
||||||
imdb = None
|
imdb = None
|
||||||
if MBitem.get("ProviderIds"):
|
if MBitem.get("ProviderIds"):
|
||||||
|
@ -155,8 +158,8 @@ class WriteKodiDB():
|
||||||
cursor.execute("select coalesce(max(idMovie),0) as movieid from movie")
|
cursor.execute("select coalesce(max(idMovie),0) as movieid from movie")
|
||||||
movieid = cursor.fetchone()[0]
|
movieid = cursor.fetchone()[0]
|
||||||
movieid = movieid + 1
|
movieid = movieid + 1
|
||||||
pathsql="insert into movie(idMovie, idFile, c00, c01, c02, c05, c06, c07, c09, c10, c11, c12, c14, c15, c16, c18, c19) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
pathsql="insert into movie(idMovie, idFile, c00, c01, c02, c05, c06, c07, c09, c10, c11, c12, c14, c15, c16, c18, c19, c21) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
cursor.execute(pathsql, (movieid, fileid, title, plot, shortplot, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailerUrl))
|
cursor.execute(pathsql, (movieid, fileid, title, plot, shortplot, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailerUrl, country))
|
||||||
|
|
||||||
#add the viewtag
|
#add the viewtag
|
||||||
self.AddTagToMedia(movieid, viewTag, "movie", cursor)
|
self.AddTagToMedia(movieid, viewTag, "movie", cursor)
|
||||||
|
@ -168,8 +171,8 @@ class WriteKodiDB():
|
||||||
#### UPDATE THE MOVIE #####
|
#### UPDATE THE MOVIE #####
|
||||||
else:
|
else:
|
||||||
utils.logMsg("UPDATE movie to Kodi library","Id: %s - Title: %s" % (embyId, title))
|
utils.logMsg("UPDATE movie to Kodi library","Id: %s - Title: %s" % (embyId, title))
|
||||||
pathsql="update movie SET c00 = ?, c01 = ?, c02 = ?, c05 = ?, c06 = ?, c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?, c16 = ?, c18 = ?, c19 = ? WHERE idMovie = ?"
|
pathsql="update movie SET c00 = ?, c01 = ?, c02 = ?, c05 = ?, c06 = ?, c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?, c16 = ?, c18 = ?, c19 = ?, c21 = ?WHERE idMovie = ?"
|
||||||
cursor.execute(pathsql, (title, plot, shortplot, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailerUrl, movieid))
|
cursor.execute(pathsql, (title, plot, shortplot, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailerUrl, country, movieid))
|
||||||
|
|
||||||
#update the checksum in emby table
|
#update the checksum in emby table
|
||||||
cursor.execute("UPDATE emby SET checksum = ? WHERE emby_id = ?", (API().getChecksum(MBitem),MBitem["Id"]))
|
cursor.execute("UPDATE emby SET checksum = ? WHERE emby_id = ?", (API().getChecksum(MBitem),MBitem["Id"]))
|
||||||
|
@ -193,10 +196,13 @@ class WriteKodiDB():
|
||||||
#update studios
|
#update studios
|
||||||
self.AddStudiosToMedia(movieid, studios, "movie", cursor)
|
self.AddStudiosToMedia(movieid, studios, "movie", cursor)
|
||||||
|
|
||||||
|
#update countries
|
||||||
|
self.AddCountriesToMedia(movieid, MBitem.get("ProductionLocations"), "movie", cursor)
|
||||||
|
|
||||||
#add streamdetails
|
#add streamdetails
|
||||||
self.AddStreamDetailsToMedia(API().getMediaStreams(MBitem), fileid, cursor)
|
self.AddStreamDetailsToMedia(API().getMediaStreams(MBitem), fileid, cursor)
|
||||||
|
|
||||||
#add to favorites tag --> todo translated label for favorites ?
|
#add to or remove from favorites tag
|
||||||
if userData.get("Favorite"):
|
if userData.get("Favorite"):
|
||||||
self.AddTagToMedia(movieid, "Favorite movies", "movie", cursor)
|
self.AddTagToMedia(movieid, "Favorite movies", "movie", cursor)
|
||||||
else:
|
else:
|
||||||
|
@ -207,7 +213,6 @@ class WriteKodiDB():
|
||||||
total = int(round(float(timeInfo.get("TotalTime"))))*60
|
total = int(round(float(timeInfo.get("TotalTime"))))*60
|
||||||
self.setKodiResumePoint(fileid, resume, total, cursor)
|
self.setKodiResumePoint(fileid, resume, total, cursor)
|
||||||
|
|
||||||
|
|
||||||
def addOrUpdateMusicVideoToKodiLibrary( self, embyId ,connection, cursor):
|
def addOrUpdateMusicVideoToKodiLibrary( self, embyId ,connection, cursor):
|
||||||
|
|
||||||
addon = xbmcaddon.Addon(id='plugin.video.emby')
|
addon = xbmcaddon.Addon(id='plugin.video.emby')
|
||||||
|
@ -377,6 +382,11 @@ class WriteKodiDB():
|
||||||
sorttitle = utils.convertEncoding(MBitem["SortName"])
|
sorttitle = utils.convertEncoding(MBitem["SortName"])
|
||||||
rating = MBitem.get("CommunityRating")
|
rating = MBitem.get("CommunityRating")
|
||||||
|
|
||||||
|
tvdb = None
|
||||||
|
if MBitem.get("ProviderIds"):
|
||||||
|
if MBitem.get("ProviderIds").get("Tvdb"):
|
||||||
|
tvdb = MBitem.get("ProviderIds").get("Tvdb")
|
||||||
|
|
||||||
if MBitem.get("DateCreated") != None:
|
if MBitem.get("DateCreated") != None:
|
||||||
dateadded = MBitem["DateCreated"].split('.')[0].replace('T', " ")
|
dateadded = MBitem["DateCreated"].split('.')[0].replace('T', " ")
|
||||||
else:
|
else:
|
||||||
|
@ -417,8 +427,8 @@ class WriteKodiDB():
|
||||||
cursor.execute("select coalesce(max(idShow),0) as showid from tvshow")
|
cursor.execute("select coalesce(max(idShow),0) as showid from tvshow")
|
||||||
showid = cursor.fetchone()[0]
|
showid = cursor.fetchone()[0]
|
||||||
showid = showid + 1
|
showid = showid + 1
|
||||||
pathsql="insert into tvshow(idShow, c00, c01, c04, c05, c08, c09, c13, c14, c15) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
pathsql="insert into tvshow(idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
cursor.execute(pathsql, (showid, title, plot, rating, premieredate, genre, title, mpaa, studio, sorttitle))
|
cursor.execute(pathsql, (showid, title, plot, rating, premieredate, genre, title, tvdb, mpaa, studio, sorttitle))
|
||||||
|
|
||||||
#create the reference in emby table
|
#create the reference in emby table
|
||||||
pathsql = "INSERT into emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
|
pathsql = "INSERT into emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)"
|
||||||
|
@ -435,8 +445,8 @@ class WriteKodiDB():
|
||||||
else:
|
else:
|
||||||
utils.logMsg("UPDATE tvshow to Kodi library","Id: %s - Title: %s" % (embyId, title))
|
utils.logMsg("UPDATE tvshow to Kodi library","Id: %s - Title: %s" % (embyId, title))
|
||||||
|
|
||||||
pathsql="UPDATE tvshow SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, c13 = ?, c14 = ?, c15 = ? WHERE idShow = ?"
|
pathsql="UPDATE tvshow SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ? WHERE idShow = ?"
|
||||||
cursor.execute(pathsql, (title, plot, rating, premieredate, title, genre, mpaa, studio, sorttitle, showid))
|
cursor.execute(pathsql, (title, plot, rating, premieredate, title, genre, tvdb, mpaa, studio, sorttitle, showid))
|
||||||
|
|
||||||
#update the checksum in emby table
|
#update the checksum in emby table
|
||||||
cursor.execute("UPDATE emby SET checksum = ? WHERE emby_id = ?", (API().getChecksum(MBitem), MBitem["Id"]))
|
cursor.execute("UPDATE emby SET checksum = ? WHERE emby_id = ?", (API().getChecksum(MBitem), MBitem["Id"]))
|
||||||
|
@ -450,7 +460,7 @@ class WriteKodiDB():
|
||||||
#update studios
|
#update studios
|
||||||
self.AddStudiosToMedia(showid, studios, "tvshow", cursor)
|
self.AddStudiosToMedia(showid, studios, "tvshow", cursor)
|
||||||
|
|
||||||
#add to favorites tag --> todo translated label for favorites ?
|
#add to or remove from favorites tag
|
||||||
if userData.get("Favorite"):
|
if userData.get("Favorite"):
|
||||||
self.AddTagToMedia(showid, "Favorite tvshows", "tvshow", cursor)
|
self.AddTagToMedia(showid, "Favorite tvshows", "tvshow", cursor)
|
||||||
else:
|
else:
|
||||||
|
@ -779,59 +789,54 @@ class WriteKodiDB():
|
||||||
peoplesql="INSERT OR REPLACE into writerlinkepisode(idWriter, idEpisode) values(?, ?)"
|
peoplesql="INSERT OR REPLACE into writerlinkepisode(idWriter, idEpisode) values(?, ?)"
|
||||||
cursor.execute(peoplesql, (actorid,id))
|
cursor.execute(peoplesql, (actorid,id))
|
||||||
|
|
||||||
def AddGenresToMedia(self, id, genres, mediatype, cursor):
|
def AddCountriesToMedia(self, id, countries, mediatype, cursor):
|
||||||
|
if countries:
|
||||||
if genres:
|
|
||||||
|
|
||||||
kodiVersion = 14
|
kodiVersion = 14
|
||||||
if xbmc.getInfoLabel("System.BuildVersion").startswith("15"):
|
if xbmc.getInfoLabel("System.BuildVersion").startswith("15"):
|
||||||
kodiVersion = 15
|
kodiVersion = 15
|
||||||
|
|
||||||
for genre in genres:
|
for country in countries:
|
||||||
|
|
||||||
if kodiVersion == 15:
|
if kodiVersion == 15:
|
||||||
genre_id = None
|
country_id = None
|
||||||
cursor.execute("SELECT genre_id as genre_id FROM genre WHERE name = ?",(genre,))
|
cursor.execute("SELECT country_id as country_id FROM country WHERE name = ?",(country,))
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
if result != None:
|
if result != None:
|
||||||
genre_id = result[0]
|
country_id = result[0]
|
||||||
#create genre
|
#create country
|
||||||
if genre_id == None:
|
if country_id == None:
|
||||||
cursor.execute("select coalesce(max(genre_id),0) as genre_id from genre")
|
cursor.execute("select coalesce(max(country_id),0) as country_id from country")
|
||||||
genre_id = cursor.fetchone()[0]
|
country_id = cursor.fetchone()[0]
|
||||||
genre_id = genre_id + 1
|
country_id = country_id + 1
|
||||||
sql="insert into genre(genre_id, name) values(?, ?)"
|
sql="insert into country(country_id, name) values(?, ?)"
|
||||||
cursor.execute(sql, (genre_id,genre))
|
cursor.execute(sql, (country_id,country))
|
||||||
utils.logMsg("AddGenresToMedia", "Processing : " + genre)
|
utils.logMsg("AddCountriesToMedia", "Processing : " + country)
|
||||||
|
|
||||||
#assign genre to item
|
#assign country to item
|
||||||
sql="INSERT OR REPLACE into genre_link(genre_id, media_id, media_type) values(?, ?, ?)"
|
sql="INSERT OR REPLACE into country_link(country_id, media_id, media_type) values(?, ?, ?)"
|
||||||
cursor.execute(sql, (genre_id, id, mediatype))
|
cursor.execute(sql, (country_id, id, mediatype))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
idGenre = None
|
idCountry = None
|
||||||
cursor.execute("SELECT idGenre as idGenre FROM genre WHERE strGenre = ?",(genre,))
|
cursor.execute("SELECT idCountry as idCountry FROM country WHERE strCountry = ?",(country,))
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
if result != None:
|
if result != None:
|
||||||
idGenre = result[0]
|
idCountry = result[0]
|
||||||
#create genre
|
#create country
|
||||||
if idGenre == None:
|
if idCountry == None:
|
||||||
cursor.execute("select coalesce(max(idGenre),0) as idGenre from genre")
|
cursor.execute("select coalesce(max(idCountry),0) as idCountry from country")
|
||||||
idGenre = cursor.fetchone()[0]
|
idCountry = cursor.fetchone()[0]
|
||||||
idGenre = idGenre + 1
|
idCountry = idCountry + 1
|
||||||
sql="insert into genre(idGenre, strGenre) values(?, ?)"
|
sql="insert into country(idCountry, strCountry) values(?, ?)"
|
||||||
cursor.execute(sql, (idGenre,genre))
|
cursor.execute(sql, (idCountry,country))
|
||||||
|
|
||||||
#assign genre to item
|
#assign country to item
|
||||||
if mediatype == "movie":
|
if mediatype == "movie":
|
||||||
sql="INSERT OR REPLACE into genrelinkmovie(idGenre, idMovie) values(?, ?)"
|
sql="INSERT OR REPLACE into countrylinkmovie(idCountry, idMovie) values(?, ?)"
|
||||||
if mediatype == "tvshow":
|
cursor.execute(sql, (idCountry,id))
|
||||||
sql="INSERT OR REPLACE into genrelinktvshow(idGenre, idShow) values(?, ?)"
|
else:
|
||||||
if mediatype == "episode":
|
#only movies have a country field
|
||||||
return
|
return
|
||||||
if mediatype == "musicvideo":
|
|
||||||
sql="INSERT OR REPLACE into genrelinkmusicvideo(idGenre, idMVideo) values(?, ?)"
|
|
||||||
cursor.execute(sql, (idGenre,id))
|
|
||||||
|
|
||||||
def AddStudiosToMedia(self, id, studios, mediatype, cursor):
|
def AddStudiosToMedia(self, id, studios, mediatype, cursor):
|
||||||
|
|
||||||
|
@ -909,7 +914,6 @@ class WriteKodiDB():
|
||||||
sql="insert into tag(tag_id, name) values(?, ?)"
|
sql="insert into tag(tag_id, name) values(?, ?)"
|
||||||
cursor.execute(sql, (tag_id,tag))
|
cursor.execute(sql, (tag_id,tag))
|
||||||
utils.logMsg("AddTagToMedia", "Adding tag: " + tag)
|
utils.logMsg("AddTagToMedia", "Adding tag: " + tag)
|
||||||
self.addVideoNodesForTag(tag, mediatype)
|
|
||||||
|
|
||||||
#assign tag to item
|
#assign tag to item
|
||||||
if doRemove:
|
if doRemove:
|
||||||
|
@ -933,7 +937,6 @@ class WriteKodiDB():
|
||||||
sql="insert into tag(idTag, strTag) values(?, ?)"
|
sql="insert into tag(idTag, strTag) values(?, ?)"
|
||||||
cursor.execute(sql, (idTag,tag))
|
cursor.execute(sql, (idTag,tag))
|
||||||
utils.logMsg("AddTagToMedia", "Adding tag: " + tag)
|
utils.logMsg("AddTagToMedia", "Adding tag: " + tag)
|
||||||
self.addVideoNodesForTag(tag, mediatype)
|
|
||||||
|
|
||||||
#assign tag to item
|
#assign tag to item
|
||||||
if doRemove:
|
if doRemove:
|
||||||
|
@ -1029,160 +1032,6 @@ class WriteKodiDB():
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def addVideoNodesForTag(self, tagname, type):
|
|
||||||
|
|
||||||
utils.logMsg("addVideoNodesForTag", "Creating nodes for tag: " + tagname)
|
|
||||||
|
|
||||||
# the library path doesn't exist on all systems
|
|
||||||
if not xbmcvfs.exists("special://userdata/library/"):
|
|
||||||
xbmcvfs.mkdir("special://userdata/library")
|
|
||||||
if not xbmcvfs.exists("special://userdata/library/video/"):
|
|
||||||
#we need to copy over the default items
|
|
||||||
import shutil
|
|
||||||
shutil.copytree(xbmc.translatePath("special://xbmc/system/library/video"), xbmc.translatePath("special://userdata/library/video"))
|
|
||||||
|
|
||||||
#create tag node for emby channels
|
|
||||||
nodefile = os.path.join(xbmc.translatePath("special://userdata/library/video"), "emby_channels.xml")
|
|
||||||
if not xbmcvfs.exists(nodefile):
|
|
||||||
root = Element("node", {"order":"20", "type":"folder"})
|
|
||||||
SubElement(root, "label").text = "Emby - Channels"
|
|
||||||
SubElement(root, "path").text = "plugin://plugin.video.emby/?id=0&mode=channels"
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
|
|
||||||
if type == "movie":
|
|
||||||
type = "movies"
|
|
||||||
elif type == "tvshow":
|
|
||||||
type = "tvshows"
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
#tagpath
|
|
||||||
libraryPath = xbmc.translatePath("special://userdata/library/video/Emby - %s/" %tagname)
|
|
||||||
|
|
||||||
if not xbmcvfs.exists(libraryPath):
|
|
||||||
#create tag node - index
|
|
||||||
xbmcvfs.mkdir(libraryPath)
|
|
||||||
nodefile = os.path.join(libraryPath, "index.xml")
|
|
||||||
root = Element("node", {"order":"1"})
|
|
||||||
SubElement(root, "label").text = "Emby - " + tagname
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
#create tag node - all items
|
|
||||||
nodefile = os.path.join(libraryPath, tagname + "_all.xml")
|
|
||||||
root = Element("node", {"order":"1", "type":"filter"})
|
|
||||||
SubElement(root, "label").text = tagname
|
|
||||||
SubElement(root, "match").text = "all"
|
|
||||||
SubElement(root, "content").text = type
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
|
|
||||||
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
|
||||||
SubElement(Rule, "value").text = tagname
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
#create tag node - recent items
|
|
||||||
nodefile = os.path.join(libraryPath, tagname + "_recent.xml")
|
|
||||||
root = Element("node", {"order":"2", "type":"filter"})
|
|
||||||
SubElement(root, "label").text = tagname + " - Recently added"
|
|
||||||
SubElement(root, "match").text = "all"
|
|
||||||
SubElement(root, "content").text = type
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
|
||||||
SubElement(Rule, "value").text = tagname
|
|
||||||
SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
|
|
||||||
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
|
||||||
SubElement(root, "limit").text = "25"
|
|
||||||
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
|
|
||||||
Rule2 = SubElement(root, "rule", {"field":"playcount","operator":"is"})
|
|
||||||
SubElement(Rule2, "value").text = "0"
|
|
||||||
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
#create tag node - inprogress items
|
|
||||||
nodefile = os.path.join(libraryPath, tagname + "_progress.xml")
|
|
||||||
root = Element("node", {"order":"3", "type":"filter"})
|
|
||||||
SubElement(root, "label").text = tagname + " - In progress"
|
|
||||||
SubElement(root, "match").text = "all"
|
|
||||||
SubElement(root, "content").text = type
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
|
||||||
SubElement(Rule, "value").text = tagname
|
|
||||||
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
|
||||||
SubElement(root, "limit").text = "25"
|
|
||||||
Rule2 = SubElement(root, "rule", {"field":"inprogress","operator":"true"})
|
|
||||||
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
#add some additional nodes for episodes
|
|
||||||
if type == "tvshows":
|
|
||||||
#create tag node - recent episodes
|
|
||||||
nodefile = os.path.join(libraryPath, tagname + "_recent_episodes.xml")
|
|
||||||
root = Element("node", {"order":"3", "type":"filter"})
|
|
||||||
SubElement(root, "label").text = tagname + " - Recently added episodes"
|
|
||||||
SubElement(root, "match").text = "all"
|
|
||||||
SubElement(root, "content").text = "episodes"
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
|
||||||
SubElement(Rule, "value").text = tagname
|
|
||||||
SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
|
|
||||||
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
|
||||||
SubElement(root, "limit").text = "25"
|
|
||||||
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
|
|
||||||
Rule2 = SubElement(root, "rule", {"field":"playcount","operator":"is"})
|
|
||||||
SubElement(Rule2, "value").text = "0"
|
|
||||||
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
#create tag node - inprogress items
|
|
||||||
nodefile = os.path.join(libraryPath, tagname + "_progress_episodes.xml")
|
|
||||||
root = Element("node", {"order":"4", "type":"filter"})
|
|
||||||
SubElement(root, "label").text = tagname + " - In progress episodes"
|
|
||||||
SubElement(root, "match").text = "all"
|
|
||||||
SubElement(root, "content").text = "episodes"
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
Rule = SubElement(root, "rule", {"field":"tag","operator":"is"})
|
|
||||||
SubElement(Rule, "value").text = tagname
|
|
||||||
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
|
|
||||||
SubElement(root, "limit").text = "25"
|
|
||||||
Rule2 = SubElement(root, "rule", {"field":"inprogress","operator":"true"})
|
|
||||||
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
#create tag node - nextup items
|
|
||||||
nodefile = os.path.join(libraryPath, tagname + "_nextup_episodes.xml")
|
|
||||||
root = Element("node", {"order":"4", "type":"folder"})
|
|
||||||
SubElement(root, "label").text = tagname + " - Nextup episodes"
|
|
||||||
SubElement(root, "path").text = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" %tagname
|
|
||||||
SubElement(root, "icon").text = "DefaultMovies.png"
|
|
||||||
try:
|
|
||||||
ET.ElementTree(root).write(nodefile, xml_declaration=True)
|
|
||||||
except:
|
|
||||||
ET.ElementTree(root).write(nodefile)
|
|
||||||
|
|
||||||
|
|
||||||
def updateBoxsetToKodiLibrary(self, boxsetmovie, boxset, connection, cursor):
|
def updateBoxsetToKodiLibrary(self, boxsetmovie, boxset, connection, cursor):
|
||||||
strSet = boxset["Name"]
|
strSet = boxset["Name"]
|
||||||
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?",(boxsetmovie["Id"],))
|
cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?",(boxsetmovie["Id"],))
|
||||||
|
|
Loading…
Reference in a new issue