ffmpeg-python-helper 0.1.0__tar.gz → 2.0.0__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.
- ffmpeg_python_helper-2.0.0/PKG-INFO +346 -0
- ffmpeg_python_helper-2.0.0/README.md +337 -0
- {ffmpeg_python_helper-0.1.0 → ffmpeg_python_helper-2.0.0}/pyproject.toml +2 -2
- {ffmpeg_python_helper-0.1.0 → ffmpeg_python_helper-2.0.0}/pyproject.toml.orig +2 -2
- ffmpeg_python_helper-2.0.0/src/ffmpeg_python_helper/__init__.py +51 -0
- ffmpeg_python_helper-2.0.0/src/ffmpeg_python_helper/ffmpeg_api.py +380 -0
- ffmpeg_python_helper-0.1.0/PKG-INFO +0 -9
- ffmpeg_python_helper-0.1.0/README.md +0 -0
- ffmpeg_python_helper-0.1.0/src/ffmpeg_python_helper/__init__.py +0 -9
- ffmpeg_python_helper-0.1.0/src/ffmpeg_python_helper/_bundled/ffmpeg.exe +0 -4
- ffmpeg_python_helper-0.1.0/src/ffmpeg_python_helper/ffmpeg_api.py +0 -121
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ffmpeg-python-helper
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A Python wrapper for FFMPEG that provides a simple, intuitive API for common video processing tasks including format conversion, GIF creation, and video trimming.
|
|
5
|
+
Author: marjon
|
|
6
|
+
Author-email: marjon <marjongodito@gmanmi.com>
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# FFMPEG Python Helper
|
|
11
|
+
|
|
12
|
+
A Python wrapper for FFMPEG that provides a simple, intuitive API for common video processing tasks.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- 🔧 **Easy FFMPEG Integration** - Automatically detects FFMPEG installation
|
|
17
|
+
- 🎥 **Video Processing** - Reformat videos between formats
|
|
18
|
+
- 🎞️ **GIF Creation** - Convert videos to optimized GIFs with customizable settings
|
|
19
|
+
- 🧠 **In-Memory Processing** - Process video data directly from bytes without temporary files
|
|
20
|
+
- ✂️ **Video Trimming** - Trim videos with precise start time and duration control
|
|
21
|
+
- 🐍 **Pythonic API** - Clean, object-oriented interface with proper error handling
|
|
22
|
+
- 📁 **File Validation** - Automatic input file existence checking
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
- Python 3.14 or higher
|
|
28
|
+
- FFMPEG installed and available in your system PATH
|
|
29
|
+
|
|
30
|
+
### Install FFMPEG Python Helper
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install ffmpeg-python-helper
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or install from source:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/yourusername/ffmpeg-python-helper.git
|
|
40
|
+
cd ffmpeg-python-helper
|
|
41
|
+
pip install -e .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from ffmpeg_python_helper import FFMPEG
|
|
48
|
+
|
|
49
|
+
# Initialize the FFMPEG wrapper
|
|
50
|
+
ffmpeg = FFMPEG()
|
|
51
|
+
|
|
52
|
+
# Check if FFMPEG is available
|
|
53
|
+
print(f"FFMPEG executable found at: {ffmpeg.executable}")
|
|
54
|
+
|
|
55
|
+
# Convert a video file
|
|
56
|
+
output = ffmpeg.reformat("input.mp4", "output.avi")
|
|
57
|
+
print(output.decode())
|
|
58
|
+
|
|
59
|
+
# Create a GIF from video
|
|
60
|
+
output = ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
|
|
61
|
+
print(output.decode())
|
|
62
|
+
|
|
63
|
+
# Trim a video
|
|
64
|
+
output = ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
|
|
65
|
+
print(output.decode())
|
|
66
|
+
|
|
67
|
+
# Create GIF from in-memory video data
|
|
68
|
+
with open("video.mp4", "rb") as f:
|
|
69
|
+
video_data = f.read()
|
|
70
|
+
gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
71
|
+
with open("memory.gif", "wb") as f:
|
|
72
|
+
f.write(gif_data)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### `FFMPEG` Class
|
|
78
|
+
|
|
79
|
+
The main class that wraps FFMPEG functionality.
|
|
80
|
+
|
|
81
|
+
#### Constructor
|
|
82
|
+
```python
|
|
83
|
+
FFMPEG()
|
|
84
|
+
```
|
|
85
|
+
Creates a new FFMPEG instance. Automatically searches for FFMPEG in the system PATH.
|
|
86
|
+
- **Raises**: `FileNotFoundError` if FFMPEG is not found in PATH
|
|
87
|
+
|
|
88
|
+
#### Properties
|
|
89
|
+
- `executable` (str): The path to the FFMPEG executable found in the system
|
|
90
|
+
|
|
91
|
+
#### Class Methods
|
|
92
|
+
```python
|
|
93
|
+
@classmethod
|
|
94
|
+
def api(cls) -> "FFMPEG"
|
|
95
|
+
```
|
|
96
|
+
Factory method that returns a new FFMPEG instance.
|
|
97
|
+
- **Returns**: `FFMPEG` instance
|
|
98
|
+
|
|
99
|
+
#### Instance Methods
|
|
100
|
+
|
|
101
|
+
##### `execute(*args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]`
|
|
102
|
+
Execute raw FFMPEG commands with the given arguments.
|
|
103
|
+
|
|
104
|
+
**Parameters:**
|
|
105
|
+
- `*args` (str): FFMPEG command-line arguments
|
|
106
|
+
- `input_data` (bytes | None, optional): Optional bytes to send to FFMPEG's stdin
|
|
107
|
+
|
|
108
|
+
**Returns:**
|
|
109
|
+
- `tuple[bytes, bytes]`: A tuple containing (stdout, stderr) as bytes
|
|
110
|
+
|
|
111
|
+
**Raises:**
|
|
112
|
+
- `FileNotFoundError`: If FFMPEG executable is not found
|
|
113
|
+
- `RuntimeError`: If FFMPEG command returns a non-zero exit code
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
```python
|
|
117
|
+
stdout, stderr = ffmpeg.execute("-version")
|
|
118
|
+
print(stdout.decode())
|
|
119
|
+
|
|
120
|
+
# Process data from memory
|
|
121
|
+
video_data = b"...video bytes..."
|
|
122
|
+
stdout, stderr = ffmpeg.execute("-i", "pipe:0", "-f", "null", "-", input_data=video_data)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
##### `reformat(input_file: str, output_file: str) -> bytes`
|
|
126
|
+
Convert a video file from one format to another.
|
|
127
|
+
|
|
128
|
+
**Parameters:**
|
|
129
|
+
- `input_file` (str): Path to the input video file
|
|
130
|
+
- `output_file` (str): Path for the output video file
|
|
131
|
+
|
|
132
|
+
**Returns:**
|
|
133
|
+
- `bytes`: FFMPEG output (stdout or stderr) as bytes
|
|
134
|
+
|
|
135
|
+
**Raises:**
|
|
136
|
+
- `FileNotFoundError`: If input file doesn't exist
|
|
137
|
+
|
|
138
|
+
**Example:**
|
|
139
|
+
```python
|
|
140
|
+
output = ffmpeg.reformat("input.mov", "output.mp4")
|
|
141
|
+
print(output.decode())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
##### `gif(input_file: str, output_file: str, fps: int = 10, scale: int = 320) -> bytes`
|
|
145
|
+
Convert a video file to an optimized GIF.
|
|
146
|
+
|
|
147
|
+
**Parameters:**
|
|
148
|
+
- `input_file` (str): Path to the input video file
|
|
149
|
+
- `output_file` (str): Path for the output GIF file
|
|
150
|
+
- `fps` (int, optional): Frames per second for the GIF (default: 10)
|
|
151
|
+
- `scale` (int, optional): Width of the GIF in pixels, height is auto-scaled (default: 320)
|
|
152
|
+
|
|
153
|
+
**Returns:**
|
|
154
|
+
- `bytes`: FFMPEG output (stdout or stderr) as bytes
|
|
155
|
+
|
|
156
|
+
**Raises:**
|
|
157
|
+
- `FileNotFoundError`: If input file doesn't exist
|
|
158
|
+
- `RuntimeError`: If GIF file was not created successfully
|
|
159
|
+
|
|
160
|
+
**Example:**
|
|
161
|
+
```python
|
|
162
|
+
output = ffmpeg.gif("video.mp4", "output.gif", fps=15, scale=640)
|
|
163
|
+
print(output.decode())
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
##### `gifs(input_byte: bytes, fps: int = 10, scale: int = 320) -> bytes`
|
|
167
|
+
Convert video data from bytes to an optimized GIF (in-memory processing).
|
|
168
|
+
|
|
169
|
+
**Parameters:**
|
|
170
|
+
- `input_byte` (bytes): Video data as bytes to convert to GIF
|
|
171
|
+
- `fps` (int, optional): Frames per second for the GIF (default: 10)
|
|
172
|
+
- `scale` (int, optional): Width of the GIF in pixels, height is auto-scaled (default: 320)
|
|
173
|
+
|
|
174
|
+
**Returns:**
|
|
175
|
+
- `bytes`: The generated GIF data as bytes
|
|
176
|
+
|
|
177
|
+
**Raises:**
|
|
178
|
+
- `RuntimeError`: If GIF conversion fails
|
|
179
|
+
|
|
180
|
+
**Example:**
|
|
181
|
+
```python
|
|
182
|
+
# Read video data from a file
|
|
183
|
+
with open("video.mp4", "rb") as f:
|
|
184
|
+
video_data = f.read()
|
|
185
|
+
|
|
186
|
+
# Convert to GIF in memory
|
|
187
|
+
gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
188
|
+
|
|
189
|
+
# Save the GIF
|
|
190
|
+
with open("output.gif", "wb") as f:
|
|
191
|
+
f.write(gif_data)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
##### `trim(input_file: str, output_file: str, start: float = 0, duration: float | None = None) -> bytes`
|
|
195
|
+
Trim a video file.
|
|
196
|
+
|
|
197
|
+
**Parameters:**
|
|
198
|
+
- `input_file` (str): Path to the input video file
|
|
199
|
+
- `output_file` (str): Path for the output trimmed video
|
|
200
|
+
- `start` (float, optional): Start time in seconds (default: 0)
|
|
201
|
+
- `duration` (float | None, optional): Duration in seconds, or None for remaining video (default: None)
|
|
202
|
+
|
|
203
|
+
**Returns:**
|
|
204
|
+
- `bytes`: FFMPEG output (stdout or stderr) as bytes
|
|
205
|
+
|
|
206
|
+
**Raises:**
|
|
207
|
+
- `FileNotFoundError`: If input file doesn't exist
|
|
208
|
+
- `ValueError`: If start is negative or duration is non-positive
|
|
209
|
+
- `RuntimeError`: If trimmed video was not created successfully
|
|
210
|
+
|
|
211
|
+
**Example:**
|
|
212
|
+
```python
|
|
213
|
+
# Trim from 5 seconds to 10 seconds (5-second clip)
|
|
214
|
+
output = ffmpeg.trim("video.mp4", "clip.mp4", start=5, duration=5)
|
|
215
|
+
print(output.decode())
|
|
216
|
+
|
|
217
|
+
# Trim from 10 seconds to the end of video
|
|
218
|
+
output = ffmpeg.trim("video.mp4", "ending.mp4", start=10)
|
|
219
|
+
print(output.decode())
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Advanced Usage
|
|
223
|
+
|
|
224
|
+
### Custom FFMPEG Commands
|
|
225
|
+
For operations not covered by the built-in methods, use the `execute` method:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Extract audio from video
|
|
229
|
+
ffmpeg.execute("-i", "video.mp4", "-q:a", "0", "-map", "a", "audio.mp3")
|
|
230
|
+
|
|
231
|
+
# Add watermark to video
|
|
232
|
+
ffmpeg.execute("-i", "video.mp4", "-i", "watermark.png",
|
|
233
|
+
"-filter_complex", "overlay=10:10", "output.mp4")
|
|
234
|
+
|
|
235
|
+
# Change video bitrate
|
|
236
|
+
ffmpeg.execute("-i", "input.mp4", "-b:v", "1M", "output.mp4")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Error Handling
|
|
240
|
+
```python
|
|
241
|
+
from ffmpeg_python_helper import FFMPEG
|
|
242
|
+
import sys
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
ffmpeg = FFMPEG()
|
|
246
|
+
ffmpeg.gif("video.mp4", "output.gif")
|
|
247
|
+
except FileNotFoundError as e:
|
|
248
|
+
print(f"FFMPEG not found: {e}", file=sys.stderr)
|
|
249
|
+
sys.exit(1)
|
|
250
|
+
except RuntimeError as e:
|
|
251
|
+
print(f"Processing failed: {e}", file=sys.stderr)
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Common Use Cases
|
|
256
|
+
|
|
257
|
+
### Batch Processing
|
|
258
|
+
```python
|
|
259
|
+
import os
|
|
260
|
+
from ffmpeg_python_helper import FFMPEG
|
|
261
|
+
|
|
262
|
+
ffmpeg = FFMPEG()
|
|
263
|
+
videos = ["video1.mp4", "video2.mp4", "video3.mp4"]
|
|
264
|
+
|
|
265
|
+
for video in videos:
|
|
266
|
+
if os.path.exists(video):
|
|
267
|
+
base_name = os.path.splitext(video)[0]
|
|
268
|
+
ffmpeg.gif(video, f"{base_name}.gif", fps=12, scale=400)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Video Compilation
|
|
272
|
+
```python
|
|
273
|
+
from ffmpeg_python_helper import FFMPEG
|
|
274
|
+
|
|
275
|
+
ffmpeg = FFMPEG()
|
|
276
|
+
|
|
277
|
+
# Trim interesting parts
|
|
278
|
+
ffmpeg.trim("concert.mp4", "intro.mp4", start=0, duration=30)
|
|
279
|
+
ffmpeg.trim("concert.mp4", "chorus.mp4", start=120, duration=45)
|
|
280
|
+
ffmpeg.trim("concert.mp4", "finale.mp4", start=300, duration=60)
|
|
281
|
+
|
|
282
|
+
# Later, use FFMPEG to concatenate trimmed parts
|
|
283
|
+
ffmpeg.execute("-f", "concat", "-safe", "0", "-i", "parts.txt", "highlight_reel.mp4")
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Troubleshooting
|
|
287
|
+
|
|
288
|
+
### FFMPEG Not Found
|
|
289
|
+
If you get `FileNotFoundError` when creating an FFMPEG instance:
|
|
290
|
+
|
|
291
|
+
1. **Install FFMPEG**:
|
|
292
|
+
- **Windows**: Download from [ffmpeg.org](https://ffmpeg.org/download.html)
|
|
293
|
+
- **macOS**: `brew install ffmpeg`
|
|
294
|
+
- **Linux**: `sudo apt install ffmpeg` (Ubuntu/Debian) or `sudo yum install ffmpeg` (Fedora/RHEL)
|
|
295
|
+
|
|
296
|
+
2. **Add to PATH**:
|
|
297
|
+
- Ensure FFMPEG is in your system PATH
|
|
298
|
+
- Test with `ffmpeg -version` in your terminal
|
|
299
|
+
|
|
300
|
+
### File Not Found Errors
|
|
301
|
+
- Ensure input file paths are correct and files exist
|
|
302
|
+
- Use absolute paths if working with files in different directories
|
|
303
|
+
- Check file permissions
|
|
304
|
+
|
|
305
|
+
### GIF Creation Issues
|
|
306
|
+
- Lower FPS or scale if GIF file is too large
|
|
307
|
+
- Ensure input video has sufficient quality
|
|
308
|
+
- Check available disk space
|
|
309
|
+
|
|
310
|
+
## Development
|
|
311
|
+
|
|
312
|
+
### Running Tests
|
|
313
|
+
```bash
|
|
314
|
+
python -m pytest tests/
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Building Documentation
|
|
318
|
+
```bash
|
|
319
|
+
# Install documentation dependencies
|
|
320
|
+
pip install pdoc3
|
|
321
|
+
|
|
322
|
+
# Generate API documentation
|
|
323
|
+
pdoc --html ffmpeg_python_helper --output-dir docs
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Contributing
|
|
327
|
+
1. Fork the repository
|
|
328
|
+
2. Create a feature branch
|
|
329
|
+
3. Add tests for your changes
|
|
330
|
+
4. Ensure all tests pass
|
|
331
|
+
5. Submit a pull request
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT License - see LICENSE file for details
|
|
336
|
+
|
|
337
|
+
## Support
|
|
338
|
+
|
|
339
|
+
- **Issues**: [GitHub Issues](https://github.com/yourusername/ffmpeg-python-helper/issues)
|
|
340
|
+
- **Documentation**: [ReadTheDocs](https://ffmpeg-python-helper.readthedocs.io)
|
|
341
|
+
- **Email**: marjongodito@gmanmi.com
|
|
342
|
+
|
|
343
|
+
## Acknowledgments
|
|
344
|
+
|
|
345
|
+
- FFMPEG team for the amazing multimedia framework
|
|
346
|
+
- Python community for excellent tooling and libraries
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# FFMPEG Python Helper
|
|
2
|
+
|
|
3
|
+
A Python wrapper for FFMPEG that provides a simple, intuitive API for common video processing tasks.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔧 **Easy FFMPEG Integration** - Automatically detects FFMPEG installation
|
|
8
|
+
- 🎥 **Video Processing** - Reformat videos between formats
|
|
9
|
+
- 🎞️ **GIF Creation** - Convert videos to optimized GIFs with customizable settings
|
|
10
|
+
- 🧠 **In-Memory Processing** - Process video data directly from bytes without temporary files
|
|
11
|
+
- ✂️ **Video Trimming** - Trim videos with precise start time and duration control
|
|
12
|
+
- 🐍 **Pythonic API** - Clean, object-oriented interface with proper error handling
|
|
13
|
+
- 📁 **File Validation** - Automatic input file existence checking
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
- Python 3.14 or higher
|
|
19
|
+
- FFMPEG installed and available in your system PATH
|
|
20
|
+
|
|
21
|
+
### Install FFMPEG Python Helper
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install ffmpeg-python-helper
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install from source:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/yourusername/ffmpeg-python-helper.git
|
|
31
|
+
cd ffmpeg-python-helper
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from ffmpeg_python_helper import FFMPEG
|
|
39
|
+
|
|
40
|
+
# Initialize the FFMPEG wrapper
|
|
41
|
+
ffmpeg = FFMPEG()
|
|
42
|
+
|
|
43
|
+
# Check if FFMPEG is available
|
|
44
|
+
print(f"FFMPEG executable found at: {ffmpeg.executable}")
|
|
45
|
+
|
|
46
|
+
# Convert a video file
|
|
47
|
+
output = ffmpeg.reformat("input.mp4", "output.avi")
|
|
48
|
+
print(output.decode())
|
|
49
|
+
|
|
50
|
+
# Create a GIF from video
|
|
51
|
+
output = ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
|
|
52
|
+
print(output.decode())
|
|
53
|
+
|
|
54
|
+
# Trim a video
|
|
55
|
+
output = ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
|
|
56
|
+
print(output.decode())
|
|
57
|
+
|
|
58
|
+
# Create GIF from in-memory video data
|
|
59
|
+
with open("video.mp4", "rb") as f:
|
|
60
|
+
video_data = f.read()
|
|
61
|
+
gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
62
|
+
with open("memory.gif", "wb") as f:
|
|
63
|
+
f.write(gif_data)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API Reference
|
|
67
|
+
|
|
68
|
+
### `FFMPEG` Class
|
|
69
|
+
|
|
70
|
+
The main class that wraps FFMPEG functionality.
|
|
71
|
+
|
|
72
|
+
#### Constructor
|
|
73
|
+
```python
|
|
74
|
+
FFMPEG()
|
|
75
|
+
```
|
|
76
|
+
Creates a new FFMPEG instance. Automatically searches for FFMPEG in the system PATH.
|
|
77
|
+
- **Raises**: `FileNotFoundError` if FFMPEG is not found in PATH
|
|
78
|
+
|
|
79
|
+
#### Properties
|
|
80
|
+
- `executable` (str): The path to the FFMPEG executable found in the system
|
|
81
|
+
|
|
82
|
+
#### Class Methods
|
|
83
|
+
```python
|
|
84
|
+
@classmethod
|
|
85
|
+
def api(cls) -> "FFMPEG"
|
|
86
|
+
```
|
|
87
|
+
Factory method that returns a new FFMPEG instance.
|
|
88
|
+
- **Returns**: `FFMPEG` instance
|
|
89
|
+
|
|
90
|
+
#### Instance Methods
|
|
91
|
+
|
|
92
|
+
##### `execute(*args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]`
|
|
93
|
+
Execute raw FFMPEG commands with the given arguments.
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
- `*args` (str): FFMPEG command-line arguments
|
|
97
|
+
- `input_data` (bytes | None, optional): Optional bytes to send to FFMPEG's stdin
|
|
98
|
+
|
|
99
|
+
**Returns:**
|
|
100
|
+
- `tuple[bytes, bytes]`: A tuple containing (stdout, stderr) as bytes
|
|
101
|
+
|
|
102
|
+
**Raises:**
|
|
103
|
+
- `FileNotFoundError`: If FFMPEG executable is not found
|
|
104
|
+
- `RuntimeError`: If FFMPEG command returns a non-zero exit code
|
|
105
|
+
|
|
106
|
+
**Example:**
|
|
107
|
+
```python
|
|
108
|
+
stdout, stderr = ffmpeg.execute("-version")
|
|
109
|
+
print(stdout.decode())
|
|
110
|
+
|
|
111
|
+
# Process data from memory
|
|
112
|
+
video_data = b"...video bytes..."
|
|
113
|
+
stdout, stderr = ffmpeg.execute("-i", "pipe:0", "-f", "null", "-", input_data=video_data)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
##### `reformat(input_file: str, output_file: str) -> bytes`
|
|
117
|
+
Convert a video file from one format to another.
|
|
118
|
+
|
|
119
|
+
**Parameters:**
|
|
120
|
+
- `input_file` (str): Path to the input video file
|
|
121
|
+
- `output_file` (str): Path for the output video file
|
|
122
|
+
|
|
123
|
+
**Returns:**
|
|
124
|
+
- `bytes`: FFMPEG output (stdout or stderr) as bytes
|
|
125
|
+
|
|
126
|
+
**Raises:**
|
|
127
|
+
- `FileNotFoundError`: If input file doesn't exist
|
|
128
|
+
|
|
129
|
+
**Example:**
|
|
130
|
+
```python
|
|
131
|
+
output = ffmpeg.reformat("input.mov", "output.mp4")
|
|
132
|
+
print(output.decode())
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
##### `gif(input_file: str, output_file: str, fps: int = 10, scale: int = 320) -> bytes`
|
|
136
|
+
Convert a video file to an optimized GIF.
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
- `input_file` (str): Path to the input video file
|
|
140
|
+
- `output_file` (str): Path for the output GIF file
|
|
141
|
+
- `fps` (int, optional): Frames per second for the GIF (default: 10)
|
|
142
|
+
- `scale` (int, optional): Width of the GIF in pixels, height is auto-scaled (default: 320)
|
|
143
|
+
|
|
144
|
+
**Returns:**
|
|
145
|
+
- `bytes`: FFMPEG output (stdout or stderr) as bytes
|
|
146
|
+
|
|
147
|
+
**Raises:**
|
|
148
|
+
- `FileNotFoundError`: If input file doesn't exist
|
|
149
|
+
- `RuntimeError`: If GIF file was not created successfully
|
|
150
|
+
|
|
151
|
+
**Example:**
|
|
152
|
+
```python
|
|
153
|
+
output = ffmpeg.gif("video.mp4", "output.gif", fps=15, scale=640)
|
|
154
|
+
print(output.decode())
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
##### `gifs(input_byte: bytes, fps: int = 10, scale: int = 320) -> bytes`
|
|
158
|
+
Convert video data from bytes to an optimized GIF (in-memory processing).
|
|
159
|
+
|
|
160
|
+
**Parameters:**
|
|
161
|
+
- `input_byte` (bytes): Video data as bytes to convert to GIF
|
|
162
|
+
- `fps` (int, optional): Frames per second for the GIF (default: 10)
|
|
163
|
+
- `scale` (int, optional): Width of the GIF in pixels, height is auto-scaled (default: 320)
|
|
164
|
+
|
|
165
|
+
**Returns:**
|
|
166
|
+
- `bytes`: The generated GIF data as bytes
|
|
167
|
+
|
|
168
|
+
**Raises:**
|
|
169
|
+
- `RuntimeError`: If GIF conversion fails
|
|
170
|
+
|
|
171
|
+
**Example:**
|
|
172
|
+
```python
|
|
173
|
+
# Read video data from a file
|
|
174
|
+
with open("video.mp4", "rb") as f:
|
|
175
|
+
video_data = f.read()
|
|
176
|
+
|
|
177
|
+
# Convert to GIF in memory
|
|
178
|
+
gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
179
|
+
|
|
180
|
+
# Save the GIF
|
|
181
|
+
with open("output.gif", "wb") as f:
|
|
182
|
+
f.write(gif_data)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
##### `trim(input_file: str, output_file: str, start: float = 0, duration: float | None = None) -> bytes`
|
|
186
|
+
Trim a video file.
|
|
187
|
+
|
|
188
|
+
**Parameters:**
|
|
189
|
+
- `input_file` (str): Path to the input video file
|
|
190
|
+
- `output_file` (str): Path for the output trimmed video
|
|
191
|
+
- `start` (float, optional): Start time in seconds (default: 0)
|
|
192
|
+
- `duration` (float | None, optional): Duration in seconds, or None for remaining video (default: None)
|
|
193
|
+
|
|
194
|
+
**Returns:**
|
|
195
|
+
- `bytes`: FFMPEG output (stdout or stderr) as bytes
|
|
196
|
+
|
|
197
|
+
**Raises:**
|
|
198
|
+
- `FileNotFoundError`: If input file doesn't exist
|
|
199
|
+
- `ValueError`: If start is negative or duration is non-positive
|
|
200
|
+
- `RuntimeError`: If trimmed video was not created successfully
|
|
201
|
+
|
|
202
|
+
**Example:**
|
|
203
|
+
```python
|
|
204
|
+
# Trim from 5 seconds to 10 seconds (5-second clip)
|
|
205
|
+
output = ffmpeg.trim("video.mp4", "clip.mp4", start=5, duration=5)
|
|
206
|
+
print(output.decode())
|
|
207
|
+
|
|
208
|
+
# Trim from 10 seconds to the end of video
|
|
209
|
+
output = ffmpeg.trim("video.mp4", "ending.mp4", start=10)
|
|
210
|
+
print(output.decode())
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Advanced Usage
|
|
214
|
+
|
|
215
|
+
### Custom FFMPEG Commands
|
|
216
|
+
For operations not covered by the built-in methods, use the `execute` method:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
# Extract audio from video
|
|
220
|
+
ffmpeg.execute("-i", "video.mp4", "-q:a", "0", "-map", "a", "audio.mp3")
|
|
221
|
+
|
|
222
|
+
# Add watermark to video
|
|
223
|
+
ffmpeg.execute("-i", "video.mp4", "-i", "watermark.png",
|
|
224
|
+
"-filter_complex", "overlay=10:10", "output.mp4")
|
|
225
|
+
|
|
226
|
+
# Change video bitrate
|
|
227
|
+
ffmpeg.execute("-i", "input.mp4", "-b:v", "1M", "output.mp4")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Error Handling
|
|
231
|
+
```python
|
|
232
|
+
from ffmpeg_python_helper import FFMPEG
|
|
233
|
+
import sys
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
ffmpeg = FFMPEG()
|
|
237
|
+
ffmpeg.gif("video.mp4", "output.gif")
|
|
238
|
+
except FileNotFoundError as e:
|
|
239
|
+
print(f"FFMPEG not found: {e}", file=sys.stderr)
|
|
240
|
+
sys.exit(1)
|
|
241
|
+
except RuntimeError as e:
|
|
242
|
+
print(f"Processing failed: {e}", file=sys.stderr)
|
|
243
|
+
sys.exit(1)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Common Use Cases
|
|
247
|
+
|
|
248
|
+
### Batch Processing
|
|
249
|
+
```python
|
|
250
|
+
import os
|
|
251
|
+
from ffmpeg_python_helper import FFMPEG
|
|
252
|
+
|
|
253
|
+
ffmpeg = FFMPEG()
|
|
254
|
+
videos = ["video1.mp4", "video2.mp4", "video3.mp4"]
|
|
255
|
+
|
|
256
|
+
for video in videos:
|
|
257
|
+
if os.path.exists(video):
|
|
258
|
+
base_name = os.path.splitext(video)[0]
|
|
259
|
+
ffmpeg.gif(video, f"{base_name}.gif", fps=12, scale=400)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Video Compilation
|
|
263
|
+
```python
|
|
264
|
+
from ffmpeg_python_helper import FFMPEG
|
|
265
|
+
|
|
266
|
+
ffmpeg = FFMPEG()
|
|
267
|
+
|
|
268
|
+
# Trim interesting parts
|
|
269
|
+
ffmpeg.trim("concert.mp4", "intro.mp4", start=0, duration=30)
|
|
270
|
+
ffmpeg.trim("concert.mp4", "chorus.mp4", start=120, duration=45)
|
|
271
|
+
ffmpeg.trim("concert.mp4", "finale.mp4", start=300, duration=60)
|
|
272
|
+
|
|
273
|
+
# Later, use FFMPEG to concatenate trimmed parts
|
|
274
|
+
ffmpeg.execute("-f", "concat", "-safe", "0", "-i", "parts.txt", "highlight_reel.mp4")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Troubleshooting
|
|
278
|
+
|
|
279
|
+
### FFMPEG Not Found
|
|
280
|
+
If you get `FileNotFoundError` when creating an FFMPEG instance:
|
|
281
|
+
|
|
282
|
+
1. **Install FFMPEG**:
|
|
283
|
+
- **Windows**: Download from [ffmpeg.org](https://ffmpeg.org/download.html)
|
|
284
|
+
- **macOS**: `brew install ffmpeg`
|
|
285
|
+
- **Linux**: `sudo apt install ffmpeg` (Ubuntu/Debian) or `sudo yum install ffmpeg` (Fedora/RHEL)
|
|
286
|
+
|
|
287
|
+
2. **Add to PATH**:
|
|
288
|
+
- Ensure FFMPEG is in your system PATH
|
|
289
|
+
- Test with `ffmpeg -version` in your terminal
|
|
290
|
+
|
|
291
|
+
### File Not Found Errors
|
|
292
|
+
- Ensure input file paths are correct and files exist
|
|
293
|
+
- Use absolute paths if working with files in different directories
|
|
294
|
+
- Check file permissions
|
|
295
|
+
|
|
296
|
+
### GIF Creation Issues
|
|
297
|
+
- Lower FPS or scale if GIF file is too large
|
|
298
|
+
- Ensure input video has sufficient quality
|
|
299
|
+
- Check available disk space
|
|
300
|
+
|
|
301
|
+
## Development
|
|
302
|
+
|
|
303
|
+
### Running Tests
|
|
304
|
+
```bash
|
|
305
|
+
python -m pytest tests/
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Building Documentation
|
|
309
|
+
```bash
|
|
310
|
+
# Install documentation dependencies
|
|
311
|
+
pip install pdoc3
|
|
312
|
+
|
|
313
|
+
# Generate API documentation
|
|
314
|
+
pdoc --html ffmpeg_python_helper --output-dir docs
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Contributing
|
|
318
|
+
1. Fork the repository
|
|
319
|
+
2. Create a feature branch
|
|
320
|
+
3. Add tests for your changes
|
|
321
|
+
4. Ensure all tests pass
|
|
322
|
+
5. Submit a pull request
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
MIT License - see LICENSE file for details
|
|
327
|
+
|
|
328
|
+
## Support
|
|
329
|
+
|
|
330
|
+
- **Issues**: [GitHub Issues](https://github.com/yourusername/ffmpeg-python-helper/issues)
|
|
331
|
+
- **Documentation**: [ReadTheDocs](https://ffmpeg-python-helper.readthedocs.io)
|
|
332
|
+
- **Email**: marjongodito@gmanmi.com
|
|
333
|
+
|
|
334
|
+
## Acknowledgments
|
|
335
|
+
|
|
336
|
+
- FFMPEG team for the amazing multimedia framework
|
|
337
|
+
- Python community for excellent tooling and libraries
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ffmpeg-python-helper"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "2.0.0"
|
|
4
|
+
description = "A Python wrapper for FFMPEG that provides a simple, intuitive API for common video processing tasks including format conversion, GIF creation, and video trimming."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
dependencies = []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ffmpeg-python-helper"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "2.0.0"
|
|
4
|
+
description = "A Python wrapper for FFMPEG that provides a simple, intuitive API for common video processing tasks including format conversion, GIF creation, and video trimming."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "marjon", email = "marjongodito@gmanmi.com" }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FFMPEG Python Helper - A Python wrapper for FFMPEG video processing.
|
|
3
|
+
|
|
4
|
+
This package provides a simple, intuitive API for common video processing tasks
|
|
5
|
+
including format conversion, GIF creation, video trimming, and in-memory processing.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from ffmpeg_python_helper import FFMPEG
|
|
9
|
+
>>> ffmpeg = FFMPEG()
|
|
10
|
+
>>> ffmpeg.reformat("input.mp4", "output.avi")
|
|
11
|
+
>>> ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
|
|
12
|
+
>>> ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
|
|
13
|
+
>>>
|
|
14
|
+
>>> # In-memory processing
|
|
15
|
+
>>> with open("video.mp4", "rb") as f:
|
|
16
|
+
... video_data = f.read()
|
|
17
|
+
>>> gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
18
|
+
|
|
19
|
+
For detailed API documentation, see:
|
|
20
|
+
- FFMPEG class documentation
|
|
21
|
+
- README.md for usage examples and tutorials
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .ffmpeg_api import FFMPEG
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'FFMPEG'
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
__author__ = "marjon <marjongodito@gmanmi.com>"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> None:
|
|
35
|
+
"""
|
|
36
|
+
Command-line entry point for the FFMPEG Python Helper.
|
|
37
|
+
|
|
38
|
+
This function is called when the package is executed as a script.
|
|
39
|
+
It demonstrates basic functionality by:
|
|
40
|
+
1. Creating an FFMPEG instance
|
|
41
|
+
2. Printing the path to the FFMPEG executable
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
$ python -m ffmpeg_python_helper
|
|
45
|
+
FFMPEG executable found at: /usr/bin/ffmpeg
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
FileNotFoundError: If FFMPEG is not found in PATH.
|
|
49
|
+
"""
|
|
50
|
+
ffmpeg = FFMPEG()
|
|
51
|
+
print(f"FFMPEG executable found at: {ffmpeg.executable}")
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FFMPEG:
|
|
6
|
+
"""
|
|
7
|
+
A Python wrapper for FFMPEG that provides a simple, intuitive API for
|
|
8
|
+
common video processing tasks.
|
|
9
|
+
|
|
10
|
+
This class automatically detects FFMPEG installation in the system PATH
|
|
11
|
+
and provides methods for video conversion, GIF creation, and video trimming.
|
|
12
|
+
It supports both file-based operations and in-memory data processing.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> from ffmpeg_python_helper import FFMPEG
|
|
16
|
+
>>> ffmpeg = FFMPEG()
|
|
17
|
+
>>> ffmpeg.reformat("input.mp4", "output.avi")
|
|
18
|
+
>>> ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
|
|
19
|
+
>>> ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
|
|
20
|
+
>>>
|
|
21
|
+
>>> # In-memory processing
|
|
22
|
+
>>> with open("video.mp4", "rb") as f:
|
|
23
|
+
... video_data = f.read()
|
|
24
|
+
>>> gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
executable (str): The path to the FFMPEG executable found in the system PATH.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Initialize a new FFMPEG instance.
|
|
33
|
+
|
|
34
|
+
Automatically searches for FFMPEG in the system PATH.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
FileNotFoundError: If FFMPEG is not found in the system PATH.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> try:
|
|
41
|
+
... ffmpeg = FFMPEG()
|
|
42
|
+
... print(f"FFMPEG found at: {ffmpeg.executable}")
|
|
43
|
+
... except FileNotFoundError as e:
|
|
44
|
+
... print(f"FFMPEG not found: {e}")
|
|
45
|
+
"""
|
|
46
|
+
import shutil
|
|
47
|
+
self.executable = shutil.which('ffmpeg')
|
|
48
|
+
|
|
49
|
+
if self.executable is None:
|
|
50
|
+
raise FileNotFoundError("""
|
|
51
|
+
FFmpeg is not installed or could not be found in PATH.
|
|
52
|
+
Please install FFmpeg and make sure it is available
|
|
53
|
+
in your system PATH.
|
|
54
|
+
""")
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def api(cls) -> "FFMPEG":
|
|
58
|
+
"""
|
|
59
|
+
Factory method that returns a new FFMPEG instance.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
FFMPEG: A new instance of the FFMPEG class.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> ffmpeg = FFMPEG.api()
|
|
66
|
+
>>> ffmpeg.execute("-version")
|
|
67
|
+
"""
|
|
68
|
+
return cls()
|
|
69
|
+
|
|
70
|
+
def execute(self, *args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]:
|
|
71
|
+
"""
|
|
72
|
+
Execute raw FFMPEG commands with the given arguments.
|
|
73
|
+
|
|
74
|
+
This method allows you to run any FFMPEG command directly,
|
|
75
|
+
providing maximum flexibility for operations not covered
|
|
76
|
+
by the built-in methods.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
*args: FFMPEG command-line arguments as strings.
|
|
80
|
+
input_data: Optional bytes to send to FFMPEG's stdin. Useful for
|
|
81
|
+
piping data directly to FFMPEG without intermediate files.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
tuple[bytes, bytes]: A tuple containing (stdout, stderr) from FFMPEG
|
|
85
|
+
as bytes objects.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
FileNotFoundError: If FFMPEG executable is not found.
|
|
89
|
+
RuntimeError: If FFMPEG command returns a non-zero exit code.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> stdout, stderr = ffmpeg.execute("-version")
|
|
93
|
+
>>> print(stdout.decode())
|
|
94
|
+
|
|
95
|
+
>>> # Extract audio from video
|
|
96
|
+
>>> ffmpeg.execute("-i", "video.mp4", "-q:a", "0", "-map", "a", "audio.mp3")
|
|
97
|
+
|
|
98
|
+
>>> # Add watermark to video
|
|
99
|
+
>>> ffmpeg.execute("-i", "video.mp4", "-i", "watermark.png",
|
|
100
|
+
... "-filter_complex", "overlay=10:10", "output.mp4")
|
|
101
|
+
|
|
102
|
+
>>> # Process data from memory
|
|
103
|
+
>>> video_data = b"...video bytes..."
|
|
104
|
+
>>> stdout, stderr = ffmpeg.execute("-i", "pipe:0", "-f", "null", "-",
|
|
105
|
+
... input_data=video_data)
|
|
106
|
+
"""
|
|
107
|
+
if self.executable:
|
|
108
|
+
result = subprocess.run(
|
|
109
|
+
[self.executable, *args],
|
|
110
|
+
input=input_data,
|
|
111
|
+
stdout=subprocess.PIPE,
|
|
112
|
+
stderr=subprocess.PIPE,
|
|
113
|
+
check=False
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if result.returncode != 0:
|
|
117
|
+
raise RuntimeError(
|
|
118
|
+
result.stderr.decode(errors="replace")
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return result.stdout, result.stderr
|
|
122
|
+
|
|
123
|
+
raise FileNotFoundError("""
|
|
124
|
+
No ffmpeg executable found.
|
|
125
|
+
|
|
126
|
+
Please Install ffmpeg first.
|
|
127
|
+
""")
|
|
128
|
+
|
|
129
|
+
def reformat(self, input_file: str, output_file: str) -> bytes:
|
|
130
|
+
"""
|
|
131
|
+
Convert a video file from one format to another.
|
|
132
|
+
|
|
133
|
+
This method performs a simple format conversion without
|
|
134
|
+
modifying video quality or other parameters.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
input_file: Path to the input video file.
|
|
138
|
+
output_file: Path for the output video file.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
bytes: FFMPEG output (stdout or stderr) as bytes.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
FileNotFoundError: If input file doesn't exist.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> output = ffmpeg.reformat("input.mov", "output.mp4")
|
|
148
|
+
>>> print(output.decode())
|
|
149
|
+
>>>
|
|
150
|
+
>>> output = ffmpeg.reformat("video.avi", "video.mkv")
|
|
151
|
+
>>> print(output.decode())
|
|
152
|
+
"""
|
|
153
|
+
if not Path(input_file).exists():
|
|
154
|
+
raise FileNotFoundError(f"""
|
|
155
|
+
Input file {input_file} did not found .
|
|
156
|
+
|
|
157
|
+
Please check filename and directory if correct.
|
|
158
|
+
""")
|
|
159
|
+
|
|
160
|
+
stdout, stderr = self.execute("-i", input_file, output_file)
|
|
161
|
+
|
|
162
|
+
if stdout:
|
|
163
|
+
return stdout
|
|
164
|
+
else:
|
|
165
|
+
return stderr
|
|
166
|
+
|
|
167
|
+
def gifs(self,
|
|
168
|
+
input_byte: bytes,
|
|
169
|
+
fps: int = 10,
|
|
170
|
+
scale: int = 320) -> bytes:
|
|
171
|
+
"""
|
|
172
|
+
Convert video data from bytes to an optimized GIF.
|
|
173
|
+
|
|
174
|
+
Creates a high-quality GIF from in-memory video data using FFMPEG's
|
|
175
|
+
palette optimization. This method is useful when you have video data
|
|
176
|
+
in memory and want to avoid writing temporary files.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
input_byte: Video data as bytes to convert to GIF.
|
|
180
|
+
fps: Frames per second for the GIF. Defaults to 10.
|
|
181
|
+
scale: Width of the GIF in pixels. Height is auto-scaled
|
|
182
|
+
to maintain aspect ratio. Defaults to 320.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
bytes: The generated GIF data as bytes.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
RuntimeError: If GIF conversion fails.
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
>>> # Read video data from a file
|
|
192
|
+
>>> with open("video.mp4", "rb") as f:
|
|
193
|
+
... video_data = f.read()
|
|
194
|
+
>>>
|
|
195
|
+
>>> # Convert to GIF in memory
|
|
196
|
+
>>> gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
|
|
197
|
+
>>>
|
|
198
|
+
>>> # Save the GIF
|
|
199
|
+
>>> with open("output.gif", "wb") as f:
|
|
200
|
+
... f.write(gif_data)
|
|
201
|
+
|
|
202
|
+
>>> # Process video from network or database
|
|
203
|
+
>>> # video_bytes = download_video_from_url(url)
|
|
204
|
+
>>> # gif_bytes = ffmpeg.gifs(video_bytes, scale=320)
|
|
205
|
+
"""
|
|
206
|
+
filter_graph: str = (
|
|
207
|
+
f"fps={fps},"
|
|
208
|
+
f"scale={scale}:-1:flags=lanczos,"
|
|
209
|
+
"split[s0][s1];"
|
|
210
|
+
"[s0]palettegen[p];"
|
|
211
|
+
"[s1][p]paletteuse"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
stdout, stderr = self.execute(
|
|
215
|
+
"-y",
|
|
216
|
+
"-i", "pipe:0",
|
|
217
|
+
"-vf", filter_graph,
|
|
218
|
+
"-f", "gif",
|
|
219
|
+
"pipe:1",
|
|
220
|
+
input_data=input_byte
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return stdout
|
|
224
|
+
|
|
225
|
+
def gif(self,
|
|
226
|
+
input_file: str,
|
|
227
|
+
output_file: str,
|
|
228
|
+
fps: int = 10,
|
|
229
|
+
scale: int = 320
|
|
230
|
+
) -> bytes:
|
|
231
|
+
"""
|
|
232
|
+
Convert a video file to an optimized GIF.
|
|
233
|
+
|
|
234
|
+
Creates a high-quality GIF using FFMPEG's palette optimization
|
|
235
|
+
for better color reproduction and smaller file sizes.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
input_file: Path to the input video file.
|
|
239
|
+
output_file: Path for the output GIF file.
|
|
240
|
+
fps: Frames per second for the GIF. Defaults to 10.
|
|
241
|
+
scale: Width of the GIF in pixels. Height is auto-scaled
|
|
242
|
+
to maintain aspect ratio. Defaults to 320.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
bytes: FFMPEG output (stdout or stderr) as bytes.
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
FileNotFoundError: If input file doesn't exist.
|
|
249
|
+
RuntimeError: If GIF file was not created successfully.
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> # Create a standard GIF
|
|
253
|
+
>>> output = ffmpeg.gif("video.mp4", "output.gif")
|
|
254
|
+
>>> print(output.decode())
|
|
255
|
+
|
|
256
|
+
>>> # Create a higher quality GIF with custom settings
|
|
257
|
+
>>> output = ffmpeg.gif("video.mp4", "output.gif",
|
|
258
|
+
... fps=15, scale=640)
|
|
259
|
+
>>> print(output.decode())
|
|
260
|
+
|
|
261
|
+
>>> # Create a small thumbnail GIF
|
|
262
|
+
>>> output = ffmpeg.gif("video.mp4", "thumbnail.gif",
|
|
263
|
+
... fps=5, scale=160)
|
|
264
|
+
>>> print(output.decode())
|
|
265
|
+
"""
|
|
266
|
+
input_path = Path(input_file)
|
|
267
|
+
output_path = Path(output_file)
|
|
268
|
+
|
|
269
|
+
if not input_path.exists():
|
|
270
|
+
raise FileNotFoundError(
|
|
271
|
+
f"Input file {input_file} was not found."
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
filter_graph: str = (
|
|
275
|
+
f"fps={fps},"
|
|
276
|
+
f"scale={scale}:-1:flags=lanczos,"
|
|
277
|
+
"split[s0][s1];"
|
|
278
|
+
"[s0]palettegen[p];"
|
|
279
|
+
"[s1][p]paletteuse"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
stdout, stderr = self.execute(
|
|
283
|
+
"-y",
|
|
284
|
+
"-i", str(input_path),
|
|
285
|
+
"-vf", filter_graph,
|
|
286
|
+
str(output_path),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if not output_path.exists():
|
|
290
|
+
raise RuntimeError(
|
|
291
|
+
f"GIF was not created: {output_file}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if stderr:
|
|
295
|
+
return stderr
|
|
296
|
+
|
|
297
|
+
return stdout
|
|
298
|
+
|
|
299
|
+
def trim(
|
|
300
|
+
self,
|
|
301
|
+
input_file: str,
|
|
302
|
+
output_file: str,
|
|
303
|
+
start: float = 0,
|
|
304
|
+
duration: float | None = None,
|
|
305
|
+
) -> bytes:
|
|
306
|
+
"""
|
|
307
|
+
Trim a video file.
|
|
308
|
+
|
|
309
|
+
Extracts a segment from a video file starting at a specified time
|
|
310
|
+
and optionally ending after a specified duration.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
input_file: Path to the input video file.
|
|
314
|
+
output_file: Path for the output trimmed video.
|
|
315
|
+
start: Start time in seconds. Defaults to 0.
|
|
316
|
+
duration: Duration in seconds, or None for remaining video.
|
|
317
|
+
Defaults to None.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
bytes: FFMPEG output (stdout or stderr) as bytes.
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
FileNotFoundError: If input file doesn't exist.
|
|
324
|
+
ValueError: If start is negative or duration is non-positive.
|
|
325
|
+
RuntimeError: If trimmed video was not created successfully.
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
>>> # Trim from 5 seconds to 10 seconds (5-second clip)
|
|
329
|
+
>>> output = ffmpeg.trim("video.mp4", "clip.mp4",
|
|
330
|
+
... start=5, duration=5)
|
|
331
|
+
>>> print(output.decode())
|
|
332
|
+
|
|
333
|
+
>>> # Trim from 10 seconds to the end of video
|
|
334
|
+
>>> output = ffmpeg.trim("video.mp4", "ending.mp4",
|
|
335
|
+
... start=10)
|
|
336
|
+
>>> print(output.decode())
|
|
337
|
+
|
|
338
|
+
>>> # Trim first 30 seconds of video
|
|
339
|
+
>>> output = ffmpeg.trim("video.mp4", "intro.mp4",
|
|
340
|
+
... start=0, duration=30)
|
|
341
|
+
>>> print(output.decode())
|
|
342
|
+
|
|
343
|
+
>>> # Trim with floating point precision
|
|
344
|
+
>>> output = ffmpeg.trim("video.mp4", "precise.mp4",
|
|
345
|
+
... start=2.5, duration=3.75)
|
|
346
|
+
>>> print(output.decode())
|
|
347
|
+
"""
|
|
348
|
+
input_path = Path(input_file)
|
|
349
|
+
output_path = Path(output_file)
|
|
350
|
+
|
|
351
|
+
if not input_path.exists():
|
|
352
|
+
raise FileNotFoundError(
|
|
353
|
+
f"Input file {input_file} was not found."
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
if start < 0:
|
|
357
|
+
raise ValueError("start must be greater than or equal to 0")
|
|
358
|
+
|
|
359
|
+
if duration is not None and duration <= 0:
|
|
360
|
+
raise ValueError("duration must be greater than 0")
|
|
361
|
+
|
|
362
|
+
args = [
|
|
363
|
+
"-y",
|
|
364
|
+
"-ss", str(start),
|
|
365
|
+
"-i", str(input_path),
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
if duration is not None:
|
|
369
|
+
args.extend(["-t", str(duration)])
|
|
370
|
+
|
|
371
|
+
args.append(str(output_path))
|
|
372
|
+
|
|
373
|
+
stdout, stderr = self.execute(*args)
|
|
374
|
+
|
|
375
|
+
if not output_path.exists():
|
|
376
|
+
raise RuntimeError(
|
|
377
|
+
f"Trimmed video was not created: {output_file}"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
return stdout if stdout else stderr
|
|
File without changes
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
class FFMPEG:
|
|
5
|
-
def __init__(self) -> None:
|
|
6
|
-
import shutil
|
|
7
|
-
self.executable = shutil.which('ffmpeg')
|
|
8
|
-
|
|
9
|
-
if self.executable is None:
|
|
10
|
-
raise FileNotFoundError("""
|
|
11
|
-
FFmpeg is not installed or could not be found in PATH.
|
|
12
|
-
Please install FFmpeg and make sure it is available
|
|
13
|
-
in your system PATH.
|
|
14
|
-
""")
|
|
15
|
-
|
|
16
|
-
@classmethod
|
|
17
|
-
def api(cls):
|
|
18
|
-
return cls()
|
|
19
|
-
|
|
20
|
-
def execute(self, *args : str):
|
|
21
|
-
if self.executable:
|
|
22
|
-
result = subprocess.run([self.executable, *args], capture_output=True, text=True)
|
|
23
|
-
return result.stdout.strip(), result.stderr.strip()
|
|
24
|
-
|
|
25
|
-
raise FileNotFoundError("""
|
|
26
|
-
No ffmpeg executable found.
|
|
27
|
-
|
|
28
|
-
Please Install ffmpeg first.
|
|
29
|
-
""")
|
|
30
|
-
|
|
31
|
-
def reformat(self, input_file : str, output_file : str):
|
|
32
|
-
if not Path(input_file).exists():
|
|
33
|
-
raise FileNotFoundError(f"""
|
|
34
|
-
Input file {input_file} did not found .
|
|
35
|
-
|
|
36
|
-
Please check filename and directory if correct.
|
|
37
|
-
""")
|
|
38
|
-
|
|
39
|
-
stdout, stderr = self.execute("-i", input_file, output_file)
|
|
40
|
-
|
|
41
|
-
if stdout:
|
|
42
|
-
print(stdout)
|
|
43
|
-
else:
|
|
44
|
-
print(stderr)
|
|
45
|
-
|
|
46
|
-
def gif(self,
|
|
47
|
-
input_file : str,
|
|
48
|
-
output_file : str,
|
|
49
|
-
fps = 10,
|
|
50
|
-
scale = 320
|
|
51
|
-
):
|
|
52
|
-
input_path = Path(input_file)
|
|
53
|
-
output_path = Path(output_file)
|
|
54
|
-
|
|
55
|
-
if not input_path.exists():
|
|
56
|
-
raise FileNotFoundError(
|
|
57
|
-
f"Input file {input_file} was not found."
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
filter_graph : str = (
|
|
61
|
-
f"fps={fps},"
|
|
62
|
-
f"scale={scale}:-1:flags=lanczos,"
|
|
63
|
-
"split[s0][s1];"
|
|
64
|
-
"[s0]palettegen[p];"
|
|
65
|
-
"[s1][p]paletteuse"
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
stdout, stderr = self.execute(
|
|
69
|
-
"-y",
|
|
70
|
-
"-i", str(input_path),
|
|
71
|
-
"-vf", filter_graph,
|
|
72
|
-
str(output_path),
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
if not output_path.exists():
|
|
76
|
-
raise RuntimeError(
|
|
77
|
-
f"GIF was not created: {output_file}"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
return stdout, stderr
|
|
81
|
-
|
|
82
|
-
def trim(
|
|
83
|
-
self,
|
|
84
|
-
input_file: str,
|
|
85
|
-
output_file: str,
|
|
86
|
-
start: float = 0,
|
|
87
|
-
duration: float | None = None,
|
|
88
|
-
):
|
|
89
|
-
input_path = Path(input_file)
|
|
90
|
-
output_path = Path(output_file)
|
|
91
|
-
|
|
92
|
-
if not input_path.exists():
|
|
93
|
-
raise FileNotFoundError(
|
|
94
|
-
f"Input file {input_file} was not found."
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
if start < 0:
|
|
98
|
-
raise ValueError("start must be greater than or equal to 0")
|
|
99
|
-
|
|
100
|
-
if duration is not None and duration <= 0:
|
|
101
|
-
raise ValueError("duration must be greater than 0")
|
|
102
|
-
|
|
103
|
-
args = [
|
|
104
|
-
"-y",
|
|
105
|
-
"-ss", str(start),
|
|
106
|
-
"-i", str(input_path),
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
if duration is not None:
|
|
110
|
-
args.extend(["-t", str(duration)])
|
|
111
|
-
|
|
112
|
-
args.append(str(output_path))
|
|
113
|
-
|
|
114
|
-
stdout, stderr = self.execute(*args)
|
|
115
|
-
|
|
116
|
-
if not output_path.exists():
|
|
117
|
-
raise RuntimeError(
|
|
118
|
-
f"Trimmed video was not created: {output_file}"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
return stdout, stderr
|