@camstack/addon-pipeline 1.2.306 → 1.2.308
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/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-CeYzI_ws.js → default-detection-model-2rHeRf0Y.js} +1 -1
- package/dist/{default-detection-model-CmdkU7Ik.mjs → default-detection-model-DATyir0t.mjs} +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-C2GsZSso.js → dist-CdRsXAov.js} +196 -1
- package/dist/{dist-B0mdAGsn.mjs → dist-jy-7xoW5.mjs} +173 -2
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-C4FxPOmq.mjs → node-NCge1qZM.mjs} +1 -53
- package/dist/{node-DA9sHeo7.js → node-pWHpwpBa.js} +1 -53
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-DtcP6dwa.mjs → process-memory-4K7QaQab.mjs} +1 -1
- package/dist/{process-memory-CT7jffcr.js → process-memory-C4ayPnww.js} +1 -1
- package/dist/recorder/index.js +82 -15
- package/dist/recorder/index.mjs +82 -15
- package/dist/{segment-demux-js-C6Z9t7EK.js → segment-demux-js-0bM2Anej.js} +22 -4
- package/dist/{segment-demux-js-CDDu7U5-.mjs → segment-demux-js-BaI2VdPX.mjs} +17 -5
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BRWrphBU.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CkxD__Uy.mjs} +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DEPTgXS_.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-CdU3jJMF.mjs → hostInit-D56Gr-hD.mjs} +1 -1
- package/dist/stream-broker/index.js +401 -242
- package/dist/stream-broker/index.mjs +398 -239
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DKNdMa3E.mjs +0 -26
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_process_memory = require("../process-memory-
|
|
6
|
+
const require_dist = require("../dist-CdRsXAov.js");
|
|
7
|
+
const require_process_memory = require("../process-memory-C4ayPnww.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
10
10
|
let node_path = require("node:path");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { A as PoolMemoryWatchdog,
|
|
3
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
2
|
+
import { A as PoolMemoryWatchdog, Gt as resolvePoolMemoryPolicy, Hn as EventCategory, T as HF_BASE_URL, Tn as nodePin, U as audioAnalysisCapability, W as audioAnalyzerCapability, bn as expandAudioChunkToF32le, dn as errMsg, gn as audioChunkBytesPerSample, n as AUDIO_BACKEND_CHOICES, p as DEFAULT_AUDIO_ANALYZER_CONFIG, pn as BaseAddon, xn as hydrateSchema, xt as mapAudioLabelToMacro } from "../dist-jy-7xoW5.mjs";
|
|
3
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-4K7QaQab.mjs";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
import { downloadFile } from "@camstack/system/addon-utils";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_dist = require("./dist-
|
|
1
|
+
const require_dist = require("./dist-CdRsXAov.js");
|
|
2
2
|
let node_crypto = require("node:crypto");
|
|
3
3
|
//#region src/detection-pipeline/registry/effective-confidence.ts
|
|
4
4
|
/** The default in force: the model's own floor, else the step's. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as audioKindId,
|
|
1
|
+
import { G as audioKindId, Jt as stepModelRunsOnFormat, _t as inferModelProvider, d as COCO_80_LABELS, f as COCO_TO_MACRO, gt as hfModelUrl, r as AUDIO_MACRO_LABELS, v as DEVICE_BACKEND_TO_FORMAT } from "./dist-jy-7xoW5.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
//#region src/detection-pipeline/registry/effective-confidence.ts
|
|
4
4
|
/** The default in force: the model's own floor, else the step's. */
|
|
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
8
|
-
const require_default_detection_model = require("../default-detection-model-
|
|
6
|
+
const require_dist = require("../dist-CdRsXAov.js");
|
|
7
|
+
const require_node = require("../node-pWHpwpBa.js");
|
|
8
|
+
const require_default_detection_model = require("../default-detection-model-2rHeRf0Y.js");
|
|
9
9
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
10
|
-
const require_process_memory = require("../process-memory-
|
|
10
|
+
const require_process_memory = require("../process-memory-C4ayPnww.js");
|
|
11
11
|
let node_os = require("node:os");
|
|
12
12
|
node_os = require_chunk.__toESM(node_os);
|
|
13
13
|
let node_fs = require("node:fs");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { $ as defaultDeviceFor$1,
|
|
3
|
-
import { s as readProcessCost } from "../node-
|
|
4
|
-
import { _ as resolveModelForFormat, a as buildSubtreeCanRun, b as resolveModelDefaultConfidence, c as macroGateVerdict, f as ALL_PIPELINE_STEPS, g as getStepDefinition, h as getStep, i as mergeCustomModels, m as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as ALL_STEPS, r as CustomModelCatalog, u as localFrameRegistry, y as landmarkPrecisionVerdict } from "../default-detection-model-
|
|
2
|
+
import { $ as defaultDeviceFor$1, $t as supportedRuntimes$1, A as PoolMemoryWatchdog, Bn as string, En as parseJsonUnknown, Gt as resolvePoolMemoryPolicy, Hn as EventCategory, Jt as stepModelRunsOnFormat, Ln as object, Pt as pipelineExecutorCapability, Tn as nodePin, V as YAMNET_TO_MACRO, Vn as union, Vt as resolveClusterStepModelId, _n as createEvent, dn as errMsg, jn as array, jt as pickClusterStepSettings, kn as sleep, kt as overlayClusterStepSettings, m as DEFAULT_CLUSTER_STEP_SETTINGS, ot as enumerateInferenceDevices, pn as BaseAddon, qt as runtimeDevices$1, rt as detectionPipelineCapability, st as evaluateZoneRules, t as APPLE_SA_TO_MACRO, u as CLUSTER_MODEL_SCOPED_STEPS, v as DEVICE_BACKEND_TO_FORMAT, xn as hydrateSchema, yt as loadContributionCapability } from "../dist-jy-7xoW5.mjs";
|
|
3
|
+
import { s as readProcessCost } from "../node-NCge1qZM.mjs";
|
|
4
|
+
import { _ as resolveModelForFormat, a as buildSubtreeCanRun, b as resolveModelDefaultConfidence, c as macroGateVerdict, f as ALL_PIPELINE_STEPS, g as getStepDefinition, h as getStep, i as mergeCustomModels, m as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as ALL_STEPS, r as CustomModelCatalog, u as localFrameRegistry, y as landmarkPrecisionVerdict } from "../default-detection-model-DATyir0t.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
6
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
6
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-4K7QaQab.mjs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path$1 from "node:path";
|
|
@@ -7684,7 +7684,7 @@ function errMsg(err) {
|
|
|
7684
7684
|
return String(err);
|
|
7685
7685
|
}
|
|
7686
7686
|
//#endregion
|
|
7687
|
-
//#region ../types/dist/composition-
|
|
7687
|
+
//#region ../types/dist/composition-Dq42XWp0.mjs
|
|
7688
7688
|
var AUDIO_ENCODER_BY_CODEC = {
|
|
7689
7689
|
opus: "libopus",
|
|
7690
7690
|
aac: "aac",
|
|
@@ -8393,6 +8393,58 @@ function replaceWithSortedKeys(_key, value) {
|
|
|
8393
8393
|
return value;
|
|
8394
8394
|
}
|
|
8395
8395
|
/**
|
|
8396
|
+
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
8397
|
+
*
|
|
8398
|
+
* ## Why a download, on every node, including the container
|
|
8399
|
+
*
|
|
8400
|
+
* The version of ffmpeg used to be CODE: baked into the image, changeable only
|
|
8401
|
+
* by rebuilding and rolling it. Now it is DATA — this file states the release,
|
|
8402
|
+
* and a node that boots with a different one downloads it. Changing ffmpeg is a
|
|
8403
|
+
* framework bump, not an image rebuild.
|
|
8404
|
+
*
|
|
8405
|
+
* It also settles the licence question by removing it. FFmpeg's licence is
|
|
8406
|
+
* chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
|
|
8407
|
+
* (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
|
|
8408
|
+
* makes the result undistributable. These are GPL v3 builds, and we do not
|
|
8409
|
+
* distribute them: the user's own node fetches the archive from the project
|
|
8410
|
+
* that publishes it, so we convey no GPL work and inherit none of the
|
|
8411
|
+
* obligations that come with conveying one. That is the whole reason the image
|
|
8412
|
+
* no longer installs ffmpeg at all — not a technical preference.
|
|
8413
|
+
*
|
|
8414
|
+
* ## Why jellyfin-ffmpeg
|
|
8415
|
+
*
|
|
8416
|
+
* One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
|
|
8417
|
+
* from one release, with the widest hardware surface of anything maintained:
|
|
8418
|
+
* VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
|
|
8419
|
+
* MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
|
|
8420
|
+
* so the binary is not linked against a driver stack it may not find.
|
|
8421
|
+
*
|
|
8422
|
+
* **What it still needs from the host, measured 2026-09-19.** On the Unraid
|
|
8423
|
+
* HOST, which has no libva, `-vaapi_device` aborts the process outright —
|
|
8424
|
+
* `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
|
|
8425
|
+
* "Assertion in generated code"' failed`. Inside our container, which installs
|
|
8426
|
+
* libva plus the iHD driver, the same command encodes: verified end to end with
|
|
8427
|
+
* `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
|
|
8428
|
+
* KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
|
|
8429
|
+
*
|
|
8430
|
+
* ## Why each artifact carries a checksum and a claim
|
|
8431
|
+
*
|
|
8432
|
+
* The download is now the only source, so `sha256` is verified before anything
|
|
8433
|
+
* is extracted — jellyfin publishes no checksums on its release assets, so
|
|
8434
|
+
* these are ours, taken at the version bump. And `expectedHwaccels` is what the
|
|
8435
|
+
* build is supposed to be able to do: a binary that probes short of its own
|
|
8436
|
+
* claim is a source that changed under us, which is precisely what went
|
|
8437
|
+
* unnoticed for months when a static build turned out to carry no VAAPI at all
|
|
8438
|
+
* (D536).
|
|
8439
|
+
*/
|
|
8440
|
+
/** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
|
|
8441
|
+
var FFMPEG_RELEASE = "8.1.2-5";
|
|
8442
|
+
var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
|
|
8443
|
+
function portable(target) {
|
|
8444
|
+
return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
|
|
8445
|
+
}
|
|
8446
|
+
portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
|
|
8447
|
+
/**
|
|
8396
8448
|
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
8397
8449
|
* valutatore puro del suo FRONTE.
|
|
8398
8450
|
*
|
|
@@ -9606,6 +9658,90 @@ async function resolveEgressDecodeHwAccel(deps) {
|
|
|
9606
9658
|
return resolved;
|
|
9607
9659
|
}
|
|
9608
9660
|
/**
|
|
9661
|
+
* The notification GIF's filter chain and argv — ONE definition for every
|
|
9662
|
+
* process that derives a gif from a notification video.
|
|
9663
|
+
*
|
|
9664
|
+
* ## Why this lives in `@camstack/types`
|
|
9665
|
+
*
|
|
9666
|
+
* Two addons derive a notification gif from an mp4, and they may never import
|
|
9667
|
+
* each other:
|
|
9668
|
+
*
|
|
9669
|
+
* - the stream broker (`addon-pipeline`), from the clip it just cut out of its
|
|
9670
|
+
* own ring (`gif-from-mp4.ts`);
|
|
9671
|
+
* - the notification centre (`addon-post-analysis`), from a clip cut out of
|
|
9672
|
+
* the RECORDINGS when the moment is older than any ring holds
|
|
9673
|
+
* ([D682](../../../../docs/decisions/adr-0682-a-pick-ups-footage-is-cut-around-the-pick-up.md)).
|
|
9674
|
+
*
|
|
9675
|
+
* Two copies of this chain would be two gifs of the same kind of moment that
|
|
9676
|
+
* look different depending on how old the moment was — the class of defect
|
|
9677
|
+
* D85/D91 already paid for once. So the chain is here, pure and dependency
|
|
9678
|
+
* free, and each caller owns only its own process lifecycle.
|
|
9679
|
+
*
|
|
9680
|
+
* ## The palette was never the problem
|
|
9681
|
+
*
|
|
9682
|
+
* The `split` + `palettegen(stats_mode=diff)` + `paletteuse(bayer)` pair is
|
|
9683
|
+
* right and stays. The measured cause of "the gif looks de-pixellated" was a
|
|
9684
|
+
* 640×360 substream scaled DOWN to 480 px, upstream of the palette. The `split`
|
|
9685
|
+
* form is a single READ of the input, not a lesser two-pass.
|
|
9686
|
+
*/
|
|
9687
|
+
/**
|
|
9688
|
+
* Format a frame rate for an ffmpeg filter argument.
|
|
9689
|
+
*
|
|
9690
|
+
* `12 / 4` must reach the filter as `3` and `15 / 4` as `3.75`, not as a float
|
|
9691
|
+
* tail that `av_parse_video_rate` then has to guess at.
|
|
9692
|
+
*/
|
|
9693
|
+
function ffmpegRate(rate) {
|
|
9694
|
+
return String(Math.round(rate * 1e3) / 1e3);
|
|
9695
|
+
}
|
|
9696
|
+
/**
|
|
9697
|
+
* The factor the derive must apply ON TOP of the speed the mp4 already carries.
|
|
9698
|
+
*
|
|
9699
|
+
* The gif is derived from a muxed mp4, so if that mux already ran `setpts` at
|
|
9700
|
+
* the caller's `speed`, applying the absolute gif rate again would compound
|
|
9701
|
+
* them — a 4× gif beside a 2× video would come out at 8×. Discounting here is
|
|
9702
|
+
* what keeps the gif rate ABSOLUTE against real time whatever the video does.
|
|
9703
|
+
*/
|
|
9704
|
+
function gifDeriveSpeed(gifSpeed, videoSpeed) {
|
|
9705
|
+
if (!Number.isFinite(videoSpeed) || videoSpeed <= 0) return gifSpeed;
|
|
9706
|
+
return gifSpeed / videoSpeed;
|
|
9707
|
+
}
|
|
9708
|
+
/**
|
|
9709
|
+
* The filter chain. Extracted so a test can assert the palette pair without
|
|
9710
|
+
* spawning ffmpeg — an argv test cannot see quality, but it can see a filter
|
|
9711
|
+
* that somebody deleted.
|
|
9712
|
+
*
|
|
9713
|
+
* `fps` BEFORE `setpts`: decimating an already sped-up timeline yields `speed`
|
|
9714
|
+
* times the frames asked for, and a gif pays for every one. So the decimation
|
|
9715
|
+
* runs at `fps / speed` in SOURCE time and `setpts` multiplies what survived.
|
|
9716
|
+
* The trailing `fps` re-declares the rate, which is NOT redundant: `setpts`
|
|
9717
|
+
* leaves the filter link advertising the sampled rate and the gif muxer
|
|
9718
|
+
* believes the link — measured on 615 as a 3.01 s gif of ten frames.
|
|
9719
|
+
*/
|
|
9720
|
+
function gifFilterComplex(input) {
|
|
9721
|
+
const speed = input.speed > 0 ? input.speed : 1;
|
|
9722
|
+
if (speed === 1) return `[0:v]fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
9723
|
+
return `[0:v]fps=${ffmpegRate(input.fps / speed)},setpts=PTS/${speed},fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
9724
|
+
}
|
|
9725
|
+
/** The full argv: mp4 in, gif on stdout. */
|
|
9726
|
+
function gifFromMp4Args(input) {
|
|
9727
|
+
return [
|
|
9728
|
+
"-y",
|
|
9729
|
+
"-v",
|
|
9730
|
+
"error",
|
|
9731
|
+
"-f",
|
|
9732
|
+
"mp4",
|
|
9733
|
+
"-i",
|
|
9734
|
+
input.input ?? "pipe:0",
|
|
9735
|
+
"-filter_complex",
|
|
9736
|
+
gifFilterComplex(input),
|
|
9737
|
+
"-loop",
|
|
9738
|
+
"0",
|
|
9739
|
+
"-f",
|
|
9740
|
+
"gif",
|
|
9741
|
+
"pipe:1"
|
|
9742
|
+
];
|
|
9743
|
+
}
|
|
9744
|
+
/**
|
|
9609
9745
|
* The sharing key for `streamBroker.acquireEgressTranscode`.
|
|
9610
9746
|
*
|
|
9611
9747
|
* **Two requesters asking for exactly the same argument set reach the same
|
|
@@ -9693,6 +9829,26 @@ function egressTranscodeSharingKey(request, delivery = egressTransportFromReques
|
|
|
9693
9829
|
return `egress:${canonicalHash(canonicalEgressPlan(request, delivery))}`;
|
|
9694
9830
|
}
|
|
9695
9831
|
/**
|
|
9832
|
+
* Memoize `ensure` on SUCCESS only.
|
|
9833
|
+
*
|
|
9834
|
+
* A failure is not remembered: the next caller asks again, so a node whose
|
|
9835
|
+
* first download failed (no network at boot) recovers on the next spawn that
|
|
9836
|
+
* needs it — with no timer and no retry loop, because the ask is driven by
|
|
9837
|
+
* work that actually wants the binary.
|
|
9838
|
+
*/
|
|
9839
|
+
function memoizePinnedFfmpeg(ensure) {
|
|
9840
|
+
let pending = null;
|
|
9841
|
+
return () => {
|
|
9842
|
+
if (pending !== null) return pending;
|
|
9843
|
+
const attempt = ensure();
|
|
9844
|
+
pending = attempt;
|
|
9845
|
+
attempt.catch(() => {
|
|
9846
|
+
if (pending === attempt) pending = null;
|
|
9847
|
+
});
|
|
9848
|
+
return attempt;
|
|
9849
|
+
};
|
|
9850
|
+
}
|
|
9851
|
+
/**
|
|
9696
9852
|
* Deep wiring healthcheck — snapshot of active reachability probes across
|
|
9697
9853
|
* every declared capability + widget of every installed plugin, on every
|
|
9698
9854
|
* node. Produced by the backend `WiringHealthService` and surfaced via
|
|
@@ -24009,6 +24165,21 @@ var ObjectEventSchema = object({
|
|
|
24009
24165
|
keyFrameMediaId: number().int().optional(),
|
|
24010
24166
|
/** Populated by B5 (recording playback URL for this event). */
|
|
24011
24167
|
mediaUrl: string().optional(),
|
|
24168
|
+
/**
|
|
24169
|
+
* When the change this row records HAPPENED, epoch ms, when that is earlier
|
|
24170
|
+
* than {@link timestamp} and the producer could date it (D684).
|
|
24171
|
+
*
|
|
24172
|
+
* Set on a package PICK-UP row only: its `timestamp` is when the departure
|
|
24173
|
+
* was CONCLUDED — five minutes of unseen observed time after the parcel went
|
|
24174
|
+
* — and a notification's footage has to be cut around the pick-up, not the
|
|
24175
|
+
* empty doormat after it (D682). Stored so every path that rebuilds a
|
|
24176
|
+
* notification subject from the row (the boot reconcile, a dry-run) anchors
|
|
24177
|
+
* on the same instant the live hook did.
|
|
24178
|
+
*
|
|
24179
|
+
* `timestamp` keeps its meaning. Absent on every other row and on every
|
|
24180
|
+
* pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
|
|
24181
|
+
*/
|
|
24182
|
+
observedAt: number().optional(),
|
|
24012
24183
|
/** The parent track's key-event importance [0,1], propagated to every object
|
|
24013
24184
|
* event of the track (so an event row can be sorted by importance without a
|
|
24014
24185
|
* track join). Absent on legacy rows / before the track was scored. */
|
|
@@ -52655,12 +52826,30 @@ Object.defineProperty(exports, "failureContributionCapability", {
|
|
|
52655
52826
|
return failureContributionCapability;
|
|
52656
52827
|
}
|
|
52657
52828
|
});
|
|
52829
|
+
Object.defineProperty(exports, "ffmpegRate", {
|
|
52830
|
+
enumerable: true,
|
|
52831
|
+
get: function() {
|
|
52832
|
+
return ffmpegRate;
|
|
52833
|
+
}
|
|
52834
|
+
});
|
|
52658
52835
|
Object.defineProperty(exports, "gbToBytes", {
|
|
52659
52836
|
enumerable: true,
|
|
52660
52837
|
get: function() {
|
|
52661
52838
|
return gbToBytes;
|
|
52662
52839
|
}
|
|
52663
52840
|
});
|
|
52841
|
+
Object.defineProperty(exports, "gifDeriveSpeed", {
|
|
52842
|
+
enumerable: true,
|
|
52843
|
+
get: function() {
|
|
52844
|
+
return gifDeriveSpeed;
|
|
52845
|
+
}
|
|
52846
|
+
});
|
|
52847
|
+
Object.defineProperty(exports, "gifFromMp4Args", {
|
|
52848
|
+
enumerable: true,
|
|
52849
|
+
get: function() {
|
|
52850
|
+
return gifFromMp4Args;
|
|
52851
|
+
}
|
|
52852
|
+
});
|
|
52664
52853
|
Object.defineProperty(exports, "hfModelUrl", {
|
|
52665
52854
|
enumerable: true,
|
|
52666
52855
|
get: function() {
|
|
@@ -52763,6 +52952,12 @@ Object.defineProperty(exports, "mayWriteToLocation", {
|
|
|
52763
52952
|
return mayWriteToLocation;
|
|
52764
52953
|
}
|
|
52765
52954
|
});
|
|
52955
|
+
Object.defineProperty(exports, "memoizePinnedFfmpeg", {
|
|
52956
|
+
enumerable: true,
|
|
52957
|
+
get: function() {
|
|
52958
|
+
return memoizePinnedFfmpeg;
|
|
52959
|
+
}
|
|
52960
|
+
});
|
|
52766
52961
|
Object.defineProperty(exports, "migrateLegacyDeviceSignalConfig", {
|
|
52767
52962
|
enumerable: true,
|
|
52768
52963
|
get: function() {
|
|
@@ -7684,7 +7684,7 @@ function errMsg(err) {
|
|
|
7684
7684
|
return String(err);
|
|
7685
7685
|
}
|
|
7686
7686
|
//#endregion
|
|
7687
|
-
//#region ../types/dist/composition-
|
|
7687
|
+
//#region ../types/dist/composition-Dq42XWp0.mjs
|
|
7688
7688
|
var AUDIO_ENCODER_BY_CODEC = {
|
|
7689
7689
|
opus: "libopus",
|
|
7690
7690
|
aac: "aac",
|
|
@@ -8393,6 +8393,58 @@ function replaceWithSortedKeys(_key, value) {
|
|
|
8393
8393
|
return value;
|
|
8394
8394
|
}
|
|
8395
8395
|
/**
|
|
8396
|
+
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
8397
|
+
*
|
|
8398
|
+
* ## Why a download, on every node, including the container
|
|
8399
|
+
*
|
|
8400
|
+
* The version of ffmpeg used to be CODE: baked into the image, changeable only
|
|
8401
|
+
* by rebuilding and rolling it. Now it is DATA — this file states the release,
|
|
8402
|
+
* and a node that boots with a different one downloads it. Changing ffmpeg is a
|
|
8403
|
+
* framework bump, not an image rebuild.
|
|
8404
|
+
*
|
|
8405
|
+
* It also settles the licence question by removing it. FFmpeg's licence is
|
|
8406
|
+
* chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
|
|
8407
|
+
* (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
|
|
8408
|
+
* makes the result undistributable. These are GPL v3 builds, and we do not
|
|
8409
|
+
* distribute them: the user's own node fetches the archive from the project
|
|
8410
|
+
* that publishes it, so we convey no GPL work and inherit none of the
|
|
8411
|
+
* obligations that come with conveying one. That is the whole reason the image
|
|
8412
|
+
* no longer installs ffmpeg at all — not a technical preference.
|
|
8413
|
+
*
|
|
8414
|
+
* ## Why jellyfin-ffmpeg
|
|
8415
|
+
*
|
|
8416
|
+
* One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
|
|
8417
|
+
* from one release, with the widest hardware surface of anything maintained:
|
|
8418
|
+
* VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
|
|
8419
|
+
* MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
|
|
8420
|
+
* so the binary is not linked against a driver stack it may not find.
|
|
8421
|
+
*
|
|
8422
|
+
* **What it still needs from the host, measured 2026-09-19.** On the Unraid
|
|
8423
|
+
* HOST, which has no libva, `-vaapi_device` aborts the process outright —
|
|
8424
|
+
* `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
|
|
8425
|
+
* "Assertion in generated code"' failed`. Inside our container, which installs
|
|
8426
|
+
* libva plus the iHD driver, the same command encodes: verified end to end with
|
|
8427
|
+
* `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
|
|
8428
|
+
* KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
|
|
8429
|
+
*
|
|
8430
|
+
* ## Why each artifact carries a checksum and a claim
|
|
8431
|
+
*
|
|
8432
|
+
* The download is now the only source, so `sha256` is verified before anything
|
|
8433
|
+
* is extracted — jellyfin publishes no checksums on its release assets, so
|
|
8434
|
+
* these are ours, taken at the version bump. And `expectedHwaccels` is what the
|
|
8435
|
+
* build is supposed to be able to do: a binary that probes short of its own
|
|
8436
|
+
* claim is a source that changed under us, which is precisely what went
|
|
8437
|
+
* unnoticed for months when a static build turned out to carry no VAAPI at all
|
|
8438
|
+
* (D536).
|
|
8439
|
+
*/
|
|
8440
|
+
/** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
|
|
8441
|
+
var FFMPEG_RELEASE = "8.1.2-5";
|
|
8442
|
+
var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
|
|
8443
|
+
function portable(target) {
|
|
8444
|
+
return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
|
|
8445
|
+
}
|
|
8446
|
+
portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
|
|
8447
|
+
/**
|
|
8396
8448
|
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
8397
8449
|
* valutatore puro del suo FRONTE.
|
|
8398
8450
|
*
|
|
@@ -9606,6 +9658,90 @@ async function resolveEgressDecodeHwAccel(deps) {
|
|
|
9606
9658
|
return resolved;
|
|
9607
9659
|
}
|
|
9608
9660
|
/**
|
|
9661
|
+
* The notification GIF's filter chain and argv — ONE definition for every
|
|
9662
|
+
* process that derives a gif from a notification video.
|
|
9663
|
+
*
|
|
9664
|
+
* ## Why this lives in `@camstack/types`
|
|
9665
|
+
*
|
|
9666
|
+
* Two addons derive a notification gif from an mp4, and they may never import
|
|
9667
|
+
* each other:
|
|
9668
|
+
*
|
|
9669
|
+
* - the stream broker (`addon-pipeline`), from the clip it just cut out of its
|
|
9670
|
+
* own ring (`gif-from-mp4.ts`);
|
|
9671
|
+
* - the notification centre (`addon-post-analysis`), from a clip cut out of
|
|
9672
|
+
* the RECORDINGS when the moment is older than any ring holds
|
|
9673
|
+
* ([D682](../../../../docs/decisions/adr-0682-a-pick-ups-footage-is-cut-around-the-pick-up.md)).
|
|
9674
|
+
*
|
|
9675
|
+
* Two copies of this chain would be two gifs of the same kind of moment that
|
|
9676
|
+
* look different depending on how old the moment was — the class of defect
|
|
9677
|
+
* D85/D91 already paid for once. So the chain is here, pure and dependency
|
|
9678
|
+
* free, and each caller owns only its own process lifecycle.
|
|
9679
|
+
*
|
|
9680
|
+
* ## The palette was never the problem
|
|
9681
|
+
*
|
|
9682
|
+
* The `split` + `palettegen(stats_mode=diff)` + `paletteuse(bayer)` pair is
|
|
9683
|
+
* right and stays. The measured cause of "the gif looks de-pixellated" was a
|
|
9684
|
+
* 640×360 substream scaled DOWN to 480 px, upstream of the palette. The `split`
|
|
9685
|
+
* form is a single READ of the input, not a lesser two-pass.
|
|
9686
|
+
*/
|
|
9687
|
+
/**
|
|
9688
|
+
* Format a frame rate for an ffmpeg filter argument.
|
|
9689
|
+
*
|
|
9690
|
+
* `12 / 4` must reach the filter as `3` and `15 / 4` as `3.75`, not as a float
|
|
9691
|
+
* tail that `av_parse_video_rate` then has to guess at.
|
|
9692
|
+
*/
|
|
9693
|
+
function ffmpegRate(rate) {
|
|
9694
|
+
return String(Math.round(rate * 1e3) / 1e3);
|
|
9695
|
+
}
|
|
9696
|
+
/**
|
|
9697
|
+
* The factor the derive must apply ON TOP of the speed the mp4 already carries.
|
|
9698
|
+
*
|
|
9699
|
+
* The gif is derived from a muxed mp4, so if that mux already ran `setpts` at
|
|
9700
|
+
* the caller's `speed`, applying the absolute gif rate again would compound
|
|
9701
|
+
* them — a 4× gif beside a 2× video would come out at 8×. Discounting here is
|
|
9702
|
+
* what keeps the gif rate ABSOLUTE against real time whatever the video does.
|
|
9703
|
+
*/
|
|
9704
|
+
function gifDeriveSpeed(gifSpeed, videoSpeed) {
|
|
9705
|
+
if (!Number.isFinite(videoSpeed) || videoSpeed <= 0) return gifSpeed;
|
|
9706
|
+
return gifSpeed / videoSpeed;
|
|
9707
|
+
}
|
|
9708
|
+
/**
|
|
9709
|
+
* The filter chain. Extracted so a test can assert the palette pair without
|
|
9710
|
+
* spawning ffmpeg — an argv test cannot see quality, but it can see a filter
|
|
9711
|
+
* that somebody deleted.
|
|
9712
|
+
*
|
|
9713
|
+
* `fps` BEFORE `setpts`: decimating an already sped-up timeline yields `speed`
|
|
9714
|
+
* times the frames asked for, and a gif pays for every one. So the decimation
|
|
9715
|
+
* runs at `fps / speed` in SOURCE time and `setpts` multiplies what survived.
|
|
9716
|
+
* The trailing `fps` re-declares the rate, which is NOT redundant: `setpts`
|
|
9717
|
+
* leaves the filter link advertising the sampled rate and the gif muxer
|
|
9718
|
+
* believes the link — measured on 615 as a 3.01 s gif of ten frames.
|
|
9719
|
+
*/
|
|
9720
|
+
function gifFilterComplex(input) {
|
|
9721
|
+
const speed = input.speed > 0 ? input.speed : 1;
|
|
9722
|
+
if (speed === 1) return `[0:v]fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
9723
|
+
return `[0:v]fps=${ffmpegRate(input.fps / speed)},setpts=PTS/${speed},fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
9724
|
+
}
|
|
9725
|
+
/** The full argv: mp4 in, gif on stdout. */
|
|
9726
|
+
function gifFromMp4Args(input) {
|
|
9727
|
+
return [
|
|
9728
|
+
"-y",
|
|
9729
|
+
"-v",
|
|
9730
|
+
"error",
|
|
9731
|
+
"-f",
|
|
9732
|
+
"mp4",
|
|
9733
|
+
"-i",
|
|
9734
|
+
input.input ?? "pipe:0",
|
|
9735
|
+
"-filter_complex",
|
|
9736
|
+
gifFilterComplex(input),
|
|
9737
|
+
"-loop",
|
|
9738
|
+
"0",
|
|
9739
|
+
"-f",
|
|
9740
|
+
"gif",
|
|
9741
|
+
"pipe:1"
|
|
9742
|
+
];
|
|
9743
|
+
}
|
|
9744
|
+
/**
|
|
9609
9745
|
* The sharing key for `streamBroker.acquireEgressTranscode`.
|
|
9610
9746
|
*
|
|
9611
9747
|
* **Two requesters asking for exactly the same argument set reach the same
|
|
@@ -9693,6 +9829,26 @@ function egressTranscodeSharingKey(request, delivery = egressTransportFromReques
|
|
|
9693
9829
|
return `egress:${canonicalHash(canonicalEgressPlan(request, delivery))}`;
|
|
9694
9830
|
}
|
|
9695
9831
|
/**
|
|
9832
|
+
* Memoize `ensure` on SUCCESS only.
|
|
9833
|
+
*
|
|
9834
|
+
* A failure is not remembered: the next caller asks again, so a node whose
|
|
9835
|
+
* first download failed (no network at boot) recovers on the next spawn that
|
|
9836
|
+
* needs it — with no timer and no retry loop, because the ask is driven by
|
|
9837
|
+
* work that actually wants the binary.
|
|
9838
|
+
*/
|
|
9839
|
+
function memoizePinnedFfmpeg(ensure) {
|
|
9840
|
+
let pending = null;
|
|
9841
|
+
return () => {
|
|
9842
|
+
if (pending !== null) return pending;
|
|
9843
|
+
const attempt = ensure();
|
|
9844
|
+
pending = attempt;
|
|
9845
|
+
attempt.catch(() => {
|
|
9846
|
+
if (pending === attempt) pending = null;
|
|
9847
|
+
});
|
|
9848
|
+
return attempt;
|
|
9849
|
+
};
|
|
9850
|
+
}
|
|
9851
|
+
/**
|
|
9696
9852
|
* Deep wiring healthcheck — snapshot of active reachability probes across
|
|
9697
9853
|
* every declared capability + widget of every installed plugin, on every
|
|
9698
9854
|
* node. Produced by the backend `WiringHealthService` and surfaced via
|
|
@@ -24009,6 +24165,21 @@ var ObjectEventSchema = object({
|
|
|
24009
24165
|
keyFrameMediaId: number().int().optional(),
|
|
24010
24166
|
/** Populated by B5 (recording playback URL for this event). */
|
|
24011
24167
|
mediaUrl: string().optional(),
|
|
24168
|
+
/**
|
|
24169
|
+
* When the change this row records HAPPENED, epoch ms, when that is earlier
|
|
24170
|
+
* than {@link timestamp} and the producer could date it (D684).
|
|
24171
|
+
*
|
|
24172
|
+
* Set on a package PICK-UP row only: its `timestamp` is when the departure
|
|
24173
|
+
* was CONCLUDED — five minutes of unseen observed time after the parcel went
|
|
24174
|
+
* — and a notification's footage has to be cut around the pick-up, not the
|
|
24175
|
+
* empty doormat after it (D682). Stored so every path that rebuilds a
|
|
24176
|
+
* notification subject from the row (the boot reconcile, a dry-run) anchors
|
|
24177
|
+
* on the same instant the live hook did.
|
|
24178
|
+
*
|
|
24179
|
+
* `timestamp` keeps its meaning. Absent on every other row and on every
|
|
24180
|
+
* pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
|
|
24181
|
+
*/
|
|
24182
|
+
observedAt: number().optional(),
|
|
24012
24183
|
/** The parent track's key-event importance [0,1], propagated to every object
|
|
24013
24184
|
* event of the track (so an event row can be sorted by importance without a
|
|
24014
24185
|
* track join). Absent on legacy rows / before the track was scored. */
|
|
@@ -52133,4 +52304,4 @@ function enumerateInferenceDevices(hw) {
|
|
|
52133
52304
|
return out;
|
|
52134
52305
|
}
|
|
52135
52306
|
//#endregion
|
|
52136
|
-
export { defaultDeviceFor as $,
|
|
52307
|
+
export { defaultDeviceFor as $, supportedRuntimes as $t, PoolMemoryWatchdog as A, _enum as An, parseProcStatus as At, VISIT_MERGE_GAP_MS as B, string as Bn, recordingSignalCapability as Bt, FailureCounters as C, makeProfileBrokerId as Cn, mayReadLocation as Ct, NativeLeaseAdmissionSchema as D, parseProfileBrokerId as Dn, modeMayWrite as Dt, MOTION_CLOSE_AFTER_MS as E, parseJsonUnknown as En, migrateLegacyDeviceSignalConfig as Et, RECORDING_SOURCE_CAMSTACK_ADDON as F, literal as Fn, pipelineRunnerCapability as Ft, audioKindId as G, resolvePoolMemoryPolicy as Gt, addonWidgetsSourceCapability as H, EventCategory as Hn, resolveCreateExportSubject as Ht, RecordingConfigSchema as I, number as In, rankStreams as It, clipExportFailure as J, stepModelRunsOnFormat as Jt, batteryCapability as K, resolveRecordingProfiles as Kt, RecordingSignalStatusSchema as L, object as Ln, recordingArchiveCapability as Lt, RATE_CONTROL_TIGHT as M, boolean as Mn, pickDetailCropConvention as Mt, RECORDING_EXPORT_MAX_READ_BYTES as N, discriminatedUnion as Nn, pickNativeLeaseOverride as Nt, NativeLeaseSettingsSchema as O, selectAssignedProfileSlots as On, motionDetectionCapability as Ot, RECORDING_SOURCE_CAMSTACK as P, lazy as Pn, pipelineExecutorCapability as Pt, declareLogChannel as Q, stripRetiredBandPreBufferSec as Qt, RingBuffer as R, preprocess as Rn, recordingCapability as Rt, ExportRecordSchema as S, isEvent as Sn, maskUrlCredentials as St, HF_BASE_URL as T, nodePin as Tn, memoizePinnedFfmpeg as Tt, audioAnalysisCapability as U, resolveEgressDecodeHwAccel as Ut, YAMNET_TO_MACRO as V, union as Vn, resolveClusterStepModelId as Vt, audioAnalyzerCapability as W, resolveLocationMode as Wt, createHwAccelCache as X, storageOccupancyCapability as Xt, clipExportFailureOf as Y, storageEvictableCapability as Yt, createLogChannelsProvider as Z, streamBrokerCapability as Zt, DEFAULT_NATIVE_LEASE_SETTINGS as _, createEvent as _n, inferModelProvider as _t, BatteryStatusSchema as a, buildFfmpegArgs as an, egressTransportFromRequest as at, EVENT_PAD_MS as b, expandAudioChunkToF32le as bn, logChannelsCapability as bt, CLIP_STILL_REASON_HEADER as c, isSoftwareDecode as cn, evictionPolicyForMode as ct, COCO_80_LABELS as d, errMsg as dn, failureContributionCapability as dt, videoclipsCapability as en, deriveBatteryPresence as et, COCO_TO_MACRO as f, BOOT_RECOVERY_BACKOFF_MS as fn, ffmpegRate as ft, DEFAULT_EVENTS_BAND_BUFFER_SEC as g, audioChunkBytesPerSample as gn, hfModelUrl as gt, DEFAULT_DETAIL_CROP_CONVENTION as h, CamProfileSchema as hn, gifFromMp4Args as ht, BackgroundRunRegistry as i, Fmp4BoxSplitter as in, egressTranscodeSharingKey as it, RATE_CONTROL_RELAXED as j, array as jn, pickClusterStepSettings as jt, OpsLogEntrySchema as k, sleep as kn, overlayClusterStepSettings as kt, CLIP_STREAM_TRANSPORT_HEADER as l, DeviceFeature as ln, evictionPolicyOfLocation as lt, DEFAULT_CLUSTER_STEP_SETTINGS as m, CAM_PROFILE_ORDER as mn, gifDeriveSpeed as mt, AUDIO_BACKEND_CHOICES as n, AUDIO_PRESETS as nn, deriveRecordingMode as nt, CLIP_BROKER_PACED_RATES as o, evaluateSensorEdge as on, enumerateInferenceDevices as ot, DEFAULT_AUDIO_ANALYZER_CONFIG as p, BaseAddon as pn, gbToBytes as pt, cameraStreamsCapability as q, runtimeDevices as qt, AUDIO_MACRO_LABELS as r, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as rn, detectionPipelineCapability as rt, CLIP_STILL_REASON_CODE_HEADER as s, invocationFromEncodeProfile as sn, evaluateZoneRules as st, APPLE_SA_TO_MACRO as t, webrtcSessionCapability as tn, deriveDetailCropRect as tt, CLUSTER_MODEL_SCOPED_STEPS as u, DeviceType as un, exportSubjectOf as ut, DEVICE_BACKEND_TO_FORMAT as v, customAction as vn, isFirstLevelMacroClass as vt, G711_SCALE_CORRECTION_DB as w, makeSourceBrokerId as wn, mayWriteToLocation as wt, EncodeProfileSchema as x, hydrateSchema as xn, mapAudioLabelToMacro as xt, EVENT_OWNER_TYPES as y, defineCustomActions as yn, loadContributionCapability as yt, VIDEOCLIPS_MAX_OFFER_BYTES as z, record as zn, recordingExportCapability as zt };
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-CdRsXAov.js");
|
|
6
6
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ot as motionDetectionCapability, pn as BaseAddon, st as evaluateZoneRules, un as DeviceType, xn as hydrateSchema } from "../dist-jy-7xoW5.mjs";
|
|
2
2
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { an as buildFfmpegArgs, cn as isSoftwareDecode, dn as errMsg, in as Fmp4BoxSplitter } from "./dist-jy-7xoW5.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
@@ -112,58 +112,6 @@ async function probeFfmpegBinary(path) {
|
|
|
112
112
|
]);
|
|
113
113
|
return parseFfmpegCapabilities(path, versionOut, hwaccelOut, encoderOut);
|
|
114
114
|
}
|
|
115
|
-
/**
|
|
116
|
-
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
117
|
-
*
|
|
118
|
-
* ## Why a download, on every node, including the container
|
|
119
|
-
*
|
|
120
|
-
* The version of ffmpeg used to be CODE: baked into the image, changeable only
|
|
121
|
-
* by rebuilding and rolling it. Now it is DATA — this file states the release,
|
|
122
|
-
* and a node that boots with a different one downloads it. Changing ffmpeg is a
|
|
123
|
-
* framework bump, not an image rebuild.
|
|
124
|
-
*
|
|
125
|
-
* It also settles the licence question by removing it. FFmpeg's licence is
|
|
126
|
-
* chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
|
|
127
|
-
* (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
|
|
128
|
-
* makes the result undistributable. These are GPL v3 builds, and we do not
|
|
129
|
-
* distribute them: the user's own node fetches the archive from the project
|
|
130
|
-
* that publishes it, so we convey no GPL work and inherit none of the
|
|
131
|
-
* obligations that come with conveying one. That is the whole reason the image
|
|
132
|
-
* no longer installs ffmpeg at all — not a technical preference.
|
|
133
|
-
*
|
|
134
|
-
* ## Why jellyfin-ffmpeg
|
|
135
|
-
*
|
|
136
|
-
* One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
|
|
137
|
-
* from one release, with the widest hardware surface of anything maintained:
|
|
138
|
-
* VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
|
|
139
|
-
* MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
|
|
140
|
-
* so the binary is not linked against a driver stack it may not find.
|
|
141
|
-
*
|
|
142
|
-
* **What it still needs from the host, measured 2026-09-19.** On the Unraid
|
|
143
|
-
* HOST, which has no libva, `-vaapi_device` aborts the process outright —
|
|
144
|
-
* `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
|
|
145
|
-
* "Assertion in generated code"' failed`. Inside our container, which installs
|
|
146
|
-
* libva plus the iHD driver, the same command encodes: verified end to end with
|
|
147
|
-
* `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
|
|
148
|
-
* KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
|
|
149
|
-
*
|
|
150
|
-
* ## Why each artifact carries a checksum and a claim
|
|
151
|
-
*
|
|
152
|
-
* The download is now the only source, so `sha256` is verified before anything
|
|
153
|
-
* is extracted — jellyfin publishes no checksums on its release assets, so
|
|
154
|
-
* these are ours, taken at the version bump. And `expectedHwaccels` is what the
|
|
155
|
-
* build is supposed to be able to do: a binary that probes short of its own
|
|
156
|
-
* claim is a source that changed under us, which is precisely what went
|
|
157
|
-
* unnoticed for months when a static build turned out to carry no VAAPI at all
|
|
158
|
-
* (D536).
|
|
159
|
-
*/
|
|
160
|
-
/** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
|
|
161
|
-
var FFMPEG_RELEASE = "8.1.2-5";
|
|
162
|
-
var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
|
|
163
|
-
function portable(target) {
|
|
164
|
-
return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
|
|
165
|
-
}
|
|
166
|
-
portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
|
|
167
115
|
var DEFAULT_FIRST_UNIT_TIMEOUT_MS = 12e3;
|
|
168
116
|
/** Heartbeat cadence — ~2 minutes of 4 s fragments. */
|
|
169
117
|
var FRAGMENT_LOG_EVERY = 30;
|