diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 566711e4a..970fe1ca5 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ ko_fi: abhitronix -liberapay: abhiTronix \ No newline at end of file +github: abhiTronix diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 6c748cefa..0e6d0c0c1 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index 86aafbc09..de74a6c73 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -23,7 +23,7 @@ on: types: [published] env: - PYTHON_VERSION: 3.7 + PYTHON_VERSION: 3.9 GIT_TOKEN: ${{ secrets.GIT_TOKEN }} GIT_NAME: ${{ secrets.GIT_NAME }} GIT_EMAIL: ${{ secrets.GIT_EMAIL }} @@ -65,7 +65,7 @@ jobs: - name: mike deploy docs release run: | echo "${{ env.NAME_RELEASE }}" - mike deploy --push --update-aliases --no-redirect ${{ env.NAME_RELEASE }} ${{ env.RELEASE_NAME }} --title=${{ env.RELEASE_NAME }} + mike deploy --push --update-aliases --no-redirect ${{ env.NAME_RELEASE }} ${{ env.RELEASE_NAME }} --title=${{ env.RELEASE_NAME }} env: NAME_RELEASE: "v${{ env.RELEASE_NAME }}-release" if: success() @@ -112,7 +112,6 @@ jobs: NAME_STABLE: "v${{ env.RELEASE_NAME }}-stable" if: success() - deploy-docs-dev: name: Deploy Development Docs if: github.event_name == 'push' && github.ref == 'refs/heads/testing' diff --git a/README.md b/README.md index 9fc6418d4..c5b8e8b71 100644 --- a/README.md +++ b/README.md @@ -650,7 +650,7 @@ It is something I am doing with my own free time. But so much more needs to be d Here is a Bibtex entry you can use to cite this project in a publication: -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7571405.svg)](https://doi.org/10.5281/zenodo.7571405) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.8174694.svg)](https://doi.org/10.5281/zenodo.8174694) ```BibTeX @software{vidgear, @@ -665,13 +665,13 @@ Here is a Bibtex entry you can use to cite this project in a publication: Benjamin Lowe and Mickaël Schoentgen and Renaud Bouckenooghe}, - title = {abhiTronix/vidgear: VidGear v0.3.0}, - month = jan, + title = {abhiTronix/vidgear: VidGear v0.3.1}, + month = jul, year = 2023, publisher = {Zenodo}, - version = {vidgear-0.3.0}, - doi = {10.5281/zenodo.7571405}, - url = {https://doi.org/10.5281/zenodo.7571405} + version = {vidgear-0.3.1}, + doi = {10.5281/zenodo.8174694}, + url = {https://doi.org/10.5281/zenodo.8174694} } ``` diff --git a/appveyor.yml b/appveyor.yml index 779f0e4e6..4b5e528c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,10 +16,6 @@ image: Visual Studio 2019 environment: matrix: - - PYTHON: "C:\\Python37-x64" - PYTHON_VERSION: "3.7.x" - PYTHON_ARCH: "64" - - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "64" @@ -32,6 +28,10 @@ environment: PYTHON_VERSION: "3.10.x" PYTHON_ARCH: "64" + - PYTHON: "C:\\Python311-x64" + PYTHON_VERSION: "3.11.x" + PYTHON_ARCH: "64" + build: off version: "{branch}-{build}" @@ -70,6 +70,5 @@ install: test_script: - cmd: python -m pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/ - after_test: - - cmd: python -m codecov \ No newline at end of file + - cmd: python -m codecov diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 28b2112b2..23cbde702 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,14 +35,14 @@ pool: strategy: matrix: - Python37: - python.version: "3.7" Python38: python.version: "3.8" Python39: python.version: "3.9" Python310: python.version: "3.10" + Python311: + python.version: "3.11" steps: - task: UsePythonVersion@0 diff --git a/docs/changelog.md b/docs/changelog.md index dfa655e79..916bf42a8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -20,6 +20,82 @@ limitations under the License. # Release Notes +## v0.3.2 (2023-09-10) + +???+ tip "New Features" + - [x] **NetGear:** + * Added new `kill` parameter to `close()` method to forcefully kill ZMQ context instead of graceful exit only in the `receive` mode. + * Added new `subscriber_timeout` integer optional parameter to support timeout with `pattern=2` _(or Publisher-Subscriber)_ pattern. + + Receiver will exit safely if timeout defined(any value(in milliseconds) > 0), and timeout occurs in Receiver Mode with `pattern=2`. + + 💬 Note: Default behavior still is to block the thread till infinite time. + - [x] **WriteGear:** + * Added new `-disable_ffmpeg_window` optional Boolean flag to enable patch that prevents FFmpeg creation window from opening when building `.exe` files on Windows OS. _(PR by @ibtsam3301)_ + + 💬 Note: `-disable_ffmpeg_window` optional Boolean flag is only available on Windows OS with logging disabled(`logging=False`) in compression mode. + + Use Case: This flag can be useful while creating an `.exe` file for a python script that uses WriteGear API. On windows even after creating the `.exe` file in windowed mode or no-console mode, the `ffmpeg.exe` command line window would pop up while its being used by WriteGear API. + - [x] **Setup.py** + * Added official support for python `3.11.x` legacies. + * Bumped version to `0.3.1`. + - [x] **Docs** + * Added doc for `subscriber_timeout` optional Integer parameter in NetGear. + * Added doc for `disable_ffmpeg_window` optional Boolean parameter in WriteGear. + * Added new asset `screengear_region.png`. + - [x] **CI** + * Added python 3.11 legacy support for MacOS, Windows and Linux environments. + * Added kill argument to close() method in various NetGear tests. + +??? success "Updates/Improvements" + - [x] Asyncio: + * Formatted TemplateResponse class parameters w.r.t new changes in backend Starlette API. + - [x] Setup.py: + * Readded latest patch to `uvicorn`, `starlette`, `pyzmq` dependencies. + * Removed `3.7` legacy from Programming Language metadata. + - [x] Maintenance: + * Added GitHub sponsors and dropped liberapay from `Funding.yml`. + * Removed redundant code. + - [x] Docs: + * Updated information related to Supported Dimensional Attributes in ScreenGear docs. + * Updated minimum python to version `3.8` while installing vidgear in docs. + * Updated API-specific dependencies in docs. + * Updated changelog.md + - [x] CI: + * Updated Azure Pipeline workflow. + * Updated Appveyor Pipeline workflow. + * Updated GitHub Actions Pipeline workflow. + * Migrated python version to `3.9` in `deploy_docs.yml` workflow. + * Removed deprecated python `3.7` legacy support. + * Increased code coverage by updating tests. + * Updated tests for `subscriber_timeout` optional Integer parameter in NetGear. + * Updated tests for `disable_ffmpeg_window` optional Boolean parameter in WriteGear. + +??? danger "Breaking Updates/Changes" + - [ ] Setup.py: + * Removed support for python-3.7 legacies + + Raised `python_requires` to `>=3.8`. Thereby python `3.7` and any before legacy are no longer supported. + +??? bug "Bug-fixes" + - [x] ScreenGear: + * Fixed swapped region dimensions bug with dxcam backend. + * Fixed "mss" backend disabled when `monitor` parameter is not defined. + - [x] Docs: + * Fixed missing `compression_mode` flags in WriteGear API docs. + * Fixed missing hyperlinks. + * Fixed typos and context. + - [x] CI: + * Temporary fix for AST constructor depth mismatch in pytest on python `3.11.x`, More information: pytest-dev/pytest#10874 + + Made temporary fix platform independent. + + Extended fix to all Webgear_RTC tests. + * Fixed NetGear tests bugs. + * Fixed condition logic bug. + +??? question "Pull Requests" + * PR #378 + * PR #375 + * PR #370 + +  + +  + ## v0.3.1 (2023-07-22) ??? tip "New Features" diff --git a/docs/gears/netgear/params.md b/docs/gears/netgear/params.md index c9562e362..949069b2f 100644 --- a/docs/gears/netgear/params.md +++ b/docs/gears/netgear/params.md @@ -169,7 +169,9 @@ This parameter provides the flexibility to alter various NetGear API's internal * **`max_retries`**(_integer_): This internal attribute controls the maximum retries before Server/Client exit itself, if it's unable to get any response/reply from the socket before a certain amount of time, when synchronous messaging patterns like (`zmq.PAIR` & `zmq.REQ/zmq.REP`) are being used. It's value can anything greater than `0`, and its default value is `3`. - * **`request_timeout`**(_integer_): This internal attribute controls the timeout value _(in seconds)_, after which the Server/Client exit itself if it's unable to get any response/reply from the socket, when synchronous messaging patterns like (`zmq.PAIR` & `zmq.REQ/zmq.REP`) are being used. It's value can anything greater than `0`, and its default value is `10` seconds. + * **`request_timeout`**(_integer_): This internal attribute controls the timeout value _(in seconds)_, after which the Server/Client exit itself with `Nonetype` value if it's unable to get any response/reply from the socket, when synchronous messaging patterns like (`zmq.PAIR` & `zmq.REQ/zmq.REP`) are being used. It's value can anything greater than `0`, and its default value is `10` seconds. + + * **`subscriber_timeout`**(_integer_): Similar to `request_timeout`, this internal attribute also controls the timeout value _(in seconds)_ but for non-synchronous `zmq.PUB/zmq.SUB` pattern in compression mode, after which the Client(Subscriber) exit itself with `Nonetype` value if it's unable to get any response from the socket. It's value can anything greater than `0`, and its disabled by default _(meaning the client will wait forever for response)_. * **`flag`**(_integer_): This PyZMQ attribute value can be either `0` or `zmq.NOBLOCK`_( i.e. 1)_. More information can be found [here ➶](https://pyzmq.readthedocs.io/en/latest/api/zmq.html). diff --git a/docs/gears/screengear/params.md b/docs/gears/screengear/params.md index 2202c912d..c12f96ad5 100644 --- a/docs/gears/screengear/params.md +++ b/docs/gears/screengear/params.md @@ -131,17 +131,18 @@ ScreenGear(colorspace="COLOR_BGR2HSV") This parameter provides the flexibility to manually set the dimensions of capture screen area. -!!! info "Supported Dimensional Parameters" +!!! info "Supported Dimensional Attributes" - Supported Dimensional Parameters are as follows: + ScreenGear API takes `left`, `top`, `width`, `height` coordinates of the bounding box of capture screen area(ROI), similar to [PIL.ImageGrab.grab](https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html), defined below: + +

+ ScreenGear ROI region +

* **`left`:** the x-coordinate of the upper-left corner of the region * **`top`:** the y-coordinate of the upper-left corner of the region - * **`width`:** the width of the region - * **`height`:** the height of the region - -!!! note "Additional Exclusive Attribute such as [`THREAD_TIMEOUT`](../../camgear/advanced/source_params/#exclusive-camgear-parameters) is also supported for this parameter." - + * **`width`:** the width of the complete region from left to the bottom-right corner of the region. + * **`height`:** the height of the complete region from top to the bottom-right corner of the region. **Data-Type:** Dictionary @@ -149,15 +150,13 @@ This parameter provides the flexibility to manually set the dimensions of captur **Usage:** -The desired dimensional parameters can be passed to ScreenGear API by formatting them as attributes, as follows: - -!!! tip "More information about screen dimensioning can be found [here ➶](https://python-mss.readthedocs.io/api.html#mss.tools.mss.base.MSSMixin.monitors)" +The desired dimensional coordinates parameters can be passed to ScreenGear API by formatting them as attributes, as follows: ```python # formatting dimensional parameters as dictionary attributes options = {'top': 40, 'left': 0, 'width': 100, 'height': 100} -# assigning it w.r.t monitor=1 -ScreenGear(monitor=1, **options) +# assigning it +ScreenGear(**options) ```   diff --git a/docs/gears/screengear/usage.md b/docs/gears/screengear/usage.md index 8400dddfe..52bb5f8a8 100644 --- a/docs/gears/screengear/usage.md +++ b/docs/gears/screengear/usage.md @@ -77,6 +77,20 @@ stream.stop() ScreenGear API provides us the flexibility to directly set the dimensions of capturing-area of the screen. These dimensions can be easily applied to ScreenGear API through its [`options`](../params/#options) dictionary parameter by formatting them as its attributes. + +??? info "Supported Dimensional Attributes" + + ScreenGear API takes `left`, `top`, `width`, `height` coordinates of the bounding box of capture screen area(ROI), similar to [PIL.ImageGrab.grab](https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html), defined below: + +

+ ScreenGear ROI region +

+ + * **`left`:** the x-coordinate of the upper-left corner of the region + * **`top`:** the y-coordinate of the upper-left corner of the region + * **`width`:** the width of the complete region from left to the bottom-right corner of the region. + * **`height`:** the height of the complete region from top to the bottom-right corner of the region. + The complete usage example is as follows: diff --git a/docs/gears/writegear/compression/params.md b/docs/gears/writegear/compression/params.md index f81393aa3..d2949d451 100644 --- a/docs/gears/writegear/compression/params.md +++ b/docs/gears/writegear/compression/params.md @@ -176,6 +176,17 @@ This parameter allows us to exploit almost all FFmpeg supported parameters effor "-ffpreheaders": ["-re"], # executes as `ffmpeg -re ` } ``` + + * **`-disable_ffmpeg_window`** _(bool)_: sets a special flag to enable detached subprocess creation on Windows OS, and can be useful while creating an `.exe` file for a python script that uses WriteGear API. On Windows, in certain cases, even after creating the `.exe` file in windowed mode or no-console mode, the FFmpeg commandline window would pop up while its being used by WriteGear API. Its usage is as follows: + + ??? new "New in v0.3.2" + This feature was added in `v0.3.2`. + + !!! warning "`-disable_ffmpeg_window` is only available on Windows OS with logging disabled(`logging=False`) in compression mode." + + ```python + output_params = {"-disable_ffmpeg_window": True} # disables FFmpeg creation window + ``` * **`-disable_force_termination`** _(bool)_: sets a special flag to manually disable the default forced-termination behaviour in WriteGear API when `-i` FFmpeg parameter is used _(For more details, see issue: #149)_. Its usage is as follows: diff --git a/docs/gears/writegear/non_compression/params.md b/docs/gears/writegear/non_compression/params.md index b9ae65f2a..034e7b351 100644 --- a/docs/gears/writegear/non_compression/params.md +++ b/docs/gears/writegear/non_compression/params.md @@ -149,7 +149,7 @@ To assign desired parameters in Non-Compression Mode, you can format it as dicti # format parameter as dictionary attribute output_params = {"-fps":30} # and then, assign it -WriteGear(output = 'output.mp4', **output_params) +WriteGear(output = 'output.mp4', compression_mode=False, **output_params) ``` !!! example "Its usage example can be found [here ➶](../usage/#using-non-compression-mode-with-videocapture-gears)." @@ -170,7 +170,7 @@ To select desired FOURCC codec in Non-Compression Mode, you can format it as dic # format codec as dictionary attribute output_params = {"-fourcc":"MJPG"} # and then, assign it -WriteGear(output = 'output.mp4', **output_params) +WriteGear(output = 'output.mp4', compression_mode=False, **output_params) ``` !!! example "Its usage example can be found [here ➶](../usage/#using-non-compression-mode-with-videocapture-gears)." @@ -188,7 +188,7 @@ This parameter enables logging _(if `True`)_, essential for debugging. **Usage:** ```python -WriteGear(output = 'output.mp4', logging=True) +WriteGear(output = 'output.mp4', compression_mode=False, logging=True) ```   diff --git a/docs/index.md b/docs/index.md index 201fad8dc..a52c5346c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -127,7 +127,7 @@ It is something I am doing with my own free time. But so much more needs to be d Here is a Bibtex entry you can use to cite this project in a publication: -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7571405.svg)](https://doi.org/10.5281/zenodo.7571405) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.8174694.svg)](https://doi.org/10.5281/zenodo.8174694) ```BibTeX @software{vidgear, @@ -142,13 +142,13 @@ Here is a Bibtex entry you can use to cite this project in a publication: Benjamin Lowe and Mickaël Schoentgen and Renaud Bouckenooghe}, - title = {abhiTronix/vidgear: VidGear v0.3.0}, - month = jan, + title = {abhiTronix/vidgear: VidGear v0.3.1}, + month = jul, year = 2023, publisher = {Zenodo}, - version = {vidgear-0.3.0}, - doi = {10.5281/zenodo.7571405}, - url = {https://doi.org/10.5281/zenodo.7571405} + version = {vidgear-0.3.1}, + doi = {10.5281/zenodo.8174694}, + url = {https://doi.org/10.5281/zenodo.8174694} } ``` diff --git a/docs/installation.md b/docs/installation.md index f28ccd451..16e72c879 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -29,7 +29,7 @@ limitations under the License. ## Supported Systems -VidGear is well-tested and supported on the following systems(but not limited to), with [python 3.7+](https://www.python.org/downloads/) and [pip](https://pip.pypa.io/en/stable/installing/#do-i-need-to-install-pip) installed: +VidGear is well-tested and supported on the following systems(but not limited to), with [python 3.8+](https://www.python.org/downloads/) and [pip](https://pip.pypa.io/en/stable/installing/#do-i-need-to-install-pip) installed: * Any :material-linux: Linux distro released in 2016 or later * :fontawesome-brands-windows: Windows 7 or later @@ -41,9 +41,9 @@ VidGear is well-tested and supported on the following systems(but not limited to !!! warning "Depreciation Notice" - Python-3.6 legacies support has been dropped from Vidgear. + Python-3.7 legacies support has been dropped from Vidgear. -:fontawesome-brands-python: [**Python 3.7+**](https://www.python.org/downloads/) are only supported legacies for installing Vidgear v0.2.5 and above. +:fontawesome-brands-python: [**Python 3.8+**](https://www.python.org/downloads/) are only supported legacies for installing Vidgear `v0.3.1` and above.   diff --git a/docs/installation/pip_install.md b/docs/installation/pip_install.md index 2ee73b8fe..ba51b9181 100644 --- a/docs/installation/pip_install.md +++ b/docs/installation/pip_install.md @@ -262,7 +262,7 @@ When installing VidGear with [pip](https://pip.pypa.io/en/stable/installing/), y | CamGear | `pafy`, `yt_dlp`, `streamlink` | | PiGear | `picamera` | | VideoGear | *Based on CamGear or PiGear backend in use* | - | ScreenGear | `dxcam`, `mss`, `pyscreenshot`, `Pillow` | + | ScreenGear | `mss`, `pyscreenshot`, `Pillow` | | WriteGear | **FFmpeg:** See [this doc ➶](../../gears/writegear/compression/advanced/ffmpeg_install/#ffmpeg-installation-instructions) | | StreamGear | **FFmpeg:** See [this doc ➶](../../gears/streamgear/ffmpeg_install/#ffmpeg-installation-instructions) | | NetGear | `pyzmq`, `simplejpeg` | @@ -329,10 +329,10 @@ pip install git+git://github.com/abhiTronix/vidgear@master#egg=vidgear[asyncio] ```sh # Install latest stable release with all Core dependencies -pip install vidgear-0.3.1-py3-none-any.whl[core] +pip install vidgear-0.3.2-py3-none-any.whl[core] # Or Install latest stable release with all Core & Asyncio dependencies -pip install vidgear-0.3.1-py3-none-any.whl[asyncio] +pip install vidgear-0.3.2-py3-none-any.whl[asyncio] ```   diff --git a/docs/installation/source_install.md b/docs/installation/source_install.md index ab2300e91..88d49ef82 100644 --- a/docs/installation/source_install.md +++ b/docs/installation/source_install.md @@ -161,9 +161,9 @@ When installing VidGear from source, FFmpeg is the only API specific prerequisit | CamGear | `yt_dlp` | | PiGear | `picamera` | | VideoGear | *Based on CamGear or PiGear backend in use* | - | ScreenGear | `mss`, `pyscreenshot`, `Pillow` | - | WriteGear | **FFmpeg:** See [this doc ➶](https://abhitronix.github.io/vidgear/v0.2.2-dev/gears/writegear/compression/advanced/ffmpeg_install/#ffmpeg-installation-instructions) | - | StreamGear | **FFmpeg:** See [this doc ➶](https://abhitronix.github.io/vidgear/v0.2.2-dev/gears/streamgear/ffmpeg_install/#ffmpeg-installation-instructions) | + | ScreenGear | `dxcam`, `mss`, `pyscreenshot`, `Pillow` | + | WriteGear | **FFmpeg:** See [this doc ➶](https://abhitronix.github.io/vidgear/dev/gears/writegear/compression/advanced/ffmpeg_install/#ffmpeg-installation-instructions) | + | StreamGear | **FFmpeg:** See [this doc ➶](https://abhitronix.github.io/vidgear/dev/gears/streamgear/ffmpeg_install/#ffmpeg-installation-instructions) | | NetGear | `pyzmq`, `simplejpeg` | | WebGear | `starlette`, `jinja2`, `uvicorn`, `simplejpeg` | | WebGear_RTC | `aiortc`, `starlette`, `jinja2`, `uvicorn` | diff --git a/docs/overrides/assets/images/screengear_region.png b/docs/overrides/assets/images/screengear_region.png new file mode 100644 index 000000000..a884fb261 Binary files /dev/null and b/docs/overrides/assets/images/screengear_region.png differ diff --git a/scripts/bash/prepare_dataset.sh b/scripts/bash/prepare_dataset.sh index e7fcdac8b..8701ef4e1 100644 --- a/scripts/bash/prepare_dataset.sh +++ b/scripts/bash/prepare_dataset.sh @@ -26,7 +26,7 @@ mkdir -p "$TMPFOLDER"/Downloads mkdir -p "$TMPFOLDER"/Downloads/{FFmpeg_static,Test_videos} # Acknowledging machine architecture -MACHINE_BIT=$(uname -m) +# MACHINE_BIT=$(uname -m) #Defining alternate ffmpeg static binaries date/version ALTBINARIES_DATE="12-07-2022" @@ -48,12 +48,12 @@ msys*) esac #Download and Configure FFmpeg Static -cd "$TMPFOLDER"/Downloads/FFmpeg_static +cd "$TMPFOLDER"/Downloads/FFmpeg_static || exit if [ $OS_NAME = "linux" ]; then echo "Downloading Linux64 Static FFmpeg Binaries..." - curl -LO https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/linux/ffmpeg-git-amd64-static.tar.xz + curl -LO https://gitlab.com/abhiTronix/ffmpeg-static-builds/-/raw/master/$ALTBINARIES_DATE/linux/ffmpeg-git-amd64-static.tar.xz tar -xJf ffmpeg-git-amd64-static.tar.xz rm *.tar.* mv ffmpeg* ffmpeg @@ -61,7 +61,7 @@ if [ $OS_NAME = "linux" ]; then elif [ $OS_NAME = "windows" ]; then echo "Downloading Win64 Static FFmpeg Binaries..." - curl -LO https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/windows/ffmpeg-latest-win64-static.zip + curl -LO https://gitlab.com/abhiTronix/ffmpeg-static-builds/-/raw/master/$ALTBINARIES_DATE/windows/ffmpeg-latest-win64-static.zip unzip -qq ffmpeg-latest-win64-static.zip rm ffmpeg-latest-win64-static.zip mv ffmpeg-latest-win64-static ffmpeg @@ -69,7 +69,7 @@ elif [ $OS_NAME = "windows" ]; then else echo "Downloading MacOS64 Static FFmpeg Binary..." - curl -LO https://github.com/abhiTronix/ffmpeg-static-builds/raw/master/$ALTBINARIES_DATE/macOS/ffmpeg-latest-macos64-static.zip + curl -LO https://gitlab.com/abhiTronix/ffmpeg-static-builds/-/raw/master/$ALTBINARIES_DATE/macOS/ffmpeg-latest-macos64-static.zip unzip -qq ffmpeg-latest-macos64-static.zip rm ffmpeg-latest-macos64-static.zip mv ffmpeg-latest-macos64-static ffmpeg diff --git a/setup.py b/setup.py index 70e1715d0..465d60516 100644 --- a/setup.py +++ b/setup.py @@ -112,7 +112,7 @@ def latest_version(package_name): "pyzmq==24.0.1", "Pillow", "simplejpeg{}".format(latest_version("simplejpeg")), - "mss==7.0.1", # TODO temporary solution, needs to be addressed + "mss{}".format(latest_version("mss")), "pyscreenshot{}".format(latest_version("pyscreenshot")), ] + (["picamera"] if ("arm" in platform.uname()[4][:3]) else []) @@ -126,15 +126,15 @@ def latest_version(package_name): "yt_dlp{}".format(latest_version("yt_dlp")), "pyzmq==24.0.1", "simplejpeg{}".format(latest_version("simplejpeg")), - "mss==7.0.1", # TODO temporary solution, needs to be addressed + "mss{}".format(latest_version("mss")), "Pillow", "pyscreenshot{}".format(latest_version("pyscreenshot")), - "starlette", + "starlette{}".format(latest_version("starlette")), "jinja2", "msgpack{}".format(latest_version("msgpack")), "msgpack_numpy{}".format(latest_version("msgpack_numpy")), "aiortc{}".format(latest_version("aiortc")), - "uvicorn", + "uvicorn{}".format(latest_version("uvicorn")), ] + (["picamera"] if ("arm" in platform.uname()[4][:3]) else []) + ( @@ -165,7 +165,7 @@ def latest_version(package_name): "dash", "hls", "Video Processing", - "Video Stablization", + "Video Stabilization", "Computer Vision", "Video Streaming", "raspberrypi", @@ -184,12 +184,12 @@ def latest_version(package_name): "Intended Audience :: Science/Research", "Intended Audience :: Education", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], - python_requires=">=3.7", + python_requires=">=3.8", scripts=[], project_urls={ "Bug Reports": "https://github.com/abhiTronix/vidgear/issues", diff --git a/vidgear/gears/asyncio/webgear.py b/vidgear/gears/asyncio/webgear.py index 64a7214a8..d68e51293 100755 --- a/vidgear/gears/asyncio/webgear.py +++ b/vidgear/gears/asyncio/webgear.py @@ -496,7 +496,7 @@ async def __homepage(self, request): Returns an HTML index page. """ return ( - self.__templates.TemplateResponse("index.html", {"request": request}) + self.__templates.TemplateResponse(request, "index.html") if not self.__skip_generate_webdata else JSONResponse( {"detail": "WebGear Data-Files Auto-Generation WorkFlow is disabled!"}, @@ -509,9 +509,7 @@ async def __not_found(self, request, exc): Returns an HTML 404 page. """ return ( - self.__templates.TemplateResponse( - "404.html", {"request": request}, status_code=404 - ) + self.__templates.TemplateResponse(request, "404.html", status_code=404) if not self.__skip_generate_webdata else JSONResponse( {"detail": "WebGear Data-Files Auto-Generation WorkFlow is disabled!"}, @@ -524,9 +522,7 @@ async def __server_error(self, request, exc): Returns an HTML 500 page. """ return ( - self.__templates.TemplateResponse( - "500.html", {"request": request}, status_code=500 - ) + self.__templates.TemplateResponse(request, "500.html", status_code=500) if not self.__skip_generate_webdata else JSONResponse( {"detail": "WebGear Data-Files Auto-Generation WorkFlow is disabled!"}, diff --git a/vidgear/gears/asyncio/webgear_rtc.py b/vidgear/gears/asyncio/webgear_rtc.py index 33e622fe1..62729559c 100644 --- a/vidgear/gears/asyncio/webgear_rtc.py +++ b/vidgear/gears/asyncio/webgear_rtc.py @@ -596,23 +596,19 @@ async def __homepage(self, request): """ Return an HTML index page. """ - return self.__templates.TemplateResponse("index.html", {"request": request}) + return self.__templates.TemplateResponse(request, "index.html") async def __not_found(self, request, exc): """ Return an HTML 404 page. """ - return self.__templates.TemplateResponse( - "404.html", {"request": request}, status_code=404 - ) + return self.__templates.TemplateResponse(request, "404.html", status_code=404) async def __server_error(self, request, exc): """ Return an HTML 500 page. """ - return self.__templates.TemplateResponse( - "500.html", {"request": request}, status_code=500 - ) + return self.__templates.TemplateResponse(request, "500.html", status_code=500) async def __reset_connections(self, request): """ diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 613792629..096de933b 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -220,7 +220,6 @@ def __init__( time_delay=0, **options ): - """ This constructor method initializes the object state and attributes of the CamGear class. @@ -449,7 +448,6 @@ def __update(self): # or frames runs out # if the thread indicator variable is set, stop the thread while not self.__terminate.is_set(): - # stream not read yet self.__stream_read.clear() diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 2765f8ba7..3ef639a84 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -119,7 +119,6 @@ def __init__( logging=False, **options ): - """ This constructor method initializes the object state and attributes of the NetGear class. @@ -241,6 +240,9 @@ def __init__( self.__max_retries = 3 # request timeout self.__request_timeout = 4000 # 4 secs + else: + # subscriber timeout + self.__subscriber_timeout = None # Handle user-defined options dictionary values # reformat dictionary @@ -384,6 +386,7 @@ def __init__( self.__max_retries = value else: logger.warning("Invalid `max_retries` value skipped!") + # assign request timeout in synchronous patterns elif key == "request_timeout" and isinstance(value, int) and pattern < 2: if value >= 4: @@ -391,6 +394,15 @@ def __init__( else: logger.warning("Invalid `request_timeout` value skipped!") + # assign subscriber timeout + elif ( + key == "subscriber_timeout" and isinstance(value, int) and pattern == 2 + ): + if value > 0: + self.__subscriber_timeout = value * 1000 # covert to milliseconds + else: + logger.warning("Invalid `request_timeout` value skipped!") + # handle ZMQ flags elif key == "flag" and isinstance(value, int): self.__msg_flag = value @@ -403,7 +415,6 @@ def __init__( # Handle Secure mode if self.__secure_mode: - # activate and log if overwriting is enabled if overwrite_cert: if not receive_mode: @@ -525,7 +536,6 @@ def __init__( # check whether `receive_mode` is enabled if self.__receive_mode: - # define connection address if address is None: address = "*" # define address @@ -609,9 +619,15 @@ def __init__( # enable CURVE connection for this socket self.__msg_socket.curve_server = True - # define exclusive socket options for patterns + # define exclusive socket options for `patterns=2` if self.__pattern == 2: self.__msg_socket.setsockopt_string(zmq.SUBSCRIBE, "") + self.__subscriber_timeout and self.__msg_socket.setsockopt( + zmq.RCVTIMEO, self.__subscriber_timeout + ) + self.__subscriber_timeout and self.__msg_socket.setsockopt( + zmq.LINGER, 0 + ) # if multiserver_mode is enabled, then assign port addresses to zmq socket if self.__multiserver_mode: @@ -640,12 +656,17 @@ def __init__( ) self.__msg_pattern = msg_pattern[1] self.__poll.register(self.__msg_socket, zmq.POLLIN) - self.__logging and logger.debug( "Reliable transmission is enabled for this pattern with max-retries: {} and timeout: {} secs.".format( self.__max_retries, self.__request_timeout / 1000 ) ) + else: + self.__logging and self.__subscriber_timeout and logger.debug( + "Timeout: {} secs is enabled for this system.".format( + self.__subscriber_timeout / 1000 + ) + ) except Exception as e: # otherwise log and raise error @@ -721,7 +742,6 @@ def __init__( logger.debug("Receive Mode is now activated.") else: - # otherwise default to `Send Mode` # define connection address @@ -931,7 +951,6 @@ def __init__( ) def __recv_handler(self): - """ A threaded receiver handler, that keep iterating data from ZMQ socket to a internally monitored deque, until the thread is terminated, or socket disconnects. @@ -941,7 +960,6 @@ def __recv_handler(self): # keep looping infinitely until the thread is terminated while not self.__terminate: - # check queue buffer for overflow if len(self.__queue) >= 96: # stop iterating if overflowing occurs @@ -986,7 +1004,14 @@ def __recv_handler(self): continue else: - msg_json = self.__msg_socket.recv_json(flags=self.__msg_flag) + try: + msg_json = self.__msg_socket.recv_json(flags=self.__msg_flag) + except zmq.ZMQError as e: + if e.errno == zmq.EAGAIN: + logger.critical("Connection Timeout. Exiting!") + self.__terminate = True + self.__queue.append(None) + break # check if terminate_flag` received if msg_json["terminate_flag"]: @@ -1302,7 +1327,6 @@ def send(self, frame, message=None): if self.__pattern < 2: # check if Bidirectional data transmission is enabled if self.__bi_mode or self.__multiclient_mode: - # handles return data recvd_data = None @@ -1452,9 +1476,12 @@ def send(self, frame, message=None): # log confirmation self.__logging and logger.debug(recv_confirmation) - def close(self): + def close(self, kill=False): """ Safely terminates the threads, and NetGear resources. + + Parameters: + kill (bool): Kills ZMQ context instead of graceful exiting in receive mode. """ # log it self.__logging and logger.debug( @@ -1469,20 +1496,29 @@ def close(self): self.__queue.clear() # call immediate termination self.__terminate = True - # wait until stream resources are released (producer thread might be still grabbing frame) + # properly close the socket + self.__logging and logger.debug("Terminating. Please wait...") + # wait until stream resources are released + # (producer thread might be still grabbing frame) if self.__thread is not None: # properly handle thread exit - self.__thread.join() + if self.__thread.is_alive() and kill: + # force close if still alive + logger.warning("Thread still running...Killing it forcefully!") + self.__msg_context.destroy() + self.__thread.join() + else: + self.__thread.join() + self.__msg_socket.close(linger=0) self.__thread = None - self.__logging and logger.debug("Terminating. Please wait...") - # properly close the socket - self.__msg_socket.close(linger=0) self.__logging and logger.debug("Terminated Successfully!") - else: # indicate that process should be terminated self.__terminate = True - + # log if kill enabled + kill and logger.warning( + "`kill` parmeter is only available in the receive mode." + ) # check if all attempts of reconnecting failed, then skip to closure if (self.__pattern < 2 and not self.__max_retries) or ( self.__multiclient_mode and not self.__port_buffer diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index c5aa17318..64ab630ee 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -106,7 +106,11 @@ def __init__( } # check whether user-defined dimensions are provided if screen_dims and len(screen_dims) == 4: - key_order = ("top", "left", "width", "height") + key_order = ( + ("top", "left", "width", "height") + if self.__backend != "dxcam" + else ("left", "top", "width", "height") + ) screen_dims = OrderedDict((k, screen_dims[k]) for k in key_order) logging and logger.debug( "Setting Capture-Area dimensions: {}".format(json.dumps(screen_dims)) @@ -165,11 +169,7 @@ def __init__( # raise error(s) for critical Class imports import_dependency_safe("pyscreenshot" if pysct is None else "") # reset backend if not provided - self.__backend = ( - "pil" - if self.__backend is None or self.__backend == "mss" - else self.__backend - ) + self.__backend = "pil" if self.__backend is None else self.__backend # check if valid backend assert ( self.__backend in pysct.backends() diff --git a/vidgear/gears/stabilizer.py b/vidgear/gears/stabilizer.py index 1bbe557e8..5bd998ab2 100644 --- a/vidgear/gears/stabilizer.py +++ b/vidgear/gears/stabilizer.py @@ -59,7 +59,6 @@ def __init__( crop_n_zoom=False, logging=False, ): - """ This constructor method initializes the object state and attributes of the Stabilizer class. @@ -67,7 +66,7 @@ def __init__( smoothing_radius (int): alter averaging window size. border_type (str): changes the extended border type. border_size (int): enables and set the value for extended border size to reduce the black borders. - crop_n_zoom (bool): enables croping and zooming of frames(to original size) to reduce the black borders. + crop_n_zoom (bool): enables cropping and zooming of frames(to original size) to reduce the black borders. logging (bool): enables/disables logging. """ # print current version @@ -88,9 +87,9 @@ def __init__( # 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.__path = None # handles path i.e cumulative sum of previous_2_current transformations along a axis + self.__transforms = [] # handles previous_2_current transformations [dx,dy,da] + self.__frame_transforms_smoothed = None # handles smoothed array of previous_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 @@ -193,24 +192,13 @@ def stabilize(self, frame): : ] # save gray frame clone for further processing - elif self.__frame_queue_indexes[-1] <= self.__smoothing_radius - 1: + 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: - # 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 - ) - # calculate deviation of path from smoothed path - deviation = self.__smoothed_path - self.__path - # save smoothed transformation - self.__frame_transforms_smoothed = self.frame_transform + deviation else: # start applying transformations self.__frame_queue.append(frame) # save frame to deque @@ -253,7 +241,7 @@ def __generate_transformations(self): status == 1 ] # previous - # calculate optimal affine transformation between pevious_2_current key-points + # calculate optimal affine transformation between previous_2_current key-points if self.__cv2_version == 3: # backward compatibility with OpenCV3 transformation = cv2.estimateRigidTransform( @@ -270,11 +258,11 @@ def __generate_transformations(self): # check if transformation is not None if not (transformation is None): - # pevious_2_current translation in x direction + # previous_2_current translation in x direction dx = transformation[0, 2] - # pevious_2_current translation in y direction + # previous_2_current translation in y direction dy = transformation[1, 2] - # pevious_2_current rotation in angle + # previous_2_current rotation in angle da = np.arctan2(transformation[1, 0], transformation[0, 0]) else: # otherwise zero it diff --git a/vidgear/gears/streamgear.py b/vidgear/gears/streamgear.py index 7d95c9191..315d66feb 100644 --- a/vidgear/gears/streamgear.py +++ b/vidgear/gears/streamgear.py @@ -79,8 +79,9 @@ def __init__( format (str): select the adaptive HTTP streaming format(DASH and HLS). custom_ffmpeg (str): assigns the location of custom path/directory for custom FFmpeg executables. logging (bool): enables/disables logging. - stream_params (dict): provides the flexibility to control supported internal parameters and FFmpeg properities. + stream_params (dict): provides the flexibility to control supported internal parameters and FFmpeg properties. """ + # print current version logcurr_vidgear_ver(logging=logging) @@ -873,7 +874,6 @@ def __generate_dash_stream(self, input_params, output_params): return (input_params, output_params) def __Build_n_Execute(self, input_params, output_params): - """ An Internal function that launches FFmpeg subprocess and pipelines commands. diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 8c537d8a8..ff42a812a 100644 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -53,7 +53,7 @@ class WriteGear: WriteGear handles various powerful Video-Writer Tools that provide us the freedom to do almost anything imaginable with multimedia data. WriteGear API provides a complete, flexible, and robust wrapper around FFmpeg, a leading multimedia framework. WriteGear can process real-time frames into a lossless - compressed video-file with any suitable specification (such asbitrate, codec, framerate, resolution, subtitles, etc.). It is powerful enough to perform complex tasks such as + compressed video-file with any suitable specification (such as bitrate, codec, framerate, resolution, subtitles, etc.). It is powerful enough to perform complex tasks such as Live-Streaming (such as for Twitch) and Multiplexing Video-Audio with real-time frames in way fewer lines of code. Best of all, WriteGear grants users the complete freedom to play with any FFmpeg parameter with its exclusive Custom Commands function without relying on any @@ -83,7 +83,6 @@ def __init__( logging=False, **output_params ): - """ This constructor method initializes the object state and attributes of the WriteGear class. @@ -92,7 +91,7 @@ def __init__( compression_mode (bool): selects the WriteGear's Primary Mode of Operation. custom_ffmpeg (str): assigns the location of custom path/directory for custom FFmpeg executables. logging (bool): enables/disables logging. - output_params (dict): provides the flexibility to control supported internal parameters and FFmpeg properities. + output_params (dict): provides the flexibility to control supported internal parameters and FFmpeg properties. """ # print current version logcurr_vidgear_ver(logging=logging) @@ -122,6 +121,9 @@ def __init__( self.__initiate_process = ( True # handles initiate one-time process for generating pipeline ) + self.__ffmpeg_window_disabler_patch = ( + False # handles disabling window for ffmpeg subprocess on Windows + ) self.__out_file = None # handles output gstpipeline_mode = False # handles GStreamer Pipeline Mode @@ -244,6 +246,22 @@ def __init__( True if ("-i" in self.__output_parameters) else False ) + # handles disabling window for ffmpeg subprocess on Windows OS (only for Compression mode) + # this patch prevents ffmpeg creation window from opening when building exe files + ffmpeg_window_disabler_patch = self.__output_parameters.pop( + "-disable_ffmpeg_window", False + ) + # check if value is valid + if not self.__os_windows or logging: + logger.warning( + "Optional `-disable_ffmpeg_window` flag is only available on Windows OS with `logging=False`. Discarding!" + ) + elif isinstance(ffmpeg_window_disabler_patch, bool): + self.__ffmpeg_window_disabler_patch = ffmpeg_window_disabler_patch + else: + # handle improper values + self.__ffmpeg_window_disabler_patch = False + # validate the FFmpeg path/binaries and returns valid executable FFmpeg # location/path (also auto-downloads static binaries on Windows OS) self.__ffmpeg = get_valid_ffmpeg_path( @@ -352,7 +370,6 @@ def __init__( ) def write(self, frame, rgb_mode=False): - """ Pipelines `ndarray` frames to respective API _(**FFmpeg** in Compression Mode & **OpenCV's VideoWriter API** in Non-Compression Mode)_. @@ -516,7 +533,6 @@ def __PreprocessFFParams(self, channels, dtype=None, rgb=False): ) def __start_FFProcess(self, input_params, output_params): - """ An Internal method that launches FFmpeg subprocess pipeline in Compression Mode for pipelining frames to `stdin`. @@ -595,7 +611,13 @@ def __start_FFProcess(self, input_params, output_params): else: # In silent mode self.__process = sp.Popen( - cmd, stdin=sp.PIPE, stdout=sp.DEVNULL, stderr=sp.STDOUT + cmd, + stdin=sp.PIPE, + stdout=sp.DEVNULL, + stderr=sp.STDOUT, + creationflags=( # this prevents ffmpeg creation window from opening when building exe files on Windows + sp.DETACHED_PROCESS if self.__ffmpeg_window_disabler_patch else 0 + ), ) def __enter__(self): diff --git a/vidgear/tests/network_tests/test_netgear.py b/vidgear/tests/network_tests/test_netgear.py index 42aecf4f1..6875dc76a 100644 --- a/vidgear/tests/network_tests/test_netgear.py +++ b/vidgear/tests/network_tests/test_netgear.py @@ -133,7 +133,13 @@ def test_patterns(pattern): Testing NetGear different messaging patterns """ # define parameters - options = {"flag": 0, "copy": False, "track": False, "jpeg_compression": False} + options = { + "flag": 0, + "copy": False, + "track": False, + "jpeg_compression": False, + "subscriber_timeout": 5, + } # initialize frame_server = None stream = None @@ -167,9 +173,9 @@ def test_patterns(pattern): if not (stream is None): stream.release() if not (server is None): - server.close() + server.close(kill=True) if not (client is None): - client.close() + client.close(kill=True) @pytest.mark.parametrize( @@ -239,9 +245,9 @@ def test_compression(options_server): if not (stream is None): stream.stop() if not (server is None): - server.close() + server.close(kill=True) if not (client is None): - client.close() + client.close(kill=True) test_data_class = [ @@ -304,9 +310,9 @@ def test_secure_mode(pattern, security_mech, custom_cert_location, overwrite_cer if not (stream is None): stream.release() if not (server is None): - server.close() + server.close(kill=True) if not (client is None): - client.close() + client.close(kill=True) @pytest.mark.parametrize( @@ -418,9 +424,9 @@ def test_bidirectional_mode(pattern, target_data, options): if not (stream is None): stream.stop() if not (server is None): - server.close() + server.close(kill=True) if not (client is None): - client.close() + client.close(kill=True) @pytest.mark.parametrize( @@ -455,6 +461,7 @@ def test_bidirectional_mode(pattern, target_data, options): { "multiserver_mode": True, "ssh_tunnel_mode": "new@sdf.org", + "subscriber_timeout": 0, }, ), ], @@ -647,6 +654,7 @@ def test_multiclient_mode(pattern): }, {"max_retries": 2, "request_timeout": 4, "multiclient_mode": True}, {"max_retries": 2, "request_timeout": -1, "multiserver_mode": True}, + {"subscriber_timeout": 4}, ], ) def test_client_reliablity(options): @@ -658,7 +666,7 @@ def test_client_reliablity(options): try: # define params client = NetGear( - pattern=1, + pattern=2 if "subscriber_timeout" in options.keys() else 1, port=[5587] if "multiserver_mode" in options.keys() else 6657, receive_mode=True, logging=True, @@ -747,8 +755,8 @@ def test_server_reliablity(options): @pytest.mark.parametrize( "server_ports, client_ports, options", [ - (0, 5555, {"multiserver_mode": True}), - (5555, 0, {"multiclient_mode": True}), + (None, 5555, {"multiserver_mode": True}), + (5555, None, {"multiclient_mode": True}), ], ) @pytest.mark.xfail(raises=ValueError) diff --git a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear_rtc.py b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear_rtc.py index 5bbe4f5dc..5477ce796 100644 --- a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear_rtc.py +++ b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear_rtc.py @@ -189,28 +189,55 @@ def stop(self): self.running = False -class Invalid_Custom_Stream_Class: +class Invalid_Custom_Channel_Class: """ Custom Invalid WebGear_RTC Server """ - def __init__(self, source=0): + def __init__(self): # define running flag self.running = True + # define stream + self.stream = Custom_Grayscale_class() + + def read(self): + # return non supported channeled frame + return self.stream.read(size=(480, 640, 5)) + def stop(self): # don't forget this function!!! # flag that we're not running self.running = False + self.stream.stop() + + +class Invalid_Custom_Stream_Class: + """ + Custom Invalid WebGear_RTC Server + """ + + def __init__(self): + # define running flag + self.running = True + + def stop(self): + # flag that we're not running + self.running = False test_data = [ + (None, False, None, 0), (return_testvideo_path(), True, None, 0), (return_testvideo_path(), False, "COLOR_BGR2HSV", 1), ] +@pytest.mark.skipif( + platform.python_version_tuple()[:2] >= ("3", "11"), + reason="Random Failures!", +) @pytest.mark.asyncio @pytest.mark.parametrize("source, stabilize, colorspace, time_delay", test_data) async def test_webgear_rtc_class(source, stabilize, colorspace, time_delay): @@ -248,7 +275,7 @@ async def test_webgear_rtc_class(source, stabilize, colorspace, time_delay): await offer_pc.close() web.shutdown() except Exception as e: - if not isinstance(e, MediaStreamError): + if source and not isinstance(e, MediaStreamError): pytest.fail(str(e)) @@ -277,6 +304,10 @@ async def test_webgear_rtc_class(source, stabilize, colorspace, time_delay): ] +@pytest.mark.skipif( + platform.python_version_tuple()[:2] >= ("3", "11"), + reason="Random Failures!", +) @pytest.mark.asyncio @pytest.mark.parametrize("options", test_data) async def test_webgear_rtc_options(options): @@ -330,7 +361,13 @@ async def test_webgear_rtc_options(options): ] -@pytest.mark.skipif((platform.system() == "Windows"), reason="Random Failures!") +@pytest.mark.skipif( + ( + platform.system() == "Windows" + or platform.python_version_tuple()[:2] >= ("3", "11") + ), + reason="Random Failures!", +) @pytest.mark.asyncio @pytest.mark.parametrize("options", test_data) async def test_webpage_reload(options): @@ -406,12 +443,22 @@ async def test_webpage_reload(options): test_stream_classes = [ (None, False), (Custom_Stream_Class(source=return_testvideo_path()), True), - (VideoGear(source=return_testvideo_path(), logging=True), True), + ( + VideoGear( + source=return_testvideo_path(), colorspace="COLOR_BGR2BGRA", logging=True + ), + True, + ), (Custom_Grayscale_class(), False), - (Invalid_Custom_Stream_Class(source=return_testvideo_path()), False), + (Invalid_Custom_Channel_Class(), False), + (Invalid_Custom_Stream_Class(), False), ] +@pytest.mark.skipif( + platform.python_version_tuple()[:2] >= ("3", "11"), + reason="Random Failures!", +) @pytest.mark.asyncio @pytest.mark.parametrize("stream_class, result", test_stream_classes) async def test_webgear_rtc_custom_stream_class(stream_class, result): @@ -462,6 +509,10 @@ async def test_webgear_rtc_custom_stream_class(stream_class, result): ] +@pytest.mark.skipif( + platform.python_version_tuple()[:2] >= ("3", "11"), + reason="Random Failures!", +) @pytest.mark.asyncio @pytest.mark.parametrize("middleware, result", test_data_class) async def test_webgear_rtc_custom_middleware(middleware, result): @@ -482,6 +533,10 @@ async def test_webgear_rtc_custom_middleware(middleware, result): pytest.xfail(str(e)) +@pytest.mark.skipif( + platform.python_version_tuple()[:2] >= ("3", "11"), + reason="Random Failures!", +) @pytest.mark.asyncio async def test_webgear_rtc_routes(): """ @@ -527,6 +582,10 @@ async def test_webgear_rtc_routes(): pytest.fail(str(e)) +@pytest.mark.skipif( + platform.python_version_tuple()[:2] >= ("3", "11"), + reason="Random Failures!", +) @pytest.mark.asyncio async def test_webgear_rtc_routes_validity(): """ diff --git a/vidgear/tests/writer_tests/test_compression_mode.py b/vidgear/tests/writer_tests/test_compression_mode.py index aac1fa472..262877ded 100644 --- a/vidgear/tests/writer_tests/test_compression_mode.py +++ b/vidgear/tests/writer_tests/test_compression_mode.py @@ -229,6 +229,7 @@ def test_output_dimensions(): output_params = { "-output_dimensions": dimensions, "-ffmpeg_download_path": tempfile.gettempdir(), + "-disable_ffmpeg_window": True, } else: output_params = {"-output_dimensions": dimensions} @@ -264,7 +265,13 @@ def test_output_dimensions(): ( "Output2.mp4", "", - {"-vcodec": "libx264", "-crf": 0, "-preset": "fast", "-ffpreheaders": False}, + { + "-vcodec": "libx264", + "-crf": 0, + "-preset": "fast", + "-ffpreheaders": False, + "-disable_ffmpeg_window": True, + }, True, ), ( @@ -276,6 +283,7 @@ def test_output_dimensions(): "-crf": 0, "-preset": "veryfast", "-ffpreheaders": ["-re"], + "-disable_ffmpeg_window": "Invalid", }, True, ), diff --git a/vidgear/tests/writer_tests/test_non_compression_mode.py b/vidgear/tests/writer_tests/test_non_compression_mode.py index 69f555e60..c9c45bfd2 100644 --- a/vidgear/tests/writer_tests/test_non_compression_mode.py +++ b/vidgear/tests/writer_tests/test_non_compression_mode.py @@ -134,6 +134,7 @@ def test_write(conversion): "-fourcc": "DIVX", "-fps": 25, "-backend": "CAP_FFMPEG", + "-disable_ffmpeg_window": True, "-color": True, "-gst_pipeline_mode": False, }, diff --git a/vidgear/version.py b/vidgear/version.py index 260c070a8..f9aa3e110 100644 --- a/vidgear/version.py +++ b/vidgear/version.py @@ -1 +1 @@ -__version__ = "0.3.1" +__version__ = "0.3.2"