Move mpvcore/player/ to player/

This commit is contained in:
wm4
2013-12-17 00:53:22 +01:00
parent 7dc7b900c6
commit e449111429
25 changed files with 56 additions and 54 deletions

471
player/audio.c Normal file
View File

@@ -0,0 +1,471 @@
/*
* 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 <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <math.h>
#include <assert.h>
#include "config.h"
#include "talloc.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/options.h"
#include "mpvcore/mp_common.h"
#include "audio/mixer.h"
#include "audio/audio.h"
#include "audio/audio_buffer.h"
#include "audio/decode/dec_audio.h"
#include "audio/filter/af.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "video/decode/dec_video.h"
#include "mp_core.h"
static int build_afilter_chain(struct MPContext *mpctx)
{
struct dec_audio *d_audio = mpctx->d_audio;
struct ao *ao = mpctx->ao;
struct MPOpts *opts = mpctx->opts;
if (!d_audio)
return 0;
struct mp_audio in_format;
mp_audio_buffer_get_format(d_audio->decode_buffer, &in_format);
int new_srate;
if (af_control_any_rev(d_audio->afilter, AF_CONTROL_SET_PLAYBACK_SPEED,
&opts->playback_speed))
new_srate = in_format.rate;
else {
new_srate = in_format.rate * opts->playback_speed;
if (new_srate != ao->samplerate) {
// limits are taken from libaf/af_resample.c
if (new_srate < 8000)
new_srate = 8000;
if (new_srate > 192000)
new_srate = 192000;
opts->playback_speed = new_srate / (double)in_format.rate;
}
}
return audio_init_filters(d_audio, new_srate,
&ao->samplerate, &ao->channels, &ao->format);
}
static int recreate_audio_filters(struct MPContext *mpctx)
{
assert(mpctx->d_audio);
// init audio filters:
if (!build_afilter_chain(mpctx)) {
MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
return -1;
}
mixer_reinit_audio(mpctx->mixer, mpctx->ao, mpctx->d_audio->afilter);
return 0;
}
int reinit_audio_filters(struct MPContext *mpctx)
{
struct dec_audio *d_audio = mpctx->d_audio;
if (!d_audio)
return -2;
af_uninit(mpctx->d_audio->afilter);
if (af_init(mpctx->d_audio->afilter) < 0)
return -1;
if (recreate_audio_filters(mpctx) < 0)
return -1;
return 0;
}
void reinit_audio_chain(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct sh_stream *sh = init_demux_stream(mpctx, STREAM_AUDIO);
if (!sh) {
uninit_player(mpctx, INITIALIZED_AO);
goto no_audio;
}
if (!(mpctx->initialized_flags & INITIALIZED_ACODEC)) {
mpctx->initialized_flags |= INITIALIZED_ACODEC;
assert(!mpctx->d_audio);
mpctx->d_audio = talloc_zero(NULL, struct dec_audio);
mpctx->d_audio->opts = opts;
mpctx->d_audio->header = sh;
if (!audio_init_best_codec(mpctx->d_audio, opts->audio_decoders))
goto init_error;
}
assert(mpctx->d_audio);
struct mp_audio in_format;
mp_audio_buffer_get_format(mpctx->d_audio->decode_buffer, &in_format);
int ao_srate = opts->force_srate;
int ao_format = opts->audio_output_format;
struct mp_chmap ao_channels = {0};
if (mpctx->initialized_flags & INITIALIZED_AO) {
ao_srate = mpctx->ao->samplerate;
ao_format = mpctx->ao->format;
ao_channels = mpctx->ao->channels;
} else {
// Automatic downmix
if (mp_chmap_is_stereo(&opts->audio_output_channels) &&
!mp_chmap_is_stereo(&in_format.channels))
{
mp_chmap_from_channels(&ao_channels, 2);
}
}
// Determine what the filter chain outputs. build_afilter_chain() also
// needs this for testing whether playback speed is changed by resampling
// or using a special filter.
if (!audio_init_filters(mpctx->d_audio, // preliminary init
// input:
in_format.rate,
// output:
&ao_srate, &ao_channels, &ao_format)) {
MP_ERR(mpctx, "Error at audio filter chain pre-init!\n");
goto init_error;
}
if (!(mpctx->initialized_flags & INITIALIZED_AO)) {
mpctx->initialized_flags |= INITIALIZED_AO;
mp_chmap_remove_useless_channels(&ao_channels,
&opts->audio_output_channels);
mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
mpctx->encode_lavc_ctx, ao_srate, ao_format,
ao_channels);
struct ao *ao = mpctx->ao;
if (!ao) {
MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
goto init_error;
}
ao->buffer = mp_audio_buffer_create(ao);
mp_audio_buffer_reinit_fmt(ao->buffer, ao->format, &ao->channels,
ao->samplerate);
char *s = mp_audio_fmt_to_str(ao->samplerate, &ao->channels, ao->format);
MP_INFO(mpctx, "AO: [%s] %s\n", ao->driver->name, s);
talloc_free(s);
MP_VERBOSE(mpctx, "AO: Description: %s\n", ao->driver->description);
update_window_title(mpctx, true);
}
if (recreate_audio_filters(mpctx) < 0)
goto init_error;
mpctx->syncing_audio = true;
return;
init_error:
uninit_player(mpctx, INITIALIZED_ACODEC | INITIALIZED_AO);
cleanup_demux_stream(mpctx, STREAM_AUDIO);
no_audio:
mpctx->current_track[STREAM_AUDIO] = NULL;
MP_INFO(mpctx, "Audio: no audio\n");
}
// Return pts value corresponding to the end point of audio written to the
// ao so far.
double written_audio_pts(struct MPContext *mpctx)
{
struct dec_audio *d_audio = mpctx->d_audio;
if (!d_audio)
return MP_NOPTS_VALUE;
struct mp_audio in_format;
mp_audio_buffer_get_format(d_audio->decode_buffer, &in_format);
// first calculate the end pts of audio that has been output by decoder
double a_pts = d_audio->pts;
if (a_pts == MP_NOPTS_VALUE)
return MP_NOPTS_VALUE;
// d_audio->pts is the timestamp of the latest input packet with
// known pts that the decoder has decoded. d_audio->pts_bytes is
// the amount of bytes the decoder has written after that timestamp.
a_pts += d_audio->pts_offset / (double)in_format.rate;
// Now a_pts hopefully holds the pts for end of audio from decoder.
// Subtract data in buffers between decoder and audio out.
// Decoded but not filtered
a_pts -= mp_audio_buffer_seconds(d_audio->decode_buffer);
// Data buffered in audio filters, measured in seconds of "missing" output
double buffered_output = af_calc_delay(d_audio->afilter);
// Data that was ready for ao but was buffered because ao didn't fully
// accept everything to internal buffers yet
buffered_output += mp_audio_buffer_seconds(mpctx->ao->buffer);
// Filters divide audio length by playback_speed, so multiply by it
// to get the length in original units without speedup or slowdown
a_pts -= buffered_output * mpctx->opts->playback_speed;
return a_pts + mpctx->video_offset;
}
// Return pts value corresponding to currently playing audio.
double playing_audio_pts(struct MPContext *mpctx)
{
double pts = written_audio_pts(mpctx);
if (pts == MP_NOPTS_VALUE)
return pts;
return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao);
}
static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags,
double pts)
{
if (mpctx->paused)
return 0;
struct ao *ao = mpctx->ao;
ao->pts = pts;
double real_samplerate = ao->samplerate / mpctx->opts->playback_speed;
int played = ao_play(mpctx->ao, data->planes, data->samples, flags);
assert(played <= data->samples);
if (played > 0) {
mpctx->shown_aframes += played;
mpctx->delay += played / real_samplerate;
// Keep correct pts for remaining data - could be used to flush
// remaining buffer when closing ao.
ao->pts += played / real_samplerate;
return played;
}
return 0;
}
static int write_silence_to_ao(struct MPContext *mpctx, int samples, int flags,
double pts)
{
struct mp_audio tmp = {0};
mp_audio_buffer_get_format(mpctx->ao->buffer, &tmp);
tmp.samples = samples;
char *p = talloc_size(NULL, tmp.samples * tmp.sstride);
for (int n = 0; n < tmp.num_planes; n++)
tmp.planes[n] = p;
mp_audio_fill_silence(&tmp, 0, tmp.samples);
int r = write_to_ao(mpctx, &tmp, 0, pts);
talloc_free(p);
return r;
}
#define ASYNC_PLAY_DONE -3
static int audio_start_sync(struct MPContext *mpctx, int playsize)
{
struct ao *ao = mpctx->ao;
struct MPOpts *opts = mpctx->opts;
struct dec_audio *d_audio = mpctx->d_audio;
int res;
assert(d_audio);
// Timing info may not be set without
res = audio_decode(d_audio, ao->buffer, 1);
if (res < 0)
return res;
int samples;
bool did_retry = false;
double written_pts;
double real_samplerate = ao->samplerate / opts->playback_speed;
bool hrseek = mpctx->hrseek_active; // audio only hrseek
mpctx->hrseek_active = false;
while (1) {
written_pts = written_audio_pts(mpctx);
double ptsdiff;
if (hrseek)
ptsdiff = written_pts - mpctx->hrseek_pts;
else
ptsdiff = written_pts - mpctx->video_next_pts - mpctx->delay
- mpctx->audio_delay;
samples = ptsdiff * real_samplerate;
// ogg demuxers give packets without timing
if (written_pts <= 1 && d_audio->pts == MP_NOPTS_VALUE) {
if (!did_retry) {
// Try to read more data to see packets that have pts
res = audio_decode(d_audio, ao->buffer, ao->samplerate);
if (res < 0)
return res;
did_retry = true;
continue;
}
samples = 0;
}
if (fabs(ptsdiff) > 300 || isnan(ptsdiff)) // pts reset or just broken?
samples = 0;
if (samples > 0)
break;
mpctx->syncing_audio = false;
int skip_samples = -samples;
int a = MPMIN(skip_samples, MPMAX(playsize, 2500));
res = audio_decode(d_audio, ao->buffer, a);
if (skip_samples <= mp_audio_buffer_samples(ao->buffer)) {
mp_audio_buffer_skip(ao->buffer, skip_samples);
if (res < 0)
return res;
return audio_decode(d_audio, ao->buffer, playsize);
}
mp_audio_buffer_clear(ao->buffer);
if (res < 0)
return res;
}
if (hrseek)
// Don't add silence in audio-only case even if position is too late
return 0;
if (samples >= playsize) {
/* This case could fall back to the one below with
* samples = playsize, but then silence would keep accumulating
* in ao->buffer if the AO accepts less data than it asks for
* in playsize. */
write_silence_to_ao(mpctx, playsize, 0,
written_pts - samples / real_samplerate);
return ASYNC_PLAY_DONE;
}
mpctx->syncing_audio = false;
mp_audio_buffer_prepend_silence(ao->buffer, samples);
return audio_decode(d_audio, ao->buffer, playsize);
}
int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
{
struct MPOpts *opts = mpctx->opts;
struct ao *ao = mpctx->ao;
int playsize;
int playflags = 0;
bool audio_eof = false;
bool signal_eof = false;
bool partial_fill = false;
struct dec_audio *d_audio = mpctx->d_audio;
// Can't adjust the start of audio with spdif pass-through.
bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK);
assert(d_audio);
if (mpctx->paused)
playsize = 1; // just initialize things (audio pts at least)
else
playsize = ao_get_space(ao);
// Coming here with hrseek_active still set means audio-only
if (!mpctx->d_video || !mpctx->sync_audio_to_video)
mpctx->syncing_audio = false;
if (!opts->initial_audio_sync || !modifiable_audio_format) {
mpctx->syncing_audio = false;
mpctx->hrseek_active = false;
}
int res;
if (mpctx->syncing_audio || mpctx->hrseek_active)
res = audio_start_sync(mpctx, playsize);
else
res = audio_decode(d_audio, ao->buffer, playsize);
if (res < 0) { // EOF, error or format change
if (res == -2) {
/* The format change isn't handled too gracefully. A more precise
* implementation would require draining buffered old-format audio
* while displaying video, then doing the output format switch.
*/
if (!mpctx->opts->gapless_audio)
uninit_player(mpctx, INITIALIZED_AO);
reinit_audio_chain(mpctx);
return -1;
} else if (res == ASYNC_PLAY_DONE)
return 0;
else if (demux_stream_eof(d_audio->header))
audio_eof = true;
}
if (endpts != MP_NOPTS_VALUE) {
double samples = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay)
* ao->samplerate / opts->playback_speed;
if (playsize > samples) {
playsize = MPMAX(samples, 0);
audio_eof = true;
partial_fill = true;
}
}
if (playsize > mp_audio_buffer_samples(ao->buffer)) {
playsize = mp_audio_buffer_samples(ao->buffer);
partial_fill = true;
}
if (!playsize)
return partial_fill && audio_eof ? -2 : -partial_fill;
if (audio_eof && partial_fill) {
if (opts->gapless_audio) {
// With gapless audio, delay this to ao_uninit. There must be only
// 1 final chunk, and that is handled when calling ao_uninit().
signal_eof = true;
} else {
playflags |= AOPLAY_FINAL_CHUNK;
}
}
assert(ao->buffer_playable_samples <= mp_audio_buffer_samples(ao->buffer));
struct mp_audio data;
mp_audio_buffer_peek(ao->buffer, &data);
data.samples = MPMIN(data.samples, playsize);
int played = write_to_ao(mpctx, &data, playflags, written_audio_pts(mpctx));
ao->buffer_playable_samples = playsize - played;
if (played > 0) {
mp_audio_buffer_skip(ao->buffer, played);
} else if (!mpctx->paused && audio_eof && ao_get_delay(ao) < .04) {
// Sanity check to avoid hanging in case current ao doesn't output
// partial chunks and doesn't check for AOPLAY_FINAL_CHUNK
signal_eof = true;
}
return signal_eof ? -2 : -partial_fill;
}
// Drop data queued for output, or which the AO is currently outputting.
void clear_audio_output_buffers(struct MPContext *mpctx)
{
if (mpctx->ao) {
ao_reset(mpctx->ao);
mp_audio_buffer_clear(mpctx->ao->buffer);
mpctx->ao->buffer_playable_samples = 0;
}
}
// Drop decoded data queued for filtering.
void clear_audio_decode_buffers(struct MPContext *mpctx)
{
if (mpctx->d_audio)
mp_audio_buffer_clear(mpctx->d_audio->decode_buffer);
}

3243
player/command.c Normal file
View File

File diff suppressed because it is too large Load Diff

50
player/command.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* 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.
*/
#ifndef MPLAYER_COMMAND_H
#define MPLAYER_COMMAND_H
struct MPContext;
struct mp_cmd;
void command_init(struct MPContext *mpctx);
void command_uninit(struct MPContext *mpctx);
void run_command(struct MPContext *mpctx, struct mp_cmd *cmd);
char *mp_property_expand_string(struct MPContext *mpctx, const char *str);
void property_print_help(void);
int mp_property_do(const char* name, int action, void* val,
struct MPContext *mpctx);
const struct m_option *mp_get_property_list(void);
enum mp_event {
MP_EVENT_NONE,
MP_EVENT_TICK,
MP_EVENT_PROPERTY, // char*, property that is changed
MP_EVENT_TRACKS_CHANGED,
MP_EVENT_START_FILE,
MP_EVENT_END_FILE,
};
void mp_notify(struct MPContext *mpctx, enum mp_event event, void *arg);
void mp_notify_property(struct MPContext *mpctx, const char *property);
void mp_flush_events(struct MPContext *mpctx);
#endif /* MPLAYER_COMMAND_H */

354
player/configfiles.c Normal file
View File

@@ -0,0 +1,354 @@
/*
* 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 <stddef.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <libavutil/md5.h>
#include "config.h"
#include "talloc.h"
#include "osdep/io.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/path.h"
#include "mpvcore/m_config.h"
#include "mpvcore/parser-cfg.h"
#include "mpvcore/playlist.h"
#include "mpvcore/options.h"
#include "mpvcore/m_property.h"
#include "stream/stream.h"
#include "mp_core.h"
#include "command.h"
#define DEF_CONFIG "# Write your default config options here!\n\n\n"
bool mp_parse_cfgfiles(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
m_config_t *conf = mpctx->mconfig;
char *conffile;
if (!opts->load_config)
return true;
if (!m_config_parse_config_file(conf, MPLAYER_CONFDIR "/mpv.conf", 0) < 0)
return false;
mp_mk_config_dir(NULL);
if ((conffile = mp_find_user_config_file("config")) == NULL)
MP_ERR(mpctx, "mp_find_user_config_file(\"config\") problem\n");
else {
int fd = open(conffile, O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
if (fd != -1) {
MP_INFO(mpctx, "Creating config file: %s\n", conffile);
write(fd, DEF_CONFIG, sizeof(DEF_CONFIG) - 1);
close(fd);
}
if (m_config_parse_config_file(conf, conffile, 0) < 0)
return false;
talloc_free(conffile);
}
return true;
}
// Set options file-local, and don't set them if the user set them via the
// command line.
#define FILE_LOCAL_FLAGS (M_SETOPT_BACKUP | M_SETOPT_PRESERVE_CMDLINE)
#define PROFILE_CFG_PROTOCOL "protocol."
void mp_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 ? */
if (!mp_is_url(bstr0(file)))
return;
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_profile0(conf, protocol);
if (p) {
mp_msg(MSGT_CPLAYER, MSGL_INFO,
"Loading protocol-related profile '%s'\n", protocol);
m_config_set_profile(conf, p, FILE_LOCAL_FLAGS);
}
}
#define PROFILE_CFG_EXTENSION "extension."
void mp_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_profile0(conf, extension);
if (p) {
mp_msg(MSGT_CPLAYER, MSGL_INFO,
"Loading extension-related profile '%s'\n", extension);
m_config_set_profile(conf, p, FILE_LOCAL_FLAGS);
}
}
void mp_load_per_output_config(m_config_t *conf, char *cfg, char *out)
{
char profile[strlen(cfg) + strlen(out) + 1];
m_profile_t *p;
if (!out && !out[0])
return;
sprintf(profile, "%s%s", cfg, out);
p = m_config_get_profile0(conf, profile);
if (p) {
mp_msg(MSGT_CPLAYER, MSGL_INFO,
"Loading extension-related profile '%s'\n", profile);
m_config_set_profile(conf, p, FILE_LOCAL_FLAGS);
}
}
/**
* Tries to load a config file (in file local mode)
* @return 0 if file was not found, 1 otherwise
*/
static int try_load_config(m_config_t *conf, const char *file, int flags)
{
if (!mp_path_exists(file))
return 0;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Loading config '%s'\n", file);
m_config_parse_config_file(conf, file, flags);
return 1;
}
void mp_load_per_file_config(m_config_t *conf, const char * const file,
bool search_file_dir)
{
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 (search_file_dir) {
char dircfg[MP_PATH_MAX];
strcpy(dircfg, cfg);
strcpy(dircfg + (name - cfg), "mpv.conf");
try_load_config(conf, dircfg, FILE_LOCAL_FLAGS);
if (try_load_config(conf, cfg, FILE_LOCAL_FLAGS))
return;
}
if ((confpath = mp_find_user_config_file(name)) != NULL) {
try_load_config(conf, confpath, FILE_LOCAL_FLAGS);
talloc_free(confpath);
}
}
#define MP_WATCH_LATER_CONF "watch_later"
char *mp_get_playback_resume_config_filename(const char *fname,
struct MPOpts *opts)
{
char *res = NULL;
void *tmp = talloc_new(NULL);
const char *realpath = fname;
bstr bfname = bstr0(fname);
if (!mp_is_url(bfname)) {
char *cwd = mp_getcwd(tmp);
if (!cwd)
goto exit;
realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
}
#if HAVE_DVDREAD || HAVE_DVDNAV
if (bstr_startswith0(bfname, "dvd://"))
realpath = talloc_asprintf(tmp, "%s - %s", realpath, dvd_device);
#endif
#if HAVE_LIBBLURAY
if (bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") ||
bstr_startswith0(bfname, "bluray://"))
realpath = talloc_asprintf(tmp, "%s - %s", realpath, bluray_device);
#endif
uint8_t md5[16];
av_md5_sum(md5, realpath, strlen(realpath));
char *conf = talloc_strdup(tmp, "");
for (int i = 0; i < 16; i++)
conf = talloc_asprintf_append(conf, "%02X", md5[i]);
conf = talloc_asprintf(tmp, "%s/%s", MP_WATCH_LATER_CONF, conf);
res = mp_find_user_config_file(conf);
exit:
talloc_free(tmp);
return res;
}
static const char *backup_properties[] = {
"osd-level",
//"loop",
"speed",
"edition",
"pause",
"volume-restore-data",
"audio-delay",
//"balance",
"fullscreen",
"colormatrix",
"colormatrix-input-range",
"colormatrix-output-range",
"ontop",
"border",
"gamma",
"brightness",
"contrast",
"saturation",
"hue",
"deinterlace",
"vf",
"af",
"panscan",
"aid",
"vid",
"sid",
"sub-delay",
"sub-pos",
"sub-visibility",
"sub-scale",
"ass-use-margins",
"ass-vsfilter-aspect-compat",
"ass-style-override",
0
};
// Should follow what parser-cfg.c does/needs
static bool needs_config_quoting(const char *s)
{
for (int i = 0; s && s[i]; i++) {
unsigned char c = s[i];
if (!isprint(c) || isspace(c) || c == '#' || c == '\'' || c == '"')
return true;
}
return false;
}
void mp_write_watch_later_conf(struct MPContext *mpctx)
{
void *tmp = talloc_new(NULL);
char *filename = mpctx->filename;
if (!filename)
goto exit;
double pos = get_current_time(mpctx);
if (pos == MP_NOPTS_VALUE)
goto exit;
mp_mk_config_dir(MP_WATCH_LATER_CONF);
char *conffile = mp_get_playback_resume_config_filename(mpctx->filename,
mpctx->opts);
talloc_steal(tmp, conffile);
if (!conffile)
goto exit;
MP_INFO(mpctx, "Saving state.\n");
FILE *file = fopen(conffile, "wb");
if (!file)
goto exit;
fprintf(file, "start=%f\n", pos);
for (int i = 0; backup_properties[i]; i++) {
const char *pname = backup_properties[i];
char *val = NULL;
int r = mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
if (r == M_PROPERTY_OK) {
if (needs_config_quoting(val)) {
// e.g. '%6%STRING'
fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
} else {
fprintf(file, "%s=%s\n", pname, val);
}
}
talloc_free(val);
}
fclose(file);
exit:
talloc_free(tmp);
}
void mp_load_playback_resume(m_config_t *conf, const char *file)
{
char *fname = mp_get_playback_resume_config_filename(file, conf->optstruct);
if (fname && mp_path_exists(fname)) {
// Never apply the saved start position to following files
m_config_backup_opt(conf, "start");
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Resuming playback. This behavior can "
"be disabled with --no-resume-playback.\n");
try_load_config(conf, fname, M_SETOPT_PRESERVE_CMDLINE);
unlink(fname);
}
talloc_free(fname);
}
// Returns the first file that has a resume config.
// Compared to hashing the playlist file or contents and managing separate
// resume file for them, this is simpler, and also has the nice property
// that appending to a playlist doesn't interfere with resuming (especially
// if the playlist comes from the command line).
struct playlist_entry *mp_resume_playlist(struct playlist *playlist,
struct MPOpts *opts)
{
if (!opts->position_resume)
return NULL;
for (struct playlist_entry *e = playlist->first; e; e = e->next) {
char *conf = mp_get_playback_resume_config_filename(e->filename, opts);
bool exists = conf && mp_path_exists(conf);
talloc_free(conf);
if (exists)
return e;
}
return NULL;
}

