8000 Merge pull request #20 from kkroening/feature/17 · craigcannon/ffmpeg-python@7669492 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7669492

Browse files
authored
Merge pull request kkroening#20 from kkroening/feature/17
Add support for multi-output filters; implement `split` filter
2 parents 17e9e46 + 4640ada commit 7669492

File tree

10 files changed

+659
-246
lines changed

10 files changed

+659
-246
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
.eggs
33
.tox/
44
dist/
5-
ffmpeg/tests/sample_data/dummy2.mp4
5+
ffmpeg/tests/sample_data/out*.mp4
66
ffmpeg_python.egg-info/
77
venv*

ffmpeg/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import unicode_literals
2+
23
from . import _filters, _ffmpeg, _run
34
from ._filters import *
45
from ._ffmpeg import *
56
from ._run import *
7+
68
__all__ = _filters.__all__ + _ffmpeg.__all__ + _run.__all__

ffmpeg/_ffmpeg.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import unicode_literals
2+
23
from .nodes import (
3-
FilterNode,
4+
filter_operator,
45
GlobalNode,
56
InputNode,
6-
operator,
7+
MergeOutputsNode,
78
OutputNode,
9+
output_operator,
810
)
911

1012

@@ -19,27 +21,27 @@ def input(filename, **kwargs):
1921
if 'format' in kwargs:
2022
raise ValueError("Can't specify both `format` and `f` kwargs")
2123
kwargs['format'] = fmt
22-
return InputNode(input.__name__, **kwargs)
24+
return InputNode(input.__name__, kwargs=kwargs).stream()
2325

2426

25-
@operator(node_classes={OutputNode, GlobalNode})
26-
def overwrite_output(parent_node):
27+
@output_operator()
28+
def overwrite_output(stream):
2729
"""Overwrite output files without asking (ffmpeg ``-y`` option)
2830
2931
Official documentation: `Main options <https://ffmpeg.org/ffmpeg.html#Main-options>`__
3032
"""
31-
return GlobalNode(parent_node, overwrite_output.__name__)
33+
return GlobalNode(stream, overwrite_output.__name__).stream()
3234

3335

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

4042

41-
@operator(node_classes={InputNode, FilterNode})
42-
def output(parent_node, filename, **kwargs):
43+
@filter_operator()
44+
def output(stream, filename, **kwargs):
4345
"""Output file URL
4446
4547
Official documentation: `Synopsis <https://ffmpeg.org/ffmpeg.html#Synopsis>`__
@@ -50,7 +52,7 @@ def output(parent_node, filename, **kwargs):
5052
if 'format' in kwargs:
5153
raise ValueError("Can't specify both `format` and `f` kwargs")
5254
kwargs['format'] = fmt
53-
return OutputNode([parent_node], output.__name__, **kwargs)
55+
return OutputNode(stream, output.__name__, kwargs=kwargs).stream()
5456

5557

5658

ffmpeg/_filters.py

Lines changed: 61 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,71 @@
11
from __future__ import unicode_literals
22

3-
from .nodes import FilterNode, operator
3+
from .nodes import FilterNode, filter_operator
44
from ._utils import escape_chars
55

66

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

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

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

50+
@filter_operator()
51+
def split(stream):
52+
return FilterNode(stream, split.__name__)
5753

5854

59-
@operator()
60-
def setpts(parent_node, expr):
55+
@filter_operator()
56+
def setpts(stream, expr):
6157
"""Change the PTS (presentation timestamp) of the input frames.
6258
6359
Args:
6460
expr: The expression which is evaluated for each frame to construct its timestamp.
6561
6662
Official documentation: `setpts, asetpts <https://ffmpeg.org/ffmpeg-filters.html#setpts_002c-asetpts>`__
6763
"""
68-
return filter_(parent_node, setpts.__name__, expr)
64+
return FilterNode(stream, setpts.__name__, args=[expr]).stream()
6965

7066

71-
@operator()
72-
def trim(parent_node, **kwargs):
67+
@filter_operator()
68+
def trim(stream, **kwargs):
7369
"""Trim the input so that the output contains one continuous subpart of the input.
7470
7571
Args:
@@ -87,10 +83,10 @@ def trim(parent_node, **kwargs):
8783
8884
Official documentation: `trim <https://ffmpeg.org/ffmpeg-filters.html#trim>`__
8985
"""
90-
return filter_(parent_node, trim.__name__, **kwargs)
86+
return FilterNode(stream, trim.__name__, kwargs=kwargs).stream()
9187

9288

93-
@operator()
89+
@filter_operator()
9490
def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs):
9591
"""Overlay one video on top of another.
9692
@@ -135,29 +131,29 @@ def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs
135131
Official documentation: `overlay <https://ffmpeg.org/ffmpeg-filters.html#overlay-1>`__
136132
"""
137133
kwargs['eof_action'] = eof_action
138-
return filter_multi([main_parent_node, overlay_parent_node], overlay.__name__, **kwargs)
134+
return FilterNode([main_parent_node, overlay_parent_node], overlay.__name__, kwargs=kwargs, max_inputs=2).stream()
139135

140136

141-
@operator()
142-
def hflip(parent_node):
137+
@filter_operator()
138+
def hflip(stream):
143139
"""Flip the input video horizontally.
144140
145141
Official documentation: `hflip <https://ffmpeg.org/ffmpeg-filters.html#hflip>`__
146142
"""
147-
return filter_(parent_node, hflip.__name__)
143+
return FilterNode(stream, hflip.__name__).stream()
148144

149145

150-
@operator()
151-
def vflip(parent_node):
146+
@filter_operator()
147+
def vflip(stream):
152148
"""Flip the input video vertically.
153149
154150
Official documentation: `vflip <https://ffmpeg.org/ffmpeg-filters.html#vflip>`__
155151
"""
156-
return filter_(parent_node, vflip.__name__)
152+
return FilterNode(stream, vflip.__name__).stream()
157153

158154

159-
@operator()
160-
def drawbox(parent_node, x, y, width, height, color, thickness=None, **kwargs):
155+
@filter_operator()
156+
def drawbox(stream, x, y, width, height, color, thickness=None, **kwargs):
161157
"""Draw a colored box on the input image.
162158
163159
Args:
@@ -178,11 +174,11 @@ def drawbox(parent_node, x, y, width, height, color, thickness=None, **kwargs):
178174
"""
179175
if thickness:
180176
kwargs['t'] = thickness
181-
return filter_(parent_node, drawbox.__name__, x, y, width, height, color, **kwargs)
177+
return FilterNode(stream, drawbox.__name__, args=[x, y, width, height, color], kwargs=kwargs).stream()
182178

183179

184-
@operator()
185-
def drawtext(parent_node, text=None, x=0, y=0, escape_text=True, **kwargs):
180+
@filter_operator()
181+
def drawtext(stream, text=None, x=0, y=0, escape_text=True, **kwargs):
186182
"""Draw a text string or text from a specified file on top of a video, using the libfreetype library.
187183
188184
To enable compilation of this filter, you need to configure FFmpeg with ``--enable-libfreetype``. To enable default
@@ -320,11 +316,11 @@ def drawtext(parent_node, text=None, x=0, y=0, escape_text=True, **kwargs):
320316
kwargs['x'] = x
321317
if y != 0:
322318
kwargs['y'] = y
323-
return filter_(parent_node, drawtext.__name__, **kwargs)
319+
return filter_(stream, drawtext.__name__, **kwargs)
324320

