From 03f99dbba0b54cfddcb21433e0ca7af94cfe745e Mon Sep 17 00:00:00 2001 From: Karl Kroening Date: Wed, 14 Jun 2017 02:07:23 -0600 Subject: [PATCH 1/4] Support input/output parameters --- ffmpeg/_ffmpeg.py | 18 +++++++++++++---- ffmpeg/_run.py | 30 +++++++++++++++++++++++++--- ffmpeg/tests/test_ffmpeg.py | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/ffmpeg/_ffmpeg.py b/ffmpeg/_ffmpeg.py index 0d0a549..ba188ec 100644 --- a/ffmpeg/_ffmpeg.py +++ b/ffmpeg/_ffmpeg.py @@ -8,12 +8,17 @@ from .nodes import ( ) -def input(filename): +def input(filename, **kwargs): """Input file URL (ffmpeg ``-i`` option) Official documentation: `Main options `__ """ - return InputNode(input.__name__, filename=filename) + kwargs['filename'] = filename + fmt = kwargs.pop('f', None) + if fmt: + assert 'format' not in kwargs, "Can't specify both `format` and `f` kwargs" + kwargs['format'] = fmt + return InputNode(input.__name__, **kwargs) @operator(node_classes={OutputNode, GlobalNode}) @@ -31,12 +36,17 @@ def merge_outputs(*parent_nodes): @operator(node_classes={InputNode, FilterNode}) -def output(parent_node, filename): +def output(parent_node, filename, **kwargs): """Output file URL Official documentation: `Synopsis `__ """ - return OutputNode([parent_node], output.__name__, filename=filename) + kwargs['filename'] = filename + fmt = kwargs.pop('f', None) + if fmt: + assert 'format' not in kwargs, "Can't specify both `format` and `f` kwargs" + kwargs['format'] = fmt + return OutputNode([parent_node], output.__name__, **kwargs) diff --git a/ffmpeg/_run.py b/ffmpeg/_run.py index d57b286..c98d65a 100644 --- a/ffmpeg/_run.py +++ b/ffmpeg/_run.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals +from functools import reduce from past.builtins import basestring +import copy import operator as _operator import subprocess as _subprocess @@ -16,7 +18,6 @@ from .nodes import ( operator, OutputNode, ) -from functools import reduce def _get_stream_name(name): return '[{}]'.format(name) @@ -24,7 +25,20 @@ def _get_stream_name(name): def _get_input_args(input_node): if input_node._name == input.__name__: - args = ['-i', input_node._kwargs['filename']] + kwargs = copy.copy(input_node._kwargs) + filename = kwargs.pop('filename') + fmt = kwargs.pop('format', None) + video_size = kwargs.pop('video_size', None) + args = [] + if fmt: + args += ['-f', fmt] + if video_size: + args += ['-video_size', '{}x{}'.format(video_size[0], video_size[1])] + for k, v in kwargs.items(): + args.append('-{}'.format(k)) + if v: + args.append('{}'.format(v)) + args += ['-i', filename] else: assert False, 'Unsupported input node: {}'.format(input_node) return args @@ -78,7 +92,17 @@ def _get_output_args(node, stream_name_map): if stream_name != '[0]': args += ['-map', stream_name] if node._name == output.__name__: - args += [node._kwargs['filename']] + kwargs = copy.copy(node._kwargs) + filename = kwargs.pop('filename') + fmt = kwargs.pop('format', None) + + if fmt: + args += ['-f', fmt] + for k, v in kwargs.items(): + args.append('-{}'.format(k)) + if v: + args.append('{}'.format(v)) + args += [filename] else: assert False, 'Unsupported output node: {}'.format(node) return args diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py index b8e983d..b0b376f 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -3,6 +3,7 @@ import ffmpeg import os import pytest import subprocess +import random TEST_DIR = os.path.dirname(__file__) @@ -167,3 +168,41 @@ def test_custom_filter_fluent(): '-map', '[v0]', 'dummy2.mp4' ] + + +def test_pipe(): + width = 32 + height = 32 + frame_size = width * height * 3 # 3 bytes for rgb24 + frame_count = 10 + start_frame = 2 + + out = (ffmpeg + .input('pipe:0', format='rawvideo', pixel_format='rgb24', video_size=(width, height), framerate=10) + .trim(start_frame=start_frame) + .output('pipe:1', format='rawvideo') + ) + args = out.get_args() + assert args == [ + '-f', 'rawvideo', + '-video_size', '{}x{}'.format(width, height), + '-pixel_format', 'rgb24', + '-framerate', '10', + '-i', 'pipe:0', + '-filter_complex', + '[0]trim=start_frame=2[v0]', + '-map', '[v0]', + '-f', 'rawvideo', + 'pipe:1' + ] + + cmd = ['ffmpeg'] + args + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + in_data = bytes(bytearray([random.randint(0,255) for _ in range(frame_size * frame_count)])) + p.stdin.write(in_data) + p.stdin.close() + + out_data = p.stdout.read() + assert len(out_data) == frame_size * (frame_count - start_frame) + assert out_data == in_data[start_frame*frame_size:] From 7824092ccc4acd2a4e81e3a2e613267d5b18aeec Mon Sep 17 00:00:00 2001 From: Karl Kroening Date: Wed, 14 Jun 2017 02:12:52 -0600 Subject: [PATCH 2/4] Add whitespace --- ffmpeg/tests/test_ffmpeg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py index b0b376f..e92b068 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -182,6 +182,7 @@ def test_pipe(): .trim(start_frame=start_frame) .output('pipe:1', format='rawvideo') ) + args = out.get_args() assert args == [ '-f', 'rawvideo', From f4025b464f40d03afa6da35cffcac6baaf901ad6 Mon Sep 17 00:00:00 2001 From: Karl Kroening Date: Wed, 14 Jun 2017 02:14:57 -0600 Subject: [PATCH 3/4] Add comment in test --- ffmpeg/tests/test_ffmpeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py index e92b068..cdf3242 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -201,7 +201,7 @@ def test_pipe(): p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) in_data = bytes(bytearray([random.randint(0,255) for _ in range(frame_size * frame_count)])) - p.stdin.write(in_data) + p.stdin.write(in_data) # note: this could block, in which case need to use threads p.stdin.close() out_data = p.stdout.read() From 525b1bf452bb2da4bd385ec9f51dec224c6f594c Mon Sep 17 00:00:00 2001 From: Karl Kroening Date: Wed, 14 Jun 2017 02:25:58 -0600 Subject: [PATCH 4/4] Make input/output cmd-line args deterministically sorted --- ffmpeg/_run.py | 21 ++++++++++++--------- ffmpeg/tests/test_ffmpeg.py | 2 +- tox.ini | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ffmpeg/_run.py b/ffmpeg/_run.py index c98d65a..2f99a3f 100644 --- a/ffmpeg/_run.py +++ b/ffmpeg/_run.py @@ -23,6 +23,16 @@ def _get_stream_name(name): return '[{}]'.format(name) +def _convert_kwargs_to_cmd_line_args(kwargs): + args = [] + for k in sorted(kwargs.keys()): + v = kwargs[k] + args.append('-{}'.format(k)) + if v: + args.append('{}'.format(v)) + return args + + def _get_input_args(input_node): if input_node._name == input.__name__: kwargs = copy.copy(input_node._kwargs) @@ -34,10 +44,7 @@ def _get_input_args(input_node): args += ['-f', fmt] if video_size: args += ['-video_size', '{}x{}'.format(video_size[0], video_size[1])] - for k, v in kwargs.items(): - args.append('-{}'.format(k)) - if v: - args.append('{}'.format(v)) + args += _convert_kwargs_to_cmd_line_args(kwargs) args += ['-i', filename] else: assert False, 'Unsupported input node: {}'.format(input_node) @@ -95,13 +102,9 @@ def _get_output_args(node, stream_name_map): kwargs = copy.copy(node._kwargs) filename = kwargs.pop('filename') fmt = kwargs.pop('format', None) - if fmt: args += ['-f', fmt] - for k, v in kwargs.items(): - args.append('-{}'.format(k)) - if v: - args.append('{}'.format(v)) + args += _convert_kwargs_to_cmd_line_args(kwargs) args += [filename] else: assert False, 'Unsupported output node: {}'.format(node) diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py index cdf3242..f2c0521 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -187,8 +187,8 @@ def test_pipe(): assert args == [ '-f', 'rawvideo', '-video_size', '{}x{}'.format(width, height), - '-pixel_format', 'rgb24', '-framerate', '10', + '-pixel_format', 'rgb24', '-i', 'pipe:0', '-filter_complex', '[0]trim=start_frame=2[v0]', diff --git a/tox.ini b/tox.ini index ed5c2e6..daad3a4 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py27, py33, py34, py35, py36, pypy [testenv] -commands = py.test +commands = py.test -vv deps = future pytest