graphparser.c 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*
 * filter graph parser
 * copyright (c) 2008 Vitor Sessak
 * copyright (c) 2007 Bobby Bingham
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <ctype.h>
#include <string.h>

#include "avfilter.h"
#include "avfiltergraph.h"

29
static AVFilterContext *create_filter(AVFilterGraph *ctx, int index,
30 31
                                      const char *name, const char *args,
                                      AVClass *log_ctx)
32 33 34 35
{
    AVFilterContext *filt;

    AVFilter *filterdef;
36
    char inst_name[30];
37

38
    snprintf(inst_name, sizeof(inst_name), "Parsed filter %d", index);
Vitor Sessak's avatar
Vitor Sessak committed
39 40

    if(!(filterdef = avfilter_get_by_name(name))) {
41
        av_log(log_ctx, AV_LOG_ERROR,
Vitor Sessak's avatar
Vitor Sessak committed
42
               "no such filter: '%s'\n", name);
43
        return NULL;
Vitor Sessak's avatar
Vitor Sessak committed
44 45
    }

46
    if(!(filt = avfilter_open(filterdef, inst_name))) {
47
        av_log(log_ctx, AV_LOG_ERROR,
48
               "error creating filter '%s'\n", name);
49
        return NULL;
50
    }
Vitor Sessak's avatar
Vitor Sessak committed
51

Vitor Sessak's avatar
Vitor Sessak committed
52
    if(avfilter_graph_add_filter(ctx, filt) < 0)
53
        return NULL;
Vitor Sessak's avatar
Vitor Sessak committed
54

55
    if(avfilter_init_filter(filt, args, NULL)) {
56
        av_log(log_ctx, AV_LOG_ERROR,
Vitor Sessak's avatar
Vitor Sessak committed
57
               "error initializing filter '%s' with args '%s'\n", name, args);
58
        return NULL;
59 60
    }

61
    return filt;
62 63
}

64
static int link_filter(AVFilterContext *src, int srcpad,
65 66
                       AVFilterContext *dst, int dstpad,
                       AVClass *log_ctx)
67
{
68
    if(avfilter_link(src, srcpad, dst, dstpad)) {
69
        av_log(log_ctx, AV_LOG_ERROR,
70 71
               "cannot create the link %s:%d -> %s:%d\n",
               src->filter->name, srcpad, dst->filter->name, dstpad);
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
        return -1;
    }

    return 0;
}

static void consume_whitespace(const char **buf)
{
    *buf += strspn(*buf, " \n\t");
}

/**
 * Consumes a string from *buf.
 * @return a copy of the consumed string, which should be free'd after use
 */
static char *consume_string(const char **buf)
{
89
    char *out = av_malloc(strlen(*buf) + 1);
90
    char *ret = out;
91 92 93

    consume_whitespace(buf);

94
    do{
Vitor Sessak's avatar
Vitor Sessak committed
95
        char c = *(*buf)++;
96 97
        switch (c) {
        case '\\':
Vitor Sessak's avatar
Vitor Sessak committed
98
            *out++= *(*buf)++;
99
            break;
100
        case '\'':
Vitor Sessak's avatar
Vitor Sessak committed
101 102 103
            while(**buf && **buf != '\'')
                *out++= *(*buf)++;
            if(**buf) (*buf)++;
104 105
            break;
        case 0:
106 107
        case ']':
        case '[':
108 109
        case '=':
        case ',':
110
        case ';':
111 112
        case ' ':
        case '\n':
113 114 115 116 117 118
            *out++= 0;
            break;
        default:
            *out++= c;
        }
    } while(out[-1]);
119

Vitor Sessak's avatar
Vitor Sessak committed
120
    (*buf)--;
121 122
    consume_whitespace(buf);

123 124 125 126
    return ret;
}

/**
Vitor Sessak's avatar
Vitor Sessak committed
127
 * Parse "[linkname]"
128 129 130
 * @arg name a pointer (that need to be free'd after use) to the name between
 *           parenthesis
 */
131
static void parse_link_name(const char **buf, char **name, AVClass *log_ctx)
132
{
Vitor Sessak's avatar
Vitor Sessak committed
133
    const char *start = *buf;
134 135 136 137
    (*buf)++;

    *name = consume_string(buf);

Vitor Sessak's avatar
Vitor Sessak committed
138
    if(!*name[0]) {
139
        av_log(log_ctx, AV_LOG_ERROR,
Vitor Sessak's avatar
Vitor Sessak committed
140
               "Bad (empty?) label found in the following: \"%s\".\n", start);
141
        goto fail;
Vitor Sessak's avatar
Vitor Sessak committed
142
    }
143

Vitor Sessak's avatar
Vitor Sessak committed
144
    if(*(*buf)++ != ']') {
145
        av_log(log_ctx, AV_LOG_ERROR,
Vitor Sessak's avatar
Vitor Sessak committed
146
               "Mismatched '[' found in the following: \"%s\".\n", start);
Vitor Sessak's avatar
Vitor Sessak committed
147 148
    fail:
        av_freep(name);
Vitor Sessak's avatar
Vitor Sessak committed
149
    }
150 151 152 153 154 155 156 157 158 159 160 161 162
}


