2019-03-28 04:47:07 +00:00
|
|
|
"""Provide functionality to record stream."""
|
2021-01-20 13:44:24 +00:00
|
|
|
import logging
|
2020-09-06 22:54:24 +00:00
|
|
|
import os
|
2019-03-28 04:47:07 +00:00
|
|
|
import threading
|
|
|
|
from typing import List
|
|
|
|
|
2019-10-14 21:20:18 +00:00
|
|
|
import av
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
from homeassistant.core import callback
|
2019-03-28 04:47:07 +00:00
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
from .const import OUTPUT_CONTAINER_FORMAT
|
|
|
|
from .core import Segment, StreamOutput
|
2019-03-28 04:47:07 +00:00
|
|
|
|
2021-01-20 13:44:24 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-03-28 04:47:07 +00:00
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_setup_recorder(hass):
|
|
|
|
"""Only here so Provider Registry works."""
|
|
|
|
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
def recorder_save_worker(file_out: str, segments: List[Segment], container_format):
|
2019-03-28 04:47:07 +00:00
|
|
|
"""Handle saving stream."""
|
2020-09-06 22:54:24 +00:00
|
|
|
if not os.path.exists(os.path.dirname(file_out)):
|
|
|
|
os.makedirs(os.path.dirname(file_out), exist_ok=True)
|
|
|
|
|
2020-08-20 03:18:54 +00:00
|
|
|
first_pts = {"video": None, "audio": None}
|
|
|
|
output = av.open(file_out, "w", format=container_format)
|
2019-03-28 04:47:07 +00:00
|
|
|
output_v = None
|
2020-08-20 03:18:54 +00:00
|
|
|
output_a = None
|
2019-03-28 04:47:07 +00:00
|
|
|
|
2020-11-16 20:13:33 +00:00
|
|
|
# Get first_pts values from first segment
|
|
|
|
if len(segments) > 0:
|
|
|
|
segment = segments[0]
|
2020-08-20 03:18:54 +00:00
|
|
|
source = av.open(segment.segment, "r", format=container_format)
|
2019-03-28 04:47:07 +00:00
|
|
|
source_v = source.streams.video[0]
|
2020-11-16 20:13:33 +00:00
|
|
|
first_pts["video"] = source_v.start_time
|
|
|
|
if len(source.streams.audio) > 0:
|
|
|
|
source_a = source.streams.audio[0]
|
|
|
|
first_pts["audio"] = int(
|
|
|
|
source_v.start_time * source_v.time_base / source_a.time_base
|
|
|
|
)
|
|
|
|
source.close()
|
2019-03-28 04:47:07 +00:00
|
|
|
|
2020-11-16 20:13:33 +00:00
|
|
|
for segment in segments:
|
|
|
|
# Open segment
|
|
|
|
source = av.open(segment.segment, "r", format=container_format)
|
|
|
|
source_v = source.streams.video[0]
|
2019-03-28 04:47:07 +00:00
|
|
|
# Add output streams
|
|
|
|
if not output_v:
|
|
|
|
output_v = output.add_stream(template=source_v)
|
2020-04-24 21:18:58 +00:00
|
|
|
context = output_v.codec_context
|
|
|
|
context.flags |= "GLOBAL_HEADER"
|
2020-08-20 03:18:54 +00:00
|
|
|
if not output_a and len(source.streams.audio) > 0:
|
|
|
|
source_a = source.streams.audio[0]
|
|
|
|
output_a = output.add_stream(template=source_a)
|
2019-03-28 04:47:07 +00:00
|
|
|
|
|
|
|
# Remux video
|
2020-08-20 03:18:54 +00:00
|
|
|
for packet in source.demux():
|
2020-11-16 20:13:33 +00:00
|
|
|
if packet.dts is None:
|
|
|
|
continue
|
|
|
|
packet.pts -= first_pts[packet.stream.type]
|
|
|
|
packet.dts -= first_pts[packet.stream.type]
|
|
|
|
packet.stream = output_v if packet.stream.type == "video" else output_a
|
|
|
|
output.mux(packet)
|
2019-03-28 04:47:07 +00:00
|
|
|
|
2020-04-24 21:18:58 +00:00
|
|
|
source.close()
|
|
|
|
|
2019-03-28 04:47:07 +00:00
|
|
|
output.close()
|
|
|
|
|
|
|
|
|
|
|
|
class RecorderOutput(StreamOutput):
|
|
|
|
"""Represents HLS Output formats."""
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
def __init__(self, hass) -> None:
|
2019-03-28 04:47:07 +00:00
|
|
|
"""Initialize recorder output."""
|
2021-02-16 14:59:43 +00:00
|
|
|
super().__init__(hass)
|
2019-03-28 04:47:07 +00:00
|
|
|
self.video_path = None
|
|
|
|
self._segments = []
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
def _async_put(self, segment: Segment) -> None:
|
|
|
|
"""Store output."""
|
|
|
|
self._segments.append(segment)
|
2019-03-28 04:47:07 +00:00
|
|
|
|
|
|
|
def prepend(self, segments: List[Segment]) -> None:
|
|
|
|
"""Prepend segments to existing list."""
|
2021-02-16 14:59:43 +00:00
|
|
|
segments = [s for s in segments if s.sequence not in self._segments]
|
2019-03-28 04:47:07 +00:00
|
|
|
self._segments = segments + self._segments
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
def save(self):
|
2019-03-28 04:47:07 +00:00
|
|
|
"""Write recording and clean up."""
|
2021-01-20 13:44:24 +00:00
|
|
|
_LOGGER.debug("Starting recorder worker thread")
|
2019-03-28 04:47:07 +00:00
|
|
|
thread = threading.Thread(
|
2019-07-31 19:25:30 +00:00
|
|
|
name="recorder_save_worker",
|
2019-03-28 04:47:07 +00:00
|
|
|
target=recorder_save_worker,
|
2021-02-16 14:59:43 +00:00
|
|
|
args=(self.video_path, self._segments, OUTPUT_CONTAINER_FORMAT),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-03-28 04:47:07 +00:00
|
|
|
thread.start()
|
|
|
|
self._segments = []
|