From 079d8e32c99a899f80d88e38ea63678c56258edf Mon Sep 17 00:00:00 2001 From: Giacomo Saccaggi <44114264+GiacomoSaccaggi@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:16:51 +0200 Subject: [PATCH] Use iter_chunked for STT audio stream to prevent LineTooLong (#180831) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/stt/__init__.py | 9 +++++-- tests/components/stt/test_init.py | 30 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index c4ad6297868c..8a98763b31c4 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -72,6 +72,11 @@ _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) +# Audio is read from the request body in chunks of at most 4096 bytes because +# line-based iteration raises LineTooLong on binary audio without newline bytes. +# At 16 kHz/16-bit/mono, 4096 bytes represents up to 128 ms of audio. +AUDIO_CHUNK_SIZE = 4096 + @callback def async_default_engine(hass: HomeAssistant) -> str | None: @@ -291,7 +296,7 @@ class SpeechToTextView(HomeAssistantView): # Process audio stream result = await stt_provider.async_process_audio_stream( - metadata, request.content + metadata, request.content.iter_chunked(AUDIO_CHUNK_SIZE) ) else: # Check format @@ -300,7 +305,7 @@ class SpeechToTextView(HomeAssistantView): # Process audio stream result = await provider_entity.internal_async_process_audio_stream( - metadata, request.content + metadata, request.content.iter_chunked(AUDIO_CHUNK_SIZE) ) # Return result diff --git a/tests/components/stt/test_init.py b/tests/components/stt/test_init.py index 8ff45b595779..76dc65329693 100644 --- a/tests/components/stt/test_init.py +++ b/tests/components/stt/test_init.py @@ -644,3 +644,33 @@ async def test_audio_processing_custom(hass: HomeAssistant, tmp_path: Path) -> N assert engine.audio_processing.requires_external_vad is False assert engine.audio_processing.prefers_auto_gain_enabled is False assert engine.audio_processing.prefers_noise_reduction_enabled is False + + +@pytest.mark.parametrize( + "setup", ["mock_setup", "mock_config_entry_setup"], indirect=True +) +async def test_stream_audio_large_no_newline_block( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + setup: MockSTTProvider | MockSTTProviderEntity, +) -> None: + """Test a newline-free audio stream larger than aiohttp's line limit.""" + # 600,000 bytes of silence - larger than the 512KB LineTooLong threshold. + # This is the reproduction case from issue #180708. + test_data = b"\x00" * 600_000 + client = await hass_client() + response = await client.post( + f"/api/stt/{setup.url_path}", + headers={ + "X-Speech-Content": ( + "format=wav; codec=pcm; sample_rate=16000; bit_rate=16; channel=1;" + " language=en" + ) + }, + data=test_data, + ) + assert response.status == HTTPStatus.OK + assert await response.json() == {"text": "test_result", "result": "success"} + + received_data = b"".join(setup.received) + assert received_data == test_data