Shiina-Mahiru commited on
Commit
2a7c18b
·
verified ·
1 Parent(s): b1eed81

Upload ffmpeg\nodes.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. ffmpeg//nodes.py +377 -0
ffmpeg//nodes.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import unicode_literals
2
+
3
+ from past.builtins import basestring
4
+ from .dag import KwargReprNode
5
+ from ._utils import escape_chars, get_hash_int
6
+ from builtins import object
7
+ import os
8
+
9
+
10
+ def _is_of_types(obj, types):
11
+ valid = False
12
+ for stream_type in types:
13
+ if isinstance(obj, stream_type):
14
+ valid = True
15
+ break
16
+ return valid
17
+
18
+
19
+ def _get_types_str(types):
20
+ return ', '.join(['{}.{}'.format(x.__module__, x.__name__) for x in types])
21
+
22
+
23
+ class Stream(object):
24
+ """Represents the outgoing edge of an upstream node; may be used to create more downstream nodes."""
25
+
26
+ def __init__(
27
+ self, upstream_node, upstream_label, node_types, upstream_selector=None
28
+ ):
29
+ if not _is_of_types(upstream_node, node_types):
30
+ raise TypeError(
31
+ 'Expected upstream node to be of one of the following type(s): {}; got {}'.format(
32
+ _get_types_str(node_types), type(upstream_node)
33
+ )
34
+ )
35
+ self.node = upstream_node
36
+ self.label = upstream_label
37
+ self.selector = upstream_selector
38
+
39
+ def __hash__(self):
40
+ return get_hash_int([hash(self.node), hash(self.label)])
41
+
42
+ def __eq__(self, other):
43
+ return hash(self) == hash(other)
44
+
45
+ def __repr__(self):
46
+ node_repr = self.node.long_repr(include_hash=False)
47
+ selector = ''
48
+ if self.selector:
49
+ selector = ':{}'.format(self.selector)
50
+ out = '{}[{!r}{}] <{}>'.format(
51
+ node_repr, self.label, selector, self.node.short_hash
52
+ )
53
+ return out
54
+
55
+ def __getitem__(self, index):
56
+ """
57
+ Select a component (audio, video) of the stream.
58
+
59
+ Example:
60
+ Process the audio and video portions of a stream independently::
61
+
62
+ input = ffmpeg.input('in.mp4')
63
+ audio = input['a'].filter("aecho", 0.8, 0.9, 1000, 0.3)
64
+ video = input['v'].hflip()
65
+ out = ffmpeg.output(audio, video, 'out.mp4')
66
+ """
67
+ if self.selector is not None:
68
+ raise ValueError('Stream already has a selector: {}'.format(self))
69
+ elif not isinstance(index, basestring):
70
+ raise TypeError("Expected string index (e.g. 'a'); got {!r}".format(index))
71
+ return self.node.stream(label=self.label, selector=index)
72
+
73
+ @property
74
+ def audio(self):
75
+ """Select the audio-portion of a stream.
76
+
77
+ Some ffmpeg filters drop audio streams, and care must be taken
78
+ to preserve the audio in the final output. The ``.audio`` and
79
+ ``.video`` operators can be used to reference the audio/video
80
+ portions of a stream so that they can be processed separately
81
+ and then re-combined later in the pipeline. This dilemma is
82
+ intrinsic to ffmpeg, and ffmpeg-python tries to stay out of the
83
+ way while users may refer to the official ffmpeg documentation
84
+ as to why certain filters drop audio.
85
+
86
+ ``stream.audio`` is a shorthand for ``stream['a']``.
87
+
88
+ Example:
89
+ Process the audio and video portions of a stream independently::
90
+
91
+ input = ffmpeg.input('in.mp4')
92
+ audio = input.audio.filter("aecho", 0.8, 0.9, 1000, 0.3)
93
+ video = input.video.hflip()
94
+ out = ffmpeg.output(audio, video, 'out.mp4')
95
+ """
96
+ return self['a']
97
+
98
+ @property
99
+ def video(self):
100
+ """Select the video-portion of a stream.
101
+
102
+ Some ffmpeg filters drop audio streams, and care must be taken
103
+ to preserve the audio in the final output. The ``.audio`` and
104
+ ``.video`` operators can be used to reference the audio/video
105
+ portions of a stream so that they can be processed separately
106
+ and then re-combined later in the pipeline. This dilemma is
107
+ intrinsic to ffmpeg, and ffmpeg-python tries to stay out of the
108
+ way while users may refer to the official ffmpeg documentation
109
+ as to why certain filters drop audio.
110
+
111
+ ``stream.video`` is a shorthand for ``stream['v']``.
112
+
113
+ Example:
114
+ Process the audio and video portions of a stream independently::
115
+
116
+ input = ffmpeg.input('in.mp4')
117
+ audio = input.audio.filter("aecho", 0.8, 0.9, 1000, 0.3)
118
+ video = input.video.hflip()
119
+ out = ffmpeg.output(audio, video, 'out.mp4')
120
+ """
121
+ return self['v']
122
+
123
+
124
+ def get_stream_map(stream_spec):
125
+ if stream_spec is None:
126
+ stream_map = {}
127
+ elif isinstance(stream_spec, Stream):
128
+ stream_map = {None: stream_spec}
129
+ elif isinstance(stream_spec, (list, tuple)):
130
+ stream_map = dict(enumerate(stream_spec))
131
+ elif isinstance(stream_spec, dict):
132
+ stream_map = stream_spec
133
+ return stream_map
134
+
135
+
136
+ def get_stream_map_nodes(stream_map):
137
+ nodes = []
138
+ for stream in list(stream_map.values()):
139
+ if not isinstance(stream, Stream):
140
+ raise TypeError('Expected Stream; got {}'.format(type(stream)))
141
+ nodes.append(stream.node)
142
+ return nodes
143
+
144
+
145
+ def get_stream_spec_nodes(stream_spec):
146
+ stream_map = get_stream_map(stream_spec)
147
+ return get_stream_map_nodes(stream_map)
148
+
149
+
150
+ class Node(KwargReprNode):
151
+ """Node base"""
152
+
153
+ @classmethod
154
+ def __check_input_len(cls, stream_map, min_inputs, max_inputs):
155
+ if min_inputs is not None and len(stream_map) < min_inputs:
156
+ raise ValueError(
157
+ 'Expected at least {} input stream(s); got {}'.format(
158
+ min_inputs, len(stream_map)
159
+ )
160
+ )
161
+ elif max_inputs is not None and len(stream_map) > max_inputs:
162
+ raise ValueError(
163
+ 'Expected at most {} input stream(s); got {}'.format(
164
+ max_inputs, len(stream_map)
165
+ )
166
+ )
167
+
168
+ @classmethod
169
+ def __check_input_types(cls, stream_map, incoming_stream_types):
170
+ for stream in list(stream_map.values()):
171
+ if not _is_of_types(stream, incoming_stream_types):
172
+ raise TypeError(
173
+ 'Expected incoming stream(s) to be of one of the following types: {}; got {}'.format(
174
+ _get_types_str(incoming_stream_types), type(stream)
175
+ )
176
+ )
177
+
178
+ @classmethod
179
+ def __get_incoming_edge_map(cls, stream_map):
180
+ incoming_edge_map = {}
181
+ for downstream_label, upstream in list(stream_map.items()):
182
+ incoming_edge_map[downstream_label] = (
183
+ upstream.node,
184
+ upstream.label,
185
+ upstream.selector,
186
+ )
187
+ return incoming_edge_map
188
+
189
+ def __init__(
190
+ self,
191
+ stream_spec,
192
+ name,
193
+ incoming_stream_types,
194
+ outgoing_stream_type,
195
+ min_inputs,
196
+ max_inputs,
197
+ args=[],
198
+ kwargs={},
199
+ ):
200
+ stream_map = get_stream_map(stream_spec)
201
+ self.__check_input_len(stream_map, min_inputs, max_inputs)
202
+ self.__check_input_types(stream_map, incoming_stream_types)
203
+ incoming_edge_map = self.__get_incoming_edge_map(stream_map)
204
+
205
+ super(Node, self).__init__(incoming_edge_map, name, args, kwargs)
206
+ self.__outgoing_stream_type = outgoing_stream_type
207
+ self.__incoming_stream_types = incoming_stream_types
208
+
209
+ def stream(self, label=None, selector=None):
210
+ """Create an outgoing stream originating from this node.
211
+
212
+ More nodes may be attached onto the outgoing stream.
213
+ """
214
+ return self.__outgoing_stream_type(self, label, upstream_selector=selector)
215
+
216
+ def __getitem__(self, item):
217
+ """Create an outgoing stream originating from this node; syntactic sugar for ``self.stream(label)``.
218
+ It can also be used to apply a selector: e.g. ``node[0:'a']`` returns a stream with label 0 and
219
+ selector ``'a'``, which is the same as ``node.stream(label=0, selector='a')``.
220
+
221
+ Example:
222
+ Process the audio and video portions of a stream independently::
223
+
224
+ input = ffmpeg.input('in.mp4')
225
+ audio = input[:'a'].filter("aecho", 0.8, 0.9, 1000, 0.3)
226
+ video = input[:'v'].hflip()
227
+ out = ffmpeg.output(audio, video, 'out.mp4')
228
+ """
229
+ if isinstance(item, slice):
230
+ return self.stream(label=item.start, selector=item.stop)
231
+ else:
232
+ return self.stream(label=item)
233
+
234
+
235
+ class FilterableStream(Stream):
236
+ def __init__(self, upstream_node, upstream_label, upstream_selector=None):
237
+ super(FilterableStream, self).__init__(
238
+ upstream_node, upstream_label, {InputNode, FilterNode}, upstream_selector
239
+ )
240
+
241
+
242
+ # noinspection PyMethodOverriding
243
+ class InputNode(Node):
244
+ """InputNode type"""
245
+
246
+ def __init__(self, name, args=[], kwargs={}):
247
+ super(InputNode, self).__init__(
248
+ stream_spec=None,
249
+ name=name,
250
+ incoming_stream_types={},
251
+ outgoing_stream_type=FilterableStream,
252
+ min_inputs=0,
253
+ max_inputs=0,
254
+ args=args,
255
+ kwargs=kwargs,
256
+ )
257
+
258
+ @property
259
+ def short_repr(self):
260
+ return os.path.basename(self.kwargs['filename'])
261
+
262
+
263
+ # noinspection PyMethodOverriding
264
+ class FilterNode(Node):
265
+ def __init__(self, stream_spec, name, max_inputs=1, args=[], kwargs={}):
266
+ super(FilterNode, self).__init__(
267
+ stream_spec=stream_spec,
268
+ name=name,
269
+ incoming_stream_types={FilterableStream},
270
+ outgoing_stream_type=FilterableStream,
271
+ min_inputs=1,
272
+ max_inputs=max_inputs,
273
+ args=args,
274
+ kwargs=kwargs,
275
+ )
276
+
277
+ """FilterNode"""
278
+
279
+ def _get_filter(self, outgoing_edges):
280
+ args = self.args
281
+ kwargs = self.kwargs
282
+ if self.name in ('split', 'asplit'):
283
+ args = [len(outgoing_edges)]
284
+
285
+ out_args = [escape_chars(x, '\\\'=:') for x in args]
286
+ out_kwargs = {}
287
+ for k, v in list(kwargs.items()):
288
+ k = escape_chars(k, '\\\'=:')
289
+ v = escape_chars(v, '\\\'=:')
290
+ out_kwargs[k] = v
291
+
292
+ arg_params = [escape_chars(v, '\\\'=:') for v in out_args]
293
+ kwarg_params = ['{}={}'.format(k, out_kwargs[k]) for k in sorted(out_kwargs)]
294
+ params = arg_params + kwarg_params
295
+
296
+ params_text = escape_chars(self.name, '\\\'=:')
297
+
298
+ if params:
299
+ params_text += '={}'.format(':'.join(params))
300
+ return escape_chars(params_text, '\\\'[],;')
301
+
302
+
303
+ # noinspection PyMethodOverriding
304
+ class OutputNode(Node):
305
+ def __init__(self, stream, name, args=[], kwargs={}):
306
+ super(OutputNode, self).__init__(
307
+ stream_spec=stream,
308
+ name=name,
309
+ incoming_stream_types={FilterableStream},
310
+ outgoing_stream_type=OutputStream,
311
+ min_inputs=1,
312
+ max_inputs=None,
313
+ args=args,
314
+ kwargs=kwargs,
315
+ )
316
+
317
+ @property
318
+ def short_repr(self):
319
+ return os.path.basename(self.kwargs['filename'])
320
+
321
+
322
+ class OutputStream(Stream):
323
+ def __init__(self, upstream_node, upstream_label, upstream_selector=None):
324
+ super(OutputStream, self).__init__(
325
+ upstream_node,
326
+ upstream_label,
327
+ {OutputNode, GlobalNode, MergeOutputsNode},
328
+ upstream_selector=upstream_selector,
329
+ )
330
+
331
+
332
+ # noinspection PyMethodOverriding
333
+ class MergeOutputsNode(Node):
334
+ def __init__(self, streams, name):
335
+ super(MergeOutputsNode, self).__init__(
336
+ stream_spec=streams,
337
+ name=name,
338
+ incoming_stream_types={OutputStream},
339
+ outgoing_stream_type=OutputStream,
340
+ min_inputs=1,
341
+ max_inputs=None,
342
+ )
343
+
344
+
345
+ # noinspection PyMethodOverriding
346
+ class GlobalNode(Node):
347
+ def __init__(self, stream, name, args=[], kwargs={}):
348
+ super(GlobalNode, self).__init__(
349
+ stream_spec=stream,
350
+ name=name,
351
+ incoming_stream_types={OutputStream},
352
+ outgoing_stream_type=OutputStream,
353
+ min_inputs=1,
354
+ max_inputs=1,
355
+ args=args,
356
+ kwargs=kwargs,
357
+ )
358
+
359
+
360
+ def stream_operator(stream_classes={Stream}, name=None):
361
+ def decorator(func):
362
+ func_name = name or func.__name__
363
+ [setattr(stream_class, func_name, func) for stream_class in stream_classes]
364
+ return func
365
+
366
+ return decorator
367
+
368
+
369
+ def filter_operator(name=None):
370
+ return stream_operator(stream_classes={FilterableStream}, name=name)
371
+
372
+
373
+ def output_operator(name=None):
374
+ return stream_operator(stream_classes={OutputStream}, name=name)
375
+
376
+
377
+ __all__ = ['Stream']