diff options
Diffstat (limited to 'demux/demux_edl.c')
-rw-r--r-- | demux/demux_edl.c | 271 |
1 files changed, 221 insertions, 50 deletions
diff --git a/demux/demux_edl.c b/demux/demux_edl.c index 9d276732fe..356b7eefb5 100644 --- a/demux/demux_edl.c +++ b/demux/demux_edl.c @@ -21,6 +21,7 @@ #include <stdbool.h> #include <string.h> #include <inttypes.h> +#include <limits.h> #include <math.h> #include "mpv_talloc.h" @@ -31,6 +32,7 @@ #include "options/path.h" #include "misc/bstr.h" #include "common/common.h" +#include "common/tags.h" #include "stream/stream.h" #define HEADER "# mpv EDL v0\n" @@ -47,8 +49,10 @@ struct tl_part { struct tl_parts { bool disable_chapters; - bool dash, no_clip; + bool dash, no_clip, delay_open; char *init_fragment_url; + struct sh_stream **sh_meta; + int num_sh_meta; struct tl_part *parts; int num_parts; struct tl_parts *next; @@ -57,22 +61,88 @@ struct tl_parts { struct tl_root { struct tl_parts **pars; int num_pars; + struct mp_tags *tags; }; struct priv { bstr data; }; -// Parse a time (absolute file time or duration). Currently equivalent to a -// number. Return false on failure. -static bool parse_time(bstr str, double *out_time) +// Static allocation out of laziness. +#define NUM_MAX_PARAMS 20 + +struct parse_ctx { + struct mp_log *log; + bool error; + bstr param_vals[NUM_MAX_PARAMS]; + bstr param_names[NUM_MAX_PARAMS]; + int num_params; +}; + +// This returns a value with bstr.start==NULL if nothing found. If the parameter +// was specified, bstr.str!=NULL, even if the string is empty (bstr.len==0). +// The parameter is removed from the list if found. +static bstr get_param(struct parse_ctx *ctx, const char *name) +{ + bstr bname = bstr0(name); + for (int n = 0; n < ctx->num_params; n++) { + if (bstr_equals(ctx->param_names[n], bname)) { + bstr res = ctx->param_vals[n]; + int count = ctx->num_params; + MP_TARRAY_REMOVE_AT(ctx->param_names, count, n); + count = ctx->num_params; + MP_TARRAY_REMOVE_AT(ctx->param_vals, count, n); + ctx->num_params -= 1; + if (!res.start) + res = bstr0(""); // keep guarantees + return res; + } + } + return (bstr){0}; +} + +// Same as get_param(), but return C string. Return NULL if missing. +static char *get_param0(struct parse_ctx *ctx, void *ta_ctx, const char *name) +{ + return bstrdup0(ta_ctx, get_param(ctx, name)); +} + +// Optional int parameter. Returns the parsed integer, or def if the parameter +// is missing or on error (sets ctx.error on error). +static int get_param_int(struct parse_ctx *ctx, const char *name, int def) { - bstr rest; - double time = bstrtod(str, &rest); - if (!str.len || rest.len || !isfinite(time)) - return false; - *out_time = time; - return true; + bstr val = get_param(ctx, name); + if (val.start) { + bstr rest; + long long ival = bstrtoll(val, &rest, 0); + if (!val.len || rest.len || ival < INT_MIN || ival > INT_MAX) { + MP_ERR(ctx, "Invalid integer: '%.*s'\n", BSTR_P(val)); + ctx->error = true; + return def; + } + return ival; + } + return def; +} + +// Optional time parameter. Currently a number. +// Returns true: parameter was present and valid, *t is set +// Returns false: parameter was not present (or broken => ctx.error set) +static bool get_param_time(struct parse_ctx *ctx, const char *name, double *t) +{ + bstr val = get_param(ctx, name); + if (val.start) { + bstr rest; + double time = bstrtod(val, &rest); + if (!val.len || rest.len || !isfinite(time)) { + MP_ERR(ctx, "Invalid time string: '%.*s'\n", BSTR_P(val)); + ctx->error = true; + return false; + } + *t = time; + return true; + } + return false; } static struct tl_parts *add_part(struct tl_root *root) @@ -82,15 +152,28 @@ static struct tl_parts *add_part(struct tl_root *root) return tl; } +static struct sh_stream *get_meta(struct tl_parts *tl, int index) +{ + for (int n = 0; n < tl->num_sh_meta; n++) { + if (tl->sh_meta[n]->index == index) + return tl->sh_meta[n]; + } + struct sh_stream *sh = demux_alloc_sh_stream(STREAM_TYPE_COUNT); + talloc_steal(tl, sh); + MP_TARRAY_APPEND(tl, tl->sh_meta, tl->num_sh_meta, sh); + return sh; +} + /* Returns a list of parts, or NULL on parse error. * Syntax (without file header or URI prefix): * url ::= <entry> ( (';' | '\n') <entry> )* * entry ::= <param> ( <param> ',' )* * param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>) */ -static struct tl_root *parse_edl(bstr str) +static struct tl_root *parse_edl(bstr str, struct mp_log *log) { struct tl_root *root = talloc_zero(NULL, struct tl_root); + root->tags = talloc_zero(root, struct mp_tags); struct tl_parts *tl = add_part(root); while (str.len) { if (bstr_eatstart0(&str, "#")) { @@ -100,9 +183,7 @@ static struct tl_root *parse_edl(bstr str) if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";")) continue; bool is_header = bstr_eatstart0(&str, "!"); - bstr f_type = {0}; - bstr f_init = {0}; - struct tl_part p = { .length = -1 }; + struct parse_ctx ctx = { .log = log }; int nparam = 0; while (1) { bstr name, val; @@ -129,65 +210,125 @@ static struct tl_root *parse_edl(bstr str) val = bstr_splice(str, 0, next); str = bstr_cut(str, next); } - // Interpret parameters. Explicitly ignore unknown ones. - if (is_header) { - if (bstr_equals0(name, "type")) { - f_type = val; - } else if (bstr_equals0(name, "init")) { - f_init = val; - } + if (ctx.num_params >= NUM_MAX_PARAMS) { + mp_err(log, "Too many parameters, ignoring '%.*s'.\n", + BSTR_P(name)); } else { - if (bstr_equals0(name, "file")) { - p.filename = bstrto0(tl, val); - } else if (bstr_equals0(name, "start")) { - if (!parse_time(val, &p.offset)) - goto error; - p.offset_set = true; - } else if (bstr_equals0(name, "length")) { - if (!parse_time(val, &p.length)) - goto error; - } else if (bstr_equals0(name, "timestamps")) { - if (bstr_equals0(val, "chapters")) - p.chapter_ts = true; - } else if (bstr_equals0(name, "title")) { - p.title = bstrto0(tl, val); - } else if (bstr_equals0(name, "layout")) { - if (bstr_equals0(val, "this")) - p.is_layout = true; - } + ctx.param_names[ctx.num_params] = name; + ctx.param_vals[ctx.num_params] = val; + ctx.num_params += 1; } nparam++; if (!bstr_eatstart0(&str, ",")) break; } if (is_header) { + bstr f_type = get_param(&ctx, "type"); if (bstr_equals0(f_type, "mp4_dash")) { tl->dash = true; - if (f_init.len) - tl->init_fragment_url = bstrto0(tl, f_init); + tl->init_fragment_url = get_param0(&ctx, tl, "init"); } else if (bstr_equals0(f_type, "no_clip")) { tl->no_clip = true; } else if (bstr_equals0(f_type, "new_stream")) { - tl = add_part(root); + // (Special case: ignore "redundant" headers at the start for + // general symmetry.) + if (root->num_pars > 1 || tl->num_parts) + tl = add_part(root); } else if (bstr_equals0(f_type, "no_chapters")) { tl->disable_chapters = true; + } else if (bstr_equals0(f_type, "track_meta")) { + int index = get_param_int(&ctx, "index", -1); + struct sh_stream *sh = index < 0 && tl->num_sh_meta + ? tl->sh_meta[tl->num_sh_meta - 1] + : get_meta(tl, index); + sh->lang = get_param0(&ctx, sh, "lang"); + sh->title = get_param0(&ctx, sh, "title"); + sh->hls_bitrate = get_param_int(&ctx, "byterate", 0) * 8; + bstr flags = get_param(&ctx, "flags"); + bstr flag; + while (bstr_split_tok(flags, "+", &flag, &flags) || flag.len) { + if (bstr_equals0(flag, "default")) { + sh->default_track = true; + } else if (bstr_equals0(flag, "forced")) { + sh->forced_track = true; + } else { + mp_warn(log, "Unknown flag: '%.*s'\n", BSTR_P(flag)); + } + } + } else if (bstr_equals0(f_type, "delay_open")) { + struct sh_stream *sh = get_meta(tl, tl->num_sh_meta); + bstr mt = get_param(&ctx, "media_type"); + if (bstr_equals0(mt, "video")) { + sh->type = sh->codec->type = STREAM_VIDEO; + } else if (bstr_equals0(mt, "audio")) { + sh->type = sh->codec->type = STREAM_AUDIO; + } else if (bstr_equals0(mt, "sub")) { + sh->type = sh->codec->type = STREAM_SUB; + } else { + mp_err(log, "Invalid or missing !delay_open media type.\n"); + goto error; + } + sh->codec->codec = get_param0(&ctx, sh, "codec"); + if (!sh->codec->codec) + sh->codec->codec = "null"; + sh->codec->disp_w = get_param_int(&ctx, "w", 0); + sh->codec->disp_h = get_param_int(&ctx, "h", 0); + sh->codec->fps = get_param_int(&ctx, "fps", 0); + sh->codec->samplerate = get_param_int(&ctx, "samplerate", 0); + tl->delay_open = true; + } else if (bstr_equals0(f_type, "global_tags")) { + for (int n = 0; n < ctx.num_params; n++) { + mp_tags_set_bstr(root->tags, ctx.param_names[n], + ctx.param_vals[n]); + } + ctx.num_params = 0; } else { + mp_err(log, "Unknown header: '%.*s'\n", BSTR_P(f_type)); goto error; } } else { - if (!p.filename) + struct tl_part p = { .length = -1 }; + p.filename = get_param0(&ctx, tl, "file"); + p.offset_set = get_param_time(&ctx, "start", &p.offset); + get_param_time(&ctx, "length", &p.length); + bstr ts = get_param(&ctx, "timestamps"); + if (bstr_equals0(ts, "chapters")) { + p.chapter_ts = true; + } else if (ts.start && !bstr_equals0(ts, "seconds")) { + mp_warn(log, "Unknown timestamp type: '%.*s'\n", BSTR_P(ts)); + } + p.title = get_param0(&ctx, tl, "title"); + bstr layout = get_param(&ctx, "layout"); + if (layout.start) { + if (bstr_equals0(layout, "this")) { + p.is_layout = true; + } else { + mp_warn(log, "Unknown layout param: '%.*s'\n", BSTR_P(layout)); + } + } + if (!p.filename) { + mp_err(log, "Missing filename in segment.'\n"); goto error; + } MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p); } + if (ctx.error) + goto error; + for (int n = 0; n < ctx.num_params; n++) { + mp_warn(log, "Unknown or duplicate parameter: '%.*s'\n", + BSTR_P(ctx.param_names[n])); + } } - if (!root->num_pars) - goto error; + assert(root->num_pars); for (int n = 0; n < root->num_pars; n++) { - if (root->pars[n]->num_parts < 1) + if (root->pars[n]->num_parts < 1) { + mp_err(log, "EDL specifies no segments.'\n"); goto error; + } } return root; error: + mp_err(log, "EDL parsing failed.\n"); talloc_free(root); return NULL; } @@ -257,6 +398,7 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer) } static struct timeline_par *build_timeline(struct timeline *root, + struct tl_root *edl_root, struct tl_parts *parts) { struct timeline_par *tl = talloc_zero(root, struct timeline_par); @@ -265,6 +407,15 @@ static struct timeline_par *build_timeline(struct timeline *root, tl->track_layout = NULL; tl->dash = parts->dash; tl->no_clip = parts->no_clip; + tl->delay_open = parts->delay_open; + + // There is no copy function for sh_stream, so just steal it. + for (int n = 0; n < parts->num_sh_meta; n++) { + MP_TARRAY_APPEND(tl, tl->sh_meta, tl->num_sh_meta, + talloc_steal(tl, parts->sh_meta[n])); + parts->sh_meta[n] = NULL; + } + parts->num_sh_meta = 0; if (parts->init_fragment_url && parts->init_fragment_url[0]) { MP_VERBOSE(root, "Opening init fragment...\n"); @@ -309,6 +460,15 @@ static struct timeline_par *build_timeline(struct timeline *root, if (!tl->track_layout) tl->track_layout = open_source(root, tl, part->filename); + } else if (tl->delay_open) { + if (n == 0 && !part->offset_set) { + part->offset = starttime; + part->offset_set = true; + } + if (part->chapter_ts || (part->length < 0 && !tl->no_clip)) { + MP_ERR(root, "Invalid specification for delay_open stream.\n"); + goto error; + } } else { MP_VERBOSE(root, "Opening segment %d...\n", n); @@ -372,6 +532,9 @@ static struct timeline_par *build_timeline(struct timeline *root, tl->num_parts++; } + if (tl->no_clip && tl->num_parts > 1) + MP_WARN(root, "Multiple parts with no_clip. Undefined behavior ahead.\n"); + if (!tl->track_layout) { // Use a heuristic to select the "broadest" part as layout. for (int n = 0; n < parts->num_parts; n++) { @@ -384,11 +547,16 @@ static struct timeline_par *build_timeline(struct timeline *root, } } - if (!tl->track_layout) + if (!tl->track_layout && !tl->delay_open) goto error; if (!root->meta) root->meta = tl->track_layout; + // Not very sane, since demuxer fields are supposed to be treated read-only + // from outside, but happens to work in this case, so who cares. + if (root->meta) + mp_tags_merge(root->meta->metadata, edl_root->tags); + assert(tl->num_parts == parts->num_parts); return tl; @@ -404,7 +572,10 @@ static void fix_filenames(struct tl_parts *parts, char *source_path) struct bstr dirname = mp_dirname(source_path); for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; - part->filename = mp_path_join_bstr(parts, dirname, bstr0(part->filename)); + if (!mp_is_url(bstr0(part->filename))) { + part->filename = + mp_path_join_bstr(parts, dirname, bstr0(part->filename)); + } } } @@ -412,7 +583,7 @@ static void build_mpv_edl_timeline(struct timeline *tl) { struct priv *p = tl->demuxer->priv; - struct tl_root *root = parse_edl(p->data); + struct tl_root *root = parse_edl(p->data, tl->log); if (!root) { MP_ERR(tl, "Error in EDL.\n"); return; @@ -425,7 +596,7 @@ static void build_mpv_edl_timeline(struct timeline *tl) for (int n = 0; n < root->num_pars; n++) { struct tl_parts *parts = root->pars[n]; fix_filenames(parts, tl->demuxer->filename); - struct timeline_par *par = build_timeline(tl, parts); + struct timeline_par *par = build_timeline(tl, root, parts); if (!par) break; all_dash &= par->dash; |