示例#1
0
文件: codecs.c 项目: ArcherSeven/mpv
// Select a decoder from the given list for the given codec. The selection
// can be influenced by the selection string, which can specify a priority
// list of preferred decoders.
// This returns a list of decoders to try, with the preferred decoders first.
// The selection string corresponds to --vd/--ad directly, and has the
// following syntax:
//   selection = [<entry> ("," <entry>)*]
//       entry = <family> ":" <decoder>         // prefer decoder
//       entry = <family> ":*"                  // prefer all decoders
//       entry = "+" <family> ":" <decoder>     // force a decoder
//       entry = "-" <family> ":" <decoder>     // exclude a decoder
//       entry = "-"                            // don't add fallback decoders
// Forcing a decoder means it's added even if the codec mismatches.
struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
                                           const char *codec,
                                           const char *selection)
{
    struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
    struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list);
    if (!codec)
        codec = "unknown";
    bool stop = false;
    bstr sel = bstr0(selection);
    while (sel.len) {
        bstr entry;
        bstr_split_tok(sel, ",", &entry, &sel);
        if (bstr_equals0(entry, "-")) {
            stop = true;
            break;
        }
        bool force = bstr_eatstart0(&entry, "+");
        bool exclude = !force && bstr_eatstart0(&entry, "-");
        struct mp_decoder_list *dest = exclude ? remove : list;
        bstr family, decoder;
        if (!bstr_split_tok(entry, ":", &family, &decoder)) {
            mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Decoders must be specified as "
                   "'family:decoder' for the --ad/--vd options.\n");
            break;
        }
        if (bstr_equals0(decoder, "*")) {
            for (int n = 0; n < all->num_entries; n++) {
                struct mp_decoder_entry *cur = &all->entries[n];
                if (bstr_equals0(family, cur->family))
                    add_new(dest, cur, codec);
            }
        } else {
            add_new(dest, find_decoder(all, family, decoder),
                    force ? NULL : codec);
        }
    }
    if (!stop) {
        // Add the remaining codecs which haven't been added yet
        for (int n = 0; n < all->num_entries; n++)
            add_new(list, &all->entries[n], codec);
    }
    for (int n = 0; n < remove->num_entries; n++) {
        struct mp_decoder_entry *ex = &remove->entries[n];
        struct mp_decoder_entry *del =
            find_decoder(list, bstr0(ex->family), bstr0(ex->decoder));
        if (del) {
            int index = del - &list->entries[0];
            MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index);
        }
    }
    talloc_free(remove);
    return list;
}
示例#2
0
文件: codecs.c 项目: ArcherSeven/mpv
static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
                                             bstr family, bstr decoder)
{
    for (int n = 0; n < list->num_entries; n++) {
        struct mp_decoder_entry *cur = &list->entries[n];
        if (bstr_equals0(decoder, cur->decoder) &&
            bstr_equals0(family, cur->family))
            return cur;
    }
    return NULL;
}
示例#3
0
文件: demux_playlist.c 项目: 2ion/mpv
static int parse_ref_init(struct pl_parser *p)
{
    bstr line = bstr_strip(pl_get_line(p));
    if (!bstr_equals0(line, "[Reference]"))
        return -1;

    // ASF http streaming redirection - this is needed because ffmpeg http://
    // and mmsh:// can not automatically switch automatically between each
    // others. Both protocols use http - MMSH requires special http headers
    // to "activate" it, and will in other cases return this playlist.
    static const char *const mmsh_types[] = {"audio/x-ms-wax",
        "audio/x-ms-wma", "video/x-ms-asf", "video/x-ms-afs", "video/x-ms-wmv",
        "video/x-ms-wma", "application/x-mms-framed",
        "application/vnd.ms.wms-hdr.asfv1", NULL};
    bstr burl = bstr0(p->s->url);
    if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) {
        MP_INFO(p, "Redirecting to mmsh://\n");
        playlist_add_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl)));
        return 0;
    }

    while (!pl_eof(p)) {
        line = bstr_strip(pl_get_line(p));
        if (bstr_case_startswith(line, bstr0("Ref"))) {
            bstr_split_tok(line, "=", &(bstr){0}, &line);
            if (line.len)
                pl_add(p, line);
        }
    }
    return 0;
}
示例#4
0
文件: context.c 项目: chyiz/mpv
int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
                              struct bstr name, struct bstr param)
{
    if (bstr_equals0(param, "help")) {
        mp_info(log, "OpenGL windowing backends:\n");
        mp_info(log, "    auto (autodetect)\n");
        for (int n = 0; n < MP_ARRAY_SIZE(backends); n++)
            mp_info(log, "    %s\n", backends[n]->name);
        return M_OPT_EXIT;
    }
    char s[20];
    snprintf(s, sizeof(s), "%.*s", BSTR_P(param));
    return mpgl_find_backend(s) >= -1 ? 1 : M_OPT_INVALID;
}
示例#5
0
static int parse_ref_init(struct pl_parser *p)
{
    bstr line = bstr_strip(pl_get_line(p));
    if (!bstr_equals0(line, "[Reference]"))
        return -1;
    while (!pl_eof(p)) {
        line = bstr_strip(pl_get_line(p));
        if (bstr_case_startswith(line, bstr0("Ref"))) {
            bstr_split_tok(line, "=", &(bstr){0}, &line);
            if (line.len)
                pl_add(p, line);
        }
    }
    return 0;
}
示例#6
0
static int parse_m3u(struct pl_parser *p)
{
    bstr line = bstr_strip(pl_get_line(p));
    if (!bstr_equals0(line, "#EXTM3U"))
        return -1;
    if (p->probing)
        return 0;
    while (!pl_eof(p)) {
        line = bstr_strip(pl_get_line(p));
        if (line.len == 0 || bstr_startswith0(line, "#"))
            continue;
        pl_add(p, line);
    }
    return 0;
}
示例#7
0
文件: ao_portaudio.c 项目: 0x0all/mpv
static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
                               struct bstr name, struct bstr param)
{
    // Note: we do not check whether the device actually exist, because this
    //       might break elaborate configs with several AOs trying several
    //       devices. We do it merely for making "help" special.
    if (bstr_equals0(param, "help")) {
        if (!check_pa_ret(log, Pa_Initialize()))
            return M_OPT_EXIT;
        find_device(log, "help");
        Pa_Terminate();
        return M_OPT_EXIT - 1;
    }
    return 0;
}
示例#8
0
static int parse_m3u(struct pl_parser *p)
{
    bstr line = bstr_strip(pl_get_line(p));
    if (p->probing && !bstr_equals0(line, "#EXTM3U")) {
        // Last resort: if the file extension is m3u, it might be headerless.
        if (p->check_level == DEMUX_CHECK_UNSAFE) {
            char *ext = mp_splitext(p->real_stream->url, NULL);
            bstr data = stream_peek(p->real_stream, PROBE_SIZE);
            if (ext && data.len > 10 && maybe_text(data)) {
                const char *exts[] = {"m3u", "m3u8", NULL};
                for (int n = 0; exts[n]; n++) {
                    if (strcasecmp(ext, exts[n]) == 0)
                        goto ok;
                }
            }
        }
        return -1;
    }

ok:
    if (p->probing)
        return 0;

    char *title = NULL;
    while (line.len || !pl_eof(p)) {
        if (bstr_eatstart0(&line, "#EXTINF:")) {
            bstr duration, btitle;
            if (bstr_split_tok(line, ",", &duration, &btitle) && btitle.len) {
                talloc_free(title);
                title = bstrto0(NULL, btitle);
            }
        } else if (bstr_startswith0(line, "#EXT-X-")) {
            p->format = "hls";
        } else if (line.len > 0 && !bstr_startswith0(line, "#")) {
            char *fn = bstrto0(NULL, line);
            struct playlist_entry *e = playlist_entry_new(fn);
            talloc_free(fn);
            e->title = talloc_steal(e, title);
            title = NULL;
            playlist_add(p->pl, e);
        }
        line = bstr_strip(pl_get_line(p));
    }
    talloc_free(title);
    return 0;
}
示例#9
0
文件: ao_openal.c 项目: benf/mpv
static int validate_device_opt(const m_option_t *opt, struct bstr name,
                               struct bstr param)
{
    if (bstr_equals0(param, "help")) {
        if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_TRUE) {
            mp_msg(MSGT_AO, MSGL_FATAL, "Device listing not supported.\n");
            return M_OPT_EXIT;
        }
        const char *list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
        mp_msg(MSGT_AO, MSGL_INFO, "OpenAL devices:\n");
        while (list && *list) {
            mp_msg(MSGT_AO, MSGL_INFO, "  '%s'\n", list);
            list = list + strlen(list) + 1;
        }
        return M_OPT_EXIT - 1;
    }
    return 0;
}
示例#10
0
文件: demux_playlist.c 项目: c-14/mpv
static int parse_m3u(struct pl_parser *p)
{
    bstr line = bstr_strip(pl_get_line(p));
    if (p->probing && !bstr_equals0(line, "#EXTM3U")) {
        // Last resort: if the file extension is m3u, it might be headerless.
        if (p->check_level == DEMUX_CHECK_UNSAFE) {
            char *ext = mp_splitext(p->real_stream->url, NULL);
            bstr data = stream_peek(p->real_stream, PROBE_SIZE);
            if (ext && !strcmp(ext, "m3u") && data.len > 10 && maybe_text(data))
                goto ok;
        }
        return -1;
    }
ok:
    if (p->probing)
        return 0;
    while (line.len || !pl_eof(p)) {
        if (line.len > 0 && !bstr_startswith0(line, "#"))
            pl_add(p, line);
        line = bstr_strip(pl_get_line(p));
    }
    return 0;
}
示例#11
0
文件: parser-cfg.c 项目: maletor/mpv
/** \param config The config object.
 *  \param conffile Path to the config file.
 *  \return 1 on sucess, -1 on error, 0 if file not accessible.
 */
