diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c
index 30db456b0e..223c7e36c4 100644
--- a/libavformat/concatdec.c
+++ b/libavformat/concatdec.c
@@ -19,6 +19,7 @@
  */
 
 #include "libavutil/avstring.h"
+#include "libavutil/avassert.h"
 #include "libavutil/bprint.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/opt.h"
@@ -407,15 +408,54 @@ static int concat_read_close(AVFormatContext *avf)
     return 0;
 }
 
-static int concat_read_header(AVFormatContext *avf)
+#define MAX_ARGS 2
+#define NEEDS_UNSAFE   (1 << 1)
+#define NEEDS_FILE     (1 << 1)
+#define NEEDS_STREAM   (1 << 2)
+
+typedef struct ParseSyntax {
+    const char *keyword;
+    char args[MAX_ARGS];
+    uint8_t flags;
+} ParseSyntax;
+
+typedef enum ParseDirective {
+   DIR_FFCONCAT,
+   DIR_FILE,
+   DIR_DURATION,
+   DIR_INPOINT,
+   DIR_OUTPOINT,
+   DIR_FPMETAS,
+   DIR_OPTION,
+   DIR_STREAM,
+   DIR_EXSID,
+} ParseDirective;
+
+static const ParseSyntax syntax[] = {
+    [DIR_FFCONCAT ] = { "ffconcat",             "kk", 0 },
+    [DIR_FILE     ] = { "file",                 "s",  0 },
+    [DIR_DURATION ] = { "duration",             "d",  NEEDS_FILE },
+    [DIR_INPOINT  ] = { "inpoint",              "d",  NEEDS_FILE },
+    [DIR_OUTPOINT ] = { "outpoint",             "d",  NEEDS_FILE },
+    [DIR_FPMETAS  ] = { "file_packet_metadata", "s",  NEEDS_FILE },
+    [DIR_OPTION   ] = { "option",               "ks", NEEDS_FILE | NEEDS_UNSAFE },
+    [DIR_STREAM   ] = { "stream",               "",   0 },
+    [DIR_EXSID    ] = { "exact_stream_id",      "i",  NEEDS_STREAM },
+};
+
+static int concat_parse_script(AVFormatContext *avf)
 {
     ConcatContext *cat = avf->priv_data;
+    unsigned nb_files_alloc = 0;
     AVBPrint bp;
     uint8_t *cursor, *keyword;
-    int line = 0, i;
-    unsigned nb_files_alloc = 0;
     ConcatFile *file = NULL;
-    int64_t ret, time = 0;
+    unsigned line = 0, arg;
+    const ParseSyntax *dir;
+    char *arg_kw[MAX_ARGS];
+    char *arg_str[MAX_ARGS] = { 0 };
+    int64_t arg_int[MAX_ARGS];
+    int ret;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -425,100 +465,131 @@ static int concat_read_header(AVFormatContext *avf)
         keyword = get_keyword(&cursor);
         if (!*keyword || *keyword == '#')
             continue;
+        for (dir = syntax; dir < syntax + FF_ARRAY_ELEMS(syntax); dir++)
+            if (!strcmp(dir->keyword, keyword))
+                break;
+        if (dir >= syntax + FF_ARRAY_ELEMS(syntax)) {
+            av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n",
+                   line, keyword);
+            FAIL(AVERROR_INVALIDDATA);
+        }
 
