| Name | ffpipe JSON |
| Version |
0.1.5
JSON |
| download |
| home_page | None |
| Summary | FFPipe: Image processing in realtime using FFmpeg's pipe mechanism with a robust video/audio sync. |
| upload_time | 2025-10-27 12:37:10 |
| maintainer | None |
| docs_url | None |
| author | Aryan Shekarlaban |
| requires_python | >=3.10 |
| license | Apache-2.0 |
| keywords |
|
| VCS |
|
| bugtrack_url |
|
| requirements |
No requirements were recorded.
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
No coveralls.
|
# FFPipe
**FFPipe** is a simple, yet robust tool that acts as a bridge between FFmpeg and Python, allowing you to process video frames in between.
It is designed to be lightweight and easy to use, making it ideal for developers who want to manipulate video streams
without diving deep into FFmpeg's complexities.
It is built on top of FFmpeg and uses its powerful capabilities to handle video streams, while providing a simple Python interface for processing frames.
(`ffpipe` uses the [`ffmpeg-python`](https://github.com/kkroening/ffmpeg-python) library under the hood)
## How It Works

`ffpipe` leverages FFmpeg’s piping mechanism and seamlessly buffers, transforms, and streams frames, letting developers focus on
processing logic while FFmpeg handles the rest.
Designed for simplicity and flexibility, `ffpipe` requires only a subclass and a `process_frame`/`process_batch` method
to define custom transformations. Its modular, state-aware architecture keeps video processing clean, efficient, and Pythonic.
The workflow is as follows:
1) The input stream is read and piped to ffmpeg's STDOUT
2) A reader thread reads the frames in the pipe and writes it to the buffer
3) A processing loop (invoked by the `run()` method):
1) reads frames from the buffer
2) processes them
3) pipes them back to ffmpeg's STDIN
4) The ffmpeg output process reads frames from the STDIN and writes them to the specified output.
## Installation
First things first, you need to install FFmpeg on your system. You can find the installation instructions for your platform
[here](https://ffmpeg.org/download.html). Make sure to add FFmpeg to your system's PATH so that it can be accessed from the command line.
Then you can install `ffpipe` using pip:
```bash
pip install ffpipe
```
## Usage
There are actually two ways to use `ffpipe`:
1. **Subclassing FFPipe**: You can subclass the `FFPipe` class and implement your own processing logic by overriding the `process_frame` or `process_batch` method and the `run()` method will take care of the rest.
2. **Using FFPipe directly**: You can use the `FFPipe` class directly and write your whole processing loop instead of calling the `run()` method (like in method 1).
#### Subclassing FFPipe
```python
import os
import cv2
import numpy as np
from ffpipe import FFPipe
class FrameFlipper(FFPipe):
"""A simple image processor that flips the image horizontally (1) or vertically (0) or both (-1)."""
def __init__(self, flip_axis: int = 1, **kwargs):
super().__init__(**kwargs) # DO NOT FORGET THIS LINE!
self.flip_axis = flip_axis
def process_frame(self, frame: np.ndarray, **kwargs) -> np.ndarray:
return cv2.flip(frame, self.flip_axis)
# For efficient batch processing also implement `process_batch`, this is just an example
def process_batch(self, batch: np.ndarray, **kwargs) -> np.ndarray:
return np.stack([cv2.flip(frame, self.flip_axis) for frame in batch])
# Initialize and run the FrameFlipper
streamer = FrameFlipper(
flip_axis=0, # Flip vertically
input="path/to/source/video.mp4",
output="path/to/output/video.mp4",
output_args={
"vcodec": "libx264",
"pix_fmt": "yuv420p",
},
)
streamer.run(batch_size=1) # Any batch_size > 1 calls the `process_batch` method instead.
```
As you can see, you just subclass the `FFPipe` class for your own image processor streamer and implement the `process_frame`
or `process_batch` method.
**Note**: The base `FFPipe` class takes in a couple of arguments and you should remember to override the `__init__`
method correctly as above (providing and passing the kwargs to the base constructor with `super().__init__(**kwargs)`)!
#### Using FFPipe Directly
```python
import cv2
from ffpipe import FFPipe
pipeline = FFPipe(
input="path/to/source/video.mp4",
output="path/to/output/video.mp4",
output_args={
"vcodec": "libx264",
"pix_fmt": "yuv420p",
},
)
pipeline.start()
while True:
# batch_size = 1 -> np.ndarray(height, width, 3)
# batch_size > 1 -> np.ndarray(batch_size, height, width, 3)
frame = pipeline.read(batch_size=1)
if frame is None:
break
# Do something with the frame
processed_frame = cv2.flip(frame, 1)
# Write the processed frame back to the pipeline
pipeline.write(processed_frame)
# End of stream check
if pipeline.end_of_buffer:
pipeline.write(None)
break
# Gracefully stop the pipeline and ffmpeg processes
pipeline.stop(gracefully=True)
```
This way you might lose some of the benefits of `ffpipe` like the progress bar but you can have more control over the processing loop.
For more info, checkout the [tutorials](docs/tutorial.md).
### Customizing FFmpeg's Arguments
Under the hood, `ffpipe` adds an extra layer in your original ffmpeg pipeline and takes care of how the streams should be
piped for your Python app and back to ffmpeg. This means that you can take any existing ffmpeg command and leverage your
Python processing logic for it. So in order for `ffpipe` to work as expected, you must include all those args and filters
from your existing ffmpeg command in the `ffpipe`'s constructor.
You can customize FFPipe by passing FFmpeg-compatible arguments as dictionaries for input and output:
- `input_args`: Controls how FFmpeg reads the input (e.g., start and end timestamps, etc.).
- `output_args`: Controls how FFmpeg writes the streams to the output (e.g, output format, codec, etc.).
- Filters: Video/Audio filters are applied just before the input stream is piped for processing and can be set using below parameters.
(so do not use `af` or `vf` in `input_args`!):
- `video_filters`: A dict of video filters with named arguments.
- `audio_filters`: A dict of audio filters with named arguments.
- `global_args`: A list of ffmpeg global args that are not tied to the input/output or a specific stream. e.g, ["-report"].
Note that this is a simple list of strings as they should be passed to ffmpeg so the `-` prefix and the order are important.
```python
# Considering `CustomPipeline` subclasses `FFPipe`
pipe = CustomPipeline(
input="input.mp4",
output="output.mp4",
input_args={"ss": "10"}, # Start from second 10
output_args={"vcodec": "libx265", "crf": "28"}, # Change codec
gloabal_args=["-report"], # Enable ffmpeg logging to file
video_filters={"hue": {"s": 0}}, # Convert to grayscale
audio_filters={"bass": {"g": 10}, "treble": {"g": 5}} # Boost sound
)
```
The above configuration corresponds to the following ffmpeg command while applying the custom processing logic defined in `CustomPipeline` in between:
```
ffmpeg -ss 10 -i input.mp4 \
-vf "hue=s=0" \
-af "bass=g=10,treble=g=5" \
-vcodec libx265 -crf 28 \
output.mp4
-report
```
**Note**: If you want to use some crazy and complex args/filters, you must absolutely know what you're doing! Any wrong
arg or filter can mess up the whole pipeline which can be hard to debug (setting a proper `ffmpeg_log_level` is recommended for better verbosity).
## Limitations
- Since `ffpipe` decodes all frames in the pipeline, you cannot use any stream copying args for video, e.g, `-vcodec copy` (doesn't make sense anyway!).
- There is no support for Python-based processing of the audio stream in `ffpipe`; The audio is passed through as is. (Only ffmpeg args/filters can be applied to audio)
- `ffpipe` is limited to a single video stream (first video stream in the input media)!
- The pipe mechanism adds some latency that would make it unsuitable for ultra-low-latency scenarios.
- The video frame rate and the video size is determined internally and cannot be changed in the output. So do not use related args or filters for input (`-s`, `-r`).
- Slow processing logic (slower than the video FPS) causes accumulating latency or hiccups in realtime processing scenarios.
- The progress bar only counts the frames read and processed and does not verify if they're actually written to the output or not.
- The internal buffering mechanism does not have a limit by default which can cause memory issues with high resolution videos. Modify `buffer_size` if needed.
- FFmpeg's hardware acceleration probably does not work since `ffpipe` needs access to the raw frames.
- Complex filters requiring multiple inputs/outputs are not supported.
- The internal pipe commands do not accept additional args. The pipe works with `rawvideo` in `bgr24` pixel format with the native fps and video size. In certain cases the thread queue size warning might pop-up which can be set with `pipe_thread_queue_size`.
Raw data
{
"_id": null,
"home_page": null,
"name": "ffpipe",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": null,
"author": "Aryan Shekarlaban",
"author_email": "arxyzan@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/fc/ec/3113c2bc99ac1d0c874b7cdf46b50e3a235a7fafa81e3ddd87eacbaa6405/ffpipe-0.1.5.tar.gz",
"platform": null,
"description": "# FFPipe\n**FFPipe** is a simple, yet robust tool that acts as a bridge between FFmpeg and Python, allowing you to process video frames in between. \nIt is designed to be lightweight and easy to use, making it ideal for developers who want to manipulate video streams \nwithout diving deep into FFmpeg's complexities.\nIt is built on top of FFmpeg and uses its powerful capabilities to handle video streams, while providing a simple Python interface for processing frames.\n(`ffpipe` uses the [`ffmpeg-python`](https://github.com/kkroening/ffmpeg-python) library under the hood)\n\n## How It Works\n\n\n`ffpipe` leverages FFmpeg\u2019s piping mechanism and seamlessly buffers, transforms, and streams frames, letting developers focus on\nprocessing logic while FFmpeg handles the rest.\n\nDesigned for simplicity and flexibility, `ffpipe` requires only a subclass and a `process_frame`/`process_batch` method \nto define custom transformations. Its modular, state-aware architecture keeps video processing clean, efficient, and Pythonic.\n\nThe workflow is as follows:\n1) The input stream is read and piped to ffmpeg's STDOUT\n2) A reader thread reads the frames in the pipe and writes it to the buffer\n3) A processing loop (invoked by the `run()` method):\n 1) reads frames from the buffer\n 2) processes them \n 3) pipes them back to ffmpeg's STDIN\n4) The ffmpeg output process reads frames from the STDIN and writes them to the specified output.\n\n## Installation\nFirst things first, you need to install FFmpeg on your system. You can find the installation instructions for your platform\n[here](https://ffmpeg.org/download.html). Make sure to add FFmpeg to your system's PATH so that it can be accessed from the command line.\n\nThen you can install `ffpipe` using pip:\n```bash\npip install ffpipe\n```\n\n## Usage\nThere are actually two ways to use `ffpipe`:\n1. **Subclassing FFPipe**: You can subclass the `FFPipe` class and implement your own processing logic by overriding the `process_frame` or `process_batch` method and the `run()` method will take care of the rest.\n2. **Using FFPipe directly**: You can use the `FFPipe` class directly and write your whole processing loop instead of calling the `run()` method (like in method 1).\n\n#### Subclassing FFPipe\n```python\nimport os\nimport cv2\nimport numpy as np\nfrom ffpipe import FFPipe\n\nclass FrameFlipper(FFPipe):\n \"\"\"A simple image processor that flips the image horizontally (1) or vertically (0) or both (-1).\"\"\"\n def __init__(self, flip_axis: int = 1, **kwargs):\n super().__init__(**kwargs) # DO NOT FORGET THIS LINE!\n self.flip_axis = flip_axis\n\n def process_frame(self, frame: np.ndarray, **kwargs) -> np.ndarray:\n return cv2.flip(frame, self.flip_axis)\n\n # For efficient batch processing also implement `process_batch`, this is just an example\n def process_batch(self, batch: np.ndarray, **kwargs) -> np.ndarray:\n return np.stack([cv2.flip(frame, self.flip_axis) for frame in batch])\n\n# Initialize and run the FrameFlipper\nstreamer = FrameFlipper(\n flip_axis=0, # Flip vertically\n input=\"path/to/source/video.mp4\",\n output=\"path/to/output/video.mp4\",\n output_args={\n \"vcodec\": \"libx264\",\n \"pix_fmt\": \"yuv420p\",\n },\n)\n\nstreamer.run(batch_size=1) # Any batch_size > 1 calls the `process_batch` method instead.\n```\nAs you can see, you just subclass the `FFPipe` class for your own image processor streamer and implement the `process_frame`\nor `process_batch` method.\n\n**Note**: The base `FFPipe` class takes in a couple of arguments and you should remember to override the `__init__`\nmethod correctly as above (providing and passing the kwargs to the base constructor with `super().__init__(**kwargs)`)!\n\n#### Using FFPipe Directly\n```python\nimport cv2\nfrom ffpipe import FFPipe\n\npipeline = FFPipe(\n input=\"path/to/source/video.mp4\",\n output=\"path/to/output/video.mp4\",\n output_args={\n \"vcodec\": \"libx264\",\n \"pix_fmt\": \"yuv420p\",\n },\n)\npipeline.start()\n\nwhile True:\n # batch_size = 1 -> np.ndarray(height, width, 3)\n # batch_size > 1 -> np.ndarray(batch_size, height, width, 3)\n frame = pipeline.read(batch_size=1)\n \n if frame is None:\n break\n \n # Do something with the frame\n processed_frame = cv2.flip(frame, 1)\n # Write the processed frame back to the pipeline\n pipeline.write(processed_frame)\n \n # End of stream check\n if pipeline.end_of_buffer:\n pipeline.write(None)\n break\n\n# Gracefully stop the pipeline and ffmpeg processes\npipeline.stop(gracefully=True)\n```\nThis way you might lose some of the benefits of `ffpipe` like the progress bar but you can have more control over the processing loop.\n\nFor more info, checkout the [tutorials](docs/tutorial.md).\n### Customizing FFmpeg's Arguments\nUnder the hood, `ffpipe` adds an extra layer in your original ffmpeg pipeline and takes care of how the streams should be\npiped for your Python app and back to ffmpeg. This means that you can take any existing ffmpeg command and leverage your\nPython processing logic for it. So in order for `ffpipe` to work as expected, you must include all those args and filters\nfrom your existing ffmpeg command in the `ffpipe`'s constructor.\n\nYou can customize FFPipe by passing FFmpeg-compatible arguments as dictionaries for input and output:\n- `input_args`: Controls how FFmpeg reads the input (e.g., start and end timestamps, etc.).\n- `output_args`: Controls how FFmpeg writes the streams to the output (e.g, output format, codec, etc.).\n- Filters: Video/Audio filters are applied just before the input stream is piped for processing and can be set using below parameters.\n (so do not use `af` or `vf` in `input_args`!):\n - `video_filters`: A dict of video filters with named arguments.\n - `audio_filters`: A dict of audio filters with named arguments.\n- `global_args`: A list of ffmpeg global args that are not tied to the input/output or a specific stream. e.g, [\"-report\"].\n Note that this is a simple list of strings as they should be passed to ffmpeg so the `-` prefix and the order are important.\n\n```python\n# Considering `CustomPipeline` subclasses `FFPipe`\npipe = CustomPipeline(\n input=\"input.mp4\",\n output=\"output.mp4\",\n input_args={\"ss\": \"10\"}, # Start from second 10\n output_args={\"vcodec\": \"libx265\", \"crf\": \"28\"}, # Change codec\n gloabal_args=[\"-report\"], # Enable ffmpeg logging to file\n video_filters={\"hue\": {\"s\": 0}}, # Convert to grayscale\n audio_filters={\"bass\": {\"g\": 10}, \"treble\": {\"g\": 5}} # Boost sound\n)\n```\nThe above configuration corresponds to the following ffmpeg command while applying the custom processing logic defined in `CustomPipeline` in between:\n```\nffmpeg -ss 10 -i input.mp4 \\\n -vf \"hue=s=0\" \\\n -af \"bass=g=10,treble=g=5\" \\\n -vcodec libx265 -crf 28 \\\n output.mp4\n -report\n```\n\n**Note**: If you want to use some crazy and complex args/filters, you must absolutely know what you're doing! Any wrong\narg or filter can mess up the whole pipeline which can be hard to debug (setting a proper `ffmpeg_log_level` is recommended for better verbosity).\n\n\n## Limitations\n- Since `ffpipe` decodes all frames in the pipeline, you cannot use any stream copying args for video, e.g, `-vcodec copy` (doesn't make sense anyway!).\n- There is no support for Python-based processing of the audio stream in `ffpipe`; The audio is passed through as is. (Only ffmpeg args/filters can be applied to audio)\n- `ffpipe` is limited to a single video stream (first video stream in the input media)!\n- The pipe mechanism adds some latency that would make it unsuitable for ultra-low-latency scenarios.\n- The video frame rate and the video size is determined internally and cannot be changed in the output. So do not use related args or filters for input (`-s`, `-r`).\n- Slow processing logic (slower than the video FPS) causes accumulating latency or hiccups in realtime processing scenarios.\n- The progress bar only counts the frames read and processed and does not verify if they're actually written to the output or not.\n- The internal buffering mechanism does not have a limit by default which can cause memory issues with high resolution videos. Modify `buffer_size` if needed.\n- FFmpeg's hardware acceleration probably does not work since `ffpipe` needs access to the raw frames.\n- Complex filters requiring multiple inputs/outputs are not supported.\n- The internal pipe commands do not accept additional args. The pipe works with `rawvideo` in `bgr24` pixel format with the native fps and video size. In certain cases the thread queue size warning might pop-up which can be set with `pipe_thread_queue_size`.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "FFPipe: Image processing in realtime using FFmpeg's pipe mechanism with a robust video/audio sync.",
"version": "0.1.5",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "5c87f3210e6ee70193179c101169ecd5b27fe913af0e54a17c11fc87e94aca26",
"md5": "ddc14a976351bf609e3e1c2e4c0f44f3",
"sha256": "973c7ad13f4107a573f422f5603750bd4e7970853741c29f731a00575f13c205"
},
"downloads": -1,
"filename": "ffpipe-0.1.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ddc14a976351bf609e3e1c2e4c0f44f3",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 15070,
"upload_time": "2025-10-27T12:37:08",
"upload_time_iso_8601": "2025-10-27T12:37:08.931169Z",
"url": "https://files.pythonhosted.org/packages/5c/87/f3210e6ee70193179c101169ecd5b27fe913af0e54a17c11fc87e94aca26/ffpipe-0.1.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "fcec3113c2bc99ac1d0c874b7cdf46b50e3a235a7fafa81e3ddd87eacbaa6405",
"md5": "e0e0807af1b69b857de96f0423a9203b",
"sha256": "38f2b3acb2aa47c00dd5744ca5a703e59c2da0fbfbc30ca36bc88bab7d083d78"
},
"downloads": -1,
"filename": "ffpipe-0.1.5.tar.gz",
"has_sig": false,
"md5_digest": "e0e0807af1b69b857de96f0423a9203b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 17794,
"upload_time": "2025-10-27T12:37:10",
"upload_time_iso_8601": "2025-10-27T12:37:10.064442Z",
"url": "https://files.pythonhosted.org/packages/fc/ec/3113c2bc99ac1d0c874b7cdf46b50e3a235a7fafa81e3ddd87eacbaa6405/ffpipe-0.1.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-27 12:37:10",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "ffpipe"
}