int m_config_parse_config_file(m_config_t *config, const char *conffile)
{
#define PRINT_LINENUM   mp_msg(MSGT_CFGPARSER, MSGL_ERR, "%s:%d: ", conffile, line_num)
#define MAX_LINE_LEN    10000
#define MAX_OPT_LEN     1000
#define MAX_PARAM_LEN   1500
    FILE *fp = NULL;
    char *line = NULL;
    char opt[MAX_OPT_LEN + 1];
    char param[MAX_PARAM_LEN + 1];
    char c;             /* for the "" and '' check */
    int tmp;
    int line_num = 0;
    int line_pos;       /* line pos */
    int opt_pos;        /* opt pos */
    int param_pos;      /* param pos */
    int ret = 1;
    int errors = 0;
    m_profile_t *profile = NULL;

    mp_msg(MSGT_CFGPARSER, MSGL_V, "Reading config file %s", conffile);

    if (recursion_depth > MAX_RECURSION_DEPTH) {
        mp_msg(MSGT_CFGPARSER, MSGL_ERR,
               ": too deep 'include'. check your configfiles\n");
        ret = -1;
        goto out;
    }

    if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
        mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
               "\ncan't get memory for 'line': %s", strerror(errno));
        ret = -1;
        goto out;
    } else

        mp_msg(MSGT_CFGPARSER, MSGL_V, "\n");

    if ((fp = fopen(conffile, "r")) == NULL) {
        mp_msg(MSGT_CFGPARSER, MSGL_V, ": %s\n", strerror(errno));
        ret = 0;
        goto out;
    }

    while (fgets(line, MAX_LINE_LEN, fp)) {
        if (errors >= 16) {
            mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "too many errors\n");
            goto out;
        }

        line_num++;
        line_pos = 0;

        /* skip whitespaces */
        while (isspace(line[line_pos]))
            ++line_pos;

        /* EOL / comment */
        if (line[line_pos] == '\0' || line[line_pos] == '#')
            continue;

        /* read option. */
        for (opt_pos = 0; isprint(line[line_pos]) &&
             line[line_pos] != ' ' &&
             line[line_pos] != '#' &&
             line[line_pos] != '='; /* NOTHING */) {
            opt[opt_pos++] = line[line_pos++];
            if (opt_pos >= MAX_OPT_LEN) {
                PRINT_LINENUM;
                mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long option\n");
                errors++;
                ret = -1;
                goto nextline;
            }
        }
        if (opt_pos == 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parse error\n");
            ret = -1;
            errors++;
            continue;
        }
        opt[opt_pos] = '\0';

        /* Profile declaration */
        if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') {
            opt[opt_pos - 1] = '\0';
            if (strcmp(opt + 1, "default"))
                profile = m_config_add_profile(config, opt + 1);
            else
                profile = NULL;
            continue;
        }

        /* skip whitespaces */
        while (isspace(line[line_pos]))
            ++line_pos;

        param_pos = 0;
        bool param_set = false;

        /* check '=' */
        if (line[line_pos] == '=') {
            line_pos++;
            param_set = true;

            /* whitespaces... */
            while (isspace(line[line_pos]))
                ++line_pos;

            /* read the parameter */
            if (line[line_pos] == '"' || line[line_pos] == '\'') {
                c = line[line_pos];
                ++line_pos;
                for (param_pos = 0; line[line_pos] != c; /* NOTHING */) {
                    param[param_pos++] = line[line_pos++];
                    if (param_pos >= MAX_PARAM_LEN) {
                        PRINT_LINENUM;
                        mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                               "option %s has a too long parameter\n", opt);
                        ret = -1;
                        errors++;
                        goto nextline;
                    }
                }
                line_pos++;                 /* skip the closing " or ' */
            } else {
                for (param_pos = 0; isprint(line[line_pos])
                        && !isspace(line[line_pos])
                        && line[line_pos] != '#'; /* NOTHING */) {
                    param[param_pos++] = line[line_pos++];
                    if (param_pos >= MAX_PARAM_LEN) {
                        PRINT_LINENUM;
                        mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long parameter\n");
                        ret = -1;
                        errors++;
                        goto nextline;
                    }
                }
            }

            while (isspace(line[line_pos]))
                ++line_pos;
        }
        param[param_pos] = '\0';

        /* EOL / comment */
        if (line[line_pos] != '\0' && line[line_pos] != '#') {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "extra characters: %s\n", line + line_pos);
            ret = -1;
        }

        bstr bopt = bstr0(opt);
        bstr bparam = bstr0(param);

        if (profile && bstr_equals0(bopt, "profile-desc")) {
            m_profile_set_desc(profile, param);
            goto nextline;
        }

        tmp = m_config_option_requires_param(config, bopt);
        if (tmp > 0 && !param_set)
            tmp = M_OPT_MISSING_PARAM;
        if (tmp < 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "error parsing option %s=%s: %s\n",
                   opt, param, m_option_strerror(tmp));
            continue;
        }

        if (profile) {
            tmp = m_config_set_profile_option(config, profile, bopt, bparam);
        } else {
            tmp = m_config_set_option_ext(config, bopt, bparam,
                                          M_SETOPT_FROM_CONFIG_FILE);
        }
        if (tmp < 0) {
            PRINT_LINENUM;
            mp_msg(MSGT_CFGPARSER, MSGL_ERR,
                   "setting option %s='%s' failed.\n", opt, param);
            continue;
            /* break */
        }
