ffmpeg-python-helper 0.1.0__py3-none-any.whl → 3.0.0__py3-none-any.whl

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.
@@ -1,9 +1,67 @@
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, audio extraction,
6
+ in-memory processing, and data pipeline utilities.
7
+
8
+ Example:
9
+ >>> from ffmpeg_python_helper import FFMPEG, Pipe
10
+ >>> ffmpeg = FFMPEG()
11
+ >>> ffmpeg.reformat("input.mp4", "output.avi")
12
+ >>> ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
13
+ >>> ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
14
+ >>> ffmpeg.extract_audio("video.mp4", "audio.m4a")
15
+ >>>
16
+ >>> # In-memory processing
17
+ >>> with open("video.mp4", "rb") as f:
18
+ ... video_data = f.read()
19
+ >>> gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
20
+ >>> trimmed_data = ffmpeg.trims(video_data, start=0, duration=30)
21
+ >>> audio_data = ffmpeg.extract_audios(video_data, output_format="m4a")
22
+ >>>
23
+ >>> # Pipeline processing
24
+ >>> def process_video(data: bytes) -> bytes:
25
+ ... return ffmpeg.trims(data, start=0, duration=30)
26
+ >>>
27
+ >>> def convert_to_gif(data: bytes) -> bytes:
28
+ ... return ffmpeg.gifs(data, fps=15, scale=480)
29
+ >>>
30
+ >>> processed_data = Pipe.pipe_bytes(video_data, process_video, convert_to_gif)
31
+
32
+ For detailed API documentation, see:
33
+ - FFMPEG class documentation
34
+ - Pipe class documentation
35
+ - README.md for usage examples and tutorials
36
+ """
37
+
1
38
  from .ffmpeg_api import FFMPEG
39
+ from .pipe_helper import Pipe
2
40
 
3
41
  __all__ = [
4
- 'FFMPEG'
42
+ 'FFMPEG',
43
+ 'Pipe'
5
44
  ]
6
45
 
7
- def main():
46
+ __version__ = "0.1.0"
47
+ __author__ = "marjon <marjongodito@gmanmi.com>"
48
+
49
+
50
+ def main() -> None:
51
+ """
52
+ Command-line entry point for the FFMPEG Python Helper.
53
+
54
+ This function is called when the package is executed as a script.
55
+ It demonstrates basic functionality by:
56
+ 1. Creating an FFMPEG instance
57
+ 2. Printing the path to the FFMPEG executable
58
+
59
+ Example:
60
+ $ python -m ffmpeg_python_helper
61
+ FFMPEG executable found at: /usr/bin/ffmpeg
62
+
63
+ Raises:
64
+ FileNotFoundError: If FFMPEG is not found in PATH.
65
+ """
8
66
  ffmpeg = FFMPEG()
9
- print(ffmpeg.executable)
67
+ print(f"FFMPEG executable found at: {ffmpeg.executable}")
@@ -1,8 +1,52 @@
1
1
  import subprocess
2
2
  from pathlib import Path
3
3
 
4
+
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, video trimming,
12
+ and audio extraction. It supports both file-based operations and
13
+ in-memory data processing.
14
+
15
+ Example:
16
+ >>> from ffmpeg_python_helper import FFMPEG
17
+ >>> ffmpeg = FFMPEG()
18
+ >>> ffmpeg.reformat("input.mp4", "output.avi")
19
+ >>> ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
20
+ >>> ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
21
+ >>> ffmpeg.extract_audio("video.mp4", "audio.m4a")
22
+ >>>
23
+ >>> # In-memory processing
24
+ >>> with open("video.mp4", "rb") as f:
25
+ ... video_data = f.read()
26
+ >>> gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
27
+ >>> trimmed_data = ffmpeg.trims(video_data, start=0, duration=30)
28
+ >>> audio_data = ffmpeg.extract_audios(video_data, output_format="m4a")
29
+
30
+ Attributes:
31
+ executable (str): The path to the FFMPEG executable found in the system PATH.
32
+ """
33
+
5
34
  def __init__(self) -> None:
35
+ """
36
+ Initialize a new FFMPEG instance.
37
+
38
+ Automatically searches for FFMPEG in the system PATH.
39
+
40
+ Raises:
41
+ FileNotFoundError: If FFMPEG is not found in the system PATH.
42
+
43
+ Example:
44
+ >>> try:
45
+ ... ffmpeg = FFMPEG()
46
+ ... print(f"FFMPEG found at: {ffmpeg.executable}")
47
+ ... except FileNotFoundError as e:
48
+ ... print(f"FFMPEG not found: {e}")
49
+ """
6
50
  import shutil
7
51
  self.executable = shutil.which('ffmpeg')
8
52
 
