8000 Merge branch 'feature/17' into feature/18 · Powercoder64/ffmpeg-python@4af484f · GitHub
[go: up one dir, main page]

Skip to content

Commit 4af484f

Browse files
committed
Merge branch 'feature/17' into feature/18
2 parents 13d9e2c + 5d78a25 commit 4af484f

File tree

5 files changed

+96
-37
lines changed

5 files changed

+96
-37
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/_run.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
overwrite_output,
1414
)
1515
from .nodes import (
16+
get_stream_spec_nodes,
17+
FilterNode,
1618
GlobalNode,
1719
InputNode,
1820
OutputNode,
1921
output_operator,
20-
Stream,
2122
)
2223

2324

@@ -108,36 +109,40 @@ def _get_output_args(node, stream_name_map):
108109

109110

110111
@output_operator()
111-
def get_args(stream):
112+
def get_args(stream_spec, overwrite_output=False):
112113
"""Get command-line arguments for ffmpeg."""
113-
if not isinstance(stream, Stream):
114-
raise TypeError('Expected Stream; got {}'.format(type(stream)))
114+
nodes = get_stream_spec_nodes(stream_spec)
115115
args = []
116116
# TODO: group nodes together, e.g. `-i somefile -r somerate`.
117-
sorted_nodes, outgoing_edge_maps = topo_sort([stream.node])
117+
sorted_nodes, outgoing_edge_maps = topo_sort(nodes)
118118
input_nodes = [node for node in sorted_nodes if isinstance(node, InputNode)]
119-
output_nodes = [node for node in sorted_nodes if isinstance(node, OutputNode) and not
120-
isinstance(node, GlobalNode)]
119+
output_nodes = [node for node in sorted_nodes if isinstance(node, OutputNode)]
121120
global_nodes = [node for node in sorted_nodes if isinstance(node, GlobalNode)]
122-
filter_nodes = [node for node in sorted_nodes if node not in (input_nodes + output_nodes + global_nodes)]
121+
filter_nodes = [node for node in sorted_nodes if isinstance(node, FilterNode)]
123122
stream_name_map = {(node, None): _get_stream_name(i) for i, node in enumerate(input_nodes)}
124123
filter_arg = _get_filter_arg(filter_nodes, outgoing_edge_maps, stream_name_map)
125124
args += reduce(operator.add, [_get_input_args(node) for node in input_nodes])
126125
if filter_arg:
127126
args += ['-filter_complex', filter_arg]
128127
args += reduce(operator.add, [_get_output_args(node, stream_name_map) for node in output_nodes])
129128
args += reduce(operator.add, [_get_global_args(node) for node in global_nodes], [])
129+
if overwrite_output:
130+
args += ['-y']
130131
return args
131132

132133

133134
@output_operator()
134-
def run(node, cmd='ffmpeg'):
135-
"""Run ffmpeg on node graph."""
135+
def run(stream_spec, cmd='ffmpeg', **kwargs):
136+
"""Run ffmpeg on node graph.
137+
138+
Args:
139+
**kwargs: keyword-arguments passed to ``get_args()`` (e.g. ``overwrite_output=True``).
140+
"""
136141
if isinstance(cmd, basestring):
137142
cmd = [cmd]
138143
elif type(cmd) != list:
139144
cmd = list(cmd)
140-
args = cmd + node.get_args()
145+
args = cmd + get_args(stream_spec, **kwargs)
141146
_subprocess.check_call(args)
142147

143148

ffmpeg/nodes.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ def get_stream_map(stream_spec):
5252
return stream_map
5353

5454

55+
def get_stream_map_nodes(stream_map):
56+
nodes = []
57+
for stream in stream_map.values():
58+
if not isinstance(stream, Stream):
59+
raise TypeError('Expected Stream; got {}'.format(type(stream)))
60+
nodes.append(stream.node)
61+
return nodes
62+
63+
64+
def get_stream_spec_nodes(stream_spec):
65+
stream_map = get_stream_map(stream_spec)
66+
return get_stream_map_nodes(stream_map)
67+
68+
5569
class Node(KwargReprNode):
5670
"""Node base"""
5771
@classmethod
@@ -75,8 +89,8 @@ def __get_incoming_edge_map(cls, stream_map):
7589
incoming_edge_map[downstream_label] = (upstream.node, upstream.label)
7690
return incoming_edge_map
7791