251
player/dvdnav.c Normal file
View File

@@ -0,0 +1,251 @@
/*
* This file is part of mpv.
*
* mpv 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.
*
* mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <limits.h>
#include <assert.h>
#include "mp_core.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/input/input.h"
#include "stream/stream_dvdnav.h"
#include "sub/dec_sub.h"
#include "sub/osd.h"
#include "video/mp_image.h"
#include "video/decode/dec_video.h"
struct mp_nav_state {
struct mp_log *log;
int nav_still_frame;
bool nav_eof;
bool nav_menu;
bool nav_draining;
int hi_visible;
int highlight[4]; // x0 y0 x1 y1
int subsize[2];
struct sub_bitmap *hi_elem;
};
// Allocate state and enable navigation features. Must happen before
// initializing cache, because the cache would read data. Since stream_dvdnav is
// in a mode which skips all transitions on reading data (before enabling
// navigation), this would skip some menu screens.
void mp_nav_init(struct MPContext *mpctx)
{
assert(!mpctx->nav_state);
// dvdnav is interactive
if (mpctx->encode_lavc_ctx)
return;
struct mp_nav_cmd inp = {MP_NAV_CMD_ENABLE};
if (stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp) < 1)
return;
mpctx->nav_state = talloc_zero(NULL, struct mp_nav_state);
mpctx->nav_state->log = mp_log_new(mpctx->nav_state, mpctx->log, "dvdnav");
MP_VERBOSE(mpctx->nav_state, "enabling\n");
mp_input_enable_section(mpctx->input, "dvdnav", 0);
mp_input_set_section_mouse_area(mpctx->input, "dvdnav-menu",
INT_MIN, INT_MIN, INT_MAX, INT_MAX);
}
void mp_nav_reset(struct MPContext *mpctx)
{
struct mp_nav_state *nav = mpctx->nav_state;
if (!nav)
return;
struct mp_nav_cmd inp = {MP_NAV_CMD_RESUME};
stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
nav->hi_visible = 0;
nav->nav_menu = false;
nav->nav_draining = false;
nav->nav_still_frame = 0;
mp_input_disable_section(mpctx->input, "dvdnav-menu");
// Prevent demuxer init code to seek to the "start"
mpctx->stream->start_pos = stream_tell(mpctx->stream);
stream_control(mpctx->stream, STREAM_CTRL_RESUME_CACHE, NULL);
}
void mp_nav_destroy(struct MPContext *mpctx)
{
if (!mpctx->nav_state)
return;
mp_input_disable_section(mpctx->input, "dvdnav");
mp_input_disable_section(mpctx->input, "dvdnav-menu");
talloc_free(mpctx->nav_state);
mpctx->nav_state = NULL;
}
void mp_nav_user_input(struct MPContext *mpctx, char *command)
{
struct mp_nav_state *nav = mpctx->nav_state;
if (!nav)
return;
if (strcmp(command, "mouse_move") == 0) {
struct mp_image_params vid = {0};
if (mpctx->d_video)
vid = mpctx->d_video->decoder_output;
struct mp_nav_cmd inp = {MP_NAV_CMD_MOUSE_POS};
int x, y;
mp_input_get_mouse_pos(mpctx->input, &x, &y);
osd_coords_to_video(mpctx->osd, vid.w, vid.h, &x, &y);
inp.u.mouse_pos.x = x;
inp.u.mouse_pos.y = y;
stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
} else {
struct mp_nav_cmd inp = {MP_NAV_CMD_MENU};
inp.u.menu.action = command;
stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
}
}
void mp_handle_nav(struct MPContext *mpctx)
{
struct mp_nav_state *nav = mpctx->nav_state;
if (!nav)
return;
while (1) {
struct mp_nav_event *ev = NULL;
stream_control(mpctx->stream, STREAM_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");
break;
}
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, "dvdnav-menu", 0);
} else {
mp_input_disable_section(mpctx->input, "dvdnav-menu");
}
break;
case MP_NAV_EVENT_HIGHLIGHT:
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;
mpctx->osd->highlight_priv = mpctx;
osd_changed(mpctx->osd, OSDTYPE_NAV_HIGHLIGHT);
break;
default: ; // ignore
}
talloc_free(ev);
}
if (mpctx->stop_play == AT_END_OF_FILE) {
if (nav->nav_still_frame > 0) {
// gross hack
mpctx->time_frame += nav->nav_still_frame;
mpctx->playing_last_frame = true;
nav->nav_still_frame = -2;
} else if (nav->nav_still_frame == -2) {
struct mp_nav_cmd inp = {MP_NAV_CMD_SKIP_STILL};
stream_control(mpctx->stream, 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};
stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
nav->nav_draining = false;
stream_control(mpctx->stream, 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;
}
// Render "fake" highlights, because using actual dvd sub highlight elements
// is too hard, and would require extra libavcodec to begin with.
// Note: a proper solution would introduce something like
// SD_CTRL_APPLY_DVDNAV, which would crop the vobsub frame,
// and apply the current CLUT.
void mp_nav_get_highlight(struct osd_state *osd, struct mp_osd_res res,
struct sub_bitmaps *out_imgs)
{
struct MPContext *mpctx = osd->highlight_priv;
struct mp_nav_state *nav = mpctx ? mpctx->nav_state : NULL;
if (!nav)
return;
struct sub_bitmap *sub = nav->hi_elem;
if (!sub)
sub = talloc_zero(nav, struct sub_bitmap);
nav->hi_elem = sub;
int sizes[2] = {0};
if (mpctx->d_sub)
sub_control(mpctx->d_sub, SD_CTRL_GET_RESOLUTION, sizes);
if (sizes[0] < 1 || sizes[1] < 1) {
struct mp_image_params vid = {0};
if (mpctx->d_video)
vid = mpctx->d_video->decoder_output;
sizes[0] = vid.w;
sizes[1] = vid.h;
}
if (sizes[0] < 1 || sizes[1] < 1)
return;
if (sizes[0] != nav->subsize[0] || sizes[1] != nav->subsize[1]) {
talloc_free(sub->bitmap);
sub->bitmap = talloc_array(sub, uint32_t, sizes[0] * sizes[1]);
memset(sub->bitmap, 0x80, talloc_get_size(sub->bitmap));
}
sub->x = nav->highlight[0];
sub->y = nav->highlight[1];
sub->w = MPCLAMP(nav->highlight[2] - sub->x, 0, sizes[0]);
sub->h = MPCLAMP(nav->highlight[3] - sub->y, 0, sizes[1]);
sub->stride = sub->w;
out_imgs->format = SUBBITMAP_RGBA;
out_imgs->parts = sub;
out_imgs->num_parts = sub->w > 0 && sub->h > 0 && nav->hi_visible;
osd_rescale_bitmaps(out_imgs, sizes[0], sizes[1], res, -1);
}

1423
player/loadfile.c Normal file
View File

File diff suppressed because it is too large Load Diff

98
player/lua/assdraw.lua Normal file
View File

@@ -0,0 +1,98 @@
local ass_mt = {}
ass_mt.__index = ass_mt
local function ass_new()
return setmetatable({ scale = 4, text = "" }, ass_mt)
end
function ass_mt.new_event(ass)
-- osd_libass.c adds an event per line
if #ass.text > 0 then
ass.text = ass.text .. "\n"
end
end
function ass_mt.draw_start(ass)
ass.text = string.format("%s{\\p%d}", ass.text, ass.scale)
end
function ass_mt.draw_stop(ass)
ass.text = ass.text .. "{\\p0}"
end
function ass_mt.coord(ass, x, y)
local scale = math.pow(2, ass.scale - 1)
local ix = math.ceil(x * scale)
local iy = math.ceil(y * scale)
ass.text = string.format("%s %d %d", ass.text, ix, iy)
end
function ass_mt.append(ass, s)
ass.text = ass.text .. s
end
function ass_mt.merge(ass1, ass2)
ass1.text = ass1.text .. ass2.text
end
function ass_mt.pos(ass, x, y)
ass:append(string.format("{\\pos(%f,%f)}", x, y))
end
function ass_mt.an(ass, an)
ass:append(string.format("{\\an%d}", an))
end
function ass_mt.move_to(ass, x, y)
ass:append(" m")
ass:coord(x, y)
end
function ass_mt.line_to(ass, x, y)
ass:append(" l")
ass:coord(x, y)
end
function ass_mt.bezier_curve(ass, x1, y1, x2, y2, x3, y3)
ass:append(" b")
ass:coord(x1, y1)
ass:coord(x2, y2)
ass:coord(x3, y3)
end
function ass_mt.rect_ccw(ass, x0, y0, x1, y1)
ass:move_to(x0, y0)
ass:line_to(x0, y1)
ass:line_to(x1, y1)
ass:line_to(x1, y0)
end
function ass_mt.rect_cw(ass, x0, y0, x1, y1)
ass:move_to(x0, y0)
ass:line_to(x1, y0)
ass:line_to(x1, y1)
ass:line_to(x0, y1)
end
function ass_mt.round_rect_cw(ass, x0, y0, x1, y1, r)
ass:move_to(x0 + r, y0)
ass:line_to(x1 - r, y0) -- top line
if r > 0 then
ass:bezier_curve(x1, y0, x1, y0, x1, y0 + r) -- top right corner
end
ass:line_to(x1, y1 - r) -- right line
if r > 0 then
ass:bezier_curve(x1, y1, x1, y1, x1 - r, y1) -- bottom right corner
end
ass:line_to(x0 + r, y1) -- bottom line
if r > 0 then
ass:bezier_curve(x0, y1, x0, y1, x0, y1 - r) -- bottom left corner
end
ass:line_to(x0, y0 + r) -- left line
if r > 0 then
ass:bezier_curve(x0, y0, x0, y0, x0 + r, y0) -- top left corner
end
end
return {ass_new = ass_new}

82
player/lua/defaults.lua Normal file
View File

@@ -0,0 +1,82 @@
local callbacks = {}
-- each script has its own section, so that they don't conflict
local default_section = "input_" .. mp.script_name
-- Set the list of key bindings. These will override the user's bindings, so
-- you should use this sparingly.
-- A call to this function will remove all bindings previously set with this
-- function. For example, set_key_bindings({}) would remove all script defined
-- key bindings.
-- Note: the bindings are not active by default. Use enable_key_bindings().
--
-- list is an array of key bindings, where each entry is an array as follow:
-- {key, callback}
-- {key, callback, callback_down}
-- key is the key string as used in input.conf, like "ctrl+a"
-- callback is a Lua function that is called when the key binding is used.
-- callback_down can be given too, and is called when a mouse button is pressed
-- if the key is a mouse button. (The normal callback will be for mouse button
-- down.)
--
-- callback can be a string too, in which case the following will be added like
-- an input.conf line: key .. " " .. callback
-- (And callback_down is ignored.)
function mp.set_key_bindings(list, section)
local cfg = ""
for i = 1, #list do
local entry = list[i]
local key = entry[1]
local cb = entry[2]
local cb_down = entry[3]
if type(cb) == "function" then
callbacks[#callbacks + 1] = {press=cb, before_press=cb_down}
cfg = cfg .. key .. " script_dispatch " .. mp.script_name
.. " " .. #callbacks .. "\n"
else
cfg = cfg .. key .. " " .. cb .. "\n"
end
end
mp.input_define_section(section or default_section, cfg)
end
function mp.enable_key_bindings(section, flags)
mp.input_enable_section(section or default_section, flags)
end
function mp.disable_key_bindings(section)
mp.input_disable_section(section or default_section)
end
function mp.set_mouse_area(x0, y0, x1, y1, section)
mp.input_set_section_mouse_area(section or default_section, x0, y0, x1, y1)
end
-- called by C on script_dispatch input command
function mp_script_dispatch(id, event)
local cb = callbacks[id]
if cb then
if event == "press" and cb.press then
cb.press()
elseif event == "keyup_follows" and cb.before_press then
cb.before_press()
end
end
end
mp.msg = {
log = mp.log,
fatal = function(...) return mp.log("fatal", ...) end,
error = function(...) return mp.log("error", ...) end,
warn = function(...) return mp.log("warn", ...) end,
info = function(...) return mp.log("info", ...) end,
verbose = function(...) return mp.log("verbose", ...) end,
debug = function(...) return mp.log("debug", ...) end,
}
_G.print = mp.msg.info
package.loaded["mp"] = mp
package.loaded["mp.msg"] = mp.msg
return {}

1288
player/lua/osc.lua Normal file
View File

File diff suppressed because it is too large Load Diff

450
player/main.c Normal file
View File

@@ -0,0 +1,450 @@
/*
* 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 <ctype.h>
#include <string.h>
#include "config.h"
#include "talloc.h"
#include "osdep/io.h"
#include "osdep/getch2.h"
#include "osdep/priority.h"
#include "osdep/timer.h"
#include "mpvcore/av_log.h"
#include "mpvcore/codecs.h"
#include "mpvcore/cpudetect.h"
#include "mpvcore/encode.h"
#include "mpvcore/m_config.h"
#include "mpvcore/m_option.h"
#include "mpvcore/m_property.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/mpv_global.h"
#include "mpvcore/resolve.h"
#include "mpvcore/parser-cfg.h"
#include "mpvcore/parser-mpcmd.h"
#include "mpvcore/playlist.h"
#include "mpvcore/playlist_parser.h"
#include "mpvcore/options.h"
#include "mpvcore/input/input.h"
#include "audio/decode/dec_audio.h"
#include "audio/out/ao.h"
#include "audio/mixer.h"
#include "demux/demux.h"
#include "stream/stream.h"
#include "sub/ass_mp.h"
#include "sub/osd.h"
#include "video/decode/dec_video.h"
#include "video/out/vo.h"
#include "mp_core.h"
#include "mp_lua.h"
#include "command.h"
#include "screenshot.h"
#if HAVE_X11
#include "video/out/x11_common.h"
#endif
#if HAVE_COCOA
#include "osdep/macosx_application.h"
#endif
#ifdef PTW32_STATIC_LIB
#include <pthread.h>
#endif
#if defined(__MINGW32__) || defined(__CYGWIN__)
#include <windows.h>
#endif
const char mp_help_text[] = _(
"Usage: mpv [options] [url|path/]filename\n"
"\n"
"Basic options:\n"
" --start=<time> seek to given (percent, 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"
" --list-options list all mpv options\n"
"\n");
void mp_print_version(int always)
{
int v = always ? MSGL_INFO : MSGL_V;
mp_msg(MSGT_CPLAYER, v,
"%s (C) 2000-2013 mpv/MPlayer/mplayer2 projects\n built on %s\n", mplayer_version, mplayer_builddate);
print_libav_versions(v);
mp_msg(MSGT_CPLAYER, v, "\n");
}
static MP_NORETURN void exit_player(struct MPContext *mpctx,
enum exit_reason how)
{
int rc;
uninit_player(mpctx, INITIALIZED_ALL);
#if HAVE_ENCODING
encode_lavc_finish(mpctx->encode_lavc_ctx);
encode_lavc_free(mpctx->encode_lavc_ctx);
#endif
mpctx->encode_lavc_ctx = NULL;
#if HAVE_LUA
mp_lua_uninit(mpctx);
#endif
#if defined(__MINGW32__)
timeEndPeriod(1);
#endif
#if HAVE_COCOA
cocoa_set_input_context(NULL);
#endif
command_uninit(mpctx);
mp_input_uninit(mpctx->input);
osd_free(mpctx->osd);
#if HAVE_LIBASS
ass_library_done(mpctx->ass_library);
mpctx->ass_library = NULL;
#endif
getch2_disable();
if (how != EXIT_NONE) {
const char *reason;
switch (how) {
case EXIT_SOMENOTPLAYED:
case EXIT_PLAYED:
reason = "End of file";
break;
case EXIT_NOTPLAYED:
reason = "No files played";
break;
case EXIT_ERROR:
reason = "Fatal error";
break;
default:
reason = "Quit";
}
MP_INFO(mpctx, "\nExiting... (%s)\n", reason);
}
if (mpctx->has_quit_custom_rc) {
rc = mpctx->quit_custom_rc;
} else {
switch (how) {
case EXIT_ERROR:
rc = 1; break;
case EXIT_NOTPLAYED:
rc = 2; break;
case EXIT_SOMENOTPLAYED:
rc = 3; break;
default:
rc = 0;
}
}
// must be last since e.g. mp_msg uses option values
// that will be freed by this.
mp_msg_uninit(mpctx->global);
talloc_free(mpctx);
#if HAVE_COCOA
terminate_cocoa_application();
// never reach here:
// terminate calls exit itself, just silence compiler warning
exit(0);
#else
exit(rc);
#endif
}
static bool handle_help_options(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
int opt_exit = 0;
if (opts->audio_decoders && strcmp(opts->audio_decoders, "help") == 0) {
struct mp_decoder_list *list = audio_decoder_list();
mp_print_decoders(MSGT_CPLAYER, MSGL_INFO, "Audio decoders:", list);
talloc_free(list);
opt_exit = 1;
}
if (opts->video_decoders && strcmp(opts->video_decoders, "help") == 0) {
struct mp_decoder_list *list = video_decoder_list();
mp_print_decoders(MSGT_CPLAYER, MSGL_INFO, "Video decoders:", list);
talloc_free(list);
opt_exit = 1;
}
#if HAVE_X11
if (opts->vo.fstype_list && strcmp(opts->vo.fstype_list[0], "help") == 0) {
fstype_help();
mp_msg(MSGT_FIXME, MSGL_FIXME, "\n");
opt_exit = 1;
}
#endif
if ((opts->demuxer_name && strcmp(opts->demuxer_name, "help") == 0) ||
(opts->audio_demuxer_name && strcmp(opts->audio_demuxer_name, "help") == 0) ||
(opts->sub_demuxer_name && strcmp(opts->sub_demuxer_name, "help") == 0)) {
demuxer_help();
MP_INFO(mpctx, "\n");
opt_exit = 1;
}
if (opts->list_properties) {
property_print_help();
opt_exit = 1;
}
#if HAVE_ENCODING
if (encode_lavc_showhelp(mpctx->opts))
opt_exit = 1;
#endif
return opt_exit;
}
#ifdef PTW32_STATIC_LIB
static void detach_ptw32(void)
{
pthread_win32_thread_detach_np();
pthread_win32_process_detach_np();
}
#endif
static void osdep_preinit(int *p_argc, char ***p_argv)
{
char *enable_talloc = getenv("MPV_LEAK_REPORT");
if (*p_argc > 1 && (strcmp((*p_argv)[1], "-leak-report") == 0 ||
strcmp((*p_argv)[1], "--leak-report") == 0))
enable_talloc = "1";
if (enable_talloc && strcmp(enable_talloc, "1") == 0)
talloc_enable_leak_report();
#ifdef __MINGW32__
mp_get_converted_argv(p_argc, p_argv);
#endif
#ifdef PTW32_STATIC_LIB
pthread_win32_process_attach_np();
pthread_win32_thread_attach_np();
atexit(detach_ptw32);
#endif
#if defined(__MINGW32__) || defined(__CYGWIN__)
// stop Windows from showing all kinds of annoying error dialogs
SetErrorMode(0x8003);
#endif
load_termcap(NULL); // load key-codes
mp_time_init();
}
static int read_keys(void *ctx, int fd)
{
if (getch2(ctx))
return MP_INPUT_NOTHING;
return MP_INPUT_DEAD;
}
static void init_input(struct MPContext *mpctx)
{
mpctx->input = mp_input_init(mpctx->global);
if (mpctx->opts->slave_mode)
mp_input_add_cmd_fd(mpctx->input, 0, USE_FD0_CMD_SELECT, MP_INPUT_SLAVE_CMD_FUNC, NULL);
else if (mpctx->opts->consolecontrols)
mp_input_add_key_fd(mpctx->input, 0, 1, read_keys, NULL, mpctx->input);
// Set the libstream interrupt callback
stream_set_interrupt_callback(mp_input_check_interrupt, mpctx->input);
#if HAVE_COCOA
cocoa_set_input_context(mpctx->input);
#endif
}
static int cfg_include(struct m_config *conf, char *filename, int flags)
{
return m_config_parse_config_file(conf, filename, flags);
}
static int mpv_main(int argc, char *argv[])
{
osdep_preinit(&argc, &argv);
if (argc >= 1) {
argc--;
argv++;
}
struct MPContext *mpctx = talloc(NULL, MPContext);
*mpctx = (struct MPContext){
.last_dvb_step = 1,
.terminal_osd_text = talloc_strdup(mpctx, ""),
.playlist = talloc_struct(mpctx, struct playlist, {0}),
};
// Create the config context and register the options
mpctx->mconfig = m_config_new(mpctx, sizeof(struct MPOpts),
&mp_default_opts, mp_opts);
mpctx->opts = mpctx->mconfig->optstruct;
mpctx->mconfig->includefunc = cfg_include;
mpctx->mconfig->use_profiles = true;
mpctx->mconfig->is_toplevel = true;
struct MPOpts *opts = mpctx->opts;
mpctx->global = talloc_zero(mpctx, struct mpv_global);
mpctx->global->opts = opts;
// Nothing must call mp_msg() before this
mp_msg_init(mpctx->global);
mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!cplayer");
init_libav();
GetCpuCaps(&gCpuCaps);
screenshot_init(mpctx);
mpctx->mixer = mixer_init(mpctx, opts);
command_init(mpctx);
// Preparse the command line
m_config_preparse_command_line(mpctx->mconfig, argc, argv);
mp_print_version(false);
if (!mp_parse_cfgfiles(mpctx))
exit_player(mpctx, EXIT_ERROR);
int r = m_config_parse_mp_command_line(mpctx->mconfig, mpctx->playlist,
argc, argv);
if (r < 0) {
if (r <= M_OPT_EXIT) {
exit_player(mpctx, EXIT_NONE);
} else {
exit_player(mpctx, EXIT_ERROR);
}
}
if (handle_help_options(mpctx))
exit_player(mpctx, EXIT_NONE);
MP_VERBOSE(mpctx, "Configuration: " CONFIGURATION "\n");
MP_VERBOSE(mpctx, "Command line:");
for (int i = 0; i < argc; i++)
MP_VERBOSE(mpctx, " '%s'", argv[i]);
MP_VERBOSE(mpctx, "\n");
if (!mpctx->playlist->first && !opts->player_idle_mode) {
mp_print_version(true);
MP_INFO(mpctx, "%s", mp_help_text);
exit_player(mpctx, EXIT_NONE);
}
#if HAVE_PRIORITY
set_priority();
#endif
init_input(mpctx);
#if HAVE_ENCODING
if (opts->encode_output.file && *opts->encode_output.file) {
mpctx->encode_lavc_ctx = encode_lavc_init(&opts->encode_output);
if(!mpctx->encode_lavc_ctx) {
mp_msg(MSGT_VO, MSGL_INFO, "Encoding initialization failed.");
exit_player(mpctx, EXIT_ERROR);
}
m_config_set_option0(mpctx->mconfig, "vo", "lavc");
m_config_set_option0(mpctx->mconfig, "ao", "lavc");
m_config_set_option0(mpctx->mconfig, "fixed-vo", "yes");
m_config_set_option0(mpctx->mconfig, "force-window", "no");
m_config_set_option0(mpctx->mconfig, "gapless-audio", "yes");
mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE);
}
#endif
if (opts->consolecontrols)
getch2_enable();
#if HAVE_LIBASS
mpctx->ass_library = mp_ass_init(opts);
#else
MP_WARN(mpctx, "Compiled without libass.\n");
MP_WARN(mpctx, "There will be no OSD and no text subtitles.\n");
#endif
mpctx->osd = osd_create(opts);
if (opts->force_vo) {
opts->fixed_vo = 1;
mpctx->video_out = init_best_video_out(mpctx->global, mpctx->input,
mpctx->encode_lavc_ctx);
if (!mpctx->video_out) {
MP_FATAL(mpctx, "Error opening/initializing "
"the selected video_out (-vo) device.\n");
exit_player(mpctx, EXIT_ERROR);
}
mpctx->mouse_cursor_visible = true;
mpctx->initialized_flags |= INITIALIZED_VO;
}
#if HAVE_LUA
// Lua user scripts can call arbitrary functions. Load them at a point
// where this is safe.
mp_lua_init(mpctx);
#endif
if (opts->shuffle)
playlist_shuffle(mpctx->playlist);
if (opts->merge_files)
merge_playlist_files(mpctx->playlist);
mpctx->playlist->current = mp_resume_playlist(mpctx->playlist, opts);
if (!mpctx->playlist->current)
mpctx->playlist->current = mpctx->playlist->first;
mp_play_files(mpctx);
exit_player(mpctx, mpctx->stop_play == PT_QUIT ? EXIT_QUIT : mpctx->quit_player_rc);
return 1;
}
int main(int argc, char *argv[])
{
#if HAVE_COCOA
return cocoa_main(mpv_main, argc, argv);
#else
return mpv_main(argc, argv);
#endif
}

210
player/misc.c Normal file
View File

@@ -0,0 +1,210 @@
/*
* 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 <stddef.h>
#include <stdbool.h>
#include <assert.h>
#include "config.h"
#include "talloc.h"
#include "osdep/io.h"
#include "osdep/timer.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/options.h"
#include "mpvcore/m_property.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/resolve.h"
#include "mpvcore/encode.h"
#include "mpvcore/playlist.h"
#include "mpvcore/input/input.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "stream/stream.h"
#include "video/out/vo.h"
#include "mp_core.h"
#include "command.h"
double get_relative_time(struct MPContext *mpctx)
{
int64_t new_time = mp_time_us();
int64_t delta = new_time - mpctx->last_time;
mpctx->last_time = new_time;
return delta * 0.000001;
}
double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t,
double fallback_time)
{
double length = get_time_length(mpctx);
switch (t.type) {
case REL_TIME_ABSOLUTE:
return t.pos;
case REL_TIME_NEGATIVE:
if (length != 0)
return MPMAX(length - t.pos, 0.0);
break;
case REL_TIME_PERCENT:
if (length != 0)
return length * (t.pos / 100.0);
break;
case REL_TIME_CHAPTER:
if (chapter_start_time(mpctx, t.pos) >= 0)
return chapter_start_time(mpctx, t.pos);
break;
}
return fallback_time;
}
double get_play_end_pts(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
if (opts->play_end.type) {
return rel_time_to_abs(mpctx, opts->play_end, MP_NOPTS_VALUE);
} else if (opts->play_length.type) {
double startpts = get_start_time(mpctx);
double start = rel_time_to_abs(mpctx, opts->play_start, startpts);
double length = rel_time_to_abs(mpctx, opts->play_length, -1);
if (start != -1 && length != -1)
return start + length;
}
return MP_NOPTS_VALUE;
}
// Time used to seek external tracks to.
double get_main_demux_pts(struct MPContext *mpctx)
{
double main_new_pos = MP_NOPTS_VALUE;
if (mpctx->demuxer) {
for (int n = 0; n < mpctx->demuxer->num_streams; n++) {
if (main_new_pos == MP_NOPTS_VALUE)
main_new_pos = demux_get_next_pts(mpctx->demuxer->streams[n]);
}
}
return main_new_pos;
}
double get_start_time(struct MPContext *mpctx)
{
struct demuxer *demuxer = mpctx->demuxer;
if (!demuxer)
return 0;
return demuxer_get_start_time(demuxer);
}
int mp_get_cache_percent(struct MPContext *mpctx)
{
if (mpctx->stream) {
int64_t size = -1;
int64_t fill = -1;
stream_control(mpctx->stream, STREAM_CTRL_GET_CACHE_SIZE, &size);
stream_control(mpctx->stream, STREAM_CTRL_GET_CACHE_FILL, &fill);
if (size > 0 && fill >= 0)
return fill / (size / 100);
}
return -1;
}
bool mp_get_cache_idle(struct MPContext *mpctx)
{
int idle = 0;
if (mpctx->stream)
stream_control(mpctx->stream, STREAM_CTRL_GET_CACHE_IDLE, &idle);
return idle;
}
void update_window_title(struct MPContext *mpctx, bool force)
{
if (!mpctx->video_out && !mpctx->ao) {
talloc_free(mpctx->last_window_title);
mpctx->last_window_title = false;
return;
}
char *title = mp_property_expand_string(mpctx, mpctx->opts->wintitle);
if (!mpctx->last_window_title || force ||
strcmp(title, mpctx->last_window_title) != 0)
{
talloc_free(mpctx->last_window_title);
mpctx->last_window_title = talloc_steal(mpctx, title);
if (mpctx->video_out) {
mpctx->video_out->window_title = talloc_strdup(mpctx->video_out, title);
vo_control(mpctx->video_out, VOCTRL_UPDATE_WINDOW_TITLE, title);
}
if (mpctx->ao) {
ao_control(mpctx->ao, AOCONTROL_UPDATE_STREAM_TITLE, title);
}
} else {
talloc_free(title);
}
}
void stream_dump(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
char *filename = opts->stream_dump;
stream_t *stream = mpctx->stream;
assert(stream && filename);
stream_set_capture_file(stream, filename);
while (mpctx->stop_play == KEEP_PLAYING && !stream->eof) {
if (!opts->quiet && ((stream->pos / (1024 * 1024)) % 2) == 1) {
uint64_t pos = stream->pos - stream->start_pos;
uint64_t end = stream->end_pos - stream->start_pos;
char *line = talloc_asprintf(NULL, "Dumping %lld/%lld...",
(long long int)pos, (long long int)end);
write_status_line(mpctx, line);
talloc_free(line);
}
stream_fill_buffer(stream);
for (;;) {
mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, 0, false);
if (!cmd)
break;
run_command(mpctx, cmd);
talloc_free(cmd);
}
}
}
void merge_playlist_files(struct playlist *pl)
{
if (!pl->first)
return;
char *edl = talloc_strdup(NULL, "edl://");
for (struct playlist_entry *e = pl->first; e; e = e->next) {
if (e != pl->first)
edl = talloc_strdup_append_buffer(edl, ";");
// Escape if needed
if (e->filename[strcspn(e->filename, "=%,;\n")] ||
bstr_strip(bstr0(e->filename)).len != strlen(e->filename))
{
// %length%
edl = talloc_asprintf_append_buffer(edl, "%%%zd%%", strlen(e->filename));
}
edl = talloc_strdup_append_buffer(edl, e->filename);
}
playlist_clear(pl);
playlist_add_file(pl, edl);
talloc_free(edl);
}

459
player/mp_core.h Normal file
View File

@@ -0,0 +1,459 @@
/*
* 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.
*/
#ifndef MPLAYER_MP_CORE_H
#define MPLAYER_MP_CORE_H
#include <stdbool.h>
#include "mpvcore/mp_common.h"
#include "mpvcore/options.h"
// definitions used internally by the core player code
#define INITIALIZED_VO 1
#define INITIALIZED_AO 2
#define INITIALIZED_PLAYBACK 16
#define INITIALIZED_LIBASS 32
#define INITIALIZED_STREAM 64
#define INITIALIZED_DEMUXER 512
#define INITIALIZED_ACODEC 1024
#define INITIALIZED_VCODEC 2048
#define INITIALIZED_SUB 4096
#define INITIALIZED_ALL 0xFFFF
enum stop_play_reason {
KEEP_PLAYING = 0, // must be 0, numeric values of others do not matter
AT_END_OF_FILE, // file has ended, prepare to play next
// also returned on unrecoverable playback errors
PT_NEXT_ENTRY, // prepare to play next entry in playlist
PT_CURRENT_ENTRY, // prepare to play mpctx->playlist->current
PT_STOP, // stop playback, clear playlist
PT_RESTART, // restart previous file
PT_RELOAD_DEMUXER, // restart playback, but keep stream open
PT_QUIT, // stop playback, quit player
};
enum exit_reason {
EXIT_NONE,
EXIT_QUIT,
EXIT_PLAYED,
EXIT_ERROR,
EXIT_NOTPLAYED,
EXIT_SOMENOTPLAYED
};
struct timeline_part {
double start;
double source_start;
struct demuxer *source;
};
struct chapter {
double start;
char *name;
};
enum mp_osd_seek_info {
OSD_SEEK_INFO_BAR = 1,
OSD_SEEK_INFO_TEXT = 2,
OSD_SEEK_INFO_CHAPTER_TEXT = 4,
OSD_SEEK_INFO_EDITION = 8,
};
enum {
OSD_MSG_TEXT = 1,
OSD_MSG_SUB_DELAY,
OSD_MSG_SPEED,
OSD_MSG_OSD_STATUS,
OSD_MSG_BAR,
OSD_MSG_PAUSE,
OSD_MSG_RADIO_CHANNEL,
OSD_MSG_TV_CHANNEL,
// Base id for messages generated from the commmand to property bridge.
OSD_MSG_PROPERTY = 0x100,
OSD_MSG_SUB_BASE = 0x1000,
// other constants
MAX_OSD_LEVEL = 3,
MAX_TERM_OSD_LEVEL = 1,
OSD_LEVEL_INVISIBLE = 4,
OSD_BAR_SEEK = 256,
};
enum seek_type {
MPSEEK_NONE = 0,
MPSEEK_RELATIVE,
MPSEEK_ABSOLUTE,
MPSEEK_FACTOR,
};
struct track {
enum stream_type type;
// The type specific ID, also called aid (audio), sid (subs), vid (video).
// For UI purposes only; this ID doesn't have anything to do with any
// IDs coming from demuxers or container files.
int user_tid;
// Same as stream->demuxer_id. -1 if not set.
int demuxer_id;
char *title;
bool default_track;
bool attached_picture;
char *lang;
// If this track is from an external file (e.g. subtitle file).
bool is_external;
char *external_filename;
bool auto_loaded;
// If the track's stream changes with the timeline (ordered chapters).
bool under_timeline;
// Value can change if under_timeline==true.
struct demuxer *demuxer;
// Invariant: !stream || stream->demuxer == demuxer
struct sh_stream *stream;
// For external subtitles, which are read fully on init. Do not attempt
// to read packets from them.
bool preloaded;
};
enum {
MAX_NUM_VO_PTS = 100,
};
typedef struct MPContext {
struct mpv_global *global;
struct MPOpts *opts;
struct mp_log *log;
struct m_config *mconfig;
struct input_ctx *input;
struct osd_state *osd;
struct mp_osd_msg *osd_msg_stack;
char *terminal_osd_text;
char *last_window_title;
int add_osd_seek_info; // bitfield of enum mp_osd_seek_info
double osd_visible; // for the osd bar only
int osd_function;
double osd_function_visible;
double osd_last_update;
struct playlist *playlist;
char *filename; // currently playing file
struct mp_resolve_result *resolve_result;
enum stop_play_reason stop_play;
unsigned int initialized_flags; // which subsystems have been initialized
// Return code to use with PT_QUIT
enum exit_reason quit_player_rc;
int quit_custom_rc;
bool has_quit_custom_rc;
bool error_playing;
int64_t shown_vframes, shown_aframes;
struct demuxer **sources;
int num_sources;
struct timeline_part *timeline;
int num_timeline_parts;
int timeline_part;
// NOTE: even if num_chapters==0, chapters being not NULL signifies presence
// of chapter metadata
struct chapter *chapters;
int num_chapters;
double video_offset;
struct stream *stream;
struct demuxer *demuxer;
struct track **tracks;
int num_tracks;
char *track_layout_hash;
// Selected tracks. NULL if no track selected.
struct track *current_track[STREAM_TYPE_COUNT];
struct sh_stream *sh[STREAM_TYPE_COUNT];
struct dec_video *d_video;
struct dec_audio *d_audio;
struct dec_sub *d_sub;
// Uses: accessing metadata (consider ordered chapters case, where the main
// demuxer defines metadata), or special purpose demuxers like TV.
struct demuxer *master_demuxer;
struct mixer *mixer;
struct ao *ao;
struct vo *video_out;
/* We're starting playback from scratch or after a seek. Show first
* video frame immediately and reinitialize sync. */
bool restart_playback;
/* Set if audio should be timed to start with video frame after seeking,
* not set when e.g. playing cover art */
bool sync_audio_to_video;
/* After playback restart (above) or audio stream change, adjust audio
* stream by cutting samples or adding silence at the beginning to make
* audio playback position match video position. */
bool syncing_audio;
bool hrseek_active;
bool hrseek_framedrop;
double hrseek_pts;
// AV sync: the next frame should be shown when the audio out has this
// much (in seconds) buffered data left. Increased when more data is
// written to the ao, decreased when moving to the next frame.
// In the audio-only case used as a timer since the last seek
// by the audio CPU usage meter.
double delay;
// AV sync: time until next frame should be shown
double time_frame;
// How long the last vo flip() call took. Used to adjust timing with
// the goal of making flip() calls finish (rather than start) at the
// specified time.
double last_vo_flip_duration;
// Display duration (as "intended") of the last flipped frame.
double last_frame_duration;
// Set to true some time after a new frame has been shown, and it turns out
// that this frame was the last one before video ends.
bool playing_last_frame;
// How much video timing has been changed to make it match the audio
// timeline. Used for status line information only.
double total_avsync_change;
// Total number of dropped frames that were "approved" to be dropped.
// Actual dropping depends on --framedrop and decoder internals.
int drop_frame_cnt;
// Number of frames dropped in a row.
int dropped_frames;
// A-V sync difference when last frame was displayed. Kept to display
// the same value if the status line is updated at a time where no new
// video frame is shown.
double last_av_difference;
/* Timestamp of the latest image that was queued on the VO, but not yet
* to be flipped. */
double video_next_pts;
/* timestamp of video frame currently visible on screen
* (or at least queued to be flipped by VO) */
double video_pts;
double last_seek_pts;
// As video_pts, but is not reset when seeking away. (For the very short
// period of time until a new frame is decoded and shown.)
double last_vo_pts;
// Video PTS, or audio PTS if video has ended.
double playback_pts;
// History of video frames timestamps that were queued in the VO
// This includes even skipped frames during hr-seek
double vo_pts_history_pts[MAX_NUM_VO_PTS];
// Whether the PTS at vo_pts_history[n] is after a seek reset
uint64_t vo_pts_history_seek[MAX_NUM_VO_PTS];
uint64_t vo_pts_history_seek_ts;
uint64_t backstep_start_seek_ts;
bool backstep_active;
double audio_delay;
double last_heartbeat;
double last_metadata_update;
double mouse_timer;
unsigned int mouse_event_ts;
bool mouse_cursor_visible;
// used to prevent hanging in some error cases
double start_timestamp;
// Timestamp from the last time some timing functions read the
// current time, in microseconds.
// Used to turn a new time value to a delta from last time.
int64_t last_time;
// Used to communicate the parameters of a seek between parts
struct seek_params {
enum seek_type type;
double amount;
int exact; // -1 = disable, 0 = default, 1 = enable
// currently not set by commands, only used internally by seek()
int direction; // -1 = backward, 0 = default, 1 = forward
} seek;
/* Heuristic for relative chapter seeks: keep track which chapter
* the user wanted to go to, even if we aren't exactly within the
* boundaries of that chapter due to an inaccurate seek. */
int last_chapter_seek;
double last_chapter_pts;
/* Subtitle renderer. This is separate, because we want to keep fonts
* loaded across ordered chapters, instead of reloading and rescanning
* them on each transition. (Both of these objects contain this state.)
*/
struct ass_renderer *ass_renderer;
struct ass_library *ass_library;
int last_dvb_step;
bool paused;
// step this many frames, then pause
int step_frames;
// Counted down each frame, stop playback if 0 is reached. (-1 = disable)
int max_frames;
bool playing_msg_shown;
bool paused_for_cache;
// Set after showing warning about decoding being too slow for realtime
// playback rate. Used to avoid showing it multiple times.
bool drop_message_shown;
struct screenshot_ctx *screenshot_ctx;
struct command_ctx *command_ctx;
struct encode_lavc_context *encode_lavc_ctx;
struct lua_ctx *lua_ctx;
struct mp_nav_state *nav_state;
} MPContext;
// audio.c
void reinit_audio_chain(struct MPContext *mpctx);
int reinit_audio_filters(struct MPContext *mpctx);
double playing_audio_pts(struct MPContext *mpctx);
int fill_audio_out_buffers(struct MPContext *mpctx, double endpts);
double written_audio_pts(struct MPContext *mpctx);
void clear_audio_output_buffers(struct MPContext *mpctx);
void clear_audio_decode_buffers(struct MPContext *mpctx);
// configfiles.c
bool mp_parse_cfgfiles(struct MPContext *mpctx);
char *mp_get_playback_resume_config_filename(const char *fname,
struct MPOpts *opts);
void mp_load_per_protocol_config(struct m_config *conf, const char * const file);
void mp_load_per_extension_config(struct m_config *conf, const char * const file);
void mp_load_per_output_config(struct m_config *conf, char *cfg, char *out);
void mp_load_per_file_config(struct m_config *conf, const char * const file,
bool search_file_dir);
void mp_load_playback_resume(struct m_config *conf, const char *file);
void mp_write_watch_later_conf(struct MPContext *mpctx);
struct playlist_entry *mp_resume_playlist(struct playlist *playlist,
struct MPOpts *opts);
// dvdnav.c
void mp_nav_init(struct MPContext *mpctx);
void mp_nav_reset(struct MPContext *mpctx);
void mp_nav_destroy(struct MPContext *mpctx);
void mp_nav_user_input(struct MPContext *mpctx, char *command);
void mp_handle_nav(struct MPContext *mpctx);
// loadfile.c
void uninit_player(struct MPContext *mpctx, unsigned int mask);
struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename);
void mp_switch_track(struct MPContext *mpctx, enum stream_type type,
struct track *track);
struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
int tid);
bool timeline_set_part(struct MPContext *mpctx, int i, bool force);
double timeline_set_from_time(struct MPContext *mpctx, double pts, bool *need_reset);
struct sh_stream *init_demux_stream(struct MPContext *mpctx,
enum stream_type type);
void cleanup_demux_stream(struct MPContext *mpctx, enum stream_type type);
void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer);
bool mp_remove_track(struct MPContext *mpctx, struct track *track);
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
bool force);
void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
void mp_play_files(struct MPContext *mpctx);
// main.c
void mp_print_version(int always);
// misc.c
double get_start_time(struct MPContext *mpctx);
double get_main_demux_pts(struct MPContext *mpctx);
double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t,
double fallback_time);
double get_play_end_pts(struct MPContext *mpctx);
double get_relative_time(struct MPContext *mpctx);
void merge_playlist_files(struct playlist *pl);
int mp_get_cache_percent(struct MPContext *mpctx);
bool mp_get_cache_idle(struct MPContext *mpctx);
void update_window_title(struct MPContext *mpctx, bool force);
void stream_dump(struct MPContext *mpctx);
// osd.c
void write_status_line(struct MPContext *mpctx, const char *line);
void print_status(struct MPContext *mpctx);
void set_osd_bar(struct MPContext *mpctx, int type, const char* name,
double min, double max, double val);
void set_osd_msg(struct MPContext *mpctx, int id, int level, int time,
const char* fmt, ...) PRINTF_ATTRIBUTE(5,6);
void rm_osd_msg(struct MPContext *mpctx, int id);
void set_osd_function(struct MPContext *mpctx, int osd_function);
void set_osd_subtitle(struct MPContext *mpctx, const char *text);
// playloop.c
void pause_player(struct MPContext *mpctx);
void unpause_player(struct MPContext *mpctx);
void add_step_frame(struct MPContext *mpctx, int dir);
void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
int exact);
bool mp_seek_chapter(struct MPContext *mpctx, int chapter);
double get_time_length(struct MPContext *mpctx);
double get_current_time(struct MPContext *mpctx);
int get_percent_pos(struct MPContext *mpctx);
double get_current_pos_ratio(struct MPContext *mpctx, bool use_range);
int get_current_chapter(struct MPContext *mpctx);
char *chapter_display_name(struct MPContext *mpctx, int chapter);
char *chapter_name(struct MPContext *mpctx, int chapter);
double chapter_start_time(struct MPContext *mpctx, int chapter);
int get_chapter_count(struct MPContext *mpctx);
void execute_queued_seek(struct MPContext *mpctx);
void run_playloop(struct MPContext *mpctx);
void idle_loop(struct MPContext *mpctx);
void handle_force_window(struct MPContext *mpctx, bool reconfig);
void add_frame_pts(struct MPContext *mpctx, double pts);
// sub.c
void reset_subtitles(struct MPContext *mpctx);
void uninit_subs(struct demuxer *demuxer);
void reinit_subs(struct MPContext *mpctx);
void update_osd_msg(struct MPContext *mpctx);
void update_subtitles(struct MPContext *mpctx);
// timeline/tl_matroska.c
void build_ordered_chapter_timeline(struct MPContext *mpctx);
// timeline/tl_mpv_edl.c
void build_mpv_edl_timeline(struct MPContext *mpctx);
// timeline/tl_cue.c
void build_cue_timeline(struct MPContext *mpctx);
// video.c
int reinit_video_chain(struct MPContext *mpctx);
int reinit_video_filters(struct MPContext *mpctx);
double update_video(struct MPContext *mpctx, double endpts);
void mp_force_video_refresh(struct MPContext *mpctx);
void update_fps(struct MPContext *mpctx);
void video_execute_format_change(struct MPContext *mpctx);
#endif /* MPLAYER_MP_CORE_H */

