blob: 04b4272e0585d8a29e1f1f993b478f8aff01d346
1 | /* |
2 | * filter graph parser |
3 | * Copyright (c) 2008 Vitor Sessak |
4 | * Copyright (c) 2007 Bobby Bingham |
5 | * |
6 | * This file is part of FFmpeg. |
7 | * |
8 | * FFmpeg is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Lesser General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2.1 of the License, or (at your option) any later version. |
12 | * |
13 | * FFmpeg is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Lesser General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Lesser General Public |
19 | * License along with FFmpeg; if not, write to the Free Software |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
21 | */ |
22 | |
23 | #include <string.h> |
24 | #include <stdio.h> |
25 | |
26 | #include "libavutil/avstring.h" |
27 | #include "libavutil/mem.h" |
28 | #include "avfilter.h" |
29 | |
30 | #define WHITESPACES " \n\t\r" |
31 | |
32 | /** |
33 | * Link two filters together. |
34 | * |
35 | * @see avfilter_link() |
36 | */ |
37 | static int link_filter(AVFilterContext *src, int srcpad, |
38 | AVFilterContext *dst, int dstpad, |
39 | void *log_ctx) |
40 | { |
41 | int ret; |
42 | if ((ret = avfilter_link(src, srcpad, dst, dstpad))) { |
43 | av_log(log_ctx, AV_LOG_ERROR, |
44 | "Cannot create the link %s:%d -> %s:%d\n", |
45 | src->filter->name, srcpad, dst->filter->name, dstpad); |
46 | return ret; |
47 | } |
48 | |
49 | return 0; |
50 | } |
51 | |
52 | /** |
53 | * Parse the name of a link, which has the format "[linkname]". |
54 | * |
55 | * @return a pointer (that need to be freed after use) to the name |
56 | * between parenthesis |
57 | */ |
58 | static char *parse_link_name(const char **buf, void *log_ctx) |
59 | { |
60 | const char *start = *buf; |
61 | char *name; |
62 | (*buf)++; |
63 | |
64 | name = av_get_token(buf, "]"); |
65 | if (!name) |
66 | goto fail; |
67 | |
68 | if (!name[0]) { |
69 | av_log(log_ctx, AV_LOG_ERROR, |
70 | "Bad (empty?) label found in the following: \"%s\".\n", start); |
71 | goto fail; |
72 | } |
73 | |
74 | if (*(*buf)++ != ']') { |
75 | av_log(log_ctx, AV_LOG_ERROR, |
76 | "Mismatched '[' found in the following: \"%s\".\n", start); |
77 | fail: |
78 | av_freep(&name); |
79 | } |
80 | |
81 | return name; |
82 | } |
83 | |
84 | /** |
85 | * Create an instance of a filter, initialize and insert it in the |
86 | * filtergraph in *ctx. |
87 | * |
88 | * @param filt_ctx put here a filter context in case of successful creation and configuration, NULL otherwise. |
89 | * @param ctx the filtergraph context |
90 | * @param index an index which is supposed to be unique for each filter instance added to the filtergraph |
91 | * @param filt_name the name of the filter to create |
92 | * @param args the arguments provided to the filter during its initialization |
93 | * @param log_ctx the log context to use |
94 | * @return >= 0 in case of success, a negative AVERROR code otherwise |
95 | */ |
96 | static int create_filter(AVFilterContext **filt_ctx, AVFilterGraph *ctx, int index, |
97 | const char *filt_name, const char *args, void *log_ctx) |
98 | { |
99 | AVFilter *filt; |
100 | char inst_name[30]; |
101 | char *tmp_args = NULL; |
102 | int ret; |
103 | |
104 | snprintf(inst_name, sizeof(inst_name), "Parsed_%s_%d", filt_name, index); |
105 | |
106 | filt = avfilter_get_by_name(filt_name); |
107 | |
108 | if (!filt) { |
109 | av_log(log_ctx, AV_LOG_ERROR, |
110 | "No such filter: '%s'\n", filt_name); |
111 | return AVERROR(EINVAL); |
112 | } |
113 | |
114 | *filt_ctx = avfilter_graph_alloc_filter(ctx, filt, inst_name); |
115 | if (!*filt_ctx) { |
116 | av_log(log_ctx, AV_LOG_ERROR, |
117 | "Error creating filter '%s'\n", filt_name); |
118 | return AVERROR(ENOMEM); |
119 | } |
120 | |
121 | if (!strcmp(filt_name, "scale") && (!args || !strstr(args, "flags")) && |
122 | ctx->scale_sws_opts) { |
123 | if (args) { |
124 | tmp_args = av_asprintf("%s:%s", |
125 | args, ctx->scale_sws_opts); |
126 | if (!tmp_args) |
127 | return AVERROR(ENOMEM); |
128 | args = tmp_args; |
129 | } else |
130 | args = ctx->scale_sws_opts; |
131 | } |
132 | |
133 | ret = avfilter_init_str(*filt_ctx, args); |
134 | if (ret < 0) { |
135 | av_log(log_ctx, AV_LOG_ERROR, |
136 | "Error initializing filter '%s'", filt_name); |
137 | if (args) |
138 | av_log(log_ctx, AV_LOG_ERROR, " with args '%s'", args); |
139 | av_log(log_ctx, AV_LOG_ERROR, "\n"); |
140 | avfilter_free(*filt_ctx); |
141 | *filt_ctx = NULL; |
142 | } |
143 | |
144 | av_free(tmp_args); |
145 | return ret; |
146 | } |
147 | |
148 | /** |
149 | * Parse a string of the form FILTER_NAME[=PARAMS], and create a |
150 | * corresponding filter instance which is added to graph with |
151 | * create_filter(). |
152 | * |
153 | * @param filt_ctx Pointer that is set to the created and configured filter |
154 | * context on success, set to NULL on failure. |
155 | * @param filt_ctx put here a pointer to the created filter context on |
156 | * success, NULL otherwise |
157 | * @param buf pointer to the buffer to parse, *buf will be updated to |
158 | * point to the char next after the parsed string |
159 | * @param index an index which is assigned to the created filter |
160 | * instance, and which is supposed to be unique for each filter |
161 | * instance added to the filtergraph |
162 | * @return >= 0 in case of success, a negative AVERROR code otherwise |
163 | */ |
164 | static int parse_filter(AVFilterContext **filt_ctx, const char **buf, AVFilterGraph *graph, |
165 | int index, void *log_ctx) |
166 | { |
167 | char *opts = NULL; |
168 | char *name = av_get_token(buf, "=,;["); |
169 | int ret; |
170 | |
171 | if (**buf == '=') { |
172 | (*buf)++; |
173 | opts = av_get_token(buf, "[],;"); |
174 | } |
175 | |
176 | ret = create_filter(filt_ctx, graph, index, name, opts, log_ctx); |
177 | av_free(name); |
178 | av_free(opts); |
179 | return ret; |
180 | } |
181 | |
182 | AVFilterInOut *avfilter_inout_alloc(void) |
183 | { |
184 | return av_mallocz(sizeof(AVFilterInOut)); |
185 | } |
186 | |
187 | void avfilter_inout_free(AVFilterInOut **inout) |
188 | { |
189 | while (*inout) { |
190 | AVFilterInOut *next = (*inout)->next; |
191 | av_freep(&(*inout)->name); |
192 | av_freep(inout); |
193 | *inout = next; |
194 | } |
195 | } |
196 | |
197 | static AVFilterInOut *extract_inout(const char *label, AVFilterInOut **links) |
198 | { |
199 | AVFilterInOut *ret; |
200 | |
201 | while (*links && (!(*links)->name || strcmp((*links)->name, label))) |
202 | links = &((*links)->next); |
203 | |
204 | ret = *links; |
205 | |
206 | if (ret) { |
207 | *links = ret->next; |
208 | ret->next = NULL; |
209 | } |
210 | |
211 | return ret; |
212 | } |
213 | |
214 | static void insert_inout(AVFilterInOut **inouts, AVFilterInOut *element) |
215 | { |
216 | element->next = *inouts; |
217 | *inouts = element; |
218 | } |
219 | |
220 | static void append_inout(AVFilterInOut **inouts, AVFilterInOut **element) |
221 | { |
222 | while (*inouts && (*inouts)->next) |
223 | inouts = &((*inouts)->next); |
224 | |
225 | if (!*inouts) |
226 | *inouts = *element; |
227 | else |
228 | (*inouts)->next = *element; |
229 | *element = NULL; |
230 | } |
231 | |
232 | static int link_filter_inouts(AVFilterContext *filt_ctx, |
233 | AVFilterInOut **curr_inputs, |
234 | AVFilterInOut **open_inputs, void *log_ctx) |
235 | { |
236 | int pad, ret; |
237 | |
238 | for (pad = 0; pad < filt_ctx->nb_inputs; pad++) { |
239 | AVFilterInOut *p = *curr_inputs; |
240 | |
241 | if (p) { |
242 | *curr_inputs = (*curr_inputs)->next; |
243 | p->next = NULL; |
244 | } else if (!(p = av_mallocz(sizeof(*p)))) |
245 | return AVERROR(ENOMEM); |
246 | |
247 | if (p->filter_ctx) { |
248 | ret = link_filter(p->filter_ctx, p->pad_idx, filt_ctx, pad, log_ctx); |
249 | av_freep(&p->name); |
250 | av_freep(&p); |
251 | if (ret < 0) |
252 | return ret; |
253 | } else { |
254 | p->filter_ctx = filt_ctx; |
255 | p->pad_idx = pad; |
256 | append_inout(open_inputs, &p); |
257 | } |
258 | } |
259 | |
260 | if (*curr_inputs) { |
261 | av_log(log_ctx, AV_LOG_ERROR, |
262 | "Too many inputs specified for the \"%s\" filter.\n", |
263 | filt_ctx->filter->name); |
264 | return AVERROR(EINVAL); |
265 | } |
266 | |
267 | pad = filt_ctx->nb_outputs; |
268 | while (pad--) { |
269 | AVFilterInOut *currlinkn = av_mallocz(sizeof(AVFilterInOut)); |
270 | if (!currlinkn) |
271 | return AVERROR(ENOMEM); |
272 | currlinkn->filter_ctx = filt_ctx; |
273 | currlinkn->pad_idx = pad; |
274 | insert_inout(curr_inputs, currlinkn); |
275 | } |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static int parse_inputs(const char **buf, AVFilterInOut **curr_inputs, |
281 | AVFilterInOut **open_outputs, void *log_ctx) |
282 | { |
283 | AVFilterInOut *parsed_inputs = NULL; |
284 | int pad = 0; |
285 | |
286 | while (**buf == '[') { |
287 | char *name = parse_link_name(buf, log_ctx); |
288 | AVFilterInOut *match; |
289 | |
290 | if (!name) |
291 | return AVERROR(EINVAL); |
292 | |
293 | /* First check if the label is not in the open_outputs list */ |
294 | match = extract_inout(name, open_outputs); |
295 | |
296 | if (match) { |
297 | av_free(name); |
298 | } else { |
299 | /* Not in the list, so add it as an input */ |
300 | if (!(match = av_mallocz(sizeof(AVFilterInOut)))) { |
301 | av_free(name); |
302 | return AVERROR(ENOMEM); |
303 | } |
304 | match->name = name; |
305 | match->pad_idx = pad; |
306 | } |
307 | |
308 | append_inout(&parsed_inputs, &match); |
309 | |
310 | *buf += strspn(*buf, WHITESPACES); |
311 | pad++; |
312 | } |
313 | |
314 | append_inout(&parsed_inputs, curr_inputs); |
315 | *curr_inputs = parsed_inputs; |
316 | |
317 | return pad; |
318 | } |
319 | |
320 | static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs, |
321 | AVFilterInOut **open_inputs, |
322 | AVFilterInOut **open_outputs, void *log_ctx) |
323 | { |
324 | int ret, pad = 0; |
325 | |
326 | while (**buf == '[') { |
327 | char *name = parse_link_name(buf, log_ctx); |
328 | AVFilterInOut *match; |
329 | |
330 | AVFilterInOut *input = *curr_inputs; |
331 | |
332 | if (!name) |
333 | return AVERROR(EINVAL); |
334 | |
335 | if (!input) { |
336 | av_log(log_ctx, AV_LOG_ERROR, |
337 | "No output pad can be associated to link label '%s'.\n", name); |
338 | av_free(name); |
339 | return AVERROR(EINVAL); |
340 | } |
341 | *curr_inputs = (*curr_inputs)->next; |
342 | |
343 | /* First check if the label is not in the open_inputs list */ |
344 | match = extract_inout(name, open_inputs); |
345 | |
346 | if (match) { |
347 | if ((ret = link_filter(input->filter_ctx, input->pad_idx, |
348 | match->filter_ctx, match->pad_idx, log_ctx)) < 0) { |
349 | av_free(name); |
350 | return ret; |
351 | } |
352 | av_freep(&match->name); |
353 | av_freep(&name); |
354 | av_freep(&match); |
355 | av_freep(&input); |
356 | } else { |
357 | /* Not in the list, so add the first input as an open_output */ |
358 | input->name = name; |
359 | insert_inout(open_outputs, input); |
360 | } |
361 | *buf += strspn(*buf, WHITESPACES); |
362 | pad++; |
363 | } |
364 | |
365 | return pad; |
366 | } |
367 | |
368 | static int parse_sws_flags(const char **buf, AVFilterGraph *graph) |
369 | { |
370 | char *p = strchr(*buf, ';'); |
371 | |
372 | if (strncmp(*buf, "sws_flags=", 10)) |
373 | return 0; |
374 | |
375 | if (!p) { |
376 | av_log(graph, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n"); |
377 | return AVERROR(EINVAL); |
378 | } |
379 | |
380 | *buf += 4; // keep the 'flags=' part |
381 | |
382 | av_freep(&graph->scale_sws_opts); |
383 | if (!(graph->scale_sws_opts = av_mallocz(p - *buf + 1))) |
384 | return AVERROR(ENOMEM); |
385 | av_strlcpy(graph->scale_sws_opts, *buf, p - *buf + 1); |
386 | |
387 | *buf = p + 1; |
388 | return 0; |
389 | } |
390 | |
391 | int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters, |
392 | AVFilterInOut **inputs, |
393 | AVFilterInOut **outputs) |
394 | { |
395 | int index = 0, ret = 0; |
396 | char chr = 0; |
397 | |
398 | AVFilterInOut *curr_inputs = NULL, *open_inputs = NULL, *open_outputs = NULL; |
399 | |
400 | filters += strspn(filters, WHITESPACES); |
401 | |
402 | if ((ret = parse_sws_flags(&filters, graph)) < 0) |
403 | goto fail; |
404 | |
405 | do { |
406 | AVFilterContext *filter; |
407 | filters += strspn(filters, WHITESPACES); |
408 | |
409 | if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, graph)) < 0) |
410 | goto end; |
411 | if ((ret = parse_filter(&filter, &filters, graph, index, graph)) < 0) |
412 | goto end; |
413 | |
414 | |
415 | if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, graph)) < 0) |
416 | goto end; |
417 | |
418 | if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs, |
419 | graph)) < 0) |
420 | goto end; |
421 | |
422 | filters += strspn(filters, WHITESPACES); |
423 | chr = *filters++; |
424 | |
425 | if (chr == ';' && curr_inputs) |
426 | append_inout(&open_outputs, &curr_inputs); |
427 | index++; |
428 | } while (chr == ',' || chr == ';'); |
429 | |
430 | if (chr) { |
431 | av_log(graph, AV_LOG_ERROR, |
432 | "Unable to parse graph description substring: \"%s\"\n", |
433 | filters - 1); |
434 | ret = AVERROR(EINVAL); |
435 | goto end; |
436 | } |
437 | |
438 | append_inout(&open_outputs, &curr_inputs); |
439 | |
440 | |
441 | *inputs = open_inputs; |
442 | *outputs = open_outputs; |
443 | return 0; |
444 | |
445 | fail:end: |
446 | while (graph->nb_filters) |
447 | avfilter_free(graph->filters[0]); |
448 | av_freep(&graph->filters); |
449 | avfilter_inout_free(&open_inputs); |
450 | avfilter_inout_free(&open_outputs); |
451 | avfilter_inout_free(&curr_inputs); |
452 | |
453 | *inputs = NULL; |
454 | *outputs = NULL; |
455 | |
456 | return ret; |
457 | } |
458 | |
459 | int avfilter_graph_parse(AVFilterGraph *graph, const char *filters, |
460 | AVFilterInOut *open_inputs, |
461 | AVFilterInOut *open_outputs, void *log_ctx) |
462 | { |
463 | int ret; |
464 | AVFilterInOut *cur, *match, *inputs = NULL, *outputs = NULL; |
465 | |
466 | if ((ret = avfilter_graph_parse2(graph, filters, &inputs, &outputs)) < 0) |
467 | goto fail; |
468 | |
469 | /* First input can be omitted if it is "[in]" */ |
470 | if (inputs && !inputs->name) |
471 | inputs->name = av_strdup("in"); |
472 | for (cur = inputs; cur; cur = cur->next) { |
473 | if (!cur->name) { |
474 | av_log(log_ctx, AV_LOG_ERROR, |
475 | "Not enough inputs specified for the \"%s\" filter.\n", |
476 | cur->filter_ctx->filter->name); |
477 | ret = AVERROR(EINVAL); |
478 | goto fail; |
479 | } |
480 | if (!(match = extract_inout(cur->name, &open_outputs))) |
481 | continue; |
482 | ret = avfilter_link(match->filter_ctx, match->pad_idx, |
483 | cur->filter_ctx, cur->pad_idx); |
484 | avfilter_inout_free(&match); |
485 | if (ret < 0) |
486 | goto fail; |
487 | } |
488 | |
489 | /* Last output can be omitted if it is "[out]" */ |
490 | if (outputs && !outputs->name) |
491 | outputs->name = av_strdup("out"); |
492 | for (cur = outputs; cur; cur = cur->next) { |
493 | if (!cur->name) { |
494 | av_log(log_ctx, AV_LOG_ERROR, |
495 | "Invalid filterchain containing an unlabelled output pad: \"%s\"\n", |
496 | filters); |
497 | ret = AVERROR(EINVAL); |
498 | goto fail; |
499 | } |
500 | if (!(match = extract_inout(cur->name, &open_inputs))) |
501 | continue; |
502 | ret = avfilter_link(cur->filter_ctx, cur->pad_idx, |
503 | match->filter_ctx, match->pad_idx); |
504 | avfilter_inout_free(&match); |
505 | if (ret < 0) |
506 | goto fail; |
507 | } |
508 | |
509 | fail: |
510 | if (ret < 0) { |
511 | while (graph->nb_filters) |
512 | avfilter_free(graph->filters[0]); |
513 | av_freep(&graph->filters); |
514 | } |
515 | avfilter_inout_free(&inputs); |
516 | avfilter_inout_free(&outputs); |
517 | avfilter_inout_free(&open_inputs); |
518 | avfilter_inout_free(&open_outputs); |
519 | return ret; |
520 | } |
521 | |
522 | int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters, |
523 | AVFilterInOut **open_inputs_ptr, AVFilterInOut **open_outputs_ptr, |
524 | void *log_ctx) |
525 | { |
526 | int index = 0, ret = 0; |
527 | char chr = 0; |
528 | |
529 | AVFilterInOut *curr_inputs = NULL; |
530 | AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL; |
531 | AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL; |
532 | |
533 | if ((ret = parse_sws_flags(&filters, graph)) < 0) |
534 | goto end; |
535 | |
536 | do { |
537 | AVFilterContext *filter; |
538 | const char *filterchain = filters; |
539 | filters += strspn(filters, WHITESPACES); |
540 | |
541 | if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, log_ctx)) < 0) |
542 | goto end; |
543 | |
544 | if ((ret = parse_filter(&filter, &filters, graph, index, log_ctx)) < 0) |
545 | goto end; |
546 | |
547 | if (filter->nb_inputs == 1 && !curr_inputs && !index) { |
548 | /* First input pad, assume it is "[in]" if not specified */ |
549 | const char *tmp = "[in]"; |
550 | if ((ret = parse_inputs(&tmp, &curr_inputs, &open_outputs, log_ctx)) < 0) |
551 | goto end; |
552 | } |
553 | |
554 | if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, log_ctx)) < 0) |
555 | goto end; |
556 | |
557 | if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs, |
558 | log_ctx)) < 0) |
559 | goto end; |
560 | |
561 | filters += strspn(filters, WHITESPACES); |
562 | chr = *filters++; |
563 | |
564 | if (chr == ';' && curr_inputs) { |
565 | av_log(log_ctx, AV_LOG_ERROR, |
566 | "Invalid filterchain containing an unlabelled output pad: \"%s\"\n", |
567 | filterchain); |
568 | ret = AVERROR(EINVAL); |
569 | goto end; |
570 | } |
571 | index++; |
572 | } while (chr == ',' || chr == ';'); |
573 | |
574 | if (chr) { |
575 | av_log(log_ctx, AV_LOG_ERROR, |
576 | "Unable to parse graph description substring: \"%s\"\n", |
577 | filters - 1); |
578 | ret = AVERROR(EINVAL); |
579 | goto end; |
580 | } |
581 | |
582 | if (curr_inputs) { |
583 | /* Last output pad, assume it is "[out]" if not specified */ |
584 | const char *tmp = "[out]"; |
585 | if ((ret = parse_outputs(&tmp, &curr_inputs, &open_inputs, &open_outputs, |
586 | log_ctx)) < 0) |
587 | goto end; |
588 | } |
589 | |
590 | end: |
591 | /* clear open_in/outputs only if not passed as parameters */ |
592 | if (open_inputs_ptr) *open_inputs_ptr = open_inputs; |
593 | else avfilter_inout_free(&open_inputs); |
594 | if (open_outputs_ptr) *open_outputs_ptr = open_outputs; |
595 | else avfilter_inout_free(&open_outputs); |
596 | avfilter_inout_free(&curr_inputs); |
597 | |
598 | if (ret < 0) { |
599 | while (graph->nb_filters) |
600 | avfilter_free(graph->filters[0]); |
601 | av_freep(&graph->filters); |
602 | } |
603 | return ret; |
604 | } |
605 |