78-
def __init__(self, stream_spec, name, incoming_stream_types, outgoing_stream_type, min_inputs, max_inputs, args,
79-
kwargs):
92+
def __init__(self, stream_spec, name, incoming_stream_types, outgoing_stream_type, min_inputs, max_inputs, args=[],
93+
kwargs={}):
8094
stream_map = get_stream_map(stream_spec)
8195
self.__check_input_len(stream_map, min_inputs, max_inputs)
8296
self.__check_input_types(stream_map, incoming_stream_types)
@@ -164,13 +178,13 @@ def short_repr(self):
164178

165179
class OutputStream(Stream):
166180
def __init__(self, upstream_node, upstream_label):
167-
super(OutputStream, self).__init__(upstream_node, upstream_label, {OutputNode, GlobalNode})
181+
super(OutputStream, self).__init__(upstream_node, upstream_label, {OutputNode, GlobalNode, MergeOutputsNode})
168182

169183

170184
class MergeOutputsNode(Node):
171-
def __init__(self, stream, name):
185+
def __init__(self, streams, name):
172186
super(MergeOutputsNode, self).__init__(
173-
stream_spec=None,
187+
stream_spec=streams,
174188
name=name,
175189
incoming_stream_types={OutputStream},
176190
outgoing_stream_type=OutputStream,
File renamed without changes.

ffmpeg/tests/test_ffmpeg.py

Lines changed: 60 additions & 20 deletions
1241
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
TEST_DIR = os.path.dirname(__file__)
1010
SAMPLE_DATA_DIR = os.path.join(TEST_DIR, 'sample_data')
11-
TEST_INPUT_FILE = os.path.join(SAMPLE_DATA_DIR, 'dummy.mp4')
11+
TEST_INPUT_FILE1 = os.path.join(SAMPLE_DATA_DIR, 'in1.mp4')
1212
TEST_OVERLAY_FILE = os.path.join(SAMPLE_DATA_DIR, 'overlay.png')
13-
TEST_OUTPUT_FILE = os.path.join(SAMPLE_DATA_DIR, 'dummy2.mp4')
13+
TEST_OUTPUT_FILE1 = os.path.join(SAMPLE_DATA_DIR, 'out1.mp4')
14+
TEST_OUTPUT_FILE2 = os.path.join(SAMPLE_DATA_DIR, 'out2.mp4')
1415

1516

1617
subprocess.check_call(['ffmpeg', '-version'])
@@ -94,7 +95,7 @@ def test_get_args_simple():
9495

9596
def _get_complex_filter_example():
9697
split = (ffmpeg
97-
.input(TEST_INPUT_FILE)
98+
.input(TEST_INPUT_FILE1)
9899
.vflip()
99100
.split()
100101
)
@@ -109,15 +110,15 @@ def _get_complex_filter_example():
109110
)
110111
.overlay(overlay_file.hflip())
111112
.drawbox(50, 50, 120, 120, color='red', thickness=5)
112-
.output(TEST_OUTPUT_FILE)
113+
.output(TEST_OUTPUT_FILE1)
113114
.overwrite_output()
114115
)
115116

116117

