Added proxy support. Bug fixes in APIHandler. RTSP support (adding)
Proxy support allows contacting the camera behind a proxy (GET and POST requests). Adding RTSP support - still in progress.
This commit is contained in:
@@ -2,10 +2,14 @@ import io
|
|||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from urllib.request import urlopen
|
import sys
|
||||||
|
from urllib import request
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
import rtsp
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from RtspClient import RtspClient
|
||||||
from resthandle import Request
|
from resthandle import Request
|
||||||
|
|
||||||
|
|
||||||
@@ -19,17 +23,23 @@ class APIHandler:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, username: str, password: str):
|
def __init__(self, ip: str, username: str, password: str, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialise the Camera API Handler (maps api calls into python)
|
Initialise the Camera API Handler (maps api calls into python)
|
||||||
:param ip:
|
:param ip:
|
||||||
:param username:
|
:param username:
|
||||||
:param password:
|
: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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.ip = ip
|
||||||
self.url = "http://" + ip + "/cgi-bin/api.cgi"
|
self.url = "http://" + ip + "/cgi-bin/api.cgi"
|
||||||
self.token = None
|
self.token = None
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
|
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# Token
|
# Token
|
||||||
@@ -380,7 +390,7 @@ class APIHandler:
|
|||||||
param = {"cmd": "GetDevInfo", "token": self.token}
|
param = {"cmd": "GetDevInfo", "token": self.token}
|
||||||
body = [{"cmd": "GetDevInfo", "action": 0, "param": {}}]
|
body = [{"cmd": "GetDevInfo", "action": 0, "param": {}}]
|
||||||
response = Request.post(self.url, data=body, params=param)
|
response = Request.post(self.url, data=body, params=param)
|
||||||
if response == 200:
|
if response.status_code == 200:
|
||||||
return json.loads(response.text)
|
return json.loads(response.text)
|
||||||
print("Could not retrieve camera information. Status:", response.status_code)
|
print("Could not retrieve camera information. Status:", response.status_code)
|
||||||
return None
|
return None
|
||||||
@@ -543,12 +553,15 @@ class APIHandler:
|
|||||||
:return: Image or None
|
:return: Image or None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
||||||
randomstr = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
|
randomstr = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
|
||||||
snap = "?cmd=Snap&channel=0&rs=" \
|
snap = self.url + "?cmd=Snap&channel=0&rs=" \
|
||||||
+ randomstr \
|
+ randomstr \
|
||||||
+ "&user=" + self.username \
|
+ "&user=" + self.username \
|
||||||
+ "&password=" + self.password
|
+ "&password=" + self.password
|
||||||
reader = urlopen(self.url + snap, timeout)
|
req = request.Request(snap)
|
||||||
|
req.set_proxy(Request.proxies, 'http')
|
||||||
|
reader = request.urlopen(req, timeout)
|
||||||
if reader.status == 200:
|
if reader.status == 200:
|
||||||
b = bytearray(reader.read())
|
b = bytearray(reader.read())
|
||||||
return Image.open(io.BytesIO(b))
|
return Image.open(io.BytesIO(b))
|
||||||
@@ -860,3 +873,25 @@ class APIHandler:
|
|||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Could not get advanced recoding", 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
|
||||||
|
|||||||
14
Camera.py
14
Camera.py
@@ -4,7 +4,19 @@ from APIHandler import APIHandler
|
|||||||
class Camera(APIHandler):
|
class Camera(APIHandler):
|
||||||
|
|
||||||
def __init__(self, ip, username="admin", password=""):
|
def __init__(self, ip, username="admin", password=""):
|
||||||
APIHandler.__init__(self, ip, username, password)
|
"""
|
||||||
|
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"})
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|||||||
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, params=params, json=data, headers=headers, proxies=Request.proxies)
|
||||||
r = requests.post(url, 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, 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, params=params, timeout=timeout)
|
data = requests.get(url=url, 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