8000 Massive refactor to break nodes into streams+nodes · Powercoder64/ffmpeg-python@6887ad8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6887ad8

Browse files
committed
Massive refactor to break nodes into streams+nodes
1 parent aa5156d commit 6887ad8

File tree

8 files changed

+324
-174
lines changed

8 files changed

+324
-174
lines changed

ffmpeg/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
from ._filters import *
44
from ._ffmpeg import *
55
from ._run import *
6-
__all__ = _filters.__all__ + _ffmpeg.__all__ + _run.__all__
6+
from ._view import *
7+
__all__ = _filters.__all__ + _ffmpeg.__all__ + _run.__all__ + _view.__all__

ffmpeg/_ffmpeg.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from __future__ import unicode_literals
22
from .nodes import (
3-
FilterNode,
3+
filter_operator,
44
GlobalNode,
55
InputNode,
6-
operator,
6+
MergeOutputsNode,
77
OutputNode,
8+
output_operator,
89
)
910

1011

@@ -19,27 +20,27 @@ def input(filename, **kwargs):
1920
if 'format' in kwargs:
2021
raise ValueError("Can't specify both `format` and `f` kwargs")
2122
kwargs['format'] = fmt
22-
return InputNode(input.__name__, **kwargs)
23+
return InputNode(input.__name__, kwargs=kwargs).stream()
2324

2425

25-
@operator(node_classes={OutputNode, GlobalNode})
26-
def overwrite_output(parent_node):
26+
@output_operator()
27+
def overwrite_output(stream):
2728
"""Overwrite output files without asking (ffmpeg ``-y`` option)
2829
2930
Official documentation: `Main options <https://ffmpeg.org/ffmpeg.html#Main-options>`__
3031
"""
31-
return GlobalNode(parent_node, overwrite_output.__name__)
32+
return GlobalNode(stream, overwrite_output.__name__).stream()
3233

3334

34-
@operator(node_classes={OutputNode})
35-
def merge_outputs(*parent_nodes):
35+
@output_operator()
36+
def merge_outputs(*streams):
3637
"""Include all given outputs in one ffmpeg command line
3738
"""
38-
return OutputNode(parent_nodes, merge_outputs.__name__)
39+
return MergeOutputsNode(streams, merge_outputs.__name__).stream()
3940

4041

41-
@operator(node_classes={InputNode, FilterNode})
42-
def output(parent_node, filename, **kwargs):
42+
@filter_operator()
43+
def output(stream, filename, **kwargs):
4344
"""Output file URL
4445
4546
Official documentation: `Synopsis <https://ffmpeg.org/ffmpeg.html#Synopsis>`__
@@ -50,7 +51,7 @@ def output(parent_node, filename, **kwargs):
5051
if 'format' in kwargs:
5152
raise ValueError("Can't specify both `format` and `f` kwargs")
5253
kwargs['format'] = fmt
53-
return OutputNode([parent_node], output.__name__, **kwargs)
54+
return OutputNode(stream, output.__name__, kwargs=kwargs).stream()
5455

5556

5657

ffmpeg/_filters.py

Lines changed: 54 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,67 @@
11
from __future__ import unicode_literals
22
from .nodes import (
33
FilterNode,
4-
operator,
4+
filter_operator,
55
)
66

77

8-
@operator()
9-
def filter_(parent_node, filter_name, *args, **kwargs):
10-
"""Apply custom single-source filter.
8+
@filter_operator()
9+
def filter_multi_output(stream_spec, filter_name, *args, **kwargs):
10+
"""Apply custom filter with one or more outputs.
1111
12-
``filter_`` is normally used by higher-level filter functions such as ``hflip``, but if a filter implementation
13-
is missing from ``fmpeg-python``, you can call ``filter_`` directly to have ``fmpeg-python`` pass the filter name
14-
and arguments to ffmpeg verbatim.
15-
16-
Args:
17-
parent_node: Source stream to apply filter to.
18-
filter_name: ffmpeg filter name, e.g. `colorchannelmixer`
19-
*args: list of args to pass to ffmpeg verbatim
20-
**kwargs: list of keyword-args to pass to ffmpeg verbatim
12+
This is the same as ``filter_`` except that the filter can produce more than one output.
2113
22-
This function is used internally by all of the other single-source filters (e.g. ``hflip``, ``crop``, etc.).
23-
For custom multi-source filters, see ``filter_multi`` instead.
24-
25-
The function name is suffixed with ``_`` in order avoid confusion with the standard python ``filter`` function.
14+
To reference an output stream, use either the ``.stream`` operator or bracket shorthand:
2615
2716
Example:
2817
29-
``ffmpeg.input('in.mp4').filter_('hflip').output('out.mp4').run()``
18+
```
19+
split = ffmpeg.input('in.mp4').filter_multi_output('split')
20+
split0 = split.stream(0)
21+
split1 = split[1]
22+
ffmpeg.concat(split0, split1).output('out.mp4').run()
23+
```
3024
"""
31-
return FilterNode([parent_node], filter_name, *args, **kwargs)
32-
25+
return FilterNode(stream_spec, filter_name, args=args, kwargs=kwargs, max_inputs=None)
3326