683
player/mp_lua.c Normal file
View File

@@ -0,0 +1,683 @@
#include <assert.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "talloc.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/m_property.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/m_option.h"
#include "mpvcore/input/input.h"
#include "mpvcore/path.h"
#include "mpvcore/bstr.h"
#include "osdep/timer.h"
#include "sub/osd.h"
#include "mp_core.h"
#include "command.h"
#include "mp_lua.h"
// List of builtin modules and their contents as strings.
// All these are generated from player/lua/*.lua
static const char *builtin_lua_scripts[][2] = {
{"mp.defaults",
# include "player/lua/defaults.inc"
},
{"mp.assdraw",
# include "player/lua/assdraw.inc"
},
{"@osc",
# include "player/lua/osc.inc"
},
{0}
};
// Represents a loaded script. Each has its own Lua state.
struct script_ctx {
const char *name;
lua_State *state;
struct mp_log *log;
struct MPContext *mpctx;
};
struct lua_ctx {
struct script_ctx **scripts;
int num_scripts;
};
static struct script_ctx *find_script(struct lua_ctx *lctx, const char *name)
{
for (int n = 0; n < lctx->num_scripts; n++) {
if (strcmp(lctx->scripts[n]->name, name) == 0)
return lctx->scripts[n];
}
return NULL;
}
static struct script_ctx *get_ctx(lua_State *L)
{
lua_getfield(L, LUA_REGISTRYINDEX, "ctx");
struct script_ctx *ctx = lua_touserdata(L, -1);
lua_pop(L, 1);
assert(ctx);
return ctx;
}
static struct MPContext *get_mpctx(lua_State *L)
{
return get_ctx(L)->mpctx;
}
static int wrap_cpcall(lua_State *L)
{
lua_CFunction fn = lua_touserdata(L, -1);
lua_pop(L, 1);
return fn(L);
}
// Call the given function fn under a Lua error handler (similar to lua_cpcall).
// Pass the given number of args from the Lua stack to fn.
// Returns 0 (and empty stack) on success.
// Returns LUA_ERR[RUN|MEM|ERR] otherwise, with the error value on the stack.
static int mp_cpcall(lua_State *L, lua_CFunction fn, int args)
{
// Don't use lua_pushcfunction() - it allocates memory on Lua 5.1.
// Instead, emulate C closures by making wrap_cpcall call fn.
lua_pushlightuserdata(L, fn); // args... fn
// Will always succeed if mp_lua_init() set it up correctly.
lua_getfield(L, LUA_REGISTRYINDEX, "wrap_cpcall"); // args... fn wrap_cpcall
lua_insert(L, -(args + 2)); // wrap_cpcall args... fn
return lua_pcall(L, args + 1, 0, 0);
}
static void report_error(lua_State *L)
{
const char *err = lua_tostring(L, -1);
mp_msg(MSGT_CPLAYER, MSGL_WARN, "[lua] Error: %s\n",
err ? err : "[unknown]");
lua_pop(L, 1);
}
static void add_functions(struct script_ctx *ctx);
static char *script_name_from_filename(void *talloc_ctx, struct lua_ctx *lctx,
const char *fname)
{
fname = mp_basename(fname);
if (fname[0] == '@')
fname += 1;
char *name = talloc_strdup(talloc_ctx, fname);
// Drop .lua extension
char *dot = strrchr(name, '.');
if (dot)
*dot = '\0';
// Turn it into a safe identifier - this is used with e.g. dispatching
// input via: "send scriptname ..."
for (int n = 0; name[n]; n++) {
char c = name[n];
if (!(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z') &&
!(c >= '0' && c <= '9'))
name[n] = '_';
}
// Make unique (stupid but simple)
while (find_script(lctx, name))
name = talloc_strdup_append(name, "_");
return name;
}
static int load_file(struct script_ctx *ctx, const char *fname)
{
int r = 0;
lua_State *L = ctx->state;
if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0)) {
report_error(L);
r = -1;
}
assert(lua_gettop(L) == 0);
return r;
}
static int load_builtin(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
for (int n = 0; builtin_lua_scripts[n][0]; n++) {
if (strcmp(name, builtin_lua_scripts[n][0]) == 0) {
if (luaL_loadstring(L, builtin_lua_scripts[n][1]))
lua_error(L);
lua_call(L, 0, 1);
return 1;
}
}
return 0;
}
// Execute "require " .. name
static bool require(lua_State *L, const char *name)
{
char buf[80];
// Lazy, but better than calling the "require" function manually
snprintf(buf, sizeof(buf), "require '%s'", name);
if (luaL_loadstring(L, buf) || lua_pcall(L, 0, 0, 0)) {
report_error(L);
return false;
}
return true;
}
static void mp_lua_load_script(struct MPContext *mpctx, const char *fname)
{
struct lua_ctx *lctx = mpctx->lua_ctx;
struct script_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct script_ctx) {
.mpctx = mpctx,
.name = script_name_from_filename(ctx, lctx, fname),
};
char *log_name = talloc_asprintf(ctx, "lua/%s", ctx->name);
ctx->log = mp_log_new(ctx, mpctx->log, log_name);
lua_State *L = ctx->state = luaL_newstate();
if (!L)
goto error_out;
// used by get_ctx()
lua_pushlightuserdata(L, ctx); // ctx
lua_setfield(L, LUA_REGISTRYINDEX, "ctx"); // -
lua_pushcfunction(L, wrap_cpcall); // closure
lua_setfield(L, LUA_REGISTRYINDEX, "wrap_cpcall"); // -
luaL_openlibs(L);
lua_newtable(L); // mp
lua_pushvalue(L, -1); // mp mp
lua_setglobal(L, "mp"); // mp
add_functions(ctx); // mp
lua_pushstring(L, ctx->name); // mp name
lua_setfield(L, -2, "script_name"); // mp
lua_pop(L, 1); // -
// Add a preloader for each builtin Lua module
lua_getglobal(L, "package"); // package
assert(lua_type(L, -1) == LUA_TTABLE);
lua_getfield(L, -1, "preload"); // package preload
assert(lua_type(L, -1) == LUA_TTABLE);
for (int n = 0; builtin_lua_scripts[n][0]; n++) {
lua_pushcfunction(L, load_builtin); // package preload load_builtin
lua_setfield(L, -2, builtin_lua_scripts[n][0]);
}
lua_pop(L, 2); // -
assert(lua_gettop(L) == 0);
if (!require(L, "mp.defaults")) {
report_error(L);
goto error_out;
}
assert(lua_gettop(L) == 0);
if (fname[0] == '@') {
if (!require(L, fname))
goto error_out;
} else {
if (load_file(ctx, fname) < 0)
goto error_out;
}
MP_TARRAY_APPEND(lctx, lctx->scripts, lctx->num_scripts, ctx);
return;
error_out:
if (ctx->state)
lua_close(ctx->state);
talloc_free(ctx);
}
static void kill_script(struct script_ctx *ctx)
{
if (!ctx)
return;
struct lua_ctx *lctx = ctx->mpctx->lua_ctx;
lua_close(ctx->state);
for (int n = 0; n < lctx->num_scripts; n++) {
if (lctx->scripts[n] == ctx) {
MP_TARRAY_REMOVE_AT(lctx->scripts, lctx->num_scripts, n);
break;
}
}
talloc_free(ctx);
}
static const char *log_level[] = {
[MSGL_FATAL] = "fatal",
[MSGL_ERR] = "error",
[MSGL_WARN] = "warn",
[MSGL_INFO] = "info",
[MSGL_V] = "verbose",
[MSGL_DBG2] = "debug",
};
static int script_log(lua_State *L)
{
struct script_ctx *ctx = get_ctx(L);
const char *level = luaL_checkstring(L, 1);
int msgl = -1;
for (int n = 0; n < MP_ARRAY_SIZE(log_level); n++) {
if (log_level[n] && strcasecmp(log_level[n], level) == 0) {
msgl = n;
break;
}
}
if (msgl < 0)
luaL_error(L, "Invalid log level '%s'", level);
int last = lua_gettop(L);
lua_getglobal(L, "tostring"); // args... tostring
for (int i = 2; i <= last; i++) {
lua_pushvalue(L, -1); // args... tostring tostring
lua_pushvalue(L, i); // args... tostring tostring args[i]
lua_call(L, 1, 1); // args... tostring str
const char *s = lua_tostring(L, -1);
if (s == NULL)
return luaL_error(L, "Invalid argument");
mp_msg_log(ctx->log, msgl, "%s%s", s, i > 0 ? " " : "");
lua_pop(L, 1); // args... tostring
}
mp_msg_log(ctx->log, msgl, "\n");
return 0;
}
static int script_find_config_file(lua_State *L)
{
const char *s = luaL_checkstring(L, 1);
char *path = mp_find_user_config_file(s);
if (path) {
lua_pushstring(L, path);
} else {
lua_pushnil(L);
}
talloc_free(path);
return 1;
}
static int run_event(lua_State *L)
{
lua_getglobal(L, "mp_event"); // name arg mp_event
if (lua_isnil(L, -1))
return 0;
lua_insert(L, -3); // mp_event name arg
lua_call(L, 2, 0);
return 0;
}
void mp_lua_event(struct MPContext *mpctx, const char *name, const char *arg)
{
// There is no proper subscription mechanism yet, so all scripts get it.
struct lua_ctx *lctx = mpctx->lua_ctx;
for (int n = 0; n < lctx->num_scripts; n++) {
struct script_ctx *ctx = lctx->scripts[n];
lua_State *L = ctx->state;
lua_pushstring(L, name);
if (arg) {
lua_pushstring(L, arg);
} else {
lua_pushnil(L);
}
if (mp_cpcall(L, run_event, 2) != 0)
report_error(L);
}
}
static int run_script_dispatch(lua_State *L)
{
int id = lua_tointeger(L, 1);
const char *event = lua_tostring(L, 2);
lua_getglobal(L, "mp_script_dispatch");
if (lua_isnil(L, -1))
return 0;
lua_pushinteger(L, id);
lua_pushstring(L, event);
lua_call(L, 2, 0);
return 0;
}
void mp_lua_script_dispatch(struct MPContext *mpctx, char *script_name,
int id, char *event)
{
struct script_ctx *ctx = find_script(mpctx->lua_ctx, script_name);
if (!ctx) {
mp_msg(MSGT_CPLAYER, MSGL_V,
"Can't find script '%s' when handling input.\n", script_name);
return;
}
lua_State *L = ctx->state;
lua_pushinteger(L, id);
lua_pushstring(L, event);
if (mp_cpcall(L, run_script_dispatch, 2) != 0)
report_error(L);
}
static int script_send_command(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
const char *s = luaL_checkstring(L, 1);
mp_cmd_t *cmd = mp_input_parse_cmd(mpctx->input, bstr0((char*)s), "<lua>");
if (!cmd)
luaL_error(L, "error parsing command");
mp_input_queue_cmd(mpctx->input, cmd);
return 0;
}
static int script_property_list(lua_State *L)
{
const struct m_option *props = mp_get_property_list();
lua_newtable(L);
for (int i = 0; props[i].name; i++) {
lua_pushinteger(L, i + 1);
lua_pushstring(L, props[i].name);
lua_settable(L, -3);
}
return 1;
}
static int script_property_string(lua_State *L)
{
const struct m_option *props = mp_get_property_list();
struct MPContext *mpctx = get_mpctx(L);
const char *name = luaL_checkstring(L, 1);
int type = lua_tointeger(L, lua_upvalueindex(1))
? M_PROPERTY_PRINT : M_PROPERTY_GET_STRING;
char *result = NULL;
if (m_property_do(props, name, type, &result, mpctx) >= 0 && result) {
lua_pushstring(L, result);
talloc_free(result);
return 1;
}
if (type == M_PROPERTY_PRINT) {
lua_pushstring(L, "");
return 1;
}
return 0;
}
static int script_set_osd_ass(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
int res_x = luaL_checkinteger(L, 1);
int res_y = luaL_checkinteger(L, 2);
const char *text = luaL_checkstring(L, 3);
if (!mpctx->osd->external ||
strcmp(mpctx->osd->external, text) != 0 ||
mpctx->osd->external_res_x != res_x ||
mpctx->osd->external_res_y != res_y)
{
talloc_free(mpctx->osd->external);
mpctx->osd->external = talloc_strdup(mpctx->osd, text);
mpctx->osd->external_res_x = res_x;
mpctx->osd->external_res_y = res_y;
osd_changed(mpctx->osd, OSDTYPE_EXTERNAL);
}
return 0;
}
static int script_get_osd_resolution(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
int w, h;
osd_object_get_resolution(mpctx->osd, mpctx->osd->objs[OSDTYPE_EXTERNAL],
&w, &h);
lua_pushnumber(L, w);
lua_pushnumber(L, h);
return 2;
}
static int script_get_screen_size(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
struct osd_object *obj = mpctx->osd->objs[OSDTYPE_EXTERNAL];
double aspect = 1.0 * obj->vo_res.w / MPMAX(obj->vo_res.h, 1) /
obj->vo_res.display_par;
lua_pushnumber(L, obj->vo_res.w);
lua_pushnumber(L, obj->vo_res.h);
lua_pushnumber(L, aspect);
return 3;
}
static int script_get_mouse_pos(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
int px, py;
mp_input_get_mouse_pos(mpctx->input, &px, &py);
double sw, sh;
osd_object_get_scale_factor(mpctx->osd, mpctx->osd->objs[OSDTYPE_EXTERNAL],
&sw, &sh);
lua_pushnumber(L, px * sw);
lua_pushnumber(L, py * sh);
return 2;
}
static int script_get_timer(lua_State *L)
{
lua_pushnumber(L, mp_time_sec());
return 1;
}
static int script_get_chapter_list(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
lua_newtable(L); // list
int num = get_chapter_count(mpctx);
for (int n = 0; n < num; n++) {
double time = chapter_start_time(mpctx, n);
char *name = chapter_display_name(mpctx, n);
lua_newtable(L); // list ch
lua_pushnumber(L, time); // list ch time
lua_setfield(L, -2, "time"); // list ch
lua_pushstring(L, name); // list ch name
lua_setfield(L, -2, "name"); // list ch
lua_pushinteger(L, n + 1); // list ch n1
lua_insert(L, -2); // list n1 ch
lua_settable(L, -3); // list
talloc_free(name);
}
return 1;
}
static const char *stream_type(enum stream_type t)
{
switch (t) {
case STREAM_VIDEO: return "video";
case STREAM_AUDIO: return "audio";
case STREAM_SUB: return "sub";
default: return "unknown";
}
}
static int script_get_track_list(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
lua_newtable(L); // list
for (int n = 0; n < mpctx->num_tracks; n++) {
struct track *track = mpctx->tracks[n];
lua_newtable(L); // list track
lua_pushstring(L, stream_type(track->type));
lua_setfield(L, -2, "type");
lua_pushinteger(L, track->user_tid);
lua_setfield(L, -2, "id");
lua_pushboolean(L, track->default_track);
lua_setfield(L, -2, "default");
lua_pushboolean(L, track->attached_picture);
lua_setfield(L, -2, "attached_picture");
if (track->lang) {
lua_pushstring(L, track->lang);
lua_setfield(L, -2, "language");
}
if (track->title) {
lua_pushstring(L, track->title);
lua_setfield(L, -2, "title");
}
lua_pushboolean(L, track->is_external);
lua_setfield(L, -2, "external");
if (track->external_filename) {
lua_pushstring(L, track->external_filename);
lua_setfield(L, -2, "external_filename");
}
lua_pushboolean(L, track->auto_loaded);
lua_setfield(L, -2, "auto_loaded");
lua_pushinteger(L, n + 1); // list track n1
lua_insert(L, -2); // list n1 track
lua_settable(L, -3); // list
}
return 1;
}
static int script_input_define_section(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
char *section = (char *)luaL_checkstring(L, 1);
char *contents = (char *)luaL_checkstring(L, 2);
mp_input_define_section(mpctx->input, section, "<script>", contents, true);
return 0;
}
static int script_input_enable_section(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
char *section = (char *)luaL_checkstring(L, 1);
char *sflags = (char *)luaL_optstring(L, 2, "");
bstr bflags = bstr0(sflags);
int flags = 0;
while (bflags.len) {
bstr val;
bstr_split_tok(bflags, "|", &val, &bflags);
if (bstr_equals0(val, "allow-hide-cursor")) {
flags |= MP_INPUT_ALLOW_HIDE_CURSOR;
} else if (bstr_equals0(val, "allow-vo-dragging")) {
flags |= MP_INPUT_ALLOW_VO_DRAGGING;
} else if (bstr_equals0(val, "exclusive")) {
flags |= MP_INPUT_EXCLUSIVE;
} else {
luaL_error(L, "invalid flag: '%.*s'", BSTR_P(val));
}
}
mp_input_enable_section(mpctx->input, section, flags);
return 0;
}
static int script_input_disable_section(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
char *section = (char *)luaL_checkstring(L, 1);
mp_input_disable_section(mpctx->input, section);
return 0;
}
static int script_input_set_section_mouse_area(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
double sw, sh;
struct osd_object *obj = mpctx->osd->objs[OSDTYPE_EXTERNAL];
osd_object_get_scale_factor(mpctx->osd, obj, &sw, &sh);
char *section = (char *)luaL_checkstring(L, 1);
int x0 = luaL_checkinteger(L, 2) / sw;
int y0 = luaL_checkinteger(L, 3) / sh;
int x1 = luaL_checkinteger(L, 4) / sw;
int y1 = luaL_checkinteger(L, 5) / sh;
mp_input_set_section_mouse_area(mpctx->input, section, x0, y0, x1, y1);
return 0;
}
static int script_format_time(lua_State *L)
{
double t = luaL_checknumber(L, 1);
const char *fmt = luaL_optstring(L, 2, "%H:%M:%S");
char *r = mp_format_time_fmt(fmt, t);
if (!r)
luaL_error(L, "Invalid time format string '%s'", fmt);
lua_pushstring(L, r);
talloc_free(r);
return 1;
}
struct fn_entry {
const char *name;
int (*fn)(lua_State *L);
};
#define FN_ENTRY(name) {#name, script_ ## name}
static struct fn_entry fn_list[] = {
FN_ENTRY(log),
FN_ENTRY(find_config_file),
FN_ENTRY(send_command),
FN_ENTRY(property_list),
FN_ENTRY(set_osd_ass),
FN_ENTRY(get_osd_resolution),
FN_ENTRY(get_screen_size),
FN_ENTRY(get_mouse_pos),
FN_ENTRY(get_timer),
FN_ENTRY(get_chapter_list),
FN_ENTRY(get_track_list),
FN_ENTRY(input_define_section),
FN_ENTRY(input_enable_section),
FN_ENTRY(input_disable_section),
FN_ENTRY(input_set_section_mouse_area),
FN_ENTRY(format_time),
};
// On stack: mp table
static void add_functions(struct script_ctx *ctx)
{
lua_State *L = ctx->state;
for (int n = 0; n < MP_ARRAY_SIZE(fn_list); n++) {
lua_pushcfunction(L, fn_list[n].fn);
lua_setfield(L, -2, fn_list[n].name);
}
lua_pushinteger(L, 0);
lua_pushcclosure(L, script_property_string, 1);
lua_setfield(L, -2, "property_get");
lua_pushinteger(L, 1);
lua_pushcclosure(L, script_property_string, 1);
lua_setfield(L, -2, "property_get_string");
}
void mp_lua_init(struct MPContext *mpctx)
{
mpctx->lua_ctx = talloc_zero(NULL, struct lua_ctx);
// Load scripts from options
if (mpctx->opts->lua_load_osc)
mp_lua_load_script(mpctx, "@osc");
char **files = mpctx->opts->lua_files;
for (int n = 0; files && files[n]; n++) {
if (files[n][0])
mp_lua_load_script(mpctx, files[n]);
}
}
void mp_lua_uninit(struct MPContext *mpctx)
{
if (mpctx->lua_ctx) {
while (mpctx->lua_ctx->num_scripts)
kill_script(mpctx->lua_ctx->scripts[0]);
talloc_free(mpctx->lua_ctx);
mpctx->lua_ctx = NULL;
}
}

