From e97f98e5e27a6df3827564cce594f27f18c89267 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Wed, 14 Jun 2023 14:06:26 +0300 Subject: wip --- include/py/homekit/camera/__init__.py | 3 +- include/py/homekit/camera/config.py | 57 ++++++++++++++++++++++++++-- include/py/homekit/camera/types.py | 29 +++++++++++++++ include/py/homekit/camera/util.py | 70 +++++++++++++++++++++++++++++++++-- 4 files changed, 150 insertions(+), 9 deletions(-) (limited to 'include/py/homekit/camera') diff --git a/include/py/homekit/camera/__init__.py b/include/py/homekit/camera/__init__.py index 626930b..4875031 100644 --- a/include/py/homekit/camera/__init__.py +++ b/include/py/homekit/camera/__init__.py @@ -1 +1,2 @@ -from .types import CameraType \ No newline at end of file +from .types import CameraType, VideoContainerType, VideoCodecType, CaptureType +from .config import IpcamConfig \ No newline at end of file diff --git a/include/py/homekit/camera/config.py b/include/py/homekit/camera/config.py index 331e595..c7dbc38 100644 --- a/include/py/homekit/camera/config.py +++ b/include/py/homekit/camera/config.py @@ -1,8 +1,9 @@ +import socket + from ..config import ConfigUnit, LinuxBoardsConfig from typing import Optional from .types import CameraType, VideoContainerType, VideoCodecType - _lbc = LinuxBoardsConfig() @@ -42,7 +43,8 @@ class IpcamConfig(ConfigUnit): 'schema': {'type': 'string', 'check_with': _validate_roi_line} } } - } + }, + 'rtsp_tcp': {'type': 'boolean'} } } }, @@ -55,7 +57,19 @@ class IpcamConfig(ConfigUnit): # TODO FIXME 'fragment_url_templates': cls._url_templates_schema(), - 'original_file_url_templates': cls._url_templates_schema() + 'original_file_url_templates': cls._url_templates_schema(), + + 'hls_path': {'type': 'string', 'required': True}, + 'motion_processing_tmpfs_path': {'type': 'string', 'required': True}, + + 'rtsp_creds': { + 'required': True, + 'type': 'dict', + 'schema': { + 'login': {'type': 'string', 'required': True}, + 'password': {'type': 'string', 'required': True}, + } + } } @staticmethod @@ -78,4 +92,39 @@ class IpcamConfig(ConfigUnit): 'empty': False, 'schema': {'type': 'string'} } - } \ No newline at end of file + } + + def get_all_cam_names(self, + filter_by_server: Optional[str] = None, + filter_by_disk: Optional[int] = None) -> list[int]: + cams = [] + if filter_by_server is not None and filter_by_server not in _lbc: + raise ValueError(f'invalid filter_by_server: {filter_by_server} not found in {_lbc.__class__.__name__}') + for cam, params in self['cams'].items(): + if filter_by_server is None or params['server'] == filter_by_server: + if filter_by_disk is None or params['disk'] == filter_by_disk: + cams.append(int(cam)) + return cams + + def get_all_cam_names_for_this_server(self, + filter_by_disk: Optional[int] = None): + return self.get_all_cam_names(filter_by_server=socket.gethostname(), + filter_by_disk=filter_by_disk) + + def get_cam_server_and_disk(self, cam: int) -> tuple[str, int]: + return self['cams'][cam]['server'], self['cams'][cam]['disk'] + + def get_camera_container(self, cam: int) -> VideoContainerType: + return VideoContainerType(self['cams'][cam]['container']) + + def get_camera_type(self, cam: int) -> CameraType: + return CameraType(self['cams'][cam]['type']) + + def get_rtsp_creds(self) -> tuple[str, str]: + return self['rtsp_creds']['login'], self['rtsp_creds']['password'] + + def should_use_tcp_for_rtsp(self, cam: int) -> bool: + return 'rtsp_tcp' in self['cams'][cam] and self['cams'][cam]['rtsp_tcp'] + + def get_camera_ip(self, camera: int) -> str: + return f'192.168.5.{camera}' diff --git a/include/py/homekit/camera/types.py b/include/py/homekit/camera/types.py index 0d3a384..c313b58 100644 --- a/include/py/homekit/camera/types.py +++ b/include/py/homekit/camera/types.py @@ -6,6 +6,19 @@ class CameraType(Enum): ALIEXPRESS_NONAME = 'ali' HIKVISION = 'hik' + def get_channel_url(self, channel: int) -> str: + if channel not in (1, 2): + raise ValueError(f'channel {channel} is invalid') + if channel == 1: + return '' + elif channel == 2: + if self.value == CameraType.HIKVISION: + return '/Streaming/Channels/2' + elif self.value == CameraType.ALIEXPRESS_NONAME: + return '/?stream=1.sdp' + else: + raise ValueError(f'unsupported camera type {self.value}') + class VideoContainerType(Enum): MP4 = 'mp4' @@ -15,3 +28,19 @@ class VideoContainerType(Enum): class VideoCodecType(Enum): H264 = 'h264' H265 = 'h265' + + +class TimeFilterType(Enum): + FIX = 'fix' + MOTION = 'motion' + MOTION_START = 'motion_start' + + +class TelegramLinkType(Enum): + FRAGMENT = 'fragment' + ORIGINAL_FILE = 'original_file' + + +class CaptureType(Enum): + HLS = 'hls' + RECORD = 'record' diff --git a/include/py/homekit/camera/util.py b/include/py/homekit/camera/util.py index 97f35aa..58c2c70 100644 --- a/include/py/homekit/camera/util.py +++ b/include/py/homekit/camera/util.py @@ -2,13 +2,21 @@ import asyncio import os.path import logging import psutil +import re +from datetime import datetime from typing import List, Tuple from ..util import chunks -from ..config import config +from ..config import config, LinuxBoardsConfig +from .config import IpcamConfig +from .types import VideoContainerType _logger = logging.getLogger(__name__) -_temporary_fixing = '.temporary_fixing.mp4' +_ipcam_config = IpcamConfig() +_lbc_config = LinuxBoardsConfig() + +datetime_format = '%Y-%m-%d-%H.%M.%S' +datetime_format_re = r'\d{4}-\d{2}-\d{2}-\d{2}\.\d{2}.\d{2}' def _get_ffmpeg_path() -> str: @@ -26,7 +34,8 @@ def time2seconds(time: str) -> int: async def ffmpeg_recreate(filename: str): filedir = os.path.dirname(filename) - tempname = os.path.join(filedir, _temporary_fixing) + _, fileext = os.path.splitext(filename) + tempname = os.path.join(filedir, f'.temporary_fixing.{fileext}') mtime = os.path.getmtime(filename) args = [_get_ffmpeg_path(), '-nostats', '-loglevel', 'error', '-i', filename, '-c', 'copy', '-y', tempname] @@ -104,4 +113,57 @@ def has_handle(fpath): except Exception: pass - return False \ No newline at end of file + return False + + +def get_recordings_path(cam: int) -> str: + server, disk = _ipcam_config.get_cam_server_and_disk(cam) + disks = _lbc_config.get_board_disks(server) + disk_mountpoint = disks[disk-1] + return f'{disk_mountpoint}/cam-{cam}' + + +def get_motion_path(cam: int) -> str: + return f'{get_recordings_path(cam)}/motion' + + +def is_valid_recording_name(filename: str) -> bool: + if not filename.startswith('record_'): + return False + + for container_type in VideoContainerType: + if filename.endswith(f'.{container_type.value}'): + return True + + return False + + +def datetime_from_filename(name: str) -> datetime: + name = os.path.basename(name) + exts = '|'.join([t.value for t in VideoContainerType]) + + if name.startswith('record_'): + return datetime.strptime(re.match(rf'record_(.*?)\.(?:{exts})', name).group(1), datetime_format) + + m = re.match(rf'({datetime_format_re})__{datetime_format_re}\.(?:{exts})', name) + if m: + return datetime.strptime(m.group(1), datetime_format) + + raise ValueError(f'unrecognized filename format: {name}') + + +def get_hls_channel_name(cam: int, channel: int) -> str: + name = str(cam) + if channel == 2: + name += '-low' + return name + + +def get_hls_directory(cam, channel) -> str: + dirname = os.path.join( + _ipcam_config['hls_path'], + get_hls_channel_name(cam, channel) + ) + if not os.path.exists(dirname): + os.makedirs(dirname) + return dirname \ No newline at end of file -- cgit v1.2.3