mirror of
https://github.com/home-assistant/core.git
synced 2025-07-11 07:17:12 +00:00
Cleanup and strict typing for MJPEG integration (#66526)
This commit is contained in:
parent
389653dc01
commit
00221f1d66
@ -119,6 +119,7 @@ homeassistant.components.lookin.*
|
|||||||
homeassistant.components.luftdaten.*
|
homeassistant.components.luftdaten.*
|
||||||
homeassistant.components.mailbox.*
|
homeassistant.components.mailbox.*
|
||||||
homeassistant.components.media_player.*
|
homeassistant.components.media_player.*
|
||||||
|
homeassistant.components.mjpeg.*
|
||||||
homeassistant.components.modbus.*
|
homeassistant.components.modbus.*
|
||||||
homeassistant.components.modem_callerid.*
|
homeassistant.components.modem_callerid.*
|
||||||
homeassistant.components.media_source.*
|
homeassistant.components.media_source.*
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections.abc import Iterable
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from aiohttp import web
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import requests
|
import requests
|
||||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||||
@ -65,17 +67,30 @@ async def async_setup_platform(
|
|||||||
|
|
||||||
if discovery_info:
|
if discovery_info:
|
||||||
config = PLATFORM_SCHEMA(discovery_info)
|
config = PLATFORM_SCHEMA(discovery_info)
|
||||||
async_add_entities([MjpegCamera(config)])
|
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
MjpegCamera(
|
||||||
|
config[CONF_NAME],
|
||||||
|
config[CONF_AUTHENTICATION],
|
||||||
|
config.get(CONF_USERNAME),
|
||||||
|
config.get(CONF_PASSWORD),
|
||||||
|
config[CONF_MJPEG_URL],
|
||||||
|
config.get(CONF_STILL_IMAGE_URL),
|
||||||
|
config[CONF_VERIFY_SSL],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def filter_urllib3_logging():
|
def filter_urllib3_logging() -> None:
|
||||||
"""Filter header errors from urllib3 due to a urllib3 bug."""
|
"""Filter header errors from urllib3 due to a urllib3 bug."""
|
||||||
urllib3_logger = logging.getLogger("urllib3.connectionpool")
|
urllib3_logger = logging.getLogger("urllib3.connectionpool")
|
||||||
if not any(isinstance(x, NoHeaderErrorFilter) for x in urllib3_logger.filters):
|
if not any(isinstance(x, NoHeaderErrorFilter) for x in urllib3_logger.filters):
|
||||||
urllib3_logger.addFilter(NoHeaderErrorFilter())
|
urllib3_logger.addFilter(NoHeaderErrorFilter())
|
||||||
|
|
||||||
|
|
||||||
def extract_image_from_mjpeg(stream):
|
def extract_image_from_mjpeg(stream: Iterable[bytes]) -> bytes | None:
|
||||||
"""Take in a MJPEG stream object, return the jpg from it."""
|
"""Take in a MJPEG stream object, return the jpg from it."""
|
||||||
data = b""
|
data = b""
|
||||||
|
|
||||||
@ -93,19 +108,30 @@ def extract_image_from_mjpeg(stream):
|
|||||||
|
|
||||||
return data[jpg_start : jpg_end + 2]
|
return data[jpg_start : jpg_end + 2]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class MjpegCamera(Camera):
|
class MjpegCamera(Camera):
|
||||||
"""An implementation of an IP camera that is reachable over a URL."""
|
"""An implementation of an IP camera that is reachable over a URL."""
|
||||||
|
|
||||||
def __init__(self, device_info):
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
authentication: str,
|
||||||
|
username: str | None,
|
||||||
|
password: str | None,
|
||||||
|
mjpeg_url: str,
|
||||||
|
still_image_url: str | None,
|
||||||
|
verify_ssl: bool,
|
||||||
|
) -> None:
|
||||||
"""Initialize a MJPEG camera."""
|
"""Initialize a MJPEG camera."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._name = device_info.get(CONF_NAME)
|
self._attr_name = name
|
||||||
self._authentication = device_info.get(CONF_AUTHENTICATION)
|
self._authentication = authentication
|
||||||
self._username = device_info.get(CONF_USERNAME)
|
self._username = username
|
||||||
self._password = device_info.get(CONF_PASSWORD)
|
self._password = password
|
||||||
self._mjpeg_url = device_info[CONF_MJPEG_URL]
|
self._mjpeg_url = mjpeg_url
|
||||||
self._still_image_url = device_info.get(CONF_STILL_IMAGE_URL)
|
self._still_image_url = still_image_url
|
||||||
|
|
||||||
self._auth = None
|
self._auth = None
|
||||||
if (
|
if (
|
||||||
@ -114,7 +140,7 @@ class MjpegCamera(Camera):
|
|||||||
and self._authentication == HTTP_BASIC_AUTHENTICATION
|
and self._authentication == HTTP_BASIC_AUTHENTICATION
|
||||||
):
|
):
|
||||||
self._auth = aiohttp.BasicAuth(self._username, password=self._password)
|
self._auth = aiohttp.BasicAuth(self._username, password=self._password)
|
||||||
self._verify_ssl = device_info.get(CONF_VERIFY_SSL)
|
self._verify_ssl = verify_ssl
|
||||||
|
|
||||||
async def async_camera_image(
|
async def async_camera_image(
|
||||||
self, width: int | None = None, height: int | None = None
|
self, width: int | None = None, height: int | None = None
|
||||||
@ -137,10 +163,10 @@ class MjpegCamera(Camera):
|
|||||||
return image
|
return image
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
_LOGGER.error("Timeout getting camera image from %s", self._name)
|
_LOGGER.error("Timeout getting camera image from %s", self.name)
|
||||||
|
|
||||||
except aiohttp.ClientError as err:
|
except aiohttp.ClientError as err:
|
||||||
_LOGGER.error("Error getting new camera image from %s: %s", self._name, err)
|
_LOGGER.error("Error getting new camera image from %s: %s", self.name, err)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -168,7 +194,9 @@ class MjpegCamera(Camera):
|
|||||||
with closing(req) as response:
|
with closing(req) as response:
|
||||||
return extract_image_from_mjpeg(response.iter_content(102400))
|
return extract_image_from_mjpeg(response.iter_content(102400))
|
||||||
|
|
||||||
async def handle_async_mjpeg_stream(self, request):
|
async def handle_async_mjpeg_stream(
|
||||||
|
self, request: web.Request
|
||||||
|
) -> web.StreamResponse | None:
|
||||||
"""Generate an HTTP MJPEG stream from the camera."""
|
"""Generate an HTTP MJPEG stream from the camera."""
|
||||||
# aiohttp don't support DigestAuth -> Fallback
|
# aiohttp don't support DigestAuth -> Fallback
|
||||||
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
|
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
|
||||||
@ -180,15 +208,10 @@ class MjpegCamera(Camera):
|
|||||||
|
|
||||||
return await async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
return await async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of this camera."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
|
|
||||||
class NoHeaderErrorFilter(logging.Filter):
|
class NoHeaderErrorFilter(logging.Filter):
|
||||||
"""Filter out urllib3 Header Parsing Errors due to a urllib3 bug."""
|
"""Filter out urllib3 Header Parsing Errors due to a urllib3 bug."""
|
||||||
|
|
||||||
def filter(self, record):
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
"""Filter out Header Parsing Errors."""
|
"""Filter out Header Parsing Errors."""
|
||||||
return "Failed to parse headers" not in record.getMessage()
|
return "Failed to parse headers" not in record.getMessage()
|
||||||
|
11
mypy.ini
11
mypy.ini
@ -1118,6 +1118,17 @@ no_implicit_optional = true
|
|||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.mjpeg.*]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.modbus.*]
|
[mypy-homeassistant.components.modbus.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user