Skip to content

Commit

Permalink
Tweaks and fixes (#11541)
Browse files Browse the repository at this point in the history
* Update config version to be stored inside of the config

* Don't remove items from list when navigating back

* Use video api instead of webps for live current hour filmstrip

* Check that the config file is writable

* Show camera name when camera is offline

* Show camera name when offline

* Cleanup
  • Loading branch information
NickM-27 committed May 26, 2024
1 parent 63d81be commit c2eac10
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 117 deletions.
1 change: 1 addition & 0 deletions frigate/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,7 @@ class FrigateConfig(FrigateBaseModel):
default_factory=TimestampStyleConfig,
title="Global timestamp style configuration.",
)
version: Optional[float] = Field(default=None, title="Current config version.")

def runtime_config(self, plus_api: PlusApi = None) -> FrigateConfig:
"""Merge camera config with globals."""
Expand Down
28 changes: 11 additions & 17 deletions frigate/util/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
def migrate_frigate_config(config_file: str):
"""handle migrating the frigate config."""
logger.info("Checking if frigate config needs migration...")
version_file = os.path.join(CONFIG_DIR, ".version")

if not os.path.isfile(version_file):
previous_version = 0.13
else:
with open(version_file) as f:
try:
previous_version = float(f.readline())
except Exception:
previous_version = 0.13
if not os.access(config_file, mode=os.W_OK):
logger.error("Config file is read-only, unable to migrate config file.")
return

yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
with open(config_file, "r") as f:
config: dict[str, dict[str, any]] = yaml.load(f)

previous_version = config.get("version", 0.13)

if previous_version == CURRENT_CONFIG_VERSION:
logger.info("frigate config does not need migration...")
Expand All @@ -35,11 +36,6 @@ def migrate_frigate_config(config_file: str):
logger.info("copying config as backup...")
shutil.copy(config_file, os.path.join(CONFIG_DIR, "backup_config.yaml"))

yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
with open(config_file, "r") as f:
config: dict[str, dict[str, any]] = yaml.load(f)

if previous_version < 0.14:
logger.info(f"Migrating frigate config from {previous_version} to 0.14...")
new_config = migrate_014(config)
Expand All @@ -57,9 +53,6 @@ def migrate_frigate_config(config_file: str):
os.path.join(EXPORT_DIR, file), os.path.join(EXPORT_DIR, new_name)
)

with open(version_file, "w") as f:
f.write(str(CURRENT_CONFIG_VERSION))

logger.info("Finished frigate config migration...")


