Merge branch 'develop' into translations
This commit is contained in:
commit
fcfb18226b
22 changed files with 458 additions and 384 deletions
|
@ -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)
|
[![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)
|
[![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)
|
[![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**:
|
2. **Compatibility**:
|
||||||
* PKC is currently not compatible with Kodi's Video Extras plugin. **Deactivate Video Extras** if trailers/movies start randomly playing.
|
* 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.
|
* 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
|
### 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.
|
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
|
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)
|
- 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:* 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:*
|
*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)
|
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)
|
||||||
|
|
19
addon.xml
19
addon.xml
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?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>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.3.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="nl_NL">Gebruik op eigen risico</disclaimer>
|
||||||
<disclaimer lang="zh_TW">使用風險由您自己承擔</disclaimer>
|
<disclaimer lang="zh_TW">使用風險由您自己承擔</disclaimer>
|
||||||
<disclaimer lang="es_ES">Usar a su propio riesgo</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
|
- Big transcoding overhaul
|
||||||
- Fix for not detecting external subtitle language
|
- Fix for not detecting external subtitle language
|
||||||
- Change Plex transcoding profile to Android
|
- Change Plex transcoding profile to Android
|
||||||
|
|
|
@ -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)
|
version 1.7.9 (beta only)
|
||||||
- Big transcoding overhaul
|
- Big transcoding overhaul
|
||||||
- Fix for not detecting external subtitle language
|
- Fix for not detecting external subtitle language
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
files:
|
|
||||||
- source: /resources/language/English/*.*
|
|
||||||
translation: /resources/language/%language%/%original_file_name%
|
|
||||||
translate_attributes: '0'
|
|
||||||
content_segmentation: '0'
|
|
|
@ -1894,3 +1894,8 @@ msgstr ""
|
||||||
msgctxt "#39710"
|
msgctxt "#39710"
|
||||||
msgid "burn-in"
|
msgid "burn-in"
|
||||||
msgstr ""
|
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 ""
|
||||||
|
|
|
@ -39,11 +39,11 @@ import xml.etree.ElementTree as etree
|
||||||
from re import compile as re_compile, sub
|
from re import compile as re_compile, sub
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from urllib import urlencode, quote_plus, unquote
|
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
|
import xbmcgui
|
||||||
from xbmc import sleep, executebuiltin
|
from xbmc import sleep, executebuiltin
|
||||||
from xbmcvfs import exists, mkdirs
|
|
||||||
|
|
||||||
import clientinfo as client
|
import clientinfo as client
|
||||||
from downloadutils import DownloadUtils
|
from downloadutils import DownloadUtils
|
||||||
|
@ -2113,11 +2113,14 @@ class API():
|
||||||
if fanarttvimage not in data:
|
if fanarttvimage not in data:
|
||||||
continue
|
continue
|
||||||
for entry in data[fanarttvimage]:
|
for entry in data[fanarttvimage]:
|
||||||
if fanartcount < maxfanarts:
|
if entry.get("url") is None:
|
||||||
if exists(entry.get("url")):
|
continue
|
||||||
allartworks['Backdrop'].append(
|
if fanartcount > maxfanarts:
|
||||||
entry.get("url", "").replace(' ', '%20'))
|
break
|
||||||
fanartcount += 1
|
if exists(tryEncode(entry['url'])):
|
||||||
|
allartworks['Backdrop'].append(
|
||||||
|
entry['url'].replace(' ', '%20'))
|
||||||
|
fanartcount += 1
|
||||||
return allartworks
|
return allartworks
|
||||||
|
|
||||||
def getSetArtwork(self, parentInfo=False):
|
def getSetArtwork(self, parentInfo=False):
|
||||||
|
@ -2184,7 +2187,7 @@ class API():
|
||||||
# Get additional info (filename / languages)
|
# Get additional info (filename / languages)
|
||||||
filename = None
|
filename = None
|
||||||
if 'file' in entry[0].attrib:
|
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 of audio streams
|
||||||
languages = []
|
languages = []
|
||||||
for stream in entry[0]:
|
for stream in entry[0]:
|
||||||
|
@ -2339,8 +2342,8 @@ class API():
|
||||||
Returns the path to the downloaded subtitle or None
|
Returns the path to the downloaded subtitle or None
|
||||||
"""
|
"""
|
||||||
if not exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
|
if not exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
|
||||||
mkdirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
|
makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
|
||||||
path = os_path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
|
path = join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
|
||||||
r = DownloadUtils().downloadUrl(url, return_response=True)
|
r = DownloadUtils().downloadUrl(url, return_response=True)
|
||||||
try:
|
try:
|
||||||
r.status_code
|
r.status_code
|
||||||
|
@ -2506,7 +2509,8 @@ class API():
|
||||||
listItem.addStreamInfo(
|
listItem.addStreamInfo(
|
||||||
"video", {'duration': self.getRuntime()[1]})
|
"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
|
Returns a valid path for Kodi, e.g. with '\' substituted to '\\' in
|
||||||
Unicode. Returns None if this is not possible
|
Unicode. Returns None if this is not possible
|
||||||
|
@ -2516,6 +2520,7 @@ class API():
|
||||||
forceCheck : Will always try to check validity of path
|
forceCheck : Will always try to check validity of path
|
||||||
Will also skip confirmation dialog if path not found
|
Will also skip confirmation dialog if path not found
|
||||||
folder : Set to True if path is a folder
|
folder : Set to True if path is a folder
|
||||||
|
omitCheck : Will entirely omit validity check if True
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
return None
|
return None
|
||||||
|
@ -2529,7 +2534,8 @@ class API():
|
||||||
elif window('replaceSMB') == 'true':
|
elif window('replaceSMB') == 'true':
|
||||||
if path.startswith('\\\\'):
|
if path.startswith('\\\\'):
|
||||||
path = 'smb:' + path.replace('\\', '/')
|
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
|
return path
|
||||||
|
|
||||||
# exist() needs a / or \ at the end to work for directories
|
# exist() needs a / or \ at the end to work for directories
|
||||||
|
|
|
@ -258,43 +258,12 @@ def GetPlexOnDeck(viewId):
|
||||||
return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId)
|
return DownloadChunks("{server}/library/sections/%s/onDeck?" % viewId)
|
||||||
|
|
||||||
|
|
||||||
def GetPlexCollections(mediatype):
|
def get_plex_sections():
|
||||||
"""
|
"""
|
||||||
Input:
|
Returns all Plex sections (libraries) of the PMS as an etree xml
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
collections = []
|
return downloadutils.DownloadUtils().downloadUrl(
|
||||||
url = "{server}/library/sections"
|
'{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
|
|
||||||
|
|
||||||
|
|
||||||
def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
||||||
|
|
|
@ -4,16 +4,16 @@
|
||||||
import logging
|
import logging
|
||||||
from json import dumps, loads
|
from json import dumps, loads
|
||||||
import requests
|
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 urllib import quote_plus, unquote
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
|
|
||||||
from xbmc import executeJSONRPC, sleep, translatePath
|
from xbmc import executeJSONRPC, sleep, translatePath
|
||||||
from xbmcvfs import listdir, delete
|
|
||||||
|
|
||||||
from utils import window, settings, language as lang, kodiSQL, tryEncode, \
|
from utils import window, settings, language as lang, kodiSQL, tryEncode, \
|
||||||
tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop, dialog
|
ThreadMethods, ThreadMethodsAdditionalStop, dialog
|
||||||
|
|
||||||
# Disable annoying requests warnings
|
# Disable annoying requests warnings
|
||||||
import requests.packages.urllib3
|
import requests.packages.urllib3
|
||||||
|
@ -228,30 +228,21 @@ class Artwork():
|
||||||
if dialog('yesno', "Image Texture Cache", lang(39251)):
|
if dialog('yesno', "Image Texture Cache", lang(39251)):
|
||||||
log.info("Resetting all cache data first")
|
log.info("Resetting all cache data first")
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = tryDecode(translatePath("special://thumbnails/"))
|
path = translatePath("special://thumbnails/")
|
||||||
if IfExists(path):
|
if exists(path):
|
||||||
allDirs, allFiles = listdir(path)
|
rmtree(path, ignore_errors=True)
|
||||||
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))
|
|
||||||
|
|
||||||
# remove all existing data from texture DB
|
# remove all existing data from texture DB
|
||||||
connection = kodiSQL('texture')
|
connection = kodiSQL('texture')
|
||||||
cursor = connection.cursor()
|
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()
|
rows = cursor.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
tableName = row[0]
|
tableName = row[0]
|
||||||
if tableName != "version":
|
if tableName != "version":
|
||||||
cursor.execute("DELETE FROM " + tableName)
|
query = "DELETE FROM ?"
|
||||||
|
cursor.execute(query, (tableName,))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
@ -259,7 +250,8 @@ class Artwork():
|
||||||
connection = kodiSQL('video')
|
connection = kodiSQL('video')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
# dont include actors
|
# 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()
|
result = cursor.fetchall()
|
||||||
total = len(result)
|
total = len(result)
|
||||||
log.info("Image cache sync about to process %s video images" % total)
|
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):
|
def addArtwork(self, artwork, kodiId, mediaType, cursor):
|
||||||
# Kodi conversion table
|
# Kodi conversion table
|
||||||
kodiart = {
|
kodiart = {
|
||||||
|
|
||||||
'Primary': ["thumb", "poster"],
|
'Primary': ["thumb", "poster"],
|
||||||
'Banner': "banner",
|
'Banner': "banner",
|
||||||
'Logo': "clearlogo",
|
'Logo': "clearlogo",
|
||||||
|
@ -307,7 +298,6 @@ class Artwork():
|
||||||
backdropsNumber = len(backdrops)
|
backdropsNumber = len(backdrops)
|
||||||
|
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"SELECT url",
|
"SELECT url",
|
||||||
"FROM art",
|
"FROM art",
|
||||||
"WHERE media_id = ?",
|
"WHERE media_id = ?",
|
||||||
|
@ -320,7 +310,6 @@ class Artwork():
|
||||||
if len(rows) > backdropsNumber:
|
if len(rows) > backdropsNumber:
|
||||||
# More backdrops in database. Delete extra fanart.
|
# More backdrops in database. Delete extra fanart.
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
"DELETE FROM art",
|
"DELETE FROM art",
|
||||||
"WHERE media_id = ?",
|
"WHERE media_id = ?",
|
||||||
"AND media_type = ?",
|
"AND media_type = ?",
|
||||||
|
@ -339,7 +328,7 @@ class Artwork():
|
||||||
cursor=cursor)
|
cursor=cursor)
|
||||||
|
|
||||||
if backdropsNumber > 1:
|
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
|
index += 1
|
||||||
except TypeError:
|
except TypeError:
|
||||||
index = 1
|
index = 1
|
||||||
|
@ -438,14 +427,10 @@ class Artwork():
|
||||||
log.info("Could not find cached url.")
|
log.info("Could not find cached url.")
|
||||||
else:
|
else:
|
||||||
# Delete thumbnail as well as the entry
|
# Delete thumbnail as well as the entry
|
||||||
thumbnails = tryDecode(
|
path = translatePath("special://thumbnails/%s" % cachedurl)
|
||||||
translatePath("special://thumbnails/%s" % cachedurl))
|
log.debug("Deleting cached thumbnail: %s" % path)
|
||||||
log.debug("Deleting cached thumbnail: %s" % thumbnails)
|
if exists(path):
|
||||||
try:
|
rmtree(path, ignore_errors=True)
|
||||||
delete(thumbnails)
|
|
||||||
except Exception as e:
|
|
||||||
log.error('Could not delete cached artwork %s. Error: %s'
|
|
||||||
% (thumbnails, e))
|
|
||||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
from re import compile as re_compile
|
|
||||||
|
|
||||||
from xbmc import Player
|
from xbmc import Player
|
||||||
|
|
||||||
|
@ -13,8 +12,6 @@ from PlexFunctions import GetPlexKeyNumber
|
||||||
|
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
REGEX_PLAYQUEUES = re_compile(r'''/playQueues/(\d+)$''')
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
###############################################################################
|
###############################################################################
|
||||||
import logging
|
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 sys import argv
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
|
||||||
|
@ -9,8 +11,8 @@ import xbmcplugin
|
||||||
from xbmc import sleep, executebuiltin, translatePath
|
from xbmc import sleep, executebuiltin, translatePath
|
||||||
from xbmcgui import ListItem
|
from xbmcgui import ListItem
|
||||||
|
|
||||||
from utils import window, settings, language as lang, dialog, tryDecode,\
|
from utils import window, settings, language as lang, dialog, tryEncode, \
|
||||||
tryEncode, CatchExceptions, JSONRPC
|
CatchExceptions, JSONRPC
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
|
||||||
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
|
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
|
||||||
|
@ -486,6 +488,7 @@ def getVideoFiles(plexId, params):
|
||||||
except:
|
except:
|
||||||
log.error('Could not get file path for item %s' % plexId)
|
log.error('Could not get file path for item %s' % plexId)
|
||||||
return xbmcplugin.endOfDirectory(HANDLE)
|
return xbmcplugin.endOfDirectory(HANDLE)
|
||||||
|
path = tryEncode(path)
|
||||||
# Assign network protocol
|
# Assign network protocol
|
||||||
if path.startswith('\\\\'):
|
if path.startswith('\\\\'):
|
||||||
path = path.replace('\\\\', 'smb://')
|
path = path.replace('\\\\', 'smb://')
|
||||||
|
@ -493,28 +496,25 @@ def getVideoFiles(plexId, params):
|
||||||
# Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv'
|
# Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv'
|
||||||
elif '\\' in path:
|
elif '\\' in path:
|
||||||
path = path.replace('\\', '\\\\')
|
path = path.replace('\\', '\\\\')
|
||||||
# Directory only, get rid of filename (!! exists() needs / or \ at end)
|
# Directory only, get rid of filename
|
||||||
path = path.replace(os_path.basename(path), '')
|
path = path.replace(basename(path), '')
|
||||||
# Only proceed if we can access this folder
|
if exists(path):
|
||||||
import xbmcvfs
|
for root, dirs, files in walk(path):
|
||||||
if xbmcvfs.exists(path):
|
for directory in dirs:
|
||||||
# Careful, returns encoded strings!
|
item_path = join(root, directory)
|
||||||
dirs, files = xbmcvfs.listdir(path)
|
li = ListItem(item_path, path=item_path)
|
||||||
for file in files:
|
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||||
file = path + tryDecode(file)
|
url=item_path,
|
||||||
li = ListItem(file, path=file)
|
listitem=li,
|
||||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
isFolder=True)
|
||||||
url=tryEncode(file),
|
for file in files:
|
||||||
listitem=li)
|
item_path = join(root, file)
|
||||||
for dir in dirs:
|
li = ListItem(item_path, path=item_path)
|
||||||
dir = path + tryDecode(dir)
|
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||||
li = ListItem(dir, path=dir)
|
url=file,
|
||||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
listitem=li)
|
||||||
url=tryEncode(dir),
|
|
||||||
listitem=li,
|
|
||||||
isFolder=True)
|
|
||||||
else:
|
else:
|
||||||
log.warn('Kodi cannot access folder %s' % path)
|
log.error('Kodi cannot access folder %s' % path)
|
||||||
xbmcplugin.endOfDirectory(HANDLE)
|
xbmcplugin.endOfDirectory(HANDLE)
|
||||||
|
|
||||||
|
|
||||||
|
@ -525,7 +525,6 @@ def getExtraFanArt(plexid, plexPath):
|
||||||
will be called by skinhelper script to get the extrafanart
|
will be called by skinhelper script to get the extrafanart
|
||||||
for tvshows we get the plexid just from the path
|
for tvshows we get the plexid just from the path
|
||||||
"""
|
"""
|
||||||
import xbmcvfs
|
|
||||||
log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath))
|
log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath))
|
||||||
if not plexid:
|
if not plexid:
|
||||||
if "plugin.video.plexkodiconnect" in plexPath:
|
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
|
# We need to store the images locally for this to work
|
||||||
# because of the caching system in xbmc
|
# because of the caching system in xbmc
|
||||||
fanartDir = tryDecode(translatePath(
|
fanartDir = translatePath("special://thumbnails/plex/%s/" % plexid)
|
||||||
"special://thumbnails/plex/%s/" % plexid))
|
if not exists(fanartDir):
|
||||||
if not xbmcvfs.exists(fanartDir):
|
|
||||||
# Download the images to the cache directory
|
# Download the images to the cache directory
|
||||||
xbmcvfs.mkdirs(tryEncode(fanartDir))
|
makedirs(fanartDir)
|
||||||
xml = GetPlexMetadata(plexid)
|
xml = GetPlexMetadata(plexid)
|
||||||
if xml is None:
|
if xml is None:
|
||||||
log.error('Could not download metadata for %s' % plexid)
|
log.error('Could not download metadata for %s' % plexid)
|
||||||
|
@ -550,29 +548,23 @@ def getExtraFanArt(plexid, plexPath):
|
||||||
backdrops = api.getAllArtwork()['Backdrop']
|
backdrops = api.getAllArtwork()['Backdrop']
|
||||||
for count, backdrop in enumerate(backdrops):
|
for count, backdrop in enumerate(backdrops):
|
||||||
# Same ordering as in artwork
|
# Same ordering as in artwork
|
||||||
if os_path.supports_unicode_filenames:
|
fanartFile = join(fanartDir, "fanart%.3d.jpg" % count)
|
||||||
fanartFile = os_path.join(fanartDir,
|
|
||||||
"fanart%.3d.jpg" % count)
|
|
||||||
else:
|
|
||||||
fanartFile = os_path.join(
|
|
||||||
tryEncode(fanartDir),
|
|
||||||
tryEncode("fanart%.3d.jpg" % count))
|
|
||||||
li = ListItem("%.3d" % count, path=fanartFile)
|
li = ListItem("%.3d" % count, path=fanartFile)
|
||||||
xbmcplugin.addDirectoryItem(
|
xbmcplugin.addDirectoryItem(
|
||||||
handle=HANDLE,
|
handle=HANDLE,
|
||||||
url=fanartFile,
|
url=fanartFile,
|
||||||
listitem=li)
|
listitem=li)
|
||||||
xbmcvfs.copy(backdrop, fanartFile)
|
copyfile(backdrop, fanartFile)
|
||||||
else:
|
else:
|
||||||
log.info("Found cached backdrop.")
|
log.info("Found cached backdrop.")
|
||||||
# Use existing cached images
|
# Use existing cached images
|
||||||
dirs, files = xbmcvfs.listdir(fanartDir)
|
for root, dirs, files in walk(fanartDir):
|
||||||
for file in files:
|
for file in files:
|
||||||
fanartFile = os_path.join(fanartDir, tryDecode(file))
|
fanartFile = join(root, file)
|
||||||
li = ListItem(file, path=fanartFile)
|
li = ListItem(file, path=fanartFile)
|
||||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||||
url=fanartFile,
|
url=fanartFile,
|
||||||
listitem=li)
|
listitem=li)
|
||||||
xbmcplugin.endOfDirectory(HANDLE)
|
xbmcplugin.endOfDirectory(HANDLE)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
from utils import settings, window, language as lang, tryEncode, \
|
from utils import settings, window, language as lang, tryEncode, \
|
||||||
get_advancessettings_xml_setting
|
advancedsettings_xml
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from userclient import UserClient
|
from userclient import UserClient
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ class InitialSetup():
|
||||||
dialog = self.dialog
|
dialog = self.dialog
|
||||||
|
|
||||||
# Get current Kodi video cache setting
|
# Get current Kodi video cache setting
|
||||||
cache = get_advancessettings_xml_setting(['cache', 'memorysize'])
|
cache, _ = advancedsettings_xml(['cache', 'memorysize'])
|
||||||
if cache is not None:
|
if cache is not None:
|
||||||
cache = str(cache.text)
|
cache = str(cache.text)
|
||||||
else:
|
else:
|
||||||
|
@ -478,8 +478,8 @@ class InitialSetup():
|
||||||
log.debug("User opted to disable Plex music library.")
|
log.debug("User opted to disable Plex music library.")
|
||||||
settings('enableMusic', value="false")
|
settings('enableMusic', value="false")
|
||||||
else:
|
else:
|
||||||
from utils import advancedSettingsXML
|
from utils import advancedsettings_tweaks
|
||||||
advancedSettingsXML()
|
advancedsettings_tweaks()
|
||||||
|
|
||||||
# Download additional art from FanArtTV
|
# Download additional art from FanArtTV
|
||||||
if dialog.yesno(heading=lang(29999), line1=lang(39061)):
|
if dialog.yesno(heading=lang(29999), line1=lang(39061)):
|
||||||
|
|
|
@ -851,7 +851,6 @@ class Kodidb_Functions():
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
log.info('Did not find matching paths, abort')
|
log.info('Did not find matching paths, abort')
|
||||||
return
|
return
|
||||||
log.debug('Result: %s' % result)
|
|
||||||
# Kodi seems to make ONE temporary entry; we only want the earlier,
|
# Kodi seems to make ONE temporary entry; we only want the earlier,
|
||||||
# permanent one
|
# permanent one
|
||||||
if len(result) > 2:
|
if len(result) > 2:
|
||||||
|
@ -859,7 +858,6 @@ class Kodidb_Functions():
|
||||||
' paths, aborting')
|
' paths, aborting')
|
||||||
return
|
return
|
||||||
idFile = result[0]
|
idFile = result[0]
|
||||||
log.debug('idFile: %s' % idFile)
|
|
||||||
|
|
||||||
# Try movies first
|
# Try movies first
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import Queue
|
import Queue
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
from os.path import exists
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
from utils import window, settings, getUnixTimestamp, sourcesXML,\
|
from utils import window, settings, getUnixTimestamp, sourcesXML,\
|
||||||
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
|
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
|
||||||
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
|
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
|
||||||
advancedSettingsXML, tryDecode, deletePlaylists, deleteNodes, \
|
advancedsettings_tweaks, tryDecode, deletePlaylists, deleteNodes, \
|
||||||
ThreadMethodsAdditionalSuspend, create_actor_db_index, dialog
|
ThreadMethodsAdditionalSuspend, create_actor_db_index, dialog
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import itemtypes
|
import itemtypes
|
||||||
|
@ -25,12 +23,13 @@ import videonodes
|
||||||
import variables as v
|
import variables as v
|
||||||
|
|
||||||
from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \
|
from PlexFunctions import GetPlexMetadata, GetAllPlexLeaves, scrobble, \
|
||||||
GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus
|
GetPlexSectionResults, GetAllPlexChildren, GetPMSStatus, get_plex_sections
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
from library_sync.get_metadata import Threaded_Get_Metadata
|
from library_sync.get_metadata import Threaded_Get_Metadata
|
||||||
from library_sync.process_metadata import Threaded_Process_Metadata
|
from library_sync.process_metadata import Threaded_Process_Metadata
|
||||||
import library_sync.sync_info as sync_info
|
import library_sync.sync_info as sync_info
|
||||||
from library_sync.fanart import Process_Fanart_Thread
|
from library_sync.fanart import Process_Fanart_Thread
|
||||||
|
import music
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -73,6 +72,7 @@ class LibrarySync(Thread):
|
||||||
self.enableMusic = settings('enableMusic') == "true"
|
self.enableMusic = settings('enableMusic') == "true"
|
||||||
self.enableBackgroundSync = settings(
|
self.enableBackgroundSync = settings(
|
||||||
'enableBackgroundSync') == "true"
|
'enableBackgroundSync') == "true"
|
||||||
|
self.direct_paths = settings('useDirectPaths') == '1'
|
||||||
|
|
||||||
# Init for replacing paths
|
# Init for replacing paths
|
||||||
window('remapSMB', value=settings('remapSMB'))
|
window('remapSMB', value=settings('remapSMB'))
|
||||||
|
@ -128,8 +128,7 @@ class LibrarySync(Thread):
|
||||||
# change in lastViewedAt
|
# change in lastViewedAt
|
||||||
|
|
||||||
# Get all Plex libraries
|
# Get all Plex libraries
|
||||||
sections = downloadutils.DownloadUtils().downloadUrl(
|
sections = get_plex_sections()
|
||||||
"{server}/library/sections")
|
|
||||||
try:
|
try:
|
||||||
sections.attrib
|
sections.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -298,6 +297,15 @@ class LibrarySync(Thread):
|
||||||
}
|
}
|
||||||
if self.enableMusic:
|
if self.enableMusic:
|
||||||
process['music'] = self.PlexMusic
|
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
|
# Do the processing
|
||||||
for itemtype in process:
|
for itemtype in process:
|
||||||
|
@ -479,8 +487,7 @@ class LibrarySync(Thread):
|
||||||
vnodes = self.vnodes
|
vnodes = self.vnodes
|
||||||
|
|
||||||
# Get views
|
# Get views
|
||||||
sections = downloadutils.DownloadUtils().downloadUrl(
|
sections = get_plex_sections()
|
||||||
"{server}/library/sections")
|
|
||||||
try:
|
try:
|
||||||
sections.attrib
|
sections.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -1463,7 +1470,7 @@ class LibrarySync(Thread):
|
||||||
self.initializeDBs()
|
self.initializeDBs()
|
||||||
|
|
||||||
if self.enableMusic:
|
if self.enableMusic:
|
||||||
advancedSettingsXML()
|
advancedsettings_tweaks()
|
||||||
|
|
||||||
if settings('FanartTV') == 'true':
|
if settings('FanartTV') == 'true':
|
||||||
self.fanartthread.start()
|
self.fanartthread.start()
|
||||||
|
@ -1505,7 +1512,7 @@ class LibrarySync(Thread):
|
||||||
# Also runs when first installed
|
# Also runs when first installed
|
||||||
# Verify the video database can be found
|
# Verify the video database can be found
|
||||||
videoDb = v.DB_VIDEO_PATH
|
videoDb = v.DB_VIDEO_PATH
|
||||||
if not xbmcvfs.exists(videoDb):
|
if not exists(videoDb):
|
||||||
# Database does not exists
|
# Database does not exists
|
||||||
log.error("The current Kodi version is incompatible "
|
log.error("The current Kodi version is incompatible "
|
||||||
"to know which Kodi versions are supported.")
|
"to know which Kodi versions are supported.")
|
||||||
|
|
119
resources/lib/music.py
Normal file
119
resources/lib/music.py
Normal 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
|
|
@ -4,7 +4,7 @@ from urlparse import parse_qsl, urlsplit
|
||||||
|
|
||||||
import plexdb_functions as plexdb
|
import plexdb_functions as plexdb
|
||||||
from downloadutils import DownloadUtils as DU
|
from downloadutils import DownloadUtils as DU
|
||||||
from utils import JSONRPC, tryEncode
|
from utils import JSONRPC, tryEncode, escape_html
|
||||||
from PlexAPI import API
|
from PlexAPI import API
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -157,6 +157,8 @@ def playlist_item_from_xml(playlist, xml_video_element):
|
||||||
item.plex_id = api.getRatingKey()
|
item.plex_id = api.getRatingKey()
|
||||||
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
|
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
|
||||||
item.guid = xml_video_element.attrib.get('guid')
|
item.guid = xml_video_element.attrib.get('guid')
|
||||||
|
if item.guid is not None:
|
||||||
|
item.guid = escape_html(item.guid)
|
||||||
if item.plex_id:
|
if item.plex_id:
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
with plexdb.Get_Plex_DB() as plex_db:
|
||||||
db_element = plex_db.getItem_byId(item.plex_id)
|
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
|
# Get the guid for this item
|
||||||
for plex_item in xml:
|
for plex_item in xml:
|
||||||
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
|
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)
|
playlist.items.append(item)
|
||||||
if pos == len(playlist.items) - 1:
|
if pos == len(playlist.items) - 1:
|
||||||
# Item was added at the end
|
# Item was added at the end
|
||||||
|
|
|
@ -57,7 +57,7 @@ def plex_type(xbmc_type):
|
||||||
|
|
||||||
|
|
||||||
def getXMLHeader():
|
def getXMLHeader():
|
||||||
return '<?xml version="1.0" encoding="utf-8" ?>\n'
|
return '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
|
|
||||||
|
|
||||||
def getOKMsg():
|
def getOKMsg():
|
||||||
|
|
|
@ -55,19 +55,8 @@ class SubscriptionManager:
|
||||||
|
|
||||||
def msg(self, players):
|
def msg(self, players):
|
||||||
msg = getXMLHeader()
|
msg = getXMLHeader()
|
||||||
msg += '<MediaContainer commandID="INSERTCOMMANDID"'
|
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
|
||||||
if players:
|
msg += ' machineIdentifier="%s">' % window('plex_client_Id')
|
||||||
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 += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
|
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
|
||||||
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
|
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
|
||||||
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
||||||
|
@ -108,7 +97,6 @@ class SubscriptionManager:
|
||||||
if keyid:
|
if keyid:
|
||||||
self.lastkey = "/library/metadata/%s" % keyid
|
self.lastkey = "/library/metadata/%s" % keyid
|
||||||
self.ratingkey = keyid
|
self.ratingkey = keyid
|
||||||
ret += ' location="%s"' % self.mainlocation
|
|
||||||
ret += ' key="%s"' % self.lastkey
|
ret += ' key="%s"' % self.lastkey
|
||||||
ret += ' ratingKey="%s"' % self.ratingkey
|
ret += ' ratingKey="%s"' % self.ratingkey
|
||||||
serv = self.getServerByHost(self.server)
|
serv = self.getServerByHost(self.server)
|
||||||
|
@ -138,7 +126,7 @@ class SubscriptionManager:
|
||||||
ret += ' subtitleStreamID="-1"'
|
ret += ' subtitleStreamID="-1"'
|
||||||
ret += ' audioStreamID="-1"'
|
ret += ' audioStreamID="-1"'
|
||||||
|
|
||||||
ret += ' />'
|
ret += '/>'
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def updateCommandID(self, uuid, commandID):
|
def updateCommandID(self, uuid, commandID):
|
||||||
|
|
|
@ -3,14 +3,15 @@
|
||||||
###############################################################################
|
###############################################################################
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
from os.path import exists
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
from utils import window, settings, language as lang, ThreadMethods, \
|
from utils import window, settings, language as lang, ThreadMethods, \
|
||||||
tryDecode, ThreadMethodsAdditionalSuspend
|
ThreadMethodsAdditionalSuspend
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
@ -209,9 +210,8 @@ class UserClient(threading.Thread):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get /profile/addon_data
|
# Get /profile/addon_data
|
||||||
addondir = tryDecode(xbmc.translatePath(
|
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile'))
|
||||||
self.addon.getAddonInfo('profile')))
|
hasSettings = exists("%ssettings.xml" % addondir)
|
||||||
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
|
|
||||||
|
|
||||||
# If there's no settings.xml
|
# If there's no settings.xml
|
||||||
if not hasSettings:
|
if not hasSettings:
|
||||||
|
|
|
@ -13,15 +13,17 @@ from unicodedata import normalize
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from calendar import timegm
|
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 xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \
|
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
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def escape_html(string):
|
||||||
|
"""
|
||||||
|
Escapes the following:
|
||||||
|
< to <
|
||||||
|
> to >
|
||||||
|
& to &
|
||||||
|
"""
|
||||||
|
escapes = {
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'&': '&'
|
||||||
|
}
|
||||||
|
for key, value in escapes.iteritems():
|
||||||
|
string = string.replace(key, value)
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
def DateToKodi(stamp):
|
def DateToKodi(stamp):
|
||||||
"""
|
"""
|
||||||
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
|
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
|
||||||
|
@ -198,27 +217,6 @@ def DateToKodi(stamp):
|
||||||
return localdate
|
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):
|
def IntFromStr(string):
|
||||||
"""
|
"""
|
||||||
Returns an int from string or the int 0 if something happened
|
Returns an int from string or the int 0 if something happened
|
||||||
|
@ -362,24 +360,14 @@ def reset():
|
||||||
line1=language(39602)):
|
line1=language(39602)):
|
||||||
log.info("Resetting all cached artwork.")
|
log.info("Resetting all cached artwork.")
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
path = xbmc.translatePath("special://thumbnails/")
|
||||||
if xbmcvfs.exists(path):
|
if exists(path):
|
||||||
allDirs, allFiles = xbmcvfs.listdir(path)
|
rmtree(path, ignore_errors=True)
|
||||||
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))
|
|
||||||
# remove all existing data from texture DB
|
# remove all existing data from texture DB
|
||||||
connection = kodiSQL('texture')
|
connection = kodiSQL('texture')
|
||||||
cursor = connection.cursor()
|
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()
|
rows = cursor.fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
tableName = row[0]
|
tableName = row[0]
|
||||||
|
@ -398,10 +386,10 @@ def reset():
|
||||||
line1=language(39603)):
|
line1=language(39603)):
|
||||||
# Delete the settings
|
# Delete the settings
|
||||||
addon = xbmcaddon.Addon()
|
addon = xbmcaddon.Addon()
|
||||||
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
addondir = xbmc.translatePath(addon.getAddonInfo('profile'))
|
||||||
dataPath = "%ssettings.xml" % addondir
|
dataPath = "%ssettings.xml" % addondir
|
||||||
log.info("Deleting: settings.xml")
|
log.info("Deleting: settings.xml")
|
||||||
xbmcvfs.delete(tryEncode(dataPath))
|
remove(dataPath)
|
||||||
|
|
||||||
# Kodi will now restart to apply the changes.
|
# Kodi will now restart to apply the changes.
|
||||||
dialog('ok',
|
dialog('ok',
|
||||||
|
@ -480,21 +468,24 @@ def normalize_string(text):
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def indent(elem, level=0):
|
def indent(elem, level=0):
|
||||||
# Prettify xml trees
|
"""
|
||||||
|
Prettifies xml trees. Pass the etree root in
|
||||||
|
"""
|
||||||
i = "\n" + level*" "
|
i = "\n" + level*" "
|
||||||
if len(elem):
|
if len(elem):
|
||||||
if not elem.text or not elem.text.strip():
|
if not elem.text or not elem.text.strip():
|
||||||
elem.text = i + " "
|
elem.text = i + " "
|
||||||
if not elem.tail or not elem.tail.strip():
|
if not elem.tail or not elem.tail.strip():
|
||||||
elem.tail = i
|
elem.tail = i
|
||||||
for elem in elem:
|
for elem in elem:
|
||||||
indent(elem, level+1)
|
indent(elem, level+1)
|
||||||
if not elem.tail or not elem.tail.strip():
|
if not elem.tail or not elem.tail.strip():
|
||||||
elem.tail = i
|
elem.tail = i
|
||||||
else:
|
else:
|
||||||
if level and (not elem.tail or not elem.tail.strip()):
|
if level and (not elem.tail or not elem.tail.strip()):
|
||||||
elem.tail = i
|
elem.tail = i
|
||||||
|
|
||||||
|
|
||||||
def guisettingsXML():
|
def guisettingsXML():
|
||||||
|
@ -548,9 +539,11 @@ def __setSubElement(element, subelement):
|
||||||
return answ
|
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
|
node_list is a list of node names starting from the outside, ignoring the
|
||||||
outter advancedsettings. Example nodelist=['video', 'busydialogdelayms']
|
outter advancedsettings. Example nodelist=['video', 'busydialogdelayms']
|
||||||
|
@ -558,7 +551,7 @@ def get_advancessettings_xml_setting(node_list):
|
||||||
|
|
||||||
<busydialogdelayms>750</busydialogdelayms>
|
<busydialogdelayms>750</busydialogdelayms>
|
||||||
|
|
||||||
Example xml:
|
for the following example xml:
|
||||||
|
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<advancedsettings>
|
<advancedsettings>
|
||||||
|
@ -566,60 +559,61 @@ def get_advancessettings_xml_setting(node_list):
|
||||||
<busydialogdelayms>750</busydialogdelayms>
|
<busydialogdelayms>750</busydialogdelayms>
|
||||||
</video>
|
</video>
|
||||||
</advancedsettings>
|
</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:
|
try:
|
||||||
xmlparse = etree.parse("%sadvancedsettings.xml" % path)
|
tree = etree.parse(path)
|
||||||
except:
|
except IOError:
|
||||||
log.debug('Could not parse advancedsettings.xml, returning None')
|
# Document is blank or missing
|
||||||
return
|
if new_value is None and attrib is None and force_create is False:
|
||||||
root = xmlparse.getroot()
|
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:
|
for node in node_list:
|
||||||
root = root.find(node)
|
element = __setSubElement(element, node)
|
||||||
if root is None:
|
# Write new values
|
||||||
break
|
element.text = new_value or ''
|
||||||
return root
|
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
|
Kodi tweaks
|
||||||
|
|
||||||
Changes advancedsettings.xml, musiclibrary:
|
Changes advancedsettings.xml, musiclibrary:
|
||||||
backgroundupdate set to "true"
|
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/"))
|
advancedsettings_xml(['musiclibrary', 'backgroundupdate'],
|
||||||
xmlpath = "%sadvancedsettings.xml" % path
|
new_value='true')
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def sourcesXML():
|
def sourcesXML():
|
||||||
|
@ -664,12 +658,13 @@ def sourcesXML():
|
||||||
|
|
||||||
def passwordsXML():
|
def passwordsXML():
|
||||||
# To add network credentials
|
# To add network credentials
|
||||||
path = tryDecode(xbmc.translatePath("special://userdata/"))
|
path = xbmc.translatePath("special://userdata/")
|
||||||
xmlpath = "%spasswords.xml" % path
|
xmlpath = "%spasswords.xml" % path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
xmlparse = etree.parse(xmlpath)
|
xmlparse = etree.parse(xmlpath)
|
||||||
except: # Document is blank or missing
|
except:
|
||||||
|
# Document is blank or missing
|
||||||
root = etree.Element('passwords')
|
root = etree.Element('passwords')
|
||||||
skipFind = True
|
skipFind = True
|
||||||
else:
|
else:
|
||||||
|
@ -725,18 +720,19 @@ def passwordsXML():
|
||||||
server = dialog.input("Enter the server name or IP address")
|
server = dialog.input("Enter the server name or IP address")
|
||||||
if not server:
|
if not server:
|
||||||
return
|
return
|
||||||
|
server = quote_plus(server)
|
||||||
|
|
||||||
# Network username
|
# Network username
|
||||||
user = dialog.input("Enter the network username")
|
user = dialog.input("Enter the network username")
|
||||||
if not user:
|
if not user:
|
||||||
return
|
return
|
||||||
|
user = quote_plus(user)
|
||||||
# Network password
|
# Network password
|
||||||
password = dialog.input("Enter the network password",
|
password = dialog.input("Enter the network password",
|
||||||
'', # Default input
|
'', # Default input
|
||||||
xbmcgui.INPUT_ALPHANUM,
|
xbmcgui.INPUT_ALPHANUM,
|
||||||
xbmcgui.ALPHANUM_HIDE_INPUT)
|
xbmcgui.ALPHANUM_HIDE_INPUT)
|
||||||
# Need to url-encode the password
|
# Need to url-encode the password
|
||||||
from urllib import quote_plus
|
|
||||||
password = quote_plus(password)
|
password = quote_plus(password)
|
||||||
# Add elements. Annoying etree bug where findall hangs forever
|
# Add elements. Annoying etree bug where findall hangs forever
|
||||||
if skipFind is False:
|
if skipFind is False:
|
||||||
|
@ -753,8 +749,6 @@ def passwordsXML():
|
||||||
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
|
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
|
||||||
topath = "smb://%s:%s@%s/" % (user, password, server)
|
topath = "smb://%s:%s@%s/" % (user, password, server)
|
||||||
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
|
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
|
||||||
# Force Kodi to see the credentials without restarting
|
|
||||||
xbmcvfs.exists(topath)
|
|
||||||
|
|
||||||
# Add credentials
|
# Add credentials
|
||||||
settings('networkCreds', value="%s" % server)
|
settings('networkCreds', value="%s" % server)
|
||||||
|
@ -762,7 +756,8 @@ def passwordsXML():
|
||||||
# Prettify and write to file
|
# Prettify and write to file
|
||||||
try:
|
try:
|
||||||
indent(root)
|
indent(root)
|
||||||
except: pass
|
except:
|
||||||
|
pass
|
||||||
etree.ElementTree(root).write(xmlpath)
|
etree.ElementTree(root).write(xmlpath)
|
||||||
|
|
||||||
# dialog.notification(
|
# dialog.notification(
|
||||||
|
@ -776,7 +771,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
"""
|
"""
|
||||||
Feed with tagname as unicode
|
Feed with tagname as unicode
|
||||||
"""
|
"""
|
||||||
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
path = xbmc.translatePath("special://profile/playlists/video/")
|
||||||
if viewtype == "mixed":
|
if viewtype == "mixed":
|
||||||
plname = "%s - %s" % (tagname, mediatype)
|
plname = "%s - %s" % (tagname, mediatype)
|
||||||
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, 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)
|
xsppath = "%sPlex %s.xsp" % (path, viewid)
|
||||||
|
|
||||||
# Create the playlist directory
|
# Create the playlist directory
|
||||||
if not xbmcvfs.exists(tryEncode(path)):
|
if not exists(path):
|
||||||
log.info("Creating directory: %s" % path)
|
log.info("Creating directory: %s" % path)
|
||||||
xbmcvfs.mkdirs(tryEncode(path))
|
makedirs(path)
|
||||||
|
|
||||||
# Only add the playlist if it doesn't already exists
|
# 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)
|
log.info('Path %s does exist' % xsppath)
|
||||||
if delete:
|
if delete:
|
||||||
xbmcvfs.delete(tryEncode(xsppath))
|
remove(xsppath)
|
||||||
log.info("Successfully removed playlist: %s." % tagname)
|
log.info("Successfully removed playlist: %s." % tagname)
|
||||||
|
|
||||||
return
|
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 = {
|
itemtypes = {
|
||||||
'homevideos': 'movies',
|
'homevideos': 'movies',
|
||||||
'movie': 'movies',
|
'movie': 'movies',
|
||||||
|
@ -806,51 +801,39 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
}
|
}
|
||||||
log.info("Writing playlist file to: %s" % xsppath)
|
log.info("Writing playlist file to: %s" % xsppath)
|
||||||
try:
|
try:
|
||||||
f = xbmcvfs.File(tryEncode(xsppath), 'wb')
|
with open(xsppath, 'wb'):
|
||||||
except:
|
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("Failed to create playlist: %s" % xsppath)
|
||||||
|
log.error(e)
|
||||||
return
|
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)
|
log.info("Successfully added playlist: %s" % tagname)
|
||||||
|
|
||||||
def deletePlaylists():
|
def deletePlaylists():
|
||||||
|
|
||||||
# Clean up the playlists
|
# Clean up the playlists
|
||||||
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
path = xbmc.translatePath("special://profile/playlists/video/")
|
||||||
dirs, files = xbmcvfs.listdir(tryEncode(path))
|
for root, _, files in walk(path):
|
||||||
for file in files:
|
for file in files:
|
||||||
if tryDecode(file).startswith('Plex'):
|
if file.startswith('Plex'):
|
||||||
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
remove(join(root, file))
|
||||||
|
|
||||||
def deleteNodes():
|
def deleteNodes():
|
||||||
|
|
||||||
# Clean up video nodes
|
# Clean up video nodes
|
||||||
import shutil
|
path = xbmc.translatePath("special://profile/library/video/")
|
||||||
path = tryDecode(xbmc.translatePath("special://profile/library/video/"))
|
for root, dirs, _ in walk(path):
|
||||||
dirs, files = xbmcvfs.listdir(tryEncode(path))
|
for directory in dirs:
|
||||||
for dir in dirs:
|
if directory.startswith('Plex-'):
|
||||||
if tryDecode(dir).startswith('Plex'):
|
rmtree(join(root, directory))
|
||||||
try:
|
break
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -891,7 +874,7 @@ def LogTime(func):
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
elapsedtotal = datetime.now() - starttotal
|
elapsedtotal = datetime.now() - starttotal
|
||||||
log.info('It took %s to run the function %s'
|
log.info('It took %s to run the function %s'
|
||||||
% (elapsedtotal, func.__name__))
|
% (elapsedtotal, func.__name__))
|
||||||
return result
|
return result
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import xbmc
|
import xbmc
|
||||||
from xbmcaddon import Addon
|
from xbmcaddon import Addon
|
||||||
|
|
||||||
|
# Paths are in string, not unicode!
|
||||||
|
|
||||||
|
|
||||||
def tryDecode(string, encoding='utf-8'):
|
def tryDecode(string, encoding='utf-8'):
|
||||||
"""
|
"""
|
||||||
|
@ -27,7 +29,7 @@ ADDON_VERSION = _ADDON.getAddonInfo('version')
|
||||||
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
||||||
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||||
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
||||||
KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile"))
|
KODI_PROFILE = xbmc.translatePath("special://profile")
|
||||||
|
|
||||||
if xbmc.getCondVisibility('system.platform.osx'):
|
if xbmc.getCondVisibility('system.platform.osx'):
|
||||||
PLATFORM = "MacOSX"
|
PLATFORM = "MacOSX"
|
||||||
|
@ -66,10 +68,10 @@ _DB_VIDEO_VERSION = {
|
||||||
15: 93, # Isengard
|
15: 93, # Isengard
|
||||||
16: 99, # Jarvis
|
16: 99, # Jarvis
|
||||||
17: 107, # Krypton
|
17: 107, # Krypton
|
||||||
18: 107 # Leia
|
18: 108 # Leia
|
||||||
}
|
}
|
||||||
DB_VIDEO_PATH = tryDecode(xbmc.translatePath(
|
DB_VIDEO_PATH = xbmc.translatePath(
|
||||||
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]))
|
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION])
|
||||||
|
|
||||||
_DB_MUSIC_VERSION = {
|
_DB_MUSIC_VERSION = {
|
||||||
13: 46, # Gotham
|
13: 46, # Gotham
|
||||||
|
@ -77,10 +79,10 @@ _DB_MUSIC_VERSION = {
|
||||||
15: 52, # Isengard
|
15: 52, # Isengard
|
||||||
16: 56, # Jarvis
|
16: 56, # Jarvis
|
||||||
17: 60, # Krypton
|
17: 60, # Krypton
|
||||||
18: 60 # Leia
|
18: 62 # Leia
|
||||||
}
|
}
|
||||||
DB_MUSIC_PATH = tryDecode(xbmc.translatePath(
|
DB_MUSIC_PATH = xbmc.translatePath(
|
||||||
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]))
|
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION])
|
||||||
|
|
||||||
_DB_TEXTURE_VERSION = {
|
_DB_TEXTURE_VERSION = {
|
||||||
13: 13, # Gotham
|
13: 13, # Gotham
|
||||||
|
@ -90,13 +92,13 @@ _DB_TEXTURE_VERSION = {
|
||||||
17: 13, # Krypton
|
17: 13, # Krypton
|
||||||
18: 13 # Leia
|
18: 13 # Leia
|
||||||
}
|
}
|
||||||
DB_TEXTURE_PATH = tryDecode(xbmc.translatePath(
|
DB_TEXTURE_PATH = xbmc.translatePath(
|
||||||
"special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION]))
|
"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(
|
EXTERNAL_SUBTITLE_TEMP_PATH = xbmc.translatePath(
|
||||||
"special://profile/addon_data/%s/temp/" % ADDON_ID))
|
"special://profile/addon_data/%s/temp/" % ADDON_ID)
|
||||||
|
|
||||||
|
|
||||||
# Multiply Plex time by this factor to receive Kodi time
|
# Multiply Plex time by this factor to receive Kodi time
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
from shutil import copytree
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
import xbmc
|
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, \
|
from utils import window, settings, language as lang, tryEncode, indent, \
|
||||||
tryEncode, indent, normalize_nodes
|
normalize_nodes
|
||||||
import variables as v
|
import variables as v
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -18,6 +17,7 @@ import variables as v
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
# Paths are strings, NOT unicode!
|
||||||
|
|
||||||
|
|
||||||
class VideoNodes(object):
|
class VideoNodes(object):
|
||||||
|
@ -61,36 +61,30 @@ class VideoNodes(object):
|
||||||
else:
|
else:
|
||||||
dirname = viewid
|
dirname = viewid
|
||||||
|
|
||||||
path = tryDecode(xbmc.translatePath(
|
# Returns strings
|
||||||
"special://profile/library/video/"))
|
path = xbmc.translatePath("special://profile/library/video/")
|
||||||
nodepath = tryDecode(xbmc.translatePath(
|
nodepath = xbmc.translatePath(
|
||||||
"special://profile/library/video/Plex-%s/" % dirname))
|
"special://profile/library/video/Plex-%s/" % dirname)
|
||||||
|
|
||||||
if delete:
|
if delete:
|
||||||
dirs, files = xbmcvfs.listdir(tryEncode(nodepath))
|
files = [f for f in listdir(nodepath) if isfile(join(nodepath, f))]
|
||||||
for file in files:
|
for file in files:
|
||||||
xbmcvfs.delete(tryEncode(
|
remove(nodepath + file)
|
||||||
(nodepath + tryDecode(file))))
|
|
||||||
log.info("Sucessfully removed videonode: %s." % tagname)
|
log.info("Sucessfully removed videonode: %s." % tagname)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Verify the video directory
|
# Verify the video directory
|
||||||
# KODI BUG
|
if exists(path) is False:
|
||||||
# Kodi caches the result of exists for directories
|
copytree(
|
||||||
# so try creating a file
|
src=xbmc.translatePath("special://xbmc/system/library/video"),
|
||||||
if IfExists(path) is False:
|
dst=xbmc.translatePath("special://profile/library/video"))
|
||||||
shutil.copytree(
|
|
||||||
src=tryDecode(xbmc.translatePath(
|
|
||||||
"special://xbmc/system/library/video")),
|
|
||||||
dst=tryDecode(xbmc.translatePath(
|
|
||||||
"special://profile/library/video")))
|
|
||||||
|
|
||||||
# Create the node directory
|
# Create the node directory
|
||||||
if mediatype != "photos":
|
if mediatype != "photos":
|
||||||
if IfExists(nodepath) is False:
|
if exists(nodepath) is False:
|
||||||
# folder does not exist yet
|
# folder does not exist yet
|
||||||
log.debug('Creating folder %s' % nodepath)
|
log.debug('Creating folder %s' % nodepath)
|
||||||
xbmcvfs.mkdirs(tryEncode(nodepath))
|
makedirs(nodepath)
|
||||||
|
|
||||||
# Create index entry
|
# Create index entry
|
||||||
nodeXML = "%sindex.xml" % nodepath
|
nodeXML = "%sindex.xml" % nodepath
|
||||||
|
@ -103,23 +97,29 @@ class VideoNodes(object):
|
||||||
|
|
||||||
if mediatype == "photos":
|
if mediatype == "photos":
|
||||||
path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid)
|
path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid)
|
||||||
|
|
||||||
window('Plex.nodes.%s.index' % indexnumber, value=path)
|
window('Plex.nodes.%s.index' % indexnumber, value=path)
|
||||||
|
|
||||||
# Root
|
# Root
|
||||||
if not mediatype == "photos":
|
if not mediatype == "photos":
|
||||||
if viewtype == "mixed":
|
if viewtype == "mixed":
|
||||||
specialtag = "%s-%s" % (tagname, mediatype)
|
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:
|
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:
|
||||||
indent(root)
|
indent(root)
|
||||||
except: pass
|
except:
|
||||||
|
pass
|
||||||
etree.ElementTree(root).write(nodeXML)
|
etree.ElementTree(root).write(nodeXML)
|
||||||
|
|
||||||
nodetypes = {
|
nodetypes = {
|
||||||
|
|
||||||
'1': "all",
|
'1': "all",
|
||||||
'2': "recent",
|
'2': "recent",
|
||||||
'3': "recentepisodes",
|
'3': "recentepisodes",
|
||||||
|
@ -255,7 +255,7 @@ class VideoNodes(object):
|
||||||
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
|
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
|
||||||
else:
|
else:
|
||||||
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
|
||||||
|
|
||||||
if mediatype == "photos":
|
if mediatype == "photos":
|
||||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
||||||
else:
|
else:
|
||||||
|
@ -264,7 +264,7 @@ class VideoNodes(object):
|
||||||
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
windowpath = "ActivateWindow(Videos,%s,return)" % path
|
||||||
else:
|
else:
|
||||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||||
|
|
||||||
if nodetype == "all":
|
if nodetype == "all":
|
||||||
|
|
||||||
if viewtype == "mixed":
|
if viewtype == "mixed":
|
||||||
|
@ -288,8 +288,8 @@ class VideoNodes(object):
|
||||||
# to be created.
|
# to be created.
|
||||||
# To do: add our photos nodes to kodi picture sources somehow
|
# To do: add our photos nodes to kodi picture sources somehow
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if xbmcvfs.exists(tryEncode(nodeXML)):
|
if exists(nodeXML):
|
||||||
# Don't recreate xml if already exists
|
# Don't recreate xml if already exists
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -370,15 +370,14 @@ class VideoNodes(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
indent(root)
|
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 = tryEncode(tagname)
|
tagname = tryEncode(tagname)
|
||||||
cleantagname = normalize_nodes(tagname)
|
cleantagname = normalize_nodes(tagname)
|
||||||
nodepath = tryDecode(xbmc.translatePath(
|
nodepath = xbmc.translatePath("special://profile/library/video/")
|
||||||
"special://profile/library/video/"))
|
|
||||||
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
||||||
path = "library://video/plex_%s.xml" % cleantagname
|
path = "library://video/plex_%s.xml" % cleantagname
|
||||||
if v.KODIVERSION >= 17:
|
if v.KODIVERSION >= 17:
|
||||||
|
@ -388,17 +387,13 @@ class VideoNodes(object):
|
||||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||||
|
|
||||||
# Create the video node directory
|
# Create the video node directory
|
||||||
if not xbmcvfs.exists(nodepath):
|
if not exists(nodepath):
|
||||||
# We need to copy over the default items
|
# We need to copy over the default items
|
||||||
shutil.copytree(
|
copytree(
|
||||||
src=tryDecode(xbmc.translatePath(
|
src=xbmc.translatePath("special://xbmc/system/library/video"),
|
||||||
"special://xbmc/system/library/video")),
|
dst=xbmc.translatePath("special://profile/library/video"))
|
||||||
dst=tryDecode(xbmc.translatePath(
|
|
||||||
"special://profile/library/video")))
|
|
||||||
xbmcvfs.exists(path)
|
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
|
|
||||||
'Favorite movies': 30180,
|
'Favorite movies': 30180,
|
||||||
'Favorite tvshows': 30181,
|
'Favorite tvshows': 30181,
|
||||||
'channels': 30173
|
'channels': 30173
|
||||||
|
@ -410,12 +405,15 @@ class VideoNodes(object):
|
||||||
window('%s.content' % embynode, value=path)
|
window('%s.content' % embynode, value=path)
|
||||||
window('%s.type' % embynode, value=itemtype)
|
window('%s.type' % embynode, value=itemtype)
|
||||||
|
|
||||||
if xbmcvfs.exists(nodeXML):
|
if exists(nodeXML):
|
||||||
# Don't recreate xml if already exists
|
# Don't recreate xml if already exists
|
||||||
return
|
return
|
||||||
|
|
||||||
if itemtype == "channels":
|
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"
|
etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels"
|
||||||
else:
|
else:
|
||||||
root = self.commonRoot(order=1, label=label, tagname=tagname)
|
root = self.commonRoot(order=1, label=label, tagname=tagname)
|
||||||
|
@ -425,7 +423,8 @@ class VideoNodes(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
indent(root)
|
indent(root)
|
||||||
except: pass
|
except:
|
||||||
|
pass
|
||||||
etree.ElementTree(root).write(nodeXML)
|
etree.ElementTree(root).write(nodeXML)
|
||||||
|
|
||||||
def clearProperties(self):
|
def clearProperties(self):
|
||||||
|
@ -433,7 +432,6 @@ class VideoNodes(object):
|
||||||
log.info("Clearing nodes properties.")
|
log.info("Clearing nodes properties.")
|
||||||
plexprops = window('Plex.nodes.total')
|
plexprops = window('Plex.nodes.total')
|
||||||
propnames = [
|
propnames = [
|
||||||
|
|
||||||
"index","path","title","content",
|
"index","path","title","content",
|
||||||
"inprogress.content","inprogress.title",
|
"inprogress.content","inprogress.title",
|
||||||
"inprogress.content","inprogress.path",
|
"inprogress.content","inprogress.path",
|
||||||
|
|
|
@ -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
|
from userclient import UserClient
|
||||||
import initialsetup
|
import initialsetup
|
||||||
from kodimonitor import KodiMonitor
|
from kodimonitor import KodiMonitor
|
||||||
|
@ -164,12 +164,12 @@ class Service():
|
||||||
counter = 0
|
counter = 0
|
||||||
while not monitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
|
|
||||||
if tryDecode(window('plex_kodiProfile')) != kodiProfile:
|
if tryEncode(window('plex_kodiProfile')) != kodiProfile:
|
||||||
# Profile change happened, terminate this thread and others
|
# Profile change happened, terminate this thread and others
|
||||||
log.warn("Kodi profile was: %s and changed to: %s. "
|
log.warn("Kodi profile was: %s and changed to: %s. "
|
||||||
"Terminating old PlexKodiConnect thread."
|
"Terminating old PlexKodiConnect thread."
|
||||||
% (kodiProfile,
|
% (kodiProfile,
|
||||||
tryDecode(window('plex_kodiProfile'))))
|
tryEncode(window('plex_kodiProfile'))))
|
||||||
break
|
break
|
||||||
|
|
||||||
# Before proceeding, need to make sure:
|
# Before proceeding, need to make sure:
|
||||||
|
|
Loading…
Reference in a new issue