diff --git a/DOCS/man/en/changes.rst b/DOCS/man/en/changes.rst index de3b24b710..ed83c402f3 100644 --- a/DOCS/man/en/changes.rst +++ b/DOCS/man/en/changes.rst @@ -128,6 +128,7 @@ Command line switches -dumpstream --stream-dump= -capture --stream-capture= -stop-xscreensaver --stop-screensaver + -subfile --sub =================================== =================================== *NOTE*: ``-opt val`` becomes ``--opt=val``. diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst index 304eeba464..57af5ff209 100644 --- a/DOCS/man/en/options.rst +++ b/DOCS/man/en/options.rst @@ -1447,11 +1447,6 @@ ``show_progress`` command (by default mapped to ``P``), or in some non-default cases when seeking. Expands properties. See property_expansion_. ---overlapsub - Allows the next subtitle to be displayed while the current one is still - visible (default is to enable the support only for specific formats). This - only matters for subtitles loaded with ``-sub``. - --panscan=<0.0-1.0> Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9 movie to make it fit a 4:3 display without black bands). The range @@ -1996,14 +1991,15 @@ Use/display these subtitle files. Only one file can be displayed at the same time. ---sub-demuxer=<[+]name> - Force subtitle demuxer type for ``--subfile``. Using a '+' before the name - will force it, this will skip some checks! Give the demuxer name as - printed by ``--sub-demuxer=help``. +--sub-fix-timing, --no-sub-fix-timing + By default, external text subtitles are preprocessed to remove minor gaps + or overlaps between subtitles (if the difference is smaller than 200 ms, + the gap or overlap is removed). This does not affect image subtitles, + subtitles muxed with audio/video, or subtitles in the ASS format. ---sub-no-text-pp - Disables any kind of text post processing done after loading the - subtitles. Used for debug purposes. +--sub-demuxer=<[+]name> + Force subtitle demuxer type for ``--sub``. Give the demuxer name as + printed by ``--sub-demuxer=help``. --sub-paths= Specify extra directories where to search for subtitles matching the @@ -2035,9 +2031,9 @@ ``--subcp=enca::`` You can specify your language using a two letter language code to make - ENCA detect the codepage automatically. If unsure, enter anything and - watch mpv ``-v`` output for available languages. Fallback codepage - specifies the codepage to use, when autodetection fails. + ENCA detect the codepage automatically. If unsure, enter anything (if the + language is invalid, mpv will complain and list valid languages). + Fallback codepage specifies the codepage to use if autodetection fails. *EXAMPLE*: @@ -2045,24 +2041,29 @@ are Czech, fall back on latin 2, if the detection fails. - ``--subcp=enca:pl:cp1250`` guess the encoding for Polish, fall back on cp1250. + - ``--subcp=enca:pl`` guess the encoding for Polish, fall back on UTF-8. + - ``--subcp=enca`` try universal detection, fall back on UTF-8. + + If the player was compiled with libguess support you can use it with: + + ``--subcp=guess::`` + + Note that libguess always needs a language. There is no universal detection + mode. Use ``--subcp=guess:help`` to get a list of languages (like with ENCA, + it will be printed only if the conversion code is somehow called, for + example when loading an external subtitle). --sub-delay= Delays subtitles by seconds. Can be negative. ---subfile= - Open the given file with a demuxer, and use its subtitle streams. Same as - ``--audiofile``, but for subtitle streams. - - *NOTE*: use ``--sub`` for subtitle files. This option is useless, unless - you want to force libavformat subtitle parsers instead of libass or - internal subtitle parsers. - --subfps= Specify the framerate of the subtitle file (default: movie fps). *NOTE*: > movie fps speeds the subtitles up for frame-based subtitle files and slows them down for time-based ones. + Also see ``--sub-speed`` option. + --sub-gauss=<0.0-3.0> Apply gaussian blur to image subtitles (default: 0). This can help making pixelated DVD/Vobsubs look nicer. A value other than 0 also switches to @@ -2089,6 +2090,16 @@ *NOTE*: this affects ASS subtitles as well, and may lead to incorrect subtitle rendering. Use with care, or use ``--sub-text-font-size`` instead. +--sub-speed=<0.1-10.0> + Multiply the subtitle event timestamps with the given value. Can be used + to fix the playback speed for frame-based subtitle formats. Works for + external text subtitles only. + + *EXAMPLE*: + + - ``--sub-speed=25/23.976`` play frame based subtitles, which have been + loaded assuming a framerate of 23.976, at 25 FPS. + --sws= Specify the software scaler algorithm to be used with ``--vf=scale``. This also affects video output drivers which lack hardware acceleration, diff --git a/DOCS/tech-overview.txt b/DOCS/tech-overview.txt index 2037ab653e..e242df1325 100644 --- a/DOCS/tech-overview.txt +++ b/DOCS/tech-overview.txt @@ -186,25 +186,31 @@ audio/out/: why buggy audio drivers can have a bad influence on playback quality. sub/: - Contains subtitle rendering, OSD rendering, and parts of subtitle loading. + Contains subtitle and OSD rendering. sub.c/.h is actually the OSD code. It queries dec_sub.c to retrieve decoded/rendered subtitles. osd_libass.c is the actual implementation of the OSD text renderer (which uses libass, and takes care of all the tricky fontconfig/freetype API usage and text layouting). - Subtitles are loaded either via libass (for .ass), subreader.c (the old - MPlayer subtitle loader code), or libavformat demuxers. The subtitles are - then passed to dec_sub.c and the subtitle decoders in sd_*.c. All text - subtitles are rendered by sd_ass.c. If text subtitles are not in the ASS - format, subtitle converters are inserted, for example sd_srt.c, which is - used to convert SRT->ASS. sd_srt.c is also used as general converter for - text->ASS (to prevent interpretation of text as ASS tags). + Subtitle loading is now in demux/ instead. demux_libass.c wraps loading + .ass subtitles via libass. demux_lavf.c loads most subtitle types via + FFmpeg. demux_subreader.c is the old MPlayer code. It's used as last + fallback, or to handle some text subtitle types on Libav. (It also can + load UTF-16 encoded subtitles without requiring the use of -subcp.) + demux_subreader.c should eventually go away (maybe). - subreader.c should eventually go away. It should be replaced by either - libavformat's demuxers, or by mpv native demuxers for more common formats - like SRT. See commit message of commit 92ae48d what needs to be done to - replace subreader.c completely and why. + The subtitles are passed to dec_sub.c and the subtitle decoders in sd_*.c + as they are demuxed. All text subtitles are rendered by sd_ass.c. If text + subtitles are not in the ASS format, subtitle converters are inserted, for + example sd_srt.c, which is used to convert SRT->ASS. sd_srt.c is also used + as general converter for text->ASS (to prevent interpretation of text as + ASS tags). + + Text subtitles can be preloaded, in which case they are read fully as soon + as the subtitle is selected, and then effectively stored in an ASS_Track. + It's used for external text subtitles, and required to make codepage + detection as well as timing postprocessing work. core/timeline/: A timeline is the abstraction used by mplayer.c to combine several files diff --git a/Makefile b/Makefile index 1460fd7c89..873a5ca64d 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,8 @@ SOURCES-$(DVDREAD) += stream/stream_dvd.c \ SOURCES-$(FTP) += stream/stream_ftp.c SOURCES-$(HAVE_SYS_MMAN_H) += audio/filter/af_export.c osdep/mmap_anon.c SOURCES-$(LADSPA) += audio/filter/af_ladspa.c -SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c +SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c \ + demux/demux_libass.c SOURCES-$(LIBBLURAY) += stream/stream_bluray.c SOURCES-$(LIBBS2B) += audio/filter/af_bs2b.c @@ -169,6 +170,7 @@ SOURCES = talloc.c \ core/av_log.c \ core/av_opts.c \ core/bstr.c \ + core/charset_conv.c \ core/codecs.c \ core/command.c \ core/cpudetect.c \ @@ -207,7 +209,7 @@ SOURCES = talloc.c \ demux/demux_mf.c \ demux/demux_mkv.c \ demux/demux_mpg.c \ - demux/demux_sub.c \ + demux/demux_subreader.c \ demux/demux_ts.c \ demux/mp3_hdr.c \ demux/parse_es.c \ @@ -234,13 +236,13 @@ SOURCES = talloc.c \ sub/img_convert.c \ sub/sd_lavc.c \ sub/sd_lavc_conv.c \ + sub/sd_lavf_srt.c \ sub/sd_microdvd.c \ sub/sd_movtext.c \ sub/sd_spu.c \ sub/sd_srt.c \ sub/spudec.c \ sub/sub.c \ - sub/subreader.c \ video/csputils.c \ video/fmt-conversion.c \ video/image_writer.c \ diff --git a/configure b/configure index a8ebcce983..3330ab56ee 100755 --- a/configure +++ b/configure @@ -292,6 +292,7 @@ Installation directories: Optional features: --disable-encoding disable encoding functionality [enable] + --disable-libguess disable libguess [autodetect] --enable-termcap use termcap database for key codes [autodetect] --enable-termios use termios database for key codes [autodetect] --disable-iconv disable iconv for encoding conversion [autodetect] @@ -465,6 +466,7 @@ networking=yes _winsock2_h=auto _smb=auto _libquvi=auto +_libguess=auto _joystick=no _lirc=auto _lircc=auto @@ -666,6 +668,8 @@ for ac_option do --disable-smb) _smb=no ;; --enable-libquvi) _libquvi=yes ;; --disable-libquvi) _libquvi=no ;; + --enable-libguess) _libguess=yes ;; + --disable-libguess) _libguess=no ;; --enable-joystick) _joystick=yes ;; --disable-joystick) _joystick=no ;; --enable-libav) ffmpeg=yes ;; @@ -1687,6 +1691,21 @@ else fi +echocheck "libguess support" +if test "$_libguess" = auto ; then + _libguess=no + if pkg_config_add 'libguess >= 1.0' ; then + _libguess=yes + fi +fi +if test "$_libguess" = yes; then + def_libguess="#define CONFIG_LIBGUESS 1" +else + def_libguess="#undef CONFIG_LIBGUESS" +fi +echores "$_libguess" + + echocheck "Samba support (libsmbclient)" if test "$_smb" = yes; then libs_mplayer="$libs_mplayer -lsmbclient" @@ -3171,6 +3190,7 @@ VF_LAVFI = $vf_lavfi AF_LAVFI = $af_lavfi LIBSMBCLIENT = $_smb LIBQUVI = $_libquvi +LIBGUESS = $_libguess LIBTHEORA = $_theora LIRC = $_lirc MACOSX_BUNDLE = $_macosx_bundle @@ -3369,6 +3389,7 @@ $def_inet_pton $def_networking $def_smb $def_libquvi +$def_libguess $def_socklen_t $def_vstream 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 + * + * 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 . + */ + +#include +#include +#include + +#include "config.h" + +#include "core/mp_msg.h" + +#ifdef CONFIG_ENCA +#include +#endif + +#ifdef CONFIG_LIBGUESS +#include +#endif + +#ifdef CONFIG_ICONV +#include +#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 +#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 4da0653425..c39bb3c16d 100644 --- a/core/command.c +++ b/core/command.c @@ -2281,7 +2281,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) case MP_CMD_SUB_ADD: if (sh_video) { - mp_add_subtitles(mpctx, cmd->args[0].v.s, sh_video->fps, 0); + mp_add_subtitles(mpctx, cmd->args[0].v.s, 0); } break; @@ -2296,8 +2296,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) struct track *sub = mp_track_by_tid(mpctx, STREAM_SUB, cmd->args[0].v.i); if (sh_video && sub && sub->is_external && sub->external_filename) { - struct track *nsub = mp_add_subtitles(mpctx, sub->external_filename, - sh_video->fps, 0); + struct track *nsub = mp_add_subtitles(mpctx, sub->external_filename, 0); if (nsub) { mp_remove_track(mpctx, sub); mp_switch_track(mpctx, nsub->type, nsub); diff --git a/core/encode_lavc.c b/core/encode_lavc.c index b09ecaa1ac..9fada7de58 100644 --- a/core/encode_lavc.c +++ b/core/encode_lavc.c @@ -404,7 +404,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx, set_to_avdictionary(dictp, "flags", "-pass2"); } else { struct bstr content = stream_read_complete(*bytebuf, NULL, - 1000000000, 1); + 1000000000); if (content.start == NULL) { mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not read '%s', " "disabling 2-pass encoding at pass 1\n", diff --git a/core/input/input.c b/core/input/input.c index 2d7569c8e9..dfa7d1e5b4 100644 --- a/core/input/input.c +++ b/core/input/input.c @@ -1737,7 +1737,7 @@ static int parse_config_file(struct input_ctx *ictx, char *file, bool warn) mp_msg(MSGT_INPUT, MSGL_ERR, "Can't open input config file %s.\n", file); return 0; } - bstr res = stream_read_complete(s, NULL, 1000000, 0); + bstr res = stream_read_complete(s, NULL, 1000000); free_stream(s); mp_msg(MSGT_INPUT, MSGL_V, "Parsing input config file %s\n", file); int n_binds = parse_config(ictx, false, res, file); diff --git a/core/mp_core.h b/core/mp_core.h index 98a92cbea4..0bd6ecda15 100644 --- a/core/mp_core.h +++ b/core/mp_core.h @@ -290,8 +290,7 @@ extern int forced_subs_only; void uninit_player(struct MPContext *mpctx, unsigned int mask); void reinit_audio_chain(struct MPContext *mpctx); double playing_audio_pts(struct MPContext *mpctx); -struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, - float fps, int noerr); +struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noerr); int reinit_video_chain(struct MPContext *mpctx); int reinit_video_filters(struct MPContext *mpctx); void pause_player(struct MPContext *mpctx); diff --git a/core/mplayer.c b/core/mplayer.c index d28b5fdc4c..3cdd83021d 100644 --- a/core/mplayer.c +++ b/core/mplayer.c @@ -71,7 +71,6 @@ #include "core/mplayer.h" #include "core/m_property.h" -#include "sub/subreader.h" #include "sub/find_subfiles.h" #include "sub/dec_sub.h" #include "sub/sd.h" @@ -197,9 +196,6 @@ static const char av_desync_help_text[] = _( static void reset_subtitles(struct MPContext *mpctx); static void reinit_subs(struct MPContext *mpctx); -static struct track *open_external_file(struct MPContext *mpctx, char *filename, - char *demuxer_name, int stream_cache, - enum stream_type filter); static double get_relative_time(struct MPContext *mpctx) { @@ -982,6 +978,9 @@ static struct track *add_stream_track(struct MPContext *mpctx, }; MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track); + if (stream->type == STREAM_SUB) + track->preloaded = !!stream->sub->track; + // Needed for DVD and Blu-ray. if (!track->lang) { struct stream_lang_req req = { @@ -1028,69 +1027,6 @@ static void add_dvd_tracks(struct MPContext *mpctx) #endif } -#ifdef CONFIG_ASS -static int free_sub_data(void *ptr) -{ - struct sh_sub *sh_sub = *(struct sh_sub **)ptr; - if (sh_sub->track) - ass_free_track(sh_sub->track); - talloc_free(sh_sub->sub_data); - return 1; -} -#endif - -struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, - float fps, int noerr) -{ - struct MPOpts *opts = &mpctx->opts; - struct ass_track *asst = NULL; - sub_data *subd = NULL; - - if (filename == NULL) - return NULL; - - // Note: no text subtitles without libass. This is mainly because sd_ass is - // used for rendering. Even when showing subtitles with term-osd, going - // through sd_ass makes the code much simpler, as sd_ass can handle all - // the weird special-cases. -#ifdef CONFIG_ASS - asst = mp_ass_read_stream(mpctx->ass_library, filename, opts->sub_cp); - if (!asst) - subd = sub_read_file(filename, fps, &mpctx->opts); - if (asst || subd) { - struct demuxer *d = new_sub_pseudo_demuxer(opts); - assert(d->num_streams == 1); - struct sh_stream *s = d->streams[0]; - assert(s->type == STREAM_SUB); - - s->codec = asst ? "ass" : subd->codec; - s->sub->track = asst; - s->sub->sub_data = subd; - - struct sh_sub **pptr = talloc(d, struct sh_sub*); - *pptr = s->sub; - talloc_set_destructor(pptr, free_sub_data); - - struct track *t = add_stream_track(mpctx, s, false); - t->is_external = true; - t->preloaded = true; - t->title = talloc_strdup(t, filename); - t->external_filename = talloc_strdup(t, filename); - MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d); - return t; - } -#endif - - // Used with libavformat subtitles. - struct track *ext = open_external_file(mpctx, filename, NULL, 0, STREAM_SUB); - if (ext) - return ext; - - mp_tmsg(MSGT_CPLAYER, noerr ? MSGL_WARN : MSGL_ERR, - "Cannot load subtitles: %s\n", filename); - return NULL; -} - int mp_get_cache_percent(struct MPContext *mpctx) { if (mpctx->stream) { @@ -2001,7 +1937,8 @@ static void reinit_subs(struct MPContext *mpctx) if (!mpctx->sh_sub->dec_sub) mpctx->sh_sub->dec_sub = sub_create(opts); - if (track->demuxer && !track->stream) { + assert(track->demuxer); + if (!track->stream) { // Lazily added DVD track - we must not miss the first subtitle packet, // which makes the demuxer create the sh_stream, and contains the first // subtitle event. @@ -2016,7 +1953,6 @@ static void reinit_subs(struct MPContext *mpctx) return; } - assert(track->demuxer && track->stream); mpctx->initialized_flags |= INITIALIZED_SUB; @@ -2027,12 +1963,22 @@ static void reinit_subs(struct MPContext *mpctx) if (!sub_is_initialized(dec_sub)) { int w = mpctx->sh_video ? mpctx->sh_video->disp_w : 0; int h = mpctx->sh_video ? mpctx->sh_video->disp_h : 0; + float fps = mpctx->sh_video ? mpctx->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->osd->ass_library, mpctx->osd->ass_renderer); sub_init_from_sh(dec_sub, sh_sub); + + // 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, 0, SEEK_ABSOLUTE); + track->preloaded = sub_read_all_packets(dec_sub, sh_sub); + } } mpctx->osd->dec_sub = dec_sub; @@ -3920,16 +3866,15 @@ static void open_subtitles_from_options(struct MPContext *mpctx) // after reading video params we should load subtitles because // we know fps so now we can adjust subtitle time to ~6 seconds AST // check .sub - double sub_fps = mpctx->sh_video ? mpctx->sh_video->fps : 25; if (mpctx->opts.sub_name) { for (int i = 0; mpctx->opts.sub_name[i] != NULL; ++i) - mp_add_subtitles(mpctx, mpctx->opts.sub_name[i], sub_fps, 0); + mp_add_subtitles(mpctx, mpctx->opts.sub_name[i], 0); } if (mpctx->opts.sub_auto) { // auto load sub file ... char **tmp = find_text_subtitles(&mpctx->opts, mpctx->filename); int nsub = MP_TALLOC_ELEMS(tmp); for (int i = 0; i < nsub; i++) { - struct track *track = mp_add_subtitles(mpctx, tmp[i], sub_fps, 1); + struct track *track = mp_add_subtitles(mpctx, tmp[i], 1); if (track) track->auto_loaded = true; } @@ -3959,9 +3904,12 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename, case STREAM_SUB: ss = -1; break; } vs = -1; // avi can't go without video + struct demuxer_params params = { + .ass_library = mpctx->ass_library, // demux_libass requires it + }; struct demuxer *demuxer = demux_open_withparams(&mpctx->opts, stream, format, demuxer_name, - as, vs, ss, filename, NULL); + as, vs, ss, filename, ¶ms); if (!demuxer) { free_stream(stream); goto err_out; @@ -3999,12 +3947,11 @@ static void open_audiofiles_from_options(struct MPContext *mpctx) opts->audio_stream_cache, STREAM_AUDIO); } -// Just for -subfile. open_subtitles_from_options handles -sub text sub files. -static void open_subfiles_from_options(struct MPContext *mpctx) +struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noerr) { struct MPOpts *opts = &mpctx->opts; - open_external_file(mpctx, opts->sub_stream, opts->sub_demuxer_name, - 0, STREAM_SUB); + return open_external_file(mpctx, filename, opts->sub_demuxer_name, 0, + STREAM_SUB); } static void print_timeline(struct MPContext *mpctx) @@ -4293,13 +4240,20 @@ goto_reopen_demuxer: ; if (mpctx->timeline) timeline_set_part(mpctx, mpctx->timeline_part, true); + // Decide correct-pts mode based on first segment of video track + opts->correct_pts = opts->user_correct_pts; + if (opts->correct_pts < 0) { + opts->correct_pts = + demux_control(mpctx->demuxer, DEMUXER_CTRL_CORRECT_PTS, + NULL) == DEMUXER_CTRL_OK; + } + mpctx->initialized_flags |= INITIALIZED_DEMUXER; add_subtitle_fonts_from_sources(mpctx); open_subtitles_from_options(mpctx); open_audiofiles_from_options(mpctx); - open_subfiles_from_options(mpctx); check_previous_track_selection(mpctx); diff --git a/core/options.c b/core/options.c index f3e262fc17..40c8527394 100644 --- a/core/options.c +++ b/core/options.c @@ -406,7 +406,6 @@ const m_option_t mp_opts[] = { // demuxer.c - select audio/sub file/demuxer OPT_STRING("audiofile", audio_stream, 0), OPT_INTRANGE("audiofile-cache", audio_stream_cache, 0, 50, 65536), - OPT_STRING("subfile", sub_stream, 0), OPT_STRING("demuxer", demuxer_name, 0), OPT_STRING("audio-demuxer", audio_demuxer_name, 0), OPT_STRING("sub-demuxer", sub_demuxer_name, 0), @@ -493,12 +492,11 @@ const m_option_t mp_opts[] = { OPT_STRING("subcp", sub_cp, 0), OPT_FLOAT("sub-delay", sub_delay, 0), OPT_FLOAT("subfps", sub_fps, 0), + OPT_FLOAT("sub-speed", sub_speed, 0), OPT_FLAG("autosub", sub_auto, 0), OPT_FLAG("sub-visibility", sub_visibility, 0), OPT_FLAG("sub-forced-only", forced_subs_only, 0), - // enable Closed Captioning display - OPT_FLAG_CONSTANTS("overlapsub", suboverlap_enabled, 0, 0, 2), - OPT_FLAG_STORE("sub-no-text-pp", sub_no_text_pp, 0, 1), + OPT_FLAG_CONSTANTS("sub-fix-timing", suboverlap_enabled, 0, 1, 0), OPT_CHOICE("autosub-match", sub_match_fuzziness, 0, ({"exact", 0}, {"fuzzy", 1}, {"all", 2})), OPT_INTRANGE("sub-pos", sub_pos, 0, 0, 100), @@ -789,6 +787,7 @@ const struct MPOpts mp_default_opts = { .audio_display = 1, .sub_visibility = 1, .sub_pos = 100, + .sub_speed = 1.0, .extension_parsing = 1, .audio_output_channels = MP_CHMAP_INIT_STEREO, .audio_output_format = -1, // AF_FORMAT_UNKNOWN @@ -804,7 +803,7 @@ const struct MPOpts mp_default_opts = { .ass_vsfilter_aspect_compat = 1, .ass_style_override = 1, .use_embedded_fonts = 1, - .suboverlap_enabled = 1, + .suboverlap_enabled = 0, .hwdec_codecs = "all", diff --git a/core/options.h b/core/options.h index f925990a6c..0c6f6c7271 100644 --- a/core/options.h +++ b/core/options.h @@ -141,17 +141,16 @@ typedef struct MPOpts { int sub_pos; float sub_delay; float sub_fps; + float sub_speed; int forced_subs_only; char *quvi_format; // subreader.c int suboverlap_enabled; char *sub_cp; - int sub_no_text_pp; char *audio_stream; int audio_stream_cache; - char *sub_stream; char *demuxer_name; char *audio_demuxer_name; char *sub_demuxer_name; diff --git a/demux/demux.c b/demux/demux.c index 5de6b84745..b66f5e3e61 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -40,7 +40,8 @@ #include "audio/format.h" -#include "libavcodec/avcodec.h" +#include + #if MP_INPUT_BUFFER_PADDING_SIZE < FF_INPUT_BUFFER_PADDING_SIZE #error MP_INPUT_BUFFER_PADDING_SIZE is too small! #endif @@ -66,7 +67,8 @@ extern const demuxer_desc_t demuxer_desc_mpeg_es; extern const demuxer_desc_t demuxer_desc_mpeg4_es; extern const demuxer_desc_t demuxer_desc_h264_es; extern const demuxer_desc_t demuxer_desc_mpeg_ts; -extern const demuxer_desc_t demuxer_desc_sub; +extern const demuxer_desc_t demuxer_desc_libass; +extern const demuxer_desc_t demuxer_desc_subreader; /* Please do not add any new demuxers here. If you want to implement a new * demuxer, add it to libavformat, except for wrappers around external @@ -80,8 +82,10 @@ const demuxer_desc_t *const demuxer_list[] = { #ifdef CONFIG_TV &demuxer_desc_tv, #endif + &demuxer_desc_libass, &demuxer_desc_matroska, &demuxer_desc_lavf, + &demuxer_desc_subreader, &demuxer_desc_avi, &demuxer_desc_asf, #ifdef CONFIG_MNG @@ -96,8 +100,6 @@ const demuxer_desc_t *const demuxer_list[] = { &demuxer_desc_mpeg_ts, // auto-probe last, because it checks file-extensions only &demuxer_desc_mf, - // no auto-probe - &demuxer_desc_sub, /* Please do not add any new demuxers here. If you want to implement a new * demuxer, add it to libavformat, except for wrappers around external * libraries and demuxers requiring binary support. */ @@ -183,6 +185,41 @@ void free_demux_packet(struct demux_packet *dp) talloc_free(dp); } +static int destroy_avpacket(void *pkt) +{ + av_free_packet(pkt); + return 0; +} + +struct demux_packet *demux_copy_packet(struct demux_packet *dp) +{ + struct demux_packet *new = NULL; + // No av_copy_packet() in Libav +#if LIBAVCODEC_VERSION_MICRO >= 100 + if (dp->avpacket) { + assert(dp->buffer == dp->avpacket->data); + assert(dp->len == dp->avpacket->size); + AVPacket *newavp = talloc_zero(NULL, AVPacket); + talloc_set_destructor(newavp, destroy_avpacket); + av_init_packet(newavp); + if (av_copy_packet(newavp, dp->avpacket) < 0) + abort(); + new = new_demux_packet_fromdata(newavp->data, newavp->size); + new->avpacket = newavp; + } +#endif + if (!new) { + new = new_demux_packet(dp->len); + memcpy(new->buffer, dp->buffer, new->len); + } + new->pts = dp->pts; + new->duration = dp->duration; + new->stream_pts = dp->stream_pts; + new->pos = dp->pos; + new->keyframe = dp->keyframe; + return new; +} + static void free_demuxer_stream(struct demux_stream *ds) { ds_free_packs(ds); @@ -251,18 +288,6 @@ static demuxer_t *new_demuxer(struct MPOpts *opts, stream_t *stream, int type, return d; } -// for demux_sub.c -demuxer_t *new_sub_pseudo_demuxer(struct MPOpts *opts) -{ - struct stream *s = open_stream("null://", NULL, NULL); - assert(s); - struct demuxer *d = new_demuxer(opts, s, DEMUXER_TYPE_SUB, - -1, -1, -1, NULL); - new_sh_stream(d, STREAM_SUB); - talloc_steal(d, s); - return d; -} - static struct sh_stream *new_sh_stream_id(demuxer_t *demuxer, enum stream_type type, int stream_index, @@ -446,13 +471,16 @@ void free_demuxer(demuxer_t *demuxer) talloc_free(demuxer); } -void demuxer_add_packet(demuxer_t *demuxer, struct sh_stream *stream, - demux_packet_t *dp) +// Returns the same value as demuxer->fill_buffer: 1 ok, 0 EOF/not selected. +int demuxer_add_packet(demuxer_t *demuxer, struct sh_stream *stream, + demux_packet_t *dp) { - if (!demuxer_stream_is_selected(demuxer, stream)) { + if (!dp || !demuxer_stream_is_selected(demuxer, stream)) { free_demux_packet(dp); + return 0; } else { ds_add_packet(demuxer->ds[stream->type], dp); + return 1; } } @@ -599,7 +627,7 @@ static bool demux_check_queue_full(demuxer_t *demux) int demux_fill_buffer(demuxer_t *demux, demux_stream_t *ds) { // Note: parameter 'ds' can be NULL! - return demux->desc->fill_buffer(demux, ds); + return demux->desc->fill_buffer ? demux->desc->fill_buffer(demux, ds) : 0; } // return value: @@ -940,11 +968,6 @@ static struct demuxer *open_given_type(struct MPOpts *opts, demuxer = demux2; } demuxer->file_format = fformat; - opts->correct_pts = opts->user_correct_pts; - if (opts->correct_pts < 0) - opts->correct_pts = - demux_control(demuxer, DEMUXER_CTRL_CORRECT_PTS, - NULL) == DEMUXER_CTRL_OK; if (stream_manages_timeline(demuxer->stream)) { // Incorrect, but fixes some behavior with DVD/BD demuxer->ts_resets_possible = false; @@ -1482,3 +1505,69 @@ int demuxer_set_angle(demuxer_t *demuxer, int angle) return angle; } + +static int packet_sort_compare(const void *p1, const void *p2) +{ + struct demux_packet *c1 = *(struct demux_packet **)p1; + struct demux_packet *c2 = *(struct demux_packet **)p2; + + if (c1->pts > c2->pts) + return 1; + else if (c1->pts < c2->pts) + return -1; + return 0; +} + +void demux_packet_list_sort(struct demux_packet **pkts, int num_pkts) +{ + qsort(pkts, num_pkts, sizeof(struct demux_packet *), packet_sort_compare); +} + +void demux_packet_list_seek(struct demux_packet **pkts, int num_pkts, + int *current, float rel_seek_secs, int flags) +{ + double ref_time = 0; + if (*current >= 0 && *current < num_pkts) { + ref_time = pkts[*current]->pts; + } else if (*current == num_pkts && num_pkts > 0) { + ref_time = pkts[num_pkts - 1]->pts + pkts[num_pkts - 1]->duration; + } + + if (flags & SEEK_ABSOLUTE) + ref_time = 0; + + if (flags & SEEK_FACTOR) { + ref_time += demux_packet_list_duration(pkts, num_pkts) * rel_seek_secs; + } else { + ref_time += rel_seek_secs; + } + + // Could do binary search, but it's probably not worth the complexity. + int last_index = 0; + for (int n = 0; n < num_pkts; n++) { + if (pkts[n]->pts > ref_time) + break; + last_index = n; + } + *current = last_index; +} + +double demux_packet_list_duration(struct demux_packet **pkts, int num_pkts) +{ + if (num_pkts > 0) + return pkts[num_pkts - 1]->pts + pkts[num_pkts - 1]->duration; + return 0; +} + +struct demux_packet *demux_packet_list_fill(struct demux_packet **pkts, + int num_pkts, int *current) +{ + if (*current < 0) + *current = 0; + if (*current >= num_pkts) + return NULL; + struct demux_packet *new = talloc(NULL, struct demux_packet); + *new = *pkts[*current]; + *current += 1; + return new; +} diff --git a/demux/demux.h b/demux/demux.h index 9ec6d0c6f0..f49a236b80 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -71,13 +71,14 @@ enum demuxer_type { DEMUXER_TYPE_MNG, DEMUXER_TYPE_EDL, DEMUXER_TYPE_CUE, + DEMUXER_TYPE_SUBREADER, + DEMUXER_TYPE_LIBASS, /* Values after this are for internal use and can not be selected * as demuxer type by the user (-demuxer option). */ DEMUXER_TYPE_END, DEMUXER_TYPE_PLAYLIST, - DEMUXER_TYPE_SUB, }; enum timestamp_type { @@ -216,6 +217,7 @@ struct demuxer_params { unsigned char (*matroska_wanted_uids)[16]; int matroska_wanted_segment; bool *matroska_was_valid; + struct ass_library *ass_library; }; typedef struct demuxer { @@ -291,6 +293,7 @@ struct demux_packet *new_demux_packet_fromdata(void *data, size_t len); struct demux_packet *new_demux_packet_from(void *data, size_t len); void resize_demux_packet(struct demux_packet *dp, size_t len); void free_demux_packet(struct demux_packet *dp); +struct demux_packet *demux_copy_packet(struct demux_packet *dp); #ifndef SIZE_MAX #define SIZE_MAX ((size_t)-1) @@ -305,13 +308,10 @@ static inline void *realloc_struct(void *ptr, size_t nmemb, size_t size) return realloc(ptr, nmemb * size); } -demuxer_t *new_sub_pseudo_demuxer(struct MPOpts *opts); - - void free_demuxer(struct demuxer *demuxer); -void demuxer_add_packet(demuxer_t *demuxer, struct sh_stream *stream, - demux_packet_t *dp); +int demuxer_add_packet(demuxer_t *demuxer, struct sh_stream *stream, + demux_packet_t *dp); void ds_add_packet(struct demux_stream *ds, struct demux_packet *dp); void ds_read_packet(struct demux_stream *ds, struct stream *stream, int len, double pts, int64_t pos, bool keyframe); @@ -425,4 +425,11 @@ struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d, bool demuxer_stream_is_selected(struct demuxer *d, struct sh_stream *stream); +void demux_packet_list_sort(struct demux_packet **pkts, int num_pkts); +void demux_packet_list_seek(struct demux_packet **pkts, int num_pkts, + int *current, float rel_seek_secs, int flags); +double demux_packet_list_duration(struct demux_packet **pkts, int num_pkts); +struct demux_packet *demux_packet_list_fill(struct demux_packet **pkts, + int num_pkts, int *current); + #endif /* MPLAYER_DEMUXER_H */ diff --git a/demux/demux_cue.c b/demux/demux_cue.c index 31a3e00e40..073fa9d336 100644 --- a/demux/demux_cue.c +++ b/demux/demux_cue.c @@ -39,7 +39,7 @@ static int try_open_file(struct demuxer *demuxer) if (!mp_probe_cue((struct bstr) { buf, len })) return 0; stream_seek(s, 0); - demuxer->file_contents = stream_read_complete(s, demuxer, 1000000, 0); + demuxer->file_contents = stream_read_complete(s, demuxer, 1000000); if (demuxer->file_contents.start == NULL) return 0; if (!mp_probe_cue((struct bstr) { buf, len })) @@ -47,11 +47,6 @@ static int try_open_file(struct demuxer *demuxer) return DEMUXER_TYPE_CUE; } -static int dummy_fill_buffer(struct demuxer *demuxer, struct demux_stream *ds) -{ - return 0; -} - const struct demuxer_desc demuxer_desc_cue = { .info = "CUE file demuxer", .name = "cue", @@ -61,5 +56,4 @@ const struct demuxer_desc demuxer_desc_cue = { .type = DEMUXER_TYPE_CUE, .safe_check = true, .check_file = try_open_file, // no separate .open - .fill_buffer = dummy_fill_buffer, }; diff --git a/demux/demux_edl.c b/demux/demux_edl.c index 1e1db5be93..c35137ffb2 100644 --- a/demux/demux_edl.c +++ b/demux/demux_edl.c @@ -34,17 +34,12 @@ static int try_open_file(struct demuxer *demuxer) if (strncmp(buf, header, len)) return 0; stream_seek(s, 0); - demuxer->file_contents = stream_read_complete(s, demuxer, 1000000, 0); + demuxer->file_contents = stream_read_complete(s, demuxer, 1000000); if (demuxer->file_contents.start == NULL) return 0; return DEMUXER_TYPE_EDL; } -static int dummy_fill_buffer(struct demuxer *demuxer, struct demux_stream *ds) -{ - return 0; -} - const struct demuxer_desc demuxer_desc_edl = { .info = "EDL file demuxer", .name = "edl", @@ -54,5 +49,4 @@ const struct demuxer_desc demuxer_desc_edl = { .type = DEMUXER_TYPE_EDL, .safe_check = true, .check_file = try_open_file, // no separate .open - .fill_buffer = dummy_fill_buffer, }; diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index d8e8109c93..48e79e949b 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -267,12 +267,11 @@ static int lavf_check_file(demuxer_t *demuxer) while (avpd.buf_size < PROBE_BUF_SIZE) { int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE, PROBE_BUF_SIZE); - int read_size = stream_read(s, avpd.buf + avpd.buf_size, - nsize - avpd.buf_size); - if (read_size <= 0) + bstr buf = stream_peek(s, nsize); + if (buf.len <= avpd.buf_size) break; - - avpd.buf_size += read_size; + memcpy(avpd.buf, buf.start, buf.len); + avpd.buf_size = buf.len; int score = 0; priv->avif = av_probe_input_format2(&avpd, avpd.buf_size > 0, &score); @@ -294,7 +293,6 @@ static int lavf_check_file(demuxer_t *demuxer) priv->avif = NULL; } - stream_unread_buffer(s, avpd.buf, avpd.buf_size); av_free(avpd.buf); if (!priv->avif) { diff --git a/demux/demux_libass.c b/demux/demux_libass.c new file mode 100644 index 0000000000..cbc85b3abe --- /dev/null +++ b/demux/demux_libass.c @@ -0,0 +1,121 @@ +/* + * 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 . + */ + +// Note: just wraps libass, and makes the subtitle track available though +// sh_sub->track. It doesn't produce packets and doesn't support seeking. + +#include +#include + +#include "core/options.h" +#include "core/mp_msg.h" +#include "core/charset_conv.h" +#include "stream/stream.h" +#include "demux.h" + +#define PROBE_SIZE (8 * 1024) + +struct priv { + ASS_Track *track; +}; + +static int d_check_file(struct demuxer *demuxer) +{ + const char *user_cp = demuxer->opts->sub_cp; + struct stream *s = demuxer->stream; + // Older versions of libass will behave strange if renderer and track + // library handles mismatch, so make sure everything uses a global handle. + ASS_Library *lib = demuxer->params ? demuxer->params->ass_library : NULL; + if (!lib) + return 0; + + // Probe by loading a part of the beginning of the file with libass. + // Incomplete scripts are usually ok, and we hope libass is not verbose + // when dealing with (from its perspective) completely broken binary + // garbage. + + bstr buf = stream_peek(s, PROBE_SIZE); + // Older versions of libass will overwrite the input buffer, and despite + // passing length, expect a 0 termination. + void *tmp = talloc_size(NULL, buf.len + 1); + memcpy(tmp, buf.start, buf.len); + buf.start = tmp; + buf.start[buf.len] = '\0'; + bstr cbuf = + mp_charset_guess_and_conv_to_utf8(buf, user_cp, MP_ICONV_ALLOW_CUTOFF); + if (cbuf.start == NULL) + cbuf = buf; + ASS_Track *track = ass_read_memory(lib, cbuf.start, cbuf.len, NULL); + if (cbuf.start != buf.start) + talloc_free(cbuf.start); + talloc_free(buf.start); + if (!track) + return 0; + ass_free_track(track); + + // Actually load the full thing. + + buf = stream_read_complete(s, NULL, 100000000); + if (!buf.start) { + mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file " + "larger than 100 MB: %s\n", demuxer->filename); + return 0; + } + cbuf = mp_charset_guess_and_conv_to_utf8(buf, user_cp, MP_ICONV_VERBOSE); + if (cbuf.start == NULL) + cbuf = buf; + track = ass_read_memory(lib, cbuf.start, cbuf.len, NULL); + if (cbuf.start != buf.start) + talloc_free(cbuf.start); + talloc_free(buf.start); + if (!track) + return 0; + + track->name = strdup(demuxer->filename); + + struct priv *p = talloc_ptrtype(demuxer, p); + *p = (struct priv) { + .track = track, + }; + + struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB); + sh->sub->track = track; + sh->codec = "ass"; + + return DEMUXER_TYPE_LIBASS; +} + +static void d_close(struct demuxer *demuxer) +{ + struct priv *p = demuxer->priv; + if (p) { + if (p->track) + ass_free_track(p->track); + } +} + +const struct demuxer_desc demuxer_desc_libass = { + .info = "Read subtitles with libass", + .name = "libass", + .shortdesc = "ASS/SSA subtitles (libass)", + .author = "", + .comment = "", + .safe_check = 1, + .type = DEMUXER_TYPE_LIBASS, + .check_file = d_check_file, + .close = d_close, +}; diff --git a/demux/demux_mf.c b/demux/demux_mf.c index 0db3fb8add..127ee8474e 100644 --- a/demux/demux_mf.c +++ b/demux/demux_mf.c @@ -78,7 +78,7 @@ static int demux_mf_fill_buffer(demuxer_t *demuxer, demux_stream_t *ds){ if (stream) { stream_seek(stream, 0); - bstr data = stream_read_complete(stream, NULL, MF_MAX_FILE_SIZE, 0); + bstr data = stream_read_complete(stream, NULL, MF_MAX_FILE_SIZE); if (data.len) { demux_packet_t *dp = new_demux_packet(data.len); memcpy(dp->buffer, data.start, data.len); diff --git a/demux/demux_sub.c b/demux/demux_sub.c deleted file mode 100644 index ab99091215..0000000000 --- a/demux/demux_sub.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 . - */ - -// Note: not a real demuxer. The frontend has its own code to open subtitle -// code, and then creates a new dummy demuxer with new_sub_demuxer(). -// But eventually, all subtitles should be opened this way, and this -// file can be removed. - -#include "demux.h" - -static int dummy_fill_buffer(struct demuxer *demuxer, struct demux_stream *ds) -{ - return 0; -} - -const struct demuxer_desc demuxer_desc_sub = { - .info = "External subtitles pseudo demuxer", - .name = "sub", - .shortdesc = "sub", - .author = "", - .comment = "", - .type = DEMUXER_TYPE_SUB, - .fill_buffer = dummy_fill_buffer, -}; diff --git a/sub/subreader.c b/demux/demux_subreader.c similarity index 70% rename from sub/subreader.c rename to demux/demux_subreader.c index f3821ba5ab..ca03e73c33 100644 --- a/sub/subreader.c +++ b/demux/demux_subreader.c @@ -28,24 +28,65 @@ #include #include +#include +#include + #include "config.h" #include "core/mp_msg.h" -#include "subreader.h" #include "core/mp_common.h" #include "core/options.h" #include "stream/stream.h" -#include "libavutil/common.h" -#include "libavutil/avstring.h" - -#ifdef CONFIG_ENCA -#include -#endif +#include "demux/demux.h" #define ERR ((void *) -1) -#ifdef CONFIG_ICONV -#include -#endif +// subtitle formats +#define SUB_INVALID -1 +#define SUB_MICRODVD 0 +#define SUB_SUBRIP 1 +#define SUB_SUBVIEWER 2 +#define SUB_SAMI 3 +#define SUB_VPLAYER 4 +#define SUB_RT 5 +#define SUB_SSA 6 +#define SUB_PJS 7 +#define SUB_MPSUB 8 +#define SUB_AQTITLE 9 +#define SUB_SUBVIEWER2 10 +#define SUB_SUBRIP09 11 +#define SUB_JACOSUB 12 +#define SUB_MPL2 13 + +#define SUB_MAX_TEXT 12 +#define SUB_ALIGNMENT_BOTTOMLEFT 1 +#define SUB_ALIGNMENT_BOTTOMCENTER 2 +#define SUB_ALIGNMENT_BOTTOMRIGHT 3 +#define SUB_ALIGNMENT_MIDDLELEFT 4 +#define SUB_ALIGNMENT_MIDDLECENTER 5 +#define SUB_ALIGNMENT_MIDDLERIGHT 6 +#define SUB_ALIGNMENT_TOPLEFT 7 +#define SUB_ALIGNMENT_TOPCENTER 8 +#define SUB_ALIGNMENT_TOPRIGHT 9 + +typedef struct subtitle { + + int lines; + + unsigned long start; + unsigned long end; + + char *text[SUB_MAX_TEXT]; + unsigned char alignment; +} subtitle; + +typedef struct sub_data { + const char *codec; + subtitle *subtitles; + int sub_uses_time; + int sub_num; // number of subtitle structs + int sub_errs; + double fallback_fps; +} sub_data; // Parameter struct for the format-specific readline functions struct readline_args { @@ -58,6 +99,8 @@ struct readline_args { float mpsub_position; int sub_slacktime; + int uses_time; + /* Some subtitling formats, namely AQT and Subrip09, define the end of a subtitle as the beginning of the following. Since currently we read one @@ -171,7 +214,7 @@ static subtitle *sub_read_line_sami(stream_t* st, subtitle *current, sami_add_line(current, text, &p); s += 4; } - else if ((*s == '{') && !args->opts->sub_no_text_pp) { state = 5; ++s; continue; } + else if ((*s == '{')) { state = 5; ++s; continue; } else if (*s == '<') { state = 4; } else if (!strncasecmp (s, " ", 6)) { *p++ = ' '; s += 6; } else if (*s == '\t') { *p++ = ' '; s++; } @@ -197,7 +240,7 @@ static subtitle *sub_read_line_sami(stream_t* st, subtitle *current, if (s) { s++; state = 3; continue; } break; case 5: /* get rid of {...} text, but read the alignment code */ - if ((*s == '\\') && (*(s + 1) == 'a') && !args->opts->sub_no_text_pp) { + if ((*s == '\\') && (*(s + 1) == 'a')) { if (stristr(s, "\\a1") != NULL) { current->alignment = SUB_ALIGNMENT_BOTTOMLEFT; s = s + 3; @@ -1018,79 +1061,6 @@ static int sub_autodetect (stream_t* st, int *uses_time, int utf16) { return SUB_INVALID; // too many bad lines } -#ifdef CONFIG_ICONV -static const char* guess_cp(stream_t *st, const char *preferred_language, const char *fallback); - -static iconv_t subcp_open (stream_t *st, const char *sub_cp) -{ - iconv_t icdsc = (iconv_t)(-1); - char *tocp = "UTF-8"; - - if (sub_cp){ - const char *cp_tmp = sub_cp; -#ifdef CONFIG_ENCA - char enca_lang[3], enca_fallback[100]; - if (sscanf(sub_cp, "enca:%2s:%99s", enca_lang, enca_fallback) == 2 - || sscanf(sub_cp, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) { - if (st && st->flags & MP_STREAM_SEEK ) { - cp_tmp = guess_cp(st, enca_lang, enca_fallback); - } else { - cp_tmp = enca_fallback; - if (st) - mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: enca failed, stream must be seekable.\n"); - } - } -#endif - if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){ - mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: opened iconv descriptor.\n"); - } else - mp_msg(MSGT_SUBREADER,MSGL_ERR,"SUB: error opening iconv descriptor.\n"); - } - return icdsc; -} - -static void subcp_close (iconv_t icdsc) -{ - if (icdsc != (iconv_t)(-1)){ - (void) iconv_close (icdsc); - mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: closed iconv descriptor.\n"); - } -} - -static subtitle* subcp_recode (iconv_t icdsc, subtitle *sub) -{ - int l=sub->lines; - size_t ileft, oleft; - char *op, *ip, *ot; - if(icdsc == (iconv_t)(-1)) return sub; - - while (l){ - ip = sub->text[--l]; - ileft = strlen(ip); - oleft = 4 * ileft; - - if (!(ot = malloc(oleft + 1))) - abort(); - op = ot; - if (iconv(icdsc, &ip, &ileft, - &op, &oleft) == (size_t)(-1)) { - mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: error recoding line.\n"); - free(ot); - continue; - } - // In some stateful encodings, we must clear the state to handle the last character - if (iconv(icdsc, NULL, NULL, - &op, &oleft) == (size_t)(-1)) { - mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: error recoding line, can't clear encoding state.\n"); - } - *op='\0' ; - free (sub->text[l]); - sub->text[l] = ot; - } - return sub; -} -#endif - static void adjust_subs_time(subtitle* sub, float subtime, float fps, float sub_fps, int block, int sub_num, int sub_uses_time) { @@ -1098,7 +1068,6 @@ static void adjust_subs_time(subtitle* sub, float subtime, float fps, subtitle* nextsub; int i = sub_num; unsigned long subfms = (sub_uses_time ? 100 : fps) * subtime; - unsigned long overlap = (sub_uses_time ? 100 : fps) / 5; // 0.2s n=m=0; if (i) for (;;){ @@ -1110,15 +1079,6 @@ static void adjust_subs_time(subtitle* sub, float subtime, float fps, if (!--i) break; nextsub = sub + 1; if(block){ - if ((sub->end > nextsub->start) && (sub->end <= nextsub->start + overlap)) { - // these subtitles overlap for less than 0.2 seconds - // and would result in very short overlapping subtitle - // so let's fix the problem here, before overlapping code - // get its hands on them - unsigned delta = sub->end - nextsub->start, half = delta / 2; - sub->end -= half + 1; - nextsub->start += delta - half; - } if (sub->end >= nextsub->start){ sub->end = nextsub->start - 1; if (sub->end - sub->start > subfms) @@ -1128,23 +1088,6 @@ static void adjust_subs_time(subtitle* sub, float subtime, float fps, } } - /* Theory: - * Movies are often converted from FILM (24 fps) - * to PAL (25) by simply speeding it up, so we - * to multiply the original timestmaps by - * (Movie's FPS / Subtitle's (guessed) FPS) - * so eg. for 23.98 fps movie and PAL time based - * subtitles we say -subfps 25 and we're fine! - */ - - /* timed sub fps correction ::atmos */ - /* the frame-based case is handled in mpcommon.c - * where find_sub is called */ - if(sub_uses_time && sub_fps) { - sub->start *= sub_fps/fps; - sub->end *= sub_fps/fps; - } - sub = nextsub; m = 0; } @@ -1157,78 +1100,12 @@ struct subreader { void (*post)(subtitle *dest); const char *name; const char *codec_name; + struct readline_args args; }; -#ifdef CONFIG_ENCA -static const char* guess_buffer_cp(unsigned char* buffer, int buflen, const char *preferred_language, const char *fallback) +static bool subreader_autodetect(stream_t *fd, struct MPOpts *opts, + struct subreader *out) { - const char **languages; - size_t langcnt; - EncaAnalyser analyser; - EncaEncoding encoding; - const char *detected_sub_cp = NULL; - int i; - - languages = enca_get_languages(&langcnt); - mp_msg(MSGT_SUBREADER, MSGL_V, "ENCA supported languages: "); - for (i = 0; i < langcnt; i++) { - mp_msg(MSGT_SUBREADER, MSGL_V, "%s ", languages[i]); - } - mp_msg(MSGT_SUBREADER, MSGL_V, "\n"); - - for (i = 0; i < langcnt; i++) { - if (strcasecmp(languages[i], preferred_language) != 0) continue; - analyser = enca_analyser_alloc(languages[i]); - encoding = enca_analyse_const(analyser, buffer, buflen); - enca_analyser_free(analyser); - if (encoding.charset != ENCA_CS_UNKNOWN) { - detected_sub_cp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV); - break; - } - } - - free(languages); - - if (!detected_sub_cp) { - detected_sub_cp = fallback; - mp_msg(MSGT_SUBREADER, MSGL_INFO, "ENCA detection failed: fallback to %s\n", fallback); - }else{ - mp_msg(MSGT_SUBREADER, MSGL_INFO, "ENCA detected charset: %s\n", detected_sub_cp); - } - - return detected_sub_cp; -} - -#define MAX_GUESS_BUFFER_SIZE (256*1024) -static const char* guess_cp(stream_t *st, const char *preferred_language, const char *fallback) -{ - size_t buflen; - unsigned char *buffer; - const char *detected_sub_cp = NULL; - - buffer = malloc(MAX_GUESS_BUFFER_SIZE); - buflen = stream_read(st,buffer, MAX_GUESS_BUFFER_SIZE); - - detected_sub_cp = guess_buffer_cp(buffer, buflen, preferred_language, fallback); - - free(buffer); - stream_seek(st,0); - - return detected_sub_cp; -} -#undef MAX_GUESS_BUFFER_SIZE -#endif - -static int sub_destroy(void *ptr); - -sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) -{ - int utf16; - stream_t* fd; - int n_max, n_first, i, j, sub_first, sub_orig; - subtitle *first, *second, *sub, *return_sub, *alloced_sub = NULL; - sub_data *subt_data; - int uses_time = 0, sub_num = 0, sub_errs = 0; static const struct subreader sr[]= { { sub_read_line_microdvd, NULL, "microdvd", "microdvd" }, @@ -1248,43 +1125,43 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) }; const struct subreader *srp; - if(filename==NULL) return NULL; //qnx segfault - fd=open_stream (filename, NULL, NULL); if (!fd) return NULL; - int sub_format = SUB_INVALID; + int utf16; + int uses_time = 0; for (utf16 = 0; sub_format == SUB_INVALID && utf16 < 3; utf16++) { sub_format=sub_autodetect (fd, &uses_time, utf16); stream_seek(fd,0); } utf16--; - struct readline_args args = {utf16, opts}; - args.sub_slacktime = 20000; //20 sec - args.mpsub_multiplier = (uses_time ? 100.0 : 1.0); - if (sub_format==SUB_INVALID) { - mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: Could not determine file format\n"); - free_stream(fd); - return NULL; + mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: Could not determine file format\n"); + return false; } srp=sr+sub_format; mp_msg(MSGT_SUBREADER, MSGL_V, "SUB: Detected subtitle file format: %s\n", srp->name); -#ifdef CONFIG_ICONV - iconv_t icdsc = (iconv_t)(-1); - { - int l,k; - k = -1; - if ((l=strlen(filename))>4){ - char *exts[] = {".utf", ".utf8", ".utf-8" }; - for (k=3;--k>=0;) - if (l >= strlen(exts[k]) && !strcasecmp(filename+(l - strlen(exts[k])), exts[k])){ - break; - } - } - if (k<0) icdsc = subcp_open(fd, opts->sub_cp); - } -#endif + *out = *srp; + out->args = (struct readline_args) { + .utf16 = utf16, + .opts = opts, + .sub_slacktime = 20000, //20 sec + .mpsub_multiplier = (uses_time ? 100.0 : 1.0), + .uses_time = uses_time, + }; + + return true; +} + +static sub_data* sub_read_file(stream_t *fd, struct subreader *srp) +{ + struct MPOpts *opts = fd->opts; + float fps = 23.976; + int n_max, i, j; + subtitle *first, *sub, *return_sub, *alloced_sub = NULL; + sub_data *subt_data; + int sub_num = 0, sub_errs = 0; + struct readline_args args = srp->args; sub_num=0;n_max=32; first=malloc(n_max*sizeof(subtitle)); @@ -1304,21 +1181,15 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) memset(sub, '\0', sizeof(subtitle)); sub=srp->read(fd, sub, &args); if(!sub) break; // EOF -#ifdef CONFIG_ICONV - if (sub!=ERR) sub=subcp_recode(icdsc, sub); -#endif + if ( sub == ERR ) { -#ifdef CONFIG_ICONV - subcp_close(icdsc); -#endif free(first); free(alloced_sub); - free_stream(fd); return NULL; } // Apply any post processing that needs recoding first - if ((sub!=ERR) && !args.opts->sub_no_text_pp && srp->post) srp->post(sub); + if ((sub!=ERR) && srp->post) srp->post(sub); if(!sub_num || (first[sub_num - 1].start <= sub->start)){ first[sub_num].start = sub->start; first[sub_num].end = sub->end; @@ -1360,11 +1231,6 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) if(sub==ERR) ++sub_errs; else ++sub_num; // Error vs. Valid } - free_stream(fd); - -#ifdef CONFIG_ICONV - subcp_close(icdsc); -#endif free(alloced_sub); // printf ("SUB: Subtitle format %s time.\n", uses_time?"uses":"doesn't use"); @@ -1376,228 +1242,13 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) return NULL; } - // we do overlap if the user forced it (suboverlap_enable == 2) or - // the user didn't forced no-overlapsub and the format is Jacosub or Ssa. - // this is because usually overlapping subtitles are found in these formats, - // while in others they are probably result of bad timing -if ((opts->suboverlap_enabled == 2) || - ((opts->suboverlap_enabled) && ((sub_format == SUB_JACOSUB) || (sub_format == SUB_SSA)))) { - adjust_subs_time(first, 6.0, fps, opts->sub_fps, 0, sub_num, uses_time);/*~6 secs AST*/ -// here we manage overlapping subtitles - sub_orig = sub_num; - n_first = sub_num; - sub_num = 0; - second = NULL; - // for each subtitle in first[] we deal with its 'block' of - // bonded subtitles - for (sub_first = 0; sub_first < n_first; ++sub_first) { - unsigned long global_start = first[sub_first].start, - global_end = first[sub_first].end, local_start, local_end; - int lines_to_add = first[sub_first].lines, sub_to_add = 0, - **placeholder = NULL, higher_line = 0, counter, start_block_sub = sub_num; - char real_block = 1; - - // here we find the number of subtitles inside the 'block' - // and its span interval. this works well only with sorted - // subtitles - while ((sub_first + sub_to_add + 1 < n_first) && (first[sub_first + sub_to_add + 1].start < global_end)) { - ++sub_to_add; - lines_to_add += first[sub_first + sub_to_add].lines; - if (first[sub_first + sub_to_add].start < global_start) { - global_start = first[sub_first + sub_to_add].start; - } - if (first[sub_first + sub_to_add].end > global_end) { - global_end = first[sub_first + sub_to_add].end; - } - } - - /* Avoid n^2 memory use for the "placeholder" data structure - * below with subtitles that have a huge number of - * consecutive overlapping lines. */ - lines_to_add = FFMIN(lines_to_add, SUB_MAX_TEXT); - - // we need a structure to keep trace of the screen lines - // used by the subs, a 'placeholder' - counter = 2 * sub_to_add + 1; // the maximum number of subs derived - // from a block of sub_to_add+1 subs - placeholder = malloc(sizeof(int *) * counter); - for (i = 0; i < counter; ++i) { - placeholder[i] = malloc(sizeof(int) * lines_to_add + 1); - for (j = 0; j < lines_to_add; ++j) { - placeholder[i][j] = -1; - } - } - - counter = 0; - local_end = global_start - 1; - do { - int ls; - - // here we find the beginning and the end of a new - // subtitle in the block - local_start = local_end + 1; - local_end = global_end; - for (j = 0; j <= sub_to_add; ++j) { - if ((first[sub_first + j].start - 1 > local_start) && (first[sub_first + j].start - 1 < local_end)) { - local_end = first[sub_first + j].start - 1; - } else if ((first[sub_first + j].end > local_start) && (first[sub_first + j].end < local_end)) { - local_end = first[sub_first + j].end; - } - } - // here we allocate the screen lines to subs we must - // display in current local_start-local_end interval. - // if the subs were yet presents in the previous interval - // they keep the same lines, otherside they get unused lines - for (j = 0; j <= sub_to_add; ++j) { - if ((first[sub_first + j].start <= local_end) && (first[sub_first + j].end > local_start)) { - unsigned long sub_lines = first[sub_first + j].lines, fragment_length = lines_to_add + 1, - tmp = 0; - char boolean = 0; - int fragment_position = -1; - - // if this is not the first new sub of the block - // we find if this sub was present in the previous - // new sub - if (counter) - for (i = 0; i < lines_to_add; ++i) { - if (placeholder[counter - 1][i] == sub_first + j) { - placeholder[counter][i] = sub_first + j; - boolean = 1; - } - } - if (boolean) - continue; - - // we are looking for the shortest among all groups of - // sequential blank lines whose length is greater than or - // equal to sub_lines. we store in fragment_position the - // position of the shortest group, in fragment_length its - // length, and in tmp the length of the group currently - // examinated - for (i = 0; i < lines_to_add; ++i) { - if (placeholder[counter][i] == -1) { - // placeholder[counter][i] is part of the current group - // of blank lines - ++tmp; - } else { - if (tmp == sub_lines) { - // current group's size fits exactly the one we - // need, so we stop looking - fragment_position = i - tmp; - tmp = 0; - break; - } - if ((tmp) && (tmp > sub_lines) && (tmp < fragment_length)) { - // current group is the best we found till here, - // but is still bigger than the one we are looking - // for, so we keep on looking - fragment_length = tmp; - fragment_position = i - tmp; - tmp = 0; - } else { - // current group doesn't fit at all, so we forget it - tmp = 0; - } - } - } - if (tmp) { - // last screen line is blank, a group ends with it - if ((tmp >= sub_lines) && (tmp < fragment_length)) { - fragment_position = i - tmp; - } - } - if (fragment_position == -1) { - // it was not possible to find free screen line(s) for a subtitle, - // usually this means a bug in the code; however we do not overlap - mp_msg(MSGT_SUBREADER, MSGL_WARN, "SUB: we could not find a suitable position for an overlapping subtitle\n"); - higher_line = SUB_MAX_TEXT + 1; - break; - } else { - for (tmp = 0; tmp < sub_lines; ++tmp) { - placeholder[counter][fragment_position + tmp] = sub_first + j; - } - } - } - } - for (j = higher_line + 1; j < lines_to_add; ++j) { - if (placeholder[counter][j] != -1) - higher_line = j; - else - break; - } - if (higher_line >= SUB_MAX_TEXT) { - // the 'block' has too much lines, so we don't overlap the - // subtitles - second = realloc(second, (sub_num + sub_to_add + 1) * sizeof(subtitle)); - for (j = 0; j <= sub_to_add; ++j) { - int ls; - memset(&second[sub_num + j], '\0', sizeof(subtitle)); - second[sub_num + j].start = first[sub_first + j].start; - second[sub_num + j].end = first[sub_first + j].end; - second[sub_num + j].lines = first[sub_first + j].lines; - second[sub_num + j].alignment = first[sub_first + j].alignment; - for (ls = 0; ls < second[sub_num + j].lines; ls++) { - second[sub_num + j].text[ls] = strdup(first[sub_first + j].text[ls]); - } - } - sub_num += sub_to_add + 1; - sub_first += sub_to_add; - real_block = 0; - break; - } - - // we read the placeholder structure and create the new - // subs. - second = realloc(second, (sub_num + 1) * sizeof(subtitle)); - memset(&second[sub_num], '\0', sizeof(subtitle)); - second[sub_num].start = local_start; - second[sub_num].end = local_end; - second[sub_num].alignment = first[sub_first].alignment; - n_max = (lines_to_add < SUB_MAX_TEXT) ? lines_to_add : SUB_MAX_TEXT; - for (i = 0, j = 0; j < n_max; ++j) { - if (placeholder[counter][j] != -1) { - int lines = first[placeholder[counter][j]].lines; - for (ls = 0; ls < lines; ++ls) { - second[sub_num].text[i++] = strdup(first[placeholder[counter][j]].text[ls]); - } - j += lines - 1; - } else { - second[sub_num].text[i++] = strdup(" "); - } - } - ++sub_num; - ++counter; - } while (local_end < global_end); - if (real_block) - for (i = 0; i < counter; ++i) - second[start_block_sub + i].lines = higher_line + 1; - - counter = 2 * sub_to_add + 1; - for (i = 0; i < counter; ++i) { - free(placeholder[i]); - } - free(placeholder); - sub_first += sub_to_add; - } - - for (j = sub_orig - 1; j >= 0; --j) { - for (i = first[j].lines - 1; i >= 0; --i) { - free(first[j].text[i]); - } - } - free(first); - - return_sub = second; -} else { //if(suboverlap_enabled) - adjust_subs_time(first, 6.0, fps, opts->sub_fps, 1, sub_num, uses_time);/*~6 secs AST*/ + adjust_subs_time(first, 6.0, fps, opts->sub_fps, 1, sub_num, args.uses_time);/*~6 secs AST*/ return_sub = first; -} + if (return_sub == NULL) return NULL; subt_data = talloc_zero(NULL, sub_data); - talloc_set_destructor(subt_data, sub_destroy); subt_data->codec = srp->codec_name ? srp->codec_name : "text"; - subt_data->filename = strdup(filename); - subt_data->sub_uses_time = uses_time; + subt_data->sub_uses_time = args.uses_time; subt_data->sub_num = sub_num; subt_data->sub_errs = sub_errs; subt_data->subtitles = return_sub; @@ -1605,14 +1256,147 @@ if ((opts->suboverlap_enabled == 2) || return subt_data; } -static int sub_destroy(void *ptr) +static void subdata_free(sub_data *subd) { - sub_data *subd = ptr; int i, j; for (i = 0; i < subd->sub_num; i++) for (j = 0; j < subd->subtitles[i].lines; j++) free( subd->subtitles[i].text[j] ); free( subd->subtitles ); - free( subd->filename ); - return 0; + talloc_free(subd); } + +struct priv { + struct demux_packet **pkts; + int num_pkts; + int current; + struct sh_stream *sh; +}; + +static void add_sub_data(struct demuxer *demuxer, struct sub_data *subdata) +{ + struct priv *priv = demuxer->priv; + + for (int i = 0; i < subdata->sub_num; i++) { + subtitle *st = &subdata->subtitles[i]; + // subdata is in 10 ms ticks, pts is in seconds + double t = subdata->sub_uses_time ? 0.01 : (1 / subdata->fallback_fps); + + int len = 0; + for (int j = 0; j < st->lines; j++) + len += st->text[j] ? strlen(st->text[j]) : 0; + + len += 2 * st->lines; // '\N', including the one after the last line + len += 6; // {\anX} + len += 1; // '\0' + + char *data = talloc_array(NULL, char, len); + + char *p = data; + char *end = p + len; + + if (st->alignment) + p += snprintf(p, end - p, "{\\an%d}", st->alignment); + + for (int j = 0; j < st->lines; j++) + p += snprintf(p, end - p, "%s\\N", st->text[j]); + + if (st->lines > 0) + p -= 2; // remove last "\N" + *p = 0; + + struct demux_packet *pkt = talloc_ptrtype(priv, pkt); + *pkt = (struct demux_packet) { + .pts = st->start * t, + .duration = (st->end - st->start) * t, + .buffer = talloc_steal(pkt, data), + .len = strlen(data), + }; + + MP_TARRAY_APPEND(priv, priv->pkts, priv->num_pkts, pkt); + } +} + +static struct stream *read_probe_stream(struct stream *s, int max) +{ + // Very roundabout, but only needed for initial probing. + bstr probe = stream_peek(s, max); + return open_memory_stream(probe.start, probe.len); +} + +#define PROBE_SIZE FFMIN(32 * 1024, STREAM_MAX_BUFFER_SIZE) + +static int d_check_file(struct demuxer *demuxer) +{ + struct stream *ps = read_probe_stream(demuxer->stream, PROBE_SIZE); + + struct subreader sr; + bool res = subreader_autodetect(ps, demuxer->opts, &sr); + + free_stream(ps); + + if (!res) + return 0; + + sub_data *sd = sub_read_file(demuxer->stream, &sr); + if (!sd) + return 0; + + struct priv *p = talloc_zero(demuxer, struct priv); + demuxer->priv = p; + + p->sh = new_sh_stream(demuxer, STREAM_SUB); + p->sh->codec = sd->codec; + p->sh->sub->frame_based = !sd->sub_uses_time; + p->sh->sub->is_utf8 = sr.args.utf16 != 0; // converted from utf-16 -> utf-8 + + add_sub_data(demuxer, sd); + subdata_free(sd); + + demuxer->accurate_seek = true; + + return DEMUXER_TYPE_SUBREADER; +} + +static int d_fill_buffer(struct demuxer *demuxer, struct demux_stream *ds) +{ + struct priv *p = demuxer->priv; + struct demux_packet *dp = demux_packet_list_fill(p->pkts, p->num_pkts, + &p->current); + return demuxer_add_packet(demuxer, p->sh, dp); +} + +static void d_seek(struct demuxer *demuxer, float secs, float audio_delay, + int flags) +{ + struct priv *p = demuxer->priv; + demux_packet_list_seek(p->pkts, p->num_pkts, &p->current, secs, flags); +} + +static int d_control(struct demuxer *demuxer, int cmd, void *arg) +{ + struct priv *p = demuxer->priv; + switch (cmd) { + case DEMUXER_CTRL_CORRECT_PTS: + return DEMUXER_CTRL_OK; + case DEMUXER_CTRL_GET_TIME_LENGTH: + *((double *) arg) = demux_packet_list_duration(p->pkts, p->num_pkts); + return DEMUXER_CTRL_OK; + default: + return DEMUXER_CTRL_NOTIMPL; + } +} + +const struct demuxer_desc demuxer_desc_subreader = { + .info = "Deprecated MPlayer subtitle reader", + .name = "subreader", + .shortdesc = "Deprecated Subreader", + .author = "", + .comment = "", + .type = DEMUXER_TYPE_SUBREADER, + .safe_check = 1, + .check_file = d_check_file, + .fill_buffer = d_fill_buffer, + .seek = d_seek, + .control = d_control, +}; diff --git a/demux/stheader.h b/demux/stheader.h index 421dfaf857..58caa27dcd 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -163,8 +163,9 @@ typedef struct sh_sub { SH_COMMON unsigned char *extradata; // extra header data passed from demuxer int extradata_len; + int frame_based; // timestamps are frame-based + bool is_utf8; // if false, subtitle packet charset is unknown struct ass_track *track; // loaded by libass - struct sub_data *sub_data; // loaded by subreader.c struct dec_sub *dec_sub; // decoder context } sh_sub_t; diff --git a/stream/stream.c b/stream/stream.c index 5e27e5827c..6b015ff2fd 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -136,7 +136,7 @@ static const stream_info_t *const auto_open_streams[] = { NULL }; -static stream_t *new_stream(void); +static stream_t *new_stream(size_t min_size); static int stream_seek_unbuffered(stream_t *s, int64_t newpos); static stream_t *open_stream_plugin(const stream_info_t *sinfo, @@ -166,7 +166,7 @@ static stream_t *open_stream_plugin(const stream_info_t *sinfo, } } } - s = new_stream(); + s = new_stream(0); s->opts = options; s->url = strdup(filename); s->flags |= mode; @@ -378,7 +378,9 @@ static int stream_read_unbuffered(stream_t *s, void *buf, int len) default: len = s->fill_buffer ? s->fill_buffer(s, buf, len) : 0; } - if (len <= 0) { + if (len < 0) + len = 0; + if (len == 0) { // do not retry if this looks like proper eof if (s->eof || (s->end_pos && s->pos == s->end_pos)) goto eof_out; @@ -402,35 +404,16 @@ eof_out: return len; } -// This works like stdio's ungetc(), but for more than one byte. Rewind the -// file position by buffer_size, and make all future reads/buffer fills read -// from the given buffer, until the buffer is exhausted or a seek outside of -// the buffer happens. -// You can unread at most STREAM_MAX_BUFFER_SIZE bytes. -void stream_unread_buffer(stream_t *s, void *buffer, size_t buffer_size) -{ - assert(stream_tell(s) >= buffer_size); // can't unread to before file start - assert(buffer_size <= STREAM_MAX_BUFFER_SIZE); - // Need to include the remaining buffer to ensure no data is lost. - int remainder = s->buf_len - s->buf_pos; - // Successive buffer unreading might trigger this. - assert(buffer_size + remainder <= TOTAL_BUFFER_SIZE); - memmove(&s->buffer[buffer_size], &s->buffer[s->buf_pos], remainder); - memcpy(s->buffer, buffer, buffer_size); - s->buf_pos = 0; - s->buf_len = buffer_size + remainder; -} - int stream_fill_buffer(stream_t *s) { int len = stream_read_unbuffered(s, s->buffer, STREAM_BUFFER_SIZE); s->buf_pos = 0; - s->buf_len = len < 0 ? 0 : len; + s->buf_len = len; return s->buf_len; } // Read between 1..buf_size bytes of data, return how much data has been read. -// Return <= 0 on EOF, error, of if buf_size was 0. +// Return 0 on EOF, error, of if buf_size was 0. int stream_read_partial(stream_t *s, char *buf, int buf_size) { assert(s->buf_pos <= s->buf_len); @@ -463,6 +446,39 @@ int stream_read(stream_t *s, char *mem, int total) return total - len; } +// Read ahead at most len bytes without changing the read position. Return a +// pointer to the internal buffer, starting from the current read position. +// Can read ahead at most STREAM_MAX_BUFFER_SIZE bytes. +// The returned buffer becomes invalid on the next stream call, and you must +// not write to it. +struct bstr stream_peek(stream_t *s, int len) +{ + assert(len >= 0); + assert(len <= STREAM_MAX_BUFFER_SIZE); + if (s->buf_len - s->buf_pos < len) { + // Move to front to guarantee we really can read up to max size. + int buf_valid = s->buf_len - s->buf_pos; + memmove(s->buffer, &s->buffer[s->buf_pos], buf_valid); + // Fill rest of the buffer. + while (buf_valid < len) { + int chunk = len - buf_valid; + if (s->sector_size) + chunk = STREAM_BUFFER_SIZE; + assert(buf_valid + chunk <= TOTAL_BUFFER_SIZE); + int read = stream_read_unbuffered(s, &s->buffer[buf_valid], chunk); + if (read == 0) + break; // EOF + buf_valid += read; + } + s->buf_pos = 0; + s->buf_len = buf_valid; + if (s->buf_len) + s->eof = 0; + } + return (bstr){.start = &s->buffer[s->buf_pos], + .len = FFMIN(len, s->buf_len - s->buf_pos)}; +} + int stream_write_buffer(stream_t *s, unsigned char *buf, int len) { int rd; @@ -646,9 +662,10 @@ void stream_update_size(stream_t *s) } } -static stream_t *new_stream(void) +static stream_t *new_stream(size_t min_size) { - stream_t *s = talloc_size(NULL, sizeof(stream_t) + TOTAL_BUFFER_SIZE); + min_size = FFMAX(min_size, TOTAL_BUFFER_SIZE); + stream_t *s = talloc_size(NULL, sizeof(stream_t) + min_size); memset(s, 0, sizeof(stream_t)); #if HAVE_WINSOCK2_H @@ -705,6 +722,20 @@ int stream_check_interrupt(int time) return stream_check_interrupt_cb(stream_check_interrupt_ctx, time); } +stream_t *open_memory_stream(void *data, int len) +{ + assert(len >= 0); + stream_t *s = new_stream(len); + + s->buf_pos = 0; + s->buf_len = len; + s->start_pos = 0; + s->end_pos = len; + s->pos = len; + memcpy(s->buffer, data, len); + return s; +} + int stream_enable_cache_percent(stream_t **stream, int64_t stream_cache_size, float stream_cache_min_percent, float stream_cache_seek_min_percent) @@ -737,7 +768,7 @@ int stream_enable_cache(stream_t **stream, int64_t size, int64_t min, // Can't handle a loaded buffer. orig->buf_len = orig->buf_pos = 0; - stream_t *cache = new_stream(); + stream_t *cache = new_stream(0); cache->type = STREAMTYPE_CACHE; cache->uncached_type = orig->type; cache->uncached_stream = orig; @@ -897,20 +928,27 @@ unsigned char *stream_read_line(stream_t *s, unsigned char *mem, int max, return mem; } +// Read the rest of the stream into memory (current pos to EOF), and return it. +// talloc_ctx: used as talloc parent for the returned allocation +// max_size: must be set to >0. If the file is larger than that, it is treated +// as error. This is a minor robustness measure. +// returns: stream contents, or .start/.len set to NULL on error +// If the file was empty, but no error happened, .start will be non-NULL and +// .len will be 0. +// For convenience, the returned buffer is padded with a 0 byte. The padding +// is not included in the returned length. struct bstr stream_read_complete(struct stream *s, void *talloc_ctx, - int max_size, int padding_bytes) + int max_size) { if (max_size > 1000000000) abort(); int bufsize; int total_read = 0; - int padding = FFMAX(padding_bytes, 1); + int padding = 1; char *buf = NULL; if (s->end_pos > max_size) - return (struct bstr){ - NULL, 0 - }; + return (struct bstr){NULL, 0}; if (s->end_pos > 0) bufsize = s->end_pos + padding; else @@ -923,16 +961,13 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx, break; if (bufsize > max_size) { talloc_free(buf); - return (struct bstr){ - NULL, 0 - }; + return (struct bstr){NULL, 0}; } bufsize = FFMIN(bufsize + (bufsize >> 1), max_size + padding); } buf = talloc_realloc_size(talloc_ctx, buf, total_read + padding); - return (struct bstr){ - buf, total_read - }; + memset(&buf[total_read], 0, padding); + return (struct bstr){buf, total_read}; } bool stream_manages_timeline(struct stream *s) diff --git a/stream/stream.h b/stream/stream.h index db58a2fba0..1ff8a85248 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -198,7 +198,6 @@ typedef struct stream { #endif int stream_fill_buffer(stream_t *s); -void stream_unread_buffer(stream_t *s, void *buffer, size_t buffer_size); void stream_set_capture_file(stream_t *s, const char *filename); @@ -297,22 +296,19 @@ int stream_skip(stream_t *s, int64_t len); int stream_seek(stream_t *s, int64_t pos); int stream_read(stream_t *s, char *mem, int total); int stream_read_partial(stream_t *s, char *buf, int buf_size); +struct bstr stream_peek(stream_t *s, int len); struct MPOpts; -/* - * Return allocated buffer for all data until EOF. - * If amount of data would be more than max_size return NULL as data ptr. - * Make the allocated buffer padding_bytes larger than the data read. - * Write number of bytes read at *amount_read. - */ + struct bstr stream_read_complete(struct stream *s, void *talloc_ctx, - int max_size, int padding_bytes); + int max_size); int stream_control(stream_t *s, int cmd, void *arg); void stream_update_size(stream_t *s); void free_stream(stream_t *s); stream_t *open_stream(const char *filename, struct MPOpts *options, int *file_format); stream_t *open_output_stream(const char *filename, struct MPOpts *options); +stream_t *open_memory_stream(void *data, int len); struct demux_stream; /// Set the callback to be used by libstream to check for user diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 258dd57688..5e87223041 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -33,7 +33,6 @@ #include "core/mp_msg.h" #include "core/path.h" #include "ass_mp.h" -#include "subreader.h" #include "sub/sub.h" #include "stream/stream.h" #include "core/options.h" @@ -113,34 +112,6 @@ ASS_Track *mp_ass_default_track(ASS_Library *library, struct MPOpts *opts) return track; } -ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname, - char *charset) -{ - ASS_Track *track; - - struct stream *s = open_stream(fname, NULL, NULL); - if (!s) - // Stream code should have printed an error already - return NULL; - struct bstr content = stream_read_complete(s, NULL, 100000000, 1); - if (content.start == NULL) - mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file " - "larger than 100 MB: %s\n", fname); - free_stream(s); - if (content.len == 0) { - talloc_free(content.start); - return NULL; - } - content.start[content.len] = 0; - track = ass_read_memory(library, content.start, content.len, charset); - if (track) { - free(track->name); - track->name = strdup(fname); - } - talloc_free(content.start); - return track; -} - void mp_ass_configure(ASS_Renderer *priv, struct MPOpts *opts, struct mp_osd_res *dim) { diff --git a/sub/ass_mp.h b/sub/ass_mp.h index 9f40b34166..c0fba934eb 100644 --- a/sub/ass_mp.h +++ b/sub/ass_mp.h @@ -25,7 +25,6 @@ #include #include "config.h" -#include "subreader.h" // This is probably arbitrary. // sd_lavc_conv might indirectly still assume this PlayResY, though. @@ -50,8 +49,6 @@ void mp_ass_set_style(ASS_Style *style, int res_y, struct osd_style_opts *opts); void mp_ass_add_default_styles(ASS_Track *track, struct MPOpts *opts); ASS_Track *mp_ass_default_track(ASS_Library *library, struct MPOpts *opts); -ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname, - char *charset); struct MPOpts; void mp_ass_configure(ASS_Renderer *priv, struct MPOpts *opts, diff --git a/sub/dec_sub.c b/sub/dec_sub.c index b72630470c..a1392017a2 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -18,16 +18,17 @@ #include #include +#include #include #include "config.h" -#include "demux/stheader.h" +#include "demux/demux.h" #include "sd.h" #include "sub.h" #include "dec_sub.h" -#include "subreader.h" #include "core/options.h" #include "core/mp_msg.h" +#include "core/charset_conv.h" extern const struct sd_functions sd_ass; extern const struct sd_functions sd_lavc; @@ -35,6 +36,7 @@ extern const struct sd_functions sd_spu; extern const struct sd_functions sd_movtext; extern const struct sd_functions sd_srt; extern const struct sd_functions sd_microdvd; +extern const struct sd_functions sd_lavf_srt; extern const struct sd_functions sd_lavc_conv; static const struct sd_functions *sd_list[] = { @@ -46,6 +48,7 @@ static const struct sd_functions *sd_list[] = { &sd_movtext, &sd_srt, &sd_microdvd, + &sd_lavf_srt, &sd_lavc_conv, NULL }; @@ -56,10 +59,18 @@ struct dec_sub { struct MPOpts *opts; struct sd init_sd; + double video_fps; + const char *charset; + struct sd *sd[MAX_NUM_SD]; int num_sd; }; +struct packet_list { + struct demux_packet **packets; + int num_packets; +}; + struct dec_sub *sub_create(struct MPOpts *opts) { struct dec_sub *sub = talloc_zero(NULL, struct dec_sub); @@ -102,6 +113,11 @@ void sub_set_video_res(struct dec_sub *sub, int w, int h) sub->init_sd.sub_video_h = h; } +void sub_set_video_fps(struct dec_sub *sub, double fps) +{ + sub->video_fps = fps; +} + void sub_set_extradata(struct dec_sub *sub, void *data, int data_len) { sub->init_sd.extradata = data_len ? talloc_memdup(sub, data, data_len) : NULL; @@ -120,74 +136,12 @@ static void print_chain(struct dec_sub *sub) mp_msg(MSGT_OSD, MSGL_V, "Subtitle filter chain: "); for (int n = 0; n < sub->num_sd; n++) { struct sd *sd = sub->sd[n]; - mp_msg(MSGT_OSD, MSGL_V, "%s%s (%s)", n > 0 ? " -> " : "", + mp_msg(MSGT_OSD, MSGL_V, "%s%s (%s)", n > 0 ? " -> " : "", sd->driver->name, sd->codec); } mp_msg(MSGT_OSD, MSGL_V, "\n"); } -// Subtitles read with subreader.c -static void read_sub_data(struct dec_sub *sub, struct sub_data *subdata) -{ - assert(sub_accept_packets_in_advance(sub)); - char *temp = NULL; - - struct sd *sd = sub_get_last_sd(sub); - - sd->no_remove_duplicates = true; - - for (int i = 0; i < subdata->sub_num; i++) { - subtitle *st = &subdata->subtitles[i]; - // subdata is in 10 ms ticks, pts is in seconds - double t = subdata->sub_uses_time ? 0.01 : (1 / subdata->fallback_fps); - - int len = 0; - for (int j = 0; j < st->lines; j++) - len += st->text[j] ? strlen(st->text[j]) : 0; - - len += 2 * st->lines; // '\N', including the one after the last line - len += 6; // {\anX} - len += 1; // '\0' - - if (talloc_get_size(temp) < len) { - talloc_free(temp); - temp = talloc_array(NULL, char, len); - } - - char *p = temp; - char *end = p + len; - - if (st->alignment) - p += snprintf(p, end - p, "{\\an%d}", st->alignment); - - for (int j = 0; j < st->lines; j++) - p += snprintf(p, end - p, "%s\\N", st->text[j]); - - if (st->lines > 0) - p -= 2; // remove last "\N" - *p = 0; - - struct demux_packet pkt = {0}; - pkt.pts = st->start * t; - pkt.duration = (st->end - st->start) * t; - pkt.buffer = temp; - pkt.len = strlen(temp); - - sub_decode(sub, &pkt); - } - - // Hack for broken FFmpeg packet format: make sd_ass keep the subtitle - // events on reset(), even though broken FFmpeg ASS packets were received - // (from sd_lavc_conv.c). Normally, these events are removed on seek/reset, - // but this is obviously unwanted in this case. - if (sd && sd->driver->fix_events) - sd->driver->fix_events(sd); - - sd->no_remove_duplicates = false; - - talloc_free(temp); -} - static int sub_init_decoder(struct dec_sub *sub, struct sd *sd) { sd->driver = NULL; @@ -230,8 +184,6 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh) // Try adding new converters until a decoder is reached if (sd->driver->get_bitmaps || sd->driver->get_text) { print_chain(sub); - if (sh->sub_data) - read_sub_data(sub, sh->sub_data); return; } init_sd = (struct sd) { @@ -249,32 +201,245 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh) sh->gsh->codec ? sh->gsh->codec : ""); } -bool sub_accept_packets_in_advance(struct dec_sub *sub) +static struct demux_packet *get_decoded_packet(struct sd *sd) { - // Converters are assumed to always accept packets in advance - struct sd *sd = sub_get_last_sd(sub); - return sd && sd->driver->accept_packets_in_advance; + return sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL; } -static void decode_next(struct dec_sub *sub, int n, struct demux_packet *packet) +static void decode_chain(struct sd **sd, int num_sd, struct demux_packet *packet) { - struct sd *sd = sub->sd[n]; - sd->driver->decode(sd, packet); - if (n + 1 >= sub->num_sd || !sd->driver->get_converted) + if (num_sd == 0) return; - while (1) { - struct demux_packet *next = - sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL; - if (!next) - break; - decode_next(sub, n + 1, next); + struct sd *dec = sd[0]; + dec->driver->decode(dec, packet); + if (num_sd > 1) { + while (1) { + struct demux_packet *next = get_decoded_packet(dec); + if (!next) + break; + decode_chain(sd + 1, num_sd - 1, next); + } + } +} + +static struct demux_packet *recode_packet(struct demux_packet *in, + const char *charset) +{ + struct demux_packet *pkt = NULL; + bstr in_buf = {in->buffer, in->len}; + bstr conv = mp_iconv_to_utf8(in_buf, charset, MP_ICONV_VERBOSE); + if (conv.start && conv.start != in_buf.start) { + pkt = talloc_ptrtype(NULL, pkt); + talloc_steal(pkt, conv.start); + *pkt = (struct demux_packet) { + .buffer = conv.start, + .len = conv.len, + .pts = in->pts, + .duration = in->duration, + .avpacket = in->avpacket, // questionable, but gives us sidedata + }; + } + return pkt; +} + +static void decode_chain_recode(struct dec_sub *sub, struct sd **sd, int num_sd, + struct demux_packet *packet) +{ + if (num_sd > 0) { + struct demux_packet *recoded = NULL; + if (sub->charset) + recoded = recode_packet(packet, sub->charset); + decode_chain(sd, num_sd, recoded ? recoded : packet); } } void sub_decode(struct dec_sub *sub, struct demux_packet *packet) { - if (sub->num_sd > 0) - decode_next(sub, 0, packet); + decode_chain_recode(sub, sub->sd, sub->num_sd, packet); +} + +static const char *guess_sub_cp(struct packet_list *subs, const char *usercp) +{ + if (!mp_charset_requires_guess(usercp)) + return usercp; + + // Concat all subs into a buffer. We can't probably do much better without + // having the original data (which we don't, not anymore). + int max_size = 2 * 1024 * 1024; + const char *sep = "\n\n"; // In utf-16: U+0A0A GURMUKHI LETTER UU + int sep_len = strlen(sep); + int num_pkt = 0; + int size = 0; + for (int n = 0; n < subs->num_packets; n++) { + struct demux_packet *pkt = subs->packets[n]; + if (size + pkt->len > max_size) + break; + size += pkt->len + sep_len; + num_pkt++; + } + bstr text = {talloc_size(NULL, size), 0}; + for (int n = 0; n < num_pkt; n++) { + struct demux_packet *pkt = subs->packets[n]; + memcpy(text.start + text.len, pkt->buffer, pkt->len); + memcpy(text.start + text.len + pkt->len, sep, sep_len); + text.len += pkt->len + sep_len; + } + const char *guess = mp_charset_guess(text, usercp); + talloc_free(text.start); + return guess; +} + +static void multiply_timings(struct packet_list *subs, double factor) +{ + for (int n = 0; n < subs->num_packets; n++) { + struct demux_packet *pkt = subs->packets[n]; + if (pkt->pts != MP_NOPTS_VALUE) + pkt->pts *= factor; + if (pkt->duration > 0) + pkt->duration *= factor; + } +} + +// Remove overlaps and fill gaps between adjacent subtitle packets. This is done +// by adjusting the duration of the earlier packet. If the gaps or overlap are +// larger than the threshold, or if the durations are close to the threshold, +// don't change the events. +// The algorithm is maximally naive and doesn't work if there are multiple +// overlapping lines. (It's not worth the trouble.) +static void fix_overlaps_and_gaps(struct packet_list *subs) +{ + double threshold = 0.2; // up to 200 ms overlaps or gaps are removed + double keep = threshold * 2;// don't change timings if durations are smaller + for (int i = 0; i < subs->num_packets - 1; i++) { + struct demux_packet *cur = subs->packets[i]; + struct demux_packet *next = subs->packets[i + 1]; + if (cur->pts != MP_NOPTS_VALUE && cur->duration > 0 && + next->pts != MP_NOPTS_VALUE && next->duration > 0) + { + double end = cur->pts + cur->duration; + if (fabs(next->pts - end) <= threshold && cur->duration >= keep && + next->duration >= keep) + { + cur->duration = next->pts - cur->pts; + } + } + } +} + +static void add_sub_list(struct dec_sub *sub, int at, struct packet_list *subs) +{ + struct sd *sd = sub_get_last_sd(sub); + assert(sd); + + sd->no_remove_duplicates = true; + + for (int n = 0; n < subs->num_packets; n++) + decode_chain_recode(sub, sub->sd + at, sub->num_sd - at, subs->packets[n]); + + // Hack for broken FFmpeg packet format: make sd_ass keep the subtitle + // events on reset(), even if broken FFmpeg ASS packets were received + // (from sd_lavc_conv.c). Normally, these events are removed on seek/reset, + // but this is obviously unwanted in this case. + if (sd->driver->fix_events) + sd->driver->fix_events(sd); + + sd->no_remove_duplicates = false; +} + +static void add_packet(struct packet_list *subs, struct demux_packet *pkt) +{ + pkt = demux_copy_packet(pkt); + talloc_steal(subs, pkt); + MP_TARRAY_APPEND(subs, subs->packets, subs->num_packets, pkt); +} + +// Read all packets from the demuxer and decode/add them. Returns false if +// there are circumstances which makes this not possible. +bool sub_read_all_packets(struct dec_sub *sub, struct sh_sub *sh) +{ + struct MPOpts *opts = sub->opts; + + if (!sub_accept_packets_in_advance(sub) || sh->track || sub->num_sd < 1) + return false; + + struct packet_list *subs = talloc_zero(NULL, struct packet_list); + + // In some cases, we want to put the packets through a decoder first. + // Preprocess until sub->sd[preprocess]. + int preprocess = 0; + + // movtext is currently the only subtitle format that has text output, + // but binary input. Do charset conversion after converting to text. + if (sub->sd[0]->driver == &sd_movtext) + preprocess = 1; + + // Broken Libav libavformat srt packet format (fix timestamps first). + if (sub->sd[0]->driver == &sd_lavf_srt) + preprocess = 1; + + for (;;) { + ds_get_next_pts(sh->ds); + struct demux_packet *pkt = ds_get_packet_sub(sh->ds); + if (!pkt) + break; + if (preprocess) { + decode_chain(sub->sd, preprocess, pkt); + while (1) { + pkt = get_decoded_packet(sub->sd[preprocess - 1]); + if (!pkt) + break; + add_packet(subs, pkt); + } + } else { + add_packet(subs, pkt); + } + } + + if (opts->sub_cp && !sh->is_utf8) + sub->charset = guess_sub_cp(subs, opts->sub_cp); + + if (sub->charset) + mp_msg(MSGT_OSD, MSGL_INFO, "Using subtitle charset: %s\n", sub->charset); + + double sub_speed = 1.0; + + // 23.976 FPS is used as default timebase for frame based formats + if (sub->video_fps && sh->frame_based) + sub_speed *= sub->video_fps / 23.976; + + if (opts->sub_fps && sub->video_fps) + sub_speed *= opts->sub_fps / sub->video_fps; + + sub_speed *= opts->sub_speed; + + if (sub_speed != 1.0) + multiply_timings(subs, sub_speed); + + if (!opts->suboverlap_enabled) + fix_overlaps_and_gaps(subs); + + if (sh->gsh->codec && strcmp(sh->gsh->codec, "microdvd") == 0) { + // The last subtitle event in MicroDVD subs can have duration unset, + // which means show the subtitle until end of video. + // See FFmpeg FATE MicroDVD_capability_tester.sub + if (subs->num_packets) { + struct demux_packet *last = subs->packets[subs->num_packets - 1]; + if (last->duration <= 0) + last->duration = 10; // arbitrary + } + } + + add_sub_list(sub, preprocess, subs); + + talloc_free(subs); + return true; +} + +bool sub_accept_packets_in_advance(struct dec_sub *sub) +{ + // Converters are assumed to always accept packets in advance + struct sd *sd = sub_get_last_sd(sub); + return sd && sd->driver->accept_packets_in_advance; } void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts, diff --git a/sub/dec_sub.h b/sub/dec_sub.h index 805a87ef5c..c285449f94 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -20,6 +20,7 @@ struct dec_sub *sub_create(struct MPOpts *opts); void sub_destroy(struct dec_sub *sub); void sub_set_video_res(struct dec_sub *sub, int w, int h); +void sub_set_video_fps(struct dec_sub *sub, double fps); void sub_set_extradata(struct dec_sub *sub, void *data, int data_len); void sub_set_ass_renderer(struct dec_sub *sub, struct ass_library *ass_library, struct ass_renderer *ass_renderer); @@ -27,6 +28,7 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh); bool sub_is_initialized(struct dec_sub *sub); +bool sub_read_all_packets(struct dec_sub *sub, struct sh_sub *sh); bool sub_accept_packets_in_advance(struct dec_sub *sub); void sub_decode(struct dec_sub *sub, struct demux_packet *packet); void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts, diff --git a/sub/sd_ass.c b/sub/sd_ass.c index c46c55c1ab..9c51398f33 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -32,6 +32,10 @@ #include "ass_mp.h" #include "sd.h" +// Enable code that treats subtitle events with duration 0 specially, and +// adjust their duration so that they will disappear with the next event. +#define INCOMPLETE_EVENTS 0 + struct sd_ass_priv { struct ass_track *ass_track; bool vsfilter_aspect; @@ -41,17 +45,14 @@ struct sd_ass_priv { char last_text[500]; }; -static bool is_native_ass(const char *t) -{ - return strcmp(t, "ass") == 0 || strcmp(t, "ssa") == 0; -} - static bool supports_format(const char *format) { // ass-text is produced by converters and the subreader.c ssa parser; this // format has ASS tags, but doesn't start with any prelude, nor does it // have extradata. - return format && (is_native_ass(format) || strcmp(format, "ass-text") == 0); + return format && (strcmp(format, "ass") == 0 || + strcmp(format, "ssa") == 0 || + strcmp(format, "ass-text") == 0); } static void free_last_event(ASS_Track *track) @@ -64,7 +65,7 @@ static void free_last_event(ASS_Track *track) static int init(struct sd *sd) { struct MPOpts *opts = sd->opts; - if (!sd->ass_library || !sd->ass_renderer) + if (!sd->ass_library || !sd->ass_renderer || !sd->codec) return -1; bool is_converted = sd->converted_from != NULL; @@ -99,16 +100,15 @@ static void decode(struct sd *sd, struct demux_packet *packet) unsigned char *text = data; struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; - if (is_native_ass(sd->codec)) { - if (bstr_startswith0((bstr){data, data_len}, "Dialogue: ")) { - // broken ffmpeg ASS packet format - ctx->flush_on_seek = true; - ass_process_data(track, data, data_len); - } else { - ass_process_chunk(track, data, data_len, - (long long)(pts*1000 + 0.5), - (long long)(duration*1000 + 0.5)); - } + if (strcmp(sd->codec, "ass") == 0) { + ass_process_chunk(track, data, data_len, + (long long)(pts*1000 + 0.5), + (long long)(duration*1000 + 0.5)); + return; + } else if (strcmp(sd->codec, "ssa") == 0) { + // broken ffmpeg ASS packet format + ctx->flush_on_seek = true; + ass_process_data(track, data, data_len); return; } // plaintext subs @@ -118,6 +118,7 @@ static void decode(struct sd *sd, struct demux_packet *packet) } long long ipts = pts * 1000 + 0.5; long long iduration = duration * 1000 + 0.5; +#if INCOMPLETE_EVENTS if (ctx->incomplete_event) { ctx->incomplete_event = false; ASS_Event *event = track->events + track->n_events - 1; @@ -148,6 +149,21 @@ static void decode(struct sd *sd, struct demux_packet *packet) iduration = 10000; ctx->incomplete_event = true; } +#else + if (duration <= 0) { + mp_msg(MSGT_SUBREADER, MSGL_WARN, "Subtitle without duration or " + "duration set to 0 at pts %f, ignored\n", pts); + return; + } + if (!sd->no_remove_duplicates) { + for (int i = 0; i < track->n_events; i++) { + if (track->events[i].Start == ipts + && (track->events[i].Duration == iduration) + && strcmp(track->events[i].Text, text) == 0) + return; // We've already added this subtitle + } + } +#endif int eid = ass_alloc_event(track); ASS_Event *event = track->events + eid; event->Start = ipts; @@ -259,8 +275,11 @@ static char *get_text(struct sd *sd, double pts) if (event->Text) { int start = b.len; ass_to_plaintext(&b, event->Text); - if (!is_whitespace_only(&b.start[b.len], b.len - start)) + if (is_whitespace_only(&b.start[start], b.len - start)) { + b.len = start; + } else { append(&b, '\n'); + } } } } diff --git a/sub/sd_lavc_conv.c b/sub/sd_lavc_conv.c index 1fc0262f96..4f24e20709 100644 --- a/sub/sd_lavc_conv.c +++ b/sub/sd_lavc_conv.c @@ -87,7 +87,7 @@ static int init(struct sd *sd) avctx->time_base = (AVRational) {1, 1000}; priv->avctx = avctx; sd->priv = priv; - sd->output_codec = "ass"; + sd->output_codec = "ssa"; sd->output_extradata = avctx->subtitle_header; sd->output_extradata_len = avctx->subtitle_header_size; if (sd->output_extradata) { diff --git a/sub/sd_lavf_srt.c b/sub/sd_lavf_srt.c new file mode 100644 index 0000000000..0d34b489c7 --- /dev/null +++ b/sub/sd_lavf_srt.c @@ -0,0 +1,94 @@ +/* + * This file is part of mpv. + * + * SRT timestamp parsing code lifted from FFmpeg srtdec.c (LGPL). + * + * 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 . + */ + +#include +#include +#include + +#include "core/bstr.h" +#include "sd.h" + +/* + * Background: + * + * Libav's .srt demuxer outputs packets that contain parts of the subtitle + * event header. Also, the packet duration is not set (they don't parse it + * on the demuxer side). As a result, the srt demuxer is useless. + * + * However, we can fix it by parsing the header, which spares us from writing + * a full SRT demuxer. + * + * Newer versions of FFmpeg do not have this problem. To avoid compatibility + * problems, they changed the codec name from "srt" to "subrip". + * + * Summary: this is a hack for broken SRT stuff in Libav. + * + */ + +static bool supports_format(const char *format) +{ + return format && strcmp(format, "srt") == 0; +} + +static int init(struct sd *sd) +{ + sd->output_codec = "subrip"; + return 0; +} + +static bool parse_pts(bstr header, double *duration) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "%.*s", BSTR_P(header)); + int hh1, mm1, ss1, ms1; + int hh2, mm2, ss2, ms2; + if (sscanf(buf, "%d:%2d:%2d%*1[,.]%3d --> %d:%2d:%2d%*1[,.]%3d", + &hh1, &mm1, &ss1, &ms1, &hh2, &mm2, &ss2, &ms2) >= 8) + { + int64_t start = (hh1*3600LL + mm1*60LL + ss1) * 1000LL + ms1; + int64_t end = (hh2*3600LL + mm2*60LL + ss2) * 1000LL + ms2; + *duration = (end - start) / 1000.0; + return true; + } + return false; +} + +static void decode(struct sd *sd, struct demux_packet *packet) +{ + bstr data = {packet->buffer, packet->len}; + // Remove the broken header. It's usually on the second or first line. + bstr left = data; + while (left.len) { + bstr line = bstr_getline(left, &left); + if (parse_pts(line, &packet->duration)) { + data = left; + break; + } + } + sd_conv_add_packet(sd, data.start, data.len, packet->pts, packet->duration); +} + +const struct sd_functions sd_lavf_srt = { + .name = "lavf_srt", + .supports_format = supports_format, + .init = init, + .decode = decode, + .get_converted = sd_conv_def_get_converted, + .reset = sd_conv_def_reset, +}; diff --git a/sub/sd_movtext.c b/sub/sd_movtext.c index a6ef120ec7..3038a4c132 100644 --- a/sub/sd_movtext.c +++ b/sub/sd_movtext.c @@ -42,7 +42,8 @@ static void decode(struct sd *sd, struct demux_packet *packet) return; len = FFMIN(len - 2, AV_RB16(data)); data += 2; - sd_conv_add_packet(sd, data, len, packet->pts, packet->duration); + if (len > 0) + sd_conv_add_packet(sd, data, len, packet->pts, packet->duration); } const struct sd_functions sd_movtext = { diff --git a/sub/sub.c b/sub/sub.c index a0965dc1ec..7eb32d5885 100644 --- a/sub/sub.c +++ b/sub/sub.c @@ -37,7 +37,6 @@ #include "dec_sub.h" #include "img_convert.h" #include "draw_bmp.h" -#include "subreader.h" #include "video/mp_image.h" #include "video/mp_image_pool.h" diff --git a/sub/subreader.h b/sub/subreader.h deleted file mode 100644 index 3b2e53efd8..0000000000 --- a/sub/subreader.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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_SUBREADER_H -#define MPLAYER_SUBREADER_H - -#include -#include - -#include "config.h" - -// subtitle formats -#define SUB_INVALID -1 -#define SUB_MICRODVD 0 -#define SUB_SUBRIP 1 -#define SUB_SUBVIEWER 2 -#define SUB_SAMI 3 -#define SUB_VPLAYER 4 -#define SUB_RT 5 -#define SUB_SSA 6 -#define SUB_PJS 7 -#define SUB_MPSUB 8 -#define SUB_AQTITLE 9 -#define SUB_SUBVIEWER2 10 -#define SUB_SUBRIP09 11 -#define SUB_JACOSUB 12 -#define SUB_MPL2 13 - -#define SUB_MAX_TEXT 12 -#define SUB_ALIGNMENT_BOTTOMLEFT 1 -#define SUB_ALIGNMENT_BOTTOMCENTER 2 -#define SUB_ALIGNMENT_BOTTOMRIGHT 3 -#define SUB_ALIGNMENT_MIDDLELEFT 4 -#define SUB_ALIGNMENT_MIDDLECENTER 5 -#define SUB_ALIGNMENT_MIDDLERIGHT 6 -#define SUB_ALIGNMENT_TOPLEFT 7 -#define SUB_ALIGNMENT_TOPCENTER 8 -#define SUB_ALIGNMENT_TOPRIGHT 9 - -typedef struct subtitle { - - int lines; - - unsigned long start; - unsigned long end; - - char *text[SUB_MAX_TEXT]; - unsigned char alignment; -} subtitle; - -typedef struct sub_data { - const char *codec; - subtitle *subtitles; - char *filename; - int sub_uses_time; - int sub_num; // number of subtitle structs - int sub_errs; - double fallback_fps; -} sub_data; - -struct MPOpts; -sub_data* sub_read_file (char *filename, float pts, struct MPOpts *opts); - -#endif /* MPLAYER_SUBREADER_H */ diff --git a/video/out/gl_lcms.c b/video/out/gl_lcms.c index 6cded6cd09..69f7c7fa12 100644 --- a/video/out/gl_lcms.c +++ b/video/out/gl_lcms.c @@ -87,7 +87,7 @@ static struct bstr load_file(void *talloc_ctx, const char *filename) struct bstr res = {0}; stream_t *s = open_stream(filename, NULL, NULL); if (s) { - res = stream_read_complete(s, talloc_ctx, 1000000000, 0); + res = stream_read_complete(s, talloc_ctx, 1000000000); free_stream(s); } return res;