typed-ffmpeg-compatible 2.6.0__py3-none-any.whl → 2.6.2__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/base.py CHANGED
@@ -6,7 +6,13 @@ from typing import Any
6
6
 
7
7
  from .dag.io._input import input
8
8
  from .dag.io._output import output
9
- from .dag.nodes import FilterableStream, FilterNode, GlobalNode, GlobalStream, OutputStream
9
+ from .dag.nodes import (
10
+ FilterableStream,
11
+ FilterNode,
12
+ GlobalNode,
13
+ GlobalStream,
14
+ OutputStream,
15
+ )
10
16
  from .schema import StreamType
11
17
  from .streams.audio import AudioStream
12
18
  from .streams.video import VideoStream
@@ -26,7 +32,10 @@ def merge_outputs(*streams: OutputStream) -> GlobalStream:
26
32
 
27
33
 
28
34
  def vfilter(
29
- *streams: FilterableStream, name: str, input_typings: tuple[StreamType, ...] = (StreamType.video,), **kwargs: Any
35
+ *streams: FilterableStream,
36
+ name: str,
37
+ input_typings: tuple[StreamType, ...] = (StreamType.video,),
38
+ **kwargs: Any,
30
39
  ) -> VideoStream:
31
40
  """
32
41
  Apply a custom video filter which has only one output to this stream
@@ -53,7 +62,10 @@ def vfilter(
53
62
 
54
63
 
55
64
  def afilter(
56
- *streams: FilterableStream, name: str, input_typings: tuple[StreamType, ...] = (StreamType.audio,), **kwargs: Any
65
+ *streams: FilterableStream,
66
+ name: str,
67
+ input_typings: tuple[StreamType, ...] = (StreamType.audio,),
68
+ **kwargs: Any,
57
69
  ) -> AudioStream:
58
70
  """
59
71
  Apply a custom audio filter which has only one output to this stream
@@ -84,7 +96,7 @@ def filter_multi_output(
84
96
  name: str,
85
97
  input_typings: tuple[StreamType, ...] = (),
86
98
  output_tyings: tuple[StreamType, ...] = (),
87
- **kwargs: Any
99
+ **kwargs: Any,
88
100
  ) -> FilterNode:
89
101
  """
90
102
  Apply a custom filter which has multiple outputs to this stream
@@ -111,4 +123,11 @@ def filter_multi_output(
111
123
  )
112
124
 
113
125
 
114
- __all__ = ["input", "output", "merge_outputs", "vfilter", "afilter", "filter_multi_output"]
126
+ __all__ = [
127
+ "input",
128
+ "output",
129
+ "merge_outputs",
130
+ "vfilter",
131
+ "afilter",
132
+ "filter_multi_output",
133
+ ]
@@ -118,8 +118,10 @@ class FFMpegFilter:
118
118
  def to_def(self) -> FFMpegFilterDef:
119
119
  return FFMpegFilterDef(
120
120
  name=self.name,
121
- typings_input=self.formula_typings_input or tuple(k.type.value for k in self.stream_typings_input),
122
- typings_output=self.formula_typings_output or tuple(k.type.value for k in self.stream_typings_output),
121
+ typings_input=self.formula_typings_input
122
+ or tuple(k.type.value for k in self.stream_typings_input),
123
+ typings_output=self.formula_typings_output
124
+ or tuple(k.type.value for k in self.stream_typings_output),
123
125
  )
124
126
 
125
127
  @property
@@ -131,13 +133,18 @@ class FFMpegFilter:
131
133
  else:
132
134
  assert self.formula_typings_input, f"{self.name} has no input"
133
135
  if "video" not in self.formula_typings_input:
134
- assert "audio" in self.formula_typings_input, f"{self.name} has no video input"
136
+ assert "audio" in self.formula_typings_input, (
137
+ f"{self.name} has no video input"
138
+ )
135
139
  return {StreamType.audio}
136
140
  elif "audio" not in self.formula_typings_input:
137
- assert "video" in self.formula_typings_input, f"{self.name} has no audio input"
141
+ assert "video" in self.formula_typings_input, (
142
+ f"{self.name} has no audio input"
143
+ )
138
144
  return {StreamType.video}
139
145
  assert (
140
- "video" in self.formula_typings_input and "audio" in self.formula_typings_input
146
+ "video" in self.formula_typings_input
147
+ and "audio" in self.formula_typings_input
141
148
  ), f"{self.name} has no video or audio input"
142
149
  return {StreamType.video, StreamType.audio}
143
150
 
@@ -150,13 +157,18 @@ class FFMpegFilter:
150
157
  else:
151
158
  assert self.formula_typings_output, f"{self.name} has no output"
152
159
  if "video" not in self.formula_typings_output:
153
- assert "audio" in self.formula_typings_output, f"{self.name} has no video output"
160
+ assert "audio" in self.formula_typings_output, (
161
+ f"{self.name} has no video output"
162
+ )
154
163
  return {StreamType.audio}
155
164
  elif "audio" not in self.formula_typings_output:
156
- assert "video" in self.formula_typings_output, f"{self.name} has no audio output"
165
+ assert "video" in self.formula_typings_output, (
166
+ f"{self.name} has no audio output"
167
+ )
157
168
  return {StreamType.video}
158
169
  assert (
159
- "video" in self.formula_typings_output and "audio" in self.formula_typings_output
170
+ "video" in self.formula_typings_output
171
+ and "audio" in self.formula_typings_output
160
172
  ), f"{self.name} has no video or audio output"
161
173
  return {StreamType.video, StreamType.audio}
162
174
 
@@ -301,7 +313,11 @@ class FFMpegOption:
301
313
 
302
314
  @property
303
315
  def is_global_option(self) -> bool:
304
- return not self.is_input_option and not self.is_output_option and not (self.flags & FFMpegOptionFlag.OPT_EXIT)
316
+ return (
317
+ not self.is_input_option
318
+ and not self.is_output_option
319
+ and not (self.flags & FFMpegOptionFlag.OPT_EXIT)
320
+ )
305
321
 
306
322
  @property
307
323
  def is_support_stream_specifier(self) -> bool:
@@ -1,4 +1,4 @@
1
- from __future__ import absolute_import, annotations
1
+ from __future__ import annotations
2
2
 
3
3
  import importlib
4
4
  import json
@@ -21,7 +21,9 @@ def load_class(path: str, strict: bool = True) -> Any:
21
21
  The class.
22
22
  """