nextline:
        ;
    }

out:
    free(line);
    if (fp)
        fclose(fp);
    --recursion_depth;
    if (ret < 0) {
        mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "Error loading config file %s.\n",
               conffile);
    }
    return ret;
}
示例#12
0
// Load options and profiles from from a config file.
//  conffile: path to the config file
//  initial_section: default section where to add normal options
//  flags: M_SETOPT_* bits
//  returns: 1 on sucess, -1 on error, 0 if file not accessible.
int m_config_parse_config_file(m_config_t *config, const char *conffile,
                               char *initial_section, int flags)
{
#define PRINT_LINENUM   MP_ERR(config, "%s:%d: ", conffile, line_num)
#define MAX_LINE_LEN    10000
#define MAX_OPT_LEN     1000
#define MAX_PARAM_LEN   1500
    FILE *fp = NULL;
    char *line = NULL;
    char opt[MAX_OPT_LEN + 1];
    char param[MAX_PARAM_LEN + 1];
    char c;             /* for the "" and '' check */
    int tmp;
    int line_num = 0;
    int line_pos;       /* line pos */
    int opt_pos;        /* opt pos */
    int param_pos;      /* param pos */
    int ret = 1;
    int errors = 0;
    m_profile_t *profile = m_config_add_profile(config, initial_section);

    flags = flags | M_SETOPT_FROM_CONFIG_FILE;

    MP_VERBOSE(config, "Reading config file %s\n", conffile);

    if (config->recursion_depth > MAX_RECURSION_DEPTH) {
        MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n");
        ret = -1;
        goto out;
    }

    if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
        ret = -1;
        goto out;
    } else

        MP_VERBOSE(config, "\n");

    if ((fp = fopen(conffile, "r")) == NULL) {
        MP_VERBOSE(config, "Can't open config file: %s\n", strerror(errno));
        ret = 0;
        goto out;
    }

    while (fgets(line, MAX_LINE_LEN, fp)) {
        if (errors >= 16) {
            MP_FATAL(config, "too many errors\n");
            goto out;
        }

        line_num++;
        line_pos = 0;

        /* skip BOM */
        if (strncmp(line, "\xEF\xBB\xBF", 3) == 0)
            line_pos += 3;

        /* skip whitespaces */
        while (mp_isspace(line[line_pos]))
            ++line_pos;

        /* EOL / comment */
        if (line[line_pos] == '\0' || line[line_pos] == '#')
            continue;

        /* read option. */
        for (opt_pos = 0; mp_isprint(line[line_pos]) &&
             line[line_pos] != ' ' &&
             line[line_pos] != '#' &&
             line[line_pos] != '='; /* NOTHING */) {
            opt[opt_pos++] = line[line_pos++];
            if (opt_pos >= MAX_OPT_LEN) {
                PRINT_LINENUM;
                MP_ERR(config, "option name too long\n");
                errors++;
                ret = -1;
                goto nextline;
            }
        }
        if (opt_pos == 0) {
            PRINT_LINENUM;
            MP_ERR(config, "parse error\n");
            ret = -1;
            errors++;
            continue;
        }
        opt[opt_pos] = '\0';

        /* Profile declaration */
        if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') {
            opt[opt_pos - 1] = '\0';
            profile = m_config_add_profile(config, opt + 1);
            continue;
        }

        /* skip whitespaces */
        while (mp_isspace(line[line_pos]))
            ++line_pos;

        param_pos = 0;
        bool param_set = false;

        /* check '=' */
        if (line[line_pos] == '=') {
            line_pos++;
            param_set = true;

            /* whitespaces... */
            while (mp_isspace(line[line_pos]))
                ++line_pos;

            /* read the parameter */
            if (line[line_pos] == '"' || line[line_pos] == '\'') {
                c = line[line_pos];
                ++line_pos;
                for (param_pos = 0; line[line_pos] != c; /* NOTHING */) {
                    param[param_pos++] = line[line_pos++];
                    if (param_pos >= MAX_PARAM_LEN) {
                        PRINT_LINENUM;
                        MP_ERR(config, "option %s has a too long parameter\n", opt);
                        ret = -1;
                        errors++;
                        goto nextline;
                    }
                }
                line_pos++;                 /* skip the closing " or ' */
                goto param_done;
            }

            if (line[line_pos] == '%') {
                char *start = &line[line_pos + 1];
                char *end = start;
                unsigned long len = strtoul(start, &end, 10);
                if (start != end && end[0] == '%') {
                    if (len >= MAX_PARAM_LEN - 1 ||
                        strlen(end + 1) < len)
                    {
                        PRINT_LINENUM;
                        MP_ERR(config, "bogus %% length\n");
                        ret = -1;
                        errors++;
                        goto nextline;
                    }
                    param_pos = snprintf(param, sizeof(param), "%.*s",
                                         (int)len, end + 1);
                    line_pos += 1 + (end - start) + 1 + len;
                    goto param_done;
                }
            }

            for (param_pos = 0; mp_isprint(line[line_pos])
                    && !mp_isspace(line[line_pos])
                    && line[line_pos] != '#'; /* NOTHING */) {
                param[param_pos++] = line[line_pos++];
                if (param_pos >= MAX_PARAM_LEN) {
                    PRINT_LINENUM;
                    MP_ERR(config, "too long parameter\n");
                    ret = -1;
                    errors++;
                    goto nextline;
                }
            }

        param_done:

            while (mp_isspace(line[line_pos]))
                ++line_pos;
        }
        param[param_pos] = '\0';

        /* EOL / comment */
        if (line[line_pos] != '\0' && line[line_pos] != '#') {
            PRINT_LINENUM;
            MP_ERR(config, "extra characters: %s\n", line + line_pos);
            ret = -1;
        }

        bstr bopt = bstr0(opt);
        bstr bparam = bstr0(param);

        if (bopt.len >= 3)
            bstr_eatstart0(&bopt, "--");

        if (profile && bstr_equals0(bopt, "profile-desc")) {
            m_profile_set_desc(profile, bparam);
            goto nextline;
        }

        bool need_param = m_config_option_requires_param(config, bopt) > 0;
        if (need_param && !param_set) {
            PRINT_LINENUM;
            MP_ERR(config, "error parsing option %.*s=%.*s: %s\n",
                   BSTR_P(bopt), BSTR_P(bparam),
                   m_option_strerror(M_OPT_MISSING_PARAM));
            continue;
        }

        if (profile) {
            tmp = m_config_set_profile_option(config, profile, bopt, bparam);
        } else {
            tmp = m_config_set_option_ext(config, bopt, bparam, flags);
        }
        if (tmp < 0) {
            PRINT_LINENUM;
            MP_ERR(config, "setting option %.*s='%.*s' failed.\n",
                   BSTR_P(bopt), BSTR_P(bparam));
            continue;
            /* break */
        }