@@ -14,13 +58,71 @@ in your system PATH.
14
58
  """)
15
59
 
16
60
  @classmethod
17
- def api(cls):
61
+ def api(cls) -> "FFMPEG":
62
+ """
63
+ Factory method that returns a new FFMPEG instance.
64
+
65
+ Returns:
66
+ FFMPEG: A new instance of the FFMPEG class.
67
+
68
+ Example:
69
+ >>> ffmpeg = FFMPEG.api()
70
+ >>> ffmpeg.execute("-version")
71
+ """
18
72
  return cls()
19
73
 
20
- def execute(self, *args : str):
74
+ def execute(self, *args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]:
75
+ """
76
+ Execute raw FFMPEG commands with the given arguments.
77
+
78
+ This method allows you to run any FFMPEG command directly,
79
+ providing maximum flexibility for operations not covered
80
+ by the built-in methods.
81
+
82
+ Args:
83
+ *args: FFMPEG command-line arguments as strings.
84
+ input_data: Optional bytes to send to FFMPEG's stdin. Useful for
85
+ piping data directly to FFMPEG without intermediate files.
86
+
87
+ Returns:
88
+ tuple[bytes, bytes]: A tuple containing (stdout, stderr) from FFMPEG
89
+ as bytes objects.
90
+
91
+ Raises:
92
+ FileNotFoundError: If FFMPEG executable is not found.
93
+ RuntimeError: If FFMPEG command returns a non-zero exit code.
94
+
95
+ Example:
96
+ >>> stdout, stderr = ffmpeg.execute("-version")
97
+ >>> print(stdout.decode())
98
+
99
+ >>> # Extract audio from video
100
+ >>> ffmpeg.execute("-i", "video.mp4", "-q:a", "0", "-map", "a", "audio.mp3")
101
+
102
+ >>> # Add watermark to video
103
+ >>> ffmpeg.execute("-i", "video.mp4", "-i", "watermark.png",
104
+ ... "-filter_complex", "overlay=10:10", "output.mp4")
105
+
106
+ >>> # Process data from memory
107
+ >>> video_data = b"...video bytes..."
108
+ >>> stdout, stderr = ffmpeg.execute("-i", "pipe:0", "-f", "null", "-",
109
+ ... input_data=video_data)
110
+ """
21
111
  if self.executable:
22
- result = subprocess.run([self.executable, *args], capture_output=True, text=True)
23
- return result.stdout.strip(), result.stderr.strip()
112
+ result = subprocess.run(
113
+ [self.executable, *args],
114
+ input=input_data,
115
+ stdout=subprocess.PIPE,
116
+ stderr=subprocess.PIPE,
117
+ check=False
118
+ )
119
+
120
+ if result.returncode != 0:
121
+ raise RuntimeError(
122
+ result.stderr.decode(errors="replace")
123
+ )
124
+
125
+ return result.stdout, result.stderr
24
126
 
25
127
  raise FileNotFoundError("""
26
128
  No ffmpeg executable found.
@@ -28,7 +130,30 @@ in your system PATH.
28
130
  Please Install ffmpeg first.
29
131
  """)
30
132
 
31
- def reformat(self, input_file : str, output_file : str):
133
+ def reformat(self, input_file: str, output_file: str) -> bytes:
134
+ """
135
+ Convert a video file from one format to another.
136
+
137
+ This method performs a simple format conversion without
138
+ modifying video quality or other parameters.
139
+
140
+ Args:
141
+ input_file: Path to the input video file.
142
+ output_file: Path for the output video file.
143
+
144
+ Returns:
145
+ bytes: FFMPEG output (stdout or stderr) as bytes.
146
+
147
+ Raises:
148
+ FileNotFoundError: If input file doesn't exist.
149
+
150
+ Example:
151
+ >>> output = ffmpeg.reformat("input.mov", "output.mp4")
152
+ >>> print(output.decode())
153
+ >>>
154
+ >>> output = ffmpeg.reformat("video.avi", "video.mkv")
155
+ >>> print(output.decode())
156
+ """
32
157
  if not Path(input_file).exists():
33
158
  raise FileNotFoundError(f"""
34
159
  Input file {input_file} did not found .
@@ -39,16 +164,103 @@ in your system PATH.
39
164
  stdout, stderr = self.execute("-i", input_file, output_file)
40
165
 
41
166
  if stdout:
42
- print(stdout)
167
+ return stdout
43
168
  else:
