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

Skip to content

Commit 0d63a9e

Browse files
committed
Merge branch 'feature/17' into feature/18
2 parents 2d51299 + 8337d34 commit 0d63a9e

File tree

8 files changed

+85
-50
lines changed

8 files changed

+85
-50
lines changed

ffmpeg/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import unicode_literals
2+
23
from . import _filters, _ffmpeg, _run
34
from ._filters import *
45
from ._ffmpeg import *
56
from ._run import *
6-
from ._view import *
7-
__all__ = _filters.__all__ + _ffmpeg.__all__ + _run.__all__ + _view.__all__
7+
8+
__all__ = _filters.__all__ + _ffmpeg.__all__ + _run.__all__

ffmpeg/_ffmpeg.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import unicode_literals
2+
23
from .nodes import (
34
filter_operator,
45
GlobalNode,

ffmpeg/_filters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import unicode_literals
2+
23
from .nodes import (
34
FilterNode,
45
filter_operator,

ffmpeg/_run.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import unicode_literals
22

3-
from .dag import topo_sort
3+
from .dag import get_outgoing_edges, topo_sort
44
from functools import reduce
55
from past.builtins import basestring
66
import copy
@@ -53,16 +53,31 @@ def _get_input_args(input_node):
5353
return args
5454

5555

56-
def _get_filter_spec(i, node, stream_name_map):
57-
stream_name = _get_stream_name('v{}'.format(i))
58-
stream_name_map[node] = stream_name
59-
inputs = [stream_name_map[edge.upstream_node] for edge in node.incoming_edges]
60-
filter_spec = '{}{}{}'.format(''.join(inputs), node._get_filter(), stream_name)
56+
def _get_filter_spec(node, outgoing_edge_map, stream_name_map):
57+
incoming_edges = node.incoming_edges
58+
outgoing_edges = get_outgoing_edges(node, outgoing_edge_map)
59+
inputs = [stream_name_map[edge.upstream_node, edge.upstream_label] for edge in incoming_edges]
60+
outputs = [stream_name_map[edge.upstream_node, edge.upstream_label] for edge in outgoing_edges]
61+
filter_spec = '{}{}{}'.format(''.join(inputs), node._get_filter(), ''.join(outputs))
6162
return filter_spec
6263

6364

64-
def _get_filter_arg(filter_nodes, stream_name_map):
65-
filter_specs = [_get_filter_spec(i, node, stream_name_map) for i, node in enumerate(filter_nodes)]
65+
def _allocate_filter_stream_names(filter_nodes, outgoing_edge_maps, stream_name_map):
66+
stream_count = 0
67+
for upstream_node in filter_nodes:
68+
outgoing_edge_map = outgoing_edge_maps[upstream_node]
69+
for upstream_label, downstreams in outgoing_edge_map.items():
70+
if len(downstreams) > 1:
71+
# TODO: automatically insert `splits` ahead of time via graph transformation.
72+
raise ValueError('Encountered {} with multiple outgoing edges with same upstream label {!r}; a '
73+
'`split` filter is probably required'.format(upstream_node, upstream_label))
74+
stream_name_map[upstream_node, upstream_label] = _get_stream_name('s{}'.format(stream_count))
75+
stream_count += 1
76+
77+
78+
def _get_filter_arg(filter_nodes, outgoing_edge_maps, stream_name_map):
79+
_allocate_filter_stream_names(filter_nodes, outgoing_edge_maps, stream_name_map)
80+
filter_specs = [_get_filter_spec(node, outgoing_edge_maps[node], stream_name_map) for node in filter_nodes]
6681
return ';'.join(filter_specs)
6782

6883

@@ -78,7 +93,8 @@ def _get_output_args(node, stream_name_map):
7893
raise ValueError('Unsupported output node: {}'.format(node))
7994
args = []
8095
assert len(node.incoming_edges) == 1
81-
stream_name = stream_name_map[node.incoming_edges[0].upstream_node]
96+
edge = node.incoming_edges[0]
97+
stream_name = stream_name_map[edge.upstream_node, edge.upstream_label]
8298
if stream_name != '[0]':
8399
args += ['-map', stream_name]
84100
kwargs = copy.copy(node.kwargs)
@@ -104,8 +120,8 @@ def get_args(stream):
104120
isinstance(node, GlobalNode)]
105121
global_nodes = [node for node in sorted_nodes if isinstance(node, GlobalNode)]
106122
filter_nodes = [node for node in sorted_nodes if node not in (input_nodes + output_nodes + global_nodes)]
107-
stream_name_map = {node: _get_stream_name(i) for i, node in enumerate(input_nodes)}
108-
filter_arg = _get_filter_arg(filter_nodes, stream_name_map)
123+
stream_name_map = {(node, None): _get_stream_name(i) for i, node in enumerate(input_nodes)}
124+
filter_arg = _get_filter_arg(filter_nodes, outgoing_edge_maps, stream_name_map)
109125
args += reduce(operator.add, [_get_input_args(node) for node in input_nodes])
110126
if filter_arg:
111127
args += ['-filter_complex', filter_arg]

ffmpeg/_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import hashlib
1+
from __future__ import unicode_literals
2+
3+
from builtins import str
4+
from past.builtins import basestring
5+
import hashlib
26

37

48
def _recursive_repr(item):

ffmpeg/dag.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import unicode_literals
2+
13
from ._utils import get_hash, get_hash_int
24
from builtins import object
35
from collections import namedtuple
@@ -72,14 +74,14 @@ def incoming_edge_map(self):
7274

7375
def get_incoming_edges(downstream_node, incoming_edge_map):
7476
edges = []
75-
for downstream_label, (upstream_node, upstream_label) in incoming_edge_map.items():
77+
for downstream_label, (upstream_node, upstream_label) in list(incoming_edge_map.items()):
7678
edges += [DagEdge(downstream_node, downstream_label, upstream_node, upstream_label)]
7779
return edges
7880

7981

8082
def get_outgoing_edges(upstream_node, outgoing_edge_map):
8183
edges = []
82-
for upstream_label, downstream_infos in outgoing_edge_map.items():
84+
for upstream_label, downstream_infos in list(outgoing_edge_map.items()):
8385
for (downstream_node, downstream_label) in downstream_infos:
8486
edges += [DagEdge(downstream_node, downstream_label, upstream_node, upstream_label)]
8587
return edges
@@ -91,7 +93,7 @@ class KwargReprNode(DagNode):
9193
@property
9294
def __upstream_hashes(self):
9395
hashes = []
94-
for downstream_label, (upstream_node, upstream_label) in self.incoming_edge_map.items():
96+
for downstream_label, (upstream_node, upstream_label) in list(self.incoming_edge_map.items()):
9597
hashes += [hash(x) for x in [downstream_label, upstream_node, upstream_label]]
9698
return hashes
9799

ffmpeg/nodes.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
from builtins import object
34
from .dag import KwargReprNode
45
from ._utils import get_hash_int
56

@@ -38,6 +39,18 @@ def __repr__(self):
3839
return out
3940

4041

42+
def get_stream_map(stream_spec):
43+
if stream_spec is None:
44+
stream_map = {}
45+
elif isinstance(stream_spec, Stream):
46+
stream_map = {None: stream_spec}
47+
elif isinstance(stream_spec, (list, tuple)):
48+
stream_map = dict(enumerate(stream_spec))
49+
elif isinstance(stream_spec, dict):
50+
stream_map = stream_spec
51+
return stream_map
52+
53+
4154
class Node(KwargReprNode):
4255
"""Node base"""
4356
@classmethod
@@ -49,33 +62,21 @@ def __check_input_len(cls, stream_map, min_inputs, max_inputs):
4962

5063
@classmethod
5164
def __check_input_types(cls, stream_map, incoming_stream_types):
52-
for stream in stream_map.values():
65+
for stream in list(stream_map.values()):
5366
if not _is_of_types(stream, incoming_stream_types):
5467
raise TypeError('Expected incoming stream(s) to be of one of the following types: {}; got {}'
5568
.format(_get_types_str(incoming_stream_types), type(stream)))
5669

57-
@classmethod
58-
def __get_stream_map(cls, stream_spec):
59-
if stream_spec is None:
60-
stream_map = {}
61-
elif isinstance(stream_spec, Stream):
62-
stream_map = {None: stream_spec}
63-
elif isinstance(stream_spec, (list, tuple)):
64-
stream_map = dict(enumerate(stream_spec))
65-
elif isinstance(stream_spec, dict):
66-
stream_map = stream_spec
67-
return stream_map
68-
6970
@classmethod
7071
def __get_incoming_edge_map(cls, stream_map):
7172
incoming_edge_map = {}
72-
for downstream_label, upstream in stream_map.items():
73+
for downstream_label, upstream in list(stream_map.items()):
7374
incoming_edge_map[downstream_label] = (upstream.node, upstream.label)
7475
return incoming_edge_map
7576

7677
def __init__(self, stream_spec, name, incoming_stream_types, outgoing_stream_type, min_inputs, max_inputs, args,
7778
kwargs):
78-
stream_map = self.__get_stream_map(stream_spec)
79+
stream_map = get_stream_map(stream_spec)
7980
self.__check_input_len(stream_map, min_inputs, max_inputs)
8081
self.__check_input_types(stream_map, incoming_stream_types)
8182
incoming_edge_map = self.__get_incoming_edge_map(stream_map)

ffmpeg/tests/test_ffmpeg.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,19 @@ def test_get_args_simple():
9393

9494

9595
def _get_complex_filter_example():
96-
in_file = ffmpeg.input(TEST_INPUT_FILE)
96+
split = (ffmpeg
97+
.input(TEST_INPUT_FILE)
98+
.vflip()
99+
.split()
100+
)
101+
split0 = split[0]
102+
split1 = split[1]
103+
97104
overlay_file = ffmpeg.input(TEST_OVERLAY_FILE)
98105
return (ffmpeg
99106
.concat(
100-
in_file.trim(start_frame=10, end_frame=20),
101-
in_file.trim(start_frame=30, end_frame=40),
107+
split0.trim(start_frame=10, end_frame=20),
108+
split1.trim(start_frame=30, end_frame=40),
102109
)
103110
.overlay(overlay_file.hflip())
104111
.drawbox(50, 50, 120, 120, color='red', thickness=5)
@@ -110,21 +117,23 @@ def _get_complex_filter_example():
110117
def test_get_args_complex_filter():
111118
out = _get_complex_filter_example()
112119
args = ffmpeg.get_args(out)
113-
assert args == [
114-
'-i', TEST_INPUT_FILE,
120+
assert args == ['-i', TEST_INPUT_FILE,
115121
'-i', TEST_OVERLAY_FILE,
116122
'-filter_complex',
117-
'[0]trim=end_frame=20:start_frame=10[v0];' \
118-
'[0]trim=end_frame=40:start_frame=30[v1];' \
119-
'[v0][v1]concat=n=2[v2];' \
120-
'[1]hflip[v3];' \
121-
'[v2][v3]overlay=eof_action=repeat[v4];' \
122-
'[v4]drawbox=50:50:120:120:red:t=5[v5]',
123-
'-map', '[v5]', os.path.join(SAMPLE_DATA_DIR, 'dummy2.mp4'),
123+
'[0]vflip[s0];' \
124+
'[s0]split[s1][s2];' \
125+
'[s1]trim=end_frame=20:start_frame=10[s3];' \
126+
'[s2]trim=end_frame=40:start_frame=30[s4];' \
127+
'[s3][s4]concat=n=2[s5];' \
128+
'[1]hflip[s6];' \
129+
'[s5][s6]overlay=eof_action=repeat[s7];' \
130+
'[s7]drawbox=50:50:120:120:red:t=5[s8]',
131+
'-map', '[s8]', os.path.join(SAMPLE_DATA_DIR, 'dummy2.mp4'),
124132
'-y'
125133
]
126134

127135

136+
128137
#def test_version():
129138
# subprocess.check_call(['ffmpeg', '-version'])
130139

@@ -156,8 +165,8 @@ def test_custom_filter():
156165
node = ffmpeg.output(node, 'dummy2.mp4')
157166
assert node.get_args() == [
158167
'-i', 'dummy.mp4',
159-
'-filter_complex', '[0]custom_filter=a:b:kwarg1=c[v0]',
160-
'-map', '[v0]',
168+
'-filter_complex', '[0]custom_filter=a:b:kwarg1=c[s0]',
169+
'-map', '[s0]',
161170
'dummy2.mp4'
162171
]
163172

@@ -170,8 +179,8 @@ def test_custom_filter_fluent():
170179
)
171180
assert node.get_args() == [
172181
'-i', 'dummy.mp4',
173-
'-filter_complex', '[0]custom_filter=a:b:kwarg1=c[v0]',
174-
'-map', '[v0]',
182+
'-filter_complex', '[0]custom_filter=a:b:kwarg1=c[s0]',
183+
'-map', '[s0]',
175184
'dummy2.mp4'
176185
]
177186

@@ -197,8 +206,8 @@ def test_pipe():
197206
'-pixel_format', 'rgb24',
198207
'-i', 'pipe:0',
199208
'-filter_complex',
200-
'[0]trim=start_frame=2[v0]',
201-
'-map', '[v0]',
209+
'[0]trim=start_frame=2[s0]',
210+
'-map', '[s0]',
202211
'-f', 'rawvideo',
203212
'pipe:1'
204213
]

0 commit comments

Comments
 (0)
0