From 3d164fe39b0786cd62d391bb02d13594880dbc41 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:23:41 +0100 Subject: [PATCH 1/6] Switch back to av 13.1.0 --- homeassistant/components/generic/manifest.json | 2 +- homeassistant/components/stream/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 8 ++++---- requirements_test_all.txt | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/generic/manifest.json b/homeassistant/components/generic/manifest.json index b19d6d6293ec5f..b02a8fa25203cb 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": ["ha-av==10.1.1", "Pillow==10.4.0"] + "requirements": ["av==13.1.0", "Pillow==10.4.0"] } diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 00387d97b8350d..23494a067442a2 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.7.5", "ha-av==10.1.1", "numpy==1.26.4"] + "requirements": ["PyTurboJPEG==1.7.5", "av==13.1.0", "numpy==1.26.4"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 42bda4d3c40270..aa8fecc73a5e68 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,6 +13,7 @@ async-interrupt==1.2.0 async-upnp-client==0.41.0 atomicwrites-homeassistant==1.4.1 attrs==24.2.0 +av==13.1.0 awesomeversion==24.6.0 bcrypt==4.2.0 bleak-retry-connector==3.6.0 @@ -27,7 +28,6 @@ cryptography==43.0.1 dbus-fast==2.24.3 fnv-hash-fast==1.0.2 go2rtc-client==0.0.1b3 -ha-av==10.1.1 ha-ffmpeg==3.2.1 habluetooth==3.6.0 hass-nabucasa==0.83.0 diff --git a/requirements_all.txt b/requirements_all.txt index 97b5b864fbae6e..c0c968f1bc1287 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -526,6 +526,10 @@ autarco==3.1.0 # homeassistant.components.husqvarna_automower_ble automower-ble==0.2.0 +# homeassistant.components.generic +# homeassistant.components.stream +av==13.1.0 + # homeassistant.components.avea # avea==1.5.1 @@ -1064,10 +1068,6 @@ guppy3==3.1.4.post1 # homeassistant.components.iaqualink h2==4.1.0 -# homeassistant.components.generic -# homeassistant.components.stream -ha-av==10.1.1 - # homeassistant.components.ffmpeg ha-ffmpeg==3.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18da37f18f4cce..e30fd045d86efa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -481,6 +481,10 @@ autarco==3.1.0 # homeassistant.components.husqvarna_automower_ble automower-ble==0.2.0 +# homeassistant.components.generic +# homeassistant.components.stream +av==13.1.0 + # homeassistant.components.axis axis==63 @@ -902,10 +906,6 @@ guppy3==3.1.4.post1 # homeassistant.components.iaqualink h2==4.1.0 -# homeassistant.components.generic -# homeassistant.components.stream -ha-av==10.1.1 - # homeassistant.components.ffmpeg ha-ffmpeg==3.2.1 From fc6b765a6968bccb6409da1c4b6181037aa58ed9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:36:59 +0100 Subject: [PATCH 2/6] New BitStreamFilterContext --- homeassistant/components/stream/worker.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 42bfa13f13ec7a..72867b88216c90 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -142,7 +142,7 @@ def __init__( hass: HomeAssistant, video_stream: av.video.VideoStream, audio_stream: av.audio.AudioStream | None, - audio_bsf: av.BitStreamFilter | None, + audio_bsf: str | None, stream_state: StreamState, stream_settings: StreamSettings, ) -> None: @@ -233,11 +233,10 @@ def make_new_av( output_astream = None if input_astream: if self._audio_bsf: - self._audio_bsf_context = self._audio_bsf.create() - self._audio_bsf_context.set_input_stream(input_astream) - output_astream = container.add_stream( - template=self._audio_bsf_context or input_astream - ) + 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 def reset(self, video_dts: int) -> None: @@ -280,10 +279,9 @@ def mux_packet(self, packet: av.Packet) -> None: elif packet.stream == self._input_audio_stream: if self._audio_bsf_context: - self._audio_bsf_context.send(packet) - while packet := self._audio_bsf_context.recv(): - packet.stream = self._output_audio_stream - self._av_output.mux(packet) + for audio_packet in self._audio_bsf_context.filter(packet): + audio_packet.stream = self._output_audio_stream + self._av_output.mux(audio_packet) return packet.stream = self._output_audio_stream self._av_output.mux(packet) @@ -492,7 +490,7 @@ def is_keyframe(packet: av.Packet) -> Any: def get_audio_bitstream_filter( packets: Iterator[av.Packet], audio_stream: Any -) -> av.BitStreamFilterContext | None: +) -> str | None: """Return the aac_adtstoasc bitstream filter if ADTS AAC is detected.""" if not audio_stream: return None @@ -509,7 +507,7 @@ def get_audio_bitstream_filter( _LOGGER.debug( "ADTS AAC detected. Adding aac_adtstoaac bitstream filter" ) - return av.BitStreamFilter("aac_adtstoasc") + return "aac_adtstoasc" break return None From 339894136a97caf12af98cc45454510a93a294fd Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:34:28 +0100 Subject: [PATCH 3/6] Add type ignores --- homeassistant/components/stream/recorder.py | 10 +++++----- homeassistant/components/stream/worker.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index d28982ea30de6d..87ca939b6d4c54 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -107,7 +107,7 @@ def write_segment(segment: Segment) -> None: # Create output on first segment if not output: container_options: dict[str, str] = { - "video_track_timescale": str(int(1 / source_v.time_base)), + "video_track_timescale": str(int(1 / source_v.time_base)), # type: ignore[operator] "movflags": "frag_keyframe+empty_moov", "min_frag_duration": str(self.stream_settings.min_segment_duration), } @@ -132,20 +132,20 @@ def write_segment(segment: Segment) -> None: last_stream_id = segment.stream_id pts_adjuster["video"] = int( (running_duration - source.start_time) - / (av.time_base * source_v.time_base) + / (av.time_base * source_v.time_base) # type: ignore[operator] ) if source_a: pts_adjuster["audio"] = int( (running_duration - source.start_time) - / (av.time_base * source_a.time_base) + / (av.time_base * source_a.time_base) # type: ignore[operator] ) # Remux video for packet in source.demux(): if packet.dts is None: continue - packet.pts += pts_adjuster[packet.stream.type] - packet.dts += pts_adjuster[packet.stream.type] + packet.pts += pts_adjuster[packet.stream.type] # type: ignore[operator] + packet.dts += pts_adjuster[packet.stream.type] # type: ignore[operator] packet.stream = output_v if packet.stream.type == "video" else output_a output.mux(packet) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 72867b88216c90..858caeb315b28e 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -53,7 +53,7 @@ class StreamWorkerError(Exception): 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] + parts = [str(err.type), err.strerror] # type: ignore[attr-defined] if err.filename is not None: parts.append(redact_credentials(err.filename)) return ", ".join(parts) @@ -182,7 +182,7 @@ def make_new_av( # in test_durations "avoid_negative_ts": "make_non_negative", "fragment_index": str(sequence + 1), - "video_track_timescale": str(int(1 / input_vstream.time_base)), + "video_track_timescale": str(int(1 / input_vstream.time_base)), # type: ignore[operator] # Only do extra fragmenting if we are using ll_hls # Let ffmpeg do the work using frag_duration # Fragment durations may exceed the 15% allowed variance but it seems ok @@ -237,7 +237,7 @@ def make_new_av( self._audio_bsf, input_astream ) output_astream = container.add_stream(template=input_astream) - return container, output_vstream, output_astream + return container, output_vstream, output_astream # type: ignore[return-value] def reset(self, video_dts: int) -> None: """Initialize a new stream segment.""" @@ -463,7 +463,7 @@ def is_valid(self, packet: av.Packet) -> bool: """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: + if self._missing_dts >= MAX_MISSING_DTS: # type: ignore[unreachable] raise StreamWorkerError( f"No dts in {MAX_MISSING_DTS+1} consecutive packets" ) @@ -545,7 +545,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 + audio_stream = None # type: ignore[unreachable] # Disable ll-hls for hls inputs if container.format.name == "hls": for field in fields(StreamSettings): @@ -560,8 +560,8 @@ def stream_worker( stream_state.diagnostics.set_value("audio_codec", audio_stream.name) dts_validator = TimestampValidator( - int(1 / video_stream.time_base), - int(1 / audio_stream.time_base) if audio_stream else 1, + int(1 / video_stream.time_base), # type: ignore[operator] + int(1 / audio_stream.time_base) if audio_stream else 1, # type: ignore[operator] ) container_packets = PeekIterator( filter(dts_validator.is_valid, container.demux((video_stream, audio_stream))) From 003e846514ea9c529087e95a5c7706c4a0e89210 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:39:25 +0100 Subject: [PATCH 4/6] Use codec_context.flush_buffers --- homeassistant/components/stream/core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index bce16ff4c8713e..8561cb9d8e83b0 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -509,9 +509,8 @@ def _generate_image(self, width: int | None, height: int | None) -> None: frames = self._codec_context.decode(None) break except EOFError: - _LOGGER.debug("Codec context needs flushing, attempting to reopen") - self._codec_context.close() - self._codec_context.open() + _LOGGER.debug("Codec context needs flushing") + self._codec_context.flush_buffers() else: _LOGGER.debug("Unable to decode keyframe") return From 19ae44e34ae0fbc6c440a52bcc2033a283855f80 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:41:48 +0100 Subject: [PATCH 5/6] Fix typing issues --- homeassistant/components/stream/core.py | 3 +-- homeassistant/components/stream/recorder.py | 6 ++++-- homeassistant/components/stream/worker.py | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 8561cb9d8e83b0..4184b23b9a07e1 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -27,8 +27,7 @@ ) if TYPE_CHECKING: - from av import Packet - from av.video.codeccontext import VideoCodecContext + from av import Packet, VideoCodecContext from homeassistant.components.camera import DynamicStreamSettings diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 87ca939b6d4c54..a24440e6d19c05 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -142,11 +142,13 @@ def write_segment(segment: Segment) -> None: # Remux video for packet in source.demux(): - if packet.dts is None: + if packet.pts is None: continue packet.pts += pts_adjuster[packet.stream.type] # type: ignore[operator] packet.dts += pts_adjuster[packet.stream.type] # type: ignore[operator] - packet.stream = output_v if packet.stream.type == "video" else output_a + stream = output_v if packet.stream.type == "video" else output_a + assert stream + packet.stream = stream output.mux(packet) running_duration += source.duration - source.start_time diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 858caeb315b28e..9ed50660389ff8 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -54,7 +54,7 @@ class StreamWorkerError(Exception): 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] - if err.filename is not None: + if err.filename: parts.append(redact_credentials(err.filename)) return ", ".join(parts) @@ -135,7 +135,7 @@ class StreamMuxer: _segment: Segment | None # the following 2 member variables are used for Part formation _memory_file_pos: int - _part_start_dts: int + _part_start_dts: float def __init__( self, @@ -278,6 +278,7 @@ def mux_packet(self, packet: av.Packet) -> None: self._part_has_keyframe |= packet.is_keyframe elif packet.stream == self._input_audio_stream: + assert self._output_audio_stream if self._audio_bsf_context: for audio_packet in self._audio_bsf_context.filter(packet): audio_packet.stream = self._output_audio_stream From dd7a41a61d10b9468dd1229879288528ec8020e7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:42:30 +0100 Subject: [PATCH 6/6] Use av.VideoStream --- homeassistant/components/stream/worker.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 9ed50660389ff8..8c9bb1b8e9e29a 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -16,7 +16,6 @@ import av.audio import av.container import av.stream -import av.video from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util @@ -130,7 +129,7 @@ class StreamMuxer: _segment_start_dts: int _memory_file: BytesIO _av_output: av.container.OutputContainer - _output_video_stream: av.video.VideoStream + _output_video_stream: av.VideoStream _output_audio_stream: av.audio.AudioStream | None _segment: Segment | None # the following 2 member variables are used for Part formation @@ -140,7 +139,7 @@ class StreamMuxer: def __init__( self, hass: HomeAssistant, - video_stream: av.video.VideoStream, + video_stream: av.VideoStream, audio_stream: av.audio.AudioStream | None, audio_bsf: str | None, stream_state: StreamState, @@ -161,11 +160,11 @@ def make_new_av( self, memory_file: BytesIO, sequence: int, - input_vstream: av.video.VideoStream, + input_vstream: av.VideoStream, input_astream: av.audio.AudioStream | None, ) -> tuple[ av.container.OutputContainer, - av.video.VideoStream, + av.VideoStream, av.audio.AudioStream | None, ]: """Make a new av OutputContainer and add output streams."""