44
- print(stderr)
45
-
46
- def gif(self,
47
- input_file : str,
48
- output_file : str,
49
- fps = 10,
50
- scale = 320
51
- ):
169
+ return stderr
170
+
171
+ def gifs(self,
172
+ input_bytes: bytes,
173
+ fps: int = 10,
174
+ scale: int = 320) -> bytes:
175
+ """
176
+ Convert video data from bytes to an optimized GIF.
177
+
178
+ Creates a high-quality GIF from in-memory video data using FFMPEG's
179
+ palette optimization. This method is useful when you have video data
180
+ in memory and want to avoid writing temporary files.
181
+
182
+ Args:
183
+ input_byte: Video data as bytes to convert to GIF.
184
+ fps: Frames per second for the GIF. Defaults to 10.
185
+ scale: Width of the GIF in pixels. Height is auto-scaled
186
+ to maintain aspect ratio. Defaults to 320.
187
+
188
+ Returns:
189
+ bytes: The generated GIF data as bytes.
190
+
191
+ Raises:
192
+ RuntimeError: If GIF conversion fails.
193
+
194
+ Example:
195
+ >>> # Read video data from a file
196
+ >>> with open("video.mp4", "rb") as f:
197
+ ... video_data = f.read()
198
+ >>>
199
+ >>> # Convert to GIF in memory
200
+ >>> gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
201
+ >>>
202
+ >>> # Save the GIF
203
+ >>> with open("output.gif", "wb") as f:
204
+ ... f.write(gif_data)
205
+
206
+ >>> # Process video from network or database
207
+ >>> # video_bytes = download_video_from_url(url)
208
+ >>> # gif_bytes = ffmpeg.gifs(video_bytes, scale=320)
209
+ """
210
+ filter_graph: str = (
211
+ f"fps={fps},"
212
+ f"scale={scale}:-1:flags=lanczos,"
213
+ "split[s0][s1];"
214
+ "[s0]palettegen[p];"
215
+ "[s1][p]paletteuse"
216
+ )
217
+
218
+ stdout, stderr = self.execute(
219
+ "-y",
220
+ "-i", "pipe:0",
221
+ "-vf", filter_graph,
222
+ "-f", "gif",
223
+ "pipe:1",
224
+ input_data=input_bytes
225
+ )
226
+
227
+ return stdout
228
+
229
+ def gif(self,
230
+ input_file: str,
231
+ output_file: str,
232
+ fps: int = 10,
233
+ scale: int = 320
234
+ ) -> None:
235
+ """
236
+ Convert a video file to an optimized GIF.
237
+
238
+ Creates a high-quality GIF using FFMPEG's palette optimization
239
+ for better color reproduction and smaller file sizes.
240
+
241
+ Args:
242
+ input_file: Path to the input video file.
243
+ output_file: Path for the output GIF file.
244
+ fps: Frames per second for the GIF. Defaults to 10.
245
+ scale: Width of the GIF in pixels. Height is auto-scaled
246
+ to maintain aspect ratio. Defaults to 320.
247
+
248
+ Raises:
249
+ FileNotFoundError: If input file doesn't exist.
250
+ RuntimeError: If GIF file was not created successfully.
251
+
252
+ Example:
253
+ >>> # Create a standard GIF
254
+ >>> ffmpeg.gif("video.mp4", "output.gif")
255
+
256
+ >>> # Create a higher quality GIF with custom settings
257
+ >>> ffmpeg.gif("video.mp4", "output.gif",
258
+ ... fps=15, scale=640)
259
+
260
+ >>> # Create a small thumbnail GIF
261
+ >>> ffmpeg.gif("video.mp4", "thumbnail.gif",
262
+ ... fps=5, scale=160)
263
+ """
52
264
  input_path = Path(input_file)
53
265
  output_path = Path(output_file)
54
266
 
@@ -57,7 +269,7 @@ in your system PATH.
57
269
  f"Input file {input_file} was not found."
58
270
  )
59
271
 
60
- filter_graph : str = (
272
+ filter_graph: str = (
61
273
  f"fps={fps},"
62
274
  f"scale={scale}:-1:flags=lanczos,"
63
275
  "split[s0][s1];"
@@ -65,7 +277,7 @@ in your system PATH.
65
277
  "[s1][p]paletteuse"
66
278
  )
67
279
 
68
- stdout, stderr = self.execute(
280
+ self.execute(
69
281
  "-y",
70
282
  "-i", str(input_path),
71
283
  "-vf", filter_graph,
@@ -77,15 +289,48 @@ in your system PATH.
77
289
  f"GIF was not created: {output_file}"
78
290
  )
79
291
 
80
- return stdout, stderr
81
-
82
292
  def trim(
83
293
  self,
84
294
  input_file: str,
85
295
  output_file: str,
86
296
  start: float = 0,
87
297
  duration: float | None = None,
88
- ):
298
+ ) -> None:
299
+ """
300
+ Trim a video file.
301
+
302
+ Extracts a segment from a video file starting at a specified time
303
+ and optionally ending after a specified duration.
304
+
305
+ Args:
306
+ input_file: Path to the input video file.
307
+ output_file: Path for the output trimmed video.
308
+ start: Start time in seconds. Defaults to 0.
309
+ duration: Duration in seconds, or None for remaining video.
310
+ Defaults to None.
311
+
312
+ Raises:
313
+ FileNotFoundError: If input file doesn't exist.
314
+ ValueError: If start is negative or duration is non-positive.
315
+ RuntimeError: If trimmed video was not created successfully.
316
+
317
+ Example:
318
+ >>> # Trim from 5 seconds to 10 seconds (5-second clip)
319
+ >>> ffmpeg.trim("video.mp4", "clip.mp4",
320
+ ... start=5, duration=5)
321
+
322
+ >>> # Trim from 10 seconds to the end of video
323
+ >>> ffmpeg.trim("video.mp4", "ending.mp4",
324
+ ... start=10)
325
+
326
+ >>> # Trim first 30 seconds of video
327
+ >>> ffmpeg.trim("video.mp4", "intro.mp4",
328
+ ... start=0, duration=30)
329
+
330
+ >>> # Trim with floating point precision
331
+ >>> ffmpeg.trim("video.mp4", "precise.mp4",
332
+ ... start=2.5, duration=3.75)
333
+ """
89
334
  input_path = Path(input_file)
90
335
  output_path = Path(output_file)
91
336
 
@@ -118,4 +363,164 @@ in your system PATH.
118
363
  f"Trimmed video was not created: {output_file}"
119
364
  )
120
365
 
