typed-ffmpeg-compatible 2.6.0__py3-none-any.whl → 2.6.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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}")