ffmpeg-python-helper 3.1.0__py3-none-any.whl → 3.2.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.
@@ -37,10 +37,12 @@ For detailed API documentation, see:
37
37
 
38
38
  from .ffmpeg_api import FFMPEG
39
39
  from .pipe_helper import Pipe
40
+ from .ffprobe_api import FFProbe
40
41
 
41
42
  __all__ = [
42
43
  'FFMPEG',
43
- 'Pipe'
44
+ 'Pipe',
45
+ 'FFProbe'
44
46
  ]
45
47
 
46
48
  __version__ = "0.1.0"
@@ -366,7 +366,10 @@ in your system PATH.
366
366
  def trims(self, input_bytes: bytes,
367
367
  start: float = 0,
368
368
  duration: float | None = None,
369
- format_type: str = "mp4") -> bytes:
369
+ format_type: str = "mp4",
370
+ v_encoder : str = "libx264",
371
+ a_encoder : str = "aac"
372
+ ) -> bytes:
370
373
  """
371
374
  Trim video data from bytes (in-memory processing).
372
375
 
@@ -422,9 +425,9 @@ in your system PATH.
422
425
 
423
426
  if format_type == 'mp4':
424
427
  args.extend([
425
- "-c:v", "libx264",
428
+ "-c:v", v_encoder,
426
429
  "-pix_fmt", "yuv420p",
427
- "-c:a", "aac",
430
+ "-c:a", a_encoder,
428
431
  "-movflags", "frag_keyframe+empty_moov"
429
432
  ])
430
433
 
@@ -523,4 +526,58 @@ in your system PATH.
523
526
  "pipe:1"
524
527
  ]
525
528
 
526
- return self.execute(*args, input_data=input_bytes)[0]
529
+ return self.execute(*args, input_data=input_bytes)[0]
530
+
531
+ def compress_file(self,
532
+ input_file : str,
533
+ output_file : str,
534
+ crf: int = 28,
535
+ v_encoder : str = "libx264",
536
+ a_encoder : str = "aac" ,
537
+ px_format : str = "yuv420p"):
538
+
539
+ args = [
540
+ "-i", input_file,
541
+ "-c:v", v_encoder,
542
+ "-crf", str(crf),
543
+ "-pix_fmt", px_format,
544
+ "-c:a", a_encoder,
545
+ "-movflags", "faststart",
546
+ output_file,
547
+ ]
548
+
549
+ self.execute(*args)
550
+
551
+ def compress_byte(self,
552
+ input_data : bytes,
553
+ crf: int = 28,
554
+ v_encoder : str = "libx264",
555
+ a_encoder : str = "aac" ,
556
+ px_format : str = "yuv420p"
557
+ ):
558
+ args = [
559
+ "-i", "pipe:0",
560
+ "-c:v", v_encoder,
561
+ "-crf", str(crf),
562
+ "-pix_fmt", px_format,
563
+ "-c:a", a_encoder,
564
+ "-movflags", "frag_keyframe+empty_moov",
565
+ "-f", "mp4",
566
+ "pipe:1"
567
+ ]
568
+
569
+ return self.execute(*args, input_data=input_data)[0]
570
+
571
+ def verify(self, input_file : str) -> tuple[bool, str]:
572
+ args = [
573
+ "-v", "error",
574
+ "-i", input_file,
575
+ "-f", "null",
576
+ "-"
577
+ ]
578
+
579
+ try:
580
+ self.execute(*args)
581
+ return True, ""
582
+ except RuntimeError as e:
583
+ return False, str(e)
@@ -0,0 +1,169 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+
4
+
5
+ class FFProbe:
6
+ """
7
+ A Python wrapper for FFProbe (FFMPEG's multimedia stream analyzer) that provides
8
+ video metadata analysis capabilities.
9
+
10
+ This class automatically detects FFProbe installation in the system PATH
11
+ and provides methods for extracting video metadata, analyzing video properties,
12
+ and checking video characteristics.
13
+
14
+ Example:
15
+ >>> from ffmpeg_python_helper import FFProbe
16
+ >>> ffprobe = FFProbe()
17
+ >>> # Check if video is short enough for social media
18
+ >>> if ffprobe.is_max_length("video.mp4", max_length=5.0):
19
+ ... print("Video is perfect for Instagram Reels!")
20
+ >>> # Get video metadata
21
+ >>> import json
22
+ >>> stdout, stderr = ffprobe.execute("-v", "quiet", "-print_format", "json",
23
+ ... "-show_format", "-show_streams", "video.mp4")
24
+ >>> metadata = json.loads(stdout.decode())
25
+ >>> print(f"Video duration: {metadata['format']['duration']} seconds")
26
+ >>> print(f"Video dimensions: {metadata['streams'][0]['width']}x{metadata['streams'][0]['height']}")
27
+
28
+ Attributes:
29
+ executable (str): The path to the FFProbe executable found in the system PATH.
30
+ """
31
+
32
+ def __init__(self) -> None:
33
+ """
34
+ Initialize a new FFProbe instance.
35
+
36
+ Automatically searches for FFProbe in the system PATH.
37
+
38
+ Raises:
39
+ FileNotFoundError: If FFProbe is not found in the system PATH.
40
+
41
+ Example:
42
+ >>> try:
43
+ ... ffprobe = FFProbe()
44
+ ... print(f"FFProbe found at: {ffprobe.executable}")
45
+ ... except FileNotFoundError as e:
46
+ ... print(f"FFProbe not found: {e}")
47
+ """
48
+ import shutil
49
+ self.executable = shutil.which('ffprobe')
50
+
51
+ if self.executable is None:
52
+ raise FileNotFoundError("""
53
+ FFProbe is not installed or could not be found in PATH.
54
+ Please install FFmpeg and make sure it is available
55
+ in your system PATH.
56
+ """)
57
+
58
+ @classmethod
59
+ def api(cls) -> "FFProbe":
60
+ """
61
+ Factory method that returns a new FFProbe instance.
62
+
63
+ Returns:
64
+ FFProbe: A new instance of the FFProbe class.
65
+
66
+ Example:
67
+ >>> ffprobe = FFProbe.api()
68
+ >>> ffprobe.execute("-version")
69
+ """
70
+ return cls()
71
+
72
+ def execute(self, *args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]:
73
+ """
74
+ Execute raw FFProbe commands with the given arguments.
75
+
76
+ This method allows you to run any FFProbe command directly,
77
+ providing maximum flexibility for metadata analysis operations
78
+ not covered by the built-in methods.
79
+
80
+ Args:
81
+ *args: FFProbe command-line arguments as strings.
82
+ input_data: Optional bytes to send to FFProbe's stdin. Useful for
83
+ analyzing data directly without intermediate files.
84
+
85
+ Returns:
86
+ tuple[bytes, bytes]: A tuple containing (stdout, stderr) from FFProbe
87
+ as bytes objects.
88
+
89
+ Raises:
90
+ FileNotFoundError: If FFProbe executable is not found.
91
+ RuntimeError: If FFProbe command returns a non-zero exit code.
92
+
93
+ Example:
94
+ >>> stdout, stderr = ffprobe.execute("-version")
95
+ >>> print(stdout.decode())
96
+
97
+ >>> # Get video metadata in JSON format
98
+ >>> stdout, stderr = ffprobe.execute("-v", "quiet", "-print_format", "json",
99
+ ... "-show_format", "-show_streams", "video.mp4")
100
+ >>> import json
101
+ >>> metadata = json.loads(stdout.decode())
102
+ >>> print(f"Video duration: {metadata['format']['duration']} seconds")
103
+
104
+ >>> # Get video dimensions
105
+ >>> stdout, stderr = ffprobe.execute("-v", "error", "-select_streams", "v:0",
106
+ ... "-show_entries", "stream=width,height",
107
+ ... "-of", "csv=p=0", "video.mp4")
108
+ >>> print(f"Video dimensions: {stdout.decode().strip()}")
109
+ """
110
+ if self.executable:
111
+ result = subprocess.run(
112
+ [self.executable, *args],
113
+ input=input_data,
114
+ stdout=subprocess.PIPE,
115
+ stderr=subprocess.PIPE,
116
+ check=False,
117
+ text=True
118
+ )
119
+
120
+ if result.returncode != 0:
121
+ raise RuntimeError(
122
+ result.stderr.decode(errors="replace")
123
+ )
124
+
125
+ return result.stdout, result.stderr
126
+
127
+ raise FileNotFoundError("""
128
+ No ffprobe executable found.
129
+
130
+ Please Install FFmpeg (which includes ffprobe) first.
131
+ """)
132
+
133
+ def is_max_length(self, input_file : str, max_length : float = 5.0) -> bool:
134
+ """
135
+ Check if a video file's duration is less than or equal to a specified maximum length.
136
+
137
+ Args:
138
+ input_file: Path to the input video file
139
+ max_length: Maximum allowed duration in seconds (default: 5.0)
140
+
141
+ Returns:
142
+ bool: True if video duration ≤ max_length, False otherwise
143
+
144
+ Raises:
145
+ FileNotFoundError: If input file doesn't exist
146
+ RuntimeError: If FFProbe command fails
147
+
148
+ Example:
149
+ >>> if ffprobe.is_max_length("video.mp4", max_length=10.0):
150
+ ... print("Video is short enough for social media upload")
151
+ ... else:
152
+ ... print("Video is too long, needs trimming")
153
+ """
154
+ args = [
155
+ "-v", "error",
156
+ "-select_streams", "v:0",
157
+ "-show_entries", "stream=duration",
158
+ "-of", "default=noprint_wrappers=1:nokey=1",
159
+ input_file,
160
+ ]
161
+
162
+ stdout, stderr = self.execute(*args)
163
+
164
+ duration = float(stdout.strip())
165
+
166
+ return duration <= max_length
167
+
168
+
169
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ffmpeg-python-helper
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
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
5
  Author: marjon
6
6
  Author-email: marjon <petmalu.marjon@gmail.com>
@@ -19,6 +19,7 @@ A Python wrapper for FFMPEG that provides a simple, intuitive API for common vid
19
19
  - 🎵 **Audio Extraction** - Extract audio tracks from videos without re-encoding
20
20
  - 🧠 **In-Memory Processing** - Process video/audio data directly from bytes without temporary files
21
21
  - ✂️ **Video Trimming** - Trim videos with precise start time and duration control
22
+ - 🔍 **Video Metadata Analysis** - Extract video information and metadata using FFProbe
22
23
  - 🐍 **Pythonic API** - Clean, object-oriented interface with proper error handling
23
24
  - 📁 **File Validation** - Automatic input file existence checking
24
25
 
@@ -82,6 +83,26 @@ with open("trimmed.mp4", "wb") as f:
82
83
  audio_data = ffmpeg.extract_audios(video_data, output_format="m4a")
83
84
  with open("audio.m4a", "wb") as f:
84
85
  f.write(audio_data)
86
+
87
+ # Analyze video metadata with FFProbe
88
+ from ffmpeg_python_helper import FFProbe
89
+ import json
90
+
91
+ ffprobe = FFProbe()
92
+ print(f"FFProbe executable found at: {ffprobe.executable}")
93
+
94
+ # Check if video is short enough for social media
95
+ if ffprobe.is_max_length("video.mp4", max_length=5.0):
96
+ print("Video is perfect for Instagram Reels!")
97
+ else:
98
+ print("Video needs trimming for short-form content")
99
+
100
+ # Get detailed video metadata
101
+ stdout, stderr = ffprobe.execute("-v", "quiet", "-print_format", "json",
102
+ "-show_format", "-show_streams", "video.mp4")
103
+ metadata = json.loads(stdout.decode())
104
+ print(f"Video duration: {metadata['format']['duration']} seconds")
105
+ print(f"Video dimensions: {metadata['streams'][0]['width']}x{metadata['streams'][0]['height']}")
85
106
  ```
86
107
 
87
108
  ## API Reference
@@ -231,6 +252,100 @@ output = ffmpeg.trim("video.mp4", "ending.mp4", start=10)
231
252
  print(output.decode())
232
253
  ```
233
254
 
255
+ ### `FFProbe` Class
256
+
257
+ A Python wrapper for FFProbe (the FFMPEG multimedia stream analyzer) that provides video metadata analysis capabilities.
258
+
259
+ #### Constructor
260
+ ```python
261
+ FFProbe()
262
+ ```
263
+ Creates a new FFProbe instance. Automatically searches for FFProbe in the system PATH.
264
+ - **Raises**: `FileNotFoundError` if FFProbe is not found in PATH
265
+
266
+ #### Properties
267
+ - `executable` (str): The path to the FFProbe executable found in the system
268
+
269
+ #### Class Methods
270
+ ```python
271
+ @classmethod
272
+ def api(cls) -> "FFProbe"
273
+ ```
274
+ Factory method that returns a new FFProbe instance.
275
+ - **Returns**: `FFProbe` instance
276
+
277
+ **Example:**
278
+ ```python
279
+ ffprobe = FFProbe.api()
280
+ print(f"FFProbe executable found at: {ffprobe.executable}")
281
+ ```
282
+
283
+ #### Instance Methods
284
+
285
+ ##### `execute(*args: str, input_data: bytes | None = None) -> tuple[bytes, bytes]`
286
+ Execute raw FFProbe commands with the given arguments.
287
+
288
+ **Parameters:**
289
+ - `*args` (str): FFProbe command-line arguments as strings
290
+ - `input_data` (bytes | None, optional): Optional bytes to send to FFProbe's stdin
291
+
292
+ **Returns:**
293
+ - `tuple[bytes, bytes]`: A tuple containing (stdout, stderr) as bytes
294
+
295
+ **Raises:**
296
+ - `FileNotFoundError`: If FFProbe executable is not found
297
+ - `RuntimeError`: If FFProbe command returns a non-zero exit code
298
+
299
+ **Example:**
300
+ ```python
301
+ # Get FFProbe version
302
+ stdout, stderr = ffprobe.execute("-version")
303
+ print(stdout.decode())
304
+
305
+ # Get video metadata in JSON format
306
+ stdout, stderr = ffprobe.execute("-v", "quiet", "-print_format", "json",
307
+ "-show_format", "-show_streams", "video.mp4")
308
+ metadata = json.loads(stdout.decode())
309
+ print(f"Video duration: {metadata['format']['duration']} seconds")
310
+
311
+ # Get video dimensions
312
+ stdout, stderr = ffprobe.execute("-v", "error", "-select_streams", "v:0",
313
+ "-show_entries", "stream=width,height",
314
+ "-of", "csv=p=0", "video.mp4")
315
+ print(f"Video dimensions: {stdout.decode().strip()}")
316
+ ```
317
+
318
+ ##### `is_max_length(input_file: str, max_length: float = 5.0) -> bool`
319
+ Check if a video file's duration is less than or equal to a specified maximum length.
320
+
321
+ **Parameters:**
322
+ - `input_file` (str): Path to the input video file
323
+ - `max_length` (float, optional): Maximum allowed duration in seconds (default: 5.0)
324
+
325
+ **Returns:**
326
+ - `bool`: `True` if video duration ≤ max_length, `False` otherwise
327
+
328
+ **Raises:**
329
+ - `FileNotFoundError`: If input file doesn't exist
330
+ - `RuntimeError`: If FFProbe command fails
331
+
332
+ **Example:**
333
+ ```python
334
+ # Check if video is shorter than 10 seconds
335
+ if ffprobe.is_max_length("video.mp4", max_length=10.0):
336
+ print("Video is short enough for social media upload")
337
+ else:
338
+ print("Video is too long, needs trimming")
339
+
340
+ # Check multiple videos for length compliance
341
+ videos = ["clip1.mp4", "clip2.mp4", "clip3.mp4"]
342
+ for video in videos:
343
+ if ffprobe.is_max_length(video, max_length=5.0):
344
+ print(f"{video}: OK (≤ 5 seconds)")
345
+ else:
346
+ print(f"{video}: Too long (> 5 seconds)")
347
+ ```
348
+
234
349
  ## Advanced Usage
235
350
 
236
351
  ### Custom FFMPEG Commands
@@ -248,6 +363,52 @@ ffmpeg.execute("-i", "video.mp4", "-i", "watermark.png",
248
363
  ffmpeg.execute("-i", "input.mp4", "-b:v", "1M", "output.mp4")
249
364
  ```
250
365
 
366
+ ### Advanced FFProbe Usage
367
+ FFProbe provides powerful video metadata analysis capabilities:
368
+
369
+ ```python
370
+ from ffmpeg_python_helper import FFProbe
371
+ import json
372
+
373
+ ffprobe = FFProbe()
374
+
375
+ # Get multiple video metadata properties at once
376
+ stdout, stderr = ffprobe.execute(
377
+ "-v", "error",
378
+ "-select_streams", "v:0",
379
+ "-show_entries", "stream=width,height,duration,bit_rate,codec_name",
380
+ "-of", "json",
381
+ "video.mp4"
382
+ )
383
+ video_info = json.loads(stdout.decode())
384
+ print(f"Video codec: {video_info['streams'][0]['codec_name']}")
385
+ print(f"Video bitrate: {video_info['streams'][0]['bit_rate']} bps")
386
+
387
+ # Check frame rate
388
+ stdout, stderr = ffprobe.execute(
389
+ "-v", "error",
390
+ "-select_streams", "v:0",
391
+ "-show_entries", "stream=r_frame_rate",
392
+ "-of", "default=noprint_wrappers=1:nokey=1",
393
+ "video.mp4"
394
+ )
395
+ print(f"Frame rate: {stdout.decode().strip()}")
396
+
397
+ # Get audio stream information
398
+ stdout, stderr = ffprobe.execute(
399
+ "-v", "error",
400
+ "-select_streams", "a:0",
401
+ "-show_entries", "stream=codec_name,channels,sample_rate",
402
+ "-of", "json",
403
+ "video.mp4"
404
+ )
405
+ audio_info = json.loads(stdout.decode())
406
+ if audio_info['streams']:
407
+ print(f"Audio codec: {audio_info['streams'][0]['codec_name']}")
408
+ print(f"Audio channels: {audio_info['streams'][0]['channels']}")
409
+ print(f"Sample rate: {audio_info['streams'][0]['sample_rate']} Hz")
410
+ ```
411
+
251
412
  ### Error Handling
252
413
  ```python
253
414
  from ffmpeg_python_helper import FFMPEG
@@ -0,0 +1,8 @@
1
+ ffmpeg_python_helper/__init__.py,sha256=CUCXwvcuMPz1WzZRSTyKJeEXkSq8YXN7rvOc8jWTWrk,2248
2
+ ffmpeg_python_helper/ffmpeg_api.py,sha256=y4oRbF3piYWD1dgGdni5o27jyBgrGMdFuD4g8VPkJJw,20143
3
+ ffmpeg_python_helper/ffprobe_api.py,sha256=cv3pC7ongB5eMCaYsdKB2mtxklfV3QrxuWUZEoG2kB4,6178
4
+ ffmpeg_python_helper/pipe_helper.py,sha256=fmEQwKNWuRU78bdTuS2-UIYWF8YcXjdHuR--FR8EDNw,4202
5
+ ffmpeg_python_helper-3.2.0.dist-info/WHEEL,sha256=y6e-a5KI2W-qDAJKfh9Xr81bin8NgUdfWgz3VSXXpe4,80
6
+ ffmpeg_python_helper-3.2.0.dist-info/entry_points.txt,sha256=M9t-tNA-GePbZIBMosX7vgLqg88pjImAYzSQ_fErZzA,68
7
+ ffmpeg_python_helper-3.2.0.dist-info/METADATA,sha256=U8BlSYF_8cfuhirdr-C70GuQkJKhgKOWKOuCQpqvgRQ,15196
8
+ ffmpeg_python_helper-3.2.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
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.1.0.dist-info/WHEEL,sha256=y6e-a5KI2W-qDAJKfh9Xr81bin8NgUdfWgz3VSXXpe4,80
5
- ffmpeg_python_helper-3.1.0.dist-info/entry_points.txt,sha256=M9t-tNA-GePbZIBMosX7vgLqg88pjImAYzSQ_fErZzA,68
6
- ffmpeg_python_helper-3.1.0.dist-info/METADATA,sha256=QygyUAW2-KC5AtEFZrbntyWSBdzdZ9mXjd_6hP2ATPo,10008
7
- ffmpeg_python_helper-3.1.0.dist-info/RECORD,,