int wmain(int argc, wchar_t *argv[]) { // If started from the console wrapper (see osdep/win32-console-wrapper.c), // attach to the console and set up the standard IO handles bool has_console = terminal_try_attach(); // If mpv is started from Explorer, the Run dialog or the Start Menu, it // will have no console and no standard IO handles. In this case, the user // is expecting mpv to show some UI, so enable the pseudo-GUI profile. bool gui = !has_console && !has_redirected_stdio(); int argv_len = 0; char **argv_u8 = NULL; // Build mpv's UTF-8 argv, and add the pseudo-GUI profile if necessary if (argv[0]) MP_TARRAY_APPEND(NULL, argv_u8, argv_len, mp_to_utf8(argv_u8, argv[0])); if (gui) MP_TARRAY_APPEND(NULL, argv_u8, argv_len, "--profile=pseudo-gui"); for (int i = 1; i < argc; i++) MP_TARRAY_APPEND(NULL, argv_u8, argv_len, mp_to_utf8(argv_u8, argv[i])); MP_TARRAY_APPEND(NULL, argv_u8, argv_len, NULL); int ret = mpv_main(argv_len - 1, argv_u8); talloc_free(argv_u8); return ret; }
static void vsmap_to_table(lua_State *L, int index, VSMap *map) { struct vf_instance *vf = get_vf(L); struct vf_priv_s *p = vf->priv; const VSAPI *vsapi = p->vsapi; for (int n = 0; n < vsapi->propNumKeys(map); n++) { const char *key = vsapi->propGetKey(map, n); VSPropTypes t = vsapi->propGetType(map, key); switch (t) { case ptInt: lua_pushnumber(L, vsapi->propGetInt(map, key, 0, NULL)); break; case ptFloat: lua_pushnumber(L, vsapi->propGetFloat(map, key, 0, NULL)); break; case ptNode: { VSNodeRef *r = vsapi->propGetNode(map, key, 0, NULL); MP_TARRAY_APPEND(p, p->gc_noderef, p->num_gc_noderef, r); lua_pushlightuserdata(L, r); break; } default: luaL_error(L, "unknown map type"); } lua_setfield(L, index, key); } }
static bstr read_file(struct mp_log *log, const char *filename) { FILE *f = fopen(filename, "rb"); if (!f) { mp_verbose(log, "Can't open config file: %s\n", mp_strerror(errno)); return (bstr){0}; } char *data = talloc_array(NULL, char, 0); size_t size = 0; while (1) { size_t left = talloc_get_size(data) - size; if (!left) { MP_TARRAY_GROW(NULL, data, size + 1); continue; } size_t s = fread(data + size, 1, left, f); if (!s) { if (ferror(f)) mp_err(log, "Error reading config file.\n"); fclose(f); MP_TARRAY_APPEND(NULL, data, size, 0); return (bstr){data, size - 1}; } size += s; } assert(0); }
static int open_file(struct demuxer *demuxer, enum demux_check check) { if (stream_get_size(demuxer->stream) == 0) return -1; int flags = 0; if (check <= DEMUX_CHECK_REQUEST) flags |= MP_ARCHIVE_FLAG_UNSAFE; struct mp_archive *mpa = mp_archive_new(demuxer->log, demuxer->stream, flags); if (!mpa) return -1; struct playlist *pl = talloc_zero(demuxer, struct playlist); demuxer->playlist = pl; // make it load archive:// pl->disable_safety = true; char *prefix = mp_url_escape(mpa, demuxer->stream->url, "~|"); char **files = NULL; int num_files = 0; for (;;) { struct archive_entry *entry; int r = archive_read_next_header(mpa->arch, &entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) MP_ERR(demuxer, "libarchive: %s\n", archive_error_string(mpa->arch)); if (r < ARCHIVE_WARN) break; if (archive_entry_filetype(entry) != AE_IFREG) continue; const char *fn = archive_entry_pathname(entry); // Some archives may have no filenames. if (!fn) fn = talloc_asprintf(mpa, "mpv_unknown#%d\n", num_files); // stream_libarchive.c does the real work char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix, fn); MP_TARRAY_APPEND(mpa, files, num_files, f); } if (files) qsort(files, num_files, sizeof(files[0]), cmp_filename); for (int n = 0; n < num_files; n++) playlist_add_file(pl, files[n]); demuxer->filetype = "archive"; demuxer->fully_read = true; mp_archive_free(mpa); return 0; }
static void build_timeline(struct timeline *tl) { struct priv *p = tl->demuxer->priv; void *ctx = talloc_new(NULL); add_source(tl, tl->demuxer); struct cue_track *tracks = NULL; size_t track_count = 0; for (size_t n = 0; n < p->f->num_tracks; n++) { struct cue_track *track = &p->f->tracks[n]; if (track->filename) { MP_TARRAY_APPEND(ctx, tracks, track_count, *track); } else { MP_WARN(tl->demuxer, "No file specified for track entry %zd. " "It will be removed\n", n + 1); } } if (track_count == 0) { MP_ERR(tl, "CUE: no tracks found!\n"); goto out; } // Remove duplicate file entries. This might be too sophisticated, since // CUE files usually use either separate files for every single track, or // only one file for all tracks. char **files = 0; size_t file_count = 0; for (size_t n = 0; n < track_count; n++) { struct cue_track *track = &tracks[n]; track->source = -1; for (size_t file = 0; file < file_count; file++) { if (strcmp(files[file], track->filename) == 0) { track->source = file; break; } } if (track->source == -1) { file_count++; files = talloc_realloc(ctx, files, char *, file_count); files[file_count - 1] = track->filename; track->source = file_count - 1; } }
static int l_invoke(lua_State *L) { struct vf_instance *vf = get_vf(L); struct vf_priv_s *p = vf->priv; const VSAPI *vsapi = p->vsapi; VSPlugin *plugin = vsapi->getPluginByNs(luaL_checkstring(L, 1), p->vscore); if (!plugin) luaL_error(L, "plugin not found"); VSMap *map = table_to_vsmap(L, 3); VSMap *r = vsapi->invoke(plugin, luaL_checkstring(L, 2), map); MP_TARRAY_APPEND(p, p->gc_map, p->num_gc_map, r); if (!r) luaL_error(L, "?"); const char *err = vsapi->getError(r); if (err) luaL_error(L, "error calling invoke(): %s", err); int err2 = 0; VSNodeRef *node = vsapi->propGetNode(r, "clip", 0, &err2); MP_TARRAY_APPEND(p, p->gc_noderef, p->num_gc_noderef, node); if (node) lua_pushlightuserdata(L, node); return 1; }
static VSMap *table_to_vsmap(lua_State *L, int index) { struct vf_instance *vf = get_vf(L); struct vf_priv_s *p = vf->priv; const VSAPI *vsapi = p->vsapi; assert(index > 0); VSMap *map = vsapi->createMap(); MP_TARRAY_APPEND(p, p->gc_map, p->num_gc_map, map); if (!map) luaL_error(L, "out of memory"); lua_pushnil(L); // nil while (lua_next(L, index) != 0) { // key value if (lua_type(L, -2) != LUA_TSTRING) { luaL_error(L, "key must be a string, but got %s", lua_typename(L, -2)); } const char *key = lua_tostring(L, -2); switch (lua_type(L, -1)) { case LUA_TNUMBER: { // gross hack because we hate everything if (strncmp(key, "i_", 2) == 0) { vsapi->propSetInt(map, key + 2, lua_tointeger(L, -1), 0); } else { vsapi->propSetFloat(map, key, lua_tonumber(L, -1), 0); } break; } case LUA_TSTRING: { const char *s = lua_tostring(L, -1); vsapi->propSetData(map, key, s, strlen(s), 0); break; } case LUA_TLIGHTUSERDATA: { // assume it's VSNodeRef* VSNodeRef *node = lua_touserdata(L, -1); vsapi->propSetNode(map, key, node, 0); break; } default: luaL_error(L, "unknown type"); break; } lua_pop(L, 1); // key } return map; }
static int demux_open_mf(demuxer_t *demuxer, enum demux_check check) { mf_t *mf; if (strncmp(demuxer->stream->url, "mf://", 5) == 0 && demuxer->stream->type == STREAMTYPE_MF) mf = open_mf_pattern(demuxer, demuxer->log, demuxer->stream->url + 5); else { mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url); int bog = 0; MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream); } if (!mf || mf->nr_of_files < 1) goto error; char *force_type = demuxer->opts->mf_type; const char *codec = mp_map_mimetype_to_video_codec(demuxer->stream->mime_type); if (!codec || (force_type && force_type[0])) codec = probe_format(mf, force_type, check); if (!codec) goto error; mf->curr_frame = 0; // create a new video stream header struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO); struct mp_codec_params *c = sh->codec; c->codec = codec; c->disp_w = 0; c->disp_h = 0; c->fps = demuxer->opts->mf_fps; demux_add_sh_stream(demuxer, sh); mf->sh = sh; demuxer->priv = (void *)mf; demuxer->seekable = true; return 0; error: return -1; }
void mp_add_decoder(struct mp_decoder_list *list, const char *family, const char *codec, const char *decoder, const char *desc) { struct mp_decoder_entry entry = { .family = talloc_strdup(list, family), .codec = talloc_strdup(list, codec), .decoder = talloc_strdup(list, decoder), .desc = talloc_strdup(list, desc), }; MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry); } static void mp_add_decoder_entry(struct mp_decoder_list *list, struct mp_decoder_entry *entry) { mp_add_decoder(list, entry->family, entry->codec, entry->decoder, entry->desc); }
// Return true if this was a readable directory. static bool scan_dir(struct pl_parser *p, char *path, struct stat *dir_stack, int num_dir_stack, char ***files, int *num_files) { if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK) return false; // things like mount bind loops DIR *dp = opendir(path); if (!dp) { MP_ERR(p, "Could not read directory.\n"); return false; } struct dirent *ep; while ((ep = readdir(dp))) { if (ep->d_name[0] == '.') continue; if (mp_cancel_test(p->s->cancel)) break; char *file = mp_path_join(p, path, ep->d_name); struct stat st; if (stat(file, &st) == 0 && S_ISDIR(st.st_mode)) { for (int n = 0; n < num_dir_stack; n++) { if (same_st(&dir_stack[n], &st)) { MP_VERBOSE(p, "Skip recursive entry: %s\n", file); goto skip; } } dir_stack[num_dir_stack] = st; scan_dir(p, file, dir_stack, num_dir_stack + 1, files, num_files); } else { MP_TARRAY_APPEND(p, *files, *num_files, file); } skip: ; } closedir(dp); return true; }
static int demux_open_mf(demuxer_t *demuxer, enum demux_check check) { sh_video_t *sh_video = NULL; mf_t *mf; if (strncmp(demuxer->stream->url, "mf://", 5) == 0 && demuxer->stream->type == STREAMTYPE_MF) mf = open_mf_pattern(demuxer, demuxer->stream->url + 5); else { mf = open_mf_single(demuxer, demuxer->stream->url); int bog = 0; MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream); } if (!mf || mf->nr_of_files < 1) goto error; const char *codec = probe_format(mf, check); if (!codec) goto error; mf->curr_frame = 0; // create a new video stream header struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO); sh_video = sh->video; sh_video->gsh->codec = codec; sh_video->disp_w = 0; sh_video->disp_h = 0; sh_video->fps = mf_fps; mf->sh = sh_video; demuxer->priv = (void *)mf; demuxer->seekable = true; return 0; error: return -1; }
static int parse_dir(struct pl_parser *p) { if (p->real_stream->type != STREAMTYPE_DIR) return -1; if (p->probing) return 0; char *path = mp_file_get_path(p, bstr0(p->real_stream->url)); if (strlen(path) >= 8192) return -1; // things like mount bind loops DIR *dp = opendir(path); if (!dp) { MP_ERR(p, "Could not read directory.\n"); return -1; } char **files = NULL; int num_files = 0; struct dirent *ep; while ((ep = readdir(dp))) { if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) continue; MP_TARRAY_APPEND(p, files, num_files, talloc_strdup(p, ep->d_name)); } if (files) qsort(files, num_files, sizeof(files[0]), cmp_filename); for (int n = 0; n < num_files; n++) playlist_add_file(p->pl, mp_path_join(p, path, files[n])); closedir(dp); p->add_base = false; return num_files > 0 ? 0 : -1; }
int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type, bstr data) { // X11 and Wayland file list format. if (strcmp(mime_type, "text/uri-list") == 0) { void *tmp = talloc_new(NULL); int num_files = 0; char **files = NULL; while (data.len) { bstr line = bstr_getline(data, &data); line = bstr_strip_linebreaks(line); if (bstr_startswith0(line, "#")) continue; char *s = bstrto0(tmp, line); MP_TARRAY_APPEND(tmp, files, num_files, s); } mp_event_drop_files(ictx, num_files, files); talloc_free(tmp); return num_files > 0; } else { return -1; } }
static int angle_init(struct MPGLContext *ctx, int flags) { struct priv *p = ctx->priv; struct vo *vo = ctx->vo; if (!angle_load()) { MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n"); goto fail; } if (!vo_w32_init(vo)) goto fail; HDC dc = GetDC(vo_w32_hwnd(vo)); if (!dc) { MP_FATAL(vo, "Couldn't get DC\n"); goto fail; } PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); if (!eglGetPlatformDisplayEXT) { MP_FATAL(vo, "Missing EGL_EXT_platform_base\n"); goto fail; } EGLint d3d_types[] = {EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE}; EGLint d3d_dev_types[] = {EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE}; for (int i = 0; i < MP_ARRAY_SIZE(d3d_types); i++) { EGLint display_attributes[] = { EGL_PLATFORM_ANGLE_TYPE_ANGLE, d3d_types[i], EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, d3d_dev_types[i], EGL_NONE, }; p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, dc, display_attributes); if (p->egl_display == EGL_NO_DISPLAY) continue; if (!eglInitialize(p->egl_display, NULL, NULL)) { p->egl_display = EGL_NO_DISPLAY; continue; } if (d3d_dev_types[i] == EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE) show_sw_adapter_msg(ctx); break; } if (p->egl_display == EGL_NO_DISPLAY) { MP_FATAL(vo, "Couldn't get display\n"); goto fail; } const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); if (exts) MP_DBG(ctx->vo, "EGL extensions: %s\n", exts); eglBindAPI(EGL_OPENGL_ES_API); if (eglGetError() != EGL_SUCCESS) { MP_FATAL(vo, "Couldn't bind GLES API\n"); goto fail; } EGLConfig config = select_fb_config_egl(ctx); if (!config) goto fail; int window_attribs_len = 0; EGLint *window_attribs = NULL; EGLint flip_val; if (eglGetConfigAttrib(p->egl_display, config, EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &flip_val)) { if (flip_val == EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) { MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_SURFACE_ORIENTATION_ANGLE); MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE); ctx->flip_v = true; MP_VERBOSE(vo, "Rendering flipped.\n"); } } // EGL_DIRECT_COMPOSITION_ANGLE enables the use of flip-mode present, which // avoids a copy of the video image and lowers vsync jitter, though the // extension is only present on Windows 8 and up. if (strstr(exts, "EGL_ANGLE_direct_composition")) { MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_DIRECT_COMPOSITION_ANGLE); MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_TRUE); MP_VERBOSE(vo, "Using DirectComposition.\n"); } MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_NONE); p->egl_surface = eglCreateWindowSurface(p->egl_display, config, vo_w32_hwnd(vo), window_attribs); talloc_free(window_attribs); if (p->egl_surface == EGL_NO_SURFACE) { MP_FATAL(ctx->vo, "Could not create EGL surface!\n"); goto fail; } if (!(!p->use_es2 && create_context_egl(ctx, config, 3)) && !create_context_egl(ctx, config, 2)) { MP_FATAL(ctx->vo, "Could not create EGL context!\n"); goto fail; } // Configure the underlying Direct3D device d3d_init(ctx); if (strstr(exts, "EGL_NV_post_sub_buffer")) { p->eglPostSubBufferNV = (PFNEGLPOSTSUBBUFFERNVPROC)eglGetProcAddress("eglPostSubBufferNV"); } mpgl_load_functions(ctx->gl, get_proc_address, NULL, vo->log); return 0; fail: angle_uninit(ctx); 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); } 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; }
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 void add_source(struct MPContext *mpctx, struct demuxer *d) { MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d); }
/* Returns a list of parts, or NULL on parse error. * Syntax (without file header or URI prefix): * url ::= <entry> ( (';' | '\n') <entry> )* * entry ::= <param> ( <param> ',' )* * param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>) */ static struct tl_parts *parse_edl(bstr str) { struct tl_parts *tl = talloc_zero(NULL, struct tl_parts); while (str.len) { if (bstr_eatstart0(&str, "#")) bstr_split_tok(str, "\n", &(bstr){0}, &str); if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";")) continue; struct tl_part p = { .length = -1 }; int nparam = 0; while (1) { bstr name, val; // Check if it's of the form "name=..." int next = bstrcspn(str, "=%,;\n"); if (next > 0 && next < str.len && str.start[next] == '=') { name = bstr_splice(str, 0, next); str = bstr_cut(str, next + 1); } else { const char *names[] = {"file", "start", "length"}; // implied name name = bstr0(nparam < 3 ? names[nparam] : "-"); } if (bstr_eatstart0(&str, "%")) { int len = bstrtoll(str, &str, 0); if (!bstr_startswith0(str, "%") || (len > str.len - 1)) goto error; val = bstr_splice(str, 1, len + 1); str = bstr_cut(str, len + 1); } else { next = bstrcspn(str, ",;\n"); val = bstr_splice(str, 0, next); str = bstr_cut(str, next); } // Interpret parameters. Explicitly ignore unknown ones. if (bstr_equals0(name, "file")) { p.filename = bstrto0(tl, val); } else if (bstr_equals0(name, "start")) { if (!parse_time(val, &p.offset)) goto error; p.offset_set = true; } else if (bstr_equals0(name, "length")) { if (!parse_time(val, &p.length)) goto error; } else if (bstr_equals0(name, "timestamps")) { if (bstr_equals0(val, "chapters")) p.chapter_ts = true; } nparam++; if (!bstr_eatstart0(&str, ",")) break; } if (!p.filename) goto error; MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p); } if (!tl->num_parts) goto error; return tl; error: talloc_free(tl); return NULL; } static struct demuxer *open_source(struct timeline *tl, char *filename) { for (int n = 0; n < tl->num_sources; n++) { struct demuxer *d = tl->sources[n]; if (strcmp(d->stream->url, filename) == 0) return d; } struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global); if (d) { MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d); } else { MP_ERR(tl, "EDL: Could not open source file '%s'.\n", filename); } return d; } static double demuxer_chapter_time(struct demuxer *demuxer, int n) { if (n < 0 || n >= demuxer->num_chapters) return -1; return demuxer->chapters[n].pts; } // Append all chapters from src to the chapters array. // Ignore chapters outside of the given time range. static void copy_chapters(struct demux_chapter **chapters, int *num_chapters, struct demuxer *src, double start, double len, double dest_offset) { for (int n = 0; n < src->num_chapters; n++) { double time = demuxer_chapter_time(src, n); if (time >= start && time <= start + len) { struct demux_chapter ch = { .pts = dest_offset + time - start, .metadata = mp_tags_dup(*chapters, src->chapters[n].metadata), }; MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch); } } } // return length of the source in seconds, or -1 if unknown static double source_get_length(struct demuxer *demuxer) { double time; // <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW if (demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, &time) <= 0) time = -1; return time; } static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer) { if (part->chapter_ts) { double start = demuxer_chapter_time(demuxer, part->offset); double length = part->length; double end = length; if (end >= 0) end = demuxer_chapter_time(demuxer, part->offset + part->length); if (end >= 0 && start >= 0) length = end - start; part->offset = start; part->length = length; } if (!part->offset_set) part->offset = demuxer->start_time; } static void build_timeline(struct timeline *tl, struct tl_parts *parts) { tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1); double starttime = 0; for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; struct demuxer *source = open_source(tl, part->filename); if (!source) goto error; resolve_timestamps(part, source); double end_time = source_get_length(source); if (end_time >= 0) end_time += source->start_time; // Unknown length => use rest of the file. If duration is unknown, make // something up. if (part->length < 0) { if (end_time < 0) { MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n", part->filename); end_time = 1; } part->length = end_time - part->offset; } else if (end_time >= 0) { double end_part = part->offset + part->length; if (end_part > end_time) { MP_WARN(tl, "EDL: entry %d uses %f " "seconds, but file has only %f seconds.\n", n, end_part, end_time); } } // Add a chapter between each file. struct demux_chapter ch = { .pts = starttime, .metadata = talloc_zero(tl, struct mp_tags), }; mp_tags_set_str(ch.metadata, "title", part->filename); MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch); // Also copy the source file's chapters for the relevant parts copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset, part->length, starttime); tl->parts[n] = (struct timeline_part) { .start = starttime, .source_start = part->offset, .source = source, }; starttime += part->length; } tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime}; tl->num_parts = parts->num_parts; tl->track_layout = tl->parts[0].source; return; error: tl->num_parts = 0; tl->num_chapters = 0; } // For security, don't allow relative or absolute paths, only plain filenames. // Also, make these filenames relative to the edl source file. static void fix_filenames(struct tl_parts *parts, char *source_path) { struct bstr dirname = mp_dirname(source_path); for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; char *filename = mp_basename(part->filename); // plain filename only part->filename = mp_path_join_bstr(parts, dirname, bstr0(filename)); } } static void build_mpv_edl_timeline(struct timeline *tl) { struct priv *p = tl->demuxer->priv; struct tl_parts *parts = parse_edl(p->data); if (!parts) { MP_ERR(tl, "Error in EDL.\n"); return; } MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer); // Source is .edl and not edl:// => don't allow arbitrary paths if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL) fix_filenames(parts, tl->demuxer->filename); build_timeline(tl, parts); talloc_free(parts); } static int try_open_file(struct demuxer *demuxer, enum demux_check check) { struct priv *p = talloc_zero(demuxer, struct priv); demuxer->priv = p; demuxer->fully_read = true; struct stream *s = demuxer->stream; if (s->uncached_type == STREAMTYPE_EDL) { p->data = bstr0(s->path); return 0; } if (check >= DEMUX_CHECK_UNSAFE) { if (!bstr_equals0(stream_peek(s, strlen(HEADER)), HEADER)) return -1; } p->data = stream_read_complete(s, demuxer, 1000000); if (p->data.start == NULL) return -1; bstr_eatstart0(&p->data, HEADER); return 0; } const struct demuxer_desc demuxer_desc_edl = { .name = "edl", .desc = "Edit decision list", .open = try_open_file, .load_timeline = build_mpv_edl_timeline, };
static void frame_queue_push(struct mpv_opengl_cb_context *ctx, struct vo_frame *frame) { MP_TARRAY_APPEND(ctx, ctx->frame_queue, ctx->queued_frames, frame); pthread_cond_broadcast(&ctx->wakeup); }
void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value) { struct playlist_param p = {bstrdup(e, name), bstrdup(e, value)}; MP_TARRAY_APPEND(e, e->params, e->num_params, p); }
static void add_pad(struct lavfi *c, int dir, int index, AVFilterContext *filter, int filter_pad, const char *name, bool first_init) { if (c->failed) return; enum AVMediaType avmt; if (dir == MP_PIN_IN) { avmt = avfilter_pad_get_type(filter->input_pads, filter_pad); } else { avmt = avfilter_pad_get_type(filter->output_pads, filter_pad); } int type; switch (avmt) { case AVMEDIA_TYPE_VIDEO: type = MP_FRAME_VIDEO; break; case AVMEDIA_TYPE_AUDIO: type = MP_FRAME_AUDIO; break; default: MP_FATAL(c, "unknown media type\n"); c->failed = true; return; } // For anonymous pads, just make something up. libavfilter allows duplicate // pad names (while we don't), so we check for collisions along with normal // duplicate pads below. char tmp[80]; const char *dir_string = dir == MP_PIN_IN ? "in" : "out"; if (name) { if (c->direct_filter) { // libavfilter has this very unpleasant thing that filter labels // don't have to be unique - in particular, both input and output // are usually named "default". With direct filters, the user has // no chance to provide better names, so do something to resolve it. snprintf(tmp, sizeof(tmp), "%s_%s", name, dir_string); name = tmp; } } else { snprintf(tmp, sizeof(tmp), "%s%d", dir_string, index); name = tmp; } struct lavfi_pad *p = NULL; for (int n = 0; n < c->num_all_pads; n++) { if (strcmp(c->all_pads[n]->name, name) == 0) { p = c->all_pads[n]; break; } } if (p) { // Graph recreation case: reassociate an existing pad. if (p->filter) { // Collision due to duplicate names. MP_FATAL(c, "more than one pad with label '%s'\n", name); c->failed = true; return; } if (p->dir != dir || p->type != type) { // libavfilter graph parser behavior not deterministic. MP_FATAL(c, "pad '%s' changed type or direction\n", name); c->failed = true; return; } } else { if (!first_init) { MP_FATAL(c, "filter pad '%s' got added later?\n", name); c->failed = true; return; } p = talloc_zero(c, struct lavfi_pad); p->main = c; p->dir = dir; p->name = talloc_strdup(p, name); p->type = type; p->pin_index = -1; p->metadata = talloc_zero(p, struct mp_tags); if (p->dir == MP_PIN_IN) MP_TARRAY_APPEND(c, c->in_pads, c->num_in_pads, p); if (p->dir == MP_PIN_OUT) MP_TARRAY_APPEND(c, c->out_pads, c->num_out_pads, p); MP_TARRAY_APPEND(c, c->all_pads, c->num_all_pads, p); } p->filter = filter; p->filter_pad = filter_pad; }
// the core might call these every frame, so cache them... static int cache_get_cached_control(stream_t *cache, int cmd, void *arg) { struct priv *s = cache->priv; switch (cmd) { case STREAM_CTRL_GET_CACHE_SIZE: *(int64_t *)arg = s->buffer_size; return STREAM_OK; case STREAM_CTRL_GET_CACHE_FILL: *(int64_t *)arg = s->max_filepos - s->read_filepos; return STREAM_OK; case STREAM_CTRL_GET_CACHE_IDLE: *(int *)arg = s->idle; return STREAM_OK; case STREAM_CTRL_GET_TIME_LENGTH: *(double *)arg = s->stream_time_length; return s->stream_time_length ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_START_TIME: *(double *)arg = s->stream_start_time; return s->stream_start_time != MP_NOPTS_VALUE ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_SIZE: *(int64_t *)arg = s->stream_size; return STREAM_OK; case STREAM_CTRL_MANAGES_TIMELINE: return s->stream_manages_timeline ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_NUM_CHAPTERS: *(unsigned int *)arg = s->stream_num_chapters; return STREAM_OK; case STREAM_CTRL_GET_CURRENT_TIME: { if (s->read_filepos >= s->min_filepos && s->read_filepos <= s->max_filepos && s->min_filepos < s->max_filepos) { int64_t fpos = FFMIN(s->read_filepos, s->max_filepos - 1); int64_t pos = fpos - s->offset; if (pos < 0) pos += s->buffer_size; else if (pos >= s->buffer_size) pos -= s->buffer_size; double pts = s->bm[pos / BYTE_META_CHUNK_SIZE].stream_pts; *(double *)arg = pts; return pts == MP_NOPTS_VALUE ? STREAM_UNSUPPORTED : STREAM_OK; } return STREAM_UNSUPPORTED; } case STREAM_CTRL_GET_METADATA: { if (s->stream_metadata && s->stream_metadata[0]) { char **m = talloc_new(NULL); int num_m = 0; for (int n = 0; s->stream_metadata[n]; n++) { char *t = talloc_strdup(m, s->stream_metadata[n]); MP_TARRAY_APPEND(NULL, m, num_m, t); } MP_TARRAY_APPEND(NULL, m, num_m, NULL); MP_TARRAY_APPEND(NULL, m, num_m, NULL); *(char ***)arg = m; return STREAM_OK; } return STREAM_UNSUPPORTED; } } return STREAM_ERROR; }
static void add_source(struct timeline *tl, struct demuxer *d) { MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d); }
static int SkipFile(struct stream *s, int *count, rar_file_t ***file, const rar_block_t *hdr, const char *volume_mrl) { int min_size = 7+21; if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) min_size += 8; if (hdr->size < (unsigned)min_size) return -1; bstr data = stream_peek(s, min_size); if (data.len < min_size) return -1; const uint8_t *peek = (uint8_t *)data.start; /* */ uint32_t file_size_low = AV_RL32(&peek[7+4]); uint8_t method = peek[7+18]; uint16_t name_size = AV_RL16(&peek[7+19]); uint32_t file_size_high = 0; if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) file_size_high = AV_RL32(&peek[7+29]); const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low; char *name = calloc(1, name_size + 1); if (!name) return -1; const int name_offset = (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25); if (name_offset + name_size <= hdr->size) { const int max_size = name_offset + name_size; bstr namedata = stream_peek(s, max_size); if (namedata.len < max_size) { free(name); return -1; } memcpy(name, &namedata.start[name_offset], name_size); } rar_file_t *current = NULL; if (method != 0x30) { MP_WARN(s, "Ignoring compressed file %s (method=0x%2.2x)\n", name, method); goto exit; } /* */ if( *count > 0 ) current = (*file)[*count - 1]; if (current && (current->is_complete || strcmp(current->name, name) || (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0)) current = NULL; if (!current) { if (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) goto exit; current = calloc(1, sizeof(*current)); if (!current) goto exit; MP_TARRAY_APPEND(NULL, *file, *count, current); current->name = name; current->size = file_size; current->is_complete = false; current->real_size = 0; current->chunk_count = 0; current->chunk = NULL; name = NULL; } /* Append chunks */ rar_file_chunk_t *chunk = malloc(sizeof(*chunk)); if (chunk) { chunk->mrl = strdup(volume_mrl); chunk->offset = stream_tell(s) + hdr->size; chunk->size = hdr->add_size; chunk->cummulated_size = 0; if (current->chunk_count > 0) { rar_file_chunk_t *previous = current->chunk[current->chunk_count-1]; chunk->cummulated_size += previous->cummulated_size + previous->size; } MP_TARRAY_APPEND(NULL, current->chunk, current->chunk_count, chunk); current->real_size += hdr->add_size; } if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0) current->is_complete = true; exit: /* */ free(name); /* We stop on the first non empty file if we cannot seek */ if (current) { bool can_seek = s->end_pos > 0; if (!can_seek && current->size > 0) return -1; } if (SkipBlock(s, hdr)) return -1; return 0; }
static void mf_add(mf_t *mf, const char *fname) { char *entry = talloc_strdup(mf, fname); MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry); }
static void frame_queue_push(struct mpv_opengl_cb_context *ctx, struct mp_image *mpi) { MP_TARRAY_APPEND(ctx, ctx->frame_queue, ctx->queued_frames, mpi); }
static void get_disc_lang(struct stream *stream, struct sh_stream *sh) { struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id}; if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB) req.id = req.id & 0x1F; // mpeg ID to index stream_control(stream, STREAM_CTRL_GET_LANG, &req); if (req.name[0]) sh->lang = talloc_strdup(sh, req.name); } static void add_dvd_streams(demuxer_t *demuxer) { struct priv *p = demuxer->priv; struct stream *stream = demuxer->stream; if (stream->uncached_type != STREAMTYPE_DVD) return; struct stream_dvd_info_req info; if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) { for (int n = 0; n < MPMIN(32, info.num_subs); n++) { struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB); sh->demuxer_id = n + 0x20; sh->codec->codec = "dvd_subtitle"; get_disc_lang(stream, sh); // p->streams _must_ match with p->slave->streams, so we can't add // it yet - it has to be done when the real stream appears, which // could be right on start, or any time later. p->dvd_subs[n] = sh; // emulate the extradata struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS; struct mp_cmat cmatrix; mp_get_csp_matrix(&csp, &cmatrix); char *s = talloc_strdup(sh, ""); s = talloc_asprintf_append(s, "palette: "); for (int i = 0; i < 16; i++) { int color = info.palette[i]; int y[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff}; int c[3]; mp_map_fixp_color(&cmatrix, 8, y, 8, c); color = (c[2] << 16) | (c[1] << 8) | c[0]; if (i != 0) s = talloc_asprintf_append(s, ", "); s = talloc_asprintf_append(s, "%06x", color); } s = talloc_asprintf_append(s, "\n"); sh->codec->extradata = s; sh->codec->extradata_size = strlen(s); demux_add_sh_stream(demuxer, sh); } } } static void add_streams(demuxer_t *demuxer) { struct priv *p = demuxer->priv; for (int n = p->num_streams; n < demux_get_num_stream(p->slave); n++) { struct sh_stream *src = demux_get_stream(p->slave, n); if (src->type == STREAM_SUB) { struct sh_stream *sub = NULL; if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F) sub = p->dvd_subs[src->demuxer_id - 0x20]; if (sub) { assert(p->num_streams == n); // directly mapped MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub); continue; } } struct sh_stream *sh = demux_alloc_sh_stream(src->type); assert(p->num_streams == n); // directly mapped MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh); // Copy all stream fields that might be relevant *sh->codec = *src->codec; sh->demuxer_id = src->demuxer_id; if (src->type == STREAM_VIDEO) { double ar; if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar) == STREAM_OK) { struct mp_image_params f = {.w = src->codec->disp_w, .h = src->codec->disp_h}; mp_image_params_set_dsize(&f, 1728 * ar, 1728); sh->codec->par_w = f.p_w; sh->codec->par_h = f.p_h; } } get_disc_lang(demuxer->stream, sh); demux_add_sh_stream(demuxer, sh); } reselect_streams(demuxer); } static void d_seek(demuxer_t *demuxer, double rel_seek_secs, int flags) { struct priv *p = demuxer->priv; if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) { demux_seek(p->slave, rel_seek_secs, flags); return; } double pts = p->seek_pts; if (flags & SEEK_ABSOLUTE) pts = 0.0f; double base_pts = pts; // to what pts is relative if (flags & SEEK_FACTOR) { double tmp = 0; stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp); pts += tmp * rel_seek_secs; } else { pts += rel_seek_secs; } MP_VERBOSE(demuxer, "seek to: %f\n", pts); double seek_arg[] = {pts, base_pts, flags}; stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, seek_arg); demux_control(p->slave, DEMUXER_CTRL_RESYNC, NULL); p->seek_pts = pts; p->seek_reinit = true; } static void reset_pts(demuxer_t *demuxer) { struct priv *p = demuxer->priv; double base; if (stream_control(demuxer->stream, STREAM_CTRL_GET_CURRENT_TIME, &base) < 1) base = 0; MP_VERBOSE(demuxer, "reset to time: %f\n", base); p->base_dts = p->last_dts = MP_NOPTS_VALUE; p->base_time = base; p->seek_reinit = false; } static int d_fill_buffer(demuxer_t *demuxer) { struct priv *p = demuxer->priv; struct demux_packet *pkt = demux_read_any_packet(p->slave); if (!pkt) return 0; demux_update(p->slave); if (p->seek_reinit) reset_pts(demuxer); add_streams(demuxer); if (pkt->stream >= p->num_streams) { // out of memory? talloc_free(pkt); return 0; } struct sh_stream *sh = p->streams[pkt->stream]; if (!demux_stream_is_selected(sh)) { talloc_free(pkt); return 1; } if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) { demux_add_packet(sh, pkt); return 1; } MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts); if (sh->type == STREAM_SUB) { if (p->base_dts == MP_NOPTS_VALUE) MP_WARN(demuxer, "subtitle packet along PTS reset\n"); } else if (pkt->dts != MP_NOPTS_VALUE) { // Use the very first DTS to rebase the start time of the MPEG stream // to the playback time. if (p->base_dts == MP_NOPTS_VALUE) p->base_dts = pkt->dts; if (p->last_dts == MP_NOPTS_VALUE) p->last_dts = pkt->dts; if (fabs(p->last_dts - pkt->dts) >= DTS_RESET_THRESHOLD) { MP_WARN(demuxer, "PTS discontinuity: %f->%f\n", p->last_dts, pkt->dts); p->base_time += p->last_dts - p->base_dts; p->base_dts = pkt->dts - pkt->duration; } p->last_dts = pkt->dts; } if (p->base_dts != MP_NOPTS_VALUE) { double delta = -p->base_dts + p->base_time; if (pkt->pts != MP_NOPTS_VALUE) pkt->pts += delta; if (pkt->dts != MP_NOPTS_VALUE) pkt->dts += delta; } MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts); if (pkt->pts != MP_NOPTS_VALUE) p->seek_pts = pkt->pts; demux_add_packet(sh, pkt); return 1; } static void add_stream_chapters(struct demuxer *demuxer) { int num = 0; if (stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_CHAPTERS, &num) < 1) return; for (int n = 0; n < num; n++) { double p = n; if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1) continue; demuxer_add_chapter(demuxer, "", p, 0); } }