Files
home-assistant-core/homeassistant/components/stream/recorder.py
T
uvjustinandJason Hunter 5355fcaba8 Add H.265 support to stream component (#38125)
* Add H.265 support to stream component

* Change find_box to generator

* Move fmp4 utilities to fmp4utils.py

* Add minimum segments and segment durations

* Remove MIN_SEGMENTS

* Fix when container_options is None

* Fix missing num_segments and update tests

* Remove unnecessary mock attribute

* Fix Segment construction in test_recorder_save

* fix recorder with lookback

Co-authored-by: Jason Hunter <hunterjm@gmail.com>
2020-08-11 17:12:41 -04:00

105 lines
2.8 KiB
Python

"""Provide functionality to record stream."""
import threading
from typing import List
import av
from homeassistant.core import callback
from .core import PROVIDERS, Segment, StreamOutput
@callback
def async_setup_recorder(hass):
"""Only here so Provider Registry works."""
def recorder_save_worker(file_out: str, segments: List[Segment]):
"""Handle saving stream."""
first_pts = segments[0].start_pts[0]
output = av.open(file_out, "w")
output_v = None
for segment in segments:
# Seek to beginning and open segment
segment.segment.seek(0)
source = av.open(segment.segment, "r", format="mp4")
source_v = source.streams.video[0]
# Add output streams
if not output_v:
output_v = output.add_stream(template=source_v)
context = output_v.codec_context
context.flags |= "GLOBAL_HEADER"
# Remux video
for packet in source.demux(source_v):
if packet is not None and packet.dts is not None:
if packet.pts < segment.start_pts[0]:
packet.pts += segment.start_pts[0]
packet.dts += segment.start_pts[0]
packet.pts -= first_pts
packet.dts -= first_pts
packet.stream = output_v
output.mux(packet)
source.close()
output.close()
@PROVIDERS.register("recorder")
class RecorderOutput(StreamOutput):
"""Represents HLS Output formats."""
def __init__(self, stream, timeout: int = 30) -> None:
"""Initialize recorder output."""
super().__init__(stream, timeout)
self.video_path = None
self._segments = []
@property
def name(self) -> str:
"""Return provider name."""
return "recorder"
@property
def format(self) -> str:
"""Return container format."""
return "mp4"
@property
def audio_codec(self) -> str:
"""Return desired audio codec."""
return "aac"
@property
def video_codecs(self) -> tuple:
"""Return desired video codecs."""
return {"hevc", "h264"}
def prepend(self, segments: List[Segment]) -> None:
"""Prepend segments to existing list."""
own_segments = self.segments
segments = [s for s in segments if s.sequence not in own_segments]
self._segments = segments + self._segments
@callback
def _timeout(self, _now=None):
"""Handle recorder timeout."""
self._unsub = None
self.cleanup()
def cleanup(self):
"""Write recording and clean up."""
thread = threading.Thread(
name="recorder_save_worker",
target=recorder_save_worker,
args=(self.video_path, self._segments),
)
thread.start()
self._segments = []
self._stream.remove_provider(self)