mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-12 16:43:01 +03:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d149de038a | ||
|
|
a3e1966013 | ||
|
|
06d1365056 | ||
|
|
8a88cefc9a | ||
|
|
fba967c74b |
@@ -105,6 +105,8 @@ if [[ -n "$sentinel" ]]; then
|
||||
mismatch_expr+=(-a ! -path "$sentinel")
|
||||
fi
|
||||
|
||||
exec 3>&1
|
||||
|
||||
for path in "$@"; do
|
||||
# An absent root is an incomplete sweep, not a finished one: /media/frigate
|
||||
# is not in the image, so a boot before the volume is mounted would
|
||||
@@ -121,7 +123,18 @@ for path in "$@"; do
|
||||
# stale mount. Tolerate it rather than aborting under errexit, but never
|
||||
# read a failed scan as "nothing to do": that would record the sweep as
|
||||
# complete without having looked.
|
||||
if ! count=$(find "$path" -name lost+found -prune -o "${mismatch_expr[@]}" -printf '.' 2>/dev/null | wc -c); then
|
||||
# The walk prints nothing until it ends, so report every 30s through fd 3
|
||||
# (stdout) while the count itself comes back through the substitution.
|
||||
if ! count=$(find "$path" -name lost+found -prune -o "(" "${mismatch_expr[@]}" ")" -printf 'm\n' -o -printf '.\n' 2>/dev/null \
|
||||
| awk -v path="$path" '
|
||||
BEGIN { next_report = systime() + 30 }
|
||||
$0 == "m" { mismatched++ }
|
||||
systime() >= next_report {
|
||||
printf "[INFO] fix-ownership: still scanning %s, %d entries checked, %d need changes\n", path, NR, mismatched > "/dev/fd/3"
|
||||
fflush("/dev/fd/3")
|
||||
next_report = systime() + 30
|
||||
}
|
||||
END { print mismatched + 0 }'); then
|
||||
swept_clean=0
|
||||
echo "[WARN] fix-ownership: could not scan ${path}; will retry on next boot"
|
||||
continue
|
||||
|
||||
@@ -71,13 +71,14 @@ Both the script and the boot sweep report progress, so you can tell a slow sweep
|
||||
|
||||
```
|
||||
[INFO] fix-ownership: scanning /media/frigate for ownership mismatches; this may take a while on large filesystems
|
||||
[INFO] fix-ownership: still scanning /media/frigate, 2481000 entries checked, 2390112 need changes
|
||||
[WARN] fix-ownership: adjusting ownership of 4823941 entries under /media/frigate
|
||||
[INFO] fix-ownership: /media/frigate 5% (241197/4823941 entries)
|
||||
[INFO] fix-ownership: /media/frigate 10% (482394/4823941 entries)
|
||||
[INFO] fix-ownership: finished /media/frigate in 12m 4s
|
||||
```
|
||||
|
||||
The scan has no percentage because the total isn't known until it finishes. Watch the boot sweep with `docker logs -f frigate`.
|
||||
The scan has no percentage because the total isn't known until it finishes, so it reports a running count every 30 seconds instead. Watch the boot sweep with `docker logs -f frigate`.
|
||||
|
||||
Once the volumes are aligned, start Frigate normally. A file at `/config/.permissions_version` records what was done, so later boots skip the sweep unless you change `PUID`/`PGID`.
|
||||
|
||||
|
||||
+14
-22
@@ -199,18 +199,14 @@ def events(
|
||||
sub_label_clauses.append(Event.sub_label.is_null())
|
||||
|
||||
for label in filtered_sub_labels:
|
||||
lowered = label.lower()
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) == lowered
|
||||
) # include exact matches (case-insensitive)
|
||||
# SQLite LOWER() only folds ASCII, so lower both sides in SQL to
|
||||
# keep non-ASCII names matching exactly
|
||||
stored = fn.LOWER(Event.sub_label.cast("text"))
|
||||
sub_label_clauses.append(stored == fn.LOWER(label))
|
||||
|
||||
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
|
||||
)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
|
||||
)
|
||||
# include this label when part of a list
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
|
||||
|
||||
sub_label_clause = reduce(operator.or_, sub_label_clauses)
|
||||
clauses.append(sub_label_clause)
|
||||
@@ -633,18 +629,14 @@ def events_search(
|
||||
sub_label_clauses.append(Event.sub_label.is_null())
|
||||
|
||||
for label in filtered_sub_labels:
|
||||
lowered = label.lower()
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) == lowered
|
||||
) # include exact matches (case-insensitive)
|
||||
# SQLite LOWER() only folds ASCII, so lower both sides in SQL to
|
||||
# keep non-ASCII names matching exactly
|
||||
stored = fn.LOWER(Event.sub_label.cast("text"))
|
||||
sub_label_clauses.append(stored == fn.LOWER(label))
|
||||
|
||||
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
|
||||
)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
|
||||
)
|
||||
# include this label when part of a list
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
|
||||
|
||||
event_filters.append(reduce(operator.or_, sub_label_clauses))
|
||||
|
||||
|
||||
@@ -485,6 +485,40 @@ class TestHttpApp(BaseTestHttp):
|
||||
assert sub_labels
|
||||
assert sub_labels == [sub_label]
|
||||
|
||||
def test_get_event_list_filter_sub_labels(self):
|
||||
sub_labels = {
|
||||
"1.cyrillic": "Кузнецова Ира",
|
||||
"2.joined": "Ира, Bob",
|
||||
"3.ascii": "Alice",
|
||||
"4.none": None,
|
||||
}
|
||||
mock_embeddings = Mock()
|
||||
mock_embeddings.search_thumbnail.return_value = [(id, 0.1) for id in sub_labels]
|
||||
|
||||
self.app.frigate_config.semantic_search.enabled = True
|
||||
self.app.embeddings = mock_embeddings
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
for id, sub_label in sub_labels.items():
|
||||
super().insert_mock_event(id)
|
||||
Event.update(sub_label=sub_label).where(Event.id == id).execute()
|
||||
|
||||
search_params = {"search_type": "similarity", "event_id": "1.cyrillic"}
|
||||
|
||||
for path, params in (("/events", {}), ("/events/search", search_params)):
|
||||
|
||||
def ids(query: str) -> set[str]:
|
||||
events = client.get(
|
||||
path, params={**params, "sub_labels": query}
|
||||
).json()
|
||||
return {event["id"] for event in events}
|
||||
|
||||
assert ids("Кузнецова Ира") == {"1.cyrillic"}
|
||||
assert ids("Ира") == {"2.joined"}
|
||||
assert ids("bob") == {"2.joined"}
|
||||
assert ids("ALICE") == {"3.ascii"}
|
||||
assert ids("None") == {"4.none"}
|
||||
|
||||
####################################################################################################################
|
||||
################################### GET /metrics Endpoint #########################################################
|
||||
####################################################################################################################
|
||||
|
||||
@@ -150,17 +150,18 @@ export function TrackingDetails({
|
||||
|
||||
// Fetch the VOD manifest JSON to get the actual clipFrom after keyframe
|
||||
// snapping. The backend may snap clipFrom backwards to a keyframe, making
|
||||
// the video start earlier than the requested time.
|
||||
// the video start earlier than the requested time. Uses sourceOffsetRef so
|
||||
// the manifest describes the same clip as videoSource.
|
||||
const vodManifestUrl = useMemo(() => {
|
||||
if (!event.camera) return null;
|
||||
const startTime =
|
||||
event.start_time + annotationOffset / 1000 - REVIEW_PADDING;
|
||||
const sourceOffset = sourceOffsetRef.current;
|
||||
const startTime = event.start_time + sourceOffset / 1000 - REVIEW_PADDING;
|
||||
const endTime =
|
||||
(event.end_time ?? Date.now() / 1000) +
|
||||
annotationOffset / 1000 +
|
||||
sourceOffset / 1000 +
|
||||
REVIEW_PADDING;
|
||||
return `vod/clip/${event.camera}/start/${startTime}/end/${endTime}`;
|
||||
}, [event, annotationOffset]);
|
||||
}, [event]);
|
||||
|
||||
const { data: vodManifest } = useSWR<VodManifest>(vodManifestUrl, null, {
|
||||
revalidateOnFocus: false,
|
||||
|
||||
Reference in New Issue
Block a user