typed-ffmpeg-compatible 2.7.3__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.
Files changed (62) hide show
  1. typed_ffmpeg/__init__.py +2 -1
  2. typed_ffmpeg/common/cache/.gitignore +3 -0
  3. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/acrossover.json +6 -0
  4. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/afir.json +9 -0
  5. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/aiir.json +6 -0
  6. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/ainterleave.json +9 -0
  7. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/amerge.json +9 -0
  8. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/amix.json +9 -0
  9. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/amovie.json +6 -0
  10. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/anequalizer.json +6 -0
  11. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/aphasemeter.json +6 -0
  12. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/asegment.json +6 -0
  13. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/aselect.json +6 -0
  14. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/asplit.json +6 -0
  15. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/astreamselect.json +9 -0
  16. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/bm3d.json +6 -0
  17. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/channelsplit.json +6 -0
  18. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/concat.json +9 -0
  19. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/decimate.json +6 -0
  20. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/ebur128.json +6 -0
  21. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/extractplanes.json +6 -0
  22. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/fieldmatch.json +6 -0
  23. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/guided.json +6 -0
  24. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/headphone.json +6 -0
  25. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/hstack.json +9 -0
  26. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/interleave.json +9 -0
  27. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/join.json +9 -0
  28. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/libplacebo.json +9 -0
  29. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/limitdiff.json +6 -0
  30. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/mergeplanes.json +6 -0
  31. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/mix.json +9 -0
  32. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/movie.json +6 -0
  33. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/premultiply.json +6 -0
  34. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/segment.json +6 -0
  35. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/select.json +6 -0
  36. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/signature.json +9 -0
  37. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/split.json +6 -0
  38. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/streamselect.json +9 -0
  39. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/unpremultiply.json +6 -0
  40. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/vstack.json +9 -0
  41. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/xmedian.json +9 -0
  42. typed_ffmpeg/common/cache/FFMpegFilterManuallyDefined/xstack.json +9 -0
  43. typed_ffmpeg/common/cache/list/filters.json +90747 -0
  44. typed_ffmpeg/common/cache.py +66 -0
  45. typed_ffmpeg/common/schema.py +9 -5
  46. typed_ffmpeg/common/serialize.py +12 -8
  47. typed_ffmpeg/compile/__init__.py +0 -0
  48. typed_ffmpeg/compile/compile_cli.py +351 -0
  49. typed_ffmpeg/compile/compile_python.py +319 -0
  50. typed_ffmpeg/{dag → compile}/context.py +26 -45
  51. typed_ffmpeg/{dag → compile}/validate.py +2 -2
  52. typed_ffmpeg/dag/global_runnable/runnable.py +1 -1
  53. typed_ffmpeg/dag/nodes.py +1 -227
  54. typed_ffmpeg/dag/schema.py +2 -18
  55. typed_ffmpeg/utils/view.py +1 -1
  56. {typed_ffmpeg_compatible-2.7.3.dist-info → typed_ffmpeg_compatible-3.0.0.dist-info}/METADATA +1 -1
  57. typed_ffmpeg_compatible-3.0.0.dist-info/RECORD +93 -0
  58. typed_ffmpeg/dag/compile.py +0 -86
  59. typed_ffmpeg_compatible-2.7.3.dist-info/RECORD +0 -48
  60. {typed_ffmpeg_compatible-2.7.3.dist-info → typed_ffmpeg_compatible-3.0.0.dist-info}/LICENSE +0 -0
  61. {typed_ffmpeg_compatible-2.7.3.dist-info → typed_ffmpeg_compatible-3.0.0.dist-info}/WHEEL +0 -0
  62. {typed_ffmpeg_compatible-2.7.3.dist-info → typed_ffmpeg_compatible-3.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ from typing import TypeVar
3
+
4
+ from .serialize import dumps, loads
5
+
6
+ T = TypeVar("T")
7
+
8
+ cache_path = Path(__file__).parent / "cache"
9
+ cache_path.mkdir(exist_ok=True)
10
+
11
+
12
+ def load(cls: type[T], id: str) -> T:
13
+ """
14
+ Load an object from the cache
15
+
16
+ Args:
17
+ cls: The class of the object
18
+ id: The id of the object
19
+
20
+ Returns:
21
+ The loaded object
22
+ """
23
+ path = cache_path / f"{cls.__name__}/{id}.json"
24
+
25
+ with path.open() as ifile:
26
+ obj = loads(ifile.read())
27
+ return obj
28
+
29
+
30
+ def save(obj: T, id: str) -> None:
31
+ """
32
+ Save an object to the cache
33
+
34
+ Args:
35
+ obj: The object to save
36
+ id: The id of the object
37
+ """
38
+ schema_path = cache_path / f"{obj.__class__.__name__}"
39
+ schema_path.mkdir(exist_ok=True)
40
+
41
+ with (schema_path / f"{id}.json").open("w") as ofile:
42
+ ofile.write(dumps(obj))
43
+
44
+
45
+ def list_all(cls: type[T]) -> list[T]:
46
+ """
47
+ List all objects of a class in the cache
48
+
49
+ Args:
50
+ cls: The class of the objects
51
+
52
+ Returns:
53
+ A list of all objects of the class in the cache
54
+ """
55
+ path = cache_path / f"{cls.__name__}"
56
+
57
+ return [loads(i.read_text()) for i in path.glob("*.json")]
58
+
59
+
60
+ def clean(cls: type[T]) -> None:
61
+ """
62
+ Clean the cache for a class
63
+ """
64
+ path = cache_path / f"{cls.__name__}"
65
+ for i in path.glob("*.json"):
66
+ i.unlink()
@@ -33,6 +33,7 @@ class StreamType(str, Enum):
33
33
  """Represents a video stream containing visual frame data"""
34
34
 
35
35
 
36
+ @serializable
36
37
  class FFMpegFilterOptionType(str, Enum):
37
38
  """
38
39
  Enumeration of possible data types for FFmpeg filter options.
@@ -60,6 +61,7 @@ class FFMpegFilterOptionType(str, Enum):
60
61
  binary = "binary"
61
62
 
62
63
 
64
+ @serializable
63
65
  class FFMpegFilterType(str, Enum):
64
66
  """
65
67
  Enumeration of FFmpeg filter types based on input/output stream types.
@@ -98,7 +100,7 @@ class FFMpegFilterType(str, Enum):
98
100
 
99
101
 
100
102
  @dataclass(frozen=True, kw_only=True)
101
- class FFMpegFilterOptionChoice:
103
+ class FFMpegFilterOptionChoice(Serializable):
102
104
  """
103
105
  Represents a single choice for an FFmpeg filter option with enumerated values.
104
106
 
@@ -120,7 +122,7 @@ class FFMpegFilterOptionChoice:
120
122
 
121
123
 
122
124
  @dataclass(frozen=True, kw_only=True)
123
- class FFMpegFilterOption:
125
+ class FFMpegFilterOption(Serializable):
124
126
  """
125
127
  Represents a configurable option for an FFmpeg filter.
126
128
 
@@ -160,7 +162,7 @@ class FFMpegFilterOption:
160
162
 
161
163
 
162
164
  @dataclass(frozen=True, kw_only=True)
163
- class FFMpegIOType:
165
+ class FFMpegIOType(Serializable):
164
166
  """
165
167
  Defines the type information for a filter's input or output stream.
166
168
 
@@ -176,7 +178,7 @@ class FFMpegIOType:
176
178
 
177
179
 
178
180
  @dataclass(frozen=True, kw_only=True)
179
- class FFMpegFilterDef:
181
+ class FFMpegFilterDef(Serializable):
180
182
  """
181
183
  Defines the basic structure of an FFmpeg filter.
182
184
 
@@ -419,6 +421,7 @@ class FFMpegFilter(Serializable):
419
421
  raise ValueError(f"Unknown filter type for {self.name}")
420
422
 
421
423
 
424
+ @serializable
422
425
  class FFMpegOptionFlag(int, Enum):
423
426
  OPT_FUNC_ARG = 1 << 0
424
427
  """
@@ -494,6 +497,7 @@ class FFMpegOptionFlag(int, Enum):
494
497
  """
495
498
 
496
499
 
500
+ @serializable
497
501
  class FFMpegOptionType(str, Enum):
498
502
  """
499
503
  Enumeration of FFmpeg option data types.
@@ -529,7 +533,7 @@ class FFMpegOptionType(str, Enum):
529
533
 
530
534
 
531
535
  @dataclass(frozen=True, kw_only=True)
532
- class FFMpegOption:
536
+ class FFMpegOption(Serializable):
533
537
  """
534
538
  Represents a command-line option for FFmpeg.
535
539
 
@@ -12,7 +12,6 @@ from __future__ import annotations
12
12
  import json
13
13
  from dataclasses import fields, is_dataclass
14
14
  from enum import Enum
15
- from functools import partial
16
15
  from pathlib import Path
17
16
  from typing import Any
18
17
 
@@ -29,6 +28,15 @@ def serializable(
29
28
  ) -> type[Serializable] | type[Enum]:
30
29
  """
31
30
  Register a class with the serialization system.
31
+
32
+ This function is used by the `serializable` decorator to register classes
33
+ with the serialization system, enabling them to be serialized and deserialized.
34
+
35
+ Args:
36
+ cls: The class to register
37
+
38
+ Returns:
39
+ The class itself
32
40
  """
33
41
  assert cls.__name__ not in CLASS_REGISTRY, (
34
42
  f"Class {cls.__name__} already registered"
@@ -109,7 +117,7 @@ def frozen(v: Any) -> Any:
109
117
  return v
110
118
 
111
119
 
112
- def object_hook(obj: Any, strict: bool = True) -> Any:
120
+ def object_hook(obj: Any) -> Any:
113
121
  """
114
122
  Custom JSON object hook for deserializing FFmpeg objects.
115
123
 
@@ -119,7 +127,6 @@ def object_hook(obj: Any, strict: bool = True) -> Any:
119
127
 
120
128
  Args:
121
129
  obj: A dictionary from the JSON parser
122
- strict: If True, only allow loading classes from the ffmpeg package
123
130
 
124
131
  Returns:
125
132
  Either the original dictionary or an instance of the specified class
@@ -148,7 +155,7 @@ def object_hook(obj: Any, strict: bool = True) -> Any:
148
155
  return obj
149
156
 
150
157
 
151
- def loads(raw: str, strict: bool = True) -> Any:
158
+ def loads(raw: str) -> Any:
152
159
  """
153
160
  Deserialize a JSON string into Python objects with proper class types.
154
161
 
@@ -158,7 +165,6 @@ def loads(raw: str, strict: bool = True) -> Any:
158
165
 
159
166
  Args:
160
167
  raw: The JSON string to deserialize
161
- strict: If True, only allow loading classes from the ffmpeg package
162
168
 
163
169
  Returns:
164
170
  The deserialized Python object with proper types
@@ -171,9 +177,7 @@ def loads(raw: str, strict: bool = True) -> Any:
171
177
  # filter_node is now a FilterNode instance
172
178
  ```
173
179
  """
174
- object_hook_strict = partial(object_hook, strict=strict)
175
-
176
- return json.loads(raw, object_hook=object_hook_strict)
180
+ return json.loads(raw, object_hook=object_hook)
177
181
 
178
182
 
179
183
  def to_dict_with_class_info(instance: Any) -> Any:
File without changes
@@ -0,0 +1,351 @@
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
+
10
+ from __future__ import annotations
11
+
12
+ from ..dag.nodes import FilterableStream, FilterNode, GlobalNode, InputNode, OutputNode
13
+ from ..dag.schema import Node, Stream
14
+ from ..exceptions import FFMpegValueError
15
+ from ..schema import Default
16
+ from ..utils.escaping import escape
17
+ from ..utils.lazy_eval.schema import LazyValue
18
+ from .context import DAGContext
19
+ from .validate import validate
20
+
21
+
22
+ def compile(stream: Stream, auto_fix: bool = True) -> list[str]:
23
+ """
24
+ Compile a stream into a list of FFmpeg command-line arguments.
25
+
26
+ This function takes a Stream object representing an FFmpeg filter graph
27
+ and converts it into a list of command-line arguments that can be passed
28
+ to FFmpeg. It processes the graph in the correct order:
29
+ 1. Global nodes (general FFmpeg options)
30
+ 2. Input nodes (input files and their options)
31
+ 3. Filter nodes (combined into a -filter_complex argument)
32
+ 4. Output nodes (output files and their options)
33
+
34
+ The function validates the graph before compilation to ensure it's properly
35
+ formed. If auto_fix is enabled, it will attempt to fix common issues.
36
+
37
+ Args:
38
+ stream: The Stream object to compile into arguments
39
+ auto_fix: Whether to automatically fix issues in the stream
40
+ (e.g., reconnecting disconnected nodes)
41
+
42
+ Returns:
43
+ A list of strings representing FFmpeg command-line arguments
44
+
45
+ Example:
46
+ ```python
47
+ # Create a simple video scaling filter graph
48
+ input_stream = ffmpeg.input("input.mp4")
49
+ scaled = input_stream.filter("scale", 1280, 720)
50
+ output_stream = scaled.output("output.mp4")
51
+
52
+ # Compile to FFmpeg arguments
53
+ args = ffmpeg.dag.compile(output_stream)
54
+ print(
55
+ args
56
+ ) # ['ffmpeg', '-i', 'input.mp4', '-filter_complex', '...', 'output.mp4']
57
+ ```
58
+ """
59
+
60
+ stream = validate(stream, auto_fix=auto_fix)
61
+ node = stream.node
62
+ context = DAGContext.build(node)
63
+
64
+ # compile the global nodes
65
+ commands = []
66
+ global_nodes = [node for node in context.all_nodes if isinstance(node, GlobalNode)]
67
+ for node in global_nodes:
68
+ commands += get_args(node, context)
69
+
70
+ # compile the input nodes
71
+ input_nodes = [node for node in context.all_nodes if isinstance(node, InputNode)]
72
+ for node in input_nodes:
73
+ commands += get_args(node, context)
74
+
75
+ # compile the filter nodes
76
+ vf_commands = []
77
+ filter_nodes = [node for node in context.all_nodes if isinstance(node, FilterNode)]
78
+
79
+ for node in sorted(filter_nodes, key=lambda node: len(node.upstream_nodes)):
80
+ vf_commands += ["".join(get_args(node, context))]
81
+
82
+ if vf_commands:
83
+ commands += ["-filter_complex", ";".join(vf_commands)]
84
+
85
+ # compile the output nodes
86
+ output_nodes = [node for node in context.all_nodes if isinstance(node, OutputNode)]
87
+ for node in output_nodes:
88
+ commands += get_args(node, context)
89
+
90
+ return commands
91
+
92
+
93
+ def get_stream_label(stream: Stream, context: DAGContext | None = None) -> str:
94
+ """
95
+ Generate the FFmpeg label for this stream in filter graphs.
96
+
97
+ This method creates the label string used to identify this stream in
98
+ FFmpeg filter graphs. The format of the label depends on the stream's
99
+ source (input file or filter) and type (video or audio).
100
+
101
+ For input streams, labels follow FFmpeg's stream specifier syntax:
102
+ - Video streams: "0:v" (first input, video stream)
103
+ - Audio streams: "0:a" (first input, audio stream)
104
+ - AV streams: "0" (first input, all streams)
105
+
106
+ For filter outputs, labels use the filter's label:
107
+ - Single output filters: "filterlabel"
108
+ - Multi-output filters: "filterlabel#index"
109
+
110
+ Args:
111
+ context: Optional DAG context for resolving node labels.
112
+ If not provided, a new context will be built.
113
+
114
+ Returns:
115
+ A string label for this stream in FFmpeg filter syntax
116
+
117
+ Raises:
118
+ FFMpegValueError: If the stream has an unknown type or node type
119
+ """
120
+ from ..streams.audio import AudioStream
121
+ from ..streams.av import AVStream
122
+ from ..streams.video import VideoStream
123
+
124
+ if not context:
125
+ context = DAGContext.build(stream.node)
126
+
127
+ match stream.node:
128
+ case InputNode():
129
+ match stream:
130
+ case AVStream():
131
+ return f"{get_node_label(stream.node, context)}"
132
+ case VideoStream():
133
+ return f"{get_node_label(stream.node, context)}:v"
134
+ case AudioStream():
135
+ return f"{get_node_label(stream.node, context)}:a"
136
+ case _:
137
+ raise FFMpegValueError(
138
+ f"Unknown stream type: {stream.__class__.__name__}"
139
+ ) # pragma: no cover
140
+ case FilterNode():
141
+ if len(stream.node.output_typings) > 1:
142
+ return f"{get_node_label(stream.node, context)}#{stream.index}"
143
+ return f"{get_node_label(stream.node, context)}"
144
+ case _:
145
+ raise FFMpegValueError(
146
+ f"Unknown node type: {stream.node.__class__.__name__}"
147
+ ) # pragma: no cover
148
+
149
+
150
+ def get_args_filter_node(node: FilterNode, context: DAGContext) -> list[str]:
151
+ """
152
+ Generate the FFmpeg filter string for this filter node.
153
+
154
+ This method creates the filter string that will be used in the
155
+ filter_complex argument of the FFmpeg command. The format follows
156
+ FFmpeg's syntax where input labels are followed by the filter name
157
+ and parameters, and then output labels.
158
+
159
+ Args:
160
+ context: Optional DAG context for resolving stream labels.
161
+ If not provided, a new context will be built.
162
+
163
+ Returns:
164
+ A list of strings that, when joined, form the filter string
165
+ for this node in the filter_complex argument
166
+
167
+ Example:
168
+ For a scale filter with width=1280 and height=720, this might return:
169
+ ['[0:v]', 'scale=', 'width=1280:height=720', '[s0]']
170
+ """
171
+
172
+ incoming_labels = "".join(f"[{get_stream_label(k, context)}]" for k in node.inputs)
173
+ outputs = context.get_outgoing_streams(node)
174
+
175
+ outgoing_labels = ""
176
+ for output in sorted(outputs, key=lambda stream: stream.index or 0):
177
+ # NOTE: all outgoing streams must be filterable
178
+ assert isinstance(output, FilterableStream)
179
+ outgoing_labels += f"[{get_stream_label(output, context)}]"
180
+
181
+ commands = []
182
+ for key, value in node.kwargs.items():
183
+ assert not isinstance(value, LazyValue), (
184
+ f"LazyValue should have been evaluated: {key}={value}"
185
+ )
186
+
187
+ # Note: the -nooption syntax cannot be used for boolean AVOptions, use -option 0/-option 1.
188
+ if isinstance(value, bool):
189
+ value = str(int(value))
190
+
191
+ if not isinstance(value, Default):
192
+ commands += [f"{key}={escape(value)}"]
193
+
194
+ if commands:
195
+ return (
196
+ [incoming_labels]
197
+ + [f"{node.name}="]
198
+ + [escape(":".join(commands), "\\'[],;")]
199
+ + [outgoing_labels]
200
+ )
201
+ return [incoming_labels] + [f"{node.name}"] + [outgoing_labels]
202
+
203
+
204
+ def get_args_input_node(node: InputNode, context: DAGContext) -> list[str]:
205
+ """
206
+ Generate the FFmpeg command-line arguments for this input file.
207
+
208
+ This method creates the command-line arguments needed to specify
209
+ this input file to FFmpeg, including any input-specific options.
210
+
211
+ Args:
212
+ context: Optional DAG context (not used for input nodes)
213
+
214
+ Returns:
215
+ A list of strings representing FFmpeg command-line arguments
216
+
217
+ Example:
218
+ For an input file "input.mp4" with options like seeking to 10 seconds:
219
+ ['-ss', '10', '-i', 'input.mp4']
220
+ """
221
+ commands = []
222
+ for key, value in node.kwargs.items():
223
+ if isinstance(value, bool):
224
+ if value is True:
225
+ commands += [f"-{key}"]
226
+ elif value is False:
227
+ commands += [f"-no{key}"]
228
+ else:
229
+ commands += [f"-{key}", str(value)]
230
+ commands += ["-i", node.filename]
231
+ return commands
232
+
233
+
234
+ def get_args_output_node(node: OutputNode, context: DAGContext) -> list[str]:
235
+ """
236
+ Generate the FFmpeg command-line arguments for this output file.
237
+
238
+ This method creates the command-line arguments needed to specify
239
+ this output file to FFmpeg, including stream mapping and output-specific
240
+ options like codecs and formats.
241
+
242
+ Args:
243
+ context: Optional DAG context for resolving stream labels.
244
+ If not provided, a new context will be built.
245
+
246
+ Returns:
247
+ A list of strings representing FFmpeg command-line arguments
248
+
249
+ Example:
250
+ For an output file "output.mp4" with H.264 video codec:
251
+ ['-map', '[v0]', '-c:v', 'libx264', 'output.mp4']
252
+ """
253
+ # !handle mapping
254
+ commands = []
255
+
256
+ if context:
257
+ for input in node.inputs:
258
+ if isinstance(input.node, InputNode):
259
+ commands += ["-map", get_stream_label(input, context)]
260
+ else:
261
+ commands += ["-map", f"[{get_stream_label(input, context)}]"]
262
+
263
+ for key, value in node.kwargs.items():
264
+ if isinstance(value, bool):
265
+ if value is True:
266
+ commands += [f"-{key}"]
267
+ elif value is False:
268
+ commands += [f"-no{key}"]
269
+ else:
270
+ commands += [f"-{key}", str(value)]
271
+ commands += [node.filename]
272
+ return commands
273
+
274
+
275
+ def get_args_global_node(node: GlobalNode, context: DAGContext) -> list[str]:
276
+ """
277
+ Generate the FFmpeg command-line arguments for these global options.
278
+
279
+ This method creates the command-line arguments needed to specify
280
+ global options to FFmpeg, such as -y for overwrite or -loglevel for
281
+ controlling log output.
282
+
283
+ Args:
284
+ context: Optional DAG context (not used for global options)
285
+
286
+ Returns:
287
+ A list of strings representing FFmpeg command-line arguments
288
+
289
+ Example:
290
+ For global options like overwrite and quiet logging:
291
+ ['-y', '-loglevel', 'quiet']
292
+ """
293
+ commands = []
294
+ for key, value in node.kwargs.items():
295
+ if isinstance(value, bool):
296
+ if value is True:
297
+ commands += [f"-{key}"]
298
+ elif value is False:
299
+ commands += [f"-no{key}"]
300
+ else:
301
+ commands += [f"-{key}", str(value)]
302
+ return commands
303
+
304
+
305
+ def get_args(node: Node, context: DAGContext | None = None) -> list[str]:
306
+ """
307
+ Get the arguments for a node.
308
+ """
309
+
310
+ context = context or DAGContext.build(node)
311
+
312
+ match node:
313
+ case FilterNode():
314
+ return get_args_filter_node(node, context)
315
+ case InputNode():
316
+ return get_args_input_node(node, context)
317
+ case OutputNode():
318
+ return get_args_output_node(node, context)
319
+ case GlobalNode():
320
+ return get_args_global_node(node, context)
321
+ case _:
322
+ raise FFMpegValueError(f"Unknown node type: {node.__class__.__name__}")
323
+
324
+
325
+ def get_node_label(node: Node, context: DAGContext) -> str:
326
+ """
327
+ Get the string label for a specific node in the filter graph.
328
+
329
+ This method returns the label assigned to the node, which is used in FFmpeg
330
+ filter graph notation. The label format depends on the node type:
331
+ - Input nodes: sequential numbers (0, 1, 2...)
332
+ - Filter nodes: 's' prefix followed by a number (s0, s1, s2...)
333
+
334
+ Args:
335
+ node: The node to get the label for (must be an InputNode or FilterNode)
336
+
337
+ Returns:
338
+ The string label for the node
339
+
340
+ Raises:
341
+ AssertionError: If the node is not an InputNode or FilterNode
342
+ """
343
+
344
+ node_id = context.node_ids[node]
345
+ match node:
346
+ case InputNode():
347
+ return str(node_id)
348
+ case FilterNode():
349
+ return f"s{node_id}"
350
+ case _:
351
+ return "out"