mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-04-18 16:33:47 +00:00
New views.py
Combines maintainsViews from librarysync and videonodes and playlist creation. Still need to move deletion from utils.
This commit is contained in:
parent
1d8d01b5f8
commit
16df4d6404
7 changed files with 291 additions and 987 deletions
|
@ -1,122 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
##################################################################################################
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
import api
|
|
||||||
import artwork
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("EMBY."+__name__)
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
class Kodidb_Functions(object):
|
|
||||||
|
|
||||||
kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, cursor):
|
|
||||||
|
|
||||||
self.cursor = cursor
|
|
||||||
self.artwork = artwork.Artwork()
|
|
||||||
|
|
||||||
def createTag(self, name):
|
|
||||||
# This will create and return the tag_id
|
|
||||||
if self.kodiversion in (15, 16, 17):
|
|
||||||
# Kodi Isengard, Jarvis, Krypton
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
# Kodi Helix
|
|
||||||
query = ' '.join((
|
|
||||||
|
|
||||||
"SELECT idTag",
|
|
||||||
"FROM tag",
|
|
||||||
"WHERE strTag = ?",
|
|
||||||
"COLLATE NOCASE"
|
|
||||||
))
|
|
||||||
self.cursor.execute(query, (name,))
|
|
||||||
try:
|
|
||||||
tag_id = self.cursor.fetchone()[0]
|
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
self.cursor.execute("select coalesce(max(idTag),0) from tag")
|
|
||||||
tag_id = self.cursor.fetchone()[0] + 1
|
|
||||||
|
|
||||||
query = "INSERT INTO tag(idTag, strTag) values(?, ?)"
|
|
||||||
self.cursor.execute(query, (tag_id, name))
|
|
||||||
log.debug("Create idTag: %s name: %s", tag_id, name)
|
|
||||||
|
|
||||||
return tag_id
|
|
||||||
|
|
||||||
def updateTag(self, oldtag, newtag, kodiid, mediatype):
|
|
||||||
# TODO: Move to video nodes eventually
|
|
||||||
log.debug("Updating: %s with %s for %s: %s", oldtag, newtag, mediatype, kodiid)
|
|
||||||
|
|
||||||
if self.kodiversion in (15, 16, 17):
|
|
||||||
# Kodi Isengard, Jarvis, Krypton
|
|
||||||
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:
|
|
||||||
# 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,))
|
|
||||||
else:
|
|
||||||
# Kodi Helix
|
|
||||||
try:
|
|
||||||
query = ' '.join((
|
|
||||||
|
|
||||||
"UPDATE taglinks",
|
|
||||||
"SET idTag = ?",
|
|
||||||
"WHERE idMedia = ?",
|
|
||||||
"AND media_type = ?",
|
|
||||||
"AND idTag = ?"
|
|
||||||
))
|
|
||||||
self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
|
|
||||||
except Exception:
|
|
||||||
# The new tag we are going to apply already exists for this item
|
|
||||||
# delete current tag instead
|
|
||||||
query = ' '.join((
|
|
||||||
|
|
||||||
"DELETE FROM taglinks",
|
|
||||||
"WHERE idMedia = ?",
|
|
||||||
"AND media_type = ?",
|
|
||||||
"AND idTag = ?"
|
|
||||||
))
|
|
||||||
self.cursor.execute(query, (kodiid, mediatype, oldtag,))
|
|
|
@ -17,10 +17,9 @@ import clientinfo
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import itemtypes
|
import itemtypes
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
import kodidb_functions as kodidb
|
|
||||||
import read_embyserver as embyserver
|
import read_embyserver as embyserver
|
||||||
import userclient
|
import userclient
|
||||||
import videonodes
|
import views
|
||||||
from objects import Movies, MusicVideos, TVShows, Music
|
from objects import Movies, MusicVideos, TVShows, Music
|
||||||
from utils import window, settings, language as lang, should_stop
|
from utils import window, settings, language as lang, should_stop
|
||||||
from ga_client import GoogleAnalytics
|
from ga_client import GoogleAnalytics
|
||||||
|
@ -60,7 +59,6 @@ class LibrarySync(threading.Thread):
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.user = userclient.UserClient()
|
self.user = userclient.UserClient()
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.vnodes = videonodes.VideoNodes()
|
|
||||||
|
|
||||||
self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||||
|
|
||||||
|
@ -276,7 +274,9 @@ class LibrarySync(threading.Thread):
|
||||||
starttotal = datetime.now()
|
starttotal = datetime.now()
|
||||||
|
|
||||||
# Set views
|
# Set views
|
||||||
self.maintainViews(cursor_emby, cursor_video)
|
views.Views(cursor_emby, cursor_video).maintain()
|
||||||
|
conn_emby.commit()
|
||||||
|
#self.maintainViews(cursor_emby, cursor_video)
|
||||||
|
|
||||||
# Sync video library
|
# Sync video library
|
||||||
process = {
|
process = {
|
||||||
|
@ -362,222 +362,10 @@ class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
def refreshViews(self):
|
def refreshViews(self):
|
||||||
|
|
||||||
with DatabaseConn('emby') as conn_emby, DatabaseConn('video') as conn_video:
|
with DatabaseConn('emby') as conn_emby, DatabaseConn() as conn_video:
|
||||||
with closing(conn_emby.cursor()) as cursor_emby, closing(conn_video.cursor()) as cursor_video:
|
with closing(conn_emby.cursor()) as cursor_emby, closing(conn_video.cursor()) as cursor_video:
|
||||||
# Compare views, assign correct tags to items
|
# Compare views, assign correct tags to items
|
||||||
self.maintainViews(cursor_emby, cursor_video)
|
views.Views(cursor_emby, cursor_video).maintain()
|
||||||
|
|
||||||
|
|
||||||
def maintainViews(self, embycursor, kodicursor):
|
|
||||||
|
|
||||||
# Compare the views to emby
|
|
||||||
emby = self.emby
|
|
||||||
emby_db = embydb.Embydb_Functions(embycursor)
|
|
||||||
kodi_db = kodidb.Kodidb_Functions(kodicursor)
|
|
||||||
|
|
||||||
# Get views
|
|
||||||
result = self.doUtils("{server}/emby/Users/{UserId}/Views?format=json")
|
|
||||||
grouped_views = result['Items'] if 'Items' in result else []
|
|
||||||
ordered_views = self.emby.getViews(sortedlist=True)
|
|
||||||
all_views = []
|
|
||||||
sorted_views = []
|
|
||||||
for view in ordered_views:
|
|
||||||
all_views.append(view['name'])
|
|
||||||
if view['type'] == "music":
|
|
||||||
continue
|
|
||||||
|
|
||||||
if view['type'] == "mixed":
|
|
||||||
sorted_views.append(view['name'])
|
|
||||||
sorted_views.append(view['name'])
|
|
||||||
log.info("Sorted views: %s" % sorted_views)
|
|
||||||
|
|
||||||
# total nodes for window properties
|
|
||||||
self.vnodes.clearProperties()
|
|
||||||
totalnodes = len(sorted_views) + 0
|
|
||||||
|
|
||||||
current_views = emby_db.getViews()
|
|
||||||
# Set views for supported media type
|
|
||||||
emby_mediatypes = {
|
|
||||||
|
|
||||||
'movies': "Movie",
|
|
||||||
'tvshows': "Series",
|
|
||||||
'musicvideos': "MusicVideo",
|
|
||||||
'homevideos': "Video",
|
|
||||||
'music': "Audio",
|
|
||||||
'photos': "Photo"
|
|
||||||
}
|
|
||||||
for mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos']:
|
|
||||||
|
|
||||||
nodes = [] # Prevent duplicate for nodes of the same type
|
|
||||||
playlists = [] # Prevent duplicate for playlists of the same type
|
|
||||||
# Get media folders from server
|
|
||||||
folders = self.emby.getViews(mediatype, root=True)
|
|
||||||
for folder in folders:
|
|
||||||
|
|
||||||
folderid = folder['id']
|
|
||||||
foldername = folder['name']
|
|
||||||
viewtype = folder['type']
|
|
||||||
|
|
||||||
if foldername not in all_views:
|
|
||||||
# Media folders are grouped into userview
|
|
||||||
params = {
|
|
||||||
'ParentId': folderid,
|
|
||||||
'Recursive': True,
|
|
||||||
'Limit': 1,
|
|
||||||
'IncludeItemTypes': emby_mediatypes[mediatype]
|
|
||||||
} # Get one item from server using the folderid
|
|
||||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
|
||||||
result = self.doUtils(url, parameters=params)
|
|
||||||
try:
|
|
||||||
verifyitem = result['Items'][0]['Id']
|
|
||||||
except (TypeError, IndexError):
|
|
||||||
# Something is wrong. Keep the same folder name.
|
|
||||||
# Could be the view is empty or the connection
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for grouped_view in grouped_views:
|
|
||||||
# This is only reserved for the detection of grouped views
|
|
||||||
if (grouped_view['Type'] == "UserView" and
|
|
||||||
grouped_view.get('CollectionType') == mediatype):
|
|
||||||
# Take the userview, and validate the item belong to the view
|
|
||||||
if self.emby.verifyView(grouped_view['Id'], verifyitem):
|
|
||||||
# Take the name of the userview
|
|
||||||
log.info("Found corresponding view: %s %s"
|
|
||||||
% (grouped_view['Name'], grouped_view['Id']))
|
|
||||||
foldername = grouped_view['Name']
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Unable to find a match, add the name to our sorted_view list
|
|
||||||
sorted_views.append(foldername)
|
|
||||||
log.info("Couldn't find corresponding grouped view: %s" % sorted_views)
|
|
||||||
|
|
||||||
# Failsafe
|
|
||||||
try:
|
|
||||||
sorted_views.index(foldername)
|
|
||||||
except ValueError:
|
|
||||||
sorted_views.append(foldername)
|
|
||||||
|
|
||||||
# Get current media folders from emby database
|
|
||||||
view = emby_db.getView_byId(folderid)
|
|
||||||
try:
|
|
||||||
current_viewname = view[0]
|
|
||||||
current_viewtype = view[1]
|
|
||||||
current_tagid = view[2]
|
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
log.info("Creating viewid: %s in Emby database." % folderid)
|
|
||||||
tagid = kodi_db.createTag(foldername)
|
|
||||||
# Create playlist for the video library
|
|
||||||
if (foldername not in playlists and
|
|
||||||
mediatype in ('movies', 'tvshows', 'musicvideos')):
|
|
||||||
utils.playlistXSP(mediatype, foldername, folderid, viewtype)
|
|
||||||
playlists.append(foldername)
|
|
||||||
# Create the video node
|
|
||||||
if foldername not in nodes and mediatype not in ("musicvideos", "music"):
|
|
||||||
self.vnodes.viewNode(sorted_views.index(foldername), foldername, mediatype,
|
|
||||||
viewtype, folderid)
|
|
||||||
if viewtype == "mixed": # Change the value
|
|
||||||
sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
|
|
||||||
nodes.append(foldername)
|
|
||||||
totalnodes += 1
|
|
||||||
# Add view to emby database
|
|
||||||
emby_db.addView(folderid, foldername, viewtype, tagid)
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.debug(' '.join((
|
|
||||||
|
|
||||||
"Found viewid: %s" % folderid,
|
|
||||||
"viewname: %s" % current_viewname,
|
|
||||||
"viewtype: %s" % current_viewtype,
|
|
||||||
"tagid: %s" % current_tagid)))
|
|
||||||
|
|
||||||
# View is still valid
|
|
||||||
try:
|
|
||||||
current_views.remove(folderid)
|
|
||||||
except ValueError:
|
|
||||||
# View was just created, nothing to remove
|
|
||||||
pass
|
|
||||||
|
|
||||||
# View was modified, update with latest info
|
|
||||||
if current_viewname != foldername:
|
|
||||||
log.info("viewid: %s new viewname: %s" % (folderid, foldername))
|
|
||||||
tagid = kodi_db.createTag(foldername)
|
|
||||||
|
|
||||||
# Update view with new info
|
|
||||||
emby_db.updateView(foldername, tagid, folderid)
|
|
||||||
|
|
||||||
if mediatype != "music":
|
|
||||||
if emby_db.getView_byName(current_viewname) is None:
|
|
||||||
# The tag could be a combined view. Ensure there's no other tags
|
|
||||||
# with the same name before deleting playlist.
|
|
||||||
utils.playlistXSP(
|
|
||||||
mediatype, current_viewname, folderid, current_viewtype, True)
|
|
||||||
# Delete video node
|
|
||||||
if mediatype != "musicvideos":
|
|
||||||
self.vnodes.viewNode(
|
|
||||||
indexnumber=None,
|
|
||||||
tagname=current_viewname,
|
|
||||||
mediatype=mediatype,
|
|
||||||
viewtype=current_viewtype,
|
|
||||||
viewid=folderid,
|
|
||||||
delete=True)
|
|
||||||
# Added new playlist
|
|
||||||
if (foldername not in playlists and
|
|
||||||
mediatype in ('movies', 'tvshows', 'musicvideos')):
|
|
||||||
utils.playlistXSP(mediatype, foldername, folderid, viewtype)
|
|
||||||
playlists.append(foldername)
|
|
||||||
# Add new video node
|
|
||||||
if foldername not in nodes and mediatype != "musicvideos":
|
|
||||||
self.vnodes.viewNode(sorted_views.index(foldername), foldername,
|
|
||||||
mediatype, viewtype, folderid)
|
|
||||||
if viewtype == "mixed": # Change the value
|
|
||||||
sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
|
|
||||||
nodes.append(foldername)
|
|
||||||
totalnodes += 1
|
|
||||||
|
|
||||||
# Update items with new tag
|
|
||||||
items = emby_db.getItem_byView(folderid)
|
|
||||||
for item in items:
|
|
||||||
# Remove the "s" from viewtype for tags
|
|
||||||
kodi_db.updateTag(
|
|
||||||
current_tagid, tagid, item[0], current_viewtype[:-1])
|
|
||||||
else:
|
|
||||||
# Validate the playlist exists or recreate it
|
|
||||||
if mediatype != "music":
|
|
||||||
if (foldername not in playlists and
|
|
||||||
mediatype in ('movies', 'tvshows', 'musicvideos')):
|
|
||||||
utils.playlistXSP(mediatype, foldername, folderid, viewtype)
|
|
||||||
playlists.append(foldername)
|
|
||||||
# Create the video node if not already exists
|
|
||||||
if foldername not in nodes and mediatype != "musicvideos":
|
|
||||||
self.vnodes.viewNode(sorted_views.index(foldername), foldername,
|
|
||||||
mediatype, viewtype, folderid)
|
|
||||||
if viewtype == "mixed": # Change the value
|
|
||||||
sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
|
|
||||||
nodes.append(foldername)
|
|
||||||
totalnodes += 1
|
|
||||||
else:
|
|
||||||
# Add video nodes listings
|
|
||||||
self.vnodes.singleNode(totalnodes, "Favorite movies", "movies", "favourites")
|
|
||||||
totalnodes += 1
|
|
||||||
self.vnodes.singleNode(totalnodes, "Favorite tvshows", "tvshows", "favourites")
|
|
||||||
totalnodes += 1
|
|
||||||
self.vnodes.singleNode(totalnodes, "Favorite episodes", "episodes", "favourites")
|
|
||||||
totalnodes += 1
|
|
||||||
self.vnodes.singleNode(totalnodes, "channels", "movies", "channels")
|
|
||||||
totalnodes += 1
|
|
||||||
# Save total
|
|
||||||
window('Emby.nodes.total', str(totalnodes))
|
|
||||||
|
|
||||||
# Remove any old referenced views
|
|
||||||
log.info("Removing views: %s" % current_views)
|
|
||||||
for view in current_views:
|
|
||||||
emby_db.removeView(view)
|
|
||||||
# Remove any items that belongs to the old view
|
|
||||||
items = emby_db.get_item_by_view(view)
|
|
||||||
items = [i[0] for i in items] # Convert list of tuple to list
|
|
||||||
self.triage_items("remove", items)
|
|
||||||
|
|
||||||
|
|
||||||
def movies(self, embycursor, kodicursor, pdialog):
|
def movies(self, embycursor, kodicursor, pdialog):
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import xbmcgui
|
||||||
|
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import kodidb_functions as kodidb
|
|
||||||
import websocket_client as wsc
|
import websocket_client as wsc
|
||||||
from utils import window, settings, language as lang
|
from utils import window, settings, language as lang
|
||||||
from ga_client import GoogleAnalytics, log_error
|
from ga_client import GoogleAnalytics, log_error
|
||||||
|
|
|
@ -31,6 +31,9 @@ class Read_EmbyServer():
|
||||||
self.userId = window('emby_currUser')
|
self.userId = window('emby_currUser')
|
||||||
self.server = window('emby_server%s' % self.userId)
|
self.server = window('emby_server%s' % self.userId)
|
||||||
|
|
||||||
|
def get_emby_url(self, handler):
|
||||||
|
return "{server}/emby/%s" % handler
|
||||||
|
|
||||||
|
|
||||||
def split_list(self, itemlist, size):
|
def split_list(self, itemlist, size):
|
||||||
# Split up list in pieces of size. Will generate a list of lists
|
# Split up list in pieces of size. Will generate a list of lists
|
||||||
|
@ -589,4 +592,15 @@ class Read_EmbyServer():
|
||||||
data = {'username': username, 'password': hashlib.sha1(password).hexdigest()}
|
data = {'username': username, 'password': hashlib.sha1(password).hexdigest()}
|
||||||
user = self.doUtils(url, postBody=data, action_type="POST", authenticate=False)
|
user = self.doUtils(url, postBody=data, action_type="POST", authenticate=False)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def get_single_item(self, media_type, parent_id):
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'ParentId': parent_id,
|
||||||
|
'Recursive': True,
|
||||||
|
'Limit': 1,
|
||||||
|
'IncludeItemTypes': media_type
|
||||||
|
}
|
||||||
|
url = self.get_emby_url('Users/{UserId}/Items?format=json')
|
||||||
|
return self.doUtils(url, parameters=params)
|
|
@ -16,8 +16,8 @@ import initialsetup
|
||||||
import kodimonitor
|
import kodimonitor
|
||||||
import librarysync
|
import librarysync
|
||||||
import player
|
import player
|
||||||
import videonodes
|
|
||||||
import websocket_client as wsc
|
import websocket_client as wsc
|
||||||
|
from views import VideoNodes
|
||||||
from utils import window, settings, dialog, language as lang
|
from utils import window, settings, dialog, language as lang
|
||||||
from ga_client import GoogleAnalytics
|
from ga_client import GoogleAnalytics
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -77,7 +77,7 @@ class Service(object):
|
||||||
window(prop, clear=True)
|
window(prop, clear=True)
|
||||||
|
|
||||||
# Clear video nodes properties
|
# Clear video nodes properties
|
||||||
videonodes.VideoNodes().clearProperties()
|
VideoNodes().clearProperties()
|
||||||
|
|
||||||
# Set the minimum database version
|
# Set the minimum database version
|
||||||
window('emby_minDBVersion', value="1.1.63")
|
window('emby_minDBVersion', value="1.1.63")
|
||||||
|
|
|
@ -1,392 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import shutil
|
|
||||||
import xml.etree.ElementTree as etree
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
import utils
|
|
||||||
from utils import window, language as lang
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("EMBY."+__name__)
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
class VideoNodes(object):
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
|
||||||
|
|
||||||
|
|
||||||
def commonRoot(self, order, label, tagname, roottype=1):
|
|
||||||
|
|
||||||
if roottype == 0:
|
|
||||||
# Index
|
|
||||||
root = etree.Element('node', attrib={'order': "%s" % order})
|
|
||||||
elif roottype == 1:
|
|
||||||
# Filter
|
|
||||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
|
|
||||||
etree.SubElement(root, 'match').text = "all"
|
|
||||||
# Add tag rule
|
|
||||||
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
|
|
||||||
etree.SubElement(rule, 'value').text = tagname
|
|
||||||
else:
|
|
||||||
# Folder
|
|
||||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
|
|
||||||
|
|
||||||
etree.SubElement(root, 'label').text = label
|
|
||||||
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
|
|
||||||
|
|
||||||
return root
|
|
||||||
|
|
||||||
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
|
|
||||||
|
|
||||||
if viewtype == "mixed":
|
|
||||||
dirname = "%s - %s" % (viewid, mediatype)
|
|
||||||
else:
|
|
||||||
dirname = viewid
|
|
||||||
|
|
||||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
|
||||||
nodepath = xbmc.translatePath(
|
|
||||||
"special://profile/library/video/emby/%s/" % dirname).decode('utf-8')
|
|
||||||
|
|
||||||
# Verify the video directory
|
|
||||||
if not xbmcvfs.exists(path):
|
|
||||||
try:
|
|
||||||
shutil.copytree(
|
|
||||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
|
||||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
|
||||||
except Exception as error:
|
|
||||||
log.error(error)
|
|
||||||
|
|
||||||
xbmcvfs.exists(path)
|
|
||||||
|
|
||||||
if delete:
|
|
||||||
dirs, files = xbmcvfs.listdir(nodepath)
|
|
||||||
for file in files:
|
|
||||||
xbmcvfs.delete(nodepath + file)
|
|
||||||
|
|
||||||
log.info("Sucessfully removed videonode: %s." % tagname)
|
|
||||||
return
|
|
||||||
# Create the node directory
|
|
||||||
if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
|
|
||||||
# We need to copy over the default items
|
|
||||||
xbmcvfs.mkdirs(nodepath)
|
|
||||||
|
|
||||||
# Create index entry
|
|
||||||
nodeXML = "%sindex.xml" % nodepath
|
|
||||||
# Set windows property
|
|
||||||
path = "library://video/emby/%s/" % dirname
|
|
||||||
for i in range(1, indexnumber):
|
|
||||||
# Verify to make sure we don't create duplicates
|
|
||||||
if window('Emby.nodes.%s.index' % i) == path:
|
|
||||||
return
|
|
||||||
|
|
||||||
if mediatype == "photos":
|
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
|
|
||||||
|
|
||||||
window('Emby.nodes.%s.index' % indexnumber, value=path)
|
|
||||||
|
|
||||||
# Root
|
|
||||||
if not mediatype == "photos":
|
|
||||||
if viewtype == "mixed":
|
|
||||||
specialtag = "%s - %s" % (tagname, mediatype)
|
|
||||||
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
|
|
||||||
else:
|
|
||||||
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
|
||||||
try:
|
|
||||||
utils.indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(nodeXML)
|
|
||||||
|
|
||||||
nodetypes = {
|
|
||||||
|
|
||||||
'1': "all",
|
|
||||||
'2': "recent",
|
|
||||||
'3': "recentepisodes",
|
|
||||||
'4': "inprogress",
|
|
||||||
'5': "inprogressepisodes",
|
|
||||||
'6': "unwatched",
|
|
||||||
'7': "nextepisodes",
|
|
||||||
'8': "sets",
|
|
||||||
'9': "genres",
|
|
||||||
'10': "random",
|
|
||||||
'11': "recommended",
|
|
||||||
}
|
|
||||||
mediatypes = {
|
|
||||||
# label according to nodetype per mediatype
|
|
||||||
'movies':
|
|
||||||
{
|
|
||||||
'1': tagname,
|
|
||||||
'2': 30174,
|
|
||||||
'4': 30177,
|
|
||||||
'6': 30189,
|
|
||||||
'8': 20434,
|
|
||||||
'9': 135,
|
|
||||||
'10': 30229,
|
|
||||||
'11': 30230
|
|
||||||
},
|
|
||||||
|
|
||||||
'tvshows':
|
|
||||||
{
|
|
||||||
'1': tagname,
|
|
||||||
'2': 30170,
|
|
||||||
'3': 30175,
|
|
||||||
'4': 30171,
|
|
||||||
'5': 30178,
|
|
||||||
'7': 30179,
|
|
||||||
'9': 135,
|
|
||||||
'10': 30229,
|
|
||||||
'11': 30230
|
|
||||||
},
|
|
||||||
|
|
||||||
'homevideos':
|
|
||||||
{
|
|
||||||
'1': tagname,
|
|
||||||
'2': 30251,
|
|
||||||
'11': 30253
|
|
||||||
},
|
|
||||||
|
|
||||||
'photos':
|
|
||||||
{
|
|
||||||
'1': tagname,
|
|
||||||
'2': 30252,
|
|
||||||
'8': 30255,
|
|
||||||
'11': 30254
|
|
||||||
},
|
|
||||||
|
|
||||||
'musicvideos':
|
|
||||||
{
|
|
||||||
'1': tagname,
|
|
||||||
'2': 30256,
|
|
||||||
'4': 30257,
|
|
||||||
'6': 30258
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes = mediatypes[mediatype]
|
|
||||||
for node in nodes:
|
|
||||||
|
|
||||||
nodetype = nodetypes[node]
|
|
||||||
nodeXML = "%s%s.xml" % (nodepath, nodetype)
|
|
||||||
# Get label
|
|
||||||
stringid = nodes[node]
|
|
||||||
if node != "1":
|
|
||||||
label = lang(stringid)
|
|
||||||
if not label:
|
|
||||||
label = xbmc.getLocalizedString(stringid)
|
|
||||||
else:
|
|
||||||
label = stringid
|
|
||||||
|
|
||||||
# Set window properties
|
|
||||||
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
|
|
||||||
# Custom query
|
|
||||||
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s"
|
|
||||||
% (tagname, mediatype))
|
|
||||||
elif (mediatype == "homevideos" or mediatype == "photos"):
|
|
||||||
# Custom query
|
|
||||||
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s"
|
|
||||||
% (tagname, mediatype, nodetype))
|
|
||||||
elif nodetype == "nextepisodes":
|
|
||||||
# Custom query
|
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
|
|
||||||
elif self.kodiversion == 14 and nodetype == "recentepisodes":
|
|
||||||
# Custom query
|
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
|
|
||||||
elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
|
|
||||||
# Custom query
|
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
|
|
||||||
else:
|
|
||||||
path = "library://video/emby/%s/%s.xml" % (viewid, nodetype)
|
|
||||||
|
|
||||||
if mediatype == "photos":
|
|
||||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
|
||||||
else:
|
|
||||||
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
|
||||||
|
|
||||||
if nodetype == "all":
|
|
||||||
|
|
||||||
if viewtype == "mixed":
|
|
||||||
templabel = "%s - %s" % (tagname, mediatype)
|
|
||||||
else:
|
|
||||||
templabel = label
|
|
||||||
|
|
||||||
embynode = "Emby.nodes.%s" % indexnumber
|
|
||||||
window('%s.title' % embynode, value=templabel)
|
|
||||||
window('%s.path' % embynode, value=windowpath)
|
|
||||||
window('%s.content' % embynode, value=path)
|
|
||||||
window('%s.type' % embynode, value=mediatype)
|
|
||||||
else:
|
|
||||||
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
|
|
||||||
window('%s.title' % embynode, value=label)
|
|
||||||
window('%s.path' % embynode, value=windowpath)
|
|
||||||
window('%s.content' % embynode, value=path)
|
|
||||||
|
|
||||||
if mediatype == "photos":
|
|
||||||
# For photos, we do not create a node in videos but we do want the window props
|
|
||||||
# to be created.
|
|
||||||
# To do: add our photos nodes to kodi picture sources somehow
|
|
||||||
continue
|
|
||||||
|
|
||||||
if xbmcvfs.exists(nodeXML):
|
|
||||||
# Don't recreate xml if already exists
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Create the root
|
|
||||||
if (nodetype == "nextepisodes" or mediatype == "homevideos" or
|
|
||||||
(self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
|
|
||||||
# Folder type with plugin path
|
|
||||||
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
|
|
||||||
etree.SubElement(root, 'path').text = path
|
|
||||||
etree.SubElement(root, 'content').text = "episodes"
|
|
||||||
else:
|
|
||||||
root = self.commonRoot(order=node, label=label, tagname=tagname)
|
|
||||||
if nodetype in ('recentepisodes', 'inprogressepisodes'):
|
|
||||||
etree.SubElement(root, 'content').text = "episodes"
|
|
||||||
else:
|
|
||||||
etree.SubElement(root, 'content').text = mediatype
|
|
||||||
|
|
||||||
limit = "25"
|
|
||||||
# Elements per nodetype
|
|
||||||
if nodetype == "all":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
|
||||||
|
|
||||||
elif nodetype == "recent":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
|
||||||
etree.SubElement(root, 'limit').text = limit
|
|
||||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
|
||||||
etree.SubElement(rule, 'value').text = "0"
|
|
||||||
|
|
||||||
elif nodetype == "inprogress":
|
|
||||||
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
|
||||||
etree.SubElement(root, 'limit').text = limit
|
|
||||||
|
|
||||||
elif nodetype == "genres":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
|
||||||
etree.SubElement(root, 'group').text = "genres"
|
|
||||||
|
|
||||||
elif nodetype == "unwatched":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
|
||||||
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
|
|
||||||
etree.SubElement(rule, 'value').text = "0"
|
|
||||||
|
|
||||||
elif nodetype == "sets":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
|
||||||
etree.SubElement(root, 'group').text = "sets"
|
|
||||||
|
|
||||||
elif nodetype == "random":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
|
|
||||||
etree.SubElement(root, 'limit').text = limit
|
|
||||||
|
|
||||||
elif nodetype == "recommended":
|
|
||||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
|
|
||||||
etree.SubElement(root, 'limit').text = limit
|
|
||||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
|
||||||
etree.SubElement(rule, 'value').text = "0"
|
|
||||||
rule2 = etree.SubElement(root, 'rule',
|
|
||||||
attrib={'field': "rating", 'operator': "greaterthan"})
|
|
||||||
etree.SubElement(rule2, 'value').text = "7"
|
|
||||||
|
|
||||||
elif nodetype == "recentepisodes":
|
|
||||||
# Kodi Isengard, Jarvis
|
|
||||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
|
||||||
etree.SubElement(root, 'limit').text = limit
|
|
||||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
|
||||||
etree.SubElement(rule, 'value').text = "0"
|
|
||||||
|
|
||||||
elif nodetype == "inprogressepisodes":
|
|
||||||
# Kodi Isengard, Jarvis
|
|
||||||
etree.SubElement(root, 'limit').text = "25"
|
|
||||||
rule = etree.SubElement(root, 'rule',
|
|
||||||
attrib={'field': "inprogress", 'operator':"true"})
|
|
||||||
|
|
||||||
try:
|
|
||||||
utils.indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(nodeXML)
|
|
||||||
|
|
||||||
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
|
||||||
|
|
||||||
tagname = tagname.encode('utf-8')
|
|
||||||
cleantagname = utils.normalize_nodes(tagname)
|
|
||||||
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
|
||||||
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
|
|
||||||
path = "library://video/emby_%s.xml" % cleantagname
|
|
||||||
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
|
||||||
|
|
||||||
# Create the video node directory
|
|
||||||
if not xbmcvfs.exists(nodepath):
|
|
||||||
# We need to copy over the default items
|
|
||||||
shutil.copytree(
|
|
||||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
|
||||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
|
||||||
xbmcvfs.exists(path)
|
|
||||||
|
|
||||||
labels = {
|
|
||||||
|
|
||||||
'Favorite movies': 30180,
|
|
||||||
'Favorite tvshows': 30181,
|
|
||||||
'Favorite episodes': 30182,
|
|
||||||
'channels': 30173
|
|
||||||
}
|
|
||||||
label = lang(labels[tagname])
|
|
||||||
embynode = "Emby.nodes.%s" % indexnumber
|
|
||||||
window('%s.title' % embynode, value=label)
|
|
||||||
window('%s.path' % embynode, value=windowpath)
|
|
||||||
window('%s.content' % embynode, value=path)
|
|
||||||
window('%s.type' % embynode, value=itemtype)
|
|
||||||
|
|
||||||
if xbmcvfs.exists(nodeXML):
|
|
||||||
# Don't recreate xml if already exists
|
|
||||||
return
|
|
||||||
|
|
||||||
if itemtype == "channels":
|
|
||||||
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
|
|
||||||
etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
|
|
||||||
elif itemtype == "favourites" and mediatype == "episodes":
|
|
||||||
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
|
|
||||||
etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=favepisodes" %(tagname, mediatype)
|
|
||||||
else:
|
|
||||||
root = self.commonRoot(order=1, label=label, tagname=tagname)
|
|
||||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
|
||||||
|
|
||||||
etree.SubElement(root, 'content').text = mediatype
|
|
||||||
|
|
||||||
try:
|
|
||||||
utils.indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(nodeXML)
|
|
||||||
|
|
||||||
def clearProperties(self):
|
|
||||||
|
|
||||||
log.info("Clearing nodes properties.")
|
|
||||||
embyprops = window('Emby.nodes.total')
|
|
||||||
propnames = [
|
|
||||||
|
|
||||||
"index","path","title","content",
|
|
||||||
"inprogress.content","inprogress.title",
|
|
||||||
"inprogress.content","inprogress.path",
|
|
||||||
"nextepisodes.title","nextepisodes.content",
|
|
||||||
"nextepisodes.path","unwatched.title",
|
|
||||||
"unwatched.content","unwatched.path",
|
|
||||||
"recent.title","recent.content","recent.path",
|
|
||||||
"recentepisodes.title","recentepisodes.content",
|
|
||||||
"recentepisodes.path","inprogressepisodes.title",
|
|
||||||
"inprogressepisodes.content","inprogressepisodes.path"
|
|
||||||
]
|
|
||||||
|
|
||||||
if embyprops:
|
|
||||||
totalnodes = int(embyprops)
|
|
||||||
for i in range(totalnodes):
|
|
||||||
for prop in propnames:
|
|
||||||
window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
|
|
@ -13,7 +13,7 @@ import xbmcvfs
|
||||||
|
|
||||||
import read_embyserver as embyserver
|
import read_embyserver as embyserver
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
from utils import window, language as lang
|
from utils import window, language as lang, normalize_nodes, indent as xml_indent
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
@ -30,20 +30,34 @@ class Views(object):
|
||||||
playlists = list()
|
playlists = list()
|
||||||
views = list()
|
views = list()
|
||||||
sorted_views = list()
|
sorted_views = list()
|
||||||
|
grouped_views = list()
|
||||||
|
|
||||||
|
media_types = {
|
||||||
|
|
||||||
|
'movies': "Movie",
|
||||||
|
'tvshows': "Series",
|
||||||
|
'musicvideos': "MusicVideo",
|
||||||
|
'homevideos': "Video",
|
||||||
|
'music': "Audio",
|
||||||
|
'photos': "Photo"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, emby_cursor, kodi_cursor):
|
def __init__(self, emby_cursor, kodi_cursor):
|
||||||
self.emby_cursor = emby_cursor
|
self.emby_cursor = emby_cursor
|
||||||
self.kodi_cursor = kodi_cursor
|
self.kodi_cursor = kodi_cursor
|
||||||
|
|
||||||
self.video_nodes = VideoNodes()
|
self.video_nodes = VideoNodes()
|
||||||
|
self.playlist = Playlist()
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.emby_db = embydb.Embydb_Functions(embycursor)
|
self.emby_db = embydb.Embydb_Functions(emby_cursor)
|
||||||
|
|
||||||
def _populate_views(self):
|
def _populate_views(self):
|
||||||
# Will get emby views and views in Kodi
|
# Will get emby views and views in Kodi
|
||||||
self.views = self.emby.getViews(sortedlist=True)
|
grouped_views = self.emby.get_views()
|
||||||
for view in self.views:
|
self.grouped_views = grouped_views['Items'] if "Items" in grouped_views else []
|
||||||
|
|
||||||
|
for view in self.emby.getViews(sortedlist=True):
|
||||||
|
self.views.append(view['name'])
|
||||||
if view['type'] == "music":
|
if view['type'] == "music":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -57,178 +71,105 @@ class Views(object):
|
||||||
def maintain(self):
|
def maintain(self):
|
||||||
# Compare views to emby
|
# Compare views to emby
|
||||||
self._populate_views()
|
self._populate_views()
|
||||||
view_list = [view['name'] for view in self.all_views]
|
curr_views = self.emby_db.getViews()
|
||||||
grouped_views = self.emby.get_views()
|
|
||||||
current_views = emby_db.getViews()
|
|
||||||
# total nodes for window properties
|
# total nodes for window properties
|
||||||
self.vnodes.clearProperties()
|
self.video_nodes.clearProperties()
|
||||||
|
|
||||||
# Set views for supported media type
|
for media_type in ('movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos'):
|
||||||
emby_mediatypes = {
|
|
||||||
|
|
||||||
'movies': "Movie",
|
|
||||||
'tvshows': "Series",
|
|
||||||
'musicvideos': "MusicVideo",
|
|
||||||
'homevideos': "Video",
|
|
||||||
'music': "Audio",
|
|
||||||
'photos': "Photo"
|
|
||||||
}
|
|
||||||
for mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos']:
|
|
||||||
|
|
||||||
self.nodes = list() # Prevent duplicate for nodes of the same type
|
self.nodes = list() # Prevent duplicate for nodes of the same type
|
||||||
self.playlists = list() # Prevent duplicate for playlists of the same type
|
self.playlists = list() # Prevent duplicate for playlists of the same type
|
||||||
# Get media folders from server
|
# Get media folders from the ordered
|
||||||
for folder in [view for view in ordered if view['type'] == mediatype]:
|
for folder in self.emby.getViews(media_type, root=True):
|
||||||
|
|
||||||
folderid = folder['id']
|
view_id = folder['id']
|
||||||
foldername = folder['name']
|
view_name = folder['name']
|
||||||
viewtype = folder['type']
|
view_type = folder['type']
|
||||||
|
|
||||||
if foldername not in view_list:
|
if view_name not in self.views:
|
||||||
# Media folders are grouped into userview
|
# Media folders are grouped into userview
|
||||||
params = {
|
view_name = self._get_grouped_view(media_type, view_id, view_name)
|
||||||
'ParentId': folderid,
|
|
||||||
'Recursive': True,
|
try: # Make sure the view is in sorted views before proceeding
|
||||||
'Limit': 1,
|
self.sorted_views.index(view_name)
|
||||||
'IncludeItemTypes': emby_mediatypes[mediatype]
|
|
||||||
} # Get one item from server using the folderid
|
|
||||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
|
||||||
result = self.doUtils(url, parameters=params)
|
|
||||||
try:
|
|
||||||
verifyitem = result['Items'][0]['Id']
|
|
||||||
except (TypeError, IndexError):
|
|
||||||
# Something is wrong. Keep the same folder name.
|
|
||||||
# Could be the view is empty or the connection
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for grouped_view in grouped_views:
|
|
||||||
# This is only reserved for the detection of grouped views
|
|
||||||
if (grouped_view['Type'] == "UserView" and
|
|
||||||
grouped_view.get('CollectionType') == mediatype):
|
|
||||||
# Take the userview, and validate the item belong to the view
|
|
||||||
if self.emby.verifyView(grouped_view['Id'], verifyitem):
|
|
||||||
# Take the name of the userview
|
|
||||||
log.info("Found corresponding view: %s %s"
|
|
||||||
% (grouped_view['Name'], grouped_view['Id']))
|
|
||||||
foldername = grouped_view['Name']
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Unable to find a match, add the name to our sorted_view list
|
|
||||||
self.sorted_views.append(foldername)
|
|
||||||
log.info("Couldn't find corresponding grouped view: %s" % self.sorted_views)
|
|
||||||
|
|
||||||
# Failsafe
|
|
||||||
try:
|
|
||||||
self.sorted_views.index(foldername)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.sorted_views.append(foldername)
|
self.sorted_views.append(view_name)
|
||||||
|
|
||||||
# Get current media folders from emby database
|
# Get current media folders from emby database and compare
|
||||||
view = emby_db.getView_byId(folderid)
|
if self.compare_view(media_type, view_id, view_name, view_type):
|
||||||
try:
|
if view_id in curr_views: # View is still valid
|
||||||
current_viewname = view[0]
|
curr_views.remove(view_id)
|
||||||
current_viewtype = view[1]
|
|
||||||
current_tagid = view[2]
|
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
self._add_view(mediatype, folderid, foldername, viewtype)
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.debug(' '.join((
|
|
||||||
|
|
||||||
"Found viewid: %s" % folderid,
|
|
||||||
"viewname: %s" % current_viewname,
|
|
||||||
"viewtype: %s" % current_viewtype,
|
|
||||||
"tagid: %s" % current_tagid)))
|
|
||||||
|
|
||||||
# View is still valid
|
|
||||||
try:
|
|
||||||
current_views.remove(folderid)
|
|
||||||
except ValueError:
|
|
||||||
# View was just created, nothing to remove
|
|
||||||
pass
|
|
||||||
|
|
||||||
# View was modified, update with latest info
|
|
||||||
if current_viewname != foldername:
|
|
||||||
log.info("viewid: %s new viewname: %s" % (folderid, foldername))
|
|
||||||
tagid = self._get_tag(foldername)
|
|
||||||
|
|
||||||
# Update view with new info
|
|
||||||
emby_db.updateView(foldername, tagid, folderid)
|
|
||||||
|
|
||||||
if mediatype != "music":
|
|
||||||
if emby_db.getView_byName(current_viewname) is None:
|
|
||||||
# The tag could be a combined view. Ensure there's no other tags
|
|
||||||
# with the same name before deleting playlist.
|
|
||||||
self._playlist(mediatype, current_viewname, folderid,
|
|
||||||
current_viewtype, True)
|
|
||||||
# Delete video node
|
|
||||||
if mediatype != "musicvideos":
|
|
||||||
self.vnodes.viewNode(
|
|
||||||
indexnumber=None,
|
|
||||||
tagname=current_viewname,
|
|
||||||
mediatype=mediatype,
|
|
||||||
viewtype=current_viewtype,
|
|
||||||
viewid=folderid,
|
|
||||||
delete=True)
|
|
||||||
# Added new playlist
|
|
||||||
self.create_playlist(mediatype, foldername, folderid, viewtype)
|
|
||||||
# Add new video node
|
|
||||||
self.create_node(mediatype, foldername, folderid, viewtype)
|
|
||||||
|
|
||||||
# Update items with new tag
|
|
||||||
items = emby_db.getItem_byView(folderid)
|
|
||||||
for item in items:
|
|
||||||
# Remove the "s" from viewtype for tags
|
|
||||||
self._update_tag(
|
|
||||||
current_tagid, tagid, item[0], current_viewtype[:-1])
|
|
||||||
else:
|
|
||||||
# Validate the playlist exists or recreate it
|
|
||||||
if mediatype != "music":
|
|
||||||
self.create_playlist(mediatype, foldername, folderid, viewtype)
|
|
||||||
# Create the video node if not already exists
|
|
||||||
self.create_node(mediatype, foldername, folderid, viewtype)
|
|
||||||
# Add video nodes listings
|
# Add video nodes listings
|
||||||
self.add_single_node(totalnodes, "Favorite movies", "movies", "favourites")
|
self.add_single_nodes()
|
||||||
self.add_single_node(totalnodes, "Favorite tvshows", "tvshows", "favourites")
|
|
||||||
self.add_single_node(totalnodes, "Favorite episodes", "episodes", "favourites")
|
|
||||||
self.add_single_node(totalnodes, "channels", "movies", "channels")
|
|
||||||
# Save total
|
# Save total
|
||||||
window('Emby.nodes.total', str(self.total_nodes))
|
window('Emby.nodes.total', str(self.total_nodes))
|
||||||
|
|
||||||
# Remove any old referenced views
|
# Remove any old referenced views
|
||||||
log.info("Removing views: %s", current_views)
|
log.info("Removing views: %s", curr_views)
|
||||||
for view in current_views:
|
for view in curr_views:
|
||||||
self.remove_view(view)
|
self.remove_view(view)
|
||||||
|
|
||||||
def create_node(self, media_type, view_name, view_id, view_type):
|
def _get_grouped_view(self, media_type, view_id, view_name):
|
||||||
|
# Get single item from view to compare
|
||||||
|
result = self.emby.get_single_item(self.media_types[media_type], view_id)
|
||||||
|
try:
|
||||||
|
item = result['Items'][0]['Id']
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
# Something is wrong. Keep the same folder name.
|
||||||
|
# Could be the view is empty or the connection
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for view in self.grouped_views:
|
||||||
|
if view['Type'] == "UserView" and view.get('CollectionType') == media_type:
|
||||||
|
# Take the userview, and validate the item belong to the view
|
||||||
|
if self.emby.verifyView(view['Id'], item):
|
||||||
|
log.info("found corresponding view: %s %s", view['Name'], view['Id'])
|
||||||
|
view_name = view['Name']
|
||||||
|
break
|
||||||
|
else: # Unable to find a match, add the name to our sorted_view list
|
||||||
|
log.info("couldn't find corresponding grouped view: %s", self.sorted_views)
|
||||||
|
|
||||||
if view_name not in self.nodes and media_type not in ('musicvideos', 'music'):
|
return view_name
|
||||||
index = self.sorted_views.index(view_name)
|
|
||||||
self.video_nodes.viewNode(index, view_name, media_type,view_type, view_id)
|
|
||||||
|
|
||||||
if view_type == "mixed": # Change the value
|
|
||||||
self.sorted_views[index] = "%ss" % view_name
|
|
||||||
|
|
||||||
self.nodes.append(view_name)
|
|
||||||
self.total_nodes += 1
|
|
||||||
|
|
||||||
def add_single_node(self, index, tag, media_type, item_type):
|
|
||||||
self.video_nodes.singleNode(index, tag, media_type, item_type)
|
|
||||||
self.total_nodes += 1
|
|
||||||
|
|
||||||
def add_view(self, media_type, view_id, view_name, view_type):
|
def add_view(self, media_type, view_id, view_name, view_type):
|
||||||
# Generate view, playlist and video node
|
# Generate view, playlist and video node
|
||||||
log.info("creating view %s: %s", view_name, view_id)
|
log.info("creating view %s: %s", view_name, view_id)
|
||||||
tag_id = self._get_tag(view_name)
|
tag_id = self.get_tag(view_name)
|
||||||
|
|
||||||
# Create playlist for the video library
|
self.add_playlist_node(media_type, view_id, view_name, view_type)
|
||||||
self.create_playlist(media_type, view_name, view_id, view_type)
|
|
||||||
# Create the video node
|
|
||||||
self.create_node(media_type, view_name, view_id, view_type)
|
|
||||||
# Add view to emby database
|
# Add view to emby database
|
||||||
self.emby_db.addView(view_id, view_name, view_type, tag_id)
|
self.emby_db.addView(view_id, view_name, view_type, tag_id)
|
||||||
|
|
||||||
|
def compare_view(self, media_type, view_id, view_name, view_type):
|
||||||
|
|
||||||
|
curr_view = self.emby_db.getView_byId(view_id)
|
||||||
|
try:
|
||||||
|
curr_view_name = curr_view[0]
|
||||||
|
curr_view_type = curr_view[1]
|
||||||
|
curr_tag_id = curr_view[2]
|
||||||
|
except TypeError:
|
||||||
|
self.add_view(media_type, view_id, view_name, view_type)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# View is still valid
|
||||||
|
log.debug("Found viewid: %s viewname: %s viewtype: %s tagid: %s",
|
||||||
|
view_id, curr_view_name, curr_view_type, curr_tag_id)
|
||||||
|
|
||||||
|
if curr_view_name != view_name:
|
||||||
|
# View was modified, update with latest info
|
||||||
|
log.info("viewid: %s new viewname: %s", view_id, view_name)
|
||||||
|
tag_id = self.get_tag(view_name)
|
||||||
|
# Update view with new info
|
||||||
|
self.emby_db.updateView(view_name, tag_id, view_id)
|
||||||
|
# Delete old playlists and video nodes
|
||||||
|
self.delete_playlist_node(media_type, curr_view_name, view_id, curr_view_type)
|
||||||
|
# Update items with new tag
|
||||||
|
self._update_items_tag(curr_view_type[:-1], view_id, curr_tag_id, tag_id)
|
||||||
|
|
||||||
|
# Verify existance of playlist and nodes
|
||||||
|
self.add_playlist_node(media_type, view_id, view_name, view_type)
|
||||||
|
return True
|
||||||
|
|
||||||
def remove_view(self, view):
|
def remove_view(self, view):
|
||||||
# Remove any items that belongs to the old view
|
# Remove any items that belongs to the old view
|
||||||
items = self.emby_db.get_item_by_view(view)
|
items = self.emby_db.get_item_by_view(view)
|
||||||
|
@ -236,79 +177,13 @@ class Views(object):
|
||||||
# TODO: Triage not accessible from here yet
|
# TODO: Triage not accessible from here yet
|
||||||
#self.triage_items("remove", items)
|
#self.triage_items("remove", items)
|
||||||
|
|
||||||
def create_playlist(self, media_type, view_name, view_id, view_type):
|
def _update_items_tag(self, media_type, view_id, tag, new_tag):
|
||||||
|
items = self.emby_db.getItem_byView(view_id)
|
||||||
|
for item in items:
|
||||||
|
# Remove the "s" from viewtype for tags
|
||||||
|
self._update_tag(tag, new_tag, item[0], media_type)
|
||||||
|
|
||||||
if view_name not in self.playlists and media_type in ('movies', 'tvshows', 'musicvideos'):
|
def get_tag(self, tag):
|
||||||
|
|
||||||
self._playlist(media_type, view_name, view_id, view_type)
|
|
||||||
self.playlists.append(view_name)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _add_playlist(cls, tag, playlist_name, path, media_type):
|
|
||||||
# Using write process since there's no guarantee the xml declaration works with etree
|
|
||||||
special_types = {'homevideos': "movies"}
|
|
||||||
log.info("writing playlist to: %s", path)
|
|
||||||
try:
|
|
||||||
f = xbmcvfs.File(path, 'w')
|
|
||||||
except:
|
|
||||||
log.info("failed to create playlist: %s", path)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
f.write(
|
|
||||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
|
||||||
'<smartplaylist type="%s">\n\t'
|
|
||||||
'<name>Emby %s</name>\n\t'
|
|
||||||
'<match>all</match>\n\t'
|
|
||||||
'<rule field="tag" operator="is">\n\t\t'
|
|
||||||
'<value>%s</value>\n\t'
|
|
||||||
'</rule>'
|
|
||||||
'</smartplaylist>'
|
|
||||||
% (special_types.get(media_type, media_type), playlist_name, tag))
|
|
||||||
f.close()
|
|
||||||
log.info("successfully added playlist: %s" % tag)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _delete_playlist(cls, path):
|
|
||||||
xbmcvfs.delete(path)
|
|
||||||
log.info("successfully removed playlist: %s", path)
|
|
||||||
|
|
||||||
def _playlist(self, media_type, tag, view_id, view_type="", delete=False):
|
|
||||||
# Tagname is in unicode - actions: add or delete
|
|
||||||
tag = tag.encode('utf-8')
|
|
||||||
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
|
||||||
|
|
||||||
if view_type == "mixed":
|
|
||||||
playlist_name = "%s - %s" % (tag, media_type)
|
|
||||||
xsp_path = os.path.join(path, "Emby %s - %s.xsp" % (view_id, media_type))
|
|
||||||
else:
|
|
||||||
playlist_name = tag
|
|
||||||
xsp_path = os.path.join(path, "Emby %s" % view_id)
|
|
||||||
|
|
||||||
# Only add the playlist if it doesn't exist
|
|
||||||
if xbmcvfs.exists(xsp_path):
|
|
||||||
if delete:
|
|
||||||
self._delete_playlist(xsp_path)
|
|
||||||
return True
|
|
||||||
|
|
||||||
elif not xbmcvfs.exists(path):
|
|
||||||
log.info("creating directory: %s", path)
|
|
||||||
xbmcvfs.mkdirs(path)
|
|
||||||
|
|
||||||
return self._add_playlist(tag, playlist_name, xsp_path, media_type)
|
|
||||||
|
|
||||||
def _add_tag(self, tag):
|
|
||||||
|
|
||||||
self.kodi_cursor.execute("select coalesce(max(tag_id),0) from tag")
|
|
||||||
tag_id = self.kodi_cursor.fetchone()[0] + 1
|
|
||||||
|
|
||||||
query = "INSERT INTO tag(tag_id, name) values(?, ?)"
|
|
||||||
self.kodi_cursor.execute(query, (tag_id, tag))
|
|
||||||
log.debug("Create tag_id: %s name: %s", tag_id, tag)
|
|
||||||
|
|
||||||
return tag_id
|
|
||||||
|
|
||||||
def _get_tag(self, tag):
|
|
||||||
# This will create and return the tag_id
|
# This will create and return the tag_id
|
||||||
if KODI in (15, 16, 17):
|
if KODI in (15, 16, 17):
|
||||||
# Kodi Isengard, Jarvis, Krypton
|
# Kodi Isengard, Jarvis, Krypton
|
||||||
|
@ -345,6 +220,17 @@ class Views(object):
|
||||||
|
|
||||||
return tag_id
|
return tag_id
|
||||||
|
|
||||||
|
def _add_tag(self, tag):
|
||||||
|
|
||||||
|
self.kodi_cursor.execute("select coalesce(max(tag_id),0) from tag")
|
||||||
|
tag_id = self.kodi_cursor.fetchone()[0] + 1
|
||||||
|
|
||||||
|
query = "INSERT INTO tag(tag_id, name) values(?, ?)"
|
||||||
|
self.kodi_cursor.execute(query, (tag_id, tag))
|
||||||
|
log.debug("Create tag_id: %s name: %s", tag_id, tag)
|
||||||
|
|
||||||
|
return tag_id
|
||||||
|
|
||||||
def _update_tag(self, tag, new_tag, kodi_id, media_type):
|
def _update_tag(self, tag, new_tag, kodi_id, media_type):
|
||||||
|
|
||||||
log.debug("Updating: %s with %s for %s: %s", tag, new_tag, media_type, kodi_id)
|
log.debug("Updating: %s with %s for %s: %s", tag, new_tag, media_type, kodi_id)
|
||||||
|
@ -395,6 +281,110 @@ class Views(object):
|
||||||
))
|
))
|
||||||
self.kodi_cursor.execute(query, (kodi_id, media_type, tag,))
|
self.kodi_cursor.execute(query, (kodi_id, media_type, tag,))
|
||||||
|
|
||||||
|
def add_playlist_node(self, media_type, view_id, view_name, view_type):
|
||||||
|
# Create playlist for the video library
|
||||||
|
if view_name not in self.playlists and media_type in ('movies', 'tvshows', 'musicvideos'):
|
||||||
|
self.playlist.process_playlist(media_type, view_id, view_name, view_type)
|
||||||
|
self.playlists.append(view_name)
|
||||||
|
# Create the video node
|
||||||
|
self._create_node(media_type, view_id, view_name, view_type)
|
||||||
|
|
||||||
|
def delete_playlist_node(self, media_type, view_id, view_name, view_type):
|
||||||
|
|
||||||
|
if media_type == "music":
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.emby_db.getView_byName(view_name) is None:
|
||||||
|
# The tag could be a combined view. Ensure there's no other tags
|
||||||
|
# with the same name before deleting playlist.
|
||||||
|
self.playlist.process_playlist(media_type, view_id, view_name, view_type, True)
|
||||||
|
# Delete video node
|
||||||
|
if media_type != "musicvideos":
|
||||||
|
self.video_nodes.viewNode(None, view_name, media_type, view_type, view_id, True)
|
||||||
|
|
||||||
|
def _create_node(self, media_type, view_id, view_name, view_type):
|
||||||
|
|
||||||
|
if view_name not in self.nodes and media_type not in ('musicvideos', 'music'):
|
||||||
|
index = self.sorted_views.index(view_name)
|
||||||
|
self.video_nodes.viewNode(index, view_name, media_type,view_type, view_id)
|
||||||
|
|
||||||
|
if view_type == "mixed": # Change the value
|
||||||
|
self.sorted_views[index] = "%ss" % view_name
|
||||||
|
|
||||||
|
self.nodes.append(view_name)
|
||||||
|
self.total_nodes += 1
|
||||||
|
|
||||||
|
def add_single_nodes(self):
|
||||||
|
singles = [
|
||||||
|
("Favorite movies", "movies", "favourites"),
|
||||||
|
("Favorite tvshows", "tvshows", "favourites"),
|
||||||
|
("Favorite episodes", "episodes", "favourites"),
|
||||||
|
("channels", "movies", "channels")
|
||||||
|
]
|
||||||
|
for args in singles:
|
||||||
|
self._single_node(self.total_nodes, *args)
|
||||||
|
|
||||||
|
def _single_node(self, index, tag, media_type, view_type):
|
||||||
|
self.video_nodes.singleNode(index, tag, media_type, view_type)
|
||||||
|
self.total_nodes += 1
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def process_playlist(self, media_type, view_id, view_name, view_type, delete=False):
|
||||||
|
# Tagname is in unicode - actions: add or delete
|
||||||
|
tag = view_name.encode('utf-8')
|
||||||
|
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
|
||||||
|
|
||||||
|
if view_type == "mixed":
|
||||||
|
playlist_name = "%s - %s" % (tag, media_type)
|
||||||
|
xsp_path = os.path.join(path, "Emby %s - %s.xsp" % (view_id, media_type))
|
||||||
|
else:
|
||||||
|
playlist_name = tag
|
||||||
|
xsp_path = os.path.join(path, "Emby %s" % view_id)
|
||||||
|
|
||||||
|
# Only add the playlist if it doesn't exist
|
||||||
|
if xbmcvfs.exists(xsp_path):
|
||||||
|
if delete:
|
||||||
|
self._delete_playlist(xsp_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
elif not xbmcvfs.exists(path):
|
||||||
|
log.info("creating directory: %s", path)
|
||||||
|
xbmcvfs.mkdirs(path)
|
||||||
|
|
||||||
|
self._add_playlist(tag, playlist_name, xsp_path, media_type)
|
||||||
|
|
||||||
|
def _add_playlist(self, tag, name, path, media_type):
|
||||||
|
# Using write process since there's no guarantee the xml declaration works with etree
|
||||||
|
special_types = {'homevideos': "movies"}
|
||||||
|
log.info("writing playlist to: %s", path)
|
||||||
|
try:
|
||||||
|
f = xbmcvfs.File(path, 'w')
|
||||||
|
except:
|
||||||
|
log.info("failed to create playlist: %s", path)
|
||||||
|
else:
|
||||||
|
f.write(
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
||||||
|
'<smartplaylist type="%s">\n\t'
|
||||||
|
'<name>Emby %s</name>\n\t'
|
||||||
|
'<match>all</match>\n\t'
|
||||||
|
'<rule field="tag" operator="is">\n\t\t'
|
||||||
|
'<value>%s</value>\n\t'
|
||||||
|
'</rule>'
|
||||||
|
'</smartplaylist>'
|
||||||
|
% (special_types.get(media_type, media_type), name, tag))
|
||||||
|
f.close()
|
||||||
|
log.info("successfully added playlist: %s" % tag)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _delete_playlist(cls, path):
|
||||||
|
xbmcvfs.delete(path)
|
||||||
|
log.info("successfully removed playlist: %s", path)
|
||||||
|
|
||||||
|
|
||||||
class VideoNodes(object):
|
class VideoNodes(object):
|
||||||
|
|
||||||
|
@ -402,7 +392,7 @@ class VideoNodes(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def commonRoot(self, order, label, tagname, roottype=1):
|
def commonRoot(self, order, label, tagname="", roottype=1):
|
||||||
|
|
||||||
if roottype == 0:
|
if roottype == 0:
|
||||||
# Index
|
# Index
|
||||||
|
@ -430,29 +420,10 @@ class VideoNodes(object):
|
||||||
else:
|
else:
|
||||||
dirname = viewid
|
dirname = viewid
|
||||||
|
|
||||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
|
||||||
nodepath = xbmc.translatePath(
|
nodepath = xbmc.translatePath(
|
||||||
"special://profile/library/video/emby/%s/" % dirname).decode('utf-8')
|
"special://profile/library/video/emby/%s/" % dirname).decode('utf-8')
|
||||||
|
|
||||||
# Verify the video directory
|
|
||||||
if not xbmcvfs.exists(path):
|
|
||||||
try:
|
|
||||||
shutil.copytree(
|
|
||||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
|
||||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
|
||||||
except Exception as error:
|
|
||||||
log.error(error)
|
|
||||||
|
|
||||||
xbmcvfs.exists(path)
|
|
||||||
|
|
||||||
emby_path = xbmc.translatePath("special://profile/library/video/emby/index.xml").decode('utf-8')
|
|
||||||
if not emby_path:
|
|
||||||
root = self.commonRoot(order=0, label="Emby", roottype=0)
|
|
||||||
try:
|
|
||||||
utils.indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(emby_path)
|
|
||||||
|
|
||||||
if delete:
|
if delete:
|
||||||
dirs, files = xbmcvfs.listdir(nodepath)
|
dirs, files = xbmcvfs.listdir(nodepath)
|
||||||
for file in files:
|
for file in files:
|
||||||
|
@ -460,10 +431,32 @@ class VideoNodes(object):
|
||||||
|
|
||||||
log.info("Sucessfully removed videonode: %s." % tagname)
|
log.info("Sucessfully removed videonode: %s." % tagname)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Verify the video directory
|
||||||
|
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||||
|
if not xbmcvfs.exists(path):
|
||||||
|
try:
|
||||||
|
shutil.copytree(
|
||||||
|
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||||
|
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||||
|
except Exception as error:
|
||||||
|
log.error(error)
|
||||||
|
|
||||||
|
xbmcvfs.mkdir(path)
|
||||||
|
|
||||||
|
embypath = xbmc.translatePath("special://profile/library/video/emby/").decode('utf-8')
|
||||||
|
if not xbmcvfs.exists(embypath):
|
||||||
|
xbmcvfs.mkdir(embypath)
|
||||||
|
root = self.commonRoot(order=0, label="Emby", roottype=0)
|
||||||
|
try:
|
||||||
|
xml_indent(root)
|
||||||
|
except: pass
|
||||||
|
etree.ElementTree(root).write(os.path.join(embypath, "index.xml"))
|
||||||
|
|
||||||
# Create the node directory
|
# Create the node directory
|
||||||
if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
|
if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
|
||||||
# We need to copy over the default items
|
# We need to copy over the default items
|
||||||
xbmcvfs.mkdirs(nodepath)
|
xbmcvfs.mkdir(nodepath)
|
||||||
|
|
||||||
# Create index entry
|
# Create index entry
|
||||||
nodeXML = "%sindex.xml" % nodepath
|
nodeXML = "%sindex.xml" % nodepath
|
||||||
|
@ -487,7 +480,7 @@ class VideoNodes(object):
|
||||||
else:
|
else:
|
||||||
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
||||||
try:
|
try:
|
||||||
utils.indent(root)
|
xml_indent(root)
|
||||||
except: pass
|
except: pass
|
||||||
etree.ElementTree(root).write(nodeXML)
|
etree.ElementTree(root).write(nodeXML)
|
||||||
|
|
||||||
|
@ -582,10 +575,10 @@ class VideoNodes(object):
|
||||||
elif nodetype == "nextepisodes":
|
elif nodetype == "nextepisodes":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
|
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
|
||||||
elif self.kodiversion == 14 and nodetype == "recentepisodes":
|
elif KODI == 14 and nodetype == "recentepisodes":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
|
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
|
||||||
elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
|
elif KODI == 14 and nodetype == "inprogressepisodes":
|
||||||
# Custom query
|
# Custom query
|
||||||
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
|
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
|
||||||
else:
|
else:
|
||||||
|
@ -626,7 +619,7 @@ class VideoNodes(object):
|
||||||
|
|
||||||
# Create the root
|
# Create the root
|
||||||
if (nodetype == "nextepisodes" or mediatype == "homevideos" or
|
if (nodetype == "nextepisodes" or mediatype == "homevideos" or
|
||||||
(self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
|
(KODI == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
|
||||||
# Folder type with plugin path
|
# Folder type with plugin path
|
||||||
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
|
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
|
||||||
etree.SubElement(root, 'path').text = path
|
etree.SubElement(root, 'path').text = path
|
||||||
|
@ -693,14 +686,14 @@ class VideoNodes(object):
|
||||||
attrib={'field': "inprogress", 'operator':"true"})
|
attrib={'field': "inprogress", 'operator':"true"})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.indent(root)
|
xml_indent(root)
|
||||||
except: pass
|
except: pass
|
||||||
etree.ElementTree(root).write(nodeXML)
|
etree.ElementTree(root).write(nodeXML)
|
||||||
|
|
||||||
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
||||||
|
|
||||||
tagname = tagname.encode('utf-8')
|
tagname = tagname.encode('utf-8')
|
||||||
cleantagname = utils.normalize_nodes(tagname)
|
cleantagname = normalize_nodes(tagname)
|
||||||
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||||
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
|
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
|
||||||
path = "library://video/emby_%s.xml" % cleantagname
|
path = "library://video/emby_%s.xml" % cleantagname
|
||||||
|
@ -745,6 +738,30 @@ class VideoNodes(object):
|
||||||
etree.SubElement(root, 'content').text = mediatype
|
etree.SubElement(root, 'content').text = mediatype
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.indent(root)
|
xml_indent(root)
|
||||||
except: pass
|
except: pass
|
||||||
etree.ElementTree(root).write(nodeXML)
|
etree.ElementTree(root).write(nodeXML)
|
||||||
|
|
||||||
|
def clearProperties(self):
|
||||||
|
|
||||||
|
log.info("Clearing nodes properties.")
|
||||||
|
embyprops = window('Emby.nodes.total')
|
||||||
|
propnames = [
|
||||||
|
|
||||||
|
"index","path","title","content",
|
||||||
|
"inprogress.content","inprogress.title",
|
||||||
|
"inprogress.content","inprogress.path",
|
||||||
|
"nextepisodes.title","nextepisodes.content",
|
||||||
|
"nextepisodes.path","unwatched.title",
|
||||||
|
"unwatched.content","unwatched.path",
|
||||||
|
"recent.title","recent.content","recent.path",
|
||||||
|
"recentepisodes.title","recentepisodes.content",
|
||||||
|
"recentepisodes.path","inprogressepisodes.title",
|
||||||
|
"inprogressepisodes.content","inprogressepisodes.path"
|
||||||
|
]
|
||||||
|
|
||||||
|
if embyprops:
|
||||||
|
totalnodes = int(embyprops)
|
||||||
|
for i in range(totalnodes):
|
||||||
|
for prop in propnames:
|
||||||
|
window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
||||||
|
|
Loading…
Add table
Reference in a new issue