Compare commits

...

7 Commits

5 changed files with 79 additions and 47 deletions

View File

@ -131,8 +131,9 @@ class AudioTranscriptionPostProcessor(PostProcessorApi):
}, },
) )
# Embed the description # Embed the description if semantic search is enabled
self.embeddings.embed_description(event_id, transcription) if self.config.semantic_search.enabled:
self.embeddings.embed_description(event_id, transcription)
except DoesNotExist: except DoesNotExist:
logger.debug("No recording found for audio transcription post-processing") logger.debug("No recording found for audio transcription post-processing")

View File

@ -46,7 +46,7 @@ def should_update_state(prev_event: Event, current_event: Event) -> bool:
if prev_event["sub_label"] != current_event["sub_label"]: if prev_event["sub_label"] != current_event["sub_label"]:
return True return True
if len(prev_event["current_zones"]) < len(current_event["current_zones"]): if set(prev_event["current_zones"]) != set(current_event["current_zones"]):
return True return True
return False return False

View File

@ -86,11 +86,11 @@ class TimelineProcessor(threading.Thread):
event_data: dict[Any, Any], event_data: dict[Any, Any],
) -> bool: ) -> bool:
"""Handle object detection.""" """Handle object detection."""
save = False
camera_config = self.config.cameras[camera] camera_config = self.config.cameras[camera]
event_id = event_data["id"] event_id = event_data["id"]
timeline_entry = { # Base timeline entry data that all entries will share
base_entry = {
Timeline.timestamp: event_data["frame_time"], Timeline.timestamp: event_data["frame_time"],
Timeline.camera: camera, Timeline.camera: camera,
Timeline.source: "tracked_object", Timeline.source: "tracked_object",
@ -123,40 +123,64 @@ class TimelineProcessor(threading.Thread):
e[Timeline.data]["sub_label"] = event_data["sub_label"] e[Timeline.data]["sub_label"] = event_data["sub_label"]
if event_type == EventStateEnum.start: if event_type == EventStateEnum.start:
timeline_entry = base_entry.copy()
timeline_entry[Timeline.class_type] = "visible" timeline_entry[Timeline.class_type] = "visible"
save = True self.insert_or_save(timeline_entry, prev_event_data, event_data)
elif event_type == EventStateEnum.update: elif event_type == EventStateEnum.update:
# Check all conditions and create timeline entries for each change
entries_to_save = []
# Check for zone changes
prev_zones = set(prev_event_data["current_zones"])
current_zones = set(event_data["current_zones"])
zones_changed = prev_zones != current_zones
# Only save "entered_zone" events when the object is actually IN zones
if ( if (
len(prev_event_data["current_zones"]) < len(event_data["current_zones"]) zones_changed
and not event_data["stationary"] and not event_data["stationary"]
and len(current_zones) > 0
): ):
timeline_entry[Timeline.class_type] = "entered_zone" zone_entry = base_entry.copy()
timeline_entry[Timeline.data]["zones"] = event_data["current_zones"] zone_entry[Timeline.class_type] = "entered_zone"
save = True zone_entry[Timeline.data] = base_entry[Timeline.data].copy()
elif prev_event_data["stationary"] != event_data["stationary"]: zone_entry[Timeline.data]["zones"] = event_data["current_zones"]
timeline_entry[Timeline.class_type] = ( entries_to_save.append(zone_entry)
# Check for stationary status change
if prev_event_data["stationary"] != event_data["stationary"]:
stationary_entry = base_entry.copy()
stationary_entry[Timeline.class_type] = (
"stationary" if event_data["stationary"] else "active" "stationary" if event_data["stationary"] else "active"
) )
save = True stationary_entry[Timeline.data] = base_entry[Timeline.data].copy()
elif prev_event_data["attributes"] == {} and event_data["attributes"] != {}: entries_to_save.append(stationary_entry)
timeline_entry[Timeline.class_type] = "attribute"
timeline_entry[Timeline.data]["attribute"] = list( # Check for new attributes
if prev_event_data["attributes"] == {} and event_data["attributes"] != {}:
attribute_entry = base_entry.copy()
attribute_entry[Timeline.class_type] = "attribute"
attribute_entry[Timeline.data] = base_entry[Timeline.data].copy()
attribute_entry[Timeline.data]["attribute"] = list(
event_data["attributes"].keys() event_data["attributes"].keys()
)[0] )[0]
if len(event_data["current_attributes"]) > 0: if len(event_data["current_attributes"]) > 0:
timeline_entry[Timeline.data]["attribute_box"] = to_relative_box( attribute_entry[Timeline.data]["attribute_box"] = to_relative_box(
camera_config.detect.width, camera_config.detect.width,
camera_config.detect.height, camera_config.detect.height,
event_data["current_attributes"][0]["box"], event_data["current_attributes"][0]["box"],
) )
save = True entries_to_save.append(attribute_entry)
elif event_type == EventStateEnum.end:
timeline_entry[Timeline.class_type] = "gone"
save = True
if save: # Save all entries
for entry in entries_to_save:
self.insert_or_save(entry, prev_event_data, event_data)
elif event_type == EventStateEnum.end:
timeline_entry = base_entry.copy()
timeline_entry[Timeline.class_type] = "gone"
self.insert_or_save(timeline_entry, prev_event_data, event_data) self.insert_or_save(timeline_entry, prev_event_data, event_data)
def handle_api_entry( def handle_api_entry(

View File

@ -233,7 +233,7 @@ export function GroupedClassificationCard({
}); });
if (!best) { if (!best) {
return group.at(-1); best = group.at(-1)!;
} }
const bestTyped: ClassificationItemData = best; const bestTyped: ClassificationItemData = best;
@ -377,30 +377,34 @@ export function GroupedClassificationCard({
)} )}
</ContentDescription> </ContentDescription>
</div> </div>
{isDesktop && ( {classifiedEvent && (
<div className="flex flex-row justify-between"> <div
{classifiedEvent && ( className={cn(
<Tooltip> "flex",
<TooltipTrigger asChild> isDesktop && "flex-row justify-between",
<div isMobile && "absolute right-4 top-8",
className="cursor-pointer"
tabIndex={-1}
onClick={() => {
navigate(`/explore?event_id=${classifiedEvent.id}`);
}}
>
<LuSearch className="size-4 text-secondary-foreground" />
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("details.item.button.viewInExplore", {
ns: "views/explore",
})}
</TooltipContent>
</TooltipPortal>
</Tooltip>
)} )}
>
<Tooltip>
<TooltipTrigger asChild>
<div
className="cursor-pointer"
tabIndex={-1}
onClick={() => {
navigate(`/explore?event_id=${classifiedEvent.id}`);
}}
>
<LuSearch className="size-4 text-secondary-foreground" />
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("details.item.button.viewInExplore", {
ns: "views/explore",
})}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div> </div>
)} )}
</Header> </Header>

View File

@ -309,7 +309,10 @@ export function RecordingView({
currentTimeRange.after <= currentTime && currentTimeRange.after <= currentTime &&
currentTimeRange.before >= currentTime currentTimeRange.before >= currentTime
) { ) {
mainControllerRef.current?.seekToTimestamp(currentTime, true); mainControllerRef.current?.seekToTimestamp(
currentTime,
mainControllerRef.current.isPlaying(),
);
} else { } else {
updateSelectedSegment(currentTime, true); updateSelectedSegment(currentTime, true);
} }