23
23
  if strict:
24
- assert path.startswith("ffmpeg."), f"Only support loading class from ffmpeg package: {path}"
24
+ assert path.startswith("ffmpeg."), (
25
+ f"Only support loading class from ffmpeg package: {path}"
26
+ )
25
27
 
26
28
  module_path, class_name = path.rsplit(".", 1)
27
29
  module = importlib.import_module(module_path)
@@ -65,7 +67,7 @@ def object_hook(obj: Any, strict: bool = True) -> Any:
65
67
  # NOTE: in our application, the dataclass is always frozen
66
68
  return cls(**{k: frozen(v) for k, v in obj.items()})
67
69
 
68
- return cls(**{k: v for k, v in obj.items()})
70
+ return cls(**dict(obj.items()))
69
71
 
70
72
  return obj
71
73
 
@@ -107,7 +109,10 @@ def to_dict_with_class_info(instance: Any) -> Any:
107
109
  elif is_dataclass(instance):
108
110
  return {
109
111
  "__class__": f"{instance.__class__.__module__}.{instance.__class__.__name__}",
110
- **{k.name: to_dict_with_class_info(getattr(instance, k.name)) for k in fields(instance)},
112
+ **{
113
+ k.name: to_dict_with_class_info(getattr(instance, k.name))
114
+ for k in fields(instance)
115
+ },
111
116
  }
112
117
  elif isinstance(instance, Enum):
