import operator as _operator import subprocess as _subprocess from ._ffmpeg import ( input, merge_outputs, output, overwrite_output, ) from .nodes import ( GlobalNode, InputNode, operator, OutputNode, ) def _get_stream_name(name): return '[{}]'.format(name) def _get_input_args(input_node): if input_node._name == input.__name__: args = ['-i', input_node._kwargs['filename']] else: assert False, 'Unsupported input node: {}'.format(input_node) return args def _topo_sort(start_node): marked_nodes = [] sorted_nodes = [] child_map = {} def visit(node, child): assert node not in marked_nodes, 'Graph is not a DAG' if child is not None: if node not in child_map: child_map[node] = [] child_map[node].append(child) if node not in sorted_nodes: marked_nodes.append(node) [visit(parent, node) for parent in node._parents] marked_nodes.remove(node) sorted_nodes.append(node) unmarked_nodes = [start_node] while unmarked_nodes: visit(unmarked_nodes.pop(), None) return sorted_nodes, child_map def _get_filter_spec(i, node, stream_name_map): stream_name = _get_stream_name('v{}'.format(i)) stream_name_map[node] = stream_name inputs = [stream_name_map[parent] for parent in node._parents] filter_spec = '{}{}{}'.format(''.join(inputs), node._get_filter(), stream_name) return filter_spec def _get_filter_arg(filter_nodes, stream_name_map): filter_specs = [_get_filter_spec(i, node, stream_name_map) for i, node in enumerate(filter_nodes)] return ';'.join(filter_specs) def _get_global_args(node): if node._name == overwrite_output.__name__: return ['-y'] else: assert False, 'Unsupported global node: {}'.format(node) def _get_output_args(node, stream_name_map): args = [] if node._name != merge_outputs.__name__: stream_name = stream_name_map[node._parents[0]] if stream_name != '[0]': args += ['-map', stream_name] if node._name == output.__name__: args += [node._kwargs['filename']] else: assert False, 'Unsupported output node: {}'.format(node) return args @operator(node_classes={OutputNode, GlobalNode}) def get_args(node): """Get command-line arguments for ffmpeg.""" args = [] # TODO: group nodes together, e.g. `-i somefile -r somerate`. sorted_nodes, child_map = _topo_sort(node) del(node) input_nodes = [node for node in sorted_nodes if isinstance(node, InputNode)] output_nodes = [node for node in sorted_nodes if isinstance(node, OutputNode) and not isinstance(node, GlobalNode)] global_nodes = [node for node in sorted_nodes if isinstance(node, GlobalNode)] filter_nodes = [node for node in sorted_nodes if node not in (input_nodes + output_nodes + global_nodes)] stream_name_map = {node: _get_stream_name(i) for i, node in enumerate(input_nodes)} filter_arg = _get_filter_arg(filter_nodes, stream_name_map) args += reduce(_operator.add, [_get_input_args(node) for node in input_nodes]) if filter_arg: args += ['-filter_complex', filter_arg] args += reduce(_operator.add, [_get_output_args(node, stream_name_map) for node in output_nodes]) args += reduce(_operator.add, [_get_global_args(node) for node in global_nodes], []) return args @operator(node_classes={OutputNode, GlobalNode}) def run(node, cmd='ffmpeg'): """Run ffmpeg on node graph.""" if isinstance(cmd, basestring): cmd = [cmd] elif type(cmd) != list: cmd = list(cmd) args = cmd + node.get_args() _subprocess.check_call(args) __all__ = [ 'get_args', 'run', ]