1
mirror of https://github.com/mpv-player/mpv synced 2024-08-24 07:21:49 +02:00

Merge branch 'sub_mess2'

...the return.
This commit is contained in:
wm4 2013-06-25 00:43:04 +02:00
commit 403a266d46
37 changed files with 1348 additions and 924 deletions

View File

@ -128,6 +128,7 @@ Command line switches
-dumpstream --stream-dump=<filename>
-capture --stream-capture=<filename>
-stop-xscreensaver --stop-screensaver
-subfile --sub
=================================== ===================================
*NOTE*: ``-opt val`` becomes ``--opt=val``.

View File

@ -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=<path1:path2:...>
Specify extra directories where to search for subtitles matching the
@ -2035,9 +2031,9 @@
``--subcp=enca:<language>:<fallback codepage>``
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:<language>:<fallback codepage>``
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=<sec>
Delays subtitles by <sec> seconds. Can be negative.
--subfile=<filename>
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=<rate>
Specify the framerate of the subtitle file (default: movie fps).
*NOTE*: <rate> > 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=<n>
Specify the software scaler algorithm to be used with ``--vf=scale``. This
also affects video output drivers which lack hardware acceleration,

View File

@ -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

View File

@ -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 \

21
configure vendored
View File

@ -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

266
core/charset_conv.c Normal file
View File

