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:
Alano Terblanche
2019-08-11 21:06:21 +02:00
parent 5bc90f7c60
commit 475c72b241
5 changed files with 152 additions and 14 deletions

View File

@@ -2,10 +2,14 @@ import io
import json
import random
import string
from urllib.request import urlopen
import sys
from urllib import request
import numpy
import rtsp
from PIL import Image
from RtspClient import RtspClient
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)
: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
"""
self.ip = ip
self.url = "http://" + ip + "/cgi-bin/api.cgi"
self.token = None
self.username = username
self.password = password
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
###########
# Token
@@ -380,7 +390,7 @@ class APIHandler:
param = {"cmd": "GetDevInfo", "token": self.token}
body = [{"cmd": "GetDevInfo", "action": 0, "param": {}}]
response = Request.post(self.url, data=body, params=param)
if response == 200:
if response.status_code == 200:
return json.loads(response.text)
print("Could not retrieve camera information. Status:", response.status_code)
return None
@@ -543,12 +553,15 @@ class APIHandler:
:return: Image or None
"""
try:
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 \
+ "&user=" + self.username \
+ "&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:
b = bytearray(reader.read())
return Image.open(io.BytesIO(b))
@@ -860,3 +873,25 @@ class APIHandler:
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

View File

@@ -4,7 +4,19 @@ from APIHandler import APIHandler
class Camera(APIHandler):
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.username = username
self.password = password

85
RtspClient.py Normal file
View 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()

View File

@@ -1,9 +1,13 @@
import json
import requests
import socket
import socks
class Request:
proxies = None
@staticmethod
def post(url: str, data, params=None) -> requests.Response or None:
@@ -16,10 +20,11 @@ class Request:
"""
try:
headers = {'content-type': 'application/json'}
if params is not None:
r = requests.post(url, params=params, json=data, headers=headers)
else:
r = requests.post(url, json=data)
r = requests.post(url, params=params, json=data, headers=headers, proxies=Request.proxies)
# if params is not None:
# r = requests.post(url, params=params, json=data, headers=headers, proxies=proxies)
# else:
# r = requests.post(url, json=data)
if r.status_code == 200:
return r
else:
@@ -38,7 +43,8 @@ class Request:
:return:
"""
try:
data = requests.get(url=url, params=params, timeout=timeout)
data = requests.get(url=url, params=params, timeout=timeout, proxies=Request.proxies)
return data
except Exception as e:
print("Get Error\n", e)

View File

@@ -1,5 +1,5 @@
from Camera import Camera
c = Camera("192.168.1.100", "admin", "jUa2kUzi")
c.get_wifi()
c.scan_wifi()
c = Camera("192.168.1.112", "admin", "jUa2kUzi")
# print("Getting information", c.get_information())
c.open_video_stream()