113
118
  return {
@@ -1,4 +1,11 @@
1
- from .nodes import FilterableStream, FilterNode, GlobalNode, InputNode, OutputNode, OutputStream
1
+ from .nodes import (
2
+ FilterableStream,
3
+ FilterNode,
4
+ GlobalNode,
5
+ InputNode,
6
+ OutputNode,
7
+ OutputStream,
8
+ )
2
9
  from .schema import Node, Stream
3
10
 
4
11
  __all__ = [
@@ -105,7 +105,10 @@ class DAGContext:
105
105
  """
106
106
  All streams in the graph sorted by the number of upstream nodes and the index of the stream.
107
107
  """
108
- return sorted(self.streams, key=lambda stream: (len(stream.node.upstream_nodes), stream.index))
108
+ return sorted(
109
+ self.streams,
110
+ key=lambda stream: (len(stream.node.upstream_nodes), stream.index),
111
+ )
109
112
 
110
113
  @cached_property
111
114
  def outgoing_nodes(self) -> dict[Stream, list[tuple[Node, int]]]:
@@ -180,7 +183,9 @@ class DAGContext:
180
183
  The label of the node.
181
184
  """
182
185
 
183
- assert isinstance(node, (InputNode, FilterNode)), "Only input and filter nodes have labels"
186
+ assert isinstance(node, (InputNode, FilterNode)), (
187
+ "Only input and filter nodes have labels"
188
+ )
184
189
  return self.node_labels[node]
185
190
 
186
191
  @override
@@ -7,23 +7,43 @@ from ..utils.run import ignore_default
7
7
  from .nodes import FilterableStream, FilterNode
8
8
 
9
9
 
10
- def filter_node_factory(filter: FFMpegFilterDef, *inputs: FilterableStream, **kwargs: Any) -> FilterNode:
10
+ def filter_node_factory(
11
+ ffmpeg_filter_def: FFMpegFilterDef, *inputs: FilterableStream, **kwargs: Any
12
+ ) -> FilterNode:
11
13
  for k, v in kwargs.items():
12
14
  if isinstance(v, Auto):
13
- kwargs[k] = eval(v, {"StreamType": StreamType, "re": re, **kwargs, "streams": inputs})
15
+ kwargs[k] = eval(
16
+ v, {"StreamType": StreamType, "re": re, **kwargs, "streams": inputs}
17
+ )
14
18
 
15
- if isinstance(filter.typings_input, str):
16
- input_typings = tuple(eval(filter.typings_input, {"StreamType": StreamType, "re": re, **kwargs}))
19
+ if isinstance(ffmpeg_filter_def.typings_input, str):
20
+ input_typings = tuple(
21
+ eval(
22
+ ffmpeg_filter_def.typings_input,
23
+ {"StreamType": StreamType, "re": re, **kwargs},
24
+ )
25
+ )
17
26
  else:
18
- input_typings = tuple(StreamType.video if k == "video" else StreamType.audio for k in filter.typings_input)
27
+ input_typings = tuple(
28
+ StreamType.video if k == "video" else StreamType.audio
29
+ for k in ffmpeg_filter_def.typings_input
30
+ )
19
31
 
20
- if isinstance(filter.typings_output, str):
21
- output_typings = tuple(eval(filter.typings_output, {"StreamType": StreamType, "re": re, **kwargs}))
32
+ if isinstance(ffmpeg_filter_def.typings_output, str):
33
+ output_typings = tuple(
34
+ eval(
35
+ ffmpeg_filter_def.typings_output,
36
+ {"StreamType": StreamType, "re": re, **kwargs},
37
+ )
38
+ )
22
39
  else:
23
- output_typings = tuple(StreamType.video if k == "video" else StreamType.audio for k in filter.typings_output)
40
+ output_typings = tuple(
41
+ StreamType.video if k == "video" else StreamType.audio
42
+ for k in ffmpeg_filter_def.typings_output
43
+ )
24
44
 
25
45
  return FilterNode(
26
- name=filter.name,
46
+ name=ffmpeg_filter_def.name,
27
47
  input_typings=input_typings,
28
48
  output_typings=output_typings,
29
49
  inputs=inputs,
@@ -12,8 +12,7 @@ if TYPE_CHECKING:
12
12
 
13
13
  class GlobalArgs(ABC):
14
14
  @abstractmethod
15
- def _global_node(self, *streams: OutputStream, **kwargs: Any) -> GlobalNode:
16
- ...
15
+ def _global_node(self, *streams: OutputStream, **kwargs: Any) -> GlobalNode: ...
17
16
 
18
17
  def global_args(
19
18
  self,
@@ -82,7 +82,9 @@ class GlobalRunable(GlobalArgs):
82
82
  Returns:
83
83
  the command-line
84
84
  """
85
- return command_line(self.compile(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix))
85
+ return command_line(
86
+ self.compile(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix)
87
+ )
86
88
 
87
89
  def run_async(
88
90
  self,
@@ -115,7 +117,9 @@ class GlobalRunable(GlobalArgs):
115
117
  stdout_stream = subprocess.PIPE if pipe_stdout or quiet else None
116
118
  stderr_stream = subprocess.PIPE if pipe_stderr or quiet else None
117
119
 
118
- logger.info(f"Running command: {self.compile_line(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix)}")
120
+ logger.info(
121
+ f"Running command: {self.compile_line(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix)}"
122
+ )
119
123
 
120
124
  return subprocess.Popen(
121
125
  args,
@@ -166,7 +170,9 @@ class GlobalRunable(GlobalArgs):
166
170
  if retcode:
167
171
  raise FFMpegExecuteError(
168
172
  retcode=retcode,
169
- cmd=self.compile_line(cmd, overwrite_output=overwrite_output, auto_fix=auto_fix),
173
+ cmd=self.compile_line(
174
+ cmd, overwrite_output=overwrite_output, auto_fix=auto_fix
175
+ ),
170
176
  stdout=stdout,
171
177
  stderr=stderr,
172
178
  )
@@ -194,4 +194,6 @@ def input(
194
194
  if v is not None
195
195
  }
196
196
 
197
- return InputNode(filename=str(filename), kwargs=tuple((options | (extra_options or {})).items())).stream()
197
+ return InputNode(
198
+ filename=str(filename), kwargs=tuple((options | (extra_options or {})).items())
199
+ ).stream()
@@ -316,5 +316,7 @@ def output(
316
316
  }
317
317
 
318
318
  return OutputNode(
319
- inputs=streams, filename=str(filename), kwargs=tuple((options | (extra_options or {})).items())
319
+ inputs=streams,
320
+ filename=str(filename),
321
+ kwargs=tuple((options | (extra_options or {})).items()),
320
322
  ).stream()
@@ -13,12 +13,13 @@ if TYPE_CHECKING:
13
13
 
14
14
  class OutputArgs(ABC):
15
15
  @abstractmethod
16
- def _output_node(self, *streams: FilterableStream, filename: str | Path, **kwargs: Any) -> OutputNode:
17
- ...
16
+ def _output_node(
17
+ self, *streams: FilterableStream, filename: str | Path, **kwargs: Any
18
+ ) -> OutputNode: ...
18
19
 
19
20
  def output(
20
21
  self,
21
- *streams: "FilterableStream",
22
+ *streams: FilterableStream,
22
23
  filename: str | Path,
23
24
  f: String = None,
24
25
  c: String = None,
@@ -324,4 +325,6 @@ class OutputArgs(ABC):
324
325
  if v is not None
325
326
  }
326
327
 
327
- return self._output_node(*streams, filename=filename, **options, **(extra_options or {})).stream()
328
+ return self._output_node(
329
+ *streams, filename=filename, **options, **(extra_options or {})
330
+ ).stream()
typed_ffmpeg/dag/nodes.py CHANGED
@@ -55,7 +55,7 @@ class FilterNode(Node):
55
55
  def repr(self) -> str:
56
56
  return self.name
57
57
 
58
- def video(self, index: int) -> "VideoStream":
58
+ def video(self, index: int) -> VideoStream:
59
59
  """
60
60
  Return the video stream at the specified index
61
61
 
@@ -67,12 +67,16 @@ class FilterNode(Node):
67
67
  """
68
68
  from ..streams.video import VideoStream
69
69
 
70
- video_outputs = [i for i, k in enumerate(self.output_typings) if k == StreamType.video]
70
+ video_outputs = [
71
+ i for i, k in enumerate(self.output_typings) if k == StreamType.video
72
+ ]
71
73
  if not len(video_outputs) > index:
72
- raise FFMpegValueError(f"Specified index {index} is out of range for video outputs {len(video_outputs)}")
74
+ raise FFMpegValueError(
75
+ f"Specified index {index} is out of range for video outputs {len(video_outputs)}"
76
+ )
73
77
  return VideoStream(node=self, index=video_outputs[index])
74
78
 
75
- def audio(self, index: int) -> "AudioStream":
79
+ def audio(self, index: int) -> AudioStream:
76
80
  """
77
81
  Return the audio stream at the specified index
78
82
 
@@ -84,9 +88,13 @@ class FilterNode(Node):
84
88
  """
85
89
  from ..streams.audio import AudioStream
86
90
 
87
- audio_outputs = [i for i, k in enumerate(self.output_typings) if k == StreamType.audio]
91
+ audio_outputs = [
92
+ i for i, k in enumerate(self.output_typings) if k == StreamType.audio
93
+ ]
88
94
  if not len(audio_outputs) > index:
89
- raise FFMpegValueError(f"Specified index {index} is out of range for audio outputs {len(audio_outputs)}")
95
+ raise FFMpegValueError(
96
+ f"Specified index {index} is out of range for audio outputs {len(audio_outputs)}"
97
+ )
90
98
 
91
99
  return AudioStream(node=self, index=audio_outputs[index])
92
100
 
@@ -97,12 +105,16 @@ class FilterNode(Node):
97
105
  super().__post_init__()
98
106
 
99
107
  if len(self.inputs) != len(self.input_typings):
100
- raise FFMpegValueError(f"Expected {len(self.input_typings)} inputs, got {len(self.inputs)}")
108
+ raise FFMpegValueError(
109
+ f"Expected {len(self.input_typings)} inputs, got {len(self.inputs)}"
110
+ )
101
111
 
102
112
  stream: FilterableStream
103
113
  expected_type: StreamType
104
114
 
105
- for i, (stream, expected_type) in enumerate(zip(self.inputs, self.input_typings)):
115
+ for i, (stream, expected_type) in enumerate(
116
+ zip(self.inputs, self.input_typings)
117
+ ):
106
118
  if expected_type == StreamType.video:
107
119
  if not isinstance(stream, VideoStream):
108
120
  raise FFMpegTypeError(
@@ -132,7 +144,9 @@ class FilterNode(Node):
132
144
 
133
145
  commands = []
134
146
  for key, value in self.kwargs:
135
- assert not isinstance(value, LazyValue), f"LazyValue should have been evaluated: {key}={value}"
147
+ assert not isinstance(value, LazyValue), (
148
+ f"LazyValue should have been evaluated: {key}={value}"
149
+ )
136
150
 
137
151
  # Note: the -nooption syntax cannot be used for boolean AVOptions, use -option 0/-option 1.
138
152
  if isinstance(value, bool):
@@ -142,7 +156,12 @@ class FilterNode(Node):
142
156
  commands += [f"{key}={escape(value)}"]
143
157
 
144
158
  if commands:
145
- return [incoming_labels] + [f"{self.name}="] + [escape(":".join(commands), "\\'[],;")] + [outgoing_labels]
159
+ return (
160
+ [incoming_labels]
161
+ + [f"{self.name}="]
162
+ + [escape(":".join(commands), "\\'[],;")]
163
+ + [outgoing_labels]
164
+ )
146
165
  return [incoming_labels] + [f"{self.name}"] + [outgoing_labels]
147
166
 
148
167
 
@@ -152,10 +171,12 @@ class FilterableStream(Stream, OutputArgs):
152
171
  A stream that can be used as input to a filter
153
172
  """
154
173
 
155
- node: "FilterNode | InputNode"
174
+ node: FilterNode | InputNode
156
175
 
157
176
  @override
158
- def _output_node(self, *streams: FilterableStream, filename: str | Path, **kwargs: Any) -> OutputNode:
177
+ def _output_node(
178
+ self, *streams: FilterableStream, filename: str | Path, **kwargs: Any
179
+ ) -> OutputNode:
159
180
  """
160
181
  Output the streams to a file URL
161
182
 
@@ -167,15 +188,19 @@ class FilterableStream(Stream, OutputArgs):
167
188
  Returns:
168
189
  the output stream
169
190
  """
170
- return OutputNode(inputs=(self, *streams), filename=str(filename), kwargs=tuple(kwargs.items()))
191
+ return OutputNode(
192
+ inputs=(self, *streams),
193
+ filename=str(filename),
194
+ kwargs=tuple(kwargs.items()),
195
+ )
171
196
 
172
197
  def vfilter(
173
198
  self,
174
- *streams: "FilterableStream",
199
+ *streams: FilterableStream,
175
200
  name: str,
176
201
  input_typings: tuple[StreamType, ...] = (StreamType.video,),
177
202
  **kwargs: Any,
178
- ) -> "VideoStream":
203
+ ) -> VideoStream:
179
204
  """
180
205
  Apply a custom video filter which has only one output to this stream
181
206
 
@@ -198,11 +223,11 @@ class FilterableStream(Stream, OutputArgs):
198
223
 
199
224
  def afilter(
200
225
  self,
201
- *streams: "FilterableStream",
226
+ *streams: FilterableStream,
202
227
  name: str,
203
228
  input_typings: tuple[StreamType, ...] = (StreamType.audio,),
204
229
  **kwargs: Any,
205
- ) -> "AudioStream":
230
+ ) -> AudioStream:
206
231
  """
207
232
  Apply a custom audio filter which has only one output to this stream
208
233
 
@@ -225,12 +250,12 @@ class FilterableStream(Stream, OutputArgs):
225
250
 
226
251
  def filter_multi_output(
227
252
  self,
228
- *streams: "FilterableStream",
253
+ *streams: FilterableStream,
229
254
  name: str,
230
255
  input_typings: tuple[StreamType, ...] = (),
231
256
  output_typings: tuple[StreamType, ...] = (),
232
257
  **kwargs: Any,
233
- ) -> "FilterNode":
258
+ ) -> FilterNode:
234
259
  """
235
260
  Apply a custom filter which has multiple outputs to this stream
236
261
 
@@ -277,13 +302,17 @@ class FilterableStream(Stream, OutputArgs):
277
302
  return f"{context.get_node_label(self.node)}:v"
278
303
  elif isinstance(self, AudioStream):
279
304
  return f"{context.get_node_label(self.node)}:a"
280
- raise FFMpegValueError(f"Unknown stream type: {self.__class__.__name__}") # pragma: no cover
305
+ raise FFMpegValueError(
306
+ f"Unknown stream type: {self.__class__.__name__}"
307
+ ) # pragma: no cover
281
308
 
282
309
  if isinstance(self.node, FilterNode):
283
310
  if len(self.node.output_typings) > 1:
284
311
  return f"{context.get_node_label(self.node)}#{self.index}"
285
312
  return f"{context.get_node_label(self.node)}"
286
- raise FFMpegValueError(f"Unknown node type: {self.node.__class__.__name__}") # pragma: no cover
313
+ raise FFMpegValueError(
314
+ f"Unknown node type: {self.node.__class__.__name__}"
315
+ ) # pragma: no cover
287
316
 
288
317
  def __post_init__(self) -> None:
289
318
  if isinstance(self.node, InputNode):
@@ -310,7 +339,7 @@ class InputNode(Node):
310
339
  return os.path.basename(self.filename)
311
340
 
312
341
  @property
313
- def video(self) -> "VideoStream":
342
+ def video(self) -> VideoStream:
314
343
  """
315
344
  Return the video stream of this node
316
345
 
@@ -322,7 +351,7 @@ class InputNode(Node):
322
351
  return VideoStream(node=self)
323
352
 
324
353
  @property
325
- def audio(self) -> "AudioStream":
354
+ def audio(self) -> AudioStream:
326
355
  """
327
356
  Return the audio stream of this node
328
357
 
@@ -333,7 +362,7 @@ class InputNode(Node):
333
362
 
334
363
  return AudioStream(node=self)
335
364
 
336
- def stream(self) -> "AVStream":
365
+ def stream(self) -> AVStream:
337
366
  """
338
367
  Return the output stream of this node
339
368
 
@@ -371,7 +400,7 @@ class OutputNode(Node):
371
400
  def repr(self) -> str:
372
401
  return os.path.basename(self.filename)
373
402
 
374
- def stream(self) -> "OutputStream":
403
+ def stream(self) -> OutputStream:
375
404
  """
376
405
  Return the output stream of this node
377
406
 
@@ -437,7 +466,7 @@ class GlobalNode(Node):
437
466
  def repr(self) -> str:
438
467
  return " ".join(self.get_args())
439
468
 
440
- def stream(self) -> "GlobalStream":
469
+ def stream(self) -> GlobalStream:
441
470
  """
442
471
  Return the output stream of this node
443
472
 
@@ -70,7 +70,7 @@ class Stream(HashableBaseModel):
70
70
  return f.read()
71
71
 
72
72
  def _repr_svg_(self) -> str: # pragma: no cover
73
- with open(self.view(format="svg"), "r") as f:
73
+ with open(self.view(format="svg")) as f:
74
74
  return f.read()
75
75
 
76
76
 
@@ -109,7 +109,7 @@ class Node(HashableBaseModel, ABC):
109
109
 
110
110
  nodes.extend(k.node for k in node.inputs)
111
111
 
112
- output[node.hex] = set(k.node.hex for k in node.inputs)
112
+ output[node.hex] = {k.node.hex for k in node.inputs}
113
113
 
114
114
  if not is_dag(output):
115
115
  raise ValueError(f"Graph is not a DAG: {output}") # pragma: no cover
@@ -206,5 +206,5 @@ class Node(HashableBaseModel, ABC):
206
206
  return f.read()
207
207
 
208
208
  def _repr_svg_(self) -> str: # pragma: no cover
209
- with open(self.view(format="svg"), "r") as f:
209
+ with open(self.view(format="svg")) as f:
210
210
  return f.read()
@@ -10,7 +10,9 @@ from .nodes import FilterNode, InputNode
10
10
  from .schema import Node, Stream
11
11
 
12
12
 
13
- def remove_split(current_stream: Stream, mapping: dict[Stream, Stream] = None) -> tuple[Stream, dict[Stream, Stream]]:
13
+ def remove_split(
14
+ current_stream: Stream, mapping: dict[Stream, Stream] = None
15
+ ) -> tuple[Stream, dict[Stream, Stream]]:
14
16
  """
15
17
  Rebuild the graph with the given mapping.
16
18
 
@@ -37,20 +39,28 @@ def remove_split(current_stream: Stream, mapping: dict[Stream, Stream] = None) -
37
39
  if isinstance(current_stream.node, FilterNode):
38
40
  # if the current node is a split node, we need to remove it
39
41
  if current_stream.node.name in ("split", "asplit"):
40
- new_stream, _mapping = remove_split(current_stream=current_stream.node.inputs[0], mapping=mapping)
42
+ new_stream, _mapping = remove_split(
43
+ current_stream=current_stream.node.inputs[0], mapping=mapping
44
+ )
41
45
  mapping[current_stream] = mapping[current_stream.node.inputs[0]]
42
46
  return mapping[current_stream.node.inputs[0]], mapping
43
47
 
44
48
  inputs = {}
45
49
  for idx, input_stream in sorted(
46
- enumerate(current_stream.node.inputs), key=lambda idx_stream: -len(idx_stream[1].node.upstream_nodes)
50
+ enumerate(current_stream.node.inputs),
51
+ key=lambda idx_stream: -len(idx_stream[1].node.upstream_nodes),
47
52
  ):
48
- new_stream, _mapping = remove_split(current_stream=input_stream, mapping=mapping)
53
+ new_stream, _mapping = remove_split(
54
+ current_stream=input_stream, mapping=mapping
55
+ )
49
56
  inputs[idx] = new_stream
50
57
  mapping |= _mapping
51
58
 
52
59
  new_node = replace(
53
- current_stream.node, inputs=tuple(stream for idx, stream in sorted(inputs.items(), key=lambda x: x[0]))
60
+ current_stream.node,
61
+ inputs=tuple(
62
+ stream for idx, stream in sorted(inputs.items(), key=lambda x: x[0])
63
+ ),
54
64
  )
55
65
  new_stream = replace(current_stream, node=new_node)
56
66
 
@@ -91,16 +101,24 @@ def add_split(
91
101
  inputs = {}
92
102
 
93
103
  for idx, input_stream in sorted(
94
- enumerate(current_stream.node.inputs), key=lambda idx_stream: -len(idx_stream[1].node.upstream_nodes)
104
+ enumerate(current_stream.node.inputs),
105
+ key=lambda idx_stream: -len(idx_stream[1].node.upstream_nodes),
95
106
  ):
96
107
  new_stream, _mapping = add_split(
97
- current_stream=input_stream, down_node=current_stream.node, down_index=idx, mapping=mapping, context=context
108
+ current_stream=input_stream,
109
+ down_node=current_stream.node,
110
+ down_index=idx,
111
+ mapping=mapping,
112
+ context=context,
98
113
  )
99
114
  inputs[idx] = new_stream
100
115
  mapping |= _mapping
101
116
 
102
117
  new_node = replace(
103
- current_stream.node, inputs=tuple(stream for idx, stream in sorted(inputs.items(), key=lambda x: x[0]))
118
+ current_stream.node,
119
+ inputs=tuple(
120
+ stream for idx, stream in sorted(inputs.items(), key=lambda x: x[0])
121
+ ),
104
122
  )
105
123
  new_stream = replace(current_stream, node=new_node)
106
124
 
@@ -39,4 +39,4 @@ class FFMpegExecuteError(FFMpegError):
39
39
  self.cmd = cmd
40
40
  self.retcode = retcode
41
41
 
42
- super(FFMpegExecuteError, self).__init__(f"{cmd} error (see stderr output for detail) {stderr!r}")
42
+ super().__init__(f"{cmd} error (see stderr output for detail) {stderr!r}")