mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-19 01:17:06 +03:00
add frontend speed filter
This commit is contained in:
parent
adaacd20a1
commit
fe231e2391
@ -25,6 +25,8 @@ class EventsQueryParams(BaseModel):
|
|||||||
favorites: Optional[int] = None
|
favorites: Optional[int] = None
|
||||||
min_score: Optional[float] = None
|
min_score: Optional[float] = None
|
||||||
max_score: Optional[float] = None
|
max_score: Optional[float] = None
|
||||||
|
min_speed: Optional[float] = None
|
||||||
|
max_speed: Optional[float] = None
|
||||||
is_submitted: Optional[int] = None
|
is_submitted: Optional[int] = None
|
||||||
min_length: Optional[float] = None
|
min_length: Optional[float] = None
|
||||||
max_length: Optional[float] = None
|
max_length: Optional[float] = None
|
||||||
@ -51,6 +53,8 @@ class EventsSearchQueryParams(BaseModel):
|
|||||||
timezone: Optional[str] = "utc"
|
timezone: Optional[str] = "utc"
|
||||||
min_score: Optional[float] = None
|
min_score: Optional[float] = None
|
||||||
max_score: Optional[float] = None
|
max_score: Optional[float] = None
|
||||||
|
min_speed: Optional[float] = None
|
||||||
|
max_speed: Optional[float] = None
|
||||||
sort: Optional[str] = None
|
sort: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -92,6 +92,8 @@ def events(params: EventsQueryParams = Depends()):
|
|||||||
favorites = params.favorites
|
favorites = params.favorites
|
||||||
min_score = params.min_score
|
min_score = params.min_score
|
||||||
max_score = params.max_score
|
max_score = params.max_score
|
||||||
|
min_speed = params.min_speed
|
||||||
|
max_speed = params.max_speed
|
||||||
is_submitted = params.is_submitted
|
is_submitted = params.is_submitted
|
||||||
min_length = params.min_length
|
min_length = params.min_length
|
||||||
max_length = params.max_length
|
max_length = params.max_length
|
||||||
@ -226,6 +228,12 @@ def events(params: EventsQueryParams = Depends()):
|
|||||||
if min_score is not None:
|
if min_score is not None:
|
||||||
clauses.append((Event.data["score"] >= min_score))
|
clauses.append((Event.data["score"] >= min_score))
|
||||||
|
|
||||||
|
if max_speed is not None:
|
||||||
|
clauses.append((Event.data["average_estimated_speed"] <= max_speed))
|
||||||
|
|
||||||
|
if min_speed is not None:
|
||||||
|
clauses.append((Event.data["average_estimated_speed"] >= min_speed))
|
||||||
|
|
||||||
if min_length is not None:
|
if min_length is not None:
|
||||||
clauses.append(((Event.end_time - Event.start_time) >= min_length))
|
clauses.append(((Event.end_time - Event.start_time) >= min_length))
|
||||||
|
|
||||||
@ -249,6 +257,10 @@ def events(params: EventsQueryParams = Depends()):
|
|||||||
order_by = Event.data["score"].asc()
|
order_by = Event.data["score"].asc()
|
||||||
elif sort == "score_desc":
|
elif sort == "score_desc":
|
||||||
order_by = Event.data["score"].desc()
|
order_by = Event.data["score"].desc()
|
||||||
|
elif sort == "speed_asc":
|
||||||
|
order_by = Event.data["average_estimated_speed"].asc()
|
||||||
|
elif sort == "speed_desc":
|
||||||
|
order_by = Event.data["average_estimated_speed"].desc()
|
||||||
elif sort == "date_asc":
|
elif sort == "date_asc":
|
||||||
order_by = Event.start_time.asc()
|
order_by = Event.start_time.asc()
|
||||||
elif sort == "date_desc":
|
elif sort == "date_desc":
|
||||||
@ -375,6 +387,8 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
before = params.before
|
before = params.before
|
||||||
min_score = params.min_score
|
min_score = params.min_score
|
||||||
max_score = params.max_score
|
max_score = params.max_score
|
||||||
|
min_speed = params.min_speed
|
||||||
|
max_speed = params.max_speed
|
||||||
time_range = params.time_range
|
time_range = params.time_range
|
||||||
has_clip = params.has_clip
|
has_clip = params.has_clip
|
||||||
has_snapshot = params.has_snapshot
|
has_snapshot = params.has_snapshot
|
||||||
@ -474,6 +488,16 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
if max_score is not None:
|
if max_score is not None:
|
||||||
event_filters.append((Event.data["score"] <= max_score))
|
event_filters.append((Event.data["score"] <= max_score))
|
||||||
|
|
||||||
|
if min_speed is not None and max_speed is not None:
|
||||||
|
event_filters.append(
|
||||||
|
(Event.data["average_estimated_speed"].between(min_speed, max_speed))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if min_speed is not None:
|
||||||
|
event_filters.append((Event.data["average_estimated_speed"] >= min_speed))
|
||||||
|
if max_speed is not None:
|
||||||
|
event_filters.append((Event.data["average_estimated_speed"] <= max_speed))
|
||||||
|
|
||||||
if time_range != DEFAULT_TIME_RANGE:
|
if time_range != DEFAULT_TIME_RANGE:
|
||||||
tz_name = params.timezone
|
tz_name = params.timezone
|
||||||
hour_modifier, minute_modifier, _ = get_tz_modifiers(tz_name)
|
hour_modifier, minute_modifier, _ = get_tz_modifiers(tz_name)
|
||||||
@ -613,6 +637,10 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
processed_events.sort(key=lambda x: x["score"])
|
processed_events.sort(key=lambda x: x["score"])
|
||||||
elif min_score is not None and max_score is not None and sort == "score_desc":
|
elif min_score is not None and max_score is not None and sort == "score_desc":
|
||||||
processed_events.sort(key=lambda x: x["score"], reverse=True)
|
processed_events.sort(key=lambda x: x["score"], reverse=True)
|
||||||
|
elif min_speed is not None and max_speed is not None and sort == "speed_asc":
|
||||||
|
processed_events.sort(key=lambda x: x["average_estimated_speed"])
|
||||||
|
elif min_speed is not None and max_speed is not None and sort == "speed_desc":
|
||||||
|
processed_events.sort(key=lambda x: x["average_estimated_speed"], reverse=True)
|
||||||
elif sort == "date_asc":
|
elif sort == "date_asc":
|
||||||
processed_events.sort(key=lambda x: x["start_time"])
|
processed_events.sort(key=lambda x: x["start_time"])
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -114,6 +114,9 @@ export default function SearchFilterGroup({
|
|||||||
if (filter?.min_score || filter?.max_score) {
|
if (filter?.min_score || filter?.max_score) {
|
||||||
sortTypes.push("score_desc", "score_asc");
|
sortTypes.push("score_desc", "score_asc");
|
||||||
}
|
}
|
||||||
|
if (filter?.min_speed || filter?.max_speed) {
|
||||||
|
sortTypes.push("speed_desc", "speed_asc");
|
||||||
|
}
|
||||||
if (filter?.event_id || filter?.query) {
|
if (filter?.event_id || filter?.query) {
|
||||||
sortTypes.push("relevance");
|
sortTypes.push("relevance");
|
||||||
}
|
}
|
||||||
@ -496,6 +499,8 @@ export function SortTypeContent({
|
|||||||
date_desc: "Date (Descending)",
|
date_desc: "Date (Descending)",
|
||||||
score_asc: "Object Score (Ascending)",
|
score_asc: "Object Score (Ascending)",
|
||||||
score_desc: "Object Score (Descending)",
|
score_desc: "Object Score (Descending)",
|
||||||
|
speed_asc: "Estimated Speed (Ascending)",
|
||||||
|
speed_desc: "Estimated Speed (Descending)",
|
||||||
relevance: "Relevance",
|
relevance: "Relevance",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -216,11 +216,14 @@ export default function InputWithTags({
|
|||||||
type == "after" ||
|
type == "after" ||
|
||||||
type == "time_range" ||
|
type == "time_range" ||
|
||||||
type == "min_score" ||
|
type == "min_score" ||
|
||||||
type == "max_score"
|
type == "max_score" ||
|
||||||
|
type == "min_speed" ||
|
||||||
|
type == "max_speed"
|
||||||
) {
|
) {
|
||||||
const newFilters = { ...filters };
|
const newFilters = { ...filters };
|
||||||
let timestamp = 0;
|
let timestamp = 0;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
let speed = 0;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "before":
|
case "before":
|
||||||
@ -294,6 +297,40 @@ export default function InputWithTags({
|
|||||||
newFilters[type] = score / 100;
|
newFilters[type] = score / 100;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "min_speed":
|
||||||
|
case "max_speed":
|
||||||
|
speed = parseFloat(value);
|
||||||
|
if (score >= 0) {
|
||||||
|
// Check for conflicts between min_speed and max_speed
|
||||||
|
if (
|
||||||
|
type === "min_speed" &&
|
||||||
|
filters.max_speed !== undefined &&
|
||||||
|
speed > filters.max_speed
|
||||||
|
) {
|
||||||
|
toast.error(
|
||||||
|
"The 'min_speed' must be less than or equal to the 'max_speed'.",
|
||||||
|
{
|
||||||
|
position: "top-center",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
type === "max_speed" &&
|
||||||
|
filters.min_speed !== undefined &&
|
||||||
|
speed < filters.min_speed
|
||||||
|
) {
|
||||||
|
toast.error(
|
||||||
|
"The 'max_speed' must be greater than or equal to the 'min_speed'.",
|
||||||
|
{
|
||||||
|
position: "top-center",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newFilters[type] = speed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "time_range":
|
case "time_range":
|
||||||
newFilters[type] = value;
|
newFilters[type] = value;
|
||||||
break;
|
break;
|
||||||
@ -369,6 +406,10 @@ export default function InputWithTags({
|
|||||||
}`;
|
}`;
|
||||||
} else if (filterType === "min_score" || filterType === "max_score") {
|
} else if (filterType === "min_score" || filterType === "max_score") {
|
||||||
return Math.round(Number(filterValues) * 100).toString() + "%";
|
return Math.round(Number(filterValues) * 100).toString() + "%";
|
||||||
|
} else if (filterType === "min_speed" || filterType === "max_speed") {
|
||||||
|
return (
|
||||||
|
filterValues + (config?.ui.unit_system == "metric" ? " kph" : " mph")
|
||||||
|
);
|
||||||
} else if (
|
} else if (
|
||||||
filterType === "has_clip" ||
|
filterType === "has_clip" ||
|
||||||
filterType === "has_snapshot" ||
|
filterType === "has_snapshot" ||
|
||||||
@ -397,7 +438,11 @@ export default function InputWithTags({
|
|||||||
((filterType === "min_score" || filterType === "max_score") &&
|
((filterType === "min_score" || filterType === "max_score") &&
|
||||||
!isNaN(Number(trimmedValue)) &&
|
!isNaN(Number(trimmedValue)) &&
|
||||||
Number(trimmedValue) >= 50 &&
|
Number(trimmedValue) >= 50 &&
|
||||||
Number(trimmedValue) <= 100)
|
Number(trimmedValue) <= 100) ||
|
||||||
|
((filterType === "min_speed" || filterType === "max_speed") &&
|
||||||
|
!isNaN(Number(trimmedValue)) &&
|
||||||
|
Number(trimmedValue) >= 1 &&
|
||||||
|
Number(trimmedValue) <= 150)
|
||||||
) {
|
) {
|
||||||
createFilter(
|
createFilter(
|
||||||
filterType,
|
filterType,
|
||||||
|
|||||||
@ -71,9 +71,11 @@ export default function SearchFilterDialog({
|
|||||||
currentFilter &&
|
currentFilter &&
|
||||||
(currentFilter.time_range ||
|
(currentFilter.time_range ||
|
||||||
(currentFilter.min_score ?? 0) > 0.5 ||
|
(currentFilter.min_score ?? 0) > 0.5 ||
|
||||||
|
(currentFilter.min_speed ?? 1) > 1 ||
|
||||||
(currentFilter.has_snapshot ?? 0) === 1 ||
|
(currentFilter.has_snapshot ?? 0) === 1 ||
|
||||||
(currentFilter.has_clip ?? 0) === 1 ||
|
(currentFilter.has_clip ?? 0) === 1 ||
|
||||||
(currentFilter.max_score ?? 1) < 1 ||
|
(currentFilter.max_score ?? 1) < 1 ||
|
||||||
|
(currentFilter.max_speed ?? 150) < 150 ||
|
||||||
(currentFilter.zones?.length ?? 0) > 0 ||
|
(currentFilter.zones?.length ?? 0) > 0 ||
|
||||||
(currentFilter.sub_labels?.length ?? 0) > 0),
|
(currentFilter.sub_labels?.length ?? 0) > 0),
|
||||||
[currentFilter],
|
[currentFilter],
|
||||||
@ -124,6 +126,14 @@ export default function SearchFilterDialog({
|
|||||||
setCurrentFilter({ ...currentFilter, min_score: min, max_score: max })
|
setCurrentFilter({ ...currentFilter, min_score: min, max_score: max })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<SpeedFilterContent
|
||||||
|
config={config}
|
||||||
|
minSpeed={currentFilter.min_speed}
|
||||||
|
maxSpeed={currentFilter.max_speed}
|
||||||
|
setSpeedRange={(min, max) =>
|
||||||
|
setCurrentFilter({ ...currentFilter, min_speed: min, max_speed: max })
|
||||||
|
}
|
||||||
|
/>
|
||||||
<SnapshotClipFilterContent
|
<SnapshotClipFilterContent
|
||||||
config={config}
|
config={config}
|
||||||
hasSnapshot={
|
hasSnapshot={
|
||||||
@ -178,6 +188,8 @@ export default function SearchFilterDialog({
|
|||||||
search_type: undefined,
|
search_type: undefined,
|
||||||
min_score: undefined,
|
min_score: undefined,
|
||||||
max_score: undefined,
|
max_score: undefined,
|
||||||
|
min_speed: undefined,
|
||||||
|
max_speed: undefined,
|
||||||
has_snapshot: undefined,
|
has_snapshot: undefined,
|
||||||
has_clip: undefined,
|
has_clip: undefined,
|
||||||
}));
|
}));
|
||||||
@ -521,6 +533,62 @@ export function ScoreFilterContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SpeedFilterContentProps = {
|
||||||
|
config?: FrigateConfig;
|
||||||
|
minSpeed: number | undefined;
|
||||||
|
maxSpeed: number | undefined;
|
||||||
|
setSpeedRange: (min: number | undefined, max: number | undefined) => void;
|
||||||
|
};
|
||||||
|
export function SpeedFilterContent({
|
||||||
|
config,
|
||||||
|
minSpeed,
|
||||||
|
maxSpeed,
|
||||||
|
setSpeedRange,
|
||||||
|
}: SpeedFilterContentProps) {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-hidden">
|
||||||
|
<DropdownMenuSeparator className="mb-3" />
|
||||||
|
<div className="mb-3 text-lg">
|
||||||
|
Estimated Speed ({config?.ui.unit_system == "metric" ? "kph" : "mph"})
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Input
|
||||||
|
className="w-14 text-center"
|
||||||
|
inputMode="numeric"
|
||||||
|
value={minSpeed ?? 1}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
setSpeedRange(parseInt(value), maxSpeed ?? 1.0);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DualThumbSlider
|
||||||
|
className="mx-2 w-full"
|
||||||
|
min={1}
|
||||||
|
max={150}
|
||||||
|
step={1}
|
||||||
|
value={[minSpeed ?? 1, maxSpeed ?? 150]}
|
||||||
|
onValueChange={([min, max]) => setSpeedRange(min, max)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
className="w-14 text-center"
|
||||||
|
inputMode="numeric"
|
||||||
|
value={maxSpeed ?? 150}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
setSpeedRange(minSpeed ?? 1, parseInt(value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type SnapshotClipContentProps = {
|
type SnapshotClipContentProps = {
|
||||||
config?: FrigateConfig;
|
config?: FrigateConfig;
|
||||||
hasSnapshot: boolean | undefined;
|
hasSnapshot: boolean | undefined;
|
||||||
|
|||||||
@ -112,6 +112,8 @@ export default function Explore() {
|
|||||||
search_type: searchSearchParams["search_type"],
|
search_type: searchSearchParams["search_type"],
|
||||||
min_score: searchSearchParams["min_score"],
|
min_score: searchSearchParams["min_score"],
|
||||||
max_score: searchSearchParams["max_score"],
|
max_score: searchSearchParams["max_score"],
|
||||||
|
min_speed: searchSearchParams["min_speed"],
|
||||||
|
max_speed: searchSearchParams["max_speed"],
|
||||||
has_snapshot: searchSearchParams["has_snapshot"],
|
has_snapshot: searchSearchParams["has_snapshot"],
|
||||||
is_submitted: searchSearchParams["is_submitted"],
|
is_submitted: searchSearchParams["is_submitted"],
|
||||||
has_clip: searchSearchParams["has_clip"],
|
has_clip: searchSearchParams["has_clip"],
|
||||||
@ -145,6 +147,8 @@ export default function Explore() {
|
|||||||
search_type: searchSearchParams["search_type"],
|
search_type: searchSearchParams["search_type"],
|
||||||
min_score: searchSearchParams["min_score"],
|
min_score: searchSearchParams["min_score"],
|
||||||
max_score: searchSearchParams["max_score"],
|
max_score: searchSearchParams["max_score"],
|
||||||
|
min_speed: searchSearchParams["min_speed"],
|
||||||
|
max_speed: searchSearchParams["max_speed"],
|
||||||
has_snapshot: searchSearchParams["has_snapshot"],
|
has_snapshot: searchSearchParams["has_snapshot"],
|
||||||
is_submitted: searchSearchParams["is_submitted"],
|
is_submitted: searchSearchParams["is_submitted"],
|
||||||
has_clip: searchSearchParams["has_clip"],
|
has_clip: searchSearchParams["has_clip"],
|
||||||
|
|||||||
@ -70,6 +70,8 @@ export type SearchFilter = {
|
|||||||
after?: number;
|
after?: number;
|
||||||
min_score?: number;
|
min_score?: number;
|
||||||
max_score?: number;
|
max_score?: number;
|
||||||
|
min_speed?: number;
|
||||||
|
max_speed?: number;
|
||||||
has_snapshot?: number;
|
has_snapshot?: number;
|
||||||
has_clip?: number;
|
has_clip?: number;
|
||||||
is_submitted?: number;
|
is_submitted?: number;
|
||||||
@ -91,6 +93,8 @@ export type SearchQueryParams = {
|
|||||||
after?: string;
|
after?: string;
|
||||||
min_score?: number;
|
min_score?: number;
|
||||||
max_score?: number;
|
max_score?: number;
|
||||||
|
min_speed?: number;
|
||||||
|
max_speed?: number;
|
||||||
search_type?: string;
|
search_type?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
in_progress?: number;
|
in_progress?: number;
|
||||||
|
|||||||
@ -158,6 +158,8 @@ export default function SearchView({
|
|||||||
after: [formatDateToLocaleString(-5)],
|
after: [formatDateToLocaleString(-5)],
|
||||||
min_score: ["50"],
|
min_score: ["50"],
|
||||||
max_score: ["100"],
|
max_score: ["100"],
|
||||||
|
min_speed: ["1"],
|
||||||
|
max_speed: ["150"],
|
||||||
has_clip: ["yes", "no"],
|
has_clip: ["yes", "no"],
|
||||||
has_snapshot: ["yes", "no"],
|
has_snapshot: ["yes", "no"],
|
||||||
...(config?.plus?.enabled &&
|
...(config?.plus?.enabled &&
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user