Beispiel #1
0
struct bstr bstr_strip_linebreaks(struct bstr str)
{
    if (bstr_endswith0(str, "\r\n")) {
        str = bstr_splice(str, 0, str.len - 2);
    } else if (bstr_endswith0(str, "\n")) {
        str = bstr_splice(str, 0, str.len - 1);
    }
    return str;
}
Beispiel #2
0
int bstr_find(struct bstr haystack, struct bstr needle)
{
    for (int i = 0; i < haystack.len; i++)
        if (bstr_startswith(bstr_splice(haystack, i, haystack.len), needle))
            return i;
    return -1;
}
Beispiel #3
0
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c)
{
    int pos = bstrchr(str, c);
    if (pos < 0)
        pos = str.len;
    if (rest)
        *rest = bstr_cut(str, pos + 1);
    return bstr_splice(str, 0, pos + 1);
}
Beispiel #4
0
struct bstr bstr_getline(struct bstr str, struct bstr *rest)
{
    int pos = bstrchr(str, '\n');
    if (pos < 0)
        pos = str.len;
    if (rest)
        *rest = bstr_cut(str, pos + 1);
    return bstr_splice(str, 0, pos + 1);
}
Beispiel #5
0
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;
        }
    }
Beispiel #6
0
// 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;
}
Beispiel #7
0
Datei: cue.c Projekt: ThreeGe/mpv
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);
}
Beispiel #8
0
struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest)
{
    int start;
    for (start = 0; start < str.len; start++)
        if (!strchr(sep, str.start[start]))
            break;
    str = bstr_cut(str, start);
    int end = bstrcspn(str, sep);
    if (rest) {
        *rest = bstr_cut(str, end);
    }
    return bstr_splice(str, 0, end);
}
Beispiel #9
0
static int parse_pls(struct pl_parser *p)
{
    bstr line = {0};
    while (!line.len && !pl_eof(p))
        line = bstr_strip(pl_get_line(p));
    if (bstrcasecmp0(line, "[playlist]") != 0)
        return -1;
    if (p->probing)
        return 0;
    while (!pl_eof(p)) {
        line = bstr_strip(pl_get_line(p));
        bstr key, value;
        if (bstr_split_tok(line, "=", &key, &value) &&
            bstr_case_startswith(key, bstr0("File")))
        {
            value = bstr_strip(value);
            if (bstr_startswith0(value, "\"") && bstr_endswith0(value, "\""))
                value = bstr_splice(value, 1, -1);
            pl_add(p, value);
        }
    }
    return 0;
}
Beispiel #10
0
static struct bstr strip_ext(struct bstr str)
{
    int dotpos = bstrrchr(str, '.');
    if (dotpos < 0)
        return str;
    return (struct bstr){str.start, dotpos};
}

static struct bstr get_ext(struct bstr s)
{
    int dotpos = bstrrchr(s, '.');
    if (dotpos < 0)
        return (struct bstr){NULL, 0};
    return bstr_splice(s, dotpos + 1, s.len);
}

bool mp_might_be_subtitle_file(const char *filename)
{
    return test_ext(get_ext(bstr0(filename))) == STREAM_SUB;
}

static int compare_sub_filename(const void *a, const void *b)
{
    const struct subfn *s1 = a;
    const struct subfn *s2 = b;
    return strcoll(s1->fname, s2->fname);
}

static int compare_sub_priority(const void *a, const void *b)
{
    const struct subfn *s1 = a;
    const struct subfn *s2 = b;
    if (s1->priority > s2->priority)
        return -1;
    if (s1->priority < s2->priority)
        return 1;
    return strcoll(s1->fname, s2->fname);
}

static struct bstr guess_lang_from_filename(struct bstr name)
{
    if (name.len < 2)
        return (struct bstr){NULL, 0};

    int n = 0;
    int i = name.len - 1;

