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.
Files changed (45) hide show
  1. typed_ffmpeg/__init__.py +25 -0
  2. typed_ffmpeg/base.py +114 -0
  3. typed_ffmpeg/common/__init__.py +0 -0
  4. typed_ffmpeg/common/schema.py +308 -0
  5. typed_ffmpeg/common/serialize.py +132 -0
  6. typed_ffmpeg/dag/__init__.py +13 -0
  7. typed_ffmpeg/dag/compile.py +51 -0
  8. typed_ffmpeg/dag/context.py +221 -0
  9. typed_ffmpeg/dag/factory.py +31 -0
  10. typed_ffmpeg/dag/global_runnable/__init__.py +0 -0
  11. typed_ffmpeg/dag/global_runnable/global_args.py +178 -0
  12. typed_ffmpeg/dag/global_runnable/runnable.py +174 -0
  13. typed_ffmpeg/dag/io/__init__.py +0 -0
  14. typed_ffmpeg/dag/io/_input.py +197 -0
  15. typed_ffmpeg/dag/io/_output.py +320 -0
  16. typed_ffmpeg/dag/io/output_args.py +327 -0
  17. typed_ffmpeg/dag/nodes.py +479 -0
  18. typed_ffmpeg/dag/schema.py +210 -0
  19. typed_ffmpeg/dag/utils.py +41 -0
  20. typed_ffmpeg/dag/validate.py +172 -0
  21. typed_ffmpeg/exceptions.py +42 -0
  22. typed_ffmpeg/filters.py +3572 -0
  23. typed_ffmpeg/probe.py +43 -0
  24. typed_ffmpeg/py.typed +0 -0
  25. typed_ffmpeg/schema.py +29 -0
  26. typed_ffmpeg/streams/__init__.py +5 -0
  27. typed_ffmpeg/streams/audio.py +7358 -0
  28. typed_ffmpeg/streams/av.py +22 -0
  29. typed_ffmpeg/streams/channel_layout.py +39 -0
  30. typed_ffmpeg/streams/video.py +13469 -0
  31. typed_ffmpeg/types.py +119 -0
  32. typed_ffmpeg/utils/__init__.py +0 -0
  33. typed_ffmpeg/utils/escaping.py +49 -0
  34. typed_ffmpeg/utils/lazy_eval/__init__.py +0 -0
  35. typed_ffmpeg/utils/lazy_eval/operator.py +134 -0
  36. typed_ffmpeg/utils/lazy_eval/schema.py +211 -0
  37. typed_ffmpeg/utils/run.py +27 -0
  38. typed_ffmpeg/utils/snapshot.py +26 -0
  39. typed_ffmpeg/utils/typing.py +17 -0
  40. typed_ffmpeg/utils/view.py +64 -0
  41. typed_ffmpeg_compatible-2.4.1.dist-info/LICENSE +21 -0
  42. typed_ffmpeg_compatible-2.4.1.dist-info/METADATA +182 -0
  43. typed_ffmpeg_compatible-2.4.1.dist-info/RECORD +45 -0
  44. typed_ffmpeg_compatible-2.4.1.dist-info/WHEEL +4 -0
  45. 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.