typed-ffmpeg-compatible 2.4.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- typed_ffmpeg/__init__.py +25 -0
- typed_ffmpeg/base.py +114 -0
- typed_ffmpeg/common/__init__.py +0 -0
- typed_ffmpeg/common/schema.py +308 -0
- typed_ffmpeg/common/serialize.py +132 -0
- typed_ffmpeg/dag/__init__.py +13 -0
- typed_ffmpeg/dag/compile.py +51 -0
- typed_ffmpeg/dag/context.py +221 -0
- typed_ffmpeg/dag/factory.py +31 -0
- typed_ffmpeg/dag/global_runnable/__init__.py +0 -0
- typed_ffmpeg/dag/global_runnable/global_args.py +178 -0
- typed_ffmpeg/dag/global_runnable/runnable.py +174 -0
- typed_ffmpeg/dag/io/__init__.py +0 -0
- typed_ffmpeg/dag/io/_input.py +197 -0
- typed_ffmpeg/dag/io/_output.py +320 -0
- typed_ffmpeg/dag/io/output_args.py +327 -0
- typed_ffmpeg/dag/nodes.py +479 -0
- typed_ffmpeg/dag/schema.py +210 -0
- typed_ffmpeg/dag/utils.py +41 -0
- typed_ffmpeg/dag/validate.py +172 -0
- typed_ffmpeg/exceptions.py +42 -0
- typed_ffmpeg/filters.py +3572 -0
- typed_ffmpeg/probe.py +43 -0
- typed_ffmpeg/py.typed +0 -0
- typed_ffmpeg/schema.py +29 -0
- typed_ffmpeg/streams/__init__.py +5 -0
- typed_ffmpeg/streams/audio.py +7358 -0
- typed_ffmpeg/streams/av.py +22 -0
- typed_ffmpeg/streams/channel_layout.py +39 -0
- typed_ffmpeg/streams/video.py +13469 -0
- typed_ffmpeg/types.py +119 -0
- typed_ffmpeg/utils/__init__.py +0 -0
- typed_ffmpeg/utils/escaping.py +49 -0
- typed_ffmpeg/utils/lazy_eval/__init__.py +0 -0
- typed_ffmpeg/utils/lazy_eval/operator.py +134 -0
- typed_ffmpeg/utils/lazy_eval/schema.py +211 -0
- typed_ffmpeg/utils/run.py +27 -0
- typed_ffmpeg/utils/snapshot.py +26 -0
- typed_ffmpeg/utils/typing.py +17 -0
- typed_ffmpeg/utils/view.py +64 -0
- typed_ffmpeg_compatible-2.4.1.dist-info/LICENSE +21 -0
- typed_ffmpeg_compatible-2.4.1.dist-info/METADATA +182 -0
- typed_ffmpeg_compatible-2.4.1.dist-info/RECORD +45 -0
- typed_ffmpeg_compatible-2.4.1.dist-info/WHEEL +4 -0
- typed_ffmpeg_compatible-2.4.1.dist-info/entry_points.txt +3 -0
typed_ffmpeg/types.py
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
"""
|
2
|
+
This module defines the various types of options that can be used with FFmpeg.
|
3
|
+
These option types can be one of several different categories.
|
4
|
+
The source of these types is defined within the AVOptionType enumeration found in FFmpeg's opt.h header file.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Literal
|
8
|
+
|
9
|
+
from .schema import Default
|
10
|
+
from .utils.lazy_eval.schema import LazyValue
|
11
|
+
|
12
|
+
Boolean = bool | Literal["true", "false", "1", "0"] | Default | LazyValue
|
13
|
+
"""
|
14
|
+
This represents FFmpeg's boolean type. It can accept either a Python boolean value (`True` or `False`)
|
15
|
+
or a string that represents a boolean value ("true", "false", "1", or "0").
|
16
|
+
|
17
|
+
"""
|
18
|
+
|
19
|
+
Duration = str | int | float | Default | LazyValue
|
20
|
+
"""
|
21
|
+
This represents FFmpeg's duration type. It can accept either a Python integer or float value
|
22
|
+
or a string that represents a duration value.
|
23
|
+
|
24
|
+
Note:
|
25
|
+
[Document](https://ffmpeg.org/ffmpeg-utils.html#Time-duration)
|
26
|
+
"""
|
27
|
+
|
28
|
+
Color = str | Default | LazyValue
|
29
|
+
"""
|
30
|
+
It can be the name of a color as defined below (case insensitive match) or a [0x|#]RRGGBB[AA] sequence, possibly followed by @ and a string representing the alpha component.
|
31
|
+
The alpha component may be a string composed by "0x" followed by an hexadecimal number or a decimal number between 0.0 and 1.0, which represents the opacity value (‘0x00’ or ‘0.0’ means completely transparent, ‘0xff’ or ‘1.0’ completely opaque). If the alpha component is not specified then ‘0xff’ is assumed.
|
32
|
+
The string ‘random’ will result in a random color.
|
33
|
+
|
34
|
+
Note:
|
35
|
+
[Document](https://ffmpeg.org/ffmpeg-utils.html#Color)
|
36
|
+
"""
|
37
|
+
|
38
|
+
Flags = str | Default | LazyValue
|
39
|
+
"""
|
40
|
+
This represents FFmpeg's flags type. It accepts a string in the format "A+B",
|
41
|
+
where "A" and "B" are individual flags. For example, "fast+bilinear" would
|
42
|
+
represent two flags, "fast" and "bilinear", to be used in FFmpeg's command line.
|
43
|
+
"""
|
44
|
+
|
45
|
+
Dictionary = str | Default | LazyValue
|
46
|
+
# format A=B:C=D:E=F
|
47
|
+
Pix_fmt = str | Default | LazyValue
|
48
|
+
"""
|
49
|
+
please see `ffmpeg -pix_fmts` for a list of supported pixel formats.
|
50
|
+
"""
|
51
|
+
|
52
|
+
Int = int | Default | LazyValue
|
53
|
+
"""
|
54
|
+
This represents FFmpeg's integer type. It can accept either a Python integer value
|
55
|
+
or a string that represents a integer value.
|
56
|
+
"""
|
57
|
+
|
58
|
+
Int64 = int | Default | LazyValue
|
59
|
+
"""
|
60
|
+
This represents FFmpeg's integer type. It can accept either a Python integer value
|
61
|
+
or a string that represents a integer value.
|
62
|
+
"""
|
63
|
+
|
64
|
+
Double = int | float | Default | LazyValue
|
65
|
+
"""
|
66
|
+
This represents FFmpeg's double type. It can accept either a Python integer or float value
|
67
|
+
or a string that represents a double value.
|
68
|
+
"""
|
69
|
+
# TODO: more info
|
70
|
+
Float = int | float | Default | LazyValue
|
71
|
+
"""
|
72
|
+
This represents FFmpeg's float type. It can accept either a Python integer or float value
|
73
|
+
or a string that represents a float value.
|
74
|
+
"""
|
75
|
+
String = str | int | float | Default | LazyValue
|
76
|
+
"""
|
77
|
+
This represents FFmpeg's string type. It can accept either a Python string value
|
78
|
+
or a int/float that will be converted to a string.
|
79
|
+
"""
|
80
|
+
|
81
|
+
Video_rate = str | int | float | Default | LazyValue
|
82
|
+
"""
|
83
|
+
Specify the frame rate of a video, expressed as the number of frames generated per second. It has to be a string in the format frame_rate_num/frame_rate_den, an integer number, a float number or a valid video frame rate abbreviation.
|
84
|
+
|
85
|
+
Note:
|
86
|
+
[Document](https://ffmpeg.org/ffmpeg-utils.html#Video-rate)
|
87
|
+
"""
|
88
|
+
|
89
|
+
# TODO: enum
|
90
|
+
Image_size = str | Default | LazyValue
|
91
|
+
"""
|
92
|
+
Specify the size of the sourced video, it may be a string of the form widthxheight, or the name of a size abbreviation.
|
93
|
+
|
94
|
+
Note:
|
95
|
+
[Document](https://ffmpeg.org/ffmpeg-utils.html#Video-size)
|
96
|
+
"""
|
97
|
+
|
98
|
+
Rational = str | Default | LazyValue
|
99
|
+
"""
|
100
|
+
Specify the frame rate of a video, expressed as the number of frames generated per second. It has to be a string in the format frame_rate_num/frame_rate_den, an integer number, a float number or a valid video frame rate abbreviation.
|
101
|
+
|
102
|
+
Note:
|
103
|
+
[Document](https://ffmpeg.org/ffmpeg-utils.html#Ratio)
|
104
|
+
"""
|
105
|
+
|
106
|
+
|
107
|
+
Sample_fmt = str | Default | LazyValue
|
108
|
+
Binary = str | Default | LazyValue
|
109
|
+
|
110
|
+
# OPT Type
|
111
|
+
Func = str | int | float | Default | LazyValue
|
112
|
+
"""
|
113
|
+
ref: OPT_TYPE_FUNC
|
114
|
+
"""
|
115
|
+
|
116
|
+
Time = str | int | float | Default | LazyValue
|
117
|
+
"""
|
118
|
+
ref: OPT_TYPE_TIME
|
119
|
+
"""
|
File without changes
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from typing import Any, Iterable
|
2
|
+
|
3
|
+
|
4
|
+
def escape(text: str | int | float, chars: str = "\\'=:") -> str:
|
5
|
+
"""
|
6
|
+
Helper function to escape uncomfortable characters.
|
7
|
+
|
8
|
+
Args:
|
9
|
+
text: The text to escape.
|
10
|
+
chars: The characters to escape.
|
11
|
+
|
12
|
+
Returns:
|
13
|
+
The escaped text.
|
14
|
+
"""
|
15
|
+
text = str(text)
|
16
|
+
_chars = list(set(chars))
|
17
|
+
if "\\" in _chars:
|
18
|
+
_chars.remove("\\")
|
19
|
+
_chars.insert(0, "\\")
|
20
|
+
|
21
|
+
for ch in _chars:
|
22
|
+
text = text.replace(ch, "\\" + ch)
|
23
|
+
|
24
|
+
return text
|
25
|
+
|
26
|
+
|
27
|
+
def convert_kwargs_to_cmd_line_args(kwargs: dict[str, Any]) -> list[str]:
|
28
|
+
"""
|
29
|
+
Helper function to build command line arguments out of dict.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
kwargs: The dict to convert.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
The command line arguments.
|
36
|
+
"""
|
37
|
+
args = []
|
38
|
+
for k in sorted(kwargs.keys()):
|
39
|
+
v = kwargs[k]
|
40
|
+
if isinstance(v, Iterable) and not isinstance(v, str):
|
41
|
+
for value in v:
|
42
|
+
args.append("-{}".format(k))
|
43
|
+
if value is not None:
|
44
|
+
args.append("{}".format(value))
|
45
|
+
continue
|
46
|
+
args.append("-{}".format(k))
|
47
|
+
if v is not None:
|
48
|
+
args.append("{}".format(v))
|
49
|
+
return args
|
File without changes
|
@@ -0,0 +1,134 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from .schema import LazyOperator
|
5
|
+
|
6
|
+
|
7
|
+
@dataclass(frozen=True, kw_only=True)
|
8
|
+
class Add(LazyOperator):
|
9
|
+
"""
|
10
|
+
A lazy operator for addition.
|
11
|
+
"""
|
12
|
+
|
13
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
14
|
+
return left + right
|
15
|
+
|
16
|
+
def __str__(self) -> str:
|
17
|
+
return f"({self.left}+{self.right})"
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass(frozen=True, kw_only=True)
|
21
|
+
class Sub(LazyOperator):
|
22
|
+
"""
|
23
|
+
A lazy operator for subtraction.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
27
|
+
return left - right
|
28
|
+
|
29
|
+
def __str__(self) -> str:
|
30
|
+
return f"({self.left}-{self.right})"
|
31
|
+
|
32
|
+
|
33
|
+
@dataclass(frozen=True, kw_only=True)
|
34
|
+
class Mul(LazyOperator):
|
35
|
+
"""
|
36
|
+
A lazy operator for multiplication.
|
37
|
+
"""
|
38
|
+
|
39
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
40
|
+
return left * right
|
41
|
+
|
42
|
+
def __str__(self) -> str:
|
43
|
+
return f"({self.left}*{self.right})"
|
44
|
+
|
45
|
+
|
46
|
+
@dataclass(frozen=True, kw_only=True)
|
47
|
+
class TrueDiv(LazyOperator):
|
48
|
+
"""
|
49
|
+
A lazy operator for true division.
|
50
|
+
"""
|
51
|
+
|
52
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
53
|
+
return left / right
|
54
|
+
|
55
|
+
def __str__(self) -> str:
|
56
|
+
return f"({self.left}/{self.right})"
|
57
|
+
|
58
|
+
|
59
|
+
@dataclass(frozen=True, kw_only=True)
|
60
|
+
class Pow(LazyOperator):
|
61
|
+
"""
|
62
|
+
A lazy operator for exponentiation.
|
63
|
+
"""
|
64
|
+
|
65
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
66
|
+
return left**right
|
67
|
+
|
68
|
+
def __str__(self) -> str:
|
69
|
+
return f"({self.left}**{self.right})"
|
70
|
+
|
71
|
+
|
72
|
+
@dataclass(frozen=True, kw_only=True)
|
73
|
+
class Neg(LazyOperator):
|
74
|
+
"""
|
75
|
+
A lazy operator for negation.
|
76
|
+
"""
|
77
|
+
|
78
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
79
|
+
return -left
|
80
|
+
|
81
|
+
def __str__(self) -> str:
|
82
|
+
return f"-{self.left}"
|
83
|
+
|
84
|
+
|
85
|
+
@dataclass(frozen=True, kw_only=True)
|
86
|
+
class Pos(LazyOperator):
|
87
|
+
"""
|
88
|
+
A lazy operator for positive.
|
89
|
+
"""
|
90
|
+
|
91
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
92
|
+
return +left
|
93
|
+
|
94
|
+
def __str__(self) -> str:
|
95
|
+
return f"+{self.left}"
|
96
|
+
|
97
|
+
|
98
|
+
@dataclass(frozen=True, kw_only=True)
|
99
|
+
class Abs(LazyOperator):
|
100
|
+
"""
|
101
|
+
A lazy operator for absolute value.
|
102
|
+
"""
|
103
|
+
|
104
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
105
|
+
return abs(left)
|
106
|
+
|
107
|
+
def __str__(self) -> str:
|
108
|
+
return f"abs({self.left})"
|
109
|
+
|
110
|
+
|
111
|
+
@dataclass(frozen=True, kw_only=True)
|
112
|
+
class Mod(LazyOperator):
|
113
|
+
"""
|
114
|
+
A lazy operator for modulo.
|
115
|
+
"""
|
116
|
+
|
117
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
118
|
+
return left % right
|
119
|
+
|
120
|
+
def __str__(self) -> str:
|
121
|
+
return f"({self.left}%{self.right})"
|
122
|
+
|
123
|
+
|
124
|
+
@dataclass(frozen=True, kw_only=True)
|
125
|
+
class FloorDiv(LazyOperator):
|
126
|
+
"""
|
127
|
+
A lazy operator for floor division.
|
128
|
+
"""
|
129
|
+
|
130
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
131
|
+
return left // right
|
132
|
+
|
133
|
+
def __str__(self) -> str:
|
134
|
+
return f"({self.left}//{self.right})"
|
@@ -0,0 +1,211 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
from ..typing import override
|
8
|
+
|
9
|
+
|
10
|
+
class LazyValue(ABC):
|
11
|
+
"""
|
12
|
+
A base class for lazy evaluation.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __add__(self, v: Any) -> LazyValue:
|
16
|
+
from .operator import Add
|
17
|
+
|
18
|
+
return Add(left=self, right=v)
|
19
|
+
|
20
|
+
def __radd__(self, v: Any) -> LazyValue:
|
21
|
+
from .operator import Add
|
22
|
+
|
23
|
+
return Add(left=v, right=self)
|
24
|
+
|
25
|
+
def __sub__(self, v: Any) -> LazyValue:
|
26
|
+
from .operator import Sub
|
27
|
+
|
28
|
+
return Sub(left=self, right=v)
|
29
|
+
|
30
|
+
def __rsub__(self, v: Any) -> LazyValue:
|
31
|
+
from .operator import Sub
|
32
|
+
|
33
|
+
return Sub(left=v, right=self)
|
34
|
+
|
35
|
+
def __mul__(self, v: Any) -> LazyValue:
|
36
|
+
from .operator import Mul
|
37
|
+
|
38
|
+
return Mul(left=self, right=v)
|
39
|
+
|
40
|
+
def __rmul__(self, v: Any) -> LazyValue:
|
41
|
+
from .operator import Mul
|
42
|
+
|
43
|
+
return Mul(left=v, right=self)
|
44
|
+
|
45
|
+
def __truediv__(self, v: Any) -> LazyValue:
|
46
|
+
from .operator import TrueDiv
|
47
|
+
|
48
|
+
return TrueDiv(left=self, right=v)
|
49
|
+
|
50
|
+
def __rtruediv__(self, v: Any) -> LazyValue:
|
51
|
+
from .operator import TrueDiv
|
52
|
+
|
53
|
+
return TrueDiv(left=v, right=self)
|
54
|
+
|
55
|
+
def __pow__(self, v: Any) -> LazyValue:
|
56
|
+
from .operator import Pow
|
57
|
+
|
58
|
+
return Pow(left=self, right=v)
|
59
|
+
|
60
|
+
def __rpow__(self, v: Any) -> LazyValue:
|
61
|
+
from .operator import Pow
|
62
|
+
|
63
|
+
return Pow(left=v, right=self)
|
64
|
+
|
65
|
+
def __neg__(self) -> LazyValue:
|
66
|
+
from .operator import Neg
|
67
|
+
|
68
|
+
return Neg(left=self)
|
69
|
+
|
70
|
+
def __pos__(self) -> LazyValue:
|
71
|
+
from .operator import Pos
|
72
|
+
|
73
|
+
return Pos(left=self)
|
74
|
+
|
75
|
+
def __abs__(self) -> LazyValue:
|
76
|
+
from .operator import Abs
|
77
|
+
|
78
|
+
return Abs(left=self)
|
79
|
+
|
80
|
+
def __mod__(self, v: Any) -> LazyValue:
|
81
|
+
from .operator import Mod
|
82
|
+
|
83
|
+
return Mod(left=self, right=v)
|
84
|
+
|
85
|
+
def __rmod__(self, v: Any) -> LazyValue:
|
86
|
+
from .operator import Mod
|
87
|
+
|
88
|
+
return Mod(left=v, right=self)
|
89
|
+
|
90
|
+
def __floordiv__(self, v: Any) -> LazyValue:
|
91
|
+
from .operator import FloorDiv
|
92
|
+
|
93
|
+
return FloorDiv(left=self, right=v)
|
94
|
+
|
95
|
+
def __rfloordiv__(self, v: Any) -> LazyValue:
|
96
|
+
from .operator import FloorDiv
|
97
|
+
|
98
|
+
return FloorDiv(left=v, right=self)
|
99
|
+
|
100
|
+
def eval(self, **values: Any) -> Any:
|
101
|
+
"""
|
102
|
+
Evaluate the lazy value with the given values.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
**values: Values to be used for evaluation.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
Any: The evaluated value.
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
ValueError: If the lazy value is not ready to be evaluated.
|
112
|
+
"""
|
113
|
+
v = self.partial(**values)
|
114
|
+
if isinstance(v, LazyValue):
|
115
|
+
raise ValueError(v.keys())
|
116
|
+
return v
|
117
|
+
|
118
|
+
@abstractmethod
|
119
|
+
def partial(self, **values: Any) -> Any:
|
120
|
+
"""
|
121
|
+
Partially evaluate the lazy value with the given values.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
**values: Values to be used for evaluation.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
Any: The partially evaluated value.
|
128
|
+
"""
|
129
|
+
|
130
|
+
...
|
131
|
+
|
132
|
+
def ready(self) -> bool:
|
133
|
+
"""
|
134
|
+
Check if the lazy value is ready to be evaluated.
|
135
|
+
"""
|
136
|
+
return not self.keys()
|
137
|
+
|
138
|
+
@abstractmethod
|
139
|
+
def keys(self) -> set[str]:
|
140
|
+
"""
|
141
|
+
Get the keys that are required to evaluate the lazy value.
|
142
|
+
"""
|
143
|
+
...
|
144
|
+
|
145
|
+
|
146
|
+
@dataclass(frozen=True)
|
147
|
+
class Symbol(LazyValue):
|
148
|
+
"""
|
149
|
+
A symbol that represents a variable in the lazy evaluation.
|
150
|
+
|
151
|
+
Such as `x`, `y`, `z`, etc.
|
152
|
+
"""
|
153
|
+
|
154
|
+
key: str
|
155
|
+
|
156
|
+
def __str__(self) -> str:
|
157
|
+
return str(self.key)
|
158
|
+
|
159
|
+
@override
|
160
|
+
def partial(self, **values: Any) -> Any:
|
161
|
+
if self.key in values:
|
162
|
+
return values[self.key]
|
163
|
+
return self
|
164
|
+
|
165
|
+
@override
|
166
|
+
def keys(self) -> set[str]:
|
167
|
+
return {self.key}
|
168
|
+
|
169
|
+
|
170
|
+
@dataclass(frozen=True, kw_only=True)
|
171
|
+
class LazyOperator(LazyValue):
|
172
|
+
"""
|
173
|
+
A base class for lazy operators.
|
174
|
+
|
175
|
+
Such as `Add`, `Sub`, `Mul`, `TrueDiv`, `Pow`, `Neg`, `Pos`, `Abs`, `Mod`, `FloorDiv`.
|
176
|
+
"""
|
177
|
+
|
178
|
+
left: Any = None
|
179
|
+
right: Any = None
|
180
|
+
|
181
|
+
@abstractmethod
|
182
|
+
def _eval(self, left: Any, right: Any) -> Any:
|
183
|
+
"""
|
184
|
+
Evaluate the operator with the given values.
|
185
|
+
"""
|
186
|
+
...
|
187
|
+
|
188
|
+
@override
|
189
|
+
def partial(self, **values: Any) -> Any:
|
190
|
+
if isinstance(self.left, LazyValue):
|
191
|
+
left = self.left.partial(**values)
|
192
|
+
else:
|
193
|
+
left = self.left
|
194
|
+
|
195
|
+
if isinstance(self.right, LazyValue):
|
196
|
+
right = self.right.partial(**values)
|
197
|
+
else:
|
198
|
+
right = self.right
|
199
|
+
|
200
|
+
return self._eval(left, right)
|
201
|
+
|
202
|
+
@override
|
203
|
+
def keys(self) -> set[str]:
|
204
|
+
r = set()
|
205
|
+
if isinstance(self.left, LazyValue):
|
206
|
+
r |= self.left.keys()
|
207
|
+
|
208
|
+
if isinstance(self.right, LazyValue):
|
209
|
+
r |= self.right.keys()
|
210
|
+
|
211
|
+
return r
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import shlex
|
2
|
+
|
3
|
+
from ..schema import Default
|
4
|
+
from ..utils.lazy_eval.schema import LazyValue
|
5
|
+
|
6
|
+
|
7
|
+
def command_line(args: list[str]) -> str:
|
8
|
+
"""
|
9
|
+
Get the command line representation of the arguments.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
args: The arguments to convert.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
The command line representation of the arguments.
|
16
|
+
"""
|
17
|
+
return " ".join(shlex.quote(arg) for arg in args)
|
18
|
+
|
19
|
+
|
20
|
+
# Filter_Node_Option_Type
|
21
|
+
def ignore_default(
|
22
|
+
kwargs: dict[str, str | int | float | bool | Default]
|
23
|
+
) -> tuple[tuple[str, str | int | float | bool | LazyValue], ...]:
|
24
|
+
"""
|
25
|
+
Convert the values of the dictionary to strings.
|
26
|
+
"""
|
27
|
+
return tuple((k, v) for k, v in kwargs.items() if not isinstance(v, Default))
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from syrupy.extensions.image import PNGImageSnapshotExtension
|
4
|
+
from syrupy.types import PropertyFilter, PropertyMatcher, SerializableData, SerializedData
|
5
|
+
|
6
|
+
from ..dag.schema import Stream
|
7
|
+
|
8
|
+
|
9
|
+
class DAGSnapshotExtenstion(PNGImageSnapshotExtension):
|
10
|
+
"""
|
11
|
+
A snapshot extension for the DAG. This extension is used to serialize and match the DAG.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def serialize(
|
15
|
+
self,
|
16
|
+
data: "SerializableData",
|
17
|
+
*,
|
18
|
+
exclude: Optional["PropertyFilter"] = None,
|
19
|
+
include: Optional["PropertyFilter"] = None,
|
20
|
+
matcher: Optional["PropertyMatcher"] = None,
|
21
|
+
) -> "SerializedData":
|
22
|
+
stream = Stream(node=data)
|
23
|
+
graph_path = stream.view()
|
24
|
+
|
25
|
+
with open(graph_path, "rb") as ifile:
|
26
|
+
return super().serialize(ifile.read())
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from typing import TypeVar
|
2
|
+
|
3
|
+
V = TypeVar("V")
|
4
|
+
|
5
|
+
|
6
|
+
def override(func: V) -> V:
|
7
|
+
"""
|
8
|
+
Decorator to indicate overriding a method.
|
9
|
+
the true override method is implemented until in python 3.12
|
10
|
+
|
11
|
+
Args:
|
12
|
+
func: The function to decorate.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
The decorated function.
|
16
|
+
"""
|
17
|
+
return func
|
@@ -0,0 +1,64 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
from ..dag.context import DAGContext
|
6
|
+
from ..dag.nodes import FilterNode, InputNode, OutputNode
|
7
|
+
from ..dag.schema import Node
|
8
|
+
|
9
|
+
|
10
|
+
def _get_node_color(node: Node) -> str | None:
|
11
|
+
if isinstance(node, InputNode):
|
12
|
+
color = "#99cc00"
|
13
|
+
elif isinstance(node, OutputNode):
|
14
|
+
color = "#99ccff"
|
15
|
+
elif isinstance(node, FilterNode):
|
16
|
+
color = "#ffcc00"
|
17
|
+
else:
|
18
|
+
color = None
|
19
|
+
return color
|
20
|
+
|
21
|
+
|
22
|
+
def view(node: Node, format: Literal["png", "svg", "dot"]) -> str:
|
23
|
+
"""
|
24
|
+
Visualize the graph via graphviz.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
node: The node to visualize.
|
28
|
+
format: The format to render the graph in.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
The path to the rendered graph.
|
32
|
+
"""
|
33
|
+
|
34
|
+
try:
|
35
|
+
import graphviz # type: ignore
|
36
|
+
except ImportError:
|
37
|
+
raise ImportError(
|
38
|
+
"failed to import graphviz; please make sure graphviz is installed (e.g. " "`pip install graphviz`)"
|
39
|
+
)
|
40
|
+
|
41
|
+
graph = graphviz.Digraph(format=format)
|
42
|
+
graph.attr(rankdir="LR")
|
43
|
+
graph.attr(fontname="Helvetica")
|
44
|
+
graph.attr(fontsize="12")
|
45
|
+
|
46
|
+
context = DAGContext.build(node)
|
47
|
+
|
48
|
+
for node in context.all_nodes:
|
49
|
+
color = _get_node_color(node)
|
50
|
+
graph.node(
|
51
|
+
name=node.hex,
|
52
|
+
label=node.repr(),
|
53
|
+
shape="box",
|
54
|
+
style="filled",
|
55
|
+
fillcolor=color,
|
56
|
+
fontname="Helvetica",
|
57
|
+
fontsize="12",
|
58
|
+
)
|
59
|
+
|
60
|
+
for node in context.all_nodes:
|
61
|
+
for idx, stream in enumerate(node.inputs):
|
62
|
+
graph.edge(stream.node.hex, node.hex, label=f"{'*' if stream.index is None else stream.index} => {idx}")
|
63
|
+
|
64
|
+
return graph.render(engine="dot")
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 livingbio
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|