Merge pull request #2 from Benehiko/dev-1.0
Update Master with new APIs
This commit is contained in:
2
.idea/.gitignore
generated
vendored
Normal file
2
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/workspace.xml
|
||||||
11
.idea/ReolinkCameraAPI.iml
generated
Normal file
11
.idea/ReolinkCameraAPI.iml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TestRunnerService">
|
||||||
|
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ReolinkCameraAPI.iml" filepath="$PROJECT_DIR$/.idea/ReolinkCameraAPI.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
772
APIHandler.py
772
APIHandler.py
@@ -1,27 +1,59 @@
|
|||||||
|
import io
|
||||||
import json
|
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
|
from resthandle import Request
|
||||||
|
|
||||||
|
|
||||||
class APIHandler:
|
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
|
||||||
|
|
||||||
def __init__(self, ip, https=False):
|
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'
|
scheme = 'https' if https else 'http'
|
||||||
self.url = f"{scheme}://{ip}/cgi-bin/api.cgi"
|
self.url = f"{scheme}://{ip}/cgi-bin/api.cgi"
|
||||||
|
self.ip = ip
|
||||||
self.token = None
|
self.token = None
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
|
||||||
|
|
||||||
|
###########
|
||||||
# Token
|
# Token
|
||||||
|
###########
|
||||||
|
|
||||||
def login(self, username: str, password: str):
|
def login(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Get login token
|
Get login token
|
||||||
Must be called first, before any other operation can be performed
|
Must be called first, before any other operation can be performed
|
||||||
:param username:
|
:return: bool
|
||||||
:param password:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
body = [{"cmd": "Login", "action": 0, "param": {"User": {"userName": username, "password": password}}}]
|
body = [{"cmd": "Login", "action": 0,
|
||||||
|
"param": {"User": {"userName": self.username, "password": self.password}}}]
|
||||||
param = {"cmd": "Login", "token": "null"}
|
param = {"cmd": "Login", "token": "null"}
|
||||||
response = Request.post(self.url, data=body, params=param)
|
response = Request.post(self.url, data=body, params=param)
|
||||||
if response is not None:
|
if response is not None:
|
||||||
@@ -30,9 +62,12 @@ class APIHandler:
|
|||||||
if int(code) == 0:
|
if int(code) == 0:
|
||||||
self.token = data["value"]["Token"]["name"]
|
self.token = data["value"]["Token"]["name"]
|
||||||
print("Login success")
|
print("Login success")
|
||||||
|
return True
|
||||||
print(self.token)
|
print(self.token)
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
print("Failed to login\nStatus Code:", response.status_code)
|
print("Failed to login\nStatus Code:", response.status_code)
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error Login\n", e)
|
print("Error Login\n", e)
|
||||||
raise
|
raise
|
||||||
@@ -44,43 +79,46 @@ class APIHandler:
|
|||||||
###########
|
###########
|
||||||
# SET Network
|
# SET Network
|
||||||
###########
|
###########
|
||||||
def set_net_port(self, httpPort=80, httpsPort=443, mediaPort=9000, onvifPort=8000, rtmpPort=1935, rtspPort=554):
|
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
|
Set network ports
|
||||||
If nothing is specified, the default values will be used
|
If nothing is specified, the default values will be used
|
||||||
:param httpPort:
|
:param rtsp_port: int
|
||||||
:param httpsPort:
|
:param rtmp_port: int
|
||||||
:param mediaPort:
|
:param onvif_port: int
|
||||||
:param onvifPort:
|
:param media_port: int
|
||||||
:param rtmpPort:
|
:param https_port: int
|
||||||
:param rtspPort:
|
:type http_port: int
|
||||||
:return:
|
:return: bool
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if self.token is None:
|
if self.token is None:
|
||||||
raise ValueError("Login first")
|
raise ValueError("Login first")
|
||||||
|
|
||||||
body = [{"cmd": "SetNetPort", "action": 0, "param": {"NetPort": {
|
body = [{"cmd": "SetNetPort", "action": 0, "param": {"NetPort": {
|
||||||
"httpPort": httpPort,
|
"httpPort": http_port,
|
||||||
"httpsPort": httpsPort,
|
"httpsPort": https_port,
|
||||||
"mediaPort": mediaPort,
|
"mediaPort": media_port,
|
||||||
"onvifPort": onvifPort,
|
"onvifPort": onvif_port,
|
||||||
"rtmpPort": rtmpPort,
|
"rtmpPort": rtmp_port,
|
||||||
"rtspPort": rtspPort
|
"rtspPort": rtsp_port
|
||||||
}}}]
|
}}}]
|
||||||
param = {"token": self.token}
|
param = {"token": self.token}
|
||||||
response = Request.post(self.url, data=body, params=param)
|
response = Request.post(self.url, data=body, params=param)
|
||||||
if response is not None:
|
if response is not None:
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
print("Successfully Set Network Ports")
|
print("Successfully Set Network Ports")
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
print("Something went wront\nStatus Code:", response.status_code)
|
print("Something went wront\nStatus Code:", response.status_code)
|
||||||
|
return False
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Setting Network Port Error\n", e)
|
print("Setting Network Port Error\n", e)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def set_wifi(self, ssid, password):
|
def set_wifi(self, ssid, password) -> json or None:
|
||||||
try:
|
try:
|
||||||
if self.token is None:
|
if self.token is None:
|
||||||
raise ValueError("Login first")
|
raise ValueError("Login first")
|
||||||
@@ -99,7 +137,7 @@ class APIHandler:
|
|||||||
###########
|
###########
|
||||||
# GET
|
# GET
|
||||||
###########
|
###########
|
||||||
def get_net_ports(self):
|
def get_net_ports(self) -> json or None:
|
||||||
"""
|
"""
|
||||||
Get network ports
|
Get network ports
|
||||||
:return:
|
:return:
|
||||||
@@ -113,7 +151,10 @@ class APIHandler:
|
|||||||
{"cmd": "GetP2p", "action": 0, "param": {}}]
|
{"cmd": "GetP2p", "action": 0, "param": {}}]
|
||||||
param = {"token": self.token}
|
param = {"token": self.token}
|
||||||
response = Request.post(self.url, data=body, params=param)
|
response = Request.post(self.url, data=body, params=param)
|
||||||
return json.loads(response.text)
|
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:
|
except Exception as e:
|
||||||
print("Get Network Ports", e)
|
print("Get Network Ports", e)
|
||||||
|
|
||||||
@@ -130,7 +171,9 @@ class APIHandler:
|
|||||||
body = [{"cmd": "GetLocalLink", "action": 1, "param": {}}]
|
body = [{"cmd": "GetLocalLink", "action": 1, "param": {}}]
|
||||||
param = {"cmd": "GetLocalLink", "token": self.token}
|
param = {"cmd": "GetLocalLink", "token": self.token}
|
||||||
request = Request.post(self.url, data=body, params=param)
|
request = Request.post(self.url, data=body, params=param)
|
||||||
return json.loads(request.text)
|
if request.status_code == 200:
|
||||||
|
return json.loads(request.text)
|
||||||
|
print("Could not get ")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Could not get Link Local", e)
|
print("Could not get Link Local", e)
|
||||||
raise
|
raise
|
||||||
@@ -159,6 +202,113 @@ class APIHandler:
|
|||||||
print("Could not Scan wifi\n", e)
|
print("Could not Scan wifi\n", e)
|
||||||
raise
|
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
|
# SYSTEM
|
||||||
###########
|
###########
|
||||||
@@ -166,14 +316,582 @@ class APIHandler:
|
|||||||
###########
|
###########
|
||||||
# GET
|
# GET
|
||||||
###########
|
###########
|
||||||
def get_general_system(self):
|
def get_general_system(self) -> json or None:
|
||||||
try:
|
try:
|
||||||
if self.token is None:
|
if self.token is None:
|
||||||
raise ValueError("Login first")
|
raise ValueError("Login first")
|
||||||
body = [{"cmd": "GetTime", "action": 1, "param": {}}, {"cmd": "GetNorm", "action": 1, "param": {}}]
|
body = [{"cmd": "GetTime", "action": 1, "param": {}}, {"cmd": "GetNorm", "action": 1, "param": {}}]
|
||||||
param = {"token": self.token}
|
param = {"token": self.token}
|
||||||
response = Request.post(self.url, data=body, params=param)
|
response = Request.post(self.url, data=body, params=param)
|
||||||
return json.loads(response.text)
|
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:
|
except Exception as e:
|
||||||
print("Could not get General System settings\n", e)
|
print("Could not get General System settings\n", e)
|
||||||
raise
|
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
|
||||||
|
|
||||||
|
##########
|
||||||
|
# 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
|
||||||
|
|||||||
17
Camera.py
17
Camera.py
@@ -4,9 +4,20 @@ from APIHandler import APIHandler
|
|||||||
class Camera(APIHandler):
|
class Camera(APIHandler):
|
||||||
|
|
||||||
def __init__(self, ip, username="admin", password="", https=False):
|
def __init__(self, ip, username="admin", password="", https=False):
|
||||||
APIHandler.__init__(self, ip, https=https)
|
"""
|
||||||
|
Initialise the Camera object by passing the ip address.
|
||||||
|
The default details {"username":"admin", "password":""} will be used if nothing passed
|
||||||
|
:param ip:
|
||||||
|
:param username:
|
||||||
|
:param password:
|
||||||
|
"""
|
||||||
|
# For when you need to connect to a camera behind a proxy
|
||||||
|
APIHandler.__init__(self, ip, username, password, proxy={"http": "socks5://127.0.0.1:8000"}, https=https)
|
||||||
|
|
||||||
|
# Normal call without proxy:
|
||||||
|
# APIHandler.__init__(self, ip, username, password)
|
||||||
|
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.https = https
|
super().login()
|
||||||
super().login(self.username, self.password)
|
|
||||||
|
|||||||
85
RtspClient.py
Normal file
85
RtspClient.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy
|
||||||
|
import socks
|
||||||
|
|
||||||
|
|
||||||
|
class RtspClient:
|
||||||
|
|
||||||
|
def __init__(self, ip, username, password, port=554, profile="main", **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param ip:
|
||||||
|
:param username:
|
||||||
|
:param password:
|
||||||
|
:param port: rtsp port
|
||||||
|
:param profile: "main" or "sub"
|
||||||
|
:param proxies: {"host": "localhost", "port": 8000}
|
||||||
|
"""
|
||||||
|
self.ip = ip
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.port = port
|
||||||
|
self.sockt = None
|
||||||
|
self.url = "rtsp://" + self.username + ":" + self.password + "@" + self.ip + ":" + str(
|
||||||
|
self.port) + "//h264Preview_01_" + profile
|
||||||
|
self.proxy = kwargs.get("proxies")
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.sockt = self.connect()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.sockt.close()
|
||||||
|
|
||||||
|
def connect(self) -> socket:
|
||||||
|
try:
|
||||||
|
sockt = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sockt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
if self.proxy is not None:
|
||||||
|
sockt.set_proxy(socks.SOCKS5, self.proxy["host"], self.proxy["port"])
|
||||||
|
sockt.connect((self.ip, self.port))
|
||||||
|
return sockt
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def get_frame(self) -> bytearray:
|
||||||
|
try:
|
||||||
|
self.sockt.send(str.encode(self.url))
|
||||||
|
data = b''
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
r = self.sockt.recv(90456)
|
||||||
|
if len(r) == 0:
|
||||||
|
break
|
||||||
|
a = r.find(b'END!')
|
||||||
|
if a != -1:
|
||||||
|
data += r[:a]
|
||||||
|
break
|
||||||
|
data += r
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
continue
|
||||||
|
nparr = numpy.fromstring(data, numpy.uint8)
|
||||||
|
frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
||||||
|
return frame
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def preview(self):
|
||||||
|
""" Blocking function. Opens OpenCV window to display stream. """
|
||||||
|
self.connect()
|
||||||
|
win_name = 'RTSP'
|
||||||
|
cv2.namedWindow(win_name, cv2.WINDOW_AUTOSIZE)
|
||||||
|
cv2.moveWindow(win_name, 20, 20)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
cv2.imshow(win_name, self.get_frame())
|
||||||
|
# if self._latest is not None:
|
||||||
|
# cv2.imshow(win_name,self._latest)
|
||||||
|
if cv2.waitKey(25) & 0xFF == ord('q'):
|
||||||
|
break
|
||||||
|
cv2.waitKey()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
cv2.waitKey()
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import socks
|
||||||
|
|
||||||
|
|
||||||
class Request:
|
class Request:
|
||||||
|
proxies = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def post(url: str, data, params=None) -> requests.Response or None:
|
def post(url: str, data, params=None) -> requests.Response or None:
|
||||||
@@ -16,10 +20,11 @@ class Request:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
headers = {'content-type': 'application/json'}
|
headers = {'content-type': 'application/json'}
|
||||||
if params is not None:
|
r = requests.post(url, verify=False, params=params, json=data, headers=headers, proxies=Request.proxies)
|
||||||
r = requests.post(url, verify=False, params=params, json=data, headers=headers)
|
# if params is not None:
|
||||||
else:
|
# r = requests.post(url, params=params, json=data, headers=headers, proxies=proxies)
|
||||||
r = requests.post(url, verify=False, json=data)
|
# else:
|
||||||
|
# r = requests.post(url, json=data)
|
||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
return r
|
return r
|
||||||
else:
|
else:
|
||||||
@@ -38,7 +43,8 @@ class Request:
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = requests.get(url=url, verify=False, params=params, timeout=timeout)
|
data = requests.get(url=url, verify=False, params=params, timeout=timeout, proxies=Request.proxies)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Get Error\n", e)
|
print("Get Error\n", e)
|
||||||
|
|||||||
Reference in New Issue
Block a user