34-
def filter_multi(parent_nodes, filter_name, *args, **kwargs):
35-
"""Apply custom multi-source filter.
3627

37-
This is nearly identical to the ``filter`` function except that it allows filters to be applied to multiple
38-
streams. It's normally used by higher-level filter functions such as ``concat``, but if a filter implementation
39-
is missing from ``fmpeg-python``, you can call ``filter_multi`` directly.
28+
@filter_operator()
29+
def filter_(stream_spec, filter_name, *args, **kwargs):
30+
"""Apply custom filter.
4031
41-
Note that because it applies to multiple streams, it can't be used as an operator, unlike the ``filter`` function
42-
(e.g. ``ffmpeg.input('in.mp4').filter_('hflip')``)
32+
``filter_`` is normally used by higher-level filter functions such as ``hflip``, but if a filter implementation
33+
is missing from ``fmpeg-python``, you can call ``filter_`` directly to have ``fmpeg-python`` pass the filter name
34+
and arguments to ffmpeg verbatim.
4335
4436
Args:
45-
parent_nodes: List of source streams to apply filter to.
46-
filter_name: ffmpeg filter name, e.g. `concat`
37+
stream_spec: a Stream, list of Streams, or label-to-Stream dictionary mapping
38+
filter_name: ffmpeg filter name, e.g. `colorchannelmixer`
4739
*args: list of args to pass to ffmpeg verbatim
4840
**kwargs: list of keyword-args to pass to ffmpeg verbatim
4941
50-
For custom single-source filters, see ``filter_multi`` instead.
42+
The function name is suffixed with ``_`` in order avoid confusion with the standard python ``filter`` function.
5143
5244
Example:
5345
54-
``ffmpeg.filter_multi(ffmpeg.input('in1.mp4'), ffmpeg.input('in2.mp4'), 'concat', n=2).output('out.mp4').run()``
46+
``ffmpeg.input('in.mp4').filter_('hflip').output('out.mp4').run()``
5547
"""
56-
return FilterNode(parent_nodes, filter_name, *args, **kwargs)
57-
48+
return filter_multi_output(stream_spec, filter_name, *args, **kwargs).stream()
5849

5950

60-
@operator()
61-
def setpts(parent_node, expr):
51+
@filter_operator()
52+
def setpts(stream, expr):
6253
"""Change the PTS (presentation timestamp) of the input frames.
6354
6455
Args:
6556
expr: The expression which is evaluated for each frame to construct its timestamp.
6657
6758
Official documentation: `setpts, asetpts <https://ffmpeg.org/ffmpeg-filters.html#setpts_002c-asetpts>`__
6859
"""
69-
return filter_(parent_node, setpts.__name__, expr)
60+
return FilterNode(stream, setpts.__name__, args=[expr]).stream()
7061

7162

72-
@operator()
73-
def trim(parent_node, **kwargs):
63+
@filter_operator()
64+
def trim(stream, **kwargs):
7465
"""Trim the input so that the output contains one continuous subpart of the input.
7566
7667
Args:
@@ -88,10 +79,10 @@ def trim(parent_node, **kwargs):
8879
8980
Official documentation: `trim <https://ffmpeg.org/ffmpeg-filters.html#trim>`__
9081
"""
91-
return filter_(parent_node, trim.__name__, **kwargs)
82+
return FilterNode(stream, trim.__name__, kwargs=kwargs).stream()
9283

9384

94-
@operator()
85+
@filter_operator()
9586
def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs):
9687
"""Overlay one video on top of another.
9788
@@ -136,29 +127,29 @@ def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs
136127
Official documentation: `overlay <https://ffmpeg.org/ffmpeg-filters.html#overlay-1>`__
137128
"""
138129
kwargs['eof_action'] = eof_action
139-
return filter_multi([main_parent_node, overlay_parent_node], overlay.__name__, **kwargs)
130+
return FilterNode([main_parent_node, overlay_parent_node], overlay.__name__, kwargs=kwargs, max_inputs=2).stream()
140131

141132

142-
@operator()
143-
def hflip(parent_node):
133+
@filter_operator()
134+
def hflip(stream):
144135
"""Flip the input video horizontally.
145136
146137
Official documentation: `hflip <https://ffmpeg.org/ffmpeg-filters.html#hflip>`__
147138
"""
148-
return filter_(parent_node, hflip.__name__)
139+
return FilterNode(stream, hflip.__name__).stream()
149140

150141

151-
@operator()
152-
def vflip(parent_node):
142+
@filter_operator()
143+
def vflip(stream):
153144
"""Flip the input video vertically.
154145
155146
Official documentation: `vflip <https://ffmpeg.org/ffmpeg-filters.html#vflip>`__
156147
"""
157-
return filter_(parent_node, vflip.__name__)
148+
return FilterNode(stream, vflip.__name__).stream()
158149

159150

160-
@operator()
161-
def drawbox(parent_node, x, y, width, height, color, thickness=None, **kwargs):
151+
@filter_operator()
152+
def drawbox(stream, x, y, width, height, color, thickness=None, **kwargs):
162153
"""Draw a colored box on the input image.
163154
164155
Args:
@@ -179,11 +170,11 @@ def drawbox(parent_node, x, y, width, height, color, thickness=None, **kwargs):
179170
"""
180171
if thickness:
181172
kwargs['t'] = thickness
182-
return filter_(parent_node, drawbox.__name__, x, y, width, height, color, **kwargs)
173+
return FilterNode(stream, drawbox.__name__, args=[x, y, width, height, color], kwargs=kwargs).stream()
183174

184175

185-
@operator()
186-
def concat(*parent_nodes, **kwargs):
176+
@filter_operator()
177+
def concat(*streams, **kwargs):
187178
"""Concatenate audio and video streams, joining them together one after the other.
188179
189180
The filter works on segments of synchronized video and audio streams. All segments must have the same number of
@@ -208,12 +199,12 @@ def concat(*parent_nodes, **kwargs):
208199
209200
Official documentation: `concat <https://ffmpeg.org/ffmpeg-filters.html#concat>`__
210201
"""
211-
kwargs['n'] = len(parent_nodes)
212-
return filter_multi(parent_nodes, concat.__name__, **kwargs)
202+
kwargs['n'] = len(streams)
203+
return FilterNode(streams, concat.__name__, kwargs=kwargs, max_inputs=None).stream()
213204

214205

215-
@operator()
216-
def zoompan(parent_node, **kwargs):
206+
@filter_operator()
207+
def zoompan(stream, **kwargs):
217208
"""Apply Zoom & Pan effect.
218209
219210
Args:
@@ -228,11 +219,11 @@ def zoompan(parent_node, **kwargs):
228219
229220
Official documentation: `zoompan <https://ffmpeg.org/ffmpeg-filters.html#zoompan>`__
230221
"""
231-
return filter_(parent_node, zoompan.__name__, **kwargs)
222+
return FilterNode(stream, zoompan.__name__, kwargs=kwargs).stream()
232223

233224

234-
@operator()
235-
def hue(parent_node, **kwargs):
225+
@filter_operator()
226+
def hue(stream, **kwargs):
236227
"""Modify the hue and/or the saturation of the input.
237228
238229
Args:
@@ -243,24 +234,23 @@ def hue(parent_node, **kwargs):
243234
244235
Official documentation: `hue <https://ffmpeg.org/ffmpeg-filters.html#hue>`__
245236
"""
246-
return filter_(parent_node, hue.__name__, **kwargs)
237+
return FilterNode(stream, hue.__name__, kwargs=kwargs).stream()
247238

248239

249-
@operator()
250-
def colorchannelmixer(parent_node, *args, **kwargs):
240+
@filter_operator()
241+
def colorchannelmixer(stream, *args, **kwargs):
251242
"""Adjust video input frames by re-mixing color channels.
252243
253244
Official documentation: `colorchannelmixer <https://ffmpeg.org/ffmpeg-filters.html#colorchannelmixer>`__
254245
"""
255-
return filter_(parent_node, colorchannelmixer.__name__, **kwargs)
246+
return FilterNode(stream, colorchannelmixer.__name__, kwargs=kwargs).stream()
256247

257248

258249
__all__ = [
259250
'colorchannelmixer',
260251
'concat',
261252
'drawbox',
262253
'filter_',
263-
'filter_multi',
264254
'hflip',
265255
'hue',
266256
'overlay',

ffmpeg/_run.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,23 @@
44
from functools import reduce
55
from past.builtins import basestring
66
import copy
7-
import operator as _operator
7+
import operator
88
import subprocess as _subprocess
99

1010
from ._ffmpeg import (
1111
input,
12-
merge_outputs,
1312
output,
1413
overwrite_output,
1514
)
1615
from .nodes import (
1716
GlobalNode,
1817
InputNode,
19-
operator,
2018
OutputNode,
19+
output_operator,
20+
Stream,
2121
)
2222

23+
2324
def _get_stream_name(name):
2425
return '[{}]'.format(name)
2526

@@ -73,48 +74,47 @@ def _get_global_args(node):
7374

7475

7576
def _get_output_args(node, stream_name_map):
77+
if node.name != output.__name__:
78+
raise ValueError('Unsupported output node: {}'.format(node))
7679
args = []
77-
if node.name != merge_outputs.__name__:
78-
assert len(node.incoming_edges) == 1
79-
stream_name = stream_name_map[node.incoming_edges[0].upstream_node]
80-
if stream_name != '[0]':
81-
args += ['-map', stream_name]
82-
if node.name == output.__name__:
83-
kwargs = copy.copy(node.kwargs)
84-
filename = kwargs.pop('filename')
85-
fmt = kwargs.pop('format', None)
86-
if fmt:
87-
args += ['-f', fmt]
88-
args += _convert_kwargs_to_cmd_line_args(kwargs)
89-
args += [filename]
90-
else:
91-
raise ValueError('Unsupported output node: {}'.format(node))
80+
assert len(node.incoming_edges) == 1
81+
stream_name = stream_name_map[node.incoming_edges[0].upstream_node]
82+
if stream_name != '[0]':
83+
args += ['-map', stream_name]
84+
kwargs = copy.copy(node.kwargs)
85+
filename = kwargs.pop('filename')
86+
fmt = kwargs.pop('format', None)
87+
if fmt:
88+
args += ['-f', fmt]
89+
args += _convert_kwargs_to_cmd_line_args(kwargs)
90+
args += [filename]
9291
return args
9392

9493

95-
@operator(node_classes={OutputNode, GlobalNode})
96-
def get_args(node):
94+
@output_operator()
95+
def get_args(stream):
9796
"""Get command-line arguments for ffmpeg."""
97+
if not isinstance(stream, Stream):
98+
raise TypeError('Expected Stream; got {}'.format(type(stream)))
9899
args = []
99100
# TODO: group nodes together, e.g. `-i somefile -r somerate`.
100-
sorted_nodes, outgoing_edge_maps = topo_sort([node])
101-
del(node)
101+
sorted_nodes, outgoing_edge_maps = topo_sort([stream.node])
102102
input_nodes = [node for node in sorted_nodes if isinstance(node, InputNode)]
103103
output_nodes = [node for node in sorted_nodes if isinstance(node, OutputNode) and not
104104
isinstance(node, GlobalNode)]
105105
global_nodes = [node for node in sorted_nodes if isinstance(node, GlobalNode)]
106106
filter_nodes = [node for node in sorted_nodes if node not in (input_nodes + output_nodes + global_nodes)]
107107
stream_name_map = {node: _get_stream_name(i) for i, node in enumerate(input_nodes)}
108108
filter_arg = _get_filter_arg(filter_nodes, stream_name_map)
109-
args += reduce(_operator.add, [_get_input_args(node) for node in input_nodes])
109+
args += reduce(operator.add, [_get_input_args(node) for node in input_nodes])
110110
if filter_arg:
111111
args += ['-filter_complex', filter_arg]
112-
args += reduce(_operator.add, [_get_output_args(node, stream_name_map) for node in output_nodes])
113-
args += reduce(_operator.add, [_get_global_args(node) for node in global_nodes], [])
112+
args += reduce(operator.add, [_get_output_args(node, stream_name_map) for node in output_nodes])
113+
args += reduce(operator.add, [_get_global_args(node) for node in global_nodes], [])
114114
return args
115115

116116

117-
@operator(node_classes={OutputNode, GlobalNode})
117+
@output_operator()
118118
def run(node, cmd='ffmpeg'):
119119
"""Run ffmpeg on node graph."""
120120
if isinstance(cmd, basestring):

0 commit comments

Comments
 (0)
0