Skip to content

Commit

Permalink
Merge pull request #1448 from dermotduffy/predefined-zoom
Browse files Browse the repository at this point in the history
Add support for pre-defined zoom/pan settings per camera
  • Loading branch information
dermotduffy authored Apr 28, 2024
2 parents f96a122 + 63797ea commit dc9ea6d
Show file tree
Hide file tree
Showing 63 changed files with 2,050 additions and 634 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
# build).
ignore: 'hacs'

- name: Verify docs links
run: yarn run docs-check-links

- name: Upload javascript
uses: actions/upload-artifact@v3
with:
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
!> This documentation is for the unreleased `dev` branch! See the [Github README](https://github.com/dermotduffy/frigate-hass-card?tab=readme-ov-file#frigate-lovelace-card) for documentation of the released version.

# Getting Started

## Installation
Expand Down
4 changes: 2 additions & 2 deletions docs/_coverpage.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
![logo](images/frigate-logo.svg)

# Frigate Card <small>6.0.0 *DEV*</small>
# Frigate Card <small>6.0.0 <small style="color: red">_*DEV*_</small></small>

> A comprehensive camera card for Home Assistant.
[GitHub](https://github.com/dermotduffy/frigate-hass-card/)
[Documentation](README.md)
[Documentation](README.md)
38 changes: 35 additions & 3 deletions docs/configuration/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,28 @@ frigate_card_action: camera_ui
Open the UI for the selected camera engine (e.g. the Frigate UI).
### `default`
### `change_zoom`

Change to the default view.
Zoom in and/or pan for a given camera.

```yaml
action: custom:frigate-card-action
frigate_card_action: default
frigate_card_action: change_zoom
[...]
```

| Parameter | Description |
| - | - |
| `action` | Must be `custom:frigate-card-action`. |
| `frigate_card_action` | Must be `change_zoom`. |
| `target_id` | The [camera ID](cameras/README.md?id=cameras) or a media ID (e.g. `frigate` event ID) to change zoom/pam settings for. |
| `zoom` | Optional parameter that controls how much to zoom-in. See the [camera zoom parameter](cameras/README.md?id=layout-configuration). |
| `pan` | Optional parameter that controls how much to pan-x/y. See the [camera pan parameter](cameras/README.md?id=layout-configuration). |

?> If neither `zoom` nor `pan` are specified the camera will return to its default zoom and pan settings.

See [example of automatically zoom/panning based on state](../examples.md?id=automatically-zoom-based-on-state).

### `clip`, `clips`, `image`, `live`, `recording`, `recordings`, `snapshot`, `snapshots`

Change to the specified view.
Expand All @@ -136,6 +149,15 @@ action: custom:frigate-card-action
frigate_card_action: [view]
```

### `default`

Change to the default view.

```yaml
action: custom:frigate-card-action
frigate_card_action: default
```

### `download`

Download the displayed media.
Expand Down Expand Up @@ -518,4 +540,14 @@ elements:
frigate_card_action: ptz
ptz_action: preset
ptz_preset: doorway
- type: custom:frigate-card-menu-icon
icon: mdi:alpha-r-circle
title: Change Zoom
tap_action:
action: custom:frigate-card-action
frigate_card_action: change_zoom
pan:
x: 50
y: 50
zoom: 1
```
18 changes: 16 additions & 2 deletions docs/configuration/cameras/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,12 @@ cameras:
| Option | Default | Description |
| - | - | - |
| `fit` | `contain` | If `contain`, the media is contained within the card and letterboxed if necessary. If `cover`, the media is expanded proportionally (i.e. maintaining the media aspect ratio) until the camera/card dimensions are fully covered. If `fill`, the media is stretched to fill the camera/card dimensions (i.e. ignoring the media aspect ratio). See [CSS object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) for technical details and a visualization. |
| `pan` | | A dictionary that may contain an `x` and `y` percentage (`0` - `100`) to control the position of the media when "digitally zoomed in" (see `zoom` parameter). This can be effectively used to "pan"/cut the media shown. A value of `0` means maximally to the left or top of the media, a value of `100` means maximally to the right or bottom of the media. See visualizations below. |
| `position` | | A dictionary that may contain an `x` and `y` percentage (`0` - `100`) to control the position of the media when the fit is `cover` (for other values of `fit` this option has no effect). This can be effectively used to "pan"/cut the media shown. At any given time, only one of `x` and `y` will have an effect, depending on whether media width is larger than the camera/card dimensions (in which case `x` controls the position) or the media height is larger than the camera/card dimensions (in which case `y` controls the position). A value of `0` means maximally to the left or top of the media, a value of `100` means maximally to the right or bottom of the media. See [CSS object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) for technicals. See visualizations below. |
| `view_box` | | A dictionary that may contain a `top`, `bottom`, `left` and `right` percentage (`0` - `100`) to precisely crop what part of the media to show by specifying a % inset value from each side. Browsers apply this cropping after `position` and `fit` have been applied. See visualizations below. |
| `view_box` | | A dictionary that may contain a `top`, `bottom`, `left` and `right` percentage (`0` - `100`) to precisely crop what part of the media to show by specifying a % inset value from each side. Browsers apply this cropping after `position` and `fit` have been applied. Unlike `zoom`, the user cannot dynamically zoom back out -- however the builtin media controls will work as normal. See visualizations below. Limited [browser support](https://caniuse.com/mdn-css_properties_object-view-box): ![](../../images/browsers/chrome_16x16.png "Google Chrome :no-zoom") ![](../../images/browsers/chromium_16x16.png "Chromium :no-zoom") ![](../../images/browsers/edge_16x16.png "Microsoft Edge :no-zoom") |
| `zoom` | `1.0` | A value between `1.0` and `10.0` inclusive that defines how much additional "digital zoom" to apply to this camera by default. Unlike with `view_box` the user can easily "zoom back out". Often used in conjuction with `pan`. When zoomed in the [builtin browser media controls](../live.md?id=controls) will automatically be disabled (as otherwise they would be enlarged also). |

!> The `view_box` parameter only has effect on browsers that support [`object-view-box`](https://caniuse.com/mdn-css_properties_object-view-box). As of writing this is exclusively Chromium based browsers (e.g. Google Chrome or Microsoft Edge).
?> Layout operations are effectively applied in this order: `fit`, `position`, `view_box`, `zoom` then `pan`.

See [media layout examples](../../examples.md?id=media-layout).

Expand All @@ -169,6 +171,11 @@ See [media layout examples](../../examples.md?id=media-layout).

![](../../images/media_layout/view-box.png "Media Layout Position: Taller than wider :size=400")

#### `pan` and `zoom`: Predefined panning and zooming

![](../../images/media_layout/pan-zoom.png "Panning and zooming :size=400")


## `triggers`

The `triggers` block configures what triggers a camera. Triggering can be used
Expand Down Expand Up @@ -300,6 +307,13 @@ cameras:
movies:
directory_pattern: '%Y-%m-%d'
file_pattern: '%H-%M-%S'
- camera_entity: camera.zoomed
dimensions:
layout:
zoom: 2.0
pan:
x: 50
y: 50
cameras_global:
live_provider: ha
```
5 changes: 5 additions & 0 deletions docs/configuration/conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ conditions:
| `condition` | Must be `view`. |
| `views` | A list of [views](view.md?id=supported-views) in which this condition is satified (e.g. `clips`). |

?> Internally, views associated with the media viewer (e.g. `clip`, `snapshot`,
`recording`) are translated to a special view called `media` after the relevant
media is fetched. When including views as part of a [condition](conditions.md),
you may need to refer to this special `media` view.

# Fully expanded reference

[](common/expanded-warning.md ':include')
Expand Down
9 changes: 8 additions & 1 deletion docs/configuration/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ menu:
| `camera_ui` | The `camera_ui` menu button: brings the user to a context-appropriate page on the UI of their camera engine (e.g. the Frigate camera homepage). Will only appear if the camera engine supports a camera UI (e.g. if `frigate.url` option is set for `frigate` engine users).|
| `cameras` | The camera selection submenu. Will only appear if multiple cameras are configured. |
| `clips` | The `clips` view menu button: brings the user to the `clips` view on tap and the most-recent `clip` view on hold. |
| `default_zoom` | The `default_zoom` button allows easily returning the camera to default pan/zoom. |
| `display_mode` | The `display_mode` button allows changing between single and grid views. |
| `download` | The `download` menu button: allow direct download of the media being displayed.|
| `expand` | The `expand` menu button: expand the card into a popup/dialog. |
Expand All @@ -53,7 +54,7 @@ menu:
| Option | Default | Description |
| - | - | - |
| `alignment` | `matching` | Whether this button should have an alignment that is `matching` the menu alignment or `opposing` the menu. Can be used to create two separate groups of buttons on the menu. `priority` orders buttons within a given `alignment`. |
| `enabled` | `true` for `frigate`, `cameras`, `substreams`, `live`, `clips`, `snapshots`, `timeline`, `download`, `camera_ui`, `fullscreen`, `media_player`, `display_mode`. `false` for `image`, `expand`, `microphone`, `mute`, `play`, `recordings`, `screenshot`, `ptz` | Whether or not to show the button. |
| `enabled` | `true` for `frigate`, `cameras`, `substreams`, `live`, `clips`, `snapshots`, `timeline`, `download`, `camera_ui`, `fullscreen`, `media_player`, `display_mode` and `default_zoom`. `false` for `image`, `expand`, `microphone`, `mute`, `play`, `recordings`, `screenshot`, `ptz` | Whether or not to show the button. |
| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. |
| `priority` | `50` | The button priority. Higher priority buttons are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`, when the menu alignment is `left`). Minimum `0`, maximum `100`.|

Expand Down Expand Up @@ -90,9 +91,15 @@ menu:
enabled: true
alignment: matching
icon: mdi:video-switch
default_zoom:
priority: 50
enabled: true
alignment: matching
icon: mdi:magnify-close
substreams:
priority: 50
enabled: true
alignment: matching
icon: mdi:video-input-component
live:
priority: 50
Expand Down
92 changes: 92 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -997,3 +997,95 @@ cameras:
webrtc_card:
ui: true
```
## Zoom
### Pre-defining camera zoom and pan
This example changes the default [zoom/pan settings for a camera](./configuration/cameras/README.md?id=layout-configuration) to always zoom in on a given area:
```yaml
type: custom:frigate-card
cameras:
- camera_entity: camera.office
dimensions:
layout:
zoom: 3
pan:
x: 20
y: 80
```
### Disable zooming in media views
This example prevents zooming on the media viewer but keeps it on in other views (e.g. `live` view):

```yaml
type: custom:frigate-card
cameras:
- camera_entity: camera.office
dimensions:
layout:
zoom: 3
pan:
x: 20
y: 80
media_viewer:
zoomable: false
```

### Different zoom settings in media viewer vs `live`

This example uses different settings for the media viewer and `live` view, by overriding the camera configuration:

```yaml
type: custom:frigate-card
cameras:
- camera_entity: camera.office
dimensions:
layout:
zoom: 2
overrides:
- conditions:
- condition: view
views:
- media
set:
'cameras[0].dimensions.layout':
zoom: 3
pan:
x: 100
y: 100
```

### Automatically zoom based on state

This example automatically zooms in and out based on the state of an entity:

```yaml
type: custom:frigate-card
cameras:
- camera_entity: camera.living_room
live_provider: go2rtc
debug:
logging: true
automations:
- conditions:
- condition: state
entity: binary_sensor.door_contact
state: 'on'
actions:
- action: custom:frigate-card-action
frigate_card_action: change_zoom
target_id: camera.living_room
zoom: 4
pan:
x: 38
y: 20
actions_not:
- action: custom:frigate-card-action
frigate_card_action: change_zoom
target_id: camera.living_room
```

![](images/zoom-automation.gif 'Zoom automation example :size=400')
Binary file added docs/images/browsers/chrome_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/browsers/chromium_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/browsers/edge_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/media_layout/fit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/media_layout/pan-zoom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/media_layout/position-shorter-than-height.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/media_layout/position-thinner-than-width.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/media_layout/view-box.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/zoom-automation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions scripts/update-images.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#!/bin/bash

WIDTH=800
WIDTH=1200

URL_FIT="https://docs.google.com/drawings/d/e/2PACX-1vTq0SVS8HWs3jGC0jjNpJoYfMbZS6P27CYyPlDhSa9OhdB_3jEb0HTLLYwu8Nv3J1TdjAJppcjTiVNy/pub?w=$WIDTH"
URL_THINNER_THAN_WIDTH="https://docs.google.com/drawings/d/e/2PACX-1vRkMd89N0tkZt5IghPKhR6gs8zMhB-5_hx5QfP6BCxbsSIga_h44IczP06Sj_YnKkxhe0lRdeR-uh04/pub?w=$WIDTH"
URL_SHORTER_THAN_HEIGHT="https://docs.google.com/drawings/d/e/2PACX-1vTKVsXEWIbj9lYKrCeugdLKcK_rOwAZZDK8IzhPdHH4wMwV2v7kEI0nsn2Qgugb00qDVHsE7kE8CBIC/pub?w=$WIDTH"
URL_VIEW_BOX="https://docs.google.com/drawings/d/e/2PACX-1vRpPNsaStxW2ENmv1kfUQg41cua9XQ2sQziq2PC8LdRCtkvjHSKYH3CyPO1Pz7kOdiQ2yQKrBX88-TF/pub?w=$WIDTH"
URL_PAN_ZOOM="https://docs.google.com/drawings/d/e/2PACX-1vTcbuMiqKo7-w0my3jaht1xdEFUhLSur1nSxhkxbuX0eagwuisaNCBfKvDjFY4hzNVGRZoBy7YehaMn/pub?w=$WIDTH"

DIR_MEDIA_LAYOUT_IMAGES="./docs/images/media_layout"

wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/fit.png" "$URL_FIT" && \
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/position-thinner-than-width.png" "$URL_THINNER_THAN_WIDTH" && \
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/position-shorter-than-height.png" "$URL_SHORTER_THAN_HEIGHT" && \
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/view-box.png" "$URL_VIEW_BOX"
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/view-box.png" "$URL_VIEW_BOX" && \
wget -q -O "$DIR_MEDIA_LAYOUT_IMAGES/pan-zoom.png" "$URL_PAN_ZOOM"

10 changes: 5 additions & 5 deletions src/camera-manager/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,6 @@ export class CameraManager {
this._store = options?.store ?? new CameraManagerStore();
}

public async reset(): Promise<void> {
await this._store.reset();
}

public async initializeCamerasFromConfig(): Promise<boolean> {
const config = this._api.getConfigManager().getConfig();
const hass = this._api.getHASSManager().getHASS();
Expand All @@ -149,7 +145,7 @@ export class CameraManager {
);

const resetAndInitialize = async () => {
await this.reset();
await this._reset();
await this._initializeCameras(cameras);
};

Expand All @@ -165,6 +161,10 @@ export class CameraManager {
return true;
}

protected async _reset(): Promise<void> {
await this._store.reset();
}

protected async _getEnginesForCameras(
camerasConfig: CamerasConfig,
): Promise<Map<CameraConfig, CameraManagerEngine>> {
Expand Down
17 changes: 14 additions & 3 deletions src/card-controller/actions-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getActionConfigGivenAction,
} from '../utils/action.js';
import { getStreamCameraID } from '../utils/substream.js';
import { generateViewContextForZoomChange } from '../components-lib/zoom/zoom-view-context.js';
import { CardActionsManagerAPI } from './types.js';

const interactionSchema = z.object({
Expand Down Expand Up @@ -263,9 +264,19 @@ export class ActionsManager {
}
break;
case 'show_ptz':
this._api
.getViewManager()
.setViewWithNewContext({ live: { ptzVisible: frigateCardAction.show_ptz } });
this._api.getViewManager().setViewWithMergedContext({
live: { ptzVisible: frigateCardAction.show_ptz },
});
break;
case 'change_zoom':
this._api.getViewManager().setViewWithMergedContext(
generateViewContextForZoomChange(frigateCardAction.target_id, {
zoom: {
pan: frigateCardAction.pan,
zoom: frigateCardAction.zoom,
},
}),
);
break;
default:
console.warn(`Frigate card received unknown card action: ${action}`);
Expand Down
12 changes: 5 additions & 7 deletions src/card-controller/card-element-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export class CardElementManager {
// Manually call the location change handler as the card will be
// disconnected/reconnected when dashboard 'tab' changes happen within HA.
this._api.getQueryStringManager().executeAll();

// Make sure reconnections call the initialization code.
this._element.requestUpdate();
}

public elementDisconnected(): void {
Expand All @@ -108,15 +111,10 @@ export class CardElementManager {
this._api.getMediaLoadedInfoManager().clear();
this._api.getFullscreenManager().disconnect();

// Reset and uninitialize cameras to cause them to reinitialize on
// Uninitialize cameras to cause them to reinitialize on
// reconnection, to ensure the state subscription/unsubscription works
// correctly for triggers.
this._api
.getCameraManager()
.reset()
.then(() =>
this._api.getInitializationManager().uninitialize(InitializationAspect.CAMERAS),
);
this._api.getInitializationManager().uninitialize(InitializationAspect.CAMERAS),

this._element.removeEventListener(
'mousemove',
Expand Down
1 change: 1 addition & 0 deletions src/card-controller/conditions-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export class ConditionsManager {
const config = this._api.getConfigManager().getConfig();
const conditions: FrigateCardCondition[] = [];
config?.overrides?.forEach((override) => conditions.push(...override.conditions));
config?.automations?.forEach((automation) => conditions.push(...automation.conditions));

// Element conditions can be arbitrarily nested underneath conditionals and
// custom elements that this card may not known. Here we recursively parse
Expand Down
Loading

0 comments on commit dc9ea6d

Please sign in to comment.