8000 Merge pull request #212 from kkroening/av-ops · Powercoder64/ffmpeg-python@a3bac57 · GitHub
[go: up one dir, main page]

Skip to content

Commit a3bac57

Browse files
authored
Merge pull request kkroening#212 from kkroening/av-ops
Add `.audio` + `.video` operators
2 parents 41daf9a + 5c4a5c7 commit a3bac57

File tree

8 files changed

+134
-53
lines changed

8 files changed

+134
-53
lines changed

examples/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ With additional filtering:
104104
```python
105105
in1 = ffmpeg.input('in1.mp4')
106106
in2 = ffmpeg.input('in2.mp4')
107-
v1 = in1['v'].hflip()
108-
a1 = in1['a']
109-
v2 = in2['v'].filter('reverse').filter('hue', s=0)
110-
a2 = in2['a'].filter('areverse').filter('aphaser')
107+
v1 = in1.video.hflip()
108+
a1 = in1.audio
109+
v2 = in2.video.filter('reverse').filter('hue', s=0)
110+
a2 = in2.audio.filter('areverse').filter('aphaser')
111111
joined = ffmpeg.concat(v1, a1, v2, a2, v=1, a=1).node
112112
v3 = joined[0]
113113
a3 = joined[1].filter('volume', 0.8)

ffmpeg/__init__.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
from __future__ import unicode_literals
2-
3-
from . import _filters, _ffmpeg, _run, _probe
4-
from ._filters import *
2+
from . import nodes
3+
from . import _ffmpeg
4+
from . import _filters
5+
from . import _probe
6+
from . import _run
7+
from . import _view
8+
from .nodes import *
59
from ._ffmpeg import *
10+
from ._filters import *
11+
from ._probe import *
612
from ._run import *
713
from ._view import *
8-
from ._probe import *
914

10-
__all__ = _filters.__all__ + _ffmpeg.__all__ + _run.__all__ + _view.__all__ + _probe.__all__
15+
__all__ = (
16+
nodes.__all__
17+
+ _ffmpeg.__all__
18+ + _probe.__all__
19+
+ _run.__all__
20+
+ _view.__all__
21+
+ _filters.__all__
22+
)

ffmpeg/_filters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
def filter_multi_output(stream_spec, filter_name, *args, **kwargs):
99
"""Apply custom filter with one or more outputs.
1010
11-
This is the same as ``filter_`` except that the filter can produce more than one output.
11+
This is the same as ``filter`` except that the filter can produce more than one output.
1212
1313
To reference an output stream, use either the ``.stream`` operator or bracket shorthand:
1414

ffmpeg/nodes.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ def __getitem__(self, index):
5353
Process the audio and video portions of a stream independently::
5454
5555
input = ffmpeg.input('in.mp4')
56-
audio = input[:'a'].filter("aecho", 0.8, 0.9, 1000, 0.3)
57-
video = input[:'v'].hflip()
56+
audio = input['a'].filter("aecho", 0.8, 0.9, 1000, 0.3)
57+
video = input['v'].hflip()
5858
out = ffmpeg.output(audio, video, 'out.mp4')
5959
"""
6060
if self.selector is not None:
@@ -63,6 +63,56 @@ def __getitem__(self, index):
6363
raise TypeError("Expected string index (e.g. 'a'); got {!r}".format(index))
6464
return self.node.stream(label=self.label, selector=index)
6565

66+
@property
67+
def audio(self):
68+
"""Select the audio-portion of a stream.
69+
70+
Some ffmpeg filters drop audio streams, and care must be taken
71+
to preserve the audio in the final output. The ``.audio`` and
72+
``.video`` operators can be used to reference the audio/video
73+
portions of a stream so that they can be processed separately
74+
and then re-combined later in the pipeline. This dilemma is
75+
intrinsic to ffmpeg, and ffmpeg-python tries to stay out of the
76+
way while users may refer to the official ffmpeg documentation
77+
as to why certain filters drop audio.
78+
79+
``stream.audio`` is a shorthand for ``stream['a']``.
80+
81+
Example:
82+
Process the audio and video portions of a stream independently::
83+
84+
input = ffmpeg.input('in.mp4')
85+
audio = input.audio.filter("aecho", 0.8, 0.9, 1000, 0.3)
86+
video = input.video.hflip()
87+
out = ffmpeg.output(audio, video, 'out.mp4')
88+
"""
89+
return self['a']
90+
91+
@property
92+
def video(self):
93+
"""Select the video-portion of a stream.
94+
95+
Some ffmpeg filters drop audio streams, and care must be taken
96+
to preserve the audio in the final output. The ``.audio`` and
97+
``.video`` operators can be used to reference the audio/video
98+
portions of a stream so that they can be processed separately
99+
and then re-combined later in the pipeline. This dilemma is
100+
intrinsic to ffmpeg, and ffmpeg-python tries to stay out of the
101+
way while users may refer to the official ffmpeg documentation
102+
as to why certain filters drop audio.
103+
104+
``stream.video`` is a shorthand for ``stream['v']``.
105+
106+
Example:
107+
Process the audio and video portions of a stream independently::
108+
109+
input = ffmpeg.input('in.mp4')
110+
audio = input.audio.filter("aecho", 0.8, 0.9, 1000, 0.3)
111+
video = input.video.hflip()
112+
out = ffmpeg.output(audio, video, 'out.mp4')
113+
"""
114+
return self['v']
115+
66116

67117
def get_stream_map(stream_spec):
68118
if stream_spec is None:
@@ -286,3 +336,8 @@ def filter_operator(name=None):
286336

287337
def output_operator(name=None):
288338
return stream_operator(stream_classes={OutputStream}, name=name)
339+
340+
341+
__all__ = [
342+
'Stream',
343+
]

ffmpeg/tests/test_ffmpeg.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,15 @@ def test_combined_output():
175175
]
176176

177177

178-
def test_filter_with_selector():
178+
@pytest.mark.parametrize('use_shorthand', [True, False])
179+
def test_filter_with_selector(use_shorthand):
179180
i = ffmpeg.input(TEST_INPUT_FILE1)
180-
v1 = i['v'].hflip()
181-
a1 = i['a'].filter('aecho', 0.8, 0.9, 1000, 0.3)
181+
if use_shorthand:
182+
v1 = i.video.hflip()
183+
a1 = i.audio.filter('aecho', 0.8, 0.9, 1000, 0.3)
184+
else:
185+
v1 = i['v'].hflip()
186+
a1 = i['a'].filter('aecho', 0.8, 0.9, 1000, 0.3)
182187
out = ffmpeg.output(a1, v1, TEST_OUTPUT_FILE1)
183188
assert out.get_args() == [
184189
'-i', TEST_INPUT_FILE1,
@@ -273,7 +278,7 @@ def test_filter_concat__audio_only():
273278
def test_filter_concat__audio_video():
274279
in1 = ffmpeg.input('in1.mp4')
275280
in2 = ffmpeg.input('in2.mp4')
276-
joined = ffmpeg.concat(in1['v'], in1['a'], in2.hflip(), in2['a'], v=1, a=1).node
281+
joined = ffmpeg.concat(in1.video, in1.audio, in2.hflip(), in2['a'], v=1, a=1).node
277282
args = (
278283
ffmpeg
279284
.output(joined[0], joined[1], 'out.mp4')
@@ -298,7 +303,7 @@ def test_filter_concat__wrong_stream_count():
298303
in1 = ffmpeg.input('in1.mp4')
299304
in2 = ffmpeg.input('in2.mp4')
300305
with pytest.raises(ValueError) as excinfo:
301-
ffmpeg.concat(in1['v'], in1['a'], in2.hflip(), v=1, a=1).node
306+
ffmpeg.concat(in1.video, in1.audio, in2.hflip(), v=1, a=1).node
302307
assert str(excinfo.value) == \
303308
'Expected concat input streams to have length multiple of 2 (v=1, a=1); got 3'
304309

requirements-base.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

requirements.txt

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
1-
alabaster==0.7.10
2-
apipkg==1.4
3-
Babel==2.5.1
4-
certifi==2017.7.27.1
1+
alabaster==0.7.12
2+
atomicwrites==1.3.0
3+
attrs==19.1.0
4+
Babel==2.7.0
5+
certifi==2019.3.9
56
chardet==3.0.4
67
docutils==0.14
7-
execnet==1.5.0
8-
funcsigs==1.0.2
9-
future==0.16.0
10-
idna==2.6
11-
imagesize==0.7.1
12-
Jinja2==2.9.6
13-
MarkupSafe==1.0
14-
mock==2.0.0
15-
pbr==4.0.3
16-
pluggy==0.5.2
17-
py==1.4.34
18-
Pygments==2.2.0
19-
pytest==3.2.3
20-
pytest-forked==0.2
21-
pytest-mock==1.10.0
22-
pytest-runner==3.0
23-
pytest-xdist==1.22.2
24-
pytz==2017.3
25-
requests==2.18.4
26-
six==1.11.0
8+
filelock==3.0.12
9+
future==0.17.1
10+
idna==2.8
11+
imagesize==1.1.0
12+
importlib-metadata==0.17
13+
Jinja2==2.10.1
14+
MarkupSafe==1.1.1
15+
more-itertools==7.0.0
16+
packaging==19.0
17+
pluggy==0.12.0
18+
py==1.8.0
19+
Pygments==2.4.2
20+
pyparsing==2.4.0
21+
pytest==4.6.1
22+
pytest-mock==1.10.4
23+
pytz==2019.1
24+
requests==2.22.0
25+
six==1.12.0
2726
snowballstemmer==1.2.1
28-
Sphinx==1.6.5
29-
sphinxcontrib-websupport==1.0.1
30-
tox==2.9.1
31-
typing==3.6.2
32-
urllib3==1.22
33-
virtualenv==15.1.0
27+
Sphinx==2.1.0
28+
sphinxcontrib-applehelp==1.0.1
29+
sphinxcontrib-devhelp==1.0.1
30+
sphinxcontrib-htmlhelp==1.0.2
31+
sphinxcontrib-jsmath==1.0.1
32+
sphinxcontrib-qthelp==1.0.2
33+
sphinxcontrib-serializinghtml==1.1.3
34+
toml==0.10.0
35+
tox==3.12.1
36+
urllib3==1.25.3
37+
virtualenv==16.6.0
38+
wcwidth==0.1.7
39+
zipp==0.5.1

setup.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@
6767
keywords=keywords,
6868
long_description=long_description,
6969
install_requires=['future'],
70+
extras_require={
71+
'dev': [
72+
'future==0.17.1',
73+
'pytest-mock==1.10.4',
74+
'pytest==4.6.1',
75+
'Sphinx==2.1.0',
76+
'tox==3.12.1',
77+
],
78+
},
7079
classifiers=[
7180
'Intended Audience :: Developers',
7281
'License :: OSI Approved :: Apache Software License',

0 commit comments

Comments
 (0)
0