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/dag/nodes.py
CHANGED
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any
|
|
9
9
|
from ..exceptions import FFMpegTypeError, FFMpegValueError
|
10
10
|
from ..schema import Default, StreamType
|
11
11
|
from ..utils.escaping import escape
|
12
|
+
from ..utils.forzendict import FrozenDict
|
12
13
|
from ..utils.lazy_eval.schema import LazyValue
|
13
14
|
from ..utils.typing import override
|
14
15
|
from .global_runnable.runnable import GlobalRunable
|
@@ -28,42 +29,61 @@ logger = logging.getLogger(__name__)
|
|
28
29
|
@dataclass(frozen=True, kw_only=True)
|
29
30
|
class FilterNode(Node):
|
30
31
|
"""
|
31
|
-
A
|
32
|
+
A node that represents an FFmpeg filter operation in the filter graph.
|
33
|
+
|
34
|
+
FilterNode represents a single filter operation in the FFmpeg filter graph,
|
35
|
+
such as scaling, cropping, or audio mixing. It connects input streams to
|
36
|
+
output streams and defines the parameters for the filter operation.
|
32
37
|
"""
|
33
38
|
|
34
39
|
name: str
|
35
40
|
"""
|
36
|
-
The name of the filter
|
41
|
+
The name of the filter as used in FFmpeg (e.g., 'scale', 'overlay', 'amix')
|
37
42
|
"""
|
38
43
|
|
39
44
|
inputs: tuple[FilterableStream, ...] = ()
|
40
45
|
"""
|
41
|
-
The input streams
|
46
|
+
The input streams that this filter processes
|
42
47
|
"""
|
43
48
|
|
44
49
|
input_typings: tuple[StreamType, ...] = ()
|
45
50
|
"""
|
46
|
-
The input
|
51
|
+
The expected types (audio/video) for each input stream
|
47
52
|
"""
|
48
53
|
|
49
54
|
output_typings: tuple[StreamType, ...] = ()
|
50
55
|
"""
|
51
|
-
The output
|
56
|
+
The types (audio/video) of each output stream this filter produces
|
52
57
|
"""
|
53
58
|
|
54
59
|
@override
|
55
60
|
def repr(self) -> str:
|
61
|
+
"""
|
62
|
+
Get a string representation of this filter node.
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
The name of the filter
|
66
|
+
"""
|
56
67
|
return self.name
|
57
68
|
|
58
69
|
def video(self, index: int) -> VideoStream:
|
59
70
|
"""
|
60
|
-
|
71
|
+
Get a video output stream from this filter node.
|
72
|
+
|
73
|
+
This method retrieves a specific video output stream from the filter,
|
74
|
+
identified by its index among all video outputs. For example, if a filter
|
75
|
+
produces multiple video outputs (like 'split'), this method allows
|
76
|
+
accessing each one individually.
|
61
77
|
|
62
78
|
Args:
|
63
|
-
index:
|
79
|
+
index: The index of the video stream to retrieve (0-based)
|
80
|
+
among all video outputs of this filter
|
64
81
|
|
65
82
|
Returns:
|
66
|
-
|
83
|
+
A VideoStream object representing the specified output
|
84
|
+
|
85
|
+
Raises:
|
86
|
+
FFMpegValueError: If the specified index is out of range
|
67
87
|
"""
|
68
88
|
from ..streams.video import VideoStream
|
69
89
|
|
@@ -78,13 +98,22 @@ class FilterNode(Node):
|
|
78
98
|
|
79
99
|
def audio(self, index: int) -> AudioStream:
|
80
100
|
"""
|
81
|
-
|
101
|
+
Get an audio output stream from this filter node.
|
102
|
+
|
103
|
+
This method retrieves a specific audio output stream from the filter,
|
104
|
+
identified by its index among all audio outputs. For example, if a filter
|
105
|
+
produces multiple audio outputs (like 'asplit'), this method allows
|
106
|
+
accessing each one individually.
|
82
107
|
|
83
108
|
Args:
|
84
|
-
index:
|
109
|
+
index: The index of the audio stream to retrieve (0-based)
|
110
|
+
among all audio outputs of this filter
|
85
111
|
|
86
112
|
Returns:
|
87
|
-
|
113
|
+
An AudioStream object representing the specified output
|
114
|
+
|
115
|
+
Raises:
|
116
|
+
FFMpegValueError: If the specified index is out of range
|
88
117
|
"""
|
89
118
|
from ..streams.audio import AudioStream
|
90
119
|
|
@@ -99,6 +128,18 @@ class FilterNode(Node):
|
|
99
128
|
return AudioStream(node=self, index=audio_outputs[index])
|
100
129
|
|
101
130
|
def __post_init__(self) -> None:
|
131
|
+
"""
|
132
|
+
Validate the filter node after initialization.
|
133
|
+
|
134
|
+
This method performs type checking to ensure that the input streams
|
135
|
+
match the expected types (audio/video) specified in input_typings.
|
136
|
+
It also verifies that the number of inputs matches the number of
|
137
|
+
input type specifications.
|
138
|
+
|
139
|
+
Raises:
|
140
|
+
FFMpegValueError: If the number of inputs doesn't match input_typings
|
141
|
+
FFMpegTypeError: If an input stream doesn't match its expected type
|
142
|
+
"""
|
102
143
|
from ..streams.audio import AudioStream
|
103
144
|
from ..streams.video import VideoStream
|
104
145
|
|
@@ -128,6 +169,26 @@ class FilterNode(Node):
|
|
128
169
|
|
129
170
|
@override
|
130
171
|
def get_args(self, context: DAGContext = None) -> list[str]:
|
172
|
+
"""
|
173
|
+
Generate the FFmpeg filter string for this filter node.
|
174
|
+
|
175
|
+
This method creates the filter string that will be used in the
|
176
|
+
filter_complex argument of the FFmpeg command. The format follows
|
177
|
+
FFmpeg's syntax where input labels are followed by the filter name
|
178
|
+
and parameters, and then output labels.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
context: Optional DAG context for resolving stream labels.
|
182
|
+
If not provided, a new context will be built.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
A list of strings that, when joined, form the filter string
|
186
|
+
for this node in the filter_complex argument
|
187
|
+
|
188
|
+
Example:
|
189
|
+
For a scale filter with width=1280 and height=720, this might return:
|
190
|
+
['[0:v]', 'scale=', 'width=1280:height=720', '[s0]']
|
191
|
+
"""
|
131
192
|
from .context import DAGContext
|
132
193
|
|
133
194
|
if not context:
|
@@ -143,7 +204,7 @@ class FilterNode(Node):
|
|
143
204
|
outgoing_labels += f"[{output.label(context)}]"
|
144
205
|
|
145
206
|
commands = []
|
146
|
-
for key, value in self.kwargs:
|
207
|
+
for key, value in self.kwargs.items():
|
147
208
|
assert not isinstance(value, LazyValue), (
|
148
209
|
f"LazyValue should have been evaluated: {key}={value}"
|
149
210
|
)
|
@@ -168,7 +229,14 @@ class FilterNode(Node):
|
|
168
229
|
@dataclass(frozen=True, kw_only=True)
|
169
230
|
class FilterableStream(Stream, OutputArgs):
|
170
231
|
"""
|
171
|
-
A stream that can be used as input to
|
232
|
+
A stream that can be used as input to an FFmpeg filter.
|
233
|
+
|
234
|
+
FilterableStream represents a media stream (audio or video) that can be
|
235
|
+
processed by FFmpeg filters. It provides methods for applying various
|
236
|
+
filters to the stream and for outputting the stream to a file.
|
237
|
+
|
238
|
+
This class serves as a base for specific stream types like VideoStream
|
239
|
+
and AudioStream, providing common functionality for filter operations.
|
172
240
|
"""
|
173
241
|
|
174
242
|
node: FilterNode | InputNode
|
@@ -178,20 +246,33 @@ class FilterableStream(Stream, OutputArgs):
|
|
178
246
|
self, *streams: FilterableStream, filename: str | Path, **kwargs: Any
|
179
247
|
) -> OutputNode:
|
180
248
|
"""
|
181
|
-
|
249
|
+
Create an output node that writes this stream (and optionally others) to a file.
|
250
|
+
|
251
|
+
This method creates an OutputNode that represents writing one or more
|
252
|
+
streams to a file. The resulting node can be used to generate the
|
253
|
+
FFmpeg command-line arguments for the output file.
|
182
254
|
|
183
255
|
Args:
|
184
|
-
*streams:
|
185
|
-
filename:
|
186
|
-
**kwargs:
|
256
|
+
*streams: Additional streams to include in the same output file
|
257
|
+
filename: Path to the output file
|
258
|
+
**kwargs: FFmpeg output options (e.g., codec, bitrate, format)
|
259
|
+
as keyword arguments
|
187
260
|
|
188
261
|
Returns:
|
189
|
-
the output
|
262
|
+
An OutputNode representing the file output operation
|
263
|
+
|
264
|
+
Example:
|
265
|
+
```python
|
266
|
+
# Output a video stream to an MP4 file with H.264 codec
|
267
|
+
output_node = video_stream._output_node(
|
268
|
+
filename="output.mp4", c="libx264", crf=23
|
269
|
+
)
|
270
|
+
```
|
190
271
|
"""
|
191
272
|
return OutputNode(
|
192
273
|
inputs=(self, *streams),
|
193
274
|
filename=str(filename),
|
194
|
-
kwargs=
|
275
|
+
kwargs=FrozenDict(kwargs),
|
195
276
|
)
|
196
277
|
|
197
278
|
def vfilter(
|
@@ -202,16 +283,28 @@ class FilterableStream(Stream, OutputArgs):
|
|
202
283
|
**kwargs: Any,
|
203
284
|
) -> VideoStream:
|
204
285
|
"""
|
205
|
-
Apply a custom video filter
|
286
|
+
Apply a custom video filter to this stream.
|
287
|
+
|
288
|
+
This method applies a custom FFmpeg video filter to this stream and
|
289
|
+
returns the resulting video stream. It's a convenience wrapper around
|
290
|
+
filter_multi_output that handles the case of filters with a single
|
291
|
+
video output.
|
206
292
|
|
207
293
|
Args:
|
208
|
-
*streams
|
209
|
-
name:
|
210
|
-
input_typings: the input
|
211
|
-
|
294
|
+
*streams: Additional input streams for the filter
|
295
|
+
name: The name of the FFmpeg filter to apply
|
296
|
+
input_typings: The expected types of the input streams
|
297
|
+
(defaults to all video)
|
298
|
+
**kwargs: Filter-specific parameters as keyword arguments
|
212
299
|
|
213
300
|
Returns:
|
214
|
-
the output
|
301
|
+
A VideoStream representing the filter's output
|
302
|
+
|
303
|
+
Example:
|
304
|
+
```python
|
305
|
+
# Apply a blur filter to a video stream
|
306
|
+
blurred = stream.vfilter(name="boxblur", luma_radius=2)
|
307
|
+
```
|
215
308
|
"""
|
216
309
|
return self.filter_multi_output(
|
217
310
|
*streams,
|
@@ -229,16 +322,28 @@ class FilterableStream(Stream, OutputArgs):
|
|
229
322
|
**kwargs: Any,
|
230
323
|
) -> AudioStream:
|
231
324
|
"""
|
232
|
-
Apply a custom audio filter
|
325
|
+
Apply a custom audio filter to this stream.
|
326
|
+
|
327
|
+
This method applies a custom FFmpeg audio filter to this stream and
|
328
|
+
returns the resulting audio stream. It's a convenience wrapper around
|
329
|
+
filter_multi_output that handles the case of filters with a single
|
330
|
+
audio output.
|
233
331
|
|
234
332
|
Args:
|
235
|
-
*streams
|
236
|
-
name:
|
237
|
-
input_typings: the input
|
238
|
-
|
333
|
+
*streams: Additional input streams for the filter
|
334
|
+
name: The name of the FFmpeg filter to apply
|
335
|
+
input_typings: The expected types of the input streams
|
336
|
+
(defaults to all audio)
|
337
|
+
**kwargs: Filter-specific parameters as keyword arguments
|
239
338
|
|
240
339
|
Returns:
|
241
|
-
the output
|
340
|
+
An AudioStream representing the filter's output
|
341
|
+
|
342
|
+
Example:
|
343
|
+
```python
|
344
|
+
# Apply a volume filter to an audio stream
|
345
|
+
louder = stream.afilter(name="volume", volume=2.0)
|
346
|
+
```
|
242
347
|
"""
|
243
348
|
return self.filter_multi_output(
|
244
349
|
*streams,
|
@@ -257,21 +362,36 @@ class FilterableStream(Stream, OutputArgs):
|
|
257
362
|
**kwargs: Any,
|
258
363
|
) -> FilterNode:
|
259
364
|
"""
|
260
|
-
Apply a custom filter
|
365
|
+
Apply a custom filter with multiple outputs to this stream.
|
366
|
+
|
367
|
+
This method creates a FilterNode that applies a custom FFmpeg filter
|
368
|
+
to this stream (and optionally additional streams). Unlike vfilter and
|
369
|
+
afilter which return a single stream, this method returns the FilterNode
|
370
|
+
itself, allowing access to multiple output streams.
|
261
371
|
|
262
372
|
Args:
|
263
|
-
*streams
|
264
|
-
name:
|
265
|
-
input_typings: the input
|
266
|
-
output_typings:
|
267
|
-
**kwargs:
|
373
|
+
*streams: Additional input streams for the filter
|
374
|
+
name: The name of the FFmpeg filter to apply
|
375
|
+
input_typings: The expected types of the input streams
|
376
|
+
output_typings: The types of output streams this filter produces
|
377
|
+
**kwargs: Filter-specific parameters as keyword arguments
|
268
378
|
|
269
379
|
Returns:
|
270
|
-
the
|
380
|
+
A FilterNode that can be used to access the filter's outputs
|
381
|
+
|
382
|
+
Example:
|
383
|
+
```python
|
384
|
+
# Split a video into two identical streams
|
385
|
+
split_node = stream.filter_multi_output(
|
386
|
+
name="split", output_typings=(StreamType.video, StreamType.video)
|
387
|
+
)
|
388
|
+
stream1 = split_node.video(0)
|
389
|
+
stream2 = split_node.video(1)
|
390
|
+
```
|
271
391
|
"""
|
272
392
|
return FilterNode(
|
273
393
|
name=name,
|
274
|
-
kwargs=
|
394
|
+
kwargs=FrozenDict(kwargs),
|
275
395
|
inputs=(self, *streams),
|
276
396
|
input_typings=input_typings,
|
277
397
|
output_typings=output_typings,
|
@@ -279,13 +399,30 @@ class FilterableStream(Stream, OutputArgs):
|
|
279
399
|
|
280
400
|
def label(self, context: DAGContext = None) -> str:
|
281
401
|
"""
|
282
|
-
|
402
|
+
Generate the FFmpeg label for this stream in filter graphs.
|
403
|
+
|
404
|
+
This method creates the label string used to identify this stream in
|
405
|
+
FFmpeg filter graphs. The format of the label depends on the stream's
|
406
|
+
source (input file or filter) and type (video or audio).
|
407
|
+
|
408
|
+
For input streams, labels follow FFmpeg's stream specifier syntax:
|
409
|
+
- Video streams: "0:v" (first input, video stream)
|
410
|
+
- Audio streams: "0:a" (first input, audio stream)
|
411
|
+
- AV streams: "0" (first input, all streams)
|
412
|
+
|
413
|
+
For filter outputs, labels use the filter's label:
|
414
|
+
- Single output filters: "filterlabel"
|
415
|
+
- Multi-output filters: "filterlabel#index"
|
283
416
|
|
284
417
|
Args:
|
285
|
-
context:
|
418
|
+
context: Optional DAG context for resolving node labels.
|
419
|
+
If not provided, a new context will be built.
|
286
420
|
|
287
421
|
Returns:
|
288
|
-
|
422
|
+
A string label for this stream in FFmpeg filter syntax
|
423
|
+
|
424
|
+
Raises:
|
425
|
+
FFMpegValueError: If the stream has an unknown type or node type
|
289
426
|
"""
|
290
427
|
from ..streams.audio import AudioStream
|
291
428
|
from ..streams.av import AVStream
|
@@ -324,27 +461,52 @@ class FilterableStream(Stream, OutputArgs):
|
|
324
461
|
@dataclass(frozen=True, kw_only=True)
|
325
462
|
class InputNode(Node):
|
326
463
|
"""
|
327
|
-
A node that
|
464
|
+
A node that represents an input file in the FFmpeg filter graph.
|
465
|
+
|
466
|
+
InputNode represents a media file that serves as input to the FFmpeg
|
467
|
+
command. It provides access to the video and audio streams contained
|
468
|
+
in the file, which can then be processed by filters.
|
328
469
|
"""
|
329
470
|
|
330
471
|
filename: str
|
331
472
|
"""
|
332
|
-
The
|
473
|
+
The path to the input media file
|
333
474
|
"""
|
334
475
|
|
335
476
|
inputs: tuple[()] = ()
|
477
|
+
"""
|
478
|
+
Input nodes have no inputs themselves (they are source nodes)
|
479
|
+
"""
|
336
480
|
|
337
481
|
@override
|
338
482
|
def repr(self) -> str:
|
483
|
+
"""
|
484
|
+
Get a string representation of this input node.
|
485
|
+
|
486
|
+
Returns:
|
487
|
+
The basename of the input file
|
488
|
+
"""
|
339
489
|
return os.path.basename(self.filename)
|
340
490
|
|
341
491
|
@property
|
342
492
|
def video(self) -> VideoStream:
|
343
493
|
"""
|
344
|
-
|
494
|
+
Get the video stream from this input file.
|
495
|
+
|
496
|
+
This property provides access to the video component of the input file.
|
497
|
+
The resulting VideoStream can be used as input to video filters.
|
345
498
|
|
346
499
|
Returns:
|
347
|
-
the video
|
500
|
+
A VideoStream representing the video content of this input file
|
501
|
+
|
502
|
+
Example:
|
503
|
+
```python
|
504
|
+
# Access the video stream from an input file
|
505
|
+
input_node = ffmpeg.input("input.mp4")
|
506
|
+
video = input_node.video
|
507
|
+
# Apply a filter to the video stream
|
508
|
+
scaled = video.scale(width=1280, height=720)
|
509
|
+
```
|
348
510
|
"""
|
349
511
|
from ..streams.video import VideoStream
|
350
512
|
|
@@ -353,10 +515,22 @@ class InputNode(Node):
|
|
353
515
|
@property
|
354
516
|
def audio(self) -> AudioStream:
|
355
517
|
"""
|
356
|
-
|
518
|
+
Get the audio stream from this input file.
|
519
|
+
|
520
|
+
This property provides access to the audio component of the input file.
|
521
|
+
The resulting AudioStream can be used as input to audio filters.
|
357
522
|
|
358
523
|
Returns:
|
359
|
-
the audio
|
524
|
+
An AudioStream representing the audio content of this input file
|
525
|
+
|
526
|
+
Example:
|
527
|
+
```python
|
528
|
+
# Access the audio stream from an input file
|
529
|
+
input_node = ffmpeg.input("input.mp4")
|
530
|
+
audio = input_node.audio
|
531
|
+
# Apply a filter to the audio stream
|
532
|
+
volume_adjusted = audio.volume(volume=2.0)
|
533
|
+
```
|
360
534
|
"""
|
361
535
|
from ..streams.audio import AudioStream
|
362
536
|
|
@@ -364,10 +538,23 @@ class InputNode(Node):
|
|
364
538
|
|
365
539
|
def stream(self) -> AVStream:
|
366
540
|
"""
|
367
|
-
|
541
|
+
Get a combined audio-video stream from this input file.
|
542
|
+
|
543
|
+
This method provides access to both the audio and video components
|
544
|
+
of the input file as a single AVStream. This is useful when you need
|
545
|
+
to work with both components together.
|
368
546
|
|
369
547
|
Returns:
|
370
|
-
|
548
|
+
An AVStream representing both audio and video content
|
549
|
+
|
550
|
+
Example:
|
551
|
+
```python
|
552
|
+
# Access both audio and video from an input file
|
553
|
+
input_node = ffmpeg.input("input.mp4")
|
554
|
+
av_stream = input_node.stream()
|
555
|
+
# Output both audio and video to a new file
|
556
|
+
output = av_stream.output("output.mp4")
|
557
|
+
```
|
371
558
|
"""
|
372
559
|
from ..streams.av import AVStream
|
373
560
|
|
@@ -375,8 +562,24 @@ class InputNode(Node):
|
|
375
562
|
|
376
563
|
@override
|
377
564
|
def get_args(self, context: DAGContext = None) -> list[str]:
|
565
|
+
"""
|
566
|
+
Generate the FFmpeg command-line arguments for this input file.
|
567
|
+
|
568
|
+
This method creates the command-line arguments needed to specify
|
569
|
+
this input file to FFmpeg, including any input-specific options.
|
570
|
+
|
571
|
+
Args:
|
572
|
+
context: Optional DAG context (not used for input nodes)
|
573
|
+
|
574
|
+
Returns:
|
575
|
+
A list of strings representing FFmpeg command-line arguments
|
576
|
+
|
577
|
+
Example:
|
578
|
+
For an input file "input.mp4" with options like seeking to 10 seconds:
|
579
|
+
['-ss', '10', '-i', 'input.mp4']
|
580
|
+
"""
|
378
581
|
commands = []
|
379
|
-
for key, value in self.kwargs:
|
582
|
+
for key, value in self.kwargs.items():
|
380
583
|
if isinstance(value, bool):
|
381
584
|
if value is True:
|
382
585
|
commands += [f"-{key}"]
|
@@ -390,27 +593,75 @@ class InputNode(Node):
|
|
390
593
|
|
391
594
|
@dataclass(frozen=True, kw_only=True)
|
392
595
|
class OutputNode(Node):
|
596
|
+
"""
|
597
|
+
A node that represents an output file in the FFmpeg filter graph.
|
598
|
+
|
599
|
+
OutputNode represents a destination file where processed media streams
|
600
|
+
will be written. It connects one or more streams (video, audio, or both)
|
601
|
+
to an output file and specifies output options like codecs and formats.
|
602
|
+
"""
|
603
|
+
|
393
604
|
filename: str
|
394
605
|
"""
|
395
|
-
The
|
606
|
+
The path to the output media file
|
396
607
|
"""
|
608
|
+
|
397
609
|
inputs: tuple[FilterableStream, ...]
|
610
|
+
"""
|
611
|
+
The streams to be written to this output file
|
612
|
+
"""
|
398
613
|
|
399
614
|
@override
|
400
615
|
def repr(self) -> str:
|
616
|
+
"""
|
617
|
+
Get a string representation of this output node.
|
618
|
+
|
619
|
+
Returns:
|
620
|
+
The basename of the output file
|
621
|
+
"""
|
401
622
|
return os.path.basename(self.filename)
|
402
623
|
|
403
624
|
def stream(self) -> OutputStream:
|
404
625
|
"""
|
405
|
-
|
626
|
+
Get an output stream representing this output file.
|
627
|
+
|
628
|
+
This method creates an OutputStream object that wraps this OutputNode,
|
629
|
+
allowing it to be used in operations that require an output stream,
|
630
|
+
such as adding global options.
|
406
631
|
|
407
632
|
Returns:
|
408
|
-
|
633
|
+
An OutputStream representing this output file
|
634
|
+
|
635
|
+
Example:
|
636
|
+
```python
|
637
|
+
# Create an output file and add global options
|
638
|
+
output_node = video.output("output.mp4")
|
639
|
+
output_stream = output_node.stream()
|
640
|
+
with_global_opts = output_stream.global_args(y=True)
|
641
|
+
```
|
409
642
|
"""
|
410
643
|
return OutputStream(node=self)
|
411
644
|
|
412
645
|
@override
|
413
646
|
def get_args(self, context: DAGContext = None) -> list[str]:
|
647
|
+
"""
|
648
|
+
Generate the FFmpeg command-line arguments for this output file.
|
649
|
+
|
650
|
+
This method creates the command-line arguments needed to specify
|
651
|
+
this output file to FFmpeg, including stream mapping and output-specific
|
652
|
+
options like codecs and formats.
|
653
|
+
|
654
|
+
Args:
|
655
|
+
context: Optional DAG context for resolving stream labels.
|
656
|
+
If not provided, a new context will be built.
|
657
|
+
|
658
|
+
Returns:
|
659
|
+
A list of strings representing FFmpeg command-line arguments
|
660
|
+
|
661
|
+
Example:
|
662
|
+
For an output file "output.mp4" with H.264 video codec:
|
663
|
+
['-map', '[v0]', '-c:v', 'libx264', 'output.mp4']
|
664
|
+
"""
|
414
665
|
# !handle mapping
|
415
666
|
commands = []
|
416
667
|
|
@@ -424,7 +675,7 @@ class OutputNode(Node):
|
|
424
675
|
else:
|
425
676
|
commands += ["-map", f"[{input.label(context)}]"]
|
426
677
|
|
427
|
-
for key, value in self.kwargs:
|
678
|
+
for key, value in self.kwargs.items():
|
428
679
|
if isinstance(value, bool):
|
429
680
|
if value is True:
|
430
681
|
commands += [f"-{key}"]
|
@@ -438,47 +689,109 @@ class OutputNode(Node):
|
|
438
689
|
|
439
690
|
@dataclass(frozen=True, kw_only=True)
|
440
691
|
class OutputStream(Stream, GlobalRunable):
|
692
|
+
"""
|
693
|
+
A stream representing an output file with additional capabilities.
|
694
|
+
|
695
|
+
OutputStream wraps an OutputNode and provides additional functionality,
|
696
|
+
particularly the ability to add global FFmpeg options. This class serves
|
697
|
+
as an intermediate step between creating an output file and executing
|
698
|
+
the FFmpeg command.
|
699
|
+
"""
|
700
|
+
|
441
701
|
node: OutputNode
|
702
|
+
"""The output node this stream represents"""
|
442
703
|
|
443
704
|
@override
|
444
705
|
def _global_node(self, *streams: OutputStream, **kwargs: Any) -> GlobalNode:
|
445
706
|
"""
|
446
|
-
|
707
|
+
Create a GlobalNode with additional global FFmpeg options.
|
708
|
+
|
709
|
+
This method creates a GlobalNode that applies global options to the
|
710
|
+
FFmpeg command. These options affect the entire command rather than
|
711
|
+
specific inputs or outputs.
|
447
712
|
|
448
713
|
Args:
|
449
|
-
|
714
|
+
*streams: Additional output streams to include in the same command
|
715
|
+
**kwargs: Global FFmpeg options as keyword arguments
|
450
716
|
|
451
717
|
Returns:
|
452
|
-
the
|
718
|
+
A GlobalNode with the specified options
|
719
|
+
|
720
|
+
Example:
|
721
|
+
```python
|
722
|
+
# Add global options to an output stream
|
723
|
+
global_node = output_stream._global_node(y=True, loglevel="quiet")
|
724
|
+
```
|
453
725
|
"""
|
454
|
-
return GlobalNode(inputs=(self, *streams), kwargs=
|
726
|
+
return GlobalNode(inputs=(self, *streams), kwargs=FrozenDict(kwargs))
|
455
727
|
|
456
728
|
|
457
729
|
@dataclass(frozen=True, kw_only=True)
|
458
730
|
class GlobalNode(Node):
|
459
731
|
"""
|
460
|
-
A node that
|
732
|
+
A node that represents global FFmpeg options.
|
733
|
+
|
734
|
+
GlobalNode represents options that apply to the entire FFmpeg command
|
735
|
+
rather than to specific inputs or outputs. These include options like
|
736
|
+
overwrite (-y), log level, and other general FFmpeg settings.
|
461
737
|
"""
|
462
738
|
|
463
739
|
inputs: tuple[OutputStream, ...]
|
740
|
+
"""The output streams this node applies to"""
|
464
741
|
|
465
742
|
@override
|
466
743
|
def repr(self) -> str:
|
744
|
+
"""
|
745
|
+
Get a string representation of this global node.
|
746
|
+
|
747
|
+
Returns:
|
748
|
+
A space-separated string of the global options
|
749
|
+
"""
|
467
750
|
return " ".join(self.get_args())
|
468
751
|
|
469
752
|
def stream(self) -> GlobalStream:
|
470
753
|
"""
|
471
|
-
|
754
|
+
Get a global stream representing this global node.
|
755
|
+
|
756
|
+
This method creates a GlobalStream object that wraps this GlobalNode,
|
757
|
+
allowing it to be used in operations that require a global stream,
|
758
|
+
such as adding more global options or executing the command.
|
472
759
|
|
473
760
|
Returns:
|
474
|
-
|
761
|
+
A GlobalStream representing this global node
|
762
|
+
|
763
|
+
Example:
|
764
|
+
```python
|
765
|
+
# Create a global node and get its stream
|
766
|
+
global_node = ffmpeg.global_args(y=True)
|
767
|
+
global_stream = global_node.stream()
|
768
|
+
# Execute the command
|
769
|
+
global_stream.run()
|
770
|
+
```
|
475
771
|
"""
|
476
772
|
return GlobalStream(node=self)
|
477
773
|
|
478
774
|
@override
|
479
775
|
def get_args(self, context: DAGContext = None) -> list[str]:
|
776
|
+
"""
|
777
|
+
Generate the FFmpeg command-line arguments for these global options.
|
778
|
+
|
779
|
+
This method creates the command-line arguments needed to specify
|
780
|
+
global options to FFmpeg, such as -y for overwrite or -loglevel for
|
781
|
+
controlling log output.
|
782
|
+
|
783
|
+
Args:
|
784
|
+
context: Optional DAG context (not used for global options)
|
785
|
+
|
786
|
+
Returns:
|
787
|
+
A list of strings representing FFmpeg command-line arguments
|
788
|
+
|
789
|
+
Example:
|
790
|
+
For global options like overwrite and quiet logging:
|
791
|
+
['-y', '-loglevel', 'quiet']
|
792
|
+
"""
|
480
793
|
commands = []
|
481
|
-
for key, value in self.kwargs:
|
794
|
+
for key, value in self.kwargs.items():
|
482
795
|
if isinstance(value, bool):
|
483
796
|
if value is True:
|
484
797
|
commands += [f"-{key}"]
|
@@ -491,21 +804,43 @@ class GlobalNode(Node):
|
|
491
804
|
|
492
805
|
@dataclass(frozen=True, kw_only=True)
|
493
806
|
class GlobalStream(Stream, GlobalRunable):
|
807
|
+
"""
|
808
|
+
A stream representing a set of global FFmpeg options.
|
809
|
+
|
810
|
+
GlobalStream wraps a GlobalNode and provides additional functionality,
|
811
|
+
particularly the ability to add more global options or execute the
|
812
|
+
FFmpeg command. This class is typically the final step in the FFmpeg
|
813
|
+
command construction process.
|
814
|
+
"""
|
815
|
+
|
494
816
|
node: GlobalNode
|
817
|
+
"""The global node this stream represents"""
|
495
818
|
|
496
819
|
@override
|
497
820
|
def _global_node(self, *streams: OutputStream, **kwargs: Any) -> GlobalNode:
|
498
821
|
"""
|
499
|
-
Add
|
822
|
+
Add additional global FFmpeg options to this stream.
|
823
|
+
|
824
|
+
This method creates a new GlobalNode that combines the existing global
|
825
|
+
options with new ones. It also allows adding more output streams to
|
826
|
+
the command.
|
500
827
|
|
501
828
|
Args:
|
502
|
-
|
829
|
+
*streams: Additional output streams to include in the command
|
830
|
+
**kwargs: Additional global FFmpeg options as keyword arguments
|
503
831
|
|
504
832
|
Returns:
|
505
|
-
the
|
833
|
+
A new GlobalNode with the combined options and streams
|
834
|
+
|
835
|
+
Example:
|
836
|
+
```python
|
837
|
+
# Add more global options to an existing global stream
|
838
|
+
global_stream = ffmpeg.output("output.mp4").global_args(y=True)
|
839
|
+
enhanced = global_stream._global_node(loglevel="quiet")
|
840
|
+
```
|
506
841
|
"""
|
507
842
|
inputs = (*self.node.inputs, *streams)
|
508
843
|
kwargs = dict(self.node.kwargs) | kwargs
|
509
844
|
|
510
|
-
new_node = replace(self.node, inputs=inputs, kwargs=
|
845
|
+
new_node = replace(self.node, inputs=inputs, kwargs=FrozenDict(kwargs))
|
511
846
|
return new_node
|