14
player/mp_lua.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef MP_LUA_H
#define MP_LUA_H
#include <stdbool.h>
struct MPContext;
void mp_lua_init(struct MPContext *mpctx);
void mp_lua_uninit(struct MPContext *mpctx);
void mp_lua_event(struct MPContext *mpctx, const char *name, const char *arg);
void mp_lua_script_dispatch(struct MPContext *mpctx, char *script_name,
int id, char *event);
#endif

518
player/osd.c Normal file
View File

@@ -0,0 +1,518 @@
/*
* 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 <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <math.h>
#include <limits.h>
#include <assert.h>
#include "config.h"
#include "talloc.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/options.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/m_property.h"
#include "mpvcore/encode.h"
#include "osdep/getch2.h"
#include "osdep/timer.h"
#include "demux/demux.h"
#include "sub/osd.h"
#include "mp_core.h"
#include "command.h"
#define saddf(var, ...) (*(var) = talloc_asprintf_append((*var), __VA_ARGS__))
// append time in the hh:mm:ss format (plus fractions if wanted)
static void sadd_hhmmssff(char **buf, double time, bool fractions)
{
char *s = mp_format_time(time, fractions);
*buf = talloc_strdup_append(*buf, s);
talloc_free(s);
}
static void sadd_percentage(char **buf, int percent) {
if (percent >= 0)
*buf = talloc_asprintf_append(*buf, " (%d%%)", percent);
}
static int get_term_width(void)
{
get_screen_size();
int width = screen_width > 0 ? screen_width : 80;
#if defined(__MINGW32__) || defined(__CYGWIN__)
/* Windows command line is broken (MinGW's rxvt works, but we
* should not depend on that). */
width--;
#endif
return width;
}
void write_status_line(struct MPContext *mpctx, const char *line)
{
struct MPOpts *opts = mpctx->opts;
if (opts->slave_mode) {
mp_msg(MSGT_STATUSLINE, MSGL_STATUS, "%s\n", line);
} else if (erase_to_end_of_line) {
mp_msg(MSGT_STATUSLINE, MSGL_STATUS,
"%s%s\r", line, erase_to_end_of_line);
} else {
int pos = strlen(line);
int width = get_term_width() - pos;
mp_msg(MSGT_STATUSLINE, MSGL_STATUS, "%s%*s\r", line, width, "");
}
}
void print_status(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
update_window_title(mpctx, false);
if (opts->quiet)
return;
if (opts->status_msg) {
char *r = mp_property_expand_string(mpctx, opts->status_msg);
write_status_line(mpctx, r);
talloc_free(r);
return;
}
char *line = NULL;
// Playback status
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
double cur = get_current_time(mpctx);
sadd_hhmmssff(&line, cur, 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) {
if (mpctx->last_av_difference != MP_NOPTS_VALUE)
saddf(&line, " A-V:%7.3f", mpctx->last_av_difference);
else
saddf(&line, " A-V: ???");
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 && mpctx->drop_frame_cnt)
saddf(&line, " Late: %d", mpctx->drop_frame_cnt);
}
int cache = mp_get_cache_percent(mpctx);
if (cache >= 0)
saddf(&line, " Cache: %d%%", cache);
// end
write_status_line(mpctx, line);
talloc_free(line);
}
typedef struct mp_osd_msg mp_osd_msg_t;
struct mp_osd_msg {
/// Previous message on the stack.
mp_osd_msg_t *prev;
/// Message text.
char *msg;
int id, level, started;
/// Display duration in seconds.
double time;
// Show full OSD for duration of message instead of msg
// (osd_show_progression command)
bool show_position;
};
// time is in ms
static mp_osd_msg_t *add_osd_msg(struct MPContext *mpctx, int id, int level,
int time)
{
rm_osd_msg(mpctx, id);
mp_osd_msg_t *msg = talloc_struct(mpctx, mp_osd_msg_t, {
.prev = mpctx->osd_msg_stack,
.msg = "",
.id = id,
.level = level,
.time = time / 1000.0,
});
mpctx->osd_msg_stack = msg;
return msg;
}
static void set_osd_msg_va(struct MPContext *mpctx, int id, int level, int time,
const char *fmt, va_list ap)
{
if (level == OSD_LEVEL_INVISIBLE)
return;
mp_osd_msg_t *msg = add_osd_msg(mpctx, id, level, time);
msg->msg = talloc_vasprintf(msg, fmt, ap);
}
void set_osd_msg(struct MPContext *mpctx, int id, int level, int time,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
set_osd_msg_va(mpctx, id, level, time, fmt, ap);
va_end(ap);
}
/**
* \brief Remove a message from the OSD stack
*
* This function can be used to get rid of a message right away.
*
*/
void rm_osd_msg(struct MPContext *mpctx, int id)
{
mp_osd_msg_t *msg, *last = NULL;
// Search for the msg
for (msg = mpctx->osd_msg_stack; msg && msg->id != id;
last = msg, msg = msg->prev) ;
if (!msg)
return;
// Detach it from the stack and free it
if (last)
last->prev = msg->prev;
else
mpctx->osd_msg_stack = msg->prev;
talloc_free(msg);
}
/**
* \brief Get the current message from the OSD stack.
*
* This function decrements the message timer and destroys the old ones.
* The message that should be displayed is returned (if any).
*
*/
static mp_osd_msg_t *get_osd_msg(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
mp_osd_msg_t *msg, *prev, *last = NULL;
double now = mp_time_sec();
double diff;
char hidden_dec_done = 0;
if (mpctx->osd_visible && now >= mpctx->osd_visible) {
mpctx->osd_visible = 0;
mpctx->osd->progbar_type = -1; // disable
osd_changed(mpctx->osd, OSDTYPE_PROGBAR);
}
if (mpctx->osd_function_visible && now >= mpctx->osd_function_visible) {
mpctx->osd_function_visible = 0;
mpctx->osd_function = 0;
}
if (!mpctx->osd_last_update)
mpctx->osd_last_update = now;
diff = now >= mpctx->osd_last_update ? now - mpctx->osd_last_update : 0;
mpctx->osd_last_update = now;
// Look for the first message in the stack with high enough level.
for (msg = mpctx->osd_msg_stack; msg; last = msg, msg = prev) {
prev = msg->prev;
if (msg->level > opts->osd_level && hidden_dec_done)
continue;
// The message has a high enough level or it is the first hidden one
// in both cases we decrement the timer or kill it.
if (!msg->started || msg->time > diff) {
if (msg->started)
msg->time -= diff;
else
msg->started = 1;
// display it
if (msg->level <= opts->osd_level)
return msg;
hidden_dec_done = 1;
continue;
}
// kill the message
talloc_free(msg);
if (last) {
last->prev = prev;
msg = last;
} else {
mpctx->osd_msg_stack = prev;
msg = NULL;
}
}
// Nothing found
return NULL;
}
// type: mp_osd_font_codepoints, ASCII, or OSD_BAR_*
// name: fallback for terminal OSD
void set_osd_bar(struct MPContext *mpctx, int type, const char *name,
double min, double max, double val)
{
struct MPOpts *opts = mpctx->opts;
if (opts->osd_level < 1 || !opts->osd_bar_visible)
return;
if (mpctx->video_out && opts->term_osd != 1) {
mpctx->osd_visible = mp_time_sec() + opts->osd_duration / 1000.0;
mpctx->osd->progbar_type = type;
mpctx->osd->progbar_value = (val - min) / (max - min);
mpctx->osd->progbar_num_stops = 0;
osd_changed(mpctx->osd, OSDTYPE_PROGBAR);
return;
}
set_osd_msg(mpctx, OSD_MSG_BAR, 1, opts->osd_duration, "%s: %d %%",
name, ROUND(100 * (val - min) / (max - min)));
}
// Update a currently displayed bar of the same type, without resetting the
// timer.
static void update_osd_bar(struct MPContext *mpctx, int type,
double min, double max, double val)
{
if (mpctx->osd->progbar_type == type) {
float new_value = (val - min) / (max - min);
if (new_value != mpctx->osd->progbar_value) {
mpctx->osd->progbar_value = new_value;
osd_changed(mpctx->osd, OSDTYPE_PROGBAR);
}
}
}
static void set_osd_bar_chapters(struct MPContext *mpctx, int type)
{
struct osd_state *osd = mpctx->osd;
osd->progbar_num_stops = 0;
if (osd->progbar_type == type) {
double len = get_time_length(mpctx);
if (len > 0) {
int num = get_chapter_count(mpctx);
for (int n = 0; n < num; n++) {
double time = chapter_start_time(mpctx, n);
if (time >= 0) {
float pos = time / len;
MP_TARRAY_APPEND(osd, osd->progbar_stops,
osd->progbar_num_stops, pos);
}
}
}
}
}
// osd_function is the symbol appearing in the video status, such as OSD_PLAY
void set_osd_function(struct MPContext *mpctx, int osd_function)
{
struct MPOpts *opts = mpctx->opts;
mpctx->osd_function = osd_function;
mpctx->osd_function_visible = mp_time_sec() + opts->osd_duration / 1000.0;
}
/**
* \brief Display text subtitles on the OSD
*/
void set_osd_subtitle(struct MPContext *mpctx, const char *text)
{
if (!text)
text = "";
if (strcmp(mpctx->osd->sub_text, text) != 0) {
osd_set_sub(mpctx->osd, text);
if (!mpctx->video_out) {
rm_osd_msg(mpctx, OSD_MSG_SUB_BASE);
if (text && text[0])
set_osd_msg(mpctx, OSD_MSG_SUB_BASE, 1, INT_MAX, "%s", text);
}
}
if (!text[0])
rm_osd_msg(mpctx, OSD_MSG_SUB_BASE);
}
// sym == mpctx->osd_function
static void saddf_osd_function_sym(char **buffer, int sym)
{
char temp[10];
osd_get_function_sym(temp, sizeof(temp), sym);
saddf(buffer, "%s ", temp);
}
static void sadd_osd_status(char **buffer, struct MPContext *mpctx, bool full)
{
bool fractions = mpctx->opts->osd_fractions;
int sym = mpctx->osd_function;
if (!sym) {
if (mpctx->paused_for_cache && !mpctx->opts->pause) {
sym = OSD_CLOCK;
} else if (mpctx->paused || mpctx->step_frames) {
sym = OSD_PAUSE;
} else {
sym = OSD_PLAY;
}
}
saddf_osd_function_sym(buffer, sym);
char *custom_msg = mpctx->opts->osd_status_msg;
if (custom_msg && full) {
char *text = mp_property_expand_string(mpctx, custom_msg);
*buffer = talloc_strdup_append(*buffer, text);
talloc_free(text);
} else {
sadd_hhmmssff(buffer, get_current_time(mpctx), fractions);
if (full) {
saddf(buffer, " / ");
sadd_hhmmssff(buffer, get_time_length(mpctx), fractions);
sadd_percentage(buffer, get_percent_pos(mpctx));
int cache = mp_get_cache_percent(mpctx);
if (cache >= 0)
saddf(buffer, " Cache: %d%%", cache);
}
}
}
// OSD messages initated by seeking commands are added lazily with this
// function, because multiple successive seek commands can be coalesced.
static void add_seek_osd_messages(struct MPContext *mpctx)
{
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_BAR) {
double pos = get_current_pos_ratio(mpctx, false);
set_osd_bar(mpctx, OSD_BAR_SEEK, "Position", 0, 1, MPCLAMP(pos, 0, 1));
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
}
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_TEXT) {
mp_osd_msg_t *msg = add_osd_msg(mpctx, OSD_MSG_TEXT, 1,
mpctx->opts->osd_duration);
msg->show_position = true;
}
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_CHAPTER_TEXT) {
char *chapter = chapter_display_name(mpctx, get_current_chapter(mpctx));
set_osd_msg(mpctx, OSD_MSG_TEXT, 1, mpctx->opts->osd_duration,
"Chapter: %s", chapter);
talloc_free(chapter);
}
if ((mpctx->add_osd_seek_info & OSD_SEEK_INFO_EDITION)
&& mpctx->master_demuxer)
{
set_osd_msg(mpctx, OSD_MSG_TEXT, 1, mpctx->opts->osd_duration,
"Playing edition %d of %d.",
mpctx->master_demuxer->edition + 1,
mpctx->master_demuxer->num_editions);
}
mpctx->add_osd_seek_info = 0;
}
/**
* \brief Update the OSD message line.
*
* This function displays the current message on the vo OSD or on the term.
* If the stack is empty and the OSD level is high enough the timer
* is displayed (only on the vo OSD).
*
*/
void update_osd_msg(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct osd_state *osd = mpctx->osd;
add_seek_osd_messages(mpctx);
double pos = get_current_pos_ratio(mpctx, false);
update_osd_bar(mpctx, OSD_BAR_SEEK, 0, 1, MPCLAMP(pos, 0, 1));
// Look if we have a msg
mp_osd_msg_t *msg = get_osd_msg(mpctx);
if (msg && !msg->show_position) {
if (mpctx->video_out && opts->term_osd != 1) {
osd_set_text(osd, msg->msg);
} else if (opts->term_osd) {
if (strcmp(mpctx->terminal_osd_text, msg->msg)) {
talloc_free(mpctx->terminal_osd_text);
mpctx->terminal_osd_text = talloc_strdup(mpctx, msg->msg);
// Multi-line message => clear what will be the second line
write_status_line(mpctx, "");
mp_msg(MSGT_CPLAYER, MSGL_STATUS, "%s%s\n", opts->term_osd_esc,
mpctx->terminal_osd_text);
print_status(mpctx);
}
}
return;
}
int osd_level = opts->osd_level;
if (msg && msg->show_position)
osd_level = 3;
if (mpctx->video_out && opts->term_osd != 1) {
// fallback on the timer
char *text = NULL;
if (osd_level >= 2)
sadd_osd_status(&text, mpctx, osd_level == 3);
osd_set_text(osd, text);
talloc_free(text);
return;
}
// Clear the term osd line
if (opts->term_osd && mpctx->terminal_osd_text[0]) {
mpctx->terminal_osd_text[0] = '\0';
mp_msg(MSGT_CPLAYER, MSGL_STATUS, "%s\n", opts->term_osd_esc);
}
}

