summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changelog1
-rw-r--r--doc/ffmpeg.texi58
-rw-r--r--fftools/ffmpeg_filter.c89
3 files changed, 140 insertions, 8 deletions
diff --git a/Changelog b/Changelog
index 18e83b99a1..b7a1af4083 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,7 @@ releases are sorted from youngest to oldest.
version <next>:
- Raw Captions with Time (RCWT) closed caption demuxer
- LC3/LC3plus decoding/encoding using external library liblc3
+- ffmpeg CLI filtergraph chaining
version 7.0:
diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 801c083705..9bd548ce4e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -2145,14 +2145,62 @@ type -- see the @option{-filter} options. @var{filtergraph} is a description of
the filtergraph, as described in the ``Filtergraph syntax'' section of the
ffmpeg-filters manual.
-Input link labels must refer to either input streams or loopback decoders. For
-input streams, use the @code{[file_index:stream_specifier]} syntax (i.e. the
-same as @option{-map} uses). If @var{stream_specifier} matches multiple streams,
-the first one will be used.
+Inputs to a complex filtergraph may come from different source types,
+distinguished by the format of the corresponding link label:
+@itemize
+@item
+To connect an input stream, use @code{[file_index:stream_specifier]} (i.e. the
+same syntax as @option{-map}). If @var{stream_specifier} matches multiple
+streams, the first one will be used.
-For decoders, the link label must be [dec:@var{dec_idx}], where @var{dec_idx} is
+@item
+To connect a loopback decoder use [dec:@var{dec_idx}], where @var{dec_idx} is
the index of the loopback decoder to be connected to given input.
+@item
+To connect an output from another complex filtergraph, use its link label. E.g
+the following example:
+
+@example
+ffmpeg -i input.mkv \
+ -filter_complex '[0:v]scale=size=hd1080,split=outputs=2[for_enc][orig_scaled]' \
+ -c:v libx264 -map '[for_enc]' output.mkv \
+ -dec 0:0 \
+ -filter_complex '[dec:0][orig_scaled]hstack[stacked]' \
+ -map '[stacked]' -c:v ffv1 comparison.mkv
+@end example
+
+reads an input video and
+@itemize
+@item
+(line 2) uses a complex filtergraph with one input and two outputs
+to scale the video to 1920x1080 and duplicate the result to both
+outputs;
+
+@item
+(line 3) encodes one scaled output with @code{libx264} and writes the result to
+@file{output.mkv};
+
+@item
+(line 4) decodes this encoded stream with a loopback decoder;
+
+@item
+(line 5) places the output of the loopback decoder (i.e. the
+@code{libx264}-encoded video) side by side with the scaled original input;
+
+@item
+(line 6) combined video is then losslessly encoded and written into
+@file{comparison.mkv}.
+
+@end itemize
+
+Note that the two filtergraphs cannot be combined into one, because then there
+would be a cycle in the transcoding pipeline (filtergraph output goes to
+encoding, from there to decoding, then back to the same graph), and such cycles
+are not allowed.
+
+@end itemize
+
An unlabeled input will be connected to the first unused input stream of the
matching type.
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 1e14962f41..f108f8daf9 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -902,6 +902,63 @@ int ofilter_bind_ost(OutputFilter *ofilter, OutputStream *ost,
return 0;
}
+static int ofilter_bind_ifilter(OutputFilter *ofilter, InputFilterPriv *ifp,
+ const OutputFilterOptions *opts)
+{
+ OutputFilterPriv *ofp = ofp_from_ofilter(ofilter);
+
+ av_assert0(!ofilter->bound);
+ av_assert0(ofilter->type == ifp->type);
+
+ ofilter->bound = 1;
+ av_freep(&ofilter->linklabel);
+
+ ofp->name = av_strdup(opts->name);
+ if (!ofp->name)
+ return AVERROR(EINVAL);
+
+ av_strlcatf(ofp->log_name, sizeof(ofp->log_name), "->%s", ofp->name);
+
+ return 0;
+}
+
+static int ifilter_bind_fg(InputFilterPriv *ifp, FilterGraph *fg_src, int out_idx)
+{
+ FilterGraphPriv *fgp = fgp_from_fg(ifp->ifilter.graph);
+ OutputFilter *ofilter_src = fg_src->outputs[out_idx];
+ OutputFilterOptions opts;
+ char name[32];
+ int ret;
+
+ av_assert0(!ifp->bound);
+ ifp->bound = 1;
+
+ if (ifp->type != ofilter_src->type) {
+ av_log(fgp, AV_LOG_ERROR, "Tried to connect %s output to %s input\n",
+ av_get_media_type_string(ofilter_src->type),
+ av_get_media_type_string(ifp->type));
+ return AVERROR(EINVAL);
+ }
+
+ ifp->type_src = ifp->type;
+
+ memset(&opts, 0, sizeof(opts));
+
+ snprintf(name, sizeof(name), "fg:%d:%d", fgp->fg.index, ifp->index);
+ opts.name = name;
+
+ ret = ofilter_bind_ifilter(ofilter_src, ifp, &opts);
+ if (ret < 0)
+ return ret;
+
+ ret = sch_connect(fgp->sch, SCH_FILTER_OUT(fg_src->index, out_idx),
+ SCH_FILTER_IN(fgp->sch_idx, ifp->index));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static InputFilter *ifilter_alloc(FilterGraph *fg)
{
InputFilterPriv *ifp;
@@ -1213,12 +1270,38 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
ifilter->name);
return ret;
} else if (ifp->linklabel) {
- // bind to an explicitly specified demuxer stream
AVFormatContext *s;
AVStream *st = NULL;
char *p;
- int file_idx = strtol(ifp->linklabel, &p, 0);
+ int file_idx;
+
+ // try finding an unbound filtergraph output with this label
+ for (int i = 0; i < nb_filtergraphs; i++) {
+ FilterGraph *fg_src = filtergraphs[i];
+
+ if (fg == fg_src)
+ continue;
+
+ for (int j = 0; j < fg_src->nb_outputs; j++) {
+ OutputFilter *ofilter = fg_src->outputs[j];
+
+ if (!ofilter->bound && ofilter->linklabel &&
+ !strcmp(ofilter->linklabel, ifp->linklabel)) {
+ av_log(fg, AV_LOG_VERBOSE,
+ "Binding input with label '%s' to filtergraph output %d:%d\n",
+ ifp->linklabel, i, j);
+ ret = ifilter_bind_fg(ifp, fg_src, j);
+ if (ret < 0)
+ av_log(fg, AV_LOG_ERROR, "Error binding filtergraph input %s\n",
+ ifp->linklabel);
+ return ret;
+ }
+ }
+ }
+
+ // bind to an explicitly specified demuxer stream
+ file_idx = strtol(ifp->linklabel, &p, 0);
if (file_idx < 0 || file_idx >= nb_input_files) {
av_log(fg, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n",
file_idx, fgp->graph_desc);
@@ -1274,7 +1357,7 @@ static int fg_complex_bind_input(FilterGraph *fg, InputFilter *ifilter)
static int bind_inputs(FilterGraph *fg)
{
- // bind filtergraph inputs to input streams
+ // bind filtergraph inputs to input streams or other filtergraphs
for (int i = 0; i < fg->nb_inputs; i++) {
InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]);
int ret;