deffcode
Advanced tools
| 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 @@ | ||
| [](https://doi.org/10.5281/zenodo.6976948) | ||
| [](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 |
+333
-150
@@ -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.") |
+192
-3
@@ -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 |
+301
-67
@@ -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: |
+30
-1
@@ -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 @@ | ||
| - 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 @@ | ||
| [](https://doi.org/10.5281/zenodo.6976948) | ||
| [](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 @@ | ||
| - 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 @@ | ||
| [](https://doi.org/10.5281/zenodo.6976948) | ||
| [](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 |
+4
-1
@@ -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", |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
217755
17.28%2396
33.56%