New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

deffcode

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

deffcode - pypi Package Compare versions

Comparing version
0.2.3
to
0.2.4
+21
-15
deffcode.egg-info/PKG-INFO
Metadata-Version: 2.1
Name: deffcode
Version: 0.2.3
Summary: A Cross-platform High-performance & Flexible Video Frames Decoder in python.
Version: 0.2.4
Summary: A cross-platform High-performance & Flexible Real-time Video Frames Decoder in Python.
Home-page: https://abhitronix.github.io/deffcode

@@ -86,7 +86,7 @@ Author: Abhishek Thakur

**Highly Adaptive -** _DeFFcode APIs implements a **standalone highly-extensible wrapper around [FFmpeg][ffmpeg]** multimedia framework. These APIs **supports a wide-ranging media streams as input** source such as [live USB/Virtual/IP camera feeds][capturing-and-previewing-frames-from-a-webcam], [regular multimedia files][capturing-rgb-frames-from-a-video-file], [screen recordings][capturing-and-previewing-frames-from-your-desktop], [image sequences][decoding-image-sequences], [network URL schemes][decoding-network-streams] (such as HTTP(s), RTP/RSTP, etc.), so on and so forth._
**Highly Adaptive -** _DeFFcode APIs implements a **standalone highly-extensible wrapper around [FFmpeg][ffmpeg]** multimedia framework. These APIs **supports a wide-ranging media streams as input** source such as [live USB/Virtual/IP camera feeds][capturing-and-previewing-frames-from-a-webcam], [regular multimedia files][decoding-video-files], [screen recordings][capturing-and-previewing-frames-from-your-desktop], [image sequences][decoding-image-sequences], [network URL schemes][decoding-network-streams] (such as HTTP(s), RTP/RSTP, etc.), so on and so forth._
**Highly Flexible -** _DeFFcode APIs gains an edge over other FFmpeg Wrappers by providing **complete control over the underline pipeline** including **access to almost any FFmpeg specification thinkable** such as specifying framerate, resolution, hardware decoder(s), filtergraph(s), and pixel-format(s) that are readily **supported by all well known Computer Vision libraries**._
**Highly Convenient -** _FFmpeg has a steep learning curve especially for users unfamiliar with a command line interface. DeFFcode helps users by maintaining the **same standard [OpenCV-Python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) (Python API for OpenCV) coding syntax for its APIs**, thereby making it even **easier to learn, create, and develop FFmpeg based apps** in Python._
**Highly Convenient -** _FFmpeg has a steep learning curve especially for users unfamiliar with a command line interface. DeFFcode helps users by providing similar to OpenCV, [**Index based Camera Device Capturing**][decoding-camera-devices-using-indexes] and the **same standard [OpenCV-Python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) (Python API for OpenCV) coding syntax for its APIs**, thereby making it even **easier to learn, create, and develop FFmpeg based apps** in Python._

@@ -103,2 +103,3 @@  

- Curated list of well-documented recipes ranging from [**Basic**][basic-recipes] to [**Advanced**][advanced-recipes] skill levels.
- Hands down the easiest [**Index based Camera Device Capturing**][decoding-camera-devices-using-indexes], similar to OpenCV.
- Easy to code **Real-time [Simple][transcoding-live-simple-filtergraphs] & [Complex][transcoding-live-complex-filtergraphs] Filtergraphs**. _(Yes, You read it correctly "Real-time"!)_

@@ -159,4 +160,4 @@ - Lightning fast dedicated **GPU-Accelerated Video [Decoding][hardware-accelerated-video-decoding] & [Transcoding][hardware-accelerated-video-transcoding]**.

- [Playing with any other FFmpeg pixel formats][capturing-and-previewing-bgr-frames-from-a-video-file]
- [Capturing and Previewing frames from a Webcam][capturing-and-previewing-frames-from-a-webcam]
- [Capturing and Previewing frames from your Desktop][capturing-and-previewing-frames-from-your-desktop] _(Screen Recording)_
- [Enumerating all Camera Devices with Indexes][enumerating-all-camera-devices-with-indexes]
- [Capturing and Previewing frames from a Camera using Indexes][capturing-and-previewing-frames-from-a-camera-using-indexes]
- [Capturing and Previewing frames from a HTTPs Stream][capturing-and-previewing-frames-from-a-https-stream]

@@ -213,2 +214,4 @@ - [Capturing and Previewing frames from a RTSP/RTP Stream][capturing-and-previewing-frames-from-a-rtsprtp-stream]

- [Generate and Decode frames from Game of Life Visualization][generate-and-decode-frames-from-game-of-life-visualization]
- [Capturing and Previewing frames from a Webcam using Custom Demuxer][capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer]
- [Capturing and Previewing frames from your Desktop][capturing-and-previewing-frames-from-your-desktop] _(Screen Recording)_
- [GPU-accelerated Hardware-based Video Decoding][gpu-accelerated-hardware-based-video-decoding]

@@ -352,3 +355,3 @@