117118
def test_get_args_complex_filter():
118119
out = _get_complex_filter_example()
119120
args = ffmpeg.get_args(out)
120-
assert args == ['-i', TEST_INPUT_FILE,
121+
assert args == ['-i', TEST_INPUT_FILE1,
121122
'-i', TEST_OVERLAY_FILE,
122123
'-filter_complex',
123124
'[0]vflip[s0];' \
@@ -128,7 +129,7 @@ def test_get_args_complex_filter():
128129
'[1]hflip[s6];' \
129130
'[s5][s6]overlay=eof_action=repeat[s7];' \
130131
'[s7]drawbox=50:50:120:120:red:t=5[s8]',
131-
'-map', '[s8]', os.path.join(SAMPLE_DATA_DIR, 'dummy2.mp4'),
132+
'-map', '[s8]', TEST_OUTPUT_FILE1,
132133
'-y'
133134
]
134135

@@ -139,31 +140,38 @@ def test_get_args_complex_filter():
139140

140141

141142
def test_run():
142-
node = _get_complex_filter_example()
143-
ffmpeg.run(node)
143+
stream = _get_complex_filter_example()
144+
ffmpeg.run(stream)
145+
146+
147+
def test_run_multi_output():
148+
in_ = ffmpeg.input(TEST_INPUT_FILE1)
149+
out1 = in_.output(TEST_OUTPUT_FILE1)
150+
out2 = in_.output(TEST_OUTPUT_FILE2)
151+
ffmpeg.run([out1, out2], overwrite_output=True)
144152

145153

146154
def test_run_dummy_cmd():
147-
node = _get_complex_filter_example()
148-
ffmpeg.run(node, cmd='true')
155+
stream = _get_complex_filter_example()
156+
ffmpeg.run(stream, cmd='true')
149157

150158

151159
def test_run_dummy_cmd_list():
152-
node = _get_complex_filter_example()
153-
ffmpeg.run(node, cmd=['true', 'ignored'])
160+
stream = _get_complex_filter_example()
161+
ffmpeg.run(stream, cmd=['true', 'ignored'])
154162

155163

156164
def test_run_failing_cmd():
157-
node = _get_complex_filter_example()
165+
stream = _get_complex_filter_example()
158166
with pytest.raises(subprocess.CalledProcessError):
159-
ffmpeg.run(node, cmd='false')
167+
ffmpeg.run(stream, cmd='false')
160168

161169

162170
def test_custom_filter():
163-
node = ffmpeg.input('dummy.mp4')
164-
node = ffmpeg.filter_(node, 'custom_filter', 'a', 'b', kwarg1='c')
165-
node = ffmpeg.output(node, 'dummy2.mp4')
166-
assert node.get_args() == [
171+
stream = ffmpeg.input('dummy.mp4')
172+
stream = ffmpeg.filter_(stream, 'custom_filter', 'a', 'b', kwarg1='c')
173+
stream = ffmpeg.output(stream, 'dummy2.mp4')
174+
assert stream.get_args() == [
167175
'-i', 'dummy.mp4',
168176
'-filter_complex', '[0]custom_filter=a:b:kwarg1=c[s0]',
169177
'-map', '[s0]',
@@ -172,19 +180,51 @@ def test_custom_filter():
172180

173181

174182
def test_custom_filter_fluent():
175-
node = (ffmpeg
183+
stream = (ffmpeg
176184
.input('dummy.mp4')
177185
.filter_('custom_filter', 'a', 'b', kwarg1='c')
178186
.output('dummy2.mp4')
179187
)
180-
assert node.get_args() == [
188+
assert stream.get_args() == [
181189
'-i', 'dummy.mp4',
182190
'-filter_complex', '[0]custom_filter=a:b:kwarg1=c[s0]',
183191
'-map', '[s0]',
184192
'dummy2.mp4'
185193
]
186194

187195

196+
def test_merge_outputs():
197+
in_ = ffmpeg.input('in.mp4')
198+
out1 = in_.output('out1.mp4')
199+
out2 = in_.output('out2.mp4')
200+
assert ffmpeg.merge_outputs(out1, out2).get_args() == [
201+
'-i', 'in.mp4', 'out1.mp4', 'out2.mp4'
202+
]
203+
assert ffmpeg.get_args([out1, out2]) == [
204+
'-i', 'in.mp4', 'out2.mp4', 'out1.mp4'
205+
]
206+
207+
208+
def test_multi_passthrough():
209+
out1 = ffmpeg.input('in1.mp4').output('out1.mp4')
210+
out2 = ffmpeg.input('in2.mp4').output('out2.mp4')
211+
out = ffmpeg.merge_outputs(out1, out2)
212+
assert ffmpeg.get_args(out) == [
213+
'-i', 'in1.mp4',
214+
'-i', 'in2.mp4',
215+
'out1.mp4',
216+
'-map', '[1]', # FIXME: this should not be here (see #23)
217+
'out2.mp4'
218+
]
219+
assert ffmpeg.get_args([out1, out2]) == [
220+
'-i', 'in2.mp4',
221+
'-i', 'in1.mp4',
222+
'out2.mp4',
223+
'-map', '[1]', # FIXME: this should not be here (see #23)
224+
'out1.mp4'
225+
]
226+
227+
188228
def test_pipe():
189229
width = 32
190230
height = 32

0 commit comments

Comments
 (0)
0