typed-ffmpeg-compatible 3.5.2__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 +23 -4
  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 +4 -3
  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 +6 -2
  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 +1 -0
  63. typed_ffmpeg/utils/typing.py +2 -1
  64. typed_ffmpeg/utils/view.py +2 -1
  65. {typed_ffmpeg_compatible-3.5.2.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.2.dist-info/RECORD +0 -67
  68. {typed_ffmpeg_compatible-3.5.2.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/WHEEL +0 -0
  69. {typed_ffmpeg_compatible-3.5.2.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/entry_points.txt +0 -0
  70. {typed_ffmpeg_compatible-3.5.2.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/licenses/LICENSE +0 -0
  71. {typed_ffmpeg_compatible-3.5.2.dist-info → typed_ffmpeg_compatible-3.6.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,11 @@
1
- from dataclasses import dataclass
1
+ """FFmpeg codec schema definitions."""
2
2
 
3
- from ..common.serialize import Serializable
4
- from ..utils.frozendict import FrozenDict
3
+ from ..schema import FFMpegOptionGroup
5
4
 
6
5
 
7
- @dataclass(frozen=True, kw_only=True)
8
- class FFMpegCodecOption(Serializable):
9
- kwargs: FrozenDict[str, str | int | float | bool] = FrozenDict({})
6
+ class FFMpegEncoderOption(FFMpegOptionGroup):
7
+ """FFmpeg encoder option group."""
10
8
 
11
9
 
12
- @dataclass(frozen=True, kw_only=True)
13
- class FFMpegEncoderOption(FFMpegCodecOption): ...
14
-
15
-
16
- @dataclass(frozen=True, kw_only=True)
17
- class FFMpegDecoderOption(FFMpegCodecOption): ...
10
+ class FFMpegDecoderOption(FFMpegOptionGroup):
11
+ """FFmpeg decoder option group."""
@@ -0,0 +1 @@
1
+ """FFmpeg common utilities package."""
@@ -1,3 +1,5 @@
1
+ """Cache utilities for FFmpeg operations."""
2
+
1
3
  from pathlib import Path
2
4
  from typing import TypeVar
3
5
 
@@ -11,7 +13,7 @@ cache_path.mkdir(exist_ok=True)
11
13
 
12
14
  def load(cls: type[T], id: str) -> T:
13
15
  """
14
- Load an object from the cache
16
+ Load an object from the cache.
15
17
 
16
18
  Args:
17
19
  cls: The class of the object
@@ -19,6 +21,7 @@ def load(cls: type[T], id: str) -> T:
19
21
 
20
22
  Returns:
21
23
  The loaded object
24
+
22
25
  """
23
26
  path = cache_path / f"{cls.__name__}/{id}.json"
24
27
 
@@ -29,11 +32,12 @@ def load(cls: type[T], id: str) -> T:
29
32
 
30
33
  def save(obj: T, id: str) -> None:
31
34
  """
32
- Save an object to the cache
35
+ Save an object to the cache.
33
36
 
34
37
  Args:
35
38
  obj: The object to save
36
39
  id: The id of the object
40
+
37
41
  """
38
42
  schema_path = cache_path / f"{obj.__class__.__name__}"
39
43
  schema_path.mkdir(exist_ok=True)
@@ -44,13 +48,14 @@ def save(obj: T, id: str) -> None:
44
48
 
45
49
  def list_all(cls: type[T]) -> list[T]:
46
50
  """
47
- List all objects of a class in the cache
51
+ List all objects of a class in the cache.
48
52
 
49
53
  Args:
50
54
  cls: The class of the objects
51
55
 
52
56
  Returns:
53
57
  A list of all objects of the class in the cache
58
+
54
59
  """
55
60
  path = cache_path / f"{cls.__name__}"
56
61
 
@@ -58,9 +63,7 @@ def list_all(cls: type[T]) -> list[T]:
58
63
 
59
64
 
60
65
  def clean(cls: type[T]) -> None:
61
- """
62
- Clean the cache for a class
63
- """
66
+ """Clean the cache for a class."""
64
67
  path = cache_path / f"{cls.__name__}"
65
68
  for i in path.glob("*.json"):
66
69
  i.unlink()
@@ -275,6 +275,7 @@ class FFMpegFilter(Serializable):
275
275
 
276
276
  Returns:
277
277
  A dictionary mapping parameter names to their values
278
+
278
279
  """
279
280
  return dict(self.pre)
280
281
 
@@ -288,6 +289,7 @@ class FFMpegFilter(Serializable):
288
289
 
289
290
  Returns:
290
291
  A simplified FFMpegFilterDef representation of this filter
292
+
291
293
  """
292
294
  return FFMpegFilterDef(
293
295
  name=self.name,
@@ -310,6 +312,7 @@ class FFMpegFilter(Serializable):
310
312
 
311
313
  Raises:
312
314
  AssertionError: If a dynamic input filter has no input formula
315
+
313
316
  """
314
317
  if self.is_filter_source:
315
318
  return set()
@@ -346,6 +349,7 @@ class FFMpegFilter(Serializable):
346
349
 
347
350
  Raises:
348
351
  AssertionError: If a dynamic output filter has no output formula
352
+
349
353
  """
350
354
  if self.is_filter_sink:
351
355
  return set()
@@ -386,6 +390,7 @@ class FFMpegFilter(Serializable):
386
390
  any known filter type
387
391
  AssertionError: If a sink filter has multiple input types or
388
392
  if a filter has no input types
393
+
389
394
  """
390
395
  if self.is_filter_sink:
391
396
  assert len(self.input_typings) == 1
@@ -423,6 +428,8 @@ class FFMpegFilter(Serializable):
423
428
 
424
429
  @serializable
425
430
  class FFMpegOptionFlag(int, Enum):
431
+ """FFmpeg option flags that define option behavior and characteristics."""
432
+
426
433
  OPT_FUNC_ARG = 1 << 0
427
434
  """
428
435
  The OPT_TYPE_FUNC option takes an argument.
@@ -567,6 +574,7 @@ class FFMpegOption(Serializable):
567
574
 
568
575
  Returns:
569
576
  True if this option is meant to be used with input files
577
+
570
578
  """
571
579
  return bool(self.flags & FFMpegOptionFlag.OPT_INPUT)
572
580
 
@@ -577,6 +585,7 @@ class FFMpegOption(Serializable):
577
585
 
578
586
  Returns:
579
587
  True if this option is meant to be used with output files
588
+
580
589
  """
581
590
  return bool(self.flags & FFMpegOptionFlag.OPT_OUTPUT)
582
591
 
@@ -588,6 +597,7 @@ class FFMpegOption(Serializable):
588
597
  Returns:
589
598
  True if this option is a global option that doesn't apply to
590
599
  specific input or output files
600
+
591
601
  """
592
602
  return (
593
603
  not self.is_input_option
@@ -605,5 +615,6 @@ class FFMpegOption(Serializable):
605
615
 
606
616
  Returns:
607
617
  True if this option can be used with stream specifiers
618
+
608
619
  """
609
620
  return bool(self.flags & FFMpegOptionFlag.OPT_SPEC)
@@ -37,6 +37,7 @@ def serializable(
37
37
 
38
38
  Returns:
39
39
  The class itself
40
+
40
41
  """
41
42
  assert cls.__name__ not in CLASS_REGISTRY, (
42
43
  f"Class {cls.__name__} already registered"
@@ -47,11 +48,10 @@ def serializable(
47
48
 
48
49
 
49
50
  class Serializable:
50
- """
51
- A base class for all serializable classes.
52
- """
51
+ """A base class for all serializable classes."""
53
52
 
54
53
  def __init_subclass__(cls, **kwargs: Any) -> None:
54
+ """Register the subclass in the serialization registry."""
55
55
  super().__init_subclass__(**kwargs)
56
56
  serializable(cls)
57
57
 
@@ -79,6 +79,7 @@ def load_class(name: str) -> type[Serializable] | type[Enum]:
79
79
  # Create an instance
80
80
  node = FilterNode(name='scale', ...)
81
81
  ```
82
+
82
83
  """
83
84
  assert name in CLASS_REGISTRY, f"Class {name} not registered"
84
85
  return CLASS_REGISTRY[name]
@@ -102,11 +103,13 @@ def frozen(v: Any) -> Any:
102
103
  Example:
103
104
  ```python
104
105
  # Convert a nested structure to immutable form
105
- frozen_data = frozen(
106
- {"options": ["option1", "option2"], "settings": {"key": "value"}}
107
- )
106
+ frozen_data = frozen({
107
+ "options": ["option1", "option2"],
108
+ "settings": {"key": "value"},
109
+ })
108
110
  # Result: FrozenDict with tuple instead of list and nested FrozenDict
109
111
  ```
112
+
110
113
  """
111
114
  if isinstance(v, list):
112
115
  return tuple(frozen(i) for i in v)
@@ -141,6 +144,7 @@ def object_hook(obj: Any) -> Any:
141
144
  }
142
145
  # Will be converted to a FilterNode instance
143
146
  ```
147
+
144
148
  """
145
149
  if isinstance(obj, dict):
146
150
  if obj.get("__class__"):
@@ -176,6 +180,7 @@ def loads(raw: str) -> Any:
176
180
  filter_node = loads(json_str)
177
181
  # filter_node is now a FilterNode instance
178
182
  ```
183
+
179
184
  """
180
185
  return json.loads(raw, object_hook=object_hook)
181
186
 
@@ -202,8 +207,8 @@ def to_dict_with_class_info(instance: Any) -> Any:
202
207
  serializable = to_dict_with_class_info(filter_node)
203
208
  # serializable now contains class information and all attributes
204
209
  ```
205
- """
206
210
 
211
+ """
207
212
  if isinstance(instance, dict | FrozenDict):
208
213
  return {k: to_dict_with_class_info(v) for k, v in instance.items()}
209
214
  elif isinstance(instance, list):
@@ -251,6 +256,7 @@ def dumps(instance: Any) -> str:
251
256
  # json_str can be saved to a file and later deserialized
252
257
  # with loads() to reconstruct the original object
253
258
  ```
259
+
254
260
  """
255
261
  obj = to_dict_with_class_info(instance)
256
262
  return json.dumps(obj, indent=2)
@@ -0,0 +1 @@
1
+ """FFmpeg compilation utilities package."""
@@ -58,6 +58,7 @@ def get_options_dict() -> dict[str, FFMpegOption]:
58
58
 
59
59
  Returns:
60
60
  Dictionary mapping option names to their FFMpegOption definitions
61
+
61
62
  """
62
63
  options = load(list[FFMpegOption], "options")
63
64
  return {option.name: option for option in options}
@@ -69,6 +70,7 @@ def get_filter_dict() -> dict[str, FFMpegFilter]:
69
70
 
70
71
  Returns:
71
72
  Dictionary mapping filter names to their FFMpegFilter definitions
73
+
72
74
  """
73
75
  filters = load(list[FFMpegFilter], "filters")
74
76
  return {filter.name: filter for filter in filters}
@@ -89,6 +91,7 @@ def parse_options(tokens: list[str]) -> dict[str, list[str | None | bool]]:
89
91
 
90
92
  Returns:
91
93
  Dictionary mapping option names to lists of their values
94
+
92
95
  """
93
96
  parsed_options: dict[str, list[str | None | bool]] = defaultdict(list)
94
97
 
@@ -134,6 +137,8 @@ def parse_stream_selector(
134
137
 
135
138
  Raises:
136
139
  AssertionError: If the stream label is not found in the mapping
140
+ FFMpegValueError: If the stream type is unknown
141
+
137
142
  """
138
143
  selector = selector.strip("[]")
139
144
 
@@ -182,6 +187,7 @@ def _is_filename(token: str) -> bool:
182
187
 
183
188
  Returns:
184
189
  True if the token is a filename, False otherwise
190
+
185
191
  """
186
192
  # not start with - and has ext
187
193
  return not token.startswith("-") and len(token.split(".")) > 1
@@ -205,6 +211,7 @@ def parse_output(
205
211
 
206
212
  Returns:
207
213
  List of OutputStream objects representing the output specifications
214
+
208
215
  """
209
216
  tokens = source.copy()
210
217
 
@@ -272,6 +279,7 @@ def parse_input(
272
279
 
273
280
  Returns:
274
281
  Dictionary mapping input indices to their FilterableStream objects
282
+
275
283
  """
276
284
  output: list[AVStream] = []
277
285
 
@@ -325,6 +333,10 @@ def parse_filter_complex(
325
333
 
326
334
  Returns:
327
335
  Updated stream mapping with new filter outputs added
336
+
337
+ Raises:
338
+ FFMpegValueError: If the stream type is unknown
339
+
328
340
  """
329
341
  # Use re.split with negative lookbehind to handle escaped semicolons
330
342
  filter_units = re.split(r"(?<!\\);", filter_complex)
@@ -425,6 +437,7 @@ def parse_global(
425
437
  Example:
426
438
  For tokens like ['-y', '-loglevel', 'quiet', '-i', 'input.mp4']:
427
439
  Returns ({'y': True, 'loglevel': 'quiet'}, ['-i', 'input.mp4'])
440
+
428
441
  """
429
442
  options = parse_options(tokens[: tokens.index("-i")])
430
443
  remaining_tokens = tokens[tokens.index("-i") :]
@@ -466,6 +479,7 @@ def parse(cli: str) -> Stream:
466
479
  "ffmpeg -i input.mp4 -filter_complex '[0:v]scale=1280:720[v]' -map '[v]' output.mp4"
467
480
  )
468
481
  ```
482
+
469
483
  """
470
484
  # ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
471
485
  ffmpeg_options = get_options_dict()
@@ -523,6 +537,7 @@ def compile(
523
537
 
524
538
  Returns:
525
539
  A command-line string that can be passed to FFmpeg
540
+
526
541
  """
527
542
  return "ffmpeg " + command_line(
528
543
  compile_as_list(stream, auto_fix, use_filter_complex_script)
@@ -596,8 +611,8 @@ def compile_as_list(
596
611
  args
597
612
  ) # ['ffmpeg', '-i', 'input.mp4', '-filter_complex', '...', 'output.mp4']
598
613
  ```
599
- """
600
614
 
615
+ """
601
616
  stream = validate(stream, auto_fix=auto_fix)
602
617
  node = stream.node
603
618
  context = DAGContext.build(node)
@@ -671,6 +686,7 @@ def get_stream_label(stream: Stream, context: DAGContext | None = None) -> str:
671
686
 
672
687
  Raises:
673
688
  FFMpegValueError: If the stream has an unknown type or node type
689
+
674
690
  """
675
691
  from ..streams.audio import AudioStream
676
692
  from ..streams.av import AVStream
@@ -741,8 +757,8 @@ def get_args_filter_node(node: FilterNode, context: DAGContext) -> list[str]:
741
757
  Example:
742
758
  For a scale filter with width=1280 and height=720, this might return:
743
759
  ['[0:v]', 'scale=', 'width=1280:height=720', '[s0]']
744
- """
745
760
 
761
+ """
746
762
  incoming_labels = "".join(f"[{get_stream_label(k, context)}]" for k in node.inputs)
747
763
  outputs = context.get_outgoing_streams(node)
748
764
 
@@ -794,6 +810,7 @@ def get_args_input_node(node: InputNode, context: DAGContext) -> list[str]:
794
810
  Example:
795
811
  For an input file "input.mp4" with options like seeking to 10 seconds:
796
812
  ['-ss', '10', '-i', 'input.mp4']
813
+
797
814
  """
798
815
  commands = []
799
816
  for key, value in node.kwargs.items():
@@ -827,6 +844,7 @@ def get_args_output_node(node: OutputNode, context: DAGContext) -> list[str]:
827
844
  Example:
828
845
  For an output file "output.mp4" with H.264 video codec:
829
846
  ['-map', '[v0]', '-c:v', 'libx264', 'output.mp4']
847
+
830
848
  """
831
849
  # !handle mapping
832
850
  commands = []
@@ -888,6 +906,7 @@ def get_args_global_node(node: GlobalNode, context: DAGContext) -> list[str]:
888
906
  Example:
889
907
  For global options like overwrite and quiet logging:
890
908
  ['-y', '-loglevel', 'quiet']
909
+
891
910
  """
892
911
  commands = []
893
912
  for key, value in node.kwargs.items():
@@ -919,8 +938,8 @@ def get_args(node: Node, context: DAGContext | None = None) -> list[str]:
919
938
 
920
939
  Raises:
921
940
  FFMpegValueError: If the node type is not recognized
922
- """
923
941
 
942
+ """
924
943
  context = context or DAGContext.build(node)
925
944
 
926
945
  match node:
@@ -955,8 +974,8 @@ def get_node_label(node: Node, context: DAGContext) -> str:
955
974
 
956
975
  Raises:
957
976
  AssertionError: If the node is not an InputNode or FilterNode
958
- """
959
977
 
978
+ """
960
979
  node_id = context.node_ids[node]
961
980
  match node:
962
981
  case InputNode():
@@ -1,3 +1,5 @@
1
+ """JSON compilation utilities for FFmpeg streams."""
2
+
1
3
  from ..common.serialize import dumps, loads
2
4
  from ..dag.schema import Stream
3
5
  from .validate import validate
@@ -16,6 +18,7 @@ def compile(stream: Stream, auto_fix: bool = True) -> str:
16
18
 
17
19
  Returns:
18
20
  A JSON string that can be passed to FFmpeg
21
+
19
22
  """
20
23
  stream = validate(stream, auto_fix=auto_fix)
21
24
 
@@ -34,5 +37,6 @@ def parse(json: str) -> Stream:
34
37
 
35
38
  Returns:
36
39
  A Stream object
40
+
37
41
  """
38
42
  return loads(json)
@@ -1,3 +1,5 @@
1
+ """Python code compilation utilities for FFmpeg streams."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  from collections.abc import Mapping
@@ -39,6 +41,7 @@ def filter_stream_typed_index(
39
41
 
40
42
  Returns:
41
43
  The index of the matched stream in the outgoing streams of the node.
44
+
42
45
  """
43
46
  matched_outgoing_streams = [
44
47
  k
@@ -69,6 +72,10 @@ def get_input_var_name(
69
72
 
70
73
  Returns:
71
74
  The input variable name for the stream.
75
+
76
+ Raises:
77
+ ValueError: If the stream type is unknown
78
+
72
79
  """
73
80
  match stream:
74
81
  case AVStream():
@@ -127,6 +134,10 @@ def get_output_var_name(node: Node, context: DAGContext) -> str:
127
134
 
128
135
  Returns:
129
136
  The output variable name for the node.
137
+
138
+ Raises:
139
+ ValueError: If the node type is unknown
140
+
130
141
  """
131
142
  match node:
132
143
  case InputNode():
@@ -153,6 +164,7 @@ def compile_kwargs(kwargs: Mapping[str, Any]) -> str:
153
164
 
154
165
  Returns:
155
166
  The compiled kwargs.
167
+
156
168
  """
157
169
  return ", ".join(f"{k}={repr(v)}" for k, v in kwargs.items())
158
170
 
@@ -169,6 +181,7 @@ def compile_fluent(code: list[str]) -> list[str]:
169
181
 
170
182
  Returns:
171
183
  The compiled code.
184
+
172
185
  """
173
186
  buffer = [k.split("=", 1)[:2] for k in code]
174
187
 
@@ -209,6 +222,7 @@ def compile(stream: Stream, auto_fix: bool = True, fluent: bool = True) -> str:
209
222
 
210
223
  Returns:
211
224
  The compiled python code.
225
+
212
226
  """
213
227
  stream = validate(stream, auto_fix=auto_fix)
214
228
  node = stream.node
@@ -314,6 +328,7 @@ def parse(code: str) -> Stream:
314
328
 
315
329
  Returns:
316
330
  The parsed stream.
331
+
317
332
  """
318
333
  local_vars: dict[str, Any] = {}
319
334
  exec(code, {}, local_vars)
@@ -35,6 +35,7 @@ def _remove_duplicates(seq: Iterable[T]) -> list[T]:
35
35
 
36
36
  Returns:
37
37
  A new list with duplicates removed, preserving the original order
38
+
38
39
  """
39
40
  seen = set()
40
41
  output: list[T] = []
@@ -62,6 +63,7 @@ def _collect(node: Node) -> tuple[list[Node], list[Stream]]:
62
63
  A tuple containing two lists:
63
64
  - A list of all nodes in the upstream path (including the starting node)
64
65
  - A list of all streams connecting these nodes
66
+
65
67
  """
66
68
  nodes: list[Node] = [node]
67
69
  streams: list[Stream] = list(node.inputs)
@@ -124,6 +126,7 @@ class DAGContext:
124
126
 
125
127
  Returns:
126
128
  A fully initialized DAGContext containing all nodes and streams in the graph
129
+
127
130
  """
128
131
  nodes, streams = _collect(node)
129
132
 
@@ -144,6 +147,7 @@ class DAGContext:
144
147
 
145
148
  Returns:
146
149
  A sorted list of all nodes in the graph
150
+
147
151
  """
148
152
  return sorted(self.nodes, key=lambda node: len(node.upstream_nodes))
149
153
 
@@ -159,6 +163,7 @@ class DAGContext:
159
163
 
160
164
  Returns:
161
165
  A sorted list of all streams in the graph
166
+
162
167
  """
163
168
  return sorted(
164
169
  self.streams,
@@ -177,6 +182,7 @@ class DAGContext:
177
182
 
178
183
  Returns:
179
184
  A dictionary mapping streams to their destination nodes and connection indices
185
+
180
186
  """
181
187
  outgoing_nodes: dict[Stream, list[tuple[Node, int]]] = defaultdict(list)
182
188
 
@@ -197,8 +203,8 @@ class DAGContext:
197
203
 
198
204
  Returns:
199
205
  A dictionary mapping nodes to their output streams
200
- """
201
206
 
207
+ """
202
208
  outgoing_streams: dict[Node, list[Stream]] = defaultdict(list)
203
209
 
204
210
  for stream in self.streams:
@@ -210,10 +216,13 @@ class DAGContext:
210
216
  def node_ids(self) -> dict[Node, int]:
211
217
  """
212
218
  Get a mapping of nodes to their unique integer IDs.
219
+
213
220
  This property assigns a unique integer ID to each node in the graph,
214
221
  based on the node type and its position in the processing chain.
222
+
215
223
  Returns:
216
- A dictionary mapping nodes to their unique integer IDs
224
+ A dictionary mapping nodes to their unique integer IDs.
225
+
217
226
  """
218
227
  node_index: dict[type[Node], int] = defaultdict(int)
219
228
  node_ids: dict[Node, int] = {}
@@ -239,8 +248,8 @@ class DAGContext:
239
248
 
240
249
  Returns:
241
250
  A dictionary mapping nodes to their string labels
242
- """
243
251
 
252
+ """
244
253
  input_node_index = 0
245
254
  filter_node_index = 0
246
255
  node_labels: dict[Node, str] = {}
@@ -271,6 +280,7 @@ class DAGContext:
271
280
 
272
281
  Returns:
273
282
  A list of (node, input_index) tuples for nodes that receive this stream
283
+
274
284
  """
275
285
  return self.outgoing_nodes[stream]
276
286
 
@@ -292,8 +302,8 @@ class DAGContext:
292
302
 
293
303
  Raises:
294
304
  AssertionError: If the node is not an InputNode or FilterNode
295
- """
296
305
 
306
+ """
297
307
  return self.node_labels[node]
298
308
 
299
309
  @override
@@ -311,5 +321,6 @@ class DAGContext:
311
321
 
312
322
  Returns:
313
323
  A list of streams that originate from this node
324
+
314
325
  """
315
326
  return self.outgoing_streams[node]
@@ -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:
@@ -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
  {