Stream selectors, .map operator (audio support)#45
Conversation
|
|
||
| def _add_streams(self, stream_spec): | ||
| """Attach additional streams after the Node is initialized. | ||
| """ |
There was a problem hiding this comment.
This is odd - Nodes should be considered immutable (see notes in dag.py). What's the intent of retroactively rewiring an already-created node?
There was a problem hiding this comment.
I think I see now. Mapping can be done either with a multi-stream output or using .map, and .map takes an existing output node and makes it a multi-output node.
Can we just make a new output node with the desired streams rather than mutating the existing one?
Making nodes mutable has a lot of widespread implications and opens up the complexity to spiral out of control - for example, downstream hashes would need to be recursively recalculated. (See some of the benefits of immutable data-structures). I'd rather not make such a fundamental change unless absolutely necessary.
There was a problem hiding this comment.
Can we just make a new output node with the desired streams rather than mutating the existing one?
I agree that this should be the best way to do it, I did it this way as I didn't know how to get back the stream specs from an existing node to regenerate the new OutputNode.
Do you have any ideas?
| i2 = ffmpeg.input(TEST_OVERLAY_FILE) | ||
|
|
||
| o_map = i1.output(TEST_OUTPUT_FILE1) | ||
| o_map.map(i2) |
There was a problem hiding this comment.
See comment above about immutability:
- o_map.map(i2)
+ o_map = o_map.map(i2)| outgoing_stream_type=OutputStream, | ||
| min_inputs=1, | ||
| max_inputs=1, | ||
| min_inputs=0, # Allow streams to be mapped afterwards |
There was a problem hiding this comment.
I don't think this is necessary. Tests pass with this change removed. See also comment about immutability.
| # self.__check_input_types(stream_map, incoming_stream_types) | ||
| # incoming_edge_map = self.__get_incoming_edge_map(stream_map) | ||
| # super(Node, self).__init__(incoming_edge_map, name, args, kwargs) | ||
| # self.__outgoing_stream_type = outgoing_stream_type |
| outgoing_edges = get_outgoing_edges(node, outgoing_edge_map) | ||
| inputs = [stream_name_map[edge.upstream_node, edge.upstream_label] for edge in incoming_edges] | ||
| outputs = [stream_name_map[edge.upstream_node, edge.upstream_label] for edge in outgoing_edges] | ||
| inputs = ["[{}{}]".format(stream_name_map[edge.upstream_node, edge.upstream_label], "" if not edge.upstream_selector else ":{}".format(edge.upstream_selector)) for edge in incoming_edges] |
There was a problem hiding this comment.
This line has gotten too complicated and is basically unreadable. Can we split it out a bit?
e.g.
def _format_input_stream_name(stream_name_map, edge):
prefix = stream_name_map[edge.upstream_node, edge.upstream_label]
if not edge.upstream_selector
suffix = ""
else:
suffix = ":{}".format(edge.upstream_selector)
return "[{}{}]".format(prefix, suffix)
def _format_output_stream_name(stream_name_map, edge):
return "[{}]".format(stream_name_map[edge.upstream_node, edge.upstream_label])
def _get_filter_spec(node, outgoing_edge_map, stream_name_map):
incoming_edges = node.incoming_edges
outgoing_edges = get_outgoing_edges(node, outgoing_edge_map)
inputs = [format_input_stream_name(stream_name_map, edge) for edge in incoming_edges]
outputs = [format_output_stream_name(stream_name_map, edge) for edge in outgoing_edges]
|
|
||
| for edge in node.incoming_edges: | ||
| # edge = node.incoming_edges[0] | ||
| stream_name = "[{}{}]".format(stream_name_map[edge.upstream_node, edge.upstream_label], "" if not edge.upstream_selector else ":{}".format(edge.upstream_selector)) |
There was a problem hiding this comment.
This line is too complicated. See comment about _format_output_stream_name above.
There was a problem hiding this comment.
i.e.
- stream_name = "[{}{}]".format(stream_name_map[edge.upstream_node, edge.upstream_label], "" if not edge.upstream_selector else ":{}".format(edge.upstream_selector))
+ stream_name = _format_input_stream_name(stream_name_map, edge)| edges = [] | ||
| for downstream_label, (upstream_node, upstream_label) in list(incoming_edge_map.items()): | ||
| edges += [DagEdge(downstream_node, downstream_label, upstream_node, upstream_label)] | ||
| # downstream_label, (upstream_node, upstream_label) in [(i[0], i[1][:2]) for i in self.incoming_edge_map.items()] |
| for downstream_label, (upstream_node, upstream_label) in list(incoming_edge_map.items()): | ||
| edges += [DagEdge(downstream_node, downstream_label, upstream_node, upstream_label)] | ||
| # downstream_label, (upstream_node, upstream_label) in [(i[0], i[1][:2]) for i in self.incoming_edge_map.items()] | ||
| for downstream_label, upstream_info in [(i[0], i[1]) for i in incoming_edge_map.items()]: |
There was a problem hiding this comment.
This line is magical and overly complex. What do i[0] and i[1] mean? Is there an i[2]? Can we split this out into something that's more obvious to understand?
| # downstream_label, (upstream_node, upstream_label) in [(i[0], i[1][:2]) for i in self.incoming_edge_map.items()] | ||
| for downstream_label, upstream_info in [(i[0], i[1]) for i in incoming_edge_map.items()]: | ||
| upstream_node, upstream_label = upstream_info[:2] | ||
| upstream_selector = None if len(upstream_info) < 3 else upstream_info[2] |
There was a problem hiding this comment.
Where does the magical 3 constant come from? Needs explanation or named constant.
e.g. at the top of the file
_MEANINGFULLY_NAMED_CONSTANT = 3then
- upstream_selector = None if len(upstream_info) < 3 else upstream_info[2]
+ upstream_selector = None if len(upstream_info) < _MEANINGFULLY_NAMED_CONSTANT else upstream_info[2]There was a problem hiding this comment.
or split it out to make it more clear:
if len(unsptream_info) < 3:
# explanation goes here
upstream_selector = None
else:
upstream_selector = upstream_info[2]| for (downstream_node, downstream_label) in downstream_infos: | ||
| edges += [DagEdge(downstream_node, downstream_label, upstream_node, upstream_label)] | ||
| for downstream_info in downstream_infos: | ||
| downstream_node, downstream_label = downstream_info[:2] |
There was a problem hiding this comment.
What's [:2] in this case? (need comment/explanation)
| hashes = [] | ||
| for downstream_label, (upstream_node, upstream_label) in list(self.incoming_edge_map.items()): | ||
| # This is needed to allow extra stuff in the incoming_edge_map's value tuples | ||
| for downstream_label, (upstream_node, upstream_label) in [(i[0], i[1][:2]) for i in self.incoming_edge_map.items()]: |
There was a problem hiding this comment.
What are i[0], i[1], etc.? Should make this code more clear and split it out into something easier to understand. Way too much happening in this line.
| def __upstream_hashes(self): | ||
| hashes = [] | ||
| for downstream_label, (upstream_node, upstream_label) in list(self.incoming_edge_map.items()): | ||
| # This is needed to allow extra stuff in the incoming_edge_map's value tuples |
There was a problem hiding this comment.
What does "extra stuff" mean? Comment should be more clear I think.
|
|
||
| def _get_stream_name(name): | ||
| return '[{}]'.format(name) | ||
| return '{}'.format(name) |
There was a problem hiding this comment.
Should just get rid of this function.
kkroening
left a comment
There was a problem hiding this comment.
Overall good approach. The main thing is immutability - see these comments first. Then a bunch of other relatively minor things of just making the code easier to read.
Honestly I don't see why we need .map since it can all be done with .output (if I understand correctly), but I guess it's okay to have it as long as it doesn't break immutability. I would also be happy with just having .output though.
Anyways, overall nice work. Feel free to ping me for more discussion.
|
I agree with all your comments. I will probably get to it after holidays. See my reply to your immutability comment. |
7fb7026 to
21bd1cd
Compare
|
Okay, the syntactic part should be done. I'll do the immutability part now. Let's see what I come up with. By the way, some tests are still failing because of py33. I think we can just let Travis alone until it's all done, then rebase the whole thing on top of |
* Selectors are used just like 'split', i.e. `stream.split()[0:"audio"]`
* No need to have a split node in between, you can just do stream[:"a"] * Split nodes are still needed to do actual splitting.
…in Ubuntu's Python3
14b6470 to
ea90d91
Compare
|
What the hell happened? I think I screwed something up with rebase, I don't know how the "Update README" commits came into the PR. |
Implements stream selection and mapping multiple streams to one output.
Example:
Closes #26 and #42