typed-ffmpeg-compatible 2.1.0__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 +318 -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 +3510 -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 +6955 -0
  28. typed_ffmpeg/streams/av.py +22 -0
  29. typed_ffmpeg/streams/channel_layout.py +39 -0
  30. typed_ffmpeg/streams/video.py +12974 -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.1.0.dist-info/LICENSE +21 -0
  42. typed_ffmpeg_compatible-2.1.0.dist-info/METADATA +183 -0
  43. typed_ffmpeg_compatible-2.1.0.dist-info/RECORD +45 -0
  44. typed_ffmpeg_compatible-2.1.0.dist-info/WHEEL +4 -0
  45. typed_ffmpeg_compatible-2.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+
5
+ # Another approach to determine if a graph is a DAG is to try to perform a topological sort.
6
+ # If the topological sort is successful (i.e., all vertices are visited exactly once),
7
+ # the graph is a DAG. If the topological sort cannot include all vertices (i.e., the graph has a cycle),
8
+ # it is not a DAG. Here is a basic implementation using Kahn's Algorithm:
9
+
10
+
11
+ def is_dag(graph: dict[str, set[str]]) -> bool:
12
+ """
13
+ Determine if a graph is a directed acyclic graph (DAG).
14
+
15
+ Args:
16
+ graph: The graph to check.
17
+
18
+ Returns:
19
+ Whether the graph is a DAG.
20
+ """
21
+
22
+ in_degree = {u: 0 for u in graph} # Initialize in-degree of each node to 0
23
+
24
+ # Calculate in-degree of each node
25
+ for u in graph:
26
+ for v in graph[u]:
27
+ in_degree[v] += 1
28
+
29
+ queue = deque([u for u in graph if in_degree[u] == 0])
30
+ count = 0
31
+
32
+ while queue:
33
+ u = queue.popleft()
34
+ count += 1
35
+
36
+ for v in graph[u]:
37
+ in_degree[v] -= 1
38
+ if in_degree[v] == 0:
39
+ queue.append(v)
40
+
41
+ return count == len(graph)
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import replace
4
+
5
+ from ..exceptions import FFMpegValueError
6
+ from ..streams.audio import AudioStream
7
+ from ..streams.video import VideoStream
8
+ from .context import DAGContext
9
+ from .nodes import FilterNode, InputNode
10
+ from .schema import Node, Stream
11
+
12
+
13
+ def remove_split(current_stream: Stream, mapping: dict[Stream, Stream] = None) -> tuple[Stream, dict[Stream, Stream]]:
14
+ """
15
+ Rebuild the graph with the given mapping.
16
+
17
+ Args:
18
+ current_stream: The stream to rebuild the graph with.
19
+ mapping: The mapping to rebuild the graph with.
20
+
21
+ Returns:
22
+ A tuple of the new node and the new mapping.
23
+ """
24
+
25
+ # remove all split nodes
26
+ # add split nodes to the graph
27
+ if mapping is None:
28
+ mapping = {}
29
+
30
+ if current_stream in mapping:
31
+ return mapping[current_stream], mapping
32
+
33
+ if not current_stream.node.inputs:
34
+ mapping[current_stream] = current_stream
35
+ return current_stream, mapping
36
+
37
+ if isinstance(current_stream.node, FilterNode):
38
+ # if the current node is a split node, we need to remove it
39
+ if current_stream.node.name in ("split", "asplit"):
40
+ new_stream, _mapping = remove_split(current_stream=current_stream.node.inputs[0], mapping=mapping)
41
+ mapping[current_stream] = mapping[current_stream.node.inputs[0]]
42
+ return mapping[current_stream.node.inputs[0]], mapping
43
+
44
+ inputs = {}
45
+ for idx, input_stream in sorted(
46
+ enumerate(current_stream.node.inputs), key=lambda idx_stream: -len(idx_stream[1].node.upstream_nodes)
47
+ ):
48
+ new_stream, _mapping = remove_split(current_stream=input_stream, mapping=mapping)
49
+ inputs[idx] = new_stream
50
+ mapping |= _mapping
51
+
52
+ new_node = replace(
53
+ current_stream.node, inputs=tuple(stream for idx, stream in sorted(inputs.items(), key=lambda x: x[0]))
54
+ )
55
+ new_stream = replace(current_stream, node=new_node)
56
+
57
+ mapping[current_stream] = new_stream
58
+ return new_stream, mapping
59
+
60
+
61
+ def add_split(
62
+ current_stream: Stream,
63
+ down_node: Node = None,
64
+ down_index: int = None,
65
+ context: DAGContext = None,
66
+ mapping: dict[tuple[Stream, Node | None, int | None], Stream] = None,
67
+ ) -> tuple[Stream, dict[tuple[Stream, Node | None, int | None], Stream]]:
68
+ """
69
+ Add split nodes to the graph.
70
+
71
+ Args:
72
+ current_stream: The stream to add split nodes to.
73
+ down_node: The node use current_stream as input.
74
+ down_index: The index of the input stream in down_node.
75
+ context: The DAG context.
76
+ mapping: The mapping to add split nodes to.
77
+
78
+ Returns:
79
+ A tuple of the new node and the new mapping.
80
+ """
81
+
82
+ if not context:
83
+ context = DAGContext.build(current_stream.node)
84
+
85
+ if mapping is None:
86
+ mapping = {}
87
+
88
+ if (current_stream, down_node, down_index) in mapping:
89
+ return mapping[(current_stream, down_node, down_index)], mapping
90
+
91
+ inputs = {}
92
+
93
+ for idx, input_stream in sorted(
94
+ enumerate(current_stream.node.inputs), key=lambda idx_stream: -len(idx_stream[1].node.upstream_nodes)
95
+ ):
96
+ new_stream, _mapping = add_split(
97
+ current_stream=input_stream, down_node=current_stream.node, down_index=idx, mapping=mapping, context=context
98
+ )
99
+ inputs[idx] = new_stream
100
+ mapping |= _mapping
101
+
102
+ new_node = replace(
103
+ current_stream.node, inputs=tuple(stream for idx, stream in sorted(inputs.items(), key=lambda x: x[0]))
104
+ )
105
+ new_stream = replace(current_stream, node=new_node)
106
+
107
+ num = len(context.get_outgoing_nodes(current_stream))
108
+ if num < 2:
109
+ mapping[(current_stream, down_node, down_index)] = new_stream
110
+ return new_stream, mapping
111
+
112
+ if isinstance(current_stream.node, InputNode):
113
+ for idx, (node, index) in enumerate(context.get_outgoing_nodes(current_stream)):
114
+ # if the current node is InputNode, we don't need to split it
115
+ mapping[(current_stream, node, index)] = new_stream
116
+ return new_stream, mapping
117
+
118
+ if isinstance(new_stream, VideoStream):
119
+ split_node = new_stream.split(outputs=num)
120
+ for idx, (node, index) in enumerate(context.get_outgoing_nodes(current_stream)):
121
+ mapping[(current_stream, node, index)] = split_node.video(idx)
122
+ return mapping[(current_stream, down_node, down_index)], mapping
123
+ elif isinstance(new_stream, AudioStream):
124
+ split_node = new_stream.asplit(outputs=num)
125
+ for idx, (node, index) in enumerate(context.get_outgoing_nodes(current_stream)):
126
+ mapping[(current_stream, node, index)] = split_node.audio(idx)
127
+ return mapping[(current_stream, down_node, down_index)], mapping
128
+ else:
129
+ raise FFMpegValueError(f"unsupported stream type: {current_stream}")
130
+
131
+
132
+ def fix_graph(stream: Stream) -> Stream:
133
+ """
134
+ Fix the graph by removing and adding split nodes.
135
+
136
+ Args:
137
+ stream: The stream to fix.
138
+
139
+ Returns:
140
+ The fixed stream.
141
+
142
+ Note:
143
+ Fix the graph by resetting split nodes.
144
+ This function is for internal use only.
145
+ """
146
+
147
+ stream, _ = remove_split(stream)
148
+ stream, _ = add_split(stream)
149
+ return stream
150
+
151
+
152
+ def validate(stream: Stream, auto_fix: bool = True) -> Stream:
153
+ """
154
+ Validate the given DAG. If auto_fix is True, the graph will be automatically fixed to follow ffmpeg's rule.
155
+
156
+ Args:
157
+ stream: The DAG to validate.
158
+ auto_fix: Whether to automatically fix the graph.
159
+
160
+ Returns:
161
+ The validated DAG context.
162
+ """
163
+ if auto_fix:
164
+ stream = fix_graph(stream)
165
+
166
+ # NOTE: we don't want to modify the original node
167
+ # validators: list[] = []
168
+
169
+ # for validator in validators:
170
+ # context = validator(context)
171
+
172
+ return stream
@@ -0,0 +1,42 @@
1
+ class FFMpegError(Exception):
2
+ """
3
+ Base exception for all ffmpeg errors.
4
+ """
5
+
6
+ ...
7
+
8
+
9
+ class FFMpegTypeError(FFMpegError, TypeError):
10
+ """
11
+ Base exception for all ffmpeg type errors.
12
+ """
13
+
14
+
15
+ class FFMpegValueError(FFMpegError, ValueError):
16
+ """
17
+ Base exception for all ffmpeg value errors.
18
+ """
19
+
20
+
21
+ class FFMpegExecuteError(FFMpegError):
22
+ """
23
+ FFmpeg error
24
+ """
25
+
26
+ def __init__(self, retcode: int | None, cmd: str, stdout: bytes, stderr: bytes):
27
+ """
28
+ Initialize the exception.
29
+
30
+ Args:
31
+ retcode: The return code of the command.
32
+ cmd: The command that was run.
33
+ stdout: The stdout of the command.
34
+ stderr: The stderr of the command.
35
+ """
36
+
37
+ self.stdout = stdout
38
+ self.stderr = stderr
39
+ self.cmd = cmd
40
+ self.retcode = retcode
41
+
42
+ super(FFMpegExecuteError, self).__init__(f"{cmd} error (see stderr output for detail) {stderr!r}")