From 6a40e8ddde2d9614b3e540caf7d11bf4f327db28 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Sat, 8 Jun 2024 14:24:44 +0530 Subject: [PATCH 01/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20WebGear=5FRTC:=20Imp?= =?UTF-8?q?roved=20connection=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ⚑️ Only close peer connections that are not already in the "closed" state. - 🎨 Logged the ICE connection state change only when it's not in the "failed" state, reducing unnecessary logging. --- vidgear/gears/asyncio/webgear_rtc.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vidgear/gears/asyncio/webgear_rtc.py b/vidgear/gears/asyncio/webgear_rtc.py index 4b3c3e4a4..08e3db72a 100644 --- a/vidgear/gears/asyncio/webgear_rtc.py +++ b/vidgear/gears/asyncio/webgear_rtc.py @@ -559,7 +559,6 @@ async def __offer(self, request): # track ICE connection state changes @pc.on("iceconnectionstatechange") async def on_iceconnectionstatechange(): - logger.debug("ICE connection state is %s" % pc.iceConnectionState) if pc.iceConnectionState == "failed": logger.error("ICE connection state failed.") # check if Live Broadcasting is enabled @@ -567,6 +566,8 @@ async def on_iceconnectionstatechange(): # if not, close connection. await pc.close() self.__pcs.discard(pc) + else: + logger.debug("ICE connection state is %s" % pc.iceConnectionState) # Change the remote description associated with the connection. await pc.setRemoteDescription(offer) @@ -628,7 +629,9 @@ async def __reset_connections(self, request): logger.critical("Resetting Server") # close old peer connections if parameter != 0: # disable if specified explicitly - coros = [pc.close() for pc in self.__pcs] + coros = [ + pc.close() for pc in self.__pcs if pc.iceConnectionState != "closed" + ] await asyncio.gather(*coros) self.__pcs.clear() await self.__default_rtc_server.reset() @@ -645,7 +648,9 @@ async def __lifespan(self, context): # close Video Server self.shutdown() # collects peer RTC connections - coros = [pc.close() for pc in self.__pcs] + coros = [ + pc.close() for pc in self.__pcs if pc.iceConnectionState != "closed" + ] await asyncio.gather(*coros) self.__pcs.clear() From 8667e5e2e6319fb1119205c76e8945c546c1e7d4 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Sun, 9 Jun 2024 00:07:24 +0530 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=91=B7=20CI:=20Improved=20WebGear?= =?UTF-8?q?=20RTC=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ‘· Added a pytest fixture `event_loop_policy` to set the WindowsSelectorEventLoopPolicy on Windows platforms. - ⚑️Replace the use of `async_asgi_testclient` with `httpx.AsyncClient` and `httpx.ASGITransport` for testing the ASGI application. - πŸ₯… Update the test cases to use the `httpx.AsyncClient` correctly: - Use the `content` parameter instead of `data` when sending POST requests with JSON payloads. - Use the `post` method instead of `get` when sending WebRTC offers, as `get` do not support `content` parameter. - πŸ”₯ Remove the `pytest.mark.skipif` conditions related to Python version 3.11 and above, as the compatibility issues have been addressed. - πŸ—‘οΈ Add the `pytest.mark.asyncio(scope="module")` marker to the test functions to ensure proper handling of asynchronous tests. --- .../asyncio_tests/test_netgear_async.py | 2 +- .../asyncio_tests/test_webgear_rtc.py | 105 +++++++++--------- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/vidgear/tests/network_tests/asyncio_tests/test_netgear_async.py b/vidgear/tests/network_tests/asyncio_tests/test_netgear_async.py index 27546cdba..b094cf836 100644 --- a/vidgear/tests/network_tests/asyncio_tests/test_netgear_async.py +++ b/vidgear/tests/network_tests/asyncio_tests/test_netgear_async.py @@ -43,7 +43,7 @@ @pytest.fixture(scope="module") def event_loop_policy(request): if platform.system() == "Windows": - logger.critical("Setting WindowsSelectorEventLoopPolicy!!!") + logger.info("Setting WindowsSelectorEventLoopPolicy!") return asyncio.WindowsSelectorEventLoopPolicy() else: return asyncio.DefaultEventLoopPolicy() 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 fbeaa893c..b9ea2ab87 100644 --- a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear_rtc.py +++ b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear_rtc.py @@ -33,7 +33,7 @@ from starlette.responses import PlainTextResponse from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware -from async_asgi_testclient import TestClient +from httpx import AsyncClient, ASGITransport from aiortc import ( MediaStreamTrack, RTCPeerConnection, @@ -53,7 +53,14 @@ logger.addHandler(logger_handler()) logger.setLevel(log.DEBUG) -pytestmark = pytest.mark.asyncio(scope="module") + +@pytest.fixture(scope="module") +def event_loop_policy(request): + if platform.system() == "Windows": + logger.info("Setting WindowsSelectorEventLoopPolicy!") + return asyncio.WindowsSelectorEventLoopPolicy() + else: + return asyncio.DefaultEventLoopPolicy() def return_testvideo_path(): @@ -228,10 +235,7 @@ def stop(self): ] -@pytest.mark.skipif( - platform.python_version_tuple()[:2] >= ("3", "11"), - reason="Random Failures!", -) +@pytest.mark.asyncio(scope="module") @pytest.mark.parametrize("source, stabilize, colorspace, time_delay", test_data) async def test_webgear_rtc_class(source, stabilize, colorspace, time_delay): """ @@ -245,7 +249,9 @@ async def test_webgear_rtc_class(source, stabilize, colorspace, time_delay): time_delay=time_delay, logging=True, ) - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: response = await client.get("/") assert response.status_code == 200 response_404 = await client.get("/test") @@ -253,15 +259,15 @@ async def test_webgear_rtc_class(source, stabilize, colorspace, time_delay): (offer_pc, data) = await get_RTCPeer_payload() response_rtc_answer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) params = response_rtc_answer.json() answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) await offer_pc.setRemoteDescription(answer) - response_rtc_offer = await client.get( + response_rtc_offer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) assert response_rtc_offer.status_code == 200 @@ -297,10 +303,7 @@ 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(scope="module") @pytest.mark.parametrize("options", test_data) async def test_webgear_rtc_options(options): """ @@ -309,7 +312,9 @@ async def test_webgear_rtc_options(options): web = None try: web = WebGear_RTC(source=return_testvideo_path(), logging=True, **options) - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: response = await client.get("/") assert response.status_code == 200 if ( @@ -319,15 +324,15 @@ async def test_webgear_rtc_options(options): (offer_pc, data) = await get_RTCPeer_payload() response_rtc_answer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) params = response_rtc_answer.json() answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) await offer_pc.setRemoteDescription(answer) - response_rtc_offer = await client.get( + response_rtc_offer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) assert response_rtc_offer.status_code == 200 @@ -369,7 +374,9 @@ async def test_webpage_reload(options): web = WebGear_RTC(source=return_testvideo_path(), logging=True, **options) try: # run webgear_rtc - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: response = await client.get("/") assert response.status_code == 200 @@ -377,15 +384,15 @@ async def test_webpage_reload(options): (offer_pc, data) = await get_RTCPeer_payload() response_rtc_answer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) params = response_rtc_answer.json() answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) await offer_pc.setRemoteDescription(answer) - response_rtc_offer = await client.get( + response_rtc_offer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) assert response_rtc_offer.status_code == 200 @@ -406,15 +413,15 @@ async def test_webpage_reload(options): (offer_pc, data) = await get_RTCPeer_payload() response_rtc_answer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) params = response_rtc_answer.json() answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) await offer_pc.setRemoteDescription(answer) - response_rtc_offer = await client.get( + response_rtc_offer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) assert response_rtc_offer.status_code == 200 @@ -446,10 +453,7 @@ async def test_webpage_reload(options): ] -@pytest.mark.skipif( - platform.python_version_tuple()[:2] >= ("3", "11"), - reason="Random Failures!", -) +@pytest.mark.asyncio(scope="module") @pytest.mark.parametrize("stream_class, result", test_stream_classes) async def test_webgear_rtc_custom_stream_class(stream_class, result): """ @@ -463,7 +467,9 @@ async def test_webgear_rtc_custom_stream_class(stream_class, result): } try: web = WebGear_RTC(logging=True, **options) - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: response = await client.get("/") assert response.status_code == 200 response_404 = await client.get("/test") @@ -471,15 +477,15 @@ async def test_webgear_rtc_custom_stream_class(stream_class, result): (offer_pc, data) = await get_RTCPeer_payload() response_rtc_answer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) params = response_rtc_answer.json() answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) await offer_pc.setRemoteDescription(answer) - response_rtc_offer = await client.get( + response_rtc_offer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) assert response_rtc_offer.status_code == 200 @@ -499,10 +505,7 @@ 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(scope="module") @pytest.mark.parametrize("middleware, result", test_data_class) async def test_webgear_rtc_custom_middleware(middleware, result): """ @@ -511,7 +514,9 @@ async def test_webgear_rtc_custom_middleware(middleware, result): try: web = WebGear_RTC(source=return_testvideo_path(), logging=True) web.middleware = middleware - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: response = await client.get("/") assert response.status_code == 200 web.shutdown() @@ -522,10 +527,7 @@ 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(scope="module") async def test_webgear_rtc_routes(): """ Test for WebGear_RTC API's custom routes @@ -542,7 +544,9 @@ async def test_webgear_rtc_routes(): web.routes.append(Route("/hello", endpoint=hello_webpage)) # test - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: response = await client.get("/") assert response.status_code == 200 response_hello = await client.get("/hello") @@ -550,15 +554,15 @@ async def test_webgear_rtc_routes(): (offer_pc, data) = await get_RTCPeer_payload() response_rtc_answer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) params = response_rtc_answer.json() answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) await offer_pc.setRemoteDescription(answer) - response_rtc_offer = await client.get( + response_rtc_offer = await client.post( "/offer", - data=data, + content=data, headers={"Content-Type": "application/json"}, ) assert response_rtc_offer.status_code == 200 @@ -570,10 +574,7 @@ 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(scope="module") async def test_webgear_rtc_routes_validity(): """ Test WebGear_RTC Routes @@ -589,7 +590,9 @@ async def test_webgear_rtc_routes_validity(): # modify route web.routes.clear() # test - async with TestClient(web()) as client: + async with AsyncClient( + transport=ASGITransport(app=web()), base_url="http://testserver" + ) as client: pass except Exception as e: if isinstance(e, (RuntimeError, MediaStreamError)): From 49cc04fa25607999df6fd062c9c813f56beebad1 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Sun, 9 Jun 2024 11:50:13 +0530 Subject: [PATCH 03/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20StreamGear:=20Update?= =?UTF-8?q?d=20support=20of=20Stream=20Copy=20in=20Single=20Source=20mode.?= =?UTF-8?q?=20(Fixes=20#396)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ♻️ Ignore the stream copy parameter if Real-time Frames Mode or Custom Streams are enabled, and log appropriate warnings. - ⚑️Updated the handling of the `-acodec` parameter: - Use the default `aac` codec for Custom Streams. - Use stream copy (`-acodec copy`) for the input video's audio stream if Custom Streams are not enabled. - ♻️ Refactor the handling of the `-livestream` parameter to ensure it is only enabled for the Real-time Frames Mode. - ♻️ Refactor the video and audio bitrate assignment to skip the assignment when stream copy is enabled. - πŸ”Š Updated log message for `-clear_prev_assets` parameter. - ✏️ Fix a typo in comments. Docs: - πŸ“ Add a new tip box explaining the benefits of using stream copy (`-vcodec copy`) in the Single Source Mode for faster transcoding of HLS/DASH streams. - πŸ’¬ Highlight the limitations of stream copy, such as incompatibility with Real-time Frames Mode and Custom Streams, which require re-encoding of frames. - πŸ’¬ Clarify that the audio stream copy (`-acodec copy`) is automatically applied when using the input video's audio stream. - 🎨 Fixed various issues like typos, formatting errors, code highlighting issues, and grammar inconsistencies. --- docs/gears/pigear/usage.md | 3 -- docs/gears/streamgear/params.md | 17 ++++++--- docs/gears/streamgear/rtfm/usage.md | 14 ++++---- docs/gears/streamgear/ssm/usage.md | 12 +++++-- vidgear/gears/streamgear.py | 56 +++++++++++++++++------------ 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/docs/gears/pigear/usage.md b/docs/gears/pigear/usage.md index 1233b60b2..fb3b36d7e 100644 --- a/docs/gears/pigear/usage.md +++ b/docs/gears/pigear/usage.md @@ -55,21 +55,18 @@ Following is the bare-minimum code you need to get started with PiGear API: === ":material-linux: Linux" ```sh - # path to file export LIBCAMERA_LOG_LEVELS=2 ``` === ":fontawesome-brands-windows: Windows (Powershell)" ```powershell - # path to file $Env:LIBCAMERA_LOG_LEVELS=2 ``` === ":material-apple: MacOS" ```sh - # path to file export LIBCAMERA_LOG_LEVELS=2 ``` diff --git a/docs/gears/streamgear/params.md b/docs/gears/streamgear/params.md index 3512b074d..5e272a386 100644 --- a/docs/gears/streamgear/params.md +++ b/docs/gears/streamgear/params.md @@ -218,6 +218,7 @@ StreamGear API provides some exclusive internal parameters to easily generate St # set video source as `/home/foo/bar.mp4` stream_params = {"-video_source": "/home/foo/bar.mp4"} ``` + * **Video URL**: Valid URL of a network video stream as follows: !!! danger "Ensure the given video URL uses a protocol supported by the installed FFmpeg _(verify with `ffmpeg -protocols` terminal command)_." @@ -269,7 +270,7 @@ StreamGear API provides some exclusive internal parameters to easily generate St   -* **`-livestream`** _(bool)_: ***(optional)*** specifies whether to enable **Low-latency Live-Streaming :material-video-wireless-outline:** in Real-time Frames Mode only, where chunks will contain information for new frames only and forget previous ones, or not. The default value is `False`. It can be used as follows: +* **`-livestream`** _(bool)_: ***(optional)*** specifies whether to enable **Low-latency Live-Streaming :material-video-wireless-outline:** in [**Real-time Frames Mode**](../rtfm/overview) only, where chunks will contain information for new frames only and forget previous ones, or not. The default value is `False`. It can be used as follows: !!! warning "The `-livestream` optional parameter is **NOT** supported in [Single-Source mode](../ssm/overview)." @@ -338,9 +339,9 @@ StreamGear API provides some exclusive internal parameters to easily generate St   -* **`-clear_prev_assets`** _(bool)_: ***(optional)*** This parameter specifies whether to force-delete any previous copies of StreamGear assets _(i.e., manifest (`mpd`), playlist (`mu38`), and streaming chunks (`.m4s`), etc. files)_ present at the path specified by the [`output`](#output) parameter. The default value is `False`. It can be used as follows: +* **`-clear_prev_assets`** _(bool)_: ***(optional)*** This parameter specifies whether to remove/delete all previous copies of StreamGear assets files for selected [`format`](#format) _(i.e., manifest (`mpd`) in DASH, playlist (`mu38`) in HLS, and respective streaming chunks (`.ts`,`.m4s`), etc.)_ present at the path specified by the [`output`](#output) parameter. The default value is `False`. It can be enabled as follows: - !!! info "Additional segments _(such as `.webm`, `.mp4` chunks)_ are also cleared automatically." + !!! info "Additional segments _(such as `.webm`, `.mp4` chunks)_ are also removed automatically." ```python # delete all previous assets @@ -381,9 +382,13 @@ stream_params = {"-vcodec":"libx264", "-crf": 0, "-preset": "fast", "-tune": "ze All encoders and decoders compiled with the FFmpeg in use are supported by the StreamGear API. You can check the compiled encoders by running the following command in your terminal: -!!! warning "Stream copy (`-vcodec copy`) is not compatible with Real-time Frames Mode as this mode requires re-encoding of incoming frames." +???+ tip "Faster Transcoding with Stream Copy in Single Source Mode" + + For faster transcoding of input video, utilize Stream copy (`-vcodec copy`) as the input video encoder in the [**Single-Source Mode**](../ssm/overview) for creating HLS/DASH chunks of the primary stream efficiently. However, consider the following points: -!!! info "Similarly, supported audio/video demuxers and filters depend on the FFmpeg binaries in use." + - :warning: Stream copy is **NOT** compatible with [**Real-time Frames Mode**](../rtfm/overview), as this mode necessitates re-encoding of incoming frames. Therefore, the `-vcodec copy` parameter will be ignored. + - :warning: Stream copying **NOT** compatible with Custom Streams ([`-streams`](#a-exclusive-parameters)), which also require re-encoding for each additional stream. Consequently, the `-vcodec copy` parameter will be ignored. + - When using the audio stream from the input video, the Audio Stream copy (`-acodec copy`) encoder will be automatically applied. ```sh # for checking encoder @@ -392,6 +397,8 @@ ffmpeg -encoders # use `ffmpeg.exe -encoders` on windows ffmpeg -decoders # use `ffmpeg.exe -decoders` on windows ``` +!!! info "Similarly, supported audio/video demuxers and filters depend on the FFmpeg binaries in use." +   ## **`logging`** diff --git a/docs/gears/streamgear/rtfm/usage.md b/docs/gears/streamgear/rtfm/usage.md index 55e84ae5a..5833545bb 100644 --- a/docs/gears/streamgear/rtfm/usage.md +++ b/docs/gears/streamgear/rtfm/usage.md @@ -30,10 +30,10 @@ limitations under the License. - [x] StreamGear API **MUST** requires FFmpeg executables for its core operations. Follow these dedicated [Platform specific Installation Instructions ➢](../../ffmpeg_install/) for its installation. API will throw **RuntimeError**, if it fails to detect valid FFmpeg executables on your system. - [x] In this mode, ==API by default generates a primary stream _(at the index `0`)_ of same resolution as the input frames and at default framerate[^1].== - [x] In this mode, API **DOES NOT** automatically maps video-source audio to generated streams. You need to manually assign separate audio-source through [`-audio`](../../params/#a-exclusive-parameters) attribute of `stream_params` dictionary parameter. - - [x] In this mode, Stream copy (`-vcodec copy`) is not compatible as this mode requires re-encoding of incoming frames. + - [x] In this mode, Stream copy (`-vcodec copy`) encoder is unsupported as it requires re-encoding of incoming frames. - [x] Always use `close()` function at the very end of the main code. -???+ danger "DEPRECATION NOTICES for `v0.3.3` and above" +??? danger "DEPRECATION NOTICES for `v0.3.3` and above" - [ ] The `terminate()` method in StreamGear is now deprecated and will be removed in a future release. Developers should use the new [`close()`](../../../../bonus/reference/streamgear/#vidgear.gears.streamgear.StreamGear.close) method instead, as it offers a more descriptive name, similar to the WriteGear API, for safely terminating StreamGear processes. - [ ] The `rgb_mode` parameter in [`stream()`](../../../bonus/reference/streamgear/#vidgear.gears.streamgear.StreamGear.stream) method, which earlier used to support RGB frames in Real-time Frames Mode is now deprecated, and will be removed in a future version. Only BGR format frames will be supported going forward. Please update your code to handle BGR format frames. @@ -515,7 +515,7 @@ To generate Secondary Streams, add each desired resolution and bitrate/framerate === "DASH" - ```python linenums="1" hl_lines="12-14" + ```python linenums="1" hl_lines="11-15" # import required libraries from vidgear.gears import CamGear from vidgear.gears import StreamGear @@ -571,7 +571,7 @@ To generate Secondary Streams, add each desired resolution and bitrate/framerate === "HLS" - ```python linenums="1" hl_lines="12-14" + ```python linenums="1" hl_lines="11-15" # import required libraries from vidgear.gears import CamGear from vidgear.gears import StreamGear @@ -1038,11 +1038,9 @@ In this example, we will be using `h264_vaapi` as our Hardware Encoder and speci !!! danger "This example is just conveying the idea of how to use FFmpeg's hardware encoders with the StreamGear API in Real-time Frames Mode, which MAY OR MAY NOT suit your system. Please use suitable parameters based on your supported system and FFmpeg configurations only." -!!! warning "Stream copy (`-vcodec copy`) is not compatible with this Mode as it requires re-encoding of incoming frames." +???+ info "Checking VAAPI Support for Hardware Encoding" -??? info "Check VAAPI support" - - To use `h264_vaapi` encoder, remember to check if its available and your FFmpeg compiled with VAAPI support. You can easily do this by executing following one-liner command in your terminal, and observing if output contains something similar as follows: + To use **VAAPI** (Video Acceleration API) as a hardware encoder in this example, follow these steps to ensure your FFmpeg supports VAAPI: ```sh ffmpeg -hide_banner -encoders | grep vaapi diff --git a/docs/gears/streamgear/ssm/usage.md b/docs/gears/streamgear/ssm/usage.md index 4f1753113..2f881ef49 100644 --- a/docs/gears/streamgear/ssm/usage.md +++ b/docs/gears/streamgear/ssm/usage.md @@ -27,11 +27,17 @@ limitations under the License. - [x] In this mode, if input video-source _(i.e. `-video_source`)_ contains any audio stream/channel, then it automatically gets mapped to all generated streams. - [x] Always use `close()` function at the very end of the main code. -???+ danger "DEPRECATION NOTICES for `v0.3.3` and above" +??? danger "DEPRECATION NOTICES for `v0.3.3` and above" - [ ] The `terminate()` method in StreamGear is now deprecated and will be removed in a future release. Developers should use the new [`close()`](../../../../bonus/reference/streamgear/#vidgear.gears.streamgear.StreamGear.close) method instead, as it offers a more descriptive name, similar to the WriteGear API, for safely terminating StreamGear processes. - [ ] The [`-livestream`](../../params/#a-exclusive-parameters) optional parameter is NOT supported in this Single-Source Mode. +??? tip "Faster Transcoding of Primary Stream with Stream Copy in Single Source Mode" + + For faster transcoding of input video in this mode, utilize Stream copy (`-vcodec copy`) as the input video encoder for creating HLS/DASH chunks of the primary stream efficiently. However, consider the following points: + + - :warning: Stream copying **NOT** compatible with Custom Streams ([`-streams`](../../params/#a-exclusive-parameters)), which require re-encoding for each additional stream. Therefore, the `-vcodec copy` parameter will be ignored. + - When using the audio stream from the input video, the Audio Stream copy (`-acodec copy`) encoder will be automatically applied. !!! example "After going through following Usage Examples, Checkout more of its advanced configurations [here ➢](../../../help/streamgear_ex/)" @@ -106,7 +112,7 @@ To generate Secondary Streams, add each desired resolution and bitrate/framerate === "DASH" - ```python linenums="1" hl_lines="6-12" + ```python linenums="1" hl_lines="7-12" # import required libraries from vidgear.gears import StreamGear @@ -130,7 +136,7 @@ To generate Secondary Streams, add each desired resolution and bitrate/framerate === "HLS" - ```python linenums="1" hl_lines="6-12" + ```python linenums="1" hl_lines="7-12" # import required libraries from vidgear.gears import StreamGear diff --git a/vidgear/gears/streamgear.py b/vidgear/gears/streamgear.py index 7fd89a1ae..25e496231 100644 --- a/vidgear/gears/streamgear.py +++ b/vidgear/gears/streamgear.py @@ -214,8 +214,10 @@ def __init__( if isinstance(clear_assets, bool): self.__clear_assets = clear_assets # log if clearing assets is enabled - clear_assets and logger.debug( - "Previous StreamGear API assets will be deleted in this run." + clear_assets and logger.info( + "The `-clear_prev_assets` parameter is enabled successfully. All previous StreamGear API assets for `{}` format will be removed for this run.".format( + self.__format.upper() + ) ) else: # reset improper values @@ -223,7 +225,7 @@ def __init__( # handle whether to livestream? livestreaming = self.__params.pop("-livestream", False) - if isinstance(livestreaming, bool): + if isinstance(livestreaming, bool) and livestreaming: # NOTE: `livestream` is only available with real-time mode. self.__livestreaming = livestreaming if not (self.__video_source) else False if self.__video_source: @@ -471,7 +473,8 @@ def __PreProcess(self, channels=0, rgb=False): # in Real-time Frames Mode output_parameters["-vcodec"] = ( default_codec - if output_vcodec == "copy" and not (self.__video_source) + if output_vcodec == "copy" + and (not (self.__video_source) or "-streams" in self.__params) else output_vcodec ) # enforce compatibility with stream copy @@ -482,7 +485,10 @@ def __PreProcess(self, channels=0, rgb=False): else: # log warnings if stream copy specified in Real-time Frames Mode not (self.__video_source) and logger.error( - "Stream copy is not compatible with Real-time Frames Mode as it requires re-encoding of incoming frames. Discarding the `-vcodec copy` parameter!" + "Stream copy is not compatible with Real-time Frames Mode as it require re-encoding of incoming frames. Discarding the `-vcodec copy` parameter!" + ) + ("-streams" in self.__params) and logger.error( + "Stream copying is incompatible with Custom Streams as it require re-encoding for each additional stream. Discarding the `-vcodec copy` parameter!" ) # log warnings for these parameters self.__params.pop("-vf", False) and logger.warning( @@ -532,9 +538,7 @@ def __PreProcess(self, channels=0, rgb=False): ) ] = self.__audio # assign audio codec - output_parameters["-acodec"] = self.__params.pop( - "-acodec", "aac" if isinstance(self.__audio, list) else "copy" - ) + output_parameters["-acodec"] = self.__params.pop("-acodec", "aac") output_parameters["a_bitrate"] = bitrate # temporary handler output_parameters["-core_audio"] = ( ["-map", "1:a:0"] if self.__format == "dash" else [] @@ -549,12 +553,14 @@ def __PreProcess(self, channels=0, rgb=False): elif self.__video_source: bitrate = validate_audio(self.__ffmpeg, source=self.__video_source) if bitrate: - logger.info("Input Video's audio source will be used for this run.") + logger.info("Input video's audio source will be used for this run.") # assign audio codec - output_parameters["-acodec"] = ( - "aac" if self.__format == "hls" else "copy" + output_parameters["-acodec"] = self.__params.pop( + "-acodec", + "aac" if ("-streams" in self.__params) else "copy", ) - output_parameters["a_bitrate"] = bitrate # temporary handler + if output_parameters["-acodec"] != "copy": + output_parameters["a_bitrate"] = bitrate # temporary handler else: logger.info( "No valid audio source available in the input video. Disabling audio while generating streams." @@ -602,7 +608,7 @@ def __PreProcess(self, channels=0, rgb=False): ), "[StreamGear:ERROR] :: `{}` stream cannot be initiated properly!".format( self.__format.upper() ) - # Finally start FFmpef pipline and process everything + # Finally start FFmpeg pipeline and process everything self.__Build_n_Execute(process_params[0], process_params[1]) def __handle_streams(self, input_params, output_params): @@ -652,22 +658,26 @@ def __handle_streams(self, input_params, output_params): if "-s:v:0" in self.__params: # prevent duplicates del self.__params["-s:v:0"] - output_params["-s:v:0"] = "{}x{}".format(self.__inputwidth, self.__inputheight) + if output_params["-vcodec"] != "copy": + output_params["-s:v:0"] = "{}x{}".format( + self.__inputwidth, self.__inputheight + ) # assign default output video-bitrate if "-b:v:0" in self.__params: # prevent duplicates del self.__params["-b:v:0"] - output_params["-b:v:0"] = ( - str( - get_video_bitrate( - int(self.__inputwidth), - int(self.__inputheight), - self.__sourceframerate, - bpp, + if output_params["-vcodec"] != "copy": + output_params["-b:v:0"] = ( + str( + get_video_bitrate( + int(self.__inputwidth), + int(self.__inputheight), + self.__sourceframerate, + bpp, + ) ) + + "k" ) - + "k" - ) # assign default output audio-bitrate if "-b:a:0" in self.__params: From 2af68cfceb403f0f71c9e49c9d14057069956188 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Sun, 9 Jun 2024 13:05:21 +0530 Subject: [PATCH 04/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Core:=20Refactored?= =?UTF-8?q?=20colorspace=20handling=20in=20videocapture=20gears.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ⚑️ Instead of raising an exception, log a warning message and discard the invalid colorspace value. - 🎨 Consolidate the colorspace logging statement to a single line using a ternary operation. CamGear API - πŸ”₯ Remove the check for GStreamer support, as it is not being used currently (marked as a TODO). - πŸ”Š Improve the readability of the livestream warning log. Maintenance - 🎨 Applied short-circuiting to simplify code across various APIs. - 🎨 Remove unnecessary parentheses and unnecessary type checks. - πŸ”§ Removed unused imports --- vidgear/gears/camgear.py | 70 ++++++++++++++----------------------- vidgear/gears/helper.py | 27 ++++++-------- vidgear/gears/pigear.py | 22 +++--------- vidgear/gears/screengear.py | 36 +++++++------------ vidgear/gears/stabilizer.py | 25 ++++++------- vidgear/gears/streamgear.py | 1 - vidgear/gears/writegear.py | 1 - 7 files changed, 68 insertions(+), 114 deletions(-) diff --git a/vidgear/gears/camgear.py b/vidgear/gears/camgear.py index 87d459307..8c2faec35 100644 --- a/vidgear/gears/camgear.py +++ b/vidgear/gears/camgear.py @@ -243,11 +243,11 @@ def __init__( # check if Stream-Mode is ON (True) if stream_mode: - # check GStreamer backend support - gst_support = check_gstreamer_support(logging=logging) + # TODO: check GStreamer backend support + # gst_support = check_gstreamer_support(logging=self.__logging) # handle special Stream Mode parameters stream_resolution = get_supported_resolution( - options.pop("STREAM_RESOLUTION", "best"), logging=logging + options.pop("STREAM_RESOLUTION", "best"), logging=self.__logging ) # handle Stream-Mode if not (yt_dlp is None): @@ -266,17 +266,16 @@ def __init__( ) # initialize YT_backend ytbackend = YT_backend( - source_url=source, logging=logging, **yt_stream_params + source_url=source, logging=self.__logging, **yt_stream_params ) if ytbackend: # save video metadata self.ytv_metadata = ytbackend.meta_data # handle live-streams - if ytbackend.is_livestream: - # Throw warning for livestreams - logger.warning( - "Livestream URL detected. It is advised to use GStreamer backend(`cv2.CAP_GSTREAMER`) with it." - ) + # Throw warning for livestreams + ytbackend.is_livestream and logger.warning( + "Livestream URL detected. It is strongly recommended to use the GStreamer backend (`backend=cv2.CAP_GSTREAMER`) with these URLs." + ) # check whether stream-resolution was specified and available if not (stream_resolution in ytbackend.streams.keys()): logger.warning( @@ -343,10 +342,9 @@ def __init__( "Threaded Queue Mode is disabled for the current video source!" ) - if self.__thread_timeout: - logger.debug( - "Setting Video-Thread Timeout to {}s.".format(self.__thread_timeout) - ) + self.__thread_timeout and logger.info( + "Setting Video-Thread Timeout to {}s.".format(self.__thread_timeout) + ) # stream variable initialization self.stream = None @@ -359,7 +357,7 @@ def __init__( else: # Two parameters are available since OpenCV 4+ (master branch) self.stream = cv2.VideoCapture(source, backend) - logger.debug("Setting backend `{}` for this source.".format(backend)) + logger.info("Setting backend `{}` for this source.".format(backend)) else: # initialize the camera stream self.stream = cv2.VideoCapture(source) @@ -371,18 +369,16 @@ def __init__( options = {str(k).strip(): v for k, v in options.items()} for key, value in options.items(): property = capPropId(key) - if not (property is None): - self.stream.set(property, value) + not (property is None) and self.stream.set(property, value) # handle colorspace value if not (colorspace is None): self.color_space = capPropId(colorspace.strip()) - if self.__logging and not (self.color_space is None): - logger.debug( - "Enabling `{}` colorspace for this video stream!".format( - colorspace.strip() - ) + self.__logging and not (self.color_space is None) and logger.debug( + "Enabling `{}` colorspace for this video stream!".format( + colorspace.strip() ) + ) # initialize and assign frame-rate variable self.framerate = 0.0 @@ -391,8 +387,7 @@ def __init__( self.framerate = _fps # applying time delay to warm-up webcam only if specified - if time_delay and isinstance(time_delay, (int, float)): - time.sleep(time_delay) + time_delay and isinstance(time_delay, (int, float)) and time.sleep(time_delay) # frame variable initialization (grabbed, self.frame) = self.stream.read() @@ -403,9 +398,8 @@ def __init__( if not (self.color_space is None): self.frame = cv2.cvtColor(self.frame, self.color_space) - if self.__threaded_queue_mode: - # initialize and append to queue - self.__queue.put(self.frame) + # initialize and append to queue + self.__threaded_queue_mode and self.__queue.put(self.frame) else: raise RuntimeError( "[CamGear:ERROR] :: Source is invalid, CamGear failed to initialize stream on this source!" @@ -465,32 +459,22 @@ def __update(self): # apply colorspace to frames if valid 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: - raise ValueError( - "Global color_space parameter value `{}` is not a valid!".format( - self.color_space - ) - ) + color_frame = cv2.cvtColor(frame, self.color_space) except Exception as e: # Catch if any error occurred + color_frame = None self.color_space = None - if self.__logging: - logger.exception(str(e)) - logger.warning("Input colorspace is not a valid colorspace!") - if not (color_frame is None): - self.frame = color_frame - else: - self.frame = frame + self.__logging and logger.exception(str(e)) + logger.warning("Assigned colorspace value is invalid. Discarding!") + self.frame = color_frame if not (color_frame is None) else frame else: self.frame = frame # append to queue - if self.__threaded_queue_mode: - self.__queue.put(self.frame) + self.__threaded_queue_mode and self.__queue.put(self.frame) # signal queue we're done self.__threaded_queue_mode and self.__queue.put(None) diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 47069021d..2e0ff729f 100755 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -833,10 +833,7 @@ def delete_file_safe(file_path): """ try: dfile = Path(file_path) - if sys.version_info >= (3, 8, 0): - dfile.unlink(missing_ok=True) - else: - dfile.exists() and dfile.unlink() + dfile.unlink(missing_ok=True) except Exception as e: logger.exception(str(e)) @@ -912,9 +909,8 @@ def capPropId(property, logging=True): try: integer_value = getattr(cv2, property) except Exception as e: - if logging: - logger.exception(str(e)) - logger.critical("`{}` is not a valid OpenCV property!".format(property)) + logging and logger.exception(str(e)) + logger.critical("`{}` is not a valid OpenCV property!".format(property)) return None return integer_value @@ -1201,18 +1197,15 @@ def validate_ffmpeg(path, logging=False): version = check_output([path, "-version"]) firstline = version.split(b"\n")[0] version = firstline.split(b" ")[2].strip() - if logging: # log if test are passed - logger.debug("FFmpeg validity Test Passed!") - logger.debug( - "Found valid FFmpeg Version: `{}` installed on this system".format( - version - ) - ) + # log if test are passed + logging and logger.info("FFmpeg validity Test Passed!") + logging and logger.debug( + "Found valid FFmpeg Version: `{}` installed on this system".format(version) + ) except Exception as e: # log if test are failed - if logging: - logger.exception(str(e)) - logger.warning("FFmpeg validity Test Failed!") + logging and logger.exception(str(e)) + logger.error("FFmpeg validity Test Failed!") return False return True diff --git a/vidgear/gears/pigear.py b/vidgear/gears/pigear.py index 1a77b0230..e457feef7 100644 --- a/vidgear/gears/pigear.py +++ b/vidgear/gears/pigear.py @@ -566,26 +566,14 @@ def __update(self): # apply colorspace to frames color_frame = None try: - if isinstance(self.color_space, int): - color_frame = cv2.cvtColor(frame, self.color_space) - else: - self.__logging and logger.warning( - "Global color_space parameter value `{}` is not a valid!".format( - self.color_space - ) - ) - self.color_space = None + color_frame = cv2.cvtColor(frame, self.color_space) except Exception as e: # Catch if any error occurred + color_frame = None self.color_space = None - if self.__logging: - logger.exception(str(e)) - logger.warning("Input colorspace is not a valid colorspace!") - - if not (color_frame is None): - self.frame = color_frame - else: - self.frame = frame + self.__logging and logger.exception(str(e)) + logger.warning("Assigned colorspace value is invalid. Discarding!") + self.frame = color_frame if not (color_frame is None) else frame else: self.frame = frame diff --git a/vidgear/gears/screengear.py b/vidgear/gears/screengear.py index d25936311..45408dac1 100644 --- a/vidgear/gears/screengear.py +++ b/vidgear/gears/screengear.py @@ -113,7 +113,7 @@ def __init__( else ("left", "top", "width", "height") ) screen_dims = OrderedDict((k, screen_dims[k]) for k in key_order) - logging and logger.debug( + self.__logging and logger.debug( "Setting Capture-Area dimensions: {}".format(json.dumps(screen_dims)) ) else: @@ -126,7 +126,7 @@ def __init__( if self.__target_fps and isinstance(self.__target_fps, (int, float)): # set values self.__target_fps = int(self.__target_fps) - logging and logger.debug( + self.__logging and logger.debug( "Setting Target FPS: {}".format(self.__target_fps) ) else: @@ -194,7 +194,7 @@ def __init__( self.__monitor_instance = self.__capture_object.monitors[monitor] # log backend - self.__backend and logging and logger.debug( + self.__backend and self.__logging and logger.debug( "Setting Backend: {}".format(self.__backend.upper()) ) @@ -202,7 +202,7 @@ def __init__( # separately handle colorspace value to int conversion if colorspace: self.color_space = capPropId(colorspace.strip()) - logging and not (self.color_space is None) and logger.debug( + self.__logging and not (self.color_space is None) and logger.debug( "Enabling `{}` colorspace for this video stream!".format( colorspace.strip() ) @@ -258,7 +258,9 @@ def __init__( except Exception as e: if isinstance(e, ScreenShotError): # otherwise catch and log errors - logging and logger.exception(self.__capture_object.get_error_details()) + self.__logging and logger.exception( + self.__capture_object.get_error_details() + ) raise ValueError( "[ScreenGear:ERROR] :: ScreenShotError caught, Wrong dimensions passed to python-mss, Kindly Refer Docs!" ) @@ -338,25 +340,14 @@ def __update(self): # apply colorspace to frames color_frame = None try: - if isinstance(self.color_space, int): - color_frame = cv2.cvtColor(frame, self.color_space) - else: - self.__logging and logger.warning( - "Global color_space parameter value `{}` is not a valid!".format( - self.color_space - ) - ) - self.color_space = None + color_frame = cv2.cvtColor(frame, self.color_space) except Exception as e: # Catch if any error occurred + color_frame = None self.color_space = None - if self.__logging: - logger.exception(str(e)) - logger.warning("Input colorspace is not a valid colorspace!") - if not (color_frame is None): - self.frame = color_frame - else: - self.frame = frame + self.__logging and logger.exception(str(e)) + logger.warning("Assigned colorspace value is invalid. Discarding!") + self.frame = color_frame if not (color_frame is None) else frame else: self.frame = frame @@ -390,5 +381,4 @@ def stop(self): self.__terminate.set() # wait until stream resources are released (producer thread might be still grabbing frame) - if self.__thread is not None: - self.__thread.join() + not (self.__thread is None) and self.__thread.join() diff --git a/vidgear/gears/stabilizer.py b/vidgear/gears/stabilizer.py index aefd9b4ea..ffd17a04c 100644 --- a/vidgear/gears/stabilizer.py +++ b/vidgear/gears/stabilizer.py @@ -103,13 +103,15 @@ def __init__( 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: - logger.debug("Setting Cropping margin {} pixels".format(border_size)) + self.__logging and 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: - logger.debug("Setting Border size {} pixels".format(border_size)) + self.__logging and border_size and logger.debug( + "Setting Border size {} pixels".format(border_size) + ) # define valid border modes border_modes = { @@ -124,19 +126,18 @@ def __init__( if not crop_n_zoom: # initialize global border mode variable self.__border_mode = border_modes[border_type] - if self.__logging and border_type != "black": - logger.debug("Setting Border type: {}".format(border_type)) + self.__logging and border_type != "black" and logger.info( + "Setting Border type: {}".format(border_type) + ) else: # log and reset to default - if self.__logging and border_type != "black": - logger.debug( - "Setting border type is disabled if cropping is enabled!" - ) + self.__logging and border_type != "black" and logger.debug( + "Setting border type is disabled if cropping is enabled!" + ) self.__border_mode = border_modes["black"] else: # otherwise log if not - if logging: - logger.debug("Invalid input border type!") + self.__logging and logger.debug("Invalid input border type!") self.__border_mode = border_modes["black"] # reset to default mode # define OpenCV version diff --git a/vidgear/gears/streamgear.py b/vidgear/gears/streamgear.py index 25e496231..9d23b4894 100644 --- a/vidgear/gears/streamgear.py +++ b/vidgear/gears/streamgear.py @@ -27,7 +27,6 @@ import logging as log import subprocess as sp from tqdm import tqdm -from fractions import Fraction from collections import OrderedDict # import helper packages diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 2a8b7ee1d..d614b1385 100644 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -22,7 +22,6 @@ import os import cv2 import time -import signal import platform import pathlib import logging as log From fb76a2deafa7b5c09d1c39eca957da398ddbfb03 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Sun, 9 Jun 2024 13:10:31 +0530 Subject: [PATCH 05/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20WriterGear:=20Improv?= =?UTF-8?q?e=20error=20handling=20in=20`execute=5Fffmpeg=5Fcmd`=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🎨 Instead of raising a generic `ValueError` exception, the method now raises a `ValueError` with a more descriptive error message when encountering a `BrokenPipeError` or `IOError`. - πŸ₯… The error handling has been updated to follow the recommendations of PEP 409 - Suppressing exception context. This ensures that the original exception context is preserved when re-raising the `ValueError`. - ⚑️ If logging is enabled (`self.__logging` is True), the `ValueError` is raised with the suppressed context (`from None`), effectively discarding the original exception context. - ⚑️ If logging is disabled, the `ValueError` is raised with the original exception context (`from e`), where `e` is the original `OSError` or `IOError` exception. --- vidgear/gears/writegear.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index d614b1385..4a8e6e9d1 100644 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -673,12 +673,17 @@ def execute_ffmpeg_cmd(self, command=None): else: # In silent mode sp.run(cmd, stdin=sp.PIPE, stdout=sp.DEVNULL, stderr=sp.STDOUT) - except (OSError, IOError): - # raise error and log if something is wrong. - logger.error( - "BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!" - ) - raise ValueError # for testing purpose only + except (OSError, IOError) as e: + # re-raise error + # PEP 409 – Suppressing exception context recommendations + if self.__logging: + raise ValueError( + "BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!" + ) from None + else: + raise ValueError( + "BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!" + ) from e def __start_CVProcess(self): """ From 741222987ec7705ee7bbbde782c54bf453d0f905 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Tue, 18 Jun 2024 19:22:31 +0530 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=A9=B9=20NetGear:=20Updated=20param?= =?UTF-8?q?eters=20and=20documentation=20(Fixes=20#390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ₯… Added a warning message in the `NetGear` class to log potential issues when `flag=1` (NOBLOCK) is set, which may cause NetGear to not terminate gracefully. - πŸ”Š Included an informational log message indicating that the `track` option will be ignored when `copy=True` is defined. - πŸ“ Changed the default value of the `copy` option from `False` to `True` across various documentation files for NetGear. πŸ‘· CI: Updated unit tests to reflect the new default value for the `copy` option. ✏️ Docs: Fixed few typos. --- docs/gears/netgear/params.md | 12 +++++++----- docs/gears/netgear/usage.md | 12 ++++++------ docs/help/screengear_ex.md | 4 ++-- vidgear/gears/netgear.py | 6 ++++++ vidgear/gears/videogear.py | 6 ++++-- vidgear/gears/writegear.py | 3 +-- vidgear/tests/network_tests/test_netgear.py | 2 +- 7 files changed, 27 insertions(+), 18 deletions(-) diff --git a/docs/gears/netgear/params.md b/docs/gears/netgear/params.md index 949069b2f..0be8d45f2 100644 --- a/docs/gears/netgear/params.md +++ b/docs/gears/netgear/params.md @@ -173,11 +173,13 @@ This parameter provides the flexibility to alter various NetGear API's internal * **`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). + * **`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#zmq.Socket.recv). + + !!! warning "With flags=1 (i.e. `NOBLOCK`), NetGear raises `ZMQError` if no messages have arrived; otherwise, this waits until a message arrives." * **`copy`**(_boolean_): This PyZMQ attribute selects if message be received in a copying or non-copying manner. If `False` a object is returned, if `True` a string copy of the message is returned. - * **`track`**(_boolean_): This PyZMQ attribute check if the message is tracked for notification that ZMQ has finished with it. (_ignored if copy=True_). + * **`track`**(_boolean_): This PyZMQ attribute check if the message is tracked for notification that ZMQ has finished with it. _(ignored if `copy=True`)_. The desired attributes can be passed to NetGear API as follows: @@ -188,9 +190,9 @@ options = { "secure_mode": 2, "custom_cert_location": "/home/foo/foo1/foo2", "overwrite_cert": True, - "flag": 0, - "copy": False, - "track": False, + "flag": 0, + "copy": True, + "track": False } # assigning it NetGear(logging=True, **options) diff --git a/docs/gears/netgear/usage.md b/docs/gears/netgear/usage.md index 5f8614fdd..2fa4847c9 100644 --- a/docs/gears/netgear/usage.md +++ b/docs/gears/netgear/usage.md @@ -143,7 +143,7 @@ from vidgear.gears import NetGear import cv2 # define various tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Define Netgear Client at given IP address and define parameters # !!! change following IP address '192.168.x.xxx' with yours !!! @@ -198,7 +198,7 @@ from vidgear.gears import VideoGear from vidgear.gears import NetGear # define various tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Open live video stream on webcam at first index(i.e. 0) device stream = VideoGear(source=0).start() @@ -260,7 +260,7 @@ from vidgear.gears import NetGear import cv2 # define tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Define Netgear Client at given IP address and define parameters # !!! change following IP address '192.168.x.xxx' with yours !!! @@ -318,7 +318,7 @@ import cv2 stream = cv2.VideoCapture(0) # define tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Define Netgear Client at given IP address and define parameters # !!! change following IP address '192.168.x.xxx' with yours !!! @@ -377,7 +377,7 @@ from vidgear.gears import NetGear import cv2 # define various tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Define Netgear Client at given IP address and define parameters # !!! change following IP address '192.168.x.xxx' with yours !!! @@ -432,7 +432,7 @@ from vidgear.gears import ScreenGear from vidgear.gears import NetGear # define various tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Start capturing live Monitor screen frames with default settings stream = ScreenGear().start() diff --git a/docs/help/screengear_ex.md b/docs/help/screengear_ex.md index 6623e6dae..49a0374ed 100644 --- a/docs/help/screengear_ex.md +++ b/docs/help/screengear_ex.md @@ -44,7 +44,7 @@ from vidgear.gears import WriteGear import cv2 # define various tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Define Netgear Client at given IP address and define parameters # !!! change following IP address '192.168.x.xxx' with yours !!! @@ -106,7 +106,7 @@ options = {"top": 40, "left": 0, "width": 100, "height": 100} stream = ScreenGear(logging=True, **options).start() # define various netgear tweak flags -options = {"flag": 0, "copy": False, "track": False} +options = {"flag": 0, "copy": True, "track": False} # Define Netgear server at given IP address and define parameters # !!! change following IP address '192.168.x.xxx' with client's IP address !!! diff --git a/vidgear/gears/netgear.py b/vidgear/gears/netgear.py index 7bae7d644..7a95c5a4e 100644 --- a/vidgear/gears/netgear.py +++ b/vidgear/gears/netgear.py @@ -412,10 +412,16 @@ def __init__( # handle ZMQ flags elif key == "flag" and isinstance(value, int): self.__msg_flag = value + self.__msg_flag and logger.warning( + "The flag optional value is set to `1` (NOBLOCK) for this run. This might cause NetGear to not terminate gracefully." + ) elif key == "copy" and isinstance(value, bool): self.__msg_copy = value elif key == "track" and isinstance(value, bool): self.__msg_track = value + self.__msg_copy and self.__msg_track and logger.info( + "The `track` optional value will be ignored for this run because `copy=True` is also defined." + ) else: pass diff --git a/vidgear/gears/videogear.py b/vidgear/gears/videogear.py index c6a5c09e4..250ddd7ac 100644 --- a/vidgear/gears/videogear.py +++ b/vidgear/gears/videogear.py @@ -37,9 +37,11 @@ class VideoGear: """ VideoGear API provides a special internal wrapper around VidGear's exclusive Video Stabilizer class. - VideoGear also acts as a Common Video-Capture API that provides internal access for both CamGear and PiGear APIs and their parameters with an exclusive enablePiCamera boolean flag. + VideoGear also acts as a Common Video-Capture API that provides internal access for both CamGear and PiGear APIs and + their parameters with an exclusive enablePiCamera boolean flag. - VideoGear is ideal when you need to switch to different video sources without changing your code much. Also, it enables easy stabilization for various video-streams (real-time or not) + VideoGear is ideal when you need to switch to different video sources without changing your code much. Also, it enables + easy stabilization for various video-streams (real-time or not) with minimum effort and writing way fewer lines of code. """ diff --git a/vidgear/gears/writegear.py b/vidgear/gears/writegear.py index 4a8e6e9d1..086804ae0 100644 --- a/vidgear/gears/writegear.py +++ b/vidgear/gears/writegear.py @@ -675,7 +675,6 @@ def execute_ffmpeg_cmd(self, command=None): sp.run(cmd, stdin=sp.PIPE, stdout=sp.DEVNULL, stderr=sp.STDOUT) except (OSError, IOError) as e: # re-raise error - # PEP 409 – Suppressing exception context recommendations if self.__logging: raise ValueError( "BrokenPipeError caught, Wrong command passed to FFmpeg Pipe, Kindly Refer Docs!" @@ -757,7 +756,7 @@ def __start_CVProcess(self): # check if OpenCV VideoCapture is opened successfully assert ( self.__process.isOpened() - ), "[WriteGear:ERROR] :: Failed to intialize OpenCV Writer!" + ), "[WriteGear:ERROR] :: Failed to initialize OpenCV Writer!" def close(self): """ diff --git a/vidgear/tests/network_tests/test_netgear.py b/vidgear/tests/network_tests/test_netgear.py index 462056577..a6d341714 100644 --- a/vidgear/tests/network_tests/test_netgear.py +++ b/vidgear/tests/network_tests/test_netgear.py @@ -131,7 +131,7 @@ def test_patterns(pattern): # define parameters options = { "flag": 0, - "copy": False, + "copy": True, "track": False, "jpeg_compression": False, "subscriber_timeout": 5, From 645231c40a8d3f9cfe3d02e795b19b925fca1b8d Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Tue, 18 Jun 2024 19:50:25 +0530 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20StreamGear:=20Mov?= =?UTF-8?q?ed=20handle=20streaming=20`format`=20to=20beginning=20to=20fix?= =?UTF-8?q?=20=20'StreamGear'=20object=20has=20no=20attribute=20'=5FStream?= =?UTF-8?q?Gear=5F=5Fformat'=20bug.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vidgear/gears/streamgear.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vidgear/gears/streamgear.py b/vidgear/gears/streamgear.py index 9d23b4894..6e504566e 100644 --- a/vidgear/gears/streamgear.py +++ b/vidgear/gears/streamgear.py @@ -135,6 +135,34 @@ def __init__( "[StreamGear:ERROR] :: Failed to find FFmpeg assets on this system. Kindly compile/install FFmpeg or provide a valid custom FFmpeg binary path!" ) + # handle streaming format + supported_formats = ["dash", "hls"] # TODO will be extended in future + if format and isinstance(format, str): + _format = format.strip().lower() + if _format in supported_formats: + self.__format = _format + logger.info( + "StreamGear will generate asset files for {} streaming format.".format( + self.__format.upper() + ) + ) + elif difflib.get_close_matches(_format, supported_formats): + raise ValueError( + "[StreamGear:ERROR] :: Incorrect `format` parameter value! Did you mean `{}`?".format( + difflib.get_close_matches(_format, supported_formats)[0] + ) + ) + else: + raise ValueError( + "[StreamGear:ERROR] :: The `format` parameter value `{}` not valid/supported!".format( + format + ) + ) + else: + raise ValueError( + "[StreamGear:ERROR] :: The `format` parameter value is Missing or Invalid!" + ) + # handle Audio-Input audio = self.__params.pop("-audio", False) if audio and isinstance(audio, str): From e4142aceaf799cdfdc4f9c3ec76d476687a26395 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Tue, 18 Jun 2024 23:53:15 +0530 Subject: [PATCH 08/17] =?UTF-8?q?=E2=9A=97=EF=B8=8F=20CI:=20Testing=20simp?= =?UTF-8?q?lejpeg=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py index a3de70ba8..a7249570a 100644 --- a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py +++ b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py @@ -17,6 +17,7 @@ limitations under the License. =============================================== """ + # import the necessary packages import os @@ -47,6 +48,9 @@ def return_testvideo_path(): """ returns Test Video path """ + # TODO Remove this + import simplejpeg + path = "{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4".format( tempfile.gettempdir() ) From 626b64a27ea0b932ff3d3dbe56f4aefc1b4fea08 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 00:25:42 +0530 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20CI:=20Fixed=20`si?= =?UTF-8?q?mplejpeg`=20and=20`opencv`=20not=20compatible=20with=20`numpy?= =?UTF-8?q?=3D=3D2.x.x`=20versions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ“Œ Pinned `numpy<2.0.0` in all CI envs. - βͺ️ Reverted testing simplejpeg import --- .github/workflows/ci_linux.yml | 3 ++- appveyor.yml | 1 + azure-pipelines.yml | 3 ++- vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py | 3 --- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 0e6d0c0c1..7fc6d2de1 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -59,7 +59,8 @@ jobs: chmod +x scripts/bash/install_opencv.sh - name: install pip_dependencies run: | - pip install -U pip wheel numpy + pip install -U pip wheel + pip install numpy<2.0.0 pip install -U .[asyncio] pip uninstall opencv-python -y pip install -U flake8 six codecov httpx pytest pytest-asyncio pytest-cov yt_dlp mpegdash paramiko m3u8 async-asgi-testclient diff --git a/appveyor.yml b/appveyor.yml index 4b5e528c2..e86e6e8ce 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -60,6 +60,7 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -m pip install --upgrade pip wheel" + - "python -m pip install --upgrade numpy<2.0.0" - "python -m pip install .[asyncio] six codecov httpx pytest-cov pytest-asyncio yt_dlp aiortc paramiko m3u8 async-asgi-testclient" - "python -m pip install --upgrade deffcode" - "python -m pip install cryptography==38.0.4" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8d3de4806..73e1ff9d5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -66,7 +66,8 @@ steps: displayName: "Prepare dataset" - script: | - python -m pip install -U pip wheel numpy + python -m pip install -U pip wheel + python -m pip install numpy<2.0.0 python -m pip install -U .[asyncio] python -m pip install -U six codecov httpx pytest pytest-asyncio pytest-cov yt_dlp mpegdash paramiko m3u8 async-asgi-testclient python -m pip install -U deffcode diff --git a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py index a7249570a..7f4dad116 100644 --- a/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py +++ b/vidgear/tests/streamer_tests/asyncio_tests/test_webgear.py @@ -48,9 +48,6 @@ def return_testvideo_path(): """ returns Test Video path """ - # TODO Remove this - import simplejpeg - path = "{}/Downloads/Test_videos/BigBuckBunny_4sec.mp4".format( tempfile.gettempdir() ) From 17429771199789f7ae5c4a2f1db9e366ee9ccbad Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 17:22:34 +0530 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20CI:=20Fixed=20wor?= =?UTF-8?q?d=20splitting=20when=20installing=20numpy.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci_linux.yml | 2 +- appveyor.yml | 2 +- azure-pipelines.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 7fc6d2de1..57b3c6d74 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -60,7 +60,7 @@ jobs: - name: install pip_dependencies run: | pip install -U pip wheel - pip install numpy<2.0.0 + pip install "numpy<2.0.0" pip install -U .[asyncio] pip uninstall opencv-python -y pip install -U flake8 six codecov httpx pytest pytest-asyncio pytest-cov yt_dlp mpegdash paramiko m3u8 async-asgi-testclient diff --git a/appveyor.yml b/appveyor.yml index e86e6e8ce..5ddc91e36 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -60,7 +60,7 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -m pip install --upgrade pip wheel" - - "python -m pip install --upgrade numpy<2.0.0" + - "python -m pip install 'numpy<2.0.0'" - "python -m pip install .[asyncio] six codecov httpx pytest-cov pytest-asyncio yt_dlp aiortc paramiko m3u8 async-asgi-testclient" - "python -m pip install --upgrade deffcode" - "python -m pip install cryptography==38.0.4" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 73e1ff9d5..d2bece7f4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -67,7 +67,7 @@ steps: - script: | python -m pip install -U pip wheel - python -m pip install numpy<2.0.0 + python -m pip install "numpy<2.0.0" python -m pip install -U .[asyncio] python -m pip install -U six codecov httpx pytest pytest-asyncio pytest-cov yt_dlp mpegdash paramiko m3u8 async-asgi-testclient python -m pip install -U deffcode From 3e1e10aff082f41dbcefd0b922609cd433e01c44 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 19:37:41 +0530 Subject: [PATCH 11/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Core:=20Improved=20e?= =?UTF-8?q?xception=20handling=20for=20module=20imports.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ⚑️ Updated `import_dependency_safe` in `helper.py`: - πŸ₯… Added specific handling for `ModuleNotFoundError`. - πŸ§‘β€πŸ’» Included original exception in `ImportError` for better error tracing. - πŸ”Š Enhanced logging to include exception traceback when error is set to "log". - ⚑️ Enhanced `import_core_dependency` in `__init__.py`: - πŸ₯… Added specific handling for `ModuleNotFoundError`. - πŸ§‘β€πŸ’» Included original exception in `ImportError` for better error tracing. --- vidgear/gears/__init__.py | 13 ++++++++----- vidgear/gears/helper.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/vidgear/gears/__init__.py b/vidgear/gears/__init__.py index ed17cbb7e..d4f4d0177 100644 --- a/vidgear/gears/__init__.py +++ b/vidgear/gears/__init__.py @@ -85,11 +85,14 @@ def import_core_dependency( # try importing dependency try: module = importlib.import_module(name) - if sub_class: - module = getattr(module, sub_class) - except ImportError: - # raise - raise ImportError(msg) from None + module = getattr(module, sub_class) if sub_class else module + except Exception as e: + if isinstance(e, ModuleNotFoundError): + # raise message + raise ModuleNotFoundError(msg) from None + else: + # raise error+message + raise ImportError(msg) from e # check if minimum required version if not (version) is None: diff --git a/vidgear/gears/helper.py b/vidgear/gears/helper.py index 2e0ff729f..a7ab5ba85 100755 --- a/vidgear/gears/helper.py +++ b/vidgear/gears/helper.py @@ -247,14 +247,17 @@ def import_dependency_safe( # try importing dependency try: module = importlib.import_module(name) - if sub_class: - module = getattr(module, sub_class) - except Exception: - # handle errors. + module = getattr(module, sub_class) if sub_class else module + except Exception as e: if error == "raise": - raise ImportError(msg) from None + if isinstance(e, ModuleNotFoundError): + # raise message + raise ModuleNotFoundError(msg) from None + else: + # raise error+message + raise ImportError(msg) from e elif error == "log": - logger.error(msg) + logger.error(msg, exc_info=sys.exc_info()) return None else: return None From 8b765b91892a509c5ea7f52757da6ba3c2f71138 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 20:15:52 +0530 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=91=B7=20CI:=20Added=20tests=20for?= =?UTF-8?q?=20`import=5Fdependency=5Fsafe`=20function=20.=20-=20=E2=9A=A1?= =?UTF-8?q?=EF=B8=8F=20Included=20various=20test=20cases=20to=20validate?= =?UTF-8?q?=20different=20scenarios=20and=20error=20handling=20in=20`impor?= =?UTF-8?q?t=5Fdependency=5Fsafe`.=20-=20=E2=98=94=20Ensured=20coverage=20?= =?UTF-8?q?for=20`raise`,=20`log`,=20`silent`,=20and=20unknown=20error=20t?= =?UTF-8?q?ypes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vidgear/tests/test_helper.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/vidgear/tests/test_helper.py b/vidgear/tests/test_helper.py index 1862ea5bd..385fae9cd 100644 --- a/vidgear/tests/test_helper.py +++ b/vidgear/tests/test_helper.py @@ -48,6 +48,7 @@ validate_ffmpeg, get_video_bitrate, get_valid_ffmpeg_path, + import_dependency_safe, download_ffmpeg_binaries, check_gstreamer_support, generate_auth_certificates, @@ -130,6 +131,35 @@ def getframe(): return np.zeros([500, 800, 3], dtype=np.uint8) +test_data = [ + ("XYZ", "raise", "Invalid", "0.0.0", "My message"), + ("cv2", "raise", "opencv-python", "6.0.0", ""), + ("cv2", "log", "opencv-python", "3.0.0", ""), + ("cv2", "silent", "opencv-python", "6.0.0", ""), + ("cv2", "unknown", "opencv-python", "6.0.0", ""), + ("from cv2 import XYZ", "raise", "opencv-python", "3.0.0", ""), + ("from xyz import XYZ", "raise", "Invalid", "0.0.0", "My message"), + ("from cv2 import XYZ", "log", "opencv-python", "3.0.0", ""), + ("from xyz import XYZ", "log", "Invalid", "0.0.0", "My message"), +] + + +@pytest.mark.parametrize( + "name, error, pkg_name, min_version, custom_message", test_data +) +def test_import_dependency_safe(name, error, pkg_name, min_version, custom_message): + """ + Testing import_dependency_safe helper function. + """ + try: + import_dependency_safe(name, error, pkg_name, min_version, custom_message) + except Exception as e: + if ( + error == "raise" and not isinstance(e, (ModuleNotFoundError, ImportError)) + ) or (error == "unknown" and not isinstance(e, AssertionError)): + pytest.fail(str(e)) + + test_data = [ {"-thread_queue_size": "512", "-f": "alsa", "-clones": 24}, { From cb769d3eaabff75606a595b9c8831011ec94f99e Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 20:19:29 +0530 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Fixed=20invalid=20es?= =?UTF-8?q?cape=20sequence=20in=20testcase=20string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vidgear/tests/streamer_tests/test_streamgear_modes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vidgear/tests/streamer_tests/test_streamgear_modes.py b/vidgear/tests/streamer_tests/test_streamgear_modes.py index b979cc600..6c983d6db 100644 --- a/vidgear/tests/streamer_tests/test_streamgear_modes.py +++ b/vidgear/tests/streamer_tests/test_streamgear_modes.py @@ -466,7 +466,7 @@ def test_input_framerate_rtf(format): { "-clear_prev_assets": True, "-seg_duration": -346.67, - "-audio": "inv/\lid", + "-audio": "invAlid", "-bpp": "unknown", "-gop": "unknown", "-s:v:0": "unknown", From 9f4a1e3c7506e9ea1637e9954bda61dc54c28d5d Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 21:12:05 +0530 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Fixed=20invalid=20es?= =?UTF-8?q?cape=20sequence=20in=20testcase=20string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appveyor.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5ddc91e36..19ab6cd5b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,13 +58,13 @@ matrix: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - "python --version" - - "python -m pip install --upgrade pip wheel" - - "python -m pip install 'numpy<2.0.0'" - - "python -m pip install .[asyncio] six codecov httpx pytest-cov pytest-asyncio yt_dlp aiortc paramiko m3u8 async-asgi-testclient" - - "python -m pip install --upgrade deffcode" - - "python -m pip install cryptography==38.0.4" - - "python -m pip install https://github.com/abhiTronix/python-mpegdash/releases/download/0.3.0-dev2/mpegdash-0.3.0.dev2-py3-none-any.whl" + - py --version + - py -m pip install --upgrade pip wheel + - py -m pip install "numpy<2.0.0" + - py -m pip install .[asyncio] six codecov httpx pytest-cov pytest-asyncio yt_dlp aiortc paramiko m3u8 async-asgi-testclient + - py -m pip install --upgrade deffcode + - py -m pip install cryptography==38.0.4 + - py -m pip install https://github.com/abhiTronix/python-mpegdash/releases/download/0.3.0-dev2/mpegdash-0.3.0.dev2-py3-none-any.whl - cmd: chmod +x scripts/bash/prepare_dataset.sh - cmd: bash scripts/bash/prepare_dataset.sh From f26396025c424c424aa792326d6a5fdc890d8284 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 21:22:22 +0530 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Fixed=20missing=20`p?= =?UTF-8?q?ytest`=20dependency=20in=20appveyor.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 19ab6cd5b..c4aeee093 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -61,7 +61,8 @@ install: - py --version - py -m pip install --upgrade pip wheel - py -m pip install "numpy<2.0.0" - - py -m pip install .[asyncio] six codecov httpx pytest-cov pytest-asyncio yt_dlp aiortc paramiko m3u8 async-asgi-testclient + - py -m pip install .[asyncio] six codecov httpx yt_dlp aiortc paramiko + - py -m pip install pytest pytest-cov pytest-asyncio m3u8 async-asgi-testclient - py -m pip install --upgrade deffcode - py -m pip install cryptography==38.0.4 - py -m pip install https://github.com/abhiTronix/python-mpegdash/releases/download/0.3.0-dev2/mpegdash-0.3.0.dev2-py3-none-any.whl From 1a77e5b67ef1eeeade9ba093409bc2b99aa76557 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 21:27:29 +0530 Subject: [PATCH 16/17] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Removed=20pinned=20`?= =?UTF-8?q?cryptography=3D=3D38.0.4`=20dependency.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci_linux.yml | 1 - appveyor.yml | 5 ++--- azure-pipelines.yml | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 57b3c6d74..55ee9ce2c 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -65,7 +65,6 @@ jobs: pip uninstall opencv-python -y pip install -U flake8 six codecov httpx pytest pytest-asyncio pytest-cov yt_dlp mpegdash paramiko m3u8 async-asgi-testclient pip install -U deffcode - pip install cryptography==38.0.4 if: success() - name: run prepare_dataset_script run: bash scripts/bash/prepare_dataset.sh diff --git a/appveyor.yml b/appveyor.yml index c4aeee093..841799d8f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -64,13 +64,12 @@ install: - py -m pip install .[asyncio] six codecov httpx yt_dlp aiortc paramiko - py -m pip install pytest pytest-cov pytest-asyncio m3u8 async-asgi-testclient - py -m pip install --upgrade deffcode - - py -m pip install cryptography==38.0.4 - py -m pip install https://github.com/abhiTronix/python-mpegdash/releases/download/0.3.0-dev2/mpegdash-0.3.0.dev2-py3-none-any.whl - cmd: chmod +x scripts/bash/prepare_dataset.sh - cmd: bash scripts/bash/prepare_dataset.sh test_script: - - cmd: python -m pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/ + - cmd: py -m pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/ after_test: - - cmd: python -m codecov + - cmd: py -m codecov diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d2bece7f4..7c1d2013d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -71,7 +71,6 @@ steps: python -m pip install -U .[asyncio] python -m pip install -U six codecov httpx pytest pytest-asyncio pytest-cov yt_dlp mpegdash paramiko m3u8 async-asgi-testclient python -m pip install -U deffcode - python -m pip install cryptography==38.0.4 displayName: "Install pip dependencies" - script: | From 0d9071e9168033d4bbd8c53ba0f3acb312afc288 Mon Sep 17 00:00:00 2001 From: abhiTronix Date: Wed, 19 Jun 2024 21:37:42 +0530 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Fixed=20python=20env?= =?UTF-8?q?ironment=20bugs=20in=20`appveyor.yml`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appveyor.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 841799d8f..a8edd1388 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,18 +58,18 @@ matrix: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - py --version - - py -m pip install --upgrade pip wheel - - py -m pip install "numpy<2.0.0" - - py -m pip install .[asyncio] six codecov httpx yt_dlp aiortc paramiko - - py -m pip install pytest pytest-cov pytest-asyncio m3u8 async-asgi-testclient - - py -m pip install --upgrade deffcode - - py -m pip install https://github.com/abhiTronix/python-mpegdash/releases/download/0.3.0-dev2/mpegdash-0.3.0.dev2-py3-none-any.whl + - "python --version" + - "python -m pip install --upgrade pip wheel" + - cmd: python -m pip install "numpy<2.0.0" + - "python -m pip install .[asyncio] six codecov httpx yt_dlp aiortc paramiko" + - "python -m pip install pytest pytest-cov pytest-asyncio m3u8 async-asgi-testclient" + - "python -m pip install --upgrade deffcode" + - "python -m pip install https://github.com/abhiTronix/python-mpegdash/releases/download/0.3.0-dev2/mpegdash-0.3.0.dev2-py3-none-any.whl" - cmd: chmod +x scripts/bash/prepare_dataset.sh - cmd: bash scripts/bash/prepare_dataset.sh test_script: - - cmd: py -m pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/ + - cmd: python -m pytest --verbose --capture=no --cov-report term-missing --cov=vidgear vidgear/tests/ after_test: - - cmd: py -m codecov + - cmd: python -m codecov