    if (name.start[i] == ')' || name.start[i] == ']')
        i--;
    while (i >= 0 && mp_isalpha(name.start[i])) {
        n++;
        if (n > 3)
            return (struct bstr){NULL, 0};
        i--;
    }
    if (n < 2)
        return (struct bstr){NULL, 0};
    return (struct bstr){name.start + i + 1, n};
}

static void append_dir_subtitles(struct mpv_global *global,
                                 struct subfn **slist, int *nsub,
                                 struct bstr path, const char *fname,
                                 int limit_fuzziness)
{
    void *tmpmem = talloc_new(NULL);
    struct MPOpts *opts = global->opts;
    struct mp_log *log = mp_log_new(tmpmem, global->log, "find_files");

    if (mp_is_url(bstr0(fname)))
        goto out;

    struct bstr f_fname = bstr0(mp_basename(fname));
    struct bstr f_fname_noext = bstrdup(tmpmem, strip_ext(f_fname));
    bstr_lower(f_fname_noext);
    struct bstr f_fname_trim = bstr_strip(f_fname_noext);

    // 0 = nothing
    // 1 = any subtitle file
    // 2 = any sub file containing movie name
    // 3 = sub file containing movie name and the lang extension
    char *path0 = bstrdup0(tmpmem, path);
    DIR *d = opendir(path0);
    if (!d)
        goto out;
    mp_verbose(log, "Loading external files in %.*s\n", BSTR_P(path));
    struct dirent *de;
    while ((de = readdir(d))) {
        struct bstr dename = bstr0(de->d_name);
        void *tmpmem2 = talloc_new(tmpmem);

        // retrieve various parts of the filename
        struct bstr tmp_fname_noext = bstrdup(tmpmem2, strip_ext(dename));
        bstr_lower(tmp_fname_noext);
        struct bstr tmp_fname_ext = get_ext(dename);
        struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext);

        // check what it is (most likely)
        int type = test_ext(tmp_fname_ext);
        char **langs = NULL;
        int fuzz = -1;
        switch (type) {
        case STREAM_SUB:
            langs = opts->sub_lang;
            fuzz = opts->sub_auto;
            break;
        case STREAM_AUDIO:
            langs = opts->audio_lang;
            fuzz = opts->audiofile_auto;
            break;
        }

        if (fuzz < 0)
            goto next_sub;

        // we have a (likely) subtitle file
        int prio = 0;
        char *found_lang = NULL;
        if (langs) {
            if (bstr_startswith(tmp_fname_trim, f_fname_trim)) {
                struct bstr lang = guess_lang_from_filename(tmp_fname_trim);
                if (lang.len) {
                    for (int n = 0; langs[n]; n++) {
                        if (bstr_startswith0(lang, langs[n])) {
                            prio = 4; // matches the movie name + lang extension
                            found_lang = langs[n];
                            break;
                        }
                    }
                }
            }
        }
        if (!prio && bstrcmp(tmp_fname_trim, f_fname_trim) == 0)
            prio = 3; // matches the movie name
        if (!prio && bstr_find(tmp_fname_trim, f_fname_trim) >= 0 && fuzz >= 1)
            prio = 2; // contains the movie name
        if (!prio) {
            // doesn't contain the movie name
            // don't try in the mplayer subtitle directory
            if (!limit_fuzziness && fuzz >= 2) {
                prio = 1;
            }
        }

        mp_dbg(log, "Potential external file: \"%s\"  Priority: %d\n",
               de->d_name, prio);

        if (prio) {
            prio += prio;
            char *subpath = mp_path_join(*slist, path, dename);
            if (mp_path_exists(subpath)) {
                MP_GROW_ARRAY(*slist, *nsub);
                struct subfn *sub = *slist + (*nsub)++;

                // annoying and redundant
                if (strncmp(subpath, "./", 2) == 0)
                    subpath += 2;

                sub->type     = type;
                sub->priority = prio;
                sub->fname    = subpath;
                sub->lang     = found_lang;
            } else
                talloc_free(subpath);
        }

    next_sub:
        talloc_free(tmpmem2);
    }
    closedir(d);

 out:
    talloc_free(tmpmem);
}