121
- return stdout, stderr
366
+ def trims(self, input_bytes: bytes,
367
+ start: float = 0,
368
+ duration: float | None = None,
369
+ format_type: str = "mp4") -> bytes:
370
+ """
371
+ Trim video data from bytes (in-memory processing).
372
+
373
+ Extracts a segment from in-memory video data starting at a specified time
374
+ and optionally ending after a specified duration. This method is useful
375
+ when you have video data in memory and want to avoid writing temporary files.
376
+
377
+ Note: For MP4 format, this method uses libx264 video codec and AAC audio codec
378
+ with fragmented MP4 output for better streaming compatibility.
379
+
380
+ Args:
381
+ input_bytes: Video data as bytes to trim.
382
+ start: Start time in seconds. Defaults to 0.
383
+ duration: Duration in seconds, or None for remaining video.
384
+ Defaults to None.
385
+ format_type: Output format (e.g., 'mp4', 'avi', 'mov'). Defaults to 'mp4'.
386
+
387
+ Returns:
388
+ bytes: The trimmed video data as bytes.
389
+
390
+ Raises:
391
+ RuntimeError: If video trimming fails.
392
+
393
+ Example:
394
+ >>> # Read video data from a file
395
+ >>> with open("video.mp4", "rb") as f:
396
+ ... video_data = f.read()
397
+ >>>
398
+ >>> # Trim first 30 seconds in memory
399
+ >>> trimmed_data = ffmpeg.trims(video_data, start=0, duration=30)
400
+ >>>
401
+ >>> # Save the trimmed video
402
+ >>> with open("intro.mp4", "wb") as f:
403
+ ... f.write(trimmed_data)
404
+ >>>
405
+ >>> # Trim with different format
406
+ >>> webm_data = ffmpeg.trims(video_data, start=10, duration=5, format_type="webm")
407
+ >>> with open("clip.webm", "wb") as f:
408
+ ... f.write(webm_data)
409
+ >>>
410
+ >>> # Trim with specific codec settings
411
+ >>> # For non-MP4 formats, FFMPEG will use default codecs
412
+ >>> avi_data = ffmpeg.trims(video_data, start=5, duration=10, format_type="avi")
413
+ """
414
+ args = [
415
+ "-y",
416
+ "-i", "pipe:0",
417
+ "-ss", str(start)
418
+ ]
419
+
420
+ if duration is not None:
421
+ args.extend(["-t", str(duration)])
422
+
423
+ if format_type == 'mp4':
424
+ args.extend([
425
+ "-c:v", "libx264",
426
+ "-pix_fmt", "yuv420p",
427
+ "-c:a", "aac",
428
+ "-movflags", "frag_keyframe+empty_moov"
429
+ ])
430
+
431
+ args.extend([
432
+ "-f", format_type,
433
+ "pipe:1"
434
+ ])
435
+
436
+ stdout, stderr = self.execute(*args, input_data=input_bytes)
437
+
438
+ return stdout
439
+
440
+ def extract_audio(self, input_file: str, output_file: str) -> None:
441
+ """
442
+ Extract audio from a video file.
443
+
444
+ Extracts the audio track from a video file without re-encoding,
445
+ preserving the original audio quality. The output file extension
446
+ should match the audio codec (e.g., .m4a for AAC, .mp3 for MP3,
447
+ .ogg for Vorbis).
448
+
449
+ Args:
450
+ input_file: Path to the input video file.
451
+ output_file: Path for the output audio file.
452
+
453
+ Raises:
454
+ FileNotFoundError: If input file doesn't exist.
455
+ RuntimeError: If audio extraction fails.
456
+
457
+ Example:
458
+ >>> # Extract audio from MP4 video
459
+ >>> ffmpeg.extract_audio("video.mp4", "audio.m4a")
460
+ >>>
461
+ >>> # Extract audio and convert to MP3
462
+ >>> ffmpeg.execute("-i", "video.mp4", "-q:a", "0", "-map", "a", "audio.mp3")
463
+ >>>
464
+ >>> # Extract audio from multiple formats
465
+ >>> ffmpeg.extract_audio("movie.mkv", "audio.m4a")
466
+ >>> ffmpeg.extract_audio("clip.avi", "audio.mp3")
467
+ """
468
+ input_path = Path(input_file)
469
+ output_path = Path(output_file)
470
+
471
+ if not input_path.exists():
472
+ raise FileNotFoundError(
473
+ f"Input file {input_file} was not found."
474
+ )
475
+
476
+ self.execute(
477
+ "-i", str(input_path), # Specifies the input video file.
478
+ "-vn", # Disables the video stream (drops the visual data).
479
+ "-c:a", "copy", # Copies the audio track as-is without re-encoding.
480
+ str(output_path) # Output file. Extension should match audio codec.
481
+ )
482
+
483
+ def extract_audios(self, input_bytes: bytes, output_format: str = "m4a") -> bytes:
484
+ """
485
+ Extract audio from video data (in-memory processing).
486
+
487
+ Extracts the audio track from in-memory video data without re-encoding,
488
+ preserving the original audio quality. This method is useful when you
489
+ have video data in memory and want to avoid writing temporary files.
490
+
491
+ Args:
492
+ input_bytes: Video data as bytes to extract audio from.
493
+ output_format: Audio output format (e.g., 'm4a', 'mp3', 'ogg', 'wav').
494
+ Defaults to 'm4a'.
495
+
496
+ Returns:
497
+ bytes: The extracted audio data as bytes.
498
+
499
+ Raises:
500
+ RuntimeError: If audio extraction fails.
501
+
502
+ Example:
503
+ >>> # Read video data from a file
504
+ >>> with open("video.mp4", "rb") as f:
505
+ ... video_data = f.read()
506
+ >>>
507
+ >>> # Extract audio in memory
508
+ >>> audio_data = ffmpeg.extract_audios(video_data, output_format="m4a")
509
+ >>>
510
+ >>> # Save the extracted audio
511
+ >>> with open("audio.m4a", "wb") as f:
512
+ ... f.write(audio_data)
513
+ >>>
514
+ >>> # Extract audio in different formats
515
+ >>> mp3_data = ffmpeg.extract_audios(video_data, output_format="mp3")
516
+ >>> ogg_data = ffmpeg.extract_audios(video_data, output_format="ogg")
517
+ """
518
+ args = [
519
+ "-i", "pipe:0",
520
+ "-vn",
521
+ "-c:a", "copy",
522
+ "-f", output_format,
523
+ "pipe:1"
524
+ ]
525
+
526
+ return self.execute(*args, input_data=input_bytes)[0]
@@ -0,0 +1,114 @@
1
+ from typing import Callable
2
+
3
+
4
+ class Pipe:
5
+ """
6
+ A utility class for piping bytes through multiple processing functions.
7
+
8
+ This class provides a convenient way to chain multiple byte-processing
9
+ operations together, creating a pipeline that transforms data step by step.
10
+
11
+ Example:
12
+ >>> from ffmpeg_python_helper import FFMPEG, Pipe
13
+ >>> ffmpeg = FFMPEG()
14
+ >>>
15
+ >>> # Define processing functions
16
+ >>> def trim_first_30s(data: bytes) -> bytes:
17
+ ... return ffmpeg.trims(data, start=0, duration=30)
18
+ >>>
19
+ >>> def convert_to_gif(data: bytes) -> bytes:
20
+ ... return ffmpeg.gifs(data, fps=15, scale=480)
21
+ >>>
22
+ >>> def extract_audio(data: bytes) -> bytes:
23
+ ... return ffmpeg.extract_audios(data, output_format="m4a")
24
+ >>>
25
+ >>> # Read video data
26
+ >>> with open("video.mp4", "rb") as f:
27
+ ... video_data = f.read()
28
+ >>>
29
+ >>> # Create a processing pipeline
30
+ >>> # Trim → Convert to GIF → Extract audio
31
+ >>> result = Pipe.pipe_bytes(
32
+ ... video_data,
33
+ ... trim_first_30s,
34
+ ... convert_to_gif,
35
+ ... extract_audio
36
+ ... )
37
+ >>>
38
+ >>> # Save the final result (audio from trimmed GIF-converted video)
39
+ >>> with open("processed_audio.m4a", "wb") as f:
40
+ ... f.write(result)
41
+ """
42
+
43
+ @staticmethod
44
+ def pipe_bytes(initial_value: bytes, *fn: Callable[[bytes], bytes]) -> bytes:
45
+ """
46
+ Pipe bytes through multiple processing functions.
47
+
48
+ This method takes an initial bytes value and passes it through
49
+ a series of functions, using the output of each function as the
50
+ input to the next function in the chain.
51
+
52
+ Args:
53
+ initial_value: The initial bytes to start the pipeline with.
54
+ *fn: One or more callable functions that take bytes as input
55
+ and return bytes as output. Functions are applied in
56
+ the order they are provided.
57
+
58
+ Returns:
59
+ bytes: The final result after passing through all functions.
60
+
61
+ Raises:
62
+ TypeError: If any function is not callable or doesn't accept bytes.
63
+ RuntimeError: If any function in the pipeline fails.
64
+
65
+ Example:
66
+ >>> # Simple pipeline: read, process, save
67
+ >>> def add_header(data: bytes) -> bytes:
68
+ ... return b"HEADER" + data
69
+ >>>
70
+ >>> def add_footer(data: bytes) -> bytes:
71
+ ... return data + b"FOOTER"
72
+ >>>
73
+ >>> def uppercase_data(data: bytes) -> bytes:
74
+ ... return data.upper()
75
+ >>>
76
+ >>> # Create pipeline
77
+ >>> result = Pipe.pipe_bytes(
78
+ ... b"hello world",
79
+ ... add_header,
80
+ ... uppercase_data,
81
+ ... add_footer
82
+ ... )
83
+ >>>
84
+ >>> print(result)
85
+ b'HEADERHELLO WORLD FOOTER'
86
+
87
+ >>> # FFMPEG processing pipeline
88
+ >>> from ffmpeg_python_helper import FFMPEG
89
+ >>> ffmpeg = FFMPEG()
90
+ >>>
91
+ >>> def trim_video(data: bytes) -> bytes:
92
+ ... return ffmpeg.trims(data, start=10, duration=5)
93
+ >>>
94
+ >>> def convert_to_webm(data: bytes) -> bytes:
95
+ ... return ffmpeg.trims(data, format_type="webm") # Re-encode as webm
96
+ >>>
97
+ >>> # Process video through pipeline
98
+ >>> with open("input.mp4", "rb") as f:
99
+ ... video_data = f.read()
100
+ >>>
101
+ >>> processed_data = Pipe.pipe_bytes(
102
+ ... video_data,
103
+ ... trim_video,
104
+ ... convert_to_webm
105
+ ... )
106
+ >>>
107
+ >>> with open("output.webm", "wb") as f:
108
+ ... f.write(processed_data)
109
+ """
110
+ current: bytes = initial_value
111
+ for callable in fn:
112
+ current = callable(current)
113
+
114
+ return current
@@ -0,0 +1,358 @@
1
+ Metadata-Version: 2.3
2
+ Name: ffmpeg-python-helper
3
+ Version: 3.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
+ - 🎵 **Audio Extraction** - Extract audio tracks from videos without re-encoding
20
+ - 🧠 **In-Memory Processing** - Process video/audio data directly from bytes without temporary files
21
+ - ✂️ **Video Trimming** - Trim videos with precise start time and duration control
22
+ - 🐍 **Pythonic API** - Clean, object-oriented interface with proper error handling
23
+ - 📁 **File Validation** - Automatic input file existence checking
24
+
25
+ ## Installation
26
+
27
+ ### Prerequisites
28
+ - Python 3.14 or higher
29
+ - FFMPEG installed and available in your system PATH
30
+
31
+ ### Install FFMPEG Python Helper
32
+
33
+ ```bash
34
+ pip install ffmpeg-python-helper
35
+ ```
36
+
37
+ Or install from source:
38
+
39
+ ```bash
40
+ git clone https://github.com/yourusername/ffmpeg-python-helper.git
41
+ cd ffmpeg-python-helper
42
+ pip install -e .
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from ffmpeg_python_helper import FFMPEG
49
+
50
+ # Initialize the FFMPEG wrapper
51
+ ffmpeg = FFMPEG()
52
+
53
+ # Check if FFMPEG is available
54
+ print(f"FFMPEG executable found at: {ffmpeg.executable}")
55
+
56
+ # Convert a video file
57
+ output = ffmpeg.reformat("input.mp4", "output.avi")
58
+ print(output.decode())
59
+
60
+ # Create a GIF from video
61
+ ffmpeg.gif("video.mp4", "animation.gif", fps=15, scale=480)
62
+
63
+ # Trim a video
64
+ ffmpeg.trim("video.mp4", "short_clip.mp4", start=10.5, duration=5.0)
65
+
66
+ # Extract audio from video
67
+ ffmpeg.extract_audio("video.mp4", "audio.m4a")
68
+
69
+ # Create GIF from in-memory video data
70
+ with open("video.mp4", "rb") as f:
71
+ video_data = f.read()
72
+ gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
73
+ with open("memory.gif", "wb") as f:
74
+ f.write(gif_data)
75
+
76
+ # Trim video in memory
77
+ trimmed_data = ffmpeg.trims(video_data, start=0, duration=30)
78
+ with open("trimmed.mp4", "wb") as f:
79
+ f.write(trimmed_data)
80
+
81
+ # Extract audio in memory
82
+ audio_data = ffmpeg.extract_audios(video_data, output_format="m4a")
83
+ with open("audio.m4a", "wb") as f:
84
+ f.write(audio_data)
85
+ ```
86
+
87
+ ## API Reference
88
+
89
+ ### `FFMPEG` Class
90
+
91
+ The main class that wraps FFMPEG functionality.
92
+
93
+ #### Constructor
94
+ ```python
95
+ FFMPEG()
96
+ ```
97
+ Creates a new FFMPEG instance. Automatically searches for FFMPEG in the system PATH.
98
+ - **Raises**: `FileNotFoundError` if FFMPEG is not found in PATH
99
+
100
+ #### Properties
101
+ - `executable` (str): The path to the FFMPEG executable found in the system
102
+
103
+ #### Class Methods
104
+ ```python
105
+ @classmethod
106
+ def api(cls) -> "FFMPEG"
107
+ ```
108
+ Factory method that returns a new FFMPEG instance.
109
+ - **Returns**: `FFMPEG` instance
110
+
111
+ #### Instance Methods
112
+
113
+ ##### `execute(*args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]`
114
+ Execute raw FFMPEG commands with the given arguments.
115
+
116
+ **Parameters:**
117
+ - `*args` (str): FFMPEG command-line arguments
118
+ - `input_data` (bytes | None, optional): Optional bytes to send to FFMPEG's stdin
119
+
120
+ **Returns:**
121
+ - `tuple[bytes, bytes]`: A tuple containing (stdout, stderr) as bytes
122
+
123
+ **Raises:**
124
+ - `FileNotFoundError`: If FFMPEG executable is not found
125
+ - `RuntimeError`: If FFMPEG command returns a non-zero exit code
126
+
127
+ **Example:**
128
+ ```python
129
+ stdout, stderr = ffmpeg.execute("-version")
130
+ print(stdout.decode())
131
+
132
+ # Process data from memory
133
+ video_data = b"...video bytes..."
134
+ stdout, stderr = ffmpeg.execute("-i", "pipe:0", "-f", "null", "-", input_data=video_data)
135
+ ```
136
+
137
+ ##### `reformat(input_file: str, output_file: str) -> bytes`
138
+ Convert a video file from one format to another.
139
+
140
+ **Parameters:**
141
+ - `input_file` (str): Path to the input video file
142
+ - `output_file` (str): Path for the output video file
143
+
144
+ **Returns:**
145
+ - `bytes`: FFMPEG output (stdout or stderr) as bytes
146
+
147
+ **Raises:**
148
+ - `FileNotFoundError`: If input file doesn't exist
149
+
150
+ **Example:**
151
+ ```python
152
+ output = ffmpeg.reformat("input.mov", "output.mp4")
153
+ print(output.decode())
154
+ ```
155
+
156
+ ##### `gif(input_file: str, output_file: str, fps: int = 10, scale: int = 320) -> bytes`
157
+ Convert a video file to an optimized GIF.
158
+
159
+ **Parameters:**
160
+ - `input_file` (str): Path to the input video file
161
+ - `output_file` (str): Path for the output GIF file
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`: FFMPEG output (stdout or stderr) as bytes
167
+
168
+ **Raises:**
169
+ - `FileNotFoundError`: If input file doesn't exist
170
+ - `RuntimeError`: If GIF file was not created successfully
171
+
172
+ **Example:**
173
+ ```python
174
+ output = ffmpeg.gif("video.mp4", "output.gif", fps=15, scale=640)
175
+ print(output.decode())
176
+ ```
177
+
178
+ ##### `gifs(input_byte: bytes, fps: int = 10, scale: int = 320) -> bytes`
179
+ Convert video data from bytes to an optimized GIF (in-memory processing).
180
+
181
+ **Parameters:**
182
+ - `input_byte` (bytes): Video data as bytes to convert to GIF
183
+ - `fps` (int, optional): Frames per second for the GIF (default: 10)
184
+ - `scale` (int, optional): Width of the GIF in pixels, height is auto-scaled (default: 320)
185
+
186
+ **Returns:**
187
+ - `bytes`: The generated GIF data as bytes
188
+
189
+ **Raises:**
190
+ - `RuntimeError`: If GIF conversion fails
191
+
192
+ **Example:**
193
+ ```python
194
+ # Read video data from a file
195
+ with open("video.mp4", "rb") as f:
196
+ video_data = f.read()
197
+
198
+ # Convert to GIF in memory
199
+ gif_data = ffmpeg.gifs(video_data, fps=15, scale=480)
200
+
201
+ # Save the GIF
202
+ with open("output.gif", "wb") as f:
203
+ f.write(gif_data)
204
+ ```
205
+
206
+ ##### `trim(input_file: str, output_file: str, start: float = 0, duration: float | None = None) -> bytes`
207
+ Trim a video file.
208
+
209
+ **Parameters:**
210
+ - `input_file` (str): Path to the input video file
211
+ - `output_file` (str): Path for the output trimmed video
212
+ - `start` (float, optional): Start time in seconds (default: 0)
213
+ - `duration` (float | None, optional): Duration in seconds, or None for remaining video (default: None)
214
+
215
+ **Returns:**
216
+ - `bytes`: FFMPEG output (stdout or stderr) as bytes
217
+
218
+ **Raises:**
219
+ - `FileNotFoundError`: If input file doesn't exist
220
+ - `ValueError`: If start is negative or duration is non-positive
221
+ - `RuntimeError`: If trimmed video was not created successfully
222
+
223
+ **Example:**
224
+ ```python
225
+ # Trim from 5 seconds to 10 seconds (5-second clip)
226
+ output = ffmpeg.trim("video.mp4", "clip.mp4", start=5, duration=5)
227
+ print(output.decode())
228
+
229
+ # Trim from 10 seconds to the end of video
230
+ output = ffmpeg.trim("video.mp4", "ending.mp4", start=10)
231
+ print(output.decode())
232
+ ```
233
+
234
+ ## Advanced Usage
235
+
236
+ ### Custom FFMPEG Commands
237
+ For operations not covered by the built-in methods, use the `execute` method:
238
+
239
+ ```python
240
+ # Extract audio from video
241
+ ffmpeg.execute("-i", "video.mp4", "-q:a", "0", "-map", "a", "audio.mp3")
242
+
243
+ # Add watermark to video
244
+ ffmpeg.execute("-i", "video.mp4", "-i", "watermark.png",
245
+ "-filter_complex", "overlay=10:10", "output.mp4")
246
+
247
+ # Change video bitrate
248
+ ffmpeg.execute("-i", "input.mp4", "-b:v", "1M", "output.mp4")
249
+ ```
250
+
251
+ ### Error Handling
252
+ ```python
253
+ from ffmpeg_python_helper import FFMPEG
254
+ import sys
255
+
256
+ try:
257
+ ffmpeg = FFMPEG()
258
+ ffmpeg.gif("video.mp4", "output.gif")
259
+ except FileNotFoundError as e:
260
+ print(f"FFMPEG not found: {e}", file=sys.stderr)
261
+ sys.exit(1)
262
+ except RuntimeError as e:
263
+ print(f"Processing failed: {e}", file=sys.stderr)
264
+ sys.exit(1)
265
+ ```
266
+
267
+ ## Common Use Cases
268
+
269
+ ### Batch Processing
270
+ ```python
271
+ import os
272
+ from ffmpeg_python_helper import FFMPEG
273
+
274
+ ffmpeg = FFMPEG()
275
+ videos = ["video1.mp4", "video2.mp4", "video3.mp4"]
276
+
277
+ for video in videos:
278
+ if os.path.exists(video):
279
+ base_name = os.path.splitext(video)[0]
280
+ ffmpeg.gif(video, f"{base_name}.gif", fps=12, scale=400)
281
+ ```
282
+
283
+ ### Video Compilation
284
+ ```python
285
+ from ffmpeg_python_helper import FFMPEG
286
+
287
+ ffmpeg = FFMPEG()
288
+
289
+ # Trim interesting parts
290
+ ffmpeg.trim("concert.mp4", "intro.mp4", start=0, duration=30)
291
+ ffmpeg.trim("concert.mp4", "chorus.mp4", start=120, duration=45)
292
+ ffmpeg.trim("concert.mp4", "finale.mp4", start=300, duration=60)
293
+
294
+ # Later, use FFMPEG to concatenate trimmed parts
295
+ ffmpeg.execute("-f", "concat", "-safe", "0", "-i", "parts.txt", "highlight_reel.mp4")
296
+ ```
297
+
298
+ ## Troubleshooting
299
+
300
+ ### FFMPEG Not Found
301
+ If you get `FileNotFoundError` when creating an FFMPEG instance:
302
+
303
+ 1. **Install FFMPEG**:
304
+ - **Windows**: Download from [ffmpeg.org](https://ffmpeg.org/download.html)
305
+ - **macOS**: `brew install ffmpeg`
306
+ - **Linux**: `sudo apt install ffmpeg` (Ubuntu/Debian) or `sudo yum install ffmpeg` (Fedora/RHEL)
307
+
308
+ 2. **Add to PATH**:
309
+ - Ensure FFMPEG is in your system PATH
310
+ - Test with `ffmpeg -version` in your terminal
311
+
312
+ ### File Not Found Errors
313
+ - Ensure input file paths are correct and files exist
314
+ - Use absolute paths if working with files in different directories
315
+ - Check file permissions
316
+
317
+ ### GIF Creation Issues
318
+ - Lower FPS or scale if GIF file is too large
319
+ - Ensure input video has sufficient quality
320
+ - Check available disk space
321
+
322
+ ## Development
323
+
324
+ ### Running Tests
325
+ ```bash
326
+ python -m pytest tests/
327
+ ```
328
+
329
+ ### Building Documentation
330
+ ```bash
331
+ # Install documentation dependencies
332
+ pip install pdoc3
333
+
334
+ # Generate API documentation
335
+ pdoc --html ffmpeg_python_helper --output-dir docs
336
+ ```
337
+
338
+ ### Contributing
339
+ 1. Fork the repository
340
+ 2. Create a feature branch
341
+ 3. Add tests for your changes
342
+ 4. Ensure all tests pass
343
+ 5. Submit a pull request
344
+
345
+ ## License
346
+
347
+ MIT License - see LICENSE file for details
348
+
349
+ ## Support
350
+
351
+ - **Issues**: [GitHub Issues](https://github.com/yourusername/ffmpeg-python-helper/issues)
352
+ - **Documentation**: [ReadTheDocs](https://ffmpeg-python-helper.readthedocs.io)
353
+ - **Email**: marjongodito@gmanmi.com
354
+
355
+ ## Acknowledgments
356
+
357
+ - FFMPEG team for the amazing multimedia framework
358
+ - Python community for excellent tooling and libraries
@@ -0,0 +1,7 @@
1
+ ffmpeg_python_helper/__init__.py,sha256=aPK2SEFdtbeRyoCz_FOn0ZF35cpPhHBSFUsqd2T7DVk,2200
2
+ ffmpeg_python_helper/ffmpeg_api.py,sha256=UTDZ8JdizWjLdU0xfFhwiwDbUBiZ7H9a5vA7aydFoOs,18513
3
+ ffmpeg_python_helper/pipe_helper.py,sha256=fmEQwKNWuRU78bdTuS2-UIYWF8YcXjdHuR--FR8EDNw,4202
4
+ ffmpeg_python_helper-3.0.0.dist-info/WHEEL,sha256=y6e-a5KI2W-qDAJKfh9Xr81bin8NgUdfWgz3VSXXpe4,80
5
+ ffmpeg_python_helper-3.0.0.dist-info/entry_points.txt,sha256=M9t-tNA-GePbZIBMosX7vgLqg88pjImAYzSQ_fErZzA,68
6
+ ffmpeg_python_helper-3.0.0.dist-info/METADATA,sha256=2JO6yyaV-D2d152tFRE5IHd2hdR7gu2BmeELUy3e93s,10007
7
+ ffmpeg_python_helper-3.0.0.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- [diffend] Oversized file quarantined before diffing.
2
- name: ffmpeg_python_helper/_bundled/ffmpeg.exe
3
- size: 105674240 bytes
4
- sha256: f1dd57a9afb2893250fe3ea227899eb4779cbe8c2a2d0915761f2318a09140ca
@@ -1,9 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: ffmpeg-python-helper
3
- Version: 0.1.0
4
- Summary: Add your description here
5
- Author: marjon
6
- Author-email: marjon <marjongodito@gmanmi.com>
7
- Requires-Python: >=3.14
8
- Description-Content-Type: text/markdown
9
-
@@ -1,7 +0,0 @@
1
- ffmpeg_python_helper/__init__.py,sha256=_8jV2YNFzNWRGPEeiXCPrNqgW6w1sdJ9XWoPjK5h33Q,123
2
- ffmpeg_python_helper/_bundled/ffmpeg.exe,sha256=8d1Xqa-yiTJQ_j6iJ4metHecvowqLQkVdh8jGKCRQMo,105674240
3
- ffmpeg_python_helper/ffmpeg_api.py,sha256=BbOzeaAepWrGvi--YmFJkQedsSMJ-NT4XRYRp2JRMxY,3317
4
- ffmpeg_python_helper-0.1.0.dist-info/WHEEL,sha256=y6e-a5KI2W-qDAJKfh9Xr81bin8NgUdfWgz3VSXXpe4,80
5
- ffmpeg_python_helper-0.1.0.dist-info/entry_points.txt,sha256=M9t-tNA-GePbZIBMosX7vgLqg88pjImAYzSQ_fErZzA,68
6
- ffmpeg_python_helper-0.1.0.dist-info/METADATA,sha256=A9Yu67-lbGGrN8Fj8O8nKv5O2ODInjKhETTvbU7P_bw,226
7
- ffmpeg_python_helper-0.1.0.dist-info/RECORD,,