This commit is contained in:
Josh Hawkins
2026-09-30 11:22:15 -05:00
parent c4fdbdf541
commit 84d8c82a8c
6 changed files with 76 additions and 3 deletions
+13
View File
@@ -202,6 +202,19 @@ class TestLiveTranscodeConfig(unittest.TestCase):
self.assertEqual(config.cameras["front"].live.transcode.source, "front_sub")
def test_startup_and_validation_agree_on_inherited_source(self):
config = camera_config({"transcode": {"enabled": True}})
config["live"] = {"streams": {"Sub": "front_sub"}}
self.assertEqual(
raw_transcode_streams(config),
generated_transcode_streams(FrigateConfig(**config)),
)
self.assertEqual(
raw_transcode_streams(config)["front_transcode_360p"],
transcode_stream_source("front_sub", 360, 250),
)
def test_placed_entries_keep_position_and_label(self):
streams = self.streams(
{
+9 -1
View File
@@ -81,6 +81,7 @@ def raw_transcode_streams(config: dict[str, Any]) -> dict[str, str]:
resolves defaults the same way the validator does.
"""
streams: dict[str, str] = {}
global_streams = (config.get("live") or {}).get("streams") or {}
for camera, camera_config in (config.get("cameras") or {}).items():
live = (camera_config or {}).get("live") or {}
@@ -89,8 +90,15 @@ def raw_transcode_streams(config: dict[str, Any]) -> dict[str, str]:
if not transcode.get("enabled"):
continue
# cameras inherit global live streams after their own, like deep_merge
camera_streams = live.get("streams") or {}
live_streams = camera_streams | {
label: name
for label, name in global_streams.items()
if label not in camera_streams
}
source = transcode.get("source") or default_transcode_source(
camera, live.get("streams") or {camera: camera}
camera, live_streams or {camera: camera}
)
if source is None:
@@ -150,6 +150,37 @@ test.describe("live stream order and transcoded streams @medium", () => {
).toEqual(["Main", "Sub"]);
});
test("save all keeps a reorder made on another page", async ({
frigateApp,
}) => {
const capture = await installRoutes(frigateApp.page);
await frigateApp.goto(SETTINGS_URL);
await expect
.poll(() => streamNames(frigateApp.page))
.toEqual(["Sub", "Main"]);
await dragRow(frigateApp.page, 1, 0);
await expect
.poll(() => streamNames(frigateApp.page))
.toEqual(["Main", "Sub"]);
await frigateApp.page.getByText("Snapshots", { exact: true }).click();
await frigateApp.page.getByRole("button", { name: "Save All" }).click();
await expect.poll(() => capture.saved()).not.toBeNull();
const saved = capture.saved() as {
replace_paths: string[];
config_data: {
cameras: { front_door: { live: { streams: Record<string, string> } } };
};
};
expect(saved.replace_paths).toEqual(["cameras.front_door.live.streams"]);
expect(
Object.keys(saved.config_data.cameras.front_door.live.streams),
).toEqual(["Main", "Sub"]);
});
test("enabling transcoding saves the shown source", async ({
frigateApp,
}) => {
@@ -484,10 +484,13 @@ export function LiveStreamsField(props: FieldProps) {
);
// Auto order commits after measuring, so edits made meanwhile must survive
// and a camera switched to meanwhile must not receive the result
const dataRef = useRef(data);
const cameraRef = useRef(camera);
useEffect(() => {
dataRef.current = data;
}, [data]);
cameraRef.current = camera;
}, [data, camera]);
const [measurement, setMeasurement] = useState<{
data: LiveStreamsData;
@@ -527,6 +530,11 @@ export function LiveStreamsField(props: FieldProps) {
}),
);
if (cameraRef.current !== camera) {
setMeasuring(false);
return;
}
const next = Object.fromEntries(measured);
const latest = dataRef.current;
setMeasurement({ data: latest, transcode, rates: next });
+3
View File
@@ -875,6 +875,9 @@ export default function Settings() {
requires_restart: payload.needsRestart ? 1 : 0,
update_topic: payload.updateTopic,
config_data: configData,
...(payload.replacePaths?.length
? { replace_paths: payload.replacePaths }
: {}),
});
if (payload.needsRestart) {
+11 -1
View File
@@ -556,6 +556,7 @@ export interface SectionSavePayload {
updateTopic: string | undefined;
needsRestart: boolean;
pendingDataKey: string;
replacePaths?: string[];
}
// ---------------------------------------------------------------------------
@@ -765,7 +766,13 @@ export function prepareSectionSavePayload(opts: {
);
// Build overrides
const overrides = buildOverrides(pendingData, rawData, effectiveDefaults);
const orderedMaps = sectionConfig.orderedMaps ?? [];
const overrides = applyOrderedMaps(
buildOverrides(pendingData, rawData, effectiveDefaults),
pendingData,
rawData,
orderedMaps,
);
const sanitizedOverrides = sanitizeOverridesForSection(
schemaSection,
level,
@@ -815,6 +822,9 @@ export function prepareSectionSavePayload(opts: {
updateTopic,
needsRestart,
pendingDataKey,
replacePaths: changedOrderedMapPaths(pendingData, rawData, orderedMaps).map(
(path) => `${basePath}.${path}`,
),
};
}