static bool case_endswith(const char *s, const char *end)
{
    size_t len = strlen(s);
    size_t elen = strlen(end);
    return len >= elen && strcasecmp(s + len - elen, end) == 0;
}

// Drop .sub file if .idx file exists.
// Assumes slist is sorted by compare_sub_filename.
static void filter_subidx(struct subfn **slist, int *nsub)
{
    const char *prev = NULL;
    for (int n = 0; n < *nsub; n++) {
        const char *fname = (*slist)[n].fname;
        if (case_endswith(fname, ".idx")) {
            prev = fname;
        } else if (case_endswith(fname, ".sub")) {
            if (prev && strncmp(prev, fname, strlen(fname) - 4) == 0)
                (*slist)[n].priority = -1;
        }
    }
    for (int n = *nsub - 1; n >= 0; n--) {
        if ((*slist)[n].priority < 0)
            MP_TARRAY_REMOVE_AT(*slist, *nsub, n);
    }
}

// Return a list of subtitles and audio files found, sorted by priority.
// Last element is terminated with a fname==NULL entry.
struct subfn *find_external_files(struct mpv_global *global, const char *fname)
{
    struct MPOpts *opts = global->opts;
    struct subfn *slist = talloc_array_ptrtype(NULL, slist, 1);
    int n = 0;

    // Load subtitles from current media directory
    append_dir_subtitles(global, &slist, &n, mp_dirname(fname), fname, 0);

    if (opts->sub_auto >= 0) {
        // Load subtitles in dirs specified by sub-paths option
        if (opts->sub_paths) {
            for (int i = 0; opts->sub_paths[i]; i++) {
                char *path = mp_path_join(slist, mp_dirname(fname),
                                        bstr0(opts->sub_paths[i]));
                append_dir_subtitles(global, &slist, &n, bstr0(path), fname, 0);
            }
        }

        // Load subtitles in ~/.mpv/sub limiting sub fuzziness
        char *mp_subdir = mp_find_config_file(NULL, global, "sub/");
        if (mp_subdir)
            append_dir_subtitles(global, &slist, &n, bstr0(mp_subdir), fname, 1);
        talloc_free(mp_subdir);
    }

    // Sort by name for filter_subidx()
    qsort(slist, n, sizeof(*slist), compare_sub_filename);

    filter_subidx(&slist, &n);

    // Sort subs by priority and append them
    qsort(slist, n, sizeof(*slist), compare_sub_priority);

    struct subfn z = {0};
    MP_TARRAY_APPEND(NULL, slist, n, z);

    return slist;
}
Beispiel #11
0
static struct bstr strip_ext(struct bstr str)
{
    int dotpos = bstrrchr(str, '.');
    if (dotpos < 0)
        return str;
    return (struct bstr){str.start, dotpos};
}

static struct bstr get_ext(struct bstr s)
{
    int dotpos = bstrrchr(s, '.');
    if (dotpos < 0)
        return (struct bstr){NULL, 0};
    return bstr_splice(s, dotpos + 1, s.len);
}

struct subfn {
    int priority;
    char *fname;
};

static int compare_sub_priority(const void *a, const void *b)
{
    const struct subfn *s1 = a;
    const struct subfn *s2 = b;
    if (s1->priority > s2->priority)
        return -1;
    if (s1->priority < s2->priority)
        return 1;
    return strcoll(s1->fname, s2->fname);
}

static struct bstr guess_lang_from_filename(struct bstr name)
{
    if (name.len < 2)
        return (struct bstr){NULL, 0};

    int n = 0;
    int i = name.len - 1;

    if (name.start[i] == ')' || name.start[i] == ']')
        i--;
    while (i >= 0 && isalpha(name.start[i])) {
        n++;
        if (n > 3)
            return (struct bstr){NULL, 0};
        i--;
    }
    if (n < 2)
        return (struct bstr){NULL, 0};
    return (struct bstr){name.start + i + 1, n};
}

