Skip to content

Commit

Permalink
Use IntEnum for stream orientation (home-assistant#81835)
Browse files Browse the repository at this point in the history
* Use IntEnum for stream orientation

* Rename enum values

* Add comments

* Fix import
  • Loading branch information
uvjustin authored Nov 9, 2022
1 parent 9de4d7c commit 84725f1
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 28 deletions.
3 changes: 2 additions & 1 deletion homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from homeassistant.components.stream import (
FORMAT_CONTENT_TYPE,
OUTPUT_FORMATS,
Orientation,
Stream,
create_stream,
)
Expand Down Expand Up @@ -869,7 +870,7 @@ async def websocket_get_prefs(
vol.Required("type"): "camera/update_prefs",
vol.Required("entity_id"): cv.entity_id,
vol.Optional(PREF_PRELOAD_STREAM): bool,
vol.Optional(PREF_ORIENTATION): vol.All(int, vol.Range(min=1, max=8)),
vol.Optional(PREF_ORIENTATION): vol.Coerce(Orientation),
}
)
@websocket_api.async_response
Expand Down
19 changes: 11 additions & 8 deletions homeassistant/components/camera/prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from typing import Final, Union, cast

from homeassistant.components.stream import Orientation
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
Expand All @@ -18,11 +19,11 @@
class CameraEntityPreferences:
"""Handle preferences for camera entity."""

def __init__(self, prefs: dict[str, bool | int]) -> None:
def __init__(self, prefs: dict[str, bool | Orientation]) -> None:
"""Initialize prefs."""
self._prefs = prefs

def as_dict(self) -> dict[str, bool | int]:
def as_dict(self) -> dict[str, bool | Orientation]:
"""Return dictionary version."""
return self._prefs

Expand All @@ -32,9 +33,11 @@ def preload_stream(self) -> bool:
return cast(bool, self._prefs.get(PREF_PRELOAD_STREAM, False))

@property
def orientation(self) -> int:
def orientation(self) -> Orientation:
"""Return the current stream orientation settings."""
return self._prefs.get(PREF_ORIENTATION, 1)
return cast(
Orientation, self._prefs.get(PREF_ORIENTATION, Orientation.NO_TRANSFORM)
)


