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.
Files changed (71) hide show
  1. typed_ffmpeg/__init__.py +4 -1
  2. typed_ffmpeg/_version.py +2 -2
  3. typed_ffmpeg/base.py +4 -1
  4. typed_ffmpeg/codecs/__init__.py +2 -0
  5. typed_ffmpeg/codecs/decoders.py +1852 -1853
  6. typed_ffmpeg/codecs/encoders.py +2001 -1782
  7. typed_ffmpeg/codecs/schema.py +6 -12
  8. typed_ffmpeg/common/__init__.py +1 -0
  9. typed_ffmpeg/common/cache.py +9 -6
  10. typed_ffmpeg/common/schema.py +11 -0
  11. typed_ffmpeg/common/serialize.py +13 -7
  12. typed_ffmpeg/compile/__init__.py +1 -0
  13. typed_ffmpeg/compile/compile_cli.py +55 -8
  14. typed_ffmpeg/compile/compile_json.py +4 -0
  15. typed_ffmpeg/compile/compile_python.py +15 -0
  16. typed_ffmpeg/compile/context.py +15 -4
  17. typed_ffmpeg/compile/validate.py +9 -8
  18. typed_ffmpeg/dag/factory.py +2 -0
  19. typed_ffmpeg/dag/global_runnable/__init__.py +1 -0
  20. typed_ffmpeg/dag/global_runnable/global_args.py +2 -2
  21. typed_ffmpeg/dag/global_runnable/runnable.py +51 -11
  22. typed_ffmpeg/dag/io/__init__.py +1 -0
  23. typed_ffmpeg/dag/io/_input.py +20 -5
  24. typed_ffmpeg/dag/io/_output.py +24 -9
  25. typed_ffmpeg/dag/io/output_args.py +21 -7
  26. typed_ffmpeg/dag/nodes.py +20 -0
  27. typed_ffmpeg/dag/schema.py +19 -6
  28. typed_ffmpeg/dag/utils.py +2 -2
  29. typed_ffmpeg/exceptions.py +2 -1
  30. typed_ffmpeg/expressions.py +884 -0
  31. typed_ffmpeg/ffprobe/__init__.py +1 -0
  32. typed_ffmpeg/ffprobe/parse.py +7 -1
  33. typed_ffmpeg/ffprobe/probe.py +3 -1
  34. typed_ffmpeg/ffprobe/schema.py +83 -1
  35. typed_ffmpeg/ffprobe/xml2json.py +8 -2
  36. typed_ffmpeg/filters.py +540 -631
  37. typed_ffmpeg/formats/__init__.py +2 -0
  38. typed_ffmpeg/formats/demuxers.py +1869 -1921
  39. typed_ffmpeg/formats/muxers.py +1382 -1107
  40. typed_ffmpeg/formats/schema.py +6 -12
  41. typed_ffmpeg/info.py +8 -0
  42. typed_ffmpeg/options/__init__.py +15 -0
  43. typed_ffmpeg/options/codec.py +711 -0
  44. typed_ffmpeg/options/format.py +196 -0
  45. typed_ffmpeg/options/framesync.py +43 -0
  46. typed_ffmpeg/options/timeline.py +22 -0
  47. typed_ffmpeg/schema.py +15 -0
  48. typed_ffmpeg/sources.py +392 -381
  49. typed_ffmpeg/streams/__init__.py +2 -0
  50. typed_ffmpeg/streams/audio.py +1071 -882
  51. typed_ffmpeg/streams/av.py +9 -3
  52. typed_ffmpeg/streams/subtitle.py +3 -3
  53. typed_ffmpeg/streams/video.py +1873 -1725
  54. typed_ffmpeg/types.py +3 -2
  55. typed_ffmpeg/utils/__init__.py +1 -0
  56. typed_ffmpeg/utils/escaping.py +8 -4
  57. typed_ffmpeg/utils/frozendict.py +31 -1
  58. typed_ffmpeg/utils/lazy_eval/__init__.py +1 -0
  59. typed_ffmpeg/utils/lazy_eval/operator.py +75 -27
  60. typed_ffmpeg/utils/lazy_eval/schema.py +176 -4
  61. typed_ffmpeg/utils/run.py +2 -0
  62. typed_ffmpeg/utils/snapshot.py +3 -2
  63. typed_ffmpeg/utils/typing.py +2 -1
  64. typed_ffmpeg/utils/view.py +2 -1
  65. {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/METADATA +1 -1
  66. typed_ffmpeg_compatible-3.6.dist-info/RECORD +73 -0
  67. typed_ffmpeg_compatible-3.5.1.dist-info/RECORD +0 -67
  68. {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/WHEEL +0 -0
  69. {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/entry_points.txt +0 -0
  70. {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/licenses/LICENSE +0 -0
  71. {typed_ffmpeg_compatible-3.5.1.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -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(cmd, auto_fix=auto_fix)
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(cmd, auto_fix=auto_fix)
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(self._global_node().stream(), auto_fix=auto_fix)
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(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix)
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
- args = self.compile(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix)
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, overwrite_output=overwrite_output, auto_fix=auto_fix
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,
@@ -0,0 +1 @@
1
+ """Input/output utilities for FFmpeg DAG operations."""
@@ -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 InputNode
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.kwargs if decoder_options else {},
209
- demuxer_options.kwargs if demuxer_options else {},
221
+ decoder_options,
222
+ demuxer_options,
223
+ format_options,
224
+ codec_options,
210
225
  extra_options,
211
226
  ),
212
227
  ).stream()
@@ -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 FilterableStream, OutputNode, OutputStream
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.kwargs if encoder_options else {},
333
- muxer_options.kwargs if muxer_options else {},
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 | None = None,
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.kwargs if encoder_options else {},
345
- muxer_options.kwargs if muxer_options else {},
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