Skip to content

Commit

Permalink
add config option to set still image resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
roflcoopter committed Jan 18, 2025
1 parent ae3d18a commit 154a683
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 61 deletions.
16 changes: 16 additions & 0 deletions docs/src/pages/components-explorer/components/ffmpeg/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@
"description": "Number of seconds between refreshes of the still image in the frontend.",
"optional": true,
"default": 10
},
{
"type": "integer",
"valueMin": 1,
"name": "width",
"description": "Width of the still image, if different from the stream width.",
"optional": true,
"default": null
},
{
"type": "integer",
"valueMin": 1,
"name": "height",
"description": "Height of the still image, if different from the stream height.",
"optional": true,
"default": null
}
],
"name": "still_image",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@
"description": "Number of seconds between refreshes of the still image in the frontend.",
"optional": true,
"default": 10
},
{
"type": "integer",
"valueMin": 1,
"name": "width",
"description": "Width of the still image, if different from the stream width.",
"optional": true,
"default": null
},
{
"type": "integer",
"valueMin": 1,
"name": "height",
"description": "Height of the still image, if different from the stream height.",
"optional": true,
"default": null
}
],
"name": "still_image",
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/camera/CameraCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ const SuccessCameraCard = ({

useEffect(() => {
// If element is on screen and browser is visible, start interval to fetch images
if (onScreen && isVisible && connected && camera.still_image_available) {
if (onScreen && isVisible && connected && camera.still_image.available) {
updateImage();
updateSnapshot.current = setInterval(
() => {
updateImage();
},
camera.still_image_refresh_interval
? camera.still_image_refresh_interval * 1000
camera.still_image.refresh_interval
? camera.still_image.refresh_interval * 1000
: 10000,
);
// If element is hidden or browser loses focus, stop updating images
Expand All @@ -136,8 +136,8 @@ const SuccessCameraCard = ({
isVisible,
onScreen,
connected,
camera.still_image_available,
camera.still_image_refresh_interval,
camera.still_image.available,
camera.still_image.refresh_interval,
]);

return (
Expand Down Expand Up @@ -184,7 +184,7 @@ const SuccessCameraCard = ({
disableSpinner={snapshotURL.disableSpinner}
disableTransition={snapshotURL.disableTransition}
animationDuration={1000}
aspectRatio={camera.width / camera.height}
aspectRatio={camera.still_image.width / camera.still_image.height}
color={theme.palette.background.default}
onLoad={() => {
setSnapshotURL((prevSnapshotURL) => ({
Expand All @@ -195,7 +195,7 @@ const SuccessCameraCard = ({
}));
}}
errorIcon={
camera.still_image_available
camera.still_image.available
? Image.defaultProps!.loading
: null
}
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,12 @@ export interface Camera {
width: number;
height: number;
access_token: string;
still_image_refresh_interval: number;
still_image_available: boolean;
still_image: {
refresh_interval: number;
available: boolean;
width: number;
height: number;
};
failed: false;
is_on: boolean;
connected: boolean;
Expand Down
64 changes: 24 additions & 40 deletions frontend/tests/lib/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ import {
} from "lib/helpers";
import * as types from "lib/types";

const mockCamera: types.Camera = {
width: 1920,
height: 1080,
identifier: "",
name: "",
access_token: "",
still_image: {
refresh_interval: 0,
available: true,
width: 1920,
height: 1080,
},
failed: false,
is_on: true,
connected: true,
};

describe("sortObj", () => {
it("should sort the object keys in ascending order", () => {
const obj = { c: 3, a: 1, b: 2 };
Expand Down Expand Up @@ -109,53 +126,18 @@ describe("getRecordingVideoJSOptions", () => {

describe("getVideoElement", () => {
it("should render VideoPlayerPlaceholder if recording is null", () => {
const camera: types.Camera = {
width: 1920,
height: 1080,
identifier: "",
name: "",
access_token: "",
still_image_refresh_interval: 0,
still_image_available: true,
failed: false,
is_on: true,
connected: true,
};
const { getByTestId } = render(getVideoElement(camera, null, false));
const { getByTestId } = render(getVideoElement(mockCamera, null, false));
expect(getByTestId("video-player-placeholder")).toBeInTheDocument();
});

it("should render VideoPlayerPlaceholder if recording is undefined", () => {
const camera: types.Camera = {
width: 1920,
height: 1080,
identifier: "",
name: "",
access_token: "",
still_image_refresh_interval: 0,
still_image_available: true,
failed: false,
is_on: true,
connected: true,
};

const { getByTestId } = render(getVideoElement(camera, undefined, false));
const { getByTestId } = render(
getVideoElement(mockCamera, undefined, false),
);
expect(getByTestId("video-player-placeholder")).toBeInTheDocument();
});

it("should render VideoPlayer if recording has values", async () => {
const camera: types.Camera = {
width: 1920,
height: 1080,
identifier: "",
name: "",
access_token: "",
still_image_refresh_interval: 0,
still_image_available: true,
failed: false,
is_on: true,
connected: true,
};
const recording: types.Recording = {
thumbnail_path: "thumbnail.jpg",
hls_url: "video.m3u8",
Expand All @@ -168,7 +150,9 @@ describe("getVideoElement", () => {
trigger_type: "",
trigger_id: 0,
};
const { getByTestId } = render(getVideoElement(camera, recording, false));
const { getByTestId } = render(
getVideoElement(mockCamera, recording, false),
);
await waitFor(() =>
expect(getByTestId("video-player")).toBeInTheDocument(),
);
Expand Down
44 changes: 32 additions & 12 deletions viseron/domains/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
CONFIG_PASSWORD,
CONFIG_REFRESH_INTERVAL,
CONFIG_STILL_IMAGE,
CONFIG_STILL_IMAGE_HEIGHT,
CONFIG_STILL_IMAGE_WIDTH,
CONFIG_URL,
EVENT_CAMERA_STARTED,
EVENT_CAMERA_STATUS,
Expand Down Expand Up @@ -172,8 +174,12 @@ def as_dict(self) -> dict[str, Any]:
"width": self.resolution[0],
"height": self.resolution[1],
"access_token": self.access_token,
"still_image_refresh_interval": self.still_image[CONFIG_REFRESH_INTERVAL],
"still_image_available": self.still_image_available,
"still_image": {
"refresh_interval": self.still_image[CONFIG_REFRESH_INTERVAL],
"available": self.still_image_available,
"width": self.still_image_width,
"height": self.still_image_height,
},
"is_on": self.is_on,
"connected": self.connected,
}
Expand Down Expand Up @@ -269,16 +275,6 @@ def access_token(self) -> str:
"""Return access token."""
return self.access_tokens[-1]

@property
def still_image(self) -> dict[str, Any]:
"""Return still image config."""
return self._config[CONFIG_STILL_IMAGE]

@property
def still_image_configured(self) -> bool:
"""Return if still image is configured."""
return bool(self._config[CONFIG_STILL_IMAGE][CONFIG_URL])

@property
@abstractmethod
def output_fps(self):
Expand Down Expand Up @@ -338,6 +334,30 @@ def connected(self, connected) -> None:
),
)

@property
def still_image(self) -> dict[str, Any]:
"""Return still image config."""
return self._config[CONFIG_STILL_IMAGE]

@property
def still_image_configured(self) -> bool:
"""Return if still image is configured."""
return bool(self._config[CONFIG_STILL_IMAGE][CONFIG_URL])

@property
def still_image_width(self) -> int:
"""Return still image width."""
if self.still_image[CONFIG_STILL_IMAGE_WIDTH]:
return self.still_image[CONFIG_STILL_IMAGE_WIDTH]
return self.resolution[0]

@property
def still_image_height(self) -> int:
"""Return still image height."""
if self.still_image[CONFIG_STILL_IMAGE_HEIGHT]:
return self.still_image[CONFIG_STILL_IMAGE_HEIGHT]
return self.resolution[1]

@property
def still_image_available(self) -> bool:
"""Return if still image is available."""
Expand Down
16 changes: 16 additions & 0 deletions viseron/domains/camera/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
CONFIG_RETAIN,
CONFIG_SAVE_TO_DISK,
CONFIG_STILL_IMAGE,
CONFIG_STILL_IMAGE_HEIGHT,
CONFIG_STILL_IMAGE_WIDTH,
CONFIG_STORAGE,
CONFIG_THUMBNAIL,
CONFIG_URL,
Expand Down Expand Up @@ -70,6 +72,8 @@
DEFAULT_REFRESH_INTERVAL,
DEFAULT_SAVE_TO_DISK,
DEFAULT_STILL_IMAGE,
DEFAULT_STILL_IMAGE_HEIGHT,
DEFAULT_STILL_IMAGE_WIDTH,
DEFAULT_STORAGE,
DEFAULT_THUMBNAIL,
DEFAULT_URL,
Expand Down Expand Up @@ -106,6 +110,8 @@
DESC_RETAIN,
DESC_SAVE_TO_DISK,
DESC_STILL_IMAGE,
DESC_STILL_IMAGE_HEIGHT,
DESC_STILL_IMAGE_WIDTH,
DESC_STORAGE,
DESC_THUMBNAIL,
DESC_URL,
Expand Down Expand Up @@ -289,6 +295,16 @@
default=DEFAULT_REFRESH_INTERVAL,
description=DESC_REFRESH_INTERVAL,
): vol.All(int, vol.Range(min=1)),
vol.Optional(
CONFIG_STILL_IMAGE_WIDTH,
default=DEFAULT_STILL_IMAGE_WIDTH,
description=DESC_STILL_IMAGE_WIDTH,
): Maybe(vol.All(int, vol.Range(min=1))),
vol.Optional(
CONFIG_STILL_IMAGE_HEIGHT,
default=DEFAULT_STILL_IMAGE_HEIGHT,
description=DESC_STILL_IMAGE_HEIGHT,
): Maybe(vol.All(int, vol.Range(min=1))),
}
)

Expand Down
9 changes: 9 additions & 0 deletions viseron/domains/camera/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,18 @@
CONFIG_PASSWORD = "password"
CONFIG_AUTHENTICATION = "authentication"
CONFIG_REFRESH_INTERVAL = "refresh_interval"
CONFIG_STILL_IMAGE_WIDTH = "width"
CONFIG_STILL_IMAGE_HEIGHT = "height"


DEFAULT_STILL_IMAGE: Final = None
DEFAULT_URL: Final = None
DEFAULT_USERNAME: Final = None
DEFAULT_PASSWORD: Final = None
DEFAULT_AUTHENTICATION: Final = None
DEFAULT_REFRESH_INTERVAL: Final = 10
DEFAULT_STILL_IMAGE_WIDTH: Final = None
DEFAULT_STILL_IMAGE_HEIGHT: Final = None

DESC_STILL_IMAGE = "Options for still image."
DESC_URL = (
Expand All @@ -192,6 +197,10 @@
DESC_REFRESH_INTERVAL = (
"Number of seconds between refreshes of the still image in the frontend."
)
DESC_STILL_IMAGE_WIDTH = "Width of the still image, if different from the stream width."
DESC_STILL_IMAGE_HEIGHT = (
"Height of the still image, if different from the stream height."
)

INCLUSION_GROUP_AUTHENTICATION = "authentication"

Expand Down

0 comments on commit 154a683

Please sign in to comment.