struct sub_list {
    struct subfn subs[MAX_SUBTITLE_FILES];
    int sid;
    void *ctx;
};

/**
 * @brief Append all the subtitles in the given path matching fname
 * @param opts MPlayer options
 * @param slist pointer to the subtitles list tallocated
 * @param nsub pointer to the number of subtitles
 * @param path Look for subtitles in this directory
 * @param fname Subtitle filename (pattern)
 * @param limit_fuzziness Ignore flag when sub_fuziness == 2
 */
static void append_dir_subtitles(struct MPOpts *opts,
                                 struct subfn **slist, int *nsub,
                                 struct bstr path, const char *fname,
                                 int limit_fuzziness)
{
    char *sub_exts[] = {"utf", "utf8", "utf-8", "sub", "srt", "smi", "rt", "txt", "ssa", "aqt", "jss", "js", "ass", NULL};
    void *tmpmem = talloc_new(NULL);
    FILE *f;
    assert(strlen(fname) < 1e6);

    struct bstr f_fname = bstr0(mp_basename(fname));
    struct bstr f_fname_noext = bstrdup(tmpmem, strip_ext(f_fname));
    bstr_lower(f_fname_noext);
    struct bstr f_fname_trim = bstr_strip(f_fname_noext);

    // 0 = nothing
    // 1 = any subtitle file
    // 2 = any sub file containing movie name
    // 3 = sub file containing movie name and the lang extension
    char *path0 = bstrdup0(tmpmem, path);
    DIR *d = opendir(path0);
    if (!d)
        goto out;
    mp_msg(MSGT_SUBREADER, MSGL_V, "Load subtitles in %.*s\n", BSTR_P(path));
    struct dirent *de;
    while ((de = readdir(d))) {
        struct bstr dename = bstr0(de->d_name);
        void *tmpmem2 = talloc_new(tmpmem);

        // retrieve various parts of the filename
        struct bstr tmp_fname_noext = bstrdup(tmpmem2, strip_ext(dename));
        bstr_lower(tmp_fname_noext);
        struct bstr tmp_fname_ext = get_ext(dename);
        struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext);

        // If it's a .sub, check if there is a .idx with the same name. If
        // there is one, it's certainly a vobsub so we skip it.
        if (bstrcasecmp(tmp_fname_ext, bstr0("sub")) == 0) {
            char *idxname = talloc_asprintf(tmpmem2, "%.*s.idx",
                                            (int)tmp_fname_noext.len,
                                            de->d_name);
            char *idx = mp_path_join(tmpmem2, path, bstr0(idxname));
            f = fopen(idx, "rt");
            if (f) {
                fclose(f);
                goto next_sub;
            }
        }

        // does it end with a subtitle extension?
#ifdef CONFIG_ICONV
#ifdef CONFIG_ENCA
        int i = (sub_cp && strncasecmp(sub_cp, "enca", 4) != 0) ? 3 : 0;
#else
        int i = sub_cp ? 3 : 0;
#endif
#else
        int i = 0;
#endif
        while (1) {
            if (!sub_exts[i])
                goto next_sub;
            if (bstrcasecmp(bstr0(sub_exts[i]), tmp_fname_ext) == 0)
                break;
            i++;
        }

        // we have a (likely) subtitle file
        int prio = 0;
        if (opts->sub_lang) {
            if (bstr_startswith(tmp_fname_trim, f_fname_trim)) {
                struct bstr lang = guess_lang_from_filename(tmp_fname_trim);
                if (lang.len) {
                    for (int n = 0; opts->sub_lang[n]; n++) {
                        if (bstr_startswith(lang,
                                            bstr0(opts->sub_lang[n]))) {
                            prio = 4; // matches the movie name + lang extension
                            break;
                        }
                    }
                }
            }
        }
        if (!prio && bstrcmp(tmp_fname_trim, f_fname_trim) == 0)
            prio = 3; // matches the movie name
        if (!prio && bstr_find(tmp_fname_trim, f_fname_trim) >= 0
            && sub_match_fuzziness >= 1)
            prio = 2; // contains the movie name
        if (!prio) {
            // doesn't contain the movie name
            // don't try in the mplayer subtitle directory
            if (!limit_fuzziness && sub_match_fuzziness >= 2) {
                prio = 1;
            }
        }

        mp_msg(MSGT_SUBREADER, MSGL_DBG2, "Potential sub file: "
               "\"%s\"  Priority: %d\n", de->d_name, prio);
        if (prio) {
            prio += prio;
#ifdef CONFIG_ICONV
            if (i < 3) // prefer UTF-8 coded
                prio++;
#endif
            char *subpath = mp_path_join(*slist, path, dename);
            if ((f = fopen(subpath, "rt"))) {
                MP_GROW_ARRAY(*slist, *nsub);
                struct subfn *sub = *slist + (*nsub)++;

                fclose(f);
                sub->priority = prio;
                sub->fname    = subpath;
            } else
                talloc_free(subpath);
        }

    next_sub:
        talloc_free(tmpmem2);
    }
    closedir(d);

 out:
    talloc_free(tmpmem);
}

