Skip to content

Commit

Permalink
⚡️Major enhancements and bugfixes [#378]
Browse files Browse the repository at this point in the history
**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.
- 🔊 Updated logging.

**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.
  - ⚡️Note: `-disable_ffmpeg_window` optional Boolean flag is only available on Windows OS with logging disabled(`logging=False`) in compression mode.
  - 🧑‍💻 This enhancement enhances the process management capabilities of the module.
  - ♿️ Use Case: This 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.
- 🥅 Disabled this patch for logging mode.
- 🔥 Removed redundant code.
- 🔊 Updated logging.

**ScreenGear:** 
- 🐛 Fixed swapped region dimensions bug with dxcam backend.
  - 🩹 Fixed "mss" backend disabled when `monitor` parameter is not defined.

**Stabilizer Class:**
- 🔥 Removed redundant code

**CI:**
- 👷 Added kill argument to close() method in various NetGear tests.
- 👷 Updated tests for subscriber_timeout optional Integer parameter in NetGear.
- 👷 Updated tests for disable_ffmpeg_window optional Boolean parameter in WriteGear.
- 💚 Fixed NetGear tests bugs.

**Docs:**
- 📝 Updated information related to Supported Dimensional Attributes in ScreenGear docs.
  - 🍱 Added new asset `screengear_region.png`.
- 📄 Fixed missing `compression_mode` flags in WriteGear API docs.
- 📝 Added doc for subscriber_timeout optional Integer parameter in NetGear.
- 📝 Added doc for disable_ffmpeg_window optional Boolean parameter in WriteGear.
- ✏️ Fixed typos and context.
  • Loading branch information
abhiTronix committed Sep 6, 2023
2 parents 1628685 + c488c6c commit 3c0dd5d
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 83 deletions.
4 changes: 3 additions & 1 deletion docs/gears/netgear/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
23 changes: 11 additions & 12 deletions docs/gears/screengear/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,33 +131,32 @@ 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:

<h2 align="center">
<img src="../../../assets/images/screengear_region.png" loading="lazy" alt="ScreenGear ROI region" width="80%"/>
</h2>

* **`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

**Default Value:** Its default value is `{}`

**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)
```

&nbsp;
Expand Down
14 changes: 14 additions & 0 deletions docs/gears/screengear/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<h2 align="center">
<img src="../../../assets/images/screengear_region.png" loading="lazy" alt="ScreenGear ROI region" width="80%"/>
</h2>

* **`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:


Expand Down
11 changes: 11 additions & 0 deletions docs/gears/writegear/compression/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,17 @@ This parameter allows us to exploit almost all FFmpeg supported parameters effor
"-ffpreheaders": ["-re"], # executes as `ffmpeg -re <rest of command>`
}
```

* **`-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:

Expand Down
6 changes: 3 additions & 3 deletions docs/gears/writegear/non_compression/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)."
Expand All @@ -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)."
Expand All @@ -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)
```

&nbsp;
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions scripts/bash/prepare_dataset.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -48,28 +48,28 @@ 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

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

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
Expand Down
72 changes: 54 additions & 18 deletions vidgear/gears/netgear.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def __init__(
logging=False,
**options
):

"""
This constructor method initializes the object state and attributes of the NetGear class.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -384,13 +386,23 @@ 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:
self.__request_timeout = value * 1000 # covert to milliseconds
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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -721,7 +742,6 @@ def __init__(
logger.debug("Receive Mode is now activated.")

else:

# otherwise default to `Send Mode`

# define connection address
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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"]:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions vidgear/gears/screengear.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 3c0dd5d

Please sign in to comment.