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 "graphparser.h" 27#include "avfilter.h" 28#include "avfiltergraph.h" 29 30static int link_filter(AVFilterContext *src, int srcpad, 31 AVFilterContext *dst, int dstpad, 32 AVClass *log_ctx) 33{ 34 if(avfilter_link(src, srcpad, dst, dstpad)) { 35 av_log(log_ctx, AV_LOG_ERROR, 36 "cannot create the link %s:%d -> %s:%d\n", 37 src->filter->name, srcpad, dst->filter->name, dstpad); 38 return -1; 39 } 40 41 return 0; 42} 43 44static int consume_whitespace(const char *buf) 45{ 46 return strspn(buf, " \n\t"); 47} 48 49/** 50 * Consumes a string from *buf. 51 * @return a copy of the consumed string, which should be free'd after use 52 */ 53static char *consume_string(const char **buf) 54{ 55 char *out = av_malloc(strlen(*buf) + 1); 56 char *ret = out; 57 58 *buf += consume_whitespace(*buf); 59 60 do{ 61 char c = *(*buf)++; 62 switch (c) { 63 case '\\': 64 *out++ = *(*buf)++; 65 break; 66 case '\'': 67 while(**buf && **buf != '\'') 68 *out++ = *(*buf)++; 69 if(**buf) (*buf)++; 70 break; 71 case 0: 72 case ']': 73 case '[': 74 case '=': 75 case ',': 76 case ';': 77 case ' ': 78 case '\n': 79 *out++ = 0; 80 break; 81 default: 82 *out++ = c; 83 } 84 } while(out[-1]); 85 86 (*buf)--; 87 *buf += consume_whitespace(*buf); 88 89 return ret; 90} 91 92/** 93 * Parse "[linkname]" 94 * @param name a pointer (that need to be free'd after use) to the name between 95 * parenthesis 96 */ 97static char *parse_link_name(const char **buf, AVClass *log_ctx) 98{ 99 const char *start = *buf; 100 char *name; 101 (*buf)++; 102 103 name = consume_string(buf); 104 105 if(!name[0]) { 106 av_log(log_ctx, AV_LOG_ERROR, 107 "Bad (empty?) label found in the following: \"%s\".\n", start); 108 goto fail; 109 } 110 111 if(*(*buf)++ != ']') { 112 av_log(log_ctx, AV_LOG_ERROR, 113 "Mismatched '[' found in the following: \"%s\".\n", start); 114 fail: 115 av_freep(&name); 116 } 117 118 return name; 119} 120 121static AVFilterContext *create_filter(AVFilterGraph *ctx, int index, 122 const char *filt_name, const char *args, 123 AVClass *log_ctx) 124{ 125 AVFilterContext *filt_ctx; 126 127 AVFilter *filt; 128 char inst_name[30]; 129 130 snprintf(inst_name, sizeof(inst_name), "Parsed filter %d", index); 131 132 filt = avfilter_get_by_name(filt_name); 133 134 if(!filt) { 135 av_log(log_ctx, AV_LOG_ERROR, 136 "no such filter: '%s'\n", filt_name); 137 return NULL; 138 } 139 140 filt_ctx = avfilter_open(filt, inst_name); 141 if(!filt_ctx) { 142 av_log(log_ctx, AV_LOG_ERROR, 143 "error creating filter '%s'\n", filt_name); 144 return NULL; 145 } 146 147 if(avfilter_graph_add_filter(ctx, filt_ctx) < 0) { 148 avfilter_destroy(filt_ctx); 149 return NULL; 150 } 151 152 if(avfilter_init_filter(filt_ctx, args, NULL)) { 153 av_log(log_ctx, AV_LOG_ERROR, 154 "error initializing filter '%s' with args '%s'\n", filt_name, args); 155 return NULL; 156 } 157 158 return filt_ctx; 159} 160 161/** 162 * Parse "filter=params" 163 */ 164static AVFilterContext *parse_filter(const char **buf, AVFilterGraph *graph, 165 int index, AVClass *log_ctx) 166{ 167 char *opts = NULL; 168 char *name = consume_string(buf); 169 AVFilterContext *ret; 170 171 if(**buf == '=') { 172 (*buf)++; 173 opts = consume_string(buf); 174 } 175 176 ret = create_filter(graph, index, name, opts, log_ctx); 177 av_free(name); 178 av_free(opts); 179 return ret; 180} 181 182static void free_inout(AVFilterInOut *head) 183{ 184 while(head) { 185 AVFilterInOut *next = head->next; 186 av_free(head->name); 187 av_free(head); 188 head = next; 189 } 190} 191 192static AVFilterInOut *extract_inout(const char *label, AVFilterInOut **links) 193{ 194 AVFilterInOut *ret; 195 196 while(*links && strcmp((*links)->name, label)) 197 links = &((*links)->next); 198 199 ret = *links; 200 201 if(ret) 202 *links = ret->next; 203 204 return ret; 205} 206 207static void insert_inout(AVFilterInOut **inouts, AVFilterInOut *element) 208{ 209 element->next = *inouts; 210 *inouts = element; 211} 212 213static int link_filter_inouts(AVFilterContext *filter, 214 AVFilterInOut **curr_inputs, 215 AVFilterInOut **open_inputs, AVClass *log_ctx) 216{ 217 int pad = filter->input_count; 218 219 while(pad--) { 220 AVFilterInOut *p = *curr_inputs; 221 if(!p) { 222 av_log(log_ctx, AV_LOG_ERROR, 223 "Not enough inputs specified for the \"%s\" filter.\n", 224 filter->filter->name); 225 return -1; 226 } 227 228 *curr_inputs = (*curr_inputs)->next; 229 230 if(p->filter) { 231 if(link_filter(p->filter, p->pad_idx, filter, pad, log_ctx)) 232 return -1; 233 av_free(p->name); 234 av_free(p); 235 } else { 236 p->filter = filter; 237 p->pad_idx = pad; 238 insert_inout(open_inputs, p); 239 } 240 } 241 242 if(*curr_inputs) { 243 av_log(log_ctx, AV_LOG_ERROR, 244 "Too many inputs specified for the \"%s\" filter.\n", 245 filter->filter->name); 246 return -1; 247 } 248 249 pad = filter->output_count; 250 while(pad--) { 251 AVFilterInOut *currlinkn = av_mallocz(sizeof(AVFilterInOut)); 252 currlinkn->filter = filter; 253 currlinkn->pad_idx = pad; 254 insert_inout(curr_inputs, currlinkn); 255 } 256 257 return 0; 258} 259 260static int parse_inputs(const char **buf, AVFilterInOut **curr_inputs, 261 AVFilterInOut **open_outputs, AVClass *log_ctx) 262{ 263 int pad = 0; 264 265 while(**buf == '[') { 266 char *name = parse_link_name(buf, log_ctx); 267 AVFilterInOut *match; 268 269 if(!name) 270 return -1; 271 272 /* First check if the label is not in the open_outputs list */ 273 match = extract_inout(name, open_outputs); 274 275 if(match) { 276 av_free(name); 277 } else { 278 /* Not in the list, so add it as an input */ 279 match = av_mallocz(sizeof(AVFilterInOut)); 280 match->name = name; 281 match->pad_idx = pad; 282 } 283 284 insert_inout(curr_inputs, match); 285 286 *buf += consume_whitespace(*buf); 287 pad++; 288 } 289 290 return pad; 291} 292 293static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs, 294 AVFilterInOut **open_inputs, 295 AVFilterInOut **open_outputs, AVClass *log_ctx) 296{ 297 int pad = 0; 298 299 while(**buf == '[') { 300 char *name = parse_link_name(buf, log_ctx); 301 AVFilterInOut *match; 302 303 AVFilterInOut *input = *curr_inputs; 304 *curr_inputs = (*curr_inputs)->next; 305 306 if(!name) 307 return -1; 308 309 /* First check if the label is not in the open_inputs list */ 310 match = extract_inout(name, open_inputs); 311 312 if(match) { 313 if(link_filter(input->filter, input->pad_idx, 314 match->filter, match->pad_idx, log_ctx) < 0) 315 return -1; 316 av_free(match->name); 317 av_free(name); 318 av_free(match); 319 av_free(input); 320 } else { 321 /* Not in the list, so add the first input as a open_output */ 322 input->name = name; 323 insert_inout(open_outputs, input); 324 } 325 *buf += consume_whitespace(*buf); 326 pad++; 327 } 328 329 return pad; 330} 331 332int avfilter_graph_parse(AVFilterGraph *graph, const char *filters, 333 AVFilterInOut *open_inputs, 334 AVFilterInOut *open_outputs, AVClass *log_ctx) 335{ 336 int index = 0; 337 char chr = 0; 338 339 AVFilterInOut *curr_inputs = NULL; 340 341 do { 342 AVFilterContext *filter; 343 filters += consume_whitespace(filters); 344 345 if(parse_inputs(&filters, &curr_inputs, &open_outputs, log_ctx) < 0) 346 goto fail; 347 348 filter = parse_filter(&filters, graph, index, log_ctx); 349 350 if(!filter) 351 goto fail; 352 353 if(filter->input_count == 1 && !curr_inputs && !index) { 354 /* First input can be omitted if it is "[in]" */ 355 const char *tmp = "[in]"; 356 if(parse_inputs(&tmp, &curr_inputs, &open_outputs, log_ctx) < 0) 357 goto fail; 358 } 359 360 if(link_filter_inouts(filter, &curr_inputs, &open_inputs, log_ctx) < 0) 361 goto fail; 362 363 if(parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs, 364 log_ctx) < 0) 365 goto fail; 366 367 filters += consume_whitespace(filters); 368 chr = *filters++; 369 370 if(chr == ';' && curr_inputs) { 371 av_log(log_ctx, AV_LOG_ERROR, 372 "Could not find a output to link when parsing \"%s\"\n", 373 filters - 1); 374 goto fail; 375 } 376 index++; 377 } while(chr == ',' || chr == ';'); 378 379 if (chr) { 380 av_log(log_ctx, AV_LOG_ERROR, 381 "Unable to parse graph description substring: \"%s\"\n", 382 filters - 1); 383 goto fail; 384 } 385 386 if(open_inputs && !strcmp(open_inputs->name, "out") && curr_inputs) { 387 /* Last output can be omitted if it is "[out]" */ 388 const char *tmp = "[out]"; 389 if(parse_outputs(&tmp, &curr_inputs, &open_inputs, 390 &open_outputs, log_ctx) < 0) 391 goto fail; 392 } 393 394 return 0; 395 396 fail: 397 avfilter_graph_destroy(graph); 398 free_inout(open_inputs); 399 free_inout(open_outputs); 400 free_inout(curr_inputs); 401 return -1; 402} 403