Commit | Line | Data |
---|---|---|
27afb09d VS |
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 <ctype.h> | |
24 | #include <string.h> | |
25 | ||
26 | #include "avfilter.h" | |
27 | #include "avfiltergraph.h" | |
28 | ||
29 | /** | |
30 | * For use in av_log | |
31 | */ | |
32 | static const char *log_name(void *p) | |
33 | { | |
34 | return "Filter parser"; | |
35 | } | |
36 | ||
37 | static const AVClass filter_parser_class = { | |
38 | "Filter parser", | |
39 | log_name | |
40 | }; | |
41 | ||
42 | static const AVClass *log_ctx = &filter_parser_class; | |
43 | ||
9710beaf | 44 | static AVFilterContext *create_filter(AVFilterGraph *ctx, int index, |
03cea461 | 45 | const char *name, const char *args) |
27afb09d VS |
46 | { |
47 | AVFilterContext *filt; | |
48 | ||
49 | AVFilter *filterdef; | |
58a2d7a5 | 50 | char inst_name[30]; |
27afb09d | 51 | |
58a2d7a5 | 52 | snprintf(inst_name, sizeof(inst_name), "Parsed filter %d", index); |
27a669ab VS |
53 | |
54 | if(!(filterdef = avfilter_get_by_name(name))) { | |
55 | av_log(&log_ctx, AV_LOG_ERROR, | |
56 | "no such filter: '%s'\n", name); | |
9710beaf | 57 | return NULL; |
27a669ab VS |
58 | } |
59 | ||
58a2d7a5 | 60 | if(!(filt = avfilter_open(filterdef, inst_name))) { |
27afb09d VS |
61 | av_log(&log_ctx, AV_LOG_ERROR, |
62 | "error creating filter '%s'\n", name); | |
9710beaf | 63 | return NULL; |
27afb09d | 64 | } |
046f49ba | 65 | |
2839ff5e | 66 | if(avfilter_graph_add_filter(ctx, filt) < 0) |
9710beaf | 67 | return NULL; |
046f49ba | 68 | |
27afb09d VS |
69 | if(avfilter_init_filter(filt, args, NULL)) { |
70 | av_log(&log_ctx, AV_LOG_ERROR, | |
27a669ab | 71 | "error initializing filter '%s' with args '%s'\n", name, args); |
9710beaf | 72 | return NULL; |
27afb09d VS |
73 | } |
74 | ||
9710beaf | 75 | return filt; |
27afb09d VS |
76 | } |
77 | ||
9710beaf VS |
78 | static int link_filter(AVFilterContext *src, int srcpad, |
79 | AVFilterContext *dst, int dstpad) | |
27afb09d | 80 | { |
9710beaf VS |
81 | if(avfilter_link(src, srcpad, dst, dstpad)) { |
82 | av_log(&log_ctx, AV_LOG_ERROR, | |
83 | "cannot create the link %s:%d -> %s:%d\n", | |
84 | src->filter->name, srcpad, dst->filter->name, dstpad); | |
27afb09d VS |
85 | return -1; |
86 | } | |
87 | ||
88 | return 0; | |
89 | } | |
90 | ||
91 | static void consume_whitespace(const char **buf) | |
92 | { | |
93 | *buf += strspn(*buf, " \n\t"); | |
94 | } | |
95 | ||
96 | /** | |
27afb09d VS |
97 | * Consumes a string from *buf. |
98 | * @return a copy of the consumed string, which should be free'd after use | |
99 | */ | |
100 | static char *consume_string(const char **buf) | |
101 | { | |
2255026d | 102 | char *out = av_malloc(strlen(*buf) + 1); |
93b7a0f6 | 103 | char *ret = out; |
27afb09d VS |
104 | |
105 | consume_whitespace(buf); | |
106 | ||
93b7a0f6 | 107 | do{ |
a64821f4 | 108 | char c = *(*buf)++; |
93b7a0f6 VS |
109 | switch (c) { |
110 | case '\\': | |
a64821f4 | 111 | *out++= *(*buf)++; |
27afb09d | 112 | break; |
93b7a0f6 | 113 | case '\'': |
a64821f4 VS |
114 | while(**buf && **buf != '\'') |
115 | *out++= *(*buf)++; | |
116 | if(**buf) (*buf)++; | |
93b7a0f6 VS |
117 | break; |
118 | case 0: | |
d7ff2297 VS |
119 | case ']': |
120 | case '[': | |
93b7a0f6 VS |
121 | case '=': |
122 | case ',': | |
a78f2ccd VS |
123 | case ' ': |
124 | case '\n': | |
93b7a0f6 VS |
125 | *out++= 0; |
126 | break; | |
127 | default: | |
128 | *out++= c; | |
129 | } | |
130 | } while(out[-1]); | |
27afb09d | 131 | |
a64821f4 | 132 | (*buf)--; |
a78f2ccd VS |
133 | consume_whitespace(buf); |
134 | ||
27afb09d VS |
135 | return ret; |
136 | } | |
137 | ||
138 | /** | |
ffac8784 | 139 | * Parse "[linkname]" |
27afb09d VS |
140 | * @arg name a pointer (that need to be free'd after use) to the name between |
141 | * parenthesis | |
142 | */ | |
143 | static void parse_link_name(const char **buf, char **name) | |
144 | { | |
22260824 | 145 | const char *start = *buf; |
27afb09d VS |
146 | (*buf)++; |
147 | ||
148 | *name = consume_string(buf); | |
149 | ||
22260824 VS |
150 | if(!*name[0]) { |
151 | av_log(&log_ctx, AV_LOG_ERROR, | |
152 | "Bad (empty?) label found in the following: \"%s\".\n", start); | |
27afb09d | 153 | goto fail; |
22260824 | 154 | } |
27afb09d | 155 | |
22260824 VS |
156 | if(*(*buf)++ != ']') { |
157 | av_log(&log_ctx, AV_LOG_ERROR, | |
158 | "Mismatched '[' found in the following: \"%s\".\n", start); | |
27afb09d | 159 | goto fail; |
22260824 | 160 | } |
27afb09d VS |
161 | |
162 | return; | |
22260824 | 163 | |
27afb09d VS |
164 | fail: |
165 | av_freep(name); | |
27afb09d VS |
166 | } |
167 | ||
168 | /** | |
169 | * Parse "filter=params" | |
170 | * @arg name a pointer (that need to be free'd after use) to the name of the | |
171 | * filter | |
172 | * @arg ars a pointer (that need to be free'd after use) to the args of the | |
173 | * filter | |
174 | */ | |
9710beaf | 175 | static AVFilterContext *parse_filter(const char **buf, AVFilterGraph *graph, int index) |
27afb09d VS |
176 | { |
177 | char *name, *opts; | |
178 | name = consume_string(buf); | |
179 | ||
2839ff5e | 180 | if(**buf == '=') { |
27afb09d VS |
181 | (*buf)++; |
182 | opts = consume_string(buf); | |
183 | } else { | |
184 | opts = NULL; | |
185 | } | |
186 | ||
187 | return create_filter(graph, index, name, opts); | |
188 | } | |
189 | ||
190 | enum LinkType { | |
191 | LinkTypeIn, | |
192 | LinkTypeOut, | |
193 | }; | |
194 | ||
195 | /** | |
196 | * A linked-list of the inputs/outputs of the filter chain. | |
197 | */ | |
198 | typedef struct AVFilterInOut { | |
199 | enum LinkType type; | |
200 | char *name; | |
37161d64 | 201 | AVFilterContext *filter; |
27afb09d VS |
202 | int pad_idx; |
203 | ||
204 | struct AVFilterInOut *next; | |
205 | } AVFilterInOut; | |
206 | ||
207 | static void free_inout(AVFilterInOut *head) | |
208 | { | |
209 | while (head) { | |
55672c83 | 210 | AVFilterInOut *next = head->next; |
27afb09d VS |
211 | av_free(head); |
212 | head = next; | |
213 | } | |
214 | } | |
215 | ||
216 | /** | |
ffac8784 | 217 | * Parse "[a1][link2] ... [etc]" |
27afb09d | 218 | */ |
325cb1ef | 219 | static int parse_inouts(const char **buf, AVFilterInOut **inout, int pad, |
37161d64 | 220 | enum LinkType type, AVFilterContext *filter) |
27afb09d | 221 | { |
d7ff2297 | 222 | while (**buf == '[') { |
27afb09d VS |
223 | AVFilterInOut *inoutn = av_malloc(sizeof(AVFilterInOut)); |
224 | parse_link_name(buf, &inoutn->name); | |
22260824 VS |
225 | |
226 | if (!inoutn->name) { | |
227 | av_free(inoutn); | |
228 | return -1; | |
229 | } | |
230 | ||
89475efd VS |
231 | inoutn->type = type; |
232 | inoutn->filter = filter; | |
27afb09d | 233 | inoutn->pad_idx = pad++; |
89475efd | 234 | inoutn->next = *inout; |
27afb09d | 235 | *inout = inoutn; |
8095a014 | 236 | consume_whitespace(buf); |
27afb09d VS |
237 | } |
238 | return pad; | |
239 | } | |
240 | ||
9710beaf VS |
241 | static const char *skip_inouts(const char *buf) |
242 | { | |
8095a014 | 243 | while (*buf == '[') { |
a3acd1d9 | 244 | buf += strcspn(buf, "]") + 1; |
8095a014 VS |
245 | consume_whitespace(&buf); |
246 | } | |
9710beaf VS |
247 | return buf; |
248 | } | |
249 | ||
250 | ||
27afb09d VS |
251 | /** |
252 | * Parse a string describing a filter graph. | |
253 | */ | |
92973a04 VS |
254 | int avfilter_parse_graph(AVFilterGraph *graph, const char *filters, |
255 | AVFilterContext *in, int inpad, | |
256 | AVFilterContext *out, int outpad) | |
27afb09d VS |
257 | { |
258 | AVFilterInOut *inout=NULL; | |
259 | AVFilterInOut *head=NULL; | |
260 | ||
261 | int index = 0; | |
262 | char chr = 0; | |
263 | int pad = 0; | |
264 | int has_out = 0; | |
265 | ||
9710beaf | 266 | AVFilterContext *last_filt = NULL; |
27afb09d | 267 | |
27afb09d | 268 | do { |
9710beaf | 269 | AVFilterContext *filter; |
27afb09d | 270 | int oldpad = pad; |
7a57c8d9 VS |
271 | const char *inouts; |
272 | ||
273 | consume_whitespace(&filters); | |
274 | inouts = filters; | |
27afb09d | 275 | |
9710beaf VS |
276 | // We need to parse the inputs of the filter after we create it, so |
277 | // skip it by now | |
278 | filters = skip_inouts(filters); | |
27afb09d | 279 | |
2839ff5e | 280 | if(!(filter = parse_filter(&filters, graph, index))) |
27afb09d VS |
281 | goto fail; |
282 | ||
9710beaf VS |
283 | pad = parse_inouts(&inouts, &inout, chr == ',', LinkTypeIn, filter); |
284 | ||
22260824 VS |
285 | if(pad < 0) |
286 | goto fail; | |
287 | ||
27afb09d VS |
288 | // If the first filter has an input and none was given, it is |
289 | // implicitly the input of the whole graph. | |
2839ff5e | 290 | if(pad == 0 && filter->input_count == 1) { |
ee75692a | 291 | if(link_filter(in, inpad, filter, 0)) |
27afb09d | 292 | goto fail; |
27afb09d VS |
293 | } |
294 | ||
295 | if(chr == ',') { | |
2839ff5e | 296 | if(link_filter(last_filt, oldpad, filter, 0) < 0) |
27afb09d | 297 | goto fail; |
27afb09d | 298 | } |
da790674 | 299 | |
9710beaf | 300 | pad = parse_inouts(&filters, &inout, 0, LinkTypeOut, filter); |
7a57c8d9 VS |
301 | |
302 | consume_whitespace(&filters); | |
303 | ||
27afb09d VS |
304 | chr = *filters++; |
305 | index++; | |
9710beaf | 306 | last_filt = filter; |
27afb09d VS |
307 | } while (chr == ',' || chr == ';'); |
308 | ||
309 | head = inout; | |
a59a7734 VS |
310 | for (; inout; inout = inout->next) { |
311 | if(!inout->filter) | |
27afb09d VS |
312 | continue; // Already processed |
313 | ||
2839ff5e | 314 | if(!strcmp(inout->name, "in")) { |
37161d64 | 315 | if(link_filter(in, inpad, inout->filter, inout->pad_idx)) |
27afb09d | 316 | goto fail; |
ee75692a | 317 | |
2839ff5e | 318 | } else if(!strcmp(inout->name, "out")) { |
27afb09d | 319 | has_out = 1; |
27afb09d | 320 | |
37161d64 | 321 | if(link_filter(inout->filter, inout->pad_idx, out, outpad)) |
27afb09d | 322 | goto fail; |
27afb09d VS |
323 | |
324 | } else { | |
325 | AVFilterInOut *p, *src, *dst; | |
326 | for (p = inout->next; | |
327 | p && strcmp(p->name,inout->name); p = p->next); | |
328 | ||
2839ff5e | 329 | if(!p) { |
27afb09d VS |
330 | av_log(&log_ctx, AV_LOG_ERROR, "Unmatched link: %s.\n", |
331 | inout->name); | |
332 | goto fail; | |
333 | } | |
334 | ||
2839ff5e | 335 | if(p->type == LinkTypeIn && inout->type == LinkTypeOut) { |
27afb09d VS |
336 | src = inout; |
337 | dst = p; | |
2839ff5e | 338 | } else if(p->type == LinkTypeOut && inout->type == LinkTypeIn) { |
27afb09d VS |
339 | src = p; |
340 | dst = inout; | |
341 | } else { | |
342 | av_log(&log_ctx, AV_LOG_ERROR, "Two links named '%s' are either both input or both output\n", | |
343 | inout->name); | |
344 | goto fail; | |
345 | } | |
346 | ||
2839ff5e | 347 | if(link_filter(src->filter, src->pad_idx, dst->filter, dst->pad_idx) < 0) |
27afb09d VS |
348 | goto fail; |
349 | ||
37161d64 VS |
350 | src->filter = NULL; |
351 | dst->filter = NULL; | |
27afb09d VS |
352 | } |
353 | } | |
354 | ||
355 | free_inout(head); | |
356 | ||
2839ff5e | 357 | if(!has_out) { |
ee75692a | 358 | if(link_filter(last_filt, pad, out, outpad)) |
27afb09d | 359 | goto fail; |
27afb09d VS |
360 | } |
361 | ||
362 | return 0; | |
363 | ||
364 | fail: | |
365 | free_inout(head); | |
366 | avfilter_destroy_graph(graph); | |
367 | return -1; | |
368 | } |