char **find_text_subtitles(struct MPOpts *opts, const char *fname)
{
    char **subnames = NULL;
    struct subfn *slist = talloc_array_ptrtype(NULL, slist, 1);
    int n = 0;

    // Load subtitles from current media directory
    append_dir_subtitles(opts, &slist, &n, mp_dirname(fname), fname, 0);

    // Load subtitles in dirs specified by sub-paths option
    if (opts->sub_paths) {
        for (int i = 0; opts->sub_paths[i]; i++) {
            char *path = mp_path_join(slist, mp_dirname(fname),
                                      bstr0(opts->sub_paths[i]));
            append_dir_subtitles(opts, &slist, &n, bstr0(path), fname, 0);
        }
    }

    // Load subtitles in ~/.mplayer/sub limiting sub fuzziness
    char *mp_subdir = get_path("sub/");
    if (mp_subdir)
        append_dir_subtitles(opts, &slist, &n, bstr0(mp_subdir), fname, 1);
    free(mp_subdir);

    // Sort subs by priority and append them
    qsort(slist, n, sizeof(*slist), compare_sub_priority);

    subnames = talloc_array_ptrtype(NULL, subnames, n);
    for (int i = 0; i < n; i++)
        subnames[i] = talloc_strdup(subnames, slist[i].fname);

    talloc_free(slist);
    return subnames;
}

