summaryrefslogtreecommitdiffstats
path: root/core/mplayer.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/mplayer.c')
-rw-r--r--core/mplayer.c4302
1 files changed, 4302 insertions, 0 deletions
diff --git a/core/mplayer.c b/core/mplayer.c
new file mode 100644
index 0000000000..7006614df1
--- /dev/null
+++ b/core/mplayer.c
@@ -0,0 +1,4302 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+
+#include <libavutil/intreadwrite.h>
+#include <libavutil/attributes.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "osdep/io.h"
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+#include <windows.h>
+// No proper file descriptor event handling; keep waking up to poll input
+#define WAKEUP_PERIOD 0.02
+#else
+/* Even if we can immediately wake up in response to most input events,
+ * there are some timers which are not registered to the event loop
+ * and need to be checked periodically (like automatic mouse cursor hiding).
+ * OSD content updates behave similarly. Also some uncommon input devices
+ * may not have proper FD event support.
+ */
+#define WAKEUP_PERIOD 0.5
+#endif
+#include <string.h>
+#include <unistd.h>
+
+// #include <sys/mman.h>
+#include <sys/types.h>
+#ifndef __MINGW32__
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <time.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <errno.h>
+
+#include "mp_msg.h"
+#include "av_log.h"
+
+
+#include "m_option.h"
+#include "m_config.h"
+#include "mplayer.h"
+#include "m_property.h"
+
+#include "sub/subreader.h"
+#include "sub/find_subfiles.h"
+#include "sub/dec_sub.h"
+
+#include "mp_osd.h"
+#include "libvo/video_out.h"
+#include "screenshot.h"
+
+#include "sub/sub.h"
+#include "cpudetect.h"
+
+#ifdef CONFIG_X11
+#include "libvo/x11_common.h"
+#endif
+
+#include "libao2/audio_out.h"
+
+#include "codec-cfg.h"
+
+#include "sub/spudec.h"
+#include "sub/vobsub.h"
+
+#include "osdep/getch2.h"
+#include "osdep/timer.h"
+
+#include "input/input.h"
+
+#include "encode.h"
+
+int slave_mode = 0;
+int enable_mouse_movements = 0;
+float start_volume = -1;
+
+#include "osdep/priority.h"
+
+char *heartbeat_cmd;
+
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#ifdef CONFIG_DVBIN
+#include "stream/dvbin.h"
+#endif
+#include "stream/cache2.h"
+
+//**************************************************************************//
+// Playtree
+//**************************************************************************//
+#include "playlist.h"
+#include "playlist_parser.h"
+
+//**************************************************************************//
+// Config
+//**************************************************************************//
+#include "parser-cfg.h"
+#include "parser-mpcmd.h"
+
+//**************************************************************************//
+// Config file
+//**************************************************************************//
+
+#include "path.h"
+
+//**************************************************************************//
+//**************************************************************************//
+// Input media streaming & demultiplexer:
+//**************************************************************************//
+
+static int max_framesize = 0;
+
+#include "stream/stream.h"
+#include "libmpdemux/demuxer.h"
+#include "libmpdemux/stheader.h"
+
+#ifdef CONFIG_DVDREAD
+#include "stream/stream_dvd.h"
+#endif
+
+#include "libmpcodecs/dec_audio.h"
+#include "libmpcodecs/dec_video.h"
+#include "libmpcodecs/mp_image.h"
+#include "libmpcodecs/vf.h"
+#include "libmpcodecs/vd.h"
+
+#include "mixer.h"
+
+#include "mp_core.h"
+#include "options.h"
+#include "defaultopts.h"
+
+static const char help_text[] = _(
+"Usage: mpv [options] [url|path/]filename\n"
+"\n"
+"Basic options: (complete list in the man page)\n"
+" --ss=<position> seek to given (seconds or hh:mm:ss) position\n"
+" --no-audio do not play sound\n"
+" --no-video do not play video\n"
+" --fs fullscreen playback\n"
+" --sub=<file> specify subtitle file to use\n"
+" --playlist=<file> specify playlist file\n"
+"\n");
+
+static const char av_desync_help_text[] = _(
+"\n\n"
+" *************************************************\n"
+" **** Audio/Video desynchronisation detected! ****\n"
+" *************************************************\n\n"
+"This means either the audio or the video is played too slowly.\n"
+"Possible reasons, problems, workarounds:\n"
+"- Your system is simply too slow for this file.\n"
+" Transcode it to a lower bitrate file with tools like HandBrake.\n"
+"- Broken/buggy _audio_ driver.\n"
+" Experiment with different values for --autosync, 30 is a good start.\n"
+" If you have PulseAudio, try --ao=alsa .\n"
+"- Slow video output.\n"
+" Try a different -vo driver (-vo help for a list) or try -framedrop!\n"
+"- Playing a video file with --vo=opengl with higher FPS than the monitor.\n"
+" This is due to vsync limiting the framerate. Try --no-vsync, or a\n"
+" different VO.\n"
+"- Playing from a slow network source.\n"
+" Download the file instead.\n"
+"- Try to find out whether audio or video is causing this by experimenting\n"
+" with --no-video and --no-audio.\n"
+"If none of this helps you, file a bug report.\n\n");
+
+
+//**************************************************************************//
+//**************************************************************************//
+
+#include "mp_fifo.h"
+
+static int drop_frame_cnt; // total number of dropped frames
+
+// seek:
+static int64_t seek_to_byte;
+static double step_sec;
+
+static m_time_size_t end_at = { .type = END_AT_NONE, .pos = 0 };
+
+// codecs:
+char **audio_codec_list; // override audio codec
+char **video_codec_list; // override video codec
+char **audio_fm_list; // override audio codec family
+char **video_fm_list; // override video codec family
+
+// this dvdsub_id was selected via slang
+// use this to allow dvdnav to follow -slang across stream resets,
+// in particular the subtitle ID for a language changes
+int dvdsub_lang_id;
+int vobsub_id = -1;
+static char *spudec_ifo = NULL;
+int forced_subs_only = 0;
+
+// A-V sync:
+static float default_max_pts_correction = -1;
+float audio_delay = 0;
+static int ignore_start = 0;
+
+double force_fps = 0;
+static int force_srate = 0;
+static int play_n_frames = -1;
+static int play_n_frames_mf = -1;
+
+#include "sub/ass_mp.h"
+
+
+// ---
+
+FILE *edl_fd; // file to write to when in -edlout mode.
+char *edl_output_filename; // file to put EDL entries in (-edlout)
+
+int use_filedir_conf;
+
+#include "mpcommon.h"
+#include "command.h"
+
+static void reset_subtitles(struct MPContext *mpctx);
+static void reinit_subs(struct MPContext *mpctx);
+
+static float get_relative_time(struct MPContext *mpctx)
+{
+ unsigned int new_time = GetTimer();
+ unsigned int delta = new_time - mpctx->last_time;
+ mpctx->last_time = new_time;
+ return delta * 0.000001;
+}
+
+static void print_stream(struct MPContext *mpctx, struct track *t, int id)
+{
+ struct sh_stream *s = t->stream;
+ const char *tname = "?";
+ const char *selopt = "?";
+ const char *langopt = "?";
+ switch (t->type) {
+ case STREAM_VIDEO:
+ tname = "Video"; selopt = "vid"; langopt = "vlang";
+ break;
+ case STREAM_AUDIO:
+ tname = "Audio"; selopt = "aid"; langopt = "alang";
+ break;
+ case STREAM_SUB:
+ tname = "Subs"; selopt = "sid"; langopt = "slang";
+ break;
+ }
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "[stream] %-5s %3s",
+ tname, mpctx->current_track[t->type] == t ? "(+)" : "");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%d", selopt, t->user_tid);
+ if (t->lang)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%s", langopt, t->lang);
+ if (t->default_track)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (*)");
+ if (t->title)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " '%s'", t->title);
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (");
+ if (s && s->common_header->format) {
+ int format = s->common_header->format;
+ // not sure about endian crap
+ char name[sizeof(format) + 1] = {0};
+ memcpy(name, &format, sizeof(format));
+ bool ok = true;
+ for (int n = 0; name[n]; n++) {
+ if ((name[n] < 32 || name[n] >= 128) && name[n] != 0)
+ ok = false;
+ }
+ if (ok && strlen(name) > 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "%s", name);
+ } else {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "%#x", format);
+ }
+ } else if (s && t->type == STREAM_SUB) {
+ char t = s->sub->type;
+ const char *name = NULL;
+ switch (t) {
+ case 't': name = "SRT"; break;
+ case 'a': name = "ASS"; break;
+ case 'v': name = "VobSub"; break;
+ }
+ if (!name)
+ name = (char[2]){t, '\0'};
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "%s", name);
+ }
+ if (s && s->common_header->demuxer_codecname)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "/%s", s->common_header->demuxer_codecname);
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, ")");
+ if (t->is_external)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (external)");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "\n");
+}
+
+static void print_file_properties(struct MPContext *mpctx, const char *filename)
+{
+ double start_pts = MP_NOPTS_VALUE;
+ double video_start_pts = MP_NOPTS_VALUE;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FILENAME=%s\n",
+ filename);
+ if (mpctx->sh_video) {
+ /* Assume FOURCC if all bytes >= 0x20 (' ') */
+ if (mpctx->sh_video->format >= 0x20202020)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FORMAT=%.4s\n", (char *)&mpctx->sh_video->format);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FORMAT=0x%08X\n", mpctx->sh_video->format);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_BITRATE=%d\n", mpctx->sh_video->i_bps * 8);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_WIDTH=%d\n", mpctx->sh_video->disp_w);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_HEIGHT=%d\n", mpctx->sh_video->disp_h);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FPS=%5.3f\n", mpctx->sh_video->fps);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_ASPECT=%1.4f\n", mpctx->sh_video->aspect);
+ video_start_pts = ds_get_next_pts(mpctx->sh_video->ds);
+ }
+ if (mpctx->sh_audio) {
+ /* Assume FOURCC if all bytes >= 0x20 (' ') */
+ if (mpctx->sh_audio->format >= 0x20202020)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_FORMAT=%.4s\n", (char *)&mpctx->sh_audio->format);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_FORMAT=%d\n", mpctx->sh_audio->format);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_BITRATE=%d\n", mpctx->sh_audio->i_bps * 8);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_RATE=%d\n", mpctx->sh_audio->samplerate);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_NCH=%d\n", mpctx->sh_audio->channels);
+ start_pts = ds_get_next_pts(mpctx->sh_audio->ds);
+ }
+ if (video_start_pts != MP_NOPTS_VALUE) {
+ if (start_pts == MP_NOPTS_VALUE || !mpctx->sh_audio ||
+ (mpctx->sh_video && video_start_pts < start_pts))
+ start_pts = video_start_pts;
+ }
+ if (start_pts != MP_NOPTS_VALUE)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_START_TIME=%.2f\n", start_pts);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_START_TIME=unknown\n");
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_LENGTH=%.2f\n", get_time_length(mpctx));
+ int chapter_count = get_chapter_count(mpctx);
+ if (chapter_count >= 0) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTERS=%d\n", chapter_count);
+ for (int i = 0; i < chapter_count; i++) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_ID=%d\n", i);
+ // print in milliseconds
+ double time = chapter_start_time(mpctx, i) * 1000.0;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_START=%"PRId64"\n",
+ i, (int64_t)(time < 0 ? -1 : time));
+ char *name = chapter_name(mpctx, i);
+ if (name) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_NAME=%s\n", i,
+ name);
+ talloc_free(name);
+ }
+ }
+ }
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (demuxer->num_editions > 1)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO,
+ "Playing edition %d of %d (--edition=%d).\n",
+ demuxer->edition + 1, demuxer->num_editions, demuxer->edition);
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
+ for (int n = 0; n < mpctx->num_tracks; n++)
+ if (mpctx->tracks[n]->type == t)
+ print_stream(mpctx, mpctx->tracks[n], n);
+ }
+}
+
+/// step size of mixer changes
+int volstep = 3;
+
+static void set_demux_field(struct MPContext *mpctx, enum stream_type type,
+ struct sh_stream *s)
+{
+ mpctx->sh[type] = s;
+ // redundant fields for convenience access
+ switch(type) {
+ case STREAM_VIDEO: mpctx->sh_video = s ? s->video : NULL; break;
+ case STREAM_AUDIO: mpctx->sh_audio = s ? s->audio : NULL; break;
+ case STREAM_SUB: mpctx->sh_sub = s ? s->sub : NULL; break;
+ }
+}
+
+static void init_demux_stream(struct MPContext *mpctx, enum stream_type type)
+{
+ struct track *track = mpctx->current_track[type];
+ set_demux_field(mpctx, type, track ? track->stream : NULL);
+ struct sh_stream *stream = mpctx->sh[type];
+ if (stream)
+ demuxer_switch_track(stream->demuxer, type, stream);
+}
+
+static void cleanup_demux_stream(struct MPContext *mpctx, enum stream_type type)
+{
+ struct sh_stream *stream = mpctx->sh[type];
+ if (stream)
+ demuxer_switch_track(stream->demuxer, type, NULL);
+ set_demux_field(mpctx, type, NULL);
+}
+
+// Switch the demuxers to current track selection. This is possibly important
+// for intialization: if something reads packets from the demuxer (like at least
+// reinit_audio_chain does, or when seeking), packets from the other streams
+// should be queued instead of discarded. So all streams should be enabled
+// before the first initialization function is called.
+static void preselect_demux_streams(struct MPContext *mpctx)
+{
+ // Disable all streams, just to be sure no unwanted streams are selected.
+ for (int n = 0; n < mpctx->num_sources; n++) {
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++)
+ demuxer_switch_track(mpctx->sources[n], type, NULL);
+ }
+
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (track && track->stream)
+ demuxer_switch_track(track->stream->demuxer, type, track->stream);
+ }
+}
+
+static void uninit_subs(struct demuxer *demuxer)
+{
+ for (int i = 0; i < MAX_S_STREAMS; i++) {
+ struct sh_sub *sh = demuxer->s_streams[i];
+ if (sh && sh->initialized)
+ sub_uninit(sh);
+ }
+}
+
+void uninit_player(struct MPContext *mpctx, unsigned int mask)
+{
+ mask &= mpctx->initialized_flags;
+
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2, "\n*** uninit(0x%X)\n", mask);
+
+ if (mask & INITIALIZED_ACODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_ACODEC;
+ if (mpctx->sh_audio)
+ uninit_audio(mpctx->sh_audio);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+ mpctx->mixer.afilter = NULL;
+ }
+
+ if (mask & INITIALIZED_SUB) {
+ mpctx->initialized_flags &= ~INITIALIZED_SUB;
+ struct track *track = mpctx->current_track[STREAM_SUB];
+ // One of these was active; they can't be both active.
+ assert(!(mpctx->sh_sub && (track && track->sh_sub)));
+ if (mpctx->sh_sub)
+ sub_switchoff(mpctx->sh_sub, mpctx->osd);
+ if (track && track->sh_sub)
+ sub_switchoff(track->sh_sub, mpctx->osd);
+ cleanup_demux_stream(mpctx, STREAM_SUB);
+ reset_subtitles(mpctx);
+ }
+
+ if (mask & INITIALIZED_VCODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_VCODEC;
+ if (mpctx->sh_video)
+ uninit_video(mpctx->sh_video);
+ cleanup_demux_stream(mpctx, STREAM_VIDEO);
+ }
+
+ if (mask & INITIALIZED_DEMUXER) {
+ mpctx->initialized_flags &= ~INITIALIZED_DEMUXER;
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ sub_free(track->subdata);
+ talloc_free(track->sh_sub);
+ talloc_free(track);
+ }
+ mpctx->num_tracks = 0;
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++)
+ mpctx->current_track[t] = NULL;
+ assert(!mpctx->sh_video && !mpctx->sh_audio && !mpctx->sh_sub);
+ mpctx->master_demuxer = NULL;
+ for (int i = 0; i < mpctx->num_sources; i++) {
+ uninit_subs(mpctx->sources[i]);
+ struct demuxer *demuxer = mpctx->sources[i];
+ if (demuxer->stream != mpctx->stream)
+ free_stream(demuxer->stream);
+ free_demuxer(demuxer);
+ }
+ talloc_free(mpctx->sources);
+ mpctx->sources = NULL;
+ mpctx->demuxer = NULL;
+ mpctx->num_sources = 0;
+ talloc_free(mpctx->timeline);
+ mpctx->timeline = NULL;
+ mpctx->num_timeline_parts = 0;
+ talloc_free(mpctx->chapters);
+ mpctx->chapters = NULL;
+ mpctx->num_chapters = 0;
+ mpctx->video_offset = 0;
+ }
+
+ // kill the cache process:
+ if (mask & INITIALIZED_STREAM) {
+ mpctx->initialized_flags &= ~INITIALIZED_STREAM;
+ if (mpctx->stream)
+ free_stream(mpctx->stream);
+ mpctx->stream = NULL;
+ }
+
+ if (mask & INITIALIZED_VO) {
+ mpctx->initialized_flags &= ~INITIALIZED_VO;
+ vo_destroy(mpctx->video_out);
+ mpctx->video_out = NULL;
+ }
+
+ // Must be after libvo uninit, as few vo drivers (svgalib) have tty code.
+ if (mask & INITIALIZED_GETCH2) {
+ mpctx->initialized_flags &= ~INITIALIZED_GETCH2;
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2, "\n[[[uninit getch2]]]\n");
+ // restore terminal:
+ getch2_disable();
+ }
+
+ if (mask & INITIALIZED_VOBSUB) {
+ mpctx->initialized_flags &= ~INITIALIZED_VOBSUB;
+ if (vo_vobsub)
+ vobsub_close(vo_vobsub);
+ vo_vobsub = NULL;
+ }
+
+ if (mask & INITIALIZED_SPUDEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_SPUDEC;
+ spudec_free(vo_spudec);
+ vo_spudec = NULL;
+ }
+
+ if (mask & INITIALIZED_AO) {
+ mpctx->initialized_flags &= ~INITIALIZED_AO;
+ if (mpctx->mixer.ao)
+ mixer_uninit(&mpctx->mixer);
+ if (mpctx->ao)
+ ao_uninit(mpctx->ao, mpctx->stop_play != AT_END_OF_FILE);
+ mpctx->ao = NULL;
+ mpctx->mixer.ao = NULL;
+ }
+}
+
+static av_noreturn void exit_player(struct MPContext *mpctx,
+ enum exit_reason how, int rc)
+{
+ uninit_player(mpctx, INITIALIZED_ALL);
+
+#ifdef CONFIG_ENCODING
+ encode_lavc_finish(mpctx->encode_lavc_ctx);
+ encode_lavc_free(mpctx->encode_lavc_ctx);
+#endif
+
+ mpctx->encode_lavc_ctx = NULL;
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ timeEndPeriod(1);
+#endif
+
+ mp_input_uninit(mpctx->input);
+
+ osd_free(mpctx->osd);
+
+#ifdef CONFIG_ASS
+ ass_library_done(mpctx->ass_library);
+ mpctx->ass_library = NULL;
+#endif
+
+ talloc_free(mpctx->key_fifo);
+
+ switch (how) {
+ case EXIT_QUIT:
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "\nExiting... (%s)\n", "Quit");
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_EXIT=QUIT\n");
+ break;
+ case EXIT_EOF:
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "\nExiting... (%s)\n", "End of file");
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_EXIT=EOF\n");
+ break;
+ case EXIT_ERROR:
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "\nExiting... (%s)\n", "Fatal error");
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_EXIT=ERROR\n");
+ break;
+ default:
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_EXIT=NONE\n");
+ }
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2,
+ "max framesize was %d bytes\n", max_framesize);
+
+ // must be last since e.g. mp_msg uses option values
+ // that will be freed by this.
+ if (mpctx->mconfig)
+ m_config_free(mpctx->mconfig);
+ mpctx->mconfig = NULL;
+
+ talloc_free(mpctx);
+
+ exit(rc);
+}
+
+#include "cfg-mplayer.h"
+
+static int cfg_include(struct m_config *conf, char *filename)
+{
+ return m_config_parse_config_file(conf, filename);
+}
+
+#define DEF_CONFIG "# Write your default config options here!\n\n\n"
+
+static bool parse_cfgfiles(struct MPContext *mpctx, m_config_t *conf)
+{
+ struct MPOpts *opts = &mpctx->opts;
+ char *conffile;
+ int conffile_fd;
+ if (!(opts->noconfig & 2) &&
+ m_config_parse_config_file(conf, MPLAYER_CONFDIR "/mpv.conf") < 0)
+ return false;
+ if ((conffile = get_path("")) == NULL)
+ mp_tmsg(MSGT_CPLAYER, MSGL_WARN, "Cannot find HOME directory.\n");
+ else {
+ mkdir(conffile, 0777);
+ free(conffile);
+ if ((conffile = get_path("config")) == NULL)
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "get_path(\"config\") problem\n");
+ else {
+ if ((conffile_fd = open(conffile, O_CREAT | O_EXCL | O_WRONLY,
+ 0666)) != -1) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Creating config file: %s\n", conffile);
+ write(conffile_fd, DEF_CONFIG, sizeof(DEF_CONFIG) - 1);
+ close(conffile_fd);
+ }
+ if (!(opts->noconfig & 1) &&
+ m_config_parse_config_file(conf, conffile) < 0)
+ return false;
+ free(conffile);
+ }
+ }
+ return true;
+}
+
+#define PROFILE_CFG_PROTOCOL "protocol."
+
+static void load_per_protocol_config(m_config_t *conf, const char * const file)
+{
+ char *str;
+ char protocol[strlen(PROFILE_CFG_PROTOCOL) + strlen(file) + 1];
+ m_profile_t *p;
+
+ /* does filename actually uses a protocol ? */
+ str = strstr(file, "://");
+ if (!str)
+ return;
+
+ sprintf(protocol, "%s%s", PROFILE_CFG_PROTOCOL, file);
+ protocol[strlen(PROFILE_CFG_PROTOCOL) + strlen(file) - strlen(str)] = '\0';
+ p = m_config_get_profile(conf, protocol);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading protocol-related profile '%s'\n", protocol);
+ m_config_set_profile(conf, p);
+ }
+}
+
+#define PROFILE_CFG_EXTENSION "extension."
+
+static void load_per_extension_config(m_config_t *conf, const char * const file)
+{
+ char *str;
+ char extension[strlen(PROFILE_CFG_EXTENSION) + 8];
+ m_profile_t *p;
+
+ /* does filename actually have an extension ? */
+ str = strrchr(file, '.');
+ if (!str)
+ return;
+
+ sprintf(extension, PROFILE_CFG_EXTENSION);
+ strncat(extension, ++str, 7);
+ p = m_config_get_profile(conf, extension);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading extension-related profile '%s'\n", extension);
+ m_config_set_profile(conf, p);
+ }
+}
+
+#define PROFILE_CFG_VO "vo."
+#define PROFILE_CFG_AO "ao."
+
+static void load_per_output_config(m_config_t *conf, char *cfg, char *out)
+{
+ char profile[strlen(cfg) + strlen(out) + 1];
+ m_profile_t *p;
+
+ sprintf(profile, "%s%s", cfg, out);
+ p = m_config_get_profile(conf, profile);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading extension-related profile '%s'\n", profile);
+ m_config_set_profile(conf, p);
+ }
+}
+
+/**
+ * Tries to load a config file
+ * @return 0 if file was not found, 1 otherwise
+ */
+static int try_load_config(m_config_t *conf, const char *file)
+{
+ if (!mp_path_exists(file))
+ return 0;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Loading config '%s'\n", file);
+ m_config_parse_config_file(conf, file);
+ return 1;
+}
+
+static void load_per_file_config(m_config_t *conf, const char * const file)
+{
+ char *confpath;
+ char cfg[MP_PATH_MAX];
+ const char *name;
+
+ if (strlen(file) > MP_PATH_MAX - 14) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Filename is too long, "
+ "can not load file or directory specific config files\n");
+ return;
+ }
+ sprintf(cfg, "%s.conf", file);
+
+ name = mp_basename(cfg);
+ if (use_filedir_conf) {
+ char dircfg[MP_PATH_MAX];
+ strcpy(dircfg, cfg);
+ strcpy(dircfg + (name - cfg), "mpv.conf");
+ try_load_config(conf, dircfg);
+
+ if (try_load_config(conf, cfg))
+ return;
+ }
+
+ if ((confpath = get_path(name)) != NULL) {
+ try_load_config(conf, confpath);
+
+ free(confpath);
+ }
+}
+
+static void load_per_file_options(m_config_t *conf,
+ struct playlist_param *params,
+ int params_count)
+{
+ for (int n = 0; n < params_count; n++)
+ m_config_set_option(conf, params[n].name, params[n].value);
+}
+
+/* When libmpdemux performs a blocking operation (network connection or
+ * cache filling) if the operation fails we use this function to check
+ * if it was interrupted by the user.
+ * The function returns whether it was interrupted. */
+static bool libmpdemux_was_interrupted(struct MPContext *mpctx)
+{
+ for (;;) {
+ if (mpctx->stop_play != KEEP_PLAYING
+ && mpctx->stop_play != AT_END_OF_FILE)
+ return true;
+ mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, 0, 0);
+ if (!cmd)
+ break;
+ if (mp_input_is_abort_cmd(cmd->id))
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ }
+ return false;
+}
+
+static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
+{
+ int new_id = -1;
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ if (track->type == t)
+ new_id = FFMAX(new_id, track->user_tid);
+ }
+ return new_id + 1;
+}
+
+// Map stream number (as used by libdvdread) to MPEG IDs (as used by demuxer).
+static int map_id_from_demuxer(struct demuxer *d, enum stream_type type, int id)
+{
+ if (d->stream->type == STREAMTYPE_DVD && type == STREAM_SUB)
+ id = id & 0x1F;
+ return id;
+}
+static int map_id_to_demuxer(struct demuxer *d, enum stream_type type, int id)
+{
+ if (d->stream->type == STREAMTYPE_DVD && type == STREAM_SUB)
+ id = id | 0x20;
+ return id;
+}
+
+static struct track *add_stream_track(struct MPContext *mpctx,
+ struct sh_stream *stream,
+ bool under_timeline)
+{
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ if (track->stream == stream)
+ return track;
+ // DVD subtitle track that was added later
+ if (stream->type == STREAM_SUB && track->type == STREAM_SUB &&
+ map_id_from_demuxer(stream->demuxer, stream->type,
+ stream->demuxer_id) == track->demuxer_id
+ && !track->stream)
+ {
+ track->stream = stream;
+ track->demuxer_id = stream->demuxer_id;
+ // Initialize lazily selected track
+ if (track == mpctx->current_track[STREAM_SUB])
+ reinit_subs(mpctx);
+ return track;
+ }
+ }
+
+ struct track *track = talloc_ptrtype(NULL, track);
+ *track = (struct track) {
+ .type = stream->type,
+ .user_tid = find_new_tid(mpctx, stream->type),
+ .demuxer_id = stream->demuxer_id,
+ .title = stream->title,
+ .default_track = stream->default_track,
+ .lang = stream->common_header->lang,
+ .under_timeline = under_timeline,
+ .demuxer = stream->demuxer,
+ .stream = stream,
+ };
+ MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
+
+ // Needed for DVD and Blu-ray.
+ if (!track->lang) {
+ struct stream_lang_req req = {
+ .type = track->type,
+ .id = map_id_from_demuxer(track->demuxer, track->type,
+ track->demuxer_id)
+ };
+ stream_control(track->demuxer->stream, STREAM_CTRL_GET_LANG, &req);
+ track->lang = talloc_steal(track, req.name);
+ }
+
+ return track;
+}
+
+static void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer)
+{
+ for (int n = 0; n < demuxer->num_streams; n++)
+ add_stream_track(mpctx, demuxer->streams[n], !!mpctx->timeline);
+}
+
+static void add_dvd_tracks(struct MPContext *mpctx)
+{
+#ifdef CONFIG_DVDREAD
+ struct demuxer *demuxer = mpctx->demuxer;
+ struct stream *stream = demuxer->stream;
+ if (stream->type == STREAMTYPE_DVD) {
+ int n_subs = dvd_number_of_subs(stream);
+ for (int n = 0; n < n_subs; n++) {
+ struct track *track = talloc_ptrtype(NULL, track);
+ *track = (struct track) {
+ .type = STREAM_SUB,
+ .user_tid = find_new_tid(mpctx, STREAM_SUB),
+ .demuxer_id = n,
+ .demuxer = mpctx->demuxer,
+ };
+ MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
+
+ struct stream_lang_req req = {.type = STREAM_SUB, .id = n};
+ stream_control(stream, STREAM_CTRL_GET_LANG, &req);
+ track->lang = talloc_steal(track, req.name);
+ }
+ }
+#endif
+}
+
+void add_subtitles(struct MPContext *mpctx, char *filename, float fps,
+ int noerr)
+{
+ struct MPOpts *opts = &mpctx->opts;
+ sub_data *subd = NULL;
+ struct sh_sub *sh = NULL;
+
+ if (filename == NULL)
+ return;
+
+ if (opts->ass_enabled) {
+#ifdef CONFIG_ASS
+ struct ass_track *asst = mp_ass_read_stream(mpctx->ass_library,
+ filename, sub_cp);
+ bool is_native_ass = asst;
+ if (!asst) {
+ subd = sub_read_file(filename, fps, &mpctx->opts);
+ if (subd) {
+ asst = mp_ass_read_subdata(mpctx->ass_library, opts, subd, fps);
+ sub_free(subd);
+ subd = NULL;
+ }
+ }
+ if (asst)
+ sh = sd_ass_create_from_track(asst, is_native_ass, opts);
+#endif
+ } else
+ subd = sub_read_file(filename, fps, &mpctx->opts);
+
+
+ if (!sh && !subd) {
+ mp_tmsg(MSGT_CPLAYER, noerr ? MSGL_WARN : MSGL_ERR,
+ "Cannot load subtitles: %s\n", filename);
+ return;
+ }
+
+ struct track *track = talloc_ptrtype(NULL, track);
+ *track = (struct track) {
+ .type = STREAM_SUB,
+ .title = talloc_strdup(track, filename),
+ .user_tid = find_new_tid(mpctx, STREAM_SUB),
+ .demuxer_id = -1,
+ .is_external = true,
+ .sh_sub = sh,
+ .subdata = subd,
+ };
+ MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
+}
+
+void init_vo_spudec(struct MPContext *mpctx)
+{
+ uninit_player(mpctx, INITIALIZED_SPUDEC);
+ unsigned width, height;
+
+ // we currently can't work without video stream
+ if (!mpctx->sh_video)
+ return;
+
+ if (spudec_ifo) {
+ unsigned int palette[16];
+ if (vobsub_parse_ifo(NULL, spudec_ifo, palette, &width, &height,
+ 1, -1, NULL) >= 0)
+ vo_spudec = spudec_new_scaled(palette, width, height, NULL, 0);
+ }
+
+ width = mpctx->sh_video->disp_w;
+ height = mpctx->sh_video->disp_h;
+
+#ifdef CONFIG_DVDREAD
+ if (vo_spudec == NULL && mpctx->stream->type == STREAMTYPE_DVD) {
+ vo_spudec = spudec_new_scaled(((dvd_priv_t *)(mpctx->stream->priv))->
+ cur_pgc->palette, width, height, NULL, 0);
+ }
+#endif
+
+ if (vo_spudec == NULL && mpctx->sh_sub) {
+ sh_sub_t *sh = mpctx->sh_sub;
+ vo_spudec = spudec_new_scaled(NULL, width, height, sh->extradata,
+ sh->extradata_len);
+ }
+
+ if (vo_spudec != NULL) {
+ mpctx->initialized_flags |= INITIALIZED_SPUDEC;
+ mp_property_do("sub-forced-only", M_PROPERTY_SET, &forced_subs_only,
+ mpctx);
+ }
+}
+
+/**
+ * \brief append a formatted string
+ * \param buf buffer to print into
+ * \param len maximum number of characters in buf, not including terminating 0
+ * \param format printf format string
+ */
+static void saddf(char *buf, int len, const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ int pos = strlen(buf);
+ pos += vsnprintf(buf + pos, len - pos, format, va);
+ va_end(va);
+ if (pos >= len && len > 0)
+ buf[len - 1] = 0;
+}
+
+/**
+ * \brief append time in the hh:mm:ss.f format
+ * \param buf buffer to print into
+ * \param len maximum number of characters in buf, not including terminating 0
+ * \param time time value to convert/append
+ */
+static void sadd_hhmmssff(char *buf, int len, double time, bool fractions)
+{
+ char *s = mp_format_time(time, fractions);
+ saddf(buf, len, "%s", s);