summaryrefslogtreecommitdiff
path: root/src/home/camera
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-09-27 00:54:57 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-09-27 00:54:57 +0300
commitd3a295872c49defb55fc8e4e43e55550991e0927 (patch)
treeb9dca15454f9027d5a9dad0d4443a20de04dbc5d /src/home/camera
parentb7cbc2571c1870b4582ead45277d0aa7f961bec8 (diff)
parentbdbb296697f55f4c3a07af43c9aaf7a9ea86f3d0 (diff)
Merge branch 'master' of ch1p.io:homekit
Diffstat (limited to 'src/home/camera')
-rw-r--r--src/home/camera/__init__.py1
-rw-r--r--src/home/camera/esp32.py226
-rw-r--r--src/home/camera/types.py5
-rw-r--r--src/home/camera/util.py107
4 files changed, 0 insertions, 339 deletions
diff --git a/src/home/camera/__init__.py b/src/home/camera/__init__.py
deleted file mode 100644
index 626930b..0000000
--- a/src/home/camera/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .types import CameraType \ No newline at end of file
diff --git a/src/home/camera/esp32.py b/src/home/camera/esp32.py
deleted file mode 100644
index fe6de0e..0000000
--- a/src/home/camera/esp32.py
+++ /dev/null
@@ -1,226 +0,0 @@
-import logging
-import requests
-import json
-import asyncio
-import aioshutil
-
-from io import BytesIO
-from functools import partial
-from typing import Union, Optional
-from enum import Enum
-from ..api.errors import ApiResponseError
-from ..util import Addr
-
-
-class FrameSize(Enum):
- UXGA_1600x1200 = 13
- SXGA_1280x1024 = 12
- HD_1280x720 = 11
- XGA_1024x768 = 10
- SVGA_800x600 = 9
- VGA_640x480 = 8
- HVGA_480x320 = 7
- CIF_400x296 = 6
- QVGA_320x240 = 5
- N_240x240 = 4
- HQVGA_240x176 = 3
- QCIF_176x144 = 2
- QQVGA_160x120 = 1
- N_96x96 = 0
-
-
-class WBMode(Enum):
- AUTO = 0
- SUNNY = 1
- CLOUDY = 2
- OFFICE = 3
- HOME = 4
-
-
-def _assert_bounds(n: int, min: int, max: int):
- if not min <= n <= max:
- raise ValueError(f'value must be between {min} and {max}')
-
-
-class WebClient:
- def __init__(self,
- addr: Addr):
- self.endpoint = f'http://{addr[0]}:{addr[1]}'
- self.logger = logging.getLogger(self.__class__.__name__)
- self.delay = 0
- self.isfirstrequest = True
-
- async def syncsettings(self, settings) -> bool:
- status = await self.getstatus()
- self.logger.debug(f'syncsettings: status={status}')
-
- changed_anything = False
-
- for name, value in settings.items():
- server_name = name
- if name == 'aec_dsp':
- server_name = 'aec2'
-
- if server_name not in status:
- # legacy compatibility
- if server_name != 'vflip':
- self.logger.warning(f'syncsettings: field `{server_name}` not found in camera status')
- continue
-
- try:
- # server returns 0 or 1 for bool values
- if type(value) is bool:
- value = int(value)
-
- if status[server_name] == value:
- continue
- except KeyError as exc:
- if name != 'vflip':
- self.logger.error(exc)
-
- try:
- # fix for cases like when field is called raw_gma, but method is setrawgma()
- name = name.replace('_', '')
-
- func = getattr(self, f'set{name}')
- self.logger.debug(f'syncsettings: calling set{name}({value})')
-
- await func(value)
-
- changed_anything = True
- except AttributeError as exc:
- self.logger.exception(exc)
- self.logger.error(f'syncsettings: method set{name}() not found')
-
- return changed_anything
-
- def setdelay(self, delay: int):
- self.delay = delay
-
- async def capture(self, output: Optional[str] = None) -> Union[BytesIO, bool]:
- kw = {}
- if output:
- kw['save_to'] = output
- else:
- kw['as_bytes'] = True
- return await self._call('capture', **kw)
-
- async def getstatus(self):
- return json.loads(await self._call('status'))
-
- async def setflash(self, enable: bool):
- await self._control('flash', int(enable))
-
- async def setframesize(self, fs: Union[int, FrameSize]):
- if type(fs) is int:
- fs = FrameSize(fs)
- await self._control('framesize', fs.value)
-
- async def sethmirror(self, enable: bool):
- await self._control('hmirror', int(enable))
-
- async def setvflip(self, enable: bool):
- await self._control('vflip', int(enable))
-
- async def setawb(self, enable: bool):
- await self._control('awb', int(enable))
-
- async def setawbgain(self, enable: bool):
- await self._control('awb_gain', int(enable))
-
- async def setwbmode(self, mode: WBMode):
- await self._control('wb_mode', mode.value)
-
- async def setaecsensor(self, enable: bool):
- await self._control('aec', int(enable))
-
- async def setaecdsp(self, enable: bool):
- await self._control('aec2', int(enable))
-
- async def setagc(self, enable: bool):
- await self._control('agc', int(enable))
-
- async def setagcgain(self, gain: int):
- _assert_bounds(gain, 1, 31)
- await self._control('agc_gain', gain)
-
- async def setgainceiling(self, gainceiling: int):
- _assert_bounds(gainceiling, 2, 128)
- await self._control('gainceiling', gainceiling)
-
- async def setbpc(self, enable: bool):
- await self._control('bpc', int(enable))
-
- async def setwpc(self, enable: bool):
- await self._control('wpc', int(enable))
-
- async def setrawgma(self, enable: bool):
- await self._control('raw_gma', int(enable))
-
- async def setlenscorrection(self, enable: bool):
- await self._control('lenc', int(enable))
-
- async def setdcw(self, enable: bool):
- await self._control('dcw', int(enable))
-
- async def setcolorbar(self, enable: bool):
- await self._control('colorbar', int(enable))
-
- async def setquality(self, q: int):
- _assert_bounds(q, 4, 63)
- await self._control('quality', q)
-
- async def setbrightness(self, brightness: int):
- _assert_bounds(brightness, -2, -2)
- await self._control('brightness', brightness)
-
- async def setcontrast(self, contrast: int):
- _assert_bounds(contrast, -2, 2)
- await self._control('contrast', contrast)
-
- async def setsaturation(self, saturation: int):
- _assert_bounds(saturation, -2, 2)
- await self._control('saturation', saturation)
-
- async def _control(self, var: str, value: Union[int, str]):
- return await self._call('control', params={'var': var, 'val': value})
-
- async def _call(self,
- method: str,
- params: Optional[dict] = None,
- save_to: Optional[str] = None,
- as_bytes=False) -> Union[str, bool, BytesIO]:
- loop = asyncio.get_event_loop()
-
- if not self.isfirstrequest and self.delay > 0:
- sleeptime = self.delay / 1000
- self.logger.debug(f'sleeping for {sleeptime}')
-
- await asyncio.sleep(sleeptime)
-
- self.isfirstrequest = False
-
- url = f'{self.endpoint}/{method}'
- self.logger.debug(f'calling {url}, params: {params}')
-
- kwargs = {}
- if params:
- kwargs['params'] = params
- if save_to:
- kwargs['stream'] = True
-
- r = await loop.run_in_executor(None,
- partial(requests.get, url, **kwargs))
- if r.status_code != 200:
- raise ApiResponseError(status_code=r.status_code)
-
- if as_bytes:
- return BytesIO(r.content)
-
- if save_to:
- r.raise_for_status()
- with open(save_to, 'wb') as f:
- await aioshutil.copyfileobj(r.raw, f)
- return True
-
- return r.text
diff --git a/src/home/camera/types.py b/src/home/camera/types.py
deleted file mode 100644
index de59022..0000000
--- a/src/home/camera/types.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from enum import Enum
-
-
-class CameraType(Enum):
- ESP32 = 'esp32'
diff --git a/src/home/camera/util.py b/src/home/camera/util.py
deleted file mode 100644
index 97f35aa..0000000
--- a/src/home/camera/util.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import asyncio
-import os.path
-import logging
-import psutil
-
-from typing import List, Tuple
-from ..util import chunks
-from ..config import config
-
-_logger = logging.getLogger(__name__)
-_temporary_fixing = '.temporary_fixing.mp4'
-
-
-def _get_ffmpeg_path() -> str:
- return 'ffmpeg' if 'ffmpeg' not in config else config['ffmpeg']['path']
-
-
-def time2seconds(time: str) -> int:
- time, frac = time.split('.')
- frac = int(frac)
-
- h, m, s = [int(i) for i in time.split(':')]
-
- return round(s + m*60 + h*3600 + frac/1000)
-
-
-async def ffmpeg_recreate(filename: str):
- filedir = os.path.dirname(filename)
- tempname = os.path.join(filedir, _temporary_fixing)
- mtime = os.path.getmtime(filename)
-
- args = [_get_ffmpeg_path(), '-nostats', '-loglevel', 'error', '-i', filename, '-c', 'copy', '-y', tempname]
- proc = await asyncio.create_subprocess_exec(*args,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE)
- stdout, stderr = await proc.communicate()
- if proc.returncode != 0:
- _logger.error(f'fix_timestamps({filename}): ffmpeg returned {proc.returncode}, stderr: {stderr.decode().strip()}')
-
- if os.path.isfile(tempname):
- os.unlink(filename)
- os.rename(tempname, filename)
- os.utime(filename, (mtime, mtime))
- _logger.info(f'fix_timestamps({filename}): OK')
- else:
- _logger.error(f'fix_timestamps({filename}): temp file \'{tempname}\' does not exists, fix failed')
-
-
-async def ffmpeg_cut(input: str,
- output: str,
- start_pos: int,
- duration: int):
- args = [_get_ffmpeg_path(), '-nostats', '-loglevel', 'error', '-i', input,
- '-ss', str(start_pos), '-t', str(duration),
- '-c', 'copy', '-y', output]
- proc = await asyncio.create_subprocess_exec(*args,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE)
- stdout, stderr = await proc.communicate()
- if proc.returncode != 0:
- _logger.error(f'ffmpeg_cut({input}, start_pos={start_pos}, duration={duration}): ffmpeg returned {proc.returncode}, stderr: {stderr.decode().strip()}')
- else:
- _logger.info(f'ffmpeg_cut({input}): OK')
-
-
-def dvr_scan_timecodes(timecodes: str) -> List[Tuple[int, int]]:
- tc_backup = timecodes
-
- timecodes = timecodes.split(',')
- if len(timecodes) % 2 != 0:
- raise DVRScanInvalidTimecodes(f'invalid number of timecodes. input: {tc_backup}')
-
- timecodes = list(map(time2seconds, timecodes))
- timecodes = list(chunks(timecodes, 2))
-
- # sort out invalid fragments (dvr-scan returns them sometimes, idk why...)
- timecodes = list(filter(lambda f: f[0] < f[1], timecodes))
- if not timecodes:
- raise DVRScanInvalidTimecodes(f'no valid timecodes. input: {tc_backup}')
-
- # https://stackoverflow.com/a/43600953
- timecodes.sort(key=lambda interval: interval[0])
- merged = [timecodes[0]]
- for current in timecodes:
- previous = merged[-1]
- if current[0] <= previous[1]:
- previous[1] = max(previous[1], current[1])
- else:
- merged.append(current)
-
- return merged
-
-
-class DVRScanInvalidTimecodes(Exception):
- pass
-
-
-def has_handle(fpath):
- for proc in psutil.process_iter():
- try:
- for item in proc.open_files():
- if fpath == item.path:
- return True
- except Exception:
- pass
-
- return False \ No newline at end of file