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:
@@ -1,4 +1,4 @@
|
||||
from .api_handler import APIHandler
|
||||
from reolinkapi.handlers.api_handler import APIHandler
|
||||
from .camera import Camera
|
||||
|
||||
__version__ = "0.1.2"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
0
reolinkapi/handlers/__init__.py
Normal file
0
reolinkapi/handlers/__init__.py
Normal 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
|
||||
0
reolinkapi/mixins/__init__.py
Normal file
0
reolinkapi/mixins/__init__.py
Normal 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
|
||||
52
reolinkapi/mixins/stream.py
Normal file
52
reolinkapi/mixins/stream.py
Normal 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
|
||||
0
reolinkapi/utils/__init__.py
Normal file
0
reolinkapi/utils/__init__.py
Normal 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
|
||||
Reference in New Issue
Block a user