char **find_vob_subtitles(struct MPOpts *opts, const char *fname)
{
    char **vobs = talloc_array_ptrtype(NULL, vobs, 1);
    int n = 0;

    // Potential vobsub in the media directory
    struct bstr bname = bstr0(mp_basename(fname));
    int pdot = bstrrchr(bname, '.');
    if (pdot >= 0)
        bname.len = pdot;
    vobs[n++] = mp_path_join(vobs, mp_dirname(fname), bname);

    // Potential vobsubs in directories specified by sub-paths option
    if (opts->sub_paths) {
        for (int i = 0; opts->sub_paths[i]; i++) {
            char *path = mp_path_join(NULL, mp_dirname(fname),
                                      bstr0(opts->sub_paths[i]));
            MP_GROW_ARRAY(vobs, n);
            vobs[n++] = mp_path_join(vobs, bstr0(path), bname);
            talloc_free(path);
        }
    }

    // Potential vobsub in ~/.mplayer/sub
    char *mp_subdir = get_path("sub/");
    if (mp_subdir) {
        MP_GROW_ARRAY(vobs, n);
        vobs[n++] = mp_path_join(vobs, bstr0(mp_subdir), bname);
    }

    free(mp_subdir);
    MP_RESIZE_ARRAY(NULL, vobs, n);
    return vobs;
}
Beispiel #12
0
// 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;
}
Beispiel #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;
}
Beispiel #14
0
/* 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,
};
Beispiel #15
0
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;
}
Beispiel #16
0
static struct bstr strip_ext(struct bstr str)
{
    int dotpos = bstrrchr(str, '.');
    if (dotpos < 0)
        return str;
    return (struct bstr){str.start, dotpos};
}

static struct bstr get_ext(struct bstr s)
{
    int dotpos = bstrrchr(s, '.');
    if (dotpos < 0)
        return (struct bstr){NULL, 0};
    return bstr_splice(s, dotpos + 1, s.len);
}

static int compare_sub_filename(const void *a, const void *b)
{
    const struct subfn *s1 = a;
    const struct subfn *s2 = b;
    return strcoll(s1->fname, s2->fname);
}

static int compare_sub_priority(const void *a, const void *b)
{
    const struct subfn *s1 = a;
    const struct subfn *s2 = b;
    if (s1->priority > s2->priority)
        return -1;
    if (s1->priority < s2->priority)
        return 1;
    return strcoll(s1->fname, s2->fname);
}

static struct bstr guess_lang_from_filename(struct bstr name)
{
    if (name.len < 2)
        return (struct bstr){NULL, 0};

    int n = 0;
    int i = name.len - 1;

    if (name.start[i] == ')' || name.start[i] == ']')
        i--;
    while (i >= 0 && isalpha(name.start[i])) {
        n++;
        if (n > 3)
            return (struct bstr){NULL, 0};
        i--;
    }
    if (n < 2)
        return (struct bstr){NULL, 0};
    return (struct bstr){name.start + i + 1, n};
}

/**
 * @brief Append all the subtitles in the given path matching fname
 * @param opts MPlayer options
 * @param slist pointer to the subtitles list tallocated
 * @param nsub pointer to the number of subtitles
 * @param path Look for subtitles in this directory
 * @param fname Subtitle filename (pattern)
 * @param limit_fuzziness Ignore flag when sub_fuziness == 2
 */
static void append_dir_subtitles(struct MPOpts *opts,
                                 struct subfn **slist, int *nsub,
                                 struct bstr path, const char *fname,
                                 int limit_fuzziness)
{
    void *tmpmem = talloc_new(NULL);

    if (mp_is_url(bstr0(fname)))
        goto out;

    struct bstr f_fname = bstr0(mp_basename(fname));
    struct bstr f_fname_noext = bstrdup(tmpmem, strip_ext(f_fname));
    bstr_lower(f_fname_noext);
    struct bstr f_fname_trim = bstr_strip(f_fname_noext);

    // 0 = nothing
    // 1 = any subtitle file
    // 2 = any sub file containing movie name
    // 3 = sub file containing movie name and the lang extension
    char *path0 = bstrdup0(tmpmem, path);
    DIR *d = opendir(path0);
    if (!d)
        goto out;
    mp_msg(MSGT_SUBREADER, MSGL_V, "Load subtitles in %.*s\n", BSTR_P(path));
    struct dirent *de;
    while ((de = readdir(d))) {
        struct bstr dename = bstr0(de->d_name);
        void *tmpmem2 = talloc_new(tmpmem);

        // retrieve various parts of the filename
        struct bstr tmp_fname_noext = bstrdup(tmpmem2, strip_ext(dename));
        bstr_lower(tmp_fname_noext);
        struct bstr tmp_fname_ext = get_ext(dename);
        struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext);

        // does it end with a subtitle extension?
        if (!is_sub_ext(tmp_fname_ext))
            goto next_sub;

        // we have a (likely) subtitle file
        int prio = 0;
        char *found_lang = NULL;
        if (opts->sub_lang) {
            if (bstr_startswith(tmp_fname_trim, f_fname_trim)) {
                struct bstr lang = guess_lang_from_filename(tmp_fname_trim);
                if (lang.len) {
                    for (int n = 0; opts->sub_lang[n]; n++) {
                        if (bstr_startswith0(lang, opts->sub_lang[n])) {
                            prio = 4; // matches the movie name + lang extension
                            found_lang = opts->sub_lang[n];
                            break;
                        }
                    }
                }
            }
        }
        if (!prio && bstrcmp(tmp_fname_trim, f_fname_trim) == 0)
            prio = 3; // matches the movie name
        if (!prio && bstr_find(tmp_fname_trim, f_fname_trim) >= 0
            && opts->sub_match_fuzziness >= 1)
            prio = 2; // contains the movie name
        if (!prio) {
            // doesn't contain the movie name
            // don't try in the mplayer subtitle directory
            if (!limit_fuzziness && opts->sub_match_fuzziness >= 2) {
                prio = 1;
            }
        }

        mp_msg(MSGT_SUBREADER, MSGL_DBG2, "Potential sub file: "
               "\"%s\"  Priority: %d\n", de->d_name, prio);
        if (prio) {
            prio += prio;
            char *subpath = mp_path_join(*slist, path, dename);
            if (mp_path_exists(subpath)) {
                MP_GROW_ARRAY(*slist, *nsub);
                struct subfn *sub = *slist + (*nsub)++;

                // annoying and redundant
                if (strncmp(subpath, "./", 2) == 0)
                    subpath += 2;

                sub->priority = prio;
                sub->fname    = subpath;
                sub->lang     = found_lang;
            } else
                talloc_free(subpath);
        }

    next_sub:
        talloc_free(tmpmem2);
    }
    closedir(d);

 out:
    talloc_free(tmpmem);
}