325321

326-
@operator()
327-
def concat(*parent_nodes, **kwargs):
322+
@filter_operator()
323+
def concat(*streams, **kwargs):
328324
"""Concatenate audio and video streams, joining them together one after the other.
329325
330326
The filter works on segments of synchronized video and audio streams. All segments must have the same number of
@@ -349,12 +345,12 @@ def concat(*parent_nodes, **kwargs):
349345
350346
Official documentation: `concat <https://ffmpeg.org/ffmpeg-filters.html#concat>`__
351347
"""
352-
kwargs['n'] = len(parent_nodes)
353-
return filter_multi(parent_nodes, concat.__name__, **kwargs)
348+
kwargs['n'] = len(streams)
349+
return FilterNode(streams, concat.__name__, kwargs=kwargs, max_inputs=None).stream()
354350

355351

356-
@operator()
357-
def zoompan(parent_node, **kwargs):
352+
@filter_operator()
353+
def zoompan(stream, **kwargs):
358354
"""Apply Zoom & Pan effect.
359355
360356
Args:
@@ -369,11 +365,11 @@ def zoompan(parent_node, **kwargs):
369365
370366
Official documentation: `zoompan <https://ffmpeg.org/ffmpeg-filters.html#zoompan>`__
371367
"""
372-
return filter_(parent_node, zoompan.__name__, **kwargs)
368+
return FilterNode(stream, zoompan.__name__, kwargs=kwargs).stream()
373369

374370

375-
@operator()
376-
def hue(parent_node, **kwargs):
371+
@filter_operator()
372+
def hue(stream, **kwargs):
377373
"""Modify the hue and/or the saturation of the input.
378374
379375
Args:
@@ -384,24 +380,23 @@ def hue(parent_node, **kwargs):
384380
385381
Official documentation: `hue <https://ffmpeg.org/ffmpeg-filters.html#hue>`__
386382
"""
387-
return filter_(parent_node, hue.__name__, **kwargs)
383+
return FilterNode(stream, hue.__name__, kwargs=kwargs).stream()
388384

389385

390-
@operator()
391-
def colorchannelmixer(parent_node, *args, **kwargs):
386+
@filter_operator()
387+
def colorchannelmixer(stream, *args, **kwargs):
392388
"""Adjust video input frames by re-mixing color channels.
393389
394390
Official documentation: `colorchannelmixer <https://ffmpeg.org/ffmpeg-filters.html#colorchannelmixer>`__
395391
"""
396-
return filter_(parent_node, colorchannelmixer.__name__, **kwargs)
392+
return FilterNode(stream, colorchannelmixer.__name__, kwargs=kwargs).stream()
397393

398394

399395
__all__ = [
400396
'colorchannelmixer',
401397
'concat',
402398
'drawbox',
403399
'filter_',
404-
'filter_multi',
405400
'hflip',
406401
'hue',
407402
'overlay',

0 commit comments

Comments
 (0)
0