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 |
|
5789 |
static int link_filter(AVFilterContext *src, int srcpad, |
38 |
|
|
AVFilterContext *dst, int dstpad, |
39 |
|
|
void *log_ctx) |
40 |
|
|
{ |
41 |
|
|
int ret; |
42 |
✗✓ |
5789 |
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 |
|
5789 |
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 |
|
375 |
static char *parse_link_name(const char **buf, void *log_ctx) |
59 |
|
|
{ |
60 |
|
375 |
const char *start = *buf; |
61 |
|
|
char *name; |
62 |
|
375 |
(*buf)++; |
63 |
|
|
|
64 |
|
375 |
name = av_get_token(buf, "]"); |
65 |
✗✓ |
375 |
if (!name) |
66 |
|
|
return NULL; |
67 |
|
|
|
68 |
✗✓ |
375 |
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 |
✗✓ |
375 |
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 |
|
|
return NULL; |
80 |
|
|
} |
81 |
|
375 |
(*buf)++; |
82 |
|
|
|
83 |
|
375 |
return name; |
84 |
|
|
} |
85 |
|
|
|
86 |
|
|
/** |
87 |
|
|
* Create an instance of a filter, initialize and insert it in the |
88 |
|
|
* filtergraph in *ctx. |
89 |
|
|
* |
90 |
|
|
* @param filt_ctx put here a filter context in case of successful creation and configuration, NULL otherwise. |
91 |
|
|
* @param ctx the filtergraph context |
92 |
|
|
* @param index an index which is supposed to be unique for each filter instance added to the filtergraph |
93 |
|
|
* @param name the name of the filter to create, can be filter name or filter_name\@id as instance name |
94 |
|
|
* @param args the arguments provided to the filter during its initialization |
95 |
|
|
* @param log_ctx the log context to use |
96 |
|
|
* @return >= 0 in case of success, a negative AVERROR code otherwise |
97 |
|
|
*/ |
98 |
|
11702 |
static int create_filter(AVFilterContext **filt_ctx, AVFilterGraph *ctx, int index, |
99 |
|
|
const char *name, const char *args, void *log_ctx) |
100 |
|
|
{ |
101 |
|
|
const AVFilter *filt; |
102 |
|
|
char name2[30]; |
103 |
|
11702 |
const char *inst_name = NULL, *filt_name = NULL; |
104 |
|
11702 |
char *tmp_args = NULL; |
105 |
|
|
int ret, k; |
106 |
|
|
|
107 |
|
11702 |
av_strlcpy(name2, name, sizeof(name2)); |
108 |
|
|
|
109 |
✓✓ |
77532 |
for (k = 0; name2[k]; k++) { |
110 |
✗✓✗✗
|
65830 |
if (name2[k] == '@' && name[k+1]) { |
111 |
|
|
name2[k] = 0; |
112 |
|
|
inst_name = name; |
113 |
|
|
filt_name = name2; |
114 |
|
|
break; |
115 |
|
|
} |
116 |
|
|
} |
117 |
|
|
|
118 |
✓✗ |
11702 |
if (!inst_name) { |
119 |
|
11702 |
snprintf(name2, sizeof(name2), "Parsed_%s_%d", name, index); |
120 |
|
11702 |
inst_name = name2; |
121 |
|
11702 |
filt_name = name; |
122 |
|
|
} |
123 |
|
|
|
124 |
|
11702 |
filt = avfilter_get_by_name(filt_name); |
125 |
|
|
|
126 |
✗✓ |
11702 |
if (!filt) { |
127 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
128 |
|
|
"No such filter: '%s'\n", filt_name); |
129 |
|
|
return AVERROR(EINVAL); |
130 |
|
|
} |
131 |
|
|
|
132 |
|
11702 |
*filt_ctx = avfilter_graph_alloc_filter(ctx, filt, inst_name); |
133 |
✗✓ |
11702 |
if (!*filt_ctx) { |
134 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
135 |
|
|
"Error creating filter '%s'\n", filt_name); |
136 |
|
|
return AVERROR(ENOMEM); |
137 |
|
|
} |
138 |
|
|
|
139 |
✓✓✓✓ ✓✓ |
11702 |
if (!strcmp(filt_name, "scale") && (!args || !strstr(args, "flags")) && |
140 |
✓✓ |
2666 |
ctx->scale_sws_opts) { |
141 |
✓✓ |
2652 |
if (args) { |
142 |
|
201 |
tmp_args = av_asprintf("%s:%s", |
143 |
|
|
args, ctx->scale_sws_opts); |
144 |
✗✓ |
201 |
if (!tmp_args) |
145 |
|
|
return AVERROR(ENOMEM); |
146 |
|
201 |
args = tmp_args; |
147 |
|
|
} else |
148 |
|
2451 |
args = ctx->scale_sws_opts; |
149 |
|
|
} |
150 |
|
|
|
151 |
|
11702 |
ret = avfilter_init_str(*filt_ctx, args); |
152 |
✗✓ |
11702 |
if (ret < 0) { |
153 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
154 |
|
|
"Error initializing filter '%s'", filt_name); |
155 |
|
|
if (args) |
156 |
|
|
av_log(log_ctx, AV_LOG_ERROR, " with args '%s'", args); |
157 |
|
|
av_log(log_ctx, AV_LOG_ERROR, "\n"); |
158 |
|
|
avfilter_free(*filt_ctx); |
159 |
|
|
*filt_ctx = NULL; |
160 |
|
|
} |
161 |
|
|
|
162 |
|
11702 |
av_free(tmp_args); |
163 |
|
11702 |
return ret; |
164 |
|
|
} |
165 |
|
|
|
166 |
|
|
/** |
167 |
|
|
* Parse a string of the form FILTER_NAME[=PARAMS], and create a |
168 |
|
|
* corresponding filter instance which is added to graph with |
169 |
|
|
* create_filter(). |
170 |
|
|
* |
171 |
|
|
* @param filt_ctx Pointer that is set to the created and configured filter |
172 |
|
|
* context on success, set to NULL on failure. |
173 |
|
|
* @param filt_ctx put here a pointer to the created filter context on |
174 |
|
|
* success, NULL otherwise |
175 |
|
|
* @param buf pointer to the buffer to parse, *buf will be updated to |
176 |
|
|
* point to the char next after the parsed string |
177 |
|
|
* @param index an index which is assigned to the created filter |
178 |
|
|
* instance, and which is supposed to be unique for each filter |
179 |
|
|
* instance added to the filtergraph |
180 |
|
|
* @return >= 0 in case of success, a negative AVERROR code otherwise |
181 |
|
|
*/ |
182 |
|
11702 |
static int parse_filter(AVFilterContext **filt_ctx, const char **buf, AVFilterGraph *graph, |
183 |
|
|
int index, void *log_ctx) |
184 |
|
|
{ |
185 |
|
11702 |
char *opts = NULL; |
186 |
|
11702 |
char *name = av_get_token(buf, "=,;["); |
187 |
|
|
int ret; |
188 |
|
|
|
189 |
✗✓ |
11702 |
if (!name) |
190 |
|
|
return AVERROR(ENOMEM); |
191 |
|
|
|
192 |
✓✓ |
11702 |
if (**buf == '=') { |
193 |
|
6098 |
(*buf)++; |
194 |
|
6098 |
opts = av_get_token(buf, "[],;"); |
195 |
✗✓ |
6098 |
if (!opts) { |
196 |
|
|
av_free(name); |
197 |
|
|
return AVERROR(ENOMEM); |
198 |
|
|
} |
199 |
|
|
} |
200 |
|
|
|
201 |
|
11702 |
ret = create_filter(filt_ctx, graph, index, name, opts, log_ctx); |
202 |
|
11702 |
av_free(name); |
203 |
|
11702 |
av_free(opts); |
204 |
|
11702 |
return ret; |
205 |
|
|
} |
206 |
|
|
|
207 |
|
|
AVFilterInOut *avfilter_inout_alloc(void) |
208 |
|
|
{ |
209 |
|
|
return av_mallocz(sizeof(AVFilterInOut)); |
210 |
|
|
} |
211 |
|
|
|
212 |
|
17722 |
void avfilter_inout_free(AVFilterInOut **inout) |
213 |
|
|
{ |
214 |
✓✓ |
29547 |
while (*inout) { |
215 |
|
11825 |
AVFilterInOut *next = (*inout)->next; |
216 |
|
11825 |
av_freep(&(*inout)->name); |
217 |
|
11825 |
av_freep(inout); |
218 |
|
11825 |
*inout = next; |
219 |
|
|
} |
220 |
|
17722 |
} |
221 |
|
|
|
222 |
|
375 |
static AVFilterInOut *extract_inout(const char *label, AVFilterInOut **links) |
223 |
|
|
{ |
224 |
|
|
AVFilterInOut *ret; |
225 |
|
|
|
226 |
✓✓✓✓ ✓✓ |
564 |
while (*links && (!(*links)->name || strcmp((*links)->name, label))) |
227 |
|
189 |
links = &((*links)->next); |
228 |
|
|
|
229 |
|
375 |
ret = *links; |
230 |
|
|
|
231 |
✓✓ |
375 |
if (ret) { |
232 |
|
156 |
*links = ret->next; |
233 |
|
156 |
ret->next = NULL; |
234 |
|
|
} |
235 |
|
|
|
236 |
|
375 |
return ret; |
237 |
|
|
} |
238 |
|
|
|
239 |
|
11937 |
static void insert_inout(AVFilterInOut **inouts, AVFilterInOut *element) |
240 |
|
|
{ |
241 |
|
11937 |
element->next = *inouts; |
242 |
|
11937 |
*inouts = element; |
243 |
|
11937 |
} |
244 |
|
|
|
245 |
|
23668 |
static void append_inout(AVFilterInOut **inouts, AVFilterInOut **element) |
246 |
|
|
{ |
247 |
✓✓✓✓
|
23783 |
while (*inouts && (*inouts)->next) |
248 |
|
115 |
inouts = &((*inouts)->next); |
249 |
|
|
|
250 |
✓✓ |
23668 |
if (!*inouts) |
251 |
|
23440 |
*inouts = *element; |
252 |
|
|
else |
253 |
|
228 |
(*inouts)->next = *element; |
254 |
|
23668 |
*element = NULL; |
255 |
|
23668 |
} |
256 |
|
|
|
257 |
|
11702 |
static int link_filter_inouts(AVFilterContext *filt_ctx, |
258 |
|
|
AVFilterInOut **curr_inputs, |
259 |
|
|
AVFilterInOut **open_inputs, void *log_ctx) |
260 |
|
|
{ |
261 |
|
|
int pad, ret; |
262 |
|
|
|
263 |
✓✓ |
23363 |
for (pad = 0; pad < filt_ctx->nb_inputs; pad++) { |
264 |
|
11661 |
AVFilterInOut *p = *curr_inputs; |
265 |
|
|
|
266 |
✓✓ |
11661 |
if (p) { |
267 |
|
5813 |
*curr_inputs = (*curr_inputs)->next; |
268 |
|
5813 |
p->next = NULL; |
269 |
✗✓ |
5848 |
} else if (!(p = av_mallocz(sizeof(*p)))) |
270 |
|
|
return AVERROR(ENOMEM); |
271 |
|
|
|
272 |
✓✓ |
11661 |
if (p->filter_ctx) { |
273 |
|
5789 |
ret = link_filter(p->filter_ctx, p->pad_idx, filt_ctx, pad, log_ctx); |
274 |
|
5789 |
av_freep(&p->name); |
275 |
|
5789 |
av_freep(&p); |
276 |
✗✓ |
5789 |
if (ret < 0) |
277 |
|
|
return ret; |
278 |
|
|
} else { |
279 |
|
5872 |
p->filter_ctx = filt_ctx; |
280 |
|
5872 |
p->pad_idx = pad; |
281 |
|
5872 |
append_inout(open_inputs, &p); |
282 |
|
|
} |
283 |
|
|
} |
284 |
|
|
|
285 |
✗✓ |
11702 |
if (*curr_inputs) { |
286 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
287 |
|
|
"Too many inputs specified for the \"%s\" filter.\n", |
288 |
|
|
filt_ctx->filter->name); |
289 |
|
|
return AVERROR(EINVAL); |
290 |
|
|
} |
291 |
|
|
|
292 |
|
11702 |
pad = filt_ctx->nb_outputs; |
293 |
✓✓ |
23444 |
while (pad--) { |
294 |
|
11742 |
AVFilterInOut *currlinkn = av_mallocz(sizeof(AVFilterInOut)); |
295 |
✗✓ |
11742 |
if (!currlinkn) |
296 |
|
|
return AVERROR(ENOMEM); |
297 |
|
11742 |
currlinkn->filter_ctx = filt_ctx; |
298 |
|
11742 |
currlinkn->pad_idx = pad; |
299 |
|
11742 |
insert_inout(curr_inputs, currlinkn); |
300 |
|
|
} |
301 |
|
|
|
302 |
|
11702 |
return 0; |
303 |
|
|
} |
304 |
|
|
|
305 |
|
11702 |
static int parse_inputs(const char **buf, AVFilterInOut **curr_inputs, |
306 |
|
|
AVFilterInOut **open_outputs, void *log_ctx) |
307 |
|
|
{ |
308 |
|
11702 |
AVFilterInOut *parsed_inputs = NULL; |
309 |
|
11702 |
int pad = 0; |
310 |
|
|
|
311 |
✓✓ |
11882 |
while (**buf == '[') { |
312 |
|
180 |
char *name = parse_link_name(buf, log_ctx); |
313 |
|
|
AVFilterInOut *match; |
314 |
|
|
|
315 |
✗✓ |
180 |
if (!name) { |
316 |
|
|
avfilter_inout_free(&parsed_inputs); |
317 |
|
|
return AVERROR(EINVAL); |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
/* First check if the label is not in the open_outputs list */ |
321 |
|
180 |
match = extract_inout(name, open_outputs); |
322 |
|
|
|
323 |
✓✓ |
180 |
if (match) { |
324 |
|
156 |
av_free(name); |
325 |
|
|
} else { |
326 |
|
|
/* Not in the list, so add it as an input */ |
327 |
✗✓ |
24 |
if (!(match = av_mallocz(sizeof(AVFilterInOut)))) { |
328 |
|
|
avfilter_inout_free(&parsed_inputs); |
329 |
|
|
av_free(name); |
330 |
|
|
return AVERROR(ENOMEM); |
331 |
|
|
} |
332 |
|
24 |
match->name = name; |
333 |
|
24 |
match->pad_idx = pad; |
334 |
|
|
} |
335 |
|
|
|
336 |
|
180 |
append_inout(&parsed_inputs, &match); |
337 |
|
|
|
338 |
|
180 |
*buf += strspn(*buf, WHITESPACES); |
339 |
|
180 |
pad++; |
340 |
|
|
} |
341 |
|
|
|
342 |
|
11702 |
append_inout(&parsed_inputs, curr_inputs); |
343 |
|
11702 |
*curr_inputs = parsed_inputs; |
344 |
|
|
|
345 |
|
11702 |
return pad; |
346 |
|
|
} |
347 |
|
|
|
348 |
|
11729 |
static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs, |
349 |
|
|
AVFilterInOut **open_inputs, |
350 |
|
|
AVFilterInOut **open_outputs, void *log_ctx) |
351 |
|
|
{ |
352 |
|
11729 |
int ret, pad = 0; |
353 |
|
|
|
354 |
✓✓ |
11924 |
while (**buf == '[') { |
355 |
|
195 |
char *name = parse_link_name(buf, log_ctx); |
356 |
|
|
AVFilterInOut *match; |
357 |
|
|
|
358 |
|
195 |
AVFilterInOut *input = *curr_inputs; |
359 |
|
|
|
360 |
✗✓ |
195 |
if (!name) |
361 |
|
|
return AVERROR(EINVAL); |
362 |
|
|
|
363 |
✗✓ |
195 |
if (!input) { |
364 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
365 |
|
|
"No output pad can be associated to link label '%s'.\n", name); |
366 |
|
|
av_free(name); |
367 |
|
|
return AVERROR(EINVAL); |
368 |
|
|
} |
369 |
|
195 |
*curr_inputs = (*curr_inputs)->next; |
370 |
|
|
|
371 |
|
|
/* First check if the label is not in the open_inputs list */ |
372 |
|
195 |
match = extract_inout(name, open_inputs); |
373 |
|
|
|
374 |
✗✓ |
195 |
if (match) { |
375 |
|
|
ret = link_filter(input->filter_ctx, input->pad_idx, |
376 |
|
|
match->filter_ctx, match->pad_idx, log_ctx); |
377 |
|
|
av_freep(&match->name); |
378 |
|
|
av_freep(&name); |
379 |
|
|
av_freep(&match); |
380 |
|
|
av_freep(&input); |
381 |
|
|
if (ret < 0) |
382 |
|
|
return ret; |
383 |
|
|
} else { |
384 |
|
|
/* Not in the list, so add the first input as an open_output */ |
385 |
|
195 |
input->name = name; |
386 |
|
195 |
insert_inout(open_outputs, input); |
387 |
|
|
} |
388 |
|
195 |
*buf += strspn(*buf, WHITESPACES); |
389 |
|
195 |
pad++; |
390 |
|
|
} |
391 |
|
|
|
392 |
|
11729 |
return pad; |
393 |
|
|
} |
394 |
|
|
|
395 |
|
5945 |
static int parse_sws_flags(const char **buf, AVFilterGraph *graph) |
396 |
|
|
{ |
397 |
|
5945 |
char *p = strchr(*buf, ';'); |
398 |
|
|
|
399 |
✓✓ |
5945 |
if (strncmp(*buf, "sws_flags=", 10)) |
400 |
|
5900 |
return 0; |
401 |
|
|
|
402 |
✗✓ |
45 |
if (!p) { |
403 |
|
|
av_log(graph, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n"); |
404 |
|
|
return AVERROR(EINVAL); |
405 |
|
|
} |
406 |
|
|
|
407 |
|
45 |
*buf += 4; // keep the 'flags=' part |
408 |
|
|
|
409 |
|
45 |
av_freep(&graph->scale_sws_opts); |
410 |
✗✓ |
45 |
if (!(graph->scale_sws_opts = av_mallocz(p - *buf + 1))) |
411 |
|
|
return AVERROR(ENOMEM); |
412 |
|
45 |
av_strlcpy(graph->scale_sws_opts, *buf, p - *buf + 1); |
413 |
|
|
|
414 |
|
45 |
*buf = p + 1; |
415 |
|
45 |
return 0; |
416 |
|
|
} |
417 |
|
|
|
418 |
|
5914 |
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters, |
419 |
|
|
AVFilterInOut **inputs, |
420 |
|
|
AVFilterInOut **outputs) |
421 |
|
|
{ |
422 |
|
5914 |
int index = 0, ret = 0; |
423 |
|
5914 |
char chr = 0; |
424 |
|
|
|
425 |
|
5914 |
AVFilterInOut *curr_inputs = NULL, *open_inputs = NULL, *open_outputs = NULL; |
426 |
|
|
|
427 |
|
5914 |
filters += strspn(filters, WHITESPACES); |
428 |
|
|
|
429 |
✗✓ |
5914 |
if ((ret = parse_sws_flags(&filters, graph)) < 0) |
430 |
|
|
goto fail; |
431 |
|
|
|
432 |
|
|
do { |
433 |
|
|
AVFilterContext *filter; |
434 |
|
11652 |
filters += strspn(filters, WHITESPACES); |
435 |
|
|
|
436 |
✗✓ |
11652 |
if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, graph)) < 0) |
437 |
|
|
goto end; |
438 |
✗✓ |
11652 |
if ((ret = parse_filter(&filter, &filters, graph, index, graph)) < 0) |
439 |
|
|
goto end; |
440 |
|
|
|
441 |
|
|
|
442 |
✗✓ |
11652 |
if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, graph)) < 0) |
443 |
|
|
goto end; |
444 |
|
|
|
445 |
✗✓ |
11652 |
if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs, |
446 |
|
|
graph)) < 0) |
447 |
|
|
goto end; |
448 |
|
|
|
449 |
|
11652 |
filters += strspn(filters, WHITESPACES); |
450 |
|
11652 |
chr = *filters++; |
451 |
|
|
|
452 |
✓✓✗✓
|
11652 |
if (chr == ';' && curr_inputs) |
453 |
|
|
append_inout(&open_outputs, &curr_inputs); |
454 |
|
11652 |
index++; |
455 |
✓✓✓✓
|
11652 |
} while (chr == ',' || chr == ';'); |
456 |
|
|
|
457 |
✗✓ |
5914 |
if (chr) { |
458 |
|
|
av_log(graph, AV_LOG_ERROR, |
459 |
|
|
"Unable to parse graph description substring: \"%s\"\n", |
460 |
|
|
filters - 1); |
461 |
|
|
ret = AVERROR(EINVAL); |
462 |
|
|
goto end; |
463 |
|
|
} |
464 |
|
|
|
465 |
|
5914 |
append_inout(&open_outputs, &curr_inputs); |
466 |
|
|
|
467 |
|
|
|
468 |
|
5914 |
*inputs = open_inputs; |
469 |
|
5914 |
*outputs = open_outputs; |
470 |
|
5914 |
return 0; |
471 |
|
|
|
472 |
|
|
fail:end: |
473 |
|
|
while (graph->nb_filters) |
474 |
|
|
avfilter_free(graph->filters[0]); |
475 |
|
|
av_freep(&graph->filters); |
476 |
|
|
avfilter_inout_free(&open_inputs); |
477 |
|
|
avfilter_inout_free(&open_outputs); |
478 |
|
|
avfilter_inout_free(&curr_inputs); |
479 |
|
|
|
480 |
|
|
*inputs = NULL; |
481 |
|
|
*outputs = NULL; |
482 |
|
|
|
483 |
|
|
return ret; |
484 |
|
|
} |
485 |
|
|
|
486 |
|
|
int avfilter_graph_parse(AVFilterGraph *graph, const char *filters, |
487 |
|
|
AVFilterInOut *open_inputs, |
488 |
|
|
AVFilterInOut *open_outputs, void *log_ctx) |
489 |
|
|
{ |
490 |
|
|
int ret; |
491 |
|
|
AVFilterInOut *cur, *match, *inputs = NULL, *outputs = NULL; |
492 |
|
|
|
493 |
|
|
if ((ret = avfilter_graph_parse2(graph, filters, &inputs, &outputs)) < 0) |
494 |
|
|
goto fail; |
495 |
|
|
|
496 |
|
|
/* First input can be omitted if it is "[in]" */ |
497 |
|
|
if (inputs && !inputs->name) |
498 |
|
|
inputs->name = av_strdup("in"); |
499 |
|
|
for (cur = inputs; cur; cur = cur->next) { |
500 |
|
|
if (!cur->name) { |
501 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
502 |
|
|
"Not enough inputs specified for the \"%s\" filter.\n", |
503 |
|
|
cur->filter_ctx->filter->name); |
504 |
|
|
ret = AVERROR(EINVAL); |
505 |
|
|
goto fail; |
506 |
|
|
} |
507 |
|
|
if (!(match = extract_inout(cur->name, &open_outputs))) |
508 |
|
|
continue; |
509 |
|
|
ret = avfilter_link(match->filter_ctx, match->pad_idx, |
510 |
|
|
cur->filter_ctx, cur->pad_idx); |
511 |
|
|
avfilter_inout_free(&match); |
512 |
|
|
if (ret < 0) |
513 |
|
|
goto fail; |
514 |
|
|
} |
515 |
|
|
|
516 |
|
|
/* Last output can be omitted if it is "[out]" */ |
517 |
|
|
if (outputs && !outputs->name) |
518 |
|
|
outputs->name = av_strdup("out"); |
519 |
|
|
for (cur = outputs; cur; cur = cur->next) { |
520 |
|
|
if (!cur->name) { |
521 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
522 |
|
|
"Invalid filterchain containing an unlabelled output pad: \"%s\"\n", |
523 |
|
|
filters); |
524 |
|
|
ret = AVERROR(EINVAL); |
525 |
|
|
goto fail; |
526 |
|
|
} |
527 |
|
|
if (!(match = extract_inout(cur->name, &open_inputs))) |
528 |
|
|
continue; |
529 |
|
|
ret = avfilter_link(cur->filter_ctx, cur->pad_idx, |
530 |
|
|
match->filter_ctx, match->pad_idx); |
531 |
|
|
avfilter_inout_free(&match); |
532 |
|
|
if (ret < 0) |
533 |
|
|
goto fail; |
534 |
|
|
} |
535 |
|
|
|
536 |
|
|
fail: |
537 |
|
|
if (ret < 0) { |
538 |
|
|
while (graph->nb_filters) |
539 |
|
|
avfilter_free(graph->filters[0]); |
540 |
|
|
av_freep(&graph->filters); |
541 |
|
|
} |
542 |
|
|
avfilter_inout_free(&inputs); |
543 |
|
|
avfilter_inout_free(&outputs); |
544 |
|
|
avfilter_inout_free(&open_inputs); |
545 |
|
|
avfilter_inout_free(&open_outputs); |
546 |
|
|
return ret; |
547 |
|
|
} |
548 |
|
|
|
549 |
|
31 |
int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters, |
550 |
|
|
AVFilterInOut **open_inputs_ptr, AVFilterInOut **open_outputs_ptr, |
551 |
|
|
void *log_ctx) |
552 |
|
|
{ |
553 |
|
31 |
int index = 0, ret = 0; |
554 |
|
31 |
char chr = 0; |
555 |
|
|
|
556 |
|
31 |
AVFilterInOut *curr_inputs = NULL; |
557 |
✓✗ |
31 |
AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL; |
558 |
✓✗ |
31 |
AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL; |
559 |
|
|
|
560 |
✗✓ |
31 |
if ((ret = parse_sws_flags(&filters, graph)) < 0) |
561 |
|
|
goto end; |
562 |
|
|
|
563 |
|
|
do { |
564 |
|
|
AVFilterContext *filter; |
565 |
|
50 |
const char *filterchain = filters; |
566 |
|
50 |
filters += strspn(filters, WHITESPACES); |
567 |
|
|
|
568 |
✗✓ |
50 |
if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, log_ctx)) < 0) |
569 |
|
|
goto end; |
570 |
|
|
|
571 |
✗✓ |
50 |
if ((ret = parse_filter(&filter, &filters, graph, index, log_ctx)) < 0) |
572 |
|
|
goto end; |
573 |
|
|
|
574 |
✓✓✗✓ ✗✗ |
50 |
if (filter->nb_inputs == 1 && !curr_inputs && !index) { |
575 |
|
|
/* First input pad, assume it is "[in]" if not specified */ |
576 |
|
|
const char *tmp = "[in]"; |
577 |
|
|
if ((ret = parse_inputs(&tmp, &curr_inputs, &open_outputs, log_ctx)) < 0) |
578 |
|
|
goto end; |
579 |
|
|
} |
580 |
|
|
|
581 |
✗✓ |
50 |
if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, log_ctx)) < 0) |
582 |
|
|
goto end; |
583 |
|
|
|
584 |
✗✓ |
50 |
if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs, |
585 |
|
|
log_ctx)) < 0) |
586 |
|
|
goto end; |
587 |
|
|
|
588 |
|
50 |
filters += strspn(filters, WHITESPACES); |
589 |
|
50 |
chr = *filters++; |
590 |
|
|
|
591 |
✓✓✗✓
|
50 |
if (chr == ';' && curr_inputs) { |
592 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
593 |
|
|
"Invalid filterchain containing an unlabelled output pad: \"%s\"\n", |
594 |
|
|
filterchain); |
595 |
|
|
ret = AVERROR(EINVAL); |
596 |
|
|
goto end; |
597 |
|
|
} |
598 |
|
50 |
index++; |
599 |
✓✓✓✓
|
50 |
} while (chr == ',' || chr == ';'); |
600 |
|
|
|
601 |
✗✓ |
31 |
if (chr) { |
602 |
|
|
av_log(log_ctx, AV_LOG_ERROR, |
603 |
|
|
"Unable to parse graph description substring: \"%s\"\n", |
604 |
|
|
filters - 1); |
605 |
|
|
ret = AVERROR(EINVAL); |
606 |
|
|
goto end; |
607 |
|
|
} |
608 |
|
|
|
609 |
✓✓ |
31 |
if (curr_inputs) { |
610 |
|
|
/* Last output pad, assume it is "[out]" if not specified */ |
611 |
|
27 |
const char *tmp = "[out]"; |
612 |
✗✓ |
27 |
if ((ret = parse_outputs(&tmp, &curr_inputs, &open_inputs, &open_outputs, |
613 |
|
|
log_ctx)) < 0) |
614 |
|
|
goto end; |
615 |
|
|
} |
616 |
|
|
|
617 |
|
4 |
end: |
618 |
|
|
/* clear open_in/outputs only if not passed as parameters */ |
619 |
✓✗ |
31 |
if (open_inputs_ptr) *open_inputs_ptr = open_inputs; |
620 |
|
|
else avfilter_inout_free(&open_inputs); |
621 |
✓✗ |
31 |
if (open_outputs_ptr) *open_outputs_ptr = open_outputs; |
622 |
|
|
else avfilter_inout_free(&open_outputs); |
623 |
|
31 |
avfilter_inout_free(&curr_inputs); |
624 |
|
|
|
625 |
✗✓ |
31 |
if (ret < 0) { |
626 |
|
|
while (graph->nb_filters) |
627 |
|
|
avfilter_free(graph->filters[0]); |
628 |
|
|
av_freep(&graph->filters); |
629 |
|
|
} |
630 |
|
31 |
return ret; |
631 |
|
|
} |