@camstack/system 1.1.25 → 1.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/platform-probe/index.d.ts +0 -11
- package/dist/builtins/platform-probe/index.js +4 -309
- package/dist/builtins/platform-probe/index.mjs +5 -308
- package/dist/builtins/system-config/system-config.addon.js +51 -118
- package/dist/builtins/system-config/system-config.addon.mjs +51 -118
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +9 -0
- package/dist/kernel/inference/inference-resolver.d.ts +16 -0
- package/dist/{manifest-python-deps-BqXCckob.js → manifest-python-deps-CCmnJmNf.js} +26 -1
- package/dist/{manifest-python-deps-Dxl4twM3.mjs → manifest-python-deps-DPA7N9UP.mjs} +26 -1
- package/package.json +1 -1
- package/dist/builtins/platform-probe/hardware-decode-accel-probe.d.ts +0 -37
- package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts +0 -13
|
@@ -11,7 +11,7 @@ import * as os from "node:os";
|
|
|
11
11
|
* entire Node process (WS handshakes, tRPC subscriptions, metrics — all
|
|
12
12
|
* stalled). Keeping it async turns the probe into a true background task.
|
|
13
13
|
*/
|
|
14
|
-
var execFileAsync
|
|
14
|
+
var execFileAsync = promisify(execFile);
|
|
15
15
|
/** Minimal no-op logger for default parameter */
|
|
16
16
|
var noopLogger = {
|
|
17
17
|
debug() {},
|
|
@@ -39,7 +39,7 @@ async function getAvailableRAM_MB() {
|
|
|
39
39
|
const platform = os.platform();
|
|
40
40
|
try {
|
|
41
41
|
if (platform === "darwin") {
|
|
42
|
-
const { stdout: output } = await execFileAsync
|
|
42
|
+
const { stdout: output } = await execFileAsync("vm_stat", [], {
|
|
43
43
|
encoding: "utf8",
|
|
44
44
|
timeout: 3e3
|
|
45
45
|
});
|
|
@@ -164,7 +164,7 @@ var PlatformScorer = class {
|
|
|
164
164
|
}
|
|
165
165
|
if (platform === "linux") {
|
|
166
166
|
try {
|
|
167
|
-
const { stdout } = await execFileAsync
|
|
167
|
+
const { stdout } = await execFileAsync("nvidia-smi", ["--query-gpu=name,memory.total", "--format=csv,noheader"], {
|
|
168
168
|
encoding: "utf8",
|
|
169
169
|
timeout: 5e3
|
|
170
170
|
});
|
|
@@ -230,7 +230,7 @@ var PlatformScorer = class {
|
|
|
230
230
|
"python"
|
|
231
231
|
];
|
|
232
232
|
for (const cmd of candidates) try {
|
|
233
|
-
await execFileAsync
|
|
233
|
+
await execFileAsync(cmd, ["--version"], { timeout: 5e3 });
|
|
234
234
|
return cmd;
|
|
235
235
|
} catch (err) {
|
|
236
236
|
this.logger.debug(`Python command "${cmd}" not found`, { meta: { error: errMsg(err) } });
|
|
@@ -328,264 +328,6 @@ var InferenceConfigResolver = class {
|
|
|
328
328
|
}
|
|
329
329
|
};
|
|
330
330
|
//#endregion
|
|
331
|
-
//#region src/builtins/platform-probe/hardware-encoder-probe.ts
|
|
332
|
-
var execFileAsync$1 = promisify(execFile);
|
|
333
|
-
function buildCandidates(hardware) {
|
|
334
|
-
const out = [];
|
|
335
|
-
if (hardware.platform === "darwin") {
|
|
336
|
-
out.push({
|
|
337
|
-
encoder: "h264_videotoolbox",
|
|
338
|
-
codec: "H264",
|
|
339
|
-
family: "videotoolbox"
|
|
340
|
-
});
|
|
341
|
-
out.push({
|
|
342
|
-
encoder: "hevc_videotoolbox",
|
|
343
|
-
codec: "H265",
|
|
344
|
-
family: "videotoolbox"
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
if (hardware.platform === "linux") {
|
|
348
|
-
if (hardware.gpu?.type === "nvidia") {
|
|
349
|
-
out.push({
|
|
350
|
-
encoder: "h264_nvenc",
|
|
351
|
-
codec: "H264",
|
|
352
|
-
family: "nvenc"
|
|
353
|
-
});
|
|
354
|
-
out.push({
|
|
355
|
-
encoder: "hevc_nvenc",
|
|
356
|
-
codec: "H265",
|
|
357
|
-
family: "nvenc"
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
if (hardware.gpu?.type === "intel" || hardware.gpu?.type === "amd" || !hardware.gpu) {
|
|
361
|
-
out.push({
|
|
362
|
-
encoder: "h264_vaapi",
|
|
363
|
-
codec: "H264",
|
|
364
|
-
family: "vaapi"
|
|
365
|
-
});
|
|
366
|
-
out.push({
|
|
367
|
-
encoder: "hevc_vaapi",
|
|
368
|
-
codec: "H265",
|
|
369
|
-
family: "vaapi"
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return out;
|
|
374
|
-
}
|
|
375
|
-
async function runTestEncode(candidate, opts) {
|
|
376
|
-
const ffmpeg = opts.ffmpegPath ?? "ffmpeg";
|
|
377
|
-
const timeout = opts.timeoutMs ?? 8e3;
|
|
378
|
-
const nullSink = os.platform() === "win32" ? "NUL" : "/dev/null";
|
|
379
|
-
const baseArgs = [
|
|
380
|
-
"-hide_banner",
|
|
381
|
-
"-loglevel",
|
|
382
|
-
"error"
|
|
383
|
-
];
|
|
384
|
-
const inArgs = [
|
|
385
|
-
"-f",
|
|
386
|
-
"lavfi",
|
|
387
|
-
"-i",
|
|
388
|
-
"testsrc=duration=0.04:size=16x16:rate=25"
|
|
389
|
-
];
|
|
390
|
-
let outArgs;
|
|
391
|
-
if (candidate.family === "vaapi") outArgs = [
|
|
392
|
-
"-vaapi_device",
|
|
393
|
-
"/dev/dri/renderD128",
|
|
394
|
-
"-vf",
|
|
395
|
-
"format=nv12,hwupload",
|
|
396
|
-
"-c:v",
|
|
397
|
-
candidate.encoder,
|
|
398
|
-
"-frames:v",
|
|
399
|
-
"1",
|
|
400
|
-
"-f",
|
|
401
|
-
"null",
|
|
402
|
-
nullSink
|
|
403
|
-
];
|
|
404
|
-
else outArgs = [
|
|
405
|
-
"-c:v",
|
|
406
|
-
candidate.encoder,
|
|
407
|
-
"-frames:v",
|
|
408
|
-
"1",
|
|
409
|
-
"-f",
|
|
410
|
-
"null",
|
|
411
|
-
nullSink
|
|
412
|
-
];
|
|
413
|
-
try {
|
|
414
|
-
await execFileAsync$1(ffmpeg, [
|
|
415
|
-
...baseArgs,
|
|
416
|
-
...inArgs,
|
|
417
|
-
...outArgs
|
|
418
|
-
], {
|
|
419
|
-
timeout,
|
|
420
|
-
encoding: "utf8"
|
|
421
|
-
});
|
|
422
|
-
return {
|
|
423
|
-
encoder: candidate.encoder,
|
|
424
|
-
codec: candidate.codec,
|
|
425
|
-
family: candidate.family,
|
|
426
|
-
available: true
|
|
427
|
-
};
|
|
428
|
-
} catch (err) {
|
|
429
|
-
return {
|
|
430
|
-
encoder: candidate.encoder,
|
|
431
|
-
codec: candidate.codec,
|
|
432
|
-
family: candidate.family,
|
|
433
|
-
available: false,
|
|
434
|
-
reason: errMsg(err)
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
var HardwareEncoderProber = class {
|
|
439
|
-
cached = null;
|
|
440
|
-
inflight = null;
|
|
441
|
-
logger;
|
|
442
|
-
ffmpegPath;
|
|
443
|
-
constructor(logger, ffmpegPath = "ffmpeg") {
|
|
444
|
-
this.logger = logger;
|
|
445
|
-
this.ffmpegPath = ffmpegPath;
|
|
446
|
-
}
|
|
447
|
-
getCached() {
|
|
448
|
-
return this.cached;
|
|
449
|
-
}
|
|
450
|
-
async probe(hardware, options = {}) {
|
|
451
|
-
if (!options.force && this.cached) return this.cached;
|
|
452
|
-
if (this.inflight) return this.inflight;
|
|
453
|
-
this.inflight = this.runProbe(hardware).then((res) => {
|
|
454
|
-
this.cached = res;
|
|
455
|
-
this.inflight = null;
|
|
456
|
-
return res;
|
|
457
|
-
}).catch((err) => {
|
|
458
|
-
this.inflight = null;
|
|
459
|
-
throw err;
|
|
460
|
-
});
|
|
461
|
-
return this.inflight;
|
|
462
|
-
}
|
|
463
|
-
async runProbe(hardware) {
|
|
464
|
-
const candidates = buildCandidates(hardware);
|
|
465
|
-
const start = Date.now();
|
|
466
|
-
this.logger.info("Probing hardware encoders", { meta: {
|
|
467
|
-
platform: hardware.platform,
|
|
468
|
-
arch: hardware.arch,
|
|
469
|
-
candidates: candidates.length
|
|
470
|
-
} });
|
|
471
|
-
const probes = await Promise.all(candidates.map((c) => runTestEncode(c, { ffmpegPath: this.ffmpegPath })));
|
|
472
|
-
probes.push({
|
|
473
|
-
encoder: "libx264",
|
|
474
|
-
codec: "H264",
|
|
475
|
-
family: "software",
|
|
476
|
-
available: true
|
|
477
|
-
});
|
|
478
|
-
probes.push({
|
|
479
|
-
encoder: "libx265",
|
|
480
|
-
codec: "H265",
|
|
481
|
-
family: "software",
|
|
482
|
-
available: true
|
|
483
|
-
});
|
|
484
|
-
const pickDefault = (codec) => {
|
|
485
|
-
const hw = probes.find((p) => p.codec === codec && p.available && p.family !== "software");
|
|
486
|
-
if (hw) return hw.encoder;
|
|
487
|
-
return codec === "H264" ? "libx264" : "libx265";
|
|
488
|
-
};
|
|
489
|
-
const result = {
|
|
490
|
-
encoders: probes,
|
|
491
|
-
defaultH264: pickDefault("H264"),
|
|
492
|
-
defaultH265: pickDefault("H265"),
|
|
493
|
-
probedAt: Date.now()
|
|
494
|
-
};
|
|
495
|
-
const elapsed = Date.now() - start;
|
|
496
|
-
this.logger.info("Hardware encoder probe complete", { meta: {
|
|
497
|
-
elapsedMs: elapsed,
|
|
498
|
-
defaultH264: result.defaultH264,
|
|
499
|
-
defaultH265: result.defaultH265,
|
|
500
|
-
availableHw: probes.filter((p) => p.available && p.family !== "software").map((p) => p.encoder)
|
|
501
|
-
} });
|
|
502
|
-
return result;
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
//#endregion
|
|
506
|
-
//#region src/builtins/platform-probe/hardware-decode-accel-probe.ts
|
|
507
|
-
var execFileAsync = promisify(execFile);
|
|
508
|
-
var defaultExecFn = async (ffmpegPath, args) => {
|
|
509
|
-
const { stdout } = await execFileAsync(ffmpegPath, [...args], {
|
|
510
|
-
timeout: 8e3,
|
|
511
|
-
encoding: "utf8"
|
|
512
|
-
});
|
|
513
|
-
return { stdout };
|
|
514
|
-
};
|
|
515
|
-
/**
|
|
516
|
-
* Parse the output of `ffmpeg -hwaccels`. The listing looks like:
|
|
517
|
-
*
|
|
518
|
-
* Hardware acceleration methods:
|
|
519
|
-
* videotoolbox
|
|
520
|
-
*
|
|
521
|
-
* The header line is dropped; remaining non-empty trimmed lines are the
|
|
522
|
-
* method names, lowercased and de-duplicated (order preserved).
|
|
523
|
-
*/
|
|
524
|
-
function parseHwAccelsOutput(stdout) {
|
|
525
|
-
const seen = /* @__PURE__ */ new Set();
|
|
526
|
-
const methods = [];
|
|
527
|
-
for (const raw of stdout.split("\n")) {
|
|
528
|
-
const line = raw.trim().toLowerCase();
|
|
529
|
-
if (line.length === 0) continue;
|
|
530
|
-
if (line.endsWith("methods:")) continue;
|
|
531
|
-
if (seen.has(line)) continue;
|
|
532
|
-
seen.add(line);
|
|
533
|
-
methods.push(line);
|
|
534
|
-
}
|
|
535
|
-
return methods;
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Probes which `-hwaccel` decode backends the configured ffmpeg binary
|
|
539
|
-
* supports. Mirrors {@link HardwareEncoderProber} (configured binary, cached,
|
|
540
|
-
* single-flight, forceable). Never throws: a probe failure yields an empty
|
|
541
|
-
* method list so callers fall back to software decode.
|
|
542
|
-
*/
|
|
543
|
-
var HardwareDecodeAccelProber = class {
|
|
544
|
-
cached = null;
|
|
545
|
-
inflight = null;
|
|
546
|
-
logger;
|
|
547
|
-
ffmpegPath;
|
|
548
|
-
execFn;
|
|
549
|
-
constructor(logger, ffmpegPath = "ffmpeg", execFn = defaultExecFn) {
|
|
550
|
-
this.logger = logger;
|
|
551
|
-
this.ffmpegPath = ffmpegPath;
|
|
552
|
-
this.execFn = execFn;
|
|
553
|
-
}
|
|
554
|
-
getCached() {
|
|
555
|
-
return this.cached;
|
|
556
|
-
}
|
|
557
|
-
async probe(options = {}) {
|
|
558
|
-
if (!options.force && this.cached) return this.cached;
|
|
559
|
-
if (this.inflight) return this.inflight;
|
|
560
|
-
this.inflight = this.runProbe().then((res) => {
|
|
561
|
-
this.cached = res;
|
|
562
|
-
this.inflight = null;
|
|
563
|
-
return res;
|
|
564
|
-
}).catch((err) => {
|
|
565
|
-
this.inflight = null;
|
|
566
|
-
throw err;
|
|
567
|
-
});
|
|
568
|
-
return this.inflight;
|
|
569
|
-
}
|
|
570
|
-
async runProbe() {
|
|
571
|
-
try {
|
|
572
|
-
const { stdout } = await this.execFn(this.ffmpegPath, ["-hide_banner", "-hwaccels"]);
|
|
573
|
-
const methods = parseHwAccelsOutput(stdout);
|
|
574
|
-
this.logger.info("Hardware decode-accel probe complete", { meta: { methods } });
|
|
575
|
-
return {
|
|
576
|
-
methods,
|
|
577
|
-
probedAt: Date.now()
|
|
578
|
-
};
|
|
579
|
-
} catch (err) {
|
|
580
|
-
this.logger.warn("Hardware decode-accel probe failed — assuming none available", { meta: { error: errMsg(err) } });
|
|
581
|
-
return {
|
|
582
|
-
methods: [],
|
|
583
|
-
probedAt: Date.now()
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
//#endregion
|
|
589
331
|
//#region src/builtins/platform-probe/index.ts
|
|
590
332
|
/**
|
|
591
333
|
* The decode-hwaccel backends `ctx.kernel.hwaccel.resolve` accepts. The cap
|
|
@@ -617,8 +359,6 @@ function narrowHwAccelPrefer(value) {
|
|
|
617
359
|
}
|
|
618
360
|
var PlatformProbeNativeAddon = class extends BaseAddon {
|
|
619
361
|
scorer = null;
|
|
620
|
-
encoderProber = null;
|
|
621
|
-
decodeAccelProber = null;
|
|
622
362
|
cachedCaps = null;
|
|
623
363
|
/**
|
|
624
364
|
* Per-boot generation stamp for the manual readiness emissions below.
|
|
@@ -666,20 +406,6 @@ var PlatformProbeNativeAddon = class extends BaseAddon {
|
|
|
666
406
|
});
|
|
667
407
|
} catch {}
|
|
668
408
|
}
|
|
669
|
-
/**
|
|
670
|
-
* Resolve the ffmpeg binary the encoder probe should test, from the cluster
|
|
671
|
-
* `ffmpeg` config section (`binaryPath`). The probe MUST exercise the same
|
|
672
|
-
* binary the broker/recorder spawn, or it may report encoders for a different
|
|
673
|
-
* ffmpeg. Defaults to PATH `ffmpeg` on any miss.
|
|
674
|
-
*/
|
|
675
|
-
async resolveFfmpegBinaryPath() {
|
|
676
|
-
try {
|
|
677
|
-
const bp = (await this.ctx.settings?.getSection("ffmpeg") ?? {})["binaryPath"];
|
|
678
|
-
return typeof bp === "string" && bp.trim().length > 0 ? bp : "ffmpeg";
|
|
679
|
-
} catch {
|
|
680
|
-
return "ffmpeg";
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
409
|
async onInitialize() {
|
|
684
410
|
this.emitProbeReadiness("starting");
|
|
685
411
|
const embeddedPython = await this.ctx.deps.ensurePython().catch((err) => {
|
|
@@ -687,9 +413,6 @@ var PlatformProbeNativeAddon = class extends BaseAddon {
|
|
|
687
413
|
return null;
|
|
688
414
|
});
|
|
689
415
|
this.scorer = new PlatformScorer(this.ctx.logger, embeddedPython);
|
|
690
|
-
const ffmpegPath = await this.resolveFfmpegBinaryPath();
|
|
691
|
-
this.encoderProber = new HardwareEncoderProber(this.ctx.logger, ffmpegPath);
|
|
692
|
-
this.decodeAccelProber = new HardwareDecodeAccelProber(this.ctx.logger, ffmpegPath);
|
|
693
416
|
const emitPhase = (phase, payload) => {
|
|
694
417
|
this.ctx.eventBus?.emit({
|
|
695
418
|
id: `platform-probe-${phase}-${Date.now()}`,
|
|
@@ -751,30 +474,6 @@ var PlatformProbeNativeAddon = class extends BaseAddon {
|
|
|
751
474
|
preferred: res.preferred,
|
|
752
475
|
rationale: res.rationale
|
|
753
476
|
};
|
|
754
|
-
},
|
|
755
|
-
getHardwareEncoders: async () => {
|
|
756
|
-
const prober = this.encoderProber;
|
|
757
|
-
if (!prober) throw new Error("Hardware encoder prober not initialized");
|
|
758
|
-
const cached = prober.getCached();
|
|
759
|
-
if (cached) return cached;
|
|
760
|
-
const caps = await getCaps();
|
|
761
|
-
return prober.probe(caps.hardware);
|
|
762
|
-
},
|
|
763
|
-
refreshHardwareEncoders: async () => {
|
|
764
|
-
const prober = this.encoderProber;
|
|
765
|
-
if (!prober) throw new Error("Hardware encoder prober not initialized");
|
|
766
|
-
const caps = await getCaps();
|
|
767
|
-
return prober.probe(caps.hardware, { force: true });
|
|
768
|
-
},
|
|
769
|
-
getHardwareDecodeAccels: async () => {
|
|
770
|
-
const prober = this.decodeAccelProber;
|
|
771
|
-
if (!prober) throw new Error("Hardware decode-accel prober not initialized");
|
|
772
|
-
return prober.probe();
|
|
773
|
-
},
|
|
774
|
-
refreshHardwareDecodeAccels: async () => {
|
|
775
|
-
const prober = this.decodeAccelProber;
|
|
776
|
-
if (!prober) throw new Error("Hardware decode-accel prober not initialized");
|
|
777
|
-
return prober.probe({ force: true });
|
|
778
477
|
}
|
|
779
478
|
}
|
|
780
479
|
}];
|
|
@@ -782,10 +481,8 @@ var PlatformProbeNativeAddon = class extends BaseAddon {
|
|
|
782
481
|
async onShutdown() {
|
|
783
482
|
this.emitProbeReadiness("down");
|
|
784
483
|
this.scorer = null;
|
|
785
|
-
this.encoderProber = null;
|
|
786
|
-
this.decodeAccelProber = null;
|
|
787
484
|
this.cachedCaps = null;
|
|
788
485
|
}
|
|
789
486
|
};
|
|
790
487
|
//#endregion
|
|
791
|
-
export {
|
|
488
|
+
export { InferenceConfigResolver, PlatformProbeNativeAddon, PlatformProbeNativeAddon as default, PlatformScorer };
|
|
@@ -44,8 +44,7 @@ let _camstack_types = require("@camstack/types");
|
|
|
44
44
|
/** Section key → user-facing section title. */
|
|
45
45
|
var SECTION_TITLES = {
|
|
46
46
|
server: "Server",
|
|
47
|
-
auth: "Authentication"
|
|
48
|
-
ffmpeg: "FFmpeg"
|
|
47
|
+
auth: "Authentication"
|
|
49
48
|
};
|
|
50
49
|
/** Keys that map 1:1 to a given yml section — used by `updateGlobalSettings`
|
|
51
50
|
* to figure out which section owns each key in the incoming patch. */
|
|
@@ -53,10 +52,7 @@ var KEY_TO_SECTION = {
|
|
|
53
52
|
port: "server",
|
|
54
53
|
host: "server",
|
|
55
54
|
dataPath: "server",
|
|
56
|
-
tokenExpiry: "auth"
|
|
57
|
-
binaryPath: "ffmpeg",
|
|
58
|
-
hwAccel: "ffmpeg",
|
|
59
|
-
threadCount: "ffmpeg"
|
|
55
|
+
tokenExpiry: "auth"
|
|
60
56
|
};
|
|
61
57
|
var SystemConfigAddon = class extends _camstack_types.BaseAddon {
|
|
62
58
|
id = "system-config";
|
|
@@ -67,119 +63,56 @@ var SystemConfigAddon = class extends _camstack_types.BaseAddon {
|
|
|
67
63
|
this.ctx.logger.info("Initialized — exposes yml-backed sections via getGlobalSettings");
|
|
68
64
|
}
|
|
69
65
|
buildGlobalSchema() {
|
|
70
|
-
return { sections: [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
type: "text",
|
|
86
|
-
key: "port",
|
|
87
|
-
label: "Port",
|
|
88
|
-
description: "Listening port",
|
|
89
|
-
disabled: true
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
type: "text",
|
|
93
|
-
key: "host",
|
|
94
|
-
label: "Host",
|
|
95
|
-
description: "Bind address",
|
|
96
|
-
disabled: true
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
type: "text",
|
|
100
|
-
key: "dataPath",
|
|
101
|
-
label: "Data Path",
|
|
102
|
-
description: "Root data directory",
|
|
103
|
-
disabled: true,
|
|
104
|
-
span: 2
|
|
105
|
-
}
|
|
106
|
-
]
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
id: "system-config-auth",
|
|
110
|
-
title: SECTION_TITLES["auth"],
|
|
111
|
-
description: "Token and session settings.",
|
|
112
|
-
columns: 1,
|
|
113
|
-
fields: [{
|
|
66
|
+
return { sections: [{
|
|
67
|
+
id: "system-config-server",
|
|
68
|
+
title: SECTION_TITLES["server"],
|
|
69
|
+
description: "Core server connection settings — read-only, loaded from config.yaml at bootstrap.",
|
|
70
|
+
columns: 2,
|
|
71
|
+
fields: [
|
|
72
|
+
{
|
|
73
|
+
type: "info",
|
|
74
|
+
key: "server-restart-note",
|
|
75
|
+
label: "Restart required",
|
|
76
|
+
content: "Server settings are read-only. Change them in config.yaml and restart.",
|
|
77
|
+
variant: "warning"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
114
80
|
type: "text",
|
|
115
|
-
key: "
|
|
116
|
-
label: "
|
|
117
|
-
description: "
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
label: "None (CPU only)"
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
value: "videotoolbox",
|
|
154
|
-
label: "VideoToolbox (macOS)"
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
value: "vaapi",
|
|
158
|
-
label: "VA-API (Linux Intel/AMD)"
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
value: "qsv",
|
|
162
|
-
label: "QSV (Intel Quick Sync)"
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
value: "cuda",
|
|
166
|
-
label: "CUDA (NVIDIA)"
|
|
167
|
-
}
|
|
168
|
-
]
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
type: "number",
|
|
172
|
-
key: "threadCount",
|
|
173
|
-
label: "Thread Count",
|
|
174
|
-
description: "0 = auto (let FFmpeg decide)",
|
|
175
|
-
min: 0,
|
|
176
|
-
max: 16,
|
|
177
|
-
step: 1,
|
|
178
|
-
default: 0
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
] };
|
|
81
|
+
key: "port",
|
|
82
|
+
label: "Port",
|
|
83
|
+
description: "Listening port",
|
|
84
|
+
disabled: true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
key: "host",
|
|
89
|
+
label: "Host",
|
|
90
|
+
description: "Bind address",
|
|
91
|
+
disabled: true
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "text",
|
|
95
|
+
key: "dataPath",
|
|
96
|
+
label: "Data Path",
|
|
97
|
+
description: "Root data directory",
|
|
98
|
+
disabled: true,
|
|
99
|
+
span: 2
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}, {
|
|
103
|
+
id: "system-config-auth",
|
|
104
|
+
title: SECTION_TITLES["auth"],
|
|
105
|
+
description: "Token and session settings.",
|
|
106
|
+
columns: 1,
|
|
107
|
+
fields: [{
|
|
108
|
+
type: "text",
|
|
109
|
+
key: "tokenExpiry",
|
|
110
|
+
label: "Token Expiry",
|
|
111
|
+
description: "JWT token lifetime (e.g. 24h, 7d, 1h)",
|
|
112
|
+
placeholder: "24h",
|
|
113
|
+
default: "24h"
|
|
114
|
+
}]
|
|
115
|
+
}] };
|
|
183
116
|
}
|
|
184
117
|
async getGlobalSettings() {
|
|
185
118
|
const schema = this.buildGlobalSchema();
|