1343
player/playloop.c Normal file
View File

File diff suppressed because it is too large Load Diff

404
player/screenshot.c Normal file
View File

@@ -0,0 +1,404 @@
/*
* This file is part of mplayer2.
*
* mplayer2 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.
*
* mplayer2 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 mplayer2; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "config.h"
#include "osdep/io.h"
#include "talloc.h"
#include "screenshot.h"
#include "mp_core.h"
#include "command.h"
#include "mpvcore/bstr.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/path.h"
#include "video/mp_image.h"
#include "video/decode/dec_video.h"
#include "video/filter/vf.h"
#include "video/out/vo.h"
#include "video/image_writer.h"
#include "sub/osd.h"
#include "video/csputils.h"
#define MODE_FULL_WINDOW 1
#define MODE_SUBTITLES 2
typedef struct screenshot_ctx {
struct MPContext *mpctx;
int mode;
bool each_frame;
bool osd;
int frameno;
} screenshot_ctx;
void screenshot_init(struct MPContext *mpctx)
{
mpctx->screenshot_ctx = talloc(mpctx, screenshot_ctx);
*mpctx->screenshot_ctx = (screenshot_ctx) {
.mpctx = mpctx,
.frameno = 1,
};
}
#define SMSG_OK 0
#define SMSG_ERR 1
static void screenshot_msg(screenshot_ctx *ctx, int status, const char *msg,
...) PRINTF_ATTRIBUTE(3,4);
static void screenshot_msg(screenshot_ctx *ctx, int status, const char *msg,
...)
{
va_list ap;
char *s;
va_start(ap, msg);
s = talloc_vasprintf(NULL, msg, ap);
va_end(ap);
mp_msg(MSGT_CPLAYER, status == SMSG_ERR ? MSGL_ERR : MSGL_INFO, "%s\n", s);
if (ctx->osd) {
set_osd_msg(ctx->mpctx, OSD_MSG_TEXT, 1, ctx->mpctx->opts->osd_duration,
"%s", s);
}
talloc_free(s);
}
static char *stripext(void *talloc_ctx, const char *s)
{
const char *end = strrchr(s, '.');
if (!end)
end = s + strlen(s);
return talloc_asprintf(talloc_ctx, "%.*s", (int)(end - s), s);
}
#ifdef _WIN32
#define ILLEGAL_FILENAME_CHARS "?\"/\\<>*|:"
#else
#define ILLEGAL_FILENAME_CHARS "/"
#endif
// Replace all characters disallowed in filenames with '_' and return the newly
// allocated result string.
static char *sanitize_filename(void *talloc_ctx, const char *s)
{
char *res = talloc_strdup(talloc_ctx, s);
char *cur = res;
while (*cur) {
if (strchr(ILLEGAL_FILENAME_CHARS, *cur) || ((unsigned char)*cur) < 32)
*cur = '_';
cur++;
}
return res;
}
static void append_filename(char **s, const char *f)
{
char *append = sanitize_filename(NULL, f);
*s = talloc_strdup_append(*s, append);
talloc_free(append);
}
static char *create_fname(struct MPContext *mpctx, char *template,
const char *file_ext, int *sequence, int *frameno)
{
char *res = talloc_strdup(NULL, ""); //empty string, non-NULL context
time_t raw_time = time(NULL);
struct tm *local_time = localtime(&raw_time);
if (!template || *template == '\0')
template = "shot%n";
for (;;) {
char *next = strchr(template, '%');
if (!next)
break;
res = talloc_strndup_append(res, template, next - template);
template = next + 1;
char fmt = *template++;
switch (fmt) {
case '#':
case '0':
case 'n': {
int digits = '4';
if (fmt == '#') {
if (!*sequence) {
*frameno = 1;
}
fmt = *template++;
}
if (fmt == '0') {
digits = *template++;
if (digits < '0' || digits > '9')
goto error_exit;
fmt = *template++;
}
if (fmt != 'n')
goto error_exit;
char fmtstr[] = {'%', '0', digits, 'd', '\0'};
res = talloc_asprintf_append(res, fmtstr, *frameno);
if (*frameno < 100000 - 1) {
(*frameno) += 1;
(*sequence) += 1;
}
break;
}
case 'f':
case 'F': {
char *video_file = mp_basename(mpctx->filename);
if (video_file) {
char *name = video_file;
if (fmt == 'F')
name = stripext(res, video_file);
append_filename(&res, name);
}
break;
}
case 'p':
case 'P': {
char *t = mp_format_time(get_current_time(mpctx), fmt == 'P');
append_filename(&res, t);
talloc_free(t);
break;
}
case 'w': {
char tfmt = *template;
if (!tfmt)
goto error_exit;
template++;
char fmtstr[] = {'%', tfmt, '\0'};
char *s = mp_format_time_fmt(fmtstr, get_current_time(mpctx));
if (!s)
goto error_exit;
append_filename(&res, s);
talloc_free(s);
break;
}
case 't': {
char tfmt = *template;
if (!tfmt)
goto error_exit;
template++;
char fmtstr[] = {'%', tfmt, '\0'};
char buffer[80];
if (strftime(buffer, sizeof(buffer), fmtstr, local_time) == 0)
buffer[0] = '\0';
append_filename(&res, buffer);
break;
}
case '{': {
char *end = strchr(template, '}');
if (!end)
goto error_exit;
struct bstr prop = bstr_splice(bstr0(template), 0, end - template);
char *tmp = talloc_asprintf(NULL, "${%.*s}", BSTR_P(prop));
char *s = mp_property_expand_string(mpctx, tmp);
talloc_free(tmp);
if (s)
append_filename(&res, s);
talloc_free(s);
template = end + 1;
break;
}
case '%':
res = talloc_strdup_append(res, "%");
break;
default:
goto error_exit;
}
}
res = talloc_strdup_append(res, template);
res = talloc_asprintf_append(res, ".%s", file_ext);
char *fname = mp_get_user_path(NULL, res);
talloc_free(res);
return fname;
error_exit:
talloc_free(res);
return NULL;
}
static char *gen_fname(screenshot_ctx *ctx, const char *file_ext)
{
int sequence = 0;
for (;;) {
int prev_sequence = sequence;
char *fname = create_fname(ctx->mpctx,
ctx->mpctx->opts->screenshot_template,
file_ext,
&sequence,
&ctx->frameno);
if (!fname) {
screenshot_msg(ctx, SMSG_ERR, "Invalid screenshot filename "
"template! Fix or remove the --screenshot-template "
"option.");
return NULL;
}
if (!mp_path_exists(fname))
return fname;
if (sequence == prev_sequence) {
screenshot_msg(ctx, SMSG_ERR, "Can't save screenshot, file '%s' "
"already exists!", fname);
talloc_free(fname);
return NULL;
}
talloc_free(fname);
}
}
static void add_subs(struct MPContext *mpctx, struct mp_image *image)
{
int d_w = image->display_w ? image->display_w : image->w;
int d_h = image->display_h ? image->display_h : image->h;
double sar = (double)image->w / image->h;
double dar = (double)d_w / d_h;
struct mp_osd_res res = {
.w = image->w,
.h = image->h,
.display_par = sar / dar,
};
osd_draw_on_image(mpctx->osd, res, mpctx->osd->vo_pts,
OSD_DRAW_SUB_ONLY, image);
}
static void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
{
screenshot_ctx *ctx = mpctx->screenshot_ctx;
struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts;
char *filename = gen_fname(ctx, image_writer_file_ext(opts));
if (filename) {
screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
if (!write_image(image, opts, filename))
screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
talloc_free(filename);
}
}
static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
{
struct mp_image *image = NULL;
if (mpctx->video_out && mpctx->video_out->config_ok) {
if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
mode = 0;
struct voctrl_screenshot_args args =
{ .full_window = (mode == MODE_FULL_WINDOW) };
if (mpctx->d_video && mpctx->d_video->vfilter)
vf_control_any(mpctx->d_video->vfilter, VFCTRL_SCREENSHOT, &args);
if (!args.out_image)
vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args);
image = args.out_image;
if (image) {
if (mode == MODE_SUBTITLES && !args.has_osd)
add_subs(mpctx, image);
}
}
return image;
}
void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
bool osd)
{
screenshot_ctx *ctx = mpctx->screenshot_ctx;
struct image_writer_opts opts = *mpctx->opts->screenshot_image_opts;
bool old_osd = ctx->osd;
ctx->osd = osd;
if (mp_path_exists(filename)) {
screenshot_msg(ctx, SMSG_ERR, "Screenshot: file '%s' already exists.",
filename);
goto end;
}
char *ext = mp_splitext(filename, NULL);
if (ext)
opts.format = ext + 1; // omit '.'
struct mp_image *image = screenshot_get(mpctx, mode);
if (!image) {
screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
goto end;
}
screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
if (!write_image(image, &opts, filename))
screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
talloc_free(image);
end:
ctx->osd = old_osd;
}
void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
bool osd)
{
screenshot_ctx *ctx = mpctx->screenshot_ctx;
if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
mode = 0;
if (each_frame) {
ctx->each_frame = !ctx->each_frame;
if (!ctx->each_frame)
return;
} else {
ctx->each_frame = false;
}
ctx->mode = mode;
ctx->osd = osd;
struct mp_image *image = screenshot_get(mpctx, mode);
if (image) {
screenshot_save(mpctx, image);
} else {
screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
}
talloc_free(image);
}
void screenshot_flip(struct MPContext *mpctx)
{
screenshot_ctx *ctx = mpctx->screenshot_ctx;
if (!ctx->each_frame)
return;
ctx->each_frame = false;
screenshot_request(mpctx, ctx->mode, true, ctx->osd);
}

46
player/screenshot.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* This file is part of mplayer2.
*
* mplayer2 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.
*
* mplayer2 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 mplayer2; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPLAYER_SCREENSHOT_H
#define MPLAYER_SCREENSHOT_H
#include <stdbool.h>
struct MPContext;
// One time initialization at program start.
void screenshot_init(struct MPContext *mpctx);
// Request a taking & saving a screenshot of the currently displayed frame.
// mode: 0: -, 1: save the actual output window contents, 2: with subtitles.
// each_frame: If set, this toggles per-frame screenshots, exactly like the
// screenshot slave command (MP_CMD_SCREENSHOT).
// osd: show status on OSD
void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
bool osd);
// filename: where to store the screenshot; doesn't try to find an alternate
// name if the file already exists
// mode, osd: same as in screenshot_request()
void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
bool osd);
// Called by the playback core code when a new frame is displayed.
void screenshot_flip(struct MPContext *mpctx);
#endif /* MPLAYER_SCREENSHOT_H */

