diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 47ba33c44d504c96d8a64d122368104b22c48ac7..d5115754e2eb9fb0701e4c50a7b1c3b9e1a5c316 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -2,7 +2,7 @@ "domain": "stream", "name": "Stream", "documentation": "https://www.home-assistant.io/integrations/stream", - "requirements": ["av==8.0.3"], + "requirements": ["ha-av==8.0.4-rc.1"], "dependencies": ["http"], "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "quality_scale": "internal", diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 398d45595d34a5479d35530291b715988f97debc..64a43f68aa00f7cc54b2bf8b262a13fdcca17e53 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -140,6 +140,8 @@ class SegmentBuffer: self._output_video_stream = self._av_output.add_stream( template=self._input_video_stream ) + if self._output_video_stream.name == "hevc": + self._output_video_stream.codec_tag = "hvc1" # Check if audio is requested self._output_audio_stream = None if self._input_audio_stream and self._input_audio_stream.name in AUDIO_CODECS: diff --git a/requirements_all.txt b/requirements_all.txt index 21e2d57df6a254526b0facfbb25afb130266f4eb..8f9ff72c97cac81041c48e47f2d9df4268eea43b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -347,9 +347,6 @@ auroranoaa==0.0.2 # homeassistant.components.aurora_abb_powerone aurorapy==0.2.6 -# homeassistant.components.stream -av==8.0.3 - # homeassistant.components.avea # avea==1.5.1 @@ -767,6 +764,9 @@ gstreamer-player==1.1.2 # homeassistant.components.profiler guppy3==3.1.0 +# homeassistant.components.stream +ha-av==8.0.4-rc.1 + # homeassistant.components.ffmpeg ha-ffmpeg==3.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c0be3e96f67464246ecf68ae3b7f522c0aa61c70..411cd336cab31cba6e9a9fbd335482a70911f4d5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -238,9 +238,6 @@ auroranoaa==0.0.2 # homeassistant.components.aurora_abb_powerone aurorapy==0.2.6 -# homeassistant.components.stream -av==8.0.3 - # homeassistant.components.axis axis==44 @@ -466,6 +463,9 @@ growattServer==1.1.0 # homeassistant.components.profiler guppy3==3.1.0 +# homeassistant.components.stream +ha-av==8.0.4-rc.1 + # homeassistant.components.ffmpeg ha-ffmpeg==3.0.2 diff --git a/tests/components/stream/common.py b/tests/components/stream/common.py index 19a4d2a9e6f077008af4b8bb199e87fd32a6b18d..0b25fd7e7c51ef89b274d10e328150218517f5af 100644 --- a/tests/components/stream/common.py +++ b/tests/components/stream/common.py @@ -37,7 +37,7 @@ def generate_audio_frame(pcm_mulaw=False): return audio_frame -def generate_h264_video(container_format="mp4", duration=5): +def generate_video(encoder, container_format, duration): """ Generate a test video. @@ -51,7 +51,7 @@ def generate_h264_video(container_format="mp4", duration=5): output.name = "test.mov" if container_format == "mov" else "test.mp4" container = av.open(output, mode="w", format=container_format) - stream = container.add_stream("libx264", rate=fps) + stream = container.add_stream(encoder, rate=fps) stream.width = 480 stream.height = 320 stream.pix_fmt = "yuv420p" @@ -82,6 +82,16 @@ def generate_h264_video(container_format="mp4", duration=5): return output +def generate_h264_video(container_format="mp4", duration=5): + """Generate a test video with libx264.""" + return generate_video("libx264", container_format, duration) + + +def generate_h265_video(container_format="mp4", duration=5): + """Generate a test video with libx265.""" + return generate_video("libx265", container_format, duration) + + def remux_with_audio(source, container_format, audio_codec): """Remux an existing source with new audio.""" av_source = av.open(source, mode="r") diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 12f859b203be75c73077ed2385fb36732d52fed9..7c9ad91f5438f14df548c17321e8f9d58547b335 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -40,7 +40,7 @@ from homeassistant.components.stream.core import StreamSettings from homeassistant.components.stream.worker import SegmentBuffer, stream_worker from homeassistant.setup import async_setup_component -from tests.components.stream.common import generate_h264_video +from tests.components.stream.common import generate_h264_video, generate_h265_video from tests.components.stream.test_ll_hls import TEST_PART_DURATION STREAM_SOURCE = "some-stream-source" @@ -201,6 +201,9 @@ class FakePyAvBuffer: def __str__(self) -> str: return f"FakeAvOutputStream<{template.name}>" + def name(self) -> str: + return "avc1" + if template.name == AUDIO_STREAM_FORMAT: return FakeAvOutputStream(self.audio_packets) return FakeAvOutputStream(self.video_packets) @@ -771,3 +774,38 @@ async def test_has_keyframe(hass, record_worker_sync): await record_worker_sync.join() stream.stop() + + +async def test_h265_video_is_hvc1(hass, record_worker_sync): + """Test that a h265 video gets muxed as hvc1.""" + await async_setup_component( + hass, + "stream", + { + "stream": { + CONF_LL_HLS: True, + CONF_SEGMENT_DURATION: SEGMENT_DURATION, + CONF_PART_DURATION: TEST_PART_DURATION, + } + }, + ) + + source = generate_h265_video() + stream = create_stream(hass, source, {}) + + # use record_worker_sync to grab output segments + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path") + + complete_segments = list(await record_worker_sync.get_segments())[:-1] + assert len(complete_segments) >= 1 + + 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" + av_part.close() + + await record_worker_sync.join() + + stream.stop()