nextline:
        ;
    }

out:
    free(line);
    if (fp)
        fclose(fp);
    config->recursion_depth -= 1;
    if (ret < 0) {
        MP_FATAL(config, "Error loading config file %s.\n",
               conffile);
    }
    return ret;
}
示例#13
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;
}
示例#14
0
文件: demux_edl.c 项目: chyiz/mpv
/* 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,
};
示例#15
0
void mp_write_watch_later_conf(struct MPContext *mpctx)
{
    struct playlist_entry *cur = mpctx->playing;
    char *conffile = NULL;
    if (!cur)
        goto exit;

    struct demuxer *demux = mpctx->demuxer;
    if (demux && (!demux->seekable || demux->partially_seekable)) {
        MP_INFO(mpctx, "Not seekable - not saving state.\n");
        goto exit;
    }

    conffile = mp_get_playback_resume_config_filename(mpctx, cur->filename);
    if (!conffile)
        goto exit;

    mp_mk_config_dir(mpctx->global, mpctx->cached_watch_later_configdir);

    MP_INFO(mpctx, "Saving state.\n");

    FILE *file = fopen(conffile, "wb");
    if (!file)
        goto exit;

    write_filename(mpctx, file, cur->filename);

    double pos = get_current_time(mpctx);
    if (pos != MP_NOPTS_VALUE)
        fprintf(file, "start=%f\n", pos);
    for (int i = 0; backup_properties[i]; i++) {
        const char *pname = backup_properties[i];
        char *val = NULL;
        int r = mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
        if (r == M_PROPERTY_OK) {
            if (strncmp(pname, "options/", 8) == 0)
                pname += 8;
            // Only store it if it's different from the initial value.
            char *prev = mpctx->resume_defaults[i];
            if (!prev || strcmp(prev, val) != 0) {
                if (needs_config_quoting(val)) {
                    // e.g. '%6%STRING'
                    fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
                } else {
                    fprintf(file, "%s=%s\n", pname, val);
                }
            }
        }
        talloc_free(val);
    }
    fclose(file);

    // This allows us to recursively resume directories etc., whose entries are
    // expanded the first time it's "played". For example, if "/a/b/c.mkv" is
    // the current entry, then we want to resume this file if the user does
    // "mpv /a". This would expand to the directory entries in "/a", and if
    // "/a/a.mkv" is not the first entry, this would be played.
    // Here, we write resume entries for "/a" and "/a/b".
    // (Unfortunately, this will leave stray resume files on resume, because
    // obviously it resumes only from one of those paths.)
    for (int n = 0; n < cur->num_redirects; n++)
        write_redirect(mpctx, cur->redirects[n]);
    // And at last, for local directories, we write an entry for each path
    // prefix, so the user can resume from an arbitrary directory. This starts
    // with the first redirect (all other redirects are further prefixes).
    if (cur->num_redirects) {
        char *path = cur->redirects[0];
        char tmp[4096];
        if (!mp_is_url(bstr0(path)) && strlen(path) < sizeof(tmp)) {
            snprintf(tmp, sizeof(tmp), "%s", path);
            for (;;) {
                bstr dir = mp_dirname(tmp);
                if (dir.len == strlen(tmp) || !dir.len || bstr_equals0(dir, "."))
                    break;

                tmp[dir.len] = '\0';
                if (strlen(tmp) >= 2) // keep "/"
                    mp_path_strip_trailing_separator(tmp);
                write_redirect(mpctx, tmp);
            }
        }
    }

exit:
    talloc_free(conffile);
}