diff --git a/examples/README.md b/examples/README.md index dc68de2..27f0fee 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,6 +57,21 @@ out, _ = (ffmpeg ) ``` +## Process audio and video simultaneously +```python +in1 = ffmpeg.input('in1.mp4') +in2 = ffmpeg.input('in2.mp4') +v1 = in1['v'].hflip() +a1 = in1['a'] +v2 = in2['v'].filter_('reverse').filter_('hue', s=0) +a2 = in2['a'].filter_('areverse').filter_('aphaser') +joined = ffmpeg.concat(v1, a1, v2, a2, v=1, a=1).node +v3 = joined[0] +a3 = joined[1].filter_('volume', 0.8) +out = ffmpeg.output(v3, a3, 'out.mp4') +out.run() +``` + ## [Jupyter Frame Viewer](https://github.com/kkroening/ffmpeg-python/blob/master/examples/ffmpeg-numpy.ipynb) jupyter screenshot diff --git a/ffmpeg/_filters.py b/ffmpeg/_filters.py index 9cffc17..ce05ae2 100644 --- a/ffmpeg/_filters.py +++ b/ffmpeg/_filters.py @@ -372,7 +372,14 @@ def concat(*streams, **kwargs): Official documentation: `concat `__ """ - kwargs['n'] = len(streams) + video_stream_count = kwargs.get('v', 1) + audio_stream_count = kwargs.get('a', 0) + stream_count = video_stream_count + audio_stream_count + if len(streams) % stream_count != 0: + raise ValueError( + 'Expected concat input streams to have length multiple of {} (v={}, a={}); got {}' + .format(stream_count, video_stream_count, audio_stream_count, len(streams))) + kwargs['n'] = int(len(streams) / stream_count) return FilterNode(streams, concat.__name__, kwargs=kwargs, max_inputs=None).stream() diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py index 49654bc..fd11cc7 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -221,6 +221,83 @@ def _get_complex_filter_asplit_example(): ) +def test_filter_concat__video_only(): + in1 = ffmpeg.input('in1.mp4') + in2 = ffmpeg.input('in2.mp4') + args = ( + ffmpeg + .concat(in1, in2) + .output('out.mp4') + .get_args() + ) + assert args == [ + '-i', + 'in1.mp4', + '-i', + 'in2.mp4', + '-filter_complex', + '[0][1]concat=n=2[s0]', + '-map', + '[s0]', + 'out.mp4', + ] + + +def test_filter_concat__audio_only(): + in1 = ffmpeg.input('in1.mp4') + in2 = ffmpeg.input('in2.mp4') + args = ( + ffmpeg + .concat(in1, in2, v=0, a=1) + .output('out.mp4') + .get_args() + ) + assert args == [ + '-i', + 'in1.mp4', + '-i', + 'in2.mp4', + '-filter_complex', + '[0][1]concat=a=1:n=2:v=0[s0]', + '-map', + '[s0]', + 'out.mp4' + ] + + +def test_filter_concat__audio_video(): + in1 = ffmpeg.input('in1.mp4') + in2 = ffmpeg.input('in2.mp4') + joined = ffmpeg.concat(in1['v'], in1['a'], in2.hflip(), in2['a'], v=1, a=1).node + args = ( + ffmpeg + .output(joined[0], joined[1], 'out.mp4') + .get_args() + ) + assert args == [ + '-i', + 'in1.mp4', + '-i', + 'in2.mp4', + '-filter_complex', + '[1]hflip[s0];[0:v][0:a][s0][1:a]concat=a=1:n=2:v=1[s1][s2]', + '-map', + '[s1]', + '-map', + '[s2]', + 'out.mp4', + ] + + +def test_filter_concat__wrong_stream_count(): + in1 = ffmpeg.input('in1.mp4') + in2 = ffmpeg.input('in2.mp4') + with pytest.raises(ValueError) as excinfo: + ffmpeg.concat(in1['v'], in1['a'], in2.hflip(), v=1, a=1).node + assert str(excinfo.value) == \ + 'Expected concat input streams to have length multiple of 2 (v=1, a=1); got 3' + + def test_filter_asplit(): out = _get_complex_filter_asplit_example() args = out.get_args()