int demuxer_seek_chapter(demuxer_t *demuxer, int chapter, double *seek_pts) { int ris; if (!demuxer->num_chapters || !demuxer->chapters) { demux_flush(demuxer); ris = stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_CHAPTER, &chapter); if (ris != STREAM_UNSUPPORTED) demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL); // exit status may be ok, but main() doesn't have to seek itself // (because e.g. dvds depend on sectors, not on pts) *seek_pts = -1.0; return ris != STREAM_UNSUPPORTED ? chapter : -1; } else { // chapters structure is set in the demuxer if (chapter >= demuxer->num_chapters) return -1; if (chapter < 0) chapter = 0; *seek_pts = demuxer->chapters[chapter].start / 1e9; return chapter; } }
int demux_seek(demuxer_t *demuxer, float rel_seek_secs, float audio_delay, int flags) { if (!demuxer->seekable) { if (demuxer->file_format == DEMUXER_TYPE_AVI) mp_tmsg(MSGT_SEEK, MSGL_WARN, "Cannot seek in raw AVI streams. (Index required, try with the -idx switch.)\n"); #ifdef CONFIG_TV else if (demuxer->file_format == DEMUXER_TYPE_TV) mp_tmsg(MSGT_SEEK, MSGL_WARN, "TV input is not seekable! (Seeking will probably be for changing channels ;)\n"); #endif else mp_tmsg(MSGT_SEEK, MSGL_WARN, "Cannot seek in this file.\n"); return 0; } // clear demux buffers: demux_flush(demuxer); demuxer->video->eof = 0; demuxer->audio->eof = 0; demuxer->sub->eof = 0; /* HACK: assume any demuxer used with these streams can cope with * the stream layer suddenly seeking to a different position under it * (nothing actually implements DEMUXER_CTRL_RESYNC now). */ struct stream *stream = demuxer->stream; if (stream->type == STREAMTYPE_DVD || stream->type == STREAMTYPE_DVDNAV) { double pts; if (flags & SEEK_ABSOLUTE) pts = 0.0f; else { if (demuxer->stream_pts == MP_NOPTS_VALUE) goto dmx_seek; pts = demuxer->stream_pts; } if (flags & SEEK_FACTOR) { double tmp = 0; if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp) == STREAM_UNSUPPORTED) goto dmx_seek; pts += tmp * rel_seek_secs; } else pts += rel_seek_secs; if (stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, &pts) != STREAM_UNSUPPORTED) { demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL); return 1; } } dmx_seek: if (demuxer->desc->seek) demuxer->desc->seek(demuxer, rel_seek_secs, audio_delay, flags); return 1; }
static int demux_demuxers_control(demuxer_t *demuxer,int cmd, void *arg){ dd_priv_t* priv = demuxer->priv; switch (cmd) { case DEMUXER_CTRL_GET_TIME_LENGTH: case DEMUXER_CTRL_GET_PERCENT_POS: return demux_control(priv->vd, cmd, arg); } return DEMUXER_CTRL_NOTIMPL; }
// return length of the source in seconds, or -1 if unknown static double source_get_length(struct demuxer *demuxer) { double get_time_ans; // <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW if (demuxer && demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, (void *) &get_time_ans) > 0) { return get_time_ans; } else { return -1; } }
static int demux_demuxers_control(demuxer_t *demuxer,int cmd, void *arg){ dd_priv_t* priv = demuxer->priv; switch (cmd) { case DEMUXER_CTRL_GET_TIME_LENGTH: *((double *)arg) = demuxer_get_time_length(priv->vd); return DEMUXER_CTRL_OK; case DEMUXER_CTRL_GET_PERCENT_POS: *((int *)arg) = demuxer_get_percent_pos(priv->vd); return DEMUXER_CTRL_OK; case DEMUXER_CTRL_CORRECT_PTS: return demux_control(priv->vd, DEMUXER_CTRL_CORRECT_PTS, NULL); } return DEMUXER_CTRL_NOTIMPL; }
int demuxer_switch_video(demuxer_t *demuxer, int index) { int res = demux_control(demuxer, DEMUXER_CTRL_SWITCH_VIDEO, &index); if (res == DEMUXER_CTRL_NOTIMPL) { struct sh_video *sh_video = demuxer->video->sh; return sh_video ? sh_video->vid : -2; } if (demuxer->video->id >= 0) { struct sh_video *sh_video = demuxer->v_streams[demuxer->video->id]; demuxer->video->sh = sh_video; index = sh_video->vid; // internal MPEG demuxers don't set it right } else demuxer->video->sh = NULL; return index; }
int demuxer_set_angle(demuxer_t *demuxer, int angle) { int ris, angles = -1; angles = demuxer_angles_count(demuxer); if ((angles < 1) || (angle > angles)) return -1; demux_flush(demuxer); ris = stream_control(demuxer->stream, STREAM_CTRL_SET_ANGLE, &angle); if (ris == STREAM_UNSUPPORTED) return -1; demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL); return angle; }
static int d_control(demuxer_t *demuxer, int cmd, void *arg) { struct priv *p = demuxer->priv; switch (cmd) { case DEMUXER_CTRL_GET_TIME_LENGTH: { double len; if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) < 1) break; *(double *)arg = len; return DEMUXER_CTRL_OK; } case DEMUXER_CTRL_RESYNC: demux_flush(p->slave); break; // relay to slave demuxer case DEMUXER_CTRL_SWITCHED_TRACKS: reselect_streams(demuxer); return DEMUXER_CTRL_OK; } return demux_control(p->slave, cmd, arg); }
static void print_status(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; update_window_title(mpctx, false); update_vo_playback_state(mpctx); if (!opts->use_terminal) return; if (opts->quiet || !mpctx->playback_initialized || !mpctx->playing_msg_shown) { term_osd_set_status(mpctx, ""); return; } if (opts->status_msg) { char *r = mp_property_expand_escaped_string(mpctx, opts->status_msg); term_osd_set_status(mpctx, r); talloc_free(r); return; } char *line = NULL; // Playback status if (is_busy(mpctx)) { saddf(&line, "(...) "); } else if (mpctx->paused_for_cache && !opts->pause) { saddf(&line, "(Buffering) "); } else if (mpctx->paused) { saddf(&line, "(Paused) "); } if (mpctx->d_audio) saddf(&line, "A"); if (mpctx->d_video) saddf(&line, "V"); saddf(&line, ": "); // Playback position sadd_hhmmssff_u(&line, get_playback_time(mpctx), mpctx->opts->osd_fractions); double len = get_time_length(mpctx); if (len >= 0) { saddf(&line, " / "); sadd_hhmmssff(&line, len, mpctx->opts->osd_fractions); } sadd_percentage(&line, get_percent_pos(mpctx)); // other if (opts->playback_speed != 1) saddf(&line, " x%4.2f", opts->playback_speed); // A-V sync if (mpctx->d_audio && mpctx->d_video && mpctx->sync_audio_to_video) { saddf(&line, " A-V:%7.3f", mpctx->last_av_difference); if (fabs(mpctx->total_avsync_change) > 0.05) saddf(&line, " ct:%7.3f", mpctx->total_avsync_change); } #if HAVE_ENCODING double position = get_current_pos_ratio(mpctx, true); char lavcbuf[80]; if (encode_lavc_getstatus(mpctx->encode_lavc_ctx, lavcbuf, sizeof(lavcbuf), position) >= 0) { // encoding stats saddf(&line, " %s", lavcbuf); } else #endif { // VO stats if (mpctx->d_video) { if (mpctx->display_sync_active) { saddf(&line, " DS: %d/%"PRId64, mpctx->mistimed_frames_total, vo_get_delayed_count(mpctx->video_out)); } int64_t c = vo_get_drop_count(mpctx->video_out); if (c > 0 || mpctx->dropped_frames_total > 0) { saddf(&line, " Dropped: %"PRId64, c); if (mpctx->dropped_frames_total) saddf(&line, "/%d", mpctx->dropped_frames_total); } } } if (mpctx->demuxer) { int64_t fill = -1; demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &fill); if (fill >= 0) { saddf(&line, " Cache: "); struct demux_ctrl_reader_state s = {.ts_duration = -1}; demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s); if (s.ts_duration < 0) { saddf(&line, "???"); } else { saddf(&line, "%2ds", (int)s.ts_duration); } if (fill >= 1024 * 1024) { saddf(&line, "+%lldMB", (long long)(fill / 1024 / 1024)); } else { saddf(&line, "+%lldKB", (long long)(fill / 1024)); } } }
void mp_handle_nav(struct MPContext *mpctx) { struct mp_nav_state *nav = mpctx->nav_state; if (!nav) return; mpctx->sleeptime = MPMIN(mpctx->sleeptime, 0.5); while (1) { if (!mpctx->demuxer) break; struct mp_nav_event *ev = NULL; demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_NAV_EVENT, &ev); if (!ev) break; switch (ev->event) { case MP_NAV_EVENT_DRAIN: { nav->nav_draining = true; MP_VERBOSE(nav, "drain requested\n"); break; } case MP_NAV_EVENT_RESET_ALL: { mpctx->stop_play = PT_RELOAD_DEMUXER; MP_VERBOSE(nav, "reload\n"); // return immediately. // other events should be handled after reloaded. talloc_free(ev); return; } case MP_NAV_EVENT_RESET: { nav->nav_still_frame = 0; break; } case MP_NAV_EVENT_EOF: nav->nav_eof = true; break; case MP_NAV_EVENT_STILL_FRAME: { int len = ev->u.still_frame.seconds; MP_VERBOSE(nav, "wait for %d seconds\n", len); if (len > 0 && nav->nav_still_frame == 0) nav->nav_still_frame = len; break; } case MP_NAV_EVENT_MENU_MODE: nav->nav_menu = ev->u.menu_mode.enable; if (nav->nav_menu) { mp_input_enable_section(mpctx->input, "discnav-menu", MP_INPUT_ON_TOP); } else { mp_input_disable_section(mpctx->input, "discnav-menu"); } update_state(mpctx); break; case MP_NAV_EVENT_HIGHLIGHT: { pthread_mutex_lock(&nav->osd_lock); MP_VERBOSE(nav, "highlight: %d %d %d - %d %d\n", ev->u.highlight.display, ev->u.highlight.sx, ev->u.highlight.sy, ev->u.highlight.ex, ev->u.highlight.ey); nav->highlight[0] = ev->u.highlight.sx; nav->highlight[1] = ev->u.highlight.sy; nav->highlight[2] = ev->u.highlight.ex; nav->highlight[3] = ev->u.highlight.ey; nav->hi_visible = ev->u.highlight.display; pthread_mutex_unlock(&nav->osd_lock); update_resolution(mpctx); osd_set_nav_highlight(mpctx->osd, mpctx); break; } case MP_NAV_EVENT_OVERLAY: { pthread_mutex_lock(&nav->osd_lock); for (int i = 0; i < 2; i++) { if (nav->overlays[i]) talloc_free(nav->overlays[i]); nav->overlays[i] = talloc_steal(nav, ev->u.overlay.images[i]); } pthread_mutex_unlock(&nav->osd_lock); update_resolution(mpctx); osd_set_nav_highlight(mpctx->osd, mpctx); break; } default: ; // ignore } talloc_free(ev); } update_resolution(mpctx); if (mpctx->stop_play == AT_END_OF_FILE) { if (nav->nav_still_frame > 0) { // gross hack mpctx->time_frame += nav->nav_still_frame; nav->nav_still_frame = -2; } else if (nav->nav_still_frame == -2) { struct mp_nav_cmd inp = {MP_NAV_CMD_SKIP_STILL}; run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp); } } if (nav->nav_draining && mpctx->stop_play == AT_END_OF_FILE) { MP_VERBOSE(nav, "execute drain\n"); struct mp_nav_cmd inp = {MP_NAV_CMD_DRAIN_OK}; run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp); nav->nav_draining = false; run_stream_control(mpctx, STREAM_CTRL_RESUME_CACHE, NULL); } // E.g. keep displaying still frames if (mpctx->stop_play == AT_END_OF_FILE && !nav->nav_eof) mpctx->stop_play = KEEP_PLAYING; }
struct demuxer *demux_open_withparams(struct MPOpts *opts, stream_t *vs, int file_format, int audio_id, int video_id, int dvdsub_id, char *filename, struct demuxer_params *params) { stream_t *as = NULL, *ss = NULL; demuxer_t *vd, *ad = NULL, *sd = NULL; demuxer_t *res; int afmt = DEMUXER_TYPE_UNKNOWN, sfmt = DEMUXER_TYPE_UNKNOWN; int demuxer_type; int audio_demuxer_type = 0, sub_demuxer_type = 0; int demuxer_force = 0, audio_demuxer_force = 0, sub_demuxer_force = 0; if ((demuxer_type = get_demuxer_type_from_name(opts->demuxer_name, &demuxer_force)) < 0) { mp_msg(MSGT_DEMUXER, MSGL_ERR, "-demuxer %s does not exist.\n", opts->demuxer_name); return NULL; } if ((audio_demuxer_type = get_demuxer_type_from_name(opts->audio_demuxer_name, &audio_demuxer_force)) < 0) { mp_msg(MSGT_DEMUXER, MSGL_ERR, "-audio-demuxer %s does not exist.\n", opts->audio_demuxer_name); if (opts->audio_stream) return NULL; } if ((sub_demuxer_type = get_demuxer_type_from_name(opts->sub_demuxer_name, &sub_demuxer_force)) < 0) { mp_msg(MSGT_DEMUXER, MSGL_ERR, "-sub-demuxer %s does not exist.\n", opts->sub_demuxer_name); if (opts->sub_stream) return NULL; } if (opts->audio_stream) { as = open_stream(opts->audio_stream, 0, &afmt); if (!as) { mp_tmsg(MSGT_DEMUXER, MSGL_ERR, "Cannot open audio stream: %s\n", opts->audio_stream); return NULL; } if (opts->audio_stream_cache) { if (!stream_enable_cache (as, opts->audio_stream_cache * 1024, opts->audio_stream_cache * 1024 * (opts->stream_cache_min_percent / 100.0), opts->audio_stream_cache * 1024 * (opts->stream_cache_seek_min_percent / 100.0))) { free_stream(as); mp_msg(MSGT_DEMUXER, MSGL_ERR, "Can't enable audio stream cache\n"); return NULL; } } } if (opts->sub_stream) { ss = open_stream(opts->sub_stream, 0, &sfmt); if (!ss) { mp_tmsg(MSGT_DEMUXER, MSGL_ERR, "Cannot open subtitle stream: %s\n", opts->sub_stream); return NULL; } } vd = demux_open_stream(opts, vs, demuxer_type ? demuxer_type : file_format, demuxer_force, opts->audio_stream ? -2 : audio_id, video_id, opts->sub_stream ? -2 : dvdsub_id, filename, params); if (!vd) { if (as) free_stream(as); if (ss) free_stream(ss); return NULL; } if (as) { ad = demux_open_stream(opts, as, audio_demuxer_type ? audio_demuxer_type : afmt, audio_demuxer_force, audio_id, -2, -2, opts->audio_stream, params); if (!ad) { mp_tmsg(MSGT_DEMUXER, MSGL_WARN, "Failed to open audio demuxer: %s\n", opts->audio_stream); free_stream(as); } else if (ad->audio->sh && ((sh_audio_t *) ad->audio->sh)->format == 0x55) // MP3 opts->hr_mp3_seek = 1; // Enable high res seeking } if (ss) { sd = demux_open_stream(opts, ss, sub_demuxer_type ? sub_demuxer_type : sfmt, sub_demuxer_force, -2, -2, dvdsub_id, opts->sub_stream, params); if (!sd) { mp_tmsg(MSGT_DEMUXER, MSGL_WARN, "Failed to open subtitle demuxer: %s\n", opts->sub_stream); free_stream(ss); } } if (ad && sd) res = new_demuxers_demuxer(vd, ad, sd); else if (ad) res = new_demuxers_demuxer(vd, ad, vd); else if (sd) res = new_demuxers_demuxer(vd, vd, sd); else res = vd; opts->correct_pts = opts->user_correct_pts; if (opts->correct_pts < 0) opts->correct_pts = demux_control(vd ? vd : res, DEMUXER_CTRL_CORRECT_PTS, NULL) == DEMUXER_CTRL_OK; return res; }
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); } }
static int d_open(demuxer_t *demuxer, enum demux_check check) { struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv); if (check != DEMUX_CHECK_FORCE) return -1; struct demuxer_params params = {.force_format = "+lavf"}; if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) params.force_format = "+rawaudio"; char *t = NULL; stream_control(demuxer->stream, STREAM_CTRL_GET_DISC_NAME, &t); if (t) { mp_tags_set_str(demuxer->metadata, "TITLE", t); talloc_free(t); } // Initialize the playback time. We need to read _some_ data to get the // correct stream-layer time (at least with libdvdnav). stream_peek(demuxer->stream, 1); reset_pts(demuxer); p->slave = demux_open(demuxer->stream, ¶ms, demuxer->global); if (!p->slave) return -1; // So that we don't miss initial packets of delayed subtitle streams. demux_set_stream_autoselect(p->slave, true); // With cache enabled, the stream can be seekable. This causes demux_lavf.c // (actually libavformat/mpegts.c) to seek sometimes when reading a packet. // It does this to seek back a bit in case the current file position points // into the middle of a packet. if (demuxer->stream->uncached_type != STREAMTYPE_CDDA) { demuxer->stream->seekable = false; // Can be seekable even if the stream isn't. demuxer->seekable = true; demuxer->rel_seeks = true; } add_dvd_streams(demuxer); add_streams(demuxer); add_stream_chapters(demuxer); return 0; } static void d_close(demuxer_t *demuxer) { struct priv *p = demuxer->priv; free_demuxer(p->slave); } static int d_control(demuxer_t *demuxer, int cmd, void *arg) { struct priv *p = demuxer->priv; switch (cmd) { case DEMUXER_CTRL_GET_TIME_LENGTH: { double len; if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) < 1) break; *(double *)arg = len; return DEMUXER_CTRL_OK; } case DEMUXER_CTRL_RESYNC: demux_flush(p->slave); break; // relay to slave demuxer case DEMUXER_CTRL_SWITCHED_TRACKS: reselect_streams(demuxer); return DEMUXER_CTRL_OK; } return demux_control(p->slave, cmd, arg); } const demuxer_desc_t demuxer_desc_disc = { .name = "disc", .desc = "CD/DVD/BD wrapper", .fill_buffer = d_fill_buffer, .open = d_open, .close = d_close, .seek = d_seek, .control = d_control, };
/* 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, };