typed-ffmpeg-compatible 3.5.1__py3-none-any.whl → 3.6__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 +4 -1
- typed_ffmpeg/_version.py +2 -2
- typed_ffmpeg/base.py +4 -1
- typed_ffmpeg/codecs/__init__.py +2 -0
- typed_ffmpeg/codecs/decoders.py +1852 -1853
- typed_ffmpeg/codecs/encoders.py +2001 -1782
- typed_ffmpeg/codecs/schema.py +6 -12
- typed_ffmpeg/common/__init__.py +1 -0
- typed_ffmpeg/common/cache.py +9 -6
- typed_ffmpeg/common/schema.py +11 -0
- typed_ffmpeg/common/serialize.py +13 -7
- typed_ffmpeg/compile/__init__.py +1 -0
- typed_ffmpeg/compile/compile_cli.py +55 -8
- typed_ffmpeg/compile/compile_json.py +4 -0
- typed_ffmpeg/compile/compile_python.py +15 -0
- typed_ffmpeg/compile/context.py +15 -4
- typed_ffmpeg/compile/validate.py +9 -8
- typed_ffmpeg/dag/factory.py +2 -0
- typed_ffmpeg/dag/global_runnable/__init__.py +1 -0
- typed_ffmpeg/dag/global_runnable/global_args.py +2 -2
- typed_ffmpeg/dag/global_runnable/runnable.py +51 -11
- typed_ffmpeg/dag/io/__init__.py +1 -0
- typed_ffmpeg/dag/io/_input.py +20 -5
- typed_ffmpeg/dag/io/_output.py +24 -9
- typed_ffmpeg/dag/io/output_args.py +21 -7
- typed_ffmpeg/dag/nodes.py +20 -0
- typed_ffmpeg/dag/schema.py +19 -6
- typed_ffmpeg/dag/utils.py +2 -2
- typed_ffmpeg/exceptions.py +2 -1
- typed_ffmpeg/expressions.py +884 -0
- typed_ffmpeg/ffprobe/__init__.py +1 -0
- typed_ffmpeg/ffprobe/parse.py +7 -1
- typed_ffmpeg/ffprobe/probe.py +3 -1
- typed_ffmpeg/ffprobe/schema.py +83 -1
- typed_ffmpeg/ffprobe/xml2json.py +8 -2
- typed_ffmpeg/filters.py +540 -631
- typed_ffmpeg/formats/__init__.py +2 -0
- typed_ffmpeg/formats/demuxers.py +1869 -1921
- typed_ffmpeg/formats/muxers.py +1382 -1107
- typed_ffmpeg/formats/schema.py +6 -12
- typed_ffmpeg/info.py +8 -0
- typed_ffmpeg/options/__init__.py +15 -0
- typed_ffmpeg/options/codec.py +711 -0
- typed_ffmpeg/options/format.py +196 -0
- typed_ffmpeg/options/framesync.py +43 -0
- typed_ffmpeg/options/timeline.py +22 -0
- typed_ffmpeg/schema.py +15 -0
- typed_ffmpeg/sources.py +392 -381
- typed_ffmpeg/streams/__init__.py +2 -0
- typed_ffmpeg/streams/audio.py +1071 -882
- typed_ffmpeg/streams/av.py +9 -3
- typed_ffmpeg/streams/subtitle.py +3 -3
- typed_ffmpeg/streams/video.py +1873 -1725
- typed_ffmpeg/types.py +3 -2
- typed_ffmpeg/utils/__init__.py +1 -0
- typed_ffmpeg/utils/escaping.py +8 -4
- typed_ffmpeg/utils/frozendict.py +31 -1
- typed_ffmpeg/utils/lazy_eval/__init__.py +1 -0
- typed_ffmpeg/utils/lazy_eval/operator.py +75 -27
- typed_ffmpeg/utils/lazy_eval/schema.py +176 -4
- typed_ffmpeg/utils/run.py +2 -0
- typed_ffmpeg/utils/snapshot.py +3 -2
- typed_ffmpeg/utils/typing.py +2 -1
- typed_ffmpeg/utils/view.py +2 -1
- {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/METADATA +1 -1
- typed_ffmpeg_compatible-3.6.dist-info/RECORD +73 -0
- typed_ffmpeg_compatible-3.5.1.dist-info/RECORD +0 -67
- {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/WHEEL +0 -0
- {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/entry_points.txt +0 -0
- {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/licenses/LICENSE +0 -0
- {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/top_level.txt +0 -0
typed_ffmpeg/compile/validate.py
CHANGED
@@ -21,7 +21,7 @@ from .context import DAGContext
|
|
21
21
|
|
22
22
|
|
23
23
|
def remove_split(
|
24
|
-
current_stream: Stream, mapping: dict[Stream, Stream] = None
|
24
|
+
current_stream: Stream, mapping: dict[Stream, Stream] | None = None
|
25
25
|
) -> tuple[Stream, dict[Stream, Stream]]:
|
26
26
|
"""
|
27
27
|
Remove all split nodes from the graph to prepare for reconstruction.
|
@@ -43,8 +43,8 @@ def remove_split(
|
|
43
43
|
A tuple containing:
|
44
44
|
- The new stream corresponding to the input stream but with splits removed
|
45
45
|
- A mapping dictionary relating original streams to their new versions
|
46
|
-
"""
|
47
46
|
|
47
|
+
"""
|
48
48
|
# remove all split nodes
|
49
49
|
# add split nodes to the graph
|
50
50
|
if mapping is None:
|
@@ -91,10 +91,10 @@ def remove_split(
|
|
91
91
|
|
92
92
|
def add_split(
|
93
93
|
current_stream: Stream,
|
94
|
-
down_node: Node = None,
|
95
|
-
down_index: int = None,
|
96
|
-
context: DAGContext = None,
|
97
|
-
mapping: dict[tuple[Stream, Node | None, int | None], Stream] = None,
|
94
|
+
down_node: Node | None = None,
|
95
|
+
down_index: int | None = None,
|
96
|
+
context: DAGContext | None = None,
|
97
|
+
mapping: dict[tuple[Stream, Node | None, int | None], Stream] | None = None,
|
98
98
|
) -> tuple[Stream, dict[tuple[Stream, Node | None, int | None], Stream]]:
|
99
99
|
"""
|
100
100
|
Add split nodes to the graph where streams are reused.
|
@@ -122,8 +122,8 @@ def add_split(
|
|
122
122
|
|
123
123
|
Raises:
|
124
124
|
FFMpegValueError: If an unsupported stream type is encountered
|
125
|
-
"""
|
126
125
|
|
126
|
+
"""
|
127
127
|
if not context:
|
128
128
|
context = DAGContext.build(current_stream.node)
|
129
129
|
|
@@ -202,8 +202,8 @@ def fix_graph(stream: Stream) -> Stream:
|
|
202
202
|
Note:
|
203
203
|
This function creates a new graph structure rather than modifying the
|
204
204
|
existing one, preserving the original graph.
|
205
|
-
"""
|
206
205
|
|
206
|
+
"""
|
207
207
|
stream, _ = remove_split(stream)
|
208
208
|
stream, _ = add_split(stream)
|
209
209
|
return stream
|
@@ -240,6 +240,7 @@ def validate(stream: Stream, auto_fix: bool = True) -> Stream:
|
|
240
240
|
# Validate will automatically insert a split filter
|
241
241
|
valid_stream = ffmpeg.dag.validate(scaled.output("output.mp4"))
|
242
242
|
```
|
243
|
+
|
243
244
|
"""
|
244
245
|
if auto_fix:
|
245
246
|
stream = fix_graph(stream)
|
typed_ffmpeg/dag/factory.py
CHANGED
@@ -28,6 +28,7 @@ def eval_formula(formula: str, **kwargs: Any) -> list[StreamType]:
|
|
28
28
|
|
29
29
|
Returns:
|
30
30
|
The result of the formula evaluation
|
31
|
+
|
31
32
|
"""
|
32
33
|
# Convert formula to Python code
|
33
34
|
return eval(
|
@@ -57,6 +58,7 @@ def filter_node_factory(
|
|
57
58
|
Note:
|
58
59
|
This function is primarily used internally by the filter generation system
|
59
60
|
to create filter nodes from the FFmpeg filter definitions.
|
61
|
+
|
60
62
|
"""
|
61
63
|
for k, v in kwargs.items():
|
62
64
|
if isinstance(v, Auto):
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Global runnable utilities for FFmpeg DAG operations."""
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# NOTE: this file is auto-generated, do not modify
|
2
|
-
|
2
|
+
"""Global arguments."""
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
@@ -136,8 +136,8 @@ class GlobalArgs(ABC):
|
|
136
136
|
|
137
137
|
Returns:
|
138
138
|
GlobalStream: GlobalStream instance
|
139
|
-
"""
|
140
139
|
|
140
|
+
"""
|
141
141
|
return self._global_node(
|
142
142
|
**merge(
|
143
143
|
{
|
@@ -56,6 +56,7 @@ class GlobalRunable(GlobalArgs):
|
|
56
56
|
merged = output1.merge_outputs(output2)
|
57
57
|
merged.run() # Creates both output files with one FFmpeg command
|
58
58
|
```
|
59
|
+
|
59
60
|
"""
|
60
61
|
return self._global_node(*streams).stream()
|
61
62
|
|
@@ -75,6 +76,7 @@ class GlobalRunable(GlobalArgs):
|
|
75
76
|
# Overwrite output file if it already exists
|
76
77
|
ffmpeg.input("input.mp4").output("output.mp4").overwrite_output().run()
|
77
78
|
```
|
79
|
+
|
78
80
|
"""
|
79
81
|
return self._global_node(y=True).stream()
|
80
82
|
|
@@ -83,6 +85,7 @@ class GlobalRunable(GlobalArgs):
|
|
83
85
|
cmd: str | list[str] = "ffmpeg",
|
84
86
|
overwrite_output: bool | None = None,
|
85
87
|
auto_fix: bool = True,
|
88
|
+
use_filter_complex_script: bool = False,
|
86
89
|
) -> list[str]:
|
87
90
|
"""
|
88
91
|
Build command-line arguments for invoking FFmpeg.
|
@@ -100,6 +103,8 @@ class GlobalRunable(GlobalArgs):
|
|
100
103
|
If None (default), use the current settings
|
101
104
|
auto_fix: Whether to automatically fix issues in the filter graph,
|
102
105
|
such as adding split filters for reused streams
|
106
|
+
use_filter_complex_script: If True, use -filter_complex_script with a
|
107
|
+
temporary file instead of -filter_complex
|
103
108
|
|
104
109
|
Returns:
|
105
110
|
A list of strings representing the complete FFmpeg command
|
@@ -110,6 +115,7 @@ class GlobalRunable(GlobalArgs):
|
|
110
115
|
args = ffmpeg.input("input.mp4").output("output.mp4").compile()
|
111
116
|
# Result: ['ffmpeg', '-i', 'input.mp4', 'output.mp4']
|
112
117
|
```
|
118
|
+
|
113
119
|
"""
|
114
120
|
from ...compile.compile_cli import compile_as_list
|
115
121
|
|
@@ -117,17 +123,30 @@ class GlobalRunable(GlobalArgs):
|
|
117
123
|
cmd = [cmd]
|
118
124
|
|
119
125
|
if overwrite_output is True:
|
120
|
-
return self.global_args(y=True).compile(
|
126
|
+
return self.global_args(y=True).compile(
|
127
|
+
cmd,
|
128
|
+
auto_fix=auto_fix,
|
129
|
+
use_filter_complex_script=use_filter_complex_script,
|
130
|
+
)
|
121
131
|
elif overwrite_output is False:
|
122
|
-
return self.global_args(n=True).compile(
|
132
|
+
return self.global_args(n=True).compile(
|
133
|
+
cmd,
|
134
|
+
auto_fix=auto_fix,
|
135
|
+
use_filter_complex_script=use_filter_complex_script,
|
136
|
+
)
|
123
137
|
|
124
|
-
return cmd + compile_as_list(
|
138
|
+
return cmd + compile_as_list(
|
139
|
+
self._global_node().stream(),
|
140
|
+
auto_fix=auto_fix,
|
141
|
+
use_filter_complex_script=use_filter_complex_script,
|
142
|
+
)
|
125
143
|
|
126
144
|
def compile_line(
|
127
145
|
self,
|
128
146
|
cmd: str | list[str] = "ffmpeg",
|
129
147
|
overwrite_output: bool | None = None,
|
130
148
|
auto_fix: bool = True,
|
149
|
+
use_filter_complex_script: bool = False,
|
131
150
|
) -> str:
|
132
151
|
"""
|
133
152
|
Build a command-line string for invoking FFmpeg.
|
@@ -143,6 +162,8 @@ class GlobalRunable(GlobalArgs):
|
|
143
162
|
If False, add the -n option to never overwrite
|
144
163
|
If None (default), use the current settings
|
145
164
|
auto_fix: Whether to automatically fix issues in the filter graph
|
165
|
+
use_filter_complex_script: If True, use -filter_complex_script with a
|
166
|
+
temporary file instead of -filter_complex
|
146
167
|
|
147
168
|
Returns:
|
148
169
|
A string representing the complete FFmpeg command with proper escaping
|
@@ -153,9 +174,15 @@ class GlobalRunable(GlobalArgs):
|
|
153
174
|
cmd_str = ffmpeg.input("input.mp4").output("output.mp4").compile_line()
|
154
175
|
# Result: 'ffmpeg -i input.mp4 output.mp4'
|
155
176
|
```
|
177
|
+
|
156
178
|
"""
|
157
179
|
return command_line(
|
158
|
-
self.compile(
|
180
|
+
self.compile(
|
181
|
+
cmd,
|
182
|
+
overwrite_output=overwrite_output,
|
183
|
+
auto_fix=auto_fix,
|
184
|
+
use_filter_complex_script=use_filter_complex_script,
|
185
|
+
)
|
159
186
|
)
|
160
187
|
|
161
188
|
def run_async(
|
@@ -167,6 +194,7 @@ class GlobalRunable(GlobalArgs):
|
|
167
194
|
quiet: bool = False,
|
168
195
|
overwrite_output: bool | None = None,
|
169
196
|
auto_fix: bool = True,
|
197
|
+
use_filter_complex_script: bool = False,
|
170
198
|
) -> subprocess.Popen[bytes]:
|
171
199
|
"""
|
172
200
|
Run FFmpeg asynchronously as a subprocess.
|
@@ -186,6 +214,8 @@ class GlobalRunable(GlobalArgs):
|
|
186
214
|
If False, add the -n option to never overwrite
|
187
215
|
If None (default), use the current settings
|
188
216
|
auto_fix: Whether to automatically fix issues in the filter graph
|
217
|
+
use_filter_complex_script: If True, use -filter_complex_script with a
|
218
|
+
temporary file instead of -filter_complex
|
189
219
|
|
190
220
|
Returns:
|
191
221
|
A subprocess.Popen object representing the running FFmpeg process
|
@@ -197,16 +227,19 @@ class GlobalRunable(GlobalArgs):
|
|
197
227
|
# Do something while FFmpeg is running
|
198
228
|
process.wait() # Wait for completion
|
199
229
|
```
|
200
|
-
"""
|
201
230
|
|
202
|
-
|
231
|
+
"""
|
232
|
+
args = self.compile(
|
233
|
+
cmd,
|
234
|
+
overwrite_output=overwrite_output,
|
235
|
+
auto_fix=auto_fix,
|
236
|
+
use_filter_complex_script=use_filter_complex_script,
|
237
|
+
)
|
203
238
|
stdin_stream = subprocess.PIPE if pipe_stdin else None
|
204
239
|
stdout_stream = subprocess.PIPE if pipe_stdout or quiet else None
|
205
240
|
stderr_stream = subprocess.PIPE if pipe_stderr or quiet else None
|
206
241
|
|
207
|
-
logger.info(
|
208
|
-
f"Running command: {self.compile_line(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix)}"
|
209
|
-
)
|
242
|
+
logger.info(f"Running command: {' '.join(args)}")
|
210
243
|
|
211
244
|
return subprocess.Popen(
|
212
245
|
args,
|
@@ -224,6 +257,7 @@ class GlobalRunable(GlobalArgs):
|
|
224
257
|
quiet: bool = False,
|
225
258
|
overwrite_output: bool | None = None,
|
226
259
|
auto_fix: bool = True,
|
260
|
+
use_filter_complex_script: bool = False,
|
227
261
|
) -> tuple[bytes, bytes]:
|
228
262
|
"""
|
229
263
|
Run FFmpeg synchronously and wait for completion.
|
@@ -243,6 +277,8 @@ class GlobalRunable(GlobalArgs):
|
|
243
277
|
If False, add the -n option to never overwrite
|
244
278
|
If None (default), use the current settings
|
245
279
|
auto_fix: Whether to automatically fix issues in the filter graph
|
280
|
+
use_filter_complex_script: If True, use -filter_complex_script with a
|
281
|
+
temporary file instead of -filter_complex
|
246
282
|
|
247
283
|
Returns:
|
248
284
|
A tuple of (stdout_bytes, stderr_bytes), which will be empty bytes
|
@@ -262,8 +298,8 @@ class GlobalRunable(GlobalArgs):
|
|
262
298
|
)
|
263
299
|
print(stderr.decode()) # Print FFmpeg's progress information
|
264
300
|
```
|
265
|
-
"""
|
266
301
|
|
302
|
+
"""
|
267
303
|
process = self.run_async(
|
268
304
|
cmd,
|
269
305
|
pipe_stdin=input is not None,
|
@@ -272,6 +308,7 @@ class GlobalRunable(GlobalArgs):
|
|
272
308
|
quiet=quiet,
|
273
309
|
overwrite_output=overwrite_output,
|
274
310
|
auto_fix=auto_fix,
|
311
|
+
use_filter_complex_script=use_filter_complex_script,
|
275
312
|
)
|
276
313
|
stdout, stderr = process.communicate(input)
|
277
314
|
retcode = process.poll()
|
@@ -280,7 +317,10 @@ class GlobalRunable(GlobalArgs):
|
|
280
317
|
raise FFMpegExecuteError(
|
281
318
|
retcode=retcode,
|
282
319
|
cmd=self.compile_line(
|
283
|
-
cmd,
|
320
|
+
cmd,
|
321
|
+
overwrite_output=overwrite_output,
|
322
|
+
auto_fix=auto_fix,
|
323
|
+
use_filter_complex_script=use_filter_complex_script,
|
284
324
|
),
|
285
325
|
stdout=stdout,
|
286
326
|
stderr=stderr,
|
typed_ffmpeg/dag/io/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
"""Input/output utilities for FFmpeg DAG operations."""
|
typed_ffmpeg/dag/io/_input.py
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# NOTE: this file is auto-generated, do not modify
|
2
|
-
|
2
|
+
"""Input node."""
|
3
3
|
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import Any
|
6
6
|
|
7
7
|
from ...codecs.schema import FFMpegDecoderOption
|
8
8
|
from ...formats.schema import FFMpegDemuxerOption
|
9
|
+
from ...options.codec import (
|
10
|
+
FFMpegAVCodecContextDecoderOption,
|
11
|
+
)
|
12
|
+
from ...options.format import (
|
13
|
+
FFMpegAVFormatContextDecoderOption,
|
14
|
+
)
|
9
15
|
from ...streams.av import AVStream
|
10
16
|
from ...types import (
|
11
17
|
Boolean,
|
@@ -16,7 +22,9 @@ from ...types import (
|
|
16
22
|
Time,
|
17
23
|
)
|
18
24
|
from ...utils.frozendict import merge
|
19
|
-
from ..nodes import
|
25
|
+
from ..nodes import (
|
26
|
+
InputNode,
|
27
|
+
)
|
20
28
|
|
21
29
|
|
22
30
|
def input(
|
@@ -76,10 +84,12 @@ def input(
|
|
76
84
|
top: Int = None,
|
77
85
|
decoder_options: FFMpegDecoderOption | None = None,
|
78
86
|
demuxer_options: FFMpegDemuxerOption | None = None,
|
87
|
+
format_options: FFMpegAVFormatContextDecoderOption | None = None,
|
88
|
+
codec_options: FFMpegAVCodecContextDecoderOption | None = None,
|
79
89
|
extra_options: dict[str, Any] | None = None,
|
80
90
|
) -> AVStream:
|
81
91
|
"""
|
82
|
-
Input file URL (ffmpeg ``-i`` option)
|
92
|
+
Input file URL (ffmpeg ``-i`` option).
|
83
93
|
|
84
94
|
Args:
|
85
95
|
filename: Input file URL
|
@@ -137,6 +147,8 @@ def input(
|
|
137
147
|
top: deprecated, use the setfield video filter
|
138
148
|
decoder_options: ffmpeg's decoder options
|
139
149
|
demuxer_options: ffmpeg's demuxer options
|
150
|
+
format_options: ffmpeg's AVFormatContext options
|
151
|
+
codec_options: ffmpeg's AVCodecContext options
|
140
152
|
extra_options: ffmpeg's input file options
|
141
153
|
|
142
154
|
Returns:
|
@@ -147,6 +159,7 @@ def input(
|
|
147
159
|
>>> input('input.mp4')
|
148
160
|
<AVStream:input.mp4:0>
|
149
161
|
```
|
162
|
+
|
150
163
|
"""
|
151
164
|
return InputNode(
|
152
165
|
filename=str(filename),
|
@@ -205,8 +218,10 @@ def input(
|
|
205
218
|
"dn": dn,
|
206
219
|
"top": top,
|
207
220
|
},
|
208
|
-
decoder_options
|
209
|
-
demuxer_options
|
221
|
+
decoder_options,
|
222
|
+
demuxer_options,
|
223
|
+
format_options,
|
224
|
+
codec_options,
|
210
225
|
extra_options,
|
211
226
|
),
|
212
227
|
).stream()
|
typed_ffmpeg/dag/io/_output.py
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# NOTE: this file is auto-generated, do not modify
|
2
|
-
|
2
|
+
"""Output node."""
|
3
3
|
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import Any
|
6
6
|
|
7
7
|
from ...codecs.schema import FFMpegEncoderOption
|
8
8
|
from ...formats.schema import FFMpegMuxerOption
|
9
|
+
from ...options.codec import (
|
10
|
+
FFMpegAVCodecContextEncoderOption,
|
11
|
+
)
|
12
|
+
from ...options.format import (
|
13
|
+
FFMpegAVFormatContextEncoderOption,
|
14
|
+
)
|
9
15
|
from ...types import (
|
10
16
|
Boolean,
|
11
17
|
Float,
|
@@ -16,7 +22,11 @@ from ...types import (
|
|
16
22
|
Time,
|
17
23
|
)
|
18
24
|
from ...utils.frozendict import merge
|
19
|
-
from ..nodes import
|
25
|
+
from ..nodes import (
|
26
|
+
FilterableStream,
|
27
|
+
OutputNode,
|
28
|
+
OutputStream,
|
29
|
+
)
|
20
30
|
|
21
31
|
|
22
32
|
def output(
|
@@ -118,10 +128,12 @@ def output(
|
|
118
128
|
top: Int = None,
|
119
129
|
encoder_options: FFMpegEncoderOption | None = None,
|
120
130
|
muxer_options: FFMpegMuxerOption | None = None,
|
131
|
+
format_options: FFMpegAVFormatContextEncoderOption | None = None,
|
132
|
+
codec_options: FFMpegAVCodecContextEncoderOption | None = None,
|
121
133
|
extra_options: dict[str, Any] | None = None,
|
122
134
|
) -> OutputStream:
|
123
|
-
"""
|
124
|
-
Output file URL
|
135
|
+
r"""
|
136
|
+
Output file URL.
|
125
137
|
|
126
138
|
Args:
|
127
139
|
*streams: the streams to output
|
@@ -142,8 +154,7 @@ def output(
|
|
142
154
|
program: add program with specified streams
|
143
155
|
stream_group: add stream group with specified streams and group type-specific arguments
|
144
156
|
dframes: set the number of data frames to output
|
145
|
-
target: specify target file type (\"vcd\", \"svcd\", \"dvd\", \"dv\" or \"dv50\
|
146
|
-
"with optional prefixes \"pal-\", \"ntsc-\" or \"film-\")
|
157
|
+
target: specify target file type (\"vcd\", \"svcd\", \"dvd\", \"dv\" or \"dv50\ "with optional prefixes \"pal-\", \"ntsc-\" or \"film-\")
|
147
158
|
shortest: finish encoding within shortest input
|
148
159
|
shortest_buf_duration: maximum buffering duration (in seconds) for the -shortest option
|
149
160
|
bitexact: bitexact mode
|
@@ -223,12 +234,14 @@ def output(
|
|
223
234
|
top: deprecated, use the setfield video filter
|
224
235
|
encoder_options: ffmpeg's encoder options
|
225
236
|
muxer_options: ffmpeg's muxer options
|
237
|
+
format_options: ffmpeg's AVFormatContext options
|
238
|
+
codec_options: ffmpeg's AVCodecContext options
|
226
239
|
extra_options: the arguments for the output
|
227
240
|
|
228
241
|
Returns:
|
229
242
|
the output stream
|
230
|
-
"""
|
231
243
|
|
244
|
+
"""
|
232
245
|
return OutputNode(
|
233
246
|
inputs=streams,
|
234
247
|
filename=str(filename),
|
@@ -329,8 +342,10 @@ def output(
|
|
329
342
|
"dn": dn,
|
330
343
|
"top": top,
|
331
344
|
},
|
332
|
-
encoder_options
|
333
|
-
muxer_options
|
345
|
+
encoder_options,
|
346
|
+
muxer_options,
|
347
|
+
format_options,
|
348
|
+
codec_options,
|
334
349
|
extra_options,
|
335
350
|
),
|
336
351
|
).stream()
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# NOTE: this file is auto-generated, do not modify
|
2
|
-
|
2
|
+
"""Output arguments."""
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
@@ -9,6 +9,12 @@ from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
10
10
|
from ...codecs.schema import FFMpegEncoderOption
|
11
11
|
from ...formats.schema import FFMpegMuxerOption
|
12
|
+
from ...options.codec import (
|
13
|
+
FFMpegAVCodecContextEncoderOption,
|
14
|
+
)
|
15
|
+
from ...options.format import (
|
16
|
+
FFMpegAVFormatContextEncoderOption,
|
17
|
+
)
|
12
18
|
from ...types import (
|
13
19
|
Boolean,
|
14
20
|
Float,
|
@@ -25,6 +31,8 @@ if TYPE_CHECKING:
|
|
25
31
|
|
26
32
|
|
27
33
|
class OutputArgs(ABC):
|
34
|
+
"""Output arguments interface."""
|
35
|
+
|
28
36
|
@abstractmethod
|
29
37
|
def _output_node(
|
30
38
|
self, *streams: FilterableStream, filename: str | Path, **kwargs: Any
|
@@ -130,10 +138,12 @@ class OutputArgs(ABC):
|
|
130
138
|
top: Int = None,
|
131
139
|
encoder_options: FFMpegEncoderOption | None = None,
|
132
140
|
muxer_options: FFMpegMuxerOption | None = None,
|
141
|
+
format_options: FFMpegAVFormatContextEncoderOption | None = None,
|
142
|
+
codec_options: FFMpegAVCodecContextEncoderOption | None = None,
|
133
143
|
extra_options: dict[str, Any] | None = None,
|
134
144
|
) -> OutputStream:
|
135
|
-
"""
|
136
|
-
Output file URL
|
145
|
+
r"""
|
146
|
+
Output file URL.
|
137
147
|
|
138
148
|
Args:
|
139
149
|
*streams: the streams to output
|
@@ -234,13 +244,15 @@ class OutputArgs(ABC):
|
|
234
244
|
dn: disable data
|
235
245
|
top: deprecated, use the setfield video filter
|
236
246
|
encoder_options: ffmpeg's encoder options
|
237
|
-
muxer_options: FFMpegMuxerOption
|
247
|
+
muxer_options: FFMpegMuxerOption
|
248
|
+
format_options: FFMpegAVFormatContextEncoderOption
|
249
|
+
codec_options: FFMpegAVCodecContextEncoderOption
|
238
250
|
extra_options: the arguments for the output
|
239
251
|
|
240
252
|
Returns:
|
241
253
|
the output stream
|
242
|
-
"""
|
243
254
|
|
255
|
+
"""
|
244
256
|
return self._output_node(
|
245
257
|
*streams,
|
246
258
|
filename=filename,
|
@@ -341,8 +353,10 @@ class OutputArgs(ABC):
|
|
341
353
|
"dn": dn,
|
342
354
|
"top": top,
|
343
355
|
},
|
344
|
-
encoder_options
|
345
|
-
muxer_options
|
356
|
+
encoder_options,
|
357
|
+
muxer_options,
|
358
|
+
format_options,
|
359
|
+
codec_options,
|
346
360
|
extra_options,
|
347
361
|
),
|
348
362
|
).stream()
|
typed_ffmpeg/dag/nodes.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
"""DAG node definitions for FFmpeg filter graphs."""
|
2
|
+
|
1
3
|
from __future__ import annotations
|
2
4
|
|
3
5
|
import logging
|
@@ -60,6 +62,7 @@ class FilterNode(Node):
|
|
60
62
|
|
61
63
|
Returns:
|
62
64
|
The name of the filter
|
65
|
+
|
63
66
|
"""
|
64
67
|
return self.name
|
65
68
|
|
@@ -81,6 +84,7 @@ class FilterNode(Node):
|
|
81
84
|
|
82
85
|
Raises:
|
83
86
|
FFMpegValueError: If the specified index is out of range
|
87
|
+
|
84
88
|
"""
|
85
89
|
from ..streams.video import VideoStream
|
86
90
|
|
@@ -111,6 +115,7 @@ class FilterNode(Node):
|
|
111
115
|
|
112
116
|
Raises:
|
113
117
|
FFMpegValueError: If the specified index is out of range
|
118
|
+
|
114
119
|
"""
|
115
120
|
from ..streams.audio import AudioStream
|
116
121
|
|
@@ -136,6 +141,7 @@ class FilterNode(Node):
|
|
136
141
|
Raises:
|
137
142
|
FFMpegValueError: If the number of inputs doesn't match input_typings
|
138
143
|
FFMpegTypeError: If an input stream doesn't match its expected type
|
144
|
+
|
139
145
|
"""
|
140
146
|
from ..streams.audio import AudioStream
|
141
147
|
from ..streams.video import VideoStream
|
@@ -207,6 +213,7 @@ class FilterableStream(Stream, OutputArgs):
|
|
207
213
|
filename="output.mp4", c="libx264", crf=23
|
208
214
|
)
|
209
215
|
```
|
216
|
+
|
210
217
|
"""
|
211
218
|
return OutputNode(
|
212
219
|
inputs=(self, *streams),
|
@@ -244,6 +251,7 @@ class FilterableStream(Stream, OutputArgs):
|
|
244
251
|
# Apply a blur filter to a video stream
|
245
252
|
blurred = stream.vfilter(name="boxblur", luma_radius=2)
|
246
253
|
```
|
254
|
+
|
247
255
|
"""
|
248
256
|
return self.filter_multi_output(
|
249
257
|
*streams,
|
@@ -283,6 +291,7 @@ class FilterableStream(Stream, OutputArgs):
|
|
283
291
|
# Apply a volume filter to an audio stream
|
284
292
|
louder = stream.afilter(name="volume", volume=2.0)
|
285
293
|
```
|
294
|
+
|
286
295
|
"""
|
287
296
|
return self.filter_multi_output(
|
288
297
|
*streams,
|
@@ -327,6 +336,7 @@ class FilterableStream(Stream, OutputArgs):
|
|
327
336
|
stream1 = split_node.video(0)
|
328
337
|
stream2 = split_node.video(1)
|
329
338
|
```
|
339
|
+
|
330
340
|
"""
|
331
341
|
return FilterNode(
|
332
342
|
name=name,
|
@@ -337,6 +347,7 @@ class FilterableStream(Stream, OutputArgs):
|
|
337
347
|
)
|
338
348
|
|
339
349
|
def __post_init__(self) -> None:
|
350
|
+
"""Validate that filter streams have an index."""
|
340
351
|
if not isinstance(self.node, InputNode):
|
341
352
|
assert self.index is not None, "Filter streams must have an index"
|
342
353
|
|
@@ -368,6 +379,7 @@ class InputNode(Node):
|
|
368
379
|
|
369
380
|
Returns:
|
370
381
|
The basename of the input file
|
382
|
+
|
371
383
|
"""
|
372
384
|
return os.path.basename(self.filename)
|
373
385
|
|
@@ -390,6 +402,7 @@ class InputNode(Node):
|
|
390
402
|
# Apply a filter to the video stream
|
391
403
|
scaled = video.scale(width=1280, height=720)
|
392
404
|
```
|
405
|
+
|
393
406
|
"""
|
394
407
|
from ..streams.video import VideoStream
|
395
408
|
|
@@ -414,6 +427,7 @@ class InputNode(Node):
|
|
414
427
|
# Apply a filter to the audio stream
|
415
428
|
volume_adjusted = audio.volume(volume=2.0)
|
416
429
|
```
|
430
|
+
|
417
431
|
"""
|
418
432
|
from ..streams.audio import AudioStream
|
419
433
|
|
@@ -438,6 +452,7 @@ class InputNode(Node):
|
|
438
452
|
# Output both audio and video to a new file
|
439
453
|
output = av_stream.output("output.mp4")
|
440
454
|
```
|
455
|
+
|
441
456
|
"""
|
442
457
|
from ..streams.av import AVStream
|
443
458
|
|
@@ -471,6 +486,7 @@ class OutputNode(Node):
|
|
471
486
|
|
472
487
|
Returns:
|
473
488
|
The basename of the output file
|
489
|
+
|
474
490
|
"""
|
475
491
|
return os.path.basename(self.filename)
|
476
492
|
|
@@ -492,6 +508,7 @@ class OutputNode(Node):
|
|
492
508
|
output_stream = output_node.stream()
|
493
509
|
with_global_opts = output_stream.global_args(y=True)
|
494
510
|
```
|
511
|
+
|
495
512
|
"""
|
496
513
|
return OutputStream(node=self)
|
497
514
|
|
@@ -531,6 +548,7 @@ class OutputStream(Stream, GlobalRunable):
|
|
531
548
|
# Add global options to an output stream
|
532
549
|
global_node = output_stream._global_node(y=True, loglevel="quiet")
|
533
550
|
```
|
551
|
+
|
534
552
|
"""
|
535
553
|
return GlobalNode(inputs=(self, *streams), kwargs=FrozenDict(kwargs))
|
536
554
|
|
@@ -567,6 +585,7 @@ class GlobalNode(Node):
|
|
567
585
|
# Execute the command
|
568
586
|
global_stream.run()
|
569
587
|
```
|
588
|
+
|
570
589
|
"""
|
571
590
|
return GlobalStream(node=self)
|
572
591
|
|
@@ -607,6 +626,7 @@ class GlobalStream(Stream, GlobalRunable):
|
|
607
626
|
global_stream = ffmpeg.output("output.mp4").global_args(y=True)
|
608
627
|
enhanced = global_stream._global_node(loglevel="quiet")
|
609
628
|
```
|
629
|
+
|
610
630
|
"""
|
611
631
|
inputs = (*self.node.inputs, *streams)
|
612
632
|
kwargs = dict(self.node.kwargs) | kwargs
|