Skip to content

Commit

Permalink
Merge pull request #110 from hunterjm/release-0.9.0
Browse files Browse the repository at this point in the history
Release 0.9.0 Updates
  • Loading branch information
blakeblackshear committed Aug 26, 2021
2 parents 4812c7b + a7fb052 commit d60261d
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 236 deletions.
41 changes: 38 additions & 3 deletions custom_components/frigate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
import re
from typing import Any, Callable, Final

from awesomeversion import AwesomeVersion

from custom_components.frigate.config_flow import get_config_entry_title
from homeassistant.components.mqtt.subscription import (
async_subscribe_topics,
async_unsubscribe_topics,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_MODEL, CONF_HOST, CONF_URL
from homeassistant.core import Config, HomeAssistant, callback
Expand All @@ -36,17 +39,25 @@
from homeassistant.components.mqtt.models import ( # pylint: disable=no-name-in-module # pragma: no cover
Message as ReceiveMessage,
)

from .api import FrigateApiClient, FrigateApiClientError
from .const import (
ATTR_CLIENT,
ATTR_CONFIG,
ATTR_COORDINATOR,
DOMAIN,
FRIGATE_RELEASES_URL,
FRIGATE_VERSION_ERROR_CUTOFF,
NAME,
PLATFORMS,
STARTUP_MESSAGE,
)
from .views import ClipsProxyView, NotificationsProxyView, RecordingsProxyView
from .views import (
NotificationsProxyView,
SnapshotsProxyView,
VodProxyView,
VodSegmentProxyView,
)

SCAN_INTERVAL = timedelta(seconds=5)

Expand Down Expand Up @@ -115,9 +126,10 @@ async def async_setup(hass: HomeAssistant, config: Config) -> bool:
hass.data.setdefault(DOMAIN, {})

session = async_get_clientsession(hass)
hass.http.register_view(ClipsProxyView(session))
hass.http.register_view(RecordingsProxyView(session))
hass.http.register_view(SnapshotsProxyView(session))
hass.http.register_view(NotificationsProxyView(session))
hass.http.register_view(VodProxyView(session))
hass.http.register_view(VodSegmentProxyView(session))
return True


Expand All @@ -134,6 +146,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except FrigateApiClientError as exc:
raise ConfigEntryNotReady from exc

if AwesomeVersion(server_version) <= AwesomeVersion(FRIGATE_VERSION_ERROR_CUTOFF):
_LOGGER.error(
"Using a Frigate server version < %s is not compatible. -- "
"Please consider upgrading: %s",
FRIGATE_VERSION_ERROR_CUTOFF,
FRIGATE_RELEASES_URL,
)

return False

model = f"{(await async_get_integration(hass, DOMAIN)).version}/{server_version}"

hass.data[DOMAIN][entry.entry_id] = {
Expand All @@ -145,6 +167,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

hass.config_entries.async_setup_platforms(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(_async_entry_updated))

# cleanup old clips switch if exists
entity_registry = er.async_get(hass)
for camera in config["cameras"].keys():
unique_id = get_frigate_entity_unique_id(
entry.entry_id, SWITCH_DOMAIN, f"{camera}_clips"
)
entity_id = entity_registry.async_get_entity_id(
SWITCH_DOMAIN, DOMAIN, unique_id
)
if entity_id:
entity_registry.async_remove(entity_id)

return True


Expand Down
2 changes: 2 additions & 0 deletions custom_components/frigate/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Base component constants
NAME = "Frigate"
DOMAIN = "frigate"
FRIGATE_VERSION_ERROR_CUTOFF = "0.8.4"
FRIGATE_RELEASES_URL = "https://github.com/blakeblackshear/frigate/releases"

# Icons
ICON_CAR = "mdi:shield-car"
Expand Down
68 changes: 28 additions & 40 deletions custom_components/frigate/media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
)

_LOGGER = logging.getLogger(__name__)

ITEM_LIMIT = 50
SECONDS_IN_DAY = 60 * 60 * 24
SECONDS_IN_MONTH = SECONDS_IN_DAY * 31
Expand Down Expand Up @@ -129,7 +130,7 @@ class FrigateMediaType(enum.Enum):
def mime_type(self) -> str:
"""Get mime type for this frigate media type."""
if self == FrigateMediaType.CLIPS:
return "video/mp4"
return "application/x-mpegURL"
else:
return "image/jpg"

Expand All @@ -153,7 +154,7 @@ def media_class(self) -> str:
def extension(self) -> str:
"""Get filename extension."""
if self == FrigateMediaType.CLIPS:
return "mp4"
return "m3u8"
else:
return "jpg"

Expand All @@ -166,7 +167,11 @@ class EventIdentifier(Identifier):
validator=[attr.validators.in_(FrigateMediaType)]
)

name: str = attr.ib(
id: str = attr.ib(
validator=[attr.validators.instance_of(str)],
)

camera: str = attr.ib(
validator=[attr.validators.instance_of(str)],
)

Expand All @@ -177,7 +182,8 @@ def __str__(self) -> str:
self.frigate_instance_id,
self.get_identifier_type(),
self.frigate_media_type.value,
self.name,
self.camera,
self.id,
)
)

Expand All @@ -190,14 +196,15 @@ def from_str(
data.split("/"), default_frigate_instance_id
)

if len(parts) != 4 or parts[1] != cls.get_identifier_type():
if len(parts) != 5 or parts[1] != cls.get_identifier_type():
return None

