mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-01 12:26:50 +03:00
fixes
This commit is contained in:
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user