diff options
Diffstat (limited to 'core')
40 files changed, 3596 insertions, 1764 deletions
diff --git a/core/av_common.c b/core/av_common.c index 5e6c8a4352..a4dc525aa9 100644 --- a/core/av_common.c +++ b/core/av_common.c @@ -18,8 +18,10 @@ #include <assert.h> #include <libavutil/common.h> +#include <libavcodec/avcodec.h> #include "core/mp_talloc.h" +#include "demux/demux_packet.h" #include "av_common.h" #include "codecs.h" @@ -58,6 +60,24 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st) avctx->bits_per_coded_sample = st->bits_per_coded_sample; } +// Set dst from mpkt. Note that dst is not refcountable. +// mpkt can be NULL to generate empty packets (used to flush delayed data). +// Does not set pts or duration fields. +void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt) +{ + av_init_packet(dst); + dst->data = mpkt ? mpkt->buffer : NULL; + dst->size = mpkt ? mpkt->len : 0; + /* Some codecs (ZeroCodec, some cases of PNG) may want keyframe info + * from demuxer. */ + if (mpkt && mpkt->keyframe) + dst->flags |= AV_PKT_FLAG_KEY; + if (mpkt && mpkt->avpacket) { + dst->side_data = mpkt->avpacket->side_data; + dst->side_data_elems = mpkt->avpacket->side_data_elems; + } +} + void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type) { AVCodec *cur = NULL; diff --git a/core/av_common.h b/core/av_common.h index 25593ed3d0..2fa8f127b0 100644 --- a/core/av_common.h +++ b/core/av_common.h @@ -22,8 +22,10 @@ #include <libavcodec/avcodec.h> struct mp_decoder_list; +struct demux_packet; void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st); +void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt); void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type); int mp_codec_to_av_codec_id(const char *codec); const char *mp_codec_from_av_codec_id(int codec_id); diff --git a/core/av_log.c b/core/av_log.c index 37c308be7a..b6cae6f8f8 100644 --- a/core/av_log.c +++ b/core/av_log.c @@ -97,10 +97,15 @@ static int extract_msg_type_from_ctx(void *ptr) return MSGT_FIXME; } +#if LIBAVCODEC_VERSION_MICRO >= 100 +#define LIB_PREFIX "ffmpeg" +#else +#define LIB_PREFIX "libav" +#endif + static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt, va_list vl) { - static bool print_prefix = 1; AVClass *avc = ptr ? *(AVClass **)ptr : NULL; int mp_level = av_log_level_to_mp_level(level); int type = extract_msg_type_from_ctx(ptr); @@ -108,9 +113,8 @@ static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt, if (!mp_msg_test(type, mp_level)) return; - if (print_prefix && avc) - mp_msg(type, mp_level, "[%s @ %p]", avc->item_name(ptr), avc); - print_prefix = fmt[strlen(fmt) - 1] == '\n'; + mp_msg(type, mp_level, "[%s/%s] ", LIB_PREFIX, + avc ? avc->item_name(ptr) : "?"); mp_msg_va(type, mp_level, fmt, vl); } diff --git a/core/av_opts.c b/core/av_opts.c index bc2e392c5f..777a1eec5a 100644 --- a/core/av_opts.c +++ b/core/av_opts.c @@ -28,6 +28,10 @@ int parse_avopts(void *v, char *str){ char *start; + + if (!str) + return 0; + start= str= strdup(str); while(str && *str){ diff --git a/core/charset_conv.c b/core/charset_conv.c new file mode 100644 index 0000000000..680c8f83f9 --- /dev/null +++ b/core/charset_conv.c @@ -0,0 +1,266 @@ +/* + * This file is part of mpv. + * + * Based on code taken from libass (ISC license), which was originally part + * of MPlayer (GPL). + * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> + * + * 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 <stdlib.h> +#include <errno.h> +#include <assert.h> + +#include "config.h" + +#include "core/mp_msg.h" + +#ifdef CONFIG_ENCA +#include <enca.h> +#endif + +#ifdef CONFIG_LIBGUESS +#include <libguess.h> +#endif + +#ifdef CONFIG_ICONV +#include <iconv.h> +#endif + +#include "charset_conv.h" + +// Split the string on ':' into components. +// out_arr is at least max entries long. +// Return number of out_arr entries filled. +static int split_colon(const char *user_cp, int max, bstr *out_arr) +{ + if (!user_cp || max < 1) + return 0; + + int count = 0; + while (1) { + const char *next = strchr(user_cp, ':'); + if (next && max - count > 1) { + out_arr[count++] = (bstr){(char *)user_cp, next - user_cp}; + user_cp = next + 1; + } else { + out_arr[count++] = (bstr){(char *)user_cp, strlen(user_cp)}; + break; + } + } + return count; +} + +// Returns true if user_cp implies that calling mp_charset_guess() on the +// input data is required to determine the real codepage. This is the case +// if user_cp is not a real iconv codepage, but a magic value that requests +// for example ENCA charset auto-detection. +bool mp_charset_requires_guess(const char *user_cp) +{ + bstr res[2] = {{0}}; + split_colon(user_cp, 2, res); + return bstrcasecmp0(res[0], "enca") == 0 || + bstrcasecmp0(res[0], "guess") == 0; +} + +#ifdef CONFIG_ENCA +static const char *enca_guess(bstr buf, const char *language) +{ + if (!language || !language[0]) + language = "__"; // neutral language + + const char *detected_cp = NULL; + + EncaAnalyser analyser = enca_analyser_alloc(language); + if (analyser) { + enca_set_termination_strictness(analyser, 0); + EncaEncoding enc = enca_analyse_const(analyser, buf.start, buf.len); + const char *tmp = enca_charset_name(enc.charset, ENCA_NAME_STYLE_ICONV); + if (tmp && enc.charset != ENCA_CS_UNKNOWN) + detected_cp = tmp; + enca_analyser_free(analyser); + } else { + mp_msg(MSGT_SUBREADER, MSGL_ERR, "ENCA doesn't know language '%s'\n", + language); + size_t langcnt; + const char **languages = enca_get_languages(&langcnt); + mp_msg(MSGT_SUBREADER, MSGL_ERR, "ENCA supported languages:"); + for (int i = 0; i < langcnt; i++) + mp_msg(MSGT_SUBREADER, MSGL_ERR, " %s", languages[i]); + mp_msg(MSGT_SUBREADER, MSGL_ERR, "\n"); + free(languages); + } + + return detected_cp; +} +#endif + +#ifdef CONFIG_LIBGUESS +static const char *libguess_guess(bstr buf, const char *language) +{ + if (libguess_validate_utf8(buf.start, buf.len)) + return "UTF-8"; + + if (!language || !language[0] || strcmp(language, "help") == 0) { + mp_msg(MSGT_SUBREADER, MSGL_ERR, "libguess needs a language: " + "japanese taiwanese chinese korean russian arabic turkish " + "greek hebrew polish baltic\n"); + return NULL; + } + + return libguess_determine_encoding(buf.start, buf.len, language); +} +#endif + +// Runs charset auto-detection on the input buffer, and returns the result. +// If auto-detection fails, NULL is returned. +// If user_cp doesn't refer to any known auto-detection (for example because +// it's a real iconv codepage), user_cp is returned without even looking at +// the buf data. +const char *mp_charset_guess(bstr buf, const char *user_cp) +{ + if (!mp_charset_requires_guess(user_cp)) + return user_cp; + + bstr params[3] = {{0}}; + split_colon(user_cp, 3, params); + + bstr type = params[0]; + char lang[100]; + snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1])); + const char *fallback = params[2].start; // last item, already 0-terminated + + const char *res = NULL; + +#ifdef CONFIG_ENCA + if (bstrcasecmp0(type, "enca") == 0) + res = enca_guess(buf, lang); +#endif +#ifdef CONFIG_LIBGUESS + if (bstrcasecmp0(type, "guess") == 0) + res = libguess_guess(buf, lang); +#endif + + if (res) { + mp_msg(MSGT_SUBREADER, MSGL_DBG2, "%.*s detected charset: '%s'\n", + BSTR_P(type), res); + } else { + res = fallback; + mp_msg(MSGT_SUBREADER, MSGL_DBG2, + "Detection with %.*s failed: fallback to %s\n", + BSTR_P(type), res && res[0] ? res : "no conversion"); + } + + return res; +} + +// Convert the data in buf to UTF-8. The charset argument can be an iconv +// codepage, a value returned by mp_charset_conv_guess(), or a special value +// that triggers autodetection of the charset (e.g. using ENCA). +// The auto-detection is the only difference to mp_iconv_to_utf8(). +// buf: same as mp_iconv_to_utf8() +// user_cp: iconv codepage, special value, NULL +// flags: same as mp_iconv_to_utf8() +// returns: same as mp_iconv_to_utf8() +bstr mp_charset_guess_and_conv_to_utf8(bstr buf, const char *user_cp, int flags) +{ + return mp_iconv_to_utf8(buf, mp_charset_guess(buf, user_cp), flags); +} + +// Use iconv to convert buf to UTF-8. +// Returns buf.start==NULL on error. Returns buf if cp is NULL, or if there is +// obviously no conversion required (e.g. if cp is "UTF-8"). +// Returns a newly allocated buffer if conversion is done and succeeds. The +// buffer will be terminated with 0 for convenience (the terminating 0 is not +// included in the returned length). +// Free the returned buffer with talloc_free(). +// buf: input data +// cp: iconv codepage (or NULL) +// flags: combination of MP_ICONV_* flags +// returns: buf (no conversion), .start==NULL (error), or allocated buffer +bstr mp_iconv_to_utf8(bstr buf, const char *cp, int flags) +{ +#ifdef CONFIG_ICONV + const char *tocp = "UTF-8"; + + if (!cp || !cp[0] || strcasecmp(cp, tocp) == 0) + return buf; + + if (strcasecmp(cp, "ASCII") == 0) + return buf; + + iconv_t icdsc; + if ((icdsc = iconv_open(tocp, cp)) == (iconv_t) (-1)) { + if (flags & MP_ICONV_VERBOSE) + mp_msg(MSGT_SUBREADER, MSGL_ERR, + "Error opening iconv with codepage '%s'\n", cp); + goto failure; + } + + size_t size = buf.len; + size_t osize = size; + size_t ileft = size; + size_t oleft = size - 1; + + char *outbuf = talloc_size(NULL, osize); + char *ip = buf.start; + char *op = outbuf; + + while (1) { + int clear = 0; + size_t rc; + if (ileft) + rc = iconv(icdsc, &ip, &ileft, &op, &oleft); + else { + clear = 1; // clear the conversion state and leave + rc = iconv(icdsc, NULL, NULL, &op, &oleft); + } + if (rc == (size_t) (-1)) { + if (errno == E2BIG) { + size_t offset = op - outbuf; + outbuf = talloc_realloc_size(NULL, outbuf, osize + size); + op = outbuf + offset; + osize += size; + oleft += size; + } else { + if (errno == EINVAL && (flags & MP_ICONV_ALLOW_CUTOFF)) { + // This is intended for cases where the input buffer is cut + // at a random byte position. If this happens in the middle + // of the buffer, it should still be an error. We say it's + // fine if the error is within 10 bytes of the end. + if (ileft <= 10) + break; + } + if (flags & MP_ICONV_VERBOSE) { + mp_msg(MSGT_SUBREADER, MSGL_ERR, + "Error recoding text with codepage '%s'\n", cp); + } + talloc_free(outbuf); + iconv_close(icdsc); + goto failure; + } + } else if (clear) + break; + } + + iconv_close(icdsc); + + outbuf[osize - oleft - 1] = 0; + return (bstr){outbuf, osize - oleft - 1}; +#endif + +failure: + return (bstr){0}; +} diff --git a/core/charset_conv.h b/core/charset_conv.h new file mode 100644 index 0000000000..00a2658da3 --- /dev/null +++ b/core/charset_conv.h @@ -0,0 +1,17 @@ +#ifndef MP_CHARSET_CONV_H +#define MP_CHARSET_CONV_H + +#include <stdbool.h> +#include "core/bstr.h" + +enum { + MP_ICONV_VERBOSE = 1, // print errors instead of failing silently + MP_ICONV_ALLOW_CUTOFF = 2, // allow partial input data +}; + +bool mp_charset_requires_guess(const char *user_cp); +const char *mp_charset_guess(bstr buf, const char *user_cp); +bstr mp_charset_guess_and_conv_to_utf8(bstr buf, const char *user_cp, int flags); +bstr mp_iconv_to_utf8(bstr buf, const char *cp, int flags); + +#endif diff --git a/core/command.c b/core/command.c index 05608c7df4..80f6f5d9d2 100644 --- a/core/command.c +++ b/core/command.c @@ -22,6 +22,10 @@ #include <string.h> #include <stdbool.h> #include <assert.h> +#include <time.h> + +#include <libavutil/avstring.h> +#include <libavutil/common.h> #include "config.h" #include "talloc.h" @@ -30,7 +34,7 @@ #include "stream/stream.h" #include "demux/demux.h" #include "demux/stheader.h" -#include "mplayer.h" +#include "resolve.h" #include "playlist.h" #include "playlist_parser.h" #include "sub/sub.h" @@ -50,9 +54,7 @@ #include "audio/filter/af.h" #include "video/decode/dec_video.h" #include "audio/decode/dec_audio.h" -#include "sub/spudec.h" #include "core/path.h" -#include "sub/ass_mp.h" #include "stream/tv.h" #include "stream/stream_radio.h" #include "stream/pvr.h" @@ -66,8 +68,9 @@ #include "screenshot.h" #include "core/mp_core.h" -#include "mp_fifo.h" -#include "libavutil/avstring.h" + +static void change_video_filters(MPContext *mpctx, const char *cmd, + const char *arg); static char *format_bitrate(int rate) { @@ -79,37 +82,17 @@ static char *format_delay(double time) return talloc_asprintf(NULL, "%d ms", ROUND(time * 1000)); } -static void rescale_input_coordinates(struct MPContext *mpctx, int ix, int iy, - double *dx, double *dy) +// Get current mouse position in OSD coordinate space. +void mp_get_osd_mouse_pos(struct MPContext *mpctx, float *x, float *y) { - struct MPOpts *opts = &mpctx->opts; - struct vo *vo = mpctx->video_out; - //remove the borders, if any, and rescale to the range [0,1],[0,1] - if (opts->vo.fs) { //we are in full-screen mode - if (opts->vo.screenwidth > vo->dwidth) - // there are borders along the x axis - ix -= (opts->vo.screenwidth - vo->dwidth) / 2; - if (opts->vo.screenheight > vo->dheight) - // there are borders along the y axis (usual way) - iy -= (opts->vo.screenheight - vo->dheight) / 2; - - if (ix < 0 || ix > vo->dwidth) { - *dx = *dy = -1.0; - return; - } //we are on one of the borders - if (iy < 0 || iy > vo->dheight) { - *dx = *dy = -1.0; - return; - } //we are on one of the borders - } - - *dx = (double) ix / (double) vo->dwidth; - *dy = (double) iy / (double) vo->dheight; - - mp_msg(MSGT_CPLAYER, MSGL_V, - "\r\nrescaled coordinates: %.3f, %.3f, screen (%d x %d), vodisplay: (%d, %d), fullscreen: %d\r\n", - *dx, *dy, opts->vo.screenwidth, opts->vo.screenheight, vo->dwidth, - vo->dheight, opts->vo.fs); + int wx, wy; + mp_input_get_mouse_pos(mpctx->input, &wx, &wy); + float p[2] = {wx, wy}; + // Raw window coordinates (VO mouse events) to OSD resolution. + if (mpctx->video_out) + vo_control(mpctx->video_out, VOCTRL_WINDOW_TO_OSD_COORDS, p); + *x = p[0]; + *y = p[1]; } // Property-option bridge. @@ -181,11 +164,14 @@ static int mp_property_media_title(m_option_t *prop, int action, void *arg, char *name = NULL; if (mpctx->resolve_result) name = mpctx->resolve_result->title; - if (name && name[0]) { + if (name && name[0]) return m_property_strdup_ro(prop, action, arg, name); - } else { - return mp_property_filename(prop, action, arg, mpctx); + if (mpctx->master_demuxer) { + name = demux_info_get(mpctx->master_demuxer, "title"); + if (name && name[0]) + return m_property_strdup_ro(prop, action, arg, name); } + return mp_property_filename(prop, action, arg, mpctx); } static int mp_property_stream_path(m_option_t *prop, int action, void *arg, @@ -197,6 +183,20 @@ static int mp_property_stream_path(m_option_t *prop, int action, void *arg, return m_property_strdup_ro(prop, action, arg, stream->url); } +static int mp_property_stream_capture(m_option_t *prop, int action, + void *arg, MPContext *mpctx) +{ + if (!mpctx->stream) + return M_PROPERTY_UNAVAILABLE; + + if (action == M_PROPERTY_SET) { + char *filename = *(char **)arg; + stream_set_capture_file(mpctx->stream, filename); + // fall through to mp_property_generic_option + } + return mp_property_generic_option(prop, action, arg, mpctx); +} + /// Demuxer name (RO) static int mp_property_demuxer(m_option_t *prop, int action, void *arg, MPContext *mpctx) @@ -300,11 +300,14 @@ static int mp_property_percent_pos(m_option_t *prop, int action, switch (action) { case M_PROPERTY_SET: ; - int pos = *(int *)arg; + double pos = *(double *)arg; queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, 0); return M_PROPERTY_OK; case M_PROPERTY_GET: - *(int *)arg = get_percent_pos(mpctx); + *(double *)arg = get_current_pos_ratio(mpctx, false) * 100.0; + return M_PROPERTY_OK; + case M_PROPERTY_PRINT: + *(char **)arg = talloc_asprintf(NULL, "%d", get_percent_pos(mpctx)); return M_PROPERTY_OK; } return M_PROPERTY_NOT_IMPLEMENTED; @@ -328,6 +331,19 @@ static int mp_property_time_pos(m_option_t *prop, int action, return M_PROPERTY_NOT_IMPLEMENTED; } +static int mp_property_remaining(m_option_t *prop, int action, + void *arg, MPContext *mpctx) +{ + double len = get_time_length(mpctx); + double pos = get_current_time(mpctx); + double start = get_start_time(mpctx); + + if (!(int)len) + return M_PROPERTY_UNAVAILABLE; + + return m_property_double_ro(prop, action, arg, len - (pos - start)); +} + /// Current chapter (RW) static int mp_property_chapter(m_option_t *prop, int action, void *arg, MPContext *mpctx) @@ -350,14 +366,43 @@ static int mp_property_chapter(m_option_t *prop, int action, void *arg, case M_PROPERTY_SET: ; int step_all = *(int *)arg - chapter; chapter += step_all; - double next_pts = 0; - queue_seek(mpctx, MPSEEK_NONE, 0, 0); - chapter = seek_chapter(mpctx, chapter, &next_pts); - if (chapter >= 0) { - if (next_pts > -1.0) - queue_seek(mpctx, MPSEEK_ABSOLUTE, next_pts, 0); - } else if (step_all > 0) + if (chapter >= get_chapter_count(mpctx) && step_all > 0) { mpctx->stop_play = PT_NEXT_ENTRY; + } else { + mp_seek_chapter(mpctx, chapter); + } + return M_PROPERTY_OK; + } + return M_PROPERTY_NOT_IMPLEMENTED; +} + +static int mp_property_list_chapters(m_option_t *prop, int action, void *arg, + MPContext *mpctx) +{ + if (action == M_PROPERTY_GET) { + int count = get_chapter_count(mpctx); + int cur = mpctx->num_sources ? get_current_chapter(mpctx) : -1; + char *res = NULL; + int n; + + if (count < 1) { + res = talloc_asprintf_append(res, "No chapters."); + } + + for (n = 0; n < count; n++) { + char *name = chapter_display_name(mpctx, n); + double t = chapter_start_time(mpctx, n); + char* time = mp_format_time(t, false); + res = talloc_asprintf_append(res, "%s", time); + talloc_free(time); + char *m1 = "> ", *m2 = " <"; + if (n != cur) + m1 = m2 = ""; + res = talloc_asprintf_append(res, " %s%s%s\n", m1, name, m2); + talloc_free(name); + } + + *(char **)arg = res; return M_PROPERTY_OK; } return M_PROPERTY_NOT_IMPLEMENTED; @@ -402,6 +447,65 @@ static int mp_property_edition(m_option_t *prop, int action, void *arg, return M_PROPERTY_NOT_IMPLEMENTED; } +static struct mp_resolve_src *find_source(struct mp_resolve_result *res, + char *url) +{ + if (res->num_srcs == 0) + return NULL; + + int src = 0; + for (int n = 0; n < res->num_srcs; n++) { + if (strcmp(res->srcs[n]->url, res->url) == 0) { + src = n; + break; + } + } + return res->srcs[src]; +} + +static int mp_property_quvi_format(m_option_t *prop, int action, void *arg, + MPContext *mpctx) +{ + struct mp_resolve_result *res = mpctx->resolve_result; + if (!res || !res->num_srcs) + return M_PROPERTY_UNAVAILABLE; + + struct mp_resolve_src *cur = find_source(res, res->url); + if (!cur) + return M_PROPERTY_UNAVAILABLE; + + switch (action) { + case M_PROPERTY_GET: + *(char **)arg = talloc_strdup(NULL, cur->encid); + return M_PROPERTY_OK; + case M_PROPERTY_SET: { + mpctx->stop_play = PT_RESTART; + break; + } + case M_PROPERTY_SWITCH: { + struct m_property_switch_arg *sarg = arg; + int pos = 0; + for (int n = 0; n < res->num_srcs; n++) { + if (res->srcs[n] == cur) { + pos = n; + break; + } + } + pos += sarg->inc; + if (pos < 0 || pos >= res->num_srcs) { + if (sarg->wrap) { + pos = (res->num_srcs + pos) % res->num_srcs; + } else { + pos = av_clip(pos, 0, res->num_srcs); + } + } + char *arg = res->srcs[pos]->encid; + return mp_property_quvi_format(prop, M_PROPERTY_SET, &arg, mpctx); + } + } + return mp_property_generic_option(prop, action, arg, mpctx); +} + /// Number of titles in file static int mp_property_titles(m_option_t *prop, int action, void *arg, MPContext *mpctx) @@ -471,6 +575,17 @@ static int mp_property_angle(m_option_t *prop, int action, void *arg, resync_audio_stream(sh_audio); } return M_PROPERTY_OK; + case M_PROPERTY_GET_TYPE: { + struct m_option opt = { + .name = prop->name, + .type = CONF_TYPE_INT, + .flags = CONF_RANGE, + .min = 1, + .max = angles, + }; + *(struct m_option *)arg = opt; + return M_PROPERTY_OK; + } } return M_PROPERTY_NOT_IMPLEMENTED; } @@ -495,6 +610,16 @@ static int mp_property_metadata(m_option_t *prop, int action, void *arg, *(char ***)arg = slist; return M_PROPERTY_OK; } + case M_PROPERTY_PRINT: { |