mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 10:33:11 +03:00
tweaks
This commit is contained in:
parent
ad00001049
commit
23c6e5231f
@ -35,13 +35,13 @@ DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT = [
|
||||
class FfmpegOutputArgsConfig(FrigateBaseModel):
|
||||
detect: Union[str, list[str]] = Field(
|
||||
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||
title="Detect output args",
|
||||
description="Default output args for detect role streams.",
|
||||
title="Detect output arguments",
|
||||
description="Default output arguments for detect role streams.",
|
||||
)
|
||||
record: Union[str, list[str]] = Field(
|
||||
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||
title="Record output args",
|
||||
description="Default output args for record role streams.",
|
||||
title="Record output arguments",
|
||||
description="Default output arguments for record role streams.",
|
||||
)
|
||||
|
||||
|
||||
@ -124,17 +124,17 @@ class CameraInput(FrigateBaseModel):
|
||||
)
|
||||
global_args: Union[str, list[str]] = Field(
|
||||
default_factory=list,
|
||||
title="FFmpeg global args",
|
||||
title="FFmpeg global arguments",
|
||||
description="FFmpeg global arguments for this input stream.",
|
||||
)
|
||||
hwaccel_args: Union[str, list[str]] = Field(
|
||||
default_factory=list,
|
||||
title="Hardware acceleration args",
|
||||
title="Hardware acceleration arguments",
|
||||
description="Hardware acceleration arguments for this input stream.",
|
||||
)
|
||||
input_args: Union[str, list[str]] = Field(
|
||||
default_factory=list,
|
||||
title="Input args",
|
||||
title="Input arguments",
|
||||
description="Input arguments specific to this stream.",
|
||||
)
|
||||
|
||||
|
||||
@ -448,7 +448,7 @@ class FrigateConfig(FrigateBaseModel):
|
||||
timestamp_style: TimestampStyleConfig = Field(
|
||||
default_factory=TimestampStyleConfig,
|
||||
title="Timestamp style",
|
||||
description="Styling options for in-feed timestamps applied to recordings and snapshots.",
|
||||
description="Styling options for in-feed timestamps applied to debug view and snapshots.",
|
||||
)
|
||||
|
||||
# Classification Config
|
||||
|
||||
@ -170,12 +170,12 @@
|
||||
"label": "Output arguments",
|
||||
"description": "Default output arguments used for different FFmpeg roles such as detect and record.",
|
||||
"detect": {
|
||||
"label": "Detect output args",
|
||||
"description": "Default output args for detect role streams."
|
||||
"label": "Detect output arguments",
|
||||
"description": "Default output arguments for detect role streams."
|
||||
},
|
||||
"record": {
|
||||
"label": "Record output args",
|
||||
"description": "Default output args for record role streams."
|
||||
"label": "Record output arguments",
|
||||
"description": "Default output arguments for record role streams."
|
||||
}
|
||||
},
|
||||
"retry_interval": {
|
||||
@ -202,15 +202,15 @@
|
||||
"description": "Roles for this input stream (for example: detect, record, audio)."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "FFmpeg global args",
|
||||
"label": "FFmpeg global arguments",
|
||||
"description": "FFmpeg global arguments for this input stream."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Hardware acceleration args",
|
||||
"label": "Hardware acceleration arguments",
|
||||
"description": "Hardware acceleration arguments for this input stream."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Input args",
|
||||
"label": "Input arguments",
|
||||
"description": "Input arguments specific to this stream."
|
||||
}
|
||||
}
|
||||
|
||||
@ -1313,12 +1313,12 @@
|
||||
"label": "Output arguments",
|
||||
"description": "Default output arguments used for different FFmpeg roles such as detect and record.",
|
||||
"detect": {
|
||||
"label": "Detect output args",
|
||||
"description": "Default output args for detect role streams."
|
||||
"label": "Detect output arguments",
|
||||
"description": "Default output arguments for detect role streams."
|
||||
},
|
||||
"record": {
|
||||
"label": "Record output args",
|
||||
"description": "Default output args for record role streams."
|
||||
"label": "Record output arguments",
|
||||
"description": "Default output arguments for record role streams."
|
||||
}
|
||||
},
|
||||
"retry_interval": {
|
||||
@ -1345,15 +1345,15 @@
|
||||
"description": "Roles for this input stream (for example: detect, record, audio)."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "FFmpeg global args",
|
||||
"label": "FFmpeg global arguments",
|
||||
"description": "FFmpeg global arguments for this input stream."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Hardware acceleration args",
|
||||
"label": "Hardware acceleration arguments",
|
||||
"description": "Hardware acceleration arguments for this input stream."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Input args",
|
||||
"label": "Input arguments",
|
||||
"description": "Input arguments specific to this stream."
|
||||
}
|
||||
}
|
||||
@ -1762,7 +1762,7 @@
|
||||
},
|
||||
"timestamp_style": {
|
||||
"label": "Timestamp style",
|
||||
"description": "Styling options for in-feed timestamps applied to recordings and snapshots.",
|
||||
"description": "Styling options for in-feed timestamps applied to debug view and snapshots.",
|
||||
"position": {
|
||||
"label": "Timestamp position",
|
||||
"description": "Position of the timestamp on the image (tl/tr/bl/br)."
|
||||
|
||||
@ -1298,6 +1298,15 @@
|
||||
"summary": "{{count}} selected",
|
||||
"empty": "No zones available"
|
||||
},
|
||||
"inputRoles": {
|
||||
"summary": "{{count}} roles selected",
|
||||
"empty": "No roles available",
|
||||
"options": {
|
||||
"detect": "Detect",
|
||||
"record": "Record",
|
||||
"audio": "Audio"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"title": "Review Settings"
|
||||
},
|
||||
|
||||
@ -22,6 +22,11 @@ const birdseye: SectionConfigOverrides = {
|
||||
"idle_heartbeat_fps",
|
||||
],
|
||||
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
||||
uiSchema: {
|
||||
mode: {
|
||||
"ui:size": "xs",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -77,7 +77,12 @@ const ffmpeg: SectionConfigOverrides = {
|
||||
path: {
|
||||
"ui:options": { size: "full" },
|
||||
},
|
||||
global_args: arrayAsTextWidget,
|
||||
roles: {
|
||||
"ui:widget": "inputRoles",
|
||||
},
|
||||
global_args: {
|
||||
"ui:widget": "hidden",
|
||||
},
|
||||
hwaccel_args: ffmpegArgsWidget("hwaccel_args"),
|
||||
input_args: ffmpegArgsWidget("input_args"),
|
||||
output_args: {
|
||||
|
||||
@ -48,23 +48,8 @@ const mqtt: SectionConfigOverrides = {
|
||||
],
|
||||
liveValidate: true,
|
||||
uiSchema: {
|
||||
host: {
|
||||
"ui:options": { size: "sm" },
|
||||
},
|
||||
topic_prefix: {
|
||||
"ui:options": { size: "md" },
|
||||
},
|
||||
client_id: {
|
||||
"ui:options": { size: "sm" },
|
||||
},
|
||||
tls_ca_certs: {
|
||||
"ui:options": { size: "md" },
|
||||
},
|
||||
tls_client_cert: {
|
||||
"ui:options": { size: "md" },
|
||||
},
|
||||
tls_client_key: {
|
||||
"ui:options": { size: "md" },
|
||||
password: {
|
||||
"ui:options": { size: "xs" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -7,6 +7,14 @@ const timestampStyle: SectionConfigOverrides = {
|
||||
fieldOrder: ["position", "format", "color", "thickness"],
|
||||
hiddenFields: ["effect", "enabled_in_config"],
|
||||
advancedFields: [],
|
||||
uiSchema: {
|
||||
position: {
|
||||
"ui:size": "xs",
|
||||
},
|
||||
format: {
|
||||
"ui:size": "xs",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@ export function AdvancedCollapsible({
|
||||
{label}
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="mt-2 space-y-4 rounded-lg border border-border/60 bg-muted/20 p-4">
|
||||
<CollapsibleContent className="mt-2 space-y-4 rounded-lg border border-border/60 bg-background_alt/70 p-4">
|
||||
{children}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
@ -23,6 +23,7 @@ import { AudioLabelSwitchesWidget } from "./widgets/AudioLabelSwitchesWidget";
|
||||
import { ZoneSwitchesWidget } from "./widgets/ZoneSwitchesWidget";
|
||||
import { ArrayAsTextWidget } from "./widgets/ArrayAsTextWidget";
|
||||
import { FfmpegArgsWidget } from "./widgets/FfmpegArgsWidget";
|
||||
import { InputRolesWidget } from "./widgets/InputRolesWidget";
|
||||
import { TimezoneSelectWidget } from "./widgets/TimezoneSelectWidget";
|
||||
|
||||
import { FieldTemplate } from "./templates/FieldTemplate";
|
||||
@ -55,6 +56,7 @@ export const frigateTheme: FrigateTheme = {
|
||||
CheckboxWidget: SwitchWidget,
|
||||
ArrayAsTextWidget: ArrayAsTextWidget,
|
||||
FfmpegArgsWidget: FfmpegArgsWidget,
|
||||
inputRoles: InputRolesWidget,
|
||||
// Custom widgets
|
||||
switch: SwitchWidget,
|
||||
password: PasswordWidget,
|
||||
|
||||
@ -92,7 +92,8 @@ export function AudioLabelSwitchesWidget(props: WidgetProps) {
|
||||
getEntities,
|
||||
getDisplayLabel: getAudioLabelDisplayName,
|
||||
i18nKey: "audioLabels",
|
||||
listClassName: "max-h-64 overflow-y-auto scrollbar-container",
|
||||
listClassName:
|
||||
"max-h-none overflow-visible md:max-h-64 md:overflow-y-auto md:overscroll-contain md:scrollbar-container",
|
||||
enableSearch: true,
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -3,6 +3,7 @@ import useSWR from "swr";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ConfigFormContext } from "@/types/configForm";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@ -86,7 +87,17 @@ const normalizeManualText = (value: unknown): string => {
|
||||
};
|
||||
|
||||
export function FfmpegArgsWidget(props: WidgetProps) {
|
||||
const { t } = useTranslation(["views/settings"]);
|
||||
const formContext = props.registry?.formContext as
|
||||
| ConfigFormContext
|
||||
| undefined;
|
||||
const i18nNamespace = formContext?.i18nNamespace as string | undefined;
|
||||
const isCameraLevel = formContext?.level === "camera";
|
||||
const effectiveNamespace = isCameraLevel ? "config/cameras" : i18nNamespace;
|
||||
const { t, i18n } = useTranslation([
|
||||
effectiveNamespace || i18nNamespace || "common",
|
||||
i18nNamespace || "common",
|
||||
"views/settings",
|
||||
]);
|
||||
const {
|
||||
value,
|
||||
onChange,
|
||||
@ -165,6 +176,47 @@ export function FfmpegArgsWidget(props: WidgetProps) {
|
||||
const manualValue = normalizeManualText(value);
|
||||
const presetValue =
|
||||
typeof value === "string" && presetOptions.includes(value) ? value : "";
|
||||
const fallbackDescriptionKey = useMemo(() => {
|
||||
if (!presetField) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isInputScoped = id.includes("_inputs_");
|
||||
const prefix = isInputScoped ? "ffmpeg.inputs" : "ffmpeg";
|
||||
|
||||
if (presetField === "hwaccel_args") {
|
||||
return `${prefix}.hwaccel_args.description`;
|
||||
}
|
||||
|
||||
if (presetField === "input_args") {
|
||||
return `${prefix}.input_args.description`;
|
||||
}
|
||||
|
||||
if (presetField === "output_args.record") {
|
||||
return isInputScoped
|
||||
? "ffmpeg.inputs.output_args.record.description"
|
||||
: "ffmpeg.output_args.record.description";
|
||||
}
|
||||
|
||||
if (presetField === "output_args.detect") {
|
||||
return isInputScoped
|
||||
? "ffmpeg.inputs.output_args.detect.description"
|
||||
: "ffmpeg.output_args.detect.description";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [id, presetField]);
|
||||
|
||||
const translatedDescription =
|
||||
fallbackDescriptionKey &&
|
||||
effectiveNamespace &&
|
||||
i18n.exists(fallbackDescriptionKey, { ns: effectiveNamespace })
|
||||
? t(fallbackDescriptionKey, { ns: effectiveNamespace })
|
||||
: "";
|
||||
const fieldDescription =
|
||||
typeof schema.description === "string" && schema.description.length > 0
|
||||
? schema.description
|
||||
: translatedDescription;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
@ -244,6 +296,10 @@ export function FfmpegArgsWidget(props: WidgetProps) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{fieldDescription ? (
|
||||
<p className="text-xs text-muted-foreground">{fieldDescription}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
import type { WidgetProps } from "@rjsf/utils";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
const INPUT_ROLES = ["detect", "record", "audio"] as const;
|
||||
|
||||
function normalizeValue(value: unknown): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter((item): item is string => typeof item === "string");
|
||||
}
|
||||
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return [value.trim()];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function InputRolesWidget(props: WidgetProps) {
|
||||
const { id, value, disabled, readonly, onChange } = props;
|
||||
const { t } = useTranslation(["views/settings"]);
|
||||
|
||||
const selectedRoles = useMemo(() => normalizeValue(value), [value]);
|
||||
|
||||
const toggleRole = (role: string, enabled: boolean) => {
|
||||
if (enabled) {
|
||||
if (!selectedRoles.includes(role)) {
|
||||
onChange([...selectedRoles, role]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(selectedRoles.filter((item) => item !== role));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2 rounded-lg bg-secondary p-2 pr-0 md:max-w-md">
|
||||
{INPUT_ROLES.map((role) => {
|
||||
const checked = selectedRoles.includes(role);
|
||||
const label = t(`configForm.inputRoles.options.${role}`, {
|
||||
ns: "views/settings",
|
||||
defaultValue: role,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
className="flex items-center justify-between rounded-md px-3 py-0"
|
||||
>
|
||||
<label htmlFor={`${id}-${role}`} className="text-sm">
|
||||
{label}
|
||||
</label>
|
||||
<Switch
|
||||
id={`${id}-${role}`}
|
||||
checked={checked}
|
||||
disabled={disabled || readonly}
|
||||
onCheckedChange={(enabled) => toggleRole(role, !!enabled)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -93,7 +93,8 @@ export function ObjectLabelSwitchesWidget(props: WidgetProps) {
|
||||
getEntities: getObjectLabels,
|
||||
getDisplayLabel: getObjectLabelDisplayName,
|
||||
i18nKey: "objectLabels",
|
||||
listClassName: "max-h-64 overflow-y-auto scrollbar-container",
|
||||
listClassName:
|
||||
"max-h-none overflow-visible md:max-h-64 md:overflow-y-auto md:overscroll-contain md:scrollbar-container",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -222,7 +222,7 @@ export default function UiSettingsView() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col md:flex-row">
|
||||
<div className="flex size-full flex-col md:flex-row md:pb-8">
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto pb-2 md:order-none">
|
||||
<div className="w-full max-w-5xl space-y-6">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user