Expand Down Expand Up @@ -141,6 +134,7 @@ def migrate_014(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:

new_config["cameras"][name] = camera_config

new_config["version"] = 0.14
return new_config


Expand Down
17 changes: 2 additions & 15 deletions web/src/components/Statusbar.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
import { useFrigateStats } from "@/api/ws";
import {
StatusBarMessagesContext,
StatusMessage,
} from "@/context/statusbar-provider";
import useStats from "@/hooks/use-stats";
import { FrigateStats } from "@/types/stats";
import useStats, { useAutoFrigateStats } from "@/hooks/use-stats";
import { useContext, useEffect, useMemo } from "react";
import { FaCheck } from "react-icons/fa";
import { IoIosWarning } from "react-icons/io";
import { MdCircle } from "react-icons/md";
import { Link } from "react-router-dom";
import useSWR from "swr";

export default function Statusbar() {
const { data: initialStats } = useSWR<FrigateStats>("stats", {
revalidateOnFocus: false,
});
const { payload: latestStats } = useFrigateStats();
const { messages, addMessage, clearMessages } = useContext(
StatusBarMessagesContext,
)!;

const stats = useMemo(() => {
if (latestStats) {
return latestStats;
}

return initialStats;
}, [initialStats, latestStats]);
const stats = useAutoFrigateStats();

const cpuPercent = useMemo(() => {
const systemCpu = stats?.cpu_usages["frigate.full_system"]?.cpu;
Expand Down
21 changes: 6 additions & 15 deletions web/src/components/card/AnimatedEventCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import { REVIEW_PADDING, ReviewSegment } from "@/types/review";
import { useNavigate } from "react-router-dom";
import { RecordingStartingPoint } from "@/types/record";
import axios from "axios";
import {
InProgressPreview,
VideoPreview,
} from "../player/PreviewThumbnailPlayer";
import { VideoPreview } from "../player/PreviewThumbnailPlayer";
import { isCurrentHour } from "@/utils/dateUtil";
import { useCameraPreviews } from "@/hooks/use-camera-previews";
import { baseUrl } from "@/api/baseUrl";

type AnimatedEventCardProps = {
event: ReviewSegment;
Expand Down Expand Up @@ -105,18 +103,11 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
windowVisible={windowVisible}
/>
) : (
<InProgressPreview
review={event}
timeRange={{
after: event.start_time,
before: event.end_time ?? event.start_time + 20,
}}
<video
src={`${baseUrl}api/review/${event.id}/preview?format=ts`}
muted
autoPlay
loop
showProgress={false}
setReviewed={() => {}}
setIgnoreClick={() => {}}
isPlayingBack={() => {}}
windowVisible={windowVisible}
/>
)}
</div>
Expand Down
15 changes: 11 additions & 4 deletions web/src/components/player/LivePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function LivePlayer({
}: LivePlayerProps) {
// camera activity

const { activeMotion, activeTracking, objects } =
const { activeMotion, activeTracking, objects, offline } =
useCameraActivity(cameraConfig);

const cameraActive = useMemo(
Expand Down Expand Up @@ -224,9 +224,16 @@ export default function LivePlayer({
/>
</div>

<div className="absolute right-2 top-2 size-4">
{activeMotion && (
<MdCircle className="size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
<div className="absolute right-2 top-2">
{!offline && activeMotion && (
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
)}
{offline && (
<Chip
className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize`}
>
{cameraConfig.name.replaceAll("_", " ")}
</Chip>
)}
</div>
</div>
Expand Down
21 changes: 21 additions & 0 deletions web/src/hooks/use-camera-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import { useTimelineUtils } from "./use-timeline-utils";
import { ObjectType } from "@/types/ws";
import useDeepMemo from "./use-deep-memo";
import { isEqual } from "lodash";
import { useAutoFrigateStats } from "./use-stats";

type useCameraActivityReturn = {
activeTracking: boolean;
activeMotion: boolean;
objects: ObjectType[];
offline: boolean;
};

export function useCameraActivity(
Expand Down Expand Up @@ -116,12 +118,31 @@ export function useCameraActivity(
handleSetObjects(newObjects);
}, [camera, updatedEvent, objects, handleSetObjects]);

// determine if camera is offline

const stats = useAutoFrigateStats();

const offline = useMemo(() => {
if (!stats) {
return false;
}

const cameras = stats["cameras"];

if (!cameras) {
return false;
}

return cameras[camera.name].camera_fps == 0;
}, [camera, stats]);

return {
activeTracking: hasActiveObjects,
activeMotion: detectingMotion
? detectingMotion === "ON"
: initialCameraState?.motion === true,
objects,
offline,
};
}

Expand Down
18 changes: 18 additions & 0 deletions web/src/hooks/use-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useMemo } from "react";
import useSWR from "swr";
import useDeepMemo from "./use-deep-memo";
import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { useFrigateStats } from "@/api/ws";

export default function useStats(stats: FrigateStats | undefined) {
const { data: config } = useSWR<FrigateConfig>("config");
Expand Down Expand Up @@ -91,3 +92,20 @@ export default function useStats(stats: FrigateStats | undefined) {

return { potentialProblems };
}

export function useAutoFrigateStats() {
const { data: initialStats } = useSWR<FrigateStats>("stats", {
revalidateOnFocus: false,
});
const { payload: latestStats } = useFrigateStats();

const stats = useMemo(() => {
if (latestStats) {
return latestStats;
}

return initialStats;
}, [initialStats, latestStats]);

return stats;
}
64 changes: 63 additions & 1 deletion web/src/pages/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ReviewSegment,
ReviewSeverity,
ReviewSummary,
SegmentedReviewData,
} from "@/types/review";
import { getTimestampOffset } from "@/utils/dateUtil";
import EventView from "@/views/events/EventView";
Expand Down Expand Up @@ -138,6 +139,66 @@ export default function Events() {
},
);

const reviewItems = useMemo<SegmentedReviewData>(() => {
if (!reviews) {
return undefined;
}

const all: ReviewSegment[] = [];
const alerts: ReviewSegment[] = [];
const detections: ReviewSegment[] = [];
const motion: ReviewSegment[] = [];

reviews?.forEach((segment) => {
all.push(segment);

switch (segment.severity) {
case "alert":
alerts.push(segment);
break;
case "detection":
detections.push(segment);
break;
default:
motion.push(segment);
break;
}
});

return {
all: all,
alert: alerts,
detection: detections,
significant_motion: motion,
};
}, [reviews]);

const currentItems = useMemo(() => {
if (!reviewItems || !severity) {
return null;
}

let current;

if (reviewFilter?.showAll) {
current = reviewItems.all;
} else {
current = reviewItems[severity];
}

if (!current || current.length == 0) {
return [];
}

if (reviewFilter?.showReviewed != 1) {
return current.filter((seg) => !seg.has_been_reviewed);
} else {
return current;
}
// only refresh when severity or filter changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [severity, reviewFilter, reviewItems?.all.length]);

// review summary

const { data: reviewSummary, mutate: updateSummary } = useSWR<ReviewSummary>(
Expand Down Expand Up @@ -353,7 +414,8 @@ export default function Events() {
} else {
return (
<EventView
reviews={reviews}
reviewItems={reviewItems}
currentReviewItems={currentItems}
reviewSummary={reviewSummary}
relevantPreviews={allPreviews}
timeRange={selectedTimeRange}
Expand Down
9 changes: 9 additions & 0 deletions web/src/types/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export type ReviewData = {
zones: string[];
};

export type SegmentedReviewData =
| {
all: ReviewSegment[];
alert: ReviewSegment[];
detection: ReviewSegment[];
significant_motion: ReviewSegment[];
}
| undefined;

export type ReviewFilter = {
cameras?: string[];
labels?: string[];
Expand Down
Loading

0 comments on commit c2eac10

Please sign in to comment.