enum LinkType {
    LinkTypeIn,
    LinkTypeOut,
};

/**
 * A linked-list of the inputs/outputs of the filter chain.
 */
typedef struct AVFilterInOut {
    enum LinkType type;
163
    const char *name;
164
    AVFilterContext *filter;
165 166 167 168 169 170 171 172
    int pad_idx;

    struct AVFilterInOut *next;
} AVFilterInOut;

static void free_inout(AVFilterInOut *head)
{
    while (head) {
173
        AVFilterInOut *next = head->next;
174 175 176 177 178
        av_free(head);
        head = next;
    }
}

179 180 181 182 183
static AVFilterInOut *extract_inout(const char *label, AVFilterInOut **links)
{
    AVFilterInOut *ret;


184 185
    while(*links && strcmp((*links)->name, label))
        links= &((*links)->next);
186

187
    ret= *links;
188

189 190
    if(ret)
        *links= ret->next;
191 192 193 194 195 196 197 198 199 200 201 202 203

    return ret;
}


static int link_filter_inouts(AVFilterContext *filter,
                              AVFilterInOut **currInputs,
                              AVFilterInOut **openLinks, AVClass *log_ctx)
{
    AVFilterInOut *p;
    int pad = 0;

    pad = filter->input_count;
204
    while(pad--) {
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
        p = *currInputs;
        if(!p) {
            av_log(log_ctx, AV_LOG_ERROR,
                   "Not enough inputs specified for the \"%s\" filter.\n",
                   filter->name);
            return -1;
        }

        if(p->filter) {
            if(link_filter(p->filter, p->pad_idx, filter, pad, log_ctx))
                return -1;
            *currInputs = (*currInputs)->next;
            av_free(p);
        } else {
            *currInputs = (*currInputs)->next;
            p->filter = filter;
            p->pad_idx = pad;
            p->next = *openLinks;
            *openLinks = p;
        }
    }


    if(*currInputs) {
        av_log(log_ctx, AV_LOG_ERROR,
               "Too many inputs specified for the \"%s\" filter.\n",
               filter->name);
        return -1;
    }

    pad = filter->output_count;
236
    while(pad--) {
237 238 239 240 241 242 243 244 245 246 247 248
        AVFilterInOut *currlinkn = av_malloc(sizeof(AVFilterInOut));
        currlinkn->name    = NULL;
        currlinkn->type    = LinkTypeOut;
        currlinkn->filter  = filter;
        currlinkn->pad_idx = pad;
        currlinkn->next    = *currInputs;
        *currInputs = currlinkn;
    }

    return 0;
}

249
/**
250 251 252 253 254
 * Parse "filter=params"
 * @arg name a pointer (that need to be free'd after use) to the name of the
 *           filter
 * @arg ars  a pointer (that need to be free'd after use) to the args of the
 *           filter
255
 */
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
static AVFilterContext *parse_filter(const char **buf, AVFilterGraph *graph,
                                     int index, AVClass *log_ctx)
{
    char *opts;
    char *name = consume_string(buf);

    if(**buf == '=') {
        (*buf)++;
        opts = consume_string(buf);
    } else {
        opts = NULL;
    }

    return create_filter(graph, index, name, opts, log_ctx);
}

static int parse_inputs(const char **buf, AVFilterInOut **currInputs,
                        AVFilterInOut **openLinks, AVClass *log_ctx)
274
{
275 276 277
    int pad = 0;
    AVFilterInOut *p;

278
    while (**buf == '[') {
279
        char *name;
Vitor Sessak's avatar
Vitor Sessak committed
280

281
        parse_link_name(buf, &name, log_ctx);
282

Vitor Sessak's avatar
Vitor Sessak committed
283
        if(!name)
Vitor Sessak's avatar
Vitor Sessak committed
284 285
            return -1;

286 287
        /* First check if the label is not in the openLinks list */
        p = extract_inout(name, openLinks);
288

289
        /* Not in the list, so add it as an input */
290
        if(!p) {
291 292 293 294 295 296 297 298
            AVFilterInOut *currlinkn = av_malloc(sizeof(AVFilterInOut));

            currlinkn->name    = name;
            currlinkn->type    = LinkTypeIn;
            currlinkn->filter  = NULL;
            currlinkn->pad_idx = pad;
            currlinkn->next    = *currInputs;
            *currInputs = currlinkn;
299
        } else {
300 301 302 303
            /* A label of a open link. Make it one of the inputs of the next
               filter */
            AVFilterInOut *currlinkn = p;
            if (p->type != LinkTypeOut) {
304
                av_log(log_ctx, AV_LOG_ERROR,
305
                       "Label \"%s\" appears twice as input!\n", p->name);
306 307
                return -1;
            }
308 309
            currlinkn->next = *currInputs;
            *currInputs = currlinkn;
310
        }
311
        consume_whitespace(buf);
312
        pad++;
313
    }
314

315 316 317
    return pad;
}

