Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor cleanups and some new example programs. #67

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
34 changes: 34 additions & 0 deletions examples/get_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import reolinkapi
import json

if __name__ == "__main__":
hostname = "watchdog1"

try:
cam = reolinkapi.Camera(hostname, username="userid", password="passwd")
except:
print(f"Failed to open camera {hostname}")
exit(1)

if not cam.is_logged_in():
print(f"Login failed for {hostname}")
exit(1)


print( json.dumps( cam.get_information(), indent=4 ) )
print( json.dumps( cam.get_network_general(), indent=4 ) )
print( json.dumps( cam.get_network_ddns(), indent=4 ) )
print( json.dumps( cam.get_network_ntp(), indent=4 ) )
print( json.dumps( cam.get_network_email(), indent=4 ) )
print( json.dumps( cam.get_network_ftp(), indent=4 ) )
print( json.dumps( cam.get_network_push(), indent=4 ) )
print( json.dumps( cam.get_network_status(), indent=4 ) )
print( json.dumps( cam.get_recording_encoding(), indent=4 ) )
print( json.dumps( cam.get_recording_advanced(), indent=4 ) )
print( json.dumps( cam.get_general_system(), indent=4 ) )
print( json.dumps( cam.get_performance(), indent=4 ) )
print( json.dumps( cam.get_dst(), indent=4 ) )
print( json.dumps( cam.get_online_user(), indent=4 ) )
print( json.dumps( cam.get_users(), indent=4 ) )


44 changes: 44 additions & 0 deletions examples/set_ftp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/opt/homebrew/bin/python3

import reolinkapi
import os
import json
from sys import argv

def ftp_cam(which):
hostname = 'watchdog%d' % which
cam_name = 'Watchdog%d' % which

try:
cam = reolinkapi.Camera(hostname, username="userid", password="passwd")
except:
print(f"Failed to open camera {cam_name}")
return 1

if not cam.is_logged_in():
print(f"Login failed for {cam_name}")
return 2

try:
print(f"Setting FTP params for {cam_name}")
response = cam.set_network_ftp("hocus", "pocus", "directory", "192.168.1.2", 0)
print( json.dumps( response, indent=4 ) )
print( json.dumps( cam.get_network_ftp(), indent=4 ) )
if response[0]["value"]["rspCode"] == 200:
print("Success!")
except Exception as e:
print(f"{argv[0]} for {cam_name} failed.")
print(e)
return 3

return 0


if __name__ == "__main__":
if len(argv) != 2:
print("Usage: %s <cam#>]" % argv[0])
exit(1)

exit( ftp_cam(int(argv[1])) )


41 changes: 41 additions & 0 deletions examples/snap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/opt/homebrew/bin/python3

import reolinkapi
import os
from sys import argv

def snap_cam(which, dirname):
hostname = 'watchdog%d' % which
cam_name = 'Watchdog%d' % which

try:
cam = reolinkapi.Camera(hostname, username="rtsp", password="darknet")
except:
print(f"Failed to open camera {cam_name}")
return 1

if not cam.is_logged_in():
print(f"Login failed for {cam_name}")
return 2

image_file = '%s/%s.jpg' % (dirname, hostname)
if os.path.exists(image_file):
os.unlink(image_file)

print("Snapping", cam_name)
image = cam.get_snap()
image.save(image_file)
return 0


if __name__ == "__main__":
dirname = '.'
if len(argv) == 3:
dirname = argv[2]
elif len(argv) != 2:
print("Usage: %s <cam#> [output-directory]" % argv[0])
exit(1)

exit( snap_cam(int(argv[1]), dirname) )


114 changes: 72 additions & 42 deletions examples/streaming_video.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
#!/opt/homebrew/bin/python3

import cv2
from queue import Queue
from sys import argv
from reolinkapi import Camera

_resize = 1024

def display_frame(cam, frame, count):
if frame is not None:
print("Frame %5d" % count, end='\r')
cv2.imshow(cam.ip, frame)

key = cv2.waitKey(1)
if key == ord('q') or key == ord('Q') or key == 27:
cam.stop_stream()
cv2.destroyAllWindows()
return False

return True

def non_blocking():
print("calling non-blocking")
def non_blocking(cam):
frames = Queue(maxsize=30)

def inner_callback(img):
cv2.imshow("name", maintain_aspect_ratio_resize(img, width=600))
print("got the image non-blocking")
key = cv2.waitKey(1)
if key == ord('q'):
cv2.destroyAllWindows()
exit(1)
def inner_callback(frame):
if _resize > 10:
frames.put(maintain_aspect_ratio_resize(frame, width=_resize))
else:
frames.put(frame.copy())

c = Camera("192.168.1.112", "admin", "jUa2kUzi")
# t in this case is a thread
t = c.open_video_stream(callback=inner_callback)
t = cam.open_video_stream(callback=inner_callback)

print(t.is_alive())
while True:
if not t.is_alive():
print("continuing")
counter = 0

while t.is_alive():
frame = frames.get()
counter = counter+1

if not display_frame(cam, frame, counter):
break
# stop the stream
# client.stop_stream()

if __debug__ and frames.qsize() > 1:
# Can't consume frames fast enough??
print("\nQueue: %d" % frames.qsize())



def blocking():
c = Camera("192.168.1.112", "admin", "jUa2kUzi")
def blocking(cam):
# stream in this case is a generator returning an image (in mat format)
stream = c.open_video_stream()

