8000 Add `.audio` + `.video` operators · Powercoder64/ffmpeg-python@881ae4e · GitHub
[go: up one dir, main page]

Skip to content

Commit 881ae4e

Browse files
committed
Add .audio + .video operators
1 parent 41daf9a commit 881ae4e

File tree

4 files changed

+73
-11
lines changed

4 files changed

+73
-11
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/nodes.py

Lines changed: 52 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:

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

setup.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@
6767
keywords=keywords,
6868
long_description=long_description,
6969
install_requires=['future'],
70+
extras_require={
71+
'dev': [
72+
'future',
73+
'pytest',
74+
'pytest-mock',
75+
],
76+
},
7077
classifiers=[
7178
'Intended Audience :: Developers',
7279
'License :: OSI Approved :: Apache Software License',

0 commit comments

Comments
 (0)
0