void playlist_add_base_path(struct playlist *pl, bstr base_path) { if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0) return; for (struct playlist_entry *e = pl->first; e; e = e->next) { if (!mp_is_url(bstr0(e->filename))) { char *new_file = mp_path_join(e, base_path, bstr0(e->filename)); talloc_free(e->filename); e->filename = new_file; } } }
static char *mp_get_playback_resume_config_filename(struct MPContext *mpctx, const char *fname) { struct MPOpts *opts = mpctx->opts; char *res = NULL; void *tmp = talloc_new(NULL); const char *realpath = fname; bstr bfname = bstr0(fname); if (!mp_is_url(bfname)) { if (opts->ignore_path_in_watch_later_config) { realpath = mp_basename(fname); } else { char *cwd = mp_getcwd(tmp); if (!cwd) goto exit; realpath = mp_path_join(tmp, cwd, fname); } } if (bstr_startswith0(bfname, "dvd://") && opts->dvd_opts && opts->dvd_opts->device) realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->dvd_opts->device); if ((bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") || bstr_startswith0(bfname, "bluray://")) && opts->bluray_device) realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->bluray_device); uint8_t md5[16]; av_md5_sum(md5, realpath, strlen(realpath)); char *conf = talloc_strdup(tmp, ""); for (int i = 0; i < 16; i++) conf = talloc_asprintf_append(conf, "%02X", md5[i]); if (!mpctx->cached_watch_later_configdir) { char *wl_dir = mpctx->opts->watch_later_directory; if (wl_dir && wl_dir[0]) { mpctx->cached_watch_later_configdir = mp_get_user_path(mpctx, mpctx->global, wl_dir); } } if (!mpctx->cached_watch_later_configdir) { mpctx->cached_watch_later_configdir = mp_find_user_config_file(mpctx, mpctx->global, MP_WATCH_LATER_CONF); } if (mpctx->cached_watch_later_configdir) res = mp_path_join(NULL, mpctx->cached_watch_later_configdir, conf); exit: talloc_free(tmp); return res; }
static char *mp_get_playback_resume_config_filename(struct mpv_global *global, const char *fname) { struct MPOpts *opts = global->opts; char *res = NULL; void *tmp = talloc_new(NULL); const char *realpath = fname; bstr bfname = bstr0(fname); if (!mp_is_url(bfname)) { if (opts->ignore_path_in_watch_later_config) { realpath = mp_basename(fname); } else { char *cwd = mp_getcwd(tmp); if (!cwd) goto exit; realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname)); } } if (bstr_startswith0(bfname, "dvd://")) realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->dvd_device); if (bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") || bstr_startswith0(bfname, "bluray://")) realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->bluray_device); uint8_t md5[16]; av_md5_sum(md5, realpath, strlen(realpath)); char *conf = talloc_strdup(tmp, ""); for (int i = 0; i < 16; i++) conf = talloc_asprintf_append(conf, "%02X", md5[i]); res = talloc_asprintf(tmp, MP_WATCH_LATER_CONF "/%s", conf); res = mp_find_config_file(NULL, global, res); if (!res) { res = mp_find_config_file(tmp, global, MP_WATCH_LATER_CONF); if (res) res = talloc_asprintf(NULL, "%s/%s", res, conf); } exit: talloc_free(tmp); return res; }
static char *mp_get_playback_resume_config_filename(struct mpv_global *global, const char *fname) { char *res = NULL; void *tmp = talloc_new(NULL); const char *realpath = fname; bstr bfname = bstr0(fname); if (!mp_is_url(bfname)) { char *cwd = mp_getcwd(tmp); if (!cwd) goto exit; realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname)); } #if HAVE_DVDREAD || HAVE_DVDNAV if (bstr_startswith0(bfname, "dvd://")) realpath = talloc_asprintf(tmp, "%s - %s", realpath, dvd_device); #endif #if HAVE_LIBBLURAY if (bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") || bstr_startswith0(bfname, "bluray://")) realpath = talloc_asprintf(tmp, "%s - %s", realpath, bluray_device); #endif uint8_t md5[16]; av_md5_sum(md5, realpath, strlen(realpath)); char *conf = talloc_strdup(tmp, ""); for (int i = 0; i < 16; i++) conf = talloc_asprintf_append(conf, "%02X", md5[i]); conf = talloc_asprintf(tmp, "%s/%s", MP_WATCH_LATER_CONF, conf); res = mp_find_user_config_file(NULL, global, conf); exit: talloc_free(tmp); 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); } 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); }