Preserve native broadcast video and add patient shared stream startup
This commit is contained in:
+58
-4
@@ -12,14 +12,17 @@ import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
VERSION = '7.0.14.0'
|
||||
VERSION = '7.0.15.0'
|
||||
ABI = '12.2.0.0'
|
||||
GUID = '193f29f9-ea6c-4595-a6f6-55e79d7c590a'
|
||||
DLL = 'Jellyfin.Plugin.Enigma2.dll'
|
||||
PLUGINS = pathlib.Path('/var/lib/jellyfin/plugins')
|
||||
BACKUPS = pathlib.Path('/var/lib/jellyfin/decoder-plugin-backups')
|
||||
LOGS = pathlib.Path('/var/log/jellyfin')
|
||||
WEB = pathlib.Path('/usr/share/jellyfin/web/decoder-channel-playback.js')
|
||||
CONFIG = PLUGINS / 'configurations/Jellyfin.Plugin.Enigma2.xml'
|
||||
PROTECTED = tuple(map(pathlib.Path, (
|
||||
'/etc/default/jellyfin', '/etc/jellyfin/encoding.xml',
|
||||
'/usr/local/lib/jellyfin-enigma/ffmpeg', '/usr/local/lib/jellyfin-enigma/ffprobe',
|
||||
@@ -217,14 +220,40 @@ def activate(old, new, stage, backup, verify, verify_rollback, control=service):
|
||||
shutil.rmtree(stage)
|
||||
|
||||
|
||||
class PluginAssetControl:
|
||||
"""Update only the plugin's assets while stopped; restore them on rollback."""
|
||||
def __init__(self, old, new, assets, control=service):
|
||||
self.old, self.new, self.assets, self.control = old, new, assets, control
|
||||
|
||||
def __call__(self, action):
|
||||
if action == 'start':
|
||||
candidate = self.new.exists() and not self.old.exists()
|
||||
for destination, prepared, saved in self.assets:
|
||||
shutil.copyfile(prepared if candidate else saved, destination)
|
||||
self.control(action)
|
||||
|
||||
|
||||
def enable_slates(content):
|
||||
pattern = r'<EnableContinuitySlates>\s*(?:true|false)\s*</EnableContinuitySlates>'
|
||||
tag = '<EnableContinuitySlates>true</EnableContinuitySlates>'
|
||||
result = re.sub(pattern, tag, content) if re.search(pattern, content) else re.sub(r'(</[\w:]+>\s*)$', ' ' + tag + '\n' + r'\1', content, count=1)
|
||||
if ET.fromstring(result).findtext('EnableContinuitySlates') != 'true':
|
||||
raise ValueError('Cannot set the plugin status-picture option')
|
||||
return result
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--maintenance-approved', action='store_true',
|
||||
help='Required confirmation of an already agreed Jellyfin restart window')
|
||||
parser.add_argument('--dll', type=pathlib.Path, required=True)
|
||||
parser.add_argument('--expected-sha256', required=True)
|
||||
parser.add_argument('--web', type=pathlib.Path)
|
||||
parser.add_argument('--expected-web-sha256')
|
||||
parser.add_argument('--enable-slates', action='store_true')
|
||||
parser.add_argument('--check-only', action='store_true')
|
||||
args = parser.parse_args(argv)
|
||||
if not args.maintenance_approved:
|
||||
if not args.maintenance_approved and not args.check_only:
|
||||
parser.error('--maintenance-approved is required; this script cannot inspect playback sessions')
|
||||
expected = args.expected_sha256.lower()
|
||||
if not re.fullmatch(r'[0-9a-f]{64}', expected):
|
||||
@@ -235,6 +264,16 @@ def main(argv=None):
|
||||
new = PLUGINS / ('Enigma2_' + VERSION)
|
||||
if new.exists() or new.is_symlink():
|
||||
raise ValueError('Target plugin directory already exists; refusing to replace it')
|
||||
if bool(args.web) != bool(args.expected_web_sha256):
|
||||
raise ValueError('Web asset and its checksum must be supplied together')
|
||||
if args.web and (not args.web.is_file() or sha256(args.web) != args.expected_web_sha256.lower() or not WEB.is_file()):
|
||||
raise ValueError('Plugin web asset validation failed')
|
||||
if args.enable_slates:
|
||||
enable_slates(CONFIG.read_text(encoding='utf-8-sig'))
|
||||
if args.check_only:
|
||||
print(json.dumps({'preflight': True, 'activated': False, 'restart': False, 'candidate': VERSION,
|
||||
'current': metadata['version'], 'web_included': bool(args.web), 'enable_slates_requested': args.enable_slates}))
|
||||
return
|
||||
before = hashes()
|
||||
old_hash = sha256(old / DLL)
|
||||
boundary = log_boundary(LOGS)
|
||||
@@ -242,7 +281,16 @@ def main(argv=None):
|
||||
backup = BACKUPS / ('plugin-only-' + stamp)
|
||||
backup.mkdir(mode=0o700, parents=True, exist_ok=False)
|
||||
stage = backup / 'candidate'
|
||||
assets = []
|
||||
try:
|
||||
if args.web:
|
||||
saved = backup / 'previous-web.js'; shutil.copy2(WEB, saved)
|
||||
assets.append((WEB, args.web, saved))
|
||||
if args.enable_slates:
|
||||
saved = backup / 'previous-plugin-config.xml'; shutil.copy2(CONFIG, saved)
|
||||
prepared = backup / 'candidate-plugin-config.xml'
|
||||
prepared.write_text(enable_slates(CONFIG.read_text(encoding='utf-8-sig')), encoding='utf-8')
|
||||
assets.append((CONFIG, prepared, saved))
|
||||
shutil.copytree(old, stage, copy_function=shutil.copy2)
|
||||
shutil.copy2(args.dll, stage / DLL)
|
||||
updated = dict(metadata, version=VERSION, status='Active', targetAbi=ABI)
|
||||
@@ -251,9 +299,15 @@ def main(argv=None):
|
||||
shutil.chown(path, user='jellyfin', group='jellyfin')
|
||||
if sha256(stage / DLL) != expected or hashes() != before:
|
||||
raise RuntimeError('Pre-restart checksum verification failed')
|
||||
def verify_plugin(directory, version, digest, candidate):
|
||||
wait_ready(directory, version, digest, before, boundary)
|
||||
for destination, prepared, saved in assets:
|
||||
if sha256(destination) != sha256(prepared if candidate else saved):
|
||||
raise RuntimeError('Plugin asset verification failed')
|
||||
activate(old, new, stage, backup,
|
||||
lambda: wait_ready(new, VERSION, expected, before, boundary),
|
||||
lambda: wait_ready(old, metadata['version'], old_hash, before, boundary))
|
||||
lambda: verify_plugin(new, VERSION, expected, True),
|
||||
lambda: verify_plugin(old, metadata['version'], old_hash, False),
|
||||
PluginAssetControl(old, new, assets))
|
||||
finally:
|
||||
if stage.exists():
|
||||
shutil.rmtree(stage)
|
||||
|
||||
Reference in New Issue
Block a user