From 9e53263b95e3d70cffca6d560478a61d01055ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Fri, 26 Jul 2019 17:38:38 +0200 Subject: [PATCH 01/39] Remove duplicate code to import MSS In fact, the old code was a copy of the `mss.mss()` factory. --- vidgear/gears/screengear.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index ff5c0b182..d03dbc7ad 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -77,17 +77,9 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): #intialize threaded queue mode self.threaded_queue_mode = True try: - # try import necessary system specific mss library - import platform - if platform.system() == 'Linux': - from mss.linux import MSS as mss - elif platform.system() == 'Windows': - from mss.windows import MSS as mss - elif platform.system() == 'Darwin': - from mss.darwin import MSS as mss - else: - from mss import mss - #import mss error handler + # import mss factory + from mss import mss + # import mss error handler from mss.exception import ScreenShotError except ImportError as error: # otherwise raise import error From d416e05c8b8b44e19df8a8638a5d21937c1c8ecc Mon Sep 17 00:00:00 2001 From: Abhishek Thakur Date: Tue, 30 Jul 2019 12:41:34 +0530 Subject: [PATCH 02/39] [Enhancement] Adding OSX environment support for Travis Cli tests (#42) [Enhancement] Adding OSX environment support for Travis Cli tests - Dropped Python 2.7 Support from CLI Tests - Reformatted travis.yml from scratch - Added native support for MacOS environment - Added necessary system-specific dependencies - Added Static binaries FFmpeg for MacOS - Fixed Redundant URL - Fixed Bugs - Updated Docs --- .travis.yml | 118 +++++++++++++----- appveyor.yml | 7 -- scripts/bash/prepare_dataset.sh | 50 ++++++-- .../test_benchmark_Videowriter.py | 6 +- .../test_benchmark_playback.py | 13 +- vidgear/tests/test_helper.py | 6 +- .../tests/videocapture_tests/test_camgear.py | 5 +- .../writer_tests/test_compression_mode.py | 6 +- .../writer_tests/test_non_compression_mode.py | 6 +- 9 files changed, 157 insertions(+), 60 deletions(-) diff --git a/.travis.yml b/.travis.yml index c880132d4..f01ef753f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,41 +1,101 @@ language: python -before_install: - - "sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y" - - "sudo apt-get update -q" - - "sudo apt-get install ffmpeg unzip wget -y" - - "sudo apt-get install dos2unix -y" - - "dos2unix scripts/bash/prepare_dataset.sh" - - "chmod +x scripts/bash/prepare_dataset.sh" - - "dos2unix scripts/bash/install_opencv.sh" - - "chmod +x scripts/bash/install_opencv.sh" +matrix: + include: + - os: osx + python: "3.5" + language: generic + osx_image: xcode11 + env: PYTHON=35 + - os: osx + python: "3.6" + language: generic + osx_image: xcode11 + env: PYTHON=36 + - os: osx + python: "3.7" + language: generic + osx_image: xcode11 + env: PYTHON=37 + - os: linux + python: "3.5" + language: python + cache: pip + - os: linux + python: "3.6" + language: python + cache: pip + - os: linux + dist: xenial + python: "3.7" + language: python + cache: pip + + + +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + brew install swig; + brew install ffmpeg; + brew install unzip; + curl -LO https://raw.githubusercontent.com/GiovanniBussi/macports-ci/master/macports-ci; + source ./macports-ci install; + yes | sudo port install python$PYTHON; + yes | sudo port install py$PYTHON-pip; + sudo port select --set python3 python$PYTHON; + sudo port select --set pip pip$PYTHON; + python3 --version; + pip --version; + export PATH=$PATH:$(python3 -c "import site; print(site.USER_BASE)")/bin; + chmod +x scripts/bash/prepare_dataset.sh; + fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y; + sudo apt-get update -q; + sudo apt-get install ffmpeg unzip wget -y; + sudo apt-get install dos2unix -y; + dos2unix scripts/bash/prepare_dataset.sh; + chmod +x scripts/bash/prepare_dataset.sh; + dos2unix scripts/bash/install_opencv.sh; + chmod +x scripts/bash/install_opencv.sh; + fi branches: only: - testing -cache: pip - install: - - "pip install --upgrade pip wheel" - - "pip install --upgrade numpy" - - "pip install ." - - "pip uninstall opencv-contrib-python -y" - - "pip install six" - - "pip install --upgrade pytest" - - "pip install --upgrade youtube-dl" - -dist: xenial - -python: - - "2.7" - - "3.5" - - "3.6" - - "3.7" + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + pip install --upgrade pip wheel; + pip install --upgrade numpy; + pip install .; + pip uninstall opencv-contrib-python -y; + pip install six; + pip install --upgrade pytest; + pip install --upgrade youtube-dl; + fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + pip install --upgrade --user pip wheel; + pip install --upgrade --user numpy; + pip install --user .; + pip install --user six; + pip install --upgrade --user pytest; + pip install --upgrade --user youtube-dl; + fi before_script: - - bash scripts/bash/install_opencv.sh - - bash scripts/bash/prepare_dataset.sh + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + bash scripts/bash/prepare_dataset.sh; + fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + bash scripts/bash/install_opencv.sh; + bash scripts/bash/prepare_dataset.sh; + fi script: - - python -m pytest -sv + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + travis_wait pytest -sv; + fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + python -m pytest -sv; + fi \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 96860c425..9b0ba0607 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,5 @@ environment: matrix: - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.x" - PYTHON_ARCH: "32" - - - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.x" - PYTHON_ARCH: "64" - PYTHON: "C:\\Python35" PYTHON_VERSION: "3.5.x" diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index e5961fe5e..fd31ae0d9 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -12,17 +12,37 @@ #The above copyright notice and this permission notice shall be included in all #copies or substantial portions of the Software. + +# Creating necessary directories mkdir -p $HOME/Downloads mkdir -p $HOME/Downloads/{FFmpeg_static,Test_videos} -cd $HOME/Downloads/FFmpeg_static - -OS_TYPE=$(uname) +# Acknowledging machine architecture MACHINE_BIT=$(uname -m) +# Acknowledging machine OS type +case $(uname | tr '[:upper:]' '[:lower:]') in +linux*) + OS_NAME=linux + ;; +darwin*) + OS_NAME=osx + ;; +msys*) + OS_NAME=windows + ;; +*) + OS_NAME=notset + ;; +esac + + #Download and Configure FFmpeg Static -if [ $OS_TYPE = "Linux" ]; then - +cd $HOME/Downloads/FFmpeg_static + +if [ $OS_NAME = "linux" ]; then + + echo "Downloading Linux Static FFmpeg Binaries..." if [ $MACHINE_BIT = "x86_64" ]; then curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg-release-amd64-static.tar.xz tar -xJf ffmpeg-release-amd64-static.tar.xz @@ -35,8 +55,9 @@ if [ $OS_TYPE = "Linux" ]; then mv ffmpeg* ffmpeg fi -else +elif [ $OS_NAME = "windows" ]; then + echo "Downloading Windows Static FFmpeg Binaries..." if [ $MACHINE_BIT = "x86_64" ]; then curl https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip -o ffmpeg-latest-win64-static.zip unzip -qq ffmpeg-latest-win64-static.zip @@ -48,14 +69,25 @@ else rm ffmpeg-latest-win32-static.zip mv ffmpeg-latest-win32-static ffmpeg fi + +else + + echo "Downloading MacOS64 Static FFmpeg Binary..." + curl -LO https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-latest-macos64-static.zip + unzip -qq ffmpeg-latest-macos64-static.zip + rm ffmpeg-latest-macos64-static.zip + mv ffmpeg-latest-macos64-static ffmpeg + fi +# Downloading Test Data cd $HOME/Downloads/Test_videos -# Download Test-Data + +echo "Downloading Test-Data..." curl http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 -o BigBuckBunny.mp4 -curl https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4 -o BigBuckBunny_4sec.mp4 +curl https://raw.githubusercontent.com/abhiTronix/Imbakup/master/Images/big_buck_bunny_720p_1mb.mp4 -o BigBuckBunny_4sec.mp4 curl http://jell.yfish.us/media/jellyfish-20-mbps-hd-hevc-10bit.mkv -o 20_mbps_hd_hevc_10bit.mkv curl http://jell.yfish.us/media/jellyfish-50-mbps-hd-h264.mkv -o 50_mbps_hd_h264.mkv curl http://jell.yfish.us/media/jellyfish-90-mbps-hd-hevc-10bit.mkv -o 90_mbps_hd_hevc_10bit.mkv curl http://jell.yfish.us/media/jellyfish-120-mbps-4k-uhd-h264.mkv -o 120_mbps_4k_uhd_h264.mkv - +echo "Done Downloading Test-Data!" \ No newline at end of file diff --git a/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py b/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py index 9de97aeac..6d2b4af43 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py @@ -23,7 +23,7 @@ =============================================== """ -import os +import os, platform import pytest from vidgear.gears import WriteGear from vidgear.gears import VideoGear @@ -45,8 +45,10 @@ def return_static_ffmpeg(): return FFmpeg static path """ path = '' - if os.name == 'nt': + if platform.system() == 'Windows': path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + elif platform.system() == 'Darwin': + path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) diff --git a/vidgear/tests/benchmark_tests/test_benchmark_playback.py b/vidgear/tests/benchmark_tests/test_benchmark_playback.py index 19adb2e74..482f296b6 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_playback.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_playback.py @@ -23,7 +23,7 @@ =============================================== """ -import os +import os, platform import pytest from vidgear.gears import CamGear from .fps import FPS @@ -64,7 +64,10 @@ def test_benchmark(level): """ Benchmarking low to extreme 4k video playback capabilities of VidGear """ - try: - playback(level) - except Exception as e: - print(e) + if platform.system() != 'Darwin': + try: + playback(level) + except Exception as e: + print(e) + else: + print("Skipping this test for macOS!") diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index dcbbf090f..c5071f5a9 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -23,7 +23,7 @@ =============================================== """ -import os, pytest, tempfile, shutil +import os, pytest, tempfile, shutil, platform from vidgear.gears.helper import download_ffmpeg_binaries from vidgear.gears.helper import validate_ffmpeg @@ -36,8 +36,10 @@ def return_static_ffmpeg(): return FFmpeg static path """ path = '' - if os.name == 'nt': + if platform.system() == 'Windows': path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + elif platform.system() == 'Darwin': + path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) diff --git a/vidgear/tests/videocapture_tests/test_camgear.py b/vidgear/tests/videocapture_tests/test_camgear.py index 21ffb5497..30de1a98a 100644 --- a/vidgear/tests/videocapture_tests/test_camgear.py +++ b/vidgear/tests/videocapture_tests/test_camgear.py @@ -25,6 +25,7 @@ import youtube_dl import cv2 +import platform import os, time import pytest import numpy as np @@ -99,7 +100,7 @@ def test_youtube_playback(): """ Testing Youtube Video Playback capabilities of VidGear """ - if os.name != 'nt': + if not platform.system() in ['Windows', 'Darwin']: Url = 'https://youtu.be/YqeW9_5kURI' result = True errored = False #keep watch if youtube streaming not successful @@ -129,7 +130,7 @@ def test_youtube_playback(): print('YouTube playback Test is skipped due to above error!') else: - print('YouTube playback Test is skipped due to bug with Appveyor on Windows builds!') + print('YouTube playback Test is skipped due to bug with opencv-python library builds on windows and macOS!') diff --git a/vidgear/tests/writer_tests/test_compression_mode.py b/vidgear/tests/writer_tests/test_compression_mode.py index 65bd33d51..0a6047b73 100644 --- a/vidgear/tests/writer_tests/test_compression_mode.py +++ b/vidgear/tests/writer_tests/test_compression_mode.py @@ -31,7 +31,7 @@ import pytest import cv2 import tempfile -import os +import os, platform import subprocess, re @@ -41,8 +41,10 @@ def return_static_ffmpeg(): return FFmpeg static path """ path = '' - if os.name == 'nt': + if platform.system() == 'Windows': path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + elif platform.system() == 'Darwin': + path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index 47c529ffb..2853032bb 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -28,7 +28,7 @@ from vidgear.gears.helper import check_output from six import string_types -import os +import os, platform import pytest import cv2 import tempfile @@ -40,8 +40,10 @@ def return_static_ffmpeg(): return FFmpeg static path """ path = '' - if os.name == 'nt': + if platform.system() == 'Windows': path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + elif platform.system() == 'Darwin': + path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) From 5d175e6f2e76946cdfa6d275e7498c2acf890156 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Thu, 1 Aug 2019 21:30:35 +0530 Subject: [PATCH 03/39] Enhancement: Introducing Multi-Server Compatibility and `PUB/SUB` messaging pattern support in NetGear - Implemented Robust Multi-Server Compatibility support in NetGear. - Implemented new Publish/Subscribe (`zmq.PUB/zmq.SUB`) patterns for seamless Live Streaming. - Upgraded NetGear ability to handle any number of servers. - Optimized overall real-time performance of NetGear. - Updated ScreenGear to use Threaded Queue Mode by default. - Removed redundant `THREADED_QUEUE_MODE` flag support from ScreenGear. - Fixed various bugs related to these implementations. --- changelog.md | 25 ++++ setup.py | 4 +- vidgear/gears/helper.py | 2 +- vidgear/gears/netgear.py | 225 ++++++++++++++++++++++++------------ vidgear/gears/screengear.py | 36 +++--- vidgear/version.py | 2 +- 6 files changed, 199 insertions(+), 95 deletions(-) diff --git a/changelog.md b/changelog.md index c7bfe5b5a..64eb14df8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,30 @@ # CHANGELOG +## VidGear 0.1.6-dev + +### New Features: + * Added VidGear's official native support for MacOS environment. + +### Updates/Improvements: + * Updated Travis CLI Tests with support for macOS environment + * Reformatted & implemented necessary changes and dependencies in `travis.yml`. + +### Breaking Updates / Improvements / Changes + * `Python 2.7` legacy support removed from CLI tests. + +### Fixes + * Fixed unreliable dataset video file URL(rehosted file on github.com) + * Remove duplicate code to import MSS(@BoboTiG) from NetGear + * Fixed various macOS environment bugs + +### Pull requests(PR) involved: + * PR #39 + * PR #42 + +:warning: PyPi Release does NOT contain Tests and Scripts! + +  + ## VidGear v0.1.5 ### New Features: diff --git a/setup.py b/setup.py index f23bb497a..40d1b9a97 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def test_opencv(): setup( name='vidgear', packages=['vidgear','vidgear.gears'], - version='0.1.5', + version='0.1.6-dev', description='Powerful python Video Processing library built with Multi-Threaded Gears(a.k.a APIs) each with a unique set of trailblazing features.', license='MIT License', author='abhiTronix', @@ -64,7 +64,7 @@ def test_opencv(): long_description_content_type="text/markdown", author_email='abhi.una12@gmail.com', url='https://github.com/abhiTronix/vidgear', - download_url='https://github.com/abhiTronix/vidgear/tarball/0.1.5', + download_url='https://github.com/abhiTronix/vidgear/tarball/0.1.6-dev', keywords=['opencv', 'multithreading', 'FFmpeg', 'picamera', 'mss', 'pyzmq', 'pafy', 'Video Processing', 'Video Stablization', 'Computer Vision'], classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 39a9e0ab0..17cfbc28b 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -28,6 +28,7 @@ # import the neccesary packages import os, sys import cv2 +import numpy as np from pkg_resources import parse_version @@ -70,7 +71,6 @@ def dict2Args(param_dict): return args - def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_download_path = '', logging = False): """ Validate the FFmpeg path/binaries and returns valid FFmpeg file executable location(also downloads static binaries on windows) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index c4367330a..424315e2d 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -28,7 +28,7 @@ from pkg_resources import parse_version import numpy as np import time - +import random try: @@ -112,14 +112,14 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log and enable threaded queue mode if logging: - print('Threaded Mode is enabled by default for NetGear.') + print('[LOG]: Threaded Mode is enabled by default for NetGear.') #import deque from collections import deque #define deque and assign it to global var self.queue = deque(maxlen=96) #max len 96 to check overflow - #define valid messaging pattern `0`: zmq.PAIR and `1`:(zmq.REQ,zmq.REP) - valid_messaging_patterns = {0:(zmq.PAIR,zmq.PAIR), 1:(zmq.REQ,zmq.REP)} + #define valid messaging pattern `0`: zmq.PAIR, `1`:(zmq.REQ,zmq.REP), and `1`:(zmq.SUB,zmq.PUB) + valid_messaging_patterns = {0:(zmq.PAIR,zmq.PAIR), 1:(zmq.REQ,zmq.REP), 2:(zmq.PUB,zmq.SUB)} # initialize messaging pattern msg_pattern = None @@ -134,28 +134,38 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r msg_pattern = valid_messaging_patterns[self.pattern] if logging: #log it - print('Wrong pattern, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') + print('[LOG]: Wrong pattern, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') #check whether user-defined messaging protocol is valid - if not (protocol is None) and protocol in ['tcp', 'upd', 'pgm', 'inproc', 'ipc']: + if protocol in ['tcp', 'upd', 'pgm', 'inproc', 'ipc']: pass else: # else default to `tcp` protocol protocol = 'tcp' if logging: #log it - print('Protocol is not valid or provided. Defaulting to `tcp` protocol! Kindly refer Docs for more Information.') + print('[LOG]: Protocol is not valid or provided. Defaulting to `tcp` protocol! Kindly refer Docs for more Information.') + #generate random device id + self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) self.msg_flag = 0 #handles connection flags self.msg_copy = False #handles whether to copy data self.msg_track = False #handles whether to track packets + + self.multiserver_mode = False + recv_filter = '' + try: #reformat dict - options = {k.strip(): v for k,v in options.items()} + options = {k.lower().strip(): v for k,v in options.items()} # apply attributes to source if specified and valid for key, value in options.items(): - if key == 'flag' and isinstance(value, str): + if key == 'multiserver_mode' and isinstance(value, bool) and self.pattern == 2: + self.multiserver_mode = value + elif key == 'filter' and isinstance(value, str): + recv_filter = value + elif key == 'flag' and isinstance(value, str): self.msg_flag = getattr(zmq, value) elif key == 'copy' and isinstance(value, bool): self.msg_copy = value @@ -166,9 +176,8 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r except Exception as e: # Catch if any error occurred if logging: - print(e) - - + print('[Exception]: '+ e) + # enable logging if specified self.logging = logging @@ -186,28 +195,40 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #check whether `receive_mode` is enabled by user if receive_mode: + # if does than define connection address and port if address is None: #define address - address = '*' - if port is None: #define port + address = 'localhost' if self.multiserver_mode else '*' + + if self.multiserver_mode: + if port is None or not isinstance(port, (tuple, list)): + raise ValueError('Incorrect port value! Kindly provide a list/tuple of ports at Receiver-end while Multi-Server mode is enabled. For more information refer VidGear docs.') + else: + print('[LOG]: Enabling Multi-Server Mode at PORTS: {}!'.format(port)) + self.port_buffer = [] + else: port = '5555' - if logging: - import random - #generate and log random device id for server-side debugging - self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) - print('This device ID is {}.'.format(self.id)) - try: # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[1]) - # bind socket to given protocol, address and port - self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) - # define socket receive timeout - self.msg_socket.setsockopt(zmq.LINGER, 0) - if logging: - #log it on success - print('Successfully Binded to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + + if self.multiserver_mode: + for pt in port: + # connect socket to given protocol, address and port + self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(pt)) + self.msg_socket.setsockopt(zmq.LINGER, 0) + # define socket options + self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) + else: + # bind socket to given protocol, address and port + self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) + # define socket options + if self.pattern == 2: + self.msg_socket.setsockopt_string(zmq.SUBSCRIBE,'') + else: + self.msg_socket.setsockopt(zmq.LINGER, 0) + except Exception as e: # otherwise raise value error raise ValueError('Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) @@ -216,39 +237,57 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.thread = Thread(target=self.update, args=()) self.thread.daemon = True self.thread.start() + if logging: - #log it - print('Multi-threaded Receive Mode is enabled Successfully!') + #log it + print('[LOG]: Successfully Binded to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') + print('[LOG]: This device Unique ID is {}.'.format(self.id)) + print('[LOG]: Receive Mode is activated successfully!') else: + #otherwise default to `Send Mode - # if does than define connection address and port` if address is None: #define address - address = 'localhost' - if port is None: #define port - port = '5555' + address = '*' if self.multiserver_mode else 'localhost' + + if self.multiserver_mode: + if port is None: + raise ValueError('Incorrect port value! Kindly provide a unique & valid port value at Server-end while Multi-Server mode is enabled. For more information refer VidGear docs.') + else: + print('[LOG]: Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) + self.port = port + else: + port = 5555 #define port + try: # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[0]) - if self.pattern == 1: - # if pattern is 1, define additional flags - self.msg_socket.REQ_RELAXED = True - self.msg_socket.REQ_CORRELATE = True - # bind socket to given protocol, address and port - self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) - # define socket receive timeout - self.msg_socket.setsockopt(zmq.LINGER, 0) - if logging: - #log it - print('Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + + if self.multiserver_mode: + # connect socket to given protocol, address and port + self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) + else: + if self.pattern == 1: + # if pattern is 1, define additional flags + self.msg_socket.REQ_RELAXED = True + self.msg_socket.REQ_CORRELATE = True + + # connect socket to given protocol, address and port + self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) + + # define socket options + self.msg_socket.setsockopt(zmq.LINGER, 0) + except Exception as e: # otherwise raise value error raise ValueError('Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) if logging: #log it - print('Send Mode is successfully activated and ready to send data!') - + print('[LOG]: Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + print('[LOG]: This device Unique ID is {}.'.format(self.id)) + print('[LOG]: Send Mode is successfully activated and ready to send data!') def update(self): @@ -259,6 +298,7 @@ def update(self): frame = None # keep looping infinitely until the thread is terminated while not self.exit_loop: + # check if global termination_flag is enabled if self.terminate: # check whether there is still frames in queue before breaking out @@ -274,21 +314,40 @@ def update(self): #stop iterating if overflowing occurs time.sleep(0.000001) continue - # extract json data out of socket msg_json = self.msg_socket.recv_json(flags=self.msg_flag) + # check if terminate_flag` is enabled in json if msg_json['terminate_flag']: - if self.pattern == 1: - # if pattern is 1, then send back server the info about termination - self.msg_socket.send_string('Termination received on port: {} !'.format(self.id)) - #assign values to global termination flag - self.terminate = msg_json['terminate_flag'] + if self.multiserver_mode: + self.port_buffer.remove(msg_json['port']) + if not self.port_buffer: + print('Termination signal received from all Servers!!!') + self.terminate = True + continue + else: + if self.pattern == 1: + # if pattern is 1, then send back server the info about termination + self.msg_socket.send_string('Termination signal received from server!') + #assign values to global termination flag + self.terminate = msg_json['terminate_flag'] + continue + + try: + assert int(msg_json['pattern']) == self.pattern + except (AssertionError) as e: + raise ValueError("Messaging pattern on the Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") + self.terminate = True continue + + # extract array from socket msg_data = self.msg_socket.recv(flags=self.msg_flag, copy=self.msg_copy, track=self.msg_track) - # send confirmation message to server for debugging - self.msg_socket.send_string('Data received on port: {} !'.format(self.id)) + + if self.pattern != 2: + # send confirmation message to server for debugging + self.msg_socket.send_string('Data received on device: {} !'.format(self.id)) + # recover frame from array buffer frame_buffer = np.frombuffer(msg_data, dtype=msg_json['dtype']) # reshape frame @@ -297,9 +356,14 @@ def update(self): if msg_json['message']: print(msg_json['message']) - # append recovered frame to queue - self.queue.append(frame) - + if self.multiserver_mode: + if not msg_json['port'] in self.port_buffer: + self.port_buffer.append(msg_json['port']) + # append recovered unique port and frame to queue + self.queue.append((msg_json['port'],frame)) + else: + # append recovered frame to queue + self.queue.append(frame) # finally properly close the socket self.msg_socket.close() @@ -353,22 +417,36 @@ def send(self, frame, message = None): else: #otherwise make it contiguous frame = np.ascontiguousarray(frame, dtype=frame.dtype) - # prepare the json dict and assign values - msg_dict = dict(terminate_flag = exit_flag, - message = message if not(message is None) else '', - dtype = str(frame.dtype), - shape = frame.shape) + + if self.multiserver_mode: + # prepare the json dict and assign values with unique port + msg_dict = dict(terminate_flag = exit_flag, + port = self.port, + pattern = str(self.pattern), + message = message if not(message is None) else '', + dtype = str(frame.dtype), + shape = frame.shape) + else: + # prepare the json dict and assign values + msg_dict = dict(terminate_flag = exit_flag, + message = message if not(message is None) else '', + pattern = self.pattern, + dtype = str(frame.dtype), + shape = frame.shape) + # send the json dict self.msg_socket.send_json(msg_dict, self.msg_flag|self.zmq.SNDMORE) # send the frame array with correct flags self.msg_socket.send(frame, flags = self.msg_flag, copy=self.msg_copy, track=self.msg_track) # wait for confirmation - if self.logging: - # log confirmation - print(self.msg_socket.recv()) - else: - # otherwise be quiet - self.msg_socket.recv() + + if self.pattern != 2: + if self.logging: + # log confirmation + print(self.msg_socket.recv()) + else: + # otherwise be quiet + self.msg_socket.recv() @@ -378,7 +456,7 @@ def close(self): """ if self.logging: #log it - print('\n Terminating various {} Processes \n'.format('Receive Mode' if self.receive_mode else 'Send Mode')) + print(' \n[LOG]: Terminating various {} Processes \n'.format('Receive Mode' if self.receive_mode else 'Send Mode')) # whether `receive_mode` is enabled or not if self.receive_mode: # indicate that process should be terminated @@ -391,12 +469,17 @@ def close(self): # wait until stream resources are released (producer thread might be still grabbing frame) if self.thread is not None: self.thread.join() + self.thread = None #properly handle thread exit else: # otherwise indicate that the thread should be terminated self.terminate = True - # send termination flag to client - term_dict = dict(terminate_flag = True) + if self.multiserver_mode: + # send termination flag to client + term_dict = dict(terminate_flag = True, port = self.port) + else: + # send termination flag to client + term_dict = dict(terminate_flag = True) self.msg_socket.send_json(term_dict) # properly close the socket self.msg_socket.close() \ No newline at end of file diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index d03dbc7ad..c9a23e54c 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -74,8 +74,10 @@ class ScreenGear: """ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): - #intialize threaded queue mode + + #intialize threaded queue mode by default self.threaded_queue_mode = True + try: # import mss factory from mss import mss @@ -92,26 +94,18 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): monitor_instance = self.mss_object.monitors[monitor] else: raise ValueError("`monitor` value cannot be negative, Read Docs!") - # Initiate User-Defined Threaded Queue Mode - if options: - if "THREADED_QUEUE_MODE" in options: - if isinstance(options["THREADED_QUEUE_MODE"],bool): - self.threaded_queue_mode = options["THREADED_QUEUE_MODE"] #assigsn special parameter to global variable - del options["THREADED_QUEUE_MODE"] #clean - #reformat option dict + + # Initialize Queue self.queue = None - #intialize deque - if self.threaded_queue_mode: - #import deque - from collections import deque - #define deque and assign it to global var - self.queue = deque(maxlen=96) #max len 96 to check overflow - #log it - if logging: - print('Enabling Threaded Queue Mode!') - else: - #otherwise disable it - self.threaded_queue_mode = False + + #import deque + from collections import deque + #define deque and assign it to global var + self.queue = deque(maxlen=96) #max len 96 to check overflow + #log it + if logging: + print('Enabling Threaded Queue Mode by default for ScreenGear!') + #intiate screen dimension handler screen_dims = {} #initializing colorspace variable @@ -126,6 +120,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): # Catch if any error occurred if logging: print(e) + # intialize mss capture instance self.mss_capture_instance = None try: @@ -144,6 +139,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): raise ValueError("ScreenShotError caught: Wrong dimensions passed to python-mss, Kindly Refer Docs!") if logging: print(self.mss_object.get_error_details()) + # enable logging if specified self.logging = logging # thread initialization diff --git a/vidgear/version.py b/vidgear/version.py index 9f090905f..35bf2e99a 100644 --- a/vidgear/version.py +++ b/vidgear/version.py @@ -1 +1 @@ -__version__ = '0.15.0' \ No newline at end of file +__version__ = '0.1.6-dev' \ No newline at end of file From 225e040af6cb6aa5a21e378cfb83b946ec512e0d Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sun, 4 Aug 2019 13:15:05 +0530 Subject: [PATCH 04/39] Code Documentation Updates - Updated Netgear Docs with new changes - Updated each API Logging format --- vidgear/gears/camgear.py | 12 +- vidgear/gears/helper.py | 16 +-- vidgear/gears/netgear.py | 104 +++++++++++------- vidgear/gears/pigear.py | 6 +- vidgear/gears/screengear.py | 6 +- vidgear/gears/stabilizer.py | 2 +- vidgear/gears/videogear.py | 2 +- vidgear/gears/writegear.py | 18 +-- vidgear/tests/test_helper.py | 4 +- .../tests/videocapture_tests/test_camgear.py | 10 +- .../writer_tests/test_compression_mode.py | 2 +- .../writer_tests/test_non_compression_mode.py | 2 +- 12 files changed, 104 insertions(+), 80 deletions(-) diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 5a1c4c3a0..6473fabc9 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -133,9 +133,9 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l if _source is None: _source = source_object.getbest("any", ftypestrict=False) if logging: - print('URL: {}'.format(url)) - print('Title: {}'.format(source_object.title)) - print('Extension: {}'.format(_source.extension)) + print('[LOG]: URL: {}'.format(url)) + print('[LOG]: Title: {}'.format(source_object.title)) + print('[LOG]: Extension: {}'.format(_source.extension)) source = _source.url except Exception as e: if logging: @@ -162,7 +162,7 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l self.queue = deque(maxlen=96) #max len 96 to check overflow #log it if logging: - print('Enabling Threaded Queue Mode for the current video source!') + print('[LOG]: Enabling Threaded Queue Mode for the current video source!') else: #otherwise disable it self.threaded_queue_mode = False @@ -289,13 +289,13 @@ def update(self): else: self.color_space = None if self.logging: - print('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None if self.logging: print(e) - print('Input Colorspace is not a valid Colorspace!') + print('[LOG]: Input Colorspace is not a valid Colorspace!') if not(color_frame is None): self.frame = color_frame diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 17cfbc28b..21604d534 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -93,7 +93,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa ffmpeg_download_path = tempfile.gettempdir() if logging: - print('FFmpeg Windows Download Path: {}'.format(ffmpeg_download_path)) + print('[LOG]: FFmpeg Windows Download Path: {}'.format(ffmpeg_download_path)) #download Binaries _path = download_ffmpeg_binaries(path = ffmpeg_download_path, os_windows = is_windows) @@ -104,7 +104,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa #log if any error occured if logging: print(e) - print('Error downloading FFmpeg binaries, Check your network and Try again!') + print('[LOG]: Error downloading FFmpeg binaries, Check your network and Try again!') return False if os.path.isfile(final_path): #check if valid FFmpeg file exist @@ -115,7 +115,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa else: #else return False if logging: - print('No valid FFmpeg executables found at Custom FFmpeg path!') + print('[LOG]: No valid FFmpeg executables found at Custom FFmpeg path!') return False else: #otherwise perform test for Unix @@ -130,14 +130,14 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa else: #else return False if logging: - print('No valid FFmpeg executables found at Custom FFmpeg path!') + print('[LOG]: No valid FFmpeg executables found at Custom FFmpeg path!') return False else: #otherwise assign ffmpeg binaries from system final_path += "ffmpeg" if logging: - print('Final FFmpeg Path: {}'.format(final_path)) + print('[LOG]: Final FFmpeg Path: {}'.format(final_path)) # Final Auto-Validation for FFmeg Binaries. returns final path if test is passed if validate_ffmpeg(final_path, logging = logging): @@ -210,13 +210,13 @@ def validate_ffmpeg(path, logging = False): version = firstline.split(b' ')[2].strip() if logging: #log if test are passed - print('FFmpeg validity Test Passed!') - print('Found valid FFmpeg Version: `{}` installed on this system'.format(version)) + print('[LOG]: FFmpeg validity Test Passed!') + print('[LOG]: Found valid FFmpeg Version: `{}` installed on this system'.format(version)) except Exception as e: #log if test are failed if logging: print(e) - print('FFmpeg validity Test Failed!') + print('[LOG]: FFmpeg validity Test Failed!') return False return True diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 424315e2d..19e9212db 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -51,20 +51,23 @@ class NetGear: This is achieved by implementing a high-level wrapper around PyZmQ python library that contains python bindings for ZeroMQ - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. - Furthermore, NetGear currently supports two ZeroMQ messaging patterns: i.e zmq.PAIR and zmq.REQ and zmq.REP and the supported - protocol are: 'tcp', 'upd', 'pgm', 'inproc', 'ipc'. + Furthermore, NetGear currently supports three ZeroMQ messaging patterns: i.e zmq.PAIR, zmq.REQ and zmq.REP,and zmq.PUB,zmq.SUB whereas + supported protocol are: 'tcp', 'upd', 'pgm', 'inproc', 'ipc'. - Threaded Queue Mode => Sequentially adds and releases frames to/from deque and handles overflow of this queue. It utilizes - deques that support thread-safe, memory efficient appends and pops from either side of the deque with approximately the - same O(1) performance in either direction. + Multi-Server Mode: This mode in NetGear API can robustly handle multiple servers at once through exclusive Publish/Subscribe (zmq.PUB/zmq.SUB) + messaging pattern for seamless Live Streaming across various device at the same time. Each device new server on network is + identied using its unique port address. Also, when all the connected servers on the network get disconnected, the client + itself automatically exits too. This mode can be activated through`multiserver_mode` option boolean attribute during + netgear initialization easily. :param address(string): sets the valid network address of the server/client. Network addresses unique identifiers across the network. Its default value of this parameter is based on mode it is working, 'localhost' for Send Mode and `*` for Receive Mode. - :param port(string): sets the valid network port of the server/client. A network port is a number that identifies one side + :param port(string/dict/list): sets the valid network port of the server/client. A network port is a number that identifies one side of a connection between two devices on network. It is used determine to which process or application - a message should be delivered. Its default value is `5555` . + a message should be delivered. In Multi-Server Mode a unique port number must required at each server, and a + list/tuple of port addresses of each connected server is required at clients end. :param protocol(string): sets the valid messaging protocol between server and client. A network protocol is a set of established rules that dictates how to format, transmit and receive data so computer network devices - from servers and @@ -81,6 +84,9 @@ class NetGear: interleaved or distributed to both the servers. socket zmq.REQ will block on send unless it has successfully received a reply back and socket zmq.REP will block on recv unless it has received a request. + 2. zmq.PUB,zmq.SUB -> Publish/Subscribe is another classic pattern where senders of messages, called publishers, + do not program the messages to be sent directly to specific receivers, called subscribers. + Messages are published without the knowledge of what or if any subscriber of that knowledge exists. Its default value is `0`(i.e zmq.PAIR). :param (boolean) receive_mode: set this flag to select the Netgear's Mode of operation. This basically activates `Receive Mode`(if True) and `Send Mode`(if False). @@ -112,7 +118,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log and enable threaded queue mode if logging: - print('[LOG]: Threaded Mode is enabled by default for NetGear.') + print('[LOG]:Threaded Mode is enabled by default for NetGear.') #import deque from collections import deque #define deque and assign it to global var @@ -134,7 +140,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r msg_pattern = valid_messaging_patterns[self.pattern] if logging: #log it - print('[LOG]: Wrong pattern, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') + print('[LOG]: Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') #check whether user-defined messaging protocol is valid if protocol in ['tcp', 'upd', 'pgm', 'inproc', 'ipc']: @@ -144,7 +150,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r protocol = 'tcp' if logging: #log it - print('[LOG]: Protocol is not valid or provided. Defaulting to `tcp` protocol! Kindly refer Docs for more Information.') + print('[LOG]:protocol is not valid or provided. Defaulting to `tcp` protocol!') #generate random device id self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) @@ -153,13 +159,13 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_copy = False #handles whether to copy data self.msg_track = False #handles whether to track packets - self.multiserver_mode = False - recv_filter = '' + self.multiserver_mode = False #handles multiserver_mode state + recv_filter = '' #user-defined filter to allow specific port/servers only in multiserver_mode try: #reformat dict options = {k.lower().strip(): v for k,v in options.items()} - # apply attributes to source if specified and valid + # assign values to global variables if specified and valid for key, value in options.items(): if key == 'multiserver_mode' and isinstance(value, bool) and self.pattern == 2: self.multiserver_mode = value @@ -196,17 +202,23 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #check whether `receive_mode` is enabled by user if receive_mode: - # if does than define connection address and port + # if does than define connection address if address is None: #define address address = 'localhost' if self.multiserver_mode else '*' + #check if multiserver_mode is enabled if self.multiserver_mode: + # check if unique server port address list/tuple is assigned or not in multiserver_mode if port is None or not isinstance(port, (tuple, list)): + # raise error if not raise ValueError('Incorrect port value! Kindly provide a list/tuple of ports at Receiver-end while Multi-Server mode is enabled. For more information refer VidGear docs.') else: + #otherwise log it print('[LOG]: Enabling Multi-Server Mode at PORTS: {}!'.format(port)) + #create port address buffer for keeping track of incoming server's port self.port_buffer = [] else: + # otherwise assign local port address port = '5555' try: @@ -214,23 +226,24 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_socket = self.msg_context.socket(msg_pattern[1]) if self.multiserver_mode: + #if multiserver_mode is enabled assign port addresses to zmq socket for pt in port: - # connect socket to given protocol, address and port + # connect socket to given server protocol, address and ports self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(pt)) self.msg_socket.setsockopt(zmq.LINGER, 0) # define socket options self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) else: - # bind socket to given protocol, address and port + # otherwise bind socket to given protocol, address and port normally self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) - # define socket options + # define exclusive socket options for patterns if self.pattern == 2: self.msg_socket.setsockopt_string(zmq.SUBSCRIBE,'') else: self.msg_socket.setsockopt(zmq.LINGER, 0) except Exception as e: - # otherwise raise value error + # otherwise raise value error if errored raise ValueError('Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) # initialize and start threading instance @@ -239,33 +252,38 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.thread.start() if logging: - #log it - print('[LOG]: Successfully Binded to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) - print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') - print('[LOG]: This device Unique ID is {}.'.format(self.id)) - print('[LOG]: Receive Mode is activated successfully!') + #log progress + print('[LOG]: Successfully Binded to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') + print('[LOG]: This device Unique ID is {}.'.format(self.id)) + print('[LOG]: Receive Mode is activated successfully!') else: - #otherwise default to `Send Mode + #otherwise default to `Send Mode` if address is None: #define address address = '*' if self.multiserver_mode else 'localhost' + #check if multiserver_mode is enabled if self.multiserver_mode: + # check if unique server port address is assigned or not in multiserver_mode if port is None: + #raise error is not raise ValueError('Incorrect port value! Kindly provide a unique & valid port value at Server-end while Multi-Server mode is enabled. For more information refer VidGear docs.') else: + #otherwise log it print('[LOG]: Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) + #assign value to global variable self.port = port else: - port = 5555 #define port + port = 5555 #define port normally try: # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[0]) if self.multiserver_mode: - # connect socket to given protocol, address and port + # connect socket to protocol, address and a unique port if multiserver_mode is activated self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) else: if self.pattern == 1: @@ -284,10 +302,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r raise ValueError('Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) if logging: - #log it - print('[LOG]: Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) - print('[LOG]: This device Unique ID is {}.'.format(self.id)) - print('[LOG]: Send Mode is successfully activated and ready to send data!') + #log progress + print('[LOG]: Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + print('[LOG]: This device Unique ID is {}.'.format(self.id)) + print('[LOG]: Send Mode is successfully activated and ready to send data!') def update(self): @@ -317,30 +335,34 @@ def update(self): # extract json data out of socket msg_json = self.msg_socket.recv_json(flags=self.msg_flag) - # check if terminate_flag` is enabled in json + # check if terminate_flag` received if msg_json['terminate_flag']: + #if multiserver_mode is enabled if self.multiserver_mode: + # check from which ports signal is received self.port_buffer.remove(msg_json['port']) + #if termination signal received from all servers then exit client. if not self.port_buffer: print('Termination signal received from all Servers!!!') - self.terminate = True + self.terminate = True #termination continue else: if self.pattern == 1: # if pattern is 1, then send back server the info about termination self.msg_socket.send_string('Termination signal received from server!') - #assign values to global termination flag + #termination self.terminate = msg_json['terminate_flag'] continue try: + #check if pattern is same at both server's and client's end. assert int(msg_json['pattern']) == self.pattern - except (AssertionError) as e: + except AssertionError as e: + #otherwise raise error and exit raise ValueError("Messaging pattern on the Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") self.terminate = True continue - # extract array from socket msg_data = self.msg_socket.recv(flags=self.msg_flag, copy=self.msg_copy, track=self.msg_track) @@ -418,8 +440,9 @@ def send(self, frame, message = None): #otherwise make it contiguous frame = np.ascontiguousarray(frame, dtype=frame.dtype) + #check if multiserver_mode is activated if self.multiserver_mode: - # prepare the json dict and assign values with unique port + # prepare the exclusive json dict and assign values with unique port msg_dict = dict(terminate_flag = exit_flag, port = self.port, pattern = str(self.pattern), @@ -427,7 +450,7 @@ def send(self, frame, message = None): dtype = str(frame.dtype), shape = frame.shape) else: - # prepare the json dict and assign values + # otherwise prepare normal json dict and assign values msg_dict = dict(terminate_flag = exit_flag, message = message if not(message is None) else '', pattern = self.pattern, @@ -472,13 +495,14 @@ def close(self): self.thread = None #properly handle thread exit else: - # otherwise indicate that the thread should be terminated + # otherwise in `send_mode`, inform client(s) that the termination is reached self.terminate = True + #check if multiserver_mode if self.multiserver_mode: - # send termination flag to client + # send termination flag to client with its unique port term_dict = dict(terminate_flag = True, port = self.port) else: - # send termination flag to client + # otherwise send termination flag to client term_dict = dict(terminate_flag = True) self.msg_socket.send_json(term_dict) # properly close the socket diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index 393569253..694c5a268 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -160,7 +160,7 @@ def update(self): # preparation for the next frame if stream is None: if self.logging: - print('The Camera Module is not working Properly!') + print('[LOG]: The Camera Module is not working Properly!') self.terminate = True if self.terminate: break @@ -177,14 +177,14 @@ def update(self): else: self.color_space = None if self.logging: - print('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None if self.logging: print(e) - print('Input Colorspace is not a valid Colorspace!') + print('[LOG]: Input Colorspace is not a valid Colorspace!') if not(color_frame is None): self.frame = color_frame diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index c9a23e54c..bbace59dc 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -104,7 +104,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): self.queue = deque(maxlen=96) #max len 96 to check overflow #log it if logging: - print('Enabling Threaded Queue Mode by default for ScreenGear!') + print('[LOG]: Enabling Threaded Queue Mode by default for ScreenGear!') #intiate screen dimension handler screen_dims = {} @@ -202,13 +202,13 @@ def update(self): else: self.color_space = None if self.logging: - print('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None if self.logging: print(e) - print('Input Colorspace is not a valid Colorspace!') + print('[LOG]: Input Colorspace is not a valid Colorspace!') if not(color_frame is None): self.frame = color_frame else: diff --git a/vidgear/gears/stabilizer.py b/vidgear/gears/stabilizer.py index ab17d39b4..f5a133f14 100644 --- a/vidgear/gears/stabilizer.py +++ b/vidgear/gears/stabilizer.py @@ -80,7 +80,7 @@ def __init__(self, smoothing_radius = 25, border_type = 'black', border_size = 0 else: #otherwise log if not if logging: - print('Invalid input border type!') + print('[LOG]: Invalid input border type!') self.border_mode = border_modes['black'] #reset to default mode # define normalized box filter diff --git a/vidgear/gears/videogear.py b/vidgear/gears/videogear.py index 78bac357a..ddfb6469c 100644 --- a/vidgear/gears/videogear.py +++ b/vidgear/gears/videogear.py @@ -105,7 +105,7 @@ def __init__(self, enablePiCamera = False, stabilize = False, source=0, y_tube = self.stabilizer_obj = Stabilizer(smoothing_radius = s_radius, border_type = border_type, border_size = border_size, logging = logging) #log info if logging: - print('Enabling Stablization Mode for the current video source!') + print('[LOG]: Enabling Stablization Mode for the current video source!') if enablePiCamera: # only import the pigear module only if required diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index ff1bbfb37..4de8c3d05 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -148,7 +148,7 @@ def __init__(self, output_filename = '', compression_mode = True , custom_ffmpeg if self.compression: if self.logging: - print('Compression Mode is enabled therefore checking for valid FFmpeg executables!') + print('[LOG]: Compression Mode is enabled therefore checking for valid FFmpeg executables!') print(self.output_parameters) # handles where to save the downloaded FFmpeg Static Binaries on Windows(if specified) @@ -169,12 +169,12 @@ def __init__(self, output_filename = '', compression_mode = True , custom_ffmpeg if actual_command: self.ffmpeg += actual_command #assign it to class variable if self.logging: - print('Found valid FFmpeg executables: `{}`'.format(self.ffmpeg)) + print('[LOG]: Found valid FFmpeg executables: `{}`'.format(self.ffmpeg)) else: #otherwise disable Compression Mode if self.logging and not self.os_windows: - print('Kindly install working FFmpeg or provide a valid custom FFmpeg Path') - print('Caution: Disabling Video Compression Mode since no valid FFmpeg executables found on this machine!') + print('[LOG]: Kindly install working FFmpeg or provide a valid custom FFmpeg Path') + print('[LOG]: Caution: Disabling Video Compression Mode since no valid FFmpeg executables found on this machine!') self.compression = False # compression mode disabled #validate this class has the access rights to specified directory or not @@ -184,10 +184,10 @@ def __init__(self, output_filename = '', compression_mode = True , custom_ffmpeg if self.compression and self.ffmpeg: self.DEVNULL = open(os.devnull, 'wb') if self.logging: - print('Compression Mode is configured properly!') + print('[LOG]: Compression Mode is configured properly!') else: if self.logging: - print('Compression Mode is disabled, Activating OpenCV In-built Writer!') + print('[LOG]: Compression Mode is disabled, Activating OpenCV In-built Writer!') @@ -214,7 +214,7 @@ def write(self, frame, rgb_mode = False): self.inputwidth = width self.inputchannels = channels if self.logging: - print('InputFrame => Height:{} Width:{} Channels:{}'.format(self.inputheight, self.inputwidth, self.inputchannels)) + print('[LOG]: InputFrame => Height:{} Width:{} Channels:{}'.format(self.inputheight, self.inputwidth, self.inputchannels)) #validate size of frame if height != self.inputheight or width != self.inputwidth: @@ -250,7 +250,7 @@ def write(self, frame, rgb_mode = False): assert self.process is not None if self.logging: # log OpenCV warning - print('Warning: RGBA and 16-bit grayscale video frames are not supported by OpenCV yet, switch to `compression_mode` to use them!') + print('[WARNING]: RGBA and 16-bit grayscale video frames are not supported by OpenCV yet, switch to `compression_mode` to use them!') #write the frame self.process.write(frame) @@ -388,7 +388,7 @@ def startCV_Process(self): if self.logging: #log values for debugging - print('FILE_PATH: {}, FOURCC = {}, FPS = {}, WIDTH = {}, HEIGHT = {}, BACKEND = {}'.format(self.out_file,FOURCC, FPS, WIDTH, HEIGHT, BACKEND)) + print('[LOG]: FILE_PATH: {}, FOURCC = {}, FPS = {}, WIDTH = {}, HEIGHT = {}, BACKEND = {}'.format(self.out_file,FOURCC, FPS, WIDTH, HEIGHT, BACKEND)) #start different process for with/without Backend. if BACKEND: diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index c5071f5a9..decf1404b 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -53,10 +53,10 @@ def test_ffmpeg_static_installation(): for root, dirs, files in os.walk(startpath): level = root.replace(startpath, '').count(os.sep) indent = ' ' * 4 * (level) - print('{}{}/'.format(indent, os.path.basename(root))) + print('[LOG]: {}{}/'.format(indent, os.path.basename(root))) subindent = ' ' * 4 * (level + 1) for f in files: - print('{}{}'.format(subindent, f)) + print('[LOG]: {}{}'.format(subindent, f)) diff --git a/vidgear/tests/videocapture_tests/test_camgear.py b/vidgear/tests/videocapture_tests/test_camgear.py index 30de1a98a..3750ad0c0 100644 --- a/vidgear/tests/videocapture_tests/test_camgear.py +++ b/vidgear/tests/videocapture_tests/test_camgear.py @@ -118,8 +118,8 @@ def test_youtube_playback(): if height == 0 or width == 0: fps = stream.framerate height,width = frame.shape[:2] - print('WIDTH: {} HEIGHT: {} FPS: {}'.format(true_video_param[0],true_video_param[1],true_video_param[2])) - print('WIDTH: {} HEIGHT: {} FPS: {}'.format(width,height,fps)) + print('[LOG]: WIDTH: {} HEIGHT: {} FPS: {}'.format(true_video_param[0],true_video_param[1],true_video_param[2])) + print('[LOG]: WIDTH: {} HEIGHT: {} FPS: {}'.format(width,height,fps)) except Exception as error: print(error) errored = True @@ -127,10 +127,10 @@ def test_youtube_playback(): if not errored: assert true_video_param[0] == width and true_video_param[1] == height and true_video_param[2] == fps else: - print('YouTube playback Test is skipped due to above error!') + print('[LOG]: YouTube playback Test is skipped due to above error!') else: - print('YouTube playback Test is skipped due to bug with opencv-python library builds on windows and macOS!') + print('[LOG]: YouTube playback Test is skipped due to bug with opencv-python library builds on windows and macOS!') @@ -151,7 +151,7 @@ def test_network_playback(): Output_data.append(frame) i+=1 output_stream.stop() - print('Output data shape:', np.array(Output_data).shape) + print('[LOG]: Output data shape:', np.array(Output_data).shape) except Exception as e: pytest.fail(str(e)) diff --git a/vidgear/tests/writer_tests/test_compression_mode.py b/vidgear/tests/writer_tests/test_compression_mode.py index 0a6047b73..b3cb22afb 100644 --- a/vidgear/tests/writer_tests/test_compression_mode.py +++ b/vidgear/tests/writer_tests/test_compression_mode.py @@ -120,7 +120,7 @@ def test_write(conversion): if result: if not isinstance(result, string_types): result = result.decode() - print('Result: {}'.format(result)) + print('[LOG]: Result: {}'.format(result)) for i in ["Error", "Invalid", "error", "invalid"]: assert not(i in result) os.remove(os.path.abspath('Output_tw.mp4')) diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index 2853032bb..f777b0b83 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -86,7 +86,7 @@ def test_write(conversion): if result: if not isinstance(result, string_types): result = result.decode() - print('Result: {}'.format(result)) + print('[LOG]: Result: {}'.format(result)) for i in ["Error", "Invalid", "error", "invalid"]: assert not(i in result) os.remove(os.path.abspath('Output_twc.avi')) From 8f7153cbd822da9a669cd292dc46ae4cdb8e457d Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 20 Aug 2019 16:06:29 +0530 Subject: [PATCH 05/39] Updates and Bug fixes for NetGear API - Bug Fix for issue #45 - Added Feature to send data in mult-server mode #44 --- vidgear/gears/netgear.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 19e9212db..82e679095 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -171,8 +171,8 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.multiserver_mode = value elif key == 'filter' and isinstance(value, str): recv_filter = value - elif key == 'flag' and isinstance(value, str): - self.msg_flag = getattr(zmq, value) + elif key == 'flag' and isinstance(value, int): + self.msg_flag = value elif key == 'copy' and isinstance(value, bool): self.msg_copy = value elif key == 'track' and isinstance(value, bool): @@ -374,18 +374,24 @@ def update(self): frame_buffer = np.frombuffer(msg_data, dtype=msg_json['dtype']) # reshape frame frame = frame_buffer.reshape(msg_json['shape']) - #extract if any message from server and display it - if msg_json['message']: - print(msg_json['message']) - if self.multiserver_mode: + if self.multiserver_mode: + # check if multiserver_mode + + #save the unique port addresses if not msg_json['port'] in self.port_buffer: self.port_buffer.append(msg_json['port']) - # append recovered unique port and frame to queue - self.queue.append((msg_json['port'],frame)) + + #extract if any message from server and display it + if msg_json['message']: + self.queue.append((msg_json['port'], msg_json['message'], frame)) + else: + # append recovered unique port and frame to queue + self.queue.append((msg_json['port'],frame)) else: # append recovered frame to queue self.queue.append(frame) + # finally properly close the socket self.msg_socket.close() @@ -453,7 +459,7 @@ def send(self, frame, message = None): # otherwise prepare normal json dict and assign values msg_dict = dict(terminate_flag = exit_flag, message = message if not(message is None) else '', - pattern = self.pattern, + pattern = str(self.pattern), dtype = str(frame.dtype), shape = frame.shape) From 816a92bdd012a43bea800189dec36491c1fa0496 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sun, 8 Sep 2019 22:29:29 +0530 Subject: [PATCH 06/39] [Enhancements] Introducing Smart & Secure ZMQ Authentication for NetGear API: - added exclusive `secure_mode` for enabling this feature - added support for powerful `Stonehouse` & `Ironhouse` ZMQ security mechanisms. - added smart auth certificates/key generation and validation - added additional dict params for enabling various tweaks. - fixed bugs related to this implementation --- vidgear/gears/helper.py | 105 ++++++++++++++++++++++++++++++++++++++- vidgear/gears/netgear.py | 62 +++++++++++++++++++++-- 2 files changed, 163 insertions(+), 4 deletions(-) diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 21604d534..7070aba17 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -250,4 +250,107 @@ def check_output(*args, **kwargs): error = sp.CalledProcessError(retcode, cmd) error.output = output raise error - return output \ No newline at end of file + return output + + +def generate_auth_certificates(path, overwrite = False): + """ Generate client and server CURVE ZMQ certificate files """ + + import shutil, errno + import zmq.auth + + if (os.path.basename(path) != ".vidgear"): + path = os.path.join(path,".vidgear") + + keys_dir = os.path.join(path, 'keys') + try: + os.makedirs(keys_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + public_keys_dir = os.path.join(keys_dir, 'public_keys') + secret_keys_dir = os.path.join(keys_dir, 'private_keys') + + if overwrite: + + for d in [public_keys_dir, secret_keys_dir]: + if os.path.exists(d): + shutil.rmtree(d) + os.mkdir(d) + + # create new keys in certificates dir + server_public_file, server_secret_file = zmq.auth.create_certificates(keys_dir, "server") + client_public_file, client_secret_file = zmq.auth.create_certificates(keys_dir, "client") + + # move public keys to appropriate directory + for key_file in os.listdir(keys_dir): + if key_file.endswith(".key"): + shutil.move(os.path.join(keys_dir, key_file), public_keys_dir) + elif key_file.endswith(".key_secret"): + shutil.move(os.path.join(keys_dir, key_file), public_keys_dir) + else: + os.remove(key_file) + + else: + + status_public_keys = validate_auth_keys(public_keys_dir, '.key') + status_private_keys = validate_auth_keys(secret_keys_dir, '.key_secret') + + if status_private_keys and status_public_keys: + return keys_dir + + if not(status_public_keys): + try: + os.makedirs(public_keys_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + if not(status_private_keys): + try: + os.makedirs(secret_keys_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + # create new keys in certificates dir + server_public_file, server_secret_file = zmq.auth.create_certificates(keys_dir, "server") + client_public_file, client_secret_file = zmq.auth.create_certificates(keys_dir, "client") + + + # move public keys to appropriate directory + for key_file in os.listdir(keys_dir): + if key_file.endswith(".key") and not(status_public_keys): + shutil.move(os.path.join(keys_dir, key_file), os.path.join(public_keys_dir, '.')) + elif key_file.endswith(".key_secret") and not(status_private_keys): + shutil.move(os.path.join(keys_dir, key_file), os.path.join(secret_keys_dir, '.')) + else: + redundant_key = os.path.join(keys_dir,key_file) + if os.path.isfile(redundant_key): + os.remove(redundant_key) + + status_public_keys = validate_auth_keys(public_keys_dir, '.key') + status_private_keys = validate_auth_keys(secret_keys_dir, '.key_secret') + + if not(status_private_keys) or not(status_public_keys): + raise RuntimeError('[Error]: Unable to create ZMQ all authentication certificates at `{}`!'.format(keys_dir)) + + return keys_dir + + +def validate_auth_keys(path, extension): + + if not(os.path.exists(path)): return False + if not(os.listdir(path)): return False + + keys_buffer = [] + + for key_file in os.listdir(path): + key = os.path.splitext(key_file) + if key and (key[0] in ['server','client']) and (key[1] == extension): + keys_buffer.append(key_file) + + if(len(keys_buffer) == 1): os.remove(os.path.join(path,keys_buffer[0])) + + return True if(len(keys_buffer) == 2) else False \ No newline at end of file diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 82e679095..3887b18a8 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -26,8 +26,11 @@ # import the necessary packages from threading import Thread from pkg_resources import parse_version +from .helper import check_python_version +from .helper import generate_auth_certificates import numpy as np import time +import os import random @@ -118,7 +121,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log and enable threaded queue mode if logging: - print('[LOG]:Threaded Mode is enabled by default for NetGear.') + print('[LOG]: Threaded Mode is enabled by default for NetGear.') #import deque from collections import deque #define deque and assign it to global var @@ -150,7 +153,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r protocol = 'tcp' if logging: #log it - print('[LOG]:protocol is not valid or provided. Defaulting to `tcp` protocol!') + print('[LOG]: protocol is not valid or provided. Defaulting to `tcp` protocol!') #generate random device id self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) @@ -161,16 +164,41 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.multiserver_mode = False #handles multiserver_mode state recv_filter = '' #user-defined filter to allow specific port/servers only in multiserver_mode + + valid_security_mech = {0:'Grasslands', 1:'Stonehouse', 2:'Ironhouse'} + self.secure_mode = 0 + self.auth_cert_location = '' + overwrite_cert = False + custom_cert_location = '' try: #reformat dict options = {k.lower().strip(): v for k,v in options.items()} # assign values to global variables if specified and valid for key, value in options.items(): + if key == 'multiserver_mode' and isinstance(value, bool) and self.pattern == 2: self.multiserver_mode = value elif key == 'filter' and isinstance(value, str): recv_filter = value + + elif key == 'secure_mode' and isinstance(value,int) and (value in valid_security_mech): + try: + assert check_python_version() >= 3,"[ERROR]: ZMQ Security feature is not available with python version < 3.0." + assert zmq.zmq_version_info() >= (4,0), "[ERROR]: ZMQ Security feature is not supported in libzmq version < 4.0." + self.secure_mode = value + except AssertionError as e: + print(e) + elif key == 'custom_cert_location' and isinstance(value,str): + try: + assert os.access(value, os.W_OK), "[ERROR]: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(value) + assert not(os.path.isfile(value)), "[ERROR]: `custom_cert_location` value must be the path to a directory and not to a file!" + custom_cert_location = os.path.abspath(value) + except AssertionError as e: + print(e) + elif key == 'overwrite_cert' and isinstance(value,bool): + overwrite_cert = value + elif key == 'flag' and isinstance(value, int): self.msg_flag = value elif key == 'copy' and isinstance(value, bool): @@ -182,7 +210,35 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r except Exception as e: # Catch if any error occurred if logging: - print('[Exception]: '+ e) + print(e) + + + if self.secure_mode: + + if logging and overwrite_cert: print('[WARNING]: Overwriting ZMQ Authentication certificates over previous ones!') + + try: + if custom_cert_location: + if os.path.isdir(custom_cert_location): + self.auth_cert_location = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) + else: + raise ValueError("[ERROR]: Invalid `custom_cert_location` value!") + else: + from os.path import expanduser + self.auth_cert_location = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) + + if logging: + print('[LOG]: Successfully enabled ZMQ Security Mechanism: `{}` for this connection.'.format(valid_security_mech[self.secure_mode])) + print('[LOG]: `{}` is the default location for storing ZMQ authentication certificates/keys.'.format(self.auth_cert_location)) + + except Exception as e: + # Catch if any error occurred + print(e) + self.secure_mode = 0 + print('[WARNING]: ZMQ Security Mechanism is disabled for this connection!') + else: + if logging: print('[LOG]: ZMQ Security Mechanism is disabled for this connection!') + # enable logging if specified self.logging = logging From 3d65344a303d9e2a52cdf691f8a43e7aec80dd76 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 9 Sep 2019 09:54:10 +0530 Subject: [PATCH 07/39] Hot fix: added support for screen casting from all monitors --- vidgear/gears/screengear.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index bbace59dc..7d4cbd9e0 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -90,7 +90,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): self.mss_object = mss() # create monitor instance for the user-defined monitor monitor_instance = None - if (monitor > 0): + if (monitor >= 0): monitor_instance = self.mss_object.monitors[monitor] else: raise ValueError("`monitor` value cannot be negative, Read Docs!") From c8451a123e0ad6147723f6f66934c02b21018274 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 9 Sep 2019 18:59:16 +0530 Subject: [PATCH 08/39] Bug Fixes and Enhancements: - implemented `IronHouse` ZMQ security mechanism - intially tested & validated this security mechanism for both Server & Client end. - added various related methods for different modes and patterns. - fixed important bugs and typos --- vidgear/gears/helper.py | 11 +++--- vidgear/gears/netgear.py | 84 ++++++++++++++++++++++++++++++++++------ 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 7070aba17..0886d0e48 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -288,17 +288,18 @@ def generate_auth_certificates(path, overwrite = False): if key_file.endswith(".key"): shutil.move(os.path.join(keys_dir, key_file), public_keys_dir) elif key_file.endswith(".key_secret"): - shutil.move(os.path.join(keys_dir, key_file), public_keys_dir) + shutil.move(os.path.join(keys_dir, key_file), secret_keys_dir) else: - os.remove(key_file) - + redundant_key = os.path.join(keys_dir,key_file) + if os.path.isfile(redundant_key): + os.remove(redundant_key) else: status_public_keys = validate_auth_keys(public_keys_dir, '.key') status_private_keys = validate_auth_keys(secret_keys_dir, '.key_secret') if status_private_keys and status_public_keys: - return keys_dir + return (keys_dir, secret_keys_dir, public_keys_dir) if not(status_public_keys): try: @@ -336,7 +337,7 @@ def generate_auth_certificates(path, overwrite = False): if not(status_private_keys) or not(status_public_keys): raise RuntimeError('[Error]: Unable to create ZMQ all authentication certificates at `{}`!'.format(keys_dir)) - return keys_dir + return (keys_dir, secret_keys_dir, public_keys_dir) def validate_auth_keys(path, extension): diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 3887b18a8..0ae76e3a0 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -121,7 +121,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log and enable threaded queue mode if logging: - print('[LOG]: Threaded Mode is enabled by default for NetGear.') + print('[LOG]: Threaded Queue Mode is enabled by default for NetGear.') #import deque from collections import deque #define deque and assign it to global var @@ -165,9 +165,11 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.multiserver_mode = False #handles multiserver_mode state recv_filter = '' #user-defined filter to allow specific port/servers only in multiserver_mode - valid_security_mech = {0:'Grasslands', 1:'Stonehouse', 2:'Ironhouse'} + valid_security_mech = {0:'Grasslands', 1:'StoneHouse', 2:'IronHouse'} self.secure_mode = 0 - self.auth_cert_location = '' + auth_cert_dir = '' + self.auth_publickeys_dir = '' + self.auth_secretkeys_dir = '' overwrite_cert = False custom_cert_location = '' @@ -215,21 +217,24 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if self.secure_mode: + #import libraries + import zmq.auth + from zmq.auth.thread import ThreadAuthenticator + if logging and overwrite_cert: print('[WARNING]: Overwriting ZMQ Authentication certificates over previous ones!') try: if custom_cert_location: if os.path.isdir(custom_cert_location): - self.auth_cert_location = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) + (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) else: raise ValueError("[ERROR]: Invalid `custom_cert_location` value!") else: from os.path import expanduser - self.auth_cert_location = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) + (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) if logging: - print('[LOG]: Successfully enabled ZMQ Security Mechanism: `{}` for this connection.'.format(valid_security_mech[self.secure_mode])) - print('[LOG]: `{}` is the default location for storing ZMQ authentication certificates/keys.'.format(self.auth_cert_location)) + print('[LOG]: `{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) except Exception as e: # Catch if any error occurred @@ -252,8 +257,9 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # initialize and assign receive mode to global variable self.receive_mode = receive_mode - # define messaging context - self.msg_context = zmq.Context() + # define messaging context instance + self.msg_context = zmq.Context.instance() # TODO changed + #check whether `receive_mode` is enabled by user if receive_mode: @@ -278,12 +284,30 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r port = '5555' try: + if self.secure_mode == 2 and not(self.multiserver_mode): + # Start an authenticator for this context. + auth = ThreadAuthenticator(self.msg_context) + auth.start() + auth.allow(str(address)) + # Tell authenticator to use the certificate in a directory + auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[1]) if self.multiserver_mode: #if multiserver_mode is enabled assign port addresses to zmq socket for pt in port: + if self.secure_mode == 2: #IronHouse security mechanism + client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + self.msg_socket.curve_secretkey = client_secret + self.msg_socket.curve_publickey = client_public + + server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") + server_public, _ = zmq.auth.load_certificate(server_public_file) + # The client must know the server's public key to make a CURVE connection. + self.msg_socket.curve_serverkey = server_public # connect socket to given server protocol, address and ports self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(pt)) self.msg_socket.setsockopt(zmq.LINGER, 0) @@ -291,6 +315,14 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) else: # otherwise bind socket to given protocol, address and port normally + + if self.secure_mode == 2: #IronHouse security mechanism + server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") + server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + self.msg_socket.curve_secretkey = server_secret + self.msg_socket.curve_publickey = server_public + self.msg_socket.curve_server = True # must come before bind + self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) # define exclusive socket options for patterns if self.pattern == 2: @@ -300,7 +332,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r except Exception as e: # otherwise raise value error if errored - raise ValueError('Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + if self.multiserver_mode: + raise ValueError('[ERROR]: Multi-Server Mode, failed to connect to ports: {} with pattern: {}! Kindly recheck all parameters.'.format( str(port), pattern)) + else: + raise ValueError('[ERROR]: Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) # initialize and start threading instance self.thread = Thread(target=self.update, args=()) @@ -313,6 +348,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') print('[LOG]: This device Unique ID is {}.'.format(self.id)) print('[LOG]: Receive Mode is activated successfully!') + if self.secure_mode: print('[LOG]: Successfully enabled ZMQ Security Mechanism: `{}` for this connection.'.format(valid_security_mech[self.secure_mode])) else: #otherwise default to `Send Mode` @@ -335,10 +371,24 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r port = 5555 #define port normally try: + if self.secure_mode == 2 and self.multiserver_mode: + # Start an authenticator for this context. + auth = ThreadAuthenticator(self.msg_context) + auth.start() + auth.allow(str(address)) + # Tell authenticator to use the certificate in a directory + auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[0]) if self.multiserver_mode: + if self.secure_mode == 2: #IronHouse security mechanism + server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") + server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + self.msg_socket.curve_secretkey = server_secret + self.msg_socket.curve_publickey = server_public + self.msg_socket.curve_server = True # must come before bind # connect socket to protocol, address and a unique port if multiserver_mode is activated self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) else: @@ -347,6 +397,18 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_socket.REQ_RELAXED = True self.msg_socket.REQ_CORRELATE = True + if self.secure_mode == 2: #IronHouse security mechanism + client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + self.msg_socket.curve_secretkey = client_secret + self.msg_socket.curve_publickey = client_public + + server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") + server_public, _ = zmq.auth.load_certificate(server_public_file) + + # The client must know the server's public key to make a CURVE connection. + self.msg_socket.curve_serverkey = server_public + # connect socket to given protocol, address and port self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) @@ -362,7 +424,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r print('[LOG]: Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) print('[LOG]: This device Unique ID is {}.'.format(self.id)) print('[LOG]: Send Mode is successfully activated and ready to send data!') - + if self.secure_mode: print('[LOG]: Successfully enabled ZMQ Security Mechanism: `{}` for this connection.'.format(valid_security_mech[self.secure_mode])) def update(self): """ From 2551989a902076df6af4b39a367ae2f9ac4fea95 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 10 Sep 2019 23:08:50 +0530 Subject: [PATCH 09/39] Enhancements pt-2: - implemented new `StoneHouse` ZMQ security mechanism - intially tested & validated this security mechanism for both Server & Client end. - added various methods for different modes and patterns. --- vidgear/gears/netgear.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 0ae76e3a0..0d732d3c2 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -284,13 +284,17 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r port = '5555' try: - if self.secure_mode == 2 and not(self.multiserver_mode): + if self.secure_mode > 0 and not(self.multiserver_mode): # Start an authenticator for this context. auth = ThreadAuthenticator(self.msg_context) auth.start() auth.allow(str(address)) # Tell authenticator to use the certificate in a directory - auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + if self.secure_mode == 2: + auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + else: + # Tell the authenticator how to handle CURVE requests + auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[1]) @@ -298,7 +302,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if self.multiserver_mode: #if multiserver_mode is enabled assign port addresses to zmq socket for pt in port: - if self.secure_mode == 2: #IronHouse security mechanism + if self.secure_mode > 0: client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") client_public, client_secret = zmq.auth.load_certificate(client_secret_file) self.msg_socket.curve_secretkey = client_secret @@ -316,7 +320,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r else: # otherwise bind socket to given protocol, address and port normally - if self.secure_mode == 2: #IronHouse security mechanism + if self.secure_mode > 0: server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") server_public, server_secret = zmq.auth.load_certificate(server_secret_file) self.msg_socket.curve_secretkey = server_secret @@ -371,19 +375,23 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r port = 5555 #define port normally try: - if self.secure_mode == 2 and self.multiserver_mode: + if self.secure_mode > 0 and self.multiserver_mode: # Start an authenticator for this context. auth = ThreadAuthenticator(self.msg_context) auth.start() auth.allow(str(address)) # Tell authenticator to use the certificate in a directory - auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + if self.secure_mode == 2: + auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + else: + # Tell the authenticator how to handle CURVE requests + auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[0]) if self.multiserver_mode: - if self.secure_mode == 2: #IronHouse security mechanism + if self.secure_mode > 0: server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") server_public, server_secret = zmq.auth.load_certificate(server_secret_file) self.msg_socket.curve_secretkey = server_secret @@ -397,7 +405,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_socket.REQ_RELAXED = True self.msg_socket.REQ_CORRELATE = True - if self.secure_mode == 2: #IronHouse security mechanism + if self.secure_mode > 0: client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") client_public, client_secret = zmq.auth.load_certificate(client_secret_file) self.msg_socket.curve_secretkey = client_secret From b0b51e694da687821408b03ab539d70e5e9d8f92 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Thu, 12 Sep 2019 12:19:51 +0530 Subject: [PATCH 10/39] NetGear API enhancements pt-3: - Reformatted and beautify code with proper indentation - Fixed Bugs and typos - Added proper Code Documentation --- vidgear/gears/helper.py | 55 +++++++++--- vidgear/gears/netgear.py | 178 +++++++++++++++++++++++++-------------- 2 files changed, 155 insertions(+), 78 deletions(-) diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 0886d0e48..c7ff58280 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -25,7 +25,7 @@ # Contains all the support functions/modules required by Vidgear -# import the neccesary packages +# import the necessary packages import os, sys import cv2 import numpy as np @@ -101,7 +101,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa final_path += _path except Exception as e: - #log if any error occured + #log if any error occurred if logging: print(e) print('[LOG]: Error downloading FFmpeg binaries, Check your network and Try again!') @@ -242,7 +242,7 @@ def check_output(*args, **kwargs): #close the process if closeNULL: DEVNULL.close() - #if error occured raise error + #if error occurred raise error if retcode: cmd = kwargs.get("args") if cmd is None: @@ -253,15 +253,22 @@ def check_output(*args, **kwargs): return output + def generate_auth_certificates(path, overwrite = False): - """ Generate client and server CURVE ZMQ certificate files """ + """ + Auto-Generates and handles valid client and server CURVE ZMQ keys/certificates + """ + + #import necessary libs import shutil, errno import zmq.auth + #check if path corresponds to vidgear only if (os.path.basename(path) != ".vidgear"): path = os.path.join(path,".vidgear") + #generate keys dir keys_dir = os.path.join(path, 'keys') try: os.makedirs(keys_dir) @@ -269,38 +276,43 @@ def generate_auth_certificates(path, overwrite = False): if e.errno != errno.EEXIST: raise + #generate separate public and private key dirs public_keys_dir = os.path.join(keys_dir, 'public_keys') secret_keys_dir = os.path.join(keys_dir, 'private_keys') + #check if overwriting is allowed if overwrite: - + #delete previous certificates for d in [public_keys_dir, secret_keys_dir]: if os.path.exists(d): shutil.rmtree(d) os.mkdir(d) - # create new keys in certificates dir + # generate new keys server_public_file, server_secret_file = zmq.auth.create_certificates(keys_dir, "server") client_public_file, client_secret_file = zmq.auth.create_certificates(keys_dir, "client") - # move public keys to appropriate directory + # move keys to their appropriate directory respectively for key_file in os.listdir(keys_dir): if key_file.endswith(".key"): shutil.move(os.path.join(keys_dir, key_file), public_keys_dir) elif key_file.endswith(".key_secret"): shutil.move(os.path.join(keys_dir, key_file), secret_keys_dir) else: + # clean redundant keys if present redundant_key = os.path.join(keys_dir,key_file) if os.path.isfile(redundant_key): os.remove(redundant_key) else: - + # otherwise validate available keys status_public_keys = validate_auth_keys(public_keys_dir, '.key') status_private_keys = validate_auth_keys(secret_keys_dir, '.key_secret') + # check if all valid keys are found if status_private_keys and status_public_keys: return (keys_dir, secret_keys_dir, public_keys_dir) + # check if valid public keys are found if not(status_public_keys): try: os.makedirs(public_keys_dir) @@ -308,6 +320,7 @@ def generate_auth_certificates(path, overwrite = False): if e.errno != errno.EEXIST: raise + # check if valid private keys are found if not(status_private_keys): try: os.makedirs(secret_keys_dir) @@ -315,43 +328,57 @@ def generate_auth_certificates(path, overwrite = False): if e.errno != errno.EEXIST: raise - # create new keys in certificates dir + # generate new keys server_public_file, server_secret_file = zmq.auth.create_certificates(keys_dir, "server") client_public_file, client_secret_file = zmq.auth.create_certificates(keys_dir, "client") - - # move public keys to appropriate directory + # move keys to their appropriate directory respectively for key_file in os.listdir(keys_dir): if key_file.endswith(".key") and not(status_public_keys): shutil.move(os.path.join(keys_dir, key_file), os.path.join(public_keys_dir, '.')) elif key_file.endswith(".key_secret") and not(status_private_keys): shutil.move(os.path.join(keys_dir, key_file), os.path.join(secret_keys_dir, '.')) else: + # clean redundant keys if present redundant_key = os.path.join(keys_dir,key_file) if os.path.isfile(redundant_key): os.remove(redundant_key) + # validate newly generated keys status_public_keys = validate_auth_keys(public_keys_dir, '.key') status_private_keys = validate_auth_keys(secret_keys_dir, '.key_secret') + # raise error is validation test fails if not(status_private_keys) or not(status_public_keys): - raise RuntimeError('[Error]: Unable to create ZMQ all authentication certificates at `{}`!'.format(keys_dir)) + raise RuntimeError('[Error]: Unable to generate valid ZMQ authentication certificates at `{}`!'.format(keys_dir)) + # finally return valid key paths return (keys_dir, secret_keys_dir, public_keys_dir) + def validate_auth_keys(path, extension): + """ + validates and maintains ZMQ Auth Keys/Certificates + """ + #check for valid path if not(os.path.exists(path)): return False + + #check if directory empty if not(os.listdir(path)): return False - keys_buffer = [] + keys_buffer = [] #stores auth-keys + # loop over auth-keys for key_file in os.listdir(path): key = os.path.splitext(key_file) + #check if valid key is generated if key and (key[0] in ['server','client']) and (key[1] == extension): - keys_buffer.append(key_file) + keys_buffer.append(key_file) #store it + #remove invalid keys if found if(len(keys_buffer) == 1): os.remove(os.path.join(path,keys_buffer[0])) + #return results return True if(len(keys_buffer) == 2) else False \ No newline at end of file diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 0d732d3c2..a9817ac48 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -57,11 +57,27 @@ class NetGear: Furthermore, NetGear currently supports three ZeroMQ messaging patterns: i.e zmq.PAIR, zmq.REQ and zmq.REP,and zmq.PUB,zmq.SUB whereas supported protocol are: 'tcp', 'upd', 'pgm', 'inproc', 'ipc'. - Multi-Server Mode: This mode in NetGear API can robustly handle multiple servers at once through exclusive Publish/Subscribe (zmq.PUB/zmq.SUB) + Multi-Server Mode: This mode enables NetGear API to robustly handle multiple servers at once through exclusive Publish/Subscribe (zmq.PUB/zmq.SUB) messaging pattern for seamless Live Streaming across various device at the same time. Each device new server on network is - identied using its unique port address. Also, when all the connected servers on the network get disconnected, the client + identified using its unique port address. Also, when all the connected servers on the network get disconnected, the client itself automatically exits too. This mode can be activated through`multiserver_mode` option boolean attribute during - netgear initialization easily. + NetGear API initialization easily. + + Secure Mode: This mode provides secure ZeroMQ's Security Layers for NetGear API that enables strong encryption on data, and (as far as we know) unbreakable + authentication between the Server and the Client with the help of custom auth certificates/keys. It's default value is `Grassland`:0 => which means no + security at all. + + This mode supports the two most powerful ZMQ security mechanisms: + + * `StoneHouse`: 1 => which switches to the "CURVE" security mechanism, giving us strong encryption on data, and unbreakable authentication. + Stonehouse is the minimum you would use over public networks and assures clients that they are speaking to an authentic server while allowing any client + to connect. It is less secure but at the same time faster than IronHouse security mechanism. + + * `IronHouse`: 2 => which extends Stonehouse with client public key authentication. This is the strongest security model ZMQ have today, + protecting against every attack we know about, except end-point attacks (where an attacker plants spyware on a machine + to capture data before it's encrypted, or after it's decrypted). IronHouse enhanced security comes at a price of additional latency. + + :param address(string): sets the valid network address of the server/client. Network addresses unique identifiers across the network. Its default value of this parameter is based on mode it is working, 'localhost' for Send Mode @@ -97,9 +113,9 @@ class NetGear: when this flag is disabled(i.e.`Send Mode`). Checkout VidGear docs for usage details. Its default value is False(i.e. Send Mode is activated by default). - :param **options(dict): can be used to pass parameters to NetGear Class. - /This attribute provides the flexibility to manipulate ZeroMQ input parameters - /directly. Checkout vidgear docs for usage details. + :param **options(dict): can be used to pass flexible parameters to NetGear API. + This attribute provides the flexibility to manipulate ZeroMQ internal parameters + directly. Checkout vidgear docs for usage details. :param (boolean) logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. @@ -112,7 +128,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r import zmq #import ZMQError from zmq.error import ZMQError - #assign values to global variable + #assign values to global variable for further use self.zmq = zmq self.ZMQError = ZMQError except ImportError as error: @@ -127,7 +143,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #define deque and assign it to global var self.queue = deque(maxlen=96) #max len 96 to check overflow - #define valid messaging pattern `0`: zmq.PAIR, `1`:(zmq.REQ,zmq.REP), and `1`:(zmq.SUB,zmq.PUB) + #define valid messaging patterns => `0`: zmq.PAIR, `1`:(zmq.REQ,zmq.REP), and `1`:(zmq.SUB,zmq.PUB) valid_messaging_patterns = {0:(zmq.PAIR,zmq.PAIR), 1:(zmq.REQ,zmq.REP), 2:(zmq.PUB,zmq.SUB)} # initialize messaging pattern @@ -165,33 +181,38 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.multiserver_mode = False #handles multiserver_mode state recv_filter = '' #user-defined filter to allow specific port/servers only in multiserver_mode + #define valid ZMQ security mechanisms => `0`: Grasslands, `1`:StoneHouse, and `1`:IronHouse valid_security_mech = {0:'Grasslands', 1:'StoneHouse', 2:'IronHouse'} - self.secure_mode = 0 - auth_cert_dir = '' - self.auth_publickeys_dir = '' - self.auth_secretkeys_dir = '' - overwrite_cert = False - custom_cert_location = '' + self.secure_mode = 0 #handles ZMQ security layer status + auth_cert_dir = '' #handles valid ZMQ certificates dir + self.auth_publickeys_dir = '' #handles valid ZMQ public certificates dir + self.auth_secretkeys_dir = '' #handles valid ZMQ private certificates dir + overwrite_cert = False #checks if certificates overwriting allowed + custom_cert_location = '' #handles custom ZMQ certificates path try: #reformat dict options = {k.lower().strip(): v for k,v in options.items()} + # assign values to global variables if specified and valid for key, value in options.items(): - if key == 'multiserver_mode' and isinstance(value, bool) and self.pattern == 2: + # multi-server mode self.multiserver_mode = value elif key == 'filter' and isinstance(value, str): + #custom filter in multi-server mode recv_filter = value elif key == 'secure_mode' and isinstance(value,int) and (value in valid_security_mech): + #secure mode try: - assert check_python_version() >= 3,"[ERROR]: ZMQ Security feature is not available with python version < 3.0." + assert check_python_version() >= 3, "[ERROR]: ZMQ Security feature is not available with python version < 3.0." assert zmq.zmq_version_info() >= (4,0), "[ERROR]: ZMQ Security feature is not supported in libzmq version < 4.0." self.secure_mode = value except AssertionError as e: print(e) elif key == 'custom_cert_location' and isinstance(value,str): + # custom auth certificates path try: assert os.access(value, os.W_OK), "[ERROR]: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(value) assert not(os.path.isfile(value)), "[ERROR]: `custom_cert_location` value must be the path to a directory and not to a file!" @@ -199,8 +220,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r except AssertionError as e: print(e) elif key == 'overwrite_cert' and isinstance(value,bool): + # enable/disable auth certificate overwriting overwrite_cert = value + # various ZMQ flags elif key == 'flag' and isinstance(value, int): self.msg_flag = value elif key == 'copy' and isinstance(value, bool): @@ -214,34 +237,38 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if logging: print(e) - + #handle secure mode if self.secure_mode: - - #import libraries + #import important libs import zmq.auth from zmq.auth.thread import ThreadAuthenticator + # log if overwriting is enabled if logging and overwrite_cert: print('[WARNING]: Overwriting ZMQ Authentication certificates over previous ones!') + #generate and validate certificates path try: + #check if custom certificates path is specified if custom_cert_location: - if os.path.isdir(custom_cert_location): + if os.path.isdir(custom_cert_location): #custom certificate location must be a directory (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) else: raise ValueError("[ERROR]: Invalid `custom_cert_location` value!") else: + # otherwise auto-generate suitable path from os.path import expanduser (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) - - if logging: - print('[LOG]: `{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) - + + #log it + if logging: print('[LOG]: `{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) except Exception as e: - # Catch if any error occurred + # catch if any error occurred print(e) + # also disable secure mode self.secure_mode = 0 print('[WARNING]: ZMQ Security Mechanism is disabled for this connection!') else: + #log if disabled if logging: print('[LOG]: ZMQ Security Mechanism is disabled for this connection!') @@ -258,8 +285,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.receive_mode = receive_mode # define messaging context instance - self.msg_context = zmq.Context.instance() # TODO changed - + self.msg_context = zmq.Context.instance() #check whether `receive_mode` is enabled by user if receive_mode: @@ -284,49 +310,60 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r port = '5555' try: + # initiate and handle secure mode if self.secure_mode > 0 and not(self.multiserver_mode): - # Start an authenticator for this context. + # start an authenticator for this context auth = ThreadAuthenticator(self.msg_context) auth.start() - auth.allow(str(address)) - # Tell authenticator to use the certificate in a directory + auth.allow(str(address)) #allow current address + + #check if `IronHouse` is activated if self.secure_mode == 2: + # tell authenticator to use the certificate from given valid dir auth.configure_curve(domain='*', location=self.auth_publickeys_dir) else: - # Tell the authenticator how to handle CURVE requests + #otherwise tell the authenticator how to handle the CURVE requests, if `StoneHouse` is activated auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[1]) if self.multiserver_mode: - #if multiserver_mode is enabled assign port addresses to zmq socket + # if multiserver_mode is enabled, then assign port addresses to zmq socket for pt in port: - if self.secure_mode > 0: + # enable specified secure mode for the zmq socket + if self.secure_mode > 0: + # load client key client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") - client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + # load all CURVE keys self.msg_socket.curve_secretkey = client_secret self.msg_socket.curve_publickey = client_public - + # load server key server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") server_public, _ = zmq.auth.load_certificate(server_public_file) - # The client must know the server's public key to make a CURVE connection. + # inject public key to make a CURVE connection. self.msg_socket.curve_serverkey = server_public + # connect socket to given server protocol, address and ports self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(pt)) self.msg_socket.setsockopt(zmq.LINGER, 0) # define socket options self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) - else: - # otherwise bind socket to given protocol, address and port normally + else: + # enable specified secure mode for the zmq socket if self.secure_mode > 0: + # load server key server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + # load all CURVE keys self.msg_socket.curve_secretkey = server_secret self.msg_socket.curve_publickey = server_public - self.msg_socket.curve_server = True # must come before bind + # enable CURVE connection for this socket + self.msg_socket.curve_server = True + # bind socket to given protocol, address and port normally self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) # define exclusive socket options for patterns if self.pattern == 2: @@ -336,6 +373,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r except Exception as e: # otherwise raise value error if errored + if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) if self.multiserver_mode: raise ValueError('[ERROR]: Multi-Server Mode, failed to connect to ports: {} with pattern: {}! Kindly recheck all parameters.'.format( str(port), pattern)) else: @@ -347,14 +385,13 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.thread.start() if logging: - #log progress + #finally log progress print('[LOG]: Successfully Binded to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + if self.secure_mode: print('[LOG]: Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') - print('[LOG]: This device Unique ID is {}.'.format(self.id)) + print('[LOG]: Device Unique ID is {}.'.format(self.id)) print('[LOG]: Receive Mode is activated successfully!') - if self.secure_mode: print('[LOG]: Successfully enabled ZMQ Security Mechanism: `{}` for this connection.'.format(valid_security_mech[self.secure_mode])) else: - #otherwise default to `Send Mode` if address is None: #define address @@ -375,46 +412,56 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r port = 5555 #define port normally try: + # initiate and handle secure mode if self.secure_mode > 0 and self.multiserver_mode: - # Start an authenticator for this context. + # start an authenticator for this context auth = ThreadAuthenticator(self.msg_context) auth.start() - auth.allow(str(address)) - # Tell authenticator to use the certificate in a directory + auth.allow(str(address)) #allow current address + + #check if `IronHouse` is activated if self.secure_mode == 2: + # tell authenticator to use the certificate/key from given valid dir auth.configure_curve(domain='*', location=self.auth_publickeys_dir) else: - # Tell the authenticator how to handle CURVE requests + #otherwise tell the authenticator how to handle the CURVE requests, if `StoneHouse` is activated auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[0]) if self.multiserver_mode: - if self.secure_mode > 0: + # enable specified secure mode for the zmq socket + if self.secure_mode > 0: + # load server key server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + # load all CURVE keys self.msg_socket.curve_secretkey = server_secret self.msg_socket.curve_publickey = server_public - self.msg_socket.curve_server = True # must come before bind + # enable CURVE connection for this socket + self.msg_socket.curve_server = True # connect socket to protocol, address and a unique port if multiserver_mode is activated self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) else: + if self.pattern == 1: # if pattern is 1, define additional flags self.msg_socket.REQ_RELAXED = True self.msg_socket.REQ_CORRELATE = True - if self.secure_mode > 0: + # enable specified secure mode for the zmq socket + if self.secure_mode > 0: + # load client key client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") - client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + # load all CURVE keys self.msg_socket.curve_secretkey = client_secret self.msg_socket.curve_publickey = client_public - + # load server key server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") server_public, _ = zmq.auth.load_certificate(server_public_file) - - # The client must know the server's public key to make a CURVE connection. + # inject public key to make a CURVE connection. self.msg_socket.curve_serverkey = server_public # connect socket to given protocol, address and port @@ -422,17 +469,20 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # define socket options self.msg_socket.setsockopt(zmq.LINGER, 0) - except Exception as e: - # otherwise raise value error + #log if errored + if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) + # raise value error raise ValueError('Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) if logging: - #log progress + #finally log progress print('[LOG]: Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + if self.secure_mode: print('[LOG]: Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) print('[LOG]: This device Unique ID is {}.'.format(self.id)) print('[LOG]: Send Mode is successfully activated and ready to send data!') - if self.secure_mode: print('[LOG]: Successfully enabled ZMQ Security Mechanism: `{}` for this connection.'.format(valid_security_mech[self.secure_mode])) + + def update(self): """ @@ -469,13 +519,13 @@ def update(self): self.port_buffer.remove(msg_json['port']) #if termination signal received from all servers then exit client. if not self.port_buffer: - print('Termination signal received from all Servers!!!') + print('[WARNING]: Termination signal received from all Servers!!!') self.terminate = True #termination continue else: if self.pattern == 1: # if pattern is 1, then send back server the info about termination - self.msg_socket.send_string('Termination signal received from server!') + self.msg_socket.send_string('[INFO]: Termination signal received from server!') #termination self.terminate = msg_json['terminate_flag'] continue @@ -485,7 +535,7 @@ def update(self): assert int(msg_json['pattern']) == self.pattern except AssertionError as e: #otherwise raise error and exit - raise ValueError("Messaging pattern on the Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") + raise ValueError("[ERROR]: Messaging patterns on both Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") self.terminate = True continue @@ -494,7 +544,7 @@ def update(self): if self.pattern != 2: # send confirmation message to server for debugging - self.msg_socket.send_string('Data received on device: {} !'.format(self.id)) + self.msg_socket.send_string('[LOG]: Data received on device: {} !'.format(self.id)) # recover frame from array buffer frame_buffer = np.frombuffer(msg_data, dtype=msg_json['dtype']) @@ -532,7 +582,7 @@ def recv(self): pass else: #otherwise raise value error and exit - raise ValueError('recv() function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') + raise ValueError('[ERROR]: `recv()` function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') self.terminate = True # check whether or not termination flag is enabled while not self.terminate: @@ -558,7 +608,7 @@ def send(self, frame, message = None): pass else: #otherwise raise value error and exit - raise ValueError('send() function cannot be used while receive_mode is enabled. Kindly refer vidgear docs!') + raise ValueError('[ERROR]: `send()` function cannot be used while receive_mode is enabled. Kindly refer vidgear docs!') self.terminate = True # define exit_flag and assign value From 073bca131ba280d0fdf3b68ef4d2c6b916c6e7b1 Mon Sep 17 00:00:00 2001 From: Abhishek Thakur Date: Sat, 5 Oct 2019 20:36:36 +0530 Subject: [PATCH 11/39] Hotfix: assigned Port address ignored bug --- vidgear/gears/netgear.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index a9817ac48..dba4da99f 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -306,8 +306,8 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #create port address buffer for keeping track of incoming server's port self.port_buffer = [] else: - # otherwise assign local port address - port = '5555' + # otherwise assign local port address if None + if port is None: port = '5555' try: # initiate and handle secure mode @@ -409,7 +409,8 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #assign value to global variable self.port = port else: - port = 5555 #define port normally + # otherwise assign local port address if None + if port is None: port = '5555' try: # initiate and handle secure mode From b0cb7cd7ea4dda3ff894f93dea8ad6f4f85e3ba7 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 9 Oct 2019 10:55:59 +0530 Subject: [PATCH 12/39] Code Maintaince & Enhancements: - Updated bash script path to $TMPDIR from $HOME - Updated Code Readability - Fixed code definitions & Typos --- changelog.md | 25 ++++++++++++++-- scripts/bash/install_opencv.sh | 6 +++- scripts/bash/prepare_dataset.sh | 10 ++++--- vidgear/gears/camgear.py | 6 ++-- vidgear/gears/helper.py | 2 +- vidgear/gears/netgear.py | 10 +++---- vidgear/gears/pigear.py | 4 +-- vidgear/gears/screengear.py | 10 +++---- vidgear/gears/writegear.py | 29 +++++++++---------- .../test_benchmark_Videocapture.py | 3 +- .../test_benchmark_Videowriter.py | 9 +++--- .../test_benchmark_playback.py | 3 +- vidgear/tests/test_helper.py | 8 ++--- .../tests/videocapture_tests/test_camgear.py | 3 +- .../writer_tests/test_compression_mode.py | 8 ++--- .../writer_tests/test_non_compression_mode.py | 8 ++--- 16 files changed, 86 insertions(+), 58 deletions(-) diff --git a/changelog.md b/changelog.md index 64eb14df8..a6d3d2326 100644 --- a/changelog.md +++ b/changelog.md @@ -3,23 +3,42 @@ ## VidGear 0.1.6-dev ### New Features: + * Added powerful ZMQ Authentication & Data Encryption features for NetGear API: + * Added exclusive `secure_mode` param for enabling it. + * Added support for two most powerful `Stonehouse` & `Ironhouse` ZMQ security mechanisms. + * Added smart auth-certificates/key generation and validation features. + * Implemented Robust Multi-Server support for NetGear API. + * Enables Multiple Servers messaging support with a single client. + * Added exclusive `multiserver_mode` param for enabling it. + * Added ability to send additional data of any datatype along with the frame in realtime in this mode. + * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming. * Added VidGear's official native support for MacOS environment. ### Updates/Improvements: + * Updated support for screen casting from all monitors in ScreenGear API. + * Updated ScreenGear API to use *Threaded Queue Mode* by default, thereby removed redundant `THREADED_QUEUE_MODE` param. + * Updated Tests bash scripts to use system-specific **Temp** directory instead of **Home** for downloading content. + * Updated Wiki-Documentation with latest examples and Information. * Updated Travis CLI Tests with support for macOS environment - * Reformatted & implemented necessary changes and dependencies in `travis.yml`. + * Reformatted & implemented necessary MacOS related changes and dependencies in `travis.yml`. ### Breaking Updates / Improvements / Changes * `Python 2.7` legacy support removed from CLI tests. + * Newly implemented `secure_mode` will only support Python 3 and above legacies. ### Fixes - * Fixed unreliable dataset video file URL(rehosted file on github.com) - * Remove duplicate code to import MSS(@BoboTiG) from NetGear + * Fixed assigned Port address ignored bug (commit 073bca1) + * Fixed several wrong definition bugs from NetGear API(commit 8f7153c). + * Fixed unreliable dataset video URL(rehosted file on `github.com`) + * Removed duplicate code to import MSS(@BoboTiG) from ScreenGear API. + * Fixed Several bugs related to new `secure_mode` & `multiserver_mode` Modes. * Fixed various macOS environment bugs ### Pull requests(PR) involved: * PR #39 * PR #42 + * PR #44 + * PR #52 :warning: PyPi Release does NOT contain Tests and Scripts! diff --git a/scripts/bash/install_opencv.sh b/scripts/bash/install_opencv.sh index f3323c2e1..5db9428e8 100644 --- a/scripts/bash/install_opencv.sh +++ b/scripts/bash/install_opencv.sh @@ -20,6 +20,10 @@ OPENCV_VERSION='4.1.0' +#determining system specific temp directory +TMPDIR=$(dirname "$(mktemp tmp.XXXXXXXXXX -ut)") + +#determining system Python suffix and version PYTHONSUFFIX=$(python -c 'import platform; a = platform.python_version(); print(".".join(a.split(".")[:2]))') PYTHONVERSION=$(python -c 'import platform; print(platform.python_version())') @@ -41,7 +45,7 @@ sudo apt-get install -y libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev gst echo "Installing OpenCV Library" -cd $HOME +cd $TMPDIR wget https://github.com/abhiTronix/OpenCV-Travis-Builds/releases/download/latest/OpenCV-$OPENCV_VERSION-$PYTHONVERSION.deb diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index fd31ae0d9..d0b5b223c 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -12,10 +12,12 @@ #The above copyright notice and this permission notice shall be included in all #copies or substantial portions of the Software. +#determining system specific temp directory +TMPDIR=$(dirname "$(mktemp tmp.XXXXXXXXXX -ut)") # Creating necessary directories -mkdir -p $HOME/Downloads -mkdir -p $HOME/Downloads/{FFmpeg_static,Test_videos} +mkdir -p $TMPDIR/Downloads +mkdir -p $TMPDIR/Downloads/{FFmpeg_static,Test_videos} # Acknowledging machine architecture MACHINE_BIT=$(uname -m) @@ -38,7 +40,7 @@ esac #Download and Configure FFmpeg Static -cd $HOME/Downloads/FFmpeg_static +cd $TMPDIR/Downloads/FFmpeg_static if [ $OS_NAME = "linux" ]; then @@ -81,7 +83,7 @@ else fi # Downloading Test Data -cd $HOME/Downloads/Test_videos +cd $TMPDIR/Downloads/Test_videos echo "Downloading Test-Data..." curl http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 -o BigBuckBunny.mp4 diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 6473fabc9..940f40882 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -53,10 +53,10 @@ if parse_version(cv2.__version__) >= parse_version('3'): pass else: - raise ImportError('OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') + raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') @@ -140,7 +140,7 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l except Exception as e: if logging: print(e) - raise ValueError('YouTube Mode is enabled and the input YouTube Url is invalid!') + raise ValueError('[ERROR]: YouTube Mode is enabled and the input YouTube Url is invalid!') # youtube mode variable initialization self.youtube_mode = y_tube diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index c7ff58280..6eb33aa91 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -54,7 +54,7 @@ def check_CV_version(): def capPropId(property): """ - Retrieves the Property's Integer(Actual) value. + Retrieves the OpenCV property Integer(Actual) value. """ return getattr(cv2, property) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index dba4da99f..b01989eab 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -41,9 +41,9 @@ if parse_version(cv2.__version__) >= parse_version('3'): pass else: - raise ImportError('OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') + raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') @@ -133,7 +133,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.ZMQError = ZMQError except ImportError as error: #raise error - raise ImportError('pyzmq python library not installed. Kindly install it with `pip install pyzmq` command.') + raise ImportError('[ERROR]: pyzmq python library not installed. Kindly install it with `pip install pyzmq` command.') #log and enable threaded queue mode if logging: @@ -299,7 +299,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # check if unique server port address list/tuple is assigned or not in multiserver_mode if port is None or not isinstance(port, (tuple, list)): # raise error if not - raise ValueError('Incorrect port value! Kindly provide a list/tuple of ports at Receiver-end while Multi-Server mode is enabled. For more information refer VidGear docs.') + raise ValueError('[ERROR]: Incorrect port value! Kindly provide a list/tuple of ports at Receiver-end while Multi-Server mode is enabled. For more information refer VidGear docs.') else: #otherwise log it print('[LOG]: Enabling Multi-Server Mode at PORTS: {}!'.format(port)) @@ -474,7 +474,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log if errored if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) # raise value error - raise ValueError('Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + raise ValueError('[ERROR]: Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) if logging: #finally log progress diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index 694c5a268..e921323ec 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -52,7 +52,7 @@ class PiGear: PiGear is similar to CamGear but exclusively made to support various Raspberry Pi Camera Modules (such as OmniVision OV5647 Camera Module and Sony IMX219 Camera Module). To interface with these modules correctly, PiGear provides a flexible multi-threaded wrapper around complete picamera - python library and provides us the ability to exploit its various features like brightness, saturation, sensor_mode, etc. effortlessly. + python library and provides us the ability to exploit its various features like `brightness, saturation, sensor_mode`, etc. effortlessly. :param (tuple) resolution: sets the resolution (width,height). Its default value is (640,480). @@ -81,7 +81,7 @@ def __init__(self, resolution=(640, 480), framerate=25, colorspace = None, loggi #print(cv2.__version__) except ImportError as error: # Output expected ImportErrors. - raise ImportError('Failed to detect Picamera executables, install it with "pip install picamera" command.') + raise ImportError('[ERROR]: Failed to detect Picamera executables, install it with "pip install picamera" command.') # initialize the picamera stream self.camera = PiCamera() diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index 7d4cbd9e0..78bea2c79 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -39,10 +39,10 @@ if parse_version(cv2.__version__) >= parse_version('3'): pass else: - raise ImportError('OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') + raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') @@ -85,7 +85,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): from mss.exception import ScreenShotError except ImportError as error: # otherwise raise import error - raise ImportError('python-mss library not found, install it with `pip install mss` command.') + raise ImportError('[ERROR]: python-mss library not found, install it with `pip install mss` command.') # create mss object self.mss_object = mss() # create monitor instance for the user-defined monitor @@ -93,7 +93,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): if (monitor >= 0): monitor_instance = self.mss_object.monitors[monitor] else: - raise ValueError("`monitor` value cannot be negative, Read Docs!") + raise ValueError("[ERROR]: `monitor` value cannot be negative, Read Docs!") # Initialize Queue self.queue = None @@ -136,7 +136,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): self.queue.append(self.frame) except ScreenShotError: #otherwise catch and log errors - raise ValueError("ScreenShotError caught: Wrong dimensions passed to python-mss, Kindly Refer Docs!") + raise ValueError("[ERROR]: ScreenShotError caught: Wrong dimensions passed to python-mss, Kindly Refer Docs!") if logging: print(self.mss_object.get_error_details()) diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 4de8c3d05..b0d0c1fc4 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -41,19 +41,18 @@ if parse_version(cv2.__version__) >= parse_version('3'): pass else: - raise ImportError('OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') + raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') class WriteGear: """ - WriteGear solely handles various powerful FFmpeg tools that allow us to do almost anything you can imagine with multimedia files. - With WriteGear API, you can process real-time video frames into a lossless format and specification suitable for our playback in - just a few lines of codes. These specifications include setting bitrate, codec, framerate, resolution, subtitles, compression, etc. - Furthermore, we can multiplex extracted audio at the output with compression and all that in real-time(see this example). + solely handles various powerful FFmpeg tools that allow us to do almost anything you can imagine with multimedia files. + With WriteGear API, you can process real-time video frames into a lossless format and specification suitable for our playback + in just a few lines of codes. These specifications include setting bitrate, codec, framerate, resolution, subtitles, compression, etc. In addition to this, WriteGear also provides flexible access to OpenCV's VideoWriter API which provide some basic tools for video frames encoding but without compression. @@ -61,11 +60,11 @@ class WriteGear: Compression Mode: In this mode, WriteGear utilizes FFmpeg's inbuilt encoders to encode lossless multimedia files. It provides us the ability to exploit almost any available parameters available within FFmpeg, with so much ease - and flexibility and while doing that it robustly handles all errors/warnings quietly. You can find more about this mode here. + and flexibility and while doing that it robustly handles all errors/warnings quietly. Non-Compression Mode: In this mode, WriteGear utilizes basic OpenCV's inbuilt VideoWriter API. Similar to compression mode, WriteGear also supports all parameters manipulation available within OpenCV's VideoWriter API. But this mode lacks the ability to manipulate encoding parameters - and other important features like video compression, audio encoding, etc. You can learn about this mode here. + and other important features like video compression, audio encoding, etc. ```Warning: In case, This class fails to detect valid FFmpeg executables on your system, It can automatically fallbacks to Non-Compression Mode.``` @@ -119,7 +118,7 @@ def __init__(self, output_filename = '', compression_mode = True , custom_ffmpeg # handles output file name (if not given) if not output_filename: - raise ValueError('Kindly provide a valid `output_filename` value, Refer VidGear Docs for more information!') + raise ValueError('[ERROR]: Kindly provide a valid `output_filename` value, Refer VidGear Docs for more information!') elif output_filename and os.path.isdir(output_filename): # check if directory path is given instead output_filename = os.path.join(output_filename, 'VidGear-{}.mp4'.format(time.strftime("%Y%m%d-%H%M%S"))) # auto-assign valid name and adds it to path else: @@ -142,7 +141,7 @@ def __init__(self, output_filename = '', compression_mode = True , custom_ffmpeg except Exception as e: if self.logging: print(e) - raise ValueError('Wrong output_params parameters passed to WriteGear class!') + raise ValueError('[ERROR]: Wrong output_params parameters passed to WriteGear class!') #handles FFmpeg binaries validity tests if self.compression: @@ -218,10 +217,10 @@ def write(self, frame, rgb_mode = False): #validate size of frame if height != self.inputheight or width != self.inputwidth: - raise ValueError('All frames in a video should have same size') + raise ValueError('[ERROR]: All frames in a video should have same size') #validate number of channels if channels != self.inputchannels: - raise ValueError('All frames in a video should have same number of channels') + raise ValueError('[ERROR]: All frames in a video should have same number of channels') if self.compression: # checks if compression mode is enabled @@ -238,7 +237,7 @@ def write(self, frame, rgb_mode = False): self.process.stdin.write(frame.tostring()) except (OSError, IOError): # log something is wrong! - print ('BrokenPipeError caught: Wrong Values passed to FFmpeg Pipe, Kindly Refer Docs!') + print ('[ERROR]: BrokenPipeError caught: Wrong Values passed to FFmpeg Pipe, Kindly Refer Docs!') self.DEVNULL.close() raise ValueError #for testing purpose only else: @@ -373,7 +372,7 @@ def startCV_Process(self): FOURCC = cv2.VideoWriter_fourcc(*(value.upper())) elif key == '-fps': FPS = float(value) - elif key =='-backend' and value.upper() in ['CAP_FFMPEG','CAP_GSTREAMER']: + elif key =='-backend': BACKEND = capPropId(value.upper()) elif key == '-color': COLOR = bool(int(value)) @@ -384,7 +383,7 @@ def startCV_Process(self): # log if something is wrong if self.logging: print(e) - raise ValueError('Wrong Values passed to OpenCV Writer, Kindly Refer Docs!') + raise ValueError('[ERROR]: Wrong Values passed to OpenCV Writer, Kindly Refer Docs!') if self.logging: #log values for debugging diff --git a/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py b/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py index 7ef0b29f0..5b89dd3e9 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py @@ -26,6 +26,7 @@ import os import cv2 import pytest +import tempfile from vidgear.gears import CamGear from .fps import FPS @@ -35,7 +36,7 @@ def return_testvideo_path(): """ return Test Video Data path """ - path = '{}/Downloads/Test_videos/BigBuckBunny.mp4'.format(os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME']) + path = '{}/Downloads/Test_videos/BigBuckBunny.mp4'.format(tempfile.gettempdir()) return os.path.abspath(path) diff --git a/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py b/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py index 6d2b4af43..4d45b928d 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py @@ -25,6 +25,7 @@ import os, platform import pytest +import tempfile from vidgear.gears import WriteGear from vidgear.gears import VideoGear from .fps import FPS @@ -35,7 +36,7 @@ def return_testvideo_path(): """ return Test Video Data path """ - path = '{}/Downloads/Test_videos/BigBuckBunny.mp4'.format(os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME']) + path = '{}/Downloads/Test_videos/BigBuckBunny.mp4'.format(tempfile.gettempdir()) return os.path.abspath(path) @@ -46,11 +47,11 @@ def return_static_ffmpeg(): """ path = '' if platform.system() == 'Windows': - path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') elif platform.system() == 'Darwin': - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) diff --git a/vidgear/tests/benchmark_tests/test_benchmark_playback.py b/vidgear/tests/benchmark_tests/test_benchmark_playback.py index 482f296b6..ffc58eb99 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_playback.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_playback.py @@ -25,6 +25,7 @@ import os, platform import pytest +import tempfile from vidgear.gears import CamGear from .fps import FPS @@ -35,7 +36,7 @@ def return_testvideo(level=0): return Test Video Data path with different Video quality/resolution/bitrate for different levels(Level-0(Lowest ~HD 2Mbps) and Level-5(Highest ~4k UHD 120mpbs)) """ Levels = ['BigBuckBunny.mp4','20_mbps_hd_hevc_10bit.mkv','50_mbps_hd_h264.mkv','90_mbps_hd_hevc_10bit.mkv','120_mbps_4k_uhd_h264.mkv'] - path = '{}/Downloads/Test_videos/{}'.format(os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME'], Levels[level]) + path = '{}/Downloads/Test_videos/{}'.format(tempfile.gettempdir(), Levels[level]) return os.path.abspath(path) diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index decf1404b..09a6ea9a6 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -37,11 +37,11 @@ def return_static_ffmpeg(): """ path = '' if platform.system() == 'Windows': - path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') elif platform.system() == 'Darwin': - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) @@ -49,7 +49,7 @@ def test_ffmpeg_static_installation(): """ Auxilary Tests to ensure successful Static FFmpeg Installation on Windows through script """ - startpath = os.path.abspath(os.path.join( os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME'],'Downloads/FFmpeg_static')) + startpath = os.path.abspath(tempfile.gettempdir(),'Downloads/FFmpeg_static') for root, dirs, files in os.walk(startpath): level = root.replace(startpath, '').count(os.sep) indent = ' ' * 4 * (level) diff --git a/vidgear/tests/videocapture_tests/test_camgear.py b/vidgear/tests/videocapture_tests/test_camgear.py index 3750ad0c0..d63e7e1fc 100644 --- a/vidgear/tests/videocapture_tests/test_camgear.py +++ b/vidgear/tests/videocapture_tests/test_camgear.py @@ -28,6 +28,7 @@ import platform import os, time import pytest +import tempfile import numpy as np from vidgear.gears import CamGear @@ -49,7 +50,7 @@ def return_testvideo_path(): """ return Test Video Data path """ - path = '{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4'.format(os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME']) + path = '{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4'.format(tempfile.gettempdir()) return os.path.abspath(path) diff --git a/vidgear/tests/writer_tests/test_compression_mode.py b/vidgear/tests/writer_tests/test_compression_mode.py index b3cb22afb..504222698 100644 --- a/vidgear/tests/writer_tests/test_compression_mode.py +++ b/vidgear/tests/writer_tests/test_compression_mode.py @@ -42,11 +42,11 @@ def return_static_ffmpeg(): """ path = '' if platform.system() == 'Windows': - path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') elif platform.system() == 'Darwin': - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) @@ -55,7 +55,7 @@ def return_testvideo_path(): """ return Test Video Data path """ - path = '{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4'.format(os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME']) + path = '{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4'.format(tempfile.gettempdir()) return os.path.abspath(path) diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index f777b0b83..f0ddc4279 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -41,11 +41,11 @@ def return_static_ffmpeg(): """ path = '' if platform.system() == 'Windows': - path += os.path.join(os.environ['USERPROFILE'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg.exe') elif platform.system() == 'Darwin': - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/bin/ffmpeg') else: - path += os.path.join(os.environ['HOME'],'Downloads/FFmpeg_static/ffmpeg/ffmpeg') + path += os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static/ffmpeg/ffmpeg') return os.path.abspath(path) @@ -54,7 +54,7 @@ def return_testvideo_path(): """ return Test Video Data path """ - path = '{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4'.format(os.environ['USERPROFILE'] if os.name == 'nt' else os.environ['HOME']) + path = '{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4'.format(tempfile.gettempdir()) return os.path.abspath(path) From a5f84d1bd2daaa1c69e59547bdc639af96baeb74 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 9 Oct 2019 12:32:32 +0530 Subject: [PATCH 13/39] Fixed Typo --- vidgear/tests/test_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index 09a6ea9a6..0a87b1e26 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -23,7 +23,7 @@ =============================================== """ -import os, pytest, tempfile, shutil, platform +import os, pytest, tempfile, shutil, platform, tempfile from vidgear.gears.helper import download_ffmpeg_binaries from vidgear.gears.helper import validate_ffmpeg @@ -49,7 +49,7 @@ def test_ffmpeg_static_installation(): """ Auxilary Tests to ensure successful Static FFmpeg Installation on Windows through script """ - startpath = os.path.abspath(tempfile.gettempdir(),'Downloads/FFmpeg_static') + startpath = os.path.abspath(os.path.join(tempfile.gettempdir(),'Downloads/FFmpeg_static')) for root, dirs, files in os.walk(startpath): level = root.replace(startpath, '').count(os.sep) indent = ' ' * 4 * (level) From 349432bc7a3deeea2c72d9104767dad800255e59 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 9 Oct 2019 13:34:39 +0530 Subject: [PATCH 14/39] Fix for MacOS environments --- scripts/bash/prepare_dataset.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index d0b5b223c..7c8f0dd30 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -13,7 +13,7 @@ #copies or substantial portions of the Software. #determining system specific temp directory -TMPDIR=$(dirname "$(mktemp tmp.XXXXXXXXXX -ut)") +TMPDIR=$(dirname "$(mktemp tmp.XXXXXXXXXX -dt)") # Creating necessary directories mkdir -p $TMPDIR/Downloads From b0c01235cbd9778e6664069e93efd01de1618083 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 9 Oct 2019 14:08:00 +0530 Subject: [PATCH 15/39] Fixes for MacOS environment-2 --- scripts/bash/install_opencv.sh | 4 ++-- scripts/bash/prepare_dataset.sh | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/bash/install_opencv.sh b/scripts/bash/install_opencv.sh index 5db9428e8..fd6f400d1 100644 --- a/scripts/bash/install_opencv.sh +++ b/scripts/bash/install_opencv.sh @@ -21,7 +21,7 @@ OPENCV_VERSION='4.1.0' #determining system specific temp directory -TMPDIR=$(dirname "$(mktemp tmp.XXXXXXXXXX -ut)") +TMPFOLDER=$(python -c 'import tempfile; print(tempfile.gettempdir())') #determining system Python suffix and version PYTHONSUFFIX=$(python -c 'import platform; a = platform.python_version(); print(".".join(a.split(".")[:2]))') @@ -45,7 +45,7 @@ sudo apt-get install -y libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev gst echo "Installing OpenCV Library" -cd $TMPDIR +cd $TMPFOLDER wget https://github.com/abhiTronix/OpenCV-Travis-Builds/releases/download/latest/OpenCV-$OPENCV_VERSION-$PYTHONVERSION.deb diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index 7c8f0dd30..cf806a73c 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -13,11 +13,11 @@ #copies or substantial portions of the Software. #determining system specific temp directory -TMPDIR=$(dirname "$(mktemp tmp.XXXXXXXXXX -dt)") +TMPFOLDER=$(python -c 'import tempfile; print(tempfile.gettempdir())') # Creating necessary directories -mkdir -p $TMPDIR/Downloads -mkdir -p $TMPDIR/Downloads/{FFmpeg_static,Test_videos} +mkdir -p $TMPFOLDER/Downloads +mkdir -p $TMPFOLDER/Downloads/{FFmpeg_static,Test_videos} # Acknowledging machine architecture MACHINE_BIT=$(uname -m) @@ -40,7 +40,7 @@ esac #Download and Configure FFmpeg Static -cd $TMPDIR/Downloads/FFmpeg_static +cd $TMPFOLDER/Downloads/FFmpeg_static if [ $OS_NAME = "linux" ]; then @@ -83,7 +83,7 @@ else fi # Downloading Test Data -cd $TMPDIR/Downloads/Test_videos +cd $TMPFOLDER/Downloads/Test_videos echo "Downloading Test-Data..." curl http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 -o BigBuckBunny.mp4 From 54c3881f5bd591b098d8af749215c1da3082d5cc Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 9 Oct 2019 18:19:42 +0530 Subject: [PATCH 16/39] Updates for ReadME.md and Minor Fixes --- README.md | 69 ++++++++++++++++++++---------------- changelog.md | 5 +-- vidgear/gears/pigear.py | 2 +- vidgear/gears/writegear.py | 2 +- vidgear/tests/test_helper.py | 2 +- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 89fd996c0..07046b7d7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ THE SOFTWARE.   -VidGear is a powerful python Video Processing library built with multi-threaded [**Gears**](#gear)(_a.k.a APIs_) each with a unique set of trailblazing features. These APIs provides a easy-to-use, highly extensible, and multi-threaded wrapper around many underlying state-of-the-art python libraries such as *[OpenCV ➶][opencv], [FFmpeg ➶][ffmpeg], [picamera ➶][picamera], [pafy ➶][pafy], [pyzmq ➶][pyzmq] and [python-mss ➶][mss]* +VidGear is a powerful python Video Processing library built with multi-threaded [**Gears**](#gear)(_a.k.a APIs_) each with a unique set of trailblazing features. These APIs provides a easy-to-use, highly extensible, and multi-threaded wrapper around many underlying state-of-the-art libraries such as *[OpenCV ➶][opencv], [FFmpeg ➶][ffmpeg], [picamera ➶][picamera], [pafy ➶][pafy], [pyzmq ➶][pyzmq] and [python-mss ➶][mss]*   @@ -58,7 +58,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi [**TL;DR**](#tldr) -[**New Release : VidGear 0.1.5**](#new-release-sneekpeak--vidgear-015) +[**New Release SneekPeak**](#new-release-sneekpeak--vidgear-016) [**Installation Options**](#installation) * [**Prerequisites**](#prerequisites) @@ -90,26 +90,28 @@ The following **functional block diagram** clearly depicts the functioning of Vi   ## TL;DR - *VidGear is an [ultrafast➶][ultrafast-wiki], compact, flexible and easy-to-adapt complete Video Processing Python Library.* + *"VidGear is an [ultrafast➶][ultrafast-wiki], compact, flexible and easy-to-adapt complete Video Processing Python Library."* - Built with simplicity in mind, VidGear lets programmers and software developers to easily integrate and perform complex Video Processing tasks in their existing or new applications, without going through various underlying python library's documentation and using just a few lines of code. Beneficial for both, if you're new to Programming with Python language or a pro at it. + Built with simplicity in mind, VidGear lets programmers and software developers to easily integrate and perform complex Video Processing tasks in their existing or new applications, without going through various underlying library's documentation and using just a few lines of code. Beneficial for both, if you're new to programming with Python language or already a pro at it. - For more advanced information see the [Wiki Documentation ➶][wiki]. + **For more detailed information see the [*Wiki Documentation ➶*][wiki].**   -## New Release SneekPeak : VidGear 0.1.5 - * Released new ScreenGear API, supports Live ScreenCasting. - * Released new NetGear API, aids real-time frame transfer through messaging(ZmQ) over the network. - * Released new Stabilizer Class, for minimum latency Video Stabilization with OpenCV. - * Updated VideoGear API to be used as an internal wrapper around Stabilizer Class. - * Implemented exclusive Threaded Queue Mode for blazingly fast, synchronized and error-free multi-threading APIs. - * Added Option to use VidGear API's standalone. - * Several Performance enhancements and Bugs exterminated. - * Revamped Docs and [many more...](changelog.md) +## New Release SneekPeak : VidGear 0.1.6 + * Added powerful ZMQ Authentication & Data Encryption features for NetGear API: + * Added exclusive `secure_mode` param for enabling it. + * Added support for two most powerful `Stonehouse` & `Ironhouse` ZMQ security mechanisms. + * Added auto auth-certificates/key generation and validation feature. + * Implemented Robust Multi-Server support for NetGear API: + * Enables Multiple Servers messaging support with a single client. + * Added exclusive `multiserver_mode` param for enabling it. + * Added ability to send additional data of any datatype along with the frame in realtime in this mode. + * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming in NetGear API. + * Added VidGear's official native support for MacOS environment and [many more...](changelog.md)   @@ -117,38 +119,38 @@ The following **functional block diagram** clearly depicts the functioning of Vi ### Prerequisites -To use VidGear in your python application, you must check the following dependencies before you install VidGear : +Before installing VidGear, you must verify that the following dependencies are met: -* Must support [these Python legacies](#supported-python-legacies) and [pip][pip] already installed. +* Must be using [supported Python legacies](#supported-python-legacies) only and thereby [pip][pip] already installed properly. * **`OpenCV:`** VidGear must require OpenCV(3.0+) python enabled binaries to be installed on your machine for its core functions. For its installation, you can follow these online tutorials for [linux][OpenCV-linux] and [raspberry pi][OpenCV-pi], otherwise, install it via pip: ```sh - pip install opencv-python + pip install -U opencv-python #or install opencv-contrib-python similarly ``` * **`FFmpeg:`** VidGear must require FFmpeg for its powerful video compression and encoding capabilities. :star2: Follow this [**FFmpeg wiki page**][ffmpeg-wiki] for its installation. :star2: -* **`picamera:`** Required for using Raspberry Pi Camera Modules(_such as OmniVision OV5647 Camera Module_) on your Raspberry Pi machine. You can easily install it via pip: +* **`picamera:`** Required if using Raspberry Pi Camera Modules(_such as OmniVision OV5647 Camera Module_) with your Raspberry Pi machine. You can easily install it via pip: ```sh pip install picamera ``` Also, make sure to enable Raspberry Pi hardware-specific settings prior to using this library. -* **`mss:`** Required for Screen Casting. Install it via pip: +* **`mss:`** Required for using Screen Casting. Install it via pip: ```sh pip install mss ``` -* **`pyzmq:`** Required for transferring video frames through _ZeroMQ messaging system_ over the network. Install it via pip: +* **`pyzmq:`** Required for transferring live video frames through _ZeroMQ messaging system_ over the network. Install it via pip: ```sh pip install pyzmq ``` -* **`pafy:`** For direct YouTube Video streaming, Vidgear needs [`pafy`][pafy] and latest [`youtube-dl`][yt-dl](as pafy's backend) python libraries installed. Install it via pip: +* **`pafy:`** Required for direct YouTube Video streaming capabilities. Both [`pafy`][pafy] and latest only [`youtube-dl`][yt-dl](_as pafy's backend_) libraries must be installed via pip as follows: ```sh pip install pafy @@ -177,7 +179,7 @@ VidGear releases are available for download as packages in the [latest release][ ### Option 3: Clone the Repository -> Best option for **automatically installing required dependencies**(_except FFmpeg_), or for **latest patches**(_maybe experimental_), or **contributing** to development. +> Best option for **latest patches**(_maybe experimental_), or **contributing** to development. You can clone this repository's `testing` branch for development and thereby can install as follows: ```sh @@ -191,7 +193,7 @@ You can clone this repository's `testing` branch for development and thereby can ## Gears: -VidGear is built with multi-threaded **Gears** each with some unique function/mechanism. Each **Gear** is designed exclusively to handle/control different device-specific video streams, network streams, and media encoders. These APIs provides an easy-to-use, highly extensible, and a multi-threaded wrapper around many underlying various python libraries to exploit their features and functions directly while providing robust error-handling. +VidGear is built with multi-threaded **Gears** each with some unique function/mechanism. Each **Gear** is designed exclusively to handle/control different device-specific video streams, network streams, and media encoders. These APIs provides an easy-to-use, highly extensible, and a multi-threaded wrapper around various underlying libraries to exploit their features and functions directly while providing robust error-handling. **These Gears can be classified as follows:** @@ -214,7 +216,7 @@ VidGear is built with multi-threaded **Gears** each with some unique function/me ### CamGear -CamGear supports a diverse range of video streams which can handle/control video stream almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), network stream URL such as `http(s), rtp, rstp, mms, etc.` In addition to this, it also supports live Gstreamer's RAW pipelines and YouTube video/livestreams URLs. CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs `pafy's` APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear relies exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. +CamGear supports a diverse range of video streams which can handle/control video stream almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), network stream URL such as `http(s), rtp, rstp, mms, etc.` In addition to this, it also supports live Gstreamer's RAW pipelines and YouTube video/livestreams URLs. CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs `pafy` python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear relies exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. **Following simplified functional block diagram depicts CamGear API's generalized working:** @@ -368,7 +370,7 @@ stream.stop() ### WriteGear -WriteGear is undoubtedly the most powerful Video Processing Gear of them all. It solely handles various powerful FFmpeg tools that allow us to do almost anything you can imagine with multimedia files. With WriteGear API, you can process real-time video frames into a lossless format and specification suitable for our playback in just a few lines of codes. These specifications include setting bitrate, codec, framerate, resolution, subtitles, compression, etc. Furthermore, we can multiplex extracted audio at the output with compression and all that in real-time(see this example). In addition to this, WriteGear also provides flexible access to OpenCV's VideoWriter API which provides some basic tools for video frames encoding but without compression. +WriteGear is undoubtedly the most powerful Video Processing Gear of them all. It solely handles various powerful FFmpeg tools that provide us the freedom to do almost anything imagine with multimedia files. For example with WriteGear API, we can process real-time video frames into a lossless compressed format with any suitable specification in just few easy [lines of codes][compression-mode-ex]. These specifications include setting any video/audio property such as `bitrate, codec, framerate, resolution, subtitles, etc.` Furthermore, we can multiplex extracted audio at the output with compression and all that in real-time(see this [example wiki][live-audio-wiki]). In addition to this, WriteGear also provides flexible access to OpenCV's VideoWriter API which provides some basic tools for video frames encoding but without compression. **WriteGear primarily operates in the following two modes:** @@ -390,14 +392,19 @@ WriteGear is undoubtedly the most powerful Video Processing Gear of them all. It ### NetGear -NetGear is exclusively designed to transfer video frames synchronously between interconnecting systems over the network in real-time. This is achieved by implementing a high-level wrapper around [PyZmQ][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. Furthermore, NetGear currently supports two ZeroMQ messaging patterns: i.e `zmq.PAIR` and `zmq.REQ and zmq.REP`. +NetGear is exclusively designed to transfer video frames synchronously between interconnecting systems over the network in real-time. This is achieved by implementing a high-level wrapper around [PyZmQ][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. Furthermore, It also provides easy access to powerful, smart & secure ZeroMQ's Security Layers in NetGear API that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings cheap, standardized privacy and authentication for distributed systems over the network. On top of that, this API can robustly handle Multiple Servers at once, thereby providing access to seamless Live Streams of the various device in a network at the same time. -**NetGear API has two modes of operations:** - * **Send Mode:**(a.k.a Server configuration) which employs `send()` function to send frames to the client(s). You can use this function to send messages to client(s) too. - * **Receive Mode:**(a.k.a Client configuration) which employs `recv()` function to receive frames sent by server. Client send back confirmation when frame is received successfully. +**NetGear as of now seamlessly supports three ZeroMQ messaging patterns:** -**Following functional block diagram depicts NetGear API:** + * ZMQ Pair Pattern + * ZMQ Client/Server Pattern + * ZMQ Publish/Subscribe Pattern + +whereas the supported protocol are: `tcp, upd, pgm, inproc, ipc`. + + +**Following functional block diagram depicts generalized functioning of NetGear API:**

NetGear Functional Block Diagram @@ -511,6 +518,8 @@ Internal URLs [wiki]:https://github.com/abhiTronix/vidgear/wiki [wiki-vidgear-purpose]:https://github.com/abhiTronix/vidgear/wiki/Project-Motivation#why-is-vidgear-a-thing [ultrafast-wiki]:https://github.com/abhiTronix/vidgear/wiki/FAQ-&-Troubleshooting#2-vidgear-is-ultrafast-but-how +[compression-mode-ex]:https://github.com/abhiTronix/vidgear/wiki/Compression-Mode:-FFmpeg#1-writegear-bare-minimum-examplecompression-mode +[live-audio-wiki]:https://github.com/abhiTronix/vidgear/wiki/Working-with-Audio#a-live-audio-input-to-writegear-class [ffmpeg-wiki]:https://github.com/abhiTronix/vidgear/wiki/FFmpeg-Installation [youtube-wiki]:https://github.com/abhiTronix/vidgear/wiki/CamGear#2-camgear-api-with-live-youtube-piplineing-using-video-url [TQM-wiki]:https://github.com/abhiTronix/vidgear/wiki/Threaded-Queue-Mode diff --git a/changelog.md b/changelog.md index a6d3d2326..6187fe92b 100644 --- a/changelog.md +++ b/changelog.md @@ -7,11 +7,11 @@ * Added exclusive `secure_mode` param for enabling it. * Added support for two most powerful `Stonehouse` & `Ironhouse` ZMQ security mechanisms. * Added smart auth-certificates/key generation and validation features. - * Implemented Robust Multi-Server support for NetGear API. + * Implemented Robust Multi-Server support for NetGear API: * Enables Multiple Servers messaging support with a single client. * Added exclusive `multiserver_mode` param for enabling it. * Added ability to send additional data of any datatype along with the frame in realtime in this mode. - * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming. + * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming in NetGear API. * Added VidGear's official native support for MacOS environment. ### Updates/Improvements: @@ -39,6 +39,7 @@ * PR #42 * PR #44 * PR #52 + * PR #55 :warning: PyPi Release does NOT contain Tests and Scripts! diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index e921323ec..7f6088815 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -42,7 +42,7 @@ raise ImportError('OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') + raise ImportError('Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index b0d0c1fc4..bc46c688e 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -43,7 +43,7 @@ else: raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-contrib-python` command.') + raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index 0a87b1e26..a96d204e5 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -23,7 +23,7 @@ =============================================== """ -import os, pytest, tempfile, shutil, platform, tempfile +import os, pytest, tempfile, shutil, platform from vidgear.gears.helper import download_ffmpeg_binaries from vidgear.gears.helper import validate_ffmpeg From d9a4c133f6ab73468cd4fc23ebc8e5f3a054004e Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 30 Oct 2019 14:06:06 +0530 Subject: [PATCH 17/39] PiGear: Bugfixes for #61 - Fixed ValueError on picamera API failure. - Improved Safe Handling of any stream related api failure. - PiGear will now raise RuntimeError Exception on any API failure. --- vidgear/gears/pigear.py | 88 ++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index 7f6088815..8d9a3a631 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -72,7 +72,7 @@ class PiGear: """ - def __init__(self, resolution=(640, 480), framerate=25, colorspace = None, logging = False, time_delay = 0, **options): + def __init__(self, resolution=(640, 480), framerate=30, colorspace = None, logging = False, time_delay = 0, **options): try: import picamera @@ -115,12 +115,15 @@ def __init__(self, resolution=(640, 480), framerate=25, colorspace = None, loggi self.rawCapture = PiRGBArray(self.camera, size=resolution) self.stream = self.camera.capture_continuous(self.rawCapture,format="bgr", use_video_port=True) - #frame variable initialization - for stream in self.stream: + #frame variable initialization + try: + stream = next(self.stream) self.frame = stream.array self.rawCapture.seek(0) self.rawCapture.truncate() - break + except Exception as e: + print(e) + raise RuntimeError('[ERROR]: Camera Module failed to initialize!') # applying time delay to warm-up picamera only if specified if time_delay: @@ -154,50 +157,53 @@ def update(self): Update frames from stream """ # keep looping infinitely until the thread is terminated - try: - for stream in self.stream: + while True: + + #check for termination flag + if self.terminate: break + + try: + #Try to iterate next frame from generator + stream = next(self.stream) + except Exception as e: + #Handle if Something is wrong + if isinstance(e, (StopIteration, ValueError)): + #raise picamera API's failures + raise RuntimeError('[ERROR]: Camera Module API failure occured!') + if self.logging: print(traceback.format_exc()) #log traceback for debugging errors + break + # grab the frame from the stream and clear the stream in # preparation for the next frame - if stream is None: - if self.logging: - print('[LOG]: The Camera Module is not working Properly!') - self.terminate = True - if self.terminate: - break - frame = stream.array - self.rawCapture.seek(0) - self.rawCapture.truncate() - - if not(self.color_space is None): - # apply colorspace to frames - color_frame = None - try: - if isinstance(self.color_space, int): - color_frame = cv2.cvtColor(frame, self.color_space) - else: - self.color_space = None - if self.logging: - print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) - - except Exception as e: - # Catch if any error occurred + frame = stream.array + self.rawCapture.seek(0) + self.rawCapture.truncate() + + #apply colorspace if specified + if not(self.color_space is None): + # apply colorspace to frames + color_frame = None + try: + if isinstance(self.color_space, int): + color_frame = cv2.cvtColor(frame, self.color_space) + else: self.color_space = None if self.logging: - print(e) - print('[LOG]: Input Colorspace is not a valid Colorspace!') + print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + + except Exception as e: + # Catch if any error occurred + self.color_space = None + if self.logging: + print(e) + print('[LOG]: Input Colorspace is not a valid Colorspace!') - if not(color_frame is None): - self.frame = color_frame - else: - self.frame = frame + if not(color_frame is None): + self.frame = color_frame else: self.frame = frame - - except Exception as e: - if self.logging: - print(traceback.format_exc()) - self.terminate =True - pass + else: + self.frame = frame # release picamera resources self.stream.close() From 46a472f52d660f0cc3fac62cc9c772882061ffc8 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 6 Nov 2019 10:09:38 +0530 Subject: [PATCH 18/39] Major Updates and BugFixes for PiGear - added new `camera_num` to support multiple Picameras(#64) - moved thread exceptions to main thread and then re-raised(#61) - added new threaded timeout function to handle any hardware failures/frozen threads(#61) - PiGear will not exit safely if Picamera ribbon cable is pulled out to save resources - Minor tweaks for more robust overall error handling. --- vidgear/gears/pigear.py | 148 +++++++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 32 deletions(-) diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index 8d9a3a631..ad783349e 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -26,7 +26,7 @@ # import the packages from threading import Thread from pkg_resources import parse_version -import traceback +import sys, time, platform from .helper import capPropId @@ -36,9 +36,7 @@ import cv2 # check whether OpenCV Binaries are 3.x+ - if parse_version(cv2.__version__) >= parse_version('3'): - pass - else: + if parse_version(cv2.__version__) < parse_version('3'): raise ImportError('OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: @@ -72,21 +70,29 @@ class PiGear: """ - def __init__(self, resolution=(640, 480), framerate=30, colorspace = None, logging = False, time_delay = 0, **options): + def __init__(self, camera_num = 0, resolution=(640, 480), framerate=30, colorspace = None, logging = False, time_delay = 0, **options): try: import picamera from picamera.array import PiRGBArray from picamera import PiCamera - #print(cv2.__version__) - except ImportError as error: - # Output expected ImportErrors. - raise ImportError('[ERROR]: Failed to detect Picamera executables, install it with "pip install picamera" command.') - - # initialize the picamera stream - self.camera = PiCamera() - self.camera.resolution = resolution - self.camera.framerate = framerate + except Exception as error: + if isinstance(error, ImportError): + # Output expected ImportErrors. + raise ImportError('[ERROR]: Failed to detect Picamera executables, install it with "pip install picamera" command.') + else: + #Handle any API errors + raise RuntimeError('[ERROR]: Picamera API failure: {}'.format(error)) + + # initialize the picamera stream at given index + self.camera = None + if isinstance(camera_num, int) and camera_num >= 0: + self.camera = PiCamera(camera_num = camera_num) + self.camera.resolution = resolution + self.camera.framerate = framerate + if logging: print("[LOG]: Activating Pi camera at index: {}".format(camera_num)) + else: + raise ValueError("[ERROR]: `camera_num` value is invalid, Kindly read docs!") #initialize framerate variable self.framerate = framerate @@ -97,7 +103,18 @@ def __init__(self, resolution=(640, 480), framerate=30, colorspace = None, loggi #reformat dict options = {k.strip(): v for k,v in options.items()} - try: + #intialize timeout variable(handles hardware failures) + self.failure_timeout = 2.0 + + #User-Defined parameter + if options and "HWFAILURE_TIMEOUT" in options: + #for altering timeout variable manually + if isinstance(options["HWFAILURE_TIMEOUT"],(int, float)): + if not(10.0 > options["HWFAILURE_TIMEOUT"] > 1.0): raise ValueError('[ERROR]: `HWFAILURE_TIMEOUT` value can only be between 1.0 ~ 10.0') + self.failure_timeout = options["HWFAILURE_TIMEOUT"] #assign special parameter + del options["HWFAILURE_TIMEOUT"] #clean + + try: # apply attributes to source if specified for key, value in options.items(): setattr(self.camera, key, value) @@ -108,11 +125,10 @@ def __init__(self, resolution=(640, 480), framerate=30, colorspace = None, loggi except Exception as e: # Catch if any error occurred - if logging: - print(e) + if logging: print(e) # enable rgb capture array thread and capture stream - self.rawCapture = PiRGBArray(self.camera, size=resolution) + self.rawCapture = PiRGBArray(self.camera, size = resolution) self.stream = self.camera.capture_continuous(self.rawCapture,format="bgr", use_video_port=True) #frame variable initialization @@ -127,15 +143,21 @@ def __init__(self, resolution=(640, 480), framerate=30, colorspace = None, loggi # applying time delay to warm-up picamera only if specified if time_delay: - import time time.sleep(time_delay) #thread initialization self.thread = None + #timer thread initialization(Keeps check on frozen thread) + self._timer = None + self.t_elasped = 0.0 #records time taken by thread + # enable logging if specified self.logging = logging + # catching thread exceptions + self.exceptions = None + # initialize termination flag self.terminate = False @@ -145,18 +167,45 @@ def start(self): """ start the thread to read frames from the video stream """ + #Start frame producer thread self.thread = Thread(target=self.update, args=()) self.thread.daemon = True self.thread.start() + + #Start internal timer thread + self._timer = Thread(target=self._timeit, args=()) + self._timer.daemon = True + self._timer.start() + return self + def _timeit(self): + """ + Keep checks on Thread excecution timing + """ + #assign current time + self.t_elasped = time.time() + + #loop until termainated + while not(self.terminate): + #check for frozen thread + if time.time() - self.t_elasped > self.failure_timeout: + #log failure + if self.logging: print("[WARNING]: Camera Module Disconnected!") + + #prepare for clean exit + self.exceptions = True + self.terminate = True #self-terminate + + + def update(self): """ Update frames from stream """ - # keep looping infinitely until the thread is terminated + #keep looping infinitely until the thread is terminated while True: #check for termination flag @@ -165,13 +214,13 @@ def update(self): try: #Try to iterate next frame from generator stream = next(self.stream) - except Exception as e: - #Handle if Something is wrong - if isinstance(e, (StopIteration, ValueError)): - #raise picamera API's failures - raise RuntimeError('[ERROR]: Camera Module API failure occured!') - if self.logging: print(traceback.format_exc()) #log traceback for debugging errors - break + except Exception: + #catch and save any exceptions + self.exceptions = sys.exc_info() + break #exit + + #update timer + self.t_elasped = time.time() # grab the frame from the stream and clear the stream in # preparation for the next frame @@ -205,6 +254,9 @@ def update(self): else: self.frame = frame + # terminate processes + if not(self.terminate): self.terminate = True + # release picamera resources self.stream.close() self.rawCapture.close() @@ -216,6 +268,21 @@ def read(self): """ return the frame """ + #check if there are any thread exceptions + if not(self.exceptions is None): + if isinstance(self.exceptions, bool): + #clear frame + self.frame = None + #notify user about hardware failure + raise SystemError('[ERROR]: Hardware failure occurred, Kindly reconnect Camera Module and restart your Pi!') + else: + #clear frame + self.frame = None + # re-raise error for debugging + error_msg = "[ERROR]: Camera Module API failure occured: {}".format(self.exceptions[1]) + raise RuntimeError(error_msg).with_traceback(self.exceptions[2]) + + # return the frame return self.frame @@ -224,10 +291,27 @@ def stop(self): """ Terminates the Read process """ - # indicate that the thread should be terminated + if self.logging: print("[LOG]: Terminating PiGear Process.") + + # make sure that the threads should be terminated self.terminate = True - # wait until stream resources are released (producer thread might be still grabbing frame) - if self.thread is not None: - self.thread.join() - #properly handle thread exit + + #stop timer thread + self._timer.join() + + if self.thread is not None: + #check if hardware failure occured + if not(self.exceptions is None) and isinstance(self.exceptions, bool): + # force release picamera resources + self.stream.close() + self.rawCapture.close() + self.camera.close() + + #properly handle thread exit + self.thread.terminate() + self.thread.wait() #wait if still process is still processing some information + self.thread = None + else: + #properly handle thread exit + self.thread.join() From 214b729230b70bfe46b731f8b8034f1bb6be3023 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 13 Nov 2019 14:48:49 +0530 Subject: [PATCH 19/39] Enhancement: Introducing real-time Encoding/Decoding for NetGear API - added compression support with on-the-fly flexible frame encoding on server side(#65) - added initial support for `jpg,png,bmp` encoding formats - added exclusive options attribute `compression_format` & `compression_param` to tweak this feature - client side will now decode frame automatically based on the encoding - Updated docs - Minor fixes --- vidgear/gears/netgear.py | 54 ++++++++++++++++++++++++++++++++++------ vidgear/gears/pigear.py | 2 +- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index b01989eab..af65d5883 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -189,6 +189,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.auth_secretkeys_dir = '' #handles valid ZMQ private certificates dir overwrite_cert = False #checks if certificates overwriting allowed custom_cert_location = '' #handles custom ZMQ certificates path + + #define stream compression handlers + self.compression = '' #disabled by default + self.compression_params = None try: #reformat dict @@ -223,6 +227,22 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # enable/disable auth certificate overwriting overwrite_cert = value + # handle encoding and decoding if specified + elif key == 'compression_format' and isinstance(value,str) and value.lower().strip() in ['.jpg', '.jpeg', '.bmp', '.png']: #few are supported + # enable encoding + if not(receive_mode): self.compression = value.lower().strip() + elif key == 'compression_param': + # specifiy encoding/decoding params + if receive_mode and isinstance(value, int): + self.compression_params = value + if logging: print("[LOG]: Decoding flag: {}.".format(value)) + elif not(receive_mode) and isinstance(value, (list,tuple)): + if logging: print("[LOG]: Encoding parameters: {}.".format(value)) + self.compression_params = list(value) + else: + if logging: print("[WARNING]: Invalid compression parameters: {} skipped!".format(value)) + self.compression_params = cv2.IMREAD_COLOR if receive_mode else [] # skip to defaults + # various ZMQ flags elif key == 'flag' and isinstance(value, int): self.msg_flag = value @@ -552,6 +572,16 @@ def update(self): # reshape frame frame = frame_buffer.reshape(msg_json['shape']) + #check if encoding was enabled + if msg_json['compression']: + frame = cv2.imdecode(frame, self.compression_params) + #check if valid frame returned + if frame is None: + #otherwise raise error and exit + raise ValueError("[ERROR]: `{}` Frame Decoding failed with Parameter: {}".format(msg_json['compression'], self.compression_params)) + self.terminate = True + continue + if self.multiserver_mode: # check if multiserver_mode @@ -579,10 +609,8 @@ def recv(self): return the recovered frame """ # check whether `receive mode` is activated - if self.receive_mode: - pass - else: - #otherwise raise value error and exit + if not(self.receive_mode): + #raise value error and exit raise ValueError('[ERROR]: `recv()` function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') self.terminate = True # check whether or not termination flag is enabled @@ -605,10 +633,8 @@ def send(self, frame, message = None): :param message(string/int): additional message for the client(s) """ # check whether `receive_mode` is disabled - if not self.receive_mode: - pass - else: - #otherwise raise value error and exit + if self.receive_mode: + #raise value error and exit raise ValueError('[ERROR]: `send()` function cannot be used while receive_mode is enabled. Kindly refer vidgear docs!') self.terminate = True @@ -623,10 +649,21 @@ def send(self, frame, message = None): #otherwise make it contiguous frame = np.ascontiguousarray(frame, dtype=frame.dtype) + #handle encoding + if self.compression: + retval, frame = cv2.imencode(self.compression, frame, self.compression_params) + #check if it works + if not(retval): + #otherwise raise error and exit + raise ValueError("[ERROR]: Frame Encoding failed with format: {} and Parameters: {}".format(self.compression, self.compression_params)) + self.terminate = True + + #check if multiserver_mode is activated if self.multiserver_mode: # prepare the exclusive json dict and assign values with unique port msg_dict = dict(terminate_flag = exit_flag, + compression=str(self.compression), port = self.port, pattern = str(self.pattern), message = message if not(message is None) else '', @@ -635,6 +672,7 @@ def send(self, frame, message = None): else: # otherwise prepare normal json dict and assign values msg_dict = dict(terminate_flag = exit_flag, + compression=str(self.compression), message = message if not(message is None) else '', pattern = str(self.pattern), dtype = str(frame.dtype), diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index ad783349e..e18a72cc0 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -26,7 +26,7 @@ # import the packages from threading import Thread from pkg_resources import parse_version -import sys, time, platform +import sys, time from .helper import capPropId From 1c5254ab992c50d55ba27e364f70fb66e8b70dd8 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sun, 1 Dec 2019 16:19:54 +0530 Subject: [PATCH 20/39] Major Updates and Bugfixes for NetGear API - Updates: - Introducing Bi-Directional mode for bi-directional data transmission in NetGear(#75) - Added new `return_data` parameter to `recv()` function. - Added new `bidirectional_mode` for enabling this mode. - Added support for `PAIR` & `REQ/REP` patterns for this mode - Added support for all python datatypes. - Added support for `REQ/REP` pattern for Multiserver Mode - BugFixes: - Reimplemented `Pub/Sub` pattern for smoother performance(#70) - Fixed `multiserver_mode` not working properly over some networks(#57) - Nemorous minor bugfixes and optimizations for robust output - Repaired broken error handling and frozen threads --- vidgear/gears/netgear.py | 340 +++++++++++++++++++++------------------ 1 file changed, 182 insertions(+), 158 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index af65d5883..a9bc30acf 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -28,6 +28,7 @@ from pkg_resources import parse_version from .helper import check_python_version from .helper import generate_auth_certificates +from collections import deque import numpy as np import time import os @@ -135,14 +136,6 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #raise error raise ImportError('[ERROR]: pyzmq python library not installed. Kindly install it with `pip install pyzmq` command.') - #log and enable threaded queue mode - if logging: - print('[LOG]: Threaded Queue Mode is enabled by default for NetGear.') - #import deque - from collections import deque - #define deque and assign it to global var - self.queue = deque(maxlen=96) #max len 96 to check overflow - #define valid messaging patterns => `0`: zmq.PAIR, `1`:(zmq.REQ,zmq.REP), and `1`:(zmq.SUB,zmq.PUB) valid_messaging_patterns = {0:(zmq.PAIR,zmq.PAIR), 1:(zmq.REQ,zmq.REP), 2:(zmq.PUB,zmq.SUB)} @@ -157,9 +150,8 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #otherwise default to 0:`zmq.PAIR` self.pattern = 0 msg_pattern = valid_messaging_patterns[self.pattern] - if logging: - #log it - print('[LOG]: Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') + #log it + if logging: print('[LOG]: Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') #check whether user-defined messaging protocol is valid if protocol in ['tcp', 'upd', 'pgm', 'inproc', 'ipc']: @@ -167,20 +159,23 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r else: # else default to `tcp` protocol protocol = 'tcp' - if logging: - #log it - print('[LOG]: protocol is not valid or provided. Defaulting to `tcp` protocol!') + #log it + if logging: print('[LOG]: protocol is not valid or provided. Defaulting to `tcp` protocol!') #generate random device id self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) self.msg_flag = 0 #handles connection flags - self.msg_copy = False #handles whether to copy data + self.msg_copy = True #handles whether to copy data self.msg_track = False #handles whether to track packets self.multiserver_mode = False #handles multiserver_mode state recv_filter = '' #user-defined filter to allow specific port/servers only in multiserver_mode + #define bi-directional data transmission mode + self.bi_mode = False #handles bi_mode state + self.bi_data = None #handles return data + #define valid ZMQ security mechanisms => `0`: Grasslands, `1`:StoneHouse, and `1`:IronHouse valid_security_mech = {0:'Grasslands', 1:'StoneHouse', 2:'IronHouse'} self.secure_mode = 0 #handles ZMQ security layer status @@ -194,68 +189,78 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.compression = '' #disabled by default self.compression_params = None - try: - #reformat dict - options = {k.lower().strip(): v for k,v in options.items()} + #reformat dict + options = {k.lower().strip(): v for k,v in options.items()} - # assign values to global variables if specified and valid - for key, value in options.items(): - if key == 'multiserver_mode' and isinstance(value, bool) and self.pattern == 2: + # assign values to global variables if specified and valid + for key, value in options.items(): + if key == 'multiserver_mode' and isinstance(value, bool): + if pattern > 0: # multi-server mode self.multiserver_mode = value - elif key == 'filter' and isinstance(value, str): - #custom filter in multi-server mode - recv_filter = value - - elif key == 'secure_mode' and isinstance(value,int) and (value in valid_security_mech): - #secure mode - try: - assert check_python_version() >= 3, "[ERROR]: ZMQ Security feature is not available with python version < 3.0." - assert zmq.zmq_version_info() >= (4,0), "[ERROR]: ZMQ Security feature is not supported in libzmq version < 4.0." - self.secure_mode = value - except AssertionError as e: - print(e) - elif key == 'custom_cert_location' and isinstance(value,str): - # custom auth certificates path - try: - assert os.access(value, os.W_OK), "[ERROR]: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(value) - assert not(os.path.isfile(value)), "[ERROR]: `custom_cert_location` value must be the path to a directory and not to a file!" - custom_cert_location = os.path.abspath(value) - except AssertionError as e: - print(e) - elif key == 'overwrite_cert' and isinstance(value,bool): - # enable/disable auth certificate overwriting - overwrite_cert = value - - # handle encoding and decoding if specified - elif key == 'compression_format' and isinstance(value,str) and value.lower().strip() in ['.jpg', '.jpeg', '.bmp', '.png']: #few are supported - # enable encoding - if not(receive_mode): self.compression = value.lower().strip() - elif key == 'compression_param': - # specifiy encoding/decoding params - if receive_mode and isinstance(value, int): - self.compression_params = value - if logging: print("[LOG]: Decoding flag: {}.".format(value)) - elif not(receive_mode) and isinstance(value, (list,tuple)): - if logging: print("[LOG]: Encoding parameters: {}.".format(value)) - self.compression_params = list(value) - else: - if logging: print("[WARNING]: Invalid compression parameters: {} skipped!".format(value)) - self.compression_params = cv2.IMREAD_COLOR if receive_mode else [] # skip to defaults - - # various ZMQ flags - elif key == 'flag' and isinstance(value, int): - self.msg_flag = value - elif key == 'copy' and isinstance(value, bool): - self.msg_copy = value - elif key == 'track' and isinstance(value, bool): - self.msg_track = value else: - pass - except Exception as e: - # Catch if any error occurred - if logging: - print(e) + self.multiserver_mode = False + print('[ALERT]: Multi-Server is disabled!') + raise ValueError('[ERROR]: `{}` pattern is not valid when Multi-Server Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) + elif key == 'filter' and isinstance(value, str): + #custom filter in multi-server mode + recv_filter = value + + elif key == 'secure_mode' and isinstance(value,int) and (value in valid_security_mech): + #secure mode + try: + assert check_python_version() >= 3, "[ERROR]: ZMQ Security feature is not available with python version < 3.0." + assert zmq.zmq_version_info() >= (4,0), "[ERROR]: ZMQ Security feature is not supported in libzmq version < 4.0." + self.secure_mode = value + except Exception as e: + print(e) + elif key == 'custom_cert_location' and isinstance(value,str): + # custom auth certificates path + try: + assert os.access(value, os.W_OK), "[ERROR]: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(value) + assert not(os.path.isfile(value)), "[ERROR]: `custom_cert_location` value must be the path to a directory and not to a file!" + custom_cert_location = os.path.abspath(value) + except Exception as e: + print(e) + elif key == 'overwrite_cert' and isinstance(value,bool): + # enable/disable auth certificate overwriting + overwrite_cert = value + + # handle encoding and decoding if specified + elif key == 'compression_format' and isinstance(value,str) and value.lower().strip() in ['.jpg', '.jpeg', '.bmp', '.png']: #few are supported + # enable encoding + if not(receive_mode): self.compression = value.lower().strip() + elif key == 'compression_param': + # specify encoding/decoding params + if receive_mode and isinstance(value, int): + self.compression_params = value + if logging: print("[LOG]: Decoding flag: {}.".format(value)) + elif not(receive_mode) and isinstance(value, (list,tuple)): + if logging: print("[LOG]: Encoding parameters: {}.".format(value)) + self.compression_params = list(value) + else: + if logging: print("[WARNING]: Invalid compression parameters: {} skipped!".format(value)) + self.compression_params = cv2.IMREAD_COLOR if receive_mode else [] # skip to defaults + + # enable bi-directional data transmission if specified + elif key == 'bidirectional_mode' and isinstance(value, bool): + # check if pattern is valid + if pattern < 2: + self.bi_mode = True + else: + self.bi_mode = False + print('[ALERT]: Bi-Directional data transmission is disabled!') + raise ValueError('[ERROR]: `{}` pattern is not valid when Bi-Directional Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) + + # various ZMQ flags + elif key == 'flag' and isinstance(value, int): + self.msg_flag = value + elif key == 'copy' and isinstance(value, bool): + self.msg_copy = value + elif key == 'track' and isinstance(value, bool): + self.msg_track = value + else: + pass #handle secure mode if self.secure_mode: @@ -291,7 +296,14 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log if disabled if logging: print('[LOG]: ZMQ Security Mechanism is disabled for this connection!') - + #disable bi_mode if multi-server is enabled + if self.bi_mode: + if self.multiserver_mode: + self.bi_mode = False + print('[ALERT]: Bi-Directional Data Transmission is disabled when Multi-Server Mode is Enabled due to incompatibility!') + else: + if logging: print('[LOG]: Bi-Directional Data Transmission is enabled for this connection!') + # enable logging if specified self.logging = logging @@ -319,7 +331,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # check if unique server port address list/tuple is assigned or not in multiserver_mode if port is None or not isinstance(port, (tuple, list)): # raise error if not - raise ValueError('[ERROR]: Incorrect port value! Kindly provide a list/tuple of ports at Receiver-end while Multi-Server mode is enabled. For more information refer VidGear docs.') + raise ValueError('[ERROR]: Incorrect port value! Kindly provide a list/tuple of ports while Multi-Server mode is enabled. For more information refer VidGear docs.') else: #otherwise log it print('[LOG]: Enabling Multi-Server Mode at PORTS: {}!'.format(port)) @@ -347,29 +359,30 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[1]) + if self.pattern == 2: self.msg_socket.set_hwm(1) if self.multiserver_mode: # if multiserver_mode is enabled, then assign port addresses to zmq socket for pt in port: # enable specified secure mode for the zmq socket if self.secure_mode > 0: - # load client key - client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") - client_public, client_secret = zmq.auth.load_certificate(client_secret_file) - # load all CURVE keys - self.msg_socket.curve_secretkey = client_secret - self.msg_socket.curve_publickey = client_public # load server key - server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") - server_public, _ = zmq.auth.load_certificate(server_public_file) - # inject public key to make a CURVE connection. - self.msg_socket.curve_serverkey = server_public + server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") + server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + # load all CURVE keys + self.msg_socket.curve_secretkey = server_secret + self.msg_socket.curve_publickey = server_public + # enable CURVE connection for this socket + self.msg_socket.curve_server = True + + # define socket options + if self.pattern == 2: self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) + + # bind socket to given server protocol, address and ports + self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(pt)) - # connect socket to given server protocol, address and ports - self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(pt)) - self.msg_socket.setsockopt(zmq.LINGER, 0) - # define socket options - self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) + # define socket optimizer + self.msg_socket.setsockopt(zmq.LINGER, 0) else: # enable specified secure mode for the zmq socket @@ -383,14 +396,15 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # enable CURVE connection for this socket self.msg_socket.curve_server = True + # define exclusive socket options for patterns + if self.pattern == 2: self.msg_socket.setsockopt_string(zmq.SUBSCRIBE,'') + # bind socket to given protocol, address and port normally self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) - # define exclusive socket options for patterns - if self.pattern == 2: - self.msg_socket.setsockopt_string(zmq.SUBSCRIBE,'') - else: - self.msg_socket.setsockopt(zmq.LINGER, 0) + # define socket optimizer + self.msg_socket.setsockopt(zmq.LINGER, 0) + except Exception as e: # otherwise raise value error if errored if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) @@ -399,6 +413,11 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r else: raise ValueError('[ERROR]: Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + #log and enable threaded queue mode + if logging: print('[LOG]: Threaded Queue Mode is enabled by default for NetGear.') + #define deque and assign it to global var + self.queue = deque(maxlen=96) #max len 96 to check overflow + # initialize and start threading instance self.thread = Thread(target=self.update, args=()) self.thread.daemon = True @@ -413,7 +432,6 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r print('[LOG]: Receive Mode is activated successfully!') else: #otherwise default to `Send Mode` - if address is None: #define address address = '*' if self.multiserver_mode else 'localhost' @@ -422,7 +440,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # check if unique server port address is assigned or not in multiserver_mode if port is None: #raise error is not - raise ValueError('Incorrect port value! Kindly provide a unique & valid port value at Server-end while Multi-Server mode is enabled. For more information refer VidGear docs.') + raise ValueError('[ERROR]: Kindly provide a unique & valid port value at Server-end. For more information refer VidGear docs.') else: #otherwise log it print('[LOG]: Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) @@ -451,45 +469,32 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # initialize and define thread-safe messaging socket self.msg_socket = self.msg_context.socket(msg_pattern[0]) - if self.multiserver_mode: - # enable specified secure mode for the zmq socket - if self.secure_mode > 0: - # load server key - server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") - server_public, server_secret = zmq.auth.load_certificate(server_secret_file) - # load all CURVE keys - self.msg_socket.curve_secretkey = server_secret - self.msg_socket.curve_publickey = server_public - # enable CURVE connection for this socket - self.msg_socket.curve_server = True - # connect socket to protocol, address and a unique port if multiserver_mode is activated - self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) - else: - - if self.pattern == 1: - # if pattern is 1, define additional flags - self.msg_socket.REQ_RELAXED = True - self.msg_socket.REQ_CORRELATE = True - - # enable specified secure mode for the zmq socket - if self.secure_mode > 0: - # load client key - client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") - client_public, client_secret = zmq.auth.load_certificate(client_secret_file) - # load all CURVE keys - self.msg_socket.curve_secretkey = client_secret - self.msg_socket.curve_publickey = client_public - # load server key - server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") - server_public, _ = zmq.auth.load_certificate(server_public_file) - # inject public key to make a CURVE connection. - self.msg_socket.curve_serverkey = server_public - - # connect socket to given protocol, address and port - self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) + if self.pattern == 1: + # if pattern is 1, define additional flags + self.msg_socket.REQ_RELAXED = True + self.msg_socket.REQ_CORRELATE = True + if self.pattern == 2: self.msg_socket.set_hwm(1) # if pattern is 2, define additional optimizer + + # enable specified secure mode for the zmq socket + if self.secure_mode > 0: + # load client key + client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + # load all CURVE keys + self.msg_socket.curve_secretkey = client_secret + self.msg_socket.curve_publickey = client_public + # load server key + server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") + server_public, _ = zmq.auth.load_certificate(server_public_file) + # inject public key to make a CURVE connection. + self.msg_socket.curve_serverkey = server_public + + # connect socket to given protocol, address and port + self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) + + # define socket options + self.msg_socket.setsockopt(zmq.LINGER, 0) - # define socket options - self.msg_socket.setsockopt(zmq.LINGER, 0) except Exception as e: #log if errored if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) @@ -511,6 +516,7 @@ def update(self): """ # initialize frame variable frame = None + # keep looping infinitely until the thread is terminated while not self.exit_loop: @@ -529,6 +535,7 @@ def update(self): #stop iterating if overflowing occurs time.sleep(0.000001) continue + # extract json data out of socket msg_json = self.msg_socket.recv_json(flags=self.msg_flag) @@ -542,6 +549,7 @@ def update(self): if not self.port_buffer: print('[WARNING]: Termination signal received from all Servers!!!') self.terminate = True #termination + if self.logging: print('[ALERT]: Termination signal received from server at port: {}!'.format(msg_json['port'])) continue else: if self.pattern == 1: @@ -549,13 +557,11 @@ def update(self): self.msg_socket.send_string('[INFO]: Termination signal received from server!') #termination self.terminate = msg_json['terminate_flag'] + if self.logging: print('[ALERT]: Termination signal received from server!') continue - try: - #check if pattern is same at both server's and client's end. - assert int(msg_json['pattern']) == self.pattern - except AssertionError as e: - #otherwise raise error and exit + #check if pattern is same at both server's and client's end. + if int(msg_json['pattern']) != self.pattern: raise ValueError("[ERROR]: Messaging patterns on both Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") self.terminate = True continue @@ -564,8 +570,14 @@ def update(self): msg_data = self.msg_socket.recv(flags=self.msg_flag, copy=self.msg_copy, track=self.msg_track) if self.pattern != 2: - # send confirmation message to server for debugging - self.msg_socket.send_string('[LOG]: Data received on device: {} !'.format(self.id)) + # check if bi-directional mode is enabled + if self.bi_mode: + # handle return data + bi_dict = dict(data = self.bi_data) + self.msg_socket.send_json(bi_dict, self.msg_flag) + else: + # send confirmation message to server + self.msg_socket.send_string('[LOG]: Data received on device: {} !'.format(self.id)) # recover frame from array buffer frame_buffer = np.frombuffer(msg_data, dtype=msg_json['dtype']) @@ -604,15 +616,21 @@ def update(self): - def recv(self): + def recv(self, return_data = None): """ return the recovered frame + + :param return_data: handles return data for bi-directional mode """ # check whether `receive mode` is activated if not(self.receive_mode): #raise value error and exit raise ValueError('[ERROR]: `recv()` function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') self.terminate = True + + #handle bi-directional return data + if (self.bi_mode and not(return_data is None)): self.bi_data = return_data + # check whether or not termination flag is enabled while not self.terminate: # check if queue is empty @@ -630,7 +648,7 @@ def send(self, frame, message = None): send the frames over the messaging network :param frame(ndarray): frame array to send - :param message(string/int): additional message for the client(s) + :param message(string/int): additional message for the client(s) """ # check whether `receive_mode` is disabled if self.receive_mode: @@ -640,14 +658,11 @@ def send(self, frame, message = None): # define exit_flag and assign value exit_flag = True if (frame is None or self.terminate) else False + #check whether exit_flag is False - if not exit_flag: + if not(exit_flag) and not(frame.flags['C_CONTIGUOUS']): #check whether the incoming frame is contiguous - if frame.flags['C_CONTIGUOUS']: - pass - else: - #otherwise make it contiguous - frame = np.ascontiguousarray(frame, dtype=frame.dtype) + frame = np.ascontiguousarray(frame, dtype=frame.dtype) #handle encoding if self.compression: @@ -685,12 +700,17 @@ def send(self, frame, message = None): # wait for confirmation if self.pattern != 2: - if self.logging: - # log confirmation - print(self.msg_socket.recv()) + #check if bi-directional data transmission is enabled + if self.bi_mode: + #handle return data + return_dict = self.msg_socket.recv_json(flags=self.msg_flag) + return return_dict['data'] if return_dict else None else: - # otherwise be quiet - self.msg_socket.recv() + #otherwise log normally + recv_confirmation = self.msg_socket.recv() + # log confirmation + if self.logging : print(recv_confirmation) + @@ -716,15 +736,19 @@ def close(self): self.thread = None #properly handle thread exit else: - # otherwise in `send_mode`, inform client(s) that the termination is reached + # indicate that process should be terminated self.terminate = True - #check if multiserver_mode if self.multiserver_mode: + #check if multiserver_mode # send termination flag to client with its unique port term_dict = dict(terminate_flag = True, port = self.port) else: # otherwise send termination flag to client term_dict = dict(terminate_flag = True) - self.msg_socket.send_json(term_dict) + # otherwise inform client(s) that the termination has been reached + if self.pattern == 2 or self.bi_mode: + for _ in range(500): self.msg_socket.send_json(term_dict) + else: + self.msg_socket.send_json(term_dict) # properly close the socket self.msg_socket.close() \ No newline at end of file From c15e1c001f57f270f073ca2cc10f7478574e1546 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sun, 1 Dec 2019 23:35:48 +0530 Subject: [PATCH 21/39] NetGear API: Added support for `message` parameter for non-exclusive modes --- vidgear/gears/netgear.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index a9bc30acf..7566783d2 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -608,8 +608,12 @@ def update(self): # append recovered unique port and frame to queue self.queue.append((msg_json['port'],frame)) else: - # append recovered frame to queue - self.queue.append(frame) + #extract if any message from server and display it + if msg_json['message']: + self.queue.append((msg_json['message'], frame)) + else: + # append recovered frame to queue + self.queue.append(frame) # finally properly close the socket self.msg_socket.close() From 68cde2f1af53c612a3bc6193d689fd0cd146c6a3 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 2 Dec 2019 11:49:38 +0530 Subject: [PATCH 22/39] Bash Script Fix: - Moved FFmpeg static binaries to more reliable source - Mirrored files on github.com --- scripts/bash/prepare_dataset.sh | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index cf806a73c..2b3f89807 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -16,12 +16,15 @@ TMPFOLDER=$(python -c 'import tempfile; print(tempfile.gettempdir())') # Creating necessary directories -mkdir -p $TMPFOLDER/Downloads -mkdir -p $TMPFOLDER/Downloads/{FFmpeg_static,Test_videos} +mkdir -p "$TMPFOLDER"/Downloads +mkdir -p "$TMPFOLDER"/Downloads/{FFmpeg_static,Test_videos} # Acknowledging machine architecture MACHINE_BIT=$(uname -m) +#Defining alternate ffmpeg static binaries date/version +ALTBINARIES_DATE=02-12-19 + # Acknowledging machine OS type case $(uname | tr '[:upper:]' '[:lower:]') in linux*) @@ -40,18 +43,18 @@ esac #Download and Configure FFmpeg Static -cd $TMPFOLDER/Downloads/FFmpeg_static +cd "$TMPFOLDER"/Downloads/FFmpeg_static if [ $OS_NAME = "linux" ]; then echo "Downloading Linux Static FFmpeg Binaries..." - if [ $MACHINE_BIT = "x86_64" ]; then - curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg-release-amd64-static.tar.xz + if [ "$MACHINE_BIT" = "x86_64" ]; then + curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-release-amd64-static.tar.xz -o ffmpeg-release-amd64-static.tar.xz tar -xJf ffmpeg-release-amd64-static.tar.xz rm *.tar.* mv ffmpeg* ffmpeg else - curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-i686-static.tar.xz -o ffmpeg-release-i686-static.tar.xz + curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-release-i686-static.tar.xz -o ffmpeg-release-i686-static.tar.xz tar -xJf ffmpeg-release-i686-static.tar.xz rm *.tar.* mv ffmpeg* ffmpeg @@ -60,13 +63,13 @@ if [ $OS_NAME = "linux" ]; then elif [ $OS_NAME = "windows" ]; then echo "Downloading Windows Static FFmpeg Binaries..." - if [ $MACHINE_BIT = "x86_64" ]; then - curl https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip -o ffmpeg-latest-win64-static.zip + if [ "$MACHINE_BIT" = "x86_64" ]; then + curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-win64-static.zip -o ffmpeg-latest-win64-static.zip unzip -qq ffmpeg-latest-win64-static.zip rm ffmpeg-latest-win64-static.zip mv ffmpeg-latest-win64-static ffmpeg else - curl https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-latest-win32-static.zip -o ffmpeg-latest-win32-static.zip + curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-win32-static.zip -o ffmpeg-latest-win32-static.zip unzip -qq ffmpeg-latest-win32-static.zip rm ffmpeg-latest-win32-static.zip mv ffmpeg-latest-win32-static ffmpeg @@ -75,7 +78,7 @@ elif [ $OS_NAME = "windows" ]; then else echo "Downloading MacOS64 Static FFmpeg Binary..." - curl -LO https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-latest-macos64-static.zip + curl -LO https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-macos64-static.zip unzip -qq ffmpeg-latest-macos64-static.zip rm ffmpeg-latest-macos64-static.zip mv ffmpeg-latest-macos64-static ffmpeg @@ -83,7 +86,7 @@ else fi # Downloading Test Data -cd $TMPFOLDER/Downloads/Test_videos +cd "$TMPFOLDER"/Downloads/Test_videos echo "Downloading Test-Data..." curl http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 -o BigBuckBunny.mp4 From 526a209c840193b0b4ee39c7317dd7622e8fbcf3 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 2 Dec 2019 16:17:20 +0530 Subject: [PATCH 23/39] Bash Script Fixes: Fixed curl flags --- scripts/bash/prepare_dataset.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index 2b3f89807..d530dcf36 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -49,12 +49,12 @@ if [ $OS_NAME = "linux" ]; then echo "Downloading Linux Static FFmpeg Binaries..." if [ "$MACHINE_BIT" = "x86_64" ]; then - curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-release-amd64-static.tar.xz -o ffmpeg-release-amd64-static.tar.xz + curl -L https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-release-amd64-static.tar.xz -o ffmpeg-release-amd64-static.tar.xz tar -xJf ffmpeg-release-amd64-static.tar.xz rm *.tar.* mv ffmpeg* ffmpeg else - curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-release-i686-static.tar.xz -o ffmpeg-release-i686-static.tar.xz + curl -L https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-release-i686-static.tar.xz -o ffmpeg-release-i686-static.tar.xz tar -xJf ffmpeg-release-i686-static.tar.xz rm *.tar.* mv ffmpeg* ffmpeg @@ -64,12 +64,12 @@ elif [ $OS_NAME = "windows" ]; then echo "Downloading Windows Static FFmpeg Binaries..." if [ "$MACHINE_BIT" = "x86_64" ]; then - curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-win64-static.zip -o ffmpeg-latest-win64-static.zip + curl -L https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-win64-static.zip -o ffmpeg-latest-win64-static.zip unzip -qq ffmpeg-latest-win64-static.zip rm ffmpeg-latest-win64-static.zip mv ffmpeg-latest-win64-static ffmpeg else - curl https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-win32-static.zip -o ffmpeg-latest-win32-static.zip + curl -L https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/ffmpeg-latest-win32-static.zip -o ffmpeg-latest-win32-static.zip unzip -qq ffmpeg-latest-win32-static.zip rm ffmpeg-latest-win32-static.zip mv ffmpeg-latest-win32-static ffmpeg From 4718d06ebed1331466bcff70fd6e8ce38b24ee3c Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 2 Dec 2019 16:55:30 +0530 Subject: [PATCH 24/39] Fix Bash Script: Fix unsecure apt sources --- scripts/bash/install_opencv.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/bash/install_opencv.sh b/scripts/bash/install_opencv.sh index fd6f400d1..138f2a895 100644 --- a/scripts/bash/install_opencv.sh +++ b/scripts/bash/install_opencv.sh @@ -31,17 +31,17 @@ echo "Installing OpenCV..." echo "Installing OpenCV Dependencies..." -sudo apt-get install -y build-essential cmake pkg-config gfortran +sudo apt-get install -y --allow-unauthenticated build-essential cmake pkg-config gfortran -sudo apt-get install -y yasm libv4l-dev libgtk-3-dev libtbb-dev +sudo apt-get install -y --allow-unauthenticated yasm libv4l-dev libgtk-3-dev libtbb-dev -sudo apt-get install -y libavcodec-dev libavformat-dev libswscale-dev libjasper-dev libopenexr-dev +sudo apt-get install -y --allow-unauthenticated libavcodec-dev libavformat-dev libswscale-dev libjasper-dev libopenexr-dev -sudo apt-get install -y libxvidcore-dev libx264-dev libatlas-base-dev libtiff5-dev python3-dev +sudo apt-get install -y --allow-unauthenticated libxvidcore-dev libx264-dev libatlas-base-dev libtiff5-dev python3-dev -sudo apt-get install -y zlib1g-dev libjpeg-dev checkinstall libwebp-dev libpng-dev libopenblas-base +sudo apt-get install -y --allow-unauthenticated zlib1g-dev libjpeg-dev checkinstall libwebp-dev libpng-dev libopenblas-base -sudo apt-get install -y libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools +sudo apt-get install -y --allow-unauthenticated libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools echo "Installing OpenCV Library" From 2e21e729a5b7e1f4064cad8c270c38a30ad1b5de Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 2 Dec 2019 18:29:40 +0530 Subject: [PATCH 25/39] NetGear API: moved `message` parameter support for non-exclusive primary modes under Bi-Directional Mode. --- vidgear/gears/netgear.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 7566783d2..c7ce0c535 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -608,11 +608,12 @@ def update(self): # append recovered unique port and frame to queue self.queue.append((msg_json['port'],frame)) else: - #extract if any message from server and display it - if msg_json['message']: + #extract if any message from server if Bi-Directional Mode is enabled + if self.bi_mode and msg_json['message']: + # append grouped frame and data to queue self.queue.append((msg_json['message'], frame)) else: - # append recovered frame to queue + # otherwise append recovered frame to queue self.queue.append(frame) # finally properly close the socket From fad7dee222ff940824344485abb357191319313e Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 3 Dec 2019 08:44:45 +0530 Subject: [PATCH 26/39] NetGear API: Fixed empty data bug at server --- vidgear/gears/netgear.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index c7ce0c535..2666d7821 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -686,14 +686,14 @@ def send(self, frame, message = None): compression=str(self.compression), port = self.port, pattern = str(self.pattern), - message = message if not(message is None) else '', + message = message, dtype = str(frame.dtype), shape = frame.shape) else: # otherwise prepare normal json dict and assign values msg_dict = dict(terminate_flag = exit_flag, compression=str(self.compression), - message = message if not(message is None) else '', + message = message, pattern = str(self.pattern), dtype = str(frame.dtype), shape = frame.shape) From 88482eb66403b97aa58f592cb1385ddf659f4ebe Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 3 Dec 2019 14:13:22 +0530 Subject: [PATCH 27/39] NetGear API: - Added `force_terminate` attribute flag for force socket termination at server if there's latency in network - fixes and updates for REQ/REP pattern in Multiserver-mode - Minor other bugfixes and doc updates --- vidgear/gears/netgear.py | 48 +++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 2666d7821..802724fd5 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -185,6 +185,9 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r overwrite_cert = False #checks if certificates overwriting allowed custom_cert_location = '' #handles custom ZMQ certificates path + #handle force socket termination if there's latency in network + self.force_close = False + #define stream compression handlers self.compression = '' #disabled by default self.compression_params = None @@ -252,6 +255,16 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r print('[ALERT]: Bi-Directional data transmission is disabled!') raise ValueError('[ERROR]: `{}` pattern is not valid when Bi-Directional Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) + # enable force socket closing if specified + elif key == 'force_terminate' and isinstance(value, bool): + # check if pattern is valid + if address is None and not(receive_mode): + self.force_close = False + print('[ALERT]: Force termination is disabled for local servers!') + else: + self.force_close = True + if logging: print("[LOG]: Force termination is enabled for this connection!") + # various ZMQ flags elif key == 'flag' and isinstance(value, int): self.msg_flag = value @@ -296,12 +309,16 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #log if disabled if logging: print('[LOG]: ZMQ Security Mechanism is disabled for this connection!') - #disable bi_mode if multi-server is enabled + #handle bi_mode if self.bi_mode: + #disable bi_mode if multi-server is enabled if self.multiserver_mode: self.bi_mode = False print('[ALERT]: Bi-Directional Data Transmission is disabled when Multi-Server Mode is Enabled due to incompatibility!') else: + #enable force termination by default + self.force_close = True + if logging: print("[LOG]: Force termination is enabled for this connection by default!") if logging: print('[LOG]: Bi-Directional Data Transmission is enabled for this connection!') # enable logging if specified @@ -323,8 +340,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if receive_mode: # if does than define connection address - if address is None: #define address - address = 'localhost' if self.multiserver_mode else '*' + if address is None: address = '*' #define address #check if multiserver_mode is enabled if self.multiserver_mode: @@ -425,15 +441,14 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if logging: #finally log progress - print('[LOG]: Successfully Binded to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + print('[LOG]: Successfully Binded to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) if self.secure_mode: print('[LOG]: Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') print('[LOG]: Device Unique ID is {}.'.format(self.id)) print('[LOG]: Receive Mode is activated successfully!') else: #otherwise default to `Send Mode` - if address is None: #define address - address = '*' if self.multiserver_mode else 'localhost' + if address is None: address = 'localhost' #define address #check if multiserver_mode is enabled if self.multiserver_mode: @@ -503,7 +518,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if logging: #finally log progress - print('[LOG]: Successfully connected to address: {}.'.format(protocol+'://' + str(address) + ':' + str(port))) + print('[LOG]: Successfully connected to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) if self.secure_mode: print('[LOG]: Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) print('[LOG]: This device Unique ID is {}.'.format(self.id)) print('[LOG]: Send Mode is successfully activated and ready to send data!') @@ -543,20 +558,23 @@ def update(self): if msg_json['terminate_flag']: #if multiserver_mode is enabled if self.multiserver_mode: - # check from which ports signal is received - self.port_buffer.remove(msg_json['port']) + # check and remove from which ports signal is received + if msg_json['port'] in self.port_buffer: + # if pattern is 1, then send back server the info about termination + if self.pattern == 1: self.msg_socket.send_string('[INFO]: Termination signal received at client!') + self.port_buffer.remove(msg_json['port']) + if self.logging: print('[ALERT]: Termination signal received from server at port: {}!'.format(msg_json['port'])) #if termination signal received from all servers then exit client. if not self.port_buffer: print('[WARNING]: Termination signal received from all Servers!!!') self.terminate = True #termination - if self.logging: print('[ALERT]: Termination signal received from server at port: {}!'.format(msg_json['port'])) continue else: - if self.pattern == 1: - # if pattern is 1, then send back server the info about termination - self.msg_socket.send_string('[INFO]: Termination signal received from server!') + # if pattern is 1, then send back server the info about termination + if self.pattern == 1: self.msg_socket.send_string('[INFO]: Termination signal received at client!') #termination - self.terminate = msg_json['terminate_flag'] + self.terminate = True + #notify client if self.logging: print('[ALERT]: Termination signal received from server!') continue @@ -751,7 +769,7 @@ def close(self): # otherwise send termination flag to client term_dict = dict(terminate_flag = True) # otherwise inform client(s) that the termination has been reached - if self.pattern == 2 or self.bi_mode: + if self.force_close: for _ in range(500): self.msg_socket.send_json(term_dict) else: self.msg_socket.send_json(term_dict) From c8a6339867877c9b9cd35c0de1307bb2209a2c03 Mon Sep 17 00:00:00 2001 From: Abhishek Thakur Date: Mon, 16 Dec 2019 03:10:47 +0000 Subject: [PATCH 28/39] Important Enhancements and BugFixes for next release (#77) - :warning: Dropped python 2.7 legacy support for vidgear. - Enhancements/Updates: - VideoGear API: * Added `framerate` global variable and removed redundant function. * Added `CROP_N_ZOOM` attribute in Videogear API for supporting Crop and Zoom feature. - WriteGear API: Added new `execute_ffmpeg_cmd` function to pass a custom command to its FFmpeg pipeline. - CLI & Tests updates: * Replaced python 3.5 matrices with latest python 3.8 matrices in Linux environment * Added full support for CODECOV in all CLI environments * Updated OpenCV to v4.2.0-pre(master branch). * Added various Netgear API tests * Added initial Screengear API test * More test RTSP feeds added with better error handling in CamGear network test * Added tests for ZMQ authentication certificate generation * Added badge and Minor doc updates - Stabilizer class Update: * Added new Crop and Zoom feature * Added `crop_n_zoom` param for enabling this feature * Updated docs * Overall APIs Code and Docs optimizations and minor tweaks - Bugfixes - NetGear API: * Fixed random freezing in `Secure Mode` and several related performance updates * Eliminated redundant code blocks * Disabled `overwrite_cert` for client-end - CamGear API: * Fixed Assertion error in CamGear API during colorspace manipulation (#78) * Implemented better error handling of colorspace in videocapture APIs * CamGear will now throw `RuntimeError` if source provided is invalid * Updates for threaded Queue mode in CamGear API for robust performance * Added additional logging messages for CamGear API * Fixed Code indentation in `setup.py` * Numerous Bug fixes --- .travis.yml | 33 +- README.md | 71 +++-- appveyor.yml | 9 +- changelog.md | 84 ++++- codecov.yml | 11 + scripts/bash/install_opencv.sh | 18 +- setup.cfg | 2 +- setup.py | 96 +++--- vidgear/gears/camgear.py | 78 +++-- vidgear/gears/helper.py | 50 ++- vidgear/gears/netgear.py | 46 +-- vidgear/gears/pigear.py | 49 +-- vidgear/gears/screengear.py | 35 +-- vidgear/gears/stabilizer.py | 55 +++- vidgear/gears/videogear.py | 31 +- vidgear/gears/writegear.py | 67 ++-- vidgear/tests/benchmark_tests/fps.py | 1 - .../test_benchmark_Videocapture.py | 2 +- .../test_benchmark_Videowriter.py | 14 +- .../test_benchmark_playback.py | 6 +- vidgear/tests/network_tests/__init__.py | 2 + vidgear/tests/network_tests/test_netgear.py | 296 ++++++++++++++++++ vidgear/tests/test_helper.py | 35 ++- .../tests/videocapture_tests/test_camgear.py | 53 ++-- .../videocapture_tests/test_screengear.py | 51 +++ .../videocapture_tests/test_videogear.py | 32 +- .../writer_tests/test_compression_mode.py | 55 +++- .../writer_tests/test_non_compression_mode.py | 4 +- 28 files changed, 933 insertions(+), 353 deletions(-) create mode 100644 codecov.yml create mode 100644 vidgear/tests/network_tests/__init__.py create mode 100644 vidgear/tests/network_tests/test_netgear.py create mode 100644 vidgear/tests/videocapture_tests/test_screengear.py diff --git a/.travis.yml b/.travis.yml index f01ef753f..f342eb9ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,18 +18,20 @@ matrix: osx_image: xcode11 env: PYTHON=37 - os: linux - python: "3.5" + dist: bionic + python: "3.6" language: python cache: pip - os: linux - python: "3.6" + dist: bionic + python: "3.7" language: python cache: pip - os: linux - dist: xenial - python: "3.7" + dist: bionic + python: "3.8" language: python - cache: pip + cache: pip @@ -37,6 +39,7 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install swig; brew install ffmpeg; + brew link ffmpeg; brew install unzip; curl -LO https://raw.githubusercontent.com/GiovanniBussi/macports-ci/master/macports-ci; source ./macports-ci install; @@ -50,9 +53,8 @@ before_install: chmod +x scripts/bash/prepare_dataset.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y; sudo apt-get update -q; - sudo apt-get install ffmpeg unzip wget -y; + sudo apt-get install unzip wget -y; sudo apt-get install dos2unix -y; dos2unix scripts/bash/prepare_dataset.sh; chmod +x scripts/bash/prepare_dataset.sh; @@ -71,7 +73,9 @@ install: pip install .; pip uninstall opencv-contrib-python -y; pip install six; + pip install codecov; pip install --upgrade pytest; + pip install --upgrade pytest-cov; pip install --upgrade youtube-dl; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then @@ -79,7 +83,9 @@ install: pip install --upgrade --user numpy; pip install --user .; pip install --user six; - pip install --upgrade --user pytest; + pip install --user codecov; + pip install --upgrade --user pytest; + pip install --upgrade --user pytest-cov; pip install --upgrade --user youtube-dl; fi @@ -93,9 +99,14 @@ before_script: fi script: + - function keep_alive() { while true; do echo -en "\a"; sleep 60; done } + - keep_alive & - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - travis_wait pytest -sv; + pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - python -m pytest -sv; - fi \ No newline at end of file + python -m pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/; + fi + +after_success: + - codecov \ No newline at end of file diff --git a/README.md b/README.md index 07046b7d7..b217d0147 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,9 @@ THE SOFTWARE. [Releases][release]   |   [Gears](#gears)   |   [Wiki Documentation][wiki]   |   [Installation](#installation)   |   [License](#license) -[![PyPi version][pypi-badge]][pypi] [![Build Status][travis-cli]][travis] [![Build Status][appveyor]][app] [![Say Thank you][Thank-you]][thanks] [![Twitter][twitter-badge]][twitter-intent] +[![Build Status][travis-cli]][travis] [![Codecov branch][codecov]][code] [![Build Status][appveyor]][app] + +[![PyPi version][pypi-badge]][pypi] [![Say Thank you][Thank-you]][thanks] [![Twitter][twitter-badge]][twitter-intent] [![Buy Me A Coffee][Coffee-badge]][coffee] @@ -102,16 +104,25 @@ The following **functional block diagram** clearly depicts the functioning of Vi   ## New Release SneekPeak : VidGear 0.1.6 - * Added powerful ZMQ Authentication & Data Encryption features for NetGear API: - * Added exclusive `secure_mode` param for enabling it. - * Added support for two most powerful `Stonehouse` & `Ironhouse` ZMQ security mechanisms. - * Added auto auth-certificates/key generation and validation feature. - * Implemented Robust Multi-Server support for NetGear API: - * Enables Multiple Servers messaging support with a single client. - * Added exclusive `multiserver_mode` param for enabling it. - * Added ability to send additional data of any datatype along with the frame in realtime in this mode. + +***:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27]!*** + +**NetGear API:** + * Added powerful ZMQ Authentication & Data Encryption features for NetGear API + * Added robust Multi-Server support for NetGear API. + * Added exclusive Bi-Directional Mode for bidirectional data transmission. + * Added frame-compression support with on-the-fly flexible encoding/decoding. * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming in NetGear API. - * Added VidGear's official native support for MacOS environment and [many more...](changelog.md) + +**PiGear API:** + * Added new threaded internal timing function for PiGear to handle any hardware failures/frozen threads + * PiGear will not exit safely with `SystemError` if Picamera ribbon cable is pulled out to save resources. + +**WriteGear API:** Added new `execute_ffmpeg_cmd` function to pass a custom command to its internal FFmpeg pipeline. + +**Stabilizer class:** Added new _Crop and Zoom_ feature. + +***Added VidGear's official native support for MacOS environment and [many more...](changelog.md)***   @@ -127,7 +138,7 @@ Before installing VidGear, you must verify that the following dependencies are m * **`OpenCV:`** VidGear must require OpenCV(3.0+) python enabled binaries to be installed on your machine for its core functions. For its installation, you can follow these online tutorials for [linux][OpenCV-linux] and [raspberry pi][OpenCV-pi], otherwise, install it via pip: ```sh - pip install -U opencv-python #or install opencv-contrib-python similarly + pip3 install -U opencv-python #or install opencv-contrib-python similarly ``` * **`FFmpeg:`** VidGear must require FFmpeg for its powerful video compression and encoding capabilities. :star2: Follow this [**FFmpeg wiki page**][ffmpeg-wiki] for its installation. :star2: @@ -135,26 +146,26 @@ Before installing VidGear, you must verify that the following dependencies are m * **`picamera:`** Required if using Raspberry Pi Camera Modules(_such as OmniVision OV5647 Camera Module_) with your Raspberry Pi machine. You can easily install it via pip: ```sh - pip install picamera + pip3 install picamera ``` Also, make sure to enable Raspberry Pi hardware-specific settings prior to using this library. * **`mss:`** Required for using Screen Casting. Install it via pip: ```sh - pip install mss + pip3 install mss ``` * **`pyzmq:`** Required for transferring live video frames through _ZeroMQ messaging system_ over the network. Install it via pip: ```sh - pip install pyzmq + pip3 install pyzmq ``` * **`pafy:`** Required for direct YouTube Video streaming capabilities. Both [`pafy`][pafy] and latest only [`youtube-dl`][yt-dl](_as pafy's backend_) libraries must be installed via pip as follows: ```sh - pip install pafy - pip install -U youtube-dl + pip3 install pafy + pip3 install -U youtube-dl ``` @@ -165,7 +176,7 @@ Before installing VidGear, you must verify that the following dependencies are m > Best option for **quickly** getting VidGear installed. ```sh - pip install vidgear + pip3 install vidgear ```   @@ -179,14 +190,14 @@ VidGear releases are available for download as packages in the [latest release][ ### Option 3: Clone the Repository -> Best option for **latest patches**(_maybe experimental_), or **contributing** to development. +> Best option for **latest patches & updates**(_but experimental_), or **contributing** to development. You can clone this repository's `testing` branch for development and thereby can install as follows: ```sh git clone https://github.com/abhiTronix/vidgear.git cd vidgear git checkout testing - pip install . + sudo pip3 install . ```   @@ -216,7 +227,7 @@ VidGear is built with multi-threaded **Gears** each with some unique function/me ### CamGear -CamGear supports a diverse range of video streams which can handle/control video stream almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), network stream URL such as `http(s), rtp, rstp, mms, etc.` In addition to this, it also supports live Gstreamer's RAW pipelines and YouTube video/livestreams URLs. CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs `pafy` python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear relies exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. +CamGear supports a diverse range of video streams which can handle/control video stream almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), network stream URL such as `http(s), rtp, rstp, rtmp, mms, etc.` In addition to this, it also supports live Gstreamer's RAW pipelines and YouTube video/livestreams URLs. CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs `pafy` python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear relies exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. **Following simplified functional block diagram depicts CamGear API's generalized working:** @@ -233,7 +244,7 @@ CamGear supports a diverse range of video streams which can handle/control video ### VideoGear -VideoGear API provides a special internal wrapper around VidGear's exclusive [**Video Stabilizer**][stablizer-wiki] class. Furthermore, VideoGear API can provide internal access to both [CamGear](#camgear) and [PiGear](#pigear) APIs separated by a special flag. Thereby, _this API holds the exclusive power for any incoming VideoStream from any source, whether it is live or not, to stabilize it directly with minimum latency and memory requirements._ +VideoGear API provides a special internal wrapper around VidGear's exclusive [**Video Stabilizer**][stablizer-wiki] class. Furthermore, VideoGear API can provide internal access to both [CamGear](#camgear) and [PiGear](#pigear) APIs separated by a special flag. Thereby, _this API holds the exclusive power for any incoming VideoStream from any source, whether it is live or not, to access and stabilize it directly with minimum latency and memory requirements._ **Below is a snapshot of a VideoGear Stabilizer in action:** @@ -300,7 +311,7 @@ stream_stab.stop() ### PiGear -PiGear is similar to CamGear but made to support various Raspberry Pi Camera Modules (such as [OmniVision OV5647 Camera Module][OV5647-picam] and [Sony IMX219 Camera Module][IMX219-picam]). To interface with these modules correctly, PiGear provides a flexible multi-threaded wrapper around complete [picamera][picamera] python library, and provides us the ability to exploit its various features like `brightness, saturation, sensor_mode, etc.` effortlessly. +PiGear is similar to CamGear but made to support various Raspberry Pi Camera Modules (such as [OmniVision OV5647 Camera Module][OV5647-picam] and [Sony IMX219 Camera Module][IMX219-picam]). To interface with these modules correctly, PiGear provides a flexible multi-threaded wrapper around complete [picamera][picamera] python library, and provides us the ability to exploit its various features like `brightness, saturation, sensor_mode, etc.` effortlessly. In addition to this, PiGear API provides excellent Error-Handling with features like a threaded internal timer that keeps active track of any frozen threads and handles hardware failures/frozen threads robustly thereby will exit safely if any failure occurs. So if you accidently pulled your camera cable out when running PiGear API in your script, instead of going into possible kernel panic due to IO error it will exit safely to save resources. **Following simplified functional block diagram depicts PiGear API:** @@ -392,7 +403,9 @@ WriteGear is undoubtedly the most powerful Video Processing Gear of them all. It ### NetGear -NetGear is exclusively designed to transfer video frames synchronously between interconnecting systems over the network in real-time. This is achieved by implementing a high-level wrapper around [PyZmQ][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. Furthermore, It also provides easy access to powerful, smart & secure ZeroMQ's Security Layers in NetGear API that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings cheap, standardized privacy and authentication for distributed systems over the network. On top of that, this API can robustly handle Multiple Servers at once, thereby providing access to seamless Live Streams of the various device in a network at the same time. +NetGear is exclusively designed to transfer video frames synchronously and asynchronously between interconnecting systems over the network in real-time. This is achieved by implementing a high-level wrapper around [PyZmQ][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. It provides seamless support for bidirectional data transmission between receiver(client) and sender(server) through bi-directional synchronous messaging patterns such as zmq.PAIR (ZMQ Pair Pattern) & zmq.REQ/zmq.REP (ZMQ Request/Reply Pattern). Plus also introduces real-time frame Encoding/Decoding compression capabilities for optimizing performance while sending the frames of large size directly over the network by encoding the frame before sending it and decoding it on the client's end automatically all in real-time. + +For security, NetGear also supports easy access to ZeroMQ's powerful, smart & secure Security Layers in that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings cheap, standardized privacy and authentication for distributed systems over the network. On top of that, this API can robustly handle Multiple Servers at once, thereby providing access to seamless Live Streams of the various device in a network at the same time. **NetGear as of now seamlessly supports three ZeroMQ messaging patterns:** @@ -433,8 +446,8 @@ The full documentation for all VidGear classes and functions can be found in the * **Download few additional python libraries:** ```sh - pip install six - pip install pytest + pip3 install six + pip3 install pytest ``` * **Download Test Dataset:** To perform tests, additional *test dataset* is required, which can be downloaded by running [*bash script*][bs_script_dataset] as follows: @@ -466,9 +479,8 @@ See [Wiki: Project Motivation][wiki-vidgear-purpose] ## Supported Python legacies - * **Python 2.7 legacies:** *VidGear v0.1.5 is officially the last Python 2.7 legacies supporting version.* Kindly migrate your source code to Python 3 as soon as possible. - - * **Python 3.x legacies:** follows the [numpy][numpy] releases. + * **Python 3+ are only supported legacies for installing Vidgear v0.1.6 and above.** + * **:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27].**   @@ -492,6 +504,7 @@ Badges --> [appveyor]:https://img.shields.io/appveyor/ci/abhitronix/vidgear.svg?style=for-the-badge&logo=appveyor +[codecov]:https://img.shields.io/codecov/c/github/abhiTronix/vidgear/testing?style=for-the-badge&logo=codecov [travis-cli]:https://img.shields.io/travis/abhiTronix/vidgear.svg?style=for-the-badge&logo=travis [prs-badge]:https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABC0lEQVRYhdWVPQoCMRCFX6HY2ghaiZUXsLW0EDyBrbWtN/EUHsHTWFnYyCL4gxibVZZlZzKTnWz0QZpk5r0vIdkF/kBPAMOKeddE+CQPKoc5Yt5cTjBMdQSwDQToWgBJAn3jmhqgltapAV6E6b5U17MGGAUaUj07TficMfIBZDV6vxowBm1BP9WbSQE4o5h9IjPJmy73TEPDDxVmoZdQrQ5jRhly9Q8tgMUXkIIWn0oG4GYQfAXQzz1PGoCiQndM7b4RgJay/h7zBLT3hASgoKjamQJMreKf0gfuAGyYtXEIAKcL/Dss15iq6ohXghozLYiAMxPuACwtIT4yeQUxAaLrZwAoqGRKGk7qDSYTfYQ8LuYnAAAAAElFTkSuQmCC [twitter-badge]:https://img.shields.io/twitter/url/http/shields.io.svg?style=for-the-badge&logo=twitter @@ -511,6 +524,7 @@ Internal URLs [license]:https://github.com/abhiTronix/vidgear/blob/master/LICENSE [travis]:https://travis-ci.org/abhiTronix/vidgear [app]:https://ci.appveyor.com/project/abhiTronix/vidgear +[code]:https://codecov.io/gh/abhiTronix/vidgear [test-4k]:https://github.com/abhiTronix/vidgear/blob/e0843720202b0921d1c26e2ce5b11fadefbec892/vidgear/tests/benchmark_tests/test_benchmark_playback.py#L65 [bs_script_dataset]:https://github.com/abhiTronix/vidgear/blob/testing/scripts/bash/prepare_dataset.sh @@ -533,6 +547,7 @@ Internal URLs [screengear-wiki]:https://github.com/abhiTronix/vidgear/wiki/ScreenGear#screengear-api [writegear-wiki]:https://github.com/abhiTronix/vidgear/wiki/WriteGear#writegear-api [netgear-wiki]:https://github.com/abhiTronix/vidgear/wiki/NetGear#netgear-api +[drop27]:https://github.com/abhiTronix/vidgear/issues/29 @@ -44,7 +39,7 @@ THE SOFTWARE.   -VidGear is a powerful python Video Processing library built with multi-threaded [**Gears**](#gear)(_a.k.a APIs_) each with a unique set of trailblazing features. These APIs provides a easy-to-use, highly extensible, and multi-threaded wrapper around many underlying state-of-the-art libraries such as *[OpenCV ➶][opencv], [FFmpeg ➶][ffmpeg], [picamera ➶][picamera], [pafy ➶][pafy], [pyzmq ➶][pyzmq] and [python-mss ➶][mss]* +VidGear is a powerful python Video Processing library built with multi-threaded [**Gears**](#gears) each with a unique set of trailblazing features. These APIs provides a easy-to-use, highly extensible, and multi-threaded wrapper around many underlying state-of-the-art libraries such as *[OpenCV ➶][opencv], [FFmpeg ➶][ffmpeg], [picamera ➶][picamera], [pafy ➶][pafy], [pyzmq ➶][pyzmq] and [python-mss ➶][mss]*   @@ -60,14 +55,6 @@ The following **functional block diagram** clearly depicts the functioning of Vi [**TL;DR**](#tldr) -[**New Release SneekPeak**](#new-release-sneekpeak--vidgear-016) - -[**Installation Options**](#installation) - * [**Prerequisites**](#prerequisites) - * [**1 - PyPI Install**](#option-1-pypi-install) - * [**2 - Release Archive Download**](#option-2-release-archive-download) - * [**3 - Clone Repo**](#option-3-clone-the-repo) - [**Gears: What are these?**](#gears) * [**CamGear**](#camgear) * [**PiGear**](#pigear) @@ -76,135 +63,46 @@ The following **functional block diagram** clearly depicts the functioning of Vi * [**WriteGear**](#writegear) * [**NetGear**](#netgear) -**For Developers/Contributors** - * [**Testing**](#testing) - * [**Contributing**](#contributing) +[**Installation Options**](#installation) + * [**Prerequisites**](#prerequisites) + * [**1 - PyPI Install**](#option-1-pypi-install) + * [**2 - Release Archive Download**](#option-2-release-archive-download) + * [**3 - Clone Repo**](#option-3-clone-the-repo) + +[**New-Release SneekPeak: v0.1.6**](#new-release-sneekpeak--vidgear-016) [**Documentation**](#documentation) -[**Project Motivation**](#project-motivation) +**For Developers/Contributors** + * [**Testing**](#testing) + * [**Contributing**](#contributing) **Additional Info** * [**Supported Python legacies**](#supported-python-legacies) * [**Changelog**](#changelog) * [**License**](#license) -  - -## TL;DR - *"VidGear is an [ultrafast➶][ultrafast-wiki], compact, flexible and easy-to-adapt complete Video Processing Python Library."* - - Built with simplicity in mind, VidGear lets programmers and software developers to easily integrate and perform complex Video Processing tasks in their existing or new applications, without going through various underlying library's documentation and using just a few lines of code. Beneficial for both, if you're new to programming with Python language or already a pro at it. - - **For more detailed information see the [*Wiki Documentation ➶*][wiki].** - - -   -## New Release SneekPeak : VidGear 0.1.6 - -***:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27]!*** - -**NetGear API:** - * Added powerful ZMQ Authentication & Data Encryption features for NetGear API - * Added robust Multi-Server support for NetGear API. - * Added exclusive Bi-Directional Mode for bidirectional data transmission. - * Added frame-compression support with on-the-fly flexible encoding/decoding. - * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming in NetGear API. - -**PiGear API:** - * Added new threaded internal timing function for PiGear to handle any hardware failures/frozen threads - * PiGear will not exit safely with `SystemError` if Picamera ribbon cable is pulled out to save resources. - -**WriteGear API:** Added new `execute_ffmpeg_cmd` function to pass a custom command to its internal FFmpeg pipeline. -**Stabilizer class:** Added new _Crop and Zoom_ feature. - -***Added VidGear's official native support for MacOS environment and [many more...](changelog.md)*** - -  - -## Installation - -### Prerequisites - -Before installing VidGear, you must verify that the following dependencies are met: - -* Must be using [supported Python legacies](#supported-python-legacies) only and thereby [pip][pip] already installed properly. - - -* **`OpenCV:`** VidGear must require OpenCV(3.0+) python enabled binaries to be installed on your machine for its core functions. For its installation, you can follow these online tutorials for [linux][OpenCV-linux] and [raspberry pi][OpenCV-pi], otherwise, install it via pip: - - ```sh - pip3 install -U opencv-python #or install opencv-contrib-python similarly - ``` - -* **`FFmpeg:`** VidGear must require FFmpeg for its powerful video compression and encoding capabilities. :star2: Follow this [**FFmpeg wiki page**][ffmpeg-wiki] for its installation. :star2: - -* **`picamera:`** Required if using Raspberry Pi Camera Modules(_such as OmniVision OV5647 Camera Module_) with your Raspberry Pi machine. You can easily install it via pip: - - ```sh - pip3 install picamera - ``` - Also, make sure to enable Raspberry Pi hardware-specific settings prior to using this library. - -* **`mss:`** Required for using Screen Casting. Install it via pip: - - ```sh - pip3 install mss - ``` -* **`pyzmq:`** Required for transferring live video frames through _ZeroMQ messaging system_ over the network. Install it via pip: - - ```sh - pip3 install pyzmq - ``` - -* **`pafy:`** Required for direct YouTube Video streaming capabilities. Both [`pafy`][pafy] and latest only [`youtube-dl`][yt-dl](_as pafy's backend_) libraries must be installed via pip as follows: - - ```sh - pip3 install pafy - pip3 install -U youtube-dl - ``` - - -  - -### Option 1: PyPI Install - -> Best option for **quickly** getting VidGear installed. - -```sh - pip3 install vidgear -``` -  +## TL;DR + + > ***"VidGear is an [ultrafast➶][ultrafast-wiki], compact, flexible and easy-to-adapt complete Video Processing Python Library."*** -### Option 2: Release Archive Download + *Built with simplicity in mind, VidGear lets programmers and software developers to easily integrate and perform complex Video Processing tasks in their existing or new applications, without going through various underlying library's documentation and using just a few lines of code. Beneficial for both, if you're new to programming with Python language or already a pro at it.* -> Best option if you want a **compressed archive**. + **For more advanced information, see the [*Wiki Documentation ➶*][wiki].** -VidGear releases are available for download as packages in the [latest release][release]   -### Option 3: Clone the Repository - -> Best option for **latest patches & updates**(_but experimental_), or **contributing** to development. - -You can clone this repository's `testing` branch for development and thereby can install as follows: -```sh - git clone https://github.com/abhiTronix/vidgear.git - cd vidgear - git checkout testing - sudo pip3 install . -``` - -  ## Gears: -VidGear is built with multi-threaded **Gears** each with some unique function/mechanism. Each **Gear** is designed exclusively to handle/control different device-specific video streams, network streams, and media encoders. These APIs provides an easy-to-use, highly extensible, and a multi-threaded wrapper around various underlying libraries to exploit their features and functions directly while providing robust error-handling. +> **VidGear is built with **multi-threaded APIs** *(a.k.a Gears)* each with some unique function/mechanism.** + +Each of these API is designed exclusively to handle/control different device-specific video streams, network streams, and media encoders. These APIs provides an easy-to-use, highly extensible, and a multi-threaded wrapper around various underlying libraries to exploit their features and functions directly while providing robust error-handling. **These Gears can be classified as follows:** @@ -221,13 +119,15 @@ VidGear is built with multi-threaded **Gears** each with some unique function/me **C. Network Gear:** - * [**NetGear:**](#netgear) _Targets synchronous video frames transferring between interconnecting systems over the network._ + * [**NetGear:**](#netgear) _Targets synchronous/asynchronous video frames transferring between interconnecting systems over the network._   ### CamGear -CamGear supports a diverse range of video streams which can handle/control video stream almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), network stream URL such as `http(s), rtp, rstp, rtmp, mms, etc.` In addition to this, it also supports live Gstreamer's RAW pipelines and YouTube video/livestreams URLs. CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs `pafy` python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear relies exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. +> **CamGear can grab ultrafast frames from diverse range of VideoStreams, which includes almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), various network stream protocols such as `http(s), rtp, rstp, rtmp, mms, etc.`, plus support for live Gstreamer's stream pipeline and YouTube video/livestreams URLs.** + +CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs [`pafy`][pafy] python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear implements exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. **Following simplified functional block diagram depicts CamGear API's generalized working:** @@ -236,22 +136,24 @@ CamGear supports a diverse range of video streams which can handle/control video CamGear Functional Block Diagram

-**CamGear API Guide:** +#### CamGear API Guide: -[>>> Usage Guide][camgear-wiki] +[**>>> Usage Guide**][camgear-wiki]   ### VideoGear -VideoGear API provides a special internal wrapper around VidGear's exclusive [**Video Stabilizer**][stablizer-wiki] class. Furthermore, VideoGear API can provide internal access to both [CamGear](#camgear) and [PiGear](#pigear) APIs separated by a special flag. Thereby, _this API holds the exclusive power for any incoming VideoStream from any source, whether it is live or not, to access and stabilize it directly with minimum latency and memory requirements._ +> **VideoGear API provides a special internal wrapper around VidGear's exclusive [**Video Stabilizer**][stablizer-wiki] class.** + +Furthermore, VideoGear API can provide internal access to both [CamGear](#camgear) and [PiGear](#pigear) APIs separated by a special flag. Thereby, _this API holds the exclusive power for any incoming VideoStream from any source, whether it is live or not, to access and stabilize it directly with minimum latency and memory requirements._ **Below is a snapshot of a VideoGear Stabilizer in action:**

VideoGear Stabilizer in action!
- Original Video Courtesy@SIGGRAPH2013 + Original Video Courtesy @SIGGRAPH2013

Code to generate above VideoGear API Stabilized Video(_See more detailed usage examples [here][stablizer-wiki-ex]_): @@ -303,15 +205,19 @@ stream_stab.stop() ``` -**VideoGear API Guide:** +#### VideoGear API Guide: -[>>> Usage Guide][videogear-wiki] +[**>>> Usage Guide**][videogear-wiki]   ### PiGear -PiGear is similar to CamGear but made to support various Raspberry Pi Camera Modules (such as [OmniVision OV5647 Camera Module][OV5647-picam] and [Sony IMX219 Camera Module][IMX219-picam]). To interface with these modules correctly, PiGear provides a flexible multi-threaded wrapper around complete [picamera][picamera] python library, and provides us the ability to exploit its various features like `brightness, saturation, sensor_mode, etc.` effortlessly. In addition to this, PiGear API provides excellent Error-Handling with features like a threaded internal timer that keeps active track of any frozen threads and handles hardware failures/frozen threads robustly thereby will exit safely if any failure occurs. So if you accidently pulled your camera cable out when running PiGear API in your script, instead of going into possible kernel panic due to IO error it will exit safely to save resources. +> **PiGear is similar to CamGear but made to support various Raspberry Pi Camera Modules *(such as [OmniVision OV5647 Camera Module][OV5647-picam] and [Sony IMX219 Camera Module][IMX219-picam])*.** + +PiGear provides a flexible multi-threaded wrapper around complete [**picamera**][picamera] python library to interface with these modules correctly, and also grants the ability to exploit its various features like `brightness, saturation, sensor_mode, etc.` effortlessly. + +Best of all, PiGear API provides excellent Error-Handling with features like a threaded internal timer that keeps active track of any frozen threads and handles hardware failures/frozen threads robustly thereby will exit safely if any failure occurs. So if you accidently pulled your camera cable out when running PiGear API in your script, instead of going into possible kernel panic due to IO error, it will exit safely to save resources. **Following simplified functional block diagram depicts PiGear API:** @@ -319,15 +225,17 @@ PiGear is similar to CamGear but made to support various Raspberry Pi Camera Mod PiGear Functional Block Diagram

-**PiGear API Guide:** +#### PiGear API Guide: -[>>> Usage Guide][pigear-wiki] +[**>>> Usage Guide**][pigear-wiki]   ### ScreenGear -With ScreenGear, we can easily define an area on the computer screen or an open window to record the live screen frames in real-time at the expense of insignificant latency. To achieve this, ScreenGear provides a high-level multi-threaded wrapper around [**`mss`**][mss] python library API and also supports the flexible direct parameter manipulation. +> **ScreenGear act as Screen Recorder, that can grab frames from your monitor in real-time either by define an area on the computer screen or fullscreen at the expense of insignificant latency. It also provide seemless support for capturing frames from multiple monitors.** + +ScreenGear provides a high-level multi-threaded wrapper around [**python-mss**][mss] python library API and also supports a easy and flexible direct internal parameter manipulation. **Below is a snapshot of a ScreenGear API in action:** @@ -371,9 +279,9 @@ stream.stop() # safely close video stream. ``` -**ScreenGear API Guide:** +#### ScreenGear API Guide: -[>>> Usage Guide][screengear-wiki] +[**>>> Usage Guide**][screengear-wiki]   @@ -381,7 +289,11 @@ stream.stop() ### WriteGear -WriteGear is undoubtedly the most powerful Video Processing Gear of them all. It solely handles various powerful FFmpeg tools that provide us the freedom to do almost anything imagine with multimedia files. For example with WriteGear API, we can process real-time video frames into a lossless compressed format with any suitable specification in just few easy [lines of codes][compression-mode-ex]. These specifications include setting any video/audio property such as `bitrate, codec, framerate, resolution, subtitles, etc.` Furthermore, we can multiplex extracted audio at the output with compression and all that in real-time(see this [example wiki][live-audio-wiki]). In addition to this, WriteGear also provides flexible access to OpenCV's VideoWriter API which provides some basic tools for video frames encoding but without compression. +> **WriteGear handles various powerful Writer Tools that provide us the freedom to do almost anything imagine with multimedia files.** + +WriteGear API provide a complete, flexible & robust wrapper around [**FFmpeg**][ffmpeg], a leading multimedia framework. With WriteGear, we can process real-time video frames into a lossless compressed format with any suitable specification in just few easy [lines of codes][compression-mode-ex]. These specifications include setting any video/audio property such as `bitrate, codec, framerate, resolution, subtitles, etc.` easily as well complex tasks such as multiplexing video with audio in real-time(see this [example wiki][live-audio-wiki]). Best of all, WriteGear grants the freedom to play with any FFmpeg parameter with its exclusive custom Command function(see this [example wiki][custom-command-wiki]), while handling all errors robustly. + +In addition to this, WriteGear also provides flexible access to [**OpenCV's VideoWriter API**][opencv-writer] which provides some basic tools for video frames encoding but without compression. **WriteGear primarily operates in the following two modes:** @@ -395,26 +307,32 @@ WriteGear is undoubtedly the most powerful Video Processing Gear of them all. It WriteGear Functional Block Diagram

-**WriteGear API Guide:** +#### WriteGear API Guide: -[>>> Usage Guide][writegear-wiki] +[**>>> Usage Guide**][writegear-wiki]   ### NetGear -NetGear is exclusively designed to transfer video frames synchronously and asynchronously between interconnecting systems over the network in real-time. This is achieved by implementing a high-level wrapper around [PyZmQ][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. It provides seamless support for bidirectional data transmission between receiver(client) and sender(server) through bi-directional synchronous messaging patterns such as zmq.PAIR (ZMQ Pair Pattern) & zmq.REQ/zmq.REP (ZMQ Request/Reply Pattern). Plus also introduces real-time frame Encoding/Decoding compression capabilities for optimizing performance while sending the frames of large size directly over the network by encoding the frame before sending it and decoding it on the client's end automatically all in real-time. +> **NetGear is exclusively designed to transfer video frames synchronously and asynchronously between interconnecting systems over the network in real-time.** -For security, NetGear also supports easy access to ZeroMQ's powerful, smart & secure Security Layers in that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings cheap, standardized privacy and authentication for distributed systems over the network. On top of that, this API can robustly handle Multiple Servers at once, thereby providing access to seamless Live Streams of the various device in a network at the same time. +NetGear implements a high-level wrapper around [**PyZmQ**][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. +NetGear provides seamless support for bidirectional data transmission between receiver(client) and sender(server) through bi-directional synchronous messaging patterns such as zmq.PAIR _(ZMQ Pair Pattern)_ & zmq.REQ/zmq.REP _(ZMQ Request/Reply Pattern)_. -**NetGear as of now seamlessly supports three ZeroMQ messaging patterns:** +NetGear also introduces real-time frame Encoding/Decoding compression capabilities for optimizing performance while sending the frames of large size directly over the network by encoding the frame before sending it and decoding it on the client's end automatically all in real-time. - * ZMQ Pair Pattern - * ZMQ Client/Server Pattern - * ZMQ Publish/Subscribe Pattern +For security, NetGear also supports easy access to ZeroMQ's powerful, smart & secure Security Layers, that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings easy, standardized privacy and authentication for distributed systems over the network. -whereas the supported protocol are: `tcp, upd, pgm, inproc, ipc`. +Best of all, NetGear can robustly handle Multiple Servers at once, thereby providing access to seamless Live Streams of the various device in a network at the same time. + + +**NetGear as of now seamlessly supports three ZeroMQ messaging patterns:** + +* [**`zmq.PAIR`**][zmq-pair] _(ZMQ Pair Pattern)_ +* [**`zmq.REQ/zmq.REP`**][zmq-req-rep] _(ZMQ Request/Reply Pattern)_ +* [**`zmq.PUB/zmq.SUB`**][zmq-pub-sub] _(ZMQ Publish/Subscribe Pattern)_ **Following functional block diagram depicts generalized functioning of NetGear API:** @@ -423,13 +341,121 @@ whereas the supported protocol are: `tcp, upd, pgm, inproc, ipc`. NetGear Functional Block Diagram

-**NetGear API Guide:** +#### NetGear API Guide: + +[**>>> Usage Guide**][netgear-wiki] + + +  + + +## New Release SneekPeak : VidGear 0.1.6 + +***:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27] !*** + +**NetGear API:** + * Added powerful ZMQ Authentication & Data Encryption features for NetGear API + * Added robust Multi-Server support for NetGear API. + * Added exclusive Bi-Directional Mode for bidirectional data transmission. + * Added frame-compression support with on-the-fly flexible encoding/decoding. + * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming in NetGear API. + +**PiGear API:** + * Added new threaded internal timing function for PiGear to handle any hardware failures/frozen threads + * PiGear will not exit safely with `SystemError` if Picamera ribbon cable is pulled out to save resources. + +**WriteGear API:** Added new `execute_ffmpeg_cmd` function to pass a custom command to its internal FFmpeg pipeline. + +**Stabilizer class:** Added new _Crop and Zoom_ feature. + +***Added VidGear's official native support for MacOS environment and [many more...](changelog.md)*** + -[>>> Usage Guide][netgear-wiki]   + + +## Installation + +### Prerequisites + +Before installing VidGear, you must verify that the following dependencies are met: + +* :warning: Must be using only [**supported Python legacies**](#supported-python-legacies) and also [**pip**][pip] already installed and configured. + + +* **`OpenCV:`** VidGear must require OpenCV(3.0+) python enabled binaries to be installed on your machine for its core functions. For its installation, you can follow these online tutorials for [linux][OpenCV-linux] and [raspberry pi][OpenCV-pi], otherwise, install it via pip: + + ```sh + pip3 install -U opencv-python #or install opencv-contrib-python similarly + ``` + +* **`FFmpeg:`** VidGear must require FFmpeg for its powerful video compression and encoding capabilities. :star2: Follow this [**FFmpeg wiki page**][ffmpeg-wiki] for its installation. :star2: + +* **`picamera:`** Required if using Raspberry Pi Camera Modules(_such as OmniVision OV5647 Camera Module_) with your Raspberry Pi machine. You can easily install it via pip: + + ```sh + pip3 install picamera + ``` + Also, make sure to enable Raspberry Pi hardware-specific settings prior to using this library. + +* **`mss:`** Required for using Screen Casting. Install it via pip: + + ```sh + pip3 install mss + ``` +* **`pyzmq:`** Required for transferring live video frames through _ZeroMQ messaging system_ over the network. Install it via pip: + + ```sh + pip3 install pyzmq + ``` + +* **`pafy:`** Required for direct YouTube Video streaming capabilities. Both [`pafy`][pafy] and latest only [`youtube-dl`][yt-dl](_as pafy's backend_) libraries must be installed via pip as follows: + + ```sh + pip3 install pafy + pip3 install -U youtube-dl + ``` + + +  + +### Option 1: PyPI Install + +> Best option for **quickly** getting VidGear installed. + +```sh + pip3 install vidgear +``` +  + +### Option 2: Release Archive Download + +> Best option if you want a **compressed archive**. + +VidGear releases are available for download as packages in the [latest release][release] + +  + +### Option 3: Clone the Repository + +> Best option for trying **latest patches, Pull requests & updgrades**(_maybe experimental_), or **contributing** to development. + +You can clone this repository's `testing` branch for development and thereby can install as follows: +```sh + git clone https://github.com/abhiTronix/vidgear.git + cd vidgear + git checkout testing + sudo pip3 install . +``` + + +  + + + ## Documentation The full documentation for all VidGear classes and functions can be found in the link below: @@ -450,11 +476,11 @@ The full documentation for all VidGear classes and functions can be found in the pip3 install pytest ``` - * **Download Test Dataset:** To perform tests, additional *test dataset* is required, which can be downloaded by running [*bash script*][bs_script_dataset] as follows: + * **Download Test Dataset:** To perform tests, additional *test dataset* is required, which can be downloaded *(to temp dir)* by running [*bash script*][bs_script_dataset] as follows: ```sh - chmod +x scripts/prepare_dataset.sh - ./scripts/prepare_dataset.sh #for windows, use `sh scripts/pre_install.sh` + chmod +x scripts/bash/prepare_dataset.sh + .scripts/bash/prepare_dataset.sh #for windows, use `sh scripts/pre_install.sh` ``` * **Run Tests:** Then various VidGear tests can be run with `pytest`(*in VidGear's root folder*) as below: @@ -467,13 +493,7 @@ The full documentation for all VidGear classes and functions can be found in the ## Contributing -See [contributing.md](contributing.md) - -  - -## Project Motivation - -See [Wiki: Project Motivation][wiki-vidgear-purpose] +See [**contributing.md**](contributing.md)   @@ -486,7 +506,7 @@ See [Wiki: Project Motivation][wiki-vidgear-purpose] ## Changelog -See [changelog.md](changelog.md) +See [**changelog.md**](changelog.md)   @@ -494,7 +514,7 @@ See [changelog.md](changelog.md) Copyright © abhiTronix 2019 -This project is licensed under the [MIT][license] license. +This library is licensed under the **[Apache 2.0 License][license]**. @@ -548,17 +568,17 @@ Internal URLs [writegear-wiki]:https://github.com/abhiTronix/vidgear/wiki/WriteGear#writegear-api [netgear-wiki]:https://github.com/abhiTronix/vidgear/wiki/NetGear#netgear-api [drop27]:https://github.com/abhiTronix/vidgear/issues/29 - +[custom-command-wiki]:https://github.com/abhiTronix/vidgear/wiki/Custom-FFmpeg-Commands-in-WriteGear-API#custom-ffmpeg-commands-in-writegear-api - +[ffmpeg]:https://www.ffmpeg.org/ +[opencv-writer]:https://docs.opencv.org/master/dd/d9e/classcv_1_1VideoWriter.html#ad59c61d8881ba2b2da22cff5487465b5 [OpenCV-linux]:https://www.pyimagesearch.com/2018/05/28/ubuntu-18-04-how-to-install-opencv/ [OpenCV-pi]:https://www.pyimagesearch.com/2018/09/26/install-opencv-4-on-your-raspberry-pi/ -[prs]:http://makeapullrequest.com "Make a Pull Request (external link) ➶" +[prs]:http://makeapullrequest.com [opencv]:https://github.com/opencv/opencv -[ffmpeg]:https://ffmpeg.org/ [picamera]:https://github.com/waveform80/picamera [pafy]:https://github.com/mps-youtube/pafy [pyzmq]:https://github.com/zeromq/pyzmq @@ -569,4 +589,7 @@ External URLs [IMX219-picam]:https://github.com/techyian/MMALSharp/wiki/Sony-IMX219-Camera-Module [opencv-vw]:https://docs.opencv.org/3.4/d8/dfe/classcv_1_1VideoCapture.html [yt-dl]:https://github.com/ytdl-org/youtube-dl/ -[numpy]:https://github.com/numpy/numpy \ No newline at end of file +[numpy]:https://github.com/numpy/numpy +[zmq-pair]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pair.html +[zmq-req-rep]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/client_server.html +[zmq-pub-sub]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pubsub.html \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 82bed9beb..0374cecff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,17 @@ +# Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + environment: matrix: diff --git a/changelog.md b/changelog.md index 39a64d242..b8b6e32bd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ # CHANGELOG -## VidGear 0.1.6-dev +## VidGear 0.1.6 ### New Features: * **NetGear API:** @@ -55,7 +55,8 @@ ### Updates/Improvements: - * Updated support for screen casting from all monitors in ScreenGear API. + * Replace `print` logging commands with python's logging module completely. + * Updated support for screen casting from multiple/all monitors in ScreenGear API. * Updated ScreenGear API to use *Threaded Queue Mode* by default, thereby removed redundant `THREADED_QUEUE_MODE` param. * Updated bash script path to download test dataset in `$TMPDIR` rather than `$HOME` directory for downloading testdata. * Added support for `REQ/REP` pattern in Multi-Server Mode @@ -70,7 +71,9 @@ ### Breaking Updates / Improvements / Changes * :warning: Python 2.7 legacy support dropped completely. - * Python 3+ are only supported legacies for installing Vidgear v0.1.6 and above. + * :warning: Source-code Relicensed to Apache 2.0 License. + * Python 3+ are only supported legacies for installing Vidgear v0.1.6 and above. + * Python 2.7 and 3.4 legacies support dropped from VidGear CLI tests. ### Fixes * Reimplemented `Pub/Sub` pattern for smoother performance(#70) @@ -94,6 +97,7 @@ * PR #72 * PR #77 * PR #78 + * PR #82 :warning: PyPi Release does NOT contain Tests and Scripts! diff --git a/codecov.yml b/codecov.yml index 66a94bdc3..36888fe5f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,17 @@ +# Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + codecov: require_ci_to_pass: yes ci: @@ -6,6 +20,10 @@ codecov: branch: testing strict_yaml_branch: testing +coverage: + status: + patch: off + ignore: - "vidgear/tests" - "setup.py" \ No newline at end of file diff --git a/scripts/bash/install_opencv.sh b/scripts/bash/install_opencv.sh index 9e375b8a0..ddd69fe2c 100644 --- a/scripts/bash/install_opencv.sh +++ b/scripts/bash/install_opencv.sh @@ -1,23 +1,26 @@ #!/bin/sh -#Copyright (c) 2019 Abhishek Thakur +# Copyright (c) 2019 Abhishek Thakur(@abhiTronix) -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at -#The above copyright notice and this permission notice shall be included in all -#copies or substantial portions of the Software. +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -###################################### -# Installing OpenCV Binaries # -###################################### +######################################## +# Installs OpenCV Offical Binaries for # +# CLI Linux Environments # +######################################## +#opencv version to install OPENCV_VERSION='4.2.0-pre' #determining system specific temp directory @@ -26,9 +29,9 @@ TMPFOLDER=$(python -c 'import tempfile; print(tempfile.gettempdir())') #determining system Python suffix and version PYTHONSUFFIX=$(python -c 'import platform; a = platform.python_version(); print(".".join(a.split(".")[:2]))') PYTHONVERSION=$(python -c 'import platform; print(platform.python_version())') +PYTHONVERSIONMIN=$(python3 -c 'import platform; print(platform.python_version()[:3])') echo "Installing OpenCV..." - echo "Installing OpenCV Dependencies..." sudo apt-get install -y --allow-unauthenticated build-essential cmake pkg-config gfortran libavutil-dev ffmpeg @@ -49,9 +52,13 @@ cd $TMPFOLDER export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH -wget https://github.com/abhiTronix/OpenCV-Travis-Builds/releases/download/opencv-$OPENCV_VERSION/OpenCV-$OPENCV_VERSION-$PYTHONVERSION.deb +curl -s https://api.github.com/repos/abhiTronix/OpenCV-Travis-Builds/releases/latest \ +| grep "OpenCV-$OPENCV_VERSION-$PYTHONVERSIONMIN.*.deb" \ +| cut -d : -f 2,3 \ +| tr -d \" \ +| wget -qi - -sudo dpkg -i OpenCV-$OPENCV_VERSION-$(python -c 'import platform; print(platform.python_version())').deb +sudo dpkg -i OpenCV-$OPENCV_VERSION-$PYTHONVERSIONMIN.*.deb sudo ln -s /usr/local/lib/python$PYTHONSUFFIX/site-packages/*.so $HOME/virtualenv/python$PYTHONVERSION/lib/python$PYTHONSUFFIX/site-packages diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index d530dcf36..b1cc3e4a1 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -1,16 +1,18 @@ #!/bin/sh -#Copyright (c) 2019 Abhishek Thakur +# Copyright (c) 2019 Abhishek Thakur(@abhiTronix) -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at -#The above copyright notice and this permission notice shall be included in all -#copies or substantial portions of the Software. +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. #determining system specific temp directory TMPFOLDER=$(python -c 'import tempfile; print(tempfile.gettempdir())') diff --git a/setup.py b/setup.py index b3724c6ea..7e3a03971 100644 --- a/setup.py +++ b/setup.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + http://www.apache.org/licenses/LICENSE-2.0 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -28,6 +23,8 @@ from pkg_resources import parse_version from setuptools import setup + + def test_opencv(): """ This function is workaround to @@ -37,7 +34,6 @@ def test_opencv(): try: # import OpenCV Binaries import cv2 - # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): raise ImportError('Incompatible (< 3.0) OpenCV version-{} Installation found on this machine!'.format(parse_version(cv2.__version__))) @@ -45,15 +41,17 @@ def test_opencv(): return True return False + + with open("README.md", "r") as fh: long_description = fh.read() setup( name='vidgear', packages=['vidgear','vidgear.gears'], - version='0.1.6-dev', + version='0.1.6', description='Most Powerful multi-threaded Video Processing Python framework powerpacked with unique trailblazing features.', - license='MIT License', + license='Apache License 2.0', author='abhiTronix', install_requires = ["pafy", "mss", "youtube-dl", "requests","pyzmq"] + (["opencv-contrib-python"] if test_opencv() else []) @@ -66,8 +64,13 @@ def test_opencv(): keywords=['opencv', 'multithreading', 'FFmpeg', 'picamera', 'mss', 'pyzmq', 'pafy', 'Video Processing', 'Video Stablization', 'Computer Vision'], classifiers=[ 'Development Status :: 5 - Production/Stable', + 'Operating System :: POSIX', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Topic :: Multimedia :: Video', + 'Topic :: Scientific/Engineering', 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', + 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', @@ -76,8 +79,8 @@ def test_opencv(): 'Programming Language :: Python :: 3.8'], python_requires='>=3', scripts=[], - project_urls={ # Optional + project_urls={ 'Bug Reports': 'https://github.com/abhiTronix/vidgear/issues', 'Funding': 'https://www.buymeacoffee.com/2twOXFvlA', 'Source': 'https://github.com/abhiTronix/vidgear',}, -) +) \ No newline at end of file diff --git a/vidgear/__init__.py b/vidgear/__init__.py index 65e6a4896..f5f5f95b4 100644 --- a/vidgear/__init__.py +++ b/vidgear/__init__.py @@ -1,3 +1,3 @@ from .version import __version__ -__author__ = "Abhishek Thakur " +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/gears/__init__.py b/vidgear/gears/__init__.py index 2a7753686..615aa3b1e 100644 --- a/vidgear/gears/__init__.py +++ b/vidgear/gears/__init__.py @@ -8,4 +8,4 @@ __all__ = ["PiGear", "CamGear", "VideoGear", "ScreenGear", "WriteGear", "NetGear"] -__author__ = "Abhishek Thakur " \ No newline at end of file +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 56d78f17b..131b62a9f 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -29,6 +24,7 @@ from .helper import capPropId from .helper import check_CV_version import re, time +import logging as log @@ -51,10 +47,10 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[CamGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') + raise ImportError('[CamGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') @@ -118,6 +114,11 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l #intialize threaded queue mode self.threaded_queue_mode = True + # enable logging if specified + self.logging = False + self.logger = log.getLogger('CamGear') + if logging: self.logging = logging + # check if Youtube Mode is ON (True) if y_tube: try: @@ -129,11 +130,12 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l source_object = pafy.new(url) _source = source_object.getbestvideo("any", ftypestrict=False) if _source is None: _source = source_object.getbest("any", ftypestrict=False) - if logging: print('[LOG]: YouTube source ID: `{}`, Title: `{}` & Video_Extension: `{}`'.format(url, source_object.title, _source.extension)) + if self.logging: self.logger.debug('YouTube source ID: `{}`, Title: `{}` & Video_Extension: `{}`'.format(url, source_object.title, _source.extension)) source = _source.url + else: raise RuntimeError('URL cannot be processed!') except Exception as e: - if logging: print(e) - raise ValueError('[ERROR]: YouTube Mode is enabled and the input YouTube Url is invalid!') + if self.logging: self.logger.exception(str(e)) + raise ValueError('[CamGear:ERROR] :: YouTube Mode is enabled and the input YouTube URL is invalid!') # youtube mode variable initialization self.youtube_mode = y_tube @@ -154,12 +156,12 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l #define deque and assign it to global var self.queue = deque(maxlen=96) #max len 96 to check overflow #log it - if logging: print('[LOG]: Enabling Threaded Queue Mode for the current video source!') + if self.logging: self.logger.debug('Enabling Threaded Queue Mode for the current video source!') else: #otherwise disable it self.threaded_queue_mode = False #log it - if logging: print('[LOG]: Threaded Queue Mode is disabled for the current video source!') + if self.logging: self.logger.debug('Threaded Queue Mode is disabled for the current video source!') # stream variable initialization self.stream = None @@ -190,12 +192,11 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l # separately handle colorspace value to int conversion if not(colorspace is None): self.color_space = capPropId(colorspace.strip()) - if logging: print('[LOG]: Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) + if self.logging: self.logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) except Exception as e: # Catch if any error occurred - if logging: - print(e) + if self.logging: self.logger.exception(str(e)) #initialize and assign framerate variable self.framerate = 0.0 @@ -203,7 +204,7 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l _fps = self.stream.get(cv2.CAP_PROP_FPS) if _fps>1: self.framerate = _fps except Exception as e: - if logging: print(e) + if self.logging: self.logger.exception(str(e)) self.framerate = 0.0 # applying time delay to warm-up webcam only if specified @@ -221,10 +222,7 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l #intitialize and append to queue self.queue.append(self.frame) else: - raise RuntimeError('[ERROR]: Source is invalid, CamGear failed to intitialize stream on this source!') - - # enable logging if specified - self.logging = logging + raise RuntimeError('[CamGear:ERROR] :: Source is invalid, CamGear failed to intitialize stream on this source!') # thread initialization self.thread=None @@ -238,7 +236,7 @@ def start(self): """ start the thread to read frames from the video stream """ - self.thread = Thread(target=self.update, args=()) + self.thread = Thread(target=self.update, name='CamGear', args=()) self.thread.daemon = True self.thread.start() return self @@ -285,13 +283,13 @@ def update(self): color_frame = cv2.cvtColor(frame, self.color_space) else: self.color_space = None - if self.logging: print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + if self.logging: self.logger.debug('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None if self.logging: - print(e) - print('[LOG]: Input Colorspace is not a valid Colorspace!') + self.logger.exception(str(e)) + self.logger.debug('Input Colorspace is not a valid Colorspace!') if not(color_frame is None): self.frame = color_frame @@ -325,7 +323,7 @@ def stop(self): """ Terminates the Read process """ - if self.logging: print('[LOG]: Terminating processes') + if self.logging: self.logger.debug('Terminating processes.') #terminate Threaded queue mode seperately if self.threaded_queue_mode and not(self.queue is None): if len(self.queue)>0: self.queue.clear() diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index e3b8202f2..4c4178b17 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -30,15 +25,11 @@ import cv2 import numpy as np from pkg_resources import parse_version +import logging as log - -def check_python_version(): - """ - returns current python version's - first bit - """ - return sys.version_info[0] - +log.basicConfig(format='%(name)s :: %(levelname)s :: %(message)s', level=log.DEBUG) +logger = log.getLogger('Helper') def check_CV_version(): @@ -60,7 +51,7 @@ def capPropId(property): try: integer_value = getattr(cv2, property) except Exception: - print('[ALERT]: {} is not a valid OpenCV property!'.format(property)) + logger.critical('{} is not a valid OpenCV property!'.format(property)) return None return integer_value @@ -97,7 +88,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa ffmpeg_download_path = tempfile.gettempdir() if logging: - print('[LOG]: FFmpeg Windows Download Path: {}'.format(ffmpeg_download_path)) + logger.debug('FFmpeg Windows Download Path: {}'.format(ffmpeg_download_path)) #download Binaries _path = download_ffmpeg_binaries(path = ffmpeg_download_path, os_windows = is_windows) @@ -107,8 +98,8 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa except Exception as e: #log if any error occurred if logging: - print(e) - print('[LOG]: Error downloading FFmpeg binaries, Check your network and Try again!') + self.logger.exception(str(e)) + logger.debug('Error in downloading FFmpeg binaries, Check your network and Try again!') return False if os.path.isfile(final_path): @@ -119,7 +110,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa final_path = os.path.join(final_path, 'ffmpeg.exe') else: #else return False - if logging: print('[LOG]: No valid FFmpeg executables found at Custom FFmpeg path!') + if logging: logger.debug('No valid FFmpeg executables found at Custom FFmpeg path!') return False else: #otherwise perform test for Unix @@ -134,14 +125,14 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa else: #else return False if logging: - print('[LOG]: No valid FFmpeg executables found at Custom FFmpeg path!') + logger.debug('No valid FFmpeg executables found at Custom FFmpeg path!') return False else: #otherwise assign ffmpeg binaries from system final_path += "ffmpeg" if logging: - print('[LOG]: Final FFmpeg Path: {}'.format(final_path)) + logger.debug('Final FFmpeg Path: {}'.format(final_path)) # Final Auto-Validation for FFmeg Binaries. returns final path if test is passed if validate_ffmpeg(final_path, logging = logging): @@ -177,7 +168,7 @@ def download_ffmpeg_binaries(path, os_windows = False): os.remove(file_name) #download and write file to the given path with open(file_name, "wb") as f: - print("No Custom FFmpeg path provided, Auto-Downloading binaries for Windows. Please wait...") + logger.debug("No Custom FFmpeg path provided, Auto-Downloading binaries for Windows. Please wait...") response = requests.get(file_url, stream=True) total_length = response.headers.get('content-length') if total_length is None: # no content length header @@ -191,12 +182,12 @@ def download_ffmpeg_binaries(path, os_windows = False): done = int(50 * dl / total_length) sys.stdout.write("\r[{}{}]{}{}".format('=' * done, ' ' * (50-done), done * 2, '%') ) sys.stdout.flush() - print("\nExtracting executables, Please Wait...") + logger.debug("\nExtracting executables, Please Wait...") with zipfile.ZipFile(file_name, "r") as zip_ref: zip_ref.extractall(base_path) #perform cleaning os.remove(file_name) - print("FFmpeg binaries for Windows Configured Successfully!") + logger.debug("FFmpeg binaries for Windows Configured Successfully!") final_path += file_path #return final path return final_path @@ -214,13 +205,13 @@ def validate_ffmpeg(path, logging = False): version = firstline.split(b' ')[2].strip() if logging: #log if test are passed - print('[LOG]: FFmpeg validity Test Passed!') - print('[LOG]: Found valid FFmpeg Version: `{}` installed on this system'.format(version)) + logger.debug('FFmpeg validity Test Passed!') + logger.debug('Found valid FFmpeg Version: `{}` installed on this system'.format(version)) except Exception as e: #log if test are failed if logging: - print(e) - print('[LOG]: FFmpeg validity Test Failed!') + self.logger.exception(str(e)) + logger.debug('FFmpeg validity Test Failed!') return False return True @@ -345,7 +336,7 @@ def generate_auth_certificates(path, overwrite = False): status_private_keys = validate_auth_keys(secret_keys_dir, '.key_secret') # raise error is validation test fails - if not(status_private_keys) or not(status_public_keys): raise RuntimeError('[Error]: Unable to generate valid ZMQ authentication certificates at `{}`!'.format(keys_dir)) + if not(status_private_keys) or not(status_public_keys): raise RuntimeError('[Helper:ERROR] :: Unable to generate valid ZMQ authentication certificates at `{}`!'.format(keys_dir)) # finally return valid key paths return (keys_dir, secret_keys_dir, public_keys_dir) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 45ffbe742..b72f415da 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -32,6 +27,7 @@ import time import os import random +import logging as log try: @@ -39,9 +35,9 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[NetGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip3 install opencv-python` command.') + raise ImportError('[NetGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip3 install opencv-python` command.') @@ -115,7 +111,7 @@ class NetGear: This attribute provides the flexibility to manipulate ZeroMQ internal parameters directly. Checkout vidgear docs for usage details. - :param (boolean) logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. + :param (boolean) self.logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. """ @@ -131,7 +127,12 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.ZMQError = ZMQError except ImportError as error: #raise error - raise ImportError('[ERROR]: pyzmq python library not installed. Kindly install it with `pip install pyzmq` command.') + raise ImportError('[NetGear:ERROR] :: pyzmq python library not installed. Kindly install it with `pip install pyzmq` command.') + + # enable logging if specified + self.logging = False + self.logger = log.getLogger('NetGear') + if logging: self.logging = logging #define valid messaging patterns => `0`: zmq.PAIR, `1`:(zmq.REQ,zmq.REP), and `1`:(zmq.SUB,zmq.PUB) valid_messaging_patterns = {0:(zmq.PAIR,zmq.PAIR), 1:(zmq.REQ,zmq.REP), 2:(zmq.PUB,zmq.SUB)} @@ -148,14 +149,14 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.pattern = 0 msg_pattern = valid_messaging_patterns[self.pattern] #log it - if logging: print('[LOG]: Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') + if self.logging: self.logger.debug('Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') #check whether user-defined messaging protocol is valid if not(protocol in ['tcp', 'udp', 'pgm', 'epgm', 'inproc', 'ipc']): # else default to `tcp` protocol protocol = 'tcp' #log it - if logging: print('[LOG]: protocol is not valid or provided. Defaulting to `tcp` protocol!') + if self.logging: self.logger.debug('protocol is not valid or provided. Defaulting to `tcp` protocol!') #generate random device id self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) @@ -198,8 +199,8 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.multiserver_mode = value else: self.multiserver_mode = False - print('[ALERT]: Multi-Server is disabled!') - raise ValueError('[ERROR]: `{}` pattern is not valid when Multi-Server Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) + self.logger.critical('Multi-Server is disabled!') + raise ValueError('[NetGear:ERROR] :: `{}` pattern is not valid when Multi-Server Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) elif key == 'filter' and isinstance(value, str): #custom filter in multi-server mode recv_filter = value @@ -207,18 +208,18 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r elif key == 'secure_mode' and isinstance(value,int) and (value in valid_security_mech): #secure mode try: - assert zmq.zmq_version_info() >= (4,0), "[ERROR]: ZMQ Security feature is not supported in libzmq version < 4.0." + assert zmq.zmq_version_info() >= (4,0), "[NetGear:ERROR] :: ZMQ Security feature is not supported in libzmq version < 4.0." self.secure_mode = value except Exception as e: - print(e) + self.logger.exception(str(e)) elif key == 'custom_cert_location' and isinstance(value,str): # custom auth certificates path try: - assert os.access(value, os.W_OK), "[ERROR]: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(value) - assert not(os.path.isfile(value)), "[ERROR]: `custom_cert_location` value must be the path to a directory and not to a file!" + assert os.access(value, os.W_OK), "[NetGear:ERROR] :: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(value) + assert not(os.path.isfile(value)), "[NetGear:ERROR] :: `custom_cert_location` value must be the path to a directory and not to a file!" custom_cert_location = os.path.abspath(value) except Exception as e: - print(e) + self.logger.exception(str(e)) elif key == 'overwrite_cert' and isinstance(value,bool): # enable/disable auth certificate overwriting overwrite_cert = value @@ -231,12 +232,12 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # specify encoding/decoding params if receive_mode and isinstance(value, int): self.compression_params = value - if logging: print("[LOG]: Decoding flag: {}.".format(value)) + if self.logging: self.logger.debug("Decoding flag: {}.".format(value)) elif not(receive_mode) and isinstance(value, (list,tuple)): - if logging: print("[LOG]: Encoding parameters: {}.".format(value)) + if self.logging: self.logger.debug("Encoding parameters: {}.".format(value)) self.compression_params = list(value) else: - if logging: print("[WARNING]: Invalid compression parameters: {} skipped!".format(value)) + if self.logging: self.logger.warning("Invalid compression parameters: {} skipped!".format(value)) self.compression_params = cv2.IMREAD_COLOR if receive_mode else [] # skip to defaults # enable bi-directional data transmission if specified @@ -246,18 +247,18 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.bi_mode = True else: self.bi_mode = False - print('[ALERT]: Bi-Directional data transmission is disabled!') - raise ValueError('[ERROR]: `{}` pattern is not valid when Bi-Directional Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) + self.logger.critical('Bi-Directional data transmission is disabled!') + raise ValueError('[NetGear:ERROR] :: `{}` pattern is not valid when Bi-Directional Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) # enable force socket closing if specified elif key == 'force_terminate' and isinstance(value, bool): # check if pattern is valid if address is None and not(receive_mode): self.force_close = False - print('[ALERT]: Force termination is disabled for local servers!') + self.logger.critical('Force termination is disabled for local servers!') else: self.force_close = True - if logging: print("[LOG]: Force termination is enabled for this connection!") + if self.logging: self.logger.debug("Force termination is enabled for this connection!") # various ZMQ flags elif key == 'flag' and isinstance(value, int): @@ -278,10 +279,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # log if overwriting is enabled if overwrite_cert: if not receive_mode: - if logging: print('[WARNING]: Overwriting ZMQ Authentication certificates over previous ones!') + if self.logging: self.logger.warning('Overwriting ZMQ Authentication certificates over previous ones!') else: overwrite_cert = False - if logging: print('[ALERT]: Overwriting ZMQ Authentication certificates is disabled for Client-end!') + if self.logging: self.logger.critical('Overwriting ZMQ Authentication certificates is disabled for Client-end!') #generate and validate certificates path try: @@ -290,38 +291,36 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if os.path.isdir(custom_cert_location): #custom certificate location must be a directory (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) else: - raise ValueError("[ERROR]: Invalid `custom_cert_location` value!") + raise ValueError("[NetGear:ERROR] :: Invalid `custom_cert_location` value!") else: # otherwise auto-generate suitable path from os.path import expanduser (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) #log it - if logging: print('[LOG]: `{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) + if self.logging: self.logger.debug('`{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) except Exception as e: # catch if any error occurred - print(e) + self.logger.exception(str(e)) # also disable secure mode self.secure_mode = 0 - print('[WARNING]: ZMQ Security Mechanism is disabled for this connection!') + self.logger.warning('ZMQ Security Mechanism is disabled for this connection!') else: #log if disabled - if logging: print('[LOG]: ZMQ Security Mechanism is disabled for this connection!') + if self.logging: self.logger.debug('ZMQ Security Mechanism is disabled for this connection!') #handle bi_mode if self.bi_mode: #disable bi_mode if multi-server is enabled if self.multiserver_mode: self.bi_mode = False - print('[ALERT]: Bi-Directional Data Transmission is disabled when Multi-Server Mode is Enabled due to incompatibility!') + self.logger.critical('Bi-Directional Data Transmission is disabled when Multi-Server Mode is Enabled due to incompatibility!') else: #enable force termination by default self.force_close = True - if logging: print("[LOG]: Force termination is enabled for this connection by default!") - if logging: print('[LOG]: Bi-Directional Data Transmission is enabled for this connection!') - - # enable logging if specified - self.logging = logging + if self.logging: + self.logger.debug("Force termination is enabled for this connection by default!") + self.logger.debug('Bi-Directional Data Transmission is enabled for this connection!') # initialize termination flag self.terminate = False @@ -346,10 +345,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # check if unique server port address list/tuple is assigned or not in multiserver_mode if port is None or not isinstance(port, (tuple, list)): # raise error if not - raise ValueError('[ERROR]: Incorrect port value! Kindly provide a list/tuple of ports while Multi-Server mode is enabled. For more information refer VidGear docs.') + raise ValueError('[NetGear:ERROR] :: Incorrect port value! Kindly provide a list/tuple of ports while Multi-Server mode is enabled. For more information refer VidGear docs.') else: #otherwise log it - print('[LOG]: Enabling Multi-Server Mode at PORTS: {}!'.format(port)) + self.logger.debug('Enabling Multi-Server Mode at PORTS: {}!'.format(port)) #create port address buffer for keeping track of incoming server's port self.port_buffer = [] else: @@ -421,30 +420,31 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_socket.setsockopt(zmq.LINGER, 0) except Exception as e: + self.logger.exception(str(e)) # otherwise raise value error if errored - if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) + if self.secure_mode: self.logger.warning('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) if self.multiserver_mode: - raise ValueError('[ERROR]: Multi-Server Mode, failed to connect to ports: {} with pattern: {}! Kindly recheck all parameters.'.format( str(port), pattern)) + raise ValueError('[NetGear:ERROR] :: Multi-Server Mode, failed to connect to ports: {} with pattern: {}! Kindly recheck all parameters.'.format( str(port), pattern)) else: - raise ValueError('[ERROR]: Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + raise ValueError('[NetGear:ERROR] :: Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) #log and enable threaded queue mode - if logging: print('[LOG]: Threaded Queue Mode is enabled by default for NetGear.') + if self.logging: self.logger.debug('Threaded Queue Mode is enabled by default for NetGear.') #define deque and assign it to global var self.queue = deque(maxlen=96) #max len 96 to check overflow # initialize and start threading instance - self.thread = Thread(target=self.update, args=()) + self.thread = Thread(target=self.update, name='NetGear', args=()) self.thread.daemon = True self.thread.start() - if logging: + if self.logging: #finally log progress - print('[LOG]: Successfully Binded to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) - if self.secure_mode: print('[LOG]: Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) - print('[LOG]: Multi-threaded Receive Mode is enabled Successfully!') - print('[LOG]: Device Unique ID is {}.'.format(self.id)) - print('[LOG]: Receive Mode is activated successfully!') + self.logger.debug('Successfully Binded to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + if self.secure_mode: self.logger.debug('Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) + self.logger.debug('Multi-threaded Receive Mode is enabled Successfully!') + self.logger.debug('Device Unique ID is {}.'.format(self.id)) + self.logger.debug('Receive Mode is activated successfully!') else: #otherwise default to `Send Mode` if address is None: address = 'localhost'#define address @@ -454,10 +454,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # check if unique server port address is assigned or not in multiserver_mode if port is None: #raise error is not - raise ValueError('[ERROR]: Kindly provide a unique & valid port value at Server-end. For more information refer VidGear docs.') + raise ValueError('[NetGear:ERROR] :: Kindly provide a unique & valid port value at Server-end. For more information refer VidGear docs.') else: #otherwise log it - print('[LOG]: Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) + self.logger.debug('Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) #assign value to global variable self.port = port else: @@ -510,17 +510,18 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r self.msg_socket.setsockopt(zmq.LINGER, 0) except Exception as e: + self.logger.exception(str(e)) #log if errored - if self.secure_mode: print('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) + if self.secure_mode: self.logger.warning('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) # raise value error - raise ValueError('[ERROR]: Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + raise ValueError('[NetGear:ERROR] :: Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) - if logging: + if self.logging: #finally log progress - print('[LOG]: Successfully connected to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) - if self.secure_mode: print('[LOG]: Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) - print('[LOG]: This device Unique ID is {}.'.format(self.id)) - print('[LOG]: Send Mode is successfully activated and ready to send data!') + self.logger.debug('Successfully connected to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + if self.secure_mode: self.logger.debug('Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) + self.logger.debug('This device Unique ID is {}.'.format(self.id)) + self.logger.debug('Send Mode is successfully activated and ready to send data!') @@ -558,26 +559,26 @@ def update(self): # check and remove from which ports signal is received if msg_json['port'] in self.port_buffer: # if pattern is 1, then send back server the info about termination - if self.pattern == 1: self.msg_socket.send_string('[INFO]: Termination signal received at client!') + if self.pattern == 1: self.msg_socket.send_string('Termination signal received at client!') self.port_buffer.remove(msg_json['port']) - if self.logging: print('[ALERT]: Termination signal received from server at port: {}!'.format(msg_json['port'])) + if self.logging: self.logger.warning('Termination signal received from server at port: {}!'.format(msg_json['port'])) #if termination signal received from all servers then exit client. if not self.port_buffer: - print('[WARNING]: Termination signal received from all Servers!!!') + self.logger.warning('Termination signal received from all Servers!!!') self.terminate = True #termination continue else: # if pattern is 1, then send back server the info about termination - if self.pattern == 1: self.msg_socket.send_string('[INFO]: Termination signal received at client!') + if self.pattern == 1: self.msg_socket.send_string('Termination signal received at client!') #termination self.terminate = True #notify client - if self.logging: print('[ALERT]: Termination signal received from server!') + if self.logging: self.logger.warning('Termination signal received from server!') continue #check if pattern is same at both server's and client's end. if int(msg_json['pattern']) != self.pattern: - raise ValueError("[ERROR]: Messaging patterns on both Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") + raise ValueError("[NetGear:ERROR] :: Messaging patterns on both Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") self.terminate = True continue @@ -592,7 +593,7 @@ def update(self): self.msg_socket.send_json(bi_dict, self.msg_flag) else: # send confirmation message to server - self.msg_socket.send_string('[LOG]: Data received on device: {} !'.format(self.id)) + self.msg_socket.send_string('Data received on device: {} !'.format(self.id)) # recover frame from array buffer frame_buffer = np.frombuffer(msg_data, dtype=msg_json['dtype']) @@ -605,7 +606,7 @@ def update(self): #check if valid frame returned if frame is None: #otherwise raise error and exit - raise ValueError("[ERROR]: `{}` Frame Decoding failed with Parameter: {}".format(msg_json['compression'], self.compression_params)) + raise ValueError("[NetGear:ERROR] :: `{}` Frame Decoding failed with Parameter: {}".format(msg_json['compression'], self.compression_params)) self.terminate = True continue @@ -645,7 +646,7 @@ def recv(self, return_data = None): # check whether `receive mode` is activated if not(self.receive_mode): #raise value error and exit - raise ValueError('[ERROR]: `recv()` function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') + raise ValueError('[NetGear:ERROR] :: `recv()` function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') self.terminate = True #handle bi-directional return data @@ -673,7 +674,7 @@ def send(self, frame, message = None): # check whether `receive_mode` is disabled if self.receive_mode: #raise value error and exit - raise ValueError('[ERROR]: `send()` function cannot be used while receive_mode is enabled. Kindly refer vidgear docs!') + raise ValueError('[NetGear:ERROR] :: `send()` function cannot be used while receive_mode is enabled. Kindly refer vidgear docs!') self.terminate = True # define exit_flag and assign value @@ -690,7 +691,7 @@ def send(self, frame, message = None): #check if it works if not(retval): #otherwise raise error and exit - raise ValueError("[ERROR]: Frame Encoding failed with format: {} and Parameters: {}".format(self.compression, self.compression_params)) + raise ValueError("[NetGear:ERROR] :: Frame Encoding failed with format: {} and Parameters: {}".format(self.compression, self.compression_params)) self.terminate = True @@ -729,7 +730,7 @@ def send(self, frame, message = None): #otherwise log normally recv_confirmation = self.msg_socket.recv() # log confirmation - if self.logging : print(recv_confirmation) + if self.logging : self.logger.debug(recv_confirmation) @@ -740,7 +741,7 @@ def close(self): """ if self.logging: #log it - print(' \n[LOG]: Terminating various {} Processes \n'.format('Receive Mode' if self.receive_mode else 'Send Mode')) + self.logger.debug('Terminating various {} Processes.'.format('Receive Mode' if self.receive_mode else 'Send Mode')) # whether `receive_mode` is enabled or not if self.receive_mode: # indicate that process should be terminated diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index 86dd07efd..d5651cb23 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -28,7 +23,7 @@ from pkg_resources import parse_version import sys, time from .helper import capPropId - +import logging as log try: @@ -37,10 +32,10 @@ # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[PiGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip3 install opencv-python` command.') + raise ImportError('[PiGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip3 install opencv-python` command.') @@ -62,7 +57,7 @@ class PiGear: / These attribute provides the flexibility to manipulate input raspicam video stream directly. / Parameters can be passed using this **option, allows you to pass key worded variable length of arguments to PiGear Class. - :param (boolean) logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. + :param (boolean) self.logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. :param (integer) time_delay: sets time delay(in seconds) before start reading the frames. / This delay is essentially required for camera to warm-up. @@ -79,20 +74,27 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo except Exception as error: if isinstance(error, ImportError): # Output expected ImportErrors. - raise ImportError('[ERROR]: Failed to detect Picamera executables, install it with "pip3 install picamera" command.') + raise ImportError('[PiGear:ERROR] :: Failed to detect Picamera executables, install it with "pip3 install picamera" command.') else: #Handle any API errors - raise RuntimeError('[ERROR]: Picamera API failure: {}'.format(error)) + raise RuntimeError('[PiGear:ERROR] :: Picamera API failure: {}'.format(error)) - assert (isinstance(framerate, (int, float)) and framerate > 5.0), "[ERROR]: Input framerate value `{}` is a Invalid! Kindly read docs.".format(framerate) - assert (isinstance(resolution, (tuple, list)) and len(resolution) == 2), "[ERROR]: Input resolution value `{}` is a Invalid! Kindly read docs.".format(resolution) - if not(isinstance(camera_num, int) and camera_num >= 0): print("[ERROR]: `camera_num` value is invalid, Kindly read docs!") + # enable logging if specified + self.logging = False + self.logger = log.getLogger('PiGear') + if logging: self.logging = logging + + assert (isinstance(framerate, (int, float)) and framerate > 5.0), "[PiGear:ERROR] :: Input framerate value `{}` is a Invalid! Kindly read docs.".format(framerate) + assert (isinstance(resolution, (tuple, list)) and len(resolution) == 2), "[PiGear:ERROR] :: Input resolution value `{}` is a Invalid! Kindly read docs.".format(resolution) + if not(isinstance(camera_num, int) and camera_num >= 0): + camera_num = 0 + self.logger.warning("Input camera_num value `{}` is invalid, Defaulting to index 0!") # initialize the picamera stream at given index self.camera = PiCamera(camera_num = camera_num) self.camera.resolution = tuple(resolution) self.camera.framerate = framerate - if logging: print("[LOG]: Activating Pi camera at index: {} with resolution: {} & framerate: {}".format(camera_num, resolution, framerate)) + if self.logging: self.logger.debug("Activating Pi camera at index: {} with resolution: {} & framerate: {}".format(camera_num, resolution, framerate)) #initialize framerate variable self.framerate = framerate @@ -110,9 +112,9 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo if options and "HWFAILURE_TIMEOUT" in options: #for altering timeout variable manually if isinstance(options["HWFAILURE_TIMEOUT"],(int, float)): - if not(10.0 > options["HWFAILURE_TIMEOUT"] > 1.0): raise ValueError('[ERROR]: `HWFAILURE_TIMEOUT` value can only be between 1.0 ~ 10.0') + if not(10.0 > options["HWFAILURE_TIMEOUT"] > 1.0): raise ValueError('[PiGear:ERROR] :: `HWFAILURE_TIMEOUT` value can only be between 1.0 ~ 10.0') self.failure_timeout = options["HWFAILURE_TIMEOUT"] #assign special parameter - if logging: print("[LOG]: Setting HW Failure Timeout: {} seconds".format(self.failure_timeout)) + if self.logging: self.logger.debug("Setting HW Failure Timeout: {} seconds".format(self.failure_timeout)) del options["HWFAILURE_TIMEOUT"] #clean try: @@ -123,11 +125,11 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo # separately handle colorspace value to int conversion if not(colorspace is None): self.color_space = capPropId(colorspace.strip()) - if logging: print('[LOG]: Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) + if self.logging: self.logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) except Exception as e: # Catch if any error occurred - if logging: print(e) + if self.logging: self.logger.exception(str(e)) # enable rgb capture array thread and capture stream self.rawCapture = PiRGBArray(self.camera, size = resolution) @@ -143,8 +145,8 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo #render colorspace if defined if not(self.frame is None and self.color_space is None): self.frame = cv2.cvtColor(self.frame, self.color_space) except Exception as e: - print(e) - raise RuntimeError('[ERROR]: Camera Module failed to initialize!') + self.logger.exception(str(e)) + raise RuntimeError('[PiGear:ERROR] :: Camera Module failed to initialize!') # applying time delay to warm-up picamera only if specified if time_delay: time.sleep(time_delay) @@ -156,9 +158,6 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo self._timer = None self.t_elasped = 0.0 #records time taken by thread - # enable logging if specified - self.logging = logging - # catching thread exceptions self.exceptions = None @@ -172,12 +171,12 @@ def start(self): start the thread to read frames from the video stream and initiate internal timer """ #Start frame producer thread - self.thread = Thread(target=self.update, args=()) + self.thread = Thread(target=self.update, name='PiGear', args=()) self.thread.daemon = True self.thread.start() #Start internal timer thread - self._timer = Thread(target=self._timeit, args=()) + self._timer = Thread(target=self._timeit, name='PiTimer', args=()) self._timer.daemon = True self._timer.start() @@ -197,7 +196,7 @@ def _timeit(self): #check for frozen thread if time.time() - self.t_elasped > self.failure_timeout: #log failure - if self.logging: print("[WARNING]: Camera Module Disconnected!") + if self.logging: self.logger.critical("Camera Module Disconnected!") #prepare for clean exit self.exceptions = True self.terminate = True #self-terminate @@ -240,14 +239,14 @@ def update(self): color_frame = cv2.cvtColor(frame, self.color_space) else: self.color_space = None - if self.logging: print('[LOG]: Colorspace value `{}` is not a valid colorspace!'.format(self.color_space)) + if self.logging: self.logger.debug('Colorspace value `{}` is not a valid colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None if self.logging: - print(e) - print('[WARNING]: Input colorspace is not a valid Colorspace!') + self.logger.exception(str(e)) + self.logger.warning('Input colorspace is not a valid Colorspace!') if not(color_frame is None): self.frame = color_frame @@ -276,12 +275,12 @@ def read(self): #clear frame self.frame = None #notify user about hardware failure - raise SystemError('[ERROR]: Hardware failure occurred, Kindly reconnect Camera Module and restart your Pi!') + raise SystemError('[PiGear:ERROR] :: Hardware failure occurred, Kindly reconnect Camera Module and restart your Pi!') else: #clear frame self.frame = None # re-raise error for debugging - error_msg = "[ERROR]: Camera Module API failure occured: {}".format(self.exceptions[1]) + error_msg = "[PiGear:ERROR] :: Camera Module API failure occured: {}".format(self.exceptions[1]) raise RuntimeError(error_msg).with_traceback(self.exceptions[2]) # return the frame @@ -293,7 +292,7 @@ def stop(self): """ Terminates the Read process """ - if self.logging: print("[LOG]: Terminating PiGear Process.") + if self.logging: self.logger.debug("Terminating PiGear Processes.") # make sure that the threads should be terminated self.terminate = True @@ -316,5 +315,4 @@ def stop(self): self.thread = None else: #properly handle thread exit - self.thread.join() - + self.thread.join() \ No newline at end of file diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index 6c6f97fb2..d7902185f 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -29,7 +24,7 @@ from .helper import capPropId import numpy as np import time - +import logging as log try: @@ -37,10 +32,10 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[ScreenGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') + raise ImportError('[ScreenGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') @@ -83,7 +78,12 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): from mss.exception import ScreenShotError except ImportError as error: # otherwise raise import error - raise ImportError('[ERROR]: python-mss library not found, install it with `pip install mss` command.') + raise ImportError('[ScreenGear:ERROR] :: python-mss library not found, install it with `pip install mss` command.') + + # enable logging if specified + self.logging = False + self.logger = log.getLogger('ScreenGear') + if logging: self.logging = logging # create mss object self.mss_object = mss() @@ -92,7 +92,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): if (monitor >= 0): monitor_instance = self.mss_object.monitors[monitor] else: - raise ValueError("[ERROR]: `monitor` value cannot be negative, Read Docs!") + raise ValueError("[ScreenGear:ERROR] :: `monitor` value cannot be negative, Read Docs!") # Initialize Queue self.queue = None @@ -102,7 +102,7 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): #define deque and assign it to global var self.queue = deque(maxlen=96) #max len 96 to check overflow #log it - if logging: print('[LOG]: Enabling Threaded Queue Mode by default for ScreenGear!') + if logging: self.logger.debug('Enabling Threaded Queue Mode by default for ScreenGear!') #intiate screen dimension handler screen_dims = {} @@ -114,17 +114,17 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): # separately handle colorspace value to int conversion if not(colorspace is None): self.color_space = capPropId(colorspace.strip()) - if logging: print('[LOG]: Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) + if logging: self.logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) except Exception as e: # Catch if any error occurred - if logging: print(e) + if logging: self.logger.exception(str(e)) # intialize mss capture instance self.mss_capture_instance = None try: # check whether user-defined dimensions are provided if screen_dims and len(screen_dims) == 4: - if logging: print('[LOG]: Setting capture dimensions: {}!'.format(screen_dims)) + if logging: self.logger.debug('Setting capture dimensions: {}!'.format(screen_dims)) self.mss_capture_instance = screen_dims #create instance from dimensions else: self.mss_capture_instance = monitor_instance #otherwise create instance from monitor @@ -135,11 +135,9 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): self.queue.append(self.frame) except ScreenShotError: #otherwise catch and log errors - raise ValueError("[ERROR]: ScreenShotError caught: Wrong dimensions passed to python-mss, Kindly Refer Docs!") - if logging: print(self.mss_object.get_error_details()) + if logging: self.logger.error(self.mss_object.get_error_details()) + raise ValueError("[ScreenGear:ERROR] :: ScreenShotError caught, Wrong dimensions passed to python-mss, Kindly Refer Docs!") - # enable logging if specified - self.logging = logging # thread initialization self.thread=None # initialize termination flag @@ -150,7 +148,7 @@ def start(self): """ start the thread to read frames from the video stream """ - self.thread = Thread(target=self.update, args=()) + self.thread = Thread(target=self.update, name='ScreenGear', args=()) self.thread.daemon = True self.thread.start() return self @@ -196,13 +194,13 @@ def update(self): color_frame = cv2.cvtColor(frame, self.color_space) else: self.color_space = None - if self.logging: print('[LOG]: Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + if self.logging: self.logger.debug('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None if self.logging: - print(e) - print('[LOG]: Input Colorspace is not a valid Colorspace!') + self.logger.exception(str(e)) + self.logger.debug('Input Colorspace is not a valid Colorspace!') if not(color_frame is None): self.frame = color_frame else: @@ -231,6 +229,7 @@ def stop(self): """ Terminates the Read process """ + if self.logging: self.logger.debug("Terminating ScreenGear Processes.") #terminate Threaded queue mode seperately if self.threaded_queue_mode and not(self.queue is None): self.queue.clear() @@ -241,10 +240,4 @@ def stop(self): # wait until stream resources are released (producer thread might be still grabbing frame) if self.thread is not None: self.thread.join() - #properly handle thread exit - - - - - - + #properly handle thread exit \ No newline at end of file diff --git a/vidgear/gears/stabilizer.py b/vidgear/gears/stabilizer.py index fea117c99..afc8aa61c 100644 --- a/vidgear/gears/stabilizer.py +++ b/vidgear/gears/stabilizer.py @@ -2,27 +2,22 @@ # published on February 20, 2014 by nghiaho12 (http://nghiaho.com/?p=2093) """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -31,7 +26,7 @@ from .helper import check_CV_version import numpy as np import cv2 - +import logging as log class Stabilizer: @@ -59,6 +54,11 @@ def __init__(self, smoothing_radius = 25, border_type = 'black', border_size = 0 self.frame_queue = deque(maxlen=smoothing_radius) self.frame_queue_indexes = deque(maxlen=smoothing_radius) + # enable logging if specified + self.logging = False + self.logger = log.getLogger('Stabilizer') + if logging: self.logging = logging + # define and create Adaptive histogram equalization (AHE) object for optimizations self.clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) @@ -78,11 +78,11 @@ def __init__(self, smoothing_radius = 25, border_type = 'black', border_size = 0 self.crop_n_zoom = border_size #crops and zoom frame to original size self.border_size = 0 #zero out border size self.frame_size = None #handles frame size for zooming - if logging: print('[LOG]: Setting Cropping margin {} pixels'.format(border_size)) + if logging: self.logger.debug('Setting Cropping margin {} pixels'.format(border_size)) else: # Add output borders to frame self.border_size = border_size - if logging and border_size: print('[LOG]: Setting Border size {} pixels'.format(border_size)) + if self.logging and border_size: self.logger.debug('Setting Border size {} pixels'.format(border_size)) # define valid border modes border_modes = {'black': cv2.BORDER_CONSTANT,'reflect': cv2.BORDER_REFLECT, 'reflect_101': cv2.BORDER_REFLECT_101, 'replicate': cv2.BORDER_REPLICATE, 'wrap': cv2.BORDER_WRAP} @@ -91,22 +91,19 @@ def __init__(self, smoothing_radius = 25, border_type = 'black', border_size = 0 if not crop_n_zoom: #initialize global border mode variable self.border_mode = border_modes[border_type] - if logging and border_type != 'black': print('[LOG]: Setting Border type: {}'.format(border_type)) + if self.logging and border_type != 'black': self.logger.debug('Setting Border type: {}'.format(border_type)) else: #log and reset to default - if logging and border_type != 'black': print('[LOG]: Setting border type is disabled if cropping is enabled!') + if self.logging and border_type != 'black': self.logger.debug('Setting border type is disabled if cropping is enabled!') self.border_mode = border_modes['black'] else: #otherwise log if not - if logging: print('[LOG]: Invalid input border type!') + if logging: self.logger.debug('Invalid input border type!') self.border_mode = border_modes['black'] #reset to default mode # define normalized box filter self.box_filter = np.ones(smoothing_radius)/smoothing_radius - # decide whether to log - self.logging = logging - def stabilize(self, frame): @@ -294,10 +291,4 @@ def clean(self): #clear frame deque self.frame_queue.clear() #clear frame indexes deque - self.frame_queue_indexes.clear() - - - - - - \ No newline at end of file + self.frame_queue_indexes.clear() \ No newline at end of file diff --git a/vidgear/gears/videogear.py b/vidgear/gears/videogear.py index 189576eb0..58bca1f14 100644 --- a/vidgear/gears/videogear.py +++ b/vidgear/gears/videogear.py @@ -1,31 +1,26 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ # import the necessary packages from .camgear import CamGear - +import logging as log class VideoGear: @@ -84,8 +79,14 @@ class VideoGear: def __init__(self, enablePiCamera = False, stabilize = False, source = 0, y_tube = False, backend = 0, colorspace = None, resolution = (640, 480), framerate = 25, logging = False, time_delay = 0, **options): + #initialize stabilizer self.stablization_mode = stabilize + # enable logging if specified + self.logging = False + self.logger = log.getLogger('VideoGear') + if logging: self.logging = logging + if self.stablization_mode: from .stabilizer import Stabilizer s_radius, border_size, border_type, crop_n_zoom = (25, 0, 'black', False) #defaults @@ -107,7 +108,7 @@ def __init__(self, enablePiCamera = False, stabilize = False, source = 0, y_tube crop_n_zoom = options["CROP_N_ZOOM"] #assigsn special parameter del options["CROP_N_ZOOM"] #clean self.stabilizer_obj = Stabilizer(smoothing_radius = s_radius, border_type = border_type, border_size = border_size, crop_n_zoom = crop_n_zoom, logging = logging) - if logging: print('[LOG]: Enabling Stablization Mode for the current video source!') #log info + if self.logging: self.logger.debug('Enabling Stablization Mode for the current video source!') #log info if enablePiCamera: # only import the pigear module only if required @@ -143,6 +144,7 @@ def read(self): def stop(self): + if self.logging: self.logger.debug("Terminating VideoGear.") # stop the thread and release any resources self.stream.stop() #clean queue diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 3036cef92..96e6b7563 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -27,6 +22,7 @@ from pkg_resources import parse_version import os, sys, time import subprocess as sp +import logging as log from .helper import get_valid_ffmpeg_path from .helper import capPropId @@ -39,9 +35,9 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[ERROR]: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[WriteGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') except ImportError as error: - raise ImportError('[ERROR]: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') + raise ImportError('[WriteGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') @@ -98,7 +94,11 @@ def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg # assign parameter values to class variables self.compression = compression_mode self.os_windows = True if os.name == 'nt' else False #checks if machine in-use is running windows os or not - self.logging = logging #enable logging + + # enable logging if specified + self.logging = False + self.logger = log.getLogger('WriteGear') + if logging: self.logging = logging # initialize various important class variables self.output_parameters = {} @@ -116,7 +116,7 @@ def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg # handles output file name (if not given) if not output_filename: - raise ValueError('[ERROR]: Kindly provide a valid `output_filename` value, Refer VidGear Docs for more information!') + raise ValueError('[WriteGear:ERROR] :: Kindly provide a valid `output_filename` value, Refer VidGear Docs for more information!') elif output_filename and os.path.isdir(output_filename): # check if directory path is given instead output_filename = os.path.join(output_filename, 'VidGear-{}.mp4'.format(time.strftime("%Y%m%d-%H%M%S"))) # auto-assign valid name and adds it to path else: @@ -137,15 +137,15 @@ def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg try: self.output_parameters = {str(k).strip().lower(): str(v).strip() for k,v in output_params.items()} except Exception as e: - if self.logging: print(e) - raise ValueError('[ERROR]: Wrong output_params parameters passed to WriteGear class!') + if self.logging: self.logger.exception(str(e)) + raise ValueError('[WriteGear:ERROR] :: Wrong output_params parameters passed to WriteGear class!') #handles FFmpeg binaries validity tests if self.compression: if self.logging: - print('[LOG]: Compression Mode is enabled therefore checking for valid FFmpeg executables!') - print(self.output_parameters) + self.logger.debug('Compression Mode is enabled therefore checking for valid FFmpeg executables!') + self.logger.debug(self.output_parameters) # handles where to save the downloaded FFmpeg Static Binaries on Windows(if specified) ffmpeg_download_path_ = '' @@ -165,12 +165,12 @@ def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg if actual_command: self.ffmpeg += actual_command #assign it to class variable if self.logging: - print('[LOG]: Found valid FFmpeg executables: `{}`'.format(self.ffmpeg)) + self.logger.debug('Found valid FFmpeg executables: `{}`'.format(self.ffmpeg)) else: #otherwise disable Compression Mode if self.logging and not self.os_windows: - print('[LOG]: Kindly install working FFmpeg or provide a valid custom FFmpeg Path') - print('[LOG]: Caution: Disabling Video Compression Mode since no valid FFmpeg executables found on this machine!') + self.logger.debug('Kindly install working FFmpeg or provide a valid custom FFmpeg Path') + self.logger.debug('Caution: Disabling Video Compression Mode since no valid FFmpeg executables found on this machine!') self.compression = False # compression mode disabled #validate this class has the access rights to specified directory or not @@ -179,9 +179,9 @@ def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg #display confirmation if logging is enabled/disabled if self.compression and self.ffmpeg: self.DEVNULL = open(os.devnull, 'wb') - if self.logging: print('[LOG]: Compression Mode is configured properly!') + if self.logging: self.logger.debug('Compression Mode is configured properly!') else: - if self.logging: print('[LOG]: Compression Mode is disabled, Activating OpenCV In-built Writer!') + if self.logging: self.logger.debug('Compression Mode is disabled, Activating OpenCV In-built Writer!') @@ -207,14 +207,14 @@ def write(self, frame, rgb_mode = False): self.inputwidth = width self.inputchannels = channels if self.logging: - print('[LOG]: InputFrame => Height:{} Width:{} Channels:{}'.format(self.inputheight, self.inputwidth, self.inputchannels)) + self.logger.debug('InputFrame => Height:{} Width:{} Channels:{}'.format(self.inputheight, self.inputwidth, self.inputchannels)) #validate size of frame if height != self.inputheight or width != self.inputwidth: - raise ValueError('[ERROR]: All frames in a video should have same size') + raise ValueError('[WriteGear:ERROR] :: All frames in a video should have same size') #validate number of channels if channels != self.inputchannels: - raise ValueError('[ERROR]: All frames in a video should have same number of channels') + raise ValueError('[WriteGear:ERROR] :: All frames in a video should have same number of channels') if self.compression: # checks if compression mode is enabled @@ -231,7 +231,7 @@ def write(self, frame, rgb_mode = False): self.process.stdin.write(frame.tostring()) except (OSError, IOError): # log something is wrong! - print ('[ERROR]: BrokenPipeError caught: Wrong Values passed to FFmpeg Pipe, Kindly Refer Docs!') + self.logger.error('BrokenPipeError caught: Wrong Values passed to FFmpeg Pipe, Kindly Refer Docs!') self.DEVNULL.close() raise ValueError #for testing purpose only else: @@ -243,7 +243,7 @@ def write(self, frame, rgb_mode = False): assert self.process is not None if self.logging: # log OpenCV warning - print('[WARNING]: RGBA and 16-bit grayscale video frames are not supported by OpenCV yet, switch to `compression_mode` to use them!') + self.logger.warning('RGBA and 16-bit grayscale video frames are not supported by OpenCV yet, switch to `compression_mode` to use them!') #write the frame self.process.write(frame) @@ -283,8 +283,7 @@ def Preprocess(self, channels, rgb = False): if self.inputframerate > 5: #set input framerate - minimum threshold is 5.0 - if self.logging: - print("Setting Input FrameRate = {}".format(self.inputframerate)) + if self.logging: self.logger.debug("Setting Input FrameRate = {}".format(self.inputframerate)) input_parameters["-framerate"] = str(self.inputframerate) #initiate FFmpeg process @@ -324,7 +323,7 @@ def startFFmpeg_Process(self, input_params, output_params): self.cmd += " ".join(cmd) # Launch the FFmpeg process if self.logging: - print('[LOG]: Executing FFmpeg command: `{}`'.format(self.cmd)) + self.logger.debug('Executing FFmpeg command: `{}`'.format(self.cmd)) # In debugging mode self.process = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=None) else: @@ -341,21 +340,21 @@ def execute_ffmpeg_cmd(self, cmd = None): """ #check if valid command if cmd is None: - print('[Alert]: Input FFmpeg command is empty, Nothing to execute!') + self.logger.warning('Input FFmpeg command is empty, Nothing to execute!') return else: if not(isinstance(cmd, list)): - raise ValueError("[ERROR]: Invalid input FFmpeg command! Kindly read docs.") + raise ValueError("[WriteGear:ERROR] :: Invalid input FFmpeg command! Kindly read docs.") #check if Compression Mode is enabled - if not(self.compression): raise RuntimeError("[ERROR]: Compression Mode is disabled, Kindly enable it to access this function!") + if not(self.compression): raise RuntimeError("[WriteGear:ERROR] :: Compression Mode is disabled, Kindly enable it to access this function!") #add configured FFmpeg path cmd = [self.ffmpeg] + cmd try: if self.logging: - print('[LOG]: Executing FFmpeg command: `{}`'.format(' '.join(cmd))) + self.logger.debug('Executing FFmpeg command: `{}`'.format(' '.join(cmd))) # In debugging mode sp.call(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=None) else: @@ -363,7 +362,7 @@ def execute_ffmpeg_cmd(self, cmd = None): sp.call(cmd, stdin=sp.PIPE, stdout=self.DEVNULL, stderr=sp.STDOUT) except (OSError, IOError): # log something is wrong! - print ('[ERROR]: BrokenPipeError caught: Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!') + self.logger.error('BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!') self.DEVNULL.close() raise ValueError #for testing purpose only @@ -409,13 +408,12 @@ def startCV_Process(self): except Exception as e: # log if something is wrong - if self.logging: - print(e) - raise ValueError('[ERROR]: Wrong Values passed to OpenCV Writer, Kindly Refer Docs!') + if self.logging: self.logger.exception(str(e)) + raise ValueError('[WriteGear:ERROR] :: Wrong Values passed to OpenCV Writer, Kindly Refer Docs!') if self.logging: #log values for debugging - print('[LOG]: FILE_PATH: {}, FOURCC = {}, FPS = {}, WIDTH = {}, HEIGHT = {}, BACKEND = {}'.format(self.out_file, FOURCC, FPS, WIDTH, HEIGHT, BACKEND)) + self.logger.debug('FILE_PATH: {}, FOURCC = {}, FPS = {}, WIDTH = {}, HEIGHT = {}, BACKEND = {}'.format(self.out_file, FOURCC, FPS, WIDTH, HEIGHT, BACKEND)) #start different process for with/without Backend. if BACKEND: @@ -429,6 +427,8 @@ def close(self): """ Terminates the Write process """ + if self.logging: self.logger.debug("Terminating WriteGear Processes.") + if self.compression: #if Compression Mode is enabled if self.process is None: @@ -450,14 +450,4 @@ def close(self): #if Compression Mode is disabled if self.process is None: return #no process was initiated at first place - self.process.release() #close it - - - - - - - - - - + self.process.release() #close it \ No newline at end of file diff --git a/vidgear/tests/__init__.py b/vidgear/tests/__init__.py index a9c9dad2b..3473f6813 100644 --- a/vidgear/tests/__init__.py +++ b/vidgear/tests/__init__.py @@ -1 +1 @@ -__author__ = "Abhishek Thakur " +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/tests/benchmark_tests/__init__.py b/vidgear/tests/benchmark_tests/__init__.py index b531d785d..446caa1ef 100644 --- a/vidgear/tests/benchmark_tests/__init__.py +++ b/vidgear/tests/benchmark_tests/__init__.py @@ -1,3 +1,3 @@ from .fps import FPS -__author__ = "Abhishek Thakur " +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py b/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py index 32d3a6561..f0e639d62 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_Videocapture.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -29,7 +24,9 @@ import tempfile from vidgear.gears import CamGear from .fps import FPS +import logging as log +logger = log.getLogger('Test_benchmark_videocapture') def return_testvideo_path(): @@ -54,9 +51,9 @@ def Videocapture_withCV(path): fps_CV.update() fps_CV.stop() stream.release() - print("OpenCV") - print("[LOG] total elasped time: {:.2f}".format(fps_CV.total_time_elapsed())) - print("[LOG] approx. FPS: {:.2f}".format(fps_CV.fps())) + logger.debug("OpenCV") + logger.debug("total elasped time: {:.2f}".format(fps_CV.total_time_elapsed())) + logger.debug("approx. FPS: {:.2f}".format(fps_CV.fps())) @@ -74,9 +71,9 @@ def Videocapture_withVidGear(path): fps_Vid.update() fps_Vid.stop() stream.stop() - print("VidGear") - print("[LOG] total elasped time: {:.2f}".format(fps_Vid.total_time_elapsed())) - print("[LOG] approx. FPS: {:.2f}".format(fps_Vid.fps())) + logger.debug("VidGear") + logger.debug("total elasped time: {:.2f}".format(fps_Vid.total_time_elapsed())) + logger.debug("approx. FPS: {:.2f}".format(fps_Vid.fps())) @pytest.mark.xfail(raises=RuntimeError) @@ -88,4 +85,4 @@ def test_benchmark_videocapture(): Videocapture_withCV(return_testvideo_path()) Videocapture_withVidGear(return_testvideo_path()) except Exception as e: - raise RuntimeError(e) + raise RuntimeError(e) \ No newline at end of file diff --git a/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py b/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py index a784cb775..52a66c7d4 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_Videowriter.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -29,7 +24,9 @@ from vidgear.gears import WriteGear from vidgear.gears import VideoGear from .fps import FPS +import logging as log +logger = log.getLogger('Test_benchmark_videowriter') def return_testvideo_path(): @@ -56,12 +53,12 @@ def return_static_ffmpeg(): -def WriteGear_non_compression_mode(path): +def WriteGear_non_compression_mode(): """ Function to Benchmark WriteGear's Non-Compression Mode(OpenCV) """ options = {'THREADED_QUEUE_MODE':False} - stream = VideoGear(source=path, **options).start() + stream = VideoGear(source=return_testvideo_path(), **options).start() writer = WriteGear(output_filename = 'Output_vnc.mp4', compression_mode = False ) fps_CV = FPS().start() while True: @@ -73,19 +70,19 @@ def WriteGear_non_compression_mode(path): fps_CV.stop() stream.stop() writer.close() - print("OpenCV Writer") - print("[LOG] total elasped time: {:.2f}".format(fps_CV.total_time_elapsed())) - print("[LOG] approx. FPS: {:.2f}".format(fps_CV.fps())) + logger.debug("OpenCV Writer") + logger.debug("total elasped time: {:.2f}".format(fps_CV.total_time_elapsed())) + logger.debug("approx. FPS: {:.2f}".format(fps_CV.fps())) os.remove(os.path.abspath('Output_vnc.mp4')) -def WriteGear_compression_mode(path): +def WriteGear_compression_mode(): """ Function to Benchmark WriteGear's Compression Mode(FFmpeg) """ options = {'THREADED_QUEUE_MODE':False} - stream = VideoGear(source=path, **options).start() + stream = VideoGear(source=return_testvideo_path(), **options).start() writer = WriteGear(output_filename = 'Output_vc.mp4', custom_ffmpeg = return_static_ffmpeg()) fps_Vid = FPS().start() while True: @@ -97,9 +94,9 @@ def WriteGear_compression_mode(path): fps_Vid.stop() stream.stop() writer.close() - print("FFmpeg Writer") - print("[LOG] total elasped time: {:.2f}".format(fps_Vid.total_time_elapsed())) - print("[LOG] approx. FPS: {:.2f}".format(fps_Vid.fps())) + logger.debug("FFmpeg Writer") + logger.debug("total elasped time: {:.2f}".format(fps_Vid.total_time_elapsed())) + logger.debug("approx. FPS: {:.2f}".format(fps_Vid.fps())) os.remove(os.path.abspath('Output_vc.mp4')) @@ -112,4 +109,4 @@ def test_benchmark_videowriter(): Videowriter_non_compression_mode(return_testvideo_path()) Videowriter_compression_mode(return_testvideo_path()) except Exception as e: - raise RuntimeError(e) + raise RuntimeError(e) \ No newline at end of file diff --git a/vidgear/tests/benchmark_tests/test_benchmark_playback.py b/vidgear/tests/benchmark_tests/test_benchmark_playback.py index 1eb1a3dc4..cb4e2955a 100644 --- a/vidgear/tests/benchmark_tests/test_benchmark_playback.py +++ b/vidgear/tests/benchmark_tests/test_benchmark_playback.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + http://www.apache.org/licenses/LICENSE-2.0 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -28,7 +23,9 @@ import tempfile from vidgear.gears import CamGear from .fps import FPS +import logging as log +logger = log.getLogger('Test_benchmark_playback') def return_testvideo(level=0): @@ -55,8 +52,8 @@ def playback(level): fps.update() stream.stop() fps.stop() - print("[LOG] total elasped time: {:.2f}".format(fps.total_time_elapsed())) - print("[LOG] approx. FPS: {:.2f}".format(fps.fps())) + logger.debug("total elasped time: {:.2f}".format(fps.total_time_elapsed())) + logger.debug("approx. FPS: {:.2f}".format(fps.fps())) @@ -69,6 +66,6 @@ def test_benchmark(level): try: playback(level) except Exception as e: - print(e) + logger.exception(str(e)) else: - print("Skipping this test for macOS!") + logger.debug("Skipping this test for macOS!") \ No newline at end of file diff --git a/vidgear/tests/network_tests/__init__.py b/vidgear/tests/network_tests/__init__.py index 03cf63684..096aade25 100644 --- a/vidgear/tests/network_tests/__init__.py +++ b/vidgear/tests/network_tests/__init__.py @@ -1,2 +1,2 @@ -__author__ = "Abhishek Thakur " +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/tests/network_tests/test_netgear.py b/vidgear/tests/network_tests/test_netgear.py index b22b56b29..1ef3a60d2 100644 --- a/vidgear/tests/network_tests/test_netgear.py +++ b/vidgear/tests/network_tests/test_netgear.py @@ -1,27 +1,23 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ + from vidgear.gears import NetGear from vidgear.gears import VideoGear @@ -33,7 +29,9 @@ import numpy as np import traceback from zmq.error import ZMQError +import logging as log +logger = log.getLogger('Test_netgear') def return_testvideo_path(): @@ -67,7 +65,7 @@ def test_playback(): client.close() except Exception as e: if isinstance(e, (ZMQError, ValueError)): - print(traceback.print_tb(e.__traceback__)) + logger.debug(traceback.print_tb(e.__traceback__)) else: pytest.fail(str(e)) @@ -106,7 +104,7 @@ def test_patterns(pattern): assert np.array_equal(frame_server, frame_client) except Exception as e: if isinstance(e, (ZMQError, ValueError)): - print(traceback.print_tb(e.__traceback__)) + logger.error(traceback.print_tb(e.__traceback__)) else: pytest.fail(str(e)) @@ -136,7 +134,7 @@ def test_compression(): client.close() except Exception as e: if isinstance(e, (ZMQError, ValueError)): - print(traceback.print_tb(e.__traceback__)) + logger.error(traceback.print_tb(e.__traceback__)) else: pytest.fail(str(e)) @@ -180,7 +178,7 @@ def test_secure_mode(pattern, security_mech, custom_cert_location, overwrite_cer assert np.array_equal(frame_server, frame_client) except Exception as e: if isinstance(e, (ZMQError, ValueError)): - print(traceback.print_tb(e.__traceback__)) + logger.error(traceback.print_tb(e.__traceback__)) else: pytest.fail(str(e)) @@ -193,7 +191,7 @@ def test_bidirectional_mode(target_data): Testing NetGear's Bidirectional Mode with different datatypes """ try: - print('[LOG] Given Input Data: {}'.format(target_data)) + logger.debug('Given Input Data: {}'.format(target_data)) #open strem stream = VideoGear(source=return_testvideo_path()).start() @@ -218,9 +216,9 @@ def test_bidirectional_mode(target_data): server.close() client.close() - #print data recieved at client-end and server-end - print('[LOG] Data recieved at Server-end: {}'.format(server_data)) - print('[LOG] Data recieved at Client-end: {}'.format(client_data)) + #logger.debug data recieved at client-end and server-end + logger.debug('Data recieved at Server-end: {}'.format(server_data)) + logger.debug('Data recieved at Client-end: {}'.format(client_data)) #check if recieved frame exactly matches input frame assert np.array_equal(frame_server, frame_client) @@ -228,7 +226,7 @@ def test_bidirectional_mode(target_data): assert client_data == server_data except Exception as e: if isinstance(e, (ZMQError, ValueError)): - print(traceback.print_tb(e.__traceback__)) + logger.error(traceback.print_tb(e.__traceback__)) else: pytest.fail(str(e)) @@ -291,6 +289,6 @@ def test_multiserver_mode(): except Exception as e: if isinstance(e, (ZMQError, ValueError)): - print(traceback.print_tb(e.__traceback__)) + logger.error(traceback.print_tb(e.__traceback__)) else: pytest.fail(str(e)) \ No newline at end of file diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index 3ccf47ca0..6573e1994 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -1,36 +1,33 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ import os, pytest, tempfile, shutil, platform from os.path import expanduser +import logging as log from vidgear.gears.helper import download_ffmpeg_binaries from vidgear.gears.helper import validate_ffmpeg from vidgear.gears.helper import get_valid_ffmpeg_path from vidgear.gears.helper import generate_auth_certificates +logger = log.getLogger('Test_helper') def return_static_ffmpeg(): @@ -56,10 +53,10 @@ def test_ffmpeg_static_installation(): for root, dirs, files in os.walk(startpath): level = root.replace(startpath, '').count(os.sep) indent = ' ' * 4 * (level) - print('[LOG]: {}{}/'.format(indent, os.path.basename(root))) + logger.debug('{}{}/'.format(indent, os.path.basename(root))) subindent = ' ' * 4 * (level + 1) for f in files: - print('[LOG]: {}{}'.format(subindent, f)) + logger.debug('{}{}'.format(subindent, f)) @@ -138,7 +135,7 @@ def test_generate_auth_certificates(paths, overwrite_cert, results): Testing auto-Generation and auto-validation of CURVE ZMQ keys/certificates """ try: - if overwrite_cert: print('[WARNING]: Overwriting ZMQ Authentication certificates over previous ones!') + if overwrite_cert: logger.warning('Overwriting ZMQ Authentication certificates over previous ones!') output = generate_auth_certificates(paths, overwrite = overwrite_cert) if paths != 'wrong_test_path': assert bool(output) == results diff --git a/vidgear/tests/videocapture_tests/__init__.py b/vidgear/tests/videocapture_tests/__init__.py index a9c9dad2b..3473f6813 100644 --- a/vidgear/tests/videocapture_tests/__init__.py +++ b/vidgear/tests/videocapture_tests/__init__.py @@ -1 +1 @@ -__author__ = "Abhishek Thakur " +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/tests/videocapture_tests/test_camgear.py b/vidgear/tests/videocapture_tests/test_camgear.py index cad6a1509..9d31ccbf7 100644 --- a/vidgear/tests/videocapture_tests/test_camgear.py +++ b/vidgear/tests/videocapture_tests/test_camgear.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -30,10 +25,10 @@ import pytest import tempfile import numpy as np - from vidgear.gears import CamGear +import logging as log - +logger = log.getLogger('Test_camgear') def return_youtubevideo_params(url): """ @@ -64,7 +59,7 @@ def return_total_frame_count(): while True: (grabbed, frame) = stream.read() if not grabbed: - print(num_cv) + logger.debug(num_cv) break num_cv += 1 stream.release() @@ -84,7 +79,7 @@ def test_threaded_queue_mode(): while True: frame = stream_camgear.read() if frame is None: - print(camgear_frames_num) + logger.debug(camgear_frames_num) break time.sleep(0.2) #dummy computational task @@ -119,19 +114,19 @@ def test_youtube_playback(): if height == 0 or width == 0: fps = stream.framerate height,width = frame.shape[:2] - print('[LOG]: WIDTH: {} HEIGHT: {} FPS: {}'.format(true_video_param[0],true_video_param[1],true_video_param[2])) - print('[LOG]: WIDTH: {} HEIGHT: {} FPS: {}'.format(width,height,fps)) + logger.debug('WIDTH: {} HEIGHT: {} FPS: {}'.format(true_video_param[0],true_video_param[1],true_video_param[2])) + logger.debug('WIDTH: {} HEIGHT: {} FPS: {}'.format(width,height,fps)) except Exception as error: - print(error) + logger.exception(error) errored = True if not errored: assert true_video_param[0] == width and true_video_param[1] == height and true_video_param[2] == fps else: - print('[LOG]: YouTube playback Test is skipped due to above error!') + logger.debug('YouTube playback Test is skipped due to above error!') else: - print('[LOG]: YouTube playback Test is skipped due to bug with opencv-python library builds on windows and macOS!') + logger.debug('YouTube playback Test is skipped due to bug with opencv-python library builds on windows and macOS!') @@ -161,25 +156,14 @@ def test_network_playback(): Output_data.append(frame) i+=1 output_stream.stop() - print('[LOG]: Output data shape:', np.array(Output_data).shape) + logger.debug('Output data shape:', np.array(Output_data).shape) if Output_data[-1].shape[:2] > (50,50): break except Exception as e: if isinstance(e, RuntimeError): - print("[LOG] `{}` URL is not working".format(Publictest_rstp_urls[index])) + logger.debug("`{}` URL is not working".format(Publictest_rstp_urls[index])) index+=1 continue else: pytest.fail(str(e)) - if (index == len(Publictest_rstp_urls)): pytest.fail(str(e)) - - - - - - - - - - - + if (index == len(Publictest_rstp_urls)): pytest.fail(str(e)) \ No newline at end of file diff --git a/vidgear/tests/videocapture_tests/test_screengear.py b/vidgear/tests/videocapture_tests/test_screengear.py index 272e0f91b..c5d3c3028 100644 --- a/vidgear/tests/videocapture_tests/test_screengear.py +++ b/vidgear/tests/videocapture_tests/test_screengear.py @@ -1,31 +1,29 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + http://www.apache.org/licenses/LICENSE-2.0 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ from vidgear.gears import ScreenGear from mss.exception import ScreenShotError import pytest, platform +import logging as log + +logger = log.getLogger('Test_screengear') def test_screengear(): """ @@ -46,6 +44,6 @@ def test_screengear(): stream.stop() except Exception as e: if platform.system() == 'Linux' or platform.system() == 'Windows': - print(e) + logger.exception(e) else: pytest.fail(str(e)) \ No newline at end of file diff --git a/vidgear/tests/videocapture_tests/test_videogear.py b/vidgear/tests/videocapture_tests/test_videogear.py index 84d48d4c5..f78fd5552 100644 --- a/vidgear/tests/videocapture_tests/test_videogear.py +++ b/vidgear/tests/videocapture_tests/test_videogear.py @@ -1,33 +1,28 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. =============================================== -""" +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#Video credit: http://www.liushuaicheng.org/CVPR2014/index.html + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================== +""" import pytest, os, tempfile from vidgear.gears import VideoGear +import logging as log +logger = log.getLogger('Test_videogear') def return_testvideo_path(): @@ -59,7 +54,7 @@ def test_CamGear_import(): output_stream = VideoGear(source = return_testvideo_path(), logging=True, **options).start() framerate = output_stream.framerate output_stream.stop() - print('[LOG] Input Framerate: {}'.format(framerate)) + logger.debug('Input Framerate: {}'.format(framerate)) assert framerate>0 except Exception as e: pytest.fail(str(e)) @@ -71,6 +66,7 @@ def test_video_stablization(): Testing VideoGear's Video Stablization playback capabilities """ try: + #Video credit: http://www.liushuaicheng.org/CVPR2014/index.html Url = 'https://raw.githubusercontent.com/abhiTronix/Imbakup/master/Images/example4_train_input.mp4' #define params options = {'SMOOTHING_RADIUS': 5, 'BORDER_SIZE': 10, 'BORDER_TYPE': 'replicate', 'CROP_N_ZOOM': True} diff --git a/vidgear/tests/writer_tests/__init__.py b/vidgear/tests/writer_tests/__init__.py index a9c9dad2b..3473f6813 100644 --- a/vidgear/tests/writer_tests/__init__.py +++ b/vidgear/tests/writer_tests/__init__.py @@ -1 +1 @@ -__author__ = "Abhishek Thakur " +__author__ = "Abhishek Thakur (@abhiTronix) " \ No newline at end of file diff --git a/vidgear/tests/writer_tests/test_IO.py b/vidgear/tests/writer_tests/test_IO.py index 2f5ab21e2..eb3a24e0e 100644 --- a/vidgear/tests/writer_tests/test_IO.py +++ b/vidgear/tests/writer_tests/test_IO.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -59,4 +54,4 @@ def test_failedextension(): with pytest.raises(ValueError): writer = WriteGear("garbage.garbage") writer.write(input_data) - writer.close() + writer.close() \ No newline at end of file diff --git a/vidgear/tests/writer_tests/test_compression_mode.py b/vidgear/tests/writer_tests/test_compression_mode.py index 8e5631e56..a8c1cf848 100644 --- a/vidgear/tests/writer_tests/test_compression_mode.py +++ b/vidgear/tests/writer_tests/test_compression_mode.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -34,7 +29,9 @@ import tempfile import os, platform import subprocess, re +import logging as log +logger = log.getLogger('Test_commpression_mode') def return_static_ffmpeg(): @@ -123,7 +120,7 @@ def test_write(conversion): if result: if not isinstance(result, string_types): result = result.decode() - print('[LOG]: Result: {}'.format(result)) + logger.debug('Result: {}'.format(result)) for i in ["Error", "Invalid", "error", "invalid"]: assert not(i in result) os.remove(os.path.abspath('Output_tw.mp4')) @@ -182,8 +179,7 @@ def test_WriteGear_compression(f_name, c_ffmpeg, output_params, result): if f_name and f_name != tempfile.gettempdir(): os.remove(os.path.abspath(f_name)) except Exception as e: - if result: - pytest.fail(str(e)) + if result: pytest.fail(str(e)) @@ -205,4 +201,4 @@ def test_WriteGear_customFFmpeg(): writer.execute_ffmpeg_cmd(ffmpeg_command_to_save_audio) #assert audio file is created successfully - assert os.path.isfile(output_audio_filename) + assert os.path.isfile(output_audio_filename) \ No newline at end of file diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index 905d24753..ac9c9ca95 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -1,25 +1,20 @@ """ -============================================ -vidgear library code is placed under the MIT license -Copyright (c) 2019 Abhishek Thakur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +=============================================== +vidgear library source-code is deployed under the Apache 2.0 License: + +Copyright (c) 2019 Abhishek Thakur(@abhiTronix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. =============================================== """ @@ -32,7 +27,9 @@ import pytest import cv2 import tempfile +import logging as log +logger = log.getLogger('Test_non_commpression_mode') def return_static_ffmpeg(): @@ -86,7 +83,7 @@ def test_write(conversion): if result: if not isinstance(result, string_types): result = result.decode() - print('[LOG]: Result: {}'.format(result)) + logger.debug('Result: {}'.format(result)) for i in ["Error", "Invalid", "error", "invalid"]: assert not(i in result) os.remove(os.path.abspath('Output_twc.avi')) @@ -117,5 +114,4 @@ def test_WriteGear_compression(f_name, output_params, result): if f_name and f_name != tempfile.gettempdir(): os.remove(os.path.abspath(f_name)) except Exception as e: - if result: - pytest.fail(str(e)) \ No newline at end of file + if result: pytest.fail(str(e)) \ No newline at end of file From e6aff9d123fff0fb808d8a39a768b565ae8719c6 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sat, 21 Dec 2019 11:40:24 +0530 Subject: [PATCH 30/39] Code Encapsulation & Updates: - implemented encapsulation for class functions and varibales on all gears - Updated README readibility --- README.md | 85 +++---- vidgear/gears/camgear.py | 108 +++++---- vidgear/gears/helper.py | 5 +- vidgear/gears/netgear.py | 439 ++++++++++++++++++------------------ vidgear/gears/pigear.py | 145 ++++++------ vidgear/gears/screengear.py | 120 +++++----- vidgear/gears/stabilizer.py | 162 ++++++------- vidgear/gears/videogear.py | 22 +- vidgear/gears/writegear.py | 249 ++++++++++---------- 9 files changed, 670 insertions(+), 665 deletions(-) diff --git a/README.md b/README.md index 23e91b89c..75cfa73bf 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi   -## Table of Contents +# Table of Contents [**TL;DR**](#tldr) @@ -86,7 +86,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi   -## TL;DR +# TL;DR > ***"VidGear is an [ultrafast➶][ultrafast-wiki], compact, flexible and easy-to-adapt complete Video Processing Python Library."*** @@ -98,7 +98,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi   -## Gears: +# Gears: > **VidGear is built with **multi-threaded APIs** *(a.k.a Gears)* each with some unique function/mechanism.** @@ -123,9 +123,9 @@ Each of these API is designed exclusively to handle/control different device-spe   -### CamGear +## CamGear -> **CamGear can grab ultrafast frames from diverse range of VideoStreams, which includes almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), various network stream protocols such as `http(s), rtp, rstp, rtmp, mms, etc.`, plus support for live Gstreamer's stream pipeline and YouTube video/livestreams URLs.** +> *CamGear can grab ultrafast frames from diverse range of VideoStreams, which includes almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), various network stream protocols such as `http(s), rtp, rstp, rtmp, mms, etc.`, plus support for live Gstreamer's stream pipeline and YouTube video/livestreams URLs.* CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs [`pafy`][pafy] python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear implements exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. @@ -136,15 +136,15 @@ CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` CamGear Functional Block Diagram

-#### CamGear API Guide: +### CamGear API Guide: [**>>> Usage Guide**][camgear-wiki]   -### VideoGear +## VideoGear -> **VideoGear API provides a special internal wrapper around VidGear's exclusive [**Video Stabilizer**][stablizer-wiki] class.** +> *VideoGear API provides a special internal wrapper around VidGear's exclusive [**Video Stabilizer**][stablizer-wiki] class.* Furthermore, VideoGear API can provide internal access to both [CamGear](#camgear) and [PiGear](#pigear) APIs separated by a special flag. Thereby, _this API holds the exclusive power for any incoming VideoStream from any source, whether it is live or not, to access and stabilize it directly with minimum latency and memory requirements._ @@ -205,15 +205,15 @@ stream_stab.stop() ``` -#### VideoGear API Guide: +### VideoGear API Guide: [**>>> Usage Guide**][videogear-wiki]   -### PiGear +## PiGear -> **PiGear is similar to CamGear but made to support various Raspberry Pi Camera Modules *(such as [OmniVision OV5647 Camera Module][OV5647-picam] and [Sony IMX219 Camera Module][IMX219-picam])*.** +> *PiGear is similar to CamGear but made to support various Raspberry Pi Camera Modules *(such as [OmniVision OV5647 Camera Module][OV5647-picam] and [Sony IMX219 Camera Module][IMX219-picam])*.* PiGear provides a flexible multi-threaded wrapper around complete [**picamera**][picamera] python library to interface with these modules correctly, and also grants the ability to exploit its various features like `brightness, saturation, sensor_mode, etc.` effortlessly. @@ -225,15 +225,15 @@ Best of all, PiGear API provides excellent Error-Handling with features like a t PiGear Functional Block Diagram

-#### PiGear API Guide: +### PiGear API Guide: [**>>> Usage Guide**][pigear-wiki]   -### ScreenGear +## ScreenGear -> **ScreenGear act as Screen Recorder, that can grab frames from your monitor in real-time either by define an area on the computer screen or fullscreen at the expense of insignificant latency. It also provide seemless support for capturing frames from multiple monitors.** +> *ScreenGear act as Screen Recorder, that can grab frames from your monitor in real-time either by define an area on the computer screen or fullscreen at the expense of insignificant latency. It also provide seemless support for capturing frames from multiple monitors.* ScreenGear provides a high-level multi-threaded wrapper around [**python-mss**][mss] python library API and also supports a easy and flexible direct internal parameter manipulation. @@ -279,7 +279,7 @@ stream.stop() # safely close video stream. ``` -#### ScreenGear API Guide: +### ScreenGear API Guide: [**>>> Usage Guide**][screengear-wiki] @@ -287,9 +287,9 @@ stream.stop()   -### WriteGear +## WriteGear -> **WriteGear handles various powerful Writer Tools that provide us the freedom to do almost anything imagine with multimedia files.** +> *WriteGear handles various powerful Writer Tools that provide us the freedom to do almost anything imagine with multimedia files.* WriteGear API provide a complete, flexible & robust wrapper around [**FFmpeg**][ffmpeg], a leading multimedia framework. With WriteGear, we can process real-time video frames into a lossless compressed format with any suitable specification in just few easy [lines of codes][compression-mode-ex]. These specifications include setting any video/audio property such as `bitrate, codec, framerate, resolution, subtitles, etc.` easily as well complex tasks such as multiplexing video with audio in real-time(see this [example wiki][live-audio-wiki]). Best of all, WriteGear grants the freedom to play with any FFmpeg parameter with its exclusive custom Command function(see this [example wiki][custom-command-wiki]), while handling all errors robustly. @@ -307,15 +307,15 @@ In addition to this, WriteGear also provides flexible access to [**OpenCV's Vide WriteGear Functional Block Diagram

-#### WriteGear API Guide: +### WriteGear API Guide: [**>>> Usage Guide**][writegear-wiki]   -### NetGear +## NetGear -> **NetGear is exclusively designed to transfer video frames synchronously and asynchronously between interconnecting systems over the network in real-time.** +> *NetGear is exclusively designed to transfer video frames synchronously and asynchronously between interconnecting systems over the network in real-time.* NetGear implements a high-level wrapper around [**PyZmQ**][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. @@ -341,7 +341,7 @@ Best of all, NetGear can robustly handle Multiple Servers at once, thereby provi NetGear Functional Block Diagram

-#### NetGear API Guide: +### NetGear API Guide: [**>>> Usage Guide**][netgear-wiki] @@ -349,26 +349,26 @@ Best of all, NetGear can robustly handle Multiple Servers at once, thereby provi   -## New Release SneekPeak : VidGear 0.1.6 +# New Release SneekPeak : VidGear 0.1.6 -***:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27] !*** +* ***:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27] !*** -**NetGear API:** +* **NetGear API:** * Added powerful ZMQ Authentication & Data Encryption features for NetGear API * Added robust Multi-Server support for NetGear API. * Added exclusive Bi-Directional Mode for bidirectional data transmission. * Added frame-compression support with on-the-fly flexible encoding/decoding. * Implemented new *Publish/Subscribe(`zmq.PUB/zmq.SUB`)* pattern for seamless Live Streaming in NetGear API. -**PiGear API:** +* **PiGear API:** * Added new threaded internal timing function for PiGear to handle any hardware failures/frozen threads * PiGear will not exit safely with `SystemError` if Picamera ribbon cable is pulled out to save resources. -**WriteGear API:** Added new `execute_ffmpeg_cmd` function to pass a custom command to its internal FFmpeg pipeline. +* **WriteGear API:** Added new `execute_ffmpeg_cmd` function to pass a custom command to its internal FFmpeg pipeline. -**Stabilizer class:** Added new _Crop and Zoom_ feature. +* **Stabilizer class:** Added new _Crop and Zoom_ feature. -***Added VidGear's official native support for MacOS environment and [many more...](changelog.md)*** +* ***Added VidGear's official native support for MacOS environment and [many more...](changelog.md)*** @@ -377,9 +377,9 @@ Best of all, NetGear can robustly handle Multiple Servers at once, thereby provi -## Installation +# Installation -### Prerequisites +## Prerequisites Before installing VidGear, you must verify that the following dependencies are met: @@ -419,9 +419,10 @@ Before installing VidGear, you must verify that the following dependencies are m pip3 install -U youtube-dl ``` -   +## Available Installation Options + ### Option 1: PyPI Install > Best option for **quickly** getting VidGear installed. @@ -429,15 +430,15 @@ Before installing VidGear, you must verify that the following dependencies are m ```sh pip3 install vidgear ``` -  + ### Option 2: Release Archive Download > Best option if you want a **compressed archive**. -VidGear releases are available for download as packages in the [latest release][release] +VidGear releases are available for download as packages in the [latest release][release]. + -  ### Option 3: Clone the Repository @@ -456,7 +457,7 @@ You can clone this repository's `testing` branch for development and thereby can -## Documentation +# Documentation The full documentation for all VidGear classes and functions can be found in the link below: @@ -464,7 +465,7 @@ The full documentation for all VidGear classes and functions can be found in the   -## Testing +# Testing * **Prerequisites:** Testing VidGear require some *additional dependencies & data* which can be downloaded manually as follows: @@ -491,28 +492,28 @@ The full documentation for all VidGear classes and functions can be found in the   -## Contributing +# Contributing -See [**contributing.md**](contributing.md) +See [**contributing.md**](contributing.md).   -## Supported Python legacies +# Supported Python legacies * **Python 3+ are only supported legacies for installing Vidgear v0.1.6 and above.** * **:warning: Python 2.7 legacy support [dropped in v0.1.6][drop27].**   -## Changelog +# Changelog See [**changelog.md**](changelog.md)   -## License +# License -Copyright © abhiTronix 2019 +**Copyright © abhiTronix 2019** This library is licensed under the **[Apache 2.0 License][license]**. diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 131b62a9f..28e665463 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -47,16 +47,15 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[CamGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') - + raise ImportError('[CamGear:ERROR] :: OpenCV API version >= 3.0 is only supported by this library.') except ImportError as error: - raise ImportError('[CamGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') + raise ImportError('[CamGear:ERROR] :: Failed to detect correct OpenCV executables, install it with `pip3 install opencv-python` command.') -def youtube_url_validation(url): +def self.__youtube_url_validation(url): """ - convert youtube video url and checks its validity + convert Youtube video URLs to a valid address """ youtube_regex = ( r'(https?://)?(www\.)?' @@ -112,12 +111,12 @@ class CamGear: def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, logging = False, time_delay = 0, **options): #intialize threaded queue mode - self.threaded_queue_mode = True + self.__threaded_queue_mode = True # enable logging if specified - self.logging = False - self.logger = log.getLogger('CamGear') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('CamGear') + if logging: self.__logging = logging # check if Youtube Mode is ON (True) if y_tube: @@ -125,43 +124,43 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l #import pafy and parse youtube stream url import pafy # validate - url = youtube_url_validation(source) + url = self.__youtube_url_validation(source) if url: source_object = pafy.new(url) _source = source_object.getbestvideo("any", ftypestrict=False) if _source is None: _source = source_object.getbest("any", ftypestrict=False) - if self.logging: self.logger.debug('YouTube source ID: `{}`, Title: `{}` & Video_Extension: `{}`'.format(url, source_object.title, _source.extension)) + if self.__logging: self.__logger.debug('YouTube source ID: `{}`, Title: `{}` & Video_Extension: `{}`'.format(url, source_object.title, _source.extension)) source = _source.url else: raise RuntimeError('URL cannot be processed!') except Exception as e: - if self.logging: self.logger.exception(str(e)) + if self.__logging: self.__logger.exception(str(e)) raise ValueError('[CamGear:ERROR] :: YouTube Mode is enabled and the input YouTube URL is invalid!') # youtube mode variable initialization - self.youtube_mode = y_tube + self.__youtube_mode = y_tube #User-Defined Threaded Queue Mode if options: if "THREADED_QUEUE_MODE" in options: if isinstance(options["THREADED_QUEUE_MODE"],bool): - self.threaded_queue_mode = options["THREADED_QUEUE_MODE"] #assigsn special parameter to global variable + self.__threaded_queue_mode = options["THREADED_QUEUE_MODE"] #assigsn special parameter to global variable del options["THREADED_QUEUE_MODE"] #clean #reformat option dict - self.queue = None + self.__queue = None #intialize deque for video files only - if self.threaded_queue_mode and isinstance(source,str): + if self.__threaded_queue_mode and isinstance(source,str): #import deque from collections import deque #define deque and assign it to global var - self.queue = deque(maxlen=96) #max len 96 to check overflow + self.__queue = deque(maxlen=96) #max len 96 to check overflow #log it - if self.logging: self.logger.debug('Enabling Threaded Queue Mode for the current video source!') + if self.__logging: self.__logger.debug('Enabling Threaded Queue Mode for the current video source!') else: #otherwise disable it - self.threaded_queue_mode = False + self.__threaded_queue_mode = False #log it - if self.logging: self.logger.debug('Threaded Queue Mode is disabled for the current video source!') + if self.__logging: self.__logger.debug('Threaded Queue Mode is disabled for the current video source!') # stream variable initialization self.stream = None @@ -192,11 +191,11 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l # separately handle colorspace value to int conversion if not(colorspace is None): self.color_space = capPropId(colorspace.strip()) - if self.logging: self.logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) + if self.__logging: self.__logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) except Exception as e: # Catch if any error occurred - if self.logging: self.logger.exception(str(e)) + if self.__logging: self.__logger.exception(str(e)) #initialize and assign framerate variable self.framerate = 0.0 @@ -204,7 +203,7 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l _fps = self.stream.get(cv2.CAP_PROP_FPS) if _fps>1: self.framerate = _fps except Exception as e: - if self.logging: self.logger.exception(str(e)) + if self.__logging: self.__logger.exception(str(e)) self.framerate = 0.0 # applying time delay to warm-up webcam only if specified @@ -218,17 +217,17 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l #render colorspace if defined if not(self.color_space is None): self.frame = cv2.cvtColor(self.frame, self.color_space) - if self.threaded_queue_mode: + if self.__threaded_queue_mode: #intitialize and append to queue - self.queue.append(self.frame) + self.__queue.append(self.frame) else: raise RuntimeError('[CamGear:ERROR] :: Source is invalid, CamGear failed to intitialize stream on this source!') # thread initialization - self.thread=None + self.__thread=None # initialize termination flag - self.terminate = False + self.__terminate = False @@ -236,14 +235,14 @@ def start(self): """ start the thread to read frames from the video stream """ - self.thread = Thread(target=self.update, name='CamGear', args=()) - self.thread.daemon = True - self.thread.start() + self.__thread = Thread(target=self.__update, name='CamGear', args=()) + self.__thread.daemon = True + self.__thread.start() return self - def update(self): + def __update(self): """ Update frames from stream """ @@ -251,12 +250,12 @@ def update(self): # keep iterating infinitely until the thread is terminated or frames runs out while True: # if the thread indicator variable is set, stop the thread - if self.terminate: + if self.__terminate: break - if self.threaded_queue_mode: + if self.__threaded_queue_mode: #check queue buffer for overflow - if len(self.queue) >= 96: + if len(self.__queue) >= 96: #stop iterating if overflowing occurs time.sleep(0.000001) continue @@ -267,8 +266,8 @@ def update(self): #check for valid frames if not grabbed: #no frames received, then safely exit - if self.threaded_queue_mode: - if len(self.queue) == 0: + if self.__threaded_queue_mode: + if len(self.__queue) == 0: break else: continue @@ -283,14 +282,13 @@ def update(self): color_frame = cv2.cvtColor(frame, self.color_space) else: self.color_space = None - if self.logging: self.logger.debug('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + if self.__logging: self.__logger.warning('Colorspace value: {}, is not a valid colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None - if self.logging: - self.logger.exception(str(e)) - self.logger.debug('Input Colorspace is not a valid Colorspace!') - + if self.__logging: + self.__logger.exception(str(e)) + self.__logger.warning('Input colorspace is not a valid colorspace!') if not(color_frame is None): self.frame = color_frame else: @@ -299,9 +297,9 @@ def update(self): self.frame = frame #append to queue - if self.threaded_queue_mode: self.queue.append(self.frame) + if self.__threaded_queue_mode: self.__queue.append(self.frame) - self.threaded_queue_mode = False + self.__threaded_queue_mode = False self.frame = None #release resources self.stream.release() @@ -312,9 +310,9 @@ def read(self): """ return the frame """ - while self.threaded_queue_mode: - if len(self.queue) > 0: - return self.queue.popleft() + while self.__threaded_queue_mode: + if len(self.__queue) > 0: + return self.__queue.popleft() return self.frame @@ -323,20 +321,20 @@ def stop(self): """ Terminates the Read process """ - if self.logging: self.logger.debug('Terminating processes.') + if self.__logging: self.__logger.debug('Terminating processes.') #terminate Threaded queue mode seperately - if self.threaded_queue_mode and not(self.queue is None): - if len(self.queue)>0: self.queue.clear() - self.threaded_queue_mode = False + if self.__threaded_queue_mode and not(self.__queue is None): + if len(self.__queue)>0: self.__queue.clear() + self.__threaded_queue_mode = False self.frame = None # indicate that the thread should be terminate - self.terminate = True + self.__terminate = True # wait until stream resources are released (producer thread might be still grabbing frame) - if self.thread is not None: - self.thread.join() + if self.__thread is not None: + self.__thread.join() #properly handle thread exit - if self.youtube_mode: + if self.__youtube_mode: # kill thread-lock in youtube mode - self.thread = None \ No newline at end of file + self.__thread = None \ No newline at end of file diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 4c4178b17..1bf56fecb 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -124,8 +124,7 @@ def get_valid_ffmpeg_path(custom_ffmpeg = '', is_windows = False, ffmpeg_downloa final_path = os.path.join(custom_ffmpeg, 'ffmpeg') else: #else return False - if logging: - logger.debug('No valid FFmpeg executables found at Custom FFmpeg path!') + if logging: logger.debug('No valid FFmpeg executables found at Custom FFmpeg path!') return False else: #otherwise assign ffmpeg binaries from system @@ -162,7 +161,7 @@ def download_ffmpeg_binaries(path, os_windows = False): import requests import zipfile #check if given pth has write access - assert os.access(path, os.W_OK), "Permission Denied: Cannot write ffmpeg binaries to directory = " + path + assert os.access(path, os.W_OK), "[Helper:ERROR] :: Permission Denied, Cannot write ffmpeg binaries to directory = " + path #remove leftovers if os.path.isfile(file_name): os.remove(file_name) diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index b72f415da..6400f27ce 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -35,9 +35,9 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[NetGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[NetGear:ERROR] :: OpenCV API version >= 3.0 is only supported by this library.') except ImportError as error: - raise ImportError('[NetGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip3 install opencv-python` command.') + raise ImportError('[NetGear:ERROR] :: Failed to detect correct OpenCV executables, install it with `pip3 install opencv-python` command.') @@ -67,12 +67,11 @@ class NetGear: Stonehouse is the minimum you would use over public networks and assures clients that they are speaking to an authentic server while allowing any client to connect. It is less secure but at the same time faster than IronHouse security mechanism. - * `IronHouse`: 2 => which extends Stonehouse with client public key authentication. This is the strongest security model ZMQ have today, + * `IronHouse`: 2 => which extends Stonehouse with client public key authentication. This is the strongest security model ZMQ have today, protecting against every attack we know about, except end-point attacks (where an attacker plants spyware on a machine to capture data before it's encrypted, or after it's decrypted). IronHouse enhanced security comes at a price of additional latency. - :param address(string): sets the valid network address of the server/client. Network addresses unique identifiers across the network. Its default value of this parameter is based on mode it is working, 'localhost' for Send Mode and `*` for Receive Mode. @@ -111,7 +110,7 @@ class NetGear: This attribute provides the flexibility to manipulate ZeroMQ internal parameters directly. Checkout vidgear docs for usage details. - :param (boolean) self.logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. + :param (boolean) logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. """ @@ -123,16 +122,16 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #import ZMQError from zmq.error import ZMQError #assign values to global variable for further use - self.zmq = zmq - self.ZMQError = ZMQError + self.__zmq = zmq + self.__ZMQError = ZMQError except ImportError as error: #raise error raise ImportError('[NetGear:ERROR] :: pyzmq python library not installed. Kindly install it with `pip install pyzmq` command.') # enable logging if specified - self.logging = False - self.logger = log.getLogger('NetGear') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('NetGear') + if logging: self.__logging = logging #define valid messaging patterns => `0`: zmq.PAIR, `1`:(zmq.REQ,zmq.REP), and `1`:(zmq.SUB,zmq.PUB) valid_messaging_patterns = {0:(zmq.PAIR,zmq.PAIR), 1:(zmq.REQ,zmq.REP), 2:(zmq.PUB,zmq.SUB)} @@ -143,50 +142,50 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if isinstance(pattern, int) and pattern in valid_messaging_patterns: #assign value msg_pattern = valid_messaging_patterns[pattern] - self.pattern = pattern #add it to global variable for further use + self.__pattern = pattern #add it to global variable for further use else: #otherwise default to 0:`zmq.PAIR` - self.pattern = 0 - msg_pattern = valid_messaging_patterns[self.pattern] + self.__pattern = 0 + msg_pattern = valid_messaging_patterns[self.__pattern] #log it - if self.logging: self.logger.debug('Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') + if self.__logging: self.__logger.warning('Wrong pattern value, Defaulting to `zmq.PAIR`! Kindly refer Docs for more Information.') #check whether user-defined messaging protocol is valid if not(protocol in ['tcp', 'udp', 'pgm', 'epgm', 'inproc', 'ipc']): # else default to `tcp` protocol protocol = 'tcp' #log it - if self.logging: self.logger.debug('protocol is not valid or provided. Defaulting to `tcp` protocol!') + if self.__logging: self.__logger.warning('protocol is not valid or provided. Defaulting to `tcp` protocol!') #generate random device id - self.id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) + self.__id = ''.join(random.choice('0123456789ABCDEF') for i in range(5)) - self.msg_flag = 0 #handles connection flags - self.msg_copy = True #handles whether to copy data - self.msg_track = False #handles whether to track packets + self.__msg_flag = 0 #handles connection flags + self.__msg_copy = True #handles whether to copy data + self.__msg_track = False #handles whether to track packets - self.multiserver_mode = False #handles multiserver_mode state + self.__multiserver_mode = False #handles multiserver_mode state recv_filter = '' #user-defined filter to allow specific port/servers only in multiserver_mode #define bi-directional data transmission mode - self.bi_mode = False #handles bi_mode state - self.bi_data = None #handles return data + self.__bi_mode = False #handles bi_mode state + self.__bi_data = None #handles return data #define valid ZMQ security mechanisms => `0`: Grasslands, `1`:StoneHouse, and `1`:IronHouse valid_security_mech = {0:'Grasslands', 1:'StoneHouse', 2:'IronHouse'} - self.secure_mode = 0 #handles ZMQ security layer status + self.__secure_mode = 0 #handles ZMQ security layer status auth_cert_dir = '' #handles valid ZMQ certificates dir - self.auth_publickeys_dir = '' #handles valid ZMQ public certificates dir - self.auth_secretkeys_dir = '' #handles valid ZMQ private certificates dir + self.__auth_publickeys_dir = '' #handles valid ZMQ public certificates dir + self.__auth_secretkeys_dir = '' #handles valid ZMQ private certificates dir overwrite_cert = False #checks if certificates overwriting allowed custom_cert_location = '' #handles custom ZMQ certificates path #handle force socket termination if there's latency in network - self.force_close = False + self.__force_close = False #define stream compression handlers - self.compression = '' #disabled by default - self.compression_params = None + self.__compression = '' #disabled by default + self.__compression_params = None #reformat dict options = {k.lower().strip(): v for k,v in options.items()} @@ -196,10 +195,10 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if key == 'multiserver_mode' and isinstance(value, bool): if pattern > 0: # multi-server mode - self.multiserver_mode = value + self.__multiserver_mode = value else: - self.multiserver_mode = False - self.logger.critical('Multi-Server is disabled!') + self.__multiserver_mode = False + self.__logger.critical('Multi-Server is disabled!') raise ValueError('[NetGear:ERROR] :: `{}` pattern is not valid when Multi-Server Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) elif key == 'filter' and isinstance(value, str): #custom filter in multi-server mode @@ -209,9 +208,9 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r #secure mode try: assert zmq.zmq_version_info() >= (4,0), "[NetGear:ERROR] :: ZMQ Security feature is not supported in libzmq version < 4.0." - self.secure_mode = value + self.__secure_mode = value except Exception as e: - self.logger.exception(str(e)) + self.__logger.exception(str(e)) elif key == 'custom_cert_location' and isinstance(value,str): # custom auth certificates path try: @@ -219,7 +218,7 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r assert not(os.path.isfile(value)), "[NetGear:ERROR] :: `custom_cert_location` value must be the path to a directory and not to a file!" custom_cert_location = os.path.abspath(value) except Exception as e: - self.logger.exception(str(e)) + self.__logger.exception(str(e)) elif key == 'overwrite_cert' and isinstance(value,bool): # enable/disable auth certificate overwriting overwrite_cert = value @@ -227,51 +226,51 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # handle encoding and decoding if specified elif key == 'compression_format' and isinstance(value,str) and value.lower().strip() in ['.jpg', '.jpeg', '.bmp', '.png']: #few are supported # enable encoding - if not(receive_mode): self.compression = value.lower().strip() + if not(receive_mode): self.__compression = value.lower().strip() elif key == 'compression_param': # specify encoding/decoding params if receive_mode and isinstance(value, int): - self.compression_params = value - if self.logging: self.logger.debug("Decoding flag: {}.".format(value)) + self.__compression_params = value + if self.__logging: self.__logger.debug("Decoding flag: {}.".format(value)) elif not(receive_mode) and isinstance(value, (list,tuple)): - if self.logging: self.logger.debug("Encoding parameters: {}.".format(value)) - self.compression_params = list(value) + if self.__logging: self.__logger.debug("Encoding parameters: {}.".format(value)) + self.__compression_params = list(value) else: - if self.logging: self.logger.warning("Invalid compression parameters: {} skipped!".format(value)) - self.compression_params = cv2.IMREAD_COLOR if receive_mode else [] # skip to defaults + if self.__logging: self.__logger.warning("Invalid compression parameters: {} skipped!".format(value)) + self.__compression_params = cv2.IMREAD_COLOR if receive_mode else [] # skip to defaults # enable bi-directional data transmission if specified elif key == 'bidirectional_mode' and isinstance(value, bool): # check if pattern is valid if pattern < 2: - self.bi_mode = True + self.__bi_mode = True else: - self.bi_mode = False - self.logger.critical('Bi-Directional data transmission is disabled!') + self.__bi_mode = False + self.__logger.critical('Bi-Directional data transmission is disabled!') raise ValueError('[NetGear:ERROR] :: `{}` pattern is not valid when Bi-Directional Mode is enabled. Kindly refer Docs for more Information.'.format(pattern)) # enable force socket closing if specified elif key == 'force_terminate' and isinstance(value, bool): # check if pattern is valid if address is None and not(receive_mode): - self.force_close = False - self.logger.critical('Force termination is disabled for local servers!') + self.__force_close = False + self.__logger.critical('Force termination is disabled for local servers!') else: - self.force_close = True - if self.logging: self.logger.debug("Force termination is enabled for this connection!") + self.__force_close = True + if self.__logging: self.__logger.warning("Force termination is enabled for this connection!") # various ZMQ flags elif key == 'flag' and isinstance(value, int): - self.msg_flag = value + self.__msg_flag = value elif key == 'copy' and isinstance(value, bool): - self.msg_copy = value + self.__msg_copy = value elif key == 'track' and isinstance(value, bool): - self.msg_track = value + self.__msg_track = value else: pass #handle secure mode - if self.secure_mode: + if self.__secure_mode: #import important libs import zmq.auth from zmq.auth.thread import ThreadAuthenticator @@ -279,60 +278,60 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r # log if overwriting is enabled if overwrite_cert: if not receive_mode: - if self.logging: self.logger.warning('Overwriting ZMQ Authentication certificates over previous ones!') + if self.__logging: self.__logger.warning('Overwriting ZMQ Authentication certificates over previous ones!') else: overwrite_cert = False - if self.logging: self.logger.critical('Overwriting ZMQ Authentication certificates is disabled for Client-end!') + if self.__logging: self.__logger.critical('Overwriting ZMQ Authentication certificates is disabled for Client-end!') #generate and validate certificates path try: #check if custom certificates path is specified if custom_cert_location: if os.path.isdir(custom_cert_location): #custom certificate location must be a directory - (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) + (auth_cert_dir, self.__auth_secretkeys_dir, self.__auth_publickeys_dir) = generate_auth_certificates(custom_cert_location, overwrite = overwrite_cert) else: raise ValueError("[NetGear:ERROR] :: Invalid `custom_cert_location` value!") else: # otherwise auto-generate suitable path from os.path import expanduser - (auth_cert_dir, self.auth_secretkeys_dir, self.auth_publickeys_dir) = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) + (auth_cert_dir, self.__auth_secretkeys_dir, self.__auth_publickeys_dir) = generate_auth_certificates(os.path.join(expanduser("~"),".vidgear"), overwrite = overwrite_cert) #log it - if self.logging: self.logger.debug('`{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) + if self.__logging: self.__logger.debug('`{}` is the default location for storing ZMQ authentication certificates/keys.'.format(auth_cert_dir)) except Exception as e: # catch if any error occurred - self.logger.exception(str(e)) + self.__logger.exception(str(e)) # also disable secure mode - self.secure_mode = 0 - self.logger.warning('ZMQ Security Mechanism is disabled for this connection!') + self.__secure_mode = 0 + self.__logger.warning('ZMQ Security Mechanism is disabled for this connection!') else: #log if disabled - if self.logging: self.logger.debug('ZMQ Security Mechanism is disabled for this connection!') + if self.__logging: self.__logger.warning('ZMQ Security Mechanism is disabled for this connection!') #handle bi_mode - if self.bi_mode: + if self.__bi_mode: #disable bi_mode if multi-server is enabled - if self.multiserver_mode: - self.bi_mode = False - self.logger.critical('Bi-Directional Data Transmission is disabled when Multi-Server Mode is Enabled due to incompatibility!') + if self.__multiserver_mode: + self.__bi_mode = False + self.__logger.critical('Bi-Directional Data Transmission is disabled when Multi-Server Mode is Enabled due to incompatibility!') else: #enable force termination by default - self.force_close = True - if self.logging: - self.logger.debug("Force termination is enabled for this connection by default!") - self.logger.debug('Bi-Directional Data Transmission is enabled for this connection!') + self.__force_close = True + if self.__logging: + self.__logger.warning("Force termination is enabled for this connection by default!") + self.__logger.debug('Bi-Directional Data Transmission is enabled for this connection!') # initialize termination flag - self.terminate = False + self.__terminate = False # initialize exit_loop flag - self.exit_loop = False + self.__exit_loop = False # initialize and assign receive mode to global variable - self.receive_mode = receive_mode + self.__receive_mode = receive_mode # define messaging context instance - self.msg_context = zmq.Context.instance() + self.__msg_context = zmq.Context.instance() #check whether `receive_mode` is enabled by user if receive_mode: @@ -341,123 +340,123 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r if address is None: address = '*' #define address #check if multiserver_mode is enabled - if self.multiserver_mode: + if self.__multiserver_mode: # check if unique server port address list/tuple is assigned or not in multiserver_mode if port is None or not isinstance(port, (tuple, list)): # raise error if not raise ValueError('[NetGear:ERROR] :: Incorrect port value! Kindly provide a list/tuple of ports while Multi-Server mode is enabled. For more information refer VidGear docs.') else: #otherwise log it - self.logger.debug('Enabling Multi-Server Mode at PORTS: {}!'.format(port)) + self.__logger.debug('Enabling Multi-Server Mode at PORTS: {}!'.format(port)) #create port address buffer for keeping track of incoming server's port - self.port_buffer = [] + self.__port_buffer = [] else: # otherwise assign local port address if None if port is None: port = '5555' try: # initiate and handle secure mode - if self.secure_mode > 0: + if self.__secure_mode > 0: # start an authenticator for this context - auth = ThreadAuthenticator(self.msg_context) + auth = ThreadAuthenticator(self.__msg_context) auth.start() auth.allow(str(address)) #allow current address #check if `IronHouse` is activated - if self.secure_mode == 2: + if self.__secure_mode == 2: # tell authenticator to use the certificate from given valid dir - auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + auth.configure_curve(domain='*', location=self.__auth_publickeys_dir) else: #otherwise tell the authenticator how to handle the CURVE requests, if `StoneHouse` is activated auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) # initialize and define thread-safe messaging socket - self.msg_socket = self.msg_context.socket(msg_pattern[1]) - if self.pattern == 2 and not(self.secure_mode): self.msg_socket.set_hwm(1) + self.__msg_socket = self.__msg_context.socket(msg_pattern[1]) + if self.__pattern == 2 and not(self.__secure_mode): self.__msg_socket.set_hwm(1) - if self.multiserver_mode: + if self.__multiserver_mode: # if multiserver_mode is enabled, then assign port addresses to zmq socket for pt in port: # enable specified secure mode for the zmq socket - if self.secure_mode > 0: + if self.__secure_mode > 0: # load server key - server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") + server_secret_file = os.path.join(self.__auth_secretkeys_dir, "server.key_secret") server_public, server_secret = zmq.auth.load_certificate(server_secret_file) # load all CURVE keys - self.msg_socket.curve_secretkey = server_secret - self.msg_socket.curve_publickey = server_public + self.__msg_socket.curve_secretkey = server_secret + self.__msg_socket.curve_publickey = server_public # enable CURVE connection for this socket - self.msg_socket.curve_server = True + self.__msg_socket.curve_server = True # define socket options - if self.pattern == 2: self.msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) + if self.__pattern == 2: self.__msg_socket.setsockopt_string(zmq.SUBSCRIBE, recv_filter) # bind socket to given server protocol, address and ports - self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(pt)) + self.__msg_socket.bind(protocol+'://' + str(address) + ':' + str(pt)) # define socket optimizer - self.msg_socket.setsockopt(zmq.LINGER, 0) + self.__msg_socket.setsockopt(zmq.LINGER, 0) else: # enable specified secure mode for the zmq socket - if self.secure_mode > 0: + if self.__secure_mode > 0: # load server key - server_secret_file = os.path.join(self.auth_secretkeys_dir, "server.key_secret") + server_secret_file = os.path.join(self.__auth_secretkeys_dir, "server.key_secret") server_public, server_secret = zmq.auth.load_certificate(server_secret_file) # load all CURVE keys - self.msg_socket.curve_secretkey = server_secret - self.msg_socket.curve_publickey = server_public + self.__msg_socket.curve_secretkey = server_secret + self.__msg_socket.curve_publickey = server_public # enable CURVE connection for this socket - self.msg_socket.curve_server = True + self.__msg_socket.curve_server = True # define exclusive socket options for patterns - if self.pattern == 2: self.msg_socket.setsockopt_string(zmq.SUBSCRIBE,'') + if self.__pattern == 2: self.__msg_socket.setsockopt_string(zmq.SUBSCRIBE,'') # bind socket to given protocol, address and port normally - self.msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) + self.__msg_socket.bind(protocol+'://' + str(address) + ':' + str(port)) # define socket optimizer - self.msg_socket.setsockopt(zmq.LINGER, 0) + self.__msg_socket.setsockopt(zmq.LINGER, 0) except Exception as e: - self.logger.exception(str(e)) + self.__logger.exception(str(e)) # otherwise raise value error if errored - if self.secure_mode: self.logger.warning('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) - if self.multiserver_mode: + if self.__secure_mode: self.__logger.warning('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.__secure_mode])) + if self.__multiserver_mode: raise ValueError('[NetGear:ERROR] :: Multi-Server Mode, failed to connect to ports: {} with pattern: {}! Kindly recheck all parameters.'.format( str(port), pattern)) else: raise ValueError('[NetGear:ERROR] :: Failed to bind address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) #log and enable threaded queue mode - if self.logging: self.logger.debug('Threaded Queue Mode is enabled by default for NetGear.') + if self.__logging: self.__logger.debug('Threaded Queue Mode is enabled by default for NetGear.') #define deque and assign it to global var - self.queue = deque(maxlen=96) #max len 96 to check overflow + self.__queue = deque(maxlen=96) #max len 96 to check overflow # initialize and start threading instance - self.thread = Thread(target=self.update, name='NetGear', args=()) - self.thread.daemon = True - self.thread.start() + self.__thread = Thread(target=self.__update, name='NetGear', args=()) + self.__thread.daemon = True + self.__thread.start() - if self.logging: + if self.__logging: #finally log progress - self.logger.debug('Successfully Binded to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) - if self.secure_mode: self.logger.debug('Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) - self.logger.debug('Multi-threaded Receive Mode is enabled Successfully!') - self.logger.debug('Device Unique ID is {}.'.format(self.id)) - self.logger.debug('Receive Mode is activated successfully!') + self.__logger.debug('Successfully Binded to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + if self.__secure_mode: self.__logger.debug('Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.__secure_mode])) + self.__logger.debug('Multi-threaded Receive Mode is enabled Successfully!') + self.__logger.debug('Device Unique ID is {}.'.format(self.__id)) + self.__logger.debug('Receive Mode is activated successfully!') else: #otherwise default to `Send Mode` if address is None: address = 'localhost'#define address #check if multiserver_mode is enabled - if self.multiserver_mode: + if self.__multiserver_mode: # check if unique server port address is assigned or not in multiserver_mode if port is None: #raise error is not raise ValueError('[NetGear:ERROR] :: Kindly provide a unique & valid port value at Server-end. For more information refer VidGear docs.') else: #otherwise log it - self.logger.debug('Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) + self.__logger.debug('Enabling Multi-Server Mode at PORT: {} on this device!'.format(port)) #assign value to global variable self.port = port else: @@ -466,66 +465,66 @@ def __init__(self, address = None, port = None, protocol = None, pattern = 0, r try: # initiate and handle secure mode - if self.secure_mode > 0: + if self.__secure_mode > 0: # start an authenticator for this context - auth = ThreadAuthenticator(self.msg_context) + auth = ThreadAuthenticator(self.__msg_context) auth.start() auth.allow(str(address)) #allow current address #check if `IronHouse` is activated - if self.secure_mode == 2: + if self.__secure_mode == 2: # tell authenticator to use the certificate from given valid dir - auth.configure_curve(domain='*', location=self.auth_publickeys_dir) + auth.configure_curve(domain='*', location=self.__auth_publickeys_dir) else: #otherwise tell the authenticator how to handle the CURVE requests, if `StoneHouse` is activated auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) # initialize and define thread-safe messaging socket - self.msg_socket = self.msg_context.socket(msg_pattern[0]) + self.__msg_socket = self.__msg_context.socket(msg_pattern[0]) - if self.pattern == 1: + if self.__pattern == 1: # if pattern is 1, define additional flags - self.msg_socket.REQ_RELAXED = True - self.msg_socket.REQ_CORRELATE = True - if self.pattern == 2 and not(self.secure_mode): self.msg_socket.set_hwm(1) # if pattern is 2, define additional optimizer + self.__msg_socket.REQ_RELAXED = True + self.__msg_socket.REQ_CORRELATE = True + if self.__pattern == 2 and not(self.__secure_mode): self.__msg_socket.set_hwm(1) # if pattern is 2, define additional optimizer # enable specified secure mode for the zmq socket - if self.secure_mode > 0: + if self.__secure_mode > 0: # load client key - client_secret_file = os.path.join(self.auth_secretkeys_dir, "client.key_secret") + client_secret_file = os.path.join(self.__auth_secretkeys_dir, "client.key_secret") client_public, client_secret = zmq.auth.load_certificate(client_secret_file) # load all CURVE keys - self.msg_socket.curve_secretkey = client_secret - self.msg_socket.curve_publickey = client_public + self.__msg_socket.curve_secretkey = client_secret + self.__msg_socket.curve_publickey = client_public # load server key - server_public_file = os.path.join(self.auth_publickeys_dir, "server.key") + server_public_file = os.path.join(self.__auth_publickeys_dir, "server.key") server_public, _ = zmq.auth.load_certificate(server_public_file) # inject public key to make a CURVE connection. - self.msg_socket.curve_serverkey = server_public + self.__msg_socket.curve_serverkey = server_public # connect socket to given protocol, address and port - self.msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) + self.__msg_socket.connect(protocol+'://' + str(address) + ':' + str(port)) # define socket options - self.msg_socket.setsockopt(zmq.LINGER, 0) + self.__msg_socket.setsockopt(zmq.LINGER, 0) except Exception as e: - self.logger.exception(str(e)) + self.__logger.exception(str(e)) #log if errored - if self.secure_mode: self.logger.warning('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.secure_mode])) + if self.__secure_mode: self.__logger.warning('Failed to activate ZMQ Security Mechanism: `{}` for this address!'.format(valid_security_mech[self.__secure_mode])) # raise value error raise ValueError('[NetGear:ERROR] :: Failed to connect address: {} and pattern: {}! Kindly recheck all parameters.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) - if self.logging: + if self.__logging: #finally log progress - self.logger.debug('Successfully connected to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) - if self.secure_mode: self.logger.debug('Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.secure_mode])) - self.logger.debug('This device Unique ID is {}.'.format(self.id)) - self.logger.debug('Send Mode is successfully activated and ready to send data!') + self.__logger.debug('Successfully connected to address: {} with pattern: {}.'.format((protocol+'://' + str(address) + ':' + str(port)), pattern)) + if self.__secure_mode: self.__logger.debug('Enabled ZMQ Security Mechanism: `{}` for this address, Successfully!'.format(valid_security_mech[self.__secure_mode])) + self.__logger.debug('This device Unique ID is {}.'.format(self.__id)) + self.__logger.debug('Send Mode is successfully activated and ready to send data!') - def update(self): + def __update(self): """ Updates recovered frames from messaging network to the queue """ @@ -533,67 +532,67 @@ def update(self): frame = None # keep looping infinitely until the thread is terminated - while not self.exit_loop: + while not self.__exit_loop: # check if global termination_flag is enabled - if self.terminate: + if self.__terminate: # check whether there is still frames in queue before breaking out - if len(self.queue)>0: + if len(self.__queue)>0: continue else: break #check queue buffer for overflow - if len(self.queue) >= 96: + if len(self.__queue) >= 96: #stop iterating if overflowing occurs time.sleep(0.000001) continue # extract json data out of socket - msg_json = self.msg_socket.recv_json(flags=self.msg_flag) + msg_json = self.__msg_socket.recv_json(flags=self.__msg_flag) # check if terminate_flag` received if msg_json['terminate_flag']: #if multiserver_mode is enabled - if self.multiserver_mode: + if self.__multiserver_mode: # check and remove from which ports signal is received - if msg_json['port'] in self.port_buffer: + if msg_json['port'] in self.__port_buffer: # if pattern is 1, then send back server the info about termination - if self.pattern == 1: self.msg_socket.send_string('Termination signal received at client!') - self.port_buffer.remove(msg_json['port']) - if self.logging: self.logger.warning('Termination signal received from server at port: {}!'.format(msg_json['port'])) + if self.__pattern == 1: self.__msg_socket.send_string('Termination signal received at client!') + self.__port_buffer.remove(msg_json['port']) + if self.__logging: self.__logger.warning('Termination signal received from server at port: {}!'.format(msg_json['port'])) #if termination signal received from all servers then exit client. - if not self.port_buffer: - self.logger.warning('Termination signal received from all Servers!!!') - self.terminate = True #termination + if not self.__port_buffer: + self.__logger.warning('Termination signal received from all Servers!!!') + self.__terminate = True #termination continue else: # if pattern is 1, then send back server the info about termination - if self.pattern == 1: self.msg_socket.send_string('Termination signal received at client!') + if self.__pattern == 1: self.__msg_socket.send_string('Termination signal received at client!') #termination - self.terminate = True + self.__terminate = True #notify client - if self.logging: self.logger.warning('Termination signal received from server!') + if self.__logging: self.__logger.warning('Termination signal received from server!') continue #check if pattern is same at both server's and client's end. - if int(msg_json['pattern']) != self.pattern: + if int(msg_json['pattern']) != self.__pattern: raise ValueError("[NetGear:ERROR] :: Messaging patterns on both Server-end & Client-end must a valid pairs! Kindly refer VidGear docs.") - self.terminate = True + self.__terminate = True continue # extract array from socket - msg_data = self.msg_socket.recv(flags=self.msg_flag, copy=self.msg_copy, track=self.msg_track) + msg_data = self.__msg_socket.recv(flags=self.__msg_flag, copy=self.__msg_copy, track=self.__msg_track) - if self.pattern != 2: + if self.__pattern != 2: # check if bi-directional mode is enabled - if self.bi_mode: + if self.__bi_mode: # handle return data - bi_dict = dict(data = self.bi_data) - self.msg_socket.send_json(bi_dict, self.msg_flag) + bi_dict = dict(data = self.__bi_data) + self.__msg_socket.send_json(bi_dict, self.__msg_flag) else: # send confirmation message to server - self.msg_socket.send_string('Data received on device: {} !'.format(self.id)) + self.__msg_socket.send_string('Data received on device: {} !'.format(self.__id)) # recover frame from array buffer frame_buffer = np.frombuffer(msg_data, dtype=msg_json['dtype']) @@ -602,38 +601,38 @@ def update(self): #check if encoding was enabled if msg_json['compression']: - frame = cv2.imdecode(frame, self.compression_params) + frame = cv2.imdecode(frame, self.__compression_params) #check if valid frame returned if frame is None: #otherwise raise error and exit - raise ValueError("[NetGear:ERROR] :: `{}` Frame Decoding failed with Parameter: {}".format(msg_json['compression'], self.compression_params)) - self.terminate = True + raise ValueError("[NetGear:ERROR] :: `{}` Frame Decoding failed with Parameter: {}".format(msg_json['compression'], self.__compression_params)) + self.__terminate = True continue - if self.multiserver_mode: + if self.__multiserver_mode: # check if multiserver_mode #save the unique port addresses - if not msg_json['port'] in self.port_buffer: - self.port_buffer.append(msg_json['port']) + if not msg_json['port'] in self.__port_buffer: + self.__port_buffer.append(msg_json['port']) #extract if any message from server and display it if msg_json['message']: - self.queue.append((msg_json['port'], msg_json['message'], frame)) + self.__queue.append((msg_json['port'], msg_json['message'], frame)) else: # append recovered unique port and frame to queue - self.queue.append((msg_json['port'],frame)) + self.__queue.append((msg_json['port'],frame)) else: #extract if any message from server if Bi-Directional Mode is enabled - if self.bi_mode and msg_json['message']: + if self.__bi_mode and msg_json['message']: # append grouped frame and data to queue - self.queue.append((msg_json['message'], frame)) + self.__queue.append((msg_json['message'], frame)) else: # otherwise append recovered frame to queue - self.queue.append(frame) + self.__queue.append(frame) # finally properly close the socket - self.msg_socket.close() + self.__msg_socket.close() @@ -644,19 +643,19 @@ def recv(self, return_data = None): :param return_data: handles return data for bi-directional mode """ # check whether `receive mode` is activated - if not(self.receive_mode): + if not(self.__receive_mode): #raise value error and exit raise ValueError('[NetGear:ERROR] :: `recv()` function cannot be used while receive_mode is disabled. Kindly refer vidgear docs!') - self.terminate = True + self.__terminate = True #handle bi-directional return data - if (self.bi_mode and not(return_data is None)): self.bi_data = return_data + if (self.__bi_mode and not(return_data is None)): self.__bi_data = return_data # check whether or not termination flag is enabled - while not self.terminate: + while not self.__terminate: # check if queue is empty - if len(self.queue)>0: - return self.queue.popleft() + if len(self.__queue)>0: + return self.__queue.popleft() else: continue # otherwise return NoneType @@ -672,13 +671,13 @@ def send(self, frame, message = None): :param message(string/int): additional message for the client(s) """ # check whether `receive_mode` is disabled - if self.receive_mode: + if self.__receive_mode: #raise value error and exit raise ValueError('[NetGear:ERROR] :: `send()` function cannot be used while receive_mode is enabled. Kindly refer vidgear docs!') - self.terminate = True + self.__terminate = True # define exit_flag and assign value - exit_flag = True if (frame is None or self.terminate) else False + exit_flag = True if (frame is None or self.__terminate) else False #check whether exit_flag is False if not(exit_flag) and not(frame.flags['C_CONTIGUOUS']): @@ -686,51 +685,51 @@ def send(self, frame, message = None): frame = np.ascontiguousarray(frame, dtype=frame.dtype) #handle encoding - if self.compression: - retval, frame = cv2.imencode(self.compression, frame, self.compression_params) + if self.__compression: + retval, frame = cv2.imencode(self.__compression, frame, self.__compression_params) #check if it works if not(retval): #otherwise raise error and exit - raise ValueError("[NetGear:ERROR] :: Frame Encoding failed with format: {} and Parameters: {}".format(self.compression, self.compression_params)) - self.terminate = True + raise ValueError("[NetGear:ERROR] :: Frame Encoding failed with format: {} and Parameters: {}".format(self.__compression, self.__compression_params)) + self.__terminate = True #check if multiserver_mode is activated - if self.multiserver_mode: + if self.__multiserver_mode: # prepare the exclusive json dict and assign values with unique port msg_dict = dict(terminate_flag = exit_flag, - compression=str(self.compression), + compression=str(self.__compression), port = self.port, - pattern = str(self.pattern), + pattern = str(self.__pattern), message = message, dtype = str(frame.dtype), shape = frame.shape) else: # otherwise prepare normal json dict and assign values msg_dict = dict(terminate_flag = exit_flag, - compression=str(self.compression), + compression=str(self.__compression), message = message, - pattern = str(self.pattern), + pattern = str(self.__pattern), dtype = str(frame.dtype), shape = frame.shape) # send the json dict - self.msg_socket.send_json(msg_dict, self.msg_flag|self.zmq.SNDMORE) + self.__msg_socket.send_json(msg_dict, self.__msg_flag|self.__zmq.SNDMORE) # send the frame array with correct flags - self.msg_socket.send(frame, flags = self.msg_flag, copy=self.msg_copy, track=self.msg_track) + self.__msg_socket.send(frame, flags = self.__msg_flag, copy=self.__msg_copy, track=self.__msg_track) # wait for confirmation - if self.pattern != 2: + if self.__pattern != 2: #check if bi-directional data transmission is enabled - if self.bi_mode: + if self.__bi_mode: #handle return data - return_dict = self.msg_socket.recv_json(flags=self.msg_flag) + return_dict = self.__msg_socket.recv_json(flags=self.__msg_flag) return return_dict['data'] if return_dict else None else: #otherwise log normally - recv_confirmation = self.msg_socket.recv() + recv_confirmation = self.__msg_socket.recv() # log confirmation - if self.logging : self.logger.debug(recv_confirmation) + if self.__logging : self.__logger.debug(recv_confirmation) @@ -739,30 +738,30 @@ def close(self): """ Terminates the NetGear processes safely """ - if self.logging: + if self.__logging: #log it - self.logger.debug('Terminating various {} Processes.'.format('Receive Mode' if self.receive_mode else 'Send Mode')) + self.__logger.debug('Terminating various {} Processes.'.format('Receive Mode' if self.__receive_mode else 'Send Mode')) # whether `receive_mode` is enabled or not - if self.receive_mode: + if self.__receive_mode: # indicate that process should be terminated - self.terminate = True + self.__terminate = True # check whether queue mode is empty - if not(self.queue is None): - self.queue.clear() + if not(self.__queue is None): + self.__queue.clear() # call immediate termination - self.exit_loop = True + self.__exit_loop = True # wait until stream resources are released (producer thread might be still grabbing frame) - if self.thread is not None: - self.thread.join() - self.thread = None + if self.__thread is not None: + self.__thread.join() + self.__thread = None #properly handle thread exit # properly close the socket - self.msg_socket.close(linger=0) + self.__msg_socket.close(linger=0) else: # indicate that process should be terminated - self.terminate = True - if self.multiserver_mode: + self.__terminate = True + if self.__multiserver_mode: #check if multiserver_mode # send termination flag to client with its unique port term_dict = dict(terminate_flag = True, port = self.port) @@ -770,13 +769,13 @@ def close(self): # otherwise send termination flag to client term_dict = dict(terminate_flag = True) # otherwise inform client(s) that the termination has been reached - if self.force_close: + if self.__force_close: #overflow socket with termination signal - for _ in range(500): self.msg_socket.send_json(term_dict) + for _ in range(500): self.__msg_socket.send_json(term_dict) else: - self.msg_socket.send_json(term_dict) + self.__msg_socket.send_json(term_dict) #check for confirmation if available - if self.pattern < 2: - if self.pattern == 1: self.msg_socket.recv() + if self.__pattern < 2: + if self.__pattern == 1: self.__msg_socket.recv() # properly close the socket - self.msg_socket.close(linger=0) \ No newline at end of file + self.__msg_socket.close(linger=0) \ No newline at end of file diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index d5651cb23..5d70c6d9e 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -29,13 +29,11 @@ try: # import OpenCV Binaries import cv2 - # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): raise ImportError('[PiGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') - except ImportError as error: - raise ImportError('[PiGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip3 install opencv-python` command.') + raise ImportError('[PiGear:ERROR] :: Failed to detect correct OpenCV executables, install it with `pip3 install opencv-python` command.') @@ -46,6 +44,11 @@ class PiGear: (such as OmniVision OV5647 Camera Module and Sony IMX219 Camera Module). To interface with these modules correctly, PiGear provides a flexible multi-threaded wrapper around complete picamera python library and provides us the ability to exploit its various features like `brightness, saturation, sensor_mode`, etc. effortlessly. + + :param (integer) camera_num: selects the camera module index that will be used by API. + / Its default value is 0 and shouldn't be altered until unless + / if you using Raspberry Pi 3/3+ compute module in your project along with multiple camera modules. + / Furthermore, Its value can only be greater than zero, otherwise, it will throw ValueError for any negative value. :param (tuple) resolution: sets the resolution (width,height). Its default value is (640,480). @@ -57,7 +60,7 @@ class PiGear: / These attribute provides the flexibility to manipulate input raspicam video stream directly. / Parameters can be passed using this **option, allows you to pass key worded variable length of arguments to PiGear Class. - :param (boolean) self.logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. + :param (boolean) logging: set this flag to enable/disable error logging essential for debugging. Its default value is False. :param (integer) time_delay: sets time delay(in seconds) before start reading the frames. / This delay is essentially required for camera to warm-up. @@ -80,21 +83,21 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo raise RuntimeError('[PiGear:ERROR] :: Picamera API failure: {}'.format(error)) # enable logging if specified - self.logging = False - self.logger = log.getLogger('PiGear') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('PiGear') + if logging: self.__logging = logging assert (isinstance(framerate, (int, float)) and framerate > 5.0), "[PiGear:ERROR] :: Input framerate value `{}` is a Invalid! Kindly read docs.".format(framerate) assert (isinstance(resolution, (tuple, list)) and len(resolution) == 2), "[PiGear:ERROR] :: Input resolution value `{}` is a Invalid! Kindly read docs.".format(resolution) if not(isinstance(camera_num, int) and camera_num >= 0): camera_num = 0 - self.logger.warning("Input camera_num value `{}` is invalid, Defaulting to index 0!") + self.__logger.warning("Input camera_num value `{}` is invalid, Defaulting to index 0!") # initialize the picamera stream at given index - self.camera = PiCamera(camera_num = camera_num) - self.camera.resolution = tuple(resolution) - self.camera.framerate = framerate - if self.logging: self.logger.debug("Activating Pi camera at index: {} with resolution: {} & framerate: {}".format(camera_num, resolution, framerate)) + self.__camera = PiCamera(camera_num = camera_num) + self.__camera.resolution = tuple(resolution) + self.__camera.framerate = framerate + if self.__logging: self.__logger.debug("Activating Pi camera at index: {} with resolution: {} & framerate: {}".format(camera_num, resolution, framerate)) #initialize framerate variable self.framerate = framerate @@ -106,63 +109,61 @@ def __init__(self, camera_num = 0, resolution = (640, 480), framerate = 30, colo options = {k.strip(): v for k,v in options.items()} #define timeout variable default value(handles hardware failures) - self.failure_timeout = 2.0 + self.__failure_timeout = 2.0 #User-Defined parameter if options and "HWFAILURE_TIMEOUT" in options: #for altering timeout variable manually if isinstance(options["HWFAILURE_TIMEOUT"],(int, float)): if not(10.0 > options["HWFAILURE_TIMEOUT"] > 1.0): raise ValueError('[PiGear:ERROR] :: `HWFAILURE_TIMEOUT` value can only be between 1.0 ~ 10.0') - self.failure_timeout = options["HWFAILURE_TIMEOUT"] #assign special parameter - if self.logging: self.logger.debug("Setting HW Failure Timeout: {} seconds".format(self.failure_timeout)) + self.__failure_timeout = options["HWFAILURE_TIMEOUT"] #assign special parameter + if self.__logging: self.__logger.debug("Setting HW Failure Timeout: {} seconds".format(self.__failure_timeout)) del options["HWFAILURE_TIMEOUT"] #clean try: # apply attributes to source if specified for key, value in options.items(): - setattr(self.camera, key, value) - + setattr(self.__camera, key, value) # separately handle colorspace value to int conversion if not(colorspace is None): self.color_space = capPropId(colorspace.strip()) - if self.logging: self.logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) - + if self.__logging: self.__logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) except Exception as e: # Catch if any error occurred - if self.logging: self.logger.exception(str(e)) + if self.__logging: self.__logger.exception(str(e)) # enable rgb capture array thread and capture stream - self.rawCapture = PiRGBArray(self.camera, size = resolution) - self.stream = self.camera.capture_continuous(self.rawCapture,format="bgr", use_video_port=True) + self.__rawCapture = PiRGBArray(self.__camera, size = resolution) + self.stream = self.__camera.capture_continuous(self.__rawCapture,format="bgr", use_video_port=True) #frame variable initialization self.frame = None try: stream = next(self.stream) self.frame = stream.array - self.rawCapture.seek(0) - self.rawCapture.truncate() + self.__rawCapture.seek(0) + self.__rawCapture.truncate() #render colorspace if defined if not(self.frame is None and self.color_space is None): self.frame = cv2.cvtColor(self.frame, self.color_space) except Exception as e: - self.logger.exception(str(e)) + self.__logger.exception(str(e)) raise RuntimeError('[PiGear:ERROR] :: Camera Module failed to initialize!') # applying time delay to warm-up picamera only if specified if time_delay: time.sleep(time_delay) #thread initialization - self.thread = None + self.__thread = None #timer thread initialization(Keeps check on frozen thread) - self._timer = None - self.t_elasped = 0.0 #records time taken by thread + self.__timer = None + self.__t_elasped = 0.0 #records time taken by thread # catching thread exceptions - self.exceptions = None + self.__exceptions = None # initialize termination flag - self.terminate = False + self.__terminate = False @@ -171,39 +172,39 @@ def start(self): start the thread to read frames from the video stream and initiate internal timer """ #Start frame producer thread - self.thread = Thread(target=self.update, name='PiGear', args=()) - self.thread.daemon = True - self.thread.start() + self.__thread = Thread(target=self.__update, name='PiGear', args=()) + self.__thread.daemon = True + self.__thread.start() #Start internal timer thread - self._timer = Thread(target=self._timeit, name='PiTimer', args=()) - self._timer.daemon = True - self._timer.start() + self.__timer = Thread(target=self.__timeit, name='PiTimer', args=()) + self.__timer.daemon = True + self.__timer.start() return self - def _timeit(self): + def __timeit(self): """ Keep checks on Thread excecution timing """ #assign current time - self.t_elasped = time.time() + self.__t_elasped = time.time() #loop until termainated - while not(self.terminate): + while not(self.__terminate): #check for frozen thread - if time.time() - self.t_elasped > self.failure_timeout: + if time.time() - self.__t_elasped > self.__failure_timeout: #log failure - if self.logging: self.logger.critical("Camera Module Disconnected!") + if self.__logging: self.__logger.critical("Camera Module Disconnected!") #prepare for clean exit - self.exceptions = True - self.terminate = True #self-terminate + self.__exceptions = True + self.__terminate = True #self-terminate - def update(self): + def __update(self): """ Update frames from stream """ @@ -211,24 +212,24 @@ def update(self): while True: #check for termination flag - if self.terminate: break + if self.__terminate: break try: #Try to iterate next frame from generator stream = next(self.stream) except Exception: #catch and save any exceptions - self.exceptions = sys.exc_info() + self.__exceptions = sys.exc_info() break #exit - #update timer - self.t_elasped = time.time() + #__update timer + self.__t_elasped = time.time() # grab the frame from the stream and clear the stream in # preparation for the next frame frame = stream.array - self.rawCapture.seek(0) - self.rawCapture.truncate() + self.__rawCapture.seek(0) + self.__rawCapture.truncate() #apply colorspace if specified if not(self.color_space is None): @@ -239,14 +240,14 @@ def update(self): color_frame = cv2.cvtColor(frame, self.color_space) else: self.color_space = None - if self.logging: self.logger.debug('Colorspace value `{}` is not a valid colorspace!'.format(self.color_space)) + if self.__logging: self.__logger.warning('Colorspace `{}` is not a valid colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None - if self.logging: - self.logger.exception(str(e)) - self.logger.warning('Input colorspace is not a valid Colorspace!') + if self.__logging: + self.__logger.exception(str(e)) + self.__logger.warning('Input colorspace is not a valid colorspace!') if not(color_frame is None): self.frame = color_frame @@ -256,12 +257,12 @@ def update(self): self.frame = frame # terminate processes - if not(self.terminate): self.terminate = True + if not(self.__terminate): self.__terminate = True # release picamera resources self.stream.close() - self.rawCapture.close() - self.camera.close() + self.__rawCapture.close() + self.__camera.close() @@ -270,8 +271,8 @@ def read(self): return the frame """ #check if there are any thread exceptions - if not(self.exceptions is None): - if isinstance(self.exceptions, bool): + if not(self.__exceptions is None): + if isinstance(self.__exceptions, bool): #clear frame self.frame = None #notify user about hardware failure @@ -280,8 +281,8 @@ def read(self): #clear frame self.frame = None # re-raise error for debugging - error_msg = "[PiGear:ERROR] :: Camera Module API failure occured: {}".format(self.exceptions[1]) - raise RuntimeError(error_msg).with_traceback(self.exceptions[2]) + error_msg = "[PiGear:ERROR] :: Camera Module API failure occured: {}".format(self.__exceptions[1]) + raise RuntimeError(error_msg).with_traceback(self.__exceptions[2]) # return the frame return self.frame @@ -292,27 +293,27 @@ def stop(self): """ Terminates the Read process """ - if self.logging: self.logger.debug("Terminating PiGear Processes.") + if self.__logging: self.__logger.debug("Terminating PiGear Processes.") # make sure that the threads should be terminated - self.terminate = True + self.__terminate = True #stop timer thread - if not(self._timer is None): self._timer.join() + if not(self.__timer is None): self.__timer.join() #handle camera thread - if not(self.thread is None): + if not(self.__thread is None): #check if hardware failure occured - if not(self.exceptions is None) and isinstance(self.exceptions, bool): + if not(self.__exceptions is None) and isinstance(self.__exceptions, bool): # force release picamera resources self.stream.close() - self.rawCapture.close() - self.camera.close() + self.__rawCapture.close() + self.__camera.close() #properly handle thread exit - self.thread.terminate() - self.thread.wait() #wait if still process is still processing some information - self.thread = None + self.__thread.terminate() + self.__thread.wait() #wait if still process is still processing some information + self.__thread = None else: #properly handle thread exit - self.thread.join() \ No newline at end of file + self.__thread.join() \ No newline at end of file diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index d7902185f..d07a0ba2f 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -32,10 +32,9 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[ScreenGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') - + raise ImportError('[ScreenGear:ERROR] :: OpenCV API version >= 3.0 is only supported by this library.') except ImportError as error: - raise ImportError('[ScreenGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') + raise ImportError('[ScreenGear:ERROR] :: Failed to detect correct OpenCV executables, install it with `pip3 install opencv-python` command.') @@ -69,7 +68,7 @@ class ScreenGear: def __init__(self, monitor = 1, colorspace = None, logging = False, **options): #intialize threaded queue mode by default - self.threaded_queue_mode = True + self.__threaded_queue_mode = True try: # import mss factory @@ -81,28 +80,32 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): raise ImportError('[ScreenGear:ERROR] :: python-mss library not found, install it with `pip install mss` command.') # enable logging if specified - self.logging = False - self.logger = log.getLogger('ScreenGear') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('ScreenGear') + if logging: self.__logging = logging # create mss object - self.mss_object = mss() + self.__mss_object = mss() # create monitor instance for the user-defined monitor monitor_instance = None if (monitor >= 0): - monitor_instance = self.mss_object.monitors[monitor] + try: + monitor_instance = self.__mss_object.monitors[monitor] + except Exception as e: + self.__logger.exception(str(e)) + monitor_instance = None else: raise ValueError("[ScreenGear:ERROR] :: `monitor` value cannot be negative, Read Docs!") # Initialize Queue - self.queue = None + self.__queue = None #import deque from collections import deque #define deque and assign it to global var - self.queue = deque(maxlen=96) #max len 96 to check overflow + self.__queue = deque(maxlen=96) #max len 96 to check overflow #log it - if logging: self.logger.debug('Enabling Threaded Queue Mode by default for ScreenGear!') + if logging: self.__logger.debug('Enabling Threaded Queue Mode by default for ScreenGear!') #intiate screen dimension handler screen_dims = {} @@ -114,48 +117,53 @@ def __init__(self, monitor = 1, colorspace = None, logging = False, **options): # separately handle colorspace value to int conversion if not(colorspace is None): self.color_space = capPropId(colorspace.strip()) - if logging: self.logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) + if logging: self.__logger.debug('Enabling `{}` colorspace for this video stream!'.format(colorspace.strip())) except Exception as e: # Catch if any error occurred - if logging: self.logger.exception(str(e)) + if logging: self.__logger.exception(str(e)) # intialize mss capture instance - self.mss_capture_instance = None + self.__mss_capture_instance = None try: # check whether user-defined dimensions are provided if screen_dims and len(screen_dims) == 4: - if logging: self.logger.debug('Setting capture dimensions: {}!'.format(screen_dims)) - self.mss_capture_instance = screen_dims #create instance from dimensions + if logging: self.__logger.debug('Setting capture dimensions: {}!'.format(screen_dims)) + self.__mss_capture_instance = screen_dims #create instance from dimensions + elif not(monitor_instance is None): + self.__mss_capture_instance = monitor_instance #otherwise create instance from monitor else: - self.mss_capture_instance = monitor_instance #otherwise create instance from monitor + raise RuntimeError("[ScreenGear:ERROR] :: API Failure occurred!") # extract global frame from instance - self.frame = np.asanyarray(self.mss_object.grab(self.mss_capture_instance)) - if self.threaded_queue_mode: + self.frame = np.asanyarray(self.__mss_object.grab(self.__mss_capture_instance)) + if self.__threaded_queue_mode: #intitialize and append to queue - self.queue.append(self.frame) - except ScreenShotError: - #otherwise catch and log errors - if logging: self.logger.error(self.mss_object.get_error_details()) - raise ValueError("[ScreenGear:ERROR] :: ScreenShotError caught, Wrong dimensions passed to python-mss, Kindly Refer Docs!") + self.__queue.append(self.frame) + except Exception as e: + if isinstance(e, ScreenShotError): + #otherwise catch and log errors + if logging: self.__logger.exception(self.__mss_object.get_error_details()) + raise ValueError("[ScreenGear:ERROR] :: ScreenShotError caught, Wrong dimensions passed to python-mss, Kindly Refer Docs!") + else: + raise SystemError("[ScreenGear:ERROR] :: Unable to initiate any MSS instance on this system, Are you running headless?") # thread initialization - self.thread=None + self.__thread=None # initialize termination flag - self.terminate = False + self.__terminate = False def start(self): """ start the thread to read frames from the video stream """ - self.thread = Thread(target=self.update, name='ScreenGear', args=()) - self.thread.daemon = True - self.thread.start() + self.__thread = Thread(target=self.__update, name='ScreenGear', args=()) + self.__thread.daemon = True + self.__thread.start() return self - def update(self): + def __update(self): """ Update frames from stream """ @@ -164,27 +172,27 @@ def update(self): # keep looping infinitely until the thread is terminated while True: # if the thread terminate is set, stop the thread - if self.terminate: + if self.__terminate: break - if self.threaded_queue_mode: + if self.__threaded_queue_mode: #check queue buffer for overflow - if len(self.queue) >= 96: + if len(self.__queue) >= 96: #stop iterating if overflowing occurs time.sleep(0.000001) continue try: - frame = np.asanyarray(self.mss_object.grab(self.mss_capture_instance)) + frame = np.asanyarray(self.__mss_object.grab(self.__mss_capture_instance)) except ScreenShotError: - raise RuntimeError(self.mss_object.get_error_details()) - self.terminate = True + raise RuntimeError(self.__mss_object.get_error_details()) + self.__terminate = True continue if frame is None or frame.size == 0: #no frames received, then safely exit - if self.threaded_queue_mode: - if len(self.queue) == 0: self.terminate = True + if self.__threaded_queue_mode: + if len(self.__queue) == 0: self.__terminate = True else: - self.terminate = True + self.__terminate = True if not(self.color_space is None): # apply colorspace to frames @@ -194,13 +202,13 @@ def update(self): color_frame = cv2.cvtColor(frame, self.color_space) else: self.color_space = None - if self.logging: self.logger.debug('Colorspace value {} is not a valid Colorspace!'.format(self.color_space)) + if self.__logging: self.__logger.warning('Colorspace value `{}` is not a valid colorspace!'.format(self.color_space)) except Exception as e: # Catch if any error occurred self.color_space = None - if self.logging: - self.logger.exception(str(e)) - self.logger.debug('Input Colorspace is not a valid Colorspace!') + if self.__logging: + self.__logger.exception(str(e)) + self.__logger.warning('Input colorspace is not a valid colorspace!') if not(color_frame is None): self.frame = color_frame else: @@ -208,9 +216,9 @@ def update(self): else: self.frame = frame #append to queue - if self.threaded_queue_mode: self.queue.append(self.frame) + if self.__threaded_queue_mode: self.__queue.append(self.frame) # finally release mss resources - self.mss_object.close() + self.__mss_object.close() @@ -218,9 +226,9 @@ def read(self): """ return the frame """ - while self.threaded_queue_mode: - if len(self.queue)>0: - return self.queue.popleft() + while self.__threaded_queue_mode: + if len(self.__queue)>0: + return self.__queue.popleft() return self.frame @@ -229,15 +237,15 @@ def stop(self): """ Terminates the Read process """ - if self.logging: self.logger.debug("Terminating ScreenGear Processes.") + if self.__logging: self.__logger.debug("Terminating ScreenGear Processes.") #terminate Threaded queue mode seperately - if self.threaded_queue_mode and not(self.queue is None): - self.queue.clear() - self.threaded_queue_mode = False + if self.__threaded_queue_mode and not(self.__queue is None): + self.__queue.clear() + self.__threaded_queue_mode = False self.frame = None # indicate that the thread should be terminated - self.terminate = True + self.__terminate = True # wait until stream resources are released (producer thread might be still grabbing frame) - if self.thread is not None: - self.thread.join() + if self.__thread is not None: + self.__thread.join() #properly handle thread exit \ No newline at end of file diff --git a/vidgear/gears/stabilizer.py b/vidgear/gears/stabilizer.py index afc8aa61c..73cd12d0b 100644 --- a/vidgear/gears/stabilizer.py +++ b/vidgear/gears/stabilizer.py @@ -51,38 +51,38 @@ class Stabilizer: def __init__(self, smoothing_radius = 25, border_type = 'black', border_size = 0, crop_n_zoom = False, logging = False): # initialize deques for handling input frames and its indexes - self.frame_queue = deque(maxlen=smoothing_radius) - self.frame_queue_indexes = deque(maxlen=smoothing_radius) + self.__frame_queue = deque(maxlen=smoothing_radius) + self.__frame_queue_indexes = deque(maxlen=smoothing_radius) # enable logging if specified - self.logging = False - self.logger = log.getLogger('Stabilizer') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('Stabilizer') + if logging: self.__logging = logging # define and create Adaptive histogram equalization (AHE) object for optimizations - self.clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) + self.__clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) # initialize global vars - self.smoothing_radius = smoothing_radius #averaging window, handles the quality of stabilization at expense of latency and sudden panning - self.smoothed_path = None #handles the smoothed path with box filter - self.path = None #handles path i.e cumulative sum of pevious_2_current transformations along a axis - self.transforms = [] #handles pevious_2_current transformations [dx,dy,da] - self.frame_transforms_smoothed = None #handles smoothed array of pevious_2_current transformations w.r.t to frames - self.previous_gray = None #handles previous gray frame - self.previous_keypoints = None #handles previous detect_GFTTed keypoints w.r.t previous gray frame - self.frame_height, self.frame_width = (0, 0) #handles width and height of input frames - self.crop_n_zoom = 0 # handles cropping and zooms frames to reduce the black borders from stabilization being too noticeable. + self.__smoothing_radius = smoothing_radius #averaging window, handles the quality of stabilization at expense of latency and sudden panning + self.__smoothed_path = None #handles the smoothed path with box filter + self.__path = None #handles path i.e cumulative sum of pevious_2_current transformations along a axis + self.__transforms = [] #handles pevious_2_current transformations [dx,dy,da] + self.__frame_transforms_smoothed = None #handles smoothed array of pevious_2_current transformations w.r.t to frames + self.__previous_gray = None #handles previous gray frame + self.__previous_keypoints = None #handles previous detect_GFTTed keypoints w.r.t previous gray frame + self.__frame_height, self.frame_width = (0, 0) #handles width and height of input frames + self.__crop_n_zoom = 0 # handles cropping and zooms frames to reduce the black borders from stabilization being too noticeable. # if check if crop_n_zoom defined if crop_n_zoom and border_size: - self.crop_n_zoom = border_size #crops and zoom frame to original size - self.border_size = 0 #zero out border size - self.frame_size = None #handles frame size for zooming - if logging: self.logger.debug('Setting Cropping margin {} pixels'.format(border_size)) + self.__crop_n_zoom = border_size #crops and zoom frame to original size + self.__border_size = 0 #zero out border size + self.__frame_size = None #handles frame size for zooming + if logging: self.__logger.debug('Setting Cropping margin {} pixels'.format(border_size)) else: # Add output borders to frame - self.border_size = border_size - if self.logging and border_size: self.logger.debug('Setting Border size {} pixels'.format(border_size)) + self.__border_size = border_size + if self.__logging and border_size: self.__logger.debug('Setting Border size {} pixels'.format(border_size)) # define valid border modes border_modes = {'black': cv2.BORDER_CONSTANT,'reflect': cv2.BORDER_REFLECT, 'reflect_101': cv2.BORDER_REFLECT_101, 'replicate': cv2.BORDER_REPLICATE, 'wrap': cv2.BORDER_WRAP} @@ -90,19 +90,19 @@ def __init__(self, smoothing_radius = 25, border_type = 'black', border_size = 0 if border_type in ['black', 'reflect', 'reflect_101', 'replicate', 'wrap']: if not crop_n_zoom: #initialize global border mode variable - self.border_mode = border_modes[border_type] - if self.logging and border_type != 'black': self.logger.debug('Setting Border type: {}'.format(border_type)) + self.__border_mode = border_modes[border_type] + if self.__logging and border_type != 'black': self.__logger.debug('Setting Border type: {}'.format(border_type)) else: #log and reset to default - if self.logging and border_type != 'black': self.logger.debug('Setting border type is disabled if cropping is enabled!') - self.border_mode = border_modes['black'] + if self.__logging and border_type != 'black': self.__logger.debug('Setting border type is disabled if cropping is enabled!') + self.__border_mode = border_modes['black'] else: #otherwise log if not - if logging: self.logger.debug('Invalid input border type!') - self.border_mode = border_modes['black'] #reset to default mode + if logging: self.__logger.debug('Invalid input border type!') + self.__border_mode = border_modes['black'] #reset to default mode # define normalized box filter - self.box_filter = np.ones(smoothing_radius)/smoothing_radius + self.__box_filter = np.ones(smoothing_radius)/smoothing_radius @@ -118,65 +118,65 @@ def stabilize(self, frame): return #save frame size for zooming - if self.crop_n_zoom and self.frame_size == None: - self.frame_size = frame.shape[:2] + if self.__crop_n_zoom and self.__frame_size == None: + self.__frame_size = frame.shape[:2] # initiate transformations capturing - if not self.frame_queue: + if not self.__frame_queue: #for first frame previous_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #convert to gray - previous_gray = self.clahe.apply(previous_gray) #optimize gray frame - self.previous_keypoints = cv2.goodFeaturesToTrack(previous_gray, maxCorners=200, qualityLevel=0.05, minDistance=30.0, blockSize=3 , mask=None, useHarrisDetector=False, k=0.04) #track features using GFTT - self.frame_height, self.frame_width = frame.shape[:2] #save input frame height and width - self.frame_queue.append(frame) #save frame to deque - self.frame_queue_indexes.append(0) #save frame index to deque - self.previous_gray = previous_gray[:] #save gray frame clone for further processing - - elif self.frame_queue_indexes[-1] <= self.smoothing_radius - 1: + previous_gray = self.__clahe.apply(previous_gray) #optimize gray frame + self.__previous_keypoints = cv2.goodFeaturesToTrack(previous_gray, maxCorners=200, qualityLevel=0.05, minDistance=30.0, blockSize=3 , mask=None, useHarrisDetector=False, k=0.04) #track features using GFTT + self.__frame_height, self.frame_width = frame.shape[:2] #save input frame height and width + self.__frame_queue.append(frame) #save frame to deque + self.__frame_queue_indexes.append(0) #save frame index to deque + self.__previous_gray = previous_gray[:] #save gray frame clone for further processing + + elif self.__frame_queue_indexes[-1] <= self.__smoothing_radius - 1: #for rest of frames - self.frame_queue.append(frame) #save frame to deque - self.frame_queue_indexes.append(self.frame_queue_indexes[-1]+1) #save frame index - self.generate_transformations() #generate transformations - if self.frame_queue_indexes[-1] == self.smoothing_radius - 1: + self.__frame_queue.append(frame) #save frame to deque + self.__frame_queue_indexes.append(self.__frame_queue_indexes[-1]+1) #save frame index + self.__generate_transformations() #generate transformations + if self.__frame_queue_indexes[-1] == self.__smoothing_radius - 1: #calculate smooth path once transformation capturing is completed for i in range(3): #apply normalized box filter to the path - self.smoothed_path[:,i] = self.box_filter_convolve((self.path[:,i]), window_size=self.smoothing_radius) + self.__smoothed_path[:,i] = self.__box_filter_convolve((self.__path[:,i]), window_size=self.__smoothing_radius) #calculate deviation of path from smoothed path - deviation = self.smoothed_path - self.path + deviation = self.__smoothed_path - self.__path #save smoothed transformation - self.frame_transforms_smoothed = self.frame_transform + deviation + self.__frame_transforms_smoothed = self.frame_transform + deviation else: #start applying transformations - self.frame_queue.append(frame) #save frame to deque - self.frame_queue_indexes.append(self.frame_queue_indexes[-1]+1) #save frame index - self.generate_transformations() #generate transformations + self.__frame_queue.append(frame) #save frame to deque + self.__frame_queue_indexes.append(self.__frame_queue_indexes[-1]+1) #save frame index + self.__generate_transformations() #generate transformations #calculate smooth path once transformation capturing is completed for i in range(3): #apply normalized box filter to the path - self.smoothed_path[:,i] = self.box_filter_convolve((self.path[:,i]), window_size=self.smoothing_radius) + self.__smoothed_path[:,i] = self.__box_filter_convolve((self.__path[:,i]), window_size=self.__smoothing_radius) #calculate deviation of path from smoothed path - deviation = self.smoothed_path - self.path + deviation = self.__smoothed_path - self.__path #save smoothed transformation - self.frame_transforms_smoothed = self.frame_transform + deviation + self.__frame_transforms_smoothed = self.frame_transform + deviation #return transformation applied stabilized frame - return self.apply_transformations() + return self.__apply_transformations() - def generate_transformations(self): + def __generate_transformations(self): """ Generate pevious_2_current transformations [dx,dy,da] """ - frame_gray = cv2.cvtColor(self.frame_queue[-1], cv2.COLOR_BGR2GRAY) #retrieve current frame and convert to gray - frame_gray = self.clahe.apply(frame_gray) #optimize it + frame_gray = cv2.cvtColor(self.__frame_queue[-1], cv2.COLOR_BGR2GRAY) #retrieve current frame and convert to gray + frame_gray = self.__clahe.apply(frame_gray) #optimize it #calculate optical flow using Lucas-Kanade differential method - curr_kps, status, error = cv2.calcOpticalFlowPyrLK(self.previous_gray, frame_gray, self.previous_keypoints, None) + curr_kps, status, error = cv2.calcOpticalFlowPyrLK(self.__previous_gray, frame_gray, self.__previous_keypoints, None) #select only valid keypoints valid_curr_kps = curr_kps[status==1] #current - valid_previous_keypoints = self.previous_keypoints[status==1] #previous + valid_previous_keypoints = self.__previous_keypoints[status==1] #previous #calculate optimal affine transformation between pevious_2_current keypoints if check_CV_version() == 3: @@ -198,22 +198,22 @@ def generate_transformations(self): dx = dy = da = 0 #save this transformation - self.transforms.append([dx, dy, da]) + self.__transforms.append([dx, dy, da]) #calculate path from cumulative transformations sum - self.frame_transform = np.array(self.transforms, dtype='float32') - self.path = np.cumsum(self.frame_transform, axis=0) + self.frame_transform = np.array(self.__transforms, dtype='float32') + self.__path = np.cumsum(self.frame_transform, axis=0) #create smoothed path from a copy of path - self.smoothed_path=np.copy(self.path) + self.__smoothed_path=np.copy(self.__path) #re-calculate and save GFTT keypoints for current gray frame - self.previous_keypoints = cv2.goodFeaturesToTrack(frame_gray, maxCorners=200, qualityLevel=0.05, minDistance=30.0, blockSize=3 , mask=None, useHarrisDetector=False, k=0.04) + self.__previous_keypoints = cv2.goodFeaturesToTrack(frame_gray, maxCorners=200, qualityLevel=0.05, minDistance=30.0, blockSize=3 , mask=None, useHarrisDetector=False, k=0.04) #save this gray frame for further processing - self.previous_gray = frame_gray[:] + self.__previous_gray = frame_gray[:] - def box_filter_convolve(self, path, window_size): + def __box_filter_convolve(self, path, window_size): """ applies normalized linear box filter to path w.r.t to averaging window @@ -223,7 +223,7 @@ def box_filter_convolve(self, path, window_size): #pad path to size of averaging window path_padded = np.pad(path, (window_size, window_size), 'median') #apply linear box filter to path - path_smoothed = np.convolve(path_padded, self.box_filter, mode='same') + path_smoothed = np.convolve(path_padded, self.__box_filter, mode='same') #crop the smoothed path to original path path_smoothed = path_smoothed[window_size:-window_size] #assert if cropping is completed @@ -233,27 +233,27 @@ def box_filter_convolve(self, path, window_size): - def apply_transformations(self): + def __apply_transformations(self): """ Applies affine transformation to the given frame build from previously calculated transformations """ #extract frame and its index from deque - queue_frame = self.frame_queue.popleft() - queue_frame_index = self.frame_queue_indexes.popleft() + queue_frame = self.__frame_queue.popleft() + queue_frame_index = self.__frame_queue_indexes.popleft() #create border around extracted frame w.r.t border_size - bordered_frame = cv2.copyMakeBorder(queue_frame, top=self.border_size, bottom=self.border_size, left=self.border_size, right=self.border_size, borderType=self.border_mode, value=[0, 0, 0]) + bordered_frame = cv2.copyMakeBorder(queue_frame, top=self.__border_size, bottom=self.__border_size, left=self.__border_size, right=self.__border_size, borderType=self.__border_mode, value=[0, 0, 0]) alpha_bordered_frame = cv2.cvtColor(bordered_frame, cv2.COLOR_BGR2BGRA) #create alpha channel #extract alpha channel alpha_bordered_frame[:, :, 3] = 0 - alpha_bordered_frame[self.border_size:self.border_size + self.frame_height, self.border_size:self.border_size + self.frame_width, 3] = 255 + alpha_bordered_frame[self.__border_size:self.__border_size + self.__frame_height, self.__border_size:self.__border_size + self.frame_width, 3] = 255 #extracting Transformations w.r.t frame index - dx = self.frame_transforms_smoothed[queue_frame_index,0] #x-axis - dy = self.frame_transforms_smoothed[queue_frame_index,1] #y-axis - da = self.frame_transforms_smoothed[queue_frame_index,2] #angle + dx = self.__frame_transforms_smoothed[queue_frame_index,0] #x-axis + dy = self.__frame_transforms_smoothed[queue_frame_index,1] #y-axis + da = self.__frame_transforms_smoothed[queue_frame_index,2] #angle #building 2x3 transformation matrix from extracted transformations queue_frame_transform = np.zeros((2,3), np.float32) @@ -265,18 +265,18 @@ def apply_transformations(self): queue_frame_transform[1,2] = dy #Applying an affine transformation to the frame - frame_wrapped = cv2.warpAffine(alpha_bordered_frame, queue_frame_transform, alpha_bordered_frame.shape[:2][::-1], borderMode=self.border_mode) + frame_wrapped = cv2.warpAffine(alpha_bordered_frame, queue_frame_transform, alpha_bordered_frame.shape[:2][::-1], borderMode=self.__border_mode) #drop alpha channel frame_stabilized = frame_wrapped[:, :, :3] #crop and zoom - if self.crop_n_zoom: + if self.__crop_n_zoom: #crop - frame_cropped = frame_stabilized[self.crop_n_zoom:-self.crop_n_zoom, self.crop_n_zoom:-self.crop_n_zoom] + frame_cropped = frame_stabilized[self.__crop_n_zoom:-self.__crop_n_zoom, self.__crop_n_zoom:-self.__crop_n_zoom] #zoom interpolation = cv2.INTER_CUBIC if (check_CV_version() == 3) else cv2.INTER_LINEAR_EXACT - frame_stabilized = cv2.resize(frame_cropped, self.frame_size[::-1], interpolation = interpolation) + frame_stabilized = cv2.resize(frame_cropped, self.__frame_size[::-1], interpolation = interpolation) #finally return stabilized frame return frame_stabilized @@ -287,8 +287,8 @@ def clean(self): clean resources(deque) """ # check if deque present - if self.frame_queue: + if self.__frame_queue: #clear frame deque - self.frame_queue.clear() + self.__frame_queue.clear() #clear frame indexes deque - self.frame_queue_indexes.clear() \ No newline at end of file + self.__frame_queue_indexes.clear() \ No newline at end of file diff --git a/vidgear/gears/videogear.py b/vidgear/gears/videogear.py index 58bca1f14..b17f5332a 100644 --- a/vidgear/gears/videogear.py +++ b/vidgear/gears/videogear.py @@ -80,14 +80,14 @@ class VideoGear: def __init__(self, enablePiCamera = False, stabilize = False, source = 0, y_tube = False, backend = 0, colorspace = None, resolution = (640, 480), framerate = 25, logging = False, time_delay = 0, **options): #initialize stabilizer - self.stablization_mode = stabilize + self.__stablization_mode = stabilize # enable logging if specified - self.logging = False - self.logger = log.getLogger('VideoGear') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('VideoGear') + if logging: self.__logging = logging - if self.stablization_mode: + if self.__stablization_mode: from .stabilizer import Stabilizer s_radius, border_size, border_type, crop_n_zoom = (25, 0, 'black', False) #defaults if options: @@ -107,8 +107,8 @@ def __init__(self, enablePiCamera = False, stabilize = False, source = 0, y_tube if isinstance(options["CROP_N_ZOOM"],bool): crop_n_zoom = options["CROP_N_ZOOM"] #assigsn special parameter del options["CROP_N_ZOOM"] #clean - self.stabilizer_obj = Stabilizer(smoothing_radius = s_radius, border_type = border_type, border_size = border_size, crop_n_zoom = crop_n_zoom, logging = logging) - if self.logging: self.logger.debug('Enabling Stablization Mode for the current video source!') #log info + self.__stabilizer_obj = Stabilizer(smoothing_radius = s_radius, border_type = border_type, border_size = border_size, crop_n_zoom = crop_n_zoom, logging = logging) + if self.__logging: self.__logger.debug('Enabling Stablization Mode for the current video source!') #log info if enablePiCamera: # only import the pigear module only if required @@ -133,19 +133,19 @@ def start(self): def read(self): # return the current frame - while self.stablization_mode: + while self.__stablization_mode: frame = self.stream.read() if frame is None: break - frame_stab = self.stabilizer_obj.stabilize(frame) + frame_stab = self.__stabilizer_obj.stabilize(frame) if not(frame_stab is None): return frame_stab return self.stream.read() def stop(self): - if self.logging: self.logger.debug("Terminating VideoGear.") + if self.__logging: self.__logger.debug("Terminating VideoGear.") # stop the thread and release any resources self.stream.stop() #clean queue - if self.stablization_mode: self.stabilizer_obj.clean() \ No newline at end of file + if self.__stablization_mode: self.__stabilizer_obj.clean() \ No newline at end of file diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 96e6b7563..3a22a2f70 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -35,9 +35,9 @@ import cv2 # check whether OpenCV Binaries are 3.x+ if parse_version(cv2.__version__) < parse_version('3'): - raise ImportError('[WriteGear:ERROR] :: OpenCV library version >= 3.0 is only supported by this library') + raise ImportError('[WriteGear:ERROR] :: OpenCV API version >= 3.0 is only supported by this library.') except ImportError as error: - raise ImportError('[WriteGear:ERROR] :: Failed to detect OpenCV executables, install it with `pip install opencv-python` command.') + raise ImportError('[WriteGear:ERROR] :: Failed to detect correct OpenCV executables, install it with `pip3 install opencv-python` command.') @@ -92,31 +92,31 @@ class WriteGear: def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg = '', logging = False, **output_params): # assign parameter values to class variables - self.compression = compression_mode - self.os_windows = True if os.name == 'nt' else False #checks if machine in-use is running windows os or not + self.__compression = compression_mode + self.__os_windows = True if os.name == 'nt' else False #checks if machine in-use is running windows os or not # enable logging if specified - self.logging = False - self.logger = log.getLogger('WriteGear') - if logging: self.logging = logging + self.__logging = False + self.__logger = log.getLogger('WriteGear') + if logging: self.__logging = logging # initialize various important class variables - self.output_parameters = {} - self.inputheight = None - self.inputwidth = None - self.inputchannels = None - self.inputframerate = 0 - self.output_dimensions = None - self.process = None #handle process to be frames written - self.DEVNULL = None #handles silent execution of FFmpeg (if logging is disabled) - self.cmd = '' #handle FFmpeg Pipe command - self.ffmpeg = '' #handle valid FFmpeg binaries location - self.initiate = True #initiate one time process for valid process initialization + self.__output_parameters = {} + self.__inputheight = None + self.__inputwidth = None + self.__inputchannels = None + self.__inputframerate = 0 + self.__output_dimensions = None + self.__process = None #handle process to be frames written + self.__DEVNULL = None #handles silent execution of FFmpeg (if logging is disabled) + self.__cmd = '' #handle FFmpeg Pipe command + self.__ffmpeg = '' #handle valid FFmpeg binaries location + self.__initiate = True #initiate one time process for valid process initialization # handles output file name (if not given) if not output_filename: - raise ValueError('[WriteGear:ERROR] :: Kindly provide a valid `output_filename` value, Refer VidGear Docs for more information!') + raise ValueError('[WriteGear:ERROR] :: Kindly provide a valid `output_filename` value. Refer Docs for more information.') elif output_filename and os.path.isdir(output_filename): # check if directory path is given instead output_filename = os.path.join(output_filename, 'VidGear-{}.mp4'.format(time.strftime("%Y%m%d-%H%M%S"))) # auto-assign valid name and adds it to path else: @@ -124,64 +124,63 @@ def __init__(self, output_filename = '', compression_mode = True, custom_ffmpeg # some definitions and assigning output file absolute path to class variable _filename = os.path.abspath(output_filename) - self.out_file = _filename + self.__out_file = _filename basepath, _ = os.path.split(_filename) #extract file base path for debugging ahead if output_params: #handle user defined output dimensions(must be a tuple or list) if output_params and "-output_dimensions" in output_params: - self.output_dimensions = output_params["-output_dimensions"] #assign special parameter to global variable + self.__output_dimensions = output_params["-output_dimensions"] #assign special parameter to global variable del output_params["-output_dimensions"] #clean - #cleans and reformat output parameters + #cleans and reformat output parameters try: - self.output_parameters = {str(k).strip().lower(): str(v).strip() for k,v in output_params.items()} + self.__output_parameters = {str(k).strip().lower(): str(v).strip() for k,v in output_params.items()} except Exception as e: - if self.logging: self.logger.exception(str(e)) + if self.__logging: self.__logger.exception(str(e)) raise ValueError('[WriteGear:ERROR] :: Wrong output_params parameters passed to WriteGear class!') #handles FFmpeg binaries validity tests - if self.compression: + if self.__compression: - if self.logging: - self.logger.debug('Compression Mode is enabled therefore checking for valid FFmpeg executables!') - self.logger.debug(self.output_parameters) + if self.__logging: + self.__logger.debug('Compression Mode is enabled therefore checking for valid FFmpeg executables.') + self.__logger.debug(self.__output_parameters) # handles where to save the downloaded FFmpeg Static Binaries on Windows(if specified) ffmpeg_download_path_ = '' - if self.output_parameters and "-ffmpeg_download_path" in self.output_parameters: - ffmpeg_download_path_ += self.output_parameters["-ffmpeg_download_path"] - del self.output_parameters["-ffmpeg_download_path"] #clean + if self.__output_parameters and "-ffmpeg_download_path" in self.__output_parameters: + ffmpeg_download_path_ += self.__output_parameters["-ffmpeg_download_path"] + del self.__output_parameters["-ffmpeg_download_path"] #clean #handle input framerate if specified - if self.output_parameters and "-input_framerate" in self.output_parameters: - self.inputframerate = float(self.output_parameters["-input_framerate"]) - del self.output_parameters["-input_framerate"] #clean + if self.__output_parameters and "-input_framerate" in self.__output_parameters: + self.__inputframerate = float(self.__output_parameters["-input_framerate"]) + del self.__output_parameters["-input_framerate"] #clean #validate the FFmpeg path/binaries and returns valid FFmpeg file executable location(also downloads static binaries on windows) - actual_command = get_valid_ffmpeg_path(custom_ffmpeg, self.os_windows, ffmpeg_download_path = ffmpeg_download_path_, logging = self.logging) + actual_command = get_valid_ffmpeg_path(custom_ffmpeg, self.__os_windows, ffmpeg_download_path = ffmpeg_download_path_, logging = self.__logging) #check if valid path returned if actual_command: - self.ffmpeg += actual_command #assign it to class variable - if self.logging: - self.logger.debug('Found valid FFmpeg executables: `{}`'.format(self.ffmpeg)) + self.__ffmpeg += actual_command #assign it to class variable + if self.__logging: + self.__logger.debug('Found valid FFmpeg executables: `{}`.'.format(self.__ffmpeg)) else: #otherwise disable Compression Mode - if self.logging and not self.os_windows: - self.logger.debug('Kindly install working FFmpeg or provide a valid custom FFmpeg Path') - self.logger.debug('Caution: Disabling Video Compression Mode since no valid FFmpeg executables found on this machine!') - self.compression = False # compression mode disabled + self.__logger.warning('Disabling Compression Mode since no valid FFmpeg executables found on this machine!') + if self.__logging and not self.__os_windows: self.__logger.debug('Kindly install working FFmpeg or provide a valid custom FFmpeg binary path. See docs for more info.') + self.__compression = False # compression mode disabled #validate this class has the access rights to specified directory or not - assert os.access(basepath, os.W_OK), "Permission Denied: Cannot write to directory = " + basepath + assert os.access(basepath, os.W_OK), "[WriteGear:ERROR] :: Permission Denied: Cannot write to directory: " + basepath #display confirmation if logging is enabled/disabled - if self.compression and self.ffmpeg: - self.DEVNULL = open(os.devnull, 'wb') - if self.logging: self.logger.debug('Compression Mode is configured properly!') + if self.__compression and self.__ffmpeg: + self.__DEVNULL = open(os.devnull, 'wb') + if self.__logging: self.__logger.debug('Compression Mode is configured properly!') else: - if self.logging: self.logger.debug('Compression Mode is disabled, Activating OpenCV In-built Writer!') + if self.__logging: self.__logger.debug('Compression Mode is disabled, Activating OpenCV built-in Writer!') @@ -202,54 +201,54 @@ def write(self, frame, rgb_mode = False): channels = frame.shape[-1] if frame.ndim == 3 else 1 # assign values to class variables on first run - if self.initiate: - self.inputheight = height - self.inputwidth = width - self.inputchannels = channels - if self.logging: - self.logger.debug('InputFrame => Height:{} Width:{} Channels:{}'.format(self.inputheight, self.inputwidth, self.inputchannels)) + if self.__initiate: + self.__inputheight = height + self.__inputwidth = width + self.__inputchannels = channels + if self.__logging: + self.__logger.debug('InputFrame => Height:{} Width:{} Channels:{}'.format(self.__inputheight, self.__inputwidth, self.__inputchannels)) #validate size of frame - if height != self.inputheight or width != self.inputwidth: - raise ValueError('[WriteGear:ERROR] :: All frames in a video should have same size') + if height != self.__inputheight or width != self.__inputwidth: + raise ValueError('[WriteGear:ERROR] :: All frames must have same size!') #validate number of channels - if channels != self.inputchannels: - raise ValueError('[WriteGear:ERROR] :: All frames in a video should have same number of channels') + if channels != self.__inputchannels: + raise ValueError('[WriteGear:ERROR] :: All frames must have same number of channels!') - if self.compression: + if self.__compression: # checks if compression mode is enabled #initiate FFmpeg process on first run - if self.initiate: + if self.__initiate: #start pre-processing and initiate process - self.Preprocess(channels, rgb = rgb_mode) + self.__Preprocess(channels, rgb = rgb_mode) # Check status of the process - assert self.process is not None + assert self.__process is not None #write the frame try: - self.process.stdin.write(frame.tostring()) + self.__process.stdin.write(frame.tostring()) except (OSError, IOError): # log something is wrong! - self.logger.error('BrokenPipeError caught: Wrong Values passed to FFmpeg Pipe, Kindly Refer Docs!') - self.DEVNULL.close() + self.__logger.error('BrokenPipeError caught, Wrong values passed to FFmpeg Pipe, Kindly Refer Docs!') + self.__DEVNULL.close() raise ValueError #for testing purpose only else: # otherwise initiate OpenCV's VideoWriter Class - if self.initiate: + if self.__initiate: #start VideoWriter Class process - self.startCV_Process() + self.__startCV_Process() # Check status of the process - assert self.process is not None - if self.logging: + assert self.__process is not None + if self.__logging: # log OpenCV warning - self.logger.warning('RGBA and 16-bit grayscale video frames are not supported by OpenCV yet, switch to `compression_mode` to use them!') + self.__logger.info('RGBA and 16-bit grayscale video frames are not supported by OpenCV yet, switch to `compression_mode` to use them!') #write the frame - self.process.write(frame) + self.__process.write(frame) - def Preprocess(self, channels, rgb = False): + def __Preprocess(self, channels, rgb = False): """ pre-processing FFmpeg parameters @@ -257,16 +256,16 @@ def Preprocess(self, channels, rgb = False): :param rgb_mode (boolean): set this flag to enable rgb_mode, Its default value is False. """ #turn off initiate flag - self.initiate = False + self.__initiate = False #initialize input parameters input_parameters = {} #handle dimensions dimensions = '' - if self.output_dimensions is None: #check if dimensions are given - dimensions += '{}x{}'.format(self.inputwidth, self.inputheight) #auto derive from frame + if self.__output_dimensions is None: #check if dimensions are given + dimensions += '{}x{}'.format(self.__inputwidth, self.__inputheight) #auto derive from frame else: - dimensions += '{}x{}'.format(self.output_dimensions[0],self.output_dimensions[1]) #apply if defined + dimensions += '{}x{}'.format(self.__output_dimensions[0],self.__output_dimensions[1]) #apply if defined input_parameters["-s"] = str(dimensions) #handles pix_fmt based on channels(HACK) @@ -279,19 +278,19 @@ def Preprocess(self, channels, rgb = False): elif channels == 4: input_parameters["-pix_fmt"] = "rgba" if rgb else "bgra" else: - raise ValueError("Handling Frames with (1 > Channels > 4) is not implemented!") + raise ValueError("[WriteGear:ERROR] :: Frames with channels, outside range 1-to-4 is not supported!") - if self.inputframerate > 5: + if self.__inputframerate > 5: #set input framerate - minimum threshold is 5.0 - if self.logging: self.logger.debug("Setting Input FrameRate = {}".format(self.inputframerate)) - input_parameters["-framerate"] = str(self.inputframerate) + if self.__logging: self.__logger.debug("Setting Input FrameRate = {}".format(self.__inputframerate)) + input_parameters["-framerate"] = str(self.__inputframerate) #initiate FFmpeg process - self.startFFmpeg_Process(input_params = input_parameters, output_params = self.output_parameters) + self.__startFFmpeg_Process(input_params = input_parameters, output_params = self.__output_parameters) - def startFFmpeg_Process(self, input_params, output_params): + def __startFFmpeg_Process(self, input_params, output_params): """ Start FFmpeg process @@ -318,17 +317,17 @@ def startFFmpeg_Process(self, input_params, output_params): #convert output parameters to list output_parameters = dict2Args(output_params) #format command - cmd = [self.ffmpeg , "-y"] + ["-f", "rawvideo", "-vcodec", "rawvideo"] + input_parameters + ["-i", "-"] + output_parameters + [self.out_file] + cmd = [self.__ffmpeg , "-y"] + ["-f", "rawvideo", "-vcodec", "rawvideo"] + input_parameters + ["-i", "-"] + output_parameters + [self.__out_file] #assign value to class variable - self.cmd += " ".join(cmd) + self.__cmd += " ".join(cmd) # Launch the FFmpeg process - if self.logging: - self.logger.debug('Executing FFmpeg command: `{}`'.format(self.cmd)) + if self.__logging: + self.__logger.debug('Executing FFmpeg command: `{}`'.format(self.__cmd)) # In debugging mode - self.process = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=None) + self.__process = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=None) else: # In silent mode - self.process = sp.Popen(cmd, stdin=sp.PIPE, stdout=self.DEVNULL, stderr=sp.STDOUT) + self.__process = sp.Popen(cmd, stdin=sp.PIPE, stdout=self.__DEVNULL, stderr=sp.STDOUT) @@ -340,41 +339,41 @@ def execute_ffmpeg_cmd(self, cmd = None): """ #check if valid command if cmd is None: - self.logger.warning('Input FFmpeg command is empty, Nothing to execute!') + self.__logger.warning('Input FFmpeg command is empty, Nothing to execute!') return else: if not(isinstance(cmd, list)): - raise ValueError("[WriteGear:ERROR] :: Invalid input FFmpeg command! Kindly read docs.") + raise ValueError("[WriteGear:ERROR] :: Invalid input FFmpeg command datatype! Kindly read docs.") #check if Compression Mode is enabled - if not(self.compression): raise RuntimeError("[WriteGear:ERROR] :: Compression Mode is disabled, Kindly enable it to access this function!") + if not(self.__compression): raise RuntimeError("[WriteGear:ERROR] :: Compression Mode is disabled, Kindly enable it to access this function!") #add configured FFmpeg path - cmd = [self.ffmpeg] + cmd + cmd = [self.__ffmpeg] + cmd try: - if self.logging: - self.logger.debug('Executing FFmpeg command: `{}`'.format(' '.join(cmd))) + #write to pipeline + if self.__logging: + self.__logger.debug('Executing FFmpeg command: `{}`'.format(' '.join(cmd))) # In debugging mode sp.call(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=None) else: - - sp.call(cmd, stdin=sp.PIPE, stdout=self.DEVNULL, stderr=sp.STDOUT) + sp.call(cmd, stdin=sp.PIPE, stdout=self.__DEVNULL, stderr=sp.STDOUT) except (OSError, IOError): # log something is wrong! - self.logger.error('BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!') - self.DEVNULL.close() + self.__logger.error('BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!') + self.__DEVNULL.close() raise ValueError #for testing purpose only - def startCV_Process(self): + def __startCV_Process(self): """ Start OpenCV VideoWriter Class process """ #turn off initiate flag - self.initiate = False + self.__initiate = False #initialize essential parameter variables FPS = 0 @@ -383,18 +382,18 @@ def startCV_Process(self): COLOR = True #pre-assign default encoder parameters (if not assigned by user). - if "-fourcc" not in self.output_parameters: + if "-fourcc" not in self.__output_parameters: FOURCC = cv2.VideoWriter_fourcc(*"MJPG") - if "-fps" not in self.output_parameters: + if "-fps" not in self.__output_parameters: FPS = 25 #auto assign dimensions - HEIGHT = self.inputheight - WIDTH = self.inputwidth + HEIGHT = self.__inputheight + WIDTH = self.__inputwidth #assign parameter dict values to variables try: - for key, value in self.output_parameters.items(): + for key, value in self.__output_parameters.items(): if key == '-fourcc': FOURCC = cv2.VideoWriter_fourcc(*(value.upper())) elif key == '-fps': @@ -408,18 +407,18 @@ def startCV_Process(self): except Exception as e: # log if something is wrong - if self.logging: self.logger.exception(str(e)) + if self.__logging: self.__logger.exception(str(e)) raise ValueError('[WriteGear:ERROR] :: Wrong Values passed to OpenCV Writer, Kindly Refer Docs!') - if self.logging: + if self.__logging: #log values for debugging - self.logger.debug('FILE_PATH: {}, FOURCC = {}, FPS = {}, WIDTH = {}, HEIGHT = {}, BACKEND = {}'.format(self.out_file, FOURCC, FPS, WIDTH, HEIGHT, BACKEND)) + self.__logger.debug('FILE_PATH: {}, FOURCC = {}, FPS = {}, WIDTH = {}, HEIGHT = {}, BACKEND = {}'.format(self.__out_file, FOURCC, FPS, WIDTH, HEIGHT, BACKEND)) #start different process for with/without Backend. if BACKEND: - self.process = cv2.VideoWriter(self.out_file, apiPreference = BACKEND, fourcc = FOURCC, fps = FPS, frameSize = (WIDTH, HEIGHT), isColor = COLOR) + self.__process = cv2.VideoWriter(self.__out_file, apiPreference = BACKEND, fourcc = FOURCC, fps = FPS, frameSize = (WIDTH, HEIGHT), isColor = COLOR) else: - self.process = cv2.VideoWriter(self.out_file, fourcc = FOURCC, fps = FPS, frameSize = (WIDTH, HEIGHT), isColor = COLOR) + self.__process = cv2.VideoWriter(self.__out_file, fourcc = FOURCC, fps = FPS, frameSize = (WIDTH, HEIGHT), isColor = COLOR) @@ -427,27 +426,27 @@ def close(self): """ Terminates the Write process """ - if self.logging: self.logger.debug("Terminating WriteGear Processes.") + if self.__logging: self.__logger.debug("Terminating WriteGear Processes.") - if self.compression: + if self.__compression: #if Compression Mode is enabled - if self.process is None: + if self.__process is None: return #no process was initiated at first place - if self.process.poll() is not None: + if self.__process.poll() is not None: return # process was already dead - if self.process.stdin: - self.process.stdin.close() #close `stdin` output - if self.output_parameters and "-i" in self.output_parameters: - self.process.terminate() - self.process.wait() #wait if still process is still processing some information - self.process = None - self.DEVNULL.close() #close it + if self.__process.stdin: + self.__process.stdin.close() #close `stdin` output + if self.__output_parameters and "-i" in self.__output_parameters: + self.__process.terminate() + self.__process.wait() #wait if still process is still processing some information + self.__process = None + self.__DEVNULL.close() #close it else: - self.process.wait() #wait if still process is still processing some information - self.process = None - self.DEVNULL.close() #close it + self.__process.wait() #wait if still process is still processing some information + self.__process = None + self.__DEVNULL.close() #close it else: #if Compression Mode is disabled - if self.process is None: + if self.__process is None: return #no process was initiated at first place - self.process.release() #close it \ No newline at end of file + self.__process.release() #close it \ No newline at end of file From 89d71e944eaf34e2ae24df815936ba253ea79901 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sat, 21 Dec 2019 12:27:47 +0530 Subject: [PATCH 31/39] Fixed Typo --- vidgear/gears/camgear.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 28e665463..fc65323e2 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -53,7 +53,7 @@ -def self.__youtube_url_validation(url): +def youtube_url_validation(url): """ convert Youtube video URLs to a valid address """ @@ -124,7 +124,7 @@ def __init__(self, source = 0, y_tube = False, backend = 0, colorspace = None, l #import pafy and parse youtube stream url import pafy # validate - url = self.__youtube_url_validation(source) + url = youtube_url_validation(source) if url: source_object = pafy.new(url) _source = source_object.getbestvideo("any", ftypestrict=False) From 149b53c53f46c851778588a7aecadbdc777ee47f Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sat, 21 Dec 2019 17:00:21 +0530 Subject: [PATCH 32/39] Fixes --- vidgear/tests/test_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index 6573e1994..21440983f 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -74,7 +74,7 @@ def test_ffmpeg_binaries_download(paths): if paths != return_static_ffmpeg(): shutil.rmtree(os.path.abspath(os.path.join(file_path ,"../.."))) except Exception as e: - if paths == 'wrong_test_path' and "Permission Denied:" in str(e): + if paths == 'wrong_test_path': pass else: pytest.fail(str(e)) From 604dd5a97c75c37a9b2f6e318874c897c1d1d724 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 23 Dec 2019 00:26:41 +0530 Subject: [PATCH 33/39] Increased Codecov and minor fixes for WriteGear API --- vidgear/gears/writegear.py | 12 ++++++------ .../tests/writer_tests/test_non_compression_mode.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 3a22a2f70..afac3602e 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -278,7 +278,7 @@ def __Preprocess(self, channels, rgb = False): elif channels == 4: input_parameters["-pix_fmt"] = "rgba" if rgb else "bgra" else: - raise ValueError("[WriteGear:ERROR] :: Frames with channels, outside range 1-to-4 is not supported!") + raise ValueError("[WriteGear:ERROR] :: Frames with channels outside range 1-to-4 are not supported!") if self.__inputframerate > 5: #set input framerate - minimum threshold is 5.0 @@ -394,14 +394,14 @@ def __startCV_Process(self): #assign parameter dict values to variables try: for key, value in self.__output_parameters.items(): - if key == '-fourcc': + if key == '-fourcc' and isinstance(value, str): FOURCC = cv2.VideoWriter_fourcc(*(value.upper())) - elif key == '-fps': + elif key == '-fps' and isinstance(value, (float, int)): FPS = float(value) - elif key =='-backend': + elif key =='-backend' and isinstance(value, str): BACKEND = capPropId(value.upper()) - elif key == '-color': - COLOR = bool(int(value)) + elif key == '-color' and isinstance(value, bool): + COLOR = value else: pass diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index ac9c9ca95..bf8c7c1d6 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -94,7 +94,7 @@ def test_write(conversion): ('', {}, False), ('Output_twc.avi', {}, True), (tempfile.gettempdir(), {}, True), - ('Output_twc.mp4', {"-fourcc":"DIVX"}, True)] + ('Output_twc.mp4', {"-fourcc":"DIVX", "-fps": 30, "-backend": "CAP_FFMPEG", "-color":True}, True)] @pytest.mark.parametrize('f_name, output_params, result', test_data_class) def test_WriteGear_compression(f_name, output_params, result): From 225ded106e454e052904626a98439069ec0242e7 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 23 Dec 2019 08:56:15 +0530 Subject: [PATCH 34/39] Fixes for CLI test failing --- vidgear/gears/writegear.py | 12 ++++++------ .../tests/writer_tests/test_non_compression_mode.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index afac3602e..2c7ba2b8b 100755 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -394,14 +394,14 @@ def __startCV_Process(self): #assign parameter dict values to variables try: for key, value in self.__output_parameters.items(): - if key == '-fourcc' and isinstance(value, str): + if key == '-fourcc': FOURCC = cv2.VideoWriter_fourcc(*(value.upper())) - elif key == '-fps' and isinstance(value, (float, int)): - FPS = float(value) - elif key =='-backend' and isinstance(value, str): + elif key == '-fps': + FPS = int(value) + elif key =='-backend': BACKEND = capPropId(value.upper()) - elif key == '-color' and isinstance(value, bool): - COLOR = value + elif key == '-color': + COLOR = bool(value) else: pass diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index bf8c7c1d6..8414aa976 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -94,7 +94,7 @@ def test_write(conversion): ('', {}, False), ('Output_twc.avi', {}, True), (tempfile.gettempdir(), {}, True), - ('Output_twc.mp4', {"-fourcc":"DIVX", "-fps": 30, "-backend": "CAP_FFMPEG", "-color":True}, True)] + ('Output_twc.mp4', {"-fourcc":"DIVX", "-fps": 25, "-backend": "CAP_FFMPEG", "-color":True}, True)] @pytest.mark.parametrize('f_name, output_params, result', test_data_class) def test_WriteGear_compression(f_name, output_params, result): From d13effc9d552d3dde1d6f42a79da8ba4573184d0 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 23 Dec 2019 15:53:30 +0530 Subject: [PATCH 35/39] Updated changelog.md --- changelog.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index b8b6e32bd..6b8238fca 100644 --- a/changelog.md +++ b/changelog.md @@ -56,12 +56,13 @@ ### Updates/Improvements: * Replace `print` logging commands with python's logging module completely. + * Implemented encapsulation for class functions and variables on all gears. * Updated support for screen casting from multiple/all monitors in ScreenGear API. * Updated ScreenGear API to use *Threaded Queue Mode* by default, thereby removed redundant `THREADED_QUEUE_MODE` param. * Updated bash script path to download test dataset in `$TMPDIR` rather than `$HOME` directory for downloading testdata. - * Added support for `REQ/REP` pattern in Multi-Server Mode - * Added new `camera_num` to support multiple Picameras - * Moved thread exceptions to the main thread and then re-raised + * Added support for `REQ/REP` pattern in Multi-Server Mode. + * Added new `camera_num` to support multiple Picameras. + * Moved thread exceptions to the main thread and then re-raised. * Replaced `traceback` with `sys.exc_info`. * Overall APIs Code and Docs optimizations. * Updated Code Readability and Wiki Docs. @@ -76,15 +77,15 @@ * Python 2.7 and 3.4 legacies support dropped from VidGear CLI tests. ### Fixes - * Reimplemented `Pub/Sub` pattern for smoother performance(#70) - * Fixed `multiserver_mode` not working properly over some networks - * Fixed assigned Port address ignored bug (commit 073bca1) + * Reimplemented `Pub/Sub` pattern for smoother performance on various networks. + * Fixed `multiserver_mode` not working properly over some networks. + * Fixed assigned Port address ignored bug (commit 073bca1). * Fixed several wrong definition bugs from NetGear API(commit 8f7153c). * Fixed unreliable dataset video URL(rehosted file on `github.com`) * Removed duplicate code to import MSS(@BoboTiG) from ScreenGear API. * Fixed code definitions & Typos. - * Fixed Several bugs related to new `secure_mode` & `multiserver_mode` Modes. - * Fixed various macOS environment bugs + * Fixed several bugs related to `secure_mode` & `multiserver_mode` Modes. + * Fixed various macOS environment bugs. ### Pull requests(PR) involved: * PR #39 @@ -98,6 +99,7 @@ * PR #77 * PR #78 * PR #82 + * PR #84 :warning: PyPi Release does NOT contain Tests and Scripts! From df0081ac8c12de4c3dc94a1add491e85bba20e14 Mon Sep 17 00:00:00 2001 From: Abhishek Thakur Date: Mon, 23 Dec 2019 20:04:28 +0530 Subject: [PATCH 36/39] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 75cfa73bf..6831ab107 100644 --- a/README.md +++ b/README.md @@ -442,7 +442,7 @@ VidGear releases are available for download as packages in the [latest release][ ### Option 3: Clone the Repository -> Best option for trying **latest patches, Pull requests & updgrades**(_maybe experimental_), or **contributing** to development. +> Best option for trying **latest patches, Pull requests & upgrades**(_maybe experimental_), or **contributing** to development. You can clone this repository's `testing` branch for development and thereby can install as follows: ```sh @@ -593,4 +593,4 @@ External URLs [numpy]:https://github.com/numpy/numpy [zmq-pair]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pair.html [zmq-req-rep]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/client_server.html -[zmq-pub-sub]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pubsub.html \ No newline at end of file +[zmq-pub-sub]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pubsub.html From 8f1dd8a8e55fae37cb1f824dda4fdfde6a1deb8e Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 25 Dec 2019 11:23:54 +0530 Subject: [PATCH 37/39] Updated docs --- README.md | 26 ++++++++++++++++---------- changelog.md | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6831ab107..d453d6db8 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,15 @@ The following **functional block diagram** clearly depicts the functioning of Vi # TL;DR + #### What is vidgear? + > ***"VidGear is an [ultrafast➶][ultrafast-wiki], compact, flexible and easy-to-adapt complete Video Processing Python Library."*** - *Built with simplicity in mind, VidGear lets programmers and software developers to easily integrate and perform complex Video Processing tasks in their existing or new applications, without going through various underlying library's documentation and using just a few lines of code. Beneficial for both, if you're new to programming with Python language or already a pro at it.* + #### What does it do? + > ***"VidGear can read, write, process, send & receive video frames from various devices in real-time."*** + + #### What is its purpose? + > ***"Built with simplicity in mind, VidGear lets programmers and software developers to easily integrate and perform complex Video Processing tasks in their existing or new applications, without going through various underlying library's documentation and using just a few lines of code. Beneficial for both, if you're new to programming with Python language or already a pro at it."*** **For more advanced information, see the [*Wiki Documentation ➶*][wiki].** @@ -100,7 +106,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi # Gears: -> **VidGear is built with **multi-threaded APIs** *(a.k.a Gears)* each with some unique function/mechanism.** +> **VidGear is built with various **Multi-Threaded APIs** *(a.k.a Gears)* each with some unique function/mechanism.** Each of these API is designed exclusively to handle/control different device-specific video streams, network streams, and media encoders. These APIs provides an easy-to-use, highly extensible, and a multi-threaded wrapper around various underlying libraries to exploit their features and functions directly while providing robust error-handling. @@ -125,7 +131,7 @@ Each of these API is designed exclusively to handle/control different device-spe ## CamGear -> *CamGear can grab ultrafast frames from diverse range of VideoStreams, which includes almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), various network stream protocols such as `http(s), rtp, rstp, rtmp, mms, etc.`, plus support for live Gstreamer's stream pipeline and YouTube video/livestreams URLs.* +> *CamGear can grab ultra-fast frames from diverse range of devices/streams, which includes almost any IP/USB Cameras, multimedia video file format ([_upto 4k tested_][test-4k]), various network stream protocols such as `http(s), rtp, rstp, rtmp, mms, etc.`, plus support for live Gstreamer's stream pipeline and YouTube video/livestreams URLs.* CamGear provides a flexible, high-level multi-threaded wrapper around `OpenCV's` [VideoCapture class][opencv-vc] with access almost all of its available parameters and also employs [`pafy`][pafy] python APIs for live [YouTube streaming][youtube-wiki]. Furthermore, CamGear implements exclusively on [**Threaded Queue mode**][TQM-wiki] for ultra-fast, error-free and synchronized frame handling. @@ -217,7 +223,7 @@ stream_stab.stop() PiGear provides a flexible multi-threaded wrapper around complete [**picamera**][picamera] python library to interface with these modules correctly, and also grants the ability to exploit its various features like `brightness, saturation, sensor_mode, etc.` effortlessly. -Best of all, PiGear API provides excellent Error-Handling with features like a threaded internal timer that keeps active track of any frozen threads and handles hardware failures/frozen threads robustly thereby will exit safely if any failure occurs. So if you accidently pulled your camera cable out when running PiGear API in your script, instead of going into possible kernel panic due to IO error, it will exit safely to save resources. +Best of all, PiGear API provides excellent Error-Handling with features like a threaded internal timer that keeps active track of any frozen threads and handles hardware failures/frozen threads robustly thereby will exit safely if any failure occurs. So now if you accidently pulled your camera module cable out when running PiGear API in your script, instead of going into possible kernel panic/frozen threads, API exit safely to save resources. **Following simplified functional block diagram depicts PiGear API:** @@ -233,7 +239,7 @@ Best of all, PiGear API provides excellent Error-Handling with features like a t ## ScreenGear -> *ScreenGear act as Screen Recorder, that can grab frames from your monitor in real-time either by define an area on the computer screen or fullscreen at the expense of insignificant latency. It also provide seemless support for capturing frames from multiple monitors.* +> *ScreenGear API act as Screen Recorder, that can grab frames from your monitor in real-time either by define an area on the computer screen or fullscreen at the expense of insignificant latency. It also provide seemless support for capturing frames from multiple monitors.* ScreenGear provides a high-level multi-threaded wrapper around [**python-mss**][mss] python library API and also supports a easy and flexible direct internal parameter manipulation. @@ -325,7 +331,7 @@ NetGear also introduces real-time frame Encoding/Decoding compression capabiliti For security, NetGear also supports easy access to ZeroMQ's powerful, smart & secure Security Layers, that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings easy, standardized privacy and authentication for distributed systems over the network. -Best of all, NetGear can robustly handle Multiple Servers at once, thereby providing access to seamless Live Streams of the various device in a network at the same time. +Best of all, NetGear can robustly handle Multiple Servers devices at once, thereby providing access to seamless Live Streaming of the multiple device in a network at the same time. **NetGear as of now seamlessly supports three ZeroMQ messaging patterns:** @@ -379,7 +385,7 @@ Best of all, NetGear can robustly handle Multiple Servers at once, thereby provi # Installation -## Prerequisites +## Prerequisites: Before installing VidGear, you must verify that the following dependencies are met: @@ -421,7 +427,7 @@ Before installing VidGear, you must verify that the following dependencies are m   -## Available Installation Options +## Available Installation Options: ### Option 1: PyPI Install @@ -442,7 +448,7 @@ VidGear releases are available for download as packages in the [latest release][ ### Option 3: Clone the Repository -> Best option for trying **latest patches, Pull requests & upgrades**(_maybe experimental_), or **contributing** to development. +> Best option for trying **latest patches, Pull requests & updgrades**(_maybe experimental_), or **contributing** to development. You can clone this repository's `testing` branch for development and thereby can install as follows: ```sh @@ -593,4 +599,4 @@ External URLs [numpy]:https://github.com/numpy/numpy [zmq-pair]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pair.html [zmq-req-rep]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/client_server.html -[zmq-pub-sub]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pubsub.html +[zmq-pub-sub]:https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/pyzmq/patterns/pubsub.html \ No newline at end of file diff --git a/changelog.md b/changelog.md index 6b8238fca..73c15bbc7 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ * Implemented Robust Multi-Server support for NetGear API: * Enables Multiple Servers messaging support with a single client. * Added exclusive `multiserver_mode` param for enabling it. + * Added support for `REQ/REP` & `PUB/SUB` patterns for this mode. * Added ability to send additional data of any datatype along with the frame in realtime in this mode. * Introducing exclusive Bi-Directional Mode for bidirectional data transmission: * Added new `return_data` parameter to `recv()` function. @@ -60,7 +61,11 @@ * Updated support for screen casting from multiple/all monitors in ScreenGear API. * Updated ScreenGear API to use *Threaded Queue Mode* by default, thereby removed redundant `THREADED_QUEUE_MODE` param. * Updated bash script path to download test dataset in `$TMPDIR` rather than `$HOME` directory for downloading testdata. - * Added support for `REQ/REP` pattern in Multi-Server Mode. + * Implemented better error handling of colorspace in various videocapture APIs. + * Updated bash scripts, Moved FFmpeg static binaries to `github.com`. + * Updated bash scripts, Added additional flag to support un-secure apt sources. + * CamGear API will now throw `RuntimeError` if source provided is invalid. + * Updated threaded Queue mode in CamGear API for more robust performance. * Added new `camera_num` to support multiple Picameras. * Moved thread exceptions to the main thread and then re-raised. * Replaced `traceback` with `sys.exc_info`. @@ -78,11 +83,17 @@ ### Fixes * Reimplemented `Pub/Sub` pattern for smoother performance on various networks. + * Fixed Assertion error in CamGear API during colorspace manipulation. + * Fixed random freezing in `Secure Mode` and several related performance updates * Fixed `multiserver_mode` not working properly over some networks. * Fixed assigned Port address ignored bug (commit 073bca1). * Fixed several wrong definition bugs from NetGear API(commit 8f7153c). - * Fixed unreliable dataset video URL(rehosted file on `github.com`) + * Fixed unreliable dataset video URL(rehosted file on `github.com`). + * Disabled `overwrite_cert` for client-end in NetGear API. + * Disabled Universal Python wheel builds in `setup.cfg `file. * Removed duplicate code to import MSS(@BoboTiG) from ScreenGear API. + * Eliminated unused redundant code blocks from library. + * Fixed Code indentation in `setup.py` and updated new release information. * Fixed code definitions & Typos. * Fixed several bugs related to `secure_mode` & `multiserver_mode` Modes. * Fixed various macOS environment bugs. From 4e5ff369d07af4ef8bb5132e182025edbe31bceb Mon Sep 17 00:00:00 2001 From: Abhishek Date: Fri, 27 Dec 2019 14:53:23 +0530 Subject: [PATCH 38/39] Updated Docs --- README.md | 31 +++++++++++++++++++++++-------- setup.py | 8 ++++---- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d453d6db8..9e272832f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi * [**WriteGear**](#writegear) * [**NetGear**](#netgear) -[**Installation Options**](#installation) +[**Installation**](#installation) * [**Prerequisites**](#prerequisites) * [**1 - PyPI Install**](#option-1-pypi-install) * [**2 - Release Archive Download**](#option-2-release-archive-download) @@ -80,6 +80,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi **Additional Info** * [**Supported Python legacies**](#supported-python-legacies) * [**Changelog**](#changelog) + * [**Citing**](#citing) * [**License**](#license) @@ -104,7 +105,7 @@ The following **functional block diagram** clearly depicts the functioning of Vi   -# Gears: +# Gears > **VidGear is built with various **Multi-Threaded APIs** *(a.k.a Gears)* each with some unique function/mechanism.** @@ -323,13 +324,13 @@ In addition to this, WriteGear also provides flexible access to [**OpenCV's Vide > *NetGear is exclusively designed to transfer video frames synchronously and asynchronously between interconnecting systems over the network in real-time.* -NetGear implements a high-level wrapper around [**PyZmQ**][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. +NetGear implements a high-level wrapper around [**PyZmQ**][pyzmq] python library that contains python bindings for [ZeroMQ](http://zeromq.org/) - a high-performance asynchronous distributed messaging library that aim to be used in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. NetGear provides seamless support for bidirectional data transmission between receiver(client) and sender(server) through bi-directional synchronous messaging patterns such as zmq.PAIR _(ZMQ Pair Pattern)_ & zmq.REQ/zmq.REP _(ZMQ Request/Reply Pattern)_. -NetGear also introduces real-time frame Encoding/Decoding compression capabilities for optimizing performance while sending the frames of large size directly over the network by encoding the frame before sending it and decoding it on the client's end automatically all in real-time. +NetGear also supports real-time frame Encoding/Decoding compression capabilities for optimizing performance while sending the frames directly over the network, by encoding the frame before sending it and decoding it on the client's end automatically in real-time. -For security, NetGear also supports easy access to ZeroMQ's powerful, smart & secure Security Layers, that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings easy, standardized privacy and authentication for distributed systems over the network. +For security, NetGear implements easy access to ZeroMQ's powerful, smart & secure Security Layers, that enables strong encryption on data, and unbreakable authentication between the Server and the Client with the help of custom certificates/keys and brings easy, standardized privacy and authentication for distributed systems over the network. Best of all, NetGear can robustly handle Multiple Servers devices at once, thereby providing access to seamless Live Streaming of the multiple device in a network at the same time. @@ -448,7 +449,7 @@ VidGear releases are available for download as packages in the [latest release][ ### Option 3: Clone the Repository -> Best option for trying **latest patches, Pull requests & updgrades**(_maybe experimental_), or **contributing** to development. +> Best option for trying **latest patches(_maybe experimental_), Pull Requests**, or **contributing** to development. You can clone this repository's `testing` branch for development and thereby can install as follows: ```sh @@ -487,7 +488,7 @@ The full documentation for all VidGear classes and functions can be found in the ```sh chmod +x scripts/bash/prepare_dataset.sh - .scripts/bash/prepare_dataset.sh #for windows, use `sh scripts/pre_install.sh` + .scripts/bash/prepare_dataset.sh #for Windows, use `sh scripts/bash/prepare_dataset.sh` ``` * **Run Tests:** Then various VidGear tests can be run with `pytest`(*in VidGear's root folder*) as below: @@ -515,7 +516,21 @@ See [**contributing.md**](contributing.md). See [**changelog.md**](changelog.md) -  +  + +# Citing + +**Here is a Bibtex entry you can use to cite this project in a publication:** + +```tex +@misc{vidgear, + Title = {vidgear}, + Author = {Abhishek Thakur}, + howpublished = {\url{https://github.com/abhiTronix/vidgear}} + } +``` + +  # License diff --git a/setup.py b/setup.py index 7e3a03971..12ef63a6c 100644 --- a/setup.py +++ b/setup.py @@ -52,16 +52,16 @@ def test_opencv(): version='0.1.6', description='Most Powerful multi-threaded Video Processing Python framework powerpacked with unique trailblazing features.', license='Apache License 2.0', - author='abhiTronix', - install_requires = ["pafy", "mss", "youtube-dl", "requests","pyzmq"] - + (["opencv-contrib-python"] if test_opencv() else []) + author='Abhishek Thakur', + install_requires = ["pafy", "mss", "youtube-dl", "requests", "pyzmq"] + + (["opencv-python"] if test_opencv() else []) + (["picamera"] if ("arm" in platform.uname()[4][:3]) else []), long_description=long_description, long_description_content_type="text/markdown", author_email='abhi.una12@gmail.com', url='https://github.com/abhiTronix/vidgear', download_url='https://github.com/abhiTronix/vidgear/releases/download/vidgear-0.1.6/vidgear-0.1.6.tar.gz', - keywords=['opencv', 'multithreading', 'FFmpeg', 'picamera', 'mss', 'pyzmq', 'pafy', 'Video Processing', 'Video Stablization', 'Computer Vision'], + keywords=['OpenCV', 'multithreading', 'FFmpeg', 'picamera', 'mss', 'pyzmq', 'pafy', 'Video Processing', 'Video Stablization', 'Computer Vision', 'raspberrypi', 'youtube'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Operating System :: POSIX', From f75d377f8630528e6a48c2f75f1ebb04716b70f7 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 30 Dec 2019 00:56:52 +0530 Subject: [PATCH 39/39] New Updates * Added alternate github mirror for FFmpeg static binaries auto-installation on windows oses. * Added `colorlog` python module for presentable colored logging. * Updated docs --- changelog.md | 2 ++ setup.py | 2 +- vidgear/gears/helper.py | 62 +++++++++++++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/changelog.md b/changelog.md index 73c15bbc7..08d7b84c7 100644 --- a/changelog.md +++ b/changelog.md @@ -68,6 +68,8 @@ * Updated threaded Queue mode in CamGear API for more robust performance. * Added new `camera_num` to support multiple Picameras. * Moved thread exceptions to the main thread and then re-raised. + * Added alternate github mirror for FFmpeg static binaries auto-installation on windows oses. + * Added `colorlog` python module for presentable colored logging. * Replaced `traceback` with `sys.exc_info`. * Overall APIs Code and Docs optimizations. * Updated Code Readability and Wiki Docs. diff --git a/setup.py b/setup.py index 12ef63a6c..1cb80c51b 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def test_opencv(): description='Most Powerful multi-threaded Video Processing Python framework powerpacked with unique trailblazing features.', license='Apache License 2.0', author='Abhishek Thakur', - install_requires = ["pafy", "mss", "youtube-dl", "requests", "pyzmq"] + install_requires = ["pafy", "mss", "youtube-dl", "requests", "pyzmq", "colorlog"] + (["opencv-python"] if test_opencv() else []) + (["picamera"] if ("arm" in platform.uname()[4][:3]) else []), long_description=long_description, diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 1bf56fecb..51edf76eb 100644 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -26,9 +26,39 @@ import numpy as np from pkg_resources import parse_version import logging as log - - -log.basicConfig(format='%(name)s :: %(levelname)s :: %(message)s', level=log.DEBUG) +import logging.config + +#logging formatter +logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'colored': { + '()': 'colorlog.ColoredFormatter', + 'log_colors': { + 'DEBUG': 'bold_green', + 'WARNING': 'bold_yellow', + 'ERROR': 'bold_red', + 'CRITICAL': 'bold_red,bg_white', + }, + 'format': + "%(bold_blue)s%(name)s%(reset)s :: %(log_color)s%(levelname)s%(reset)s :: %(message)s", + } + }, + 'handlers': { + 'stream': { + 'class': 'logging.StreamHandler', + 'formatter': 'colored', + 'level': 'DEBUG' + }, + }, + 'loggers': { + '': { + 'handlers': ['stream'], + 'level': 'DEBUG', + }, + }, +}) +#logger logger = log.getLogger('Helper') @@ -158,17 +188,25 @@ def download_ffmpeg_binaries(path, os_windows = False): if os.path.isfile(file_path): final_path += file_path #skip download if does else: + #import libs import requests import zipfile - #check if given pth has write access - assert os.access(path, os.W_OK), "[Helper:ERROR] :: Permission Denied, Cannot write ffmpeg binaries to directory = " + path - #remove leftovers - if os.path.isfile(file_name): - os.remove(file_name) + #check if given path has write access + assert os.access(path, os.W_OK), "[Helper:ERROR] :: Permission Denied, Cannot write binaries to directory = " + path + #remove leftovers if exists + if os.path.isfile(file_name): os.remove(file_name) #download and write file to the given path with open(file_name, "wb") as f: - logger.debug("No Custom FFmpeg path provided, Auto-Downloading binaries for Windows. Please wait...") - response = requests.get(file_url, stream=True) + logger.debug("No Custom FFmpeg path provided. Auto-Installing FFmpeg static binaries now. Please wait...") + try: + response = requests.get(file_url, stream=True, timeout=2) + response.raise_for_status() + except Exception as e: + logger.exception(str(e)) + logger.warning("Downloading Failed. Trying GitHub mirror now!") + file_url = 'https://raw.githubusercontent.com/abhiTronix/ffmpeg-static-builds/master/windows/ffmpeg-latest-{}-static.zip'.format(windows_bit, windows_bit) + response = requests.get(file_url, stream=True, timeout=2) + response.raise_for_status() total_length = response.headers.get('content-length') if total_length is None: # no content length header f.write(response.content) @@ -181,12 +219,12 @@ def download_ffmpeg_binaries(path, os_windows = False): done = int(50 * dl / total_length) sys.stdout.write("\r[{}{}]{}{}".format('=' * done, ' ' * (50-done), done * 2, '%') ) sys.stdout.flush() - logger.debug("\nExtracting executables, Please Wait...") + logger.debug("Extracting executables.") with zipfile.ZipFile(file_name, "r") as zip_ref: zip_ref.extractall(base_path) #perform cleaning os.remove(file_name) - logger.debug("FFmpeg binaries for Windows Configured Successfully!") + logger.debug("FFmpeg binaries for Windows configured successfully!") final_path += file_path #return final path return final_path