Skip to content

Commit

Permalink
[script.plexmod] 0.7.7
Browse files Browse the repository at this point in the history
  • Loading branch information
pannal committed Apr 21, 2024
2 parents 1ac7714 + 26fc0ca commit 5472ee1
Show file tree
Hide file tree
Showing 130 changed files with 4,087 additions and 2,214 deletions.
Binary file added script.plexmod/fanart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added script.plexmod/icon2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions script.plexmod/lib/advancedsettings.py
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()
172 changes: 172 additions & 0 deletions script.plexmod/lib/cache.py
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
104 changes: 104 additions & 0 deletions script.plexmod/lib/plex_hosts.py
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()
45 changes: 45 additions & 0 deletions script.plexmod/path_mapping.json.example
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"
}
}
Loading

0 comments on commit 5472ee1

Please sign in to comment.