-
Notifications
You must be signed in to change notification settings - Fork 307
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
130 changed files
with
4,087 additions
and
2,214 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# coding=utf-8 | ||
|
||
from kodi_six import xbmcvfs | ||
|
||
from lib.util import LOG, ERROR | ||
|
||
|
||
class AdvancedSettings(object): | ||
_data = None | ||
|
||
def __init__(self): | ||
self.load() | ||
|
||
def __bool__(self): | ||
return bool(self._data) | ||
|
||
def getData(self): | ||
return self._data | ||
|
||
def load(self): | ||
if xbmcvfs.exists("special://profile/advancedsettings.xml"): | ||
try: | ||
f = xbmcvfs.File("special://profile/advancedsettings.xml") | ||
self._data = f.read() | ||
f.close() | ||
except: | ||
LOG('script.plex: No advancedsettings.xml found') | ||
|
||
def write(self, data=None): | ||
self._data = data = data or self._data | ||
if not data: | ||
return | ||
|
||
try: | ||
f = xbmcvfs.File("special://profile/advancedsettings.xml", "w") | ||
f.write(data) | ||
f.close() | ||
except: | ||
ERROR("Couldn't write advancedsettings.xml") | ||
|
||
|
||
adv = AdvancedSettings() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# coding=utf-8 | ||
import os | ||
import re | ||
|
||
from kodi_six import xbmc | ||
from kodi_six import xbmcvfs | ||
|
||
from plexnet import plexapp | ||
|
||
from lib.kodijsonrpc import rpc | ||
from lib.util import ADDON, translatePath, KODI_BUILD_NUMBER, DEBUG_LOG, LOG, ERROR | ||
from lib.advancedsettings import adv | ||
|
||
|
||
ADV_MSIZE_RE = re.compile(r'<memorysize>(\d+)</memorysize>') | ||
ADV_RFACT_RE = re.compile(r'<readfactor>(\d+)</readfactor>') | ||
ADV_CACHE_RE = re.compile(r'\s*<cache>.*</cache>', re.S | re.I) | ||
|
||
|
||
class KodiCacheManager(object): | ||
""" | ||
A pretty cheap approach at managing the <cache> section of advancedsettings.xml | ||
Starting with build 20.90.821 (Kodi 21.0-BETA2) a lot of caching issues have been fixed and | ||
readfactor behaves better. We need to adjust for that. | ||
""" | ||
useModernAPI = False | ||
memorySize = 20 # in MB | ||
readFactor = 4 | ||
defRF = 4 | ||
defRFSM = 20 | ||
recRFRange = "4-10" | ||
template = None | ||
orig_tpl_path = os.path.join(ADDON.getAddonInfo('path'), "pm4k_cache_template.xml") | ||
custom_tpl_path = "special://profile/pm4k_cache_template.xml" | ||
translated_ctpl_path = translatePath(custom_tpl_path) | ||
|
||
# give Android a little more leeway with its sometimes weird memory management; otherwise stick with 23% of free mem | ||
safeFactor = .20 if xbmc.getCondVisibility('System.Platform.Android') else .23 | ||
|
||
def __init__(self): | ||
if KODI_BUILD_NUMBER >= 2090821: | ||
self.memorySize = rpc.Settings.GetSettingValue(setting='filecache.memorysize')['value'] | ||
self.readFactor = rpc.Settings.GetSettingValue(setting='filecache.readfactor')['value'] / 100.0 | ||
if self.readFactor % 1 == 0: | ||
self.readFactor = int(self.readFactor) | ||
DEBUG_LOG("Not using advancedsettings.xml for cache/buffer management, we're at least Kodi 21 non-alpha") | ||
self.useModernAPI = True | ||
self.defRFSM = 7 | ||
self.recRFRange = "1.5-4" | ||
|
||
if KODI_BUILD_NUMBER >= 2090830: | ||
self.recRFRange = ADDON.getLocalizedString(32976) | ||
|
||
else: | ||
self.load() | ||
self.template = self.getTemplate() | ||
|
||
plexapp.util.APP.on('change:slow_connection', | ||
lambda value=None, **kwargs: self.write(readFactor=value and self.defRFSM or self.defRF)) | ||
|
||
def getTemplate(self): | ||
if xbmcvfs.exists(self.custom_tpl_path): | ||
try: | ||
f = xbmcvfs.File(self.custom_tpl_path) | ||
data = f.read() | ||
f.close() | ||
if data: | ||
return data | ||
except: | ||
pass | ||
|
||
DEBUG_LOG("Custom pm4k_cache_template.xml not found, using default") | ||
f = xbmcvfs.File(self.orig_tpl_path) | ||
data = f.read() | ||
f.close() | ||
return data | ||
|
||
def load(self): | ||
data = adv.getData() | ||
if not data: | ||
return | ||
|
||
cachexml_match = ADV_CACHE_RE.search(data) | ||
if cachexml_match: | ||
cachexml = cachexml_match.group(0) | ||
|
||
try: | ||
self.memorySize = int(ADV_MSIZE_RE.search(cachexml).group(1)) // 1024 // 1024 | ||
except: | ||
DEBUG_LOG("script.plex: invalid or not found memorysize in advancedsettings.xml") | ||
|
||
try: | ||
self.readFactor = int(ADV_RFACT_RE.search(cachexml).group(1)) | ||
except: | ||
DEBUG_LOG("script.plex: invalid or not found readfactor in advancedsettings.xml") | ||
|
||
# self._cleanData = data.replace(cachexml, "") | ||
#else: | ||
# self._cleanData = data | ||
|
||
def write(self, memorySize=None, readFactor=None): | ||
memorySize = self.memorySize = memorySize if memorySize is not None else self.memorySize | ||
readFactor = self.readFactor = readFactor if readFactor is not None else self.readFactor | ||
|
||
if self.useModernAPI: | ||
# kodi cache settings have moved to Services>Caching | ||
try: | ||
rpc.Settings.SetSettingValue(setting='filecache.memorysize', value=self.memorySize) | ||
rpc.Settings.SetSettingValue(setting='filecache.readfactor', value=int(self.readFactor * 100)) | ||
except: | ||
pass | ||
return | ||
|
||
data = adv.getData() | ||
cd = "<advancedsettings>\n</advancedsettings>" | ||
if data: | ||
cachexml_match = ADV_CACHE_RE.search(data) | ||
if cachexml_match: | ||
cachexml = cachexml_match.group(0) | ||
cd = data.replace(cachexml, "") | ||
else: | ||
cd = data | ||
|
||
finalxml = "{}\n</advancedsettings>".format( | ||
cd.replace("</advancedsettings>", self.template.format(memorysize=memorySize * 1024 * 1024, | ||
readfactor=readFactor)) | ||
) | ||
|
||
adv.write(finalxml) | ||
|
||
def clamp16(self, x): | ||
return x - x % 16 | ||
|
||
@property | ||
def viableOptions(self): | ||
default = list(filter(lambda x: x < self.recMax, | ||
[16, 20, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024])) | ||
|
||
# add option to overcommit slightly | ||
overcommit = [] | ||
if xbmc.getCondVisibility('System.Platform.Android'): | ||
overcommit.append(min(self.clamp16(int(self.free * 0.23)), 2048)) | ||
|
||
overcommit.append(min(self.clamp16(int(self.free * 0.26)), 2048)) | ||
overcommit.append(min(self.clamp16(int(self.free * 0.3)), 2048)) | ||
|
||
# re-append current memorySize here, as recommended max might have changed | ||
return list(sorted(list(set(default + [self.memorySize, self.recMax] + overcommit)))) | ||
|
||
@property | ||
def readFactorOpts(self): | ||
ret = list(sorted(list(set([1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5, 7, 10, 15, 20, 30, 50] + [self.readFactor])))) | ||
if KODI_BUILD_NUMBER >= 2090830 and self.readFactor > 0: | ||
# support for adaptive read factor from build 2090822 onwards | ||
ret.insert(0, 0) | ||
return ret | ||
|
||
@property | ||
def free(self): | ||
return float(xbmc.getInfoLabel('System.Memory(free)')[:-2]) | ||
|
||
@property | ||
def recMax(self): | ||
freeMem = self.free | ||
recMem = min(int(freeMem * self.safeFactor), 2048) | ||
LOG("Free memory: {} MB, recommended max: {} MB".format(freeMem, recMem)) | ||
return recMem | ||
|
||
|
||
kcm = KodiCacheManager() | ||
CACHE_SIZE = kcm.memorySize |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# coding=utf-8 | ||
import re | ||
try: | ||
from urllib.parse import urlparse | ||
except ImportError: | ||
from requests.compat import urlparse | ||
|
||
import plexnet.http | ||
|
||
from lib import util | ||
from lib.advancedsettings import adv | ||
|
||
from plexnet.util import parsePlexDirectHost | ||
|
||
HOSTS_RE = re.compile(r'\s*<hosts>.*</hosts>', re.S | re.I) | ||
HOST_RE = re.compile(r'<entry name="(?P<hostname>.+)">(?P<ip>.+)</entry>') | ||
|
||
|
||
class PlexHostsManager(object): | ||
_hosts = None | ||
_orig_hosts = None | ||
|
||
HOSTS_TPL = """\ | ||
<hosts><!-- managed by PM4K --> | ||
{} | ||
</hosts>""" | ||
ENTRY_TPL = ' <entry name="{}">{}</entry>' | ||
|
||
def __init__(self): | ||
self.load() | ||
|
||
def __bool__(self): | ||
return bool(self._hosts) | ||
|
||
def __len__(self): | ||
return self and len(self._hosts) or 0 | ||
|
||
def getHosts(self): | ||
return self._hosts or {} | ||
|
||
@property | ||
def hadHosts(self): | ||
return bool(self._orig_hosts) | ||
|
||
def newHosts(self, hosts, source="stored"): | ||
""" | ||
hosts should be a list of plex.direct connection uri's | ||
""" | ||
for address in hosts: | ||
parsed = urlparse(address) | ||
if parsed.hostname not in self._hosts: | ||
self._hosts[parsed.hostname] = plexnet.http.RESOLVED_PD_HOSTS.get(parsed.hostname, | ||
parsePlexDirectHost(parsed.hostname)) | ||
util.LOG("Found new unmapped {} plex.direct host: {}".format(source, parsed.hostname)) | ||
|
||
@property | ||
def differs(self): | ||
return self._hosts != self._orig_hosts | ||
|
||
@property | ||
def diff(self): | ||
return set(self._hosts) - set(self._orig_hosts) | ||
|
||
def load(self): | ||
data = adv.getData() | ||
self._hosts = {} | ||
self._orig_hosts = {} | ||
if not data: | ||
return | ||
|
||
hosts_match = HOSTS_RE.search(data) | ||
if hosts_match: | ||
hosts_xml = hosts_match.group(0) | ||
|
||
hosts = HOST_RE.findall(hosts_xml) | ||
if hosts: | ||
self._hosts = dict(hosts) | ||
self._orig_hosts = dict(hosts) | ||
util.DEBUG_LOG("Found {} hosts in advancedsettings.xml".format(len(self._hosts))) | ||
|
||
def write(self, hosts=None): | ||
self._hosts = hosts or self._hosts | ||
if not self._hosts: | ||
return | ||
data = adv.getData() | ||
cd = "<advancedsettings>\n</advancedsettings>" | ||
if data: | ||
hosts_match = HOSTS_RE.search(data) | ||
if hosts_match: | ||
hosts_xml = hosts_match.group(0) | ||
cd = data.replace(hosts_xml, "") | ||
else: | ||
cd = data | ||
|
||
finalxml = "{}\n</advancedsettings>".format( | ||
cd.replace("</advancedsettings>", self.HOSTS_TPL.format("\n".join(self.ENTRY_TPL.format(hostname, ip) | ||
for hostname, ip in self._hosts.items()))) | ||
) | ||
|
||
adv.write(finalxml) | ||
self._orig_hosts = dict(self._hosts) | ||
|
||
|
||
pdm = PlexHostsManager() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
This is used to tell the addon to not use the HTTP handler for certain paths. | ||
The paths are mapped by comparing the file path of a media item with the right-hand-side and then | ||
replaced with the corresponding left-hand-side. | ||
|
||
e.g. | ||
file path in Plex Server: "/library/path/on/server/data/movies/thisisfun.mkv" | ||
left-hand-side right-hand-side | ||
"smb://serverip/movies": "/library/path/on/server/data/movies", | ||
|
||
or, if all of your libraries are based on the same folder: | ||
"smb://serverip/data": "/library/path/on/server/data", | ||
|
||
To find out how your Plex Server sees the paths of your media items, visit Plex Web, click the three dots on an item, | ||
"Get Info", "View XML", then take note of the file="..." attribute of the <Part /> element. | ||
|
||
Let's say you have a common Movie library path of "/mnt/data/movies" and you've exposed that path via SMB/Samba to the | ||
share "Movies", you'd do the following ([:port] is optional for SMB): | ||
- Go to the Kodi file manager and add a new source | ||
- Add a source with "smb://serverip[:port]/Movies" | ||
- Copy this file to "userdata/addon_data/script.plexmod/path_mapping.json" | ||
- Fill in your servername (the name of the server in Plex) in place of your_server_name below | ||
- Add "smb://serverip[:port]/Movies": "/mnt/data/Movies" below "// add your own mounts here" below | ||
|
||
You can leave the examples in there, they don't matter (you can delete them if you want). | ||
|
||
Note: For paths containing backslashes ("\"), you need to escape them here, so "\\asdf\bla" becomes "\\\\asdf\\bla". | ||
|
||
This is not limited to SMB, though. You can add NFS, local mounts, webdav, whatever, (even http(s):// if you'd like) | ||
as long as the current video file path starts with the right-hand-side of the mapping, and the left hand side exists | ||
(you can disable the existence-checks in the addon settings), it will be replaced with the left-hand-side of it. | ||
*/ | ||
{ | ||
"your_server_name": { | ||
// add your own mounts here | ||
|
||
// standard SMB mounts in Kodi | ||
"smb://serverip/mountname": "/library/path/on/server", | ||
"smb://serverip/mountname2": "/library2/path/on/server", | ||
// NVIDIA SHIELD direct mount | ||
"/storage/SERVERNAME/this_is_a_SHIELD_mountpoint": "/library3/path/on/server", | ||
// Plex Server running on Windows using network shares for libraries | ||
"smb://serverip/share": "\\\\some_server_or_ip\\share" | ||
} | ||
} |
Oops, something went wrong.