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.
- video_helper-1.6.3/LICENSE +29 -0
- video_helper-1.6.3/PKG-INFO +241 -0
- video_helper-1.6.3/README.md +183 -0
- video_helper-1.6.3/pyproject.toml +110 -0
- video_helper-1.6.3/setup.cfg +4 -0
- video_helper-1.6.3/tests/test_api.py +76 -0
- video_helper-1.6.3/tests/test_cli.py +104 -0
- video_helper-1.6.3/tests/test_extract_frames_backends.py +622 -0
- video_helper-1.6.3/tests/test_mcp.py +39 -0
- video_helper-1.6.3/tests/test_pipeline.py +286 -0
- video_helper-1.6.3/tests/test_url_support.py +73 -0
- video_helper-1.6.3/tests/test_v15_features.py +268 -0
- video_helper-1.6.3/tests/test_video_helper.py +148 -0
- video_helper-1.6.3/video_helper/__init__.py +79 -0
- video_helper-1.6.3/video_helper/api.py +554 -0
- video_helper-1.6.3/video_helper/cli_argparse.py +867 -0
- video_helper-1.6.3/video_helper/cli_click.py +455 -0
- video_helper-1.6.3/video_helper/main.py +2309 -0
- video_helper-1.6.3/video_helper/mcp.py +92 -0
- video_helper-1.6.3/video_helper.egg-info/PKG-INFO +241 -0
- video_helper-1.6.3/video_helper.egg-info/SOURCES.txt +23 -0
- video_helper-1.6.3/video_helper.egg-info/dependency_links.txt +1 -0
- video_helper-1.6.3/video_helper.egg-info/entry_points.txt +4 -0
- video_helper-1.6.3/video_helper.egg-info/requires.txt +41 -0
- video_helper-1.6.3/video_helper.egg-info/top_level.txt +1 -0
|
@@ -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
|
+
[](https://github.com/warith-harchaoui/video-helper/actions/workflows/ci.yml) [](https://github.com/warith-harchaoui/video-helper/blob/main/LICENSE) [](#)
|
|
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
|
+
[](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
|
+
[](https://github.com/warith-harchaoui/video-helper/actions/workflows/ci.yml) [](https://github.com/warith-harchaoui/video-helper/blob/main/LICENSE) [](#)
|
|
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
|
+
[](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,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()
|