diff --git a/README.md b/README.md
index 19332b7..4fcdd37 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
-
-
-
+
+
+
diff --git a/examples/basic_usage.py b/examples/basic_usage.py
new file mode 100644
index 0000000..0ba744c
--- /dev/null
+++ b/examples/basic_usage.py
@@ -0,0 +1,11 @@
+import reolinkapi
+
+if __name__ == "__main__":
+ cam = reolinkapi.Camera("192.168.0.102", defer_login=True)
+
+ # must first login since I defer have deferred the login process
+ cam.login()
+
+ dst = cam.get_dst()
+ ok = cam.add_user("foo", "bar", "admin")
+ alarm = cam.get_alarm_motion()
diff --git a/reolinkapi/__init__.py b/reolinkapi/__init__.py
index e623a67..0a886c6 100644
--- a/reolinkapi/__init__.py
+++ b/reolinkapi/__init__.py
@@ -1,4 +1,4 @@
-from .api_handler import APIHandler
+from reolinkapi.handlers.api_handler import APIHandler
from .camera import Camera
__version__ = "0.1.2"
diff --git a/reolinkapi/camera.py b/reolinkapi/camera.py
index 47db97e..98c208b 100644
--- a/reolinkapi/camera.py
+++ b/reolinkapi/camera.py
@@ -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()
diff --git a/reolinkapi/config_handler.py b/reolinkapi/config_handler.py
deleted file mode 100644
index a1c08ec..0000000
--- a/reolinkapi/config_handler.py
+++ /dev/null
@@ -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
diff --git a/reolinkapi/handlers/__init__.py b/reolinkapi/handlers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/reolinkapi/api_handler.py b/reolinkapi/handlers/api_handler.py
similarity index 84%
rename from reolinkapi/api_handler.py
rename to reolinkapi/handlers/api_handler.py
index 3ceae88..46d4637 100644
--- a/reolinkapi/api_handler.py
+++ b/reolinkapi/handlers/api_handler.py
@@ -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
diff --git a/reolinkapi/resthandle.py b/reolinkapi/handlers/rest_handler.py
similarity index 100%
rename from reolinkapi/resthandle.py
rename to reolinkapi/handlers/rest_handler.py
diff --git a/reolinkapi/mixins/__init__.py b/reolinkapi/mixins/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/reolinkapi/alarm.py b/reolinkapi/mixins/alarm.py
similarity index 100%
rename from reolinkapi/alarm.py
rename to reolinkapi/mixins/alarm.py
diff --git a/reolinkapi/device.py b/reolinkapi/mixins/device.py
similarity index 100%
rename from reolinkapi/device.py
rename to reolinkapi/mixins/device.py
diff --git a/reolinkapi/display.py b/reolinkapi/mixins/display.py
similarity index 100%
rename from reolinkapi/display.py
rename to reolinkapi/mixins/display.py
diff --git a/reolinkapi/download.py b/reolinkapi/mixins/download.py
similarity index 100%
rename from reolinkapi/download.py
rename to reolinkapi/mixins/download.py
diff --git a/reolinkapi/image.py b/reolinkapi/mixins/image.py
similarity index 100%
rename from reolinkapi/image.py
rename to reolinkapi/mixins/image.py
diff --git a/reolinkapi/motion.py b/reolinkapi/mixins/motion.py
similarity index 100%
rename from reolinkapi/motion.py
rename to reolinkapi/mixins/motion.py
diff --git a/reolinkapi/network.py b/reolinkapi/mixins/network.py
similarity index 100%
rename from reolinkapi/network.py
rename to reolinkapi/mixins/network.py
diff --git a/reolinkapi/ptz.py b/reolinkapi/mixins/ptz.py
similarity index 100%
rename from reolinkapi/ptz.py
rename to reolinkapi/mixins/ptz.py
diff --git a/reolinkapi/recording.py b/reolinkapi/mixins/record.py
similarity index 57%
rename from reolinkapi/recording.py
rename to reolinkapi/mixins/record.py
index 41284f2..375b5ca 100644
--- a/reolinkapi/recording.py
+++ b/reolinkapi/mixins/record.py
@@ -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
diff --git a/reolinkapi/mixins/stream.py b/reolinkapi/mixins/stream.py
new file mode 100644
index 0000000..5d6e419
--- /dev/null
+++ b/reolinkapi/mixins/stream.py
@@ -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
diff --git a/reolinkapi/system.py b/reolinkapi/mixins/system.py
similarity index 100%
rename from reolinkapi/system.py
rename to reolinkapi/mixins/system.py
diff --git a/reolinkapi/user.py b/reolinkapi/mixins/user.py
similarity index 100%
rename from reolinkapi/user.py
rename to reolinkapi/mixins/user.py
diff --git a/reolinkapi/zoom.py b/reolinkapi/mixins/zoom.py
similarity index 100%
rename from reolinkapi/zoom.py
rename to reolinkapi/mixins/zoom.py
diff --git a/reolinkapi/utils/__init__.py b/reolinkapi/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/reolinkapi/rtsp_client.py b/reolinkapi/utils/rtsp_client.py
similarity index 97%
rename from reolinkapi/rtsp_client.py
rename to reolinkapi/utils/rtsp_client.py
index 0c1db0e..e260a74 100644
--- a/reolinkapi/rtsp_client.py
+++ b/reolinkapi/utils/rtsp_client.py
@@ -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
diff --git a/reolinkapi/util.py b/reolinkapi/utils/util.py
similarity index 100%
rename from reolinkapi/util.py
rename to reolinkapi/utils/util.py
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..a8aabcd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+numpy==1.19.4
+opencv-python==4.4.0.46
+Pillow==8.0.1
+PySocks==1.7.1
+PyYaml==5.3.1
+requests>=2.18.4
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 500a40f..3764023 100644
--- a/setup.py
+++ b/setup.py
@@ -20,9 +20,9 @@ def find_version(*file_paths):
# Package meta-data.
NAME = 'reolinkapi'
-DESCRIPTION = 'Reolink Camera API written in Python 3.6'
-URL = 'https://github.com/Benehiko/ReolinkCameraAPI'
-AUTHOR_EMAIL = ''
+DESCRIPTION = 'Reolink Camera API client written in Python 3'
+URL = 'https://github.com/ReolinkCameraAPI/reolinkapipy'
+AUTHOR_EMAIL = 'alanoterblanche@gmail.com'
AUTHOR = 'Benehiko'
LICENSE = 'GPL-3.0'
INSTALL_REQUIRES = [