-        if (!strcmp(keyword, "file")) {
-            char *filename = av_get_token((const char **)&cursor, SPACE_CHARS);
-            if (!filename) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: filename required\n", line);
-                FAIL(AVERROR_INVALIDDATA);
+        /* Flags check */
+        if ((dir->flags & NEEDS_UNSAFE) && cat->safe) {
+            av_log(avf, AV_LOG_ERROR, "Line %d: %s not allowed if safe\n", line, keyword);
+            FAIL(AVERROR_INVALIDDATA);
+        }
+        if ((dir->flags & NEEDS_FILE) && !cat->nb_files) {
+            av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n", line, keyword);
+            FAIL(AVERROR_INVALIDDATA);
+        }
+        if ((dir->flags & NEEDS_STREAM) && !avf->nb_streams) {
+            av_log(avf, AV_LOG_ERROR, "Line %d: %s without stream\n", line, keyword);
+            FAIL(AVERROR_INVALIDDATA);
+        }
+
+        /* Arguments parsing */
+        for (arg = 0; arg < FF_ARRAY_ELEMS(dir->args) && dir->args[arg]; arg++) {
+            switch (dir->args[arg]) {
+            case 'd': /* duration */
+                arg_kw[arg] = get_keyword(&cursor);
+                ret = av_parse_time(&arg_int[arg], arg_kw[arg], 1);
+                if (ret < 0) {
+                    av_log(avf, AV_LOG_ERROR, "Line %d: invalid duration '%s'\n",
+                           line, arg_kw[arg]);
+                    goto fail;
+                }
+                break;
+            case 'i': /* integer */
+                arg_int[arg] = strtol(get_keyword(&cursor), NULL, 0);
+                break;
+            case 'k': /* keyword */
+                arg_kw[arg] = get_keyword(&cursor);
+                break;
+            case 's': /* string */
+                av_assert0(!arg_str[arg]);
+                arg_str[arg] = av_get_token((const char **)&cursor, SPACE_CHARS);
+                if (!arg_str[arg])
+                    FAIL(AVERROR(ENOMEM));
+                if (!*arg_str[arg]) {
+                    av_log(avf, AV_LOG_ERROR, "Line %d: string required\n", line);
+                    FAIL(AVERROR_INVALIDDATA);
+                }
+                break;
+            default:
+                FAIL(AVERROR_BUG);
             }
-            if ((ret = add_file(avf, filename, &file, &nb_files_alloc)) < 0)
-                goto fail;
-        } else if (!strcmp(keyword, "duration") || !strcmp(keyword, "inpoint") || !strcmp(keyword, "outpoint")) {
-            char *dur_str = get_keyword(&cursor);
-            int64_t dur;
-            if (!file) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n",
-                       line, keyword);
+        }
+
+        /* Directive action */
+        switch ((ParseDirective)(dir - syntax)) {
+        case DIR_FFCONCAT:
+            if (strcmp(arg_kw[0], "version") || strcmp(arg_kw[1], "1.0")) {
+                av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line);
                 FAIL(AVERROR_INVALIDDATA);
             }
-            if ((ret = av_parse_time(&dur, dur_str, 1)) < 0) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: invalid %s '%s'\n",
-                       line, keyword, dur_str);
+            break;
+        case DIR_FILE:
+            ret = add_file(avf, arg_str[0], &file, &nb_files_alloc);
+            arg_str[0] = NULL;
+            if (ret < 0)
                 goto fail;
