static void mp_load_per_file_config(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; char *confpath; char cfg[512]; const char *file = mpctx->filename; if (snprintf(cfg, sizeof(cfg), "%s.conf", file) >= sizeof(cfg)) { MP_WARN(mpctx, "Filename is too long, " "can not load file or directory specific config files\n"); return; } char *name = mp_basename(cfg); if (opts->use_filedir_conf) { bstr dir = mp_dirname(cfg); char *dircfg = mp_path_join(NULL, dir, bstr0("mpv.conf")); try_load_config(mpctx, dircfg, FILE_LOCAL_FLAGS); talloc_free(dircfg); if (try_load_config(mpctx, cfg, FILE_LOCAL_FLAGS)) return; } if ((confpath = mp_find_user_config_file(NULL, mpctx->global, name))) { try_load_config(mpctx, confpath, FILE_LOCAL_FLAGS); talloc_free(confpath); } }
static int open_file(struct demuxer *demuxer, enum demux_check check) { bool force = check < DEMUX_CHECK_UNSAFE || check == DEMUX_CHECK_REQUEST; struct pl_parser *p = talloc_zero(NULL, struct pl_parser); p->pl = talloc_zero(p, struct playlist); bstr probe_buf = stream_peek(demuxer->stream, PROBE_SIZE); p->s = open_memory_stream(probe_buf.start, probe_buf.len); p->utf16 = stream_skip_bom(p->s); p->probing = true; const struct pl_format *fmt = probe_pl(p, force); free_stream(p->s); playlist_clear(p->pl); if (!fmt) { talloc_free(p); return -1; } p->probing = false; p->s = demuxer->stream; p->utf16 = stream_skip_bom(p->s); bool ok = fmt->parse(p) >= 0; if (ok) playlist_add_base_path(p->pl, mp_dirname(demuxer->filename)); demuxer->playlist = talloc_steal(demuxer, p->pl); demuxer->filetype = fmt->name; talloc_free(p); return ok ? 0 : -1; }
static bool open_source(struct MPContext *mpctx, struct bstr filename) { void *ctx = talloc_new(NULL); bool res = false; struct bstr dirname = mp_dirname(mpctx->demuxer->filename); struct bstr base_filename = bstr0(mp_basename(bstrdup0(ctx, filename))); if (!base_filename.len) { mp_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: Invalid audio filename in .cue file!\n"); } else { char *fullname = mp_path_join(ctx, dirname, base_filename); if (try_open(mpctx, fullname)) { res = true; goto out; } } // Try an audio file with the same name as the .cue file (but different // extension). // Rationale: this situation happens easily if the audio file or both files // are renamed. struct bstr cuefile = bstr_strip_ext(bstr0(mp_basename(mpctx->demuxer->filename))); DIR *d = opendir(bstrdup0(ctx, dirname)); if (!d) goto out; struct dirent *de; while ((de = readdir(d))) { char *dename0 = de->d_name; struct bstr dename = bstr0(dename0); if (bstr_case_startswith(dename, cuefile)) { mp_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: No useful audio filename " "in .cue file found, trying with '%s' instead!\n", dename0); if (try_open(mpctx, mp_path_join(ctx, dirname, dename))) { res = true; break; } } } closedir(d); out: talloc_free(ctx); if (!res) mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: Could not open audio file!\n"); return res; }
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; }
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; }
/* 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 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; }
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); }