class CameraPreferences:
Expand All @@ -45,11 +48,11 @@ def __init__(self, hass: HomeAssistant) -> None:
self._hass = hass
# The orientation prefs are stored in in the entity registry options
# The preload_stream prefs are stored in this Store
self._store = Store[dict[str, dict[str, Union[bool, int]]]](
self._store = Store[dict[str, dict[str, Union[bool, Orientation]]]](
hass, STORAGE_VERSION, STORAGE_KEY
)
# Local copy of the preload_stream prefs
self._prefs: dict[str, dict[str, bool | int]] | None = None
self._prefs: dict[str, dict[str, bool | Orientation]] | None = None

async def async_initialize(self) -> None:
"""Finish initializing the preferences."""
Expand All @@ -63,9 +66,9 @@ async def async_update(
entity_id: str,
*,
preload_stream: bool | UndefinedType = UNDEFINED,
orientation: int | UndefinedType = UNDEFINED,
orientation: Orientation | UndefinedType = UNDEFINED,
stream_options: dict[str, str] | UndefinedType = UNDEFINED,
) -> dict[str, bool | int]:
) -> dict[str, bool | Orientation]:
"""Update camera preferences.
Returns a dict with the preferences on success.
Expand Down
8 changes: 5 additions & 3 deletions homeassistant/components/stream/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
STREAM_SETTINGS_NON_LL_HLS,
IdleTimer,
KeyFrameConverter,
Orientation,
StreamOutput,
StreamSettings,
)
Expand All @@ -82,6 +83,7 @@
"SOURCE_TIMEOUT",
"Stream",
"create_stream",
"Orientation",
]

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -229,7 +231,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
part_target_duration=conf[CONF_PART_DURATION],
hls_advance_part_limit=max(int(3 / conf[CONF_PART_DURATION]), 3),
hls_part_timeout=2 * conf[CONF_PART_DURATION],
orientation=1,
orientation=Orientation.NO_TRANSFORM,
)
else:
hass.data[DOMAIN][ATTR_SETTINGS] = STREAM_SETTINGS_NON_LL_HLS
Expand Down Expand Up @@ -292,12 +294,12 @@ def __init__(
self._diagnostics = Diagnostics()

@property
def orientation(self) -> int:
def orientation(self) -> Orientation:
"""Return the current orientation setting."""
return self._stream_settings.orientation

@orientation.setter
def orientation(self, value: int) -> None:
def orientation(self, value: Orientation) -> None:
"""Set the stream orientation setting."""
self._stream_settings.orientation = value

Expand Down
18 changes: 16 additions & 2 deletions homeassistant/components/stream/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from collections import deque
from collections.abc import Callable, Coroutine, Iterable
import datetime
from enum import IntEnum
import logging
from typing import TYPE_CHECKING, Any

Expand Down Expand Up @@ -35,6 +36,19 @@
PROVIDERS: Registry[str, type[StreamOutput]] = Registry()


class Orientation(IntEnum):
"""Orientations for stream transforms. These are based on EXIF orientation tags."""

NO_TRANSFORM = 1
MIRROR = 2
ROTATE_180 = 3
FLIP = 4
ROTATE_LEFT_AND_FLIP = 5
ROTATE_LEFT = 6
ROTATE_RIGHT_AND_FLIP = 7
ROTATE_RIGHT = 8


@attr.s(slots=True)
class StreamSettings:
"""Stream settings."""
Expand All @@ -44,7 +58,7 @@ class StreamSettings:
part_target_duration: float = attr.ib()
hls_advance_part_limit: int = attr.ib()
hls_part_timeout: float = attr.ib()
orientation: int = attr.ib()
orientation: Orientation = attr.ib()


STREAM_SETTINGS_NON_LL_HLS = StreamSettings(
Expand All @@ -53,7 +67,7 @@ class StreamSettings:
part_target_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS,
hls_advance_part_limit=3,
hls_part_timeout=TARGET_SEGMENT_DURATION_NON_LL_HLS,
orientation=1,
orientation=Orientation.NO_TRANSFORM,
)


Expand Down
28 changes: 16 additions & 12 deletions homeassistant/components/stream/fmp4utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from homeassistant.exceptions import HomeAssistantError

from .core import Orientation

if TYPE_CHECKING:
from io import BufferedIOBase

Expand Down Expand Up @@ -179,22 +181,24 @@ def read_init(bytes_io: BufferedIOBase) -> bytes:
ROTATE_RIGHT_FLIP = (ZERO32 + ONE32 + ZERO32) + (ONE32 + ZERO32 + ZERO32)

TRANSFORM_MATRIX_TOP = (
# The first two entries are just to align the indices with the EXIF orientation tags
b"",
b"",
MIRROR,
ROTATE_180,
FLIP,
ROTATE_LEFT_FLIP,
ROTATE_LEFT,
ROTATE_RIGHT_FLIP,
ROTATE_RIGHT,
# The index into this tuple corresponds to the EXIF orientation tag
# Only index values of 2 through 8 are used
# The first two entries are just to keep everything aligned
b"", # 0
b"", # 1
MIRROR, # 2
ROTATE_180, # 3
FLIP, # 4
ROTATE_LEFT_FLIP, # 5
ROTATE_LEFT, # 6
ROTATE_RIGHT_FLIP, # 7
ROTATE_RIGHT, # 8
)


def transform_init(init: bytes, orientation: int) -> bytes:
def transform_init(init: bytes, orientation: Orientation) -> bytes:
"""Change the transformation matrix in the header."""
if orientation == 1:
if orientation == Orientation.NO_TRANSFORM:
return init
# Find moov
moov_location = next(find_box(init, b"moov"))
Expand Down
4 changes: 2 additions & 2 deletions tests/components/camera/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,15 @@ async def test_websocket_update_orientation_prefs(hass, hass_ws_client, mock_cam
assert response["success"]

er_camera_prefs = registry.async_get("camera.demo_uniquecamera").options[DOMAIN]
assert er_camera_prefs[PREF_ORIENTATION] == 3
assert er_camera_prefs[PREF_ORIENTATION] == camera.Orientation.ROTATE_180
assert response["result"][PREF_ORIENTATION] == er_camera_prefs[PREF_ORIENTATION]
# Check that the preference was saved
await client.send_json(
{"id": 12, "type": "camera/get_prefs", "entity_id": "camera.demo_uniquecamera"}
)
msg = await client.receive_json()
# orientation entry for this camera should have been added
assert msg["result"]["orientation"] == 3
assert msg["result"]["orientation"] == camera.Orientation.ROTATE_180


async def test_play_stream_service_no_source(hass, mock_camera, mock_stream):
Expand Down

0 comments on commit 84725f1

Please sign in to comment.