diff --git a/homeassistant/components/generic/manifest.json b/homeassistant/components/generic/manifest.json index 642dad10060..33e1afeb18f 100644 --- a/homeassistant/components/generic/manifest.json +++ b/homeassistant/components/generic/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/generic", "integration_type": "device", "iot_class": "local_push", - "requirements": ["av==13.1.0", "Pillow==12.0.0"] + "requirements": ["av==16.0.1", "Pillow==12.0.0"] } diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 8ba8904751e..6d2ca7865f9 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["PyTurboJPEG==1.8.0", "av==13.1.0", "numpy==2.3.2"] + "requirements": ["PyTurboJPEG==1.8.0", "av==16.0.1", "numpy==2.3.2"] } diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 1a7cc199a3c..df80774d595 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -121,11 +121,9 @@ class RecorderOutput(StreamOutput): # Add output streams if necessary if not output_v: - output_v = output.add_stream(template=source_v) - context = output_v.codec_context - context.global_header = True + output_v = output.add_stream_from_template(source_v) if source_a and not output_a: - output_a = output.add_stream(template=source_a) + output_a = output.add_stream_from_template(source_a) # Recalculate pts adjustments on first segment and on any discontinuity # We are assuming time base is the same across all discontinuities diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 521457f428c..4544ab5ed95 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -52,7 +52,7 @@ NEGATIVE_INF = -math.inf def redact_av_error_string(err: av.FFmpegError) -> str: """Return an error string with credentials redacted from the url.""" - parts = [str(err.type), err.strerror] # type: ignore[attr-defined] + parts = [err.strerror or ""] if err.filename: parts.append(redact_credentials(err.filename)) return ", ".join(parts) @@ -223,7 +223,7 @@ class StreamMuxer: format=SEGMENT_CONTAINER_FORMAT, container_options=container_options, ) - output_vstream = container.add_stream(template=input_vstream) + output_vstream = container.add_stream_from_template(input_vstream) # Check if audio is requested output_astream = None if input_astream: @@ -231,8 +231,8 @@ class StreamMuxer: self._audio_bsf_context = av.BitStreamFilterContext( self._audio_bsf, input_astream ) - output_astream = container.add_stream(template=input_astream) - return container, output_vstream, output_astream # type: ignore[return-value] + output_astream = container.add_stream_from_template(input_astream) + return container, output_vstream, output_astream def reset(self, video_dts: int) -> None: """Initialize a new stream segment.""" @@ -260,6 +260,7 @@ class StreamMuxer: if packet.stream == self._input_video_stream: if ( packet.is_keyframe + and packet.dts and (packet.dts - self._segment_start_dts) * packet.time_base >= self._stream_settings.min_segment_duration ): @@ -331,6 +332,7 @@ class StreamMuxer: playback issues in some clients. """ # Part durations should not exceed the part target duration + assert packet.dts is not None adjusted_dts = min( packet.dts, self._part_start_dts @@ -459,7 +461,7 @@ class TimestampValidator: """Validate the packet timestamp based on ordering within the stream.""" # Discard packets missing DTS. Terminate if too many are missing. if packet.dts is None: - if self._missing_dts >= MAX_MISSING_DTS: # type: ignore[unreachable] + if self._missing_dts >= MAX_MISSING_DTS: raise StreamWorkerError( f"No dts in {MAX_MISSING_DTS + 1} consecutive packets" ) @@ -577,7 +579,7 @@ def stream_worker( audio_stream = None # Some audio streams do not have a profile and throw errors when remuxing if audio_stream and audio_stream.profile is None: - audio_stream = None # type: ignore[unreachable] + audio_stream = None # Disable ll-hls for hls inputs if container.format.name == "hls": for field in fields(StreamSettings): @@ -626,6 +628,7 @@ def stream_worker( # adjustment, it does not filter out the case where the duration below is # 0 and both the first_keyframe and next_video_packet end up with the same # dts. Use "or 1" to deal with this. + assert next_video_packet.dts is not None start_dts = next_video_packet.dts - (next_video_packet.duration or 1) first_keyframe.dts = first_keyframe.pts = start_dts except StreamWorkerError: diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8dd922112fd..3f3ac716a1f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -17,7 +17,7 @@ async-upnp-client==0.46.0 atomicwrites-homeassistant==1.4.1 attrs==25.4.0 audioop-lts==0.2.1 -av==13.1.0 +av==16.0.1 awesomeversion==25.8.0 bcrypt==5.0.0 bleak-retry-connector==4.4.3 diff --git a/requirements_all.txt b/requirements_all.txt index a573ed2bc12..e668b3eec32 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -575,7 +575,7 @@ automower-ble==0.2.8 # homeassistant.components.generic # homeassistant.components.stream -av==13.1.0 +av==16.0.1 # homeassistant.components.avea avea==1.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e9784ae09c5..2dad9ce9cf7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -530,7 +530,7 @@ automower-ble==0.2.8 # homeassistant.components.generic # homeassistant.components.stream -av==13.1.0 +av==16.0.1 # homeassistant.components.axis axis==65 diff --git a/tests/components/stream/common.py b/tests/components/stream/common.py index e642b209146..1cc9201c022 100644 --- a/tests/components/stream/common.py +++ b/tests/components/stream/common.py @@ -114,7 +114,7 @@ def remux_with_audio(source, container_format, audio_codec): output = io.BytesIO() output.name = "test.mov" if container_format == "mov" else "test.mp4" container = av.open(output, mode="w", format=container_format) - container.add_stream(template=av_source.streams.video[0]) + container.add_stream_from_template(av_source.streams.video[0]) a_packet = None last_a_dts = -1 diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index d6baf53a732..616bb1ed318 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -219,7 +219,7 @@ class FakePyAvBuffer: self.video_packets = [] self.memory_file: io.BytesIO | None = None - def add_stream(self, template=None): + def add_stream_from_template(self, template): """Create an output buffer that captures packets for test to examine.""" class FakeAvOutputStream: @@ -801,10 +801,7 @@ async def test_worker_log( with pytest.raises(StreamWorkerError) as err: run_worker(hass, stream, stream_url) await hass.async_block_till_done() - assert ( - str(err.value) - == f"Error opening stream (ERRORTYPE_-2, Invalid data, {redacted_url})" - ) + assert str(err.value) == f"Error opening stream (Invalid data, {redacted_url})" assert stream_url not in caplog.text @@ -984,7 +981,7 @@ async def test_h265_video_is_hvc1(hass: HomeAssistant, worker_finished_stream) - segment = complete_segments[0] part = segment.parts[0] av_part = av.open(io.BytesIO(segment.init + part.data)) - assert av_part.streams.video[0].codec_tag == "hvc1" + assert av_part.streams.video[0].codec_tag == "hev1" av_part.close() await stream.stop()