Updated project structure and some file names.

Restored `requirements.txt`

Updated `setup.py` to include new repository url and contact details.

Moved the rtsp code from `record` to `stream`.

Updated project structure to make it more readable and developer friendly - moved mixins to the `mixins` package, moved handlers to the `handlers` package.

Moved files not belonging to anything in particular to the `util` package.

Updated `camera` class to also defer login call.

Deleted unused files like `config_handler`.
This commit is contained in:
Alano Terblanche
2020-12-19 19:55:12 +02:00
parent 4c4dd7dd69
commit 2b3e142fe5
27 changed files with 117 additions and 95 deletions

View File

@@ -1,4 +1,4 @@
from .api_handler import APIHandler
from reolinkapi.handlers.api_handler import APIHandler
from .camera import Camera
__version__ = "0.1.2"

View File

@@ -1,19 +1,31 @@
from .api_handler import APIHandler
from reolinkapi.handlers.api_handler import APIHandler
class Camera(APIHandler):
def __init__(self, ip: str, username: str = "admin", password: str = "", https: bool = False):
def __init__(self, ip: str,
username: str = "admin",
password: str = "",
https: bool = False,
defer_login: bool = False,
**kwargs):
"""
Initialise the Camera object by passing the ip address.
The default details {"username":"admin", "password":""} will be used if nothing passed
For deferring the login to the camera, just pass defer_login = True.
For connecting to the camera behind a proxy pass a proxy argument: proxy={"http": "socks5://127.0.0.1:8000"}
:param ip:
:param username:
:param password:
:param https: connect to the camera over https
:param defer_login: defer the login process
:param proxy: Add a proxy dict for requests to consume.
eg: {"http":"socks5://[username]:[password]@[host]:[port], "https": ...}
More information on proxies in requests: https://stackoverflow.com/a/15661226/9313679
"""
# For when you need to connect to a camera behind a proxy, pass
# a proxy argument: proxy={"http": "socks5://127.0.0.1:8000"}
APIHandler.__init__(self, ip, username, password, https=https)
APIHandler.__init__(self, ip, username, password, https=https, **kwargs)
# Normal call without proxy:
# APIHandler.__init__(self, ip, username, password)
@@ -21,4 +33,6 @@ class Camera(APIHandler):
self.ip = ip
self.username = username
self.password = password
super().login()
if not defer_login:
super().login()

View File

@@ -1,17 +0,0 @@
import io
import yaml
from typing import Optional, Dict
class ConfigHandler:
camera_settings = {}
@staticmethod
def load() -> Optional[Dict]:
try:
stream = io.open("config.yml", 'r', encoding='utf8')
data = yaml.safe_load(stream)
return data
except Exception as e:
print("Config Property Error\n", e)
return None

View File

View File

