Merge branch 'Benehiko-master'
This commit is contained in:
917
APIHandler.py
917
APIHandler.py
@@ -1,917 +0,0 @@
|
|||||||
import io
|
|
||||||
import json
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import sys
|
|
||||||
from urllib import request
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
import rtsp
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from RtspClient import RtspClient
|
|
||||||
from resthandle import Request
|
|
||||||
|
|
||||||
|
|
||||||
class APIHandler:
|
|
||||||
"""
|
|
||||||
The APIHandler class is the backend part of the API.
|
|
||||||
This handles communication directly with the camera.
|
|
||||||
Current camera's tested: RLC-411WS
|
|
||||||
|
|
||||||
All Code will try to follow the PEP 8 standard as described here: https://www.python.org/dev/peps/pep-0008/
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, ip: str, username: str, password: str, https = False, **kwargs):
|
|
||||||
"""
|
|
||||||
Initialise the Camera API Handler (maps api calls into python)
|
|
||||||
:param ip:
|
|
||||||
:param username:
|
|
||||||
:param password:
|
|
||||||
: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
|
|
||||||
"""
|
|
||||||
scheme = 'https' if https else 'http'
|
|
||||||
self.url = f"{scheme}://{ip}/cgi-bin/api.cgi"
|
|
||||||
self.ip = ip
|
|
||||||
self.token = None
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Token
|
|
||||||
###########
|
|
||||||
|
|
||||||
def login(self) -> bool:
|
|
||||||
"""
|
|
||||||
Get login token
|
|
||||||
Must be called first, before any other operation can be performed
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
body = [{"cmd": "Login", "action": 0,
|
|
||||||
"param": {"User": {"userName": self.username, "password": self.password}}}]
|
|
||||||
param = {"cmd": "Login", "token": "null"}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response is not None:
|
|
||||||
data = json.loads(response.text)[0]
|
|
||||||
code = data["code"]
|
|
||||||
if int(code) == 0:
|
|
||||||
self.token = data["value"]["Token"]["name"]
|
|
||||||
print("Login success")
|
|
||||||
return True
|
|
||||||
print(self.token)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
print("Failed to login\nStatus Code:", response.status_code)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Error Login\n", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# NETWORK
|
|
||||||
###########
|
|
||||||
|
|
||||||
###########
|
|
||||||
# SET Network
|
|
||||||
###########
|
|
||||||
def set_net_port(self, http_port=80, https_port=443, media_port=9000, onvif_port=8000, rtmp_port=1935,
|
|
||||||
rtsp_port=554) -> bool:
|
|
||||||
"""
|
|
||||||
Set network ports
|
|
||||||
If nothing is specified, the default values will be used
|
|
||||||
:param rtsp_port: int
|
|
||||||
:param rtmp_port: int
|
|
||||||
:param onvif_port: int
|
|
||||||
:param media_port: int
|
|
||||||
:param https_port: int
|
|
||||||
:type http_port: int
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if self.token is None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
|
|
||||||
body = [{"cmd": "SetNetPort", "action": 0, "param": {"NetPort": {
|
|
||||||
"httpPort": http_port,
|
|
||||||
"httpsPort": https_port,
|
|
||||||
"mediaPort": media_port,
|
|
||||||
"onvifPort": onvif_port,
|
|
||||||
"rtmpPort": rtmp_port,
|
|
||||||
"rtspPort": rtsp_port
|
|
||||||
}}}]
|
|
||||||
param = {"token": self.token}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response is not None:
|
|
||||||
if response.status_code == 200:
|
|
||||||
print("Successfully Set Network Ports")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print("Something went wront\nStatus Code:", response.status_code)
|
|
||||||
return False
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Setting Network Port Error\n", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def set_wifi(self, ssid, password) -> json or None:
|
|
||||||
try:
|
|
||||||
if self.token is None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
body = [{"cmd": "SetWifi", "action": 0, "param": {
|
|
||||||
"Wifi": {
|
|
||||||
"ssid": ssid,
|
|
||||||
"password": password
|
|
||||||
}}}]
|
|
||||||
param = {"cmd": "SetWifi", "token": self.token}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
return json.loads(response.text)
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not Set Wifi details", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# GET
|
|
||||||
###########
|
|
||||||
def get_net_ports(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get network ports
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if self.token is not None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
|
|
||||||
body = [{"cmd": "GetNetPort", "action": 1, "param": {}},
|
|
||||||
{"cmd": "GetUpnp", "action": 0, "param": {}},
|
|
||||||
{"cmd": "GetP2p", "action": 0, "param": {}}]
|
|
||||||
param = {"token": self.token}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not get network ports data. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Get Network Ports", e)
|
|
||||||
|
|
||||||
def get_link_local(self):
|
|
||||||
"""
|
|
||||||
Get General network data
|
|
||||||
This includes IP address, Device mac, Gateway and DNS
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if self.token is None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
|
|
||||||
body = [{"cmd": "GetLocalLink", "action": 1, "param": {}}]
|
|
||||||
param = {"cmd": "GetLocalLink", "token": self.token}
|
|
||||||
request = Request.post(self.url, data=body, params=param)
|
|
||||||
if request.status_code == 200:
|
|
||||||
return json.loads(request.text)
|
|
||||||
print("Could not get ")
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get Link Local", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_wifi(self):
|
|
||||||
try:
|
|
||||||
if self.token is None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
body = [{"cmd": "GetWifi", "action": 1, "param": {}}]
|
|
||||||
param = {"cmd": "GetWifi", "token": self.token}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
return json.loads(response.text)
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get Wifi\n", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def scan_wifi(self):
|
|
||||||
try:
|
|
||||||
if self.token is None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
body = [{"cmd": "ScanWifi", "action": 1, "param": {}}]
|
|
||||||
param = {"cmd": "ScanWifi", "token": self.token}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
return json.loads(response.text)
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not Scan wifi\n", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Display
|
|
||||||
###########
|
|
||||||
|
|
||||||
###########
|
|
||||||
# GET
|
|
||||||
###########
|
|
||||||
def get_osd(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get OSD information.
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetOsd","code" : 0, "initial" : {
|
|
||||||
"Osd" : {"bgcolor" : 0,"channel" : 0,"osdChannel" : {"enable" : 1,"name" : "Camera1","pos" : "Lower Right"},
|
|
||||||
"osdTime" : {"enable" : 1,"pos" : "Top Center"}
|
|
||||||
}},"range" : {"Osd" : {"bgcolor" : "boolean","channel" : 0,"osdChannel" : {"enable" : "boolean","name" : {"maxLen" : 31},
|
|
||||||
"pos" : ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
|
|
||||||
},
|
|
||||||
"osdTime" : {"enable" : "boolean","pos" : ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
|
|
||||||
}
|
|
||||||
}},"value" : {"Osd" : {"bgcolor" : 0,"channel" : 0,"osdChannel" : {"enable" : 0,"name" : "FarRight","pos" : "Lower Right"},
|
|
||||||
"osdTime" : {"enable" : 0,"pos" : "Top Center"}
|
|
||||||
}}}]
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetOsd", "token": self.token}
|
|
||||||
body = [{"cmd": "GetOsd", "action": 1, "param": {"channel": 0}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve OSD from camera successfully. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get OSD", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_mask(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get the camera mask information
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetMask","code" : 0,"initial" : {
|
|
||||||
"Mask" : {
|
|
||||||
"area" : [{"block" : {"height" : 0,"width" : 0,"x" : 0,"y" : 0},"screen" : {"height" : 0,"width" : 0}}],
|
|
||||||
"channel" : 0,
|
|
||||||
"enable" : 0
|
|
||||||
}
|
|
||||||
},"range" : {"Mask" : {"channel" : 0,"enable" : "boolean","maxAreas" : 4}},"value" : {
|
|
||||||
"Mask" : {
|
|
||||||
"area" : null,
|
|
||||||
"channel" : 0,
|
|
||||||
"enable" : 0}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetMask", "token": self.token}
|
|
||||||
body = [{"cmd": "GetMask", "action": 1, "param": {"channel": 0}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not get Mask from camera successfully. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get mask", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# SET
|
|
||||||
###########
|
|
||||||
def set_osd(self, bg_color: bool = 0, channel: int = 0, osd_channel_enabled: bool = 0, osd_channel_name: str = "",
|
|
||||||
osd_channel_pos: str = "Lower Right", osd_time_enabled: bool = 0,
|
|
||||||
osd_time_pos: str = "Lower Right") -> bool:
|
|
||||||
"""
|
|
||||||
Set OSD
|
|
||||||
:param bg_color: bool
|
|
||||||
:param channel: int channel id
|
|
||||||
:param osd_channel_enabled: bool
|
|
||||||
:param osd_channel_name: string channel name
|
|
||||||
:param osd_channel_pos: string channel position ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
|
|
||||||
:param osd_time_enabled: bool
|
|
||||||
:param osd_time_pos: string time position ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "setOsd", "token": self.token}
|
|
||||||
body = [{"cmd": "SetOsd", "action": 1, "param":
|
|
||||||
{"Osd": {"bgcolor": bg_color, "channel": channel,
|
|
||||||
"osdChannel": {"enable": osd_channel_enabled, "name": osd_channel_name,
|
|
||||||
"pos": osd_channel_pos},
|
|
||||||
"osdTime": {"enable": osd_time_enabled, "pos": osd_time_pos}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
r_data = json.loads(response.text)
|
|
||||||
if r_data["value"]["rspCode"] == "200":
|
|
||||||
return True
|
|
||||||
print("Could not set OSD. Camera responded with status:", r_data["value"])
|
|
||||||
return False
|
|
||||||
print("Could not set OSD. Status:", response.status_code)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not set OSD", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# SYSTEM
|
|
||||||
###########
|
|
||||||
|
|
||||||
###########
|
|
||||||
# GET
|
|
||||||
###########
|
|
||||||
def get_general_system(self) -> json or None:
|
|
||||||
try:
|
|
||||||
if self.token is None:
|
|
||||||
raise ValueError("Login first")
|
|
||||||
body = [{"cmd": "GetTime", "action": 1, "param": {}}, {"cmd": "GetNorm", "action": 1, "param": {}}]
|
|
||||||
param = {"token": self.token}
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve general information from camera successfully. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get General System settings\n", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_performance(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get a snapshot of the current performance of the camera.
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetPerformance",
|
|
||||||
"code" : 0,
|
|
||||||
"value" : {
|
|
||||||
"Performance" : {
|
|
||||||
"codecRate" : 2154,
|
|
||||||
"cpuUsed" : 14,
|
|
||||||
"netThroughput" : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetPerformance", "token": self.token}
|
|
||||||
body = [{"cmd": "GetPerformance", "action": 0, "param": {}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Cound not retrieve performance information from camera successfully. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get performance", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_information(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get the camera information
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetDevInfo","code" : 0,"value" : {
|
|
||||||
"DevInfo" : {
|
|
||||||
"B485" : 0,
|
|
||||||
"IOInputNum" : 0,
|
|
||||||
"IOOutputNum" : 0,
|
|
||||||
"audioNum" : 0,
|
|
||||||
"buildDay" : "build 18081408",
|
|
||||||
"cfgVer" : "v2.0.0.0",
|
|
||||||
"channelNum" : 1,
|
|
||||||
"detail" : "IPC_3816M100000000100000",
|
|
||||||
"diskNum" : 1,
|
|
||||||
"firmVer" : "v2.0.0.1389_18081408",
|
|
||||||
"hardVer" : "IPC_3816M",
|
|
||||||
"model" : "RLC-411WS",
|
|
||||||
"name" : "Camera1_withpersonality",
|
|
||||||
"serial" : "00000000000000",
|
|
||||||
"type" : "IPC",
|
|
||||||
"wifi" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetDevInfo", "token": self.token}
|
|
||||||
body = [{"cmd": "GetDevInfo", "action": 0, "param": {}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve camera information. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get device information", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# SET
|
|
||||||
###########
|
|
||||||
def reboot_camera(self) -> bool:
|
|
||||||
"""
|
|
||||||
Reboots the camera
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "Reboot", "token": self.token}
|
|
||||||
body = [{"cmd": "Reboot", "action": 0, "param": {}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return True
|
|
||||||
print("Something went wrong. Could not reboot camera. Status:", response.status_code)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not reboot camera", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
##########
|
|
||||||
# User
|
|
||||||
##########
|
|
||||||
|
|
||||||
##########
|
|
||||||
# GET
|
|
||||||
##########
|
|
||||||
def get_online_user(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Return a list of current logged-in users in json format
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetOnline","code" : 0,"value" : {
|
|
||||||
"User" : [{
|
|
||||||
"canbeDisconn" : 0,
|
|
||||||
"ip" : "192.168.1.100",
|
|
||||||
"level" : "admin",
|
|
||||||
"sessionId" : 1000,
|
|
||||||
"userName" : "admin"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetOnline", "token": self.token}
|
|
||||||
body = [{"cmd": "GetOnline", "action": 1, "param": {}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve online user from camera. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get online user", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_users(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Return a list of user accounts from the camera in json format
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetUser","code" : 0,"initial" : {
|
|
||||||
"User" : {
|
|
||||||
"level" : "guest"
|
|
||||||
}},
|
|
||||||
"range" : {"User" : {
|
|
||||||
"level" : [ "guest", "admin" ],
|
|
||||||
"password" : {
|
|
||||||
"maxLen" : 31,
|
|
||||||
"minLen" : 6
|
|
||||||
},
|
|
||||||
"userName" : {
|
|
||||||
"maxLen" : 31,
|
|
||||||
"minLen" : 1
|
|
||||||
}}
|
|
||||||
},"value" : {
|
|
||||||
"User" : [
|
|
||||||
{
|
|
||||||
"level" : "admin",
|
|
||||||
"userName" : "admin"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetUser", "token": self.token}
|
|
||||||
body = [{"cmd": "GetUser", "action": 1, "param": {}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve users from camera. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get users", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
##########
|
|
||||||
# SET
|
|
||||||
##########
|
|
||||||
def add_user(self, username: str, password: str, level: str = "guest") -> bool:
|
|
||||||
"""
|
|
||||||
Add a new user account to the camera
|
|
||||||
:param username: The user's username
|
|
||||||
:param password: The user's password
|
|
||||||
:param level: The privilege level 'guest' or 'admin'. Default is 'guest'
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "AddUser", "token": self.token}
|
|
||||||
body = [{"cmd": "AddUser", "action": 0,
|
|
||||||
"param": {"User": {"userName": username, "password": password, "level": level}}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
r_data = json.loads(response.text)
|
|
||||||
if r_data["value"]["rspCode"] == "200":
|
|
||||||
return True
|
|
||||||
print("Could not add user. Camera responded with:", r_data["value"])
|
|
||||||
return False
|
|
||||||
print("Something went wrong. Could not add user. Status:", response.status_code)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not add user", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def modify_user(self, username: str, password: str) -> bool:
|
|
||||||
"""
|
|
||||||
Modify the user's password by specifying their username
|
|
||||||
:param username: The user which would want to be modified
|
|
||||||
:param password: The new password
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "ModifyUser", "token": self.token}
|
|
||||||
body = [{"cmd": "ModifyUser", "action": 0, "param": {"User": {"userName": username, "password": password}}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
r_data = json.loads(response.text)
|
|
||||||
if r_data["value"]["rspCode"] == "200":
|
|
||||||
return True
|
|
||||||
print("Could not modify user:", username, "\nCamera responded with:", r_data["value"])
|
|
||||||
print("Something went wrong. Could not modify user. Status:", response.status_code)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not modify user", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def delete_user(self, username: str) -> bool:
|
|
||||||
"""
|
|
||||||
Delete a user by specifying their username
|
|
||||||
:param username: The user which would want to be deleted
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "DelUser", "token": self.token}
|
|
||||||
body = [{"cmd": "DelUser", "action": 0, "param": {"User": {"userName": username}}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
r_data = json.loads(response.text)
|
|
||||||
if r_data["value"]["rspCode"] == "200":
|
|
||||||
return True
|
|
||||||
print("Could not delete user:", username, "\nCamera responded with:", r_data["value"])
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not delete user", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
##########
|
|
||||||
# Image Data
|
|
||||||
##########
|
|
||||||
def get_snap(self, timeout: int = 3) -> Image or None:
|
|
||||||
"""
|
|
||||||
Gets a "snap" of the current camera video data and returns a Pillow Image or None
|
|
||||||
:param timeout: Request timeout to camera in seconds
|
|
||||||
:return: Image or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
|
|
||||||
randomstr = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
|
|
||||||
snap = self.url + "?cmd=Snap&channel=0&rs=" \
|
|
||||||
+ randomstr \
|
|
||||||
+ "&user=" + self.username \
|
|
||||||
+ "&password=" + self.password
|
|
||||||
req = request.Request(snap)
|
|
||||||
req.set_proxy(Request.proxies, 'http')
|
|
||||||
reader = request.urlopen(req, timeout)
|
|
||||||
if reader.status == 200:
|
|
||||||
b = bytearray(reader.read())
|
|
||||||
return Image.open(io.BytesIO(b))
|
|
||||||
print("Could not retrieve data from camera successfully. Status:", reader.status)
|
|
||||||
return None
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get Image data\n", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
#########
|
|
||||||
# Device
|
|
||||||
#########
|
|
||||||
def get_hdd_info(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Gets all HDD and SD card information from Camera
|
|
||||||
Response data format is as follows:
|
|
||||||
[{"cmd" : "GetHddInfo",
|
|
||||||
"code" : 0,
|
|
||||||
"value" : {
|
|
||||||
"HddInfo" : [{
|
|
||||||
"capacity" : 15181,
|
|
||||||
"format" : 1,
|
|
||||||
"id" : 0,
|
|
||||||
"mount" : 1,
|
|
||||||
"size" : 15181
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetHddInfo", "token": self.token}
|
|
||||||
body = [{"cmd": "GetHddInfo", "action": 0, "param": {}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve HDD/SD info from camera successfully. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get HDD/SD card information", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def format_hdd(self, hdd_id: [int] = [0]) -> bool:
|
|
||||||
"""
|
|
||||||
Format specified HDD/SD cards with their id's
|
|
||||||
:param hdd_id: List of id's specified by the camera with get_hdd_info api. Default is 0 (SD card)
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "Format", "token": self.token}
|
|
||||||
body = [{"cmd": "Format", "action": 0, "param": {"HddInfo": {"id": hdd_id}}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
r_data = json.loads(response.text)
|
|
||||||
if r_data["value"]["rspCode"] == "200":
|
|
||||||
return True
|
|
||||||
print("Could not format HDD/SD. Camera responded with:", r_data["value"])
|
|
||||||
return False
|
|
||||||
print("Could not format HDD/SD. Status:", response.status_code)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not format HDD/SD", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Recording
|
|
||||||
###########
|
|
||||||
|
|
||||||
###########
|
|
||||||
# SET
|
|
||||||
###########
|
|
||||||
|
|
||||||
###########
|
|
||||||
# GET
|
|
||||||
###########
|
|
||||||
def get_recording_encoding(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get the current camera encoding settings for "Clear" and "Fluent" profiles.
|
|
||||||
Response data format is as follows:
|
|
||||||
[{
|
|
||||||
"cmd" : "GetEnc",
|
|
||||||
"code" : 0,
|
|
||||||
"initial" : {
|
|
||||||
"Enc" : {
|
|
||||||
"audio" : 0,
|
|
||||||
"channel" : 0,
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : 4096,
|
|
||||||
"frameRate" : 15,
|
|
||||||
"profile" : "High",
|
|
||||||
"size" : "3072*1728"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : 160,
|
|
||||||
"frameRate" : 7,
|
|
||||||
"profile" : "High",
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"range" : {
|
|
||||||
"Enc" : [
|
|
||||||
{
|
|
||||||
"audio" : "boolean",
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : [ 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 4096,
|
|
||||||
"frameRate" : 15
|
|
||||||
},
|
|
||||||
"frameRate" : [ 20, 18, 16, 15, 12, 10, 8, 6, 4, 2 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "3072*1728"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : [ 64, 128, 160, 192, 256, 384, 512 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 160,
|
|
||||||
"frameRate" : 7
|
|
||||||
},
|
|
||||||
"frameRate" : [ 15, 10, 7, 4 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"audio" : "boolean",
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : [ 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 4096,
|
|
||||||
"frameRate" : 15
|
|
||||||
},
|
|
||||||
"frameRate" : [ 20, 18, 16, 15, 12, 10, 8, 6, 4, 2 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "2592*1944"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : [ 64, 128, 160, 192, 256, 384, 512 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 160,
|
|
||||||
"frameRate" : 7
|
|
||||||
},
|
|
||||||
"frameRate" : [ 15, 10, 7, 4 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"audio" : "boolean",
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : [ 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 3072,
|
|
||||||
"frameRate" : 15
|
|
||||||
},
|
|
||||||
"frameRate" : [ 30, 22, 20, 18, 16, 15, 12, 10, 8, 6, 4, 2 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "2560*1440"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : [ 64, 128, 160, 192, 256, 384, 512 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 160,
|
|
||||||
"frameRate" : 7
|
|
||||||
},
|
|
||||||
"frameRate" : [ 15, 10, 7, 4 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"audio" : "boolean",
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : [ 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 3072,
|
|
||||||
"frameRate" : 15
|
|
||||||
},
|
|
||||||
"frameRate" : [ 30, 22, 20, 18, 16, 15, 12, 10, 8, 6, 4, 2 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "2048*1536"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : [ 64, 128, 160, 192, 256, 384, 512 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 160,
|
|
||||||
"frameRate" : 7
|
|
||||||
},
|
|
||||||
"frameRate" : [ 15, 10, 7, 4 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"audio" : "boolean",
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : [ 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 3072,
|
|
||||||
"frameRate" : 15
|
|
||||||
},
|
|
||||||
"frameRate" : [ 30, 22, 20, 18, 16, 15, 12, 10, 8, 6, 4, 2 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "2304*1296"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : [ 64, 128, 160, 192, 256, 384, 512 ],
|
|
||||||
"default" : {
|
|
||||||
"bitRate" : 160,
|
|
||||||
"frameRate" : 7
|
|
||||||
},
|
|
||||||
"frameRate" : [ 15, 10, 7, 4 ],
|
|
||||||
"profile" : [ "Base", "Main", "High" ],
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"value" : {
|
|
||||||
"Enc" : {
|
|
||||||
"audio" : 0,
|
|
||||||
"channel" : 0,
|
|
||||||
"mainStream" : {
|
|
||||||
"bitRate" : 2048,
|
|
||||||
"frameRate" : 20,
|
|
||||||
"profile" : "Main",
|
|
||||||
"size" : "3072*1728"
|
|
||||||
},
|
|
||||||
"subStream" : {
|
|
||||||
"bitRate" : 64,
|
|
||||||
"frameRate" : 4,
|
|
||||||
"profile" : "High",
|
|
||||||
"size" : "640*360"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetEnc", "token": self.token}
|
|
||||||
body = [{"cmd": "GetEnc", "action": 1, "param": {"channel": 0}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve recording encoding data. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get recording encoding", e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_recording_advanced(self) -> json or None:
|
|
||||||
"""
|
|
||||||
Get recording advanced setup data
|
|
||||||
Response data format is as follows:
|
|
||||||
[{
|
|
||||||
"cmd" : "GetRec",
|
|
||||||
"code" : 0,
|
|
||||||
"initial" : {
|
|
||||||
"Rec" : {
|
|
||||||
"channel" : 0,
|
|
||||||
"overwrite" : 1,
|
|
||||||
"postRec" : "15 Seconds",
|
|
||||||
"preRec" : 1,
|
|
||||||
"schedule" : {
|
|
||||||
"enable" : 1,
|
|
||||||
"table" : "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"range" : {
|
|
||||||
"Rec" : {
|
|
||||||
"channel" : 0,
|
|
||||||
"overwrite" : "boolean",
|
|
||||||
"postRec" : [ "15 Seconds", "30 Seconds", "1 Minute" ],
|
|
||||||
"preRec" : "boolean",
|
|
||||||
"schedule" : {
|
|
||||||
"enable" : "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"value" : {
|
|
||||||
"Rec" : {
|
|
||||||
"channel" : 0,
|
|
||||||
"overwrite" : 1,
|
|
||||||
"postRec" : "15 Seconds",
|
|
||||||
"preRec" : 1,
|
|
||||||
"schedule" : {
|
|
||||||
"enable" : 1,
|
|
||||||
"table" : "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
:return: json or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
param = {"cmd": "GetRec", "token": self.token}
|
|
||||||
body = [{"cmd": "GetRec", "action": 1, "param": {"channel": 0}}]
|
|
||||||
response = Request.post(self.url, data=body, params=param)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return json.loads(response.text)
|
|
||||||
print("Could not retrieve advanced recording. Status:", response.status_code)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not get advanced recoding", e)
|
|
||||||
|
|
||||||
###########
|
|
||||||
# RTSP Stream
|
|
||||||
###########
|
|
||||||
def open_video_stream(self, profile: str = "main") -> Image:
|
|
||||||
"""
|
|
||||||
profile is "main" or "sub"
|
|
||||||
https://support.reolink.com/hc/en-us/articles/360007010473-How-to-Live-View-Reolink-Cameras-via-VLC-Media-Player
|
|
||||||
:param profile:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
with RtspClient(ip=self.ip, username=self.username, password=self.password,
|
|
||||||
proxies={"host": "127.0.0.1", "port": 8000}) as rtsp_client:
|
|
||||||
rtsp_client.preview()
|
|
||||||
# with rtsp.Client(
|
|
||||||
# rtsp_server_uri="rtsp://"
|
|
||||||
# + self.username + ":"
|
|
||||||
# + self.password + "@"
|
|
||||||
# + self.ip
|
|
||||||
# + ":554//h264Preview_01_"
|
|
||||||
# + profile) as client:
|
|
||||||
# return client
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from APIHandler import APIHandler
|
from api import APIHandler
|
||||||
|
|
||||||
|
|
||||||
class Camera(APIHandler):
|
class Camera(APIHandler):
|
||||||
@@ -11,8 +11,9 @@ class Camera(APIHandler):
|
|||||||
:param username:
|
:param username:
|
||||||
:param password:
|
:param password:
|
||||||
"""
|
"""
|
||||||
# For when you need to connect to a camera behind a proxy
|
# For when you need to connect to a camera behind a proxy, pass
|
||||||
APIHandler.__init__(self, ip, username, password, proxy={"http": "socks5://127.0.0.1:8000"}, https=https)
|
# a proxy argument: proxy={"http": "socks5://127.0.0.1:8000"}
|
||||||
|
APIHandler.__init__(self, ip, username, password, https=https)
|
||||||
|
|
||||||
# Normal call without proxy:
|
# Normal call without proxy:
|
||||||
# APIHandler.__init__(self, ip, username, password)
|
# APIHandler.__init__(self, ip, username, password)
|
||||||
|
|||||||
13
Pipfile
Normal file
13
Pipfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
pillow = "*"
|
||||||
|
pyyaml = "*"
|
||||||
|
requests = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
102
Pipfile.lock
generated
Normal file
102
Pipfile.lock
generated
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "0016c39167fc595718fc98862c09aecb3938149a1ff375707052007de7c2ad6f"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||||
|
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||||
|
],
|
||||||
|
"version": "==2019.11.28"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||||
|
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||||
|
],
|
||||||
|
"version": "==2.9"
|
||||||
|
},
|
||||||
|
"pillow": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be",
|
||||||
|
"sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946",
|
||||||
|
"sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837",
|
||||||
|
"sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f",
|
||||||
|
"sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00",
|
||||||
|
"sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d",
|
||||||
|
"sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533",
|
||||||
|
"sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a",
|
||||||
|
"sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358",
|
||||||
|
"sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda",
|
||||||
|
"sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435",
|
||||||
|
"sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2",
|
||||||
|
"sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313",
|
||||||
|
"sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff",
|
||||||
|
"sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317",
|
||||||
|
"sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2",
|
||||||
|
"sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614",
|
||||||
|
"sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0",
|
||||||
|
"sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386",
|
||||||
|
"sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9",
|
||||||
|
"sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636",
|
||||||
|
"sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==7.0.0"
|
||||||
|
},
|
||||||
|
"pyyaml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
|
||||||
|
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
|
||||||
|
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
|
||||||
|
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
|
||||||
|
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
|
||||||
|
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
|
||||||
|
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
|
||||||
|
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
|
||||||
|
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
|
||||||
|
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
|
||||||
|
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.3"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
||||||
|
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.23.0"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||||
|
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||||
|
],
|
||||||
|
"version": "==1.25.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
90
api/APIHandler.py
Normal file
90
api/APIHandler.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from api.recording import RecordingAPIMixin
|
||||||
|
from .device import DeviceAPIMixin
|
||||||
|
from .display import DisplayAPIMixin
|
||||||
|
from .network import NetworkAPIMixin
|
||||||
|
from .system import SystemAPIMixin
|
||||||
|
from .user import UserAPIMixin
|
||||||
|
from resthandle import Request
|
||||||
|
|
||||||
|
|
||||||
|
class APIHandler(SystemAPIMixin,
|
||||||
|
NetworkAPIMixin,
|
||||||
|
UserAPIMixin,
|
||||||
|
DeviceAPIMixin,
|
||||||
|
DisplayAPIMixin,
|
||||||
|
RecordingAPIMixin):
|
||||||
|
"""
|
||||||
|
The APIHandler class is the backend part of the API, the actual API calls
|
||||||
|
are implemented in Mixins.
|
||||||
|
This handles communication directly with the camera.
|
||||||
|
Current camera's tested: RLC-411WS
|
||||||
|
|
||||||
|
All Code will try to follow the PEP 8 standard as described here: https://www.python.org/dev/peps/pep-0008/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ip: str, username: str, password: str, https=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialise the Camera API Handler (maps api calls into python)
|
||||||
|
:param ip:
|
||||||
|
:param username:
|
||||||
|
:param password:
|
||||||
|
: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
|
||||||
|
"""
|
||||||
|
scheme = 'https' if https else 'http'
|
||||||
|
self.url = f"{scheme}://{ip}/cgi-bin/api.cgi"
|
||||||
|
self.ip = ip
|
||||||
|
self.token = None
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""
|
||||||
|
Get login token
|
||||||
|
Must be called first, before any other operation can be performed
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
body = [{"cmd": "Login", "action": 0,
|
||||||
|
"param": {"User": {"userName": self.username, "password": self.password}}}]
|
||||||
|
param = {"cmd": "Login", "token": "null"}
|
||||||
|
response = Request.post(self.url, data=body, params=param)
|
||||||
|
if response is not None:
|
||||||
|
data = response.json()[0]
|
||||||
|
code = data["code"]
|
||||||
|
if int(code) == 0:
|
||||||
|
self.token = data["value"]["Token"]["name"]
|
||||||
|
print("Login success")
|
||||||
|
return True
|
||||||
|
print(self.token)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("Failed to login\nStatus Code:", response.status_code)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print("Error Login\n", e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _execute_command(self, command, data, multi=False):
|
||||||
|
"""
|
||||||
|
Send a POST request to the IP camera with given data.
|
||||||
|
:param command: name of the command to send
|
||||||
|
:param data: object to send to the camera (send as json)
|
||||||
|
:param multi: whether the given command name should be added to the
|
||||||
|
url parameters of the request. Defaults to False. (Some multi-step
|
||||||
|
commands seem to not have a single command name)
|
||||||
|
:return: response JSON as python object
|
||||||
|
"""
|
||||||
|
params = {"token": self.token, 'cmd': command}
|
||||||
|
if multi:
|
||||||
|
del params['cmd']
|
||||||
|
try:
|
||||||
|
if self.token is None:
|
||||||
|
raise ValueError("Login first")
|
||||||
|
response = Request.post(self.url, data=data, params=params)
|
||||||
|
return response.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Command {command} failed: {e}")
|
||||||
|
raise
|
||||||
1
api/__init__.py
Normal file
1
api/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .APIHandler import APIHandler
|
||||||
23
api/device.py
Normal file
23
api/device.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class DeviceAPIMixin:
|
||||||
|
"""API calls for getting device information."""
|
||||||
|
def get_hdd_info(self) -> object:
|
||||||
|
"""
|
||||||
|
Gets all HDD and SD card information from Camera
|
||||||
|
See examples/response/GetHddInfo.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetHddInfo", "action": 0, "param": {}}]
|
||||||
|
return self._execute_command('GetHddInfo', body)
|
||||||
|
|
||||||
|
def format_hdd(self, hdd_id: [int] = [0]) -> bool:
|
||||||
|
"""
|
||||||
|
Format specified HDD/SD cards with their id's
|
||||||
|
:param hdd_id: List of id's specified by the camera with get_hdd_info api. Default is 0 (SD card)
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "Format", "action": 0, "param": {"HddInfo": {"id": hdd_id}}}]
|
||||||
|
r_data = self._execute_command('Format', body)[0]
|
||||||
|
if r_data["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
print("Could not format HDD/SD. Camera responded with:", r_data["value"])
|
||||||
|
return False
|
||||||
47
api/display.py
Normal file
47
api/display.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
class DisplayAPIMixin:
|
||||||
|
"""API calls related to the current image (osd, on screen display)."""
|
||||||
|
|
||||||
|
def get_osd(self) -> object:
|
||||||
|
"""
|
||||||
|
Get OSD information.
|
||||||
|
See examples/response/GetOsd.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetOsd", "action": 1, "param": {"channel": 0}}]
|
||||||
|
return self._execute_command('GetOsd', body)
|
||||||
|
|
||||||
|
def get_mask(self) -> object:
|
||||||
|
"""
|
||||||
|
Get the camera mask information.
|
||||||
|
See examples/response/GetMask.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetMask", "action": 1, "param": {"channel": 0}}]
|
||||||
|
return self._execute_command('GetMask', body)
|
||||||
|
|
||||||
|
def set_osd(self, bg_color: bool = 0, channel: int = 0, osd_channel_enabled: bool = 0, osd_channel_name: str = "",
|
||||||
|
osd_channel_pos: str = "Lower Right", osd_time_enabled: bool = 0,
|
||||||
|
osd_time_pos: str = "Lower Right") -> bool:
|
||||||
|
"""
|
||||||
|
Set OSD
|
||||||
|
:param bg_color: bool
|
||||||
|
:param channel: int channel id
|
||||||
|
:param osd_channel_enabled: bool
|
||||||
|
:param osd_channel_name: string channel name
|
||||||
|
:param osd_channel_pos: string channel position ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
|
||||||
|
:param osd_time_enabled: bool
|
||||||
|
:param osd_time_pos: string time position ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
|
||||||
|
:return: whether the action was successful
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "SetOsd", "action": 1, "param": {
|
||||||
|
"Osd": {"bgcolor": bg_color, "channel": channel,
|
||||||
|
"osdChannel": {"enable": osd_channel_enabled, "name": osd_channel_name,
|
||||||
|
"pos": osd_channel_pos},
|
||||||
|
"osdTime": {"enable": osd_time_enabled, "pos": osd_time_pos}
|
||||||
|
}
|
||||||
|
}}]
|
||||||
|
r_data = self._execute_command('SetOsd', body)[0]
|
||||||
|
if r_data["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
print("Could not set OSD. Camera responded with status:", r_data["value"])
|
||||||
|
return False
|
||||||
52
api/network.py
Normal file
52
api/network.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
class NetworkAPIMixin:
|
||||||
|
"""API calls for network settings."""
|
||||||
|
def set_net_port(self, http_port=80, https_port=443, media_port=9000, onvif_port=8000, rtmp_port=1935,
|
||||||
|
rtsp_port=554) -> bool:
|
||||||
|
"""
|
||||||
|
Set network ports
|
||||||
|
If nothing is specified, the default values will be used
|
||||||
|
:param rtsp_port: int
|
||||||
|
:param rtmp_port: int
|
||||||
|
:param onvif_port: int
|
||||||
|
:param media_port: int
|
||||||
|
:param https_port: int
|
||||||
|
:type http_port: int
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "SetNetPort", "action": 0, "param": {"NetPort": {
|
||||||
|
"httpPort": http_port,
|
||||||
|
"httpsPort": https_port,
|
||||||
|
"mediaPort": media_port,
|
||||||
|
"onvifPort": onvif_port,
|
||||||
|
"rtmpPort": rtmp_port,
|
||||||
|
"rtspPort": rtsp_port
|
||||||
|
}}}]
|
||||||
|
self._execute_command('SetNetPort', body, multi=True)
|
||||||
|
print("Successfully Set Network Ports")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_wifi(self, ssid, password) -> object:
|
||||||
|
body = [{"cmd": "SetWifi", "action": 0, "param": {
|
||||||
|
"Wifi": {
|
||||||
|
"ssid": ssid,
|
||||||
|
"password": password
|
||||||
|
}}}]
|
||||||
|
return self._execute_command('SetWifi', body)
|
||||||
|
|
||||||
|
def get_net_ports(self) -> object:
|
||||||
|
"""
|
||||||
|
Get network ports
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetNetPort", "action": 1, "param": {}},
|
||||||
|
{"cmd": "GetUpnp", "action": 0, "param": {}},
|
||||||
|
{"cmd": "GetP2p", "action": 0, "param": {}}]
|
||||||
|
return self._execute_command('GetNetPort', body, multi=True)
|
||||||
|
|
||||||
|
def get_wifi(self):
|
||||||
|
body = [{"cmd": "GetWifi", "action": 1, "param": {}}]
|
||||||
|
return self._execute_command('GetWifi', body)
|
||||||
|
|
||||||
|
def scan_wifi(self):
|
||||||
|
body = [{"cmd": "ScanWifi", "action": 1, "param": {}}]
|
||||||
|
return self._execute_command('ScanWifi', body)
|
||||||
69
api/recording.py
Normal file
69
api/recording.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import io
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from urllib import request
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from RtspClient import RtspClient
|
||||||
|
from resthandle import Request
|
||||||
|
|
||||||
|
|
||||||
|
class RecordingAPIMixin:
|
||||||
|
"""API calls for recording/streaming image or video."""
|
||||||
|
def get_recording_encoding(self) -> object:
|
||||||
|
"""
|
||||||
|
Get the current camera encoding settings for "Clear" and "Fluent" profiles.
|
||||||
|
See examples/response/GetEnc.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetEnc", "action": 1, "param": {"channel": 0}}]
|
||||||
|
return self._execute_command('GetEnc', body)
|
||||||
|
|
||||||
|
def get_recording_advanced(self) -> object:
|
||||||
|
"""
|
||||||
|
Get recording advanced setup data
|
||||||
|
See examples/response/GetRec.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetRec", "action": 1, "param": {"channel": 0}}]
|
||||||
|
return self._execute_command('GetRec', body)
|
||||||
|
|
||||||
|
###########
|
||||||
|
# RTSP Stream
|
||||||
|
###########
|
||||||
|
def open_video_stream(self, profile: str = "main") -> Image:
|
||||||
|
"""
|
||||||
|
profile is "main" or "sub"
|
||||||
|
https://support.reolink.com/hc/en-us/articles/360007010473-How-to-Live-View-Reolink-Cameras-via-VLC-Media-Player
|
||||||
|
:param profile:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
with RtspClient(ip=self.ip, username=self.username, password=self.password,
|
||||||
|
proxies={"host": "127.0.0.1", "port": 8000}) as rtsp_client:
|
||||||
|
rtsp_client.preview()
|
||||||
|
|
||||||
|
def get_snap(self, timeout: int = 3) -> Image or None:
|
||||||
|
"""
|
||||||
|
Gets a "snap" of the current camera video data and returns a Pillow Image or None
|
||||||
|
:param timeout: Request timeout to camera in seconds
|
||||||
|
:return: Image or None
|
||||||
|
"""
|
||||||
|
randomstr = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
|
||||||
|
snap = self.url + "?cmd=Snap&channel=0&rs=" \
|
||||||
|
+ randomstr \
|
||||||
|
+ "&user=" + self.username \
|
||||||
|
+ "&password=" + self.password
|
||||||
|
try:
|
||||||
|
req = request.Request(snap)
|
||||||
|
req.set_proxy(Request.proxies, 'http')
|
||||||
|
reader = request.urlopen(req, timeout)
|
||||||
|
if reader.status == 200:
|
||||||
|
b = bytearray(reader.read())
|
||||||
|
return Image.open(io.BytesIO(b))
|
||||||
|
print("Could not retrieve data from camera successfully. Status:", reader.status)
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("Could not get Image data\n", e)
|
||||||
|
raise
|
||||||
32
api/system.py
Normal file
32
api/system.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
class SystemAPIMixin:
|
||||||
|
"""API for accessing general system information of the camera."""
|
||||||
|
def get_general_system(self) -> object:
|
||||||
|
""":return: response json"""
|
||||||
|
body = [{"cmd": "GetTime", "action": 1, "param": {}}, {"cmd": "GetNorm", "action": 1, "param": {}}]
|
||||||
|
return self._execute_command('get_general_system', body, multi=True)
|
||||||
|
|
||||||
|
def get_performance(self) -> object:
|
||||||
|
"""
|
||||||
|
Get a snapshot of the current performance of the camera.
|
||||||
|
See examples/response/GetPerformance.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetPerformance", "action": 0, "param": {}}]
|
||||||
|
return self._execute_command('GetPerformance', body)
|
||||||
|
|
||||||
|
def get_information(self) -> object:
|
||||||
|
"""
|
||||||
|
Get the camera information
|
||||||
|
See examples/response/GetDevInfo.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetDevInfo", "action": 0, "param": {}}]
|
||||||
|
return self._execute_command('GetDevInfo', body)
|
||||||
|
|
||||||
|
def reboot_camera(self) -> object:
|
||||||
|
"""
|
||||||
|
Reboots the camera
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "Reboot", "action": 0, "param": {}}]
|
||||||
|
return self._execute_command('Reboot', body)
|
||||||
62
api/user.py
Normal file
62
api/user.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
class UserAPIMixin:
|
||||||
|
"""User-related API calls."""
|
||||||
|
def get_online_user(self) -> object:
|
||||||
|
"""
|
||||||
|
Return a list of current logged-in users in json format
|
||||||
|
See examples/response/GetOnline.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetOnline", "action": 1, "param": {}}]
|
||||||
|
return self._execute_command('GetOnline', body)
|
||||||
|
|
||||||
|
def get_users(self) -> object:
|
||||||
|
"""
|
||||||
|
Return a list of user accounts from the camera in json format.
|
||||||
|
See examples/response/GetUser.json for example response data.
|
||||||
|
:return: response json
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "GetUser", "action": 1, "param": {}}]
|
||||||
|
return self._execute_command('GetUser', body)
|
||||||
|
|
||||||
|
def add_user(self, username: str, password: str, level: str = "guest") -> bool:
|
||||||
|
"""
|
||||||
|
Add a new user account to the camera
|
||||||
|
:param username: The user's username
|
||||||
|
:param password: The user's password
|
||||||
|
:param level: The privilege level 'guest' or 'admin'. Default is 'guest'
|
||||||
|
:return: whether the user was added successfully
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "AddUser", "action": 0,
|
||||||
|
"param": {"User": {"userName": username, "password": password, "level": level}}}]
|
||||||
|
r_data = self._execute_command('AddUser', body)[0]
|
||||||
|
if r_data["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
print("Could not add user. Camera responded with:", r_data["value"])
|
||||||
|
return False
|
||||||
|
|
||||||
|
def modify_user(self, username: str, password: str) -> bool:
|
||||||
|
"""
|
||||||
|
Modify the user's password by specifying their username
|
||||||
|
:param username: The user which would want to be modified
|
||||||
|
:param password: The new password
|
||||||
|
:return: whether the user was modified successfully
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "ModifyUser", "action": 0, "param": {"User": {"userName": username, "password": password}}}]
|
||||||
|
r_data = self._execute_command('ModifyUser', body)[0]
|
||||||
|
if r_data["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
print("Could not modify user:", username, "\nCamera responded with:", r_data["value"])
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_user(self, username: str) -> bool:
|
||||||
|
"""
|
||||||
|
Delete a user by specifying their username
|
||||||
|
:param username: The user which would want to be deleted
|
||||||
|
:return: whether the user was deleted successfully
|
||||||
|
"""
|
||||||
|
body = [{"cmd": "DelUser", "action": 0, "param": {"User": {"userName": username}}}]
|
||||||
|
r_data = self._execute_command('DelUser', body)[0]
|
||||||
|
if r_data["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
print("Could not delete user:", username, "\nCamera responded with:", r_data["value"])
|
||||||
|
return False
|
||||||
26
examples/response/GetDevInfo.json
Normal file
26
examples/response/GetDevInfo.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetDevInfo",
|
||||||
|
"code": 0,
|
||||||
|
"value": {
|
||||||
|
"DevInfo": {
|
||||||
|
"B485": 0,
|
||||||
|
"IOInputNum": 0,
|
||||||
|
"IOOutputNum": 0,
|
||||||
|
"audioNum": 0,
|
||||||
|
"buildDay": "build 18081408",
|
||||||
|
"cfgVer": "v2.0.0.0",
|
||||||
|
"channelNum": 1,
|
||||||
|
"detail": "IPC_3816M100000000100000",
|
||||||
|
"diskNum": 1,
|
||||||
|
"firmVer": "v2.0.0.1389_18081408",
|
||||||
|
"hardVer": "IPC_3816M",
|
||||||
|
"model": "RLC-411WS",
|
||||||
|
"name": "Camera1_withpersonality",
|
||||||
|
"serial": "00000000000000",
|
||||||
|
"type": "IPC",
|
||||||
|
"wifi": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
377
examples/response/GetEnc.json
Normal file
377
examples/response/GetEnc.json
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetEnc",
|
||||||
|
"code": 0,
|
||||||
|
"initial": {
|
||||||
|
"Enc": {
|
||||||
|
"audio": 0,
|
||||||
|
"channel": 0,
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": 4096,
|
||||||
|
"frameRate": 15,
|
||||||
|
"profile": "High",
|
||||||
|
"size": "3072*1728"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": 160,
|
||||||
|
"frameRate": 7,
|
||||||
|
"profile": "High",
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"Enc": [
|
||||||
|
{
|
||||||
|
"audio": "boolean",
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": [
|
||||||
|
1024,
|
||||||
|
1536,
|
||||||
|
2048,
|
||||||
|
3072,
|
||||||
|
4096,
|
||||||
|
5120,
|
||||||
|
6144,
|
||||||
|
7168,
|
||||||
|
8192
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 4096,
|
||||||
|
"frameRate": 15
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
20,
|
||||||
|
18,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "3072*1728"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": [
|
||||||
|
64,
|
||||||
|
128,
|
||||||
|
160,
|
||||||
|
192,
|
||||||
|
256,
|
||||||
|
384,
|
||||||
|
512
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 160,
|
||||||
|
"frameRate": 7
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
7,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"audio": "boolean",
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": [
|
||||||
|
1024,
|
||||||
|
1536,
|
||||||
|
2048,
|
||||||
|
3072,
|
||||||
|
4096,
|
||||||
|
5120,
|
||||||
|
6144,
|
||||||
|
7168,
|
||||||
|
8192
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 4096,
|
||||||
|
"frameRate": 15
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
20,
|
||||||
|
18,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "2592*1944"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": [
|
||||||
|
64,
|
||||||
|
128,
|
||||||
|
160,
|
||||||
|
192,
|
||||||
|
256,
|
||||||
|
384,
|
||||||
|
512
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 160,
|
||||||
|
"frameRate": 7
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
7,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"audio": "boolean",
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": [
|
||||||
|
1024,
|
||||||
|
1536,
|
||||||
|
2048,
|
||||||
|
3072,
|
||||||
|
4096,
|
||||||
|
5120,
|
||||||
|
6144,
|
||||||
|
7168,
|
||||||
|
8192
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 3072,
|
||||||
|
"frameRate": 15
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
30,
|
||||||
|
22,
|
||||||
|
20,
|
||||||
|
18,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "2560*1440"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": [
|
||||||
|
64,
|
||||||
|
128,
|
||||||
|
160,
|
||||||
|
192,
|
||||||
|
256,
|
||||||
|
384,
|
||||||
|
512
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 160,
|
||||||
|
"frameRate": 7
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
7,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"audio": "boolean",
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": [
|
||||||
|
1024,
|
||||||
|
1536,
|
||||||
|
2048,
|
||||||
|
3072,
|
||||||
|
4096,
|
||||||
|
5120,
|
||||||
|
6144,
|
||||||
|
7168,
|
||||||
|
8192
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 3072,
|
||||||
|
"frameRate": 15
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
30,
|
||||||
|
22,
|
||||||
|
20,
|
||||||
|
18,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "2048*1536"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": [
|
||||||
|
64,
|
||||||
|
128,
|
||||||
|
160,
|
||||||
|
192,
|
||||||
|
256,
|
||||||
|
384,
|
||||||
|
512
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 160,
|
||||||
|
"frameRate": 7
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
7,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"audio": "boolean",
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": [
|
||||||
|
1024,
|
||||||
|
1536,
|
||||||
|
2048,
|
||||||
|
3072,
|
||||||
|
4096,
|
||||||
|
5120,
|
||||||
|
6144,
|
||||||
|
7168,
|
||||||
|
8192
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 3072,
|
||||||
|
"frameRate": 15
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
30,
|
||||||
|
22,
|
||||||
|
20,
|
||||||
|
18,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "2304*1296"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": [
|
||||||
|
64,
|
||||||
|
128,
|
||||||
|
160,
|
||||||
|
192,
|
||||||
|
256,
|
||||||
|
384,
|
||||||
|
512
|
||||||
|
],
|
||||||
|
"default": {
|
||||||
|
"bitRate": 160,
|
||||||
|
"frameRate": 7
|
||||||
|
},
|
||||||
|
"frameRate": [
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
7,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"profile": [
|
||||||
|
"Base",
|
||||||
|
"Main",
|
||||||
|
"High"
|
||||||
|
],
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"Enc": {
|
||||||
|
"audio": 0,
|
||||||
|
"channel": 0,
|
||||||
|
"mainStream": {
|
||||||
|
"bitRate": 2048,
|
||||||
|
"frameRate": 20,
|
||||||
|
"profile": "Main",
|
||||||
|
"size": "3072*1728"
|
||||||
|
},
|
||||||
|
"subStream": {
|
||||||
|
"bitRate": 64,
|
||||||
|
"frameRate": 4,
|
||||||
|
"profile": "High",
|
||||||
|
"size": "640*360"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
17
examples/response/GetHddInfo.json
Normal file
17
examples/response/GetHddInfo.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetHddInfo",
|
||||||
|
"code": 0,
|
||||||
|
"value": {
|
||||||
|
"HddInfo": [
|
||||||
|
{
|
||||||
|
"capacity": 15181,
|
||||||
|
"format": 1,
|
||||||
|
"id": 0,
|
||||||
|
"mount": 1,
|
||||||
|
"size": 15181
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
40
examples/response/GetMask.json
Normal file
40
examples/response/GetMask.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetMask",
|
||||||
|
"code": 0,
|
||||||
|
"initial": {
|
||||||
|
"Mask": {
|
||||||
|
"area": [
|
||||||
|
{
|
||||||
|
"block": {
|
||||||
|
"height": 0,
|
||||||
|
"width": 0,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"screen": {
|
||||||
|
"height": 0,
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channel": 0,
|
||||||
|
"enable": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"Mask": {
|
||||||
|
"channel": 0,
|
||||||
|
"enable": "boolean",
|
||||||
|
"maxAreas": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"Mask": {
|
||||||
|
"area": null,
|
||||||
|
"channel": 0,
|
||||||
|
"enable": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
17
examples/response/GetOnline.json
Normal file
17
examples/response/GetOnline.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetOnline",
|
||||||
|
"code": 0,
|
||||||
|
"value": {
|
||||||
|
"User": [
|
||||||
|
{
|
||||||
|
"canbeDisconn": 0,
|
||||||
|
"ip": "192.168.1.100",
|
||||||
|
"level": "admin",
|
||||||
|
"sessionId": 1000,
|
||||||
|
"userName": "admin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
67
examples/response/GetOsd.json
Normal file
67
examples/response/GetOsd.json
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetOsd",
|
||||||
|
"code": 0,
|
||||||
|
"initial": {
|
||||||
|
"Osd": {
|
||||||
|
"bgcolor": 0,
|
||||||
|
"channel": 0,
|
||||||
|
"osdChannel": {
|
||||||
|
"enable": 1,
|
||||||
|
"name": "Camera1",
|
||||||
|
"pos": "Lower Right"
|
||||||
|
},
|
||||||
|
"osdTime": {
|
||||||
|
"enable": 1,
|
||||||
|
"pos": "Top Center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"Osd": {
|
||||||
|
"bgcolor": "boolean",
|
||||||
|
"channel": 0,
|
||||||
|
"osdChannel": {
|
||||||
|
"enable": "boolean",
|
||||||
|
"name": {
|
||||||
|
"maxLen": 31
|
||||||
|
},
|
||||||
|
"pos": [
|
||||||
|
"Upper Left",
|
||||||
|
"Top Center",
|
||||||
|
"Upper Right",
|
||||||
|
"Lower Left",
|
||||||
|
"Bottom Center",
|
||||||
|
"Lower Right"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"osdTime": {
|
||||||
|
"enable": "boolean",
|
||||||
|
"pos": [
|
||||||
|
"Upper Left",
|
||||||
|
"Top Center",
|
||||||
|
"Upper Right",
|
||||||
|
"Lower Left",
|
||||||
|
"Bottom Center",
|
||||||
|
"Lower Right"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"Osd": {
|
||||||
|
"bgcolor": 0,
|
||||||
|
"channel": 0,
|
||||||
|
"osdChannel": {
|
||||||
|
"enable": 0,
|
||||||
|
"name": "FarRight",
|
||||||
|
"pos": "Lower Right"
|
||||||
|
},
|
||||||
|
"osdTime": {
|
||||||
|
"enable": 0,
|
||||||
|
"pos": "Top Center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
13
examples/response/GetPerformance.json
Normal file
13
examples/response/GetPerformance.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetPerformance",
|
||||||
|
"code": 0,
|
||||||
|
"value": {
|
||||||
|
"Performance": {
|
||||||
|
"codecRate": 2154,
|
||||||
|
"cpuUsed": 14,
|
||||||
|
"netThroughput": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
45
examples/response/GetRec.json
Normal file
45
examples/response/GetRec.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetRec",
|
||||||
|
"code": 0,
|
||||||
|
"initial": {
|
||||||
|
"Rec": {
|
||||||
|
"channel": 0,
|
||||||
|
"overwrite": 1,
|
||||||
|
"postRec": "15 Seconds",
|
||||||
|
"preRec": 1,
|
||||||
|
"schedule": {
|
||||||
|
"enable": 1,
|
||||||
|
"table": "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"Rec": {
|
||||||
|
"channel": 0,
|
||||||
|
"overwrite": "boolean",
|
||||||
|
"postRec": [
|
||||||
|
"15 Seconds",
|
||||||
|
"30 Seconds",
|
||||||
|
"1 Minute"
|
||||||
|
],
|
||||||
|
"preRec": "boolean",
|
||||||
|
"schedule": {
|
||||||
|
"enable": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"Rec": {
|
||||||
|
"channel": 0,
|
||||||
|
"overwrite": 1,
|
||||||
|
"postRec": "15 Seconds",
|
||||||
|
"preRec": 1,
|
||||||
|
"schedule": {
|
||||||
|
"enable": 1,
|
||||||
|
"table": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
35
examples/response/GetUser.json
Normal file
35
examples/response/GetUser.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"cmd": "GetUser",
|
||||||
|
"code": 0,
|
||||||
|
"initial": {
|
||||||
|
"User": {
|
||||||
|
"level": "guest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"User": {
|
||||||
|
"level": [
|
||||||
|
"guest",
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"password": {
|
||||||
|
"maxLen": 31,
|
||||||
|
"minLen": 6
|
||||||
|
},
|
||||||
|
"userName": {
|
||||||
|
"maxLen": 31,
|
||||||
|
"minLen": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"User": [
|
||||||
|
{
|
||||||
|
"level": "admin",
|
||||||
|
"userName": "admin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import socket
|
|
||||||
|
|
||||||
import socks
|
|
||||||
|
|
||||||
|
|
||||||
class Request:
|
class Request:
|
||||||
@@ -28,7 +25,7 @@ class Request:
|
|||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
return r
|
return r
|
||||||
else:
|
else:
|
||||||
raise ValueError("Status: ", r.status_code)
|
raise ValueError(f"Http Request had non-200 Status: {r.status_code}", r.status_code)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Post Error\n", e)
|
print("Post Error\n", e)
|
||||||
raise
|
raise
|
||||||
|
|||||||
Reference in New Issue
Block a user