typed-ffmpeg-compatible 2.6.3__py3-none-any.whl → 2.6.4__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.
- typed_ffmpeg/__init__.py +24 -0
- typed_ffmpeg/base.py +87 -29
- typed_ffmpeg/common/schema.py +281 -3
- typed_ffmpeg/common/serialize.py +118 -21
- typed_ffmpeg/dag/__init__.py +13 -0
- typed_ffmpeg/dag/compile.py +39 -4
- typed_ffmpeg/dag/context.py +137 -29
- typed_ffmpeg/dag/factory.py +27 -0
- typed_ffmpeg/dag/global_runnable/global_args.py +11 -0
- typed_ffmpeg/dag/global_runnable/runnable.py +143 -34
- typed_ffmpeg/dag/io/_input.py +2 -1
- typed_ffmpeg/dag/io/_output.py +2 -1
- typed_ffmpeg/dag/nodes.py +402 -67
- typed_ffmpeg/dag/schema.py +3 -1
- typed_ffmpeg/dag/utils.py +29 -8
- typed_ffmpeg/dag/validate.py +83 -20
- typed_ffmpeg/exceptions.py +42 -9
- typed_ffmpeg/info.py +137 -16
- typed_ffmpeg/probe.py +31 -6
- typed_ffmpeg/schema.py +32 -5
- typed_ffmpeg/streams/channel_layout.py +13 -0
- typed_ffmpeg/utils/escaping.py +47 -7
- typed_ffmpeg/utils/forzendict.py +108 -0
- typed_ffmpeg/utils/lazy_eval/operator.py +43 -1
- typed_ffmpeg/utils/lazy_eval/schema.py +122 -6
- typed_ffmpeg/utils/run.py +44 -7
- typed_ffmpeg/utils/snapshot.py +36 -1
- typed_ffmpeg/utils/typing.py +29 -4
- typed_ffmpeg/utils/view.py +46 -4
- {typed_ffmpeg_compatible-2.6.3.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/METADATA +1 -1
- typed_ffmpeg_compatible-2.6.4.dist-info/RECORD +48 -0
- typed_ffmpeg_compatible-2.6.3.dist-info/RECORD +0 -47
- {typed_ffmpeg_compatible-2.6.3.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/LICENSE +0 -0
- {typed_ffmpeg_compatible-2.6.3.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/WHEEL +0 -0
- {typed_ffmpeg_compatible-2.6.3.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/entry_points.txt +0 -0
typed_ffmpeg/dag/schema.py
CHANGED
@@ -5,6 +5,7 @@ from dataclasses import dataclass, replace
|
|
5
5
|
from functools import cached_property
|
6
6
|
from typing import TYPE_CHECKING, Literal
|
7
7
|
|
8
|
+
from ..utils.forzendict import FrozenDict
|
8
9
|
from ..utils.lazy_eval.schema import LazyValue
|
9
10
|
from .utils import is_dag
|
10
11
|
|
@@ -84,7 +85,8 @@ class Node(HashableBaseModel, ABC):
|
|
84
85
|
"""
|
85
86
|
|
86
87
|
# Filter_Node_Option_Type
|
87
|
-
kwargs: tuple[tuple[str, str | int | float | bool | LazyValue], ...] = ()
|
88
|
+
# kwargs: tuple[tuple[str, str | int | float | bool | LazyValue], ...] = ()
|
89
|
+
kwargs: FrozenDict[str, str | int | float | bool | LazyValue] = FrozenDict({})
|
88
90
|
"""
|
89
91
|
Represents the keyword arguments of the node.
|
90
92
|
"""
|
typed_ffmpeg/dag/utils.py
CHANGED
@@ -1,22 +1,43 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for working with directed acyclic graphs (DAGs).
|
3
|
+
|
4
|
+
This module provides functions for validating and analyzing graph structures,
|
5
|
+
particularly for ensuring that filter graphs are properly formed as DAGs
|
6
|
+
without cycles, which is a requirement for FFmpeg filter chains.
|
7
|
+
"""
|
8
|
+
|
1
9
|
from __future__ import annotations
|
2
10
|
|
3
11
|
from collections import deque
|
4
12
|
|
5
|
-
# Another approach to determine if a graph is a DAG is to try to perform a topological sort.
|
6
|
-
# If the topological sort is successful (i.e., all vertices are visited exactly once),
|
7
|
-
# the graph is a DAG. If the topological sort cannot include all vertices (i.e., the graph has a cycle),
|
8
|
-
# it is not a DAG. Here is a basic implementation using Kahn's Algorithm:
|
9
|
-
|
10
13
|
|
11
14
|
def is_dag(graph: dict[str, set[str]]) -> bool:
|
12
15
|
"""
|
13
|
-
Determine if a graph is a directed acyclic graph (DAG).
|
16
|
+
Determine if a graph is a directed acyclic graph (DAG) using topological sorting.
|
17
|
+
|
18
|
+
This function implements Kahn's algorithm for topological sorting to check
|
19
|
+
if the given graph is a DAG. A graph is a DAG if it has no directed cycles.
|
20
|
+
The algorithm works by repeatedly removing nodes with no incoming edges
|
21
|
+
and their outgoing edges. If all nodes can be removed this way, the graph
|
22
|
+
is a DAG; otherwise, it contains at least one cycle.
|
14
23
|
|
15
24
|
Args:
|
16
|
-
graph:
|
25
|
+
graph: A dictionary representing the graph, where keys are node IDs and
|
26
|
+
values are sets of node IDs that the key node points to
|
17
27
|
|
18
28
|
Returns:
|
19
|
-
|
29
|
+
True if the graph is a DAG (has no cycles), False otherwise
|
30
|
+
|
31
|
+
Example:
|
32
|
+
```python
|
33
|
+
# A simple linear graph (A -> B -> C)
|
34
|
+
graph = {"A": {"B"}, "B": {"C"}, "C": set()}
|
35
|
+
assert is_dag(graph) == True
|
36
|
+
|
37
|
+
# A graph with a cycle (A -> B -> C -> A)
|
38
|
+
graph = {"A": {"B"}, "B": {"C"}, "C": {"A"}}
|
39
|
+
assert is_dag(graph) == False
|
40
|
+
```
|
20
41
|
"""
|
21
42
|
|
22
43
|
in_degree = {u: 0 for u in graph} # Initialize in-degree of each node to 0
|
typed_ffmpeg/dag/validate.py
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
"""
|
2
|
+
Graph validation and transformation for FFmpeg filter chains.
|
3
|
+
|
4
|
+
This module provides functionality to validate and fix FFmpeg filter graphs,
|
5
|
+
particularly handling the case of stream reuse. In FFmpeg, a stream cannot
|
6
|
+
be used as input to multiple filters without explicit split/asplit filters.
|
7
|
+
This module detects such cases and automatically inserts the necessary split
|
8
|
+
filters to ensure the graph is valid for FFmpeg processing.
|
9
|
+
"""
|
10
|
+
|
1
11
|
from __future__ import annotations
|
2
12
|
|
3
13
|
from dataclasses import replace
|
@@ -14,14 +24,25 @@ def remove_split(
|
|
14
24
|
current_stream: Stream, mapping: dict[Stream, Stream] = None
|
15
25
|
) -> tuple[Stream, dict[Stream, Stream]]:
|
16
26
|
"""
|
17
|
-
|
27
|
+
Remove all split nodes from the graph to prepare for reconstruction.
|
28
|
+
|
29
|
+
This function performs the first step of graph repair by recursively traversing
|
30
|
+
the graph and removing all existing split/asplit nodes. This creates a clean
|
31
|
+
graph without any stream splitting, which will then be reconstructed with proper
|
32
|
+
split nodes where needed.
|
33
|
+
|
34
|
+
The function works recursively, processing each node's inputs and creating a
|
35
|
+
new graph structure with the split nodes removed.
|
18
36
|
|
19
37
|
Args:
|
20
|
-
current_stream: The stream to
|
21
|
-
mapping:
|
38
|
+
current_stream: The starting stream to process
|
39
|
+
mapping: Dictionary mapping original streams to their new versions without splits
|
40
|
+
(used for recursion, pass None for initial call)
|
22
41
|
|
23
42
|
Returns:
|
24
|
-
A tuple
|
43
|
+
A tuple containing:
|
44
|
+
- The new stream corresponding to the input stream but with splits removed
|
45
|
+
- A mapping dictionary relating original streams to their new versions
|
25
46
|
"""
|
26
47
|
|
27
48
|
# remove all split nodes
|
@@ -76,17 +97,31 @@ def add_split(
|
|
76
97
|
mapping: dict[tuple[Stream, Node | None, int | None], Stream] = None,
|
77
98
|
) -> tuple[Stream, dict[tuple[Stream, Node | None, int | None], Stream]]:
|
78
99
|
"""
|
79
|
-
Add split nodes to the graph.
|
100
|
+
Add split nodes to the graph where streams are reused.
|
101
|
+
|
102
|
+
This function performs the second step of graph repair by traversing the
|
103
|
+
graph and adding split/asplit nodes where a stream is used as input to
|
104
|
+
multiple downstream nodes. In FFmpeg, each stream can only be used once
|
105
|
+
unless explicitly split.
|
106
|
+
|
107
|
+
The function detects cases where a stream has multiple outgoing connections
|
108
|
+
and inserts the appropriate split filter (split for video, asplit for audio),
|
109
|
+
connecting each output of the split to the corresponding downstream node.
|
80
110
|
|
81
111
|
Args:
|
82
|
-
current_stream: The stream to
|
83
|
-
down_node: The node
|
84
|
-
down_index: The index
|
85
|
-
context: The DAG context
|
86
|
-
mapping:
|
112
|
+
current_stream: The stream to process for potential splitting
|
113
|
+
down_node: The downstream node that uses current_stream as input (for recursion)
|
114
|
+
down_index: The input index in down_node where current_stream connects (for recursion)
|
115
|
+
context: The DAG context containing graph relationship information
|
116
|
+
mapping: Dictionary tracking the transformations (used for recursion,
|
117
|
+
pass None for initial call)
|
87
118
|
|
88
119
|
Returns:
|
89
|
-
|
120
|
+
Stream: The new stream (possibly from a split node output) for the specified downstream connection
|
121
|
+
dict: A mapping dictionary relating original stream/connections to their new versions
|
122
|
+
|
123
|
+
Raises:
|
124
|
+
FFMpegValueError: If an unsupported stream type is encountered
|
90
125
|
"""
|
91
126
|
|
92
127
|
if not context:
|
@@ -149,17 +184,24 @@ def add_split(
|
|
149
184
|
|
150
185
|
def fix_graph(stream: Stream) -> Stream:
|
151
186
|
"""
|
152
|
-
Fix the graph by
|
187
|
+
Fix stream reuse issues in the filter graph by properly adding split nodes.
|
188
|
+
|
189
|
+
This function performs a complete graph repair operation by:
|
190
|
+
1. First removing all existing split/asplit nodes from the graph
|
191
|
+
2. Then adding new split/asplit nodes where needed to handle stream reuse
|
192
|
+
|
193
|
+
This ensures that the graph follows FFmpeg's requirement that each stream
|
194
|
+
output can only be used as input to one filter unless explicitly split.
|
153
195
|
|
154
196
|
Args:
|
155
|
-
stream: The stream to fix
|
197
|
+
stream: The root stream of the graph to fix (typically an output stream)
|
156
198
|
|
157
199
|
Returns:
|
158
|
-
|
200
|
+
A new stream representing the fixed graph with proper splitting
|
159
201
|
|
160
202
|
Note:
|
161
|
-
|
162
|
-
|
203
|
+
This function creates a new graph structure rather than modifying the
|
204
|
+
existing one, preserving the original graph.
|
163
205
|
"""
|
164
206
|
|
165
207
|
stream, _ = remove_split(stream)
|
@@ -169,14 +211,35 @@ def fix_graph(stream: Stream) -> Stream:
|
|
169
211
|
|
170
212
|
def validate(stream: Stream, auto_fix: bool = True) -> Stream:
|
171
213
|
"""
|
172
|
-
Validate
|
214
|
+
Validate a filter graph and optionally fix stream reuse issues.
|
215
|
+
|
216
|
+
This function validates that the filter graph follows FFmpeg's rules,
|
217
|
+
particularly regarding stream reuse. In FFmpeg, a stream cannot be used
|
218
|
+
as input to multiple filters without an explicit split/asplit filter.
|
219
|
+
|
220
|
+
When auto_fix is True (the default), this function automatically inserts
|
221
|
+
the necessary split filters where needed, ensuring the graph is valid for
|
222
|
+
FFmpeg processing.
|
173
223
|
|
174
224
|
Args:
|
175
|
-
stream: The
|
176
|
-
auto_fix: Whether to automatically fix
|
225
|
+
stream: The stream representing the filter graph to validate
|
226
|
+
auto_fix: Whether to automatically fix stream reuse issues by adding
|
227
|
+
appropriate split nodes
|
177
228
|
|
178
229
|
Returns:
|
179
|
-
|
230
|
+
Either the original stream (if no fixing needed/requested) or a new
|
231
|
+
stream representing the fixed graph
|
232
|
+
|
233
|
+
Example:
|
234
|
+
```python
|
235
|
+
# Create a graph where the same stream is used twice (reused)
|
236
|
+
input_stream = ffmpeg.input("input.mp4")
|
237
|
+
# Use the same stream for both scaling and blurring (invalid in FFmpeg)
|
238
|
+
scaled = input_stream.filter("scale", 1280, 720)
|
239
|
+
blurred = input_stream.filter("boxblur", 2)
|
240
|
+
# Validate will automatically insert a split filter
|
241
|
+
valid_stream = ffmpeg.dag.validate(scaled.output("output.mp4"))
|
242
|
+
```
|
180
243
|
"""
|
181
244
|
if auto_fix:
|
182
245
|
stream = fix_graph(stream)
|
typed_ffmpeg/exceptions.py
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
+
"""
|
2
|
+
Exception classes for handling FFmpeg-related errors.
|
3
|
+
|
4
|
+
This module defines a hierarchy of exceptions that can be raised during
|
5
|
+
FFmpeg operations, providing detailed error reporting for type errors,
|
6
|
+
value errors, and execution failures.
|
7
|
+
"""
|
8
|
+
|
9
|
+
|
1
10
|
class FFMpegError(Exception):
|
2
11
|
"""
|
3
|
-
Base exception for all
|
12
|
+
Base exception for all FFmpeg errors.
|
13
|
+
|
14
|
+
This is the parent class for all exceptions that may be raised by the
|
15
|
+
typed-ffmpeg library. It inherits from the standard Python Exception class.
|
4
16
|
"""
|
5
17
|
|
6
18
|
...
|
@@ -8,30 +20,51 @@ class FFMpegError(Exception):
|
|
8
20
|
|
9
21
|
class FFMpegTypeError(FFMpegError, TypeError):
|
10
22
|
"""
|
11
|
-
|
23
|
+
Exception raised for FFmpeg-related type errors.
|
24
|
+
|
25
|
+
This exception is raised when an operation receives an argument of incorrect
|
26
|
+
type, such as passing a string when a numeric value is expected, or vice versa.
|
27
|
+
|
28
|
+
Inherits from both FFMpegError and the standard Python TypeError.
|
12
29
|
"""
|
13
30
|
|
14
31
|
|
15
32
|
class FFMpegValueError(FFMpegError, ValueError):
|
16
33
|
"""
|
17
|
-
|
34
|
+
Exception raised for FFmpeg-related value errors.
|
35
|
+
|
36
|
+
This exception is raised when an operation receives an argument with the correct
|
37
|
+
type but an inappropriate value, such as a negative duration or invalid codec name.
|
38
|
+
|
39
|
+
Inherits from both FFMpegError and the standard Python ValueError.
|
18
40
|
"""
|
19
41
|
|
20
42
|
|
21
43
|
class FFMpegExecuteError(FFMpegError):
|
22
44
|
"""
|
23
|
-
FFmpeg
|
45
|
+
Exception raised when an FFmpeg command fails during execution.
|
46
|
+
|
47
|
+
This exception is raised when an FFmpeg process returns a non-zero exit code,
|
48
|
+
indicating that the command failed to execute properly. The exception captures
|
49
|
+
the return code, command string, and stdout/stderr output to help diagnose
|
50
|
+
the issue.
|
51
|
+
|
52
|
+
Attributes:
|
53
|
+
stdout: The standard output of the failed command
|
54
|
+
stderr: The standard error output of the failed command
|
55
|
+
cmd: The command string that was executed
|
56
|
+
retcode: The process return code
|
24
57
|
"""
|
25
58
|
|
26
59
|
def __init__(self, retcode: int | None, cmd: str, stdout: bytes, stderr: bytes):
|
27
60
|
"""
|
28
|
-
Initialize the
|
61
|
+
Initialize the FFMpegExecuteError with execution details.
|
29
62
|
|
30
63
|
Args:
|
31
|
-
retcode: The return code of the command
|
32
|
-
cmd: The command that was
|
33
|
-
stdout: The
|
34
|
-
stderr: The
|
64
|
+
retcode: The return code of the FFmpeg command
|
65
|
+
cmd: The FFmpeg command string that was executed
|
66
|
+
stdout: The captured standard output from the process
|
67
|
+
stderr: The captured standard error from the process
|
35
68
|
"""
|
36
69
|
|
37
70
|
self.stdout = stdout
|
typed_ffmpeg/info.py
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Module for querying FFmpeg codec and encoder information.
|
3
|
+
|
4
|
+
This module provides functionality to query the available FFmpeg codecs,
|
5
|
+
decoders, and encoders on the system. It parses the output of FFmpeg's
|
6
|
+
command-line tools to extract information about supported formats and
|
7
|
+
capabilities.
|
8
|
+
"""
|
9
|
+
|
1
10
|
import logging
|
2
11
|
import subprocess
|
3
12
|
from dataclasses import dataclass
|
@@ -10,24 +19,56 @@ logger = logging.getLogger(__name__)
|
|
10
19
|
|
11
20
|
|
12
21
|
class CodecFlags(Flag):
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
22
|
+
"""
|
23
|
+
Flag enumeration representing the capabilities of a codec.
|
24
|
+
|
25
|
+
These flags correspond to the character flags in FFmpeg's codec information
|
26
|
+
output and indicate what features a codec supports.
|
27
|
+
"""
|
28
|
+
|
29
|
+
video = auto() # Codec supports video encoding/decoding
|
30
|
+
audio = auto() # Codec supports audio encoding/decoding
|
31
|
+
subtitle = auto() # Codec supports subtitle processing
|
32
|
+
frame_level_multithreading = auto() # Codec supports frame-level multithreading
|
33
|
+
slice_level_multithreading = auto() # Codec supports slice-level multithreading
|
34
|
+
experimental = auto() # Codec is considered experimental
|
35
|
+
draw_horiz_band = auto() # Codec supports drawing horizontal bands
|
36
|
+
direct_rendering_method_1 = auto() # Codec supports direct rendering method 1
|
21
37
|
|
22
38
|
|
23
39
|
@dataclass(frozen=True)
|
24
40
|
class Codec:
|
41
|
+
"""
|
42
|
+
Represents an FFmpeg codec with its capabilities and description.
|
43
|
+
|
44
|
+
This immutable dataclass stores information about a codec identified by the
|
45
|
+
`ffmpeg -codecs` command, including its name, supported features (as flags),
|
46
|
+
and its textual description.
|
47
|
+
|
48
|
+
Attributes:
|
49
|
+
name: The codec identifier used in FFmpeg commands
|
50
|
+
flags: Bitmap of capabilities supported by the codec
|
51
|
+
description: Human-readable description of the codec
|
52
|
+
"""
|
53
|
+
|
25
54
|
name: str
|
26
55
|
flags: CodecFlags
|
27
56
|
description: str
|
28
57
|
|
29
58
|
|
30
59
|
def parse_codec_flags(flags: str) -> CodecFlags:
|
60
|
+
"""
|
61
|
+
Parse the FFmpeg codec flags string into a CodecFlags enum.
|
62
|
+
|
63
|
+
This function interprets the character flags from FFmpeg's codec listing
|
64
|
+
and converts them to the corresponding CodecFlags enum values.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
flags: A string of flag characters from FFmpeg codec information
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
A CodecFlags bitmap with the appropriate flags set
|
71
|
+
"""
|
31
72
|
flags_enum = CodecFlags(0)
|
32
73
|
if flags[0] == "V":
|
33
74
|
flags_enum |= CodecFlags.video
|
@@ -49,6 +90,18 @@ def parse_codec_flags(flags: str) -> CodecFlags:
|
|
49
90
|
|
50
91
|
|
51
92
|
def get_codecs() -> tuple[Codec, ...]:
|
93
|
+
"""
|
94
|
+
Query the system for all available FFmpeg codecs.
|
95
|
+
|
96
|
+
This function calls `ffmpeg -codecs` and parses the output to create
|
97
|
+
a tuple of Codec objects representing all codecs available on the system.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
A tuple of Codec objects with information about each codec
|
101
|
+
|
102
|
+
Raises:
|
103
|
+
FFMpegExecuteError: If the ffmpeg command fails
|
104
|
+
"""
|
52
105
|
args = ["ffmpeg", "-hide_banner", "-codecs"]
|
53
106
|
logger.info("Running ffmpeg command: %s", command_line(args))
|
54
107
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
@@ -79,17 +132,36 @@ def get_codecs() -> tuple[Codec, ...]:
|
|
79
132
|
|
80
133
|
|
81
134
|
class CoderFlags(Flag):
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
135
|
+
"""
|
136
|
+
Flag enumeration representing the capabilities of a coder (encoder/decoder).
|
137
|
+
|
138
|
+
These flags correspond to the character flags in FFmpeg's encoder/decoder
|
139
|
+
information output and indicate what features a coder supports.
|
140
|
+
"""
|
141
|
+
|
142
|
+
video = auto() # Coder supports video processing
|
143
|
+
audio = auto() # Coder supports audio processing
|
144
|
+
subtitle = auto() # Coder supports subtitle processing
|
145
|
+
frame_level_multithreading = auto() # Coder supports frame-level multithreading
|
146
|
+
slice_level_multithreading = auto() # Coder supports slice-level multithreading
|
147
|
+
experimental = auto() # Coder is considered experimental
|
148
|
+
draw_horiz_band = auto() # Coder supports drawing horizontal bands
|
149
|
+
direct_rendering_method_1 = auto() # Coder supports direct rendering method 1
|
90
150
|
|
91
151
|
|
92
152
|
def parse_coder_flags(flags: str) -> CoderFlags:
|
153
|
+
"""
|
154
|
+
Parse the FFmpeg coder flags string into a CoderFlags enum.
|
155
|
+
|
156
|
+
This function interprets the character flags from FFmpeg's encoder/decoder
|
157
|
+
listing and converts them to the corresponding CoderFlags enum values.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
flags: A string of flag characters from FFmpeg encoder/decoder information
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
A CoderFlags bitmap with the appropriate flags set
|
164
|
+
"""
|
93
165
|
flags_enum = CoderFlags(0)
|
94
166
|
if flags[0] == "V":
|
95
167
|
flags_enum |= CoderFlags.video
|
@@ -112,12 +184,37 @@ def parse_coder_flags(flags: str) -> CoderFlags:
|
|
112
184
|
|
113
185
|
@dataclass
|
114
186
|
class Coder:
|
187
|
+
"""
|
188
|
+
Represents an FFmpeg encoder or decoder with its capabilities and description.
|
189
|
+
|
190
|
+
This dataclass stores information about a coder identified by the
|
191
|
+
`ffmpeg -encoders` or `ffmpeg -decoders` command, including its name,
|
192
|
+
supported features (as flags), and its textual description.
|
193
|
+
|
194
|
+
Attributes:
|
195
|
+
name: The coder identifier used in FFmpeg commands
|
196
|
+
flags: Bitmap of capabilities supported by the coder
|
197
|
+
description: Human-readable description of the coder
|
198
|
+
"""
|
199
|
+
|
115
200
|
name: str
|
116
201
|
flags: CoderFlags
|
117
202
|
description: str
|
118
203
|
|
119
204
|
|
120
205
|
def get_coders(codes: str) -> tuple[Coder, ...]:
|
206
|
+
"""
|
207
|
+
Parse the output of an FFmpeg encoder/decoder listing into Coder objects.
|
208
|
+
|
209
|
+
This function parses the text output from `ffmpeg -encoders` or
|
210
|
+
`ffmpeg -decoders` commands and constructs Coder objects for each entry.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
codes: String output from the FFmpeg encoders/decoders command
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
A tuple of Coder objects representing the available encoders/decoders
|
217
|
+
"""
|
121
218
|
codecs_lines = codes.strip().split("\n")
|
122
219
|
# Skip header lines until we find the separator
|
123
220
|
for i, line in enumerate(codecs_lines):
|
@@ -136,6 +233,18 @@ def get_coders(codes: str) -> tuple[Coder, ...]:
|
|
136
233
|
|
137
234
|
|
138
235
|
def get_decoders() -> tuple[Coder, ...]:
|
236
|
+
"""
|
237
|
+
Query the system for all available FFmpeg decoders.
|
238
|
+
|
239
|
+
This function calls `ffmpeg -decoders` and parses the output to create
|
240
|
+
a tuple of Coder objects representing all decoders available on the system.
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
A tuple of Coder objects with information about each decoder
|
244
|
+
|
245
|
+
Raises:
|
246
|
+
FFMpegExecuteError: If the ffmpeg command fails
|
247
|
+
"""
|
139
248
|
args = ["ffmpeg", "-hide_banner", "-decoders"]
|
140
249
|
logger.info("Running ffmpeg command: %s", command_line(args))
|
141
250
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
@@ -152,6 +261,18 @@ def get_decoders() -> tuple[Coder, ...]:
|
|
152
261
|
|
153
262
|
|
154
263
|
def get_encoders() -> tuple[Coder, ...]:
|
264
|
+
"""
|
265
|
+
Query the system for all available FFmpeg encoders.
|
266
|
+
|
267
|
+
This function calls `ffmpeg -encoders` and parses the output to create
|
268
|
+
a tuple of Coder objects representing all encoders available on the system.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
A tuple of Coder objects with information about each encoder
|
272
|
+
|
273
|
+
Raises:
|
274
|
+
FFMpegExecuteError: If the ffmpeg command fails
|
275
|
+
"""
|
155
276
|
args = ["ffmpeg", "-hide_banner", "-encoders"]
|
156
277
|
logger.info("Running ffmpeg command: %s", command_line(args))
|
157
278
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
typed_ffmpeg/probe.py
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
"""
|
2
|
+
Module for analyzing media files with ffprobe.
|
3
|
+
|
4
|
+
This module provides functionality to extract detailed metadata from media files
|
5
|
+
using FFmpeg's ffprobe utility. It returns a structured representation of the
|
6
|
+
file's format, streams, and other relevant information.
|
7
|
+
"""
|
8
|
+
|
1
9
|
import json
|
2
10
|
import logging
|
3
11
|
import subprocess
|
@@ -18,16 +26,33 @@ def probe(
|
|
18
26
|
**kwargs: Any,
|
19
27
|
) -> dict[str, Any]:
|
20
28
|
"""
|
21
|
-
|
29
|
+
Analyze a media file using ffprobe and return its metadata as a dictionary.
|
30
|
+
|
31
|
+
This function executes ffprobe to extract detailed information about a media file,
|
32
|
+
including format metadata (container format, duration, bitrate) and stream information
|
33
|
+
(codecs, resolution, sample rate, etc). The result is returned as a Python dictionary
|
34
|
+
parsed from ffprobe's JSON output.
|
22
35
|
|
23
36
|
Args:
|
24
|
-
filename:
|
25
|
-
cmd:
|
26
|
-
timeout:
|
27
|
-
**kwargs:
|
37
|
+
filename: Path to the media file to analyze
|
38
|
+
cmd: Path or name of the ffprobe executable (default: "ffprobe")
|
39
|
+
timeout: Maximum time in seconds to wait for ffprobe to complete (default: None, wait indefinitely)
|
40
|
+
**kwargs: Additional arguments to pass to ffprobe as command line parameters
|
41
|
+
(e.g., loglevel="quiet", skip_frame="nokey")
|
28
42
|
|
29
43
|
Returns:
|
30
|
-
|
44
|
+
A dictionary containing the parsed JSON output from ffprobe with format and stream information
|
45
|
+
|
46
|
+
Raises:
|
47
|
+
FFMpegExecuteError: If ffprobe returns a non-zero exit code
|
48
|
+
subprocess.TimeoutExpired: If the timeout is reached before ffprobe completes
|
49
|
+
|
50
|
+
Example:
|
51
|
+
```python
|
52
|
+
info = probe("video.mp4")
|
53
|
+
print(f"Duration: {float(info['format']['duration']):.2f} seconds")
|
54
|
+
print(f"Streams: {len(info['streams'])}")
|
55
|
+
```
|
31
56
|
"""
|
32
57
|
args = [cmd, "-show_format", "-show_streams", "-of", "json"]
|
33
58
|
args += convert_kwargs_to_cmd_line_args(kwargs)
|
typed_ffmpeg/schema.py
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
"""
|
2
|
-
Defines the basic schema for the
|
2
|
+
Defines the basic schema for the FFmpeg command line options.
|
3
|
+
|
4
|
+
This module contains base classes used throughout the typed-ffmpeg library to handle
|
5
|
+
default values, automatic parameter derivation, and stream type definitions.
|
6
|
+
These components form the foundation of the type annotation system that
|
7
|
+
enables static type checking in the FFmpeg filter graph.
|
3
8
|
"""
|
4
9
|
|
5
10
|
from .common.schema import StreamType
|
@@ -7,8 +12,18 @@ from .common.schema import StreamType
|
|
7
12
|
|
8
13
|
class Default(str):
|
9
14
|
"""
|
10
|
-
|
11
|
-
|
15
|
+
Represents a default value for an FFmpeg option.
|
16
|
+
|
17
|
+
This class is used for annotation purposes only and indicates that a parameter
|
18
|
+
should use its default value. When a parameter is marked with Default, it
|
19
|
+
will not be explicitly passed to the FFmpeg command line, letting FFmpeg use
|
20
|
+
its built-in default value instead.
|
21
|
+
|
22
|
+
Example:
|
23
|
+
```python
|
24
|
+
# This will use FFmpeg's default crf value
|
25
|
+
video.output("output.mp4", crf=Default("23"))
|
26
|
+
```
|
12
27
|
"""
|
13
28
|
|
14
29
|
...
|
@@ -16,8 +31,20 @@ class Default(str):
|
|
16
31
|
|
17
32
|
class Auto(Default):
|
18
33
|
"""
|
19
|
-
|
20
|
-
|
34
|
+
Represents an automatically derived value for an FFmpeg option.
|
35
|
+
|
36
|
+
This is a special case of Default that indicates the value should be
|
37
|
+
calculated automatically based on the context. For example, the number
|
38
|
+
of inputs to a filter might be derived from the number of streams passed
|
39
|
+
to that filter.
|
40
|
+
|
41
|
+
Auto contains an expression string that defines how the value should be computed.
|
42
|
+
|
43
|
+
Example:
|
44
|
+
```python
|
45
|
+
# The number of inputs is automatically derived from the length of streams
|
46
|
+
hstack(*streams, inputs=Auto("len(streams)"))
|
47
|
+
```
|
21
48
|
"""
|
22
49
|
|
23
50
|
|
@@ -1,3 +1,16 @@
|
|
1
|
+
"""
|
2
|
+
Audio channel layout definitions for FFmpeg.
|
3
|
+
|
4
|
+
This module defines a mapping between FFmpeg's channel layout names and
|
5
|
+
the corresponding number of audio channels. This information is used for
|
6
|
+
audio stream processing, filtering, and conversion operations.
|
7
|
+
|
8
|
+
Channel layouts in FFmpeg represent different speaker configurations,
|
9
|
+
such as mono (1 channel), stereo (2 channels), 5.1 (6 channels), etc.
|
10
|
+
The names used in this dictionary match the standard names used by FFmpeg's
|
11
|
+
channel layout API.
|
12
|
+
"""
|
13
|
+
|
1
14
|
CHANNEL_LAYOUT = {
|
2
15
|
"mono": 1,
|
3
16
|
"stereo": 2,
|