video-helper 1.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Warith HARCHAOUI
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: video-helper
3
+ Version: 1.6.3
4
+ Summary: Video Helper โ€” utility functions for video processing exposed as library, argparse CLI, click CLI, FastAPI HTTP surface, and MCP tools. Validation, format conversion, frame extraction (multi-backend dispatcher: VidGear / PyAV / ffmpeg-pipe), HTTP-headers passthrough, exact-size output with aspect-preserving padding, subtitle conversion (SRT/VTT/CSS), and pipeline primitives (black video, image loop, concat, overlay, audio mux, subtitle burn).
5
+ Author-email: Warith HARCHAOUI <warithmetics@deraison.ai>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/warith-harchaoui/video-helper
8
+ Project-URL: Issues, https://github.com/warith-harchaoui/video-helper/issues
9
+ Keywords: video,ffmpeg,opencv,pyav,vidgear,frames,subtitles,extraction
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Multimedia :: Video
20
+ Classifier: Topic :: Multimedia :: Video :: Conversion
21
+ Requires-Python: <3.14,>=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: os-helper>=1.5.0
25
+ Requires-Dist: opencv-python<5,>=4.10.0.84
26
+ Requires-Dist: ffmpeg-python<0.3,>=0.2.0
27
+ Requires-Dist: vidgear<1,>=0.3.3
28
+ Provides-Extra: pyav
29
+ Requires-Dist: av<18,>=12; extra == "pyav"
30
+ Provides-Extra: torch
31
+ Requires-Dist: torch<3,>=2.0; extra == "torch"
32
+ Provides-Extra: pil
33
+ Requires-Dist: pillow<12,>=10; extra == "pil"
34
+ Provides-Extra: cli
35
+ Requires-Dist: click<9,>=8.1; extra == "cli"
36
+ Provides-Extra: api
37
+ Requires-Dist: fastapi<1,>=0.111; extra == "api"
38
+ Requires-Dist: uvicorn<1,>=0.30; extra == "api"
39
+ Requires-Dist: python-multipart<1,>=0.0.9; extra == "api"
40
+ Provides-Extra: mcp
41
+ Requires-Dist: fastapi-mcp<1,>=0.3; extra == "mcp"
42
+ Requires-Dist: fastapi<1,>=0.111; extra == "mcp"
43
+ Requires-Dist: uvicorn<1,>=0.30; extra == "mcp"
44
+ Requires-Dist: python-multipart<1,>=0.0.9; extra == "mcp"
45
+ Provides-Extra: all
46
+ Requires-Dist: av<18,>=12; extra == "all"
47
+ Requires-Dist: torch<3,>=2.0; extra == "all"
48
+ Requires-Dist: pillow<12,>=10; extra == "all"
49
+ Provides-Extra: dev
50
+ Requires-Dist: pytest>=8; extra == "dev"
51
+ Requires-Dist: av<18,>=12; extra == "dev"
52
+ Requires-Dist: httpx>=0.27; extra == "dev"
53
+ Requires-Dist: click<9,>=8.1; extra == "dev"
54
+ Requires-Dist: fastapi<1,>=0.111; extra == "dev"
55
+ Requires-Dist: python-multipart<1,>=0.0.9; extra == "dev"
56
+ Requires-Dist: fastapi-mcp<1,>=0.3; extra == "dev"
57
+ Dynamic: license-file
58
+
59
+ # Video Helper
60
+
61
+ [๐Ÿ‡ซ๐Ÿ‡ท](https://github.com/warith-harchaoui/video-helper/blob/main/LISEZMOI.md) ยท [๐Ÿ‡ฌ๐Ÿ‡ง](https://github.com/warith-harchaoui/video-helper/blob/main/README.md)
62
+
63
+ [![CI](https://github.com/warith-harchaoui/video-helper/actions/workflows/ci.yml/badge.svg)](https://github.com/warith-harchaoui/video-helper/actions/workflows/ci.yml) [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/warith-harchaoui/video-helper/blob/main/LICENSE) [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](#)
64
+
65
+ `Video Helper` belongs to a collection of libraries called `AI Helpers` developed for building Artificial Intelligence.
66
+
67
+ [๐ŸŒ AI Helpers](https://harchaoui.org/warith/ai-helpers)
68
+
69
+ [![logo](https://raw.githubusercontent.com/warith-harchaoui/video-helper/main/assets/logo.png)](https://harchaoui.org/warith/ai-helpers)
70
+
71
+ Video Helper is a Python library that provides utility functions for processing video files. It includes features like loading, converting, extracting frames as well as working with subtitle formats.
72
+
73
+ # Installation
74
+
75
+ **Prerequisites** โ€” **Python 3.10โ€“3.13** and **git**, **ffmpeg**, cross-platform:
76
+
77
+ - ๐ŸŽ **macOS** ([Homebrew](https://brew.sh)): `brew install python git ffmpeg`
78
+ - ๐Ÿง **Ubuntu/Debian**: `sudo apt update && sudo apt install -y python3 python3-pip git ffmpeg`
79
+ - ๐ŸชŸ **Windows** (PowerShell): `winget install Python.Python.3.12 Git.Git Gyan.FFmpeg`
80
+
81
+ Then install the package:
82
+
83
+
84
+ ## Install Package
85
+
86
+ We recommend using Python environments. Check this link if you're unfamiliar with setting one up:
87
+
88
+ [๐Ÿฅธ Tech tips](https://harchaoui.org/warith/4ml/#install)
89
+
90
+ ## Install `ffmpeg`
91
+ To use Video Helper, you must install `ffmpeg`:
92
+
93
+ - For macOS ๐ŸŽ
94
+
95
+ Get [brew](https://brew.sh)
96
+ ```bash
97
+ brew install ffmpeg
98
+ ```
99
+ - For Ubuntu ๐Ÿง
100
+ ```bash
101
+ sudo apt install ffmpeg
102
+ ```
103
+ - For Windows ๐ŸชŸ
104
+ Go to the [FFmpeg website](https://ffmpeg.org/download.html) and follow the instructions for downloading FFmpeg. You'll need to manually add FFmpeg to your system PATH.
105
+
106
+
107
+ finally we still discuss between different python package managers and try to support as much as possible
108
+
109
+ ```bash
110
+ pip install --force-reinstall --no-cache-dir \
111
+ git+https://github.com/warith-harchaoui/video-helper.git@v1.6.2
112
+ ```
113
+
114
+ # Usage
115
+
116
+ For the full catalog of recipes, see [๐Ÿ“‹ EXAMPLES.md](https://github.com/warith-harchaoui/video-helper/blob/main/EXAMPLES.md).
117
+
118
+ Hereโ€™s an example of how to use Video Helper to load, convert, and extract frames from a video file:
119
+
120
+ ```python
121
+ import video_helper as vh
122
+
123
+ # Check if the video file is valid
124
+ video_file = "example.mp4"
125
+ valid = vh.is_valid_video_file(video_file) # True or False
126
+
127
+ # Get video dimensions and details
128
+ details = vh.video_dimensions(video_file)
129
+ print(details)
130
+ # {'width': 1920, 'height': 1080, 'duration': 10.0, 'frame_rate': 30.0, 'has_sound': True}
131
+
132
+ # Convert the video file to a different format
133
+ output_video = "video_tests/example_converted.mp4"
134
+ vh.video_converter(video_file, output_video,
135
+ frame_rate=30, width=640, without_sound = True)
136
+
137
+ # The images will never be distorted:
138
+ # aspect ratios are kept even for arbitrary width and height thanks to black padding if necessary
139
+
140
+ # Extract frames from the video
141
+
142
+ start_instant=5 # seconds
143
+ # it corresponds to start_index = start_instant * frame_rate = 5 * 30 = 150th frame
144
+
145
+ end_instant=10 # seconds
146
+ # it corresponds to end_index = end_instant * frame_rate = 10 * 30 = 300th frame
147
+
148
+ frame_step=5 # take one frame every 5
149
+ # which corresponds to 1 frame every 5 / frame_rate = 5 / 30 = 0.17 second
150
+
151
+ # This means that in the video we take 1 frame every 5 from the 150th to the 300th
152
+
153
+ # List example
154
+ frames = list(
155
+ vh.extract_frames(video_file, start_instant=start_instant, end_instant=end_instant, frame_step=frame_step)
156
+ )
157
+
158
+ # For loop example
159
+ for frame in vh.extract_frames(
160
+ video_file,
161
+ start_instant=start_instant,
162
+ end_instant=end_instant,
163
+ frame_step=frame_step):
164
+ pass # Replace with your frame processing logic
165
+
166
+ # Each frame is a numpy array with shape (height, width, channels)
167
+ # with pixel values between 0 and 255.
168
+
169
+ ```
170
+
171
+ Another example is about subtitles
172
+
173
+ Convert SRT subtitles to WebVTT with color preservation:
174
+
175
+ ```python
176
+ import video_helper as vh
177
+
178
+ srt_file = "subtitles.srt"
179
+ vtt_file = "subtitles.vtt"
180
+ css_file = "subtitles.css"
181
+
182
+ vh.srt2vtt(srt_file, vtt_file, css_file)
183
+ ```
184
+
185
+ # Multi-surface exposure
186
+
187
+ Every public function is reachable from five surfaces, all
188
+ systematically wired (nothing is CLI-only or library-only):
189
+
190
+ | Surface | Install | Entry point |
191
+ | --- | --- | --- |
192
+ | Python library | `pip install video-helper` | `import video_helper as vh` |
193
+ | Argparse CLI (stdlib) | `pip install video-helper` | `video-helper --help` |
194
+ | Click CLI | `pip install 'video-helper[cli]'` | `video-helper-click --help` |
195
+ | FastAPI HTTP | `pip install 'video-helper[api]'` | `uvicorn video_helper.api:app` |
196
+ | MCP server | `pip install 'video-helper[api,mcp]'` | `video-helper-mcp` |
197
+
198
+ The `Dockerfile` at the repo root ships `.[api,mcp,pyav]` by default on
199
+ `python:3.11-slim` with `ffmpeg` and `libass` โ€” one `docker build && docker run -p 8000:8000` gives you the HTTP + MCP surfaces immediately.
200
+
201
+ See [GUI.md](https://github.com/warith-harchaoui/video-helper/blob/main/GUI.md) for the innovative GUI design plan (Recipe
202
+ Canvas + frame-first comparator + batch drop zone โ€” not a CLI mirror)
203
+ and [LANDSCAPE.md](https://github.com/warith-harchaoui/video-helper/blob/main/LANDSCAPE.md) for how `video-helper` compares with
204
+ moviepy, PyAV, decord, torchvision.io, VidGear, OpenCV, and friends.
205
+
206
+ # Features
207
+ - **Video validation**: `is_valid_video_file` โ€” extension + `ffmpeg.probe` round-trip.
208
+ - **Conversion**: `video_converter` โ€” re-encode, resample fps, resize (aspect-preserving), strip audio.
209
+ - **Frame access**: `extract_frames` (generator with time/index range, stabilization, sampling) and `dump_frames` (list โ†’ video).
210
+ - **Temporal crop**: `extract_video_chunk`, `video_duration`.
211
+ - **Pipeline primitives**: `black_video`, `image_loop_to_video`, `concat_videos`, `overlay_image`, `extract_audio_track`, `mux_audio_video`, `burn_subtitles`.
212
+ - **Subtitles**: `srt2vtt` (with companion CSS), `extract_unique_colors`.
213
+
214
+ # API Reference
215
+
216
+ | Function | Signature | Description |
217
+ | --- | --- | --- |
218
+ | `is_valid_video_file` | `(video_file: str) -> bool` | True iff the file exists, has a known video extension, and `ffmpeg.probe` finds a video stream. |
219
+ | `video_dimensions` | `(video_file: str) -> dict` | Returns `{width, height, duration, frame_rate, has_sound}` via `ffmpeg.probe`. |
220
+ | `video_duration` | `(input_video: str) -> float` | Duration in seconds (thin wrapper over `video_dimensions`). |
221
+ | `video_converter` | `(input_video, output_video=None, frame_rate=None, width=None, height=None, without_sound=False)` | Re-encode with optional fps, resize (aspect-preserving black padding when both width and height are given), and audio stripping. |
222
+ | `extract_frames` | `(video_path, start_index=None, end_index=None, start_instant=None, end_instant=None, stabilize=False, frame_step=1, frame_interval=None, frame_indices=None, frame_times=None, backend="auto", hwaccel=None, http_headers=None, output_width=None, output_height=None, pad_color="black", destination="numpy", device="cpu", batch_size=None, layout="image") -> Iterator` | Multi-backend dispatcher (VidGear / PyAV / ffmpeg-pipe). `destination`: `"numpy"` (HWC BGR), `"torch"` (CHW RGB), or `"pil"` (PIL.Image RGB, `size=(W, H)`). `batch_size`+`layout` yields NHWC/NCHW or THWC/CTHW. `frame_indices`/`frame_times` = sparse access via PyAV keyframe-seek. `http_headers` forwards User-Agent/Referer/Cookie to PyAV / ffmpeg-pipe (needed for yt-dlp-resolved YouTube live, members-only, age-gated). `output_width`+`output_height` โ†’ exact size with `pad_color`-padded letterbox/pillarbox; one of them alone โ†’ aspect-preserving scale. `pad_color="transparent"` โ†’ v1.6.0. See [SPEED_ANALYSIS.md](https://github.com/warith-harchaoui/video-helper/blob/main/SPEED_ANALYSIS.md) and [EXAMPLES.md](https://github.com/warith-harchaoui/video-helper/blob/main/EXAMPLES.md#frame-access). |
223
+ | `dump_frames` | `(frames_list, output_movie, fps=30)` | Write a list of BGR frames (OpenCV convention, same as `extract_frames` yields) to a video file. |
224
+ | `extract_video_chunk` | `(input_video, sample_start, sample_end, output_video)` | Temporal crop from `sample_start` to `sample_end` (seconds). |
225
+ | `black_video` | `(duration, width, height, output_video, frame_rate=30)` | Generate a silent solid-black video. Odd dimensions are rounded down. |
226
+ | `image_loop_to_video` | `(image, duration, output_video, frame_rate=30, width=None, height=None)` | Loop a still image into a silent video; optional letterboxing. |
227
+ | `concat_videos` | `(input_videos, output_video, reencode=True, frame_rate=None)` | Concatenate clips end-to-end via the ffmpeg concat demuxer. |
228
+ | `overlay_image` | `(input_video, image, output_video, x="0", y="0", scale_width=None)` | Overlay a PNG/JPG (alpha supported); `x` / `y` accept ffmpeg expressions for time-varying motion. |
229
+ | `extract_audio_track` | `(input_video, output_audio, sample_rate=44100, channels=2, encoding="pcm_s16le")` | Pull the audio stream out of a video file. |
230
+ | `mux_audio_video` | `(input_video, input_audio, output_video, audio_codec="aac", audio_bitrate="192k", shortest=False)` | Replace the audio track of a (typically silent) video. |
231
+ | `burn_subtitles` | `(input_video, subtitles_file, output_video, force_style=None)` | Burn `.srt` / `.vtt` / `.ass` / `.ssa` into the video frames (requires ffmpeg built with libass). |
232
+ | `srt2vtt` | `(srt_file_path, vtt_file_path=None, css_file_path=None)` | Convert SRT โ†’ WebVTT, lifting `<font color>` tags into a sidecar CSS file. |
233
+ | `extract_unique_colors` | `(srt_file_path: str) -> Set[str]` | Set of unique hex colors found in `<font color>` tags of an SRT. |
234
+
235
+ By default frames are BGR `numpy.ndarray` of shape `(H, W, 3)` with pixel values in `[0, 255]`. See [EXAMPLES.md โ†’ Destination](https://github.com/warith-harchaoui/video-helper/blob/main/EXAMPLES.md#destination-numpy-torch-or-pil) for the full shape ร— colorspace table including torch (CHW/NCHW/CTHW RGB) and PIL (RGB, `size=(W, H)`).
236
+
237
+ # Author
238
+ - [Warith HARCHAOUI](https://linkedin.com/in/warith-harchaoui)
239
+
240
+ # Acknowledgements
241
+ Special thanks to [Mohamed Chelali](https://mchelali.github.io) and [Bachir Zerroug](https://www.linkedin.com/in/bachirzerroug) for fruitful discussions.
@@ -0,0 +1,183 @@
1
+ # Video Helper
2
+
3
+ [๐Ÿ‡ซ๐Ÿ‡ท](https://github.com/warith-harchaoui/video-helper/blob/main/LISEZMOI.md) ยท [๐Ÿ‡ฌ๐Ÿ‡ง](https://github.com/warith-harchaoui/video-helper/blob/main/README.md)
4
+
5
+ [![CI](https://github.com/warith-harchaoui/video-helper/actions/workflows/ci.yml/badge.svg)](https://github.com/warith-harchaoui/video-helper/actions/workflows/ci.yml) [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/warith-harchaoui/video-helper/blob/main/LICENSE) [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](#)
6
+
7
+ `Video Helper` belongs to a collection of libraries called `AI Helpers` developed for building Artificial Intelligence.
8
+
9
+ [๐ŸŒ AI Helpers](https://harchaoui.org/warith/ai-helpers)
10
+
11
+ [![logo](https://raw.githubusercontent.com/warith-harchaoui/video-helper/main/assets/logo.png)](https://harchaoui.org/warith/ai-helpers)
12
+
13
+ Video Helper is a Python library that provides utility functions for processing video files. It includes features like loading, converting, extracting frames as well as working with subtitle formats.
14
+
15
+ # Installation
16
+
17
+ **Prerequisites** โ€” **Python 3.10โ€“3.13** and **git**, **ffmpeg**, cross-platform:
18
+
19
+ - ๐ŸŽ **macOS** ([Homebrew](https://brew.sh)): `brew install python git ffmpeg`
20
+ - ๐Ÿง **Ubuntu/Debian**: `sudo apt update && sudo apt install -y python3 python3-pip git ffmpeg`
21
+ - ๐ŸชŸ **Windows** (PowerShell): `winget install Python.Python.3.12 Git.Git Gyan.FFmpeg`
22
+
23
+ Then install the package:
24
+
25
+
26
+ ## Install Package
27
+
28
+ We recommend using Python environments. Check this link if you're unfamiliar with setting one up:
29
+
30
+ [๐Ÿฅธ Tech tips](https://harchaoui.org/warith/4ml/#install)
31
+
32
+ ## Install `ffmpeg`
33
+ To use Video Helper, you must install `ffmpeg`:
34
+
35
+ - For macOS ๐ŸŽ
36
+
37
+ Get [brew](https://brew.sh)
38
+ ```bash
39
+ brew install ffmpeg
40
+ ```
41
+ - For Ubuntu ๐Ÿง
42
+ ```bash
43
+ sudo apt install ffmpeg
44
+ ```
45
+ - For Windows ๐ŸชŸ
46
+ Go to the [FFmpeg website](https://ffmpeg.org/download.html) and follow the instructions for downloading FFmpeg. You'll need to manually add FFmpeg to your system PATH.
47
+
48
+
49
+ finally we still discuss between different python package managers and try to support as much as possible
50
+
51
+ ```bash
52
+ pip install --force-reinstall --no-cache-dir \
53
+ git+https://github.com/warith-harchaoui/video-helper.git@v1.6.2
54
+ ```
55
+
56
+ # Usage
57
+
58
+ For the full catalog of recipes, see [๐Ÿ“‹ EXAMPLES.md](https://github.com/warith-harchaoui/video-helper/blob/main/EXAMPLES.md).
59
+
60
+ Hereโ€™s an example of how to use Video Helper to load, convert, and extract frames from a video file:
61
+
62
+ ```python
63
+ import video_helper as vh
64
+
65
+ # Check if the video file is valid
66
+ video_file = "example.mp4"
67
+ valid = vh.is_valid_video_file(video_file) # True or False
68
+
69
+ # Get video dimensions and details
70
+ details = vh.video_dimensions(video_file)
71
+ print(details)
72
+ # {'width': 1920, 'height': 1080, 'duration': 10.0, 'frame_rate': 30.0, 'has_sound': True}
73
+
74
+ # Convert the video file to a different format
75
+ output_video = "video_tests/example_converted.mp4"
76
+ vh.video_converter(video_file, output_video,
77
+ frame_rate=30, width=640, without_sound = True)
78
+
79
+ # The images will never be distorted:
80
+ # aspect ratios are kept even for arbitrary width and height thanks to black padding if necessary
81
+
82
+ # Extract frames from the video
83
+
84
+ start_instant=5 # seconds
85
+ # it corresponds to start_index = start_instant * frame_rate = 5 * 30 = 150th frame
86
+
87
+ end_instant=10 # seconds
88
+ # it corresponds to end_index = end_instant * frame_rate = 10 * 30 = 300th frame
89
+
90
+ frame_step=5 # take one frame every 5
91
+ # which corresponds to 1 frame every 5 / frame_rate = 5 / 30 = 0.17 second
92
+
93
+ # This means that in the video we take 1 frame every 5 from the 150th to the 300th
94
+
95
+ # List example
96
+ frames = list(
97
+ vh.extract_frames(video_file, start_instant=start_instant, end_instant=end_instant, frame_step=frame_step)
98
+ )
99
+
100
+ # For loop example
101
+ for frame in vh.extract_frames(
102
+ video_file,
103
+ start_instant=start_instant,
104
+ end_instant=end_instant,
105
+ frame_step=frame_step):
106
+ pass # Replace with your frame processing logic
107
+
108
+ # Each frame is a numpy array with shape (height, width, channels)
109
+ # with pixel values between 0 and 255.
110
+
111
+ ```
112
+
113
+ Another example is about subtitles
114
+
115
+ Convert SRT subtitles to WebVTT with color preservation:
116
+
117
+ ```python
118
+ import video_helper as vh
119
+
120
+ srt_file = "subtitles.srt"
121
+ vtt_file = "subtitles.vtt"
122
+ css_file = "subtitles.css"
123
+
124
+ vh.srt2vtt(srt_file, vtt_file, css_file)
125
+ ```
126
+
127
+ # Multi-surface exposure
128
+
129
+ Every public function is reachable from five surfaces, all
130
+ systematically wired (nothing is CLI-only or library-only):
131
+
132
+ | Surface | Install | Entry point |
133
+ | --- | --- | --- |
134
+ | Python library | `pip install video-helper` | `import video_helper as vh` |
135
+ | Argparse CLI (stdlib) | `pip install video-helper` | `video-helper --help` |
136
+ | Click CLI | `pip install 'video-helper[cli]'` | `video-helper-click --help` |
137
+ | FastAPI HTTP | `pip install 'video-helper[api]'` | `uvicorn video_helper.api:app` |
138
+ | MCP server | `pip install 'video-helper[api,mcp]'` | `video-helper-mcp` |
139
+
140
+ The `Dockerfile` at the repo root ships `.[api,mcp,pyav]` by default on
141
+ `python:3.11-slim` with `ffmpeg` and `libass` โ€” one `docker build && docker run -p 8000:8000` gives you the HTTP + MCP surfaces immediately.
142
+
143
+ See [GUI.md](https://github.com/warith-harchaoui/video-helper/blob/main/GUI.md) for the innovative GUI design plan (Recipe
144
+ Canvas + frame-first comparator + batch drop zone โ€” not a CLI mirror)
145
+ and [LANDSCAPE.md](https://github.com/warith-harchaoui/video-helper/blob/main/LANDSCAPE.md) for how `video-helper` compares with
146
+ moviepy, PyAV, decord, torchvision.io, VidGear, OpenCV, and friends.
147
+
148
+ # Features
149
+ - **Video validation**: `is_valid_video_file` โ€” extension + `ffmpeg.probe` round-trip.
150
+ - **Conversion**: `video_converter` โ€” re-encode, resample fps, resize (aspect-preserving), strip audio.
151
+ - **Frame access**: `extract_frames` (generator with time/index range, stabilization, sampling) and `dump_frames` (list โ†’ video).
152
+ - **Temporal crop**: `extract_video_chunk`, `video_duration`.
153
+ - **Pipeline primitives**: `black_video`, `image_loop_to_video`, `concat_videos`, `overlay_image`, `extract_audio_track`, `mux_audio_video`, `burn_subtitles`.
154
+ - **Subtitles**: `srt2vtt` (with companion CSS), `extract_unique_colors`.
155
+
156
+ # API Reference
157
+
158
+ | Function | Signature | Description |
159
+ | --- | --- | --- |
160
+ | `is_valid_video_file` | `(video_file: str) -> bool` | True iff the file exists, has a known video extension, and `ffmpeg.probe` finds a video stream. |
161
+ | `video_dimensions` | `(video_file: str) -> dict` | Returns `{width, height, duration, frame_rate, has_sound}` via `ffmpeg.probe`. |
162
+ | `video_duration` | `(input_video: str) -> float` | Duration in seconds (thin wrapper over `video_dimensions`). |
163
+ | `video_converter` | `(input_video, output_video=None, frame_rate=None, width=None, height=None, without_sound=False)` | Re-encode with optional fps, resize (aspect-preserving black padding when both width and height are given), and audio stripping. |
164
+ | `extract_frames` | `(video_path, start_index=None, end_index=None, start_instant=None, end_instant=None, stabilize=False, frame_step=1, frame_interval=None, frame_indices=None, frame_times=None, backend="auto", hwaccel=None, http_headers=None, output_width=None, output_height=None, pad_color="black", destination="numpy", device="cpu", batch_size=None, layout="image") -> Iterator` | Multi-backend dispatcher (VidGear / PyAV / ffmpeg-pipe). `destination`: `"numpy"` (HWC BGR), `"torch"` (CHW RGB), or `"pil"` (PIL.Image RGB, `size=(W, H)`). `batch_size`+`layout` yields NHWC/NCHW or THWC/CTHW. `frame_indices`/`frame_times` = sparse access via PyAV keyframe-seek. `http_headers` forwards User-Agent/Referer/Cookie to PyAV / ffmpeg-pipe (needed for yt-dlp-resolved YouTube live, members-only, age-gated). `output_width`+`output_height` โ†’ exact size with `pad_color`-padded letterbox/pillarbox; one of them alone โ†’ aspect-preserving scale. `pad_color="transparent"` โ†’ v1.6.0. See [SPEED_ANALYSIS.md](https://github.com/warith-harchaoui/video-helper/blob/main/SPEED_ANALYSIS.md) and [EXAMPLES.md](https://github.com/warith-harchaoui/video-helper/blob/main/EXAMPLES.md#frame-access). |
165
+ | `dump_frames` | `(frames_list, output_movie, fps=30)` | Write a list of BGR frames (OpenCV convention, same as `extract_frames` yields) to a video file. |
166
+ | `extract_video_chunk` | `(input_video, sample_start, sample_end, output_video)` | Temporal crop from `sample_start` to `sample_end` (seconds). |
167
+ | `black_video` | `(duration, width, height, output_video, frame_rate=30)` | Generate a silent solid-black video. Odd dimensions are rounded down. |
168
+ | `image_loop_to_video` | `(image, duration, output_video, frame_rate=30, width=None, height=None)` | Loop a still image into a silent video; optional letterboxing. |
169
+ | `concat_videos` | `(input_videos, output_video, reencode=True, frame_rate=None)` | Concatenate clips end-to-end via the ffmpeg concat demuxer. |
170
+ | `overlay_image` | `(input_video, image, output_video, x="0", y="0", scale_width=None)` | Overlay a PNG/JPG (alpha supported); `x` / `y` accept ffmpeg expressions for time-varying motion. |
171
+ | `extract_audio_track` | `(input_video, output_audio, sample_rate=44100, channels=2, encoding="pcm_s16le")` | Pull the audio stream out of a video file. |
172
+ | `mux_audio_video` | `(input_video, input_audio, output_video, audio_codec="aac", audio_bitrate="192k", shortest=False)` | Replace the audio track of a (typically silent) video. |
173
+ | `burn_subtitles` | `(input_video, subtitles_file, output_video, force_style=None)` | Burn `.srt` / `.vtt` / `.ass` / `.ssa` into the video frames (requires ffmpeg built with libass). |
174
+ | `srt2vtt` | `(srt_file_path, vtt_file_path=None, css_file_path=None)` | Convert SRT โ†’ WebVTT, lifting `<font color>` tags into a sidecar CSS file. |
175
+ | `extract_unique_colors` | `(srt_file_path: str) -> Set[str]` | Set of unique hex colors found in `<font color>` tags of an SRT. |
176
+
177
+ By default frames are BGR `numpy.ndarray` of shape `(H, W, 3)` with pixel values in `[0, 255]`. See [EXAMPLES.md โ†’ Destination](https://github.com/warith-harchaoui/video-helper/blob/main/EXAMPLES.md#destination-numpy-torch-or-pil) for the full shape ร— colorspace table including torch (CHW/NCHW/CTHW RGB) and PIL (RGB, `size=(W, H)`).
178
+
179
+ # Author
180
+ - [Warith HARCHAOUI](https://linkedin.com/in/warith-harchaoui)
181
+
182
+ # Acknowledgements
183
+ Special thanks to [Mohamed Chelali](https://mchelali.github.io) and [Bachir Zerroug](https://www.linkedin.com/in/bachirzerroug) for fruitful discussions.
@@ -0,0 +1,110 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "video-helper"
7
+ version = "1.6.3"
8
+ description = "Video Helper โ€” utility functions for video processing exposed as library, argparse CLI, click CLI, FastAPI HTTP surface, and MCP tools. Validation, format conversion, frame extraction (multi-backend dispatcher: VidGear / PyAV / ffmpeg-pipe), HTTP-headers passthrough, exact-size output with aspect-preserving padding, subtitle conversion (SRT/VTT/CSS), and pipeline primitives (black video, image loop, concat, overlay, audio mux, subtitle burn)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10,<3.14"
11
+ license = {text = "BSD-3-Clause"}
12
+ authors = [
13
+ {name = "Warith HARCHAOUI", email = "warithmetics@deraison.ai"},
14
+ ]
15
+ keywords = ["video", "ffmpeg", "opencv", "pyav", "vidgear", "frames", "subtitles", "extraction"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: BSD License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Multimedia :: Video",
27
+ "Topic :: Multimedia :: Video :: Conversion",
28
+ ]
29
+ dependencies = [
30
+ "os-helper>=1.5.0",
31
+ "opencv-python>=4.10.0.84,<5",
32
+ "ffmpeg-python>=0.2.0,<0.3",
33
+ "vidgear>=0.3.3,<1",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ # PyAV unlocks the windowed-sequential + sparse PyAV backend (best default
38
+ # for those access patterns โ€” see SPEED_ANALYSIS.md). Without it the
39
+ # dispatcher falls back to VidGear / ffmpeg-pipe.
40
+ pyav = ["av>=12,<18"]
41
+ # Torch unlocks destination="torch" (NCHW/CTHW RGB on cpu/mps/cuda).
42
+ torch = ["torch>=2.0,<3"]
43
+ # Pillow unlocks destination="pil" (PIL.Image RGB, size=(W, H)).
44
+ pil = ["pillow>=10,<12"]
45
+ # CLI-only-with-click extra. The argparse variant needs no runtime dep
46
+ # and is always available.
47
+ cli = ["click>=8.1,<9"]
48
+ # FastAPI HTTP surface (video_helper.api). uvicorn + python-multipart
49
+ # so multipart file uploads work out of the box.
50
+ api = [
51
+ "fastapi>=0.111,<1",
52
+ "uvicorn>=0.30,<1",
53
+ "python-multipart>=0.0.9,<1",
54
+ ]
55
+ # MCP surface (video_helper.mcp). Depends on the `api` extra
56
+ # โ€” fastapi-mcp wraps the FastAPI app.
57
+ mcp = [
58
+ "fastapi-mcp>=0.3,<1",
59
+ "fastapi>=0.111,<1",
60
+ "uvicorn>=0.30,<1",
61
+ "python-multipart>=0.0.9,<1",
62
+ ]
63
+ all = ["av>=12,<18", "torch>=2.0,<3", "pillow>=10,<12"]
64
+ # `dev` keeps the bar low for CI โ€” pytest + httpx (for FastAPI TestClient)
65
+ # + click (so the click CLI import always resolves in tests) + api / mcp
66
+ # so smoke tests exercise every surface.
67
+ dev = [
68
+ "pytest>=8",
69
+ "av>=12,<18",
70
+ "httpx>=0.27",
71
+ "click>=8.1,<9",
72
+ "fastapi>=0.111,<1",
73
+ "python-multipart>=0.0.9,<1",
74
+ "fastapi-mcp>=0.3,<1",
75
+ ]
76
+
77
+ [project.scripts]
78
+ # argparse-based CLI โ€” no runtime dep beyond stdlib + core package.
79
+ video-helper = "video_helper.cli_argparse:main"
80
+ # click-based twin โ€” install with [cli] extra.
81
+ video-helper-click = "video_helper.cli_click:cli"
82
+ # MCP server (over FastAPI) โ€” install with [api,mcp] extras.
83
+ video-helper-mcp = "video_helper.mcp:main"
84
+
85
+ [project.urls]
86
+ Homepage = "https://github.com/warith-harchaoui/video-helper"
87
+ Issues = "https://github.com/warith-harchaoui/video-helper/issues"
88
+
89
+ [tool.setuptools.packages.find]
90
+ include = ["video_helper*"]
91
+ exclude = ["tests*", "assets*"]
92
+
93
+ [tool.ruff]
94
+ line-length = 100
95
+ target-version = "py310"
96
+ extend-exclude = ["assets", "tests"]
97
+
98
+ [tool.ruff.lint]
99
+ # Conservative starter set: pyflakes (F), pycodestyle errors (E), warnings (W),
100
+ # isort (I), bugbear (B), comprehensions (C4), pyupgrade (UP), simplify (SIM).
101
+ # Easy to tighten later; this is the baseline.
102
+ select = ["E", "F", "W", "I", "B", "C4", "UP", "SIM"]
103
+ ignore = [
104
+ "E501", # line-too-long โ€” handled by formatter / context-dependent
105
+ "B008", # function call in argument default โ€” too noisy for our style
106
+ "SIM108", # ternary preferred over if/else โ€” sometimes hurts readability
107
+ ]
108
+
109
+ [tool.ruff.lint.per-file-ignores]
110
+ "tests/*" = ["B011"] # assert False in pytest is fine
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,76 @@
1
+ """
2
+ Smoke tests for the FastAPI HTTP surface.
3
+
4
+ Only exercises endpoints that do not require ffmpeg or the network
5
+ (``/health``, plus OpenAPI schema introspection to catch endpoint-name
6
+ drift). Heavier round-trip tests belong to an integration suite where
7
+ a real ffmpeg is available.
8
+
9
+ Usage Example
10
+ -------------
11
+ >>> # pytest tests/test_api.py
12
+
13
+ Author
14
+ ------
15
+ Warith Harchaoui, Ph.D. โ€” https://linkedin.com/in/warith-harchaoui/
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import Iterator
21
+
22
+ import pytest
23
+
24
+ # FastAPI is in the ``[api]`` optional extra โ€” skip cleanly otherwise.
25
+ fastapi = pytest.importorskip("fastapi")
26
+ httpx = pytest.importorskip("httpx")
27
+
28
+ from fastapi.testclient import TestClient # noqa: E402
29
+
30
+
31
+ @pytest.fixture(scope="module")
32
+ def client() -> Iterator[TestClient]:
33
+ """Yield a TestClient bound to the video-helper FastAPI app."""
34
+ from video_helper.api import app
35
+
36
+ with TestClient(app) as c:
37
+ yield c
38
+
39
+
40
+ def test_health_returns_ok(client) -> None:
41
+ """``/health`` should return 200 + ``{"status": "ok"}``."""
42
+ r = client.get("/health")
43
+ assert r.status_code == 200
44
+ assert r.json() == {"status": "ok"}
45
+
46
+
47
+ def test_openapi_lists_expected_endpoints(client) -> None:
48
+ """The OpenAPI spec should list every expected route path."""
49
+ r = client.get("/openapi.json")
50
+ assert r.status_code == 200
51
+ paths = r.json()["paths"]
52
+ expected = {
53
+ "/health",
54
+ "/validate",
55
+ "/dimensions",
56
+ "/duration",
57
+ "/convert",
58
+ "/chunk",
59
+ "/black",
60
+ "/image-loop",
61
+ "/concat",
62
+ "/overlay",
63
+ "/extract-audio",
64
+ "/mux-audio",
65
+ "/burn-subs",
66
+ "/srt2vtt",
67
+ "/extract-frames",
68
+ }
69
+ assert expected.issubset(set(paths.keys()))
70
+
71
+
72
+ def test_docs_endpoint_is_served(client) -> None:
73
+ """``/docs`` should serve the Swagger UI landing HTML."""
74
+ r = client.get("/docs")
75
+ assert r.status_code == 200
76
+ assert "swagger" in r.text.lower() or "openapi" in r.text.lower()