mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Switch back to av 13.1.0 (#129699)
This commit is contained in:
parent
ff621d5bf3
commit
41a81cbf15
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/generic",
|
"documentation": "https://www.home-assistant.io/integrations/generic",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"requirements": ["ha-av==10.1.1", "Pillow==10.4.0"]
|
"requirements": ["av==13.1.0", "Pillow==10.4.0"]
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,7 @@ from .const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from av import Packet
|
from av import Packet, VideoCodecContext
|
||||||
from av.video.codeccontext import VideoCodecContext
|
|
||||||
|
|
||||||
from homeassistant.components.camera import DynamicStreamSettings
|
from homeassistant.components.camera import DynamicStreamSettings
|
||||||
|
|
||||||
@ -509,9 +508,8 @@ class KeyFrameConverter:
|
|||||||
frames = self._codec_context.decode(None)
|
frames = self._codec_context.decode(None)
|
||||||
break
|
break
|
||||||
except EOFError:
|
except EOFError:
|
||||||
_LOGGER.debug("Codec context needs flushing, attempting to reopen")
|
_LOGGER.debug("Codec context needs flushing")
|
||||||
self._codec_context.close()
|
self._codec_context.flush_buffers()
|
||||||
self._codec_context.open()
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Unable to decode keyframe")
|
_LOGGER.debug("Unable to decode keyframe")
|
||||||
return
|
return
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"quality_scale": "internal",
|
"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"]
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ class RecorderOutput(StreamOutput):
|
|||||||
# Create output on first segment
|
# Create output on first segment
|
||||||
if not output:
|
if not output:
|
||||||
container_options: dict[str, str] = {
|
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",
|
"movflags": "frag_keyframe+empty_moov",
|
||||||
"min_frag_duration": str(self.stream_settings.min_segment_duration),
|
"min_frag_duration": str(self.stream_settings.min_segment_duration),
|
||||||
}
|
}
|
||||||
@ -132,21 +132,23 @@ class RecorderOutput(StreamOutput):
|
|||||||
last_stream_id = segment.stream_id
|
last_stream_id = segment.stream_id
|
||||||
pts_adjuster["video"] = int(
|
pts_adjuster["video"] = int(
|
||||||
(running_duration - source.start_time)
|
(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:
|
if source_a:
|
||||||
pts_adjuster["audio"] = int(
|
pts_adjuster["audio"] = int(
|
||||||
(running_duration - source.start_time)
|
(running_duration - source.start_time)
|
||||||
/ (av.time_base * source_a.time_base)
|
/ (av.time_base * source_a.time_base) # type: ignore[operator]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remux video
|
# Remux video
|
||||||
for packet in source.demux():
|
for packet in source.demux():
|
||||||
if packet.dts is None:
|
if packet.pts is None:
|
||||||
continue
|
continue
|
||||||
packet.pts += pts_adjuster[packet.stream.type]
|
packet.pts += pts_adjuster[packet.stream.type] # type: ignore[operator]
|
||||||
packet.dts += pts_adjuster[packet.stream.type]
|
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)
|
output.mux(packet)
|
||||||
|
|
||||||
running_duration += source.duration - source.start_time
|
running_duration += source.duration - source.start_time
|
||||||
|
@ -16,7 +16,6 @@ import av
|
|||||||
import av.audio
|
import av.audio
|
||||||
import av.container
|
import av.container
|
||||||
import av.stream
|
import av.stream
|
||||||
import av.video
|
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
@ -53,8 +52,8 @@ class StreamWorkerError(Exception):
|
|||||||
|
|
||||||
def redact_av_error_string(err: av.FFmpegError) -> str:
|
def redact_av_error_string(err: av.FFmpegError) -> str:
|
||||||
"""Return an error string with credentials redacted from the url."""
|
"""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:
|
if err.filename:
|
||||||
parts.append(redact_credentials(err.filename))
|
parts.append(redact_credentials(err.filename))
|
||||||
return ", ".join(parts)
|
return ", ".join(parts)
|
||||||
|
|
||||||
@ -130,19 +129,19 @@ class StreamMuxer:
|
|||||||
_segment_start_dts: int
|
_segment_start_dts: int
|
||||||
_memory_file: BytesIO
|
_memory_file: BytesIO
|
||||||
_av_output: av.container.OutputContainer
|
_av_output: av.container.OutputContainer
|
||||||
_output_video_stream: av.video.VideoStream
|
_output_video_stream: av.VideoStream
|
||||||
_output_audio_stream: av.audio.AudioStream | None
|
_output_audio_stream: av.audio.AudioStream | None
|
||||||
_segment: Segment | None
|
_segment: Segment | None
|
||||||
# the following 2 member variables are used for Part formation
|
# the following 2 member variables are used for Part formation
|
||||||
_memory_file_pos: int
|
_memory_file_pos: int
|
||||||
_part_start_dts: int
|
_part_start_dts: float
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
video_stream: av.video.VideoStream,
|
video_stream: av.VideoStream,
|
||||||
audio_stream: av.audio.AudioStream | None,
|
audio_stream: av.audio.AudioStream | None,
|
||||||
audio_bsf: av.BitStreamFilter | None,
|
audio_bsf: str | None,
|
||||||
stream_state: StreamState,
|
stream_state: StreamState,
|
||||||
stream_settings: StreamSettings,
|
stream_settings: StreamSettings,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -161,11 +160,11 @@ class StreamMuxer:
|
|||||||
self,
|
self,
|
||||||
memory_file: BytesIO,
|
memory_file: BytesIO,
|
||||||
sequence: int,
|
sequence: int,
|
||||||
input_vstream: av.video.VideoStream,
|
input_vstream: av.VideoStream,
|
||||||
input_astream: av.audio.AudioStream | None,
|
input_astream: av.audio.AudioStream | None,
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
av.container.OutputContainer,
|
av.container.OutputContainer,
|
||||||
av.video.VideoStream,
|
av.VideoStream,
|
||||||
av.audio.AudioStream | None,
|
av.audio.AudioStream | None,
|
||||||
]:
|
]:
|
||||||
"""Make a new av OutputContainer and add output streams."""
|
"""Make a new av OutputContainer and add output streams."""
|
||||||
@ -182,7 +181,7 @@ class StreamMuxer:
|
|||||||
# in test_durations
|
# in test_durations
|
||||||
"avoid_negative_ts": "make_non_negative",
|
"avoid_negative_ts": "make_non_negative",
|
||||||
"fragment_index": str(sequence + 1),
|
"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
|
# Only do extra fragmenting if we are using ll_hls
|
||||||
# Let ffmpeg do the work using frag_duration
|
# Let ffmpeg do the work using frag_duration
|
||||||
# Fragment durations may exceed the 15% allowed variance but it seems ok
|
# Fragment durations may exceed the 15% allowed variance but it seems ok
|
||||||
@ -233,12 +232,11 @@ class StreamMuxer:
|
|||||||
output_astream = None
|
output_astream = None
|
||||||
if input_astream:
|
if input_astream:
|
||||||
if self._audio_bsf:
|
if self._audio_bsf:
|
||||||
self._audio_bsf_context = self._audio_bsf.create()
|
self._audio_bsf_context = av.BitStreamFilterContext(
|
||||||
self._audio_bsf_context.set_input_stream(input_astream)
|
self._audio_bsf, input_astream
|
||||||
output_astream = container.add_stream(
|
)
|
||||||
template=self._audio_bsf_context or input_astream
|
output_astream = container.add_stream(template=input_astream)
|
||||||
)
|
return container, output_vstream, output_astream # type: ignore[return-value]
|
||||||
return container, output_vstream, output_astream
|
|
||||||
|
|
||||||
def reset(self, video_dts: int) -> None:
|
def reset(self, video_dts: int) -> None:
|
||||||
"""Initialize a new stream segment."""
|
"""Initialize a new stream segment."""
|
||||||
@ -279,11 +277,11 @@ class StreamMuxer:
|
|||||||
self._part_has_keyframe |= packet.is_keyframe
|
self._part_has_keyframe |= packet.is_keyframe
|
||||||
|
|
||||||
elif packet.stream == self._input_audio_stream:
|
elif packet.stream == self._input_audio_stream:
|
||||||
|
assert self._output_audio_stream
|
||||||
if self._audio_bsf_context:
|
if self._audio_bsf_context:
|
||||||
self._audio_bsf_context.send(packet)
|
for audio_packet in self._audio_bsf_context.filter(packet):
|
||||||
while packet := self._audio_bsf_context.recv():
|
audio_packet.stream = self._output_audio_stream
|
||||||
packet.stream = self._output_audio_stream
|
self._av_output.mux(audio_packet)
|
||||||
self._av_output.mux(packet)
|
|
||||||
return
|
return
|
||||||
packet.stream = self._output_audio_stream
|
packet.stream = self._output_audio_stream
|
||||||
self._av_output.mux(packet)
|
self._av_output.mux(packet)
|
||||||
@ -465,7 +463,7 @@ class TimestampValidator:
|
|||||||
"""Validate the packet timestamp based on ordering within the stream."""
|
"""Validate the packet timestamp based on ordering within the stream."""
|
||||||
# Discard packets missing DTS. Terminate if too many are missing.
|
# Discard packets missing DTS. Terminate if too many are missing.
|
||||||
if packet.dts is None:
|
if packet.dts is None:
|
||||||
if self._missing_dts >= MAX_MISSING_DTS:
|
if self._missing_dts >= MAX_MISSING_DTS: # type: ignore[unreachable]
|
||||||
raise StreamWorkerError(
|
raise StreamWorkerError(
|
||||||
f"No dts in {MAX_MISSING_DTS+1} consecutive packets"
|
f"No dts in {MAX_MISSING_DTS+1} consecutive packets"
|
||||||
)
|
)
|
||||||
@ -492,7 +490,7 @@ def is_keyframe(packet: av.Packet) -> Any:
|
|||||||
|
|
||||||
def get_audio_bitstream_filter(
|
def get_audio_bitstream_filter(
|
||||||
packets: Iterator[av.Packet], audio_stream: Any
|
packets: Iterator[av.Packet], audio_stream: Any
|
||||||
) -> av.BitStreamFilterContext | None:
|
) -> str | None:
|
||||||
"""Return the aac_adtstoasc bitstream filter if ADTS AAC is detected."""
|
"""Return the aac_adtstoasc bitstream filter if ADTS AAC is detected."""
|
||||||
if not audio_stream:
|
if not audio_stream:
|
||||||
return None
|
return None
|
||||||
@ -509,7 +507,7 @@ def get_audio_bitstream_filter(
|
|||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"ADTS AAC detected. Adding aac_adtstoaac bitstream filter"
|
"ADTS AAC detected. Adding aac_adtstoaac bitstream filter"
|
||||||
)
|
)
|
||||||
return av.BitStreamFilter("aac_adtstoasc")
|
return "aac_adtstoasc"
|
||||||
break
|
break
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -547,7 +545,7 @@ def stream_worker(
|
|||||||
audio_stream = None
|
audio_stream = None
|
||||||
# Some audio streams do not have a profile and throw errors when remuxing
|
# Some audio streams do not have a profile and throw errors when remuxing
|
||||||
if audio_stream and audio_stream.profile is None:
|
if audio_stream and audio_stream.profile is None:
|
||||||
audio_stream = None
|
audio_stream = None # type: ignore[unreachable]
|
||||||
# Disable ll-hls for hls inputs
|
# Disable ll-hls for hls inputs
|
||||||
if container.format.name == "hls":
|
if container.format.name == "hls":
|
||||||
for field in fields(StreamSettings):
|
for field in fields(StreamSettings):
|
||||||
@ -562,8 +560,8 @@ def stream_worker(
|
|||||||
stream_state.diagnostics.set_value("audio_codec", audio_stream.name)
|
stream_state.diagnostics.set_value("audio_codec", audio_stream.name)
|
||||||
|
|
||||||
dts_validator = TimestampValidator(
|
dts_validator = TimestampValidator(
|
||||||
int(1 / video_stream.time_base),
|
int(1 / video_stream.time_base), # type: ignore[operator]
|
||||||
int(1 / audio_stream.time_base) if audio_stream else 1,
|
int(1 / audio_stream.time_base) if audio_stream else 1, # type: ignore[operator]
|
||||||
)
|
)
|
||||||
container_packets = PeekIterator(
|
container_packets = PeekIterator(
|
||||||
filter(dts_validator.is_valid, container.demux((video_stream, audio_stream)))
|
filter(dts_validator.is_valid, container.demux((video_stream, audio_stream)))
|
||||||
|
@ -13,6 +13,7 @@ async-interrupt==1.2.0
|
|||||||
async-upnp-client==0.41.0
|
async-upnp-client==0.41.0
|
||||||
atomicwrites-homeassistant==1.4.1
|
atomicwrites-homeassistant==1.4.1
|
||||||
attrs==24.2.0
|
attrs==24.2.0
|
||||||
|
av==13.1.0
|
||||||
awesomeversion==24.6.0
|
awesomeversion==24.6.0
|
||||||
bcrypt==4.2.0
|
bcrypt==4.2.0
|
||||||
bleak-retry-connector==3.6.0
|
bleak-retry-connector==3.6.0
|
||||||
@ -27,7 +28,6 @@ cryptography==43.0.1
|
|||||||
dbus-fast==2.24.3
|
dbus-fast==2.24.3
|
||||||
fnv-hash-fast==1.0.2
|
fnv-hash-fast==1.0.2
|
||||||
go2rtc-client==0.0.1b3
|
go2rtc-client==0.0.1b3
|
||||||
ha-av==10.1.1
|
|
||||||
ha-ffmpeg==3.2.1
|
ha-ffmpeg==3.2.1
|
||||||
habluetooth==3.6.0
|
habluetooth==3.6.0
|
||||||
hass-nabucasa==0.83.0
|
hass-nabucasa==0.83.0
|
||||||
|
@ -526,6 +526,10 @@ autarco==3.1.0
|
|||||||
# homeassistant.components.husqvarna_automower_ble
|
# homeassistant.components.husqvarna_automower_ble
|
||||||
automower-ble==0.2.0
|
automower-ble==0.2.0
|
||||||
|
|
||||||
|
# homeassistant.components.generic
|
||||||
|
# homeassistant.components.stream
|
||||||
|
av==13.1.0
|
||||||
|
|
||||||
# homeassistant.components.avea
|
# homeassistant.components.avea
|
||||||
# avea==1.5.1
|
# avea==1.5.1
|
||||||
|
|
||||||
@ -1064,10 +1068,6 @@ guppy3==3.1.4.post1
|
|||||||
# homeassistant.components.iaqualink
|
# homeassistant.components.iaqualink
|
||||||
h2==4.1.0
|
h2==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.generic
|
|
||||||
# homeassistant.components.stream
|
|
||||||
ha-av==10.1.1
|
|
||||||
|
|
||||||
# homeassistant.components.ffmpeg
|
# homeassistant.components.ffmpeg
|
||||||
ha-ffmpeg==3.2.1
|
ha-ffmpeg==3.2.1
|
||||||
|
|
||||||
|
@ -481,6 +481,10 @@ autarco==3.1.0
|
|||||||
# homeassistant.components.husqvarna_automower_ble
|
# homeassistant.components.husqvarna_automower_ble
|
||||||
automower-ble==0.2.0
|
automower-ble==0.2.0
|
||||||
|
|
||||||
|
# homeassistant.components.generic
|
||||||
|
# homeassistant.components.stream
|
||||||
|
av==13.1.0
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==63
|
axis==63
|
||||||
|
|
||||||
@ -902,10 +906,6 @@ guppy3==3.1.4.post1
|
|||||||
# homeassistant.components.iaqualink
|
# homeassistant.components.iaqualink
|
||||||
h2==4.1.0
|
h2==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.generic
|
|
||||||
# homeassistant.components.stream
|
|
||||||
ha-av==10.1.1
|
|
||||||
|
|
||||||
# homeassistant.components.ffmpeg
|
# homeassistant.components.ffmpeg
|
||||||
ha-ffmpeg==3.2.1
|
ha-ffmpeg==3.2.1
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user