# using next()
# while True:
# img = next(stream)
# cv2.imshow("name", maintain_aspect_ratio_resize(img, width=600))
# print("got the image blocking")
# key = cv2.waitKey(1)
# if key == ord('q'):
# cv2.destroyAllWindows()
# exit(1)

# or using a for loop
for img in stream:
cv2.imshow("name", maintain_aspect_ratio_resize(img, width=600))
print("got the image blocking")
key = cv2.waitKey(1)
if key == ord('q'):
cv2.destroyAllWindows()
exit(1)
stream = cam.open_video_stream()
counter = 0

for frame in stream:
if _resize > 10:
frame = maintain_aspect_ratio_resize(frame, width=_resize)

counter = counter + 1

if not display_frame(cam, frame, counter):
break;


# Resizes a image and maintains aspect ratio
Expand Down Expand Up @@ -76,6 +87,25 @@ def maintain_aspect_ratio_resize(image, width=None, height=None, inter=cv2.INTER
return cv2.resize(image, dim, interpolation=inter)


# Call the methods. Either Blocking (using generator) or Non-Blocking using threads
# non_blocking()
blocking()

if __name__ == "__main__":
if len(argv) != 2:
print(f"Usage: {argv[0]} <Camera #>")
exit(1)

try:
host = f"watchdog{argv[1]}"
cam = Camera(host, username="rtsp", password="darknet")
except:
print(f"Failed to open camera: {host}")
exit(1)

if not cam.is_logged_in():
print(f"Login failed for {host}")
exit(1)

# Call the methods. Either Blocking (using generator) or Non-Blocking using threads
non_blocking(cam)
# blocking(cam)

print("\nDone.")
17 changes: 15 additions & 2 deletions reolinkapi/handlers/api_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ def login(self) -> bool:
return False
except Exception as e:
print("Error Login\n", e)
raise
return False

def is_logged_in(self) -> bool:
return self.token is not None

def logout(self) -> bool:
"""
Expand Down Expand Up @@ -135,7 +138,17 @@ def _execute_command(self, command: str, data: List[Dict], multi: bool = False)

else:
response = Request.post(self.url, data=data, params=params)
return response.json()
# print(f"Command: {command}, Response: {response.text}")
response.raise_for_status() # raises exception when not a 2xx response
if response.status_code != 204:
try:
return response.json()
except Exception as e:
print(f"JSON failure for {command}; Respose: ", response.text)
raise e

raise RuntimeError(f"Unexpected response code {response.status_code}")

except Exception as e:
print(f"Command {command} failed: {e}")
raise
46 changes: 46 additions & 0 deletions reolinkapi/mixins/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,52 @@ def set_net_port(self, http_port: float = 80, https_port: float = 443, media_por
print("Successfully Set Network Ports")
return True

def set_network_ftp(self, username, password, directory, server_ip, enable) -> Dict:
"""
Set the camera FTP network information
{
"cmd": "GetFtp",
"code": 0,
"value": {
"Ftp": {
"anonymous": 0,
"interval": 15,
"maxSize": 100,
"mode": 0,
"password": "***********",
"port": 21,
"remoteDir": "incoming1",
"schedule": {
"enable": 1,
"table": "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
},
"server": "192.168.1.2",
"streamType": 0,
"userName": "ftpuser"
}
}
}
:return: response
"""
body = [
{
"cmd": "SetFtp",
"action": 0,
"param": {
"Ftp": {
"password": password,
"remoteDir": directory,
"server": server_ip,
"userName": username,
"schedule": {
"enable": enable
}
}
}
}
]
return self._execute_command('SetFtp', body)

def set_wifi(self, ssid: str, password: str) -> Dict:
body = [{"cmd": "SetWifi", "action": 0, "param": {
"Wifi": {
Expand Down
10 changes: 8 additions & 2 deletions reolinkapi/mixins/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from PIL.Image import Image, open as open_image

from reolinkapi.utils.rtsp_client import RtspClient
def __init__(self):
self.rtsp_client = None


class StreamAPIMixin:
Expand All @@ -22,9 +24,13 @@ def open_video_stream(self, callback: Any = None, proxies: Any = None) -> Any:
:param callback:
:param proxies: Default is none, example: {"host": "localhost", "port": 8000}
"""
rtsp_client = RtspClient(
self.rtsp_client = RtspClient(
ip=self.ip, username=self.username, password=self.password, profile=self.profile, proxies=proxies, callback=callback)
return rtsp_client.open_stream()
return self.rtsp_client.open_stream()

def stop_stream(self):
if self.rtsp_client:
self.rtsp_client.stop_stream()

def get_snap(self, timeout: float = 3, proxies: Any = None) -> Optional[Image]:
"""
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def find_version(*file_paths):
]
EXTRAS_REQUIRE = {
'streaming': [
'numpy==1.19.4',
'opencv-python==4.4.0.46',
'Pillow==8.0.1',
'numpy>=1.19.4',
'opencv-python>=4.4.0.46',
'Pillow>=8.0.1',
],
Comment on lines +35 to 38
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree with the inclusive ordered comparison >=. Minor versions could introduce breaking changes at some point. I would rather stick with a specific version and in this case update the specific version manually to a later release.

https://peps.python.org/pep-0440/#version-specifiers

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The install failed on one of my systems so this was the easy fix. Not a big issue for me.

}

Expand Down