Skip to content
Merged

0.115.4 #40706

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/airly/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Airly",
"documentation": "https://www.home-assistant.io/integrations/airly",
"codeowners": ["@bieniu"],
"requirements": ["airly==0.0.2"],
"requirements": ["airly==1.0.0"],
"config_flow": true,
"quality_scale": "platinum"
}
44 changes: 39 additions & 5 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from homeassistant.components.media_player.const import (
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_EXTRA,
DOMAIN as DOMAIN_MP,
SERVICE_PLAY_MEDIA,
)
Expand Down Expand Up @@ -46,7 +47,7 @@
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, entity_sources
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.network import get_url
from homeassistant.loader import bind_hass
Expand Down Expand Up @@ -695,14 +696,47 @@ async def async_handle_play_stream_service(camera, service_call):
options=camera.stream_options,
)
data = {
ATTR_ENTITY_ID: entity_ids,
ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}",
ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt],
}

await hass.services.async_call(
DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context
)
# It is required to send a different payload for cast media players
cast_entity_ids = [
entity
for entity, source in entity_sources(hass).items()
if entity in entity_ids and source["domain"] == "cast"
]
other_entity_ids = list(set(entity_ids) - set(cast_entity_ids))

if cast_entity_ids:
await hass.services.async_call(
DOMAIN_MP,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: cast_entity_ids,
**data,
ATTR_MEDIA_EXTRA: {
"stream_type": "LIVE",
"media_info": {
"hlsVideoSegmentFormat": "fmp4",
},
},
},
blocking=True,
context=service_call.context,
)

if other_entity_ids:
await hass.services.async_call(
DOMAIN_MP,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: other_entity_ids,
**data,
},
blocking=True,
context=service_call.context,
)


