// Disable style definitions generated by the libavcodec converter. // We always want the user defined style instead. static void disable_styles(bstr header) { while (header.len) { int n = bstr_find(header, bstr0("\nStyle: ")); if (n < 0) break; header.start[n + 1] = '#'; // turn into a comment header = bstr_cut(header, 2); } }
// Unlike with bstr_split(), tok is a string, and not a set of char. // If tok is in str, return true, and: concat(out_left, tok, out_right) == str // Otherwise, return false, and set out_left==str, out_right=="" bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right) { bstr bsep = bstr0(tok); int pos = bstr_find(str, bsep); if (pos < 0) pos = str.len; *out_left = bstr_splice(str, 0, pos); *out_right = bstr_cut(str, pos + bsep.len); return pos != str.len; }
bool mp_replace_legacy_cmd(void *t, bstr *s) { for (const struct legacy_cmd *entry = legacy_cmds; entry->old; entry++) { bstr old = bstr0(entry->old); if (bstrcasecmp(bstr_splice(*s, 0, old.len), old) == 0) { bstr rest = bstr_cut(*s, old.len); *s = bstr0(talloc_asprintf(t, "%s%.*s", entry->new, BSTR_P(rest))); return true; } }
static char *read_quoted(void *talloc_ctx, struct bstr *data) { *data = bstr_lstrip(*data); if (!eat_char(data, '"')) return NULL; int end = bstrchr(*data, '"'); if (end < 0) return NULL; struct bstr res = bstr_splice(*data, 0, end); *data = bstr_cut(*data, end + 1); return bstrto0(talloc_ctx, res); }
double bstrtod(struct bstr str, struct bstr *rest) { str = bstr_lstrip(str); char buf[101]; int len = FFMIN(str.len, 100); memcpy(buf, str.start, len); buf[len] = 0; char *endptr; double r = strtod(buf, &endptr); if (rest) *rest = bstr_cut(str, endptr - buf); return r; }
long long bstrtoll(struct bstr str, struct bstr *rest, int base) { str = bstr_lstrip(str); char buf[51]; int len = FFMIN(str.len, 50); memcpy(buf, str.start, len); buf[len] = 0; char *endptr; long long r = strtoll(buf, &endptr, base); if (rest) *rest = bstr_cut(str, endptr - buf); return r; }
static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params) { struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data)); line = bstr_lstrip(line); if (line.len == 0) return CUE_EMPTY; for (int n = 0; cue_command_strings[n].command != -1; n++) { struct bstr name = bstr0(cue_command_strings[n].text); if (bstr_startswith(line, name)) { struct bstr rest = bstr_cut(line, name.len); if (rest.len && !strchr(WHITESPACE, rest.start[0])) continue; if (out_params) *out_params = rest; return cue_command_strings[n].command; } } return CUE_ERROR; }
static struct bstr skip_utf8_bom(struct bstr data) { return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data; }
// Returns 0 if a valid option/file is available, <0 on error, 1 on end of args. static int split_opt_silent(struct parse_state *p) { assert(!p->error); if (p->argc < 1) return 1; p->mp_opt = NULL; p->arg = bstr0(p->argv[0]); p->param = bstr0(NULL); p->argc--; p->argv++; if (p->no_more_opts || !bstr_startswith0(p->arg, "-") || p->arg.len == 1) return 0; if (bstrcmp0(p->arg, "--") == 0) { p->no_more_opts = true; return split_opt_silent(p); } bool old_syntax = !bstr_startswith0(p->arg, "--"); if (old_syntax) { p->arg = bstr_cut(p->arg, 1); } else { p->arg = bstr_cut(p->arg, 2); int idx = bstrchr(p->arg, '='); if (idx > 0) { p->param = bstr_cut(p->arg, idx + 1); p->arg = bstr_splice(p->arg, 0, idx); } } p->mp_opt = m_config_get_option(p->config, p->arg); if (!p->mp_opt) { // Automagic "no-" arguments: "--no-bla" turns into "--bla=no". if (!bstr_startswith0(p->arg, "no-")) return -1; struct bstr s = bstr_cut(p->arg, 3); p->mp_opt = m_config_get_option(p->config, s); if (!p->mp_opt || p->mp_opt->type != &m_option_type_flag) return -1; // Avoid allowing "--no-no-bla". if (bstr_startswith(bstr0(p->mp_opt->name), bstr0("no-"))) return -1; // Flag options never have parameters. old_syntax = false; if (p->param.len) return -2; p->arg = s; p->param = bstr0("no"); } if (bstr_endswith0(p->arg, "-clr")) old_syntax = false; if (old_syntax && !(p->mp_opt->type->flags & M_OPT_TYPE_OLD_SYNTAX_NO_PARAM)) { if (p->argc < 1) return -3; p->param = bstr0(p->argv[0]); p->argc--; p->argv++; } return 0; }
int m_config_parse(m_config_t *config, const char *location, bstr data, char *initial_section, int flags) { m_profile_t *profile = m_config_add_profile(config, initial_section); void *tmp = talloc_new(NULL); int line_no = 0; int errors = 0; bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM while (data.len) { talloc_free_children(tmp); bool ok = false; line_no++; char loc[512]; snprintf(loc, sizeof(loc), "%s:%d:", location, line_no); bstr line = bstr_strip_linebreaks(bstr_getline(data, &data)); if (!skip_ws(&line)) continue; // Profile declaration if (bstr_eatstart0(&line, "[")) { bstr profilename; if (!bstr_split_tok(line, "]", &profilename, &line)) { MP_ERR(config, "%s missing closing ]\n", loc); goto error; } if (skip_ws(&line)) { MP_ERR(config, "%s unparseable extra characters: '%.*s'\n", loc, BSTR_P(line)); goto error; } profile = m_config_add_profile(config, bstrto0(tmp, profilename)); continue; } bstr_eatstart0(&line, "--"); bstr option = line; while (line.len && (mp_isalnum(line.start[0]) || line.start[0] == '_' || line.start[0] == '-')) line = bstr_cut(line, 1); option.len = option.len - line.len; skip_ws(&line); bstr value = {0}; if (bstr_eatstart0(&line, "=")) { skip_ws(&line); if (line.len && (line.start[0] == '"' || line.start[0] == '\'')) { // Simple quoting, like "value" char term[2] = {line.start[0], 0}; line = bstr_cut(line, 1); if (!bstr_split_tok(line, term, &value, &line)) { MP_ERR(config, "%s unterminated quote\n", loc); goto error; } } else if (bstr_eatstart0(&line, "%")) { // Quoting with length, like %5%value bstr rest; long long len = bstrtoll(line, &rest, 10); if (rest.len == line.len || !bstr_eatstart0(&rest, "%") || len > rest.len) { MP_ERR(config, "%s fixed-length quoting expected - put " "\"quotes\" around the option value if you did not " "intend to use this, but your option value starts " "with '%%'\n", loc); goto error; } value = bstr_splice(rest, 0, len); line = bstr_cut(rest, len); } else { // No quoting; take everything until the comment or end of line int end = bstrchr(line, '#'); value = bstr_strip(end < 0 ? line : bstr_splice(line, 0, end)); line.len = 0; } } if (skip_ws(&line)) { MP_ERR(config, "%s unparseable extra characters: '%.*s'\n", loc, BSTR_P(line)); goto error; } int res; if (profile) { if (bstr_equals0(option, "profile-desc")) { m_profile_set_desc(profile, value); res = 0; } else { res = m_config_set_profile_option(config, profile, option, value); } } else { res = m_config_set_option_ext(config, option, value, flags); } if (res < 0) { MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n", loc, BSTR_P(option), BSTR_P(value)); goto error; } ok = true; error: if (!ok) errors++; if (errors > 16) { MP_ERR(config, "%s: too many errors, stopping.\n", location); break; } } talloc_free(tmp); return 1; }
/* 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_parts *parse_edl(bstr str) { struct tl_parts *tl = talloc_zero(NULL, struct tl_parts); while (str.len) { if (bstr_eatstart0(&str, "#")) bstr_split_tok(str, "\n", &(bstr){0}, &str); if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";")) continue; struct tl_part p = { .length = -1 }; int nparam = 0; while (1) { bstr name, val; // Check if it's of the form "name=..." int next = bstrcspn(str, "=%,;\n"); if (next > 0 && next < str.len && str.start[next] == '=') { name = bstr_splice(str, 0, next); str = bstr_cut(str, next + 1); } else { const char *names[] = {"file", "start", "length"}; // implied name name = bstr0(nparam < 3 ? names[nparam] : "-"); } if (bstr_eatstart0(&str, "%")) { int len = bstrtoll(str, &str, 0); if (!bstr_startswith0(str, "%") || (len > str.len - 1)) goto error; val = bstr_splice(str, 1, len + 1); str = bstr_cut(str, len + 1); } else { next = bstrcspn(str, ",;\n"); val = bstr_splice(str, 0, next); str = bstr_cut(str, next); } // Interpret parameters. Explicitly ignore unknown ones. 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; } nparam++; if (!bstr_eatstart0(&str, ",")) break; } if (!p.filename) goto error; MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p); } if (!tl->num_parts) goto error; return tl; error: talloc_free(tl); return NULL; } static struct demuxer *open_source(struct timeline *tl, char *filename) { for (int n = 0; n < tl->num_sources; n++) { struct demuxer *d = tl->sources[n]; if (strcmp(d->stream->url, filename) == 0) return d; } struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global); if (d) { MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d); } else { MP_ERR(tl, "EDL: Could not open source file '%s'.\n", filename); } return d; } static double demuxer_chapter_time(struct demuxer *demuxer, int n) { if (n < 0 || n >= demuxer->num_chapters) return -1; return demuxer->chapters[n].pts; } // Append all chapters from src to the chapters array. // Ignore chapters outside of the given time range. static void copy_chapters(struct demux_chapter **chapters, int *num_chapters, struct demuxer *src, double start, double len, double dest_offset) { for (int n = 0; n < src->num_chapters; n++) { double time = demuxer_chapter_time(src, n); if (time >= start && time <= start + len) { struct demux_chapter ch = { .pts = dest_offset + time - start, .metadata = mp_tags_dup(*chapters, src->chapters[n].metadata), }; MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch); } } } // return length of the source in seconds, or -1 if unknown static double source_get_length(struct demuxer *demuxer) { double time; // <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW if (demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, &time) <= 0) time = -1; return time; } static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer) { if (part->chapter_ts) { double start = demuxer_chapter_time(demuxer, part->offset); double length = part->length; double end = length; if (end >= 0) end = demuxer_chapter_time(demuxer, part->offset + part->length); if (end >= 0 && start >= 0) length = end - start; part->offset = start; part->length = length; } if (!part->offset_set) part->offset = demuxer->start_time; } static void build_timeline(struct timeline *tl, struct tl_parts *parts) { tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1); double starttime = 0; for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; struct demuxer *source = open_source(tl, part->filename); if (!source) goto error; resolve_timestamps(part, source); double end_time = source_get_length(source); if (end_time >= 0) end_time += source->start_time; // Unknown length => use rest of the file. If duration is unknown, make // something up. if (part->length < 0) { if (end_time < 0) { MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n", part->filename); end_time = 1; } part->length = end_time - part->offset; } else if (end_time >= 0) { double end_part = part->offset + part->length; if (end_part > end_time) { MP_WARN(tl, "EDL: entry %d uses %f " "seconds, but file has only %f seconds.\n", n, end_part, end_time); } } // Add a chapter between each file. struct demux_chapter ch = { .pts = starttime, .metadata = talloc_zero(tl, struct mp_tags), }; mp_tags_set_str(ch.metadata, "title", part->filename); MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch); // Also copy the source file's chapters for the relevant parts copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset, part->length, starttime); tl->parts[n] = (struct timeline_part) { .start = starttime, .source_start = part->offset, .source = source, }; starttime += part->length; } tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime}; tl->num_parts = parts->num_parts; tl->track_layout = tl->parts[0].source; return; error: tl->num_parts = 0; tl->num_chapters = 0; } // For security, don't allow relative or absolute paths, only plain filenames. // Also, make these filenames relative to the edl source file. 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]; char *filename = mp_basename(part->filename); // plain filename only part->filename = mp_path_join_bstr(parts, dirname, bstr0(filename)); } } static void build_mpv_edl_timeline(struct timeline *tl) { struct priv *p = tl->demuxer->priv; struct tl_parts *parts = parse_edl(p->data); if (!parts) { MP_ERR(tl, "Error in EDL.\n"); return; } MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer); // Source is .edl and not edl:// => don't allow arbitrary paths if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL) fix_filenames(parts, tl->demuxer->filename); build_timeline(tl, parts); talloc_free(parts); } static int try_open_file(struct demuxer *demuxer, enum demux_check check) { struct priv *p = talloc_zero(demuxer, struct priv); demuxer->priv = p; demuxer->fully_read = true; struct stream *s = demuxer->stream; if (s->uncached_type == STREAMTYPE_EDL) { p->data = bstr0(s->path); return 0; } if (check >= DEMUX_CHECK_UNSAFE) { if (!bstr_equals0(stream_peek(s, strlen(HEADER)), HEADER)) return -1; } p->data = stream_read_complete(s, demuxer, 1000000); if (p->data.start == NULL) return -1; bstr_eatstart0(&p->data, HEADER); return 0; } const struct demuxer_desc demuxer_desc_edl = { .name = "edl", .desc = "Edit decision list", .open = try_open_file, .load_timeline = build_mpv_edl_timeline, };
static struct bstr read_quoted(struct bstr *data) { *data = bstr_lstrip(*data); if (!eat_char(data, '"')) return (struct bstr) {0}; int end = bstrchr(*data, '"'); if (end < 0) return (struct bstr) {0}; struct bstr res = bstr_splice(*data, 0, end); *data = bstr_cut(*data, end + 1); return res; } // Read a 2 digit unsigned decimal integer. // Return -1 on failure. static int read_int_2(struct bstr *data) { *data = bstr_lstrip(*data); if (data->len && data->start[0] == '-') return -1; struct bstr s = *data; int res = (int)bstrtoll(s, &s, 10); if (data->len == s.len || data->len - s.len > 2) return -1; *data = s; return res; } static double read_time(struct bstr *data) { struct bstr s = *data; bool ok = true; double t1 = read_int_2(&s); ok = eat_char(&s, ':') && ok; double t2 = read_int_2(&s); ok = eat_char(&s, ':') && ok; double t3 = read_int_2(&s); ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0; return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0; } static struct bstr skip_utf8_bom(struct bstr data) { return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data; } // Check if the text in data is most likely CUE data. This is used by the // demuxer code to check the file type. // data is the start of the probed file, possibly cut off at a random point. bool mp_probe_cue(struct bstr data) { bool valid = false; data = skip_utf8_bom(data); for (;;) { enum cue_command cmd = read_cmd(&data, NULL); // End reached. Since the line was most likely cut off, don't use the // result of the last parsing call. if (data.len == 0) break; if (cmd == CUE_ERROR) return false; if (cmd != CUE_EMPTY) valid = true; } return valid; }