@ -0,0 +1,266 @@
/*
* This file is part of mpv.
*
* Based on code taken from libass (ISC license), which was originally part
* of MPlayer (GPL).
* Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include "config.h"
#include "core/mp_msg.h"
#ifdef CONFIG_ENCA
#include <enca.h>
#endif
#ifdef CONFIG_LIBGUESS
#include <libguess.h>
#endif
#ifdef CONFIG_ICONV
#include <iconv.h>
#endif
#include "charset_conv.h"
// Split the string on ':' into components.
// out_arr is at least max entries long.
// Return number of out_arr entries filled.
static int split_colon(const char *user_cp, int max, bstr *out_arr)
{
if (!user_cp || max < 1)
return 0;
int count = 0;
while (1) {
const char *next = strchr(user_cp, ':');
if (next && max - count > 1) {
out_arr[count++] = (bstr){(char *)user_cp, next - user_cp};
user_cp = next + 1;
} else {
out_arr[count++] = (bstr){(char *)user_cp, strlen(user_cp)};
break;
}
}
return count;
}
// Returns true if user_cp implies that calling mp_charset_guess() on the
// input data is required to determine the real codepage. This is the case
// if user_cp is not a real iconv codepage, but a magic value that requests
// for example ENCA charset auto-detection.
bool mp_charset_requires_guess(const char *user_cp)
{
bstr res[2] = {{0}};
split_colon(user_cp, 2, res);
return bstrcasecmp0(res[0], "enca") == 0 ||
bstrcasecmp0(res[0], "guess") == 0;
}
#ifdef CONFIG_ENCA
static const char *enca_guess(bstr buf, const char *language)
{
if (!language || !language[0])
language = "__"; // neutral language
const char *detected_cp = NULL;
EncaAnalyser analyser = enca_analyser_alloc(language);
if (analyser) {
enca_set_termination_strictness(analyser, 0);
EncaEncoding enc = enca_analyse_const(analyser, buf.start, buf.len);
const char *tmp = enca_charset_name(enc.charset, ENCA_NAME_STYLE_ICONV);
if (tmp && enc.charset != ENCA_CS_UNKNOWN)
detected_cp = tmp;
enca_analyser_free(analyser);
} else {
mp_msg(MSGT_SUBREADER, MSGL_ERR, "ENCA doesn't know language '%s'\n",
language);
size_t langcnt;
const char **languages = enca_get_languages(&langcnt);
mp_msg(MSGT_SUBREADER, MSGL_ERR, "ENCA supported languages:");
for (int i = 0; i < langcnt; i++)
mp_msg(MSGT_SUBREADER, MSGL_ERR, " %s", languages[i]);
mp_msg(MSGT_SUBREADER, MSGL_ERR, "\n");
free(languages);
}
return detected_cp;
}
#endif
#ifdef CONFIG_LIBGUESS
static const char *libguess_guess(bstr buf, const char *language)
{
if (libguess_validate_utf8(buf.start, buf.len))
return "UTF-8";
if (!language || !language[0] || strcmp(language, "help") == 0) {
mp_msg(MSGT_SUBREADER, MSGL_ERR, "libguess needs a language: "
"japanese taiwanese chinese korean russian arabic turkish "
"greek hebrew polish baltic\n");
return NULL;
}
return libguess_determine_encoding(buf.start, buf.len, language);
}
#endif
// Runs charset auto-detection on the input buffer, and returns the result.
// If auto-detection fails, NULL is returned.
// If user_cp doesn't refer to any known auto-detection (for example because
// it's a real iconv codepage), user_cp is returned without even looking at
// the buf data.
const char *mp_charset_guess(bstr buf, const char *user_cp)
{
if (!mp_charset_requires_guess(user_cp))
return user_cp;
bstr params[3] = {{0}};
split_colon(user_cp, 3, params);
bstr type = params[0];
char lang[100];
snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1]));
const char *fallback = params[2].start; // last item, already 0-terminated
const char *res = NULL;
#ifdef CONFIG_ENCA
if (bstrcasecmp0(type, "enca") == 0)
res = enca_guess(buf, lang);
#endif
#ifdef CONFIG_LIBGUESS
if (bstrcasecmp0(type, "guess") == 0)
res = libguess_guess(buf, lang);
#endif
if (res) {
mp_msg(MSGT_SUBREADER, MSGL_DBG2, "%.*s detected charset: '%s'\n",
BSTR_P(type), res);
} else {
res = fallback;
mp_msg(MSGT_SUBREADER, MSGL_DBG2,
"Detection with %.*s failed: fallback to %s\n",
BSTR_P(type), res && res[0] ? res : "no conversion");
}
return res;
}
// Convert the data in buf to UTF-8. The charset argument can be an iconv
// codepage, a value returned by mp_charset_conv_guess(), or a special value
// that triggers autodetection of the charset (e.g. using ENCA).
// The auto-detection is the only difference to mp_iconv_to_utf8().
// buf: same as mp_iconv_to_utf8()
// user_cp: iconv codepage, special value, NULL
// flags: same as mp_iconv_to_utf8()
// returns: same as mp_iconv_to_utf8()
bstr mp_charset_guess_and_conv_to_utf8(bstr buf, const char *user_cp, int flags)
{
return mp_iconv_to_utf8(buf, mp_charset_guess(buf, user_cp), flags);
}
// Use iconv to convert buf to UTF-8.
// Returns buf.start==NULL on error. Returns buf if cp is NULL, or if there is
// obviously no conversion required (e.g. if cp is "UTF-8").
// Returns a newly allocated buffer if conversion is done and succeeds. The
// buffer will be terminated with 0 for convenience (the terminating 0 is not
// included in the returned length).
// Free the returned buffer with talloc_free().
// buf: input data
// cp: iconv codepage (or NULL)
// flags: combination of MP_ICONV_* flags
// returns: buf (no conversion), .start==NULL (error), or allocated buffer
bstr mp_iconv_to_utf8(bstr buf, const char *cp, int flags)
{
#ifdef CONFIG_ICONV
const char *tocp = "UTF-8";
if (!cp || !cp[0] || strcasecmp(cp, tocp) == 0)
return buf;
if (strcasecmp(cp, "ASCII") == 0)
return buf;
iconv_t icdsc;
if ((icdsc = iconv_open(tocp, cp)) == (iconv_t) (-1)) {
if (flags & MP_ICONV_VERBOSE)
mp_msg(MSGT_SUBREADER, MSGL_ERR,
"Error opening iconv with codepage '%s'\n", cp);
goto failure;
}
size_t size = buf.len;
size_t osize = size;
size_t ileft = size;
size_t oleft = size - 1;
char *outbuf = talloc_size(NULL, osize);
char *ip = buf.start;
char *op = outbuf;
while (1) {
int clear = 0;
size_t rc;
if (ileft)
rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
else {
clear = 1; // clear the conversion state and leave
rc = iconv(icdsc, NULL, NULL, &op, &oleft);
}
if (rc == (size_t) (-1)) {
if (errno == E2BIG) {
size_t offset = op - outbuf;
outbuf = talloc_realloc_size(NULL, outbuf, osize + size);
op = outbuf + offset;
osize += size;
oleft += size;
} else {
if (errno == EINVAL && (flags & MP_ICONV_ALLOW_CUTOFF)) {
// This is intended for cases where the input buffer is cut
// at a random byte position. If this happens in the middle
// of the buffer, it should still be an error. We say it's
// fine if the error is within 10 bytes of the end.
if (ileft <= 10)
break;
}
if (flags & MP_ICONV_VERBOSE) {
mp_msg(MSGT_SUBREADER, MSGL_ERR,
"Error recoding text with codepage '%s'\n", cp);
}
talloc_free(outbuf);
iconv_close(icdsc);
goto failure;
}
} else if (clear)
break;
}
iconv_close(icdsc);
outbuf[osize - oleft - 1] = 0;
return (bstr){outbuf, osize - oleft - 1};
#endif
failure:
return (bstr){0};
}

17
core/charset_conv.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef MP_CHARSET_CONV_H
#define MP_CHARSET_CONV_H
#include <stdbool.h>
#include "core/bstr.h"
enum {
MP_ICONV_VERBOSE = 1, // print errors instead of failing silently
MP_ICONV_ALLOW_CUTOFF = 2, // allow partial input data
};
bool mp_charset_requires_guess(const char *user_cp);
const char *mp_charset_guess(bstr buf, const char *user_cp);
bstr mp_charset_guess_and_conv_to_utf8(bstr buf, const char *user_cp, int flags);
bstr mp_iconv_to_utf8(bstr buf, const char *cp, int flags);
#endif

View File

@ -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);

View File

@ -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",

View File

@ -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);

View File

@ -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);

View File

@ -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, &params);
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);

View File

@ -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",

View File

@ -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;

View File

@ -40,7 +40,8 @@
#include "audio/format.h"
#include "libavcodec/avcodec.h"
#include <libavcodec/avcodec.h>
#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;
}

View File

@ -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 */

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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) {

121
demux/demux_libass.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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 <ass/ass.h>
#include <ass/ass_types.h>
#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,
};

View File

@ -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);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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,
};

View File

@ -28,24 +28,65 @@
#include <dirent.h>
#include <ctype.h>
#include <libavutil/common.h>
#include <libavutil/avstring.h>
#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 <enca.h>
#endif
#include "demux/demux.h"
#define ERR ((void *) -1)
#ifdef CONFIG_ICONV
#include <iconv.h>
#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, "&nbsp;", 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,
};

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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)
{

View File

@ -25,7 +25,6 @@
#include <stdbool.h>
#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,

View File

@ -18,16 +18,17 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#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 : "<unknown>");
}
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,

View File

@ -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,

View File

@ -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');
}
}
}
}

View File

@ -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) {

94
sub/sd_lavf_srt.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#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,
};

View File

@ -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 = {

View File

@ -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"

View File

@ -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 <stdio.h>
#include <stdbool.h>
#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 */

View File

@ -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;