233
player/sub.c Normal file
View File

@@ -0,0 +1,233 @@
/*
* 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 <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <math.h>
#include <assert.h>
#include "config.h"
#include "talloc.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/options.h"
#include "mpvcore/mp_common.h"
#include "stream/stream.h"
#include "sub/dec_sub.h"
#include "demux/demux.h"
#include "video/mp_image.h"
#include "video/decode/dec_video.h"
#include "mp_core.h"
void uninit_subs(struct demuxer *demuxer)
{
for (int i = 0; i < demuxer->num_streams; i++) {
struct sh_stream *sh = demuxer->streams[i];
if (sh->sub) {
sub_destroy(sh->sub->dec_sub);
sh->sub->dec_sub = NULL;
}
}
}
// When reading subtitles from a demuxer, and we read video or audio from the
// demuxer, we should not explicitly read subtitle packets. (With external
// subs, we have to.)
static bool is_interleaved(struct MPContext *mpctx, struct track *track)
{
if (track->is_external || !track->demuxer)
return false;
struct demuxer *demuxer = track->demuxer;
for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
struct track *other = mpctx->current_track[type];
if (other && other != track && other->demuxer && other->demuxer == demuxer)
return true;
}
return false;
}
void reset_subtitles(struct MPContext *mpctx)
{
if (mpctx->d_sub)
sub_reset(mpctx->d_sub);
set_osd_subtitle(mpctx, NULL);
osd_changed(mpctx->osd, OSDTYPE_SUB);
}
void update_subtitles(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
if (!(mpctx->initialized_flags & INITIALIZED_SUB))
return;
struct track *track = mpctx->current_track[STREAM_SUB];
struct dec_sub *dec_sub = mpctx->d_sub;
assert(track && dec_sub);
if (mpctx->d_video) {
struct mp_image_params params = mpctx->d_video->vf_input;
if (params.imgfmt)
sub_control(dec_sub, SD_CTRL_SET_VIDEO_PARAMS, &params);
}
mpctx->osd->video_offset = track->under_timeline ? mpctx->video_offset : 0;
double refpts_s = mpctx->playback_pts - mpctx->osd->video_offset;
double curpts_s = refpts_s + opts->sub_delay;
if (!track->preloaded && track->stream) {
struct sh_stream *sh_stream = track->stream;
bool interleaved = is_interleaved(mpctx, track);
assert(sh_stream->sub->dec_sub == dec_sub);
while (1) {
if (interleaved && !demux_has_packet(sh_stream))
break;
double subpts_s = demux_get_next_pts(sh_stream);
if (!demux_has_packet(sh_stream))
break;
if (subpts_s > curpts_s) {
mp_msg(MSGT_CPLAYER, MSGL_DBG2,
"Sub early: c_pts=%5.3f s_pts=%5.3f\n",
curpts_s, subpts_s);
// Libass handled subs can be fed to it in advance
if (!sub_accept_packets_in_advance(dec_sub))
break;
// Try to avoid demuxing whole file at once
if (subpts_s > curpts_s + 1 && !interleaved)
break;
}
struct demux_packet *pkt = demux_read_packet(sh_stream);
mp_msg(MSGT_CPLAYER, MSGL_V, "Sub: c_pts=%5.3f s_pts=%5.3f "
"duration=%5.3f len=%d\n", curpts_s, pkt->pts, pkt->duration,
pkt->len);
sub_decode(dec_sub, pkt);
talloc_free(pkt);
}
}
if (!mpctx->osd->render_bitmap_subs || !mpctx->video_out)
set_osd_subtitle(mpctx, sub_get_text(dec_sub, curpts_s));
}
static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st,
int width, int height)
{
if (!st)
return;
struct stream_dvd_info_req info;
if (stream_control(st, STREAM_CTRL_GET_DVD_INFO, &info) < 0)
return;
struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
csp.int_bits_in = 8;
csp.int_bits_out = 8;
float cmatrix[3][4];
mp_get_yuv2rgb_coeffs(&csp, cmatrix);
if (width == 0 || height == 0) {
width = 720;
height = 480;
}
char *s = NULL;
s = talloc_asprintf_append(s, "size: %dx%d\n", width, height);
s = talloc_asprintf_append(s, "palette: ");
for (int i = 0; i < 16; i++) {
int color = info.palette[i];
int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
mp_map_int_color(cmatrix, 8, c);
color = (c[2] << 16) | (c[1] << 8) | c[0];
if (i != 0)
talloc_asprintf_append(s, ", ");
s = talloc_asprintf_append(s, "%06x", color);
}
s = talloc_asprintf_append(s, "\n");
sub_set_extradata(dec_sub, s, strlen(s));
talloc_free(s);
}
void reinit_subs(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct track *track = mpctx->current_track[STREAM_SUB];
assert(!(mpctx->initialized_flags & INITIALIZED_SUB));
init_demux_stream(mpctx, STREAM_SUB);
struct sh_stream *sh = mpctx->sh[STREAM_SUB];
// No track selected, or lazily added DVD track (will actually be created
// on first sub packet)
if (!sh)
return;
if (!sh->sub->dec_sub) {
assert(!mpctx->d_sub);
sh->sub->dec_sub = sub_create(opts);
}
assert(!mpctx->d_sub || sh->sub->dec_sub == mpctx->d_sub);
// The decoder is kept in the stream header in order to make ordered
// chapters work well.
mpctx->d_sub = sh->sub->dec_sub;
mpctx->initialized_flags |= INITIALIZED_SUB;
struct dec_sub *dec_sub = mpctx->d_sub;
assert(dec_sub);
if (!sub_is_initialized(dec_sub)) {
struct sh_video *sh_video =
mpctx->d_video ? mpctx->d_video->header->video : NULL;
int w = sh_video ? sh_video->disp_w : 0;
int h = sh_video ? sh_video->disp_h : 0;
float fps = sh_video ? sh_video->fps : 25;
set_dvdsub_fake_extradata(dec_sub, track->demuxer->stream, w, h);
sub_set_video_res(dec_sub, w, h);
sub_set_video_fps(dec_sub, fps);
sub_set_ass_renderer(dec_sub, mpctx->ass_library, mpctx->ass_renderer);
sub_init_from_sh(dec_sub, sh);
// Don't do this if the file has video/audio streams. Don't do it even
// if it has only sub streams, because reading packets will change the
// demuxer position.
if (!track->preloaded && track->is_external) {
demux_seek(track->demuxer, 0, SEEK_ABSOLUTE);
track->preloaded = sub_read_all_packets(dec_sub, sh);
}
}
mpctx->osd->dec_sub = dec_sub;
// Decides whether to use OSD path or normal subtitle rendering path.
mpctx->osd->render_bitmap_subs =
opts->ass_enabled || !sub_has_get_text(dec_sub);
reset_subtitles(mpctx);
}

417
player/timeline/tl_cue.c Normal file
View File

@@ -0,0 +1,417 @@
/*
* This file is part of mplayer2.
*
* mplayer2 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.
*
* mplayer2 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 mplayer2; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <dirent.h>
#include <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <ctype.h>
#include "talloc.h"
#include "player/mp_core.h"
#include "mpvcore/mp_msg.h"
#include "demux/demux.h"
#include "mpvcore/path.h"
#include "mpvcore/bstr.h"
#include "mpvcore/mp_common.h"
#include "stream/stream.h"
// used by demuxer_cue.c
bool mp_probe_cue(struct bstr data);
#define SECS_PER_CUE_FRAME (1.0/75.0)
enum cue_command {
CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
CUE_EMPTY, // line with whitespace only
CUE_UNUSED, // valid CUE command, but ignored by this code
CUE_FILE,
CUE_TRACK,
CUE_INDEX,
CUE_TITLE,
};
static const struct {
enum cue_command command;
const char *text;
} cue_command_strings[] = {
{ CUE_FILE, "FILE" },
{ CUE_TRACK, "TRACK" },
{ CUE_INDEX, "INDEX" },
{ CUE_TITLE, "TITLE" },
{ CUE_UNUSED, "CATALOG" },
{ CUE_UNUSED, "CDTEXTFILE" },
{ CUE_UNUSED, "FLAGS" },
{ CUE_UNUSED, "ISRC" },
{ CUE_UNUSED, "PERFORMER" },
{ CUE_UNUSED, "POSTGAP" },
{ CUE_UNUSED, "PREGAP" },
{ CUE_UNUSED, "REM" },
{ CUE_UNUSED, "SONGWRITER" },
{ CUE_UNUSED, "MESSAGE" },
{ -1 },
};
struct cue_track {
double pregap_start; // corresponds to INDEX 00
double start; // corresponds to INDEX 01
struct bstr filename;
int source;
struct bstr title;
};
static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
{
struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
line = bstr_lstrip(line);
if (line.len == 0)
return CUE_EMPTY;
for (int n = 0; cue_command_strings[n].command != -1; n++) {
struct bstr name = bstr0(cue_command_strings[n].text);
if (bstr_startswith(line, name)) {
struct bstr rest = bstr_cut(line, name.len);
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
continue;
if (out_params)
*out_params = rest;
return cue_command_strings[n].command;
}
}
return CUE_ERROR;
}
static bool eat_char(struct bstr *data, char ch)
{
if (data->len && data->start[0] == ch) {
*data = bstr_cut(*data, 1);
return true;
} else {
return false;
}
}
static struct bstr read_quoted(struct bstr *data)
{
*data = bstr_lstrip(*data);
if (!eat_char(data, '"'))
return (struct bstr) {0};
int end = bstrchr(*data, '"');
if (end < 0)
return (struct bstr) {0};
struct bstr res = bstr_splice(*data, 0, end);
*data = bstr_cut(*data, end + 1);
return res;
}
// Read a 2 digit unsigned decimal integer.
// Return -1 on failure.
static int read_int_2(struct bstr *data)
{
*data = bstr_lstrip(*data);
if (data->len && data->start[0] == '-')
return -1;
struct bstr s = *data;
int res = (int)bstrtoll(s, &s, 10);
if (data->len == s.len || data->len - s.len > 2)
return -1;
*data = s;
return res;
}
static double read_time(struct bstr *data)
{
struct bstr s = *data;
bool ok = true;
double t1 = read_int_2(&s);
ok = eat_char(&s, ':') && ok;
double t2 = read_int_2(&s);
ok = eat_char(&s, ':') && ok;
double t3 = read_int_2(&s);
ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
}
static struct bstr skip_utf8_bom(struct bstr data)
{
return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
}
// Check if the text in data is most likely CUE data. This is used by the
// demuxer code to check the file type.
// data is the start of the probed file, possibly cut off at a random point.
bool mp_probe_cue(struct bstr data)
{
bool valid = false;
data = skip_utf8_bom(data);
for (;;) {
enum cue_command cmd = read_cmd(&data, NULL);
// End reached. Since the line was most likely cut off, don't use the
// result of the last parsing call.
if (data.len == 0)
break;
if (cmd == CUE_ERROR)
return false;
if (cmd != CUE_EMPTY)
valid = true;
}
return valid;
}
static void add_source(struct MPContext *mpctx, struct demuxer *d)
{
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d);
}
static bool try_open(struct MPContext *mpctx, char *filename)
{
struct bstr bfilename = bstr0(filename);
// Avoid trying to open itself or another .cue file. Best would be
// to check the result of demuxer auto-detection, but the demuxer
// API doesn't allow this without opening a full demuxer.
if (bstr_case_endswith(bfilename, bstr0(".cue"))
|| bstrcasecmp(bstr0(mpctx->demuxer->filename), bfilename) == 0)
return false;
struct stream *s = stream_open(filename, mpctx->opts);
if (!s)
return false;
struct demuxer *d = demux_open(s, NULL, NULL, mpctx->opts);
// Since .bin files are raw PCM data with no headers, we have to explicitly
// open them. Also, try to avoid to open files that are most likely not .bin
// files, as that would only play noise. Checking the file extension is
// fragile, but it's about the only way we have.
// TODO: maybe also could check if the .bin file is a multiple of the Audio
// CD sector size (2352 bytes)
if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) {
mp_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: Opening as BIN file!\n");
d = demux_open(s, "rawaudio", NULL, mpctx->opts);
}
if (d) {
add_source(mpctx, d);
return true;
}
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Could not open source '%s'!\n", filename);
free_stream(s);
return false;
}
static bool open_source(struct MPContext *mpctx, struct bstr filename)
{
void *ctx = talloc_new(NULL);
bool res = false;
struct bstr dirname = mp_dirname(mpctx->demuxer->filename);
struct bstr base_filename = bstr0(mp_basename(bstrdup0(ctx, filename)));
if (!base_filename.len) {
mp_msg(MSGT_CPLAYER, MSGL_WARN,
"CUE: Invalid audio filename in .cue file!\n");
} else {
char *fullname = mp_path_join(ctx, dirname, base_filename);
if (try_open(mpctx, fullname)) {
res = true;
goto out;
}
}
// Try an audio file with the same name as the .cue file (but different
// extension).
// Rationale: this situation happens easily if the audio file or both files
// are renamed.
struct bstr cuefile =
bstr_strip_ext(bstr0(mp_basename(mpctx->demuxer->filename)));
DIR *d = opendir(bstrdup0(ctx, dirname));
if (!d)
goto out;
struct dirent *de;
while ((de = readdir(d))) {
char *dename0 = de->d_name;
struct bstr dename = bstr0(dename0);
if (bstr_case_startswith(dename, cuefile)) {
mp_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: No useful audio filename "
"in .cue file found, trying with '%s' instead!\n",
dename0);
if (try_open(mpctx, mp_path_join(ctx, dirname, dename))) {
res = true;
break;
}
}
}
closedir(d);
out:
talloc_free(ctx);
if (!res)
mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: Could not open audio file!\n");
return res;
}
// 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;
}
}
void build_cue_timeline(struct MPContext *mpctx)
{
void *ctx = talloc_new(NULL);
struct bstr data = mpctx->demuxer->file_contents;
data = skip_utf8_bom(data);
struct cue_track *tracks = NULL;
size_t track_count = 0;
struct bstr filename = {0};
// Global metadata, and copied into new tracks.
struct cue_track proto_track = {0};
struct cue_track *cur_track = &proto_track;
while (data.len) {
struct bstr param;
switch (read_cmd(&data, &param)) {
case CUE_ERROR:
mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: error parsing input file!\n");
goto out;
case CUE_TRACK: {
track_count++;
tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count);
cur_track = &tracks[track_count - 1];
*cur_track = proto_track;
break;
}
case CUE_TITLE:
cur_track->title = read_quoted(&param);
break;
case CUE_INDEX: {
int type = read_int_2(&param);
double time = read_time(&param);
if (type == 1) {
cur_track->start = time;
cur_track->filename = filename;
} else if (type == 0) {
cur_track->pregap_start = time;
}
break;
}
case CUE_FILE:
// NOTE: FILE comes before TRACK, so don't use cur_track->filename
filename = read_quoted(&param);
break;
}
}
if (track_count == 0) {
mp_msg(MSGT_CPLAYER, MSGL_ERR, "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.
struct bstr *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 (bstrcmp(files[file], track->filename) == 0) {
track->source = file;
break;
}
}
if (track->source == -1) {
file_count++;
files = talloc_realloc(ctx, files, struct bstr, file_count);
files[file_count - 1] = track->filename;
track->source = file_count - 1;
}
}
for (size_t i = 0; i < file_count; i++) {
if (!open_source(mpctx, files[i]))
goto out;
}
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline,
track_count + 1);
struct chapter *chapters = talloc_array_ptrtype(NULL, chapters,
track_count);
double starttime = 0;
for (int i = 0; i < track_count; i++) {
struct demuxer *source = mpctx->sources[1 + tracks[i].source];
double duration;
if (i + 1 < track_count && tracks[i].source == tracks[i + 1].source) {
duration = tracks[i + 1].start - tracks[i].start;
} else {
duration = source_get_length(source);
// Two cases: 1) last track of a single-file cue, or 2) any track of
// a multi-file cue. We need to do this for 1) only because the
// timeline needs to be terminated with the length of the last
// track.
duration -= tracks[i].start;
}
if (duration < 0) {
mp_msg(MSGT_CPLAYER, MSGL_WARN,
"CUE: Can't get duration of source file!\n");
// xxx: do something more reasonable
duration = 0.0;
}
timeline[i] = (struct timeline_part) {
.start = starttime,
.source_start = tracks[i].start,
.source = source,
};
chapters[i] = (struct chapter) {
.start = timeline[i].start,
// might want to include other metadata here
.name = bstrdup0(chapters, tracks[i].title),
};
starttime += duration;
}
// apparently we need this to give the last part a non-zero length
timeline[track_count] = (struct timeline_part) {
.start = starttime,
// perhaps unused by the timeline code
.source_start = 0,
.source = timeline[0].source,
};
mpctx->timeline = timeline;
// the last part is not included it in the count
mpctx->num_timeline_parts = track_count + 1 - 1;
mpctx->chapters = chapters;
mpctx->num_chapters = track_count;
out:
talloc_free(ctx);
}

View File

@@ -0,0 +1,591 @@
/*
* 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 <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <assert.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libavutil/common.h>
#include "osdep/io.h"
#include "talloc.h"
#include "player/mp_core.h"
#include "mpvcore/mp_msg.h"
#include "demux/demux.h"
#include "mpvcore/path.h"
#include "mpvcore/bstr.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/playlist.h"
#include "mpvcore/playlist_parser.h"
#include "stream/stream.h"
struct find_entry {
char *name;
int matchlen;
off_t size;
};
static int cmp_entry(const void *pa, const void *pb)
{
const struct find_entry *a = pa, *b = pb;
// check "similar" filenames first
int matchdiff = b->matchlen - a->matchlen;
if (matchdiff)
return FFSIGN(matchdiff);
// check small files first
off_t sizediff = a->size - b->size;
if (sizediff)
return FFSIGN(sizediff);
return 0;
}
static char **find_files(const char *original_file, const char *suffix)
{
void *tmpmem = talloc_new(NULL);
char *basename = mp_basename(original_file);
struct bstr directory = mp_dirname(original_file);
char **results = talloc_size(NULL, 0);
char *dir_zero = bstrdup0(tmpmem, directory);
DIR *dp = opendir(dir_zero);
if (!dp) {
talloc_free(tmpmem);
return results;
}
struct find_entry *entries = NULL;
struct dirent *ep;
int num_results = 0;
while ((ep = readdir(dp))) {
int suffix_offset = strlen(ep->d_name) - strlen(suffix);
// name must end with suffix
if (suffix_offset < 0 || strcmp(ep->d_name + suffix_offset, suffix))
continue;
// don't list the original name
if (!strcmp(ep->d_name, basename))
continue;
char *name = mp_path_join(results, directory, bstr0(ep->d_name));
char *s1 = ep->d_name;
char *s2 = basename;
int matchlen = 0;
while (*s1 && *s1++ == *s2++)
matchlen++;
// be a bit more fuzzy about matching the filename
matchlen = (matchlen + 3) / 5;
struct stat statbuf;
if (stat(name, &statbuf) != 0)
continue;
off_t size = statbuf.st_size;
entries = talloc_realloc(tmpmem, entries, struct find_entry,
num_results + 1);
entries[num_results] = (struct find_entry) { name, matchlen, size };
num_results++;
}
closedir(dp);
// NOTE: maybe should make it compare pointers instead
if (entries)
qsort(entries, num_results, sizeof(struct find_entry), cmp_entry);
results = talloc_realloc(NULL, results, char *, num_results);
for (int i = 0; i < num_results; i++) {
results[i] = entries[i].name;
}
talloc_free(tmpmem);
return results;
}
static int enable_cache(struct MPContext *mpctx, struct stream **stream,
struct demuxer **demuxer, struct demuxer_params *params)
{
struct MPOpts *opts = mpctx->opts;
if (opts->stream_cache_size <= 0)
return 0;
char *filename = talloc_strdup(NULL, (*demuxer)->filename);
free_demuxer(*demuxer);
free_stream(*stream);
*stream = stream_open(filename, opts);
if (!*stream) {
talloc_free(filename);
return -1;
}
stream_enable_cache_percent(stream,
opts->stream_cache_size,
opts->stream_cache_def_size,
opts->stream_cache_min_percent,
opts->stream_cache_seek_min_percent);
*demuxer = demux_open(*stream, "mkv", params, opts);
if (!*demuxer) {
talloc_free(filename);
free_stream(*stream);
return -1;
}
talloc_free(filename);
return 1;
}
static bool has_source_request(struct matroska_segment_uid *uids,
int num_sources,
struct matroska_segment_uid *new_uid)
{
for (int i = 0; i < num_sources; ++i) {
if (demux_matroska_uid_cmp(uids + i, new_uid))
return true;
}
return false;
}
// segment = get Nth segment of a multi-segment file
static bool check_file_seg(struct MPContext *mpctx, struct demuxer ***sources,
int *num_sources, struct matroska_segment_uid **uids,
char *filename, int segment)
{
bool was_valid = false;
struct demuxer_params params = {
.matroska_num_wanted_uids = *num_sources,
.matroska_wanted_uids = *uids,
.matroska_wanted_segment = segment,
.matroska_was_valid = &was_valid,
};
struct stream *s = stream_open(filename, mpctx->opts);
if (!s)
return false;
struct demuxer *d = demux_open(s, "mkv", &params, mpctx->opts);
if (!d) {
free_stream(s);
return was_valid;
}
if (d->type == DEMUXER_TYPE_MATROSKA) {
struct matroska_data *m = &d->matroska_data;
for (int i = 1; i < *num_sources; i++) {
struct matroska_segment_uid *uid = *uids + i;
if ((*sources)[i])
continue;
/* Accept the source if the segment uid matches and the edition
* either matches or isn't specified. */
if (!memcmp(uid->segment, m->uid.segment, 16) &&
(!uid->edition || uid->edition == m->uid.edition)) {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Match for source %d: %s\n",
i, d->filename);
for (int j = 0; j < m->num_ordered_chapters; j++) {
struct matroska_chapter *c = m->ordered_chapters + j;
if (!c->has_segment_uid)
continue;
if (has_source_request(*uids, *num_sources, &c->uid))
continue;
/* Set the requested segment. */
MP_TARRAY_GROW(NULL, *uids, *num_sources);
memcpy((*uids) + *num_sources, &c->uid, sizeof(c->uid));
/* Add a new source slot. */
MP_TARRAY_APPEND(NULL, *sources, *num_sources, NULL);
}
if (enable_cache(mpctx, &s, &d, &params) < 0)
continue;
(*sources)[i] = d;
return true;
}
}
}
free_demuxer(d);
free_stream(s);
return was_valid;
}
static void check_file(struct MPContext *mpctx, struct demuxer ***sources,
int *num_sources, struct matroska_segment_uid **uids,
char *filename, int first)
{
for (int segment = first; ; segment++) {
if (!check_file_seg(mpctx, sources, num_sources,
uids, filename, segment))
break;
}
}
static bool missing(struct demuxer **sources, int num_sources)
{
for (int i = 0; i < num_sources; i++) {
if (!sources[i])
return true;
}
return false;
}
static int find_ordered_chapter_sources(struct MPContext *mpctx,
struct demuxer ***sources,
int *num_sources,
struct matroska_segment_uid **uids)
{
struct MPOpts *opts = mpctx->opts;
void *tmp = talloc_new(NULL);
int num_filenames = 0;
char **filenames = NULL;
if (*num_sources > 1) {
char *main_filename = mpctx->demuxer->filename;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from "
"other sources.\n");
if (opts->ordered_chapters_files && opts->ordered_chapters_files[0]) {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Loading references from '%s'.\n",
opts->ordered_chapters_files);
struct playlist *pl =
playlist_parse_file(opts->ordered_chapters_files, opts);
talloc_steal(tmp, pl);
for (struct playlist_entry *e = pl->first; e; e = e->next)
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
} else if (mpctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
mp_msg(MSGT_CPLAYER, MSGL_WARN, "Playback source is not a "
"normal disk file. Will not search for related files.\n");
} else {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Will scan other files in the "
"same directory to find referenced sources.\n");
filenames = find_files(main_filename, ".mkv");
num_filenames = MP_TALLOC_ELEMS(filenames);
talloc_steal(tmp, filenames);
}
// Possibly get further segments appended to the first segment
check_file(mpctx, sources, num_sources, uids, main_filename, 1);
}
int old_source_count;
do {
old_source_count = *num_sources;
for (int i = 0; i < num_filenames; i++) {
if (!missing(*sources, *num_sources))
break;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", filenames[i]);
check_file(mpctx, sources, num_sources, uids, filenames[i], 0);
}
/* Loop while we have new sources to look for. */
} while (old_source_count != *num_sources);
if (missing(*sources, *num_sources)) {
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Failed to find ordered chapter part!\n"
"There will be parts MISSING from the video!\n");
int j = 1;
for (int i = 1; i < *num_sources; i++)
if ((*sources)[i]) {
struct matroska_segment_uid *source_uid = *uids + i;
struct matroska_segment_uid *target_uid = *uids + j;
(*sources)[j] = (*sources)[i];
memmove(target_uid, source_uid, sizeof(*source_uid));
j++;
}
*num_sources = j;
}
talloc_free(tmp);
return *num_sources;
}
static int64_t add_timeline_part(struct MPOpts *opts,
struct demuxer *source,
struct timeline_part **timeline,
int *part_count,
uint64_t start,
uint64_t *last_end_time,
uint64_t *starttime)
{
/* Only add a separate part if the time or file actually changes.
* Matroska files have chapter divisions that are redundant from
* timeline point of view because the same chapter structure is used
* both to specify the timeline and for normal chapter information.
* Removing a missing inserted external chapter can also cause this.
* We allow for a configurable fudge factor because of files which
* specify chapter end times that are one frame too early;
* we don't want to try seeking over a one frame gap. */
int64_t join_diff = start - *last_end_time;
if (*part_count == 0
|| FFABS(join_diff) > opts->chapter_merge_threshold * 1e6
|| source != (*timeline)[*part_count - 1].source) {
struct timeline_part new = {
.start = *starttime / 1e9,
.source_start = start / 1e9,
.source = source,
};
MP_TARRAY_APPEND(NULL, *timeline, *part_count, new);
} else if (*part_count > 0 && join_diff) {
/* Chapter was merged at an inexact boundary;
* adjust timestamps to match. */
mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with "
"offset %g ms.\n", *part_count, join_diff / 1e6);
*starttime += join_diff;
return join_diff;
}
return 0;
}
static void account_missing_time(uint64_t *total_time,
uint64_t new_time,
const char *message)
{
if (!new_time)
return;
*total_time += new_time;
mp_msg(MSGT_CPLAYER, MSGL_HINT,
"missing %"PRIu64" nanoseconds: %s\n",
new_time, message);
}
static void build_timeline_loop(struct MPOpts *opts,
struct demuxer **sources,
int num_sources,
int current_source,
uint64_t *starttime,
uint64_t *missing_time,
uint64_t *last_end_time,
struct timeline_part **timeline,
struct chapter *chapters,
int *part_count,
uint64_t skip,
uint64_t limit)
{
uint64_t local_starttime = 0;
struct demuxer *source = sources[current_source];
struct matroska_data *m = &source->matroska_data;
for (int i = 0; i < m->num_ordered_chapters; i++) {
struct matroska_chapter *c = m->ordered_chapters + i;
uint64_t chapter_length = c->end - c->start;
/* Fill in the uid with the current one if one isn't requested. */
if (!c->has_segment_uid)
memcpy(&c->uid, &m->uid, sizeof(c->uid));
/* "Seek" to the end of the chapter. */
local_starttime += chapter_length;
/* If we're before the start time for the chapter, skip to the next
* one. */
if (local_starttime <= skip)
continue;
/* Look for the source for this chapter. */
for (int j = 0; j < num_sources; j++) {
struct demuxer *linked_source = sources[j];
struct matroska_data *linked_m = &linked_source->matroska_data;
/* Skip if the segment or edition isn't acceptable. */
if (!demux_matroska_uid_cmp(&c->uid, &linked_m->uid))
continue;
/* TODO: Add option to support recursive chapters when loading
* recursive ordered chapter editions? If so, more code will be
* needed to add chapters for external non-ordered segment loading
* as well since that part is not recursive. */
if (!limit) {
chapters[i].start = *starttime / 1e9;
chapters[i].name = talloc_strdup(chapters, c->name);
}
/* If we're the source or it's a non-ordered edition reference,
* just add a timeline part from the source. */
if (current_source == j || !linked_m->num_ordered_chapters) {
double source_full_length_seconds = demuxer_get_time_length(linked_source);
/* Some accuracy lost, but not enough to care. (Over one
* million parts, a nanosecond off here could add up to a
* millisecond and trigger a false-positive error message, but
* if that's your biggest problem at that point,
* congratulations. */
uint64_t source_full_length = source_full_length_seconds * 1e9;
uint64_t source_length = source_full_length - c->start;
int64_t join_diff = 0;
/* If the chapter starts after the end of a source, there's
* nothing we can get from it. Instead, mark the entire chapter
* as missing and make the chapter length 0. */
if (source_full_length <= c->start) {
account_missing_time(missing_time, chapter_length,
"referenced segment ends before the requested start time");
chapter_length = 0;
goto found;
}
/* If the source length starting at the chapter start is
* shorter than the chapter it is supposed to fill, add the gap
* to missing_time. Also, modify the chapter length to be what
* we actually have to avoid playing off the end of the file
* and not switching to the next source. */
if (source_length < chapter_length) {
account_missing_time(missing_time, chapter_length - source_length,
"referenced segment ends before the requested end time");
chapter_length = source_length;
}
join_diff = add_timeline_part(opts, linked_source, timeline, part_count,
c->start, last_end_time, starttime);
/* If we merged two chapters into a single part due to them
* being off by a few frames, we need to change the limit to
* avoid chopping the end of the intended chapter (the adding
* frames case) or showing extra content (the removing frames
* case). Also update chapter_length to incorporate the extra
* time. */
if (limit) {
limit += join_diff;
chapter_length += join_diff;
}
/* Otherwise, we have an ordered edition as the source. Since this
* can jump around all over the place, we need to build up the
* timeline parts for each of its chapters, but not add them as
* chapters. */
} else {
build_timeline_loop(opts, sources, num_sources, j, starttime,
missing_time, last_end_time, timeline,
chapters, part_count, c->start, c->end);
/* The loop call has added time as needed (we can't add it here
* due to 'join_diff' in the add_timeline_part function. Since
* the time has already been added as needed, the chapter has
* an effective 0 length at this point. */
chapter_length = 0;
}
*last_end_time = c->end;
goto found;
}
/* We're missing a part of the chapter, so add it to the accounting. */
account_missing_time(missing_time, chapter_length,
"the source for a chapter could not be found");
/* We don't have the source, but don't leave a gap in the timeline for
* the source. */
chapter_length = 0;
found:;
*starttime += chapter_length;
/* If we're after the limit on this chapter, stop here. */
if (limit && local_starttime >= limit) {
/* Back up the global start time by the overflow. */
*starttime -= local_starttime - limit;
break;
}
}
/* If we stopped before the limit, add up the missing time. */
if (local_starttime < limit)
account_missing_time(missing_time, limit - local_starttime,
"nested ordered chapter segment is shorter than the requested end time");
}
void build_ordered_chapter_timeline(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
if (!opts->ordered_chapters) {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, but "
"you have disabled support for them. Ignoring.\n");
return;
}
mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, will build "
"edit timeline.\n");
struct demuxer *demuxer = mpctx->demuxer;
struct matroska_data *m = &demuxer->matroska_data;
// +1 because sources/uid_map[0] is original file even if all chapters
// actually use other sources and need separate entries
struct demuxer **sources = talloc_zero_array(NULL, struct demuxer *,
m->num_ordered_chapters+1);
sources[0] = mpctx->demuxer;
struct matroska_segment_uid *uids =
talloc_zero_array(NULL, struct matroska_segment_uid,
m->num_ordered_chapters + 1);
int num_sources = 1;
memcpy(uids[0].segment, m->uid.segment, 16);
uids[0].edition = 0;
for (int i = 0; i < m->num_ordered_chapters; i++) {
struct matroska_chapter *c = m->ordered_chapters + i;
/* If there isn't a segment uid, we are the source. If the segment uid
* is our segment uid and the edition matches. We can't accept the
* "don't care" edition value of 0 since the user may have requested a
* non-default edition. */
if (!c->has_segment_uid || demux_matroska_uid_cmp(&c->uid, &m->uid))
continue;
if (has_source_request(uids, num_sources, &c->uid))
continue;
memcpy(uids + num_sources, &c->uid, sizeof(c->uid));
sources[num_sources] = NULL;
num_sources++;
}
num_sources = find_ordered_chapter_sources(mpctx, &sources, &num_sources,
&uids);
talloc_free(uids);
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, 0);
struct chapter *chapters =
talloc_zero_array(NULL, struct chapter, m->num_ordered_chapters);
uint64_t starttime = 0;
uint64_t missing_time = 0;
uint64_t last_end_time = 0;
int part_count = 0;
build_timeline_loop(opts, sources, num_sources, 0, &starttime,
&missing_time, &last_end_time, &timeline,
chapters, &part_count, 0, 0);
if (!part_count) {
// None of the parts come from the file itself???
talloc_free(sources);
talloc_free(timeline);
talloc_free(chapters);
return;
}
struct timeline_part new = {
.start = starttime / 1e9,
};
MP_TARRAY_APPEND(NULL, timeline, part_count, new);
/* Ignore anything less than a millisecond when reporting missing time. If
* users really notice less than a millisecond missing, maybe this can be
* revisited. */
if (missing_time >= 1e6)
mp_msg(MSGT_CPLAYER, MSGL_ERR, "There are %.3f seconds missing "
"from the timeline!\n", missing_time / 1e9);
talloc_free(mpctx->sources);
mpctx->sources = sources;
mpctx->num_sources = num_sources;
mpctx->timeline = timeline;
mpctx->num_timeline_parts = part_count - 1;
mpctx->num_chapters = m->num_ordered_chapters;
mpctx->chapters = chapters;
}