try:
return cls(
frigate_instance_id=parts[0],
frigate_media_type=FrigateMediaType(parts[2]),
name=parts[3],
camera=parts[3],
id=parts[4],
)
except ValueError:
return None
Expand All @@ -209,7 +216,10 @@ def get_identifier_type(cls) -> str:

def get_frigate_server_path(self) -> str:
"""Get the equivalent Frigate server path."""
return f"clips/{self.name}"
if self.frigate_media_type == FrigateMediaType.CLIPS:
return f"vod/event/{self.id}/index.{self.frigate_media_type.extension}"
else:
return f"clips/{self.camera}-{self.id}.{self.frigate_media_type.extension}"

@property
def mime_type(self) -> str:
Expand Down Expand Up @@ -380,10 +390,6 @@ class RecordingIdentifier(Identifier):
default=None, validator=[attr.validators.instance_of((str, type(None)))]
)

recording_name: str | None = attr.ib(
default=None, validator=[attr.validators.instance_of((str, type(None)))]
)

@classmethod
def from_str(
cls, data: str, default_frigate_instance_id: str | None = None
Expand All @@ -403,7 +409,6 @@ def from_str(
day=cls._get_index(parts, 3),
hour=cls._get_index(parts, 4),
camera=cls._get_index(parts, 5),
recording_name=cls._get_index(parts, 6),
)
except ValueError:
return None
Expand All @@ -419,7 +424,6 @@ def __str__(self) -> str:
f"{self.day:02}" if self.day is not None else None,
f"{self.hour:02}" if self.hour is not None else None,
self.camera,
self.recording_name,
)
]
)
Expand All @@ -439,19 +443,20 @@ def get_frigate_server_path(self) -> str:
# missing attribute.

in_parts = [
self.get_identifier_type(),
self.get_identifier_type() if not self.camera else "vod",
self.year_month,
f"{self.day:02}" if self.day is not None else None,
f"{self.hour:02}" if self.hour is not None else None,
self.camera,
self.recording_name,
"index.m3u8" if self.camera else None,
]

out_parts = []
for val in in_parts:
if val is None:
break
out_parts.append(str(val))

return "/".join(out_parts)

def get_changes_to_set_next_empty(self, data: str) -> dict[str, str]:
Expand All @@ -464,7 +469,7 @@ def get_changes_to_set_next_empty(self, data: str) -> dict[str, str]:
@property
def mime_type(self) -> str:
"""Get mime type for this identifier."""
return "video/mp4"
return "application/x-mpegURL"

@property
def media_class(self) -> str:
Expand All @@ -473,7 +478,7 @@ def media_class(self) -> str:

@property
def media_type(self) -> str:
"""Get mime type for this identifier."""
"""Get media type for this identifier."""
return str(MEDIA_TYPE_VIDEO)


Expand Down Expand Up @@ -657,9 +662,9 @@ async def async_browse_media(
except FrigateApiClientError as exc:
raise MediaSourceError from exc

if identifier.camera:
return self._browse_recordings(identifier, recordings_folder)
return self._browse_recording_folders(identifier, recordings_folder)
if identifier.hour is None:
return self._browse_recording_folders(identifier, recordings_folder)
return self._browse_recordings(identifier, recordings_folder)

raise MediaSourceError("Invalid media source identifier: %s" % item.identifier)

Expand Down Expand Up @@ -782,10 +787,8 @@ def _build_event_response(
identifier=EventIdentifier(
identifier.frigate_instance_id,
frigate_media_type=identifier.frigate_media_type,
name=(
f"{event['camera']}-{event['id']}."
+ identifier.frigate_media_type.extension
),
camera=event["camera"],
id=event["id"],
),
media_class=identifier.media_class,
media_content_type=identifier.media_type,
Expand Down Expand Up @@ -1176,14 +1179,6 @@ def _generate_recording_title(
) -> str | None:
"""Generate recording title."""
try:
if identifier.camera is not None:
if folder is None:
return get_friendly_name(identifier.camera)
minute_seconds = folder["name"].replace(".mp4", "")
return dt.datetime.strptime(
f"{identifier.hour}.{minute_seconds}", "%H.%M.%S"
).strftime("%T")

if identifier.hour is not None:
if folder is None:
return dt.datetime.strptime(
Expand Down Expand Up @@ -1277,17 +1272,10 @@ def _browse_recordings(

for recording in recordings:
title = self._generate_recording_title(identifier, recording)
if not title:
_LOGGER.warning(
"Skipping non-standard recording name: %s", recording["name"]
)
continue
base.children.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=attr.evolve(
identifier, recording_name=recording["name"]
),
identifier=attr.evolve(identifier, camera=recording["name"]),
media_class=identifier.media_class,
media_content_type=identifier.media_type,
title=title,
Expand Down
4 changes: 2 additions & 2 deletions custom_components/frigate/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def async_setup_entry(
entities.extend(
[
FrigateSwitch(entry, frigate_config, camera, "detect"),
FrigateSwitch(entry, frigate_config, camera, "clips"),
FrigateSwitch(entry, frigate_config, camera, "recordings"),
FrigateSwitch(entry, frigate_config, camera, "snapshots"),
]
)
Expand Down Expand Up @@ -70,7 +70,7 @@ def __init__(

if self._switch_name == "snapshots":
self._icon = ICON_IMAGE_MULTIPLE
elif self._switch_name == "clips":
elif self._switch_name == "recordings":
self._icon = ICON_FILM_MULTIPLE
else:
self._icon = ICON_MOTION_SENSOR
Expand Down

0 comments on commit d60261d

Please sign in to comment.