-            }
-            if (!strcmp(keyword, "duration"))
-                file->user_duration = dur;
-            else if (!strcmp(keyword, "inpoint"))
-                file->inpoint = dur;
-            else if (!strcmp(keyword, "outpoint"))
-                file->outpoint = dur;
-        } else if (!strcmp(keyword, "file_packet_metadata")) {
-            char *metadata;
-            if (!file) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n",
-                       line, keyword);
-                FAIL(AVERROR_INVALIDDATA);
-            }
-            metadata = av_get_token((const char **)&cursor, SPACE_CHARS);
-            if (!metadata) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: packet metadata required\n", line);
-                FAIL(AVERROR_INVALIDDATA);
-            }
-            if ((ret = av_dict_parse_string(&file->metadata, metadata, "=", "", 0)) < 0) {
+            break;
+        case DIR_DURATION:
+            file->user_duration = arg_int[0];
+            break;
+        case DIR_INPOINT:
+            file->inpoint = arg_int[0];
+            break;
+        case DIR_OUTPOINT:
+            file->outpoint = arg_int[0];
+            break;
+        case DIR_FPMETAS:
+            if ((ret = av_dict_parse_string(&file->metadata, arg_str[0], "=", "", 0)) < 0) {
                 av_log(avf, AV_LOG_ERROR, "Line %d: failed to parse metadata string\n", line);
-                av_freep(&metadata);
                 FAIL(AVERROR_INVALIDDATA);
             }
-            av_freep(&metadata);
-        } else if (!strcmp(keyword, "option")) {
-            char *key, *val;
-            if (cat->safe) {
-                av_log(avf, AV_LOG_ERROR, "Options not permitted in safe mode.\n");
-                FAIL(AVERROR(EPERM));
-            }
-            if (!file) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n",
-                       line, keyword);
-                FAIL(AVERROR_INVALIDDATA);
-            }
-            if (!(key = av_get_token((const char **)&cursor, SPACE_CHARS)) ||
-                !(val = av_get_token((const char **)&cursor, SPACE_CHARS))) {
-                av_freep(&key);
-                FAIL(AVERROR(ENOMEM));
-            }
-            ret = av_dict_set(&file->options, key, val,
-                              AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
+            av_freep(&arg_str[0]);
+            break;
+        case DIR_OPTION:
+            ret = av_dict_set(&file->options, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
             if (ret < 0)
                 FAIL(ret);
-        } else if (!strcmp(keyword, "stream")) {
+            arg_str[1] = NULL;
+            break;
+        case DIR_STREAM:
             if (!avformat_new_stream(avf, NULL))
                 FAIL(AVERROR(ENOMEM));
-        } else if (!strcmp(keyword, "exact_stream_id")) {
-            if (!avf->nb_streams) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: exact_stream_id without stream\n",
-                       line);
-                FAIL(AVERROR_INVALIDDATA);
-            }
-            avf->streams[avf->nb_streams - 1]->id =
-                strtol(get_keyword(&cursor), NULL, 0);
-        } else if (!strcmp(keyword, "ffconcat")) {
-            char *ver_kw  = get_keyword(&cursor);
-            char *ver_val = get_keyword(&cursor);
-            if (strcmp(ver_kw, "version") || strcmp(ver_val, "1.0")) {
-                av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line);
-                FAIL(AVERROR_INVALIDDATA);
-            }
-        } else {
-            av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n",
-                   line, keyword);
-            FAIL(AVERROR_INVALIDDATA);
+            break;
+        case DIR_EXSID:
+            avf->streams[avf->nb_streams - 1]->id = arg_int[0];
+            break;
+        default:
+            FAIL(AVERROR_BUG);
         }
     }
-    if (ret != AVERROR_EOF && ret < 0)
-        goto fail;
-    if (!cat->nb_files)
-        FAIL(AVERROR_INVALIDDATA);
+
+fail:
+    for (arg = 0; arg < MAX_ARGS; arg++)
+        av_freep(&arg_str[arg]);
+    av_bprint_finalize(&bp, NULL);
+    return ret == AVERROR_EOF ? 0 : ret;
+}
+
+static int concat_read_header(AVFormatContext *avf)
+{
+    ConcatContext *cat = avf->priv_data;
+    int64_t time = 0;
+    unsigned i;
+    int ret;
+
+    ret = concat_parse_script(avf);
+    if (ret < 0)
+        return ret;
+    if (!cat->nb_files) {
+        av_log(avf, AV_LOG_ERROR, "No files to concat\n");
+        return AVERROR_INVALIDDATA;
+    }
 
     for (i = 0; i < cat->nb_files; i++) {
         if (cat->files[i].start_time == AV_NOPTS_VALUE)
@@ -541,11 +612,9 @@ static int concat_read_header(AVFormatContext *avf)
     cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID :
                                                MATCH_ONE_TO_ONE;
     if ((ret = open_file(avf, 0)) < 0)
-        goto fail;
+        return ret;
 
-fail:
-    av_bprint_finalize(&bp, NULL);
-    return ret;
+    return 0;
 }
 
 static int open_next_file(AVFormatContext *avf)
