summaryrefslogtreecommitdiff
path: root/include/py/homekit
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-06-14 14:06:26 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-06-19 23:19:35 +0300
commite97f98e5e27a6df3827564cce594f27f18c89267 (patch)
tree0490a13b5df2d9e1fe9d5d6039401b234be99b01 /include/py/homekit
parent5d8e81b6c8fc7abe75188007c6a86bb501a314ad (diff)
wip
Diffstat (limited to 'include/py/homekit')
-rw-r--r--include/py/homekit/audio/amixer.py14
-rw-r--r--include/py/homekit/camera/__init__.py3
-rw-r--r--include/py/homekit/camera/config.py57
-rw-r--r--include/py/homekit/camera/types.py29
-rw-r--r--include/py/homekit/camera/util.py70
-rw-r--r--include/py/homekit/config/_configs.py6
6 files changed, 163 insertions, 16 deletions
diff --git a/include/py/homekit/audio/amixer.py b/include/py/homekit/audio/amixer.py
index 5133c97..8ed754b 100644
--- a/include/py/homekit/audio/amixer.py
+++ b/include/py/homekit/audio/amixer.py
@@ -1,6 +1,6 @@
import subprocess
-from ..config import app_config as config
+from ..config import config
from threading import Lock
from typing import Union, List
@@ -10,14 +10,14 @@ _default_step = 5
def has_control(s: str) -> bool:
- for control in config['amixer']['controls']:
+ for control in config.app_config['amixer']['controls']:
if control['name'] == s:
return True
return False
def get_caps(s: str) -> List[str]:
- for control in config['amixer']['controls']:
+ for control in config.app_config['amixer']['controls']:
if control['name'] == s:
return control['caps']
raise KeyError(f'control {s} not found')
@@ -25,7 +25,7 @@ def get_caps(s: str) -> List[str]:
def get_all() -> list:
controls = []
- for control in config['amixer']['controls']:
+ for control in config.app_config['amixer']['controls']:
controls.append({
'name': control['name'],
'info': get(control['name']),
@@ -55,8 +55,8 @@ def nocap(control):
def _get_default_step() -> int:
- if 'step' in config['amixer']:
- return int(config['amixer']['step'])
+ if 'step' in config.app_config['amixer']:
+ return int(config.app_config['amixer']['step'])
return _default_step
@@ -75,7 +75,7 @@ def decr(control, step=None):
def call(*args, return_code=False) -> Union[int, str]:
with _lock:
- result = subprocess.run([config['amixer']['bin'], *args],
+ result = subprocess.run([config.app_config['amixer']['bin'], *args],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if return_code:
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
diff --git a/include/py/homekit/config/_configs.py b/include/py/homekit/config/_configs.py
index 1628cba..f88c8ea 100644
--- a/include/py/homekit/config/_configs.py
+++ b/include/py/homekit/config/_configs.py
@@ -53,3 +53,9 @@ class LinuxBoardsConfig(ConfigUnit):
},
}
}
+
+ def get_board_disks(self, name: str) -> list[dict]:
+ return self[name]['ext_hdd']
+
+ def get_board_disks_count(self, name: str) -> int:
+ return len(self[name]['ext_hdd'])