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); } }
bool mp_parse_cfgfiles(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; if (!opts->load_config) return true; m_config_t *conf = mpctx->mconfig; void *tmp = talloc_new(NULL); bool r = true; char *conffile; // The #if is a stupid hack to avoid errors if libavfilter is not available. #if HAVE_VF_LAVFI && HAVE_ENCODING conffile = mp_find_config_file(tmp, mpctx->global, "encoding-profiles.conf"); if (conffile && mp_path_exists(conffile)) m_config_parse_config_file(mpctx->mconfig, conffile, 0); #endif conffile = mp_find_global_config_file(tmp, mpctx->global, "mpv.conf"); if (conffile && m_config_parse_config_file(conf, conffile, 0) < 0) { r = false; goto done; } mp_mk_config_dir(mpctx->global, NULL); if (!(conffile = mp_find_user_config_file(tmp, mpctx->global, "config"))) MP_ERR(mpctx, "mp_find_user_config_file(\"config\") problem\n"); else if (m_config_parse_config_file(conf, conffile, 0) < 0) { r = false; goto done; } done: talloc_free(tmp); return r; }
/* Allocate memory and set function pointers af audio filter instance returns AF_OK or AF_ERROR */ static int af_open( struct af_instance* af ) { af->control = control; af->uninit = uninit; af->play = play; af->setup = calloc(1, sizeof(af_export_t)); if(af->setup == NULL) return AF_ERROR; ((af_export_t *)af->setup)->filename = mp_find_user_config_file(SHARED_FILE); return AF_OK; }
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; }
/* Allocate memory and set function pointers af audio filter instance returns AF_OK or AF_ERROR */ static int af_open( struct af_instance* af ) { af->control = control; af->uninit = uninit; af->filter = filter; af_export_t *priv = af->priv; if (!priv->filename || !priv->filename[0]) priv->filename = mp_find_user_config_file(SHARED_FILE); if (!priv->filename || !priv->filename[0]) return AF_ERROR; return AF_OK; }
void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts) { char *default_font = mp_find_user_config_file("subfont.ttf"); char *config = mp_find_config_file("fonts.conf"); if (!mp_path_exists(default_font)) { talloc_free(default_font); default_font = NULL; } ass_set_fonts(priv, default_font, opts->font, 1, config, 1); talloc_free(default_font); talloc_free(config); }
void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts, struct mpv_global *global, struct mp_log *log) { void *tmp = talloc_new(NULL); char *default_font = mp_find_user_config_file(tmp, global, "subfont.ttf"); char *config = mp_find_config_file(tmp, global, "fonts.conf"); if (default_font && !mp_path_exists(default_font)) default_font = NULL; mp_verbose(log, "Setting up fonts...\n"); ass_set_fonts(priv, default_font, opts->font, 1, config, 1); mp_verbose(log, "Done.\n"); talloc_free(tmp); }
bool mp_parse_cfgfiles(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; if (!opts->load_config) return true; m_config_t *conf = mpctx->mconfig; void *tmp = talloc_new(NULL); bool r = true; char *conffile; char *section = NULL; bool encoding = opts->encode_output.file && *opts->encode_output.file; // In encoding mode, we don't want to apply normal config options. // So we "divert" normal options into a separate section, and the diverted // section is never used - unless maybe it's explicitly referenced from an // encoding profile. if (encoding) section = "playback-default"; // The #if is a stupid hack to avoid errors if libavfilter is not available. #if HAVE_LIBAVFILTER && HAVE_ENCODING conffile = mp_find_config_file(tmp, mpctx->global, "encoding-profiles.conf"); if (conffile && mp_path_exists(conffile)) m_config_parse_config_file(mpctx->mconfig, conffile, SECT_ENCODE, 0); #endif conffile = mp_find_global_config_file(tmp, mpctx->global, "mpv.conf"); if (conffile && m_config_parse_config_file(conf, conffile, section, 0) < 0) { r = false; goto done; } mp_mk_config_dir(mpctx->global, NULL); if (!(conffile = mp_find_user_config_file(tmp, mpctx->global, "config"))) MP_ERR(mpctx, "mp_find_user_config_file(\"config\") problem\n"); else if (m_config_parse_config_file(conf, conffile, section, 0) < 0) { r = false; goto done; } if (encoding) m_config_set_profile(conf, m_config_add_profile(conf, SECT_ENCODE), 0); done: talloc_free(tmp); return r; }
void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts) { char *default_font = mp_find_user_config_file("subfont.ttf"); char *config = mp_find_config_file("fonts.conf"); if (default_font && !mp_path_exists(default_font)) { talloc_free(default_font); default_font = NULL; } mp_msg(MSGT_ASS, MSGL_V, "[ass] Setting up fonts...\n"); ass_set_fonts(priv, default_font, opts->font, 1, config, 1); mp_msg(MSGT_ASS, MSGL_V, "[ass] Done.\n"); talloc_free(default_font); talloc_free(config); }
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; }
dvb_config_t *dvb_get_config(void) { int i, fd, type, size; char filename[30], *conf_file, *name; dvb_channels_list *list; dvb_card_config_t *cards = NULL, *tmp; dvb_config_t *conf = NULL; conf = malloc(sizeof(dvb_config_t)); if(conf == NULL) return NULL; conf->priv = NULL; conf->count = 0; conf->cards = NULL; for(i=0; i<MAX_CARDS; i++) { snprintf(filename, sizeof(filename), "/dev/dvb/adapter%d/frontend0", i); fd = open(filename, O_RDONLY|O_NONBLOCK); if(fd < 0) { mp_msg(MSGT_DEMUX, MSGL_V, "DVB_CONFIG, can't open device %s, skipping\n", filename); continue; } type = dvb_get_tuner_type(fd); close(fd); if(type != TUNER_SAT && type != TUNER_TER && type != TUNER_CBL && type != TUNER_ATSC) { mp_msg(MSGT_DEMUX, MSGL_V, "DVB_CONFIG, can't detect tuner type of card %d, skipping\n", i); continue; } void *talloc_ctx = talloc_new(NULL); conf_file = talloc_steal(talloc_ctx, mp_find_user_config_file("channels.conf")); switch(type) { case TUNER_TER: conf_file = talloc_steal(talloc_ctx, mp_find_user_config_file("channels.conf.ter")); break; case TUNER_CBL: conf_file = talloc_steal(talloc_ctx, mp_find_user_config_file("channels.conf.cbl")); break; case TUNER_SAT: conf_file = talloc_steal(talloc_ctx, mp_find_user_config_file("channels.conf.sat")); break; case TUNER_ATSC: conf_file = talloc_steal(talloc_ctx, mp_find_user_config_file("channels.conf.atsc")); break; } if(conf_file && (access(conf_file, F_OK | R_OK) != 0)) { conf_file = talloc_steal(talloc_ctx, mp_find_user_config_file("channels.conf")); if(conf_file && (access(conf_file, F_OK | R_OK) != 0)) { conf_file = talloc_steal(talloc_ctx, mp_find_global_config_file("channels.conf")); } } list = dvb_get_channels(conf_file, type); talloc_free(talloc_ctx); if(list == NULL) continue; size = sizeof(dvb_card_config_t) * (conf->count + 1); tmp = realloc(conf->cards, size); if(tmp == NULL) { fprintf(stderr, "DVB_CONFIG, can't realloc %d bytes, skipping\n", size); continue; } cards = tmp; name = malloc(20); if(name==NULL) { fprintf(stderr, "DVB_CONFIG, can't realloc 20 bytes, skipping\n"); continue; } conf->cards = cards; conf->cards[conf->count].devno = i; conf->cards[conf->count].list = list; conf->cards[conf->count].type = type; snprintf(name, 20, "DVB-%c card n. %d", type==TUNER_TER ? 'T' : (type==TUNER_CBL ? 'C' : 'S'), conf->count+1); conf->cards[conf->count].name = name; conf->count++; } if(conf->count == 0) { free(conf); conf = NULL; } return conf; }
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]; 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", "idx", "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); // does it end with a subtitle extension? #ifdef CONFIG_ICONV #ifdef CONFIG_ENCA int i = (opts->sub_cp && strncasecmp(opts->sub_cp, "enca", 4) != 0) ? 3 : 0; #else int i = opts->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 && 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; #ifdef CONFIG_ICONV if (i < 4) // prefer UTF-8 coded, or idx over sub (vobsubs) 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 = 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 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; }
int cddb_resolve(const char *dev, char **xmcd_file) { char cddb_cache_dir[] = DEFAULT_CACHE_DIR; char *home_dir = NULL; cddb_data_t cddb_data; void *talloc_ctx = talloc_new(NULL); if (cdtoc_last_track <= 0) { cdtoc_last_track = read_toc(dev); if (cdtoc_last_track < 0) { mp_tmsg(MSGT_OPEN, MSGL_ERR, "Failed to open %s device.\n", dev); return -1; } } cddb_data.tracks = cdtoc_last_track; cddb_data.disc_id = cddb_discid(cddb_data.tracks); cddb_data.anonymous = 1; // Don't send user info by default mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CDDB_DISCID=%08lx\n", cddb_data.disc_id); // Check if there is a CD in the drive // FIXME: That's not really a good way to check if (cddb_data.disc_id == 0) { mp_tmsg(MSGT_DEMUX, MSGL_ERR, "No CD in the drive.\n"); return -1; } home_dir = getenv("HOME"); #ifdef __MINGW32__ if (home_dir == NULL) home_dir = getenv("USERPROFILE"); if (home_dir == NULL) home_dir = getenv("HOMEPATH"); // Last resort, store the cddb cache in the mplayer directory if (home_dir == NULL) home_dir = (char *)talloc_steal(talloc_ctx, mp_find_user_config_file("")); #endif if (home_dir == NULL) { cddb_data.cache_dir = NULL; } else { unsigned len = strlen(home_dir) + strlen(cddb_cache_dir) + 1; cddb_data.cache_dir = malloc(len); if (cddb_data.cache_dir == NULL) { mp_tmsg(MSGT_DEMUX, MSGL_ERR, "Memory allocation failed.\n"); talloc_free(talloc_ctx); return -1; } snprintf(cddb_data.cache_dir, len, "%s%s", home_dir, cddb_cache_dir); } talloc_free(talloc_ctx); // Check for a cached file if (cddb_read_cache(&cddb_data) < 0) { // No Cache found if (cddb_retrieve(&cddb_data) < 0) { return -1; } } if (cddb_data.xmcd_file != NULL) { // printf("%s\n", cddb_data.xmcd_file); *xmcd_file = cddb_data.xmcd_file; return 0; } return -1; }
/* Initialization and runtime control af audio filter instance cmd control command arg argument */ static int control(struct af_instance* af, int cmd, void* arg) { af_export_t* s = af->setup; switch (cmd){ case AF_CONTROL_REINIT:{ int i=0; int mapsize; // Free previous buffers if (s->buf) free(s->buf[0]); // unmap previous area if(s->mmap_area) munmap(s->mmap_area, SIZE_HEADER + (af->data->bps*s->sz*af->data->nch)); // close previous file descriptor if(s->fd) close(s->fd); // Accept only int16_t as input format (which sucks) mp_audio_copy_config(af->data, (struct mp_audio*)arg); mp_audio_set_format(af->data, AF_FORMAT_S16); // If buffer length isn't set, set it to the default value if(s->sz == 0) s->sz = DEF_SZ; // Allocate new buffers (as one continuous block) s->buf[0] = calloc(s->sz*af->data->nch, af->data->bps); if(NULL == s->buf[0]) mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Out of memory\n"); for(i = 1; i < af->data->nch; i++) s->buf[i] = (uint8_t *)s->buf[0] + i*s->sz*af->data->bps; if (!s->filename) { mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] No filename set.\n"); return AF_ERROR; } // Init memory mapping s->fd = open(s->filename, O_RDWR | O_CREAT | O_TRUNC, 0640); mp_msg(MSGT_AFILTER, MSGL_INFO, "[export] Exporting to file: %s\n", s->filename); if(s->fd < 0) { mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Could not open/create file: %s\n", s->filename); return AF_ERROR; } // header + buffer mapsize = (SIZE_HEADER + (af->data->bps * s->sz * af->data->nch)); // grow file to needed size for(i = 0; i < mapsize; i++){ char null = 0; write(s->fd, (void*) &null, 1); } // mmap size s->mmap_area = mmap(0, mapsize, PROT_READ|PROT_WRITE,MAP_SHARED, s->fd, 0); if(s->mmap_area == NULL) mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Could not mmap file %s\n", s->filename); mp_msg(MSGT_AFILTER, MSGL_INFO, "[export] Memory mapped to file: %s (%p)\n", s->filename, s->mmap_area); // Initialize header *((int*)s->mmap_area) = af->data->nch; *((int*)s->mmap_area + 1) = s->sz * af->data->bps * af->data->nch; msync(s->mmap_area, mapsize, MS_ASYNC); // Use test_output to return FALSE if necessary return af_test_output(af, (struct mp_audio*)arg); } case AF_CONTROL_COMMAND_LINE:{ int i=0; char *str = arg; if (!str){ talloc_free(s->filename); s->filename = mp_find_user_config_file(SHARED_FILE); return AF_OK; } while((str[i]) && (str[i] != ':')) i++; talloc_free(s->filename); s->filename = talloc_array_size(NULL, 1, i + 1); memcpy(s->filename, str, i); s->filename[i] = 0; sscanf(str + i + 1, "%d", &(s->sz)); if((s->sz <= 0) || (s->sz > 2048)) mp_msg(MSGT_AFILTER, MSGL_ERR, "[export] Buffer size must be between" " 1 and 2048\n" ); return AF_OK; } } return AF_UNKNOWN; }
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 is_sub_ext(get_ext(bstr0(filename))); } 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 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_subfiles"); 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, "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_dbg(log, "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 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); // 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_user_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; }