int demux_info_add_bstr(demuxer_t *demuxer, struct bstr opt, struct bstr param) { char **info = demuxer->info; int n = 0; for (n = 0; info && info[2 * n] != NULL; n++) { if (!bstrcasecmp(opt, bstr(info[2*n]))) { if (!bstrcmp(param, bstr(info[2*n + 1]))) { mp_msg(MSGT_DEMUX, MSGL_V, "Demuxer info %.*s set to unchanged value %.*s\n", BSTR_P(opt), BSTR_P(param)); return 0; } mp_tmsg(MSGT_DEMUX, MSGL_INFO, "Demuxer info %.*s changed to %.*s\n", BSTR_P(opt), BSTR_P(param)); talloc_free(info[2*n + 1]); info[2*n + 1] = talloc_strndup(demuxer->info, param.start, param.len); return 0; } } info = demuxer->info = talloc_realloc(demuxer, info, char *, 2 * (n + 2)); info[2*n] = talloc_strndup(demuxer->info, opt.start, opt.len); info[2*n + 1] = talloc_strndup(demuxer->info, param.start, param.len); memset(&info[2 * (n + 1)], 0, 2 * sizeof(char *)); return 1; }
// Runs charset auto-detection on the input buffer, and returns the result. // If auto-detection fails, NULL is returned. // If user_cp doesn't refer to any known auto-detection (for example because // it's a real iconv codepage), user_cp is returned without even looking at // the buf data. const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp, int flags) { if (!mp_charset_requires_guess(user_cp)) return user_cp; // Do our own UTF-8 detection, because at least ENCA seems to get it // wrong sometimes (suggested by divVerent). int r = bstr_validate_utf8(buf); if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF))) return "UTF-8"; bstr params[3] = {{0}}; split_colon(user_cp, 3, params); bstr type = params[0]; char lang[100]; snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1])); const char *fallback = params[2].start; // last item, already 0-terminated const char *res = NULL; #if HAVE_ENCA if (bstrcasecmp0(type, "enca") == 0) res = enca_guess(log, buf, lang); #endif #if HAVE_LIBGUESS if (bstrcasecmp0(type, "guess") == 0) res = libguess_guess(log, buf, lang); #endif if (bstrcasecmp0(type, "utf8") == 0 || bstrcasecmp0(type, "utf-8") == 0) { if (!fallback) fallback = params[1].start; // must be already 0-terminated } if (res) { mp_dbg(log, "%.*s detected charset: '%s'\n", BSTR_P(type), res); } else { res = fallback; mp_dbg(log, "Detection with %.*s failed: fallback to %s\n", BSTR_P(type), res && res[0] ? res : "broken UTF-8/Latin1"); } if (!res && !(flags & MP_STRICT_UTF8)) res = "UTF-8-BROKEN"; return res; }
static int parse_ref_init(struct pl_parser *p) { bstr line = bstr_strip(pl_get_line(p)); if (!bstr_equals0(line, "[Reference]")) return -1; // ASF http streaming redirection - this is needed because ffmpeg http:// // and mmsh:// can not automatically switch automatically between each // others. Both protocols use http - MMSH requires special http headers // to "activate" it, and will in other cases return this playlist. static const char *const mmsh_types[] = {"audio/x-ms-wax", "audio/x-ms-wma", "video/x-ms-asf", "video/x-ms-afs", "video/x-ms-wmv", "video/x-ms-wma", "application/x-mms-framed", "application/vnd.ms.wms-hdr.asfv1", NULL}; bstr burl = bstr0(p->s->url); if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) { MP_INFO(p, "Redirecting to mmsh://\n"); playlist_add_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl))); return 0; } while (!pl_eof(p)) { line = bstr_strip(pl_get_line(p)); if (bstr_case_startswith(line, bstr0("Ref"))) { bstr_split_tok(line, "=", &(bstr){0}, &line); if (line.len) pl_add(p, line); } } return 0; }
static int validate_backend_opt(const m_option_t *opt, struct bstr name, struct bstr param) { char s[20]; snprintf(s, sizeof(s), "%.*s", BSTR_P(param)); return mpgl_find_backend(s) >= -1 ? 1 : M_OPT_INVALID; }
// Returns true if more args, false if all parsed or an error occurred. static bool split_opt(struct parse_state *p) { int r = split_opt_silent(p); if (r >= 0) return r == 0; p->error = true; if (r == -2) mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "A no-* option can't take parameters: --%.*s=%.*s\n", BSTR_P(p->arg), BSTR_P(p->param)); else if (r == -3) mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s needs a parameter.\n", BSTR_P(p->arg)); else mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Unknown option on the command line: %.*s\n", BSTR_P(p->arg)); return false; }
// Returns true if more args, false if all parsed or an error occurred. static bool split_opt(struct parse_state *p) { int r = split_opt_silent(p); if (r >= 0) return r == 0; p->error = true; MP_FATAL(p->config, "Error parsing command line option '%.*s': %s\n", BSTR_P(p->arg), m_option_strerror(r)); return false; }
// Returns true if more args, false if all parsed or an error occurred. static bool split_opt(struct parse_state *p) { int r = split_opt_silent(p); if (r >= 0) return r == 0; p->error = true; mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL, "Error parsing commandline option %.*s: %s\n", BSTR_P(p->arg), m_option_strerror(r)); return false; }
static void mp_auto_load_profile(struct MPContext *mpctx, char *category, bstr item) { if (!item.len) return; char t[512]; snprintf(t, sizeof(t), "%s.%.*s", category, BSTR_P(item)); m_profile_t *p = m_config_get_profile0(mpctx->mconfig, t); if (p) { MP_INFO(mpctx, "Auto-loading profile '%s'\n", t); m_config_set_profile(mpctx->mconfig, p, FILE_LOCAL_FLAGS); } }
static void add_term_osd_bar(struct MPContext *mpctx, char **line, int width) { struct MPOpts *opts = mpctx->opts; if (width < 5) return; int pos = get_current_pos_ratio(mpctx, false) * (width - 3); pos = MPCLAMP(pos, 0, width - 3); bstr chars = bstr0(opts->term_osd_bar_chars); bstr parts[5]; for (int n = 0; n < 5; n++) parts[n] = bstr_split_utf8(chars, &chars); saddf(line, "\r%.*s", BSTR_P(parts[0])); for (int n = 0; n < pos; n++) saddf(line, "%.*s", BSTR_P(parts[1])); saddf(line, "%.*s", BSTR_P(parts[2])); for (int n = 0; n < width - 3 - pos; n++) saddf(line, "%.*s", BSTR_P(parts[3])); saddf(line, "%.*s", BSTR_P(parts[4])); }
int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt, struct bstr name, struct bstr param) { if (bstr_equals0(param, "help")) { mp_info(log, "OpenGL windowing backends:\n"); mp_info(log, " auto (autodetect)\n"); for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) mp_info(log, " %s\n", backends[n]->name); return M_OPT_EXIT; } char s[20]; snprintf(s, sizeof(s), "%.*s", BSTR_P(param)); return mpgl_find_backend(s) >= -1 ? 1 : M_OPT_INVALID; }
// Runs charset auto-detection on the input buffer, and returns the result. // If auto-detection fails, NULL is returned. // If user_cp doesn't refer to any known auto-detection (for example because // it's a real iconv codepage), user_cp is returned without even looking at // the buf data. const char *mp_charset_guess(bstr buf, const char *user_cp) { if (!mp_charset_requires_guess(user_cp)) return user_cp; bstr params[3] = {{0}}; split_colon(user_cp, 3, params); bstr type = params[0]; char lang[100]; snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1])); const char *fallback = params[2].start; // last item, already 0-terminated const char *res = NULL; #ifdef CONFIG_ENCA if (bstrcasecmp0(type, "enca") == 0) res = enca_guess(buf, lang); #endif #ifdef CONFIG_LIBGUESS if (bstrcasecmp0(type, "guess") == 0) res = libguess_guess(buf, lang); #endif if (res) { mp_msg(MSGT_SUBREADER, MSGL_DBG2, "%.*s detected charset: '%s'\n", BSTR_P(type), res); } else { res = fallback; mp_msg(MSGT_SUBREADER, MSGL_DBG2, "Detection with %.*s failed: fallback to %s\n", BSTR_P(type), res && res[0] ? res : "no conversion"); } return res; }
int m_struct_set(const m_struct_t *st, void *obj, const char *field, struct bstr param) { const m_option_t* f = m_struct_get_field(st,field); if(!f) { mp_msg(MSGT_CFGPARSER, MSGL_ERR,"Struct %s doesn't have any %s field\n", st->name,field); return 0; } if(f->type->parse(f, bstr0(field), param, M_ST_MB_P(obj,f->p)) < 0) { mp_msg(MSGT_CFGPARSER, MSGL_ERR,"Struct %s, field %s parsing error: %.*s\n", st->name, field, BSTR_P(param)); return 0; } return 1; }
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged( IMMNotificationClient *This, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { change_notify *change = (change_notify *)This; struct ao *ao = change->ao; // don't care about "eCapture" or non-"eMultimedia" roles if (flow == eCapture || role != eMultimedia) return S_OK; if (change->is_hotplug) { MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: sending hotplug event\n"); ao_hotplug_event(ao); } else { // stay on the device the user specified bstr device = wasapi_get_specified_device_string(ao); if (device.len) { MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: " "staying on specified device %.*s\n", BSTR_P(device)); return S_OK; } // don't reload if already on the new default if (pwstrDeviceId && !wcscmp(pwstrDeviceId, change->monitored)) { MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: " "already using default device, no reload required\n"); return S_OK; } // if we got here, we need to reload MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: requesting ao reload\n"); ao_request_reload(ao); } return S_OK; }
// returns M_OPT_... error code int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, struct mpv_global *global, char **argv) { int ret = M_OPT_UNKNOWN; int mode = 0; struct playlist_entry *local_start = NULL; int local_params_count = 0; struct playlist_param *local_params = 0; assert(config != NULL); mode = GLOBAL; struct parse_state p = {config, argv}; while (split_opt(&p)) { if (p.is_opt) { int flags = M_SETOPT_FROM_CMDLINE; if (mode == LOCAL) flags |= M_SETOPT_BACKUP | M_SETOPT_CHECK_ONLY; int r = m_config_set_option_ext(config, p.arg, p.param, flags); if (r == M_OPT_EXIT) { ret = r; goto err_out; } else if (r < 0) { MP_FATAL(config, "Setting command line option '--%.*s=%.*s' failed.\n", BSTR_P(p.arg), BSTR_P(p.param)); goto err_out; } // Handle some special arguments outside option parser. if (!bstrcmp0(p.arg, "{")) { if (mode != GLOBAL) { MP_ERR(config, "'--{' can not be nested.\n"); goto err_out; } mode = LOCAL; assert(!local_start); local_start = files->last; continue; } if (!bstrcmp0(p.arg, "}")) { if (mode != LOCAL) { MP_ERR(config, "Too many closing '--}'.\n"); goto err_out; } if (local_params_count) { // The files added between '{' and '}' are the entries from // the entry _after_ local_start, until the end of the list. // If local_start is NULL, the list was empty on '{', and we // want all files in the list. struct playlist_entry *cur = local_start ? local_start->next : files->first; if (!cur) MP_WARN(config, "Ignored options!\n"); while (cur) { playlist_entry_add_params(cur, local_params, local_params_count); cur = cur->next; } } local_params_count = 0; mode = GLOBAL; m_config_restore_backups(config); local_start = NULL; continue; } if (bstrcmp0(p.arg, "playlist") == 0) { // append the playlist to the local args char *param0 = bstrdup0(NULL, p.param); struct playlist *pl = playlist_parse_file(param0, global); talloc_free(param0); if (!pl) { MP_FATAL(config, "Error reading playlist '%.*s'\n", BSTR_P(p.param)); goto err_out; } playlist_transfer_entries(files, pl); talloc_free(pl); continue; } if (mode == LOCAL) { MP_TARRAY_APPEND(NULL, local_params, local_params_count, (struct playlist_param) {p.arg, p.param}); }
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; }
static char *old_rar_volume_url(void *ctx, const char *format, struct bstr base, int index) { return talloc_asprintf(ctx, format, BSTR_P(base), 'r' + index / 100, index % 100); }
static char *standard_volume_url(void *ctx, const char *format, struct bstr base, int index) { return talloc_asprintf(ctx, format, BSTR_P(base), index); }
bool m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, int argc, char **argv) { int mode = 0; struct playlist_entry *local_start = NULL; bool shuffle = false; int local_params_count = 0; struct playlist_param *local_params = 0; assert(config != NULL); assert(!config->file_local_mode); config->mode = M_COMMAND_LINE; mode = GLOBAL; #ifdef CONFIG_MACOSX_FINDER if (macosx_finder_args(config, files, argc, argv)) return true; #endif struct parse_state p = {config, argc, argv}; while (split_opt(&p)) { if (p.mp_opt) { int r; if (mode == GLOBAL && !(p.mp_opt->flags & M_OPT_PRE_PARSE)) { r = m_config_set_option(config, p.arg, p.param); } else { r = m_config_check_option(config, p.arg, p.param); } if (r <= M_OPT_EXIT) goto err_out; if (r < 0) { char *msg = m_option_strerror(r); if (!msg) goto print_err; mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL, "Error parsing commandline option %.*s: %s\n", BSTR_P(p.arg), msg); goto err_out; } // Handle some special arguments outside option parser. if (!bstrcmp0(p.arg, "{")) { if (mode != GLOBAL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "'--{' can not be nested.\n"); goto err_out; } mode = LOCAL; // Needed for option checking. m_config_enter_file_local(config); assert(!local_start); local_start = files->last; continue; } if (!bstrcmp0(p.arg, "}")) { if (mode != LOCAL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Too many closing '--}'.\n"); goto err_out; } if (local_params_count) { // The files added between '{' and '}' are the entries from // the entry _after_ local_start, until the end of the list. // If local_start is NULL, the list was empty on '{', and we // want all files in the list. struct playlist_entry *cur = local_start ? local_start->next : files->first; if (!cur) mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Ignored options!\n"); while (cur) { playlist_entry_add_params(cur, local_params, local_params_count); cur = cur->next; } } local_params_count = 0; mode = GLOBAL; m_config_leave_file_local(config); local_start = NULL; shuffle = false; continue; } if (bstrcmp0(p.arg, "shuffle") == 0) { shuffle = parse_flag(p.arg, p.param); continue; } if (bstrcmp0(p.arg, "playlist") == 0) { // append the playlist to the local args char *param0 = bstrdup0(NULL, p.param); struct playlist *pl = playlist_parse_file(param0); talloc_free(param0); if (!pl) goto print_err; playlist_transfer_entries(files, pl); talloc_free(pl); continue; } if (mode == LOCAL) { MP_TARRAY_APPEND(NULL, local_params, local_params_count, (struct playlist_param) {p.arg, p.param}); }
dvb_state_t *dvb_get_state(stream_t *stream) { if (global_dvb_state != NULL) { return global_dvb_state; } struct mp_log *log = stream->log; struct mpv_global *global = stream->global; stream->priv = mp_get_config_group(stream, stream->global, &stream_dvb_conf); dvb_priv_t *priv = stream->priv; int type, size; char filename[30], *name; dvb_channels_list *list; dvb_card_config_t *cards = NULL, *tmp; dvb_state_t *state = NULL; bstr prog, card; if (!bstr_split_tok(bstr0(stream->path), "@", &card, &prog)) { prog = card; card.len = 0; } if (prog.len) { talloc_free(priv->cfg_prog); priv->cfg_prog = bstrto0(NULL, prog); } if (card.len) { bstr r; priv->cfg_card = bstrtoll(card, &r, 0); if (r.len || priv->cfg_card < 1 || priv->cfg_card > 4) { MP_ERR(stream, "invalid card: '%.*s'\n", BSTR_P(card)); return NULL; } } state = malloc(sizeof(dvb_state_t)); if (state == NULL) return NULL; state->count = 0; state->switching_channel = false; state->stream_used = true; state->cards = NULL; state->fe_fd = state->dvr_fd = -1; for (int i = 0; i < MAX_CARDS; i++) { snprintf(filename, sizeof(filename), "/dev/dvb/adapter%d/frontend0", i); int fd = open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { mp_verbose(log, "DVB_CONFIG, can't open device %s, skipping\n", filename); continue; } mp_verbose(log, "Opened device %s, FD: %d\n", filename, fd); int* tuner_types = NULL; int num_tuner_types = dvb_get_tuner_types(fd, log, &tuner_types); close(fd); mp_verbose(log, "Frontend device %s offers %d supported delivery systems.\n", filename, num_tuner_types); for (int num_tuner_type=0; num_tuner_type<num_tuner_types; num_tuner_type++) { type = tuner_types[num_tuner_type]; if (type != TUNER_SAT && type != TUNER_TER && type != TUNER_CBL && type != TUNER_ATSC) { mp_verbose(log, "DVB_CONFIG, can't detect tuner type of " "card %d, skipping\n", i); continue; } void *talloc_ctx = talloc_new(NULL); char *conf_file = NULL; if (priv->cfg_file && priv->cfg_file[0]) conf_file = priv->cfg_file; else { switch (type) { case TUNER_TER: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.ter"); break; case TUNER_CBL: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.cbl"); break; case TUNER_SAT: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.sat"); break; case TUNER_ATSC: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.atsc"); break; } if (conf_file) { mp_verbose(log, "Ignoring other channels.conf files.\n"); } else { conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf"); } } list = dvb_get_channels(log, priv->cfg_full_transponder, conf_file, type); talloc_free(talloc_ctx); if (list == NULL) continue; size = sizeof(dvb_card_config_t) * (state->count + 1); tmp = realloc(state->cards, size); if (tmp == NULL) { mp_err(log, "DVB_CONFIG, can't realloc %d bytes, skipping\n", size); free(list); continue; } cards = tmp; name = malloc(20); if (name == NULL) { mp_err(log, "DVB_CONFIG, can't realloc 20 bytes, skipping\n"); free(list); free(tmp); continue; } state->cards = cards; state->cards[state->count].devno = i; state->cards[state->count].list = list; state->cards[state->count].type = type; snprintf(name, 20, "DVB-%c card n. %d", type == TUNER_TER ? 'T' : (type == TUNER_CBL ? 'C' : 'S'), state->count + 1); state->cards[state->count].name = name; state->count++; } talloc_free(tuner_types); } if (state->count == 0) { free(state); state = NULL; } global_dvb_state = state; return state; }
// Runs charset auto-detection on the input buffer, and returns the result. // If auto-detection fails, NULL is returned. // If user_cp doesn't refer to any known auto-detection (for example because // it's a real iconv codepage), user_cp is returned without even looking at // the buf data. // The return value may (but doesn't have to) be allocated under talloc_ctx. const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf, const char *user_cp, int flags) { if (!mp_charset_requires_guess(user_cp)) return user_cp; bool use_auto = strcasecmp(user_cp, "auto") == 0; if (use_auto) { #if HAVE_UCHARDET user_cp = "uchardet"; #elif HAVE_ENCA user_cp = "enca"; #else user_cp = "UTF-8:UTF-8-BROKEN"; #endif } bstr params[3] = {{0}}; split_colon(user_cp, 3, params); bstr type = params[0]; char lang[100]; snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1])); const char *fallback = params[2].start; // last item, already 0-terminated const char *res = NULL; if (use_auto) { res = ms_bom_guess(buf); if (res) type = bstr0("auto"); } #if HAVE_ENCA if (bstrcasecmp0(type, "enca") == 0) res = enca_guess(log, buf, lang); #endif #if HAVE_LIBGUESS if (bstrcasecmp0(type, "guess") == 0) res = libguess_guess(log, buf, lang); #endif #if HAVE_UCHARDET if (bstrcasecmp0(type, "uchardet") == 0) res = mp_uchardet(talloc_ctx, log, buf); #endif if (bstrcasecmp0(type, "utf8") == 0 || bstrcasecmp0(type, "utf-8") == 0) { if (!fallback) fallback = params[1].start; // must be already 0-terminated int r = bstr_validate_utf8(buf); if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF))) res = "utf-8"; } if (res) { mp_dbg(log, "%.*s detected charset: '%s'\n", BSTR_P(type), res); } else { res = fallback; mp_dbg(log, "Detection with %.*s failed: fallback to %s\n", BSTR_P(type), res && res[0] ? res : "broken UTF-8/Latin1"); } if (!res && !(flags & MP_STRICT_UTF8)) res = "UTF-8-BROKEN"; mp_verbose(log, "Using charset '%s'.\n", res); return res; }
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; }
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; }
// Load options and profiles from from a config file. // conffile: path to the config file // initial_section: default section where to add normal options // flags: M_SETOPT_* bits // returns: 1 on sucess, -1 on error, 0 if file not accessible. int m_config_parse_config_file(m_config_t *config, const char *conffile, char *initial_section, int flags) { #define PRINT_LINENUM MP_ERR(config, "%s:%d: ", conffile, line_num) #define MAX_LINE_LEN 10000 #define MAX_OPT_LEN 1000 #define MAX_PARAM_LEN 1500 FILE *fp = NULL; char *line = NULL; char opt[MAX_OPT_LEN + 1]; char param[MAX_PARAM_LEN + 1]; char c; /* for the "" and '' check */ int tmp; int line_num = 0; int line_pos; /* line pos */ int opt_pos; /* opt pos */ int param_pos; /* param pos */ int ret = 1; int errors = 0; m_profile_t *profile = m_config_add_profile(config, initial_section); flags = flags | M_SETOPT_FROM_CONFIG_FILE; MP_VERBOSE(config, "Reading config file %s\n", conffile); if (config->recursion_depth > MAX_RECURSION_DEPTH) { MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n"); ret = -1; goto out; } if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) { ret = -1; goto out; } else MP_VERBOSE(config, "\n"); if ((fp = fopen(conffile, "r")) == NULL) { MP_VERBOSE(config, "Can't open config file: %s\n", strerror(errno)); ret = 0; goto out; } while (fgets(line, MAX_LINE_LEN, fp)) { if (errors >= 16) { MP_FATAL(config, "too many errors\n"); goto out; } line_num++; line_pos = 0; /* skip BOM */ if (strncmp(line, "\xEF\xBB\xBF", 3) == 0) line_pos += 3; /* skip whitespaces */ while (mp_isspace(line[line_pos])) ++line_pos; /* EOL / comment */ if (line[line_pos] == '\0' || line[line_pos] == '#') continue; /* read option. */ for (opt_pos = 0; mp_isprint(line[line_pos]) && line[line_pos] != ' ' && line[line_pos] != '#' && line[line_pos] != '='; /* NOTHING */) { opt[opt_pos++] = line[line_pos++]; if (opt_pos >= MAX_OPT_LEN) { PRINT_LINENUM; MP_ERR(config, "option name too long\n"); errors++; ret = -1; goto nextline; } } if (opt_pos == 0) { PRINT_LINENUM; MP_ERR(config, "parse error\n"); ret = -1; errors++; continue; } opt[opt_pos] = '\0'; /* Profile declaration */ if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') { opt[opt_pos - 1] = '\0'; profile = m_config_add_profile(config, opt + 1); continue; } /* skip whitespaces */ while (mp_isspace(line[line_pos])) ++line_pos; param_pos = 0; bool param_set = false; /* check '=' */ if (line[line_pos] == '=') { line_pos++; param_set = true; /* whitespaces... */ while (mp_isspace(line[line_pos])) ++line_pos; /* read the parameter */ if (line[line_pos] == '"' || line[line_pos] == '\'') { c = line[line_pos]; ++line_pos; for (param_pos = 0; line[line_pos] != c; /* NOTHING */) { param[param_pos++] = line[line_pos++]; if (param_pos >= MAX_PARAM_LEN) { PRINT_LINENUM; MP_ERR(config, "option %s has a too long parameter\n", opt); ret = -1; errors++; goto nextline; } } line_pos++; /* skip the closing " or ' */ goto param_done; } if (line[line_pos] == '%') { char *start = &line[line_pos + 1]; char *end = start; unsigned long len = strtoul(start, &end, 10); if (start != end && end[0] == '%') { if (len >= MAX_PARAM_LEN - 1 || strlen(end + 1) < len) { PRINT_LINENUM; MP_ERR(config, "bogus %% length\n"); ret = -1; errors++; goto nextline; } param_pos = snprintf(param, sizeof(param), "%.*s", (int)len, end + 1); line_pos += 1 + (end - start) + 1 + len; goto param_done; } } for (param_pos = 0; mp_isprint(line[line_pos]) && !mp_isspace(line[line_pos]) && line[line_pos] != '#'; /* NOTHING */) { param[param_pos++] = line[line_pos++]; if (param_pos >= MAX_PARAM_LEN) { PRINT_LINENUM; MP_ERR(config, "too long parameter\n"); ret = -1; errors++; goto nextline; } } param_done: while (mp_isspace(line[line_pos])) ++line_pos; } param[param_pos] = '\0'; /* EOL / comment */ if (line[line_pos] != '\0' && line[line_pos] != '#') { PRINT_LINENUM; MP_ERR(config, "extra characters: %s\n", line + line_pos); ret = -1; } bstr bopt = bstr0(opt); bstr bparam = bstr0(param); if (bopt.len >= 3) bstr_eatstart0(&bopt, "--"); if (profile && bstr_equals0(bopt, "profile-desc")) { m_profile_set_desc(profile, bparam); goto nextline; } bool need_param = m_config_option_requires_param(config, bopt) > 0; if (need_param && !param_set) { PRINT_LINENUM; MP_ERR(config, "error parsing option %.*s=%.*s: %s\n", BSTR_P(bopt), BSTR_P(bparam), m_option_strerror(M_OPT_MISSING_PARAM)); continue; } if (profile) { tmp = m_config_set_profile_option(config, profile, bopt, bparam); } else { tmp = m_config_set_option_ext(config, bopt, bparam, flags); } if (tmp < 0) { PRINT_LINENUM; MP_ERR(config, "setting option %.*s='%.*s' failed.\n", BSTR_P(bopt), BSTR_P(bparam)); continue; /* break */ } nextline: ; } out: free(line); if (fp) fclose(fp); config->recursion_depth -= 1; if (ret < 0) { MP_FATAL(config, "Error loading config file %s.\n", conffile); } return ret; }
static int demux_open_tv(demuxer_t *demuxer, enum demux_check check) { tvi_handle_t *tvh; const tvi_functions_t *funcs; if (check > DEMUX_CHECK_REQUEST || demuxer->stream->type != STREAMTYPE_TV) return -1; tv_param_t *params = mp_get_config_group(demuxer, demuxer->global, &tv_params_conf); bstr urlparams = bstr0(demuxer->stream->path); bstr channel, input; bstr_split_tok(urlparams, "/", &channel, &input); if (channel.len) { talloc_free(params->channels); params->channel = bstrto0(NULL, channel); } if (input.len) { bstr r; params->input = bstrtoll(input, &r, 0); if (r.len) { MP_ERR(demuxer->stream, "invalid input: '%.*s'\n", BSTR_P(input)); return -1; } } assert(demuxer->priv==NULL); if(!(tvh=tv_begin(params, demuxer->log))) return -1; if (!tvh->functions->init(tvh->priv)) return -1; tvh->demuxer = demuxer; if (!open_tv(tvh)){ tv_uninit(tvh); return -1; } funcs = tvh->functions; demuxer->priv=tvh; struct sh_stream *sh_v = demux_alloc_sh_stream(STREAM_VIDEO); /* get IMAGE FORMAT */ int fourcc; funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FORMAT, &fourcc); if (fourcc == MP_FOURCC_MJPEG) { sh_v->codec->codec = "mjpeg"; } else { sh_v->codec->codec = "rawvideo"; sh_v->codec->codec_tag = fourcc; } /* set FPS and FRAMETIME */ if(!sh_v->codec->fps) { float tmp; if (funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FPS, &tmp) != TVI_CONTROL_TRUE) sh_v->codec->fps = 25.0f; /* on PAL */ else sh_v->codec->fps = tmp; } if (tvh->tv_param->fps != -1.0f) sh_v->codec->fps = tvh->tv_param->fps; /* If playback only mode, go to immediate mode, fail silently */ if(tvh->tv_param->immediate == 1) { funcs->control(tvh->priv, TVI_CONTROL_IMMEDIATE, 0); tvh->tv_param->audio = 0; } /* set width */ funcs->control(tvh->priv, TVI_CONTROL_VID_GET_WIDTH, &sh_v->codec->disp_w); /* set height */ funcs->control(tvh->priv, TVI_CONTROL_VID_GET_HEIGHT, &sh_v->codec->disp_h); demux_add_sh_stream(demuxer, sh_v); demuxer->seekable = 0; /* here comes audio init */ if (tvh->tv_param->audio && funcs->control(tvh->priv, TVI_CONTROL_IS_AUDIO, 0) == TVI_CONTROL_TRUE) { int audio_format; /* yeah, audio is present */ funcs->control(tvh->priv, TVI_CONTROL_AUD_SET_SAMPLERATE, &tvh->tv_param->audiorate); if (funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_FORMAT, &audio_format) != TVI_CONTROL_TRUE) goto no_audio; switch(audio_format) { // This is the only format any of the current inputs generate. case AF_FORMAT_S16: break; default: MP_ERR(tvh, "Audio type '%s' unsupported!\n", af_fmt_to_str(audio_format)); goto no_audio; } struct sh_stream *sh_a = demux_alloc_sh_stream(STREAM_AUDIO); funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_SAMPLERATE, &sh_a->codec->samplerate); int nchannels = sh_a->codec->channels.num; funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_CHANNELS, &nchannels); mp_chmap_from_channels(&sh_a->codec->channels, nchannels); // s16ne mp_set_pcm_codec(sh_a->codec, true, false, 16, BYTE_ORDER == BIG_ENDIAN); demux_add_sh_stream(demuxer, sh_a); MP_VERBOSE(tvh, " TV audio: %d channels, %d bits, %d Hz\n", nchannels, 16, sh_a->codec->samplerate); } no_audio: if(!(funcs->start(tvh->priv))){ // start failed :( tv_uninit(tvh); return -1; } /* set color eq */ tv_set_color_options(tvh, TV_COLOR_BRIGHTNESS, tvh->tv_param->brightness); tv_set_color_options(tvh, TV_COLOR_HUE, tvh->tv_param->hue); tv_set_color_options(tvh, TV_COLOR_SATURATION, tvh->tv_param->saturation); tv_set_color_options(tvh, TV_COLOR_CONTRAST, tvh->tv_param->contrast); if(tvh->tv_param->gain!=-1) if(funcs->control(tvh->priv,TVI_CONTROL_VID_SET_GAIN,&tvh->tv_param->gain)!=TVI_CONTROL_TRUE) MP_WARN(tvh, "Unable to set gain control!\n"); return 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; }