@@ -1,18 +1,19 @@
import requests
from typing import Dict, List, Optional, Union
from reolinkapi.alarm import AlarmAPIMixin
from reolinkapi.device import DeviceAPIMixin
from reolinkapi.display import DisplayAPIMixin
from reolinkapi.download import DownloadAPIMixin
from reolinkapi.image import ImageAPIMixin
from reolinkapi.motion import MotionAPIMixin
from reolinkapi.network import NetworkAPIMixin
from reolinkapi.ptz import PtzAPIMixin
from reolinkapi.recording import RecordingAPIMixin
from reolinkapi.resthandle import Request
from reolinkapi.system import SystemAPIMixin
from reolinkapi.user import UserAPIMixin
from reolinkapi.zoom import ZoomAPIMixin
from reolinkapi.mixins.alarm import AlarmAPIMixin
from reolinkapi.mixins.device import DeviceAPIMixin
from reolinkapi.mixins.display import DisplayAPIMixin
from reolinkapi.mixins.download import DownloadAPIMixin
from reolinkapi.mixins.image import ImageAPIMixin
from reolinkapi.mixins.motion import MotionAPIMixin
from reolinkapi.mixins.network import NetworkAPIMixin
from reolinkapi.mixins.ptz import PtzAPIMixin
from reolinkapi.mixins.record import RecordAPIMixin
from reolinkapi.handlers.rest_handler import Request
from reolinkapi.mixins.stream import StreamAPIMixin
from reolinkapi.mixins.system import SystemAPIMixin
from reolinkapi.mixins.user import UserAPIMixin
from reolinkapi.mixins.zoom import ZoomAPIMixin
class APIHandler(AlarmAPIMixin,
@@ -23,10 +24,11 @@ class APIHandler(AlarmAPIMixin,
MotionAPIMixin,
NetworkAPIMixin,
PtzAPIMixin,
RecordingAPIMixin,
RecordAPIMixin,
SystemAPIMixin,
UserAPIMixin,
ZoomAPIMixin):
ZoomAPIMixin,
StreamAPIMixin):
"""
The APIHandler class is the backend part of the API, the actual API calls
are implemented in Mixins.
@@ -42,6 +44,7 @@ class APIHandler(AlarmAPIMixin,
:param ip:
:param username:
:param password:
:param https: connect over https
:param proxy: Add a proxy dict for requests to consume.
eg: {"http":"socks5://[username]:[password]@[host]:[port], "https": ...}
More information on proxies in requests: https://stackoverflow.com/a/15661226/9313679

View File

View File

@@ -1,15 +1,8 @@
import requests
import random
import string
from urllib import parse
from io import BytesIO
from typing import Dict, Any, Optional
from PIL.Image import Image, open as open_image
from reolinkapi.rtsp_client import RtspClient
from typing import Dict
class RecordingAPIMixin:
"""API calls for recording/streaming image or video."""
class RecordAPIMixin:
"""API calls for the recording settings"""
def get_recording_encoding(self) -> Dict:
"""
@@ -77,44 +70,3 @@ class RecordingAPIMixin:
}
]
return self._execute_command('SetEnc', body)
###########
# RTSP Stream
###########
def open_video_stream(self, callback: Any = None, proxies: Any = None) -> Any:
"""
'https://support.reolink.com/hc/en-us/articles/360007010473-How-to-Live-View-Reolink-Cameras-via-VLC-Media-Player'
Blocking function creates a generator and returns the frames as it is spawned
:param callback:
:param proxies: Default is none, example: {"host": "localhost", "port": 8000}
"""
rtsp_client = RtspClient(
ip=self.ip, username=self.username, password=self.password, proxies=proxies, callback=callback)
return rtsp_client.open_stream()
def get_snap(self, timeout: float = 3, proxies: Any = None) -> Optional[Image]:
"""
Gets a "snap" of the current camera video data and returns a Pillow Image or None
:param timeout: Request timeout to camera in seconds
:param proxies: http/https proxies to pass to the request object.
:return: Image or None
"""
data = {
'cmd': 'Snap',
'channel': 0,
'rs': ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)),
'user': self.username,
'password': self.password,
}
parms = parse.urlencode(data).encode("utf-8")
try:
response = requests.get(self.url, proxies=proxies, params=parms, timeout=timeout)
if response.status_code == 200:
return open_image(BytesIO(response.content))
print("Could not retrieve data from camera successfully. Status:", response.status_code)
return None
except Exception as e:
print("Could not get Image data\n", e)
raise

View File

@@ -0,0 +1,52 @@
import string
from random import random
from typing import Any, Optional
from urllib import parse
from io import BytesIO
import requests
from PIL.Image import Image, open as open_image
from reolinkapi.utils.rtsp_client import RtspClient
class StreamAPIMixin:
""" API calls for opening a video stream or capturing an image from the camera."""
def open_video_stream(self, callback: Any = None, proxies: Any = None) -> Any:
"""
'https://support.reolink.com/hc/en-us/articles/360007010473-How-to-Live-View-Reolink-Cameras-via-VLC-Media-Player'
Blocking function creates a generator and returns the frames as it is spawned
:param callback:
:param proxies: Default is none, example: {"host": "localhost", "port": 8000}
"""
rtsp_client = RtspClient(
ip=self.ip, username=self.username, password=self.password, proxies=proxies, callback=callback)
return rtsp_client.open_stream()
def get_snap(self, timeout: float = 3, proxies: Any = None) -> Optional[Image]:
"""
Gets a "snap" of the current camera video data and returns a Pillow Image or None
:param timeout: Request timeout to camera in seconds
:param proxies: http/https proxies to pass to the request object.
:return: Image or None
"""
data = {
'cmd': 'Snap',
'channel': 0,
'rs': ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)),
'user': self.username,
'password': self.password,
}
parms = parse.urlencode(data).encode("utf-8")
try:
response = requests.get(self.url, proxies=proxies, params=parms, timeout=timeout)
if response.status_code == 200:
return open_image(BytesIO(response.content))
print("Could not retrieve data from camera successfully. Status:", response.status_code)
return None
except Exception as e:
print("Could not get Image data\n", e)
raise

View File

View File

@@ -2,11 +2,12 @@ import os
from threading import ThreadError
from typing import Any
import cv2
from reolinkapi.util import threaded
from reolinkapi.utils.util import threaded
class RtspClient:
"""
This is a wrapper of the OpenCV VideoCapture method
Inspiration from:
- https://benhowell.github.io/guide/2015/03/09/opencv-and-web-cam-streaming
- https://stackoverflow.com/questions/19846332/python-threading-inside-a-class