Skip to content

Stream selectors, .map operator (audio support)#45

Merged
kkroening merged 41 commits into
kkroening:masterfrom
depau-forks:stream_selectors
May 9, 2018
Merged

Stream selectors, .map operator (audio support)#45
kkroening merged 41 commits into
kkroening:masterfrom
depau-forks:stream_selectors

Conversation

@depau

@depau depau commented Dec 21, 2017

Copy link
Copy Markdown
Collaborator

Implements stream selection and mapping multiple streams to one output.

Example:

i = ffmpeg.input("video_with_sound.mp4")
audio = i[:"a"].filter_("aecho", 0.8, 0.9, 1000, 0.3)
video = i[:"v"].hflip().vflip()
single_output = ffmpeg.output(video, audio, "my_output.mp4")
# or
single_output = video.output("my_output.mp4")
single_output = single_output.map(audio)

Imgur

ffmpeg -i video_with_sound.mp4
-filter_complex
'[0:v]hflip[s0];[s0]vflip[s1];[0:a]aecho=0.8:0.9:1000:0.3[s2]'
-map '[s1]' -map '[s2]' my_output.mp4

Closes #26 and #42

Comment thread ffmpeg/nodes.py Outdated

def _add_streams(self, stream_spec):
"""Attach additional streams after the Node is initialized.
"""

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is odd - Nodes should be considered immutable (see notes in dag.py). What's the intent of retroactively rewiring an already-created node?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread ffmpeg/tests/test_ffmpeg.py Outdated
i2 = ffmpeg.input(TEST_OVERLAY_FILE)

o_map = i1.output(TEST_OUTPUT_FILE1)
o_map.map(i2)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about immutability:

-    o_map.map(i2)
+    o_map = o_map.map(i2)

Comment thread ffmpeg/nodes.py Outdated
outgoing_stream_type=OutputStream,
min_inputs=1,
max_inputs=1,
min_inputs=0, # Allow streams to be mapped afterwards

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary. Tests pass with this change removed. See also comment about immutability.

Comment thread ffmpeg/_ffmpeg.py Outdated
# 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

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented code.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 4349b18

Comment thread ffmpeg/_run.py Outdated
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]

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

@depau depau Jan 9, 2018

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 21bd1cd

Comment thread ffmpeg/_run.py Outdated

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))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is too complicated. See comment about _format_output_stream_name above.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 21bd1cd

Comment thread ffmpeg/dag.py Outdated
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()]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented line.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 4349b18

Comment thread ffmpeg/dag.py Outdated
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()]:

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread ffmpeg/dag.py Outdated
# 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]

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the magical 3 constant come from? Needs explanation or named constant.

e.g. at the top of the file

_MEANINGFULLY_NAMED_CONSTANT = 3

then

-        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]

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Comment thread ffmpeg/dag.py Outdated
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]

@kkroening kkroening Dec 23, 2017

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's [:2] in this case? (need comment/explanation)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 14b6470

Comment thread ffmpeg/dag.py Outdated
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()]:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 14b6470

Comment thread ffmpeg/dag.py Outdated
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

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "extra stuff" mean? Comment should be more clear I think.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 14b6470

Comment thread ffmpeg/_run.py Outdated

def _get_stream_name(name):
return '[{}]'.format(name)
return '{}'.format(name)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should just get rid of this function.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 03a20ff

@kkroening kkroening left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@depau

depau commented Dec 23, 2017

Copy link
Copy Markdown
Collaborator Author

I agree with all your comments. I will probably get to it after holidays. See my reply to your immutability comment.

@depau depau force-pushed the stream_selectors branch from 7fb7026 to 21bd1cd Compare January 9, 2018 10:03
@depau

depau commented Jan 9, 2018

Copy link
Copy Markdown
Collaborator Author

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 master.

@depau depau force-pushed the stream_selectors branch from 14b6470 to ea90d91 Compare January 9, 2018 15:05
@depau

depau commented Jan 9, 2018

Copy link
Copy Markdown
Collaborator Author

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.
Anyway, it should be done. Check the new immutable implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants