From 84ed8481e278175d77dc085d41197d143d5e84f9 Mon Sep 17 00:00:00 2001 From: Bobrock Date: Fri, 18 Dec 2020 08:48:56 -0600 Subject: [PATCH] Add back pipfiles, README --- Pipfile | 16 ++++ Pipfile.lock | 169 +++++++++++++++++++++++++++++++++++ README.md | 149 ++++++++++++++++++++++++++++-- reolink_api/APIHandler.py | 133 --------------------------- reolink_api/Camera.py | 24 ----- reolink_api/ConfigHandler.py | 16 ---- reolink_api/RtspClient.py | 106 ---------------------- 7 files changed, 325 insertions(+), 288 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 reolink_api/APIHandler.py delete mode 100644 reolink_api/Camera.py delete mode 100644 reolink_api/ConfigHandler.py delete mode 100644 reolink_api/RtspClient.py 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)_ +

+ Reolink Approval + GitHub + GitHub tag (latest SemVer) + PyPI + Discord +

-### 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()