diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..defbd59
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,16 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+pillow = "*"
+pyyaml = "*"
+requests = "*"
+numpy = "*"
+opencv-python = "*"
+pysocks = "*"
+
+[requires]
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..15b8698
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,169 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "6700bce6ed08db166eff9d3105158923ffd2ffbf35c814a4d0133552bda03b5a"
+ },
+ "pipfile-spec": 6,
+ "requires": {},
+ "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"
+ },
+ "numpy": {
+ "hashes": [
+ "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6",
+ "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e",
+ "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc",
+ "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc",
+ "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a",
+ "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa",
+ "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3",
+ "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121",
+ "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971",
+ "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26",
+ "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd",
+ "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480",
+ "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec",
+ "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77",
+ "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57",
+ "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07",
+ "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572",
+ "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73",
+ "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca",
+ "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474",
+ "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"
+ ],
+ "index": "pypi",
+ "version": "==1.18.1"
+ },
+ "opencv-python": {
+ "hashes": [
+ "sha256:0f2e739c582e8c5e432130648bc6d66a56bc65f4cd9ff0bc7033033d2130c7a3",
+ "sha256:0f3d159ad6cb9cbd188c726f87485f0799a067a0a15f34c25d7b5c8db3cb2e50",
+ "sha256:167a6aff9bd124a3a67e0ec25d0da5ecdc8d96a56405e3e5e7d586c4105eb1bb",
+ "sha256:1b90d50bc7a31e9573a8da1b80fcd1e4d9c86c0e5f76387858e1b87eb8b0332b",
+ "sha256:2baf1213ae2fd678991f905d7b2b94eddfdfb5f75757db0f0b31eebd48ca200d",
+ "sha256:312dda54c7e809c20d7409418060ae0e9cdbe82975e7ced429eb3c234ffc0d4a",
+ "sha256:32384e675f7cefe707cac40a95eeb142d6869065e39c5500374116297cd8ca6d",
+ "sha256:5c50634dd8f2f866fd99fd939292ce10e52bef82804ebc4e7f915221c3b7e951",
+ "sha256:6841bb9cc24751dbdf94e7eefc4e6d70ec297952501954471299fd12ab67391c",
+ "sha256:68c1c846dd267cd7e293d3fc0bb238db0a744aa1f2e721e327598f00cb982098",
+ "sha256:703910aaa1dcd25a412f78a190fb7a352d9a64ee7d9a35566d786f3cc66ebf20",
+ "sha256:8002959146ed21959e3118c60c8e94ceac02eea15b691da6c62cff4787c63f7f",
+ "sha256:889eef049d38488b5b4646c48a831feed37c0fd44f3d83c05cff80f4baded145",
+ "sha256:8c76983c9ec3e4cf3a4c1d172ec4285332d9fb1c7194d724aff0c518437471ee",
+ "sha256:9cd9bd72f4a9743ef6f11f0f96784bd215a542e996db1717d4c2d3d03eb81a1b",
+ "sha256:a1a5517301dc8d56243a14253d231ec755b94486b4fff2ae68269bc941bb1f2e",
+ "sha256:a2b08aec2eacae868723136383d9eb84a33062a7a7ec5ec3bd2c423bd1355946",
+ "sha256:a8529a79233f3581a66984acd16bce52ab0163f6f77568dd69e9ee4956d2e1db",
+ "sha256:afbc81a3870739610a9f9a1197374d6a45892cf1933c90fc5617d39790991ed3",
+ "sha256:baeb5dd8b21c718580687f5b4efd03f8139b1c56239cdf6b9805c6946e80f268",
+ "sha256:db1d49b753e6e6c76585f21d09c7e9812176732baa9bddb64bc2fc6cd24d4179",
+ "sha256:e242ed419aeb2488e0f9ee6410a34917f0f8d62b3ae96aa3170d83bae75004e2",
+ "sha256:e36a8857be2c849e54009f1bee25e8c34fbc683fcd38c6c700af4cba5f8d57c2",
+ "sha256:e699232fd033ef0053efec2cba0a7505514f374ba7b18c732a77cb5304311ef9",
+ "sha256:eae3da9231d87980f8082d181c276a04f7a6fdac130cebd467390b96dd05f944",
+ "sha256:ee6814c94dbf1cae569302afef9dd29efafc52373e8770ded0db549a3b6e0c00",
+ "sha256:f01a87a015227d8af407161eb48222fc3c8b01661cdc841e2b86eee4f1a7a417"
+ ],
+ "index": "pypi",
+ "version": "==4.2.0.32"
+ },
+ "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"
+ },
+ "pysocks": {
+ "hashes": [
+ "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
+ "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
+ "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
+ ],
+ "index": "pypi",
+ "version": "==1.7.1"
+ },
+ "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": {}
+}
diff --git a/README.md b/README.md
index e8ef0da..19332b7 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,146 @@
-# (Forked) Reolink Python Api Client
-A Reolink Camera client written in Python.
+
Reolink Python Api Client
-_NB! for the original API client of this fork, go [here](https://github.com/ReolinkCameraAPI/reolink-python-api)_
+
+
+
+
+
+
+
-### Purpose
+---
-This repository's purpose is to deliver a complete API for the Reolink Camera's, (tested on RLC-522)
+A Reolink Camera client written in Python. This repository's purpose **(with Reolink's full support)** is to deliver a complete API for the Reolink Cameras,
+although they have a basic API document - it does not satisfy the need for extensive camera communication.
-### Installation
+Check out our documentation for more information on how to use the software at [https://reolink.oleaintueri.com](https://reolink.oleaintueri.com)
-```bash
-python3 -m pip install git+https://github.com/barretobrock/reolink-python-api.git
-```
+Other Supported Languages:
+ - Go: [reolinkapigo](https://github.com/ReolinkCameraAPI/reolinkapigo)
+
+### Join us on Discord
+
+ https://discord.gg/8z3fdAmZJP
+
+
+### Sponsorship
+
+
+
+[Oleaintueri](https://oleaintueri.com) is sponsoring the development and maintenance of these projects within their organisation.
+
+
+---
+
+### Get started
+
+Implement a "Camera" object by passing it an IP address, Username and Password. By instantiating the object, it will try retrieve a login token from the Reolink Camera. This token is necessary to interact with the Camera using other commands.
+
+See the `examples` directory.
+
+### Using the library as a Python Module
+
+Install the package via Pip
+
+ pip install reolink-api==0.0.5
+
+Install from GitHub
+
+ pip install git+https://github.com/ReolinkCameraAPI/reolink-python-api.git
+
+## Contributors
+
+---
+
+### Styling and Standards
+
+This project intends to stick with [PEP8](https://www.python.org/dev/peps/pep-0008/)
+
+### How can I become a contributor?
+
+#### Step 1
+
+Get the Restful API calls by looking through the HTTP Requests made in the camera's web UI. I use Google Chrome developer mode (ctr + shift + i) -> Network.
+
+#### Step 2
+
+Fork the repository and make your changes.
+
+#### Step 3
+
+Make a pull request.
+
+### API Requests Implementation Plan:
+
+Stream:
+- [X] Blocking RTSP stream
+- [X] Non-Blocking RTSP stream
+
+GET:
+- [X] Login
+- [X] Logout
+- [X] Display -> OSD
+- [X] Recording -> Encode (Clear and Fluent Stream)
+- [X] Recording -> Advance (Scheduling)
+- [X] Network -> General
+- [X] Network -> Advanced
+- [X] Network -> DDNS
+- [X] Network -> NTP
+- [X] Network -> E-mail
+- [X] Network -> FTP
+- [X] Network -> Push
+- [X] Network -> WIFI
+- [X] Alarm -> Motion
+- [X] System -> General
+- [X] System -> DST
+- [X] System -> Information
+- [ ] System -> Maintenance
+- [X] System -> Performance
+- [ ] System -> Reboot
+- [X] User -> Online User
+- [X] User -> Add User
+- [X] User -> Manage User
+- [X] Device -> HDD/SD Card
+- [ ] Zoom
+- [ ] Focus
+- [ ] Image (Brightness, Contrast, Saturation, Hue, Sharp, Mirror, Rotate)
+- [ ] Advanced Image (Anti-flicker, Exposure, White Balance, DayNight, Backlight, LED light, 3D-NR)
+- [X] Image Data -> "Snap" Frame from Video Stream
+
+SET:
+- [X] Display -> OSD
+- [X] Recording -> Encode (Clear and Fluent Stream)
+- [ ] Recording -> Advance (Scheduling)
+- [X] Network -> General
+- [X] Network -> Advanced
+- [ ] Network -> DDNS
+- [ ] Network -> NTP
+- [ ] Network -> E-mail
+- [ ] Network -> FTP
+- [ ] Network -> Push
+- [X] Network -> WIFI
+- [ ] Alarm -> Motion
+- [ ] System -> General
+- [ ] System -> DST
+- [X] System -> Reboot
+- [X] User -> Online User
+- [X] User -> Add User
+- [X] User -> Manage User
+- [X] Device -> HDD/SD Card (Format)
+- [x] PTZ
+- [x] Zoom
+- [x] Focus
+- [X] Image (Brightness, Contrast, Saturation, Hue, Sharp, Mirror, Rotate)
+- [X] Advanced Image (Anti-flicker, Exposure, White Balance, DayNight, Backlight, LED light, 3D-NR)
+
+### Supported Cameras
+
+Any Reolink camera that has a web UI should work. The other's requiring special Reolink clients
+do not work and is not supported here.
+
+- RLC-411WS
+- RLC-423
+- RLC-420-5MP
+- RLC-410-5MP
+- RLC-520
diff --git a/reolink_api/APIHandler.py b/reolink_api/APIHandler.py
deleted file mode 100644
index 093fd2c..0000000
--- a/reolink_api/APIHandler.py
+++ /dev/null
@@ -1,133 +0,0 @@
-import requests
-from reolink_api.resthandle import Request
-from .alarm import AlarmAPIMixin
-from .device import DeviceAPIMixin
-from .display import DisplayAPIMixin
-from .download import DownloadAPIMixin
-from .image import ImageAPIMixin
-from .motion import MotionAPIMixin
-from .network import NetworkAPIMixin
-from .ptz import PtzAPIMixin
-from .recording import RecordingAPIMixin
-from .system import SystemAPIMixin
-from .user import UserAPIMixin
-from .zoom import ZoomAPIMixin
-
-
-class APIHandler(AlarmAPIMixin,
- DeviceAPIMixin,
- DisplayAPIMixin,
- DownloadAPIMixin,
- ImageAPIMixin,
- MotionAPIMixin,
- NetworkAPIMixin,
- PtzAPIMixin,
- RecordingAPIMixin,
- SystemAPIMixin,
- UserAPIMixin,
- ZoomAPIMixin):
- """
- 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 logout(self) -> bool:
- """
- Logout of the camera
- :return: bool
- """
- try:
- data = [{"cmd": "Logout", "action": 0}]
- self._execute_command('Logout', data)
- # print(ret)
- return True
- except Exception as e:
- print("Error Logout\n", e)
- return False
-
- 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")
- if command == 'Download':
- # Special handling for downloading an mp4
- # Pop the filepath from data
- tgt_filepath = data[0].pop('filepath')
- # Apply the data to the params
- params.update(data[0])
- with requests.get(self.url, params=params, stream=True) as req:
- if req.status_code == 200:
- with open(tgt_filepath, 'wb') as f:
- f.write(req.content)
- return True
- else:
- print(f'Error received: {req.status_code}')
- return False
-
- else:
- response = Request.post(self.url, data=data, params=params)
- return response.json()
- except Exception as e:
- print(f"Command {command} failed: {e}")
- raise
diff --git a/reolink_api/Camera.py b/reolink_api/Camera.py
deleted file mode 100644
index 0a166f1..0000000
--- a/reolink_api/Camera.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from reolink_api import APIHandler
-
-
-class Camera(APIHandler):
-
- def __init__(self, ip: str, username: str = "admin", password: str = "", https: bool = False):
- """
- 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, pass
- # a proxy argument: proxy={"http": "socks5://127.0.0.1:8000"}
- APIHandler.__init__(self, ip, username, password, https=https)
-
- # Normal call without proxy:
- # APIHandler.__init__(self, ip, username, password)
-
- self.ip = ip
- self.username = username
- self.password = password
- super().login()
diff --git a/reolink_api/ConfigHandler.py b/reolink_api/ConfigHandler.py
deleted file mode 100644
index 37f255e..0000000
--- a/reolink_api/ConfigHandler.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import io
-import yaml
-
-
-class ConfigHandler:
- camera_settings = {}
-
- @staticmethod
- def load() -> yaml or None:
- try:
- stream = io.open("config.yml", 'r', encoding='utf8')
- data = yaml.safe_load(stream)
- return data
- except Exception as e:
- print("Config Property Error\n", e)
- return None
diff --git a/reolink_api/RtspClient.py b/reolink_api/RtspClient.py
deleted file mode 100644
index 655f95f..0000000
--- a/reolink_api/RtspClient.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import os
-from threading import ThreadError
-import cv2
-from reolink_api.util import threaded
-
-
-class RtspClient:
- """
- Inspiration from:
- - https://benhowell.github.io/guide/2015/03/09/opencv-and-web-cam-streaming
- - https://stackoverflow.com/questions/19846332/python-threading-inside-a-class
- - https://stackoverflow.com/questions/55828451/video-streaming-from-ip-camera-in-python-using-opencv-cv2-videocapture
- """
-
- def __init__(self, ip, username, password, port=554, profile="main", use_udp=True, callback=None, **kwargs):
- """
- RTSP client is used to retrieve frames from the camera in a stream
-
- :param ip: Camera IP
- :param username: Camera Username
- :param password: Camera User Password
- :param port: RTSP port
- :param profile: "main" or "sub"
- :param use_upd: True to use UDP, False to use TCP
- :param proxies: {"host": "localhost", "port": 8000}
- """
- self.capture = None
- self.thread_cancelled = False
- self.callback = callback
-
- capture_options = 'rtsp_transport;'
- self.ip = ip
- self.username = username
- self.password = password
- self.port = port
- self.proxy = kwargs.get("proxies")
- self.url = "rtsp://" + self.username + ":" + self.password + "@" + \
- self.ip + ":" + str(self.port) + "//h264Preview_01_" + profile
- if use_udp:
- capture_options = capture_options + 'udp'
- else:
- capture_options = capture_options + 'tcp'
-
- os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = capture_options
-
- # opens the stream capture, but does not retrieve any frames yet.
- self._open_video_capture()
-
- def _open_video_capture(self):
- # To CAP_FFMPEG or not To ?
- self.capture = cv2.VideoCapture(self.url, cv2.CAP_FFMPEG)
-
- def _stream_blocking(self):
- while True:
- try:
- if self.capture.isOpened():
- ret, frame = self.capture.read()
- if ret:
- yield frame
- else:
- print("stream closed")
- self.capture.release()
- return
- except Exception as e:
- print(e)
- self.capture.release()
- return
-
- @threaded
- def _stream_non_blocking(self):
- while not self.thread_cancelled:
- try:
- if self.capture.isOpened():
- ret, frame = self.capture.read()
- if ret:
- self.callback(frame)
- else:
- print("stream is closed")
- self.stop_stream()
- except ThreadError as e:
- print(e)
- self.stop_stream()
-
- def stop_stream(self):
- self.capture.release()
- self.thread_cancelled = True
-
- def open_stream(self):
- """
- Opens OpenCV Video stream and returns the result according to the OpenCV documentation
- https://docs.opencv.org/3.4/d8/dfe/classcv_1_1VideoCapture.html#a473055e77dd7faa4d26d686226b292c1
- """
-
- # Reset the capture object
- if self.capture is None or not self.capture.isOpened():
- self._open_video_capture()
-
- print("opening stream")
-
- if self.callback is None:
- return self._stream_blocking()
- else:
- # reset the thread status if the object was not re-created
- if not self.thread_cancelled:
- self.thread_cancelled = False
- return self._stream_non_blocking()