[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6976948.svg)](https://doi.org/10.5281/zenodo.6976948)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6984364.svg)](https://doi.org/10.5281/zenodo.6984364)

@@ -358,9 +361,9 @@ ```BibTeX

author = {Abhishek Thakur},
title = {abhiTronix/deffcode: v0.2.2},
title = {abhiTronix/deffcode: v0.2.3},
month = aug,
year = 2022,
publisher = {Zenodo},
version = {v0.2.2},
doi = {10.5281/zenodo.6976948},
url = {https://doi.org/10.5281/zenodo.6976948}
version = {v0.2.3},
doi = {10.5281/zenodo.6984364},
url = {https://doi.org/10.5281/zenodo.6984364}
}

@@ -401,3 +404,3 @@ ```

[license]:https://github.com/abhiTronix/deffcode/blob/master/LICENSE
[help]:https://abhitronix.github.io/deffcode/latest/https://abhitronix.github.io/deffcode/latest/help/get_help
[help]:https://abhitronix.github.io/deffcode/latest/help/get_help
[installation-notes]:https://abhitronix.github.io/deffcode/latest/installation/#installation-notes

@@ -413,3 +416,3 @@ [ffdecoder-api]:https://abhitronix.github.io/deffcode/latest/reference/ffdecoder/#ffdecoder-api

[decoding-video-files]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-video-files/#decoding-video-files
[decoding-live-feed-devices]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#decoding-live-feed-devices
[decoding-camera-devices-using-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#decoding-camera-devices-using-indexes
[decoding-network-streams]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#decoding-network-streams

@@ -425,4 +428,4 @@ [decoding-image-sequences]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-image-sequences/#decoding-image-sequences

[capturing-and-previewing-bgr-frames-from-a-video-file]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-video-files/#capturing-and-previewing-bgr-frames-from-a-video-file
[capturing-and-previewing-frames-from-a-webcam]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#capturing-and-previewing-frames-from-a-webcam
[capturing-and-previewing-frames-from-your-desktop]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#capturing-and-previewing-frames-from-your-desktop
[enumerating-all-camera-devices-with-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#enumerating-all-camera-devices-with-indexes
[capturing-and-previewing-frames-from-a-camera-using-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#capturing-and-previewing-frames-from-a-camera-using-indexes
[capturing-and-previewing-frames-from-a-https-stream]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#capturing-and-previewing-frames-from-a-https-stream

@@ -449,2 +452,3 @@ [capturing-and-previewing-frames-from-a-rtsprtp-stream]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#capturing-and-previewing-frames-from-a-rtsprtp-stream

[decoding-live-virtual-sources]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-virtual-sources/#decoding-live-virtual-sources
[decoding-live-feed-devices]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#decoding-live-feed-devices
[hardware-accelerated-video-decoding]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-hw-acceleration/#hardware-accelerated-video-decoding

@@ -469,2 +473,4 @@ [transcoding-live-complex-filtergraphs]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-live-frames-complexgraphs/#transcoding-live-complex-filtergraphs

[transcoding-video-art-with-pixelation-effect]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-art-filtergraphs/#transcoding-video-art-with-pixelation-effect
[capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer
[capturing-and-previewing-frames-from-your-desktop]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#capturing-and-previewing-frames-from-your-desktop
[gpu-accelerated-hardware-based-video-transcoding-with-writegear-api]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-hw-acceleration/#gpu-accelerated-hardware-based-video-transcoding-with-writegear-api

@@ -471,0 +477,0 @@ [added-new-attributes-to-metadata-in-ffdecoder-api]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/update-metadata/#added-new-attributes-to-metadata-in-ffdecoder-api

@@ -103,3 +103,3 @@ """

self.__frame_format = (
frame_format.strip() if isinstance(frame_format, str) else None
frame_format.lower().strip() if isinstance(frame_format, str) else None
)

@@ -147,2 +147,18 @@

# handle user ffmpeg pre-headers(parameters such as `-re`) parameters (must be a list)
self.__ffmpeg_prefixes = self.__extra_params.pop("-ffprefixes", [])
# check if not valid type
if not isinstance(self.__ffmpeg_prefixes, list):
# log it
logger.warning(
"Discarding invalid `-ffprefixes` value of wrong type: `{}`!".format(
type(self.__ffmpeg_prefixes).__name__
)
)
# reset improper values
self.__ffmpeg_prefixes = []
else:
# also pass valid ffmpeg pre-headers to Sourcer API
sourcer_params["-ffprefixes"] = self.__ffmpeg_prefixes
# pass parameter(if specified) to Sourcer API, specifying where to save the downloaded FFmpeg Static

@@ -165,6 +181,11 @@ # assets on Windows(if specified)

# pass FFmpeg filter to Sourcer API params for processing
if set(["-vf", "-filter_complex"]).intersection(self.__extra_params.keys()):
key = "-vf" if "-vf" in self.__extra_params else "-filter_complex"
sourcer_params[key] = self.__extra_params[key]
# define dict to store user-defined parameters
self.__user_metadata = {}
# extract and assign source metadata as dict
self.__source_metadata = (
(self.__sourcer_metadata, self.__missing_prop) = (
Sourcer(

@@ -178,9 +199,7 @@ source=source,

.probe_stream(default_stream_indexes=default_stream_indexes)
.retrieve_metadata()
.retrieve_metadata(force_retrieve_missing=True)
)
# add dummy value for `output_frames_pixfmt` source metadata
self.__source_metadata["output_frames_pixfmt"] = "unknown"
# handle valid FFmpeg assets location
self.__ffmpeg = self.__source_metadata["ffmpeg_binary_path"]
self.__ffmpeg = self.__sourcer_metadata["ffmpeg_binary_path"]

@@ -193,16 +212,16 @@ # handle pass-through audio mode works in conjunction with WriteGear [TODO]

# handle mode of operation
if self.__source_metadata["source_has_image_sequence"]:
if self.__sourcer_metadata["source_has_image_sequence"]:
# image-sequence mode
self.__opmode = "imgseq"
elif (
self.__source_metadata[
self.__sourcer_metadata[
"source_has_video"
] # audio is only for pass-through, not really for audio decoding yet.
and self.__source_metadata["source_has_audio"]
and self.__sourcer_metadata["source_has_audio"]
and self.__passthrough_mode # [TODO]
):
self.__opmode = "av"
# elif __defop_mode == "ao" and self.__source_metadata.contains_audio: # [TODO]
# elif __defop_mode == "ao" and self.__sourcer_metadata.contains_audio: # [TODO]
# self.__opmode = "ao"
elif self.__source_metadata["source_has_video"]:
elif self.__sourcer_metadata["source_has_video"]:
# video-only mode

@@ -216,19 +235,19 @@ self.__opmode = "vo"

# store as metadata
self.__source_metadata["ffdecoder_operational_mode"] = self.__supported_opmodes[
self.__missing_prop["ffdecoder_operational_mode"] = self.__supported_opmodes[
self.__opmode
]
# and log it
self.__verbose_logs and logger.critical(
"Activating {} Mode of Operation.".format(
self.__supported_opmodes[self.__opmode]
)
)
# handle user-defined framerate
__framerate = self.__extra_params.pop("-framerate", 0.0)
if not (__framerate is None) and isinstance(__framerate, (float, int)):
# handle user-defined output framerate
__framerate = self.__extra_params.pop("-framerate", None)
if (
isinstance(__framerate, str)
and __framerate
== "null" # special mode to discard `-framerate/-r` parameter
):
self.__inputframerate = __framerate
elif isinstance(__framerate, (float, int)):
self.__inputframerate = float(__framerate) if __framerate > 0.0 else 0.0
else:
# warn if wrong type
logger.warning(
not (__framerate is None) and logger.warning(
"Discarding invalid `-framerate` value of wrong type `{}`!".format(

@@ -241,15 +260,22 @@ type(__framerate).__name__

# FFmpeg parameter `-s` is unsupported
if not (self.__extra_params.pop("-s", None) is None):
logger.warning(
"Discarding user-defined `-s` FFmpeg parameter as it can only be assigned with `-custom_resolution` attribute! Read docs for more details."
)
# handle user defined decoded frame resolution(must be a tuple or list)
# handle user defined decoded frame resolution
self.__custom_resolution = self.__extra_params.pop("-custom_resolution", None)
if not (self.__custom_resolution is None) and (
not isinstance(self.__custom_resolution, (list, tuple))
or len(self.__custom_resolution) != 2
if (
isinstance(self.__custom_resolution, str)
and self.__custom_resolution
== "null" # special mode to discard `-size/-s` parameter
) or (
isinstance(self.__custom_resolution, (list, tuple))
and len(self.__custom_resolution)
== 2 # valid resolution(must be a tuple or list)
):
# log it
logger.warning(
self.__verbose_logs and not isinstance(
self.__custom_resolution, str
) and logger.debug(
"Setting raw frames size: `{}`.".format(self.__custom_resolution)
)
else:
# log it
not (self.__custom_resolution is None) and logger.warning(
"Discarding invalid `-custom_resolution` value: `{}`!".format(

@@ -262,14 +288,2 @@ self.__custom_resolution

# handle user defined ffmpeg pre-headers(parameters such as `-re`) parameters (must be a list)
self.__ffmpeg_prefixes = self.__extra_params.pop("-ffprefixes", [])
if not isinstance(self.__ffmpeg_prefixes, list):
# log it
logger.warning(
"Discarding invalid `-ffprefixes` value of wrong type: `{}`!".format(
type(self.__ffmpeg_prefixes).__name__
)
)
# reset improper values
self.__ffmpeg_prefixes = []
def formulate(self):

@@ -291,4 +305,5 @@

default_vdecodec = (
self.__source_metadata["source_video_decoder"]
if self.__source_metadata["source_video_decoder"] in supported_vdecodecs
self.__sourcer_metadata["source_video_decoder"]
if self.__sourcer_metadata["source_video_decoder"]
in supported_vdecodecs
else "unknown"

@@ -348,83 +363,145 @@ )

# dynamically calculate default raw-frames pixel format(if not assigned by user).
# notify FFmpeg `-pix_fmt` parameter cannot be assigned directly
if "-pix_fmt" in self.__extra_params:
logger.warning(
"Discarding user-defined `-pix_fmt` value as it can only be assigned with `frame_format` parameter!"
)
self.__extra_params.pop("-pix_fmt", None)
# get supported FFmpeg pixfmt data with depth and bpp(bits-per-pixel)
self.__ff_pixfmt_metadata = get_supported_pixfmts(self.__ffmpeg)
supported_pixfmts = [fmts[0] for fmts in self.__ff_pixfmt_metadata]
# calculate default pixel-format
# Check special case - `frame_format`(or `-pix_fmt`) parameter discarded from pipeline
self.__frame_format == "null" and logger.critical(
"Manually discarding `frame_format`(or `-pix_fmt`) parameter from this pipeline."
)
# choose between rgb24(if available) or source pixel-format
# otherwise, only source pixel-format for special case
default_pixfmt = (
"rgb24"
if "rgb24" in supported_pixfmts
else self.__source_metadata["source_video_pixfmt"]
if "rgb24" in supported_pixfmts and self.__frame_format != "null"
else self.__sourcer_metadata["source_video_pixfmt"]
)
# assigning `-pix_fmt` parameter cannot be assigned directly
if "-pix_fmt" in self.__extra_params:
logger.warning(
"Discarding user-defined `-pix_fmt` value as it can only be assigned with `frame_format` parameter! Read docs for more details."
)
self.__extra_params.pop("-pix_fmt", None)
# assign output raw-frames pixel format
rawframe_pixfmt = None
if (
"output_frames_pixfmt" in self.__source_metadata
and self.__source_metadata["output_frames_pixfmt"] in supported_pixfmts
not (self.__frame_format is None)
and self.__frame_format in supported_pixfmts
):
# check if manually defined via `metadata` property object
# check if valid and supported `frame_format` parameter assigned
rawframe_pixfmt = self.__frame_format.strip()
self.__verbose_logs and logger.info(
"User-defined `{}` frame pixel-format will be used for this pipeline.".format(
rawframe_pixfmt
)
)
elif (
"output_frames_pixfmt"
in self.__sourcer_metadata # means `format` filter is defined
and self.__sourcer_metadata["output_frames_pixfmt"] in supported_pixfmts
):
# assign if valid and supported
output_params["-pix_fmt"] = self.__source_metadata[
rawframe_pixfmt = self.__sourcer_metadata[
"output_frames_pixfmt"
].strip()
elif (
not (self.__frame_format is None)
and self.__frame_format in supported_pixfmts
):
# check if assigned via `frame_format` parameter
# assign if valid and supported
output_params["-pix_fmt"] = self.__frame_format.strip()
self.__verbose_logs and logger.info(
"FFmpeg filter values will be used for this pipeline for defining output pixel-format."
)
else:
# reset to default if not supported
not (self.__frame_format is None) and logger.critical(
"Provided FFmpeg does not support `{}` pixel format(pix_fmt). Switching to default `{}`!".format(
self.__frame_format, "rgb24"
rawframe_pixfmt = default_pixfmt
# log it accordingly
if self.__frame_format is None:
logger.info(
"Using default `{}` pixel-format for this pipeline.".format(
default_pixfmt
)
)
)
output_params["-pix_fmt"] = default_pixfmt
else:
logger.warning(
"{} Switching to default `{}` pixel-format!".format(
"Provided FFmpeg does not supports `{}` pixel-format.".format(
self.__sourcer_metadata["output_frames_pixfmt"]
if "output_frames_pixfmt" in self.__sourcer_metadata
else self.__frame_format
)
if self.__frame_format != "null"
else "No usable pixel-format defined.",
default_pixfmt,
)
)
# dynamically calculate raw-frame dtype based on pix format selected
frames_pixfmt = output_params["-pix_fmt"]
# dynamically calculate raw-frame datatype based on pixel-format selected
(self.__raw_frame_depth, rawframesbpp) = [
(int(x[1]), int(x[2]))
for x in self.__ff_pixfmt_metadata
if x[0] == frames_pixfmt
if x[0] == rawframe_pixfmt
][0]
raw_bit_per_component = rawframesbpp // self.__raw_frame_depth
if raw_bit_per_component in [4, 8]:
if 4 <= raw_bit_per_component <= 8:
self.__raw_frame_dtype = np.dtype("u1")
elif raw_bit_per_component == 16:
if frames_pixfmt.endswith("le"):
elif 8 < raw_bit_per_component <= 16 and rawframe_pixfmt.endswith(
("le", "be")
):
if rawframe_pixfmt.endswith("le"):
self.__raw_frame_dtype = np.dtype("<u2")
elif frames_pixfmt.endswith("be"):
else:
self.__raw_frame_dtype = np.dtype(">u2")
else:
pass
else:
# reset to default if not supported
logger.critical(
"Given `frame_format` value: {} is not supported by FFdecoder. Switching to default `{}`!".format(
frames_pixfmt, "rgb24"
# reset to both pixel-format and datatype to default if not supported
not (self.__frame_format is None) and logger.warning(
"Selected pixel-format `{}` dtype is not supported by FFdecoder API. Switching to default `rgb24` pixel-format!".format(
rawframe_pixfmt
)
)
output_params["-pix_fmt"] = default_pixfmt
# change dtype
rawframe_pixfmt = "rgb24"
self.__raw_frame_dtype = np.dtype("u1")
# assign to global parameter
self.__raw_frame_pixfmt = output_params["-pix_fmt"]
# also store as metadata
self.__source_metadata["output_frames_pixfmt"] = output_params["-pix_fmt"]
# handle raw-frame size
if not (self.__custom_resolution is None):
# assign if assigned by user
# Check if not special case
if self.__frame_format != "null":
# assign to FFmpeg pipeline otherwise
output_params["-pix_fmt"] = rawframe_pixfmt
# assign to global parameter further usage
self.__raw_frame_pixfmt = rawframe_pixfmt
# also override as metadata(if available)
if "output_frames_pixfmt" in self.__sourcer_metadata:
self.__sourcer_metadata[
"output_frames_pixfmt"
] = self.__raw_frame_pixfmt
# handle raw-frame resolution
# notify FFmpeg `-s` parameter cannot be assigned directly
if "-s" in self.__extra_params:
logger.warning(
"Discarding user-defined `-s` FFmpeg parameter as it can only be assigned with `-custom_resolution` attribute! Read docs for more details."
)
self.__extra_params.pop("-s", None)
# assign output rawframe resolution
if not (self.__custom_resolution is None) and not isinstance(
self.__custom_resolution, str
):
# assign if assigned by user and not "null"(str)
self.__raw_frame_resolution = self.__custom_resolution
self.__verbose_logs and logger.info(
"User-defined `{}` frame resolution will be used for this pipeline.".format(
self.__raw_frame_resolution
)
)
elif (
self.__source_metadata["source_video_resolution"]
and len(self.__source_metadata["source_video_resolution"]) == 2
"output_frames_resolution"
in self.__sourcer_metadata # means `scale` filter is defined
and self.__sourcer_metadata["output_frames_resolution"]
and len(self.__sourcer_metadata["output_frames_resolution"]) == 2
):
# calculate raw-frame resolution/dimensions based on source (if not assigned by user).
self.__raw_frame_resolution = self.__source_metadata[
# calculate raw-frame resolution/dimensions based on output.
self.__raw_frame_resolution = self.__sourcer_metadata[
"output_frames_resolution"
]
elif (
self.__sourcer_metadata["source_video_resolution"]
and len(self.__sourcer_metadata["source_video_resolution"]) == 2
):
# calculate raw-frame resolution/dimensions based on source.
self.__raw_frame_resolution = self.__sourcer_metadata[
"source_video_resolution"

@@ -435,23 +512,83 @@ ]

raise RuntimeError(
"Invalid `source_video_resolution` metadata value detected!"
"Both source and output metadata values found Invalid with {} `-custom_resolution` attribute. Aborting!".format(
"null"
if isinstance(self.__inputframerate, str)
else "undefined"
)
)
# add to pipeline
dimensions = "{}x{}".format(
self.__raw_frame_resolution[0], self.__raw_frame_resolution[1]
# special mode to discard `-size/-s` FFmpeg parameter completely
if isinstance(self.__custom_resolution, str):
logger.critical(
"Manually discarding `-size/-s` FFmpeg parameter from this pipeline."
)
else:
# add to pipeline
dimensions = "{}x{}".format(
self.__raw_frame_resolution[0], self.__raw_frame_resolution[1]
)
output_params["-s"] = str(dimensions)
# log if filters or default source is used
self.__verbose_logs and (
self.__custom_resolution is None
or isinstance(self.__custom_resolution, str)
) and logger.info(
"{} for this pipeline for defining output resolution.".format(
"FFmpeg filter values will be used"
if "output_frames_resolution" in self.__sourcer_metadata
else "Default source resolution will be used"
)
)
output_params["-s"] = str(dimensions)
# dynamically calculate raw-frame framerate based on source (if not assigned by user).
if self.__inputframerate > 0.0:
# assign if assigned by user
if (
not isinstance(self.__inputframerate, str)
and self.__inputframerate > 0.0
):
# assign if assigned by user and not "null"(str)
output_params["-framerate"] = str(self.__inputframerate)
elif self.__source_metadata["source_video_framerate"] > 0.0:
# calculate raw-frame framerate based on source
output_params["-framerate"] = str(
self.__source_metadata["source_video_framerate"]
self.__verbose_logs and logger.info(
"User-defined `{}` output framerate will be used for this pipeline.".format(
str(self.__inputframerate)
)
)
elif (
"output_framerate"
in self.__sourcer_metadata # means `fps` filter is defined
and self.__sourcer_metadata["output_framerate"] > 0.0
):
# special mode to discard `-framerate/-r` FFmpeg parameter completely
if self.__inputframerate == "null":
logger.critical(
"Manually discarding `-framerate/-r` FFmpeg parameter from this pipeline."
)
else:
# calculate raw-frame framerate based on output
output_params["-framerate"] = str(
self.__sourcer_metadata["output_framerate"]
)
self.__verbose_logs and logger.info(
"FFmpeg filter values will be used for this pipeline for defining output framerate."
)
elif self.__sourcer_metadata["source_video_framerate"] > 0.0:
# special mode to discard `-framerate/-r` FFmpeg parameter completely
if self.__inputframerate == "null":
logger.critical(
"Manually disabling `-framerate/-r` FFmpeg parameter for this pipeline."
)
else:
# calculate raw-frame framerate based on source
output_params["-framerate"] = str(
self.__sourcer_metadata["source_video_framerate"]
)
self.__verbose_logs and logger.info(
"Default source framerate will be used for this pipeline for defining output framerate."
)
else:
# otherwise raise error
raise RuntimeError(
"Invalid `source_video_framerate` metadata value detected!"
"Both source and output metadata values found Invalid with {} `-framerate` attribute. Aborting!".format(
"null"
if isinstance(self.__inputframerate, str)
else "undefined"
)
)

@@ -466,4 +603,7 @@

self.__raw_frame_num = input_params["-frames:v"]
elif self.__source_metadata["approx_video_nframes"]:
self.__raw_frame_num = self.__source_metadata["approx_video_nframes"]
elif (
not (self.__sourcer_metadata["approx_video_nframes"] is None)
and self.__sourcer_metadata["approx_video_nframes"] > 0
):
self.__raw_frame_num = self.__sourcer_metadata["approx_video_nframes"]
else:

@@ -476,2 +616,9 @@ self.__raw_frame_num = None

# log Mode of Operation
self.__verbose_logs and logger.critical(
"Activating {} Mode of Operation.".format(
self.__supported_opmodes[self.__opmode]
)
)
# compose the Pipeline using formulated FFmpeg parameters

@@ -493,3 +640,3 @@ self.__launch_FFdecoderline(input_params, output_params)

self.__process is None
), "Pipeline is not running! Check if you called `create()` method."
), "Pipeline is not running! You must call `formulate()` method first."

@@ -513,3 +660,3 @@ # formulated raw frame size

except Exception as e:
raise RuntimeError("Frame fetching failed with error: {}".format(str(e)))
raise RuntimeError("Frame buffering failed with error: {}".format(str(e)))
return (

@@ -531,3 +678,3 @@ nparray

elif self.__raw_frame_pixfmt.startswith("gray"):
# reconstruct gray frames
# reconstruct exclusive `gray` frames
frame = frame.reshape(

@@ -540,11 +687,2 @@ (

)[:, :, 0]
elif self.__raw_frame_pixfmt == "yuv444p":
# reconstruct exclusive frames
frame = frame.reshape(
(
self.__raw_frame_depth,
self.__raw_frame_resolution[1],
self.__raw_frame_resolution[0],
)
).transpose((1, 2, 0))
else:

@@ -568,10 +706,10 @@ # reconstruct default frames

if self.__raw_frame_num is None or not self.__raw_frame_num:
while not self.__terminate_stream:
while not self.__terminate_stream: # infinite raw frames
frame = self.__fetchNextFrame()
if frame is None:
self.__terminate_stream = True
continue
break
yield frame
else:
for _ in range(self.__raw_frame_num):
for _ in range(self.__raw_frame_num): # finite raw frames
frame = self.__fetchNextFrame()

@@ -607,4 +745,11 @@ if frame is None:

# return complete (source+user-defined) metadata as JSON string
return json.dumps({**self.__source_metadata, **self.__user_metadata}, indent=2)
# return complete metadata information as JSON string
return json.dumps(
{
**self.__sourcer_metadata, # source video
**self.__missing_prop, # missing properties
**self.__user_metadata, # user-defined
},
indent=2,
)

@@ -623,20 +768,57 @@ @metadata.setter

self.__verbose_logs and logger.info("Updating Metadata...")
# extract any source metadata keys
default_keys = set(value).intersection(self.__source_metadata)
# extract any source and output internal metadata keys
default_keys = set(value).intersection(
{**self.__sourcer_metadata, **self.__missing_prop}
)
# counterpart source properties for each output properties
counterpart_prop = {
"output_frames_resolution": "source_video_resolution",
"output_frames_pixfmt": "source_video_pixfmt",
"output_framerate": "source_video_framerate",
}
# iterate over source metadata keys and sanitize it
for key in default_keys or []:
if key == "source":
# `source` metadata value cannot be altered
# metadata properties that cannot be altered
logger.warning(
"`source` metadata value cannot be altered. Discarding!"
"`{}` metadata property value cannot be altered. Discarding!".format(
key
)
)
elif isinstance(value[key], type(self.__source_metadata[key])):
elif key in self.__missing_prop:
# missing metadata properties are unavailable and read-only
# notify user about alternative counterpart property (if available)
logger.warning(
"`{}` metadata property is read-only".format(key)
+ (
". Try updating `{}` property instead!".format(
counterpart_prop[key]
)
if key in counterpart_prop.keys()
else " and cannot be updated!"
)
)
elif isinstance(value[key], type(self.__sourcer_metadata[key])):
# check if correct datatype as original
self.__verbose_logs and logger.info(
"Updating `{}`{} metadata property to `{}`.".format(
key,
" and its counterpart"
if key in counterpart_prop.values()
else "",
value[key],
)
)
# update source metadata if valid
self.__source_metadata.update(value)
continue
self.__sourcer_metadata[key] = value[key]
# also update missing counterpart property (if available)
counter_key = next(
(k for k, v in counterpart_prop.items() if v == key), ""
)
if counter_key:
self.__missing_prop[counter_key] = value[key]
else:
# otherwise discard and log it
logger.warning(
"Manually assigned `{}` metadata value is invalid type. Discarding!"
"Manually assigned `{}` metadata property value is of invalid type. Discarding!"
).format(key)

@@ -649,3 +831,3 @@ # delete invalid key

any(isinstance(value[x], tuple) for x in value) and logger.warning(
"All TUPLE metadata values will be converted to LIST datatype. Read docs for more details."
"All TUPLE metadata properties will be converted to LIST datatype. Read docs for more details."
)

@@ -680,21 +862,21 @@ # update user-defined metadata

+ (
["-f", self.__source_metadata["source_demuxer"]]
if ("source_demuxer" in self.__source_metadata.keys())
["-f", self.__sourcer_metadata["source_demuxer"]]
if ("source_demuxer" in self.__sourcer_metadata.keys())
else []
)
+ ["-i", self.__source_metadata["source"]]
+ ["-i", self.__sourcer_metadata["source"]]
+ output_parameters
+ ["-f", "rawvideo", "-"]
)
# assign value to class variable
_cmd = " ".join(cmd)
# compose the FFmpeg process
if self.__verbose_logs:
logger.debug("Executing FFmpeg command: `{}`".format(_cmd))
logger.debug("Executing FFmpeg command: `{}`".format(" ".join(cmd)))
# In debugging mode
self.__process = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=None)
self.__process = sp.Popen(
cmd, stdin=sp.DEVNULL, stdout=sp.PIPE, stderr=None
)
else:
# In silent mode
self.__process = sp.Popen(
cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.DEVNULL
cmd, stdin=sp.DEVNULL, stdout=sp.PIPE, stderr=sp.DEVNULL
)

@@ -706,2 +888,3 @@

"""
# signal we are closing

@@ -712,3 +895,3 @@ self.__verbose_logs and logger.debug("Terminating FFdecoder Pipeline...")

if self.__process is None or not (self.__process.poll() is None):
logger.warning("Pipeline already terminated!")
logger.info("Pipeline already terminated.")
return

@@ -720,9 +903,9 @@ # Attempt to close pipeline.

self.__process.stdout and self.__process.stdout.close()
# wait if still process is still processing some information
# terminate/kill process if still processing
if self.__process.poll() is None:
self.__process.terminate()
# demuxers prefer kill
self.__process.kill()
# wait if not exiting
self.__process.wait()
self.__process = None
self.__verbose_logs and logger.debug(
"FFdecoder Pipeline terminated successfully"
)
logger.info("Pipeline terminated successfully.")

@@ -51,3 +51,3 @@ """

def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs):
self.timeout = DEFAULT_TIMEOUT

@@ -354,2 +354,188 @@ if "timeout" in kwargs:

def extract_device_n_demuxer(path, machine_OS=None, verbose=False):
"""
## get_valid_devicepath
Discovers and extracts all Video-Capture device(s) name/path/index present on system and
supported by valid OS specific FFmpeg demuxer.
Parameters:
path (string): absolute path of FFmpeg binaries.
machine_OS(string): OS running _(Must be a value of `platform.system()` module)_.
verbose (bool): enables/disables verbose.
**Returns:** Tuple of list of supported device(s) path/name/index and OS specific demuxer used.
"""
# validate `machine_OS` parameter value
assert not (machine_OS is None) and isinstance(
machine_OS, str
), "`machine_OS` parameter value is empty or invalid type. Aborting!"
# initialize params
devices = [] # handles devices discovered
req_demuxer = None # handle required demuxer
# define all valid FFmpeg demuxers w.r.t OS platforms
valid_demuxers = dict(Windows="dshow", Darwin="avfoundation", Linux="v4l2")
# check OS is supported
if not machine_OS.strip() in list(valid_demuxers.keys()):
# raise error if OS isn't supported
raise ValueError(
"""Unsupported OS detected! The `source_demuxer='auto'` value isn't supported on your OS,
Kindly assign `source` and `source_demuxer` parameter values manually."""
)
else:
# assign required demuxer
req_demuxer = valid_demuxers[machine_OS.strip()]
# log if specified
verbose and logger.debug("Auto-Searching for valid devices...")
# assert if demuxer is supported by provided ffmpeg.
assert req_demuxer in get_supported_demuxers(
path
), "Required `{}` demuxer isn't supported by provided FFmpeg binaries. Kindly compile FFmpeg with \
suitable flags or manually assign `source` and `source_demuxer` parameter values. Aborting!".format(
valid_demuxers[machine_OS]
)
# create default ffmpeg command (for Windows and MacOS)
default_ffcommand = "-hide_banner -list_devices true -f {} -i dummy".format(
req_demuxer
)
# find all OS specific FFmpeg devices path and demuxer
if machine_OS == "Windows":
# get metadata
metadata = check_sp_output(
[path] + default_ffcommand.split(" "),
force_retrieve_stderr=True,
)
# clean and split metadata
splitted = [x.decode("utf-8").strip() for x in metadata.split(b"\n")]
# find video only
head, sep, tail = "\n".join(splitted).partition("DirectShow audio")
if head.strip():
# compile regex
finder = re.compile(r'"(.*?[^\\])"')
# find all outputs
outputs = finder.findall(head.strip())
# return output findings
devices = [o.strip() for o in outputs if not (o.startswith("@device"))]
elif machine_OS == "Linux":
# get devices if `video4linux` drivers are correctly configured.
metadata = check_sp_output(
["v4l2-ctl", "--list-devices"],
force_retrieve_stderr=True,
)
# decode metadata
decoded = metadata.decode("utf-8").strip()
# check if command executed properly
if (
not decoded
or set(["command", "not", "found"]).issubset(decoded.split(" "))
or (
set(["Cannot", "open", "device"]).issubset(decoded.split(" "))
and not ("):" in decoded)
)
):
logger.error(
"Cannot execute `v4l2-ctl` command. "
+ (
"Kindly install `v4l-utils` package on your linux machine."
if set(["command", "not", "found"]).issubset(decoded.split(" "))
else "Permission denied! Add your username to the `video` group to fix this error."
)
)
else:
# clean metadata
clean_n_splitted = [
x.strip()
for x in decoded.split("\n\n")
if "/dev/video" in x and "):" in x
]
# compile regex
finder = re.compile(r"^[a-zA-Z0-9_.\- ]*")
# iterate over data
for data in clean_n_splitted:
# find all valid device names
device_name = [dev.strip() for dev in finder.findall(data) if dev]
dev_paths = [x.strip() for x in data.split("\n") if "/dev/video" in x]
# patch for multiple /dev/video paths for single device
if len(dev_paths) > 1:
# warn users about this
verbose and logger.warning(
"Multiple `/dev/video` paths detected for {} device. This might take a while!".format(
device_name
)
)
# iterate over each device's paths
for path in dev_paths:
# search in path properties
metadata_path = check_sp_output(
["v4l2-ctl", "--device={}".format(path), "--all"],
)
# decode path metadata
decoded_path = metadata_path.decode("utf-8").strip()
if (
"Width/Height" in decoded_path
and "Pixel Format" in decoded_path
):
# append once required Width/Height and Pixel Format detected
devices.append({path: device_name})
else:
# skip path otherwise
pass
elif len(dev_paths) == 1:
# append path directly if only one
devices.append({dev_paths[0]: device_name})
else:
pass
else: # Darwin OSes
# get metadata
metadata = check_sp_output(
[path] + default_ffcommand.split(" "),
force_retrieve_stderr=True,
)
# clean and split metadata
splitted = [x.decode("utf-8").strip() for x in metadata.split(b"\n")]
# find video only
head, sep, tail = "\n".join(splitted).partition("AVFoundation audio")
if head.strip():
# compile regex
finder = re.compile(r"\[[0-9]\](.*)")
# find all outputs
outputs = finder.findall(head)
# return output findings
devices = [o.strip() for o in outputs]
# check if any devices were found
if devices:
# log everything if `verbose=True`
if verbose:
for idx, dev in enumerate(devices):
logger.info(
"[{}]: {}".format(
idx,
dev
if machine_OS != "Linux"
else "{} at path `{}`".format(
next(iter(dev.values()))[0], next(iter(dev.keys()))
),
)
)
logger.debug(
"Auto-Search completed successfully! Found `{}` valid device(s).".format(
len(devices)
)
)
# return list of device name/path/index and related demuxer
return (devices, req_demuxer)
else:
# otherwise raise error
raise RuntimeError(
"API unable to discover any valid device(s) connected your machine. Aborting!"
)
def validate_imgseqdir(source, extension="jpg", verbose=False):

@@ -493,2 +679,3 @@ """

if retcode and not (retrieve_stderr):
logger.error("[Pipline-Error] :: {}".format(output.decode("utf-8")))
cmd = kwargs.get("args")

@@ -502,5 +689,7 @@ if cmd is None:

bool(output) or bool(stderr) or logger.error(
"FFmpeg Pipline failed to exact any metadata!"
"[Pipline-Error] :: Pipline failed to exact any data from command: {}!".format(
args[0] if args else []
)
)
# return output otherwise
return output if not (retrieve_stderr) else stderr
return stderr if retrieve_stderr and stderr else output

@@ -24,2 +24,3 @@ """

import os
import copy
import json

@@ -31,3 +32,3 @@ import logging

# import utils packages
from .utils import logger_handler
from .utils import logger_handler, validate_device_index, dict2Args
from .ffhelper import (

@@ -39,2 +40,3 @@ check_sp_output,

get_valid_ffmpeg_path,
extract_device_n_demuxer,
)

@@ -79,3 +81,3 @@

verbose=False,
**sourcer_params
**sourcer_params,
):

@@ -93,3 +95,3 @@ """

# checks if machine in-use is running windows os or not
self.__os_windows = True if os.name == "nt" else False
self.__machine_OS = platform.system()

@@ -107,3 +109,3 @@ # define internal parameters

str(k).strip(): str(v).strip()
if not isinstance(v, (dict, list, int, float))
if not isinstance(v, (dict, list, int, float, tuple))
else v

@@ -142,3 +144,3 @@ for k, v in sourcer_params.items()

str(custom_ffmpeg),
self.__os_windows,
True if self.__machine_OS == "Windows" else False,
ffmpeg_download_path=__ffmpeg_download_path,

@@ -159,26 +161,71 @@ verbose=self.__verbose_logs,

# define externally accessible parameters
self.__source = source # handles source stream
# sanitize externally accessible parameters and assign them
# handles source demuxer
self.__source_demuxer = (
source_demuxer.strip().lower() if isinstance(source_demuxer, str) else None
)
# handles source stream extension
self.__source_extension = os.path.splitext(source)[-1]
self.__default_video_resolution = "" # handle stream resolution
self.__default_video_framerate = "" # handle stream framerate
self.__default_video_bitrate = "" # handle stream's video bitrate
self.__default_video_pixfmt = "" # handle stream's video pixfmt
self.__default_video_decoder = "" # handle stream's video decoder
self.__default_source_duration = "" # handle stream's video duration
self.__approx_video_nframes = "" # handle approx stream frame number
self.__default_audio_bitrate = "" # handle stream's audio bitrate
self.__default_audio_samplerate = "" # handle stream's audio samplerate
if source is None:
# first check if source value is empty
# raise error if true
raise ValueError("Input `source` parameter is empty!")
elif isinstance(source_demuxer, str):
# assign if valid demuxer value
self.__source_demuxer = source_demuxer.strip().lower()
# assign if valid demuxer value
assert self.__source_demuxer != "auto" or validate_device_index(
source
), "Invalid `source_demuxer='auto'` value detected with source: `{}`. Aborting!".format(
source
)
else:
# otherwise find valid default source demuxer value
# enforce "auto" if valid index device
self.__source_demuxer = "auto" if validate_device_index(source) else None
# log if not valid index device and invalid type
self.__verbose_logs and not self.__source_demuxer in [
"auto",
None,
] and logger.warning(
"Discarding invalid `source_demuxer` parameter value of wrong type: `{}`".format(
type(source_demuxer).__name__
)
)
# log if not valid index device and invalid type
self.__verbose_logs and self.__source_demuxer == "auto" and logger.critical(
"Given source `{}` is a valid device index. Enforcing 'auto' demuxer.".format(
source
)
)
# handle flags
self.__contains_video = False # contain video
self.__contains_audio = False # contain audio
self.__contains_images = False # contain image-sequence
# handles source stream
self.__source = source
# check whether metadata probed or not
# creates shallow copy for further usage #TODO
self.__source_org = copy.copy(self.__source)
self.__source_demuxer_org = copy.copy(self.__source_demuxer)
# handles all extracted devices names/paths list
# when source_demuxer = "auto"
self.__extracted_devices_list = []
# various source stream params
self.__default_video_resolution = "" # handles stream resolution
self.__default_video_framerate = "" # handles stream framerate
self.__default_video_bitrate = "" # handles stream's video bitrate
self.__default_video_pixfmt = "" # handles stream's video pixfmt
self.__default_video_decoder = "" # handles stream's video decoder
self.__default_source_duration = "" # handles stream's video duration
self.__approx_video_nframes = "" # handles approx stream frame number
self.__default_audio_bitrate = "" # handles stream's audio bitrate
self.__default_audio_samplerate = "" # handles stream's audio samplerate
# handle various stream flags
self.__contains_video = False # contains video
self.__contains_audio = False # contains audio
self.__contains_images = False # contains image-sequence
# handles output parameters through filters
self.__metadata_output = None # handles output stream metadata
self.__output_frames_resolution = "" # handles output stream resolution
self.__output_framerate = "" # handles output stream framerate
self.__output_frames_pixfmt = "" # handles output frame pixel format
# check whether metadata probed or not?
self.__metadata_probed = False

@@ -215,6 +262,22 @@

self.__default_video_framerate = video_rfparams["framerate"]
# parse pixel format
# parse output parameters through filters (if available)
if not (self.__metadata_output is None):
# parse output resolution and framerate
out_video_rfparams = self.__extract_resolution_framerate(
default_stream=default_stream_indexes[0], extract_output=True
)
if out_video_rfparams:
self.__output_frames_resolution = out_video_rfparams["resolution"]
self.__output_framerate = out_video_rfparams["framerate"]
# parse output pixel-format
self.__output_frames_pixfmt = self.__extract_video_pixfmt(
default_stream=default_stream_indexes[0], extract_output=True
)
# parse pixel-format
self.__default_video_pixfmt = self.__extract_video_pixfmt(
default_stream=default_stream_indexes[0]
)
# parse video decoder

@@ -268,3 +331,3 @@ self.__default_video_decoder = self.__extract_video_decoder(

def retrieve_metadata(self, pretty_json=False):
def retrieve_metadata(self, pretty_json=False, force_retrieve_missing=False):
"""

@@ -275,4 +338,5 @@ This method returns Parsed/Probed Metadata of the given source.

pretty_json (bool): whether to return metadata as JSON string(if `True`) or Dictionary(if `False`) type?
force_retrieve_output (bool): whether to also return metadata missing in current Pipeline. This method returns `(metadata, metadata_missing)` tuple if `force_retrieve_output=True` instead of `metadata`.
**Returns:** metadata formatted as JSON string or python dictionary.
**Returns:** `metadata` or `(metadata, metadata_missing)`, formatted as JSON string or python dictionary.
"""

@@ -284,3 +348,3 @@ # check if metadata has been probed or not

# log it
self.__verbose_logs and logger.debug("Retrieving Metadata...")
self.__verbose_logs and logger.debug("Extracting Metadata...")
# create metadata dictionary from information populated in private class variables

@@ -291,14 +355,19 @@ metadata = {

}
metadata_missing = {}
# Only either `source_demuxer` or `source_extension` attribute can be
# present in metadata.
if self.__source_demuxer is None:
metadata.update({"source_extension": os.path.splitext(self.__source)[-1]})
# update missing
force_retrieve_missing and metadata_missing.update({"source_demuxer": ""})
else:
metadata.update({"source_demuxer": self.__source_demuxer})
# update missing
force_retrieve_missing and metadata_missing.update({"source_extension": ""})
# add source video metadata properties
metadata.update(
{"source_extension": self.__source_extension}
if self.__source_demuxer is None
else {"source_demuxer": self.__source_demuxer}
)
metadata.update(
{
"source_video_resolution": self.__default_video_resolution,
"source_video_pixfmt": self.__default_video_pixfmt,
"source_video_framerate": self.__default_video_framerate,
"source_video_pixfmt": self.__default_video_pixfmt,
"source_video_decoder": self.__default_video_decoder,

@@ -319,5 +388,56 @@ "source_duration_sec": self.__default_source_duration,

)
# return metadata as either JSON string or Python dictionary
return json.dumps(metadata, indent=2) if pretty_json else metadata
# add output metadata properties (if available)
if not (self.__metadata_output is None):
metadata.update(
{
"output_frames_resolution": self.__output_frames_resolution,
"output_frames_pixfmt": self.__output_frames_pixfmt,
"output_framerate": self.__output_framerate,
}
)
else:
# since output stream metadata properties are only available when additional
# FFmpeg parameters(such as filters) are defined manually, thereby missing
# output stream properties are handled by assigning them counterpart source
# stream metadata property values
force_retrieve_missing and metadata_missing.update(
{
"output_frames_resolution": self.__default_video_resolution,
"output_frames_pixfmt": self.__default_video_pixfmt,
"output_framerate": self.__default_video_framerate,
}
)
# log it
self.__verbose_logs and logger.debug(
"Metadata Extraction completed successfully!"
)
# parse as JSON string(`json.dumps`), if defined
metadata = json.dumps(metadata, indent=2) if pretty_json else metadata
metadata_missing = (
json.dumps(metadata_missing, indent=2) if pretty_json else metadata_missing
)
# return `metadata` or `(metadata, metadata_missing)`
return metadata if not force_retrieve_missing else (metadata, metadata_missing)
@property
def enumerate_devices(self):
"""
A property object that enumerate all probed Camera Devices connected to your system names
along with their respective "device indexes" or "camera indexes" as python dictionary.
**Returns:** Probed Camera Devices as python dictionary.
"""
# check if metadata has been probed or not
assert (
self.__metadata_probed
), "Source Metadata not been probed yet! Check if you called `probe_stream()` method."
# log if specified
self.__verbose_logs and logger.debug("Enumerating all probed Camera Devices.")
# return probed Camera Devices as python dictionary.
return {
dev_idx: dev for dev_idx, dev in enumerate(self.__extracted_devices_list)
}
def __validate_source(self, source, source_demuxer=None, forced_validate=False):

@@ -333,12 +453,84 @@ """

"""
# validate source demuxer(if defined)
if not (source_demuxer is None):
# check if "auto" demuxer is specified
if source_demuxer == "auto":
# integerise source to get index
index = int(source)
# extract devices list and actual demuxer value
(
self.__extracted_devices_list,
source_demuxer,
) = extract_device_n_demuxer(
self.__ffmpeg,
machine_OS=self.__machine_OS,
verbose=self.__verbose_logs,
)
# valid indexes range
valid_indexes = [
x
for x in range(
-len(self.__extracted_devices_list),
len(self.__extracted_devices_list),
)
]
# check index is within valid range
if self.__extracted_devices_list and index in valid_indexes:
# overwrite actual source device name/path/index
if self.__machine_OS == "Windows":
# Windows OS requires "video=" suffix
self.__source = source = "video={}".format(
self.__extracted_devices_list[index]
)
elif self.__machine_OS == "Darwin":
# Darwin OS requires only device indexes
self.__source = source = (
str(index)
if index >= 0
else str(len(self.__extracted_devices_list) + index)
)
else:
# Linux OS require /dev/video format
self.__source = source = next(
iter(self.__extracted_devices_list[index].keys())
)
# overwrite source_demuxer global variable
self.__source_demuxer = source_demuxer
self.__verbose_logs and logger.debug(
"Successfully configured device `{}` at index `{}` with demuxer `{}`.".format(
self.__extracted_devices_list[index]
if self.__machine_OS != "Linux"
else next(
iter(self.__extracted_devices_list[index].values())
)[0],
index
if index >= 0
else len(self.__extracted_devices_list) + index,
self.__source_demuxer,
)
)
else:
# raise error otherwise
raise ValueError(
"Given source `{}` is not a valid device index. Possible values index values can be: {}".format(
source,
",".join(f"{x}" for x in valid_indexes),
)
)
# otherwise validate against supported demuxers
elif not (source_demuxer in get_supported_demuxers(self.__ffmpeg)):
# raise if fails
raise ValueError(
"Installed FFmpeg failed to recognize `{}` demuxer. Check `source_demuxer` parameter value again!".format(
source_demuxer
)
)
else:
pass
# assert if valid source
assert source and isinstance(
source, str
), "Input source is empty or invalid type!"
# assert if valid source demuxer
assert source_demuxer is None or source_demuxer in get_supported_demuxers(
self.__ffmpeg
), "Installed FFmpeg failed to recognise `{}` demuxer. Check ``source_demuxer`` parameter value again!".format(
source_demuxer
)
), "Input `source` parameter is of invalid type!"
# Differentiate input

@@ -363,13 +555,37 @@ if forced_validate:

raise ValueError("Input source is invalid. Aborting!")
# extract metadata
metadata = check_sp_output(
[self.__ffmpeg]
+ (["-hide_banner"] if not self.__verbose_logs else [])
+ self.__ffmpeg_prefixes
+ (["-f", source_demuxer] if source_demuxer else [])
+ ["-i", source],
force_retrieve_stderr=True,
# format command
if self.__sourcer_params:
# handle additional params separately
meta_cmd = (
[self.__ffmpeg]
+ (["-hide_banner"] if not self.__verbose_logs else [])
+ ["-t", "0.0001"]
+ self.__ffmpeg_prefixes
+ (["-f", source_demuxer] if source_demuxer else [])
+ ["-i", source]
+ dict2Args(self.__sourcer_params)
+ ["-f", "null", "-"]
)
else:
meta_cmd = (
[self.__ffmpeg]
+ (["-hide_banner"] if not self.__verbose_logs else [])
+ self.__ffmpeg_prefixes
+ (["-f", source_demuxer] if source_demuxer else [])
+ ["-i", source]
)
# extract metadata, decode, and filter
metadata = (
check_sp_output(
meta_cmd,
force_retrieve_stderr=True,
)
.decode("utf-8")
.strip()
)
# filter and return
return metadata.decode("utf-8").strip()
# separate input and output metadata (if available)
if "Output #" in metadata:
(metadata, self.__metadata_output) = metadata.split("Output #")
# return metadata based on params
return metadata

@@ -438,3 +654,3 @@ def __extract_video_bitrate(self, default_stream=0):

def __extract_video_pixfmt(self, default_stream=0):
def __extract_video_pixfmt(self, default_stream=0, extract_output=False):
"""

@@ -449,7 +665,15 @@ This Internal method parses default video-stream pixel-format from metadata.

identifiers = ["Video:", "Stream #"]
meta_text = [
line.strip()
for line in self.__ffsp_output.split("\n")
if all(x in line for x in identifiers)
]
meta_text = (
[
line.strip()
for line in self.__ffsp_output.split("\n")
if all(x in line for x in identifiers)
]
if not extract_output
else [
line.strip()
for line in self.__metadata_output.split("\n")
if all(x in line for x in identifiers)
]
)
if meta_text:

@@ -514,3 +738,3 @@ selected_stream = meta_text[

def __extract_resolution_framerate(self, default_stream=0):
def __extract_resolution_framerate(self, default_stream=0, extract_output=False):
"""

@@ -521,2 +745,3 @@ This Internal method parses default video-stream resolution and framerate from metadata.

default_stream (int): selects specific audio-stream in case of multiple ones.
extract_output (bool): Whether to extract from output(if true) or input(if false) stream?

@@ -526,7 +751,16 @@ **Returns:** Default Video resolution and framerate as dictionary value.

identifiers = ["Video:", "Stream #"]
meta_text = [
line.strip()
for line in self.__ffsp_output.split("\n")
if all(x in line for x in identifiers)
]
# use output metadata if available
meta_text = (
[
line.strip()
for line in self.__ffsp_output.split("\n")
if all(x in line for x in identifiers)
]
if not extract_output
else [
line.strip()
for line in self.__metadata_output.split("\n")
if all(x in line for x in identifiers)
]
)
result = {}

@@ -533,0 +767,0 @@ if meta_text:

@@ -81,3 +81,3 @@ """

# define logger
logger = logging.getLogger("Utilies")
logger = logging.getLogger("Utilities")
logger.propagate = False

@@ -136,1 +136,30 @@ logger.addHandler(logger_handler())

logger.exception(str(e))
def validate_device_index(index):
"""
## validate_device_index
Validates if given device index is valid or not? Only Integers or String of integers are valid indexes.
Parameters:
index (int/str): Index of the device
**Returns:** A boolean value, confirming whether valid, or not?.
"""
# validate index value
if isinstance(index, int):
# return true
return True
elif isinstance(index, str):
# remove any whitespaces
index.replace(" ", "")
# return true
return (
True
if (index.isnumeric() or (index.startswith("-") and index[1:].isnumeric()))
else False
)
else:
# return false otherwise
return False

@@ -1,1 +0,1 @@

__version__ = "0.2.3"
__version__ = "0.2.4"
+21
-15
Metadata-Version: 2.1
Name: deffcode
Version: 0.2.3
Summary: A Cross-platform High-performance & Flexible Video Frames Decoder in python.
Version: 0.2.4
Summary: A cross-platform High-performance & Flexible Real-time Video Frames Decoder in Python.
Home-page: https://abhitronix.github.io/deffcode

@@ -86,7 +86,7 @@ Author: Abhishek Thakur

**Highly Adaptive -** _DeFFcode APIs implements a **standalone highly-extensible wrapper around [FFmpeg][ffmpeg]** multimedia framework. These APIs **supports a wide-ranging media streams as input** source such as [live USB/Virtual/IP camera feeds][capturing-and-previewing-frames-from-a-webcam], [regular multimedia files][capturing-rgb-frames-from-a-video-file], [screen recordings][capturing-and-previewing-frames-from-your-desktop], [image sequences][decoding-image-sequences], [network URL schemes][decoding-network-streams] (such as HTTP(s), RTP/RSTP, etc.), so on and so forth._
**Highly Adaptive -** _DeFFcode APIs implements a **standalone highly-extensible wrapper around [FFmpeg][ffmpeg]** multimedia framework. These APIs **supports a wide-ranging media streams as input** source such as [live USB/Virtual/IP camera feeds][capturing-and-previewing-frames-from-a-webcam], [regular multimedia files][decoding-video-files], [screen recordings][capturing-and-previewing-frames-from-your-desktop], [image sequences][decoding-image-sequences], [network URL schemes][decoding-network-streams] (such as HTTP(s), RTP/RSTP, etc.), so on and so forth._
**Highly Flexible -** _DeFFcode APIs gains an edge over other FFmpeg Wrappers by providing **complete control over the underline pipeline** including **access to almost any FFmpeg specification thinkable** such as specifying framerate, resolution, hardware decoder(s), filtergraph(s), and pixel-format(s) that are readily **supported by all well known Computer Vision libraries**._
**Highly Convenient -** _FFmpeg has a steep learning curve especially for users unfamiliar with a command line interface. DeFFcode helps users by maintaining the **same standard [OpenCV-Python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) (Python API for OpenCV) coding syntax for its APIs**, thereby making it even **easier to learn, create, and develop FFmpeg based apps** in Python._
**Highly Convenient -** _FFmpeg has a steep learning curve especially for users unfamiliar with a command line interface. DeFFcode helps users by providing similar to OpenCV, [**Index based Camera Device Capturing**][decoding-camera-devices-using-indexes] and the **same standard [OpenCV-Python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) (Python API for OpenCV) coding syntax for its APIs**, thereby making it even **easier to learn, create, and develop FFmpeg based apps** in Python._

@@ -103,2 +103,3 @@ &nbsp;

- Curated list of well-documented recipes ranging from [**Basic**][basic-recipes] to [**Advanced**][advanced-recipes] skill levels.
- Hands down the easiest [**Index based Camera Device Capturing**][decoding-camera-devices-using-indexes], similar to OpenCV.
- Easy to code **Real-time [Simple][transcoding-live-simple-filtergraphs] & [Complex][transcoding-live-complex-filtergraphs] Filtergraphs**. _(Yes, You read it correctly "Real-time"!)_

@@ -159,4 +160,4 @@ - Lightning fast dedicated **GPU-Accelerated Video [Decoding][hardware-accelerated-video-decoding] & [Transcoding][hardware-accelerated-video-transcoding]**.

- [Playing with any other FFmpeg pixel formats][capturing-and-previewing-bgr-frames-from-a-video-file]
- [Capturing and Previewing frames from a Webcam][capturing-and-previewing-frames-from-a-webcam]
- [Capturing and Previewing frames from your Desktop][capturing-and-previewing-frames-from-your-desktop] _(Screen Recording)_
- [Enumerating all Camera Devices with Indexes][enumerating-all-camera-devices-with-indexes]
- [Capturing and Previewing frames from a Camera using Indexes][capturing-and-previewing-frames-from-a-camera-using-indexes]
- [Capturing and Previewing frames from a HTTPs Stream][capturing-and-previewing-frames-from-a-https-stream]

@@ -213,2 +214,4 @@ - [Capturing and Previewing frames from a RTSP/RTP Stream][capturing-and-previewing-frames-from-a-rtsprtp-stream]

- [Generate and Decode frames from Game of Life Visualization][generate-and-decode-frames-from-game-of-life-visualization]
- [Capturing and Previewing frames from a Webcam using Custom Demuxer][capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer]
- [Capturing and Previewing frames from your Desktop][capturing-and-previewing-frames-from-your-desktop] _(Screen Recording)_
- [GPU-accelerated Hardware-based Video Decoding][gpu-accelerated-hardware-based-video-decoding]

@@ -352,3 +355,3 @@

[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6976948.svg)](https://doi.org/10.5281/zenodo.6976948)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6984364.svg)](https://doi.org/10.5281/zenodo.6984364)

@@ -358,9 +361,9 @@ ```BibTeX

author = {Abhishek Thakur},
title = {abhiTronix/deffcode: v0.2.2},
title = {abhiTronix/deffcode: v0.2.3},
month = aug,
year = 2022,
publisher = {Zenodo},
version = {v0.2.2},
doi = {10.5281/zenodo.6976948},
url = {https://doi.org/10.5281/zenodo.6976948}
version = {v0.2.3},
doi = {10.5281/zenodo.6984364},
url = {https://doi.org/10.5281/zenodo.6984364}
}

@@ -401,3 +404,3 @@ ```

[license]:https://github.com/abhiTronix/deffcode/blob/master/LICENSE
[help]:https://abhitronix.github.io/deffcode/latest/https://abhitronix.github.io/deffcode/latest/help/get_help
[help]:https://abhitronix.github.io/deffcode/latest/help/get_help
[installation-notes]:https://abhitronix.github.io/deffcode/latest/installation/#installation-notes

@@ -413,3 +416,3 @@ [ffdecoder-api]:https://abhitronix.github.io/deffcode/latest/reference/ffdecoder/#ffdecoder-api

[decoding-video-files]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-video-files/#decoding-video-files
[decoding-live-feed-devices]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#decoding-live-feed-devices
[decoding-camera-devices-using-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#decoding-camera-devices-using-indexes
[decoding-network-streams]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#decoding-network-streams

@@ -425,4 +428,4 @@ [decoding-image-sequences]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-image-sequences/#decoding-image-sequences

[capturing-and-previewing-bgr-frames-from-a-video-file]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-video-files/#capturing-and-previewing-bgr-frames-from-a-video-file
[capturing-and-previewing-frames-from-a-webcam]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#capturing-and-previewing-frames-from-a-webcam
[capturing-and-previewing-frames-from-your-desktop]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#capturing-and-previewing-frames-from-your-desktop
[enumerating-all-camera-devices-with-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#enumerating-all-camera-devices-with-indexes
[capturing-and-previewing-frames-from-a-camera-using-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#capturing-and-previewing-frames-from-a-camera-using-indexes
[capturing-and-previewing-frames-from-a-https-stream]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#capturing-and-previewing-frames-from-a-https-stream

@@ -449,2 +452,3 @@ [capturing-and-previewing-frames-from-a-rtsprtp-stream]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#capturing-and-previewing-frames-from-a-rtsprtp-stream

[decoding-live-virtual-sources]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-virtual-sources/#decoding-live-virtual-sources
[decoding-live-feed-devices]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#decoding-live-feed-devices
[hardware-accelerated-video-decoding]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-hw-acceleration/#hardware-accelerated-video-decoding

@@ -469,2 +473,4 @@ [transcoding-live-complex-filtergraphs]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-live-frames-complexgraphs/#transcoding-live-complex-filtergraphs

[transcoding-video-art-with-pixelation-effect]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-art-filtergraphs/#transcoding-video-art-with-pixelation-effect
[capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer
[capturing-and-previewing-frames-from-your-desktop]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#capturing-and-previewing-frames-from-your-desktop
[gpu-accelerated-hardware-based-video-transcoding-with-writegear-api]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-hw-acceleration/#gpu-accelerated-hardware-based-video-transcoding-with-writegear-api

@@ -471,0 +477,0 @@ [added-new-attributes-to-metadata-in-ffdecoder-api]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/update-metadata/#added-new-attributes-to-metadata-in-ffdecoder-api

+19
-13

@@ -53,7 +53,7 @@ <!--

**<ins>Highly Adaptive</ins> -** _DeFFcode APIs implements a **standalone highly-extensible wrapper around [FFmpeg][ffmpeg]** multimedia framework. These APIs **supports a wide-ranging media streams as input** source such as [live USB/Virtual/IP camera feeds][capturing-and-previewing-frames-from-a-webcam], [regular multimedia files][capturing-rgb-frames-from-a-video-file], [screen recordings][capturing-and-previewing-frames-from-your-desktop], [image sequences][decoding-image-sequences], [network URL schemes][decoding-network-streams] (such as HTTP(s), RTP/RSTP, etc.), so on and so forth._
**<ins>Highly Adaptive</ins> -** _DeFFcode APIs implements a **standalone highly-extensible wrapper around [FFmpeg][ffmpeg]** multimedia framework. These APIs **supports a wide-ranging media streams as input** source such as [live USB/Virtual/IP camera feeds][capturing-and-previewing-frames-from-a-webcam], [regular multimedia files][decoding-video-files], [screen recordings][capturing-and-previewing-frames-from-your-desktop], [image sequences][decoding-image-sequences], [network URL schemes][decoding-network-streams] (such as HTTP(s), RTP/RSTP, etc.), so on and so forth._
**<ins>Highly Flexible</ins> -** _DeFFcode APIs gains an edge over other FFmpeg Wrappers by providing **complete control over the underline pipeline** including **access to almost any FFmpeg specification thinkable** such as specifying framerate, resolution, hardware decoder(s), filtergraph(s), and pixel-format(s) that are readily **supported by all well known Computer Vision libraries**._
**<ins>Highly Convenient</ins> -** _FFmpeg has a steep learning curve especially for users unfamiliar with a command line interface. DeFFcode helps users by maintaining the **same standard [OpenCV-Python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) (Python API for OpenCV) coding syntax for its APIs**, thereby making it even **easier to learn, create, and develop FFmpeg based apps** in Python._
**<ins>Highly Convenient</ins> -** _FFmpeg has a steep learning curve especially for users unfamiliar with a command line interface. DeFFcode helps users by providing similar to OpenCV, [**Index based Camera Device Capturing**][decoding-camera-devices-using-indexes] and the **same standard [OpenCV-Python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) (Python API for OpenCV) coding syntax for its APIs**, thereby making it even **easier to learn, create, and develop FFmpeg based apps** in Python._

@@ -70,2 +70,3 @@ &nbsp;

- Curated list of well-documented recipes ranging from [**Basic**][basic-recipes] to [**Advanced**][advanced-recipes] skill levels.
- Hands down the easiest [**Index based Camera Device Capturing**][decoding-camera-devices-using-indexes], similar to OpenCV.
- Easy to code **Real-time [Simple][transcoding-live-simple-filtergraphs] & [Complex][transcoding-live-complex-filtergraphs] Filtergraphs**. _(Yes, You read it correctly "Real-time"!)_

@@ -126,4 +127,4 @@ - Lightning fast dedicated **GPU-Accelerated Video [Decoding][hardware-accelerated-video-decoding] & [Transcoding][hardware-accelerated-video-transcoding]**.

- [Playing with any other FFmpeg pixel formats][capturing-and-previewing-bgr-frames-from-a-video-file]
- [Capturing and Previewing frames from a Webcam][capturing-and-previewing-frames-from-a-webcam]
- [Capturing and Previewing frames from your Desktop][capturing-and-previewing-frames-from-your-desktop] _(Screen Recording)_
- [Enumerating all Camera Devices with Indexes][enumerating-all-camera-devices-with-indexes]
- [Capturing and Previewing frames from a Camera using Indexes][capturing-and-previewing-frames-from-a-camera-using-indexes]
- [Capturing and Previewing frames from a HTTPs Stream][capturing-and-previewing-frames-from-a-https-stream]

@@ -180,2 +181,4 @@ - [Capturing and Previewing frames from a RTSP/RTP Stream][capturing-and-previewing-frames-from-a-rtsprtp-stream]

- [Generate and Decode frames from Game of Life Visualization][generate-and-decode-frames-from-game-of-life-visualization]
- [Capturing and Previewing frames from a Webcam using Custom Demuxer][capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer]
- [Capturing and Previewing frames from your Desktop][capturing-and-previewing-frames-from-your-desktop] _(Screen Recording)_
- [GPU-accelerated Hardware-based Video Decoding][gpu-accelerated-hardware-based-video-decoding]

@@ -319,3 +322,3 @@

[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6976948.svg)](https://doi.org/10.5281/zenodo.6976948)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6984364.svg)](https://doi.org/10.5281/zenodo.6984364)

@@ -325,9 +328,9 @@ ```BibTeX

author = {Abhishek Thakur},
title = {abhiTronix/deffcode: v0.2.2},
title = {abhiTronix/deffcode: v0.2.3},
month = aug,
year = 2022,
publisher = {Zenodo},
version = {v0.2.2},
doi = {10.5281/zenodo.6976948},
url = {https://doi.org/10.5281/zenodo.6976948}
version = {v0.2.3},
doi = {10.5281/zenodo.6984364},
url = {https://doi.org/10.5281/zenodo.6984364}
}

@@ -368,3 +371,3 @@ ```

[license]:https://github.com/abhiTronix/deffcode/blob/master/LICENSE
[help]:https://abhitronix.github.io/deffcode/latest/https://abhitronix.github.io/deffcode/latest/help/get_help
[help]:https://abhitronix.github.io/deffcode/latest/help/get_help
[installation-notes]:https://abhitronix.github.io/deffcode/latest/installation/#installation-notes

@@ -380,3 +383,3 @@ [ffdecoder-api]:https://abhitronix.github.io/deffcode/latest/reference/ffdecoder/#ffdecoder-api

[decoding-video-files]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-video-files/#decoding-video-files
[decoding-live-feed-devices]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#decoding-live-feed-devices
[decoding-camera-devices-using-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#decoding-camera-devices-using-indexes
[decoding-network-streams]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#decoding-network-streams

@@ -392,4 +395,4 @@ [decoding-image-sequences]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-image-sequences/#decoding-image-sequences

[capturing-and-previewing-bgr-frames-from-a-video-file]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-video-files/#capturing-and-previewing-bgr-frames-from-a-video-file
[capturing-and-previewing-frames-from-a-webcam]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#capturing-and-previewing-frames-from-a-webcam
[capturing-and-previewing-frames-from-your-desktop]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-live-feed-devices/#capturing-and-previewing-frames-from-your-desktop
[enumerating-all-camera-devices-with-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#enumerating-all-camera-devices-with-indexes
[capturing-and-previewing-frames-from-a-camera-using-indexes]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-camera-devices/#capturing-and-previewing-frames-from-a-camera-using-indexes
[capturing-and-previewing-frames-from-a-https-stream]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#capturing-and-previewing-frames-from-a-https-stream

@@ -416,2 +419,3 @@ [capturing-and-previewing-frames-from-a-rtsprtp-stream]:https://abhitronix.github.io/deffcode/latest/recipes/basic/decode-network-streams/#capturing-and-previewing-frames-from-a-rtsprtp-stream

[decoding-live-virtual-sources]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-virtual-sources/#decoding-live-virtual-sources
[decoding-live-feed-devices]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#decoding-live-feed-devices
[hardware-accelerated-video-decoding]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-hw-acceleration/#hardware-accelerated-video-decoding

@@ -436,2 +440,4 @@ [transcoding-live-complex-filtergraphs]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-live-frames-complexgraphs/#transcoding-live-complex-filtergraphs

[transcoding-video-art-with-pixelation-effect]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-art-filtergraphs/#transcoding-video-art-with-pixelation-effect
[capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#capturing-and-previewing-frames-from-a-webcam-using-custom-demuxer
[capturing-and-previewing-frames-from-your-desktop]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/decode-live-feed-devices/#capturing-and-previewing-frames-from-your-desktop
[gpu-accelerated-hardware-based-video-transcoding-with-writegear-api]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/transcode-hw-acceleration/#gpu-accelerated-hardware-based-video-transcoding-with-writegear-api

@@ -438,0 +444,0 @@ [added-new-attributes-to-metadata-in-ffdecoder-api]:https://abhitronix.github.io/deffcode/latest/recipes/advanced/update-metadata/#added-new-attributes-to-metadata-in-ffdecoder-api

@@ -26,2 +26,3 @@ """

# parse PKG version
pkg_version = {}

@@ -32,2 +33,4 @@ ver_path = convert_path("deffcode/version.py")

# apply various patches to README text and prepare
# valid long_description
with open("README.md", "r", encoding="utf-8") as fh:

@@ -60,3 +63,3 @@ long_description = fh.read()

version=pkg_version["__version__"],
description="A Cross-platform High-performance & Flexible Video Frames Decoder in python.",
description="A cross-platform High-performance & Flexible Real-time Video Frames Decoder in Python.",
license="Apache License 2.0",

@@ -63,0 +66,0 @@ author="Abhishek Thakur",