typed-ffmpeg-compatible 2.6.2__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 +26 -1
- 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/sources.py +2825 -0
- 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.2.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.2.dist-info/RECORD +0 -46
- {typed_ffmpeg_compatible-2.6.2.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/LICENSE +0 -0
- {typed_ffmpeg_compatible-2.6.2.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/WHEEL +0 -0
- {typed_ffmpeg_compatible-2.6.2.dist-info → typed_ffmpeg_compatible-2.6.4.dist-info}/entry_points.txt +0 -0
typed_ffmpeg/common/serialize.py
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Serialization utilities for FFmpeg filter graphs and components.
|
3
|
+
|
4
|
+
This module provides functions for serializing and deserializing FFmpeg filter
|
5
|
+
graph components to and from JSON. It handles dataclasses, enums, and other
|
6
|
+
custom types used in the typed-ffmpeg library, enabling filter graphs to be
|
7
|
+
saved to disk and loaded back.
|
8
|
+
"""
|
9
|
+
|
1
10
|
from __future__ import annotations
|
2
11
|
|
3
12
|
import importlib
|
@@ -8,17 +17,36 @@ from functools import partial
|
|
8
17
|
from pathlib import Path
|
9
18
|
from typing import Any
|
10
19
|
|
20
|
+
from ..utils.forzendict import FrozenDict
|
21
|
+
|
11
22
|
|
12
23
|
def load_class(path: str, strict: bool = True) -> Any:
|
13
24
|
"""
|
14
|
-
Load a class from a string path
|
25
|
+
Load a class from a string path.
|
26
|
+
|
27
|
+
This function dynamically imports a class based on its fully qualified
|
28
|
+
path (e.g., 'ffmpeg.dag.nodes.FilterNode'). It's used during deserialization
|
29
|
+
to reconstruct objects from their class names.
|
15
30
|
|
16
31
|
Args:
|
17
|
-
path: The path to the class.
|
18
|
-
strict: If True,
|
32
|
+
path: The fully qualified path to the class (module.submodule.ClassName)
|
33
|
+
strict: If True, only allow loading classes from the ffmpeg package
|
34
|
+
as a security measure
|
19
35
|
|
20
36
|
Returns:
|
21
|
-
The class
|
37
|
+
The class object that can be instantiated
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
AssertionError: If strict is True and the path doesn't start with 'ffmpeg.'
|
41
|
+
ImportError: If the module or class cannot be found
|
42
|
+
|
43
|
+
Example:
|
44
|
+
```python
|
45
|
+
# Load the FilterNode class
|
46
|
+
FilterNode = load_class('ffmpeg.dag.nodes.FilterNode')
|
47
|
+
# Create an instance
|
48
|
+
node = FilterNode(name='scale', ...)
|
49
|
+
```
|
22
50
|
"""
|
23
51
|
if strict:
|
24
52
|
assert path.startswith("ffmpeg."), (
|
@@ -32,32 +60,62 @@ def load_class(path: str, strict: bool = True) -> Any:
|
|
32
60
|
|
33
61
|
def frozen(v: Any) -> Any:
|
34
62
|
"""
|
35
|
-
Convert
|
63
|
+
Convert mutable data structures to immutable (frozen) equivalents.
|
64
|
+
|
65
|
+
This function recursively converts lists to tuples and dictionaries to
|
66
|
+
FrozenDict instances, ensuring that the resulting data structure is
|
67
|
+
completely immutable. This is important for dataclasses that are marked
|
68
|
+
as frozen, as they can only contain immutable data.
|
36
69
|
|
37
70
|
Args:
|
38
|
-
v: The
|
71
|
+
v: The value to convert, which may be a list, dict, or any other type
|
39
72
|
|
40
73
|
Returns:
|
41
|
-
|
74
|
+
An immutable version of the input value
|
75
|
+
|
76
|
+
Example:
|
77
|
+
```python
|
78
|
+
# Convert a nested structure to immutable form
|
79
|
+
frozen_data = frozen(
|
80
|
+
{"options": ["option1", "option2"], "settings": {"key": "value"}}
|
81
|
+
)
|
82
|
+
# Result: FrozenDict with tuple instead of list and nested FrozenDict
|
83
|
+
```
|
42
84
|
"""
|
43
85
|
if isinstance(v, list):
|
44
86
|
return tuple(frozen(i) for i in v)
|
45
87
|
|
46
88
|
if isinstance(v, dict):
|
47
|
-
return
|
89
|
+
return FrozenDict({k: frozen(v) for k, v in v.items()})
|
48
90
|
|
49
91
|
return v
|
50
92
|
|
51
93
|
|
52
94
|
def object_hook(obj: Any, strict: bool = True) -> Any:
|
53
95
|
"""
|
54
|
-
|
96
|
+
Custom JSON object hook for deserializing FFmpeg objects.
|
97
|
+
|
98
|
+
This function is used by the JSON decoder to convert dictionaries into
|
99
|
+
appropriate Python objects during deserialization. It looks for a special
|
100
|
+
'__class__' key that indicates the type of object to create.
|
55
101
|
|
56
102
|
Args:
|
57
|
-
obj:
|
103
|
+
obj: A dictionary from the JSON parser
|
104
|
+
strict: If True, only allow loading classes from the ffmpeg package
|
58
105
|
|
59
106
|
Returns:
|
60
|
-
|
107
|
+
Either the original dictionary or an instance of the specified class
|
108
|
+
|
109
|
+
Example:
|
110
|
+
```python
|
111
|
+
# A JSON object with class information
|
112
|
+
json_obj = {
|
113
|
+
"__class__": "ffmpeg.dag.nodes.FilterNode",
|
114
|
+
"name": "scale",
|
115
|
+
"kwargs": {"width": 1280, "height": 720},
|
116
|
+
}
|
117
|
+
# Will be converted to a FilterNode instance
|
118
|
+
```
|
61
119
|
"""
|
62
120
|
if isinstance(obj, dict):
|
63
121
|
if obj.get("__class__"):
|
@@ -74,13 +132,26 @@ def object_hook(obj: Any, strict: bool = True) -> Any:
|
|
74
132
|
|
75
133
|
def loads(raw: str, strict: bool = True) -> Any:
|
76
134
|
"""
|
77
|
-
Deserialize
|
135
|
+
Deserialize a JSON string into Python objects with proper class types.
|
136
|
+
|
137
|
+
This function parses a JSON string and reconstructs the original Python
|
138
|
+
objects, including dataclasses and enums, based on class information
|
139
|
+
embedded in the JSON.
|
78
140
|
|
79
141
|
Args:
|
80
|
-
raw: The JSON string to deserialize
|
142
|
+
raw: The JSON string to deserialize
|
143
|
+
strict: If True, only allow loading classes from the ffmpeg package
|
81
144
|
|
82
145
|
Returns:
|
83
|
-
The deserialized
|
146
|
+
The deserialized Python object with proper types
|
147
|
+
|
148
|
+
Example:
|
149
|
+
```python
|
150
|
+
# Deserialize a filter graph from JSON
|
151
|
+
json_str = '{"__class__": "ffmpeg.dag.nodes.FilterNode", "name": "scale", ...}'
|
152
|
+
filter_node = loads(json_str)
|
153
|
+
# filter_node is now a FilterNode instance
|
154
|
+
```
|
84
155
|
"""
|
85
156
|
object_hook_strict = partial(object_hook, strict=strict)
|
86
157
|
|
@@ -89,16 +160,29 @@ def loads(raw: str, strict: bool = True) -> Any:
|
|
89
160
|
|
90
161
|
def to_dict_with_class_info(instance: Any) -> Any:
|
91
162
|
"""
|
92
|
-
Convert
|
163
|
+
Convert Python objects to dictionaries with embedded class information.
|
164
|
+
|
165
|
+
This function recursively converts Python objects to dictionaries, lists,
|
166
|
+
and primitive types suitable for JSON serialization. For dataclasses and
|
167
|
+
enums, it adds a '__class__' key with the fully qualified class name,
|
168
|
+
allowing them to be reconstructed during deserialization.
|
93
169
|
|
94
170
|
Args:
|
95
|
-
instance: The
|
171
|
+
instance: The Python object to convert
|
96
172
|
|
97
173
|
Returns:
|
98
|
-
|
174
|
+
A JSON-serializable representation with embedded class information
|
175
|
+
|
176
|
+
Example:
|
177
|
+
```python
|
178
|
+
# Convert a FilterNode to a serializable dict
|
179
|
+
filter_node = FilterNode(name='scale', ...)
|
180
|
+
serializable = to_dict_with_class_info(filter_node)
|
181
|
+
# serializable now contains class information and all attributes
|
182
|
+
```
|
99
183
|
"""
|
100
184
|
|
101
|
-
if isinstance(instance, dict):
|
185
|
+
if isinstance(instance, dict | FrozenDict):
|
102
186
|
return {k: to_dict_with_class_info(v) for k, v in instance.items()}
|
103
187
|
elif isinstance(instance, list):
|
104
188
|
return [to_dict_with_class_info(v) for v in instance]
|
@@ -125,13 +209,26 @@ def to_dict_with_class_info(instance: Any) -> Any:
|
|
125
209
|
# Serialization
|
126
210
|
def dumps(instance: Any) -> str:
|
127
211
|
"""
|
128
|
-
Serialize
|
212
|
+
Serialize a Python object to a JSON string with class information.
|
213
|
+
|
214
|
+
This function converts a Python object (including dataclasses, enums,
|
215
|
+
and other custom types) to a JSON string that includes class information,
|
216
|
+
allowing it to be deserialized back into the original object types.
|
129
217
|
|
130
218
|
Args:
|
131
|
-
instance: The
|
219
|
+
instance: The Python object to serialize
|
132
220
|
|
133
221
|
Returns:
|
134
|
-
|
222
|
+
A JSON string representation of the object with class information
|
223
|
+
|
224
|
+
Example:
|
225
|
+
```python
|
226
|
+
# Serialize a filter graph to JSON
|
227
|
+
filter_node = FilterNode(name='scale', ...)
|
228
|
+
json_str = dumps(filter_node)
|
229
|
+
# json_str can be saved to a file and later deserialized
|
230
|
+
# with loads() to reconstruct the original object
|
231
|
+
```
|
135
232
|
"""
|
136
233
|
obj = to_dict_with_class_info(instance)
|
137
234
|
return json.dumps(obj, indent=2)
|
typed_ffmpeg/dag/__init__.py
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
"""
|
2
|
+
Directed Acyclic Graph (DAG) implementation for FFmpeg filter chains.
|
3
|
+
|
4
|
+
This package provides the core components for representing FFmpeg filter chains
|
5
|
+
as directed acyclic graphs. It includes classes for different types of nodes
|
6
|
+
(inputs, filters, outputs) and streams, as well as utilities for validating,
|
7
|
+
compiling, and manipulating these graphs.
|
8
|
+
|
9
|
+
The DAG structure enables type-safe construction and validation of complex
|
10
|
+
FFmpeg filter chains, ensuring that the resulting FFmpeg commands are valid
|
11
|
+
and correctly structured.
|
12
|
+
"""
|
13
|
+
|
1
14
|
from .nodes import (
|
2
15
|
FilterableStream,
|
3
16
|
FilterNode,
|
typed_ffmpeg/dag/compile.py
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Compiles FFmpeg filter graphs into command-line arguments.
|
3
|
+
|
4
|
+
This module provides functionality to convert the internal DAG (Directed Acyclic Graph)
|
5
|
+
representation of an FFmpeg filter chain into the actual command-line arguments
|
6
|
+
that would be passed to FFmpeg. It traverses the graph in the correct order,
|
7
|
+
handling global options, inputs, complex filtergraphs, and outputs.
|
8
|
+
"""
|
9
|
+
|
1
10
|
from __future__ import annotations
|
2
11
|
|
3
12
|
from .context import DAGContext
|
@@ -8,14 +17,40 @@ from .validate import validate
|
|
8
17
|
|
9
18
|
def compile(stream: Stream, auto_fix: bool = True) -> list[str]:
|
10
19
|
"""
|
11
|
-
Compile
|
20
|
+
Compile a stream into a list of FFmpeg command-line arguments.
|
21
|
+
|
22
|
+
This function takes a Stream object representing an FFmpeg filter graph
|
23
|
+
and converts it into a list of command-line arguments that can be passed
|
24
|
+
to FFmpeg. It processes the graph in the correct order:
|
25
|
+
1. Global nodes (general FFmpeg options)
|
26
|
+
2. Input nodes (input files and their options)
|
27
|
+
3. Filter nodes (combined into a -filter_complex argument)
|
28
|
+
4. Output nodes (output files and their options)
|
29
|
+
|
30
|
+
The function validates the graph before compilation to ensure it's properly
|
31
|
+
formed. If auto_fix is enabled, it will attempt to fix common issues.
|
12
32
|
|
13
33
|
Args:
|
14
|
-
stream: The
|
15
|
-
auto_fix: Whether to automatically fix the stream
|
34
|
+
stream: The Stream object to compile into arguments
|
35
|
+
auto_fix: Whether to automatically fix issues in the stream
|
36
|
+
(e.g., reconnecting disconnected nodes)
|
16
37
|
|
17
38
|
Returns:
|
18
|
-
|
39
|
+
A list of strings representing FFmpeg command-line arguments
|
40
|
+
|
41
|
+
Example:
|
42
|
+
```python
|
43
|
+
# Create a simple video scaling filter graph
|
44
|
+
input_stream = ffmpeg.input("input.mp4")
|
45
|
+
scaled = input_stream.filter("scale", 1280, 720)
|
46
|
+
output_stream = scaled.output("output.mp4")
|
47
|
+
|
48
|
+
# Compile to FFmpeg arguments
|
49
|
+
args = ffmpeg.dag.compile(output_stream)
|
50
|
+
print(
|
51
|
+
args
|
52
|
+
) # ['ffmpeg', '-i', 'input.mp4', '-filter_complex', '...', 'output.mp4']
|
53
|
+
```
|
19
54
|
"""
|
20
55
|
|
21
56
|
stream = validate(stream, auto_fix=auto_fix)
|
typed_ffmpeg/dag/context.py
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Context management for FFmpeg filter graph traversal and manipulation.
|
3
|
+
|
4
|
+
This module provides the DAGContext class, which represents the context
|
5
|
+
of a Directed Acyclic Graph (DAG) of FFmpeg filter nodes. It provides methods
|
6
|
+
for traversing, manipulating, and rendering the graph structure, and is used
|
7
|
+
during graph validation and command-line compilation.
|
8
|
+
"""
|
9
|
+
|
1
10
|
from __future__ import annotations
|
2
11
|
|
3
12
|
from collections import defaultdict
|
@@ -14,13 +23,17 @@ T = TypeVar("T")
|
|
14
23
|
|
15
24
|
def _remove_duplicates(seq: list[T]) -> list[T]:
|
16
25
|
"""
|
17
|
-
Remove duplicates from a list while preserving order.
|
26
|
+
Remove duplicates from a list while preserving the original order.
|
27
|
+
|
28
|
+
This helper function processes a list and removes any duplicate elements
|
29
|
+
while maintaining the relative ordering of elements. The first occurrence
|
30
|
+
of each element is kept, subsequent duplicates are removed.
|
18
31
|
|
19
32
|
Args:
|
20
|
-
seq: The list to remove duplicates from
|
33
|
+
seq: The list to remove duplicates from
|
21
34
|
|
22
35
|
Returns:
|
23
|
-
|
36
|
+
A new list with duplicates removed, preserving the original order
|
24
37
|
"""
|
25
38
|
seen = set()
|
26
39
|
output = []
|
@@ -35,13 +48,19 @@ def _remove_duplicates(seq: list[T]) -> list[T]:
|
|
35
48
|
|
36
49
|
def _collect(node: Node) -> tuple[list[Node], list[Stream]]:
|
37
50
|
"""
|
38
|
-
|
51
|
+
Recursively collect all nodes and streams in the upstream path of a given node.
|
52
|
+
|
53
|
+
This function traverses the graph starting from the given node and collects
|
54
|
+
all nodes and streams that are upstream (input sources) to the node. The
|
55
|
+
traversal is performed recursively to ensure all dependencies are captured.
|
39
56
|
|
40
57
|
Args:
|
41
|
-
node: The node to collect from
|
58
|
+
node: The starting node to collect dependencies from
|
42
59
|
|
43
60
|
Returns:
|
44
|
-
A tuple
|
61
|
+
A tuple containing two lists:
|
62
|
+
- A list of all nodes in the upstream path (including the starting node)
|
63
|
+
- A list of all streams connecting these nodes
|
45
64
|
"""
|
46
65
|
nodes, streams = [node], [*node.inputs]
|
47
66
|
|
@@ -56,34 +75,53 @@ def _collect(node: Node) -> tuple[list[Node], list[Stream]]:
|
|
56
75
|
@dataclass(frozen=True, kw_only=True)
|
57
76
|
class DAGContext:
|
58
77
|
"""
|
59
|
-
|
78
|
+
Context class for working with a Directed Acyclic Graph (DAG) of FFmpeg filter nodes.
|
79
|
+
|
80
|
+
This immutable class provides methods and properties for analyzing, traversing,
|
81
|
+
and manipulating a filter graph. It maintains information about nodes and streams
|
82
|
+
in the graph, their relationships, and provides efficient lookups for graph operations.
|
83
|
+
|
84
|
+
The context is built from a "root" node (typically an output node) and captures all
|
85
|
+
upstream dependencies (input nodes, filter nodes, and connecting streams).
|
60
86
|
"""
|
61
87
|
|
62
88
|
node: Node
|
63
89
|
"""
|
64
90
|
The root node (the destination) of the DAG.
|
91
|
+
|
92
|
+
This is typically an output node where the graph traversal begins.
|
93
|
+
All nodes collected in the context are upstream from this node.
|
65
94
|
"""
|
66
95
|
|
67
96
|
nodes: tuple[Node, ...]
|
68
97
|
"""
|
69
|
-
All nodes in the graph.
|
98
|
+
All nodes in the graph as an immutable tuple.
|
99
|
+
|
100
|
+
This includes the root node and all upstream nodes (inputs, filters)
|
101
|
+
that contribute to the filter graph.
|
70
102
|
"""
|
71
103
|
|
72
104
|
streams: tuple[Stream, ...]
|
73
105
|
"""
|
74
|
-
All streams in the graph.
|
106
|
+
All streams in the graph as an immutable tuple.
|
107
|
+
|
108
|
+
These streams represent the connections between nodes in the filter graph.
|
75
109
|
"""
|
76
110
|
|
77
111
|
@classmethod
|
78
112
|
def build(cls, node: Node) -> DAGContext:
|
79
113
|
"""
|
80
|
-
|
114
|
+
Create a DAG context by traversing the graph from the specified root node.
|
115
|
+
|
116
|
+
This factory method builds a complete DAGContext by recursively collecting
|
117
|
+
all nodes and streams that are upstream from the specified node. It removes
|
118
|
+
duplicates to ensure each node and stream is represented only once in the context.
|
81
119
|
|
82
120
|
Args:
|
83
|
-
node: The root node
|
121
|
+
node: The root node to build the context from (typically an output node)
|
84
122
|
|
85
123
|
Returns:
|
86
|
-
A
|
124
|
+
A fully initialized DAGContext containing all nodes and streams in the graph
|
87
125
|
"""
|
88
126
|
nodes, streams = _collect(node)
|
89
127
|
|
@@ -96,14 +134,29 @@ class DAGContext:
|
|
96
134
|
@cached_property
|
97
135
|
def all_nodes(self) -> list[Node]:
|
98
136
|
"""
|
99
|
-
|
137
|
+
Get all nodes in the graph sorted by their position in the processing chain.
|
138
|
+
|
139
|
+
This property returns a list of all nodes in the graph, sorted by the number
|
140
|
+
of upstream nodes. This ensures that nodes earlier in the processing chain
|
141
|
+
(closer to inputs) come before nodes later in the chain (closer to outputs).
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
A sorted list of all nodes in the graph
|
100
145
|
"""
|
101
146
|
return sorted(self.nodes, key=lambda node: len(node.upstream_nodes))
|
102
147
|
|
103
148
|
@cached_property
|
104
149
|
def all_streams(self) -> list[Stream]:
|
105
150
|
"""
|
106
|
-
|
151
|
+
Get all streams in the graph sorted by their position in the processing chain.
|
152
|
+
|
153
|
+
This property returns a list of all streams in the graph, sorted first by the
|
154
|
+
number of upstream nodes of the source node, and then by the stream index.
|
155
|
+
This ensures a consistent and logical ordering of streams based on their
|
156
|
+
position in the processing pipeline.
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
A sorted list of all streams in the graph
|
107
160
|
"""
|
108
161
|
return sorted(
|
109
162
|
self.streams,
|
@@ -113,7 +166,15 @@ class DAGContext:
|
|
113
166
|
@cached_property
|
114
167
|
def outgoing_nodes(self) -> dict[Stream, list[tuple[Node, int]]]:
|
115
168
|
"""
|
116
|
-
|
169
|
+
Get a mapping of streams to the nodes they connect to.
|
170
|
+
|
171
|
+
This property builds a dictionary that maps each stream to a list of
|
172
|
+
tuples containing (node, input_index) pairs. Each tuple represents a node
|
173
|
+
that receives this stream as input, along with the index position where
|
174
|
+
the stream connects to that node.
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
A dictionary mapping streams to their destination nodes and connection indices
|
117
178
|
"""
|
118
179
|
outgoing_nodes: dict[Stream, list[tuple[Node, int]]] = defaultdict(list)
|
119
180
|
|
@@ -126,7 +187,14 @@ class DAGContext:
|
|
126
187
|
@cached_property
|
127
188
|
def outgoing_streams(self) -> dict[Node, list[Stream]]:
|
128
189
|
"""
|
129
|
-
|
190
|
+
Get a mapping of nodes to the streams they output.
|
191
|
+
|
192
|
+
This property builds a dictionary that maps each node to a list of streams
|
193
|
+
that originate from it. This is particularly useful for determining all the
|
194
|
+
outputs from a specific filter or input node.
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
A dictionary mapping nodes to their output streams
|
130
198
|
"""
|
131
199
|
|
132
200
|
outgoing_streams: dict[Node, list[Stream]] = defaultdict(list)
|
@@ -139,7 +207,18 @@ class DAGContext:
|
|
139
207
|
@cached_property
|
140
208
|
def node_labels(self) -> dict[Node, str]:
|
141
209
|
"""
|
142
|
-
|
210
|
+
Get a mapping of nodes to their string labels used in FFmpeg filter graphs.
|
211
|
+
|
212
|
+
This property assigns a unique label to each node in the graph, following
|
213
|
+
the FFmpeg filter graph labeling conventions:
|
214
|
+
- Input nodes are labeled with sequential numbers (0, 1, 2...)
|
215
|
+
- Filter nodes are labeled with 's' followed by a number (s0, s1, s2...)
|
216
|
+
- Output nodes are labeled as 'out'
|
217
|
+
|
218
|
+
These labels are used when generating the filter_complex argument for FFmpeg.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
A dictionary mapping nodes to their string labels
|
143
222
|
"""
|
144
223
|
|
145
224
|
input_node_index = 0
|
@@ -161,26 +240,38 @@ class DAGContext:
|
|
161
240
|
@override
|
162
241
|
def get_outgoing_nodes(self, stream: Stream) -> list[tuple[Node, int]]:
|
163
242
|
"""
|
164
|
-
Get all
|
243
|
+
Get all nodes that receive a specific stream as input.
|
244
|
+
|
245
|
+
This method returns a list of (node, index) tuples representing the nodes
|
246
|
+
that receive the given stream as input, along with the input index position
|
247
|
+
where the stream connects to each node.
|
165
248
|
|
166
249
|
Args:
|
167
|
-
stream: The stream to get the
|
250
|
+
stream: The stream to get the destination nodes for
|
168
251
|
|
169
252
|
Returns:
|
170
|
-
|
253
|
+
A list of (node, input_index) tuples for nodes that receive this stream
|
171
254
|
"""
|
172
255
|
return self.outgoing_nodes[stream]
|
173
256
|
|
174
257
|
@override
|
175
258
|
def get_node_label(self, node: Node) -> str:
|
176
259
|
"""
|
177
|
-
Get the label
|
260
|
+
Get the string label for a specific node in the filter graph.
|
261
|
+
|
262
|
+
This method returns the label assigned to the node, which is used in FFmpeg
|
263
|
+
filter graph notation. The label format depends on the node type:
|
264
|
+
- Input nodes: sequential numbers (0, 1, 2...)
|
265
|
+
- Filter nodes: 's' prefix followed by a number (s0, s1, s2...)
|
178
266
|
|
179
267
|
Args:
|
180
|
-
node: The node to get the label
|
268
|
+
node: The node to get the label for (must be an InputNode or FilterNode)
|
181
269
|
|
182
270
|
Returns:
|
183
|
-
The label
|
271
|
+
The string label for the node
|
272
|
+
|
273
|
+
Raises:
|
274
|
+
AssertionError: If the node is not an InputNode or FilterNode
|
184
275
|
"""
|
185
276
|
|
186
277
|
assert isinstance(node, (InputNode, FilterNode)), (
|
@@ -191,25 +282,42 @@ class DAGContext:
|
|
191
282
|
@override
|
192
283
|
def get_outgoing_streams(self, node: Node) -> list[Stream]:
|
193
284
|
"""
|
194
|
-
|
285
|
+
Get all streams that originate from a specific node.
|
286
|
+
|
287
|
+
This method returns all streams where the given node is the source.
|
288
|
+
It's particularly useful because nodes natively only track their inputs,
|
289
|
+
not their outputs, so this context method provides a way to look up
|
290
|
+
a node's outputs.
|
195
291
|
|
196
292
|
Args:
|
197
|
-
node: The node to get the
|
293
|
+
node: The node to get the output streams for
|
198
294
|
|
199
295
|
Returns:
|
200
|
-
|
296
|
+
A list of streams that originate from this node
|
201
297
|
"""
|
202
298
|
return self.outgoing_streams[node]
|
203
299
|
|
204
300
|
def render(self, obj: Any) -> Any:
|
205
301
|
"""
|
206
|
-
|
302
|
+
Recursively convert graph objects to a human-readable representation.
|
303
|
+
|
304
|
+
This method processes arbitrary objects, with special handling for graph
|
305
|
+
elements like nodes and streams. It converts them to a readable string format
|
306
|
+
that includes node labels. It recursively handles nested structures like
|
307
|
+
lists, tuples, and dictionaries.
|
308
|
+
|
309
|
+
This is primarily used for debugging, logging, and visualization purposes.
|
207
310
|
|
208
311
|
Args:
|
209
|
-
obj: The object to render
|
312
|
+
obj: The object to render, which may be a Node, Stream, or a container
|
313
|
+
with these objects nested inside
|
210
314
|
|
211
315
|
Returns:
|
212
|
-
The rendered object
|
316
|
+
The rendered representation of the object:
|
317
|
+
- For nodes: "Node(repr#label)"
|
318
|
+
- For streams: "Stream(node_repr#label#index)"
|
319
|
+
- For containers: recursively rendered contents
|
320
|
+
- For other objects: the original object unchanged
|
213
321
|
"""
|
214
322
|
|
215
323
|
if isinstance(obj, (list, tuple)):
|
typed_ffmpeg/dag/factory.py
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
"""
|
2
|
+
Factory functions for creating FFmpeg filter nodes.
|
3
|
+
|
4
|
+
This module provides factory functions that create filter nodes based on
|
5
|
+
FFmpeg filter definitions. These factories handle the evaluation of automatic
|
6
|
+
parameters and the conversion of input/output typing specifications.
|
7
|
+
"""
|
8
|
+
|
1
9
|
import re
|
2
10
|
from typing import Any
|
3
11
|
|
@@ -10,6 +18,25 @@ from .nodes import FilterableStream, FilterNode
|
|
10
18
|
def filter_node_factory(
|
11
19
|
ffmpeg_filter_def: FFMpegFilterDef, *inputs: FilterableStream, **kwargs: Any
|
12
20
|
) -> FilterNode:
|
21
|
+
"""
|
22
|
+
Create a FilterNode from an FFmpeg filter definition.
|
23
|
+
|
24
|
+
This function creates a FilterNode based on the provided FFmpeg filter definition.
|
25
|
+
It handles the evaluation of Auto parameters and the conversion of input/output
|
26
|
+
typing specifications from the filter definition.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
ffmpeg_filter_def: The FFmpeg filter definition to create a node from
|
30
|
+
*inputs: The input streams to connect to the filter
|
31
|
+
**kwargs: Filter-specific parameters as keyword arguments
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
A FilterNode configured according to the filter definition
|
35
|
+
|
36
|
+
Note:
|
37
|
+
This function is primarily used internally by the filter generation system
|
38
|
+
to create filter nodes from the FFmpeg filter definitions.
|
39
|
+
"""
|
13
40
|
for k, v in kwargs.items():
|
14
41
|
if isinstance(v, Auto):
|
15
42
|
kwargs[k] = eval(
|
@@ -11,6 +11,17 @@ if TYPE_CHECKING:
|
|
11
11
|
|
12
12
|
|
13
13
|
class GlobalArgs(ABC):
|
14
|
+
"""
|
15
|
+
Abstract base class for providing global FFmpeg command-line arguments.
|
16
|
+
|
17
|
+
This class defines an interface for setting global options that apply to the entire
|
18
|
+
FFmpeg command. These options control the general behavior of FFmpeg such as
|
19
|
+
logging levels, overwrite behavior, thread usage, and hardware acceleration.
|
20
|
+
|
21
|
+
Implementers must define the _global_node abstract method to apply these arguments
|
22
|
+
to actual FFmpeg command execution.
|
23
|
+
"""
|
24
|
+
|
14
25
|
@abstractmethod
|
15
26
|
def _global_node(self, *streams: OutputStream, **kwargs: Any) -> GlobalNode: ...
|
16
27
|
|