typed-ffmpeg-compatible 2.6.0__tar.gz → 2.6.2__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/PKG-INFO +1 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/pyproject.toml +1 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/base.py +24 -5
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/common/schema.py +25 -9
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/common/serialize.py +9 -4
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/__init__.py +8 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/context.py +7 -2
- typed_ffmpeg_compatible-2.6.2/src/typed_ffmpeg/dag/factory.py +51 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/global_runnable/global_args.py +1 -2
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/global_runnable/runnable.py +9 -3
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/io/_input.py +3 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/io/_output.py +3 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/io/output_args.py +7 -4
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/nodes.py +55 -26
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/schema.py +3 -3
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/validate.py +26 -8
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/exceptions.py +1 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/filters.py +304 -72
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/info.py +9 -3
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/probe.py +9 -2
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/schema.py +0 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/streams/audio.py +697 -214
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/streams/video.py +1400 -401
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/escaping.py +6 -5
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/run.py +1 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/snapshot.py +6 -1
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/view.py +7 -2
- typed_ffmpeg_compatible-2.6.0/src/typed_ffmpeg/dag/factory.py +0 -31
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/LICENSE +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/README.md +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/common/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/compile.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/global_runnable/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/io/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/utils.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/py.typed +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/streams/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/streams/av.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/streams/channel_layout.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/types.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/lazy_eval/__init__.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/lazy_eval/operator.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/lazy_eval/schema.py +0 -0
- {typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/utils/typing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: typed-ffmpeg-compatible
|
3
|
-
Version: 2.6.
|
3
|
+
Version: 2.6.2
|
4
4
|
Summary: Modern Python FFmpeg wrappers offer comprehensive support for complex filters, complete with detailed typing and documentation.
|
5
5
|
Home-page: https://livingbio.github.io/typed-ffmpeg/
|
6
6
|
License: MIT
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "typed-ffmpeg-compatible"
|
3
|
-
version = "2.6.
|
3
|
+
version = "2.6.2"
|
4
4
|
description = "Modern Python FFmpeg wrappers offer comprehensive support for complex filters, complete with detailed typing and documentation."
|
5
5
|
authors = ["lucemia <lucemia@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -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
|
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,
|
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,
|
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__ = [
|
126
|
+
__all__ = [
|
127
|
+
"input",
|
128
|
+
"output",
|
129
|
+
"merge_outputs",
|
130
|
+
"vfilter",
|
131
|
+
"afilter",
|
132
|
+
"filter_multi_output",
|
133
|
+
]
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/common/schema.py
RENAMED
@@ -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
|
122
|
-
|
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,
|
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,
|
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
|
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,
|
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,
|
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
|
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
|
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:
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/common/serialize.py
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
from __future__ import
|
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."),
|
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(**
|
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
|
-
**{
|
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 {
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/__init__.py
RENAMED
@@ -1,4 +1,11 @@
|
|
1
|
-
from .nodes import
|
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__ = [
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/context.py
RENAMED
@@ -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(
|
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)),
|
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
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from ..common.schema import FFMpegFilterDef, StreamType
|
5
|
+
from ..schema import Auto
|
6
|
+
from ..utils.run import ignore_default
|
7
|
+
from .nodes import FilterableStream, FilterNode
|
8
|
+
|
9
|
+
|
10
|
+
def filter_node_factory(
|
11
|
+
ffmpeg_filter_def: FFMpegFilterDef, *inputs: FilterableStream, **kwargs: Any
|
12
|
+
) -> FilterNode:
|
13
|
+
for k, v in kwargs.items():
|
14
|
+
if isinstance(v, Auto):
|
15
|
+
kwargs[k] = eval(
|
16
|
+
v, {"StreamType": StreamType, "re": re, **kwargs, "streams": inputs}
|
17
|
+
)
|
18
|
+
|
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
|
+
)
|
26
|
+
else:
|
27
|
+
input_typings = tuple(
|
28
|
+
StreamType.video if k == "video" else StreamType.audio
|
29
|
+
for k in ffmpeg_filter_def.typings_input
|
30
|
+
)
|
31
|
+
|
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
|
+
)
|
39
|
+
else:
|
40
|
+
output_typings = tuple(
|
41
|
+
StreamType.video if k == "video" else StreamType.audio
|
42
|
+
for k in ffmpeg_filter_def.typings_output
|
43
|
+
)
|
44
|
+
|
45
|
+
return FilterNode(
|
46
|
+
name=ffmpeg_filter_def.name,
|
47
|
+
input_typings=input_typings,
|
48
|
+
output_typings=output_typings,
|
49
|
+
inputs=inputs,
|
50
|
+
kwargs=ignore_default(kwargs),
|
51
|
+
)
|
@@ -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(
|
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(
|
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(
|
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
|
)
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/io/_input.py
RENAMED
@@ -194,4 +194,6 @@ def input(
|
|
194
194
|
if v is not None
|
195
195
|
}
|
196
196
|
|
197
|
-
return InputNode(
|
197
|
+
return InputNode(
|
198
|
+
filename=str(filename), kwargs=tuple((options | (extra_options or {})).items())
|
199
|
+
).stream()
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/io/_output.py
RENAMED
@@ -316,5 +316,7 @@ def output(
|
|
316
316
|
}
|
317
317
|
|
318
318
|
return OutputNode(
|
319
|
-
inputs=streams,
|
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(
|
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:
|
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(
|
328
|
+
return self._output_node(
|
329
|
+
*streams, filename=filename, **options, **(extra_options or {})
|
330
|
+
).stream()
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/nodes.py
RENAMED
@@ -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) ->
|
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 = [
|
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(
|
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) ->
|
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 = [
|
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(
|
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(
|
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(
|
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),
|
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
|
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:
|
174
|
+
node: FilterNode | InputNode
|
156
175
|
|
157
176
|
@override
|
158
|
-
def _output_node(
|
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(
|
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:
|
199
|
+
*streams: FilterableStream,
|
175
200
|
name: str,
|
176
201
|
input_typings: tuple[StreamType, ...] = (StreamType.video,),
|
177
202
|
**kwargs: Any,
|
178
|
-
) ->
|
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:
|
226
|
+
*streams: FilterableStream,
|
202
227
|
name: str,
|
203
228
|
input_typings: tuple[StreamType, ...] = (StreamType.audio,),
|
204
229
|
**kwargs: Any,
|
205
|
-
) ->
|
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:
|
253
|
+
*streams: FilterableStream,
|
229
254
|
name: str,
|
230
255
|
input_typings: tuple[StreamType, ...] = (),
|
231
256
|
output_typings: tuple[StreamType, ...] = (),
|
232
257
|
**kwargs: Any,
|
233
|
-
) ->
|
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(
|
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(
|
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) ->
|
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) ->
|
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) ->
|
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) ->
|
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) ->
|
469
|
+
def stream(self) -> GlobalStream:
|
441
470
|
"""
|
442
471
|
Return the output stream of this node
|
443
472
|
|
{typed_ffmpeg_compatible-2.6.0 → typed_ffmpeg_compatible-2.6.2}/src/typed_ffmpeg/dag/schema.py
RENAMED
@@ -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")
|
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] =
|
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")
|
209
|
+
with open(self.view(format="svg")) as f:
|
210
210
|
return f.read()
|