View File

@@ -0,0 +1,274 @@
/*
* 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 <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <ctype.h>
#include <math.h>
#include "talloc.h"
#include "player/mp_core.h"
#include "mpvcore/mp_msg.h"
#include "demux/demux.h"
#include "mpvcore/path.h"
#include "mpvcore/bstr.h"
#include "mpvcore/mp_common.h"
#include "stream/stream.h"
struct tl_part {
char *filename; // what is stream_open()ed
double offset; // offset into the source file
double length; // length of the part (-1 if rest of the file)
};
struct tl_parts {
struct tl_part *parts;
int num_parts;
};
// Parse a time (absolute file time or duration). Currently equivalent to a
// number. Return false on failure.
static bool parse_time(bstr str, double *out_time)
{
bstr rest;
double time = bstrtod(str, &rest);
if (!str.len || rest.len || !isfinite(time))
return false;
*out_time = time;
return true;
}
/* 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;
} else if (bstr_equals0(name, "length")) {
if (!parse_time(val, &p.length))
goto error;
}
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_file(char *filename, struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct demuxer *d = NULL;
struct stream *s = stream_open(filename, opts);
if (s) {
stream_enable_cache_percent(&s,
opts->stream_cache_size,
opts->stream_cache_def_size,
opts->stream_cache_min_percent,
opts->stream_cache_seek_min_percent);
d = demux_open(s, NULL, NULL, opts);
}
if (!d) {
mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Could not open source file '%s'.\n",
filename);
free_stream(s);
}
return d;
}
static struct demuxer *open_source(struct MPContext *mpctx, char *filename)
{
for (int n = 0; n < mpctx->num_sources; n++) {
struct demuxer *d = mpctx->sources[n];
if (strcmp(d->stream->url, filename) == 0)
return d;
}
struct demuxer *d = open_file(filename, mpctx);
if (d)
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d);
return d;
}
// Append all chapters from src to the chapters array.
// Ignore chapters outside of the given time range.
static void copy_chapters(struct chapter **chapters, int *num_chapters,
struct demuxer *src, double start, double len,
double dest_offset)
{
int count = demuxer_chapter_count(src);
for (int n = 0; n < count; n++) {
double time = demuxer_chapter_time(src, n);
if (time >= start && time <= start + len) {
struct chapter ch = {
.start = dest_offset + time,
.name = talloc_steal(*chapters, demuxer_chapter_name(src, n)),
};
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 build_timeline(struct MPContext *mpctx, struct tl_parts *parts)
{
struct chapter *chapters = talloc_new(NULL);
int num_chapters = 0;
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline,
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(mpctx, part->filename);
if (!source)
goto error;
double len = source_get_length(source);
if (len <= 0) {
mp_msg(MSGT_CPLAYER, MSGL_WARN,
"EDL: source file '%s' has unknown duration.\n",
part->filename);
}
// Unkown length => use rest of the file. If duration is unknown, make
// something up.
if (part->length < 0)
part->length = (len < 0 ? 1 : len) - part->offset;
if (len > 0) {
double partlen = part->offset + part->length;
if (partlen > len) {
mp_msg(MSGT_CPLAYER, MSGL_WARN, "EDL: entry %d uses %f "
"seconds, but file has only %f seconds.\n",
n, partlen, len);
}
}
// Add a chapter between each file.
struct chapter ch = {
.start = starttime,
.name = talloc_strdup(chapters, part->filename),
};
MP_TARRAY_APPEND(NULL, chapters, num_chapters, ch);
// Also copy the source file's chapters for the relevant parts
copy_chapters(&chapters, &num_chapters, source, part->offset,
part->length, starttime);
timeline[n] = (struct timeline_part) {
.start = starttime,
.source_start = part->offset,
.source = source,
};
starttime += part->length;
}
timeline[parts->num_parts] = (struct timeline_part) {.start = starttime};
mpctx->timeline = timeline;
mpctx->num_timeline_parts = parts->num_parts;
mpctx->chapters = chapters;
mpctx->num_chapters = num_chapters;
return;
error:
talloc_free(timeline);
talloc_free(chapters);
}
// 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(parts, dirname, bstr0(filename));
}
}
void build_mpv_edl_timeline(struct MPContext *mpctx)
{
struct tl_parts *parts = parse_edl(mpctx->demuxer->file_contents);
if (!parts) {
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Error in EDL.\n");
return;
}
// Source is .edl and not edl:// => don't allow arbitrary paths
if (mpctx->demuxer->stream->uncached_type != STREAMTYPE_EDL)
fix_filenames(parts, mpctx->demuxer->filename);
build_timeline(mpctx, parts);
talloc_free(parts);
}

422
player/video.c Normal file
View File

@@ -0,0 +1,422 @@
/*
* 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 <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <math.h>
#include <assert.h>
#include "config.h"
#include "talloc.h"
#include "mpvcore/mp_msg.h"
#include "mpvcore/options.h"
#include "mpvcore/mp_common.h"
#include "mpvcore/encode.h"
#include "mpvcore/m_property.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "stream/stream.h"
#include "sub/osd.h"
#include "video/hwdec.h"
#include "video/filter/vf.h"
#include "video/decode/dec_video.h"
#include "video/decode/vd.h"
#include "video/out/vo.h"
#include "mp_core.h"
#include "command.h"
void update_fps(struct MPContext *mpctx)
{
#if HAVE_ENCODING
struct dec_video *d_video = mpctx->d_video;
if (mpctx->encode_lavc_ctx && d_video)
encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, d_video->fps);
#endif
}
static void set_allowed_vo_formats(struct vf_chain *c, struct vo *vo)
{
for (int fmt = IMGFMT_START; fmt < IMGFMT_END; fmt++) {
c->allowed_output_formats[fmt - IMGFMT_START] =
vo->driver->query_format(vo, fmt);
}
}
static void reconfig_video(struct MPContext *mpctx,
const struct mp_image_params *params,
bool probe_only)
{
struct dec_video *d_video = mpctx->d_video;
d_video->decoder_output = *params;
set_allowed_vo_formats(d_video->vfilter, mpctx->video_out);
if (video_reconfig_filters(d_video, params) < 0) {
// Most video filters don't work with hardware decoding, so this
// might be the reason filter reconfig failed.
if (!probe_only &&
video_vd_control(d_video, VDCTRL_FORCE_HWDEC_FALLBACK, NULL) == CONTROL_OK)
{
// Fallback active; decoder will return software format next
// time. Don't abort video decoding.
d_video->vfilter->initialized = 0;
}
return;
}
if (d_video->vfilter->initialized < 1)
return;
struct mp_image_params p = d_video->vfilter->output_params;
const struct vo_driver *info = mpctx->video_out->driver;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "VO: [%s] %dx%d => %dx%d %s\n",
info->name, p.w, p.h, p.d_w, p.d_h, vo_format_name(p.imgfmt));
mp_msg(MSGT_CPLAYER, MSGL_V, "VO: Description: %s\n", info->description);
int r = vo_reconfig(mpctx->video_out, &p, 0);
if (r < 0)
d_video->vfilter->initialized = -1;
}
static void recreate_video_filters(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct dec_video *d_video = mpctx->d_video;
assert(d_video);
vf_destroy(d_video->vfilter);
d_video->vfilter = vf_new(opts);
d_video->vfilter->hwdec = &d_video->hwdec_info;
vf_append_filter_list(d_video->vfilter, opts->vf_settings);
// for vf_sub
vf_control_any(d_video->vfilter, VFCTRL_SET_OSD_OBJ, mpctx->osd);
mpctx->osd->render_subs_in_filter
= vf_control_any(d_video->vfilter, VFCTRL_INIT_OSD, NULL) == CONTROL_OK;
set_allowed_vo_formats(d_video->vfilter, mpctx->video_out);
}
int reinit_video_filters(struct MPContext *mpctx)
{
struct dec_video *d_video = mpctx->d_video;
if (!d_video || !d_video->decoder_output.imgfmt)
return -2;
recreate_video_filters(mpctx);
reconfig_video(mpctx, &d_video->decoder_output, true);
return d_video->vfilter && d_video->vfilter->initialized > 0 ? 0 : -1;
}
int reinit_video_chain(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
assert(!(mpctx->initialized_flags & INITIALIZED_VCODEC));
assert(!mpctx->d_video);
init_demux_stream(mpctx, STREAM_VIDEO);
struct sh_stream *sh = mpctx->sh[STREAM_VIDEO];
if (!sh)
goto no_video;
MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n",
sh->format,
sh->video->disp_w, sh->video->disp_h,
sh->video->fps);
double ar = -1.0;
//================== Init VIDEO (codec & libvo) ==========================
if (!opts->fixed_vo || !(mpctx->initialized_flags & INITIALIZED_VO)) {
mpctx->video_out = init_best_video_out(mpctx->global, mpctx->input,
mpctx->encode_lavc_ctx);
if (!mpctx->video_out) {
MP_FATAL(mpctx, "Error opening/initializing "
"the selected video_out (-vo) device.\n");
goto err_out;
}
mpctx->mouse_cursor_visible = true;
mpctx->initialized_flags |= INITIALIZED_VO;
}
update_window_title(mpctx, true);
struct dec_video *d_video = talloc_zero(NULL, struct dec_video);
mpctx->d_video = d_video;
d_video->opts = mpctx->opts;
d_video->header = sh;
d_video->fps = sh->video->fps;
d_video->vo = mpctx->video_out;
mpctx->initialized_flags |= INITIALIZED_VCODEC;
vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info);
if (stream_control(sh->demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
!= STREAM_UNSUPPORTED)
d_video->stream_aspect = ar;
recreate_video_filters(mpctx);
if (!video_init_best_codec(d_video, opts->video_decoders))
goto err_out;
bool saver_state = opts->pause || !opts->stop_screensaver;
vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER
: VOCTRL_KILL_SCREENSAVER, NULL);
vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE
: VOCTRL_RESUME, NULL);
mpctx->restart_playback = true;
mpctx->sync_audio_to_video = !sh->attached_picture;
mpctx->delay = 0;
mpctx->video_next_pts = MP_NOPTS_VALUE;
mpctx->playing_last_frame = false;
mpctx->last_frame_duration = 0;
mpctx->vo_pts_history_seek_ts++;
vo_seek_reset(mpctx->video_out);
reset_subtitles(mpctx);
if (opts->force_fps) {
d_video->fps = opts->force_fps;
MP_INFO(mpctx, "FPS forced to be %5.3f.\n", d_video->fps);
}
if (!sh->video->fps && !opts->force_fps && !opts->correct_pts) {
MP_ERR(mpctx, "FPS not specified in the "
"header or invalid, use the -fps option.\n");
}
update_fps(mpctx);
return 1;
err_out:
no_video:
uninit_player(mpctx, INITIALIZED_VCODEC | (opts->force_vo ? 0 : INITIALIZED_VO));
cleanup_demux_stream(mpctx, STREAM_VIDEO);
mpctx->current_track[STREAM_VIDEO] = NULL;
handle_force_window(mpctx, true);
MP_INFO(mpctx, "Video: no video\n");
return 0;
}
// Try to refresh the video by doing a precise seek to the currently displayed
// frame. This can go wrong in all sorts of ways, so use sparingly.
void mp_force_video_refresh(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
// If not paused, the next frame should come soon enough.
if (opts->pause && mpctx->last_vo_pts != MP_NOPTS_VALUE)
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, 1);
}
static bool filter_output_queued_frame(struct MPContext *mpctx)
{
struct dec_video *d_video = mpctx->d_video;
struct vo *video_out = mpctx->video_out;
struct mp_image *img = vf_output_queued_frame(d_video->vfilter);
if (img)
vo_queue_image(video_out, img);
talloc_free(img);
return !!img;
}
static bool load_next_vo_frame(struct MPContext *mpctx, bool eof)
{
if (vo_get_buffered_frame(mpctx->video_out, eof) >= 0)
return true;
if (filter_output_queued_frame(mpctx))
return true;
return false;
}
// Called after video reinit. This can be generally used to try to insert more
// filters using the filter chain edit functionality in command.c.
static void init_filter_params(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
// Note that the filter chain is already initialized. This code might
// recreate the chain a second time, which is not very elegant, but allows
// us to test whether enabling deinterlacing works with the current video
// format and other filters.
if (opts->deinterlace >= 0)
mp_property_do("deinterlace", M_PROPERTY_SET, &opts->deinterlace, mpctx);
}
static void filter_video(struct MPContext *mpctx, struct mp_image *frame,
bool reconfig_ok)
{
struct dec_video *d_video = mpctx->d_video;
struct mp_image_params params;
mp_image_params_from_image(&params, frame);
if (!mp_image_params_equals(&d_video->decoder_output, &params) ||
d_video->vfilter->initialized < 1)
{
// In case we want to wait until filter chain is drained
if (!reconfig_ok) {
talloc_free(d_video->waiting_decoded_mpi);
d_video->waiting_decoded_mpi = frame;
return;
}
reconfig_video(mpctx, &params, false);
if (d_video->vfilter->initialized > 0)
init_filter_params(mpctx);
}
if (d_video->vfilter->initialized < 1) {
talloc_free(frame);
return;
}
mp_image_set_params(frame, &d_video->vf_input); // force csp/aspect overrides
vf_filter_frame(d_video->vfilter, frame);
filter_output_queued_frame(mpctx);
}
// Reconfigure the video chain and the VO on a format change. This is separate,
// because we wait with the reconfig until the currently buffered video has
// finished displaying. Otherwise, we'd resize the window and then wait for the
// video finishing, which would result in a black window for that frame.
// Does nothing if there was no pending change.
void video_execute_format_change(struct MPContext *mpctx)
{
struct dec_video *d_video = mpctx->d_video;
struct mp_image *decoded_frame = d_video->waiting_decoded_mpi;
d_video->waiting_decoded_mpi = NULL;
if (decoded_frame)
filter_video(mpctx, decoded_frame, true);
}
static int check_framedrop(struct MPContext *mpctx, double frame_time)
{
struct MPOpts *opts = mpctx->opts;
// check for frame-drop:
if (mpctx->d_audio && !mpctx->ao->untimed &&
!demux_stream_eof(mpctx->sh[STREAM_AUDIO]))
{
float delay = opts->playback_speed * ao_get_delay(mpctx->ao);
float d = delay - mpctx->delay;
float fps = mpctx->d_video->fps;
if (frame_time < 0)
frame_time = fps > 0 ? 1.0 / fps : 0;
// we should avoid dropping too many frames in sequence unless we
// are too late. and we allow 100ms A-V delay here:
if (d < -mpctx->dropped_frames * frame_time - 0.100 && !mpctx->paused
&& !mpctx->restart_playback) {
mpctx->drop_frame_cnt++;
mpctx->dropped_frames++;
return mpctx->opts->frame_dropping;
} else
mpctx->dropped_frames = 0;
}
return 0;
}
static double update_video_attached_pic(struct MPContext *mpctx)
{
struct dec_video *d_video = mpctx->d_video;
// Try to decode the picture multiple times, until it is displayed.
if (mpctx->video_out->hasframe)
return -1;
struct mp_image *decoded_frame =
video_decode(d_video, d_video->header->attached_picture, 0);
if (decoded_frame)
filter_video(mpctx, decoded_frame, true);
load_next_vo_frame(mpctx, true);
mpctx->video_next_pts = MP_NOPTS_VALUE;
return 0;
}
double update_video(struct MPContext *mpctx, double endpts)
{
struct dec_video *d_video = mpctx->d_video;
struct vo *video_out = mpctx->video_out;
if (d_video->header->attached_picture)
return update_video_attached_pic(mpctx);
if (load_next_vo_frame(mpctx, false)) {
// Use currently queued VO frame
} else if (d_video->waiting_decoded_mpi) {
// Draining on reconfig
if (!load_next_vo_frame(mpctx, true))
return -1;
} else {
// Decode a new frame
struct demux_packet *pkt = demux_read_packet(d_video->header);
if (pkt && pkt->pts != MP_NOPTS_VALUE)
pkt->pts += mpctx->video_offset;
if ((pkt && pkt->pts >= mpctx->hrseek_pts - .005) ||
d_video->has_broken_packet_pts)
{
mpctx->hrseek_framedrop = false;
}
int framedrop_type = mpctx->hrseek_active && mpctx->hrseek_framedrop ?
1 : check_framedrop(mpctx, -1);
struct mp_image *decoded_frame =
video_decode(d_video, pkt, framedrop_type);
talloc_free(pkt);
if (decoded_frame) {
filter_video(mpctx, decoded_frame, false);
} else if (!pkt) {
if (!load_next_vo_frame(mpctx, true))
return -1;
}
}
// Whether the VO has an image queued.
// If it does, it will be used to time and display the next frame.
if (!video_out->frame_loaded)
return 0;
double pts = video_out->next_pts;
if (endpts == MP_NOPTS_VALUE || pts < endpts)
add_frame_pts(mpctx, pts);
if (mpctx->hrseek_active && pts < mpctx->hrseek_pts - .005) {
vo_skip_frame(video_out);
return 0;
}
mpctx->hrseek_active = false;
double last_pts = mpctx->video_next_pts;
if (last_pts == MP_NOPTS_VALUE)
last_pts = pts;
double frame_time = pts - last_pts;
if (frame_time < 0 || frame_time >= 60) {
// Assume a PTS difference >= 60 seconds is a discontinuity.
MP_WARN(mpctx, "Jump in video pts: %f -> %f\n", last_pts, pts);
frame_time = 0;
}
mpctx->video_next_pts = pts;
if (mpctx->d_audio)
mpctx->delay -= frame_time;
return frame_time;
}