static bool case_endswith(const char *s, const char *end)
{
    size_t len = strlen(s);
    size_t elen = strlen(end);
    return len >= elen && strcasecmp(s + len - elen, end) == 0;
}

// Drop .sub file if .idx file exists.
// Assumes slist is sorted by compare_sub_filename.
static void filter_subidx(struct subfn **slist, int *nsub)
{
    const char *prev = NULL;
    for (int n = 0; n < *nsub; n++) {
        const char *fname = (*slist)[n].fname;
        if (case_endswith(fname, ".idx")) {
            prev = fname;
        } else if (case_endswith(fname, ".sub")) {
            if (prev && strncmp(prev, fname, strlen(fname) - 4) == 0)
                (*slist)[n].priority = -1;
        }
    }
    for (int n = *nsub - 1; n >= 0; n--) {
        if ((*slist)[n].priority < 0)
            MP_TARRAY_REMOVE_AT(*slist, *nsub, n);
    }
}

// Return a list of subtitles found, sorted by priority.
// Last element is terminated with a fname==NULL entry.
struct subfn *find_text_subtitles(struct MPOpts *opts, const char *fname)
{
    struct subfn *slist = talloc_array_ptrtype(NULL, slist, 1);
    int n = 0;

    // Load subtitles from current media directory
    append_dir_subtitles(opts, &slist, &n, mp_dirname(fname), fname, 0);

    // Load subtitles in dirs specified by sub-paths option
    if (opts->sub_paths) {
        for (int i = 0; opts->sub_paths[i]; i++) {
            char *path = mp_path_join(slist, mp_dirname(fname),
                                      bstr0(opts->sub_paths[i]));
            append_dir_subtitles(opts, &slist, &n, bstr0(path), fname, 0);
        }
    }

    // Load subtitles in ~/.mpv/sub limiting sub fuzziness
    char *mp_subdir = mp_find_user_config_file("sub/");
    if (mp_subdir)
        append_dir_subtitles(opts, &slist, &n, bstr0(mp_subdir), fname, 1);
    talloc_free(mp_subdir);

    // Sort by name for filter_subidx()
    qsort(slist, n, sizeof(*slist), compare_sub_filename);

    filter_subidx(&slist, &n);

    // Sort subs by priority and append them
    qsort(slist, n, sizeof(*slist), compare_sub_priority);

    struct subfn z = {0};
    MP_TARRAY_APPEND(NULL, slist, n, z);

    return slist;
}