async def async_handle_record_service(camera, call):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cast/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Google Cast",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==7.2.1"],
"requirements": ["pychromecast==7.5.0"],
"after_dependencies": ["cloud", "http", "media_source", "tts", "zeroconf"],
"zeroconf": ["_googlecast._tcp.local."],
"codeowners": ["@emontnemery"]
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/cast/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from homeassistant.components.http.auth import async_sign_path
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
from homeassistant.components.media_player.const import (
ATTR_MEDIA_EXTRA,
MEDIA_TYPE_MOVIE,
MEDIA_TYPE_MUSIC,
MEDIA_TYPE_TVSHOW,
Expand Down Expand Up @@ -574,7 +575,9 @@ def play_media(self, media_type, media_id, **kwargs):
except NotImplementedError:
_LOGGER.error("App %s not supported", app_name)
else:
self._chromecast.media_controller.play_media(media_id, media_type)
self._chromecast.media_controller.play_media(
media_id, media_type, **kwargs.get(ATTR_MEDIA_EXTRA, {})
)

# ========== Properties ==========
@property
Expand Down
10 changes: 3 additions & 7 deletions homeassistant/components/fitbit/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,7 @@ def fitbit_configuration_callback(callback_data):
else:
setup_platform(hass, config, add_entities, discovery_info)

start_url = (
f"{get_url(hass, require_current_request=True)}{FITBIT_AUTH_CALLBACK_PATH}"
)
start_url = f"{get_url(hass)}{FITBIT_AUTH_CALLBACK_PATH}"

description = f"""Please create a Fitbit developer app at
https://dev.fitbit.com/apps/new.
Expand Down Expand Up @@ -222,7 +220,7 @@ def request_oauth_completion(hass):
def fitbit_configuration_callback(callback_data):
"""Handle configuration updates."""

start_url = f"{get_url(hass, require_current_request=True)}{FITBIT_AUTH_START}"
start_url = f"{get_url(hass)}{FITBIT_AUTH_START}"

description = f"Please authorize Fitbit by visiting {start_url}"

Expand Down Expand Up @@ -314,9 +312,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
config_file.get(CONF_CLIENT_ID), config_file.get(CONF_CLIENT_SECRET)
)

redirect_uri = (
f"{get_url(hass, require_current_request=True)}{FITBIT_AUTH_CALLBACK_PATH}"
)
redirect_uri = f"{get_url(hass)}{FITBIT_AUTH_CALLBACK_PATH}"

fitbit_auth_start_url, _ = oauth.authorize_token_url(
redirect_uri=redirect_uri,
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/media_player/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
ATTR_MEDIA_DURATION,
ATTR_MEDIA_ENQUEUE,
ATTR_MEDIA_EPISODE,
ATTR_MEDIA_EXTRA,
ATTR_MEDIA_PLAYLIST,
ATTR_MEDIA_POSITION,
ATTR_MEDIA_POSITION_UPDATED_AT,
Expand Down Expand Up @@ -139,6 +140,7 @@
vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string,
vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string,
vol.Optional(ATTR_MEDIA_ENQUEUE): cv.boolean,
vol.Optional(ATTR_MEDIA_EXTRA, default={}): dict,
}

ATTR_TO_PROPERTY = [
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/media_player/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ATTR_MEDIA_CONTENT_TYPE = "media_content_type"
ATTR_MEDIA_DURATION = "media_duration"
ATTR_MEDIA_ENQUEUE = "enqueue"
ATTR_MEDIA_EXTRA = "extra"
ATTR_MEDIA_EPISODE = "media_episode"
ATTR_MEDIA_PLAYLIST = "media_playlist"
ATTR_MEDIA_POSITION = "media_position"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/plugwise/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "plugwise",
"name": "Plugwise",
"documentation": "https://www.home-assistant.io/integrations/plugwise",
"requirements": ["Plugwise_Smile==1.4.0"],
"requirements": ["Plugwise_Smile==1.5.1"],
"codeowners": ["@CoMPaTech", "@bouwew"],
"zeroconf": ["_plugwise._tcp.local."],
"config_flow": true
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/shelly/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ async def async_step_user(self, user_input=None):
info = await self._async_get_info(host)
except HTTP_CONNECT_ERRORS:
errors["base"] = "cannot_connect"
except aioshelly.FirmwareUnsupported:
return self.async_abort(reason="unsupported_firmware")
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
Expand Down Expand Up @@ -128,6 +130,8 @@ async def async_step_zeroconf(self, zeroconf_info):
self.info = info = await self._async_get_info(zeroconf_info["host"])
except HTTP_CONNECT_ERRORS:
return self.async_abort(reason="cannot_connect")
except aioshelly.FirmwareUnsupported:
return self.async_abort(reason="unsupported_firmware")

await self.async_set_unique_id(info["mac"])
self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]})
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/shelly/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Shelly",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/shelly",
"requirements": ["aioshelly==0.3.2"],
"requirements": ["aioshelly==0.3.3"],
"zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }],
"codeowners": ["@balloob", "@bieniu"]
}
3 changes: 2 additions & 1 deletion homeassistant/components/shelly/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"unsupported_firmware": "The device is using an unsupported firmware version."
}
}
}
111 changes: 111 additions & 0 deletions homeassistant/components/stream/fmp4utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,114 @@ def get_m4s(segment: io.BytesIO, sequence: int) -> bytes:
mfra_location = next(find_box(segment, b"mfra"))
segment.seek(moof_location)
return segment.read(mfra_location - moof_location)


def get_codec_string(segment: io.BytesIO) -> str:
"""Get RFC 6381 codec string."""
codecs = []

# Find moov
moov_location = next(find_box(segment, b"moov"))

# Find tracks
for trak_location in find_box(segment, b"trak", moov_location):
# Drill down to media info
mdia_location = next(find_box(segment, b"mdia", trak_location))
minf_location = next(find_box(segment, b"minf", mdia_location))
stbl_location = next(find_box(segment, b"stbl", minf_location))
stsd_location = next(find_box(segment, b"stsd", stbl_location))

# Get stsd box
segment.seek(stsd_location)
stsd_length = int.from_bytes(segment.read(4), byteorder="big")
segment.seek(stsd_location)
stsd_box = segment.read(stsd_length)

# Base Codec
codec = stsd_box[20:24].decode("utf-8")

# Handle H264
if (
codec in ("avc1", "avc2", "avc3", "avc4")
and stsd_length > 110
and stsd_box[106:110] == b"avcC"
):
profile = stsd_box[111:112].hex()
compatibility = stsd_box[112:113].hex()
level = stsd_box[113:114].hex()
codec += "." + profile + compatibility + level

# Handle H265
elif (
codec in ("hev1", "hvc1")
and stsd_length > 110
and stsd_box[106:110] == b"hvcC"
):
tmp_byte = int.from_bytes(stsd_box[111:112], byteorder="big")

# Profile Space
codec += "."
profile_space_map = {0: "", 1: "A", 2: "B", 3: "C"}
profile_space = tmp_byte >> 6
codec += profile_space_map[profile_space]
general_profile_idc = tmp_byte & 31
codec += str(general_profile_idc)

# Compatibility
codec += "."
general_profile_compatibility = int.from_bytes(
stsd_box[112:116], byteorder="big"
)
reverse = 0
for i in range(0, 32):
reverse |= general_profile_compatibility & 1
if i == 31:
break
reverse <<= 1
general_profile_compatibility >>= 1
codec += hex(reverse)[2:]

# Tier Flag
if (tmp_byte & 32) >> 5 == 0:
codec += ".L"
else:
codec += ".H"
codec += str(int.from_bytes(stsd_box[122:123], byteorder="big"))

# Constraint String
has_byte = False
constraint_string = ""
for i in range(121, 115, -1):
gci = int.from_bytes(stsd_box[i : i + 1], byteorder="big")
if gci or has_byte:
constraint_string = "." + hex(gci)[2:] + constraint_string
has_byte = True
codec += constraint_string

# Handle Audio
elif codec == "mp4a":
oti = None
dsi = None

# Parse ES Descriptors
oti_loc = stsd_box.find(b"\x04\x80\x80\x80")
if oti_loc > 0:
oti = stsd_box[oti_loc + 5 : oti_loc + 6].hex()
codec += f".{oti}"

dsi_loc = stsd_box.find(b"\x05\x80\x80\x80")
if dsi_loc > 0:
dsi_length = int.from_bytes(
stsd_box[dsi_loc + 4 : dsi_loc + 5], byteorder="big"
)
dsi_data = stsd_box[dsi_loc + 5 : dsi_loc + 5 + dsi_length]
dsi0 = int.from_bytes(dsi_data[0:1], byteorder="big")
dsi = (dsi0 & 248) >> 3
if dsi == 31 and len(dsi_data) >= 2:
dsi1 = int.from_bytes(dsi_data[1:2], byteorder="big")
dsi = 32 + ((dsi0 & 7) << 3) + ((dsi1 & 224) >> 5)
codec += f".{dsi}"

codecs.append(codec)

return ",".join(codecs)
Loading