318 319
static int parse_outputs(const char **buf, AVFilterInOut **currInputs,
                         AVFilterInOut **openLinks, AVClass *log_ctx)
320
{
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
    int pad = 0;

    while (**buf == '[') {
        char *name;
        AVFilterInOut *match;

        parse_link_name(buf, &name, log_ctx);

        if(!name)
            return -1;

        /* First check if the label is not in the openLinks list */
        match = extract_inout(name, openLinks);

        /* Not in the list, so add the first input as a openLink */
        if(!match) {
            AVFilterInOut *p = *currInputs;
            *currInputs = (*currInputs)->next;
            p->next = *openLinks;
            p->type = LinkTypeOut;
            p->name = name;
            *openLinks = p;
        } else {
            /* A label of a open link. Link it. */
            AVFilterInOut *p = *currInputs;
            if (match->type != LinkTypeIn) {
                av_log(log_ctx, AV_LOG_ERROR,
                       "Label \"%s\" appears twice as output!\n", match->name);
                return -1;
            }

            *currInputs = (*currInputs)->next;
            if(link_filter(p->filter, p->pad_idx,
                           match->filter, match->pad_idx, log_ctx) < 0)
                return -1;
            av_free(match);
            av_free(p);
        }
        consume_whitespace(buf);
        pad++;
361
    }
362

363 364
    return pad;
}
365

366 367 368
/**
 * Parse a string describing a filter graph.
 */
369 370
int avfilter_parse_graph(AVFilterGraph *graph, const char *filters,
                         AVFilterContext *in, int inpad,
371 372
                         AVFilterContext *out, int outpad,
                         AVClass *log_ctx)
373 374 375 376 377
{
    int index = 0;
    char chr = 0;
    int pad = 0;

378 379 380 381 382 383 384 385 386 387 388 389 390 391
    AVFilterInOut *currInputs=NULL;
    AVFilterInOut *openLinks  = av_malloc(sizeof(AVFilterInOut));

    openLinks->name = "in";
    openLinks->filter = in;
    openLinks->type = LinkTypeOut;
    openLinks->pad_idx = inpad;
    openLinks->next = av_malloc(sizeof(AVFilterInOut));

    openLinks->next->name = "out";
    openLinks->next->filter = out;
    openLinks->next->type = LinkTypeIn;
    openLinks->next->pad_idx = outpad;
    openLinks->next->next = NULL;
392 393

    do {
394
        AVFilterContext *filter;
395
        consume_whitespace(&filters);
396

397
        pad = parse_inputs(&filters, &currInputs, &openLinks, log_ctx);
398

399
        if(pad < 0)
400 401
            goto fail;

402
        if(!(filter = parse_filter(&filters, graph, index, log_ctx)))
Vitor Sessak's avatar
Vitor Sessak committed
403 404
            goto fail;

405 406 407 408 409
        if(filter->input_count == 1 && !currInputs && !index) {
            // First input can be ommitted if it is "[in]"
            const char *tmp = "[in]";
            pad = parse_inputs(&tmp, &currInputs, &openLinks, log_ctx);
            if (pad < 0)
410 411 412
                goto fail;
        }

413 414
        if(link_filter_inouts(filter, &currInputs, &openLinks, log_ctx) < 0)
            goto fail;
Vitor Sessak's avatar
Vitor Sessak committed
415

416
        pad = parse_outputs(&filters, &currInputs, &openLinks, log_ctx);
417

418
        if(pad < 0)
Vitor Sessak's avatar
Vitor Sessak committed
419 420
            goto fail;

421
        consume_whitespace(&filters);
422 423
        chr = *filters++;

424 425 426 427 428
        if (chr == ';' && currInputs) {
            av_log(log_ctx, AV_LOG_ERROR,
                   "Could not find a output to link when parsing \"%s\"\n",
                   filters - 1);
            goto fail;
429
        }
430 431
        index++;
    } while (chr == ',' || chr == ';');
432

433 434 435 436
    if(openLinks && !strcmp(openLinks->name, "out") && currInputs) {
        // Last output can be ommitted if it is "[out]"
        const char *tmp = "[out]";
        if(parse_outputs(&tmp, &currInputs, &openLinks, log_ctx) < 0)
437 438 439 440 441 442 443
            goto fail;
    }

    return 0;

 fail:
    avfilter_destroy_graph(graph);
444 445
    free_inout(openLinks);
    free_inout(currInputs);
446 447
    return -1;
}