Skip to content

Commit

Permalink
🐛 WriteGear: Fixed bug in disable_force_termination logic which acc…
Browse files Browse the repository at this point in the history
…identally disables force termination. (Fixes #259)

- ⚗️ Introduced experimental python short-circuiting for handling logging.
- 🔊 Enabled logging for `check_WriteAccess` method in WriteGear, StreamGear and NetGear.
- 💡 Improved code comments.

Helper:
- 🐛 Fixed check_WriteAccess failing to recognize correct permission for writing the output file on windows platform. (Fixes #260)
- ⚡️ Implemented separate logic for Windows and *nix platforms.
- 🥅 Improved warnings and error handling in `check_WriteAccess`.
- 💥 `check_WriteAccess` will now throw error if writing directory not present.
- 🎨 Added logging parameter to `check_WriteAccess`.
- ➕ Added `stat` import.

Docs:
- 📝 Fixed bugs in WriteGear's Compression Mode with Live Audio Input example.
- 📝 Added warnings for properly formatting output_params when assigning external audio-source in WriteGear.

CI:
- 👷 Replaced `env` with `export` in ci_linux.yml.
- 👷 Replaced `bubkoo/needs-more-info@v1` with `wow-actions/needs-more-info@v1`.
  • Loading branch information
abhiTronix committed Oct 25, 2021
1 parent 4491df6 commit 5feafb7
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 128 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/ci_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ jobs:
- name: pytest with_ENV
run: |
mkdir -p $HOME/logs
export VIDGEAR_LOGFILE="$HOME/logs"
timeout 1200 pytest --verbose --cov=vidgear --cov-report=xml --cov-report term-missing vidgear/tests/ || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; else echo "EXIT_CODE=$code" >>$GITHUB_ENV; fi
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
env:
VIDGEAR_LOGFILE: $HOME/logs
if: success() && matrix.python-version == 3.7
- name: pytest without_ENV
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/need_info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/needs-more-info@v1
- uses: wow-actions/needs-more-info@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG_FILE: .github/needs-more-info.yml
22 changes: 10 additions & 12 deletions docs/gears/writegear/compression/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,9 +474,9 @@ In this example code, we will merging the audio from a Audio Device _(for e.g. W
```python
# assign appropriate input audio-source
output_params = {
"-f": "dshow", # !!! warning: always keep this line above "-i" parameter !!!
"-i":"audio=Microphone (USB2.0 Camera)",
"-thread_queue_size": "512",
"-f": "dshow",
"-ac": "2",
"-acodec": "aac",
"-ar": "44100",
Expand Down Expand Up @@ -521,12 +521,11 @@ In this example code, we will merging the audio from a Audio Device _(for e.g. W
```python
# assign appropriate input audio-source
output_params = {
"-i": "hw:1",
"-thread_queue_size": "512",
"-f": "alsa",
"-ac": "2",
"-acodec": "aac",
"-ar": "44100",
"-ar": "48000",
"-f": "alsa", # !!! warning: always keep this line above "-i" parameter !!!
"-i": "hw:1",
}
```

Expand Down Expand Up @@ -564,12 +563,11 @@ In this example code, we will merging the audio from a Audio Device _(for e.g. W
```python
# assign appropriate input audio-source
output_params = {
"-audio_device_index": "0",
"-thread_queue_size": "512",
"-f": "avfoundation",
"-ac": "2",
"-acodec": "aac",
"-ar": "44100",
"-ar": "48000",
"-f": "avfoundation", # !!! warning: always keep this line above "-audio_device_index" parameter !!!
"-audio_device_index": "0",
}
```

Expand All @@ -592,10 +590,10 @@ stream = VideoGear(source=0).start()
# change with your webcam soundcard, plus add additional required FFmpeg parameters for your writer
output_params = {
"-thread_queue_size": "512",
"-f": "alsa",
"-ac": "1",
"-ac": "2",
"-ar": "48000",
"-i": "plughw:CARD=CAMERA,DEV=0",
"-f": "alsa", # !!! warning: always keep this line above "-i" parameter !!!
"-i": "hw:1",
}

# Define writer with defined parameters and suitable output filename for e.g. `Output.mp4
Expand Down
127 changes: 66 additions & 61 deletions vidgear/gears/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import cv2
import types
import errno
import stat
import shutil
import importlib
import requests
Expand Down Expand Up @@ -295,7 +296,7 @@ def check_open_port(address, port=22):
return False


def check_WriteAccess(path, is_windows=False):
def check_WriteAccess(path, is_windows=False, logging=False):
"""
## check_WriteAccess
Expand All @@ -304,26 +305,46 @@ def check_WriteAccess(path, is_windows=False):
Parameters:
path (string): absolute path of directory
is_windows (boolean): is running on Windows OS?
logging (bool): enables logging for its operations
**Returns:** A boolean value, confirming whether Write-Access available, or not?.
"""
write_accessible = False
if not is_windows and not os.access(
path, os.W_OK, effective_ids=os.access in os.supports_effective_ids
):
dirpath = Path(path)
if not (dirpath.exists() and dirpath.is_dir()):
logger.warning("Specified path `{}` doesn't exists.".format(path))
return False
temp_fname = os.path.join(path, "temp.tmp")
try:
mkdir_safe(path)
fd = os.open(temp_fname, os.O_WRONLY | os.O_CREAT)
os.close(fd)
write_accessible = True
except Exception:
else:
path = dirpath.resolve()
# check path on *nix systems
if not is_windows:
uid = os.geteuid()
gid = os.getegid()
s = os.stat(path)
mode = s[stat.ST_MODE]
return (
((s[stat.ST_UID] == uid) and (mode & stat.S_IWUSR))
or ((s[stat.ST_GID] == gid) and (mode & stat.S_IWGRP))
or (mode & stat.S_IWOTH)
)
# otherwise, check path on windows
else:
write_accessible = False
finally:
if os.path.exists(temp_fname):
temp_fname = os.path.join(path, "temp.tmp")
try:
fd = os.open(temp_fname, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
os.close(fd)
write_accessible = True
except Exception as e:
if isinstance(e, PermissionError):
logger.error(
"You don't have adequate access rights to use `{}` directory!".format(
path
)
)
logging and logger.exception(str(e))
finally:
delete_file_safe(temp_fname)
return write_accessible
return write_accessible


def check_gstreamer_support(logging=False):
Expand All @@ -345,8 +366,7 @@ def check_gstreamer_support(logging=False):
]
if gst and "YES" in gst[0]:
version = re.search(r"(\d+\.)?(\d+\.)?(\*|\d+)", gst[0])
if logging:
logger.debug("Found GStreamer version:{}".format(version[0]))
logging and logger.debug("Found GStreamer version:{}".format(version[0]))
return version[0] >= "1.0.0"
else:
logger.warning("GStreamer not found!")
Expand Down Expand Up @@ -380,10 +400,9 @@ def get_supported_resolution(value, logging=False):
if isinstance(value, str):
if value.strip().lower() in supported_stream_qualities:
stream_resolution = value.strip().lower()
if logging:
logger.debug(
"Selecting `{}` resolution for streams.".format(stream_resolution)
)
logging and logger.debug(
"Selecting `{}` resolution for streams.".format(stream_resolution)
)
else:
logger.warning(
"Specified stream-resolution `{}` is not supported. Reverting to `best`!".format(
Expand Down Expand Up @@ -502,14 +521,13 @@ def is_valid_url(path, url=None, logging=False):
supported_protocols += ["rtsp"] if "rtsp" in get_supported_demuxers(path) else []
# Test and return result whether scheme is supported
if extracted_scheme_url and extracted_scheme_url in supported_protocols:
if logging:
logger.debug(
"URL scheme `{}` is supported by FFmpeg.".format(extracted_scheme_url)
)
logging and logger.debug(
"URL scheme `{}` is supported by FFmpeg.".format(extracted_scheme_url)
)
return True
else:
logger.warning(
"URL scheme `{}` is not supported by FFmpeg!".format(extracted_scheme_url)
"URL scheme `{}` isn't supported by FFmpeg!".format(extracted_scheme_url)
)
return False

Expand All @@ -536,8 +554,7 @@ def validate_video(path, video_path=None, logging=False):
)
# clean and search
stripped_data = [x.decode("utf-8").strip() for x in metadata.split(b"\n")]
if logging:
logger.debug(stripped_data)
logging and logger.debug(stripped_data)
result = {}
for data in stripped_data:
output_a = re.findall(r"([1-9]\d+)x([1-9]\d+)", data)
Expand Down Expand Up @@ -573,8 +590,7 @@ def create_blank_frame(frame=None, text="", logging=False):
blank_frame = np.zeros(frame.shape, frame.dtype)
# setup text
if text and isinstance(text, str):
if logging:
logger.debug("Adding text: {}".format(text))
logging and logger.debug("Adding text: {}".format(text))
# setup font
font = cv2.FONT_HERSHEY_SCRIPT_COMPLEX
# get boundary of this text
Expand Down Expand Up @@ -701,10 +717,9 @@ def delete_file_safe(file_path):
if sys.version_info >= (3, 8, 0):
dfile.unlink(missing_ok=True)
else:
if dfile.exists():
dfile.unlink()
dfile.exists() and dfile.unlink()
except Exception as e:
logger.exception(e)
logger.exception(str(e))


def mkdir_safe(dir_path, logging=False):
Expand All @@ -720,13 +735,10 @@ def mkdir_safe(dir_path, logging=False):
"""
try:
os.makedirs(dir_path)
if logging:
logger.debug("Created directory at `{}`".format(dir_path))
except OSError as e:
if e.errno != errno.EEXIST:
logging and logger.debug("Created directory at `{}`".format(dir_path))
except (OSError, IOError) as e:
if e.errno != errno.EACCES and e.errno != errno.EEXIST:
raise
if logging:
logger.debug("Directory already exists at `{}`".format(dir_path))


def delete_ext_safe(dir_path, extensions=[], logging=False):
Expand Down Expand Up @@ -762,8 +774,7 @@ def delete_ext_safe(dir_path, extensions=[], logging=False):
]
for file in files_ext:
delete_file_safe(file)
if logging:
logger.debug("Deleted file: `{}`".format(file))
logging and logger.debug("Deleted file: `{}`".format(file))


def capPropId(property, logging=True):
Expand Down Expand Up @@ -930,10 +941,9 @@ def get_valid_ffmpeg_path(

ffmpeg_download_path = tempfile.gettempdir()

if logging:
logger.debug(
"FFmpeg Windows Download Path: {}".format(ffmpeg_download_path)
)
logging and logger.debug(
"FFmpeg Windows Download Path: {}".format(ffmpeg_download_path)
)

# download Binaries
os_bit = (
Expand Down Expand Up @@ -963,8 +973,9 @@ def get_valid_ffmpeg_path(
final_path = os.path.join(final_path, "ffmpeg.exe")
else:
# else return False
if logging:
logger.debug("No valid FFmpeg executables found at Custom FFmpeg path!")
logging and logger.debug(
"No valid FFmpeg executables found at Custom FFmpeg path!"
)
return False
else:
# otherwise perform test for Unix
Expand All @@ -978,17 +989,15 @@ def get_valid_ffmpeg_path(
final_path = os.path.join(custom_ffmpeg, "ffmpeg")
else:
# else return False
if logging:
logger.debug(
"No valid FFmpeg executables found at Custom FFmpeg path!"
)
logging and logger.debug(
"No valid FFmpeg executables found at Custom FFmpeg path!"
)
return False
else:
# otherwise assign ffmpeg binaries from system
final_path += "ffmpeg"

if logging:
logger.debug("Final FFmpeg Path: {}".format(final_path))
logging and logger.debug("Final FFmpeg Path: {}".format(final_path))

# Final Auto-Validation for FFmeg Binaries. returns final path if test is passed
return final_path if validate_ffmpeg(final_path, logging=logging) else False
Expand Down Expand Up @@ -1035,8 +1044,7 @@ def download_ffmpeg_binaries(path, os_windows=False, os_bit=""):
+ path
)
# remove leftovers if exists
if os.path.isfile(file_name):
delete_file_safe(file_name)
os.path.isfile(file_name) and delete_file_safe(file_name)
# download and write file to the given path
with open(file_name, "wb") as f:
logger.debug(
Expand All @@ -1062,8 +1070,7 @@ def download_ffmpeg_binaries(path, os_windows=False, os_bit=""):
bar = tqdm(total=int(total_length), unit="B", unit_scale=True)
for data in response.iter_content(chunk_size=4096):
f.write(data)
if len(data) > 0:
bar.update(len(data))
len(data) > 0 and bar.update(len(data))
bar.close()
logger.debug("Extracting executables.")
with zipfile.ZipFile(file_name, "r") as zip_ref:
Expand Down Expand Up @@ -1094,8 +1101,7 @@ 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
if logging: # log if test are passed
logger.debug("FFmpeg validity Test Passed!")
logger.debug(
"Found valid FFmpeg Version: `{}` installed on this system".format(
Expand Down Expand Up @@ -1292,8 +1298,7 @@ def validate_auth_keys(path, extension):
keys_buffer.append(key_file) # store it

# remove invalid keys if found
if len(keys_buffer) == 1:
delete_file_safe(os.path.join(path, keys_buffer[0]))
len(keys_buffer) == 1 and delete_file_safe(os.path.join(path, keys_buffer[0]))

# return results
return True if (len(keys_buffer) == 2) else False
1 change: 1 addition & 0 deletions vidgear/gears/netgear.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ def __init__(
assert check_WriteAccess(
custom_cert_location,
is_windows=True if os.name == "nt" else False,
logging=self.__logging,
), "[NetGear:ERROR] :: Permission Denied!, cannot write ZMQ authentication certificates to '{}' directory!".format(
value
)
Expand Down
4 changes: 3 additions & 1 deletion vidgear/gears/streamgear.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ def __init__(
abs_path = os.path.abspath(output)

if check_WriteAccess(
os.path.dirname(abs_path), is_windows=self.__os_windows
os.path.dirname(abs_path),
is_windows=self.__os_windows,
logging=self.__logging,
):
# check if given path is directory
valid_extension = "mpd" if self.__format == "dash" else "m3u8"
Expand Down
Loading

0 comments on commit 5feafb7

Please sign in to comment.