deffcode
Advanced tools
+159
-120
| Metadata-Version: 2.1 | ||
| Name: deffcode | ||
| Version: 0.1.0 | ||
| Summary: Performant Pythonic FFmpeg Decoder with easy to adapt flexible API. | ||
| Version: 0.2.0 | ||
| Summary: High-performance Real-time Video frames Generator for generating blazingly fast video frames in python. | ||
| Home-page: https://abhitronix.github.io/deffcode | ||
@@ -37,3 +37,3 @@ Author: Abhishek Thakur | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -56,106 +56,110 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| <h1 align="center"> | ||
| <i>de</i><b>FF</b><i>code</i> | ||
| </h1> | ||
| <p align="center">Performant ⚡️ Pythonic FFmpeg Decoder with easy to adapt flexible API 🐍.</p> | ||
| <h2 align="center"> | ||
| </h2> | ||
| <div align="center"> | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/deffcode.png" alt="DeFFcode" title="Logo designed by Abhishek Thakur(@abhiTronix), under CC-BY-NC-SA 4.0 License" width="80%"/> | ||
| </div> | ||
| <div align="center"> | ||
| | ||
| [![Build Status][github-cli]][github-flow] [![Codecov branch][codecov]][code] [![Azure DevOps builds (branch)][azure-badge]][azure-pipeline] | ||
| **deffcode** is a Performant and Robust FFmpeg Pythonic Wrapper that aimed at decoding any stream that you throw at it. Requiring minimal efforts, deffcode provides an easy-to-adapt flexible API to read a wide range of streams, and can ingest video using any decoder(even hardware ones) into any pixel format ffmpeg supports. It also provides pin-point accurate seeking for extracting only a specific part of your input as required. | ||
| [![Glitter chat][gitter-bagde]][gitter] [![Build Status][appveyor]][app] [![PyPi version][pypi-badge]][pypi] | ||
| It is cross-platform, runs on Python 3.7+, and is easy to install. | ||
| [![Code Style][black-badge]][black] | ||
| | ||
| </div> | ||
| <div align="center"> | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/deffcode-tagline.png" alt="DeFFcode tagline" width="40%"/> | ||
| ## Examples | ||
| ### Basic Example | ||
| ---- | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| [Releases][release] | [Documentation][docs] | [Installation][install] | [License](https://github.com/abhiTronix/deffcode#copyright) | ||
| # initialize and formulate the decoder | ||
| decoder = FFdecoder("foo.mp4").formulate() | ||
| ---- | ||
| # grab the RGB24(default) frame from | ||
| # the decoder(generator) | ||
| for frame in decoder.generateFrame(): | ||
| print(frame.shape) | ||
| </div> | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
|   | ||
| The output: | ||
| ## Overview | ||
| > DeFFcode is a powerful High-performance Real-time **Video frames Generator** that wraps FFmpeg pipeline inside a subprocess module for generating blazingly fast video frames in python 🔥 | ||
| The primary purpose of DeFFcode is to provide a cross-platform solution for fast and low-overhead decoding of a wide range of video streams into 3D [`ndarray`](https://numpy.org/doc/stable/reference/arrays.ndarray.html#the-n-dimensional-array-ndarray) frames while providing **complete control over the underlying FFmpeg pipeline** without the need to go deeper into hefty documentation and in just a few lines of python code. | ||
| DeFFcode can **extract frames in real-time with any custom specification imaginable** such as Framerate, Resolution, Hardware decoding, Complex Filters into any pixel format while giving users the complete freedom to play with any desired FFmpeg supported parameter. On top of that, DeFFcode enables **effortless and precise FFmpeg Frame Seeking** natively. | ||
| Finally, DeFFcode APIs are designed with **simplicity, flexibility, and modularity** in mind for the best developer experience. | ||
|   | ||
| ## Key Features | ||
| DeFFcode APIs are build on [**FFmpeg**][ffmpeg] - a leading multimedia framework, that gives you the following: | ||
| - Extremely exceptional real-time performance ⚡ with low-memory footprints. | ||
| - Flexible API with access to almost every parameter available within FFmpeg. | ||
| - Fast dedicated [Hardware-Accelerated Decoding](https://abhitronix.github.io/deffcode/latest/examples/advanced/#gpu-enabled-hardware-accelerated-decoding). | ||
| - Precise FFmpeg [Frame Seeking](https://abhitronix.github.io/deffcode/latest/examples/basic/#saving-keyframes-as-image) with pinpoint accuracy. | ||
| - Extensive support for real-time [Complex FFmpeg Filters](https://abhitronix.github.io/deffcode/latest/examples/advanced/#generating-video-with-complex-filter-applied). | ||
| - Out-of-the-box support for Computer Vision libraries like OpenCV, Pytorch, etc. | ||
| - Support a wide range of media files, devices, image-sequence and network streams. | ||
| - Easier to ingest streams into any pixel format that FFmpeg supports. | ||
| - Lossless Transcoding support with [WriteGear](https://abhitronix.github.io/deffcode/latest/gears/writegear/introduction/). | ||
| - Fewer hard dependencies, and easy to install. | ||
| - Designed modular for best developer experience. | ||
| - Cross-platform and runs on Python 3.7+ | ||
| | ||
| ## Requirements | ||
| - Python 3.7+ | ||
| - FFmpeg _(See [this doc](https://abhitronix.github.io/deffcode/latest/installation/ffmpeg_install/) for its installation)_ | ||
| | ||
| ## Installation | ||
| Installation is as simple as: | ||
| ```sh | ||
| (720, 1280, 3) | ||
| (720, 1280, 3) | ||
| ... | ||
| ... | ||
| ... | ||
| (720, 1280, 3) | ||
| $ (sudo) pip install deffcode | ||
| ``` | ||
| ### Basic OpenCV Example | ||
| 💡 For more details, see [installation notes][install]. | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| import cv2 | ||
| | ||
| # initialize and formulate the decoder for BGR24 output | ||
| decoder = FFdecoder("foo.mp4", frame_format="bgr24").formulate() | ||
| # loop over frames | ||
| while True: | ||
| # grab the BGR24 frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| ## Getting Started | ||
| # check if frame is None | ||
| if frame is None: | ||
| break | ||
| # Show output window | ||
| cv2.imshow("Output", frame) | ||
| --- | ||
| # check for 'q' key if pressed | ||
| key = cv2.waitKey(1) & 0xFF | ||
| if key == ord("q"): | ||
| break | ||
| **📚 Documentation: https://abhitronix.github.io/deffcode** | ||
| # close output window | ||
| cv2.destroyAllWindows() | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
| --- | ||
| ### Basic PIL Example | ||
| The default function of DeFFcode's [FFdecoder API](https://abhitronix.github.io/deffcode/latest/reference/ffdecoder/#ffdecoder-api) is to generate 24-bit RGB (3D [`ndarray`](https://numpy.org/doc/stable/reference/arrays.ndarray.html#the-n-dimensional-array-ndarray)) frames from the given source: | ||
| ```python | ||
| ```py title="grab_frames.py" | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| from PIL import Image | ||
| # define the FFmpeg parameter to seek | ||
| # to 00:00:01 in time and get one single frame | ||
| extraparams = {"-ss": "00:00:01", "-frames:v":1} | ||
| # formulate the decoder with suitable source(for e.g. foo.mp4) | ||
| decoder = FFdecoder("foo.mp4").formulate() | ||
| # initialize and formulate the decode | ||
| decoder = FFdecoder("foo.mp4", **extraparams).formulate() | ||
| # grab RGB24(default) 3D frames from decoder | ||
| for frame in decoder.generateFrame(): | ||
| # lets print its shape | ||
| print(frame.shape) | ||
| # grab the RGB24(default) frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| # print metadata as `json.dump` | ||
| print(decoder.metadata) | ||
| # check if frame is None | ||
| if not(frame is None) | ||
| # Convert and Show output window | ||
| im = Image.fromarray(frame) | ||
| im.show() | ||
| # terminate the decoder | ||
@@ -165,40 +169,34 @@ decoder.terminate() | ||
| ### Basic Matplotlib Example | ||
| For more examples and in-depth usage guide, kindly refer our **[Basic Recipes 🥧](https://abhitronix.github.io/deffcode/latest/examples/basic)** and **[Advanced Recipes 🔬](https://abhitronix.github.io/deffcode/latest/examples/advanced)** | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| import matplotlib.pyplot as plt | ||
| # define the FFmpeg parameter to seek | ||
| # to 00:00:02.01 in time and get one single frame | ||
| extraparams = {"-ss": "00:00:02.01", "-frames:v":1} | ||
| 💡 In case you're run into any problems, consult our [Help](https://abhitronix.github.io/deffcode/latest/help/) section. | ||
| # initialize and formulate the decode for Grayscale output | ||
| decoder = FFdecoder("foo.mp4", frame_format="gray", **extraparams).formulate() | ||
| # grab single Grayscale frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| | ||
| # Show output window | ||
| plt.imshow(frame, cmap='gray', vmin=0, vmax=255) | ||
| plt.show() | ||
| ## Roadmap | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
| - [x] Add clean and elegant documentation. | ||
| - [x] Add project Issue and PR templates. | ||
| - [x] Add related unit tests with `pytests`. | ||
| - [x] Automate stuff with Continuous Integration. | ||
| - [ ] Add Multiple Source Inputs support. | ||
| - [ ] Add Devices and Screen Capture support. | ||
| - [ ] Resolve High CPU usage issue with WriteGear API. | ||
| - [ ] Add more parameters to Sourcer API's metadata. | ||
| - [ ] Implement Buffer and Audio pass-through modes. | ||
| - [ ] Add recipe for Source with Multiple Embedded Streams. | ||
| - [ ] Add example to dynamically change writable FFdecoder API's metadata parameters. | ||
| - [ ] Add more Advanced Recipes and use cases. | ||
| - [ ] Add preliminary benchmarks. | ||
| - [ ] Make Frame Seeking dynamic. | ||
| | ||
| ## Dependencies | ||
| ## Contributions | ||
| Minimal requirements: | ||
| - Python 3.7+ | ||
| - FFmpeg (See [this](https://abhitronix.github.io/vidgear/latest/gears/writegear/compression/advanced/ffmpeg_install/#ffmpeg-installation-instructions) for its installation) | ||
| - NumPy >=1.20.0 | ||
| - requests | ||
| - colorlog | ||
| - tqdm | ||
| > Contributions are welcome. We'd love to have your contributions to fix bugs or to implement new features! | ||
| :bulb: These requirements are installed automatically(except FFmpeg). | ||
| Please see our **[Contribution Guidelines](https://abhitronix.github.io/deffcode/latest/contribution/)** for more details. | ||
@@ -208,29 +206,70 @@ | ||
| ## Installation | ||
| ## Community Support | ||
| Join our Gitter community channel for quick discussions: | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install -U deffcode | ||
| ``` | ||
| [](https://gitter.im/deffcode-python/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
| **And if you prefer to install deffcode directly from the repository:** | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install git+git://github.com/abhiTronix/deffcode@master#egg=deffcode | ||
| ``` | ||
| | ||
| **Or you can also download its wheel (`.whl`) package from our repository's [releases](https://github.com/abhiTronix/deffcode/releases) section, and thereby can be installed as follows:** | ||
| # Donations | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install deffcode-0.1.0-py3-none-any.whl | ||
| ``` | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/help_us.png" alt="PiGear" width="50%" /> | ||
| > DeFFcode is free and open source and will always remain so. ❤️ | ||
| It is something I am doing with my own free time. If you would like to say thanks, please feel free to make a donation: | ||
| [![ko-fi][kofi-badge]][kofi] | ||
| | ||
| ---- | ||
| <p align="center"><i><b>deffcode</b> is <a href="https://github.com/abhiTronix/deffcode/blob/master/LICENSE.md">Apache 2.0 Licensed</a> code.<br/>Designed & crafted with care.</i></br>⭐️</p> | ||
| # Copyright | ||
| **Copyright (c) abhiTronix 2021** | ||
| This library is released under the **[Apache 2.0 License][license]**. | ||
| <!-- | ||
| Badges | ||
| --> | ||
| [appveyor]:https://img.shields.io/appveyor/ci/abhitronix/deffcode.svg?style=for-the-badge&logo=appveyor | ||
| [codecov]:https://img.shields.io/codecov/c/gh/abhiTronix/deffcode?logo=codecov&style=for-the-badge&token=zrES4mwVKe | ||
| [github-cli]:https://img.shields.io/github/workflow/status/abhiTronix/deffcode/GitHub%20Action%20workflow%20for%20Linux?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMWE5IDkgMCAwMTkgOSA5IDkgMCAwMS05IDkgOSA5IDAgMDEtOS05IDkgOSAwIDAxOS05ek0yMyAxOWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyek0yMyAzNWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyeiIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1wcmltYXJ5LCAjMjA4OEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MSAzNWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyeiIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1zZWNvbmRhcnksICM3OUI4RkYpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0yNS4wMzcgMjMuNjA3bC0zLjA3IDMuMDY1LTEuNDktMS40ODUiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tcHJpbWFyeSwgIzIwODhGRikiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNDEgMTlhNiA2IDAgMTEwIDEyIDYgNiAwIDAxMC0xMnoiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tcHJpbWFyeSwgIzIwODhGRikiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTQzLjAzNiAyMy42MDdsLTMuMDY5IDMuMDY1LTEuNDktMS40ODVNNyA2LjgxMmExIDEgMCAwMTEuNTMzLS44NDZsNS4xMTMgMy4yMmExIDEgMCAwMS0uMDA2IDEuNjk3bC01LjExMyAzLjE3QTEgMSAwIDAxNyAxMy4yMDNWNi44MTN6TTkgMTl2MTVjMCAzLjg2NiAzLjE3NyA3IDcgN2gxIiBzdHJva2U9InZhcigtLWNvbG9yLW1hcmtldGluZy1pY29uLXByaW1hcnksICMyMDg4RkYpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xNi45NDkgMjZhMSAxIDAgMTAwLTJ2MnpNOCAxOS4wMzVBNi45NjUgNi45NjUgMCAwMDE0Ljk2NSAyNnYtMkE0Ljk2NSA0Ljk2NSAwIDAxMTAgMTkuMDM1SDh6TTE0Ljk2NSAyNmgxLjk4NHYtMmgtMS45ODR2MnoiIGZpbGw9InZhcigtLWNvbG9yLW1hcmtldGluZy1pY29uLXByaW1hcnksICMyMDg4RkYpIi8+PHBhdGggZD0iTTI5LjA1NSAyNWg1Ljk0NCIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1wcmltYXJ5LCAjMjA4OEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTIxIDQwYTEgMSAwIDExLS4wMDEgMi4wMDFBMSAxIDAgMDEyMSA0MHpNMjUgNDBhMSAxIDAgMTEtLjAwMSAyLjAwMUExIDEgMCAwMTI1IDQweiIgZmlsbD0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tc2Vjb25kYXJ5LCAjNzlCOEZGKSIvPjxwYXRoIGQ9Ik0zNC4wMDUgNDEuMDA3bC0xLjAxMy4wMzMiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tc2Vjb25kYXJ5LCAjNzlCOEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4= | ||
| [prs-badge]:https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABC0lEQVRYhdWVPQoCMRCFX6HY2ghaiZUXsLW0EDyBrbWtN/EUHsHTWFnYyCL4gxibVZZlZzKTnWz0QZpk5r0vIdkF/kBPAMOKeddE+CQPKoc5Yt5cTjBMdQSwDQToWgBJAn3jmhqgltapAV6E6b5U17MGGAUaUj07TficMfIBZDV6vxowBm1BP9WbSQE4o5h9IjPJmy73TEPDDxVmoZdQrQ5jRhly9Q8tgMUXkIIWn0oG4GYQfAXQzz1PGoCiQndM7b4RgJay/h7zBLT3hASgoKjamQJMreKf0gfuAGyYtXEIAKcL/Dss15iq6ohXghozLYiAMxPuACwtIT4yeQUxAaLrZwAoqGRKGk7qDSYTfYQ8LuYnAAAAAElFTkSuQmCC | ||
| [azure-badge]:https://img.shields.io/azure-devops/build/abhiuna12/942b3b13-d745-49e9-8d7d-b3918ff43ac2/3/master?logo=azure-pipelines&style=for-the-badge | ||
| [pypi-badge]:https://img.shields.io/pypi/v/deffcode.svg?style=for-the-badge&logo=pypi | ||
| [gitter-bagde]:https://img.shields.io/badge/Chat-Gitter-blueviolet.svg?style=for-the-badge&logo=gitter | ||
| [Coffee-badge]:https://abhitronix.github.io/img/deffcode/orange_img.png | ||
| [kofi-badge]:https://www.ko-fi.com/img/githubbutton_sm.svg | ||
| [black-badge]:https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge&logo=github | ||
| <!-- | ||
| Internal URLs | ||
| --> | ||
| [docs]:https://abhitronix.github.io/deffcode/latest/ | ||
| [install]:https://abhitronix.github.io/deffcode/latest/installation/ | ||
| [release]:https://github.com/abhiTronix/deffcode/releases/latest | ||
| [license]:https://github.com/abhiTronix/deffcode/blob/master/LICENSE | ||
| [github-flow]:https://github.com/abhiTronix/deffcode/actions/workflows/CIlinux.yml | ||
| [azure-pipeline]:https://dev.azure.com/abhiuna12/public/_build?definitionId=3 | ||
| [app]:https://ci.appveyor.com/project/abhiTronix/deffcode | ||
| [code]:https://codecov.io/gh/abhiTronix/deffcode | ||
| [black]: https://github.com/psf/black | ||
| <!-- | ||
| External URLs | ||
| --> | ||
| [ffmpeg]:https://www.ffmpeg.org/ | ||
| [pypi]:https://pypi.org/project/deffcode/ | ||
| [gitter]:https://gitter.im/deffcode-python/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge | ||
| [coffee]:https://www.buymeacoffee.com/2twOXFvlA | ||
| [kofi]: https://ko-fi.com/W7W8WTYO | ||
@@ -1,2 +0,2 @@ | ||
| """deffcode - Performant Pythonic FFmpeg Decoder with easy to adapt flexible API.""" | ||
| """DeFFcode - High-performance Real-time Video frames Generator for generating blazingly fast video frames in python.""" | ||
@@ -6,2 +6,3 @@ __author__ = "Abhishek Thakur (@abhiTronix) <abhi.una12@gmail.com>" | ||
| from .ffdecoder import FFdecoder | ||
| from .sourcer import Sourcer | ||
| from .sourcer import Sourcer | ||
| from .version import __version__ |
+104
-74
| """ | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -22,3 +22,4 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| # import the necessary packages | ||
| import logging, os | ||
| import logging | ||
| import json | ||
| import numpy as np | ||
@@ -28,7 +29,6 @@ import subprocess as sp | ||
| # import helper packages | ||
| # import utils packages | ||
| from .utils import dict2Args, logger_handler | ||
| from .sourcer import Sourcer | ||
| from .ffhelper import ( | ||
| get_valid_ffmpeg_path, | ||
| get_supported_pixfmts, | ||
@@ -55,3 +55,3 @@ get_supported_vdecoders, | ||
| Parameters: | ||
| source (str): defines the source for the input stream. | ||
| source (str): defines the default input source. | ||
| frame_format (str): sets pixel format(-pix_fmt) of the decoded frames. | ||
@@ -62,4 +62,2 @@ custom_ffmpeg (str): assigns the location of custom path/directory for custom FFmpeg executable. | ||
| """ | ||
| # checks if machine in-use is running windows os or not | ||
| self.__os_windows = True if os.name == "nt" else False | ||
@@ -115,36 +113,39 @@ # enable verbose if specified | ||
| # handle where to save the downloaded FFmpeg Static assets on Windows(if specified) | ||
| __ffmpeg_download_path = self.__extra_params.pop("-ffmpeg_download_path", "") | ||
| if not isinstance(__ffmpeg_download_path, str): | ||
| # reset improper values | ||
| __ffmpeg_download_path = "" | ||
| # handle custom Sourcer API params | ||
| sourcer_params = self.__extra_params.pop("-custom_sourcer_params", {}) | ||
| # reset improper values | ||
| sourcer_params = {} if not isinstance(sourcer_params, dict) else sourcer_params | ||
| # validate the FFmpeg assets and return location (also downloads static assets on windows) | ||
| self.__ffmpeg = get_valid_ffmpeg_path( | ||
| str(custom_ffmpeg), | ||
| self.__os_windows, | ||
| ffmpeg_download_path=__ffmpeg_download_path, | ||
| verbose=self.__verbose_logs, | ||
| # pass parameter(if specified) to Sourcer API, specifying where to save the downloaded FFmpeg Static | ||
| # assets on Windows(if specified) | ||
| sourcer_params["-ffmpeg_download_path"] = self.__extra_params.pop( | ||
| "-ffmpeg_download_path", "" | ||
| ) | ||
| # check if valid FFmpeg path returned | ||
| if self.__ffmpeg: | ||
| self.__verbose_logs and logger.debug( | ||
| "Found valid FFmpeg executables: `{}`.".format(self.__ffmpeg) | ||
| # handle video and audio stream indexes in case of multiple ones. | ||
| default_stream_indexes = self.__extra_params.pop( | ||
| "-default_stream_indexes", (0, 0) | ||
| ) | ||
| # reset improper values | ||
| default_stream_indexes = ( | ||
| (0, 0) | ||
| if not isinstance(default_stream_indexes, (list, tuple)) | ||
| else default_stream_indexes | ||
| ) | ||
| # extract and assign source metadata as dict | ||
| self.__source_metadata = ( | ||
| Sourcer( | ||
| source=source, | ||
| verbose=verbose, | ||
| ffmpeg_path=self.__ffmpeg, | ||
| **sourcer_params | ||
| ) | ||
| else: | ||
| # else raise error | ||
| raise RuntimeError( | ||
| "[StreamGear:ERROR] :: Failed to find FFmpeg assets on this system. Kindly compile/install FFmpeg or provide a valid custom FFmpeg binary path!" | ||
| ) | ||
| .probe_stream(default_stream_indexes=default_stream_indexes) | ||
| .retrieve_metadata() | ||
| ) | ||
| # handle where to save the downloaded FFmpeg Static assets on Windows(if specified) | ||
| sourcer_params = self.__extra_params.pop("-custom_source_params", {}) | ||
| # reset improper values | ||
| sourcer_params = {} if not isinstance(sourcer_params, dict) else sourcer_params | ||
| # get valid ffmpeg path | ||
| self.__ffmpeg = self.__source_metadata["ffmpeg_binary_path"] | ||
| # handle input source metadata(extracts all required data) | ||
| self.__source_metadata = Sourcer( | ||
| source=source, verbose=False, ffmpeg_path=self.__ffmpeg, **sourcer_params | ||
| ).decode_stream() | ||
| # handle pass-through audio mode works in conjunction with WriteGear [WIP] | ||
@@ -155,8 +156,10 @@ self.__passthrough_mode = self.__extra_params.pop("-passthrough_audio", False) | ||
| # handle mode of operation | ||
| if self.__source_metadata.contains_images: | ||
| if self.__source_metadata["source_has_image_sequence"]: | ||
| # image-sequence mode | ||
| self.__opmode = "imgseq" | ||
| elif ( | ||
| self.__source_metadata.contains_video # audio is only for pass-through, not really for audio decoding yet. | ||
| and self.__source_metadata.contains_audio | ||
| self.__source_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.__passthrough_mode # [WIP] | ||
@@ -167,3 +170,3 @@ ): | ||
| # self.__opmode = "ao" | ||
| elif self.__source_metadata.contains_video: | ||
| elif self.__source_metadata["source_has_video"]: | ||
| # video-only mode | ||
@@ -176,3 +179,7 @@ self.__opmode = "vo" | ||
| ) | ||
| # log it | ||
| # store as metadata | ||
| self.__source_metadata["ffdecoder_operational_mode"] = self.__supported_opmodes[ | ||
| self.__opmode | ||
| ] | ||
| # and log it | ||
| self.__verbose_logs and logger.critical( | ||
@@ -185,10 +192,18 @@ "Activating {} Mode of Operation.".format( | ||
| # handle user-defined framerate | ||
| self.__constantframerate = self.__extra_params.pop("-constant_framerate", 0.0) | ||
| if isinstance(self.__constantframerate, (float, int)): | ||
| self.__inputframerate = self.__extra_params.pop("-framerate", 0.0) | ||
| if ( | ||
| isinstance(self.__inputframerate, (float, int)) | ||
| and self.__inputframerate > 0.0 | ||
| ): | ||
| # must be float | ||
| self.__constantframerate = float(self.__constantframerate) | ||
| self.__inputframerate = float(self.__inputframerate) | ||
| else: | ||
| # reset improper values | ||
| self.__constantframerate = 0.0 | ||
| self.__inputframerate = 0.0 | ||
| # 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`!" | ||
| ) | ||
| # handle user defined decoded frame resolution(must be a tuple or list) | ||
@@ -229,4 +244,4 @@ self.__custom_resolution = self.__extra_params.pop("-custom_resolution", None) | ||
| default_vdecodec = ( | ||
| self.__source_metadata.default_video_decoder | ||
| if self.__source_metadata.default_video_decoder in supported_vdecodecs | ||
| self.__source_metadata["source_video_decoder"] | ||
| if self.__source_metadata["source_video_decoder"] in supported_vdecodecs | ||
| else "unknown" | ||
@@ -243,14 +258,8 @@ ) | ||
| self.__extra_params.pop("-vcodec", None) | ||
| # handle exclusive input sequence framerate | ||
| sequence_framerate = self.__extra_params.pop("-r", 1.0) | ||
| if ( | ||
| isinstance(sequence_framerate, (float, int)) | ||
| and sequence_framerate > 1.0 | ||
| ): | ||
| # must be float | ||
| sequence_framerate = float(sequence_framerate) | ||
| else: | ||
| # reset improper values | ||
| sequence_framerate = 1.0 | ||
| input_params["-r"] = sequence_framerate | ||
| elif ( | ||
| "-vcodec" in self.__extra_params | ||
| and self.__extra_params["-vcodec"] is None | ||
| ): | ||
| # special case when -vcodec is not needed intentionally | ||
| self.__extra_params.pop("-vcodec", None) | ||
| else: | ||
@@ -298,9 +307,7 @@ # assign video decoder selected here. | ||
| if "rgb24" in supported_pixfmts | ||
| else self.__source_metadata.default_video_pixfmt | ||
| else self.__source_metadata["source_video_pixfmt"] | ||
| ) | ||
| if "-vf" in self.__extra_params: | ||
| self.__extra_params["-pix_fmt"] = self.__extra_params.pop("-vf", None) | ||
| if "-pix_fmt" in self.__extra_params: | ||
| logger.warning( | ||
| "Discarding user-defined `-pix_fmt/-vf` value as it can only be assigned with `frame_format` parameter!" | ||
| "Discarding user-defined `-pix_fmt` value as it can only be assigned with `frame_format` parameter!" | ||
| ) | ||
@@ -314,3 +321,3 @@ self.__extra_params.pop("-pix_fmt", None) | ||
| not (self.__frame_format is None) and logger.critical( | ||
| "Provided FFmpeg does not support `{}` frame format(pix_fmt). Switching to default `{}`!".format( | ||
| "Provided FFmpeg does not support `{}` pixel format(pix_fmt). Switching to default `{}`!".format( | ||
| self.__frame_format, "rgb24" | ||
@@ -355,3 +362,3 @@ ) | ||
| if not (self.__custom_resolution is None) | ||
| else self.__source_metadata.default_video_resolution | ||
| else self.__source_metadata["source_video_resolution"] | ||
| ) | ||
@@ -365,5 +372,5 @@ dimensions = "{}x{}".format( | ||
| framerate = ( | ||
| self.__constantframerate | ||
| if self.__constantframerate > 0.0 | ||
| else self.__source_metadata.default_video_framerate | ||
| self.__inputframerate | ||
| if self.__inputframerate > 0.0 | ||
| else self.__source_metadata["source_video_framerate"] | ||
| ) | ||
@@ -378,4 +385,4 @@ output_params["-framerate"] = str(framerate) | ||
| 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 self.__source_metadata["approx_video_nframes"]: | ||
| self.__raw_frame_num = self.__source_metadata["approx_video_nframes"] | ||
| else: | ||
@@ -404,3 +411,3 @@ self.__raw_frame_num = None | ||
| self.__process is None | ||
| ), "Pipeline is not running! Did you called `create()`?" | ||
| ), "Pipeline is not running! Check if you called `create()` method." | ||
@@ -449,5 +456,3 @@ # formulated raw frame size | ||
| )[:, :, 0] | ||
| elif self.__raw_frame_pixfmt.startswith( | ||
| "yuv" | ||
| ) and self.__raw_frame_pixfmt.startswith("444p"): | ||
| elif self.__raw_frame_pixfmt == "yuv444p": | ||
| # reconstruct exclusive frames | ||
@@ -505,2 +510,25 @@ frame = frame.reshape( | ||
| @property | ||
| def metadata(self): | ||
| """ | ||
| A property object that dumps Source metadata dict as JSON for pretty printing. As well as can be used to update source metadata with user-defined dictionary. | ||
| **Returns:** A [`json.dumps`](https://docs.python.org/3/library/json.html#json.dumps) output. | ||
| """ | ||
| return json.dumps(self.__source_metadata, indent=2) | ||
| @metadata.setter | ||
| def metadata(self, value): | ||
| """ | ||
| A property object that updates source metadata with user-defined dictionary. | ||
| Parameters: | ||
| value (dict): User-defined dictionary. | ||
| """ | ||
| self.__verbose_logs and logger.info("Updating Metadata...") | ||
| if value and isinstance(value, dict): | ||
| self.__source_metadata.update(value) | ||
| else: | ||
| raise ValueError("Invalid value specified.") | ||
| def __launch_FFdecoderline(self, input_params, output_params): | ||
@@ -528,3 +556,3 @@ | ||
| + ["-i"] | ||
| + [self.__source_metadata.source] | ||
| + [self.__source_metadata["source"]] | ||
| + output_parameters | ||
@@ -561,3 +589,5 @@ + ["-f", "rawvideo", "-"] | ||
| # wait if still process is still processing some information | ||
| if self.__process.poll() is None: | ||
| self.__process.terminate() | ||
| self.__process.wait() | ||
| self.__process = None |
+12
-6
| """ | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -33,4 +33,4 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| # import helper packages | ||
| from .utils import logger_handler | ||
| # import utils packages | ||
| from .utils import logger_handler, delete_file_safe | ||
@@ -354,3 +354,3 @@ # define logger | ||
| def validate_imgseqdir(source, extension="jpg"): | ||
| def validate_imgseqdir(source, extension="jpg", verbose=False): | ||
| """ | ||
@@ -371,3 +371,3 @@ ## validate_imgseqdir | ||
| if not (dirpath.exists() and dirpath.is_dir()): | ||
| logger.warning( | ||
| verbose and logger.warning( | ||
| "Specified path `{}` doesn't exists or valid.".format(dirpath) | ||
@@ -468,3 +468,9 @@ ) | ||
| Returns stdin output from subprocess module | ||
| Returns FFmpeg `stdout` output from subprocess module | ||
| Parameters: | ||
| args (based on input): Non Keyword Arguments | ||
| kwargs (based on input): Keyword Arguments | ||
| **Returns:** A string value. | ||
| """ | ||
@@ -471,0 +477,0 @@ # workaround for python bug: https://bugs.python.org/issue37380 |
+249
-128
| """ | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -25,5 +25,10 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| # import helper packages | ||
| # import utils packages | ||
| from .utils import logger_handler | ||
| from .ffhelper import check_sp_output, is_valid_url, is_valid_image_seq | ||
| from .ffhelper import ( | ||
| check_sp_output, | ||
| is_valid_url, | ||
| is_valid_image_seq, | ||
| get_valid_ffmpeg_path, | ||
| ) | ||
@@ -40,3 +45,3 @@ # define logger | ||
| def __init__(self, source, verbose=False, ffmpeg_path=None, **sourcer_params): | ||
| def __init__(self, source, custom_ffmpeg="", verbose=False, **sourcer_params): | ||
| """ | ||
@@ -46,14 +51,16 @@ This constructor method initializes the object state and attributes of the Sourcer. | ||
| Parameters: | ||
| source (str): defines the source for the input stream. | ||
| source (str): defines the default input source. | ||
| verbose (bool): enables/disables verbose. | ||
| ffmpeg_path (str): assigns the location of custom path/directory for custom FFmpeg executables. | ||
| custom_ffmpeg (str): assigns the location of custom path/directory for custom FFmpeg executable. | ||
| sourcer_params (dict): provides the flexibility to control supported internal Sourcer parameters. | ||
| """ | ||
| # checks if machine in-use is running windows os or not | ||
| self.__os_windows = True if os.name == "nt" else False | ||
| # define internal parameters | ||
| self.__verbose = ( # enable verbose if specified | ||
| self.__verbose_logs = ( # enable verbose if specified | ||
| verbose if (verbose and isinstance(verbose, bool)) else False | ||
| ) | ||
| self.__metadata = None # handles metadata recieved | ||
| self.__ffmpeg = ffmpeg_path # handles FFmpeg executable path | ||
| self.__sourcer_params = { # No use yet (Reserved for future) [TODO] | ||
| self.__ffsp_output = None # handles metadata received | ||
| self.__sourcer_params = { | ||
| str(k).strip(): str(v).strip() | ||
@@ -65,52 +72,114 @@ if not isinstance(v, (dict, list, int, float)) | ||
| # handle whether to force validate source | ||
| self.__forcevalidatesource = self.__sourcer_params.pop( | ||
| "-force_validate_source", False | ||
| ) | ||
| if not isinstance(self.__forcevalidatesource, bool): | ||
| # reset improper values | ||
| self.__forcevalidatesource = False | ||
| # handle where to save the downloaded FFmpeg Static assets on Windows(if specified) | ||
| __ffmpeg_download_path = self.__sourcer_params.pop("-ffmpeg_download_path", "") | ||
| if not isinstance(__ffmpeg_download_path, str): | ||
| # reset improper values | ||
| __ffmpeg_download_path = "" | ||
| # validate the FFmpeg assets and return location (also downloads static assets on windows) | ||
| self.__ffmpeg = get_valid_ffmpeg_path( | ||
| str(custom_ffmpeg), | ||
| self.__os_windows, | ||
| ffmpeg_download_path=__ffmpeg_download_path, | ||
| verbose=self.__verbose_logs, | ||
| ) | ||
| # check if valid FFmpeg path returned | ||
| if self.__ffmpeg: | ||
| self.__verbose_logs and logger.debug( | ||
| "Found valid FFmpeg executable: `{}`.".format(self.__ffmpeg) | ||
| ) | ||
| else: | ||
| # else raise error | ||
| raise RuntimeError( | ||
| "[DeFFcode:ERROR] :: Failed to find FFmpeg assets on this system. Kindly compile/install FFmpeg or provide a valid custom FFmpeg binary path!" | ||
| ) | ||
| # define externally accessible parameters | ||
| self.source = source # handles source stream | ||
| self.source_extension = os.path.splitext(source)[ | ||
| self.__source = source # handles source stream | ||
| self.__source_extension = os.path.splitext(source)[ | ||
| -1 | ||
| ] # handles source stream extension | ||
| self.default_video_resolution = None # handle stream resolution | ||
| self.default_video_framerate = 0 # handle stream framerate | ||
| self.default_video_bitrate = 0 # handle stream's video bitrate | ||
| self.default_video_pixfmt = None # handle stream's video pixfmt | ||
| self.default_video_decoder = None # handle stream's video decoder | ||
| self.default_source_duration = 0 # handle stream's video duration | ||
| self.approx_video_nframes = 0 # handle approx stream frame number | ||
| self.default_audio_bitrate = None # handle stream's audio bitrate | ||
| 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 | ||
| # handle flags | ||
| self.contains_video = False # contain video | ||
| self.contains_audio = False # contain audio | ||
| self.contains_images = False # contain image-sequence | ||
| self.__contains_video = False # contain video | ||
| self.__contains_audio = False # contain audio | ||
| self.__contains_images = False # contain image-sequence | ||
| def decode_stream(self): | ||
| # check whether metadata probed or not | ||
| self.__metadata_probed = False | ||
| def probe_stream(self, default_stream_indexes=(0, 0)): | ||
| """ | ||
| Parses Source's FFmpeg Metadata and stores them in externally accessible parameters | ||
| Parses Source's FFmpeg Output and populates metadata in private class variables | ||
| Parameters: | ||
| default_stream_indexes (list, tuple): selects specific video and audio stream index in case of multiple ones. Value can be of format: (int,int). For example (0,1) is ("0th video stream", "1st audio stream"). | ||
| **Returns:** Reference to the instance object. | ||
| """ | ||
| assert ( | ||
| isinstance(default_stream_indexes, (list, tuple)) | ||
| and len(default_stream_indexes) == 2 | ||
| and all(isinstance(x, int) for x in default_stream_indexes) | ||
| ), "Invalid default_stream_indexes value!" | ||
| # validate source and extract metadata | ||
| self.__metadata = self.__validate_source(self.source) | ||
| self.__ffsp_output = self.__validate_source(self.__source) | ||
| # parse resolution and framerate | ||
| video_rfparams = self.extract_resolution_framerate() | ||
| video_rfparams = self.__extract_resolution_framerate( | ||
| default_stream=default_stream_indexes[0] | ||
| ) | ||
| if video_rfparams: | ||
| self.default_video_resolution = video_rfparams["resolution"] | ||
| self.default_video_framerate = video_rfparams["framerate"] | ||
| self.__default_video_resolution = video_rfparams["resolution"] | ||
| self.__default_video_framerate = video_rfparams["framerate"] | ||
| # parse pixel format | ||
| self.default_video_pixfmt = self.extract_video_pixfmt() | ||
| self.__default_video_pixfmt = self.__extract_video_pixfmt( | ||
| default_stream=default_stream_indexes[0] | ||
| ) | ||
| # parse video decoder | ||
| self.default_video_decoder = self.extract_video_decoder() | ||
| self.__default_video_decoder = self.__extract_video_decoder( | ||
| default_stream=default_stream_indexes[0] | ||
| ) | ||
| # parse rest of metadata | ||
| if not self.contains_images: | ||
| if not self.__contains_images: | ||
| # parse video bitrate | ||
| self.default_video_bitrate = self.extract_video_bitrate() | ||
| # parse audio bitrate | ||
| self.default_audio_bitrate = self.extract_audio_bitrate() | ||
| self.__default_video_bitrate = self.__extract_video_bitrate( | ||
| default_stream=default_stream_indexes[0] | ||
| ) | ||
| # parse audio bitrate and samplerate | ||
| audio_params = self.__extract_audio_bitrate_nd_samplerate( | ||
| default_stream=default_stream_indexes[1] | ||
| ) | ||
| if audio_params: | ||
| self.__default_audio_bitrate = audio_params["bitrate"] | ||
| self.__default_audio_samplerate = audio_params["samplerate"] | ||
| # parse video duration | ||
| self.default_source_duration = self.extract_duration() | ||
| self.__default_source_duration = self.__extract_duration() | ||
| # calculate all flags | ||
| if self.default_video_bitrate and self.default_audio_bitrate: | ||
| self.contains_video = True | ||
| self.contains_audio = True | ||
| elif self.default_video_bitrate: | ||
| self.contains_video = True | ||
| elif self.default_audio_bitrate: | ||
| self.contains_audio = True | ||
| if (self.__default_video_bitrate or self.__default_video_framerate) and ( | ||
| self.__default_audio_bitrate or self.__default_audio_samplerate | ||
| ): | ||
| self.__contains_video = True | ||
| self.__contains_audio = True | ||
| elif self.__default_video_bitrate or self.__default_video_framerate: | ||
| self.__contains_video = True | ||
| elif self.__default_audio_bitrate or self.__default_audio_samplerate: | ||
| self.__contains_audio = True | ||
| else: | ||
@@ -121,8 +190,46 @@ raise IOError( | ||
| # calculate approximate number of video frame | ||
| if self.default_video_framerate and self.default_source_duration: | ||
| self.approx_video_nframes = np.rint( | ||
| self.default_video_framerate * self.default_source_duration | ||
| if self.__default_video_framerate and self.__default_source_duration: | ||
| self.__approx_video_nframes = np.rint( | ||
| self.__default_video_framerate * self.__default_source_duration | ||
| ).astype(int, casting="unsafe") | ||
| # signal metadata has been probed | ||
| self.__metadata_probed = True | ||
| # return reference to the instance object. | ||
| return self | ||
| def retrieve_metadata(self): | ||
| """ | ||
| Returns Source metadata formatted as python dictionary. | ||
| **Returns:** A dictionary value containing metadata. | ||
| """ | ||
| # 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." | ||
| self.__verbose_logs and logger.debug("Retrieving Metadata...") | ||
| metadata = { | ||
| "ffmpeg_binary_path": self.__ffmpeg, | ||
| "source": self.__source, | ||
| "source_extension": self.__source_extension, | ||
| "source_video_resolution": self.__default_video_resolution, | ||
| "source_video_framerate": self.__default_video_framerate, | ||
| "source_video_pixfmt": self.__default_video_pixfmt, | ||
| "source_video_decoder": self.__default_video_decoder, | ||
| "source_duration_sec": self.__default_source_duration, | ||
| "approx_video_nframes": int(self.__approx_video_nframes) | ||
| if self.__approx_video_nframes | ||
| else None, | ||
| "source_video_bitrate": self.__default_video_bitrate, | ||
| "source_audio_bitrate": self.__default_audio_bitrate, | ||
| "source_audio_samplerate": self.__default_audio_samplerate, | ||
| "source_has_video": self.__contains_video, | ||
| "source_has_audio": self.__contains_audio, | ||
| "source_has_image_sequence": self.__contains_images, | ||
| } | ||
| return metadata | ||
| def __validate_source(self, source): | ||
@@ -137,9 +244,14 @@ """ | ||
| self.__video_source = os.path.abspath(source) | ||
| elif is_valid_image_seq(self.__ffmpeg, source=source, verbose=self.__verbose): | ||
| elif is_valid_image_seq( | ||
| self.__ffmpeg, source=source, verbose=self.__verbose_logs | ||
| ): | ||
| self.__video_source = source | ||
| self.contains_images = True | ||
| elif is_valid_url(self.__ffmpeg, url=source, verbose=self.__verbose): | ||
| self.__contains_images = True | ||
| elif is_valid_url(self.__ffmpeg, url=source, verbose=self.__verbose_logs): | ||
| self.__video_source = source | ||
| elif self.__forcevalidatesource: | ||
| logger.critical("Forcefully passing validation test for given source!") | ||
| self.__video_source = source | ||
| else: | ||
| logger.error("`source` value is unusuable or unsupported!") | ||
| logger.error("`source` value is unusable or unsupported!") | ||
| # discard the value otherwise | ||
@@ -151,5 +263,6 @@ raise ValueError("Input source is invalid. Aborting!") | ||
| ) | ||
| # filter and return | ||
| return metadata.decode("utf-8").strip() | ||
| def extract_video_bitrate(self, default_stream=0): | ||
| def __extract_video_bitrate(self, default_stream=0): | ||
| """ | ||
@@ -159,11 +272,10 @@ Parses default video-stream bitrate from metadata. | ||
| Parameters: | ||
| default_stream (int): selects particular video-stream in case of multiple ones. | ||
| default_stream (int): selects specific video-stream in case of multiple ones. | ||
| **Returns:** A string value. | ||
| """ | ||
| assert isinstance(default_stream, int), "Invalid input!" | ||
| identifiers = ["Video:", "Stream #"] | ||
| video_bitrate_text = [ | ||
| line.strip() | ||
| for line in self.__metadata.split("\n") | ||
| for line in self.__ffsp_output.split("\n") | ||
| if all(x in line for x in identifiers) | ||
@@ -178,14 +290,14 @@ ] | ||
| filtered_bitrate = re.findall( | ||
| r",\s[0-9]+\s\w\w[/]s", selected_stream.strip() | ||
| r",\s[0-9]+\s\w\w[\/]s", selected_stream.strip() | ||
| ) | ||
| default_video_bitrate = filtered_bitrate[0].split(" ")[1:3] | ||
| final_bitrate = "{}{}".format( | ||
| int(default_video_bitrate[0].strip()), | ||
| "k" if (default_video_bitrate[1].strip().startswith("k")) else "M", | ||
| ) | ||
| return final_bitrate | ||
| else: | ||
| return "" | ||
| if len(filtered_bitrate): | ||
| default_video_bitrate = filtered_bitrate[0].split(" ")[1:3] | ||
| final_bitrate = "{}{}".format( | ||
| int(default_video_bitrate[0].strip()), | ||
| "k" if (default_video_bitrate[1].strip().startswith("k")) else "M", | ||
| ) | ||
| return final_bitrate | ||
| return "" | ||
| def extract_video_decoder(self, default_stream=0): | ||
| def __extract_video_decoder(self, default_stream=0): | ||
| """ | ||
@@ -195,3 +307,3 @@ Parses default video-stream decoder from metadata. | ||
| Parameters: | ||
| default_stream (int): selects particular video-stream in case of multiple ones. | ||
| default_stream (int): selects specific video-stream in case of multiple ones. | ||
@@ -204,3 +316,3 @@ **Returns:** A string value. | ||
| line.strip() | ||
| for line in self.__metadata.split("\n") | ||
| for line in self.__ffsp_output.split("\n") | ||
| if all(x in line for x in identifiers) | ||
@@ -217,7 +329,7 @@ ] | ||
| ) | ||
| return filtered_pixfmt[0].split(" ")[-1] | ||
| else: | ||
| return None | ||
| if filtered_pixfmt: | ||
| return filtered_pixfmt[0].split(" ")[-1] | ||
| return "" | ||
| def extract_video_pixfmt(self, default_stream=0): | ||
| def __extract_video_pixfmt(self, default_stream=0): | ||
| """ | ||
@@ -227,11 +339,10 @@ Parses default video-stream pixel format from metadata. | ||
| Parameters: | ||
| default_stream (int): selects particular video-stream in case of multiple ones. | ||
| default_stream (int): selects specific video-stream in case of multiple ones. | ||
| **Returns:** A string value. | ||
| """ | ||
| assert isinstance(default_stream, int), "Invalid input!" | ||
| identifiers = ["Video:", "Stream #"] | ||
| meta_text = [ | ||
| line.strip() | ||
| for line in self.__metadata.split("\n") | ||
| for line in self.__ffsp_output.split("\n") | ||
| if all(x in line for x in identifiers) | ||
@@ -248,55 +359,51 @@ ] | ||
| ) | ||
| return filtered_pixfmt[0].split(" ")[-1] | ||
| else: | ||
| return None | ||
| if filtered_pixfmt: | ||
| return filtered_pixfmt[0].split(" ")[-1] | ||
| return "" | ||
| def extract_audio_bitrate(self, default_stream=0): | ||
| def __extract_audio_bitrate_nd_samplerate(self, default_stream=0): | ||
| """ | ||
| Parses default audio-stream bitrate from metadata. | ||
| Parses default audio-stream bitrate and samplerate from metadata. | ||
| Parameters: | ||
| default_stream (int): selects particular audio-stream in case of multiple ones. | ||
| default_stream (int): selects specific audio-stream in case of multiple ones. | ||
| **Returns:** A string value. | ||
| """ | ||
| assert isinstance(default_stream, int), "Invalid input!" | ||
| default_audio_bitrate = re.findall(r"fltp,\s[0-9]+\s\w\w[/]s", self.__metadata) | ||
| sample_rate_identifiers = ["Audio", "Hz"] + ( | ||
| ["fltp"] if isinstance(self.source, str) else [] | ||
| ) | ||
| audio_sample_rate = [ | ||
| identifiers = ["Audio:", "Stream #"] | ||
| meta_text = [ | ||
| line.strip() | ||
| for line in self.__metadata.split("\n") | ||
| if all(x in line for x in sample_rate_identifiers) | ||
| for line in self.__ffsp_output.split("\n") | ||
| if all(x in line for x in identifiers) | ||
| ] | ||
| if default_audio_bitrate: | ||
| selected_stream = ( | ||
| result = {} | ||
| if meta_text: | ||
| selected_stream = meta_text[ | ||
| default_stream | ||
| if default_stream > 0 and default_stream < len(default_audio_bitrate) | ||
| if default_stream > 0 and default_stream < len(meta_text) | ||
| else 0 | ||
| ] | ||
| # filter data | ||
| filtered_audio_bitrate = re.findall( | ||
| r"fltp,\s[0-9]+\s\w\w[\/]s", selected_stream.strip() | ||
| ) | ||
| filtered = default_audio_bitrate[selected_stream].split(" ")[1:3] | ||
| final_bitrate = "{}{}".format( | ||
| int(filtered[0].strip()), | ||
| "k" if (filtered[1].strip().startswith("k")) else "M", | ||
| filtered_audio_samplerate = re.findall( | ||
| r",\s[0-9]+\sHz", selected_stream.strip() | ||
| ) | ||
| return final_bitrate | ||
| elif audio_sample_rate: | ||
| selected_stream = ( | ||
| default_stream | ||
| if default_stream > 0 and default_stream < len(audio_sample_rate) | ||
| else 0 | ||
| ) | ||
| sample_rate = re.findall(r"[0-9]+\sHz", audio_sample_rate[selected_stream])[ | ||
| 0 | ||
| ] | ||
| sample_rate_value = int(sample_rate.split(" ")[0]) | ||
| samplerate_2_bitrate = int( | ||
| (sample_rate_value - 44100) * (320 - 96) / (48000 - 44100) + 96 | ||
| ) | ||
| return str(samplerate_2_bitrate) + "k" | ||
| else: | ||
| return None | ||
| # get audio bitrate and samplerate metadata | ||
| if filtered_audio_bitrate: | ||
| filtered = filtered_audio_bitrate[0].split(" ")[1:3] | ||
| result["bitrate"] = "{}{}".format( | ||
| int(filtered[0].strip()), | ||
| "k" if (filtered[1].strip().startswith("k")) else "M", | ||
| ) | ||
| else: | ||
| result["bitrate"] = "" | ||
| if filtered_audio_samplerate: | ||
| result["samplerate"] = filtered_audio_samplerate[0].split(", ")[1] | ||
| else: | ||
| result["samplerate"] = "" | ||
| return result if result and (len(result) == 2) else {} | ||
| def extract_resolution_framerate(self): | ||
| def __extract_resolution_framerate(self, default_stream=0): | ||
| """ | ||
@@ -307,18 +414,32 @@ Parses default video-stream resolution and framerate from metadata. | ||
| """ | ||
| self.__verbose and logger.debug(stripped_data) | ||
| identifiers = ["Video:", "Stream #"] | ||
| meta_text = [ | ||
| line.strip() | ||
| for line in self.__ffsp_output.split("\n") | ||
| if all(x in line for x in identifiers) | ||
| ] | ||
| result = {} | ||
| stripped_data = [x.strip() for x in self.__metadata.split("\n")] | ||
| for data in stripped_data: | ||
| output_a = re.findall(r"([1-9]\d+)x([1-9]\d+)", data) | ||
| output_b = re.findall(r"\d+(?:\.\d+)?\sfps", data) | ||
| if len(result) == 2: | ||
| break | ||
| if output_b and not "framerate" in result: | ||
| result["framerate"] = float(re.findall(r"[\d\.\d]+", output_b[0])[0]) | ||
| if output_a and not "resolution" in result: | ||
| result["resolution"] = [int(x) for x in output_a[-1]] | ||
| # return values | ||
| return result if (len(result) == 2) else None | ||
| if meta_text: | ||
| selected_stream = meta_text[ | ||
| default_stream | ||
| if default_stream > 0 and default_stream < len(meta_text) | ||
| else 0 | ||
| ] | ||
| # filter data | ||
| filtered_resolution = re.findall( | ||
| r"([1-9]\d+)x([1-9]\d+)", selected_stream.strip() | ||
| ) | ||
| filtered_framerate = re.findall( | ||
| r"\d+(?:\.\d+)?\sfps", selected_stream.strip() | ||
| ) | ||
| # get framerate and resolution metadata | ||
| if filtered_framerate: | ||
| result["framerate"] = float( | ||
| re.findall(r"[\d\.\d]+", filtered_framerate[0])[0] | ||
| ) | ||
| if filtered_resolution: | ||
| result["resolution"] = [int(x) for x in filtered_resolution[0]] | ||
| return result if result and (len(result) == 2) else {} | ||
| def extract_duration(self, inseconds=True): | ||
| def __extract_duration(self, inseconds=True): | ||
| """ | ||
@@ -332,3 +453,3 @@ Parses stream duration from metadata. | ||
| line.strip() | ||
| for line in self.__metadata.split("\n") | ||
| for line in self.__ffsp_output.split("\n") | ||
| if all(x in line for x in identifiers) | ||
@@ -344,3 +465,3 @@ ] | ||
| sum( | ||
| float(x) * 60 ** i | ||
| float(x) * 60**i | ||
| for i, x in enumerate(reversed(t_duration[0].split(":"))) | ||
@@ -347,0 +468,0 @@ ) |
+26
-6
| """ | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -24,7 +24,8 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| # import the necessary packages | ||
| import os | ||
| import os, sys | ||
| import logging | ||
| from pathlib import Path | ||
| from colorlog import ColoredFormatter | ||
| # import helper packages | ||
| # import internal packages | ||
| from .version import __version__ | ||
@@ -56,3 +57,3 @@ | ||
| # check if FFdecoder_LOGFILE defined | ||
| file_mode = os.environ.get("FFRAVEL_LOGFILE", False) | ||
| file_mode = os.environ.get("DEFFCODE_LOGFILE", False) | ||
| # define handler | ||
@@ -66,3 +67,3 @@ handler = logging.StreamHandler() | ||
| file_path = ( | ||
| os.path.join(file_path, "FFdecoder.log") | ||
| os.path.join(file_path, "deffcode.log") | ||
| if os.path.isdir(file_path) | ||
@@ -88,3 +89,3 @@ else file_path | ||
| # log current version for debugging | ||
| logger.info("Running FFdecoder Version: {}".format(str(__version__))) | ||
| logger.info("Running DeFFcode Version: {}".format(str(__version__))) | ||
@@ -119,1 +120,20 @@ | ||
| return args | ||
| def delete_file_safe(file_path): | ||
| """ | ||
| ## delete_ext_safe | ||
| Safely deletes files at given path. | ||
| Parameters: | ||
| file_path (string): path to the file | ||
| """ | ||
| try: | ||
| dfile = Path(file_path) | ||
| if sys.version_info >= (3, 8, 0): | ||
| dfile.unlink(missing_ok=True) | ||
| else: | ||
| dfile.exists() and dfile.unlink() | ||
| except Exception as e: | ||
| logger.exception(str(e)) |
@@ -1,1 +0,1 @@ | ||
| __version__ = "0.1.0" | ||
| __version__ = "0.2.0" |
+3
-1
@@ -203,3 +203,3 @@ Apache License | ||
| ====================================== | ||
| ============================================================================ | ||
@@ -221,1 +221,3 @@ This library also borrows code from vidgear(https://github.com/abhiTronix/vidgear) | ||
| limitations under the License. | ||
| ============================================================================ |
+159
-120
| Metadata-Version: 2.1 | ||
| Name: deffcode | ||
| Version: 0.1.0 | ||
| Summary: Performant Pythonic FFmpeg Decoder with easy to adapt flexible API. | ||
| Version: 0.2.0 | ||
| Summary: High-performance Real-time Video frames Generator for generating blazingly fast video frames in python. | ||
| Home-page: https://abhitronix.github.io/deffcode | ||
@@ -37,3 +37,3 @@ Author: Abhishek Thakur | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -56,106 +56,110 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| <h1 align="center"> | ||
| <i>de</i><b>FF</b><i>code</i> | ||
| </h1> | ||
| <p align="center">Performant ⚡️ Pythonic FFmpeg Decoder with easy to adapt flexible API 🐍.</p> | ||
| <h2 align="center"> | ||
| </h2> | ||
| <div align="center"> | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/deffcode.png" alt="DeFFcode" title="Logo designed by Abhishek Thakur(@abhiTronix), under CC-BY-NC-SA 4.0 License" width="80%"/> | ||
| </div> | ||
| <div align="center"> | ||
| | ||
| [![Build Status][github-cli]][github-flow] [![Codecov branch][codecov]][code] [![Azure DevOps builds (branch)][azure-badge]][azure-pipeline] | ||
| **deffcode** is a Performant and Robust FFmpeg Pythonic Wrapper that aimed at decoding any stream that you throw at it. Requiring minimal efforts, deffcode provides an easy-to-adapt flexible API to read a wide range of streams, and can ingest video using any decoder(even hardware ones) into any pixel format ffmpeg supports. It also provides pin-point accurate seeking for extracting only a specific part of your input as required. | ||
| [![Glitter chat][gitter-bagde]][gitter] [![Build Status][appveyor]][app] [![PyPi version][pypi-badge]][pypi] | ||
| It is cross-platform, runs on Python 3.7+, and is easy to install. | ||
| [![Code Style][black-badge]][black] | ||
| | ||
| </div> | ||
| <div align="center"> | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/deffcode-tagline.png" alt="DeFFcode tagline" width="40%"/> | ||
| ## Examples | ||
| ### Basic Example | ||
| ---- | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| [Releases][release] | [Documentation][docs] | [Installation][install] | [License](https://github.com/abhiTronix/deffcode#copyright) | ||
| # initialize and formulate the decoder | ||
| decoder = FFdecoder("foo.mp4").formulate() | ||
| ---- | ||
| # grab the RGB24(default) frame from | ||
| # the decoder(generator) | ||
| for frame in decoder.generateFrame(): | ||
| print(frame.shape) | ||
| </div> | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
|   | ||
| The output: | ||
| ## Overview | ||
| > DeFFcode is a powerful High-performance Real-time **Video frames Generator** that wraps FFmpeg pipeline inside a subprocess module for generating blazingly fast video frames in python 🔥 | ||
| The primary purpose of DeFFcode is to provide a cross-platform solution for fast and low-overhead decoding of a wide range of video streams into 3D [`ndarray`](https://numpy.org/doc/stable/reference/arrays.ndarray.html#the-n-dimensional-array-ndarray) frames while providing **complete control over the underlying FFmpeg pipeline** without the need to go deeper into hefty documentation and in just a few lines of python code. | ||
| DeFFcode can **extract frames in real-time with any custom specification imaginable** such as Framerate, Resolution, Hardware decoding, Complex Filters into any pixel format while giving users the complete freedom to play with any desired FFmpeg supported parameter. On top of that, DeFFcode enables **effortless and precise FFmpeg Frame Seeking** natively. | ||
| Finally, DeFFcode APIs are designed with **simplicity, flexibility, and modularity** in mind for the best developer experience. | ||
|   | ||
| ## Key Features | ||
| DeFFcode APIs are build on [**FFmpeg**][ffmpeg] - a leading multimedia framework, that gives you the following: | ||
| - Extremely exceptional real-time performance ⚡ with low-memory footprints. | ||
| - Flexible API with access to almost every parameter available within FFmpeg. | ||
| - Fast dedicated [Hardware-Accelerated Decoding](https://abhitronix.github.io/deffcode/latest/examples/advanced/#gpu-enabled-hardware-accelerated-decoding). | ||
| - Precise FFmpeg [Frame Seeking](https://abhitronix.github.io/deffcode/latest/examples/basic/#saving-keyframes-as-image) with pinpoint accuracy. | ||
| - Extensive support for real-time [Complex FFmpeg Filters](https://abhitronix.github.io/deffcode/latest/examples/advanced/#generating-video-with-complex-filter-applied). | ||
| - Out-of-the-box support for Computer Vision libraries like OpenCV, Pytorch, etc. | ||
| - Support a wide range of media files, devices, image-sequence and network streams. | ||
| - Easier to ingest streams into any pixel format that FFmpeg supports. | ||
| - Lossless Transcoding support with [WriteGear](https://abhitronix.github.io/deffcode/latest/gears/writegear/introduction/). | ||
| - Fewer hard dependencies, and easy to install. | ||
| - Designed modular for best developer experience. | ||
| - Cross-platform and runs on Python 3.7+ | ||
| | ||
| ## Requirements | ||
| - Python 3.7+ | ||
| - FFmpeg _(See [this doc](https://abhitronix.github.io/deffcode/latest/installation/ffmpeg_install/) for its installation)_ | ||
| | ||
| ## Installation | ||
| Installation is as simple as: | ||
| ```sh | ||
| (720, 1280, 3) | ||
| (720, 1280, 3) | ||
| ... | ||
| ... | ||
| ... | ||
| (720, 1280, 3) | ||
| $ (sudo) pip install deffcode | ||
| ``` | ||
| ### Basic OpenCV Example | ||
| 💡 For more details, see [installation notes][install]. | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| import cv2 | ||
| | ||
| # initialize and formulate the decoder for BGR24 output | ||
| decoder = FFdecoder("foo.mp4", frame_format="bgr24").formulate() | ||
| # loop over frames | ||
| while True: | ||
| # grab the BGR24 frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| ## Getting Started | ||
| # check if frame is None | ||
| if frame is None: | ||
| break | ||
| # Show output window | ||
| cv2.imshow("Output", frame) | ||
| --- | ||
| # check for 'q' key if pressed | ||
| key = cv2.waitKey(1) & 0xFF | ||
| if key == ord("q"): | ||
| break | ||
| **📚 Documentation: https://abhitronix.github.io/deffcode** | ||
| # close output window | ||
| cv2.destroyAllWindows() | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
| --- | ||
| ### Basic PIL Example | ||
| The default function of DeFFcode's [FFdecoder API](https://abhitronix.github.io/deffcode/latest/reference/ffdecoder/#ffdecoder-api) is to generate 24-bit RGB (3D [`ndarray`](https://numpy.org/doc/stable/reference/arrays.ndarray.html#the-n-dimensional-array-ndarray)) frames from the given source: | ||
| ```python | ||
| ```py title="grab_frames.py" | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| from PIL import Image | ||
| # define the FFmpeg parameter to seek | ||
| # to 00:00:01 in time and get one single frame | ||
| extraparams = {"-ss": "00:00:01", "-frames:v":1} | ||
| # formulate the decoder with suitable source(for e.g. foo.mp4) | ||
| decoder = FFdecoder("foo.mp4").formulate() | ||
| # initialize and formulate the decode | ||
| decoder = FFdecoder("foo.mp4", **extraparams).formulate() | ||
| # grab RGB24(default) 3D frames from decoder | ||
| for frame in decoder.generateFrame(): | ||
| # lets print its shape | ||
| print(frame.shape) | ||
| # grab the RGB24(default) frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| # print metadata as `json.dump` | ||
| print(decoder.metadata) | ||
| # check if frame is None | ||
| if not(frame is None) | ||
| # Convert and Show output window | ||
| im = Image.fromarray(frame) | ||
| im.show() | ||
| # terminate the decoder | ||
@@ -165,40 +169,34 @@ decoder.terminate() | ||
| ### Basic Matplotlib Example | ||
| For more examples and in-depth usage guide, kindly refer our **[Basic Recipes 🥧](https://abhitronix.github.io/deffcode/latest/examples/basic)** and **[Advanced Recipes 🔬](https://abhitronix.github.io/deffcode/latest/examples/advanced)** | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| import matplotlib.pyplot as plt | ||
| # define the FFmpeg parameter to seek | ||
| # to 00:00:02.01 in time and get one single frame | ||
| extraparams = {"-ss": "00:00:02.01", "-frames:v":1} | ||
| 💡 In case you're run into any problems, consult our [Help](https://abhitronix.github.io/deffcode/latest/help/) section. | ||
| # initialize and formulate the decode for Grayscale output | ||
| decoder = FFdecoder("foo.mp4", frame_format="gray", **extraparams).formulate() | ||
| # grab single Grayscale frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| | ||
| # Show output window | ||
| plt.imshow(frame, cmap='gray', vmin=0, vmax=255) | ||
| plt.show() | ||
| ## Roadmap | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
| - [x] Add clean and elegant documentation. | ||
| - [x] Add project Issue and PR templates. | ||
| - [x] Add related unit tests with `pytests`. | ||
| - [x] Automate stuff with Continuous Integration. | ||
| - [ ] Add Multiple Source Inputs support. | ||
| - [ ] Add Devices and Screen Capture support. | ||
| - [ ] Resolve High CPU usage issue with WriteGear API. | ||
| - [ ] Add more parameters to Sourcer API's metadata. | ||
| - [ ] Implement Buffer and Audio pass-through modes. | ||
| - [ ] Add recipe for Source with Multiple Embedded Streams. | ||
| - [ ] Add example to dynamically change writable FFdecoder API's metadata parameters. | ||
| - [ ] Add more Advanced Recipes and use cases. | ||
| - [ ] Add preliminary benchmarks. | ||
| - [ ] Make Frame Seeking dynamic. | ||
| | ||
| ## Dependencies | ||
| ## Contributions | ||
| Minimal requirements: | ||
| - Python 3.7+ | ||
| - FFmpeg (See [this](https://abhitronix.github.io/vidgear/latest/gears/writegear/compression/advanced/ffmpeg_install/#ffmpeg-installation-instructions) for its installation) | ||
| - NumPy >=1.20.0 | ||
| - requests | ||
| - colorlog | ||
| - tqdm | ||
| > Contributions are welcome. We'd love to have your contributions to fix bugs or to implement new features! | ||
| :bulb: These requirements are installed automatically(except FFmpeg). | ||
| Please see our **[Contribution Guidelines](https://abhitronix.github.io/deffcode/latest/contribution/)** for more details. | ||
@@ -208,29 +206,70 @@ | ||
| ## Installation | ||
| ## Community Support | ||
| Join our Gitter community channel for quick discussions: | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install -U deffcode | ||
| ``` | ||
| [](https://gitter.im/deffcode-python/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
| **And if you prefer to install deffcode directly from the repository:** | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install git+git://github.com/abhiTronix/deffcode@master#egg=deffcode | ||
| ``` | ||
| | ||
| **Or you can also download its wheel (`.whl`) package from our repository's [releases](https://github.com/abhiTronix/deffcode/releases) section, and thereby can be installed as follows:** | ||
| # Donations | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install deffcode-0.1.0-py3-none-any.whl | ||
| ``` | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/help_us.png" alt="PiGear" width="50%" /> | ||
| > DeFFcode is free and open source and will always remain so. ❤️ | ||
| It is something I am doing with my own free time. If you would like to say thanks, please feel free to make a donation: | ||
| [![ko-fi][kofi-badge]][kofi] | ||
| | ||
| ---- | ||
| <p align="center"><i><b>deffcode</b> is <a href="https://github.com/abhiTronix/deffcode/blob/master/LICENSE.md">Apache 2.0 Licensed</a> code.<br/>Designed & crafted with care.</i></br>⭐️</p> | ||
| # Copyright | ||
| **Copyright (c) abhiTronix 2021** | ||
| This library is released under the **[Apache 2.0 License][license]**. | ||
| <!-- | ||
| Badges | ||
| --> | ||
| [appveyor]:https://img.shields.io/appveyor/ci/abhitronix/deffcode.svg?style=for-the-badge&logo=appveyor | ||
| [codecov]:https://img.shields.io/codecov/c/gh/abhiTronix/deffcode?logo=codecov&style=for-the-badge&token=zrES4mwVKe | ||
| [github-cli]:https://img.shields.io/github/workflow/status/abhiTronix/deffcode/GitHub%20Action%20workflow%20for%20Linux?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMWE5IDkgMCAwMTkgOSA5IDkgMCAwMS05IDkgOSA5IDAgMDEtOS05IDkgOSAwIDAxOS05ek0yMyAxOWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyek0yMyAzNWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyeiIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1wcmltYXJ5LCAjMjA4OEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MSAzNWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyeiIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1zZWNvbmRhcnksICM3OUI4RkYpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0yNS4wMzcgMjMuNjA3bC0zLjA3IDMuMDY1LTEuNDktMS40ODUiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tcHJpbWFyeSwgIzIwODhGRikiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNDEgMTlhNiA2IDAgMTEwIDEyIDYgNiAwIDAxMC0xMnoiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tcHJpbWFyeSwgIzIwODhGRikiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTQzLjAzNiAyMy42MDdsLTMuMDY5IDMuMDY1LTEuNDktMS40ODVNNyA2LjgxMmExIDEgMCAwMTEuNTMzLS44NDZsNS4xMTMgMy4yMmExIDEgMCAwMS0uMDA2IDEuNjk3bC01LjExMyAzLjE3QTEgMSAwIDAxNyAxMy4yMDNWNi44MTN6TTkgMTl2MTVjMCAzLjg2NiAzLjE3NyA3IDcgN2gxIiBzdHJva2U9InZhcigtLWNvbG9yLW1hcmtldGluZy1pY29uLXByaW1hcnksICMyMDg4RkYpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xNi45NDkgMjZhMSAxIDAgMTAwLTJ2MnpNOCAxOS4wMzVBNi45NjUgNi45NjUgMCAwMDE0Ljk2NSAyNnYtMkE0Ljk2NSA0Ljk2NSAwIDAxMTAgMTkuMDM1SDh6TTE0Ljk2NSAyNmgxLjk4NHYtMmgtMS45ODR2MnoiIGZpbGw9InZhcigtLWNvbG9yLW1hcmtldGluZy1pY29uLXByaW1hcnksICMyMDg4RkYpIi8+PHBhdGggZD0iTTI5LjA1NSAyNWg1Ljk0NCIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1wcmltYXJ5LCAjMjA4OEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTIxIDQwYTEgMSAwIDExLS4wMDEgMi4wMDFBMSAxIDAgMDEyMSA0MHpNMjUgNDBhMSAxIDAgMTEtLjAwMSAyLjAwMUExIDEgMCAwMTI1IDQweiIgZmlsbD0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tc2Vjb25kYXJ5LCAjNzlCOEZGKSIvPjxwYXRoIGQ9Ik0zNC4wMDUgNDEuMDA3bC0xLjAxMy4wMzMiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tc2Vjb25kYXJ5LCAjNzlCOEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4= | ||
| [prs-badge]:https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABC0lEQVRYhdWVPQoCMRCFX6HY2ghaiZUXsLW0EDyBrbWtN/EUHsHTWFnYyCL4gxibVZZlZzKTnWz0QZpk5r0vIdkF/kBPAMOKeddE+CQPKoc5Yt5cTjBMdQSwDQToWgBJAn3jmhqgltapAV6E6b5U17MGGAUaUj07TficMfIBZDV6vxowBm1BP9WbSQE4o5h9IjPJmy73TEPDDxVmoZdQrQ5jRhly9Q8tgMUXkIIWn0oG4GYQfAXQzz1PGoCiQndM7b4RgJay/h7zBLT3hASgoKjamQJMreKf0gfuAGyYtXEIAKcL/Dss15iq6ohXghozLYiAMxPuACwtIT4yeQUxAaLrZwAoqGRKGk7qDSYTfYQ8LuYnAAAAAElFTkSuQmCC | ||
| [azure-badge]:https://img.shields.io/azure-devops/build/abhiuna12/942b3b13-d745-49e9-8d7d-b3918ff43ac2/3/master?logo=azure-pipelines&style=for-the-badge | ||
| [pypi-badge]:https://img.shields.io/pypi/v/deffcode.svg?style=for-the-badge&logo=pypi | ||
| [gitter-bagde]:https://img.shields.io/badge/Chat-Gitter-blueviolet.svg?style=for-the-badge&logo=gitter | ||
| [Coffee-badge]:https://abhitronix.github.io/img/deffcode/orange_img.png | ||
| [kofi-badge]:https://www.ko-fi.com/img/githubbutton_sm.svg | ||
| [black-badge]:https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge&logo=github | ||
| <!-- | ||
| Internal URLs | ||
| --> | ||
| [docs]:https://abhitronix.github.io/deffcode/latest/ | ||
| [install]:https://abhitronix.github.io/deffcode/latest/installation/ | ||
| [release]:https://github.com/abhiTronix/deffcode/releases/latest | ||
| [license]:https://github.com/abhiTronix/deffcode/blob/master/LICENSE | ||
| [github-flow]:https://github.com/abhiTronix/deffcode/actions/workflows/CIlinux.yml | ||
| [azure-pipeline]:https://dev.azure.com/abhiuna12/public/_build?definitionId=3 | ||
| [app]:https://ci.appveyor.com/project/abhiTronix/deffcode | ||
| [code]:https://codecov.io/gh/abhiTronix/deffcode | ||
| [black]: https://github.com/psf/black | ||
| <!-- | ||
| External URLs | ||
| --> | ||
| [ffmpeg]:https://www.ffmpeg.org/ | ||
| [pypi]:https://pypi.org/project/deffcode/ | ||
| [gitter]:https://gitter.im/deffcode-python/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge | ||
| [coffee]:https://www.buymeacoffee.com/2twOXFvlA | ||
| [kofi]: https://ko-fi.com/W7W8WTYO | ||
+156
-118
| <!-- | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -21,106 +21,110 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| <h1 align="center"> | ||
| <i>de</i><b>FF</b><i>code</i> | ||
| </h1> | ||
| <p align="center">Performant ⚡️ Pythonic FFmpeg Decoder with easy to adapt flexible API 🐍.</p> | ||
| <h2 align="center"> | ||
| </h2> | ||
| <div align="center"> | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/deffcode.png" alt="DeFFcode" title="Logo designed by Abhishek Thakur(@abhiTronix), under CC-BY-NC-SA 4.0 License" width="80%"/> | ||
| </div> | ||
| <div align="center"> | ||
| | ||
| [![Build Status][github-cli]][github-flow] [![Codecov branch][codecov]][code] [![Azure DevOps builds (branch)][azure-badge]][azure-pipeline] | ||
| **deffcode** is a Performant and Robust FFmpeg Pythonic Wrapper that aimed at decoding any stream that you throw at it. Requiring minimal efforts, deffcode provides an easy-to-adapt flexible API to read a wide range of streams, and can ingest video using any decoder(even hardware ones) into any pixel format ffmpeg supports. It also provides pin-point accurate seeking for extracting only a specific part of your input as required. | ||
| [![Glitter chat][gitter-bagde]][gitter] [![Build Status][appveyor]][app] [![PyPi version][pypi-badge]][pypi] | ||
| It is cross-platform, runs on Python 3.7+, and is easy to install. | ||
| [![Code Style][black-badge]][black] | ||
| | ||
| </div> | ||
| <div align="center"> | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/deffcode-tagline.png" alt="DeFFcode tagline" width="40%"/> | ||
| ## Examples | ||
| ### Basic Example | ||
| ---- | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| [Releases][release] | [Documentation][docs] | [Installation][install] | [License](#copyright) | ||
| # initialize and formulate the decoder | ||
| decoder = FFdecoder("foo.mp4").formulate() | ||
| ---- | ||
| # grab the RGB24(default) frame from | ||
| # the decoder(generator) | ||
| for frame in decoder.generateFrame(): | ||
| print(frame.shape) | ||
| </div> | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
|   | ||
| The output: | ||
| ## Overview | ||
| > DeFFcode is a powerful High-performance Real-time **Video frames Generator** that wraps FFmpeg pipeline inside a subprocess module for generating blazingly fast video frames in python 🔥 | ||
| The primary purpose of DeFFcode is to provide a cross-platform solution for fast and low-overhead decoding of a wide range of video streams into 3D [`ndarray`](https://numpy.org/doc/stable/reference/arrays.ndarray.html#the-n-dimensional-array-ndarray) frames while providing **complete control over the underlying FFmpeg pipeline** without the need to go deeper into hefty documentation and in just a few lines of python code. | ||
| DeFFcode can **extract frames in real-time with any custom specification imaginable** such as Framerate, Resolution, Hardware decoding, Complex Filters into any pixel format while giving users the complete freedom to play with any desired FFmpeg supported parameter. On top of that, DeFFcode enables **effortless and precise FFmpeg Frame Seeking** natively. | ||
| Finally, DeFFcode APIs are designed with **simplicity, flexibility, and modularity** in mind for the best developer experience. | ||
|   | ||
| ## Key Features | ||
| DeFFcode APIs are build on [**FFmpeg**][ffmpeg] - a leading multimedia framework, that gives you the following: | ||
| - Extremely exceptional real-time performance ⚡ with low-memory footprints. | ||
| - Flexible API with access to almost every parameter available within FFmpeg. | ||
| - Fast dedicated [Hardware-Accelerated Decoding](https://abhitronix.github.io/deffcode/latest/examples/advanced/#gpu-enabled-hardware-accelerated-decoding). | ||
| - Precise FFmpeg [Frame Seeking](https://abhitronix.github.io/deffcode/latest/examples/basic/#saving-keyframes-as-image) with pinpoint accuracy. | ||
| - Extensive support for real-time [Complex FFmpeg Filters](https://abhitronix.github.io/deffcode/latest/examples/advanced/#generating-video-with-complex-filter-applied). | ||
| - Out-of-the-box support for Computer Vision libraries like OpenCV, Pytorch, etc. | ||
| - Support a wide range of media files, devices, image-sequence and network streams. | ||
| - Easier to ingest streams into any pixel format that FFmpeg supports. | ||
| - Lossless Transcoding support with [WriteGear](https://abhitronix.github.io/deffcode/latest/gears/writegear/introduction/). | ||
| - Fewer hard dependencies, and easy to install. | ||
| - Designed modular for best developer experience. | ||
| - Cross-platform and runs on Python 3.7+ | ||
| | ||
| ## Requirements | ||
| - Python 3.7+ | ||
| - FFmpeg _(See [this doc](https://abhitronix.github.io/deffcode/latest/installation/ffmpeg_install/) for its installation)_ | ||
| | ||
| ## Installation | ||
| Installation is as simple as: | ||
| ```sh | ||
| (720, 1280, 3) | ||
| (720, 1280, 3) | ||
| ... | ||
| ... | ||
| ... | ||
| (720, 1280, 3) | ||
| $ (sudo) pip install deffcode | ||
| ``` | ||
| ### Basic OpenCV Example | ||
| 💡 For more details, see [installation notes][install]. | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| import cv2 | ||
| | ||
| # initialize and formulate the decoder for BGR24 output | ||
| decoder = FFdecoder("foo.mp4", frame_format="bgr24").formulate() | ||
| # loop over frames | ||
| while True: | ||
| # grab the BGR24 frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| ## Getting Started | ||
| # check if frame is None | ||
| if frame is None: | ||
| break | ||
| # Show output window | ||
| cv2.imshow("Output", frame) | ||
| --- | ||
| # check for 'q' key if pressed | ||
| key = cv2.waitKey(1) & 0xFF | ||
| if key == ord("q"): | ||
| break | ||
| **📚 Documentation: https://abhitronix.github.io/deffcode** | ||
| # close output window | ||
| cv2.destroyAllWindows() | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
| --- | ||
| ### Basic PIL Example | ||
| The default function of DeFFcode's [FFdecoder API](https://abhitronix.github.io/deffcode/latest/reference/ffdecoder/#ffdecoder-api) is to generate 24-bit RGB (3D [`ndarray`](https://numpy.org/doc/stable/reference/arrays.ndarray.html#the-n-dimensional-array-ndarray)) frames from the given source: | ||
| ```python | ||
| ```py title="grab_frames.py" | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| from PIL import Image | ||
| # define the FFmpeg parameter to seek | ||
| # to 00:00:01 in time and get one single frame | ||
| extraparams = {"-ss": "00:00:01", "-frames:v":1} | ||
| # formulate the decoder with suitable source(for e.g. foo.mp4) | ||
| decoder = FFdecoder("foo.mp4").formulate() | ||
| # initialize and formulate the decode | ||
| decoder = FFdecoder("foo.mp4", **extraparams).formulate() | ||
| # grab RGB24(default) 3D frames from decoder | ||
| for frame in decoder.generateFrame(): | ||
| # lets print its shape | ||
| print(frame.shape) | ||
| # grab the RGB24(default) frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| # print metadata as `json.dump` | ||
| print(decoder.metadata) | ||
| # check if frame is None | ||
| if not(frame is None) | ||
| # Convert and Show output window | ||
| im = Image.fromarray(frame) | ||
| im.show() | ||
| # terminate the decoder | ||
@@ -130,40 +134,34 @@ decoder.terminate() | ||
| ### Basic Matplotlib Example | ||
| For more examples and in-depth usage guide, kindly refer our **[Basic Recipes 🥧](https://abhitronix.github.io/deffcode/latest/examples/basic)** and **[Advanced Recipes 🔬](https://abhitronix.github.io/deffcode/latest/examples/advanced)** | ||
| ```python | ||
| # import the necessary packages | ||
| from deffcode import FFdecoder | ||
| import matplotlib.pyplot as plt | ||
| # define the FFmpeg parameter to seek | ||
| # to 00:00:02.01 in time and get one single frame | ||
| extraparams = {"-ss": "00:00:02.01", "-frames:v":1} | ||
| 💡 In case you're run into any problems, consult our [Help](https://abhitronix.github.io/deffcode/latest/help/) section. | ||
| # initialize and formulate the decode for Grayscale output | ||
| decoder = FFdecoder("foo.mp4", frame_format="gray", **extraparams).formulate() | ||
| # grab single Grayscale frame from the decoder | ||
| frame = next(decoder.generateFrame(), None) | ||
| | ||
| # Show output window | ||
| plt.imshow(frame, cmap='gray', vmin=0, vmax=255) | ||
| plt.show() | ||
| ## Roadmap | ||
| # terminate the decoder | ||
| decoder.terminate() | ||
| ``` | ||
| - [x] Add clean and elegant documentation. | ||
| - [x] Add project Issue and PR templates. | ||
| - [x] Add related unit tests with `pytests`. | ||
| - [x] Automate stuff with Continuous Integration. | ||
| - [ ] Add Multiple Source Inputs support. | ||
| - [ ] Add Devices and Screen Capture support. | ||
| - [ ] Resolve High CPU usage issue with WriteGear API. | ||
| - [ ] Add more parameters to Sourcer API's metadata. | ||
| - [ ] Implement Buffer and Audio pass-through modes. | ||
| - [ ] Add recipe for Source with Multiple Embedded Streams. | ||
| - [ ] Add example to dynamically change writable FFdecoder API's metadata parameters. | ||
| - [ ] Add more Advanced Recipes and use cases. | ||
| - [ ] Add preliminary benchmarks. | ||
| - [ ] Make Frame Seeking dynamic. | ||
| | ||
| ## Dependencies | ||
| ## Contributions | ||
| Minimal requirements: | ||
| - Python 3.7+ | ||
| - FFmpeg (See [this](https://abhitronix.github.io/vidgear/latest/gears/writegear/compression/advanced/ffmpeg_install/#ffmpeg-installation-instructions) for its installation) | ||
| - NumPy >=1.20.0 | ||
| - requests | ||
| - colorlog | ||
| - tqdm | ||
| > Contributions are welcome. We'd love to have your contributions to fix bugs or to implement new features! | ||
| :bulb: These requirements are installed automatically(except FFmpeg). | ||
| Please see our **[Contribution Guidelines](https://abhitronix.github.io/deffcode/latest/contribution/)** for more details. | ||
@@ -173,28 +171,68 @@ | ||
| ## Installation | ||
| ## Community Support | ||
| Join our Gitter community channel for quick discussions: | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install -U deffcode | ||
| ``` | ||
| [](https://gitter.im/deffcode-python/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
| **And if you prefer to install deffcode directly from the repository:** | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install git+git://github.com/abhiTronix/deffcode@master#egg=deffcode | ||
| ``` | ||
| | ||
| **Or you can also download its wheel (`.whl`) package from our repository's [releases](https://github.com/abhiTronix/deffcode/releases) section, and thereby can be installed as follows:** | ||
| # Donations | ||
| ```sh | ||
| # Install latest stable release | ||
| pip install deffcode-0.1.0-py3-none-any.whl | ||
| ``` | ||
| <img src="https://abhitronix.github.io/deffcode/latest/assets/images/help_us.png" alt="PiGear" width="50%" /> | ||
| > DeFFcode is free and open source and will always remain so. ❤️ | ||
| It is something I am doing with my own free time. If you would like to say thanks, please feel free to make a donation: | ||
| [![ko-fi][kofi-badge]][kofi] | ||
| | ||
| ---- | ||
| <p align="center"><i><b>deffcode</b> is <a href="https://github.com/abhiTronix/deffcode/blob/master/LICENSE.md">Apache 2.0 Licensed</a> code.<br/>Designed & crafted with care.</i></br>⭐️</p> | ||
| # Copyright | ||
| **Copyright © abhiTronix 2021** | ||
| This library is released under the **[Apache 2.0 License][license]**. | ||
| <!-- | ||
| Badges | ||
| --> | ||
| [appveyor]:https://img.shields.io/appveyor/ci/abhitronix/deffcode.svg?style=for-the-badge&logo=appveyor | ||
| [codecov]:https://img.shields.io/codecov/c/gh/abhiTronix/deffcode?logo=codecov&style=for-the-badge&token=zrES4mwVKe | ||
| [github-cli]:https://img.shields.io/github/workflow/status/abhiTronix/deffcode/GitHub%20Action%20workflow%20for%20Linux?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMWE5IDkgMCAwMTkgOSA5IDkgMCAwMS05IDkgOSA5IDAgMDEtOS05IDkgOSAwIDAxOS05ek0yMyAxOWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyek0yMyAzNWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyeiIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1wcmltYXJ5LCAjMjA4OEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MSAzNWE2IDYgMCAxMTAgMTIgNiA2IDAgMDEwLTEyeiIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1zZWNvbmRhcnksICM3OUI4RkYpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0yNS4wMzcgMjMuNjA3bC0zLjA3IDMuMDY1LTEuNDktMS40ODUiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tcHJpbWFyeSwgIzIwODhGRikiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNDEgMTlhNiA2IDAgMTEwIDEyIDYgNiAwIDAxMC0xMnoiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tcHJpbWFyeSwgIzIwODhGRikiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTQzLjAzNiAyMy42MDdsLTMuMDY5IDMuMDY1LTEuNDktMS40ODVNNyA2LjgxMmExIDEgMCAwMTEuNTMzLS44NDZsNS4xMTMgMy4yMmExIDEgMCAwMS0uMDA2IDEuNjk3bC01LjExMyAzLjE3QTEgMSAwIDAxNyAxMy4yMDNWNi44MTN6TTkgMTl2MTVjMCAzLjg2NiAzLjE3NyA3IDcgN2gxIiBzdHJva2U9InZhcigtLWNvbG9yLW1hcmtldGluZy1pY29uLXByaW1hcnksICMyMDg4RkYpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xNi45NDkgMjZhMSAxIDAgMTAwLTJ2MnpNOCAxOS4wMzVBNi45NjUgNi45NjUgMCAwMDE0Ljk2NSAyNnYtMkE0Ljk2NSA0Ljk2NSAwIDAxMTAgMTkuMDM1SDh6TTE0Ljk2NSAyNmgxLjk4NHYtMmgtMS45ODR2MnoiIGZpbGw9InZhcigtLWNvbG9yLW1hcmtldGluZy1pY29uLXByaW1hcnksICMyMDg4RkYpIi8+PHBhdGggZD0iTTI5LjA1NSAyNWg1Ljk0NCIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1tYXJrZXRpbmctaWNvbi1wcmltYXJ5LCAjMjA4OEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTIxIDQwYTEgMSAwIDExLS4wMDEgMi4wMDFBMSAxIDAgMDEyMSA0MHpNMjUgNDBhMSAxIDAgMTEtLjAwMSAyLjAwMUExIDEgMCAwMTI1IDQweiIgZmlsbD0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tc2Vjb25kYXJ5LCAjNzlCOEZGKSIvPjxwYXRoIGQ9Ik0zNC4wMDUgNDEuMDA3bC0xLjAxMy4wMzMiIHN0cm9rZT0idmFyKC0tY29sb3ItbWFya2V0aW5nLWljb24tc2Vjb25kYXJ5LCAjNzlCOEZGKSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4= | ||
| [prs-badge]:https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABC0lEQVRYhdWVPQoCMRCFX6HY2ghaiZUXsLW0EDyBrbWtN/EUHsHTWFnYyCL4gxibVZZlZzKTnWz0QZpk5r0vIdkF/kBPAMOKeddE+CQPKoc5Yt5cTjBMdQSwDQToWgBJAn3jmhqgltapAV6E6b5U17MGGAUaUj07TficMfIBZDV6vxowBm1BP9WbSQE4o5h9IjPJmy73TEPDDxVmoZdQrQ5jRhly9Q8tgMUXkIIWn0oG4GYQfAXQzz1PGoCiQndM7b4RgJay/h7zBLT3hASgoKjamQJMreKf0gfuAGyYtXEIAKcL/Dss15iq6ohXghozLYiAMxPuACwtIT4yeQUxAaLrZwAoqGRKGk7qDSYTfYQ8LuYnAAAAAElFTkSuQmCC | ||
| [azure-badge]:https://img.shields.io/azure-devops/build/abhiuna12/942b3b13-d745-49e9-8d7d-b3918ff43ac2/3/master?logo=azure-pipelines&style=for-the-badge | ||
| [pypi-badge]:https://img.shields.io/pypi/v/deffcode.svg?style=for-the-badge&logo=pypi | ||
| [gitter-bagde]:https://img.shields.io/badge/Chat-Gitter-blueviolet.svg?style=for-the-badge&logo=gitter | ||
| [Coffee-badge]:https://abhitronix.github.io/img/deffcode/orange_img.png | ||
| [kofi-badge]:https://www.ko-fi.com/img/githubbutton_sm.svg | ||
| [black-badge]:https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge&logo=github | ||
| <!-- | ||
| Internal URLs | ||
| --> | ||
| [docs]:https://abhitronix.github.io/deffcode/latest/ | ||
| [install]:https://abhitronix.github.io/deffcode/latest/installation/ | ||
| [release]:https://github.com/abhiTronix/deffcode/releases/latest | ||
| [license]:https://github.com/abhiTronix/deffcode/blob/master/LICENSE | ||
| [github-flow]:https://github.com/abhiTronix/deffcode/actions/workflows/CIlinux.yml | ||
| [azure-pipeline]:https://dev.azure.com/abhiuna12/public/_build?definitionId=3 | ||
| [app]:https://ci.appveyor.com/project/abhiTronix/deffcode | ||
| [code]:https://codecov.io/gh/abhiTronix/deffcode | ||
| [black]: https://github.com/psf/black | ||
| <!-- | ||
| External URLs | ||
| --> | ||
| [ffmpeg]:https://www.ffmpeg.org/ | ||
| [pypi]:https://pypi.org/project/deffcode/ | ||
| [gitter]:https://gitter.im/deffcode-python/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge | ||
| [coffee]:https://www.buymeacoffee.com/2twOXFvlA | ||
| [kofi]: https://ko-fi.com/W7W8WTYO |
+2
-2
| """ | ||
| =============================================== | ||
| deffcode library source-code is deployed under the Apache 2.0 License: | ||
| DeFFcode library source-code is deployed under the Apache 2.0 License: | ||
@@ -44,3 +44,3 @@ Copyright (c) 2021 Abhishek Thakur(@abhiTronix) <abhi.una12@gmail.com> | ||
| version=pkg_version["__version__"], | ||
| description="Performant Pythonic FFmpeg Decoder with easy to adapt flexible API.", | ||
| description="High-performance Real-time Video frames Generator for generating blazingly fast video frames in python.", | ||
| license="Apache License 2.0", | ||
@@ -47,0 +47,0 @@ author="Abhishek Thakur", |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
121275
30.83%1544
11.08%