Merge branch 'develop' into translations

This commit is contained in:
tomkat83 2017-05-06 19:02:16 +02:00
commit fcfb18226b
22 changed files with 458 additions and 384 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-1.7.7-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
[![beta version](https://img.shields.io/badge/beta_version-1.7.9-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
[![beta version](https://img.shields.io/badge/beta_version-1.7.12-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
@ -81,7 +81,7 @@ Install PKC via the PlexKodiConnect Kodi repository below (we cannot use the off
2. **Compatibility**:
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
* PKC is not (and will never be) compatible with the **MySQL database replacement** in Kodi. In fact, PKC replaces the MySQL functionality because it acts as a "man in the middle" for your entire media library.
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths)
* If **another plugin is not working** like it's supposed to, try to use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Direct-Paths-Explained)
### Donations
I'm not in any way affiliated with Plex. Thank you very much for a small donation via ko-fi.com and PayPal if you appreciate PKC.
@ -98,8 +98,6 @@ I'm not in any way affiliated with Plex. Thank you very much for a small donatio
Solutions are unlikely due to the nature of these issues
- A Plex Media Server "bug" leads to frequent and slow syncs, see [here for more info](https://github.com/croneter/PlexKodiConnect/issues/135)
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details
- *Plex Music when using Addon paths instead of Native Direct Paths:* You must have a static IP address for your Plex media server if you plan to use Plex Music features
- External Plex subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly and tell what language they are in. However, this is not the case if you use direct paths
*Background Sync:*
The Plex Server does not tell anyone of the following changes. Hence PKC cannot detect these changes instantly but will notice them only on full/delta syncs (standard settings is every 60 minutes)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.9" provider-name="croneter">
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.12" provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.3.0" />
@ -44,7 +44,22 @@
<disclaimer lang="nl_NL">Gebruik op eigen risico</disclaimer>
<disclaimer lang="zh_TW">使用風險由您自己承擔</disclaimer>
<disclaimer lang="es_ES">Usar a su propio riesgo</disclaimer>
<news>version 1.7.9 (beta only)
<news>version 1.7.12 (beta only)
- Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database
- Some Plex Companion fixes
- Fix UnicodeDecodeError on user switch
- Remove link to Crowdin.com
- Update Readme
version 1.7.11 (beta only)
- Add support to Kodi 18.0-alpha1 (thanks @CotzaDev)
- Fix PKC not storing network credentials correctly
version 1.7.10 (beta only)
- Avoid xbmcvfs entirely; use encoded paths
- Update Czech translation
version 1.7.9 (beta only)
- Big transcoding overhaul
- Fix for not detecting external subtitle language
- Change Plex transcoding profile to Android

View file

@ -1,3 +1,18 @@
version 1.7.12 (beta only)
- Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database
- Some Plex Companion fixes
- Fix UnicodeDecodeError on user switch
- Remove link to Crowdin.com
- Update Readme
version 1.7.11 (beta only)
- Add support to Kodi 18.0-alpha1 (thanks @CotzaDev)
- Fix PKC not storing network credentials correctly
version 1.7.10 (beta only)
- Avoid xbmcvfs entirely; use encoded paths
- Update Czech translation
version 1.7.9 (beta only)
- Big transcoding overhaul
- Fix for not detecting external subtitle language

View file

@ -1,5 +0,0 @@
files:
- source: /resources/language/English/*.*
translation: /resources/language/%language%/%original_file_name%
translate_attributes: '0'
content_segmentation: '0'

View file

@ -1894,3 +1894,8 @@ msgstr ""
msgctxt "#39710"
msgid "burn-in"
msgstr ""
# Dialog text if PKC detected a new Music library and Kodi needs to be restarted
msgctxt "#39711"
msgid "New Plex music library detected. Sorry, but we need to restart Kodi now due to the changes made."
msgstr ""

View file

@ -39,11 +39,11 @@ import xml.etree.ElementTree as etree
from re import compile as re_compile, sub
from json import dumps
from urllib import urlencode, quote_plus, unquote
from os import path as os_path
from os.path import basename, join, exists
from os import makedirs
import xbmcgui
from xbmc import sleep, executebuiltin
from xbmcvfs import exists, mkdirs
import clientinfo as client
from downloadutils import DownloadUtils
@ -2113,11 +2113,14 @@ class API():
if fanarttvimage not in data:
continue
for entry in data[fanarttvimage]:
if fanartcount < maxfanarts:
if exists(entry.get("url")):
allartworks['Backdrop'].append(
entry.get("url", "").replace(' ', '%20'))
fanartcount += 1
if entry.get("url") is None:
continue
if fanartcount > maxfanarts:
break
if exists(tryEncode(entry['url'])):
allartworks['Backdrop'].append(
entry['url'].replace(' ', '%20'))
fanartcount += 1
return allartworks
def getSetArtwork(self, parentInfo=False):
@ -2184,7 +2187,7 @@ class API():
# Get additional info (filename / languages)
filename = None
if 'file' in entry[0].attrib:
filename = os_path.basename(entry[0].attrib['file'])
filename = basename(entry[0].attrib['file'])
# Languages of audio streams
languages = []
for stream in entry[0]:
@ -2339,8 +2342,8 @@ class API():
Returns the path to the downloaded subtitle or None
"""
if not exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
mkdirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
path = os_path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
path = join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
r = DownloadUtils().downloadUrl(url, return_response=True)
try:
r.status_code
@ -2506,7 +2509,8 @@ class API():
listItem.addStreamInfo(
"video", {'duration': self.getRuntime()[1]})
def validatePlayurl(self, path, typus, forceCheck=False, folder=False):
def validatePlayurl(self, path, typus, forceCheck=False, folder=False,
omitCheck=False):
"""
Returns a valid path for Kodi, e.g. with '\' substituted to '\\' in
Unicode. Returns None if this is not possible
@ -2516,6 +2520,7 @@ class API():
forceCheck : Will always try to check validity of path
Will also skip confirmation dialog if path not found
folder : Set to True if path is a folder
omitCheck : Will entirely omit validity check if True
"""
if path is None:
return None
@ -2529,7 +2534,8 @@ class API():
elif window('replaceSMB') == 'true':
if path.startswith('\\\\'):
path = 'smb:' + path.replace('\\', '/')
if window('plex_pathverified') == 'true' and forceCheck is False:
if ((window('plex_pathverified') == 'true' and forceCheck is False) or
omitCheck is True):
return path
# exist() needs a / or \ at the end to work for directories

View file

@ -258,43 +258,12 @@ def GetPlexOnDeck(viewId):
return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId)
def GetPlexCollections(mediatype):
def get_plex_sections():
"""
Input:
mediatype String or list of strings with possible values
'movie', 'show', 'artist', 'photo'
Output:
List with an entry of the form:
{
'name': xxx Plex title for the media section
'type': xxx Plex type: 'movie', 'show', 'artist', 'photo'
'id': xxx Plex unique key for the section (1, 2, 3...)
'uuid': xxx Other unique Plex key, e.g.
74aec9f2-a312-4723-9436-de2ea43843c1
}
Returns an empty list if nothing is found.
Returns all Plex sections (libraries) of the PMS as an etree xml
"""
collections = []
url = "{server}/library/sections"
xml = downloadutils.DownloadUtils().downloadUrl(url)
try:
xml.attrib
except AttributeError:
log.error('Could not download PMS sections for %s' % url)
return {}
for item in xml:
contentType = item['type']
if contentType in mediatype:
name = item['title']
contentId = item['key']
uuid = item['uuid']
collections.append({
'name': name,
'type': contentType,
'id': str(contentId),
'uuid': uuid
})
return collections
return downloadutils.DownloadUtils().downloadUrl(
'{server}/library/sections')
def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',

View file

@ -4,16 +4,16 @@
import logging
from json import dumps, loads
import requests
from os import path as os_path
from os.path import exists
from shutil import rmtree
from urllib import quote_plus, unquote
from threading import Thread
from Queue import Queue, Empty
from xbmc import executeJSONRPC, sleep, translatePath
from xbmcvfs import listdir, delete
from utils import window, settings, language as lang, kodiSQL, tryEncode, \
tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop, dialog
ThreadMethods, ThreadMethodsAdditionalStop, dialog
# Disable annoying requests warnings
import requests.packages.urllib3
@ -228,30 +228,21 @@ class Artwork():
if dialog('yesno', "Image Texture Cache", lang(39251)):
log.info("Resetting all cache data first")
# Remove all existing textures first
path = tryDecode(translatePath("special://thumbnails/"))
if IfExists(path):
allDirs, allFiles = listdir(path)
for dir in allDirs:
allDirs, allFiles = listdir(path+dir)
for file in allFiles:
if os_path.supports_unicode_filenames:
delete(os_path.join(
path + tryDecode(dir),
tryDecode(file)))
else:
delete(os_path.join(
tryEncode(path) + dir,
file))
path = translatePath("special://thumbnails/")
if exists(path):
rmtree(path, ignore_errors=True)
# remove all existing data from texture DB
connection = kodiSQL('texture')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ('table', ))
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if tableName != "version":
cursor.execute("DELETE FROM " + tableName)
query = "DELETE FROM ?"
cursor.execute(query, (tableName,))
connection.commit()
connection.close()
@ -259,7 +250,8 @@ class Artwork():
connection = kodiSQL('video')
cursor = connection.cursor()
# dont include actors
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'")
query = "SELECT url FROM art WHERE media_type != ?"
cursor.execute(query, ('actor', ))
result = cursor.fetchall()
total = len(result)
log.info("Image cache sync about to process %s video images" % total)
@ -286,7 +278,6 @@ class Artwork():
def addArtwork(self, artwork, kodiId, mediaType, cursor):
# Kodi conversion table
kodiart = {
'Primary': ["thumb", "poster"],
'Banner': "banner",
'Logo': "clearlogo",
@ -307,7 +298,6 @@ class Artwork():
backdropsNumber = len(backdrops)
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
@ -320,7 +310,6 @@ class Artwork():
if len(rows) > backdropsNumber:
# More backdrops in database. Delete extra fanart.
query = ' '.join((
"DELETE FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
@ -339,7 +328,7 @@ class Artwork():
cursor=cursor)
if backdropsNumber > 1:
try: # Will only fail on the first try, str to int.
try: # Will only fail on the first try, str to int.
index += 1
except TypeError:
index = 1
@ -438,14 +427,10 @@ class Artwork():
log.info("Could not find cached url.")
else:
# Delete thumbnail as well as the entry
thumbnails = tryDecode(
translatePath("special://thumbnails/%s" % cachedurl))
log.debug("Deleting cached thumbnail: %s" % thumbnails)
try:
delete(thumbnails)
except Exception as e:
log.error('Could not delete cached artwork %s. Error: %s'
% (thumbnails, e))
path = translatePath("special://thumbnails/%s" % cachedurl)
log.debug("Deleting cached thumbnail: %s" % path)
if exists(path):
rmtree(path, ignore_errors=True)
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
finally:

View file

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
import logging
from re import compile as re_compile
from xbmc import Player
@ -13,8 +12,6 @@ from PlexFunctions import GetPlexKeyNumber
log = logging.getLogger("PLEX."+__name__)
REGEX_PLAYQUEUES = re_compile(r'''/playQueues/(\d+)$''')
###############################################################################

View file

@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from os import path as os_path
from shutil import copyfile
from os import walk, makedirs
from os.path import basename, join, exists
from sys import argv
from urllib import urlencode
@ -9,8 +11,8 @@ import xbmcplugin
from xbmc import sleep, executebuiltin, translatePath
from xbmcgui import ListItem
from utils import window, settings, language as lang, dialog, tryDecode,\
tryEncode, CatchExceptions, JSONRPC
from utils import window, settings, language as lang, dialog, tryEncode, \
CatchExceptions, JSONRPC
import downloadutils
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
@ -486,6 +488,7 @@ def getVideoFiles(plexId, params):
except:
log.error('Could not get file path for item %s' % plexId)
return xbmcplugin.endOfDirectory(HANDLE)
path = tryEncode(path)
# Assign network protocol
if path.startswith('\\\\'):
path = path.replace('\\\\', 'smb://')
@ -493,28 +496,25 @@ def getVideoFiles(plexId, params):
# Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv'
elif '\\' in path:
path = path.replace('\\', '\\\\')
# Directory only, get rid of filename (!! exists() needs / or \ at end)
path = path.replace(os_path.basename(path), '')
# Only proceed if we can access this folder
import xbmcvfs
if xbmcvfs.exists(path):
# Careful, returns encoded strings!
dirs, files = xbmcvfs.listdir(path)
for file in files:
file = path + tryDecode(file)
li = ListItem(file, path=file)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=tryEncode(file),
listitem=li)
for dir in dirs:
dir = path + tryDecode(dir)
li = ListItem(dir, path=dir)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=tryEncode(dir),
listitem=li,
isFolder=True)
# Directory only, get rid of filename
path = path.replace(basename(path), '')
if exists(path):
for root, dirs, files in walk(path):
for directory in dirs:
item_path = join(root, directory)
li = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=item_path,
listitem=li,
isFolder=True)
for file in files:
item_path = join(root, file)
li = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=file,
listitem=li)
else:
log.warn('Kodi cannot access folder %s' % path)
log.error('Kodi cannot access folder %s' % path)
xbmcplugin.endOfDirectory(HANDLE)
@ -525,7 +525,6 @@ def getExtraFanArt(plexid, plexPath):
will be called by skinhelper script to get the extrafanart
for tvshows we get the plexid just from the path
"""
import xbmcvfs
log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath))
if not plexid:
if "plugin.video.plexkodiconnect" in plexPath:
@ -536,11 +535,10 @@ def getExtraFanArt(plexid, plexPath):
# We need to store the images locally for this to work
# because of the caching system in xbmc
fanartDir = tryDecode(translatePath(
"special://thumbnails/plex/%s/" % plexid))
if not xbmcvfs.exists(fanartDir):
fanartDir = translatePath("special://thumbnails/plex/%s/" % plexid)
if not exists(fanartDir):
# Download the images to the cache directory
xbmcvfs.mkdirs(tryEncode(fanartDir))
makedirs(fanartDir)
xml = GetPlexMetadata(plexid)
if xml is None:
log.error('Could not download metadata for %s' % plexid)
@ -550,29 +548,23 @@ def getExtraFanArt(plexid, plexPath):
backdrops = api.getAllArtwork()['Backdrop']
for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork
if os_path.supports_unicode_filenames:
fanartFile = os_path.join(fanartDir,
"fanart%.3d.jpg" % count)
else:
fanartFile = os_path.join(
tryEncode(fanartDir),
tryEncode("fanart%.3d.jpg" % count))
fanartFile = join(fanartDir, "fanart%.3d.jpg" % count)
li = ListItem("%.3d" % count, path=fanartFile)
xbmcplugin.addDirectoryItem(
handle=HANDLE,
url=fanartFile,
listitem=li)
xbmcvfs.copy(backdrop, fanartFile)
copyfile(backdrop, fanartFile)
else:
log.info("Found cached backdrop.")
# Use existing cached images
dirs, files = xbmcvfs.listdir(fanartDir)
for file in files:
fanartFile = os_path.join(fanartDir, tryDecode(file))
li = ListItem(file, path=fanartFile)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=fanartFile,
listitem=li)
for root, dirs, files in walk(fanartDir):
for file in files:
fanartFile = join(root, file)
li = ListItem(file, path=fanartFile)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=fanartFile,
listitem=li)
xbmcplugin.endOfDirectory(HANDLE)

View file

@ -7,7 +7,7 @@ import xbmc
import xbmcgui
from utils import settings, window, language as lang, tryEncode, \
get_advancessettings_xml_setting
advancedsettings_xml
import downloadutils
from userclient import UserClient
@ -401,7 +401,7 @@ class InitialSetup():
dialog = self.dialog
# Get current Kodi video cache setting
cache = get_advancessettings_xml_setting(['cache', 'memorysize'])
cache, _ = advancedsettings_xml(['cache', 'memorysize'])
if cache is not None:
cache = str(cache.text)
else:
@ -478,8 +478,8 @@ class InitialSetup():
log.debug("User opted to disable Plex music library.")
settings('enableMusic', value="false")
else:
from utils import advancedSettingsXML
advancedSettingsXML()
from utils import advancedsettings_tweaks
advancedsettings_tweaks()
# Download additional art from FanArtTV
if dialog.yesno(heading=lang(29999), line1=lang(39061)):

View file

@ -851,7 +851,6 @@ class Kodidb_Functions():
if len(result) == 0:
log.info('Did not find matching paths, abort')
return
log.debug('Result: %s' % result)
# Kodi seems to make ONE temporary entry; we only want the earlier,
# permanent one
if len(result) > 2:
@ -859,7 +858,6 @@ class Kodidb_Functions():
' paths, aborting')
return
idFile = result[0]
log.debug('idFile: %s' % idFile)
# Try movies first
query = ' '.join((

View file

@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from threading import Thread
import Queue
from random import shuffle
from os.path import exists
import xbmc
import xbmcgui
import xbmcvfs
from utils import window, settings, getUnixTimestamp, sourcesXML,\
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
advancedSettingsXML, tryDecode, deletePlaylists, deleteNodes, \
advancedsettings_tweaks, tryDecode, deletePlaylists, deleteNodes, \
ThreadMethodsAdditionalSuspend, create_actor_db_index, dialog
import downloadutils
import itemtypes
@ -25,12 +23,13 @@ import videonodes
import variables as v
from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \
GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus
GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus, get_plex_sections
import PlexAPI
from library_sync.get_metadata import Threaded_Get_Metadata
from library_sync.process_metadata import Threaded_Process_Metadata
import library_sync.sync_info as sync_info
from library_sync.fanart import Process_Fanart_Thread
import music
###############################################################################
@ -73,6 +72,7 @@ class LibrarySync(Thread):
self.enableMusic = settings('enableMusic') == "true"
self.enableBackgroundSync = settings(
'enableBackgroundSync') == "true"
self.direct_paths = settings('useDirectPaths') == '1'
# Init for replacing paths
window('remapSMB', value=settings('remapSMB'))
@ -128,8 +128,7 @@ class LibrarySync(Thread):
# change in lastViewedAt
# Get all Plex libraries
sections = downloadutils.DownloadUtils().downloadUrl(
"{server}/library/sections")
sections = get_plex_sections()
try:
sections.attrib
except AttributeError:
@ -298,6 +297,15 @@ class LibrarySync(Thread):
}
if self.enableMusic:
process['music'] = self.PlexMusic
if self.direct_paths is True:
if music.set_excludefromscan_music_folders() is True:
log.info('Detected new Music library - restarting now')
# 'New Plex music library detected. Sorry, but we need to
# restart Kodi now due to the changes made.'
dialog('ok', lang(29999), lang(39711))
from xbmc import executebuiltin
executebuiltin('RestartApp')
return False
# Do the processing
for itemtype in process:
@ -479,8 +487,7 @@ class LibrarySync(Thread):
vnodes = self.vnodes
# Get views
sections = downloadutils.DownloadUtils().downloadUrl(
"{server}/library/sections")
sections = get_plex_sections()
try:
sections.attrib
except AttributeError:
@ -1463,7 +1470,7 @@ class LibrarySync(Thread):
self.initializeDBs()
if self.enableMusic:
advancedSettingsXML()
advancedsettings_tweaks()
if settings('FanartTV') == 'true':
self.fanartthread.start()
@ -1505,7 +1512,7 @@ class LibrarySync(Thread):
# Also runs when first installed
# Verify the video database can be found
videoDb = v.DB_VIDEO_PATH
if not xbmcvfs.exists(videoDb):
if not exists(videoDb):
# Database does not exists
log.error("The current Kodi version is incompatible "
"to know which Kodi versions are supported.")

119
resources/lib/music.py Normal file
View file

@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from re import compile as re_compile
import xml.etree.ElementTree as etree
from utils import advancedsettings_xml, indent, tryEncode
from PlexFunctions import get_plex_sections
from PlexAPI import API
import variables as v
###############################################################################
log = getLogger("PLEX."+__name__)
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
###############################################################################
def get_current_music_folders():
"""
Returns a list of encoded strings as paths to the currently "blacklisted"
excludefromscan music folders in the advancedsettings.xml
"""
paths = []
root, _ = advancedsettings_xml(['audio', 'excludefromscan'])
if root is None:
return paths
for element in root:
try:
path = REGEX_MUSICPATH.findall(element.text)[0]
except IndexError:
log.error('Could not parse %s of xml element %s'
% (element.text, element.tag))
continue
else:
paths.append(path)
return paths
def set_excludefromscan_music_folders():
"""
Gets a complete list of paths for music libraries from the PMS. Sets them
to be excluded in the advancedsettings.xml from being scanned by Kodi.
Existing keys will be replaced
Returns False if no new Plex libraries needed to be exluded, True otherwise
"""
changed = False
write_xml = False
xml = get_plex_sections()
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
log.error('Could not get Plex sections')
return
# Build paths
paths = []
api = API(item=None)
for library in xml:
if library.attrib['type'] != v.PLEX_TYPE_ARTIST:
# Only look at music libraries
continue
for location in library:
if location.tag == 'Location':
path = api.validatePlayurl(location.attrib['path'],
typus=v.PLEX_TYPE_ARTIST,
omitCheck=True)
path = tryEncode(path)
paths.append(__turn_to_regex(path))
# Get existing advancedsettings
root, tree = advancedsettings_xml(['audio', 'excludefromscan'],
force_create=True)
for path in paths:
for element in root:
if element.text == path:
# Path already excluded
break
else:
changed = True
write_xml = True
log.info('New Plex music library detected: %s' % path)
element = etree.Element(tag='regexp')
element.text = path
root.append(element)
# Delete obsolete entries (unlike above, we don't change 'changed' to not
# enforce a restart)
for element in root:
for path in paths:
if element.text == path:
break
else:
log.info('Deleting Plex music library from advancedsettings: %s'
% element.text)
root.remove(element)
write_xml = True
if write_xml is True:
indent(tree.getroot())
tree.write('%sadvancedsettings.xml' % v.KODI_PROFILE)
return changed
def __turn_to_regex(path):
"""
Turns a path into regex expression to be fed to Kodi's advancedsettings.xml
"""
# Make sure we have a slash or backslash at the end of the path
if '/' in path:
if not path.endswith('/'):
path = '%s/' % path
else:
if not path.endswith('\\'):
path = '%s\\' % path
# Need to escape backslashes
path = path.replace('\\', '\\\\')
# Beginning of path only needs to be similar
return '^%s' % path

View file

@ -4,7 +4,7 @@ from urlparse import parse_qsl, urlsplit
import plexdb_functions as plexdb
from downloadutils import DownloadUtils as DU
from utils import JSONRPC, tryEncode
from utils import JSONRPC, tryEncode, escape_html
from PlexAPI import API
###############################################################################
@ -157,6 +157,8 @@ def playlist_item_from_xml(playlist, xml_video_element):
item.plex_id = api.getRatingKey()
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
item.guid = xml_video_element.attrib.get('guid')
if item.guid is not None:
item.guid = escape_html(item.guid)
if item.plex_id:
with plexdb.Get_Plex_DB() as plex_db:
db_element = plex_db.getItem_byId(item.plex_id)
@ -336,7 +338,7 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
# Get the guid for this item
for plex_item in xml:
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
item.guid = plex_item.attrib['guid']
item.guid = escape_html(plex_item.attrib['guid'])
playlist.items.append(item)
if pos == len(playlist.items) - 1:
# Item was added at the end

View file

@ -57,7 +57,7 @@ def plex_type(xbmc_type):
def getXMLHeader():
return '<?xml version="1.0" encoding="utf-8" ?>\n'
return '<?xml version="1.0" encoding="UTF-8"?>\n'
def getOKMsg():

View file

@ -55,19 +55,8 @@ class SubscriptionManager:
def msg(self, players):
msg = getXMLHeader()
msg += '<MediaContainer commandID="INSERTCOMMANDID"'
if players:
self.getVolume()
maintype = plex_audio()
for p in players.values():
if p.get('type') == xbmc_video():
maintype = plex_video()
elif p.get('type') == xbmc_photo():
maintype = plex_photo()
self.mainlocation = "fullScreen" + maintype[0:1].upper() + maintype[1:].lower()
else:
self.mainlocation = "navigation"
msg += ' location="%s">' % self.mainlocation
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
msg += ' machineIdentifier="%s">' % window('plex_client_Id')
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
@ -108,7 +97,6 @@ class SubscriptionManager:
if keyid:
self.lastkey = "/library/metadata/%s" % keyid
self.ratingkey = keyid
ret += ' location="%s"' % self.mainlocation
ret += ' key="%s"' % self.lastkey
ret += ' ratingKey="%s"' % self.ratingkey
serv = self.getServerByHost(self.server)
@ -138,7 +126,7 @@ class SubscriptionManager:
ret += ' subtitleStreamID="-1"'
ret += ' audioStreamID="-1"'
ret += ' />'
ret += '/>'
return ret
def updateCommandID(self, uuid, commandID):

View file

@ -3,14 +3,15 @@
###############################################################################
import logging
import threading
from os.path import exists
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
from utils import window, settings, language as lang, ThreadMethods, \
tryDecode, ThreadMethodsAdditionalSuspend
ThreadMethodsAdditionalSuspend
import downloadutils
import PlexAPI
@ -209,9 +210,8 @@ class UserClient(threading.Thread):
return False
# Get /profile/addon_data
addondir = tryDecode(xbmc.translatePath(
self.addon.getAddonInfo('profile')))
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile'))
hasSettings = exists("%ssettings.xml" % addondir)
# If there's no settings.xml
if not hasSettings:

View file

@ -13,15 +13,17 @@ from unicodedata import normalize
import xml.etree.ElementTree as etree
from functools import wraps
from calendar import timegm
from os import path as os_path
from os.path import exists, join
from os import remove, makedirs, walk
from shutil import rmtree
from urllib import quote_plus
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \
DB_PLEX_PATH
DB_PLEX_PATH, KODI_PROFILE
###############################################################################
@ -180,6 +182,23 @@ def tryDecode(string, encoding='utf-8'):
return string
def escape_html(string):
"""
Escapes the following:
< to &lt;
> to &gt;
& to &amp;
"""
escapes = {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;'
}
for key, value in escapes.iteritems():
string = string.replace(key, value)
return string
def DateToKodi(stamp):
"""
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
@ -198,27 +217,6 @@ def DateToKodi(stamp):
return localdate
def IfExists(path):
"""
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
path: path to a directory (with a slash at the end)
Returns True if path exists, else false
"""
dummyfile = tryEncode(os_path.join(path, 'dummyfile.txt'))
try:
etree.ElementTree(etree.Element('test')).write(dummyfile)
except:
# folder does not exist yet
answer = False
else:
# Folder exists. Delete file again.
xbmcvfs.delete(dummyfile)
answer = True
return answer
def IntFromStr(string):
"""
Returns an int from string or the int 0 if something happened
@ -362,24 +360,14 @@ def reset():
line1=language(39602)):
log.info("Resetting all cached artwork.")
# Remove all existing textures first
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
if os_path.supports_unicode_filenames:
xbmcvfs.delete(os_path.join(
path + tryDecode(dir),
tryDecode(file)))
else:
xbmcvfs.delete(os_path.join(
tryEncode(path) + dir,
file))
path = xbmc.translatePath("special://thumbnails/")
if exists(path):
rmtree(path, ignore_errors=True)
# remove all existing data from texture DB
connection = kodiSQL('texture')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ("table", ))
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
@ -398,10 +386,10 @@ def reset():
line1=language(39603)):
# Delete the settings
addon = xbmcaddon.Addon()
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
addondir = xbmc.translatePath(addon.getAddonInfo('profile'))
dataPath = "%ssettings.xml" % addondir
log.info("Deleting: settings.xml")
xbmcvfs.delete(tryEncode(dataPath))
remove(dataPath)
# Kodi will now restart to apply the changes.
dialog('ok',
@ -480,21 +468,24 @@ def normalize_string(text):
return text
def indent(elem, level=0):
# Prettify xml trees
"""
Prettifies xml trees. Pass the etree root in
"""
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
elem.tail = i
for elem in elem:
indent(elem, level+1)
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
elem.tail = i
def guisettingsXML():
@ -548,9 +539,11 @@ def __setSubElement(element, subelement):
return answ
def get_advancessettings_xml_setting(node_list):
def advancedsettings_xml(node_list, new_value=None, attrib=None,
force_create=False):
"""
Returns the etree element for nodelist (if it exists) and None if not set
Returns the etree element for nodelist (if it exists) and the tree. None if
not set
node_list is a list of node names starting from the outside, ignoring the
outter advancedsettings. Example nodelist=['video', 'busydialogdelayms']
@ -558,7 +551,7 @@ def get_advancessettings_xml_setting(node_list):
<busydialogdelayms>750</busydialogdelayms>
Example xml:
for the following example xml:
<?xml version="1.0" encoding="UTF-8" ?>
<advancedsettings>
@ -566,60 +559,61 @@ def get_advancessettings_xml_setting(node_list):
<busydialogdelayms>750</busydialogdelayms>
</video>
</advancedsettings>
If new_value is set, '750' will be replaced accordingly, returning the new
etree Element. Advancedsettings might be generated if it did not exist
already
If the dict attrib is set, the Element's attributs will be appended
accordingly
force_create=True will forcibly create the key even if no value is provided
"""
path = tryDecode(xbmc.translatePath("special://profile/"))
path = '%sadvancedsettings.xml' % KODI_PROFILE
try:
xmlparse = etree.parse("%sadvancedsettings.xml" % path)
except:
log.debug('Could not parse advancedsettings.xml, returning None')
return
root = xmlparse.getroot()
tree = etree.parse(path)
except IOError:
# Document is blank or missing
if new_value is None and attrib is None and force_create is False:
log.debug('Could not parse advancedsettings.xml, returning None')
return
# Create topmost xml entry
tree = etree.ElementTree(element=etree.Element('advancedsettings'))
root = tree.getroot()
element = root
# Reading values
if new_value is None and attrib is None and force_create is False:
for node in node_list:
element = element.find(node)
if element is None:
break
return element, tree
# Setting new values. Get correct element first
for node in node_list:
root = root.find(node)
if root is None:
break
return root
element = __setSubElement(element, node)
# Write new values
element.text = new_value or ''
if attrib is not None:
for key, attribute in attrib.iteritems():
element.set(key, attribute)
# Indent and make readable
indent(root)
# Safe the changed xml
tree.write(path)
return element, tree
def advancedSettingsXML():
def advancedsettings_tweaks():
"""
Kodi tweaks
Changes advancedsettings.xml, musiclibrary:
backgroundupdate set to "true"
Overrides guisettings.xml in Kodi userdata folder:
updateonstartup : set to "false"
usetags : set to "false"
findremotethumbs : set to "false"
"""
path = tryDecode(xbmc.translatePath("special://profile/"))
xmlpath = "%sadvancedsettings.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except:
# Document is blank or missing
root = etree.Element('advancedsettings')
else:
root = xmlparse.getroot()
music = __setSubElement(root, 'musiclibrary')
__setXMLTag(music, 'backgroundupdate', "true")
# __setXMLTag(music, 'updateonstartup', "false")
# Subtag 'musicfiles'
# music = __setSubElement(root, 'musicfiles')
# __setXMLTag(music, 'usetags', "false")
# __setXMLTag(music, 'findremotethumbs', "false")
# Prettify and write to file
try:
indent(root)
except:
pass
etree.ElementTree(root).write(xmlpath)
advancedsettings_xml(['musiclibrary', 'backgroundupdate'],
new_value='true')
def sourcesXML():
@ -664,12 +658,13 @@ def sourcesXML():
def passwordsXML():
# To add network credentials
path = tryDecode(xbmc.translatePath("special://userdata/"))
path = xbmc.translatePath("special://userdata/")
xmlpath = "%spasswords.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
except:
# Document is blank or missing
root = etree.Element('passwords')
skipFind = True
else:
@ -725,18 +720,19 @@ def passwordsXML():
server = dialog.input("Enter the server name or IP address")
if not server:
return
server = quote_plus(server)
# Network username
user = dialog.input("Enter the network username")
if not user:
return
user = quote_plus(user)
# Network password
password = dialog.input("Enter the network password",
'', # Default input
xbmcgui.INPUT_ALPHANUM,
xbmcgui.ALPHANUM_HIDE_INPUT)
# Need to url-encode the password
from urllib import quote_plus
password = quote_plus(password)
# Add elements. Annoying etree bug where findall hangs forever
if skipFind is False:
@ -753,8 +749,6 @@ def passwordsXML():
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
topath = "smb://%s:%s@%s/" % (user, password, server)
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
# Force Kodi to see the credentials without restarting
xbmcvfs.exists(topath)
# Add credentials
settings('networkCreds', value="%s" % server)
@ -762,7 +756,8 @@ def passwordsXML():
# Prettify and write to file
try:
indent(root)
except: pass
except:
pass
etree.ElementTree(root).write(xmlpath)
# dialog.notification(
@ -776,7 +771,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
"""
Feed with tagname as unicode
"""
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
path = xbmc.translatePath("special://profile/playlists/video/")
if viewtype == "mixed":
plname = "%s - %s" % (tagname, mediatype)
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
@ -785,20 +780,20 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
xsppath = "%sPlex %s.xsp" % (path, viewid)
# Create the playlist directory
if not xbmcvfs.exists(tryEncode(path)):
if not exists(path):
log.info("Creating directory: %s" % path)
xbmcvfs.mkdirs(tryEncode(path))
makedirs(path)
# Only add the playlist if it doesn't already exists
if xbmcvfs.exists(tryEncode(xsppath)):
if exists(xsppath):
log.info('Path %s does exist' % xsppath)
if delete:
xbmcvfs.delete(tryEncode(xsppath))
remove(xsppath)
log.info("Successfully removed playlist: %s." % tagname)
return
# Using write process since there's no guarantee the xml declaration works with etree
# Using write process since there's no guarantee the xml declaration works
# with etree
itemtypes = {
'homevideos': 'movies',
'movie': 'movies',
@ -806,51 +801,39 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
}
log.info("Writing playlist file to: %s" % xsppath)
try:
f = xbmcvfs.File(tryEncode(xsppath), 'wb')
except:
with open(xsppath, 'wb'):
tryEncode(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
'<name>Plex %s</name>\n\t'
'<match>all</match>\n\t'
'<rule field="tag" operator="is">\n\t\t'
'<value>%s</value>\n\t'
'</rule>\n'
'</smartplaylist>\n'
% (itemtypes.get(mediatype, mediatype), plname, tagname))
except Exception as e:
log.error("Failed to create playlist: %s" % xsppath)
log.error(e)
return
else:
f.write(tryEncode(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
'<name>Plex %s</name>\n\t'
'<match>all</match>\n\t'
'<rule field="tag" operator="is">\n\t\t'
'<value>%s</value>\n\t'
'</rule>\n'
'</smartplaylist>\n'
% (itemtypes.get(mediatype, mediatype), plname, tagname)))
f.close()
log.info("Successfully added playlist: %s" % tagname)
def deletePlaylists():
# Clean up the playlists
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
dirs, files = xbmcvfs.listdir(tryEncode(path))
for file in files:
if tryDecode(file).startswith('Plex'):
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
path = xbmc.translatePath("special://profile/playlists/video/")
for root, _, files in walk(path):
for file in files:
if file.startswith('Plex'):
remove(join(root, file))
def deleteNodes():
# Clean up video nodes
import shutil
path = tryDecode(xbmc.translatePath("special://profile/library/video/"))
dirs, files = xbmcvfs.listdir(tryEncode(path))
for dir in dirs:
if tryDecode(dir).startswith('Plex'):
try:
shutil.rmtree("%s%s" % (path, tryDecode(dir)))
except:
log.error("Failed to delete directory: %s" % tryDecode(dir))
for file in files:
if tryDecode(file).startswith('plex'):
try:
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
except:
log.error("Failed to file: %s" % tryDecode(file))
path = xbmc.translatePath("special://profile/library/video/")
for root, dirs, _ in walk(path):
for directory in dirs:
if directory.startswith('Plex-'):
rmtree(join(root, directory))
break
###############################################################################
@ -891,7 +874,7 @@ def LogTime(func):
result = func(*args, **kwargs)
elapsedtotal = datetime.now() - starttotal
log.info('It took %s to run the function %s'
% (elapsedtotal, func.__name__))
% (elapsedtotal, func.__name__))
return result
return wrapper

View file

@ -2,6 +2,8 @@
import xbmc
from xbmcaddon import Addon
# Paths are in string, not unicode!
def tryDecode(string, encoding='utf-8'):
"""
@ -27,7 +29,7 @@ ADDON_VERSION = _ADDON.getAddonInfo('version')
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile"))
KODI_PROFILE = xbmc.translatePath("special://profile")
if xbmc.getCondVisibility('system.platform.osx'):
PLATFORM = "MacOSX"
@ -66,10 +68,10 @@ _DB_VIDEO_VERSION = {
15: 93, # Isengard
16: 99, # Jarvis
17: 107, # Krypton
18: 107 # Leia
18: 108 # Leia
}
DB_VIDEO_PATH = tryDecode(xbmc.translatePath(
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]))
DB_VIDEO_PATH = xbmc.translatePath(
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION])
_DB_MUSIC_VERSION = {
13: 46, # Gotham
@ -77,10 +79,10 @@ _DB_MUSIC_VERSION = {
15: 52, # Isengard
16: 56, # Jarvis
17: 60, # Krypton
18: 60 # Leia
18: 62 # Leia
}
DB_MUSIC_PATH = tryDecode(xbmc.translatePath(
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]))
DB_MUSIC_PATH = xbmc.translatePath(
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION])
_DB_TEXTURE_VERSION = {
13: 13, # Gotham
@ -90,13 +92,13 @@ _DB_TEXTURE_VERSION = {
17: 13, # Krypton
18: 13 # Leia
}
DB_TEXTURE_PATH = tryDecode(xbmc.translatePath(
"special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION]))
DB_TEXTURE_PATH = xbmc.translatePath(
"special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION])
DB_PLEX_PATH = tryDecode(xbmc.translatePath("special://database/plex.db"))
DB_PLEX_PATH = xbmc.translatePath("special://database/plex.db")
EXTERNAL_SUBTITLE_TEMP_PATH = tryDecode(xbmc.translatePath(
"special://profile/addon_data/%s/temp/" % ADDON_ID))
EXTERNAL_SUBTITLE_TEMP_PATH = xbmc.translatePath(
"special://profile/addon_data/%s/temp/" % ADDON_ID)
# Multiply Plex time by this factor to receive Kodi time

View file

@ -1,16 +1,15 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
import shutil
from shutil import copytree
import xml.etree.ElementTree as etree
import xbmc
import xbmcvfs
from os import remove, makedirs, listdir
from os.path import exists, isfile, join
from utils import window, settings, language as lang, IfExists, tryDecode, \
tryEncode, indent, normalize_nodes
from utils import window, settings, language as lang, tryEncode, indent, \
normalize_nodes
import variables as v
###############################################################################
@ -18,6 +17,7 @@ import variables as v
log = logging.getLogger("PLEX."+__name__)
###############################################################################
# Paths are strings, NOT unicode!
class VideoNodes(object):
@ -61,36 +61,30 @@ class VideoNodes(object):
else:
dirname = viewid
path = tryDecode(xbmc.translatePath(
"special://profile/library/video/"))
nodepath = tryDecode(xbmc.translatePath(
"special://profile/library/video/Plex-%s/" % dirname))
# Returns strings
path = xbmc.translatePath("special://profile/library/video/")
nodepath = xbmc.translatePath(
"special://profile/library/video/Plex-%s/" % dirname)
if delete:
dirs, files = xbmcvfs.listdir(tryEncode(nodepath))
files = [f for f in listdir(nodepath) if isfile(join(nodepath, f))]
for file in files:
xbmcvfs.delete(tryEncode(
(nodepath + tryDecode(file))))
remove(nodepath + file)
log.info("Sucessfully removed videonode: %s." % tagname)
return
# Verify the video directory
# KODI BUG
# Kodi caches the result of exists for directories
# so try creating a file
if IfExists(path) is False:
shutil.copytree(
src=tryDecode(xbmc.translatePath(
"special://xbmc/system/library/video")),
dst=tryDecode(xbmc.translatePath(
"special://profile/library/video")))
if exists(path) is False:
copytree(
src=xbmc.translatePath("special://xbmc/system/library/video"),
dst=xbmc.translatePath("special://profile/library/video"))
# Create the node directory
if mediatype != "photos":
if IfExists(nodepath) is False:
if exists(nodepath) is False:
# folder does not exist yet
log.debug('Creating folder %s' % nodepath)
xbmcvfs.mkdirs(tryEncode(nodepath))
makedirs(nodepath)
# Create index entry
nodeXML = "%sindex.xml" % nodepath
@ -103,23 +97,29 @@ class VideoNodes(object):
if mediatype == "photos":
path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid)
window('Plex.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)
root = self.commonRoot(order=0,
label=specialtag,
tagname=tagname,
roottype=0)
else:
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
root = self.commonRoot(order=0,
label=tagname,
tagname=tagname,
roottype=0)
try:
indent(root)
except: pass
except:
pass
etree.ElementTree(root).write(nodeXML)
nodetypes = {
'1': "all",
'2': "recent",
'3': "recentepisodes",
@ -255,7 +255,7 @@ class VideoNodes(object):
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
else:
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
if mediatype == "photos":
windowpath = "ActivateWindow(Pictures,%s,return)" % path
else:
@ -264,7 +264,7 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Videos,%s,return)" % path
else:
windowpath = "ActivateWindow(Video,%s,return)" % path
if nodetype == "all":
if viewtype == "mixed":
@ -288,8 +288,8 @@ class VideoNodes(object):
# to be created.
# To do: add our photos nodes to kodi picture sources somehow
continue
if xbmcvfs.exists(tryEncode(nodeXML)):
if exists(nodeXML):
# Don't recreate xml if already exists
continue
@ -370,15 +370,14 @@ class VideoNodes(object):
try:
indent(root)
except: pass
except:
pass
etree.ElementTree(root).write(nodeXML)
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
tagname = tryEncode(tagname)
cleantagname = normalize_nodes(tagname)
nodepath = tryDecode(xbmc.translatePath(
"special://profile/library/video/"))
nodepath = xbmc.translatePath("special://profile/library/video/")
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
path = "library://video/plex_%s.xml" % cleantagname
if v.KODIVERSION >= 17:
@ -388,17 +387,13 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Video,%s,return)" % path
# Create the video node directory
if not xbmcvfs.exists(nodepath):
if not exists(nodepath):
# We need to copy over the default items
shutil.copytree(
src=tryDecode(xbmc.translatePath(
"special://xbmc/system/library/video")),
dst=tryDecode(xbmc.translatePath(
"special://profile/library/video")))
xbmcvfs.exists(path)
copytree(
src=xbmc.translatePath("special://xbmc/system/library/video"),
dst=xbmc.translatePath("special://profile/library/video"))
labels = {
'Favorite movies': 30180,
'Favorite tvshows': 30181,
'channels': 30173
@ -410,12 +405,15 @@ class VideoNodes(object):
window('%s.content' % embynode, value=path)
window('%s.type' % embynode, value=itemtype)
if xbmcvfs.exists(nodeXML):
if exists(nodeXML):
# Don't recreate xml if already exists
return
if itemtype == "channels":
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
root = self.commonRoot(order=1,
label=label,
tagname=tagname,
roottype=2)
etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels"
else:
root = self.commonRoot(order=1, label=label, tagname=tagname)
@ -425,7 +423,8 @@ class VideoNodes(object):
try:
indent(root)
except: pass
except:
pass
etree.ElementTree(root).write(nodeXML)
def clearProperties(self):
@ -433,7 +432,6 @@ class VideoNodes(object):
log.info("Clearing nodes properties.")
plexprops = window('Plex.nodes.total')
propnames = [
"index","path","title","content",
"inprogress.content","inprogress.title",
"inprogress.content","inprogress.path",

View file

@ -30,7 +30,7 @@ sys_path.append(_base_resource)
###############################################################################
from utils import settings, window, language as lang, dialog, tryDecode
from utils import settings, window, language as lang, dialog, tryEncode
from userclient import UserClient
import initialsetup
from kodimonitor import KodiMonitor
@ -164,12 +164,12 @@ class Service():
counter = 0
while not monitor.abortRequested():
if tryDecode(window('plex_kodiProfile')) != kodiProfile:
if tryEncode(window('plex_kodiProfile')) != kodiProfile:
# Profile change happened, terminate this thread and others
log.warn("Kodi profile was: %s and changed to: %s. "
"Terminating old PlexKodiConnect thread."
% (kodiProfile,
tryDecode(window('plex_kodiProfile'))))
tryEncode(window('plex_kodiProfile'))))
break
# Before proceeding, need to make sure: