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:
commit
403a266d46
@ -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``.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
8
Makefile
8
Makefile
@ -37,7 +37,8 @@ SOURCES-$(DVDREAD) += stream/stream_dvd.c \
|
||||
SOURCES-$(FTP) += stream/stream_ftp.c
|
||||
SOURCES-$(HAVE_SYS_MMAN_H) += audio/filter/af_export.c osdep/mmap_anon.c
|
||||
SOURCES-$(LADSPA) += audio/filter/af_ladspa.c
|
||||
SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c
|
||||
SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c \
|
||||
demux/demux_libass.c
|
||||
|
||||
SOURCES-$(LIBBLURAY) += stream/stream_bluray.c
|
||||
SOURCES-$(LIBBS2B) += audio/filter/af_bs2b.c
|
||||
@ -169,6 +170,7 @@ SOURCES = talloc.c \
|
||||
core/av_log.c \
|
||||
core/av_opts.c \
|
||||
core/bstr.c \
|
||||
core/charset_conv.c \
|
||||
core/codecs.c \
|
||||
core/command.c \
|
||||
core/cpudetect.c \
|
||||
@ -207,7 +209,7 @@ SOURCES = talloc.c \
|
||||
demux/demux_mf.c \
|
||||
demux/demux_mkv.c \
|
||||
demux/demux_mpg.c \
|
||||
demux/demux_sub.c \
|
||||
demux/demux_subreader.c \
|
||||
demux/demux_ts.c \
|
||||
demux/mp3_hdr.c \
|
||||
demux/parse_es.c \
|
||||
@ -234,13 +236,13 @@ SOURCES = talloc.c \
|
||||
sub/img_convert.c \
|
||||
sub/sd_lavc.c \
|
||||
sub/sd_lavc_conv.c \
|
||||
sub/sd_lavf_srt.c \
|
||||
sub/sd_microdvd.c \
|
||||
sub/sd_movtext.c \
|
||||
sub/sd_spu.c \
|
||||
sub/sd_srt.c \
|
||||
sub/spudec.c \
|
||||
sub/sub.c \
|
||||
sub/subreader.c \
|
||||
video/csputils.c \
|
||||
video/fmt-conversion.c \
|
||||
video/image_writer.c \
|
||||
|
21
configure
vendored
21
configure
vendored
@ -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
266
core/charset_conv.c
Normal 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
17
core/charset_conv.h
Normal 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
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
110
core/mplayer.c
110
core/mplayer.c
@ -71,7 +71,6 @@
|
||||
#include "core/mplayer.h"
|
||||
#include "core/m_property.h"
|
||||
|
||||
#include "sub/subreader.h"
|
||||
#include "sub/find_subfiles.h"
|
||||
#include "sub/dec_sub.h"
|
||||
#include "sub/sd.h"
|
||||
@ -197,9 +196,6 @@ static const char av_desync_help_text[] = _(
|
||||
|
||||
static void reset_subtitles(struct MPContext *mpctx);
|
||||
static void reinit_subs(struct MPContext *mpctx);
|
||||
static struct track *open_external_file(struct MPContext *mpctx, char *filename,
|
||||
char *demuxer_name, int stream_cache,
|
||||
enum stream_type filter);
|
||||
|
||||
static double get_relative_time(struct MPContext *mpctx)
|
||||
{
|
||||
@ -982,6 +978,9 @@ static struct track *add_stream_track(struct MPContext *mpctx,
|
||||
};
|
||||
MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
|
||||
|
||||
if (stream->type == STREAM_SUB)
|
||||
track->preloaded = !!stream->sub->track;
|
||||
|
||||
// Needed for DVD and Blu-ray.
|
||||
if (!track->lang) {
|
||||
struct stream_lang_req req = {
|
||||
@ -1028,69 +1027,6 @@ static void add_dvd_tracks(struct MPContext *mpctx)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ASS
|
||||
static int free_sub_data(void *ptr)
|
||||
{
|
||||
struct sh_sub *sh_sub = *(struct sh_sub **)ptr;
|
||||
if (sh_sub->track)
|
||||
ass_free_track(sh_sub->track);
|
||||
talloc_free(sh_sub->sub_data);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename,
|
||||
float fps, int noerr)
|
||||
{
|
||||
struct MPOpts *opts = &mpctx->opts;
|
||||
struct ass_track *asst = NULL;
|
||||
sub_data *subd = NULL;
|
||||
|
||||
if (filename == NULL)
|
||||
return NULL;
|
||||
|
||||
// Note: no text subtitles without libass. This is mainly because sd_ass is
|
||||
// used for rendering. Even when showing subtitles with term-osd, going
|
||||
// through sd_ass makes the code much simpler, as sd_ass can handle all
|
||||
// the weird special-cases.
|
||||
#ifdef CONFIG_ASS
|
||||
asst = mp_ass_read_stream(mpctx->ass_library, filename, opts->sub_cp);
|
||||
if (!asst)
|
||||
subd = sub_read_file(filename, fps, &mpctx->opts);
|
||||
if (asst || subd) {
|
||||
struct demuxer *d = new_sub_pseudo_demuxer(opts);
|
||||
assert(d->num_streams == 1);
|
||||
struct sh_stream *s = d->streams[0];
|
||||
assert(s->type == STREAM_SUB);
|
||||
|
||||
s->codec = asst ? "ass" : subd->codec;
|
||||
s->sub->track = asst;
|
||||
s->sub->sub_data = subd;
|
||||
|
||||
struct sh_sub **pptr = talloc(d, struct sh_sub*);
|
||||
*pptr = s->sub;
|
||||
talloc_set_destructor(pptr, free_sub_data);
|
||||
|
||||
struct track *t = add_stream_track(mpctx, s, false);
|
||||
t->is_external = true;
|
||||
t->preloaded = true;
|
||||
t->title = talloc_strdup(t, filename);
|
||||
t->external_filename = talloc_strdup(t, filename);
|
||||
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d);
|
||||
return t;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Used with libavformat subtitles.
|
||||
struct track *ext = open_external_file(mpctx, filename, NULL, 0, STREAM_SUB);
|
||||
if (ext)
|
||||
return ext;
|
||||
|
||||
mp_tmsg(MSGT_CPLAYER, noerr ? MSGL_WARN : MSGL_ERR,
|
||||
"Cannot load subtitles: %s\n", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mp_get_cache_percent(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->stream) {
|
||||
@ -2001,7 +1937,8 @@ static void reinit_subs(struct MPContext *mpctx)
|
||||
if (!mpctx->sh_sub->dec_sub)
|
||||
mpctx->sh_sub->dec_sub = sub_create(opts);
|
||||
|
||||
if (track->demuxer && !track->stream) {
|
||||
assert(track->demuxer);
|
||||
if (!track->stream) {
|
||||
// Lazily added DVD track - we must not miss the first subtitle packet,
|
||||
// which makes the demuxer create the sh_stream, and contains the first
|
||||
// subtitle event.
|
||||
@ -2016,7 +1953,6 @@ static void reinit_subs(struct MPContext *mpctx)
|
||||
|
||||
return;
|
||||
}
|
||||
assert(track->demuxer && track->stream);
|
||||
|
||||
mpctx->initialized_flags |= INITIALIZED_SUB;
|
||||
|
||||
@ -2027,12 +1963,22 @@ static void reinit_subs(struct MPContext *mpctx)
|
||||
if (!sub_is_initialized(dec_sub)) {
|
||||
int w = mpctx->sh_video ? mpctx->sh_video->disp_w : 0;
|
||||
int h = mpctx->sh_video ? mpctx->sh_video->disp_h : 0;
|
||||
float fps = mpctx->sh_video ? mpctx->sh_video->fps : 25;
|
||||
|
||||
set_dvdsub_fake_extradata(dec_sub, track->demuxer->stream, w, h);
|
||||
sub_set_video_res(dec_sub, w, h);
|
||||
sub_set_video_fps(dec_sub, fps);
|
||||
sub_set_ass_renderer(dec_sub, mpctx->osd->ass_library,
|
||||
mpctx->osd->ass_renderer);
|
||||
sub_init_from_sh(dec_sub, sh_sub);
|
||||
|
||||
// Don't do this if the file has video/audio streams. Don't do it even
|
||||
// if it has only sub streams, because reading packets will change the
|
||||
// demuxer position.
|
||||
if (!track->preloaded && track->is_external) {
|
||||
demux_seek(track->demuxer, 0, 0, SEEK_ABSOLUTE);
|
||||
track->preloaded = sub_read_all_packets(dec_sub, sh_sub);
|
||||
}
|
||||
}
|
||||
|
||||
mpctx->osd->dec_sub = dec_sub;
|
||||
@ -3920,16 +3866,15 @@ static void open_subtitles_from_options(struct MPContext *mpctx)
|
||||
// after reading video params we should load subtitles because
|
||||
// we know fps so now we can adjust subtitle time to ~6 seconds AST
|
||||
// check .sub
|
||||
double sub_fps = mpctx->sh_video ? mpctx->sh_video->fps : 25;
|
||||
if (mpctx->opts.sub_name) {
|
||||
for (int i = 0; mpctx->opts.sub_name[i] != NULL; ++i)
|
||||
mp_add_subtitles(mpctx, mpctx->opts.sub_name[i], sub_fps, 0);
|
||||
mp_add_subtitles(mpctx, mpctx->opts.sub_name[i], 0);
|
||||
}
|
||||
if (mpctx->opts.sub_auto) { // auto load sub file ...
|
||||
char **tmp = find_text_subtitles(&mpctx->opts, mpctx->filename);
|
||||
int nsub = MP_TALLOC_ELEMS(tmp);
|
||||
for (int i = 0; i < nsub; i++) {
|
||||
struct track *track = mp_add_subtitles(mpctx, tmp[i], sub_fps, 1);
|
||||
struct track *track = mp_add_subtitles(mpctx, tmp[i], 1);
|
||||
if (track)
|
||||
track->auto_loaded = true;
|
||||
}
|
||||
@ -3959,9 +3904,12 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
|
||||
case STREAM_SUB: ss = -1; break;
|
||||
}
|
||||
vs = -1; // avi can't go without video
|
||||
struct demuxer_params params = {
|
||||
.ass_library = mpctx->ass_library, // demux_libass requires it
|
||||
};
|
||||
struct demuxer *demuxer =
|
||||
demux_open_withparams(&mpctx->opts, stream, format, demuxer_name,
|
||||
as, vs, ss, filename, NULL);
|
||||
as, vs, ss, filename, ¶ms);
|
||||
if (!demuxer) {
|
||||
free_stream(stream);
|
||||
goto err_out;
|
||||
@ -3999,12 +3947,11 @@ static void open_audiofiles_from_options(struct MPContext *mpctx)
|
||||
opts->audio_stream_cache, STREAM_AUDIO);
|
||||
}
|
||||
|
||||
// Just for -subfile. open_subtitles_from_options handles -sub text sub files.
|
||||
static void open_subfiles_from_options(struct MPContext *mpctx)
|
||||
struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noerr)
|
||||
{
|
||||
struct MPOpts *opts = &mpctx->opts;
|
||||
open_external_file(mpctx, opts->sub_stream, opts->sub_demuxer_name,
|
||||
0, STREAM_SUB);
|
||||
return open_external_file(mpctx, filename, opts->sub_demuxer_name, 0,
|
||||
STREAM_SUB);
|
||||
}
|
||||
|
||||
static void print_timeline(struct MPContext *mpctx)
|
||||
@ -4293,13 +4240,20 @@ goto_reopen_demuxer: ;
|
||||
if (mpctx->timeline)
|
||||
timeline_set_part(mpctx, mpctx->timeline_part, true);
|
||||
|
||||
// Decide correct-pts mode based on first segment of video track
|
||||
opts->correct_pts = opts->user_correct_pts;
|
||||
if (opts->correct_pts < 0) {
|
||||
opts->correct_pts =
|
||||
demux_control(mpctx->demuxer, DEMUXER_CTRL_CORRECT_PTS,
|
||||
NULL) == DEMUXER_CTRL_OK;
|
||||
}
|
||||
|
||||
mpctx->initialized_flags |= INITIALIZED_DEMUXER;
|
||||
|
||||
add_subtitle_fonts_from_sources(mpctx);
|
||||
|
||||
open_subtitles_from_options(mpctx);
|
||||
open_audiofiles_from_options(mpctx);
|
||||
open_subfiles_from_options(mpctx);
|
||||
|
||||
check_previous_track_selection(mpctx);
|
||||
|
||||
|
@ -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",
|
||||
|
||||
|
@ -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;
|
||||
|
139
demux/demux.c
139
demux/demux.c
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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
121
demux/demux_libass.c
Normal 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,
|
||||
};
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
@ -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, " ", 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,
|
||||
};
|
@ -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;
|
||||
|
||||
|
111
stream/stream.c
111
stream/stream.c
@ -136,7 +136,7 @@ static const stream_info_t *const auto_open_streams[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static stream_t *new_stream(void);
|
||||
static stream_t *new_stream(size_t min_size);
|
||||
static int stream_seek_unbuffered(stream_t *s, int64_t newpos);
|
||||
|
||||
static stream_t *open_stream_plugin(const stream_info_t *sinfo,
|
||||
@ -166,7 +166,7 @@ static stream_t *open_stream_plugin(const stream_info_t *sinfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
s = new_stream();
|
||||
s = new_stream(0);
|
||||
s->opts = options;
|
||||
s->url = strdup(filename);
|
||||
s->flags |= mode;
|
||||
@ -378,7 +378,9 @@ static int stream_read_unbuffered(stream_t *s, void *buf, int len)
|
||||
default:
|
||||
len = s->fill_buffer ? s->fill_buffer(s, buf, len) : 0;
|
||||
}
|
||||
if (len <= 0) {
|
||||
if (len < 0)
|
||||
len = 0;
|
||||
if (len == 0) {
|
||||
// do not retry if this looks like proper eof
|
||||
if (s->eof || (s->end_pos && s->pos == s->end_pos))
|
||||
goto eof_out;
|
||||
@ -402,35 +404,16 @@ eof_out:
|
||||
return len;
|
||||
}
|
||||
|
||||
// This works like stdio's ungetc(), but for more than one byte. Rewind the
|
||||
// file position by buffer_size, and make all future reads/buffer fills read
|
||||
// from the given buffer, until the buffer is exhausted or a seek outside of
|
||||
// the buffer happens.
|
||||
// You can unread at most STREAM_MAX_BUFFER_SIZE bytes.
|
||||
void stream_unread_buffer(stream_t *s, void *buffer, size_t buffer_size)
|
||||
{
|
||||
assert(stream_tell(s) >= buffer_size); // can't unread to before file start
|
||||
assert(buffer_size <= STREAM_MAX_BUFFER_SIZE);
|
||||
// Need to include the remaining buffer to ensure no data is lost.
|
||||
int remainder = s->buf_len - s->buf_pos;
|
||||
// Successive buffer unreading might trigger this.
|
||||
assert(buffer_size + remainder <= TOTAL_BUFFER_SIZE);
|
||||
memmove(&s->buffer[buffer_size], &s->buffer[s->buf_pos], remainder);
|
||||
memcpy(s->buffer, buffer, buffer_size);
|
||||
s->buf_pos = 0;
|
||||
s->buf_len = buffer_size + remainder;
|
||||
}
|
||||
|
||||
int stream_fill_buffer(stream_t *s)
|
||||
{
|
||||
int len = stream_read_unbuffered(s, s->buffer, STREAM_BUFFER_SIZE);
|
||||
s->buf_pos = 0;
|
||||
s->buf_len = len < 0 ? 0 : len;
|
||||
s->buf_len = len;
|
||||
return s->buf_len;
|
||||
}
|
||||
|
||||
// Read between 1..buf_size bytes of data, return how much data has been read.
|
||||
// Return <= 0 on EOF, error, of if buf_size was 0.
|
||||
// Return 0 on EOF, error, of if buf_size was 0.
|
||||
int stream_read_partial(stream_t *s, char *buf, int buf_size)
|
||||
{
|
||||
assert(s->buf_pos <= s->buf_len);
|
||||
@ -463,6 +446,39 @@ int stream_read(stream_t *s, char *mem, int total)
|
||||
return total - len;
|
||||
}
|
||||
|
||||
// Read ahead at most len bytes without changing the read position. Return a
|
||||
// pointer to the internal buffer, starting from the current read position.
|
||||
// Can read ahead at most STREAM_MAX_BUFFER_SIZE bytes.
|
||||
// The returned buffer becomes invalid on the next stream call, and you must
|
||||
// not write to it.
|
||||
struct bstr stream_peek(stream_t *s, int len)
|
||||
{
|
||||
assert(len >= 0);
|
||||
assert(len <= STREAM_MAX_BUFFER_SIZE);
|
||||
if (s->buf_len - s->buf_pos < len) {
|
||||
// Move to front to guarantee we really can read up to max size.
|
||||
int buf_valid = s->buf_len - s->buf_pos;
|
||||
memmove(s->buffer, &s->buffer[s->buf_pos], buf_valid);
|
||||
// Fill rest of the buffer.
|
||||
while (buf_valid < len) {
|
||||
int chunk = len - buf_valid;
|
||||
if (s->sector_size)
|
||||
chunk = STREAM_BUFFER_SIZE;
|
||||
assert(buf_valid + chunk <= TOTAL_BUFFER_SIZE);
|
||||
int read = stream_read_unbuffered(s, &s->buffer[buf_valid], chunk);
|
||||
if (read == 0)
|
||||
break; // EOF
|
||||
buf_valid += read;
|
||||
}
|
||||
s->buf_pos = 0;
|
||||
s->buf_len = buf_valid;
|
||||
if (s->buf_len)
|
||||
s->eof = 0;
|
||||
}
|
||||
return (bstr){.start = &s->buffer[s->buf_pos],
|
||||
.len = FFMIN(len, s->buf_len - s->buf_pos)};
|
||||
}
|
||||
|
||||
int stream_write_buffer(stream_t *s, unsigned char *buf, int len)
|
||||
{
|
||||
int rd;
|
||||
@ -646,9 +662,10 @@ void stream_update_size(stream_t *s)
|
||||
}
|
||||
}
|
||||
|
||||
static stream_t *new_stream(void)
|
||||
static stream_t *new_stream(size_t min_size)
|
||||
{
|
||||
stream_t *s = talloc_size(NULL, sizeof(stream_t) + TOTAL_BUFFER_SIZE);
|
||||
min_size = FFMAX(min_size, TOTAL_BUFFER_SIZE);
|
||||
stream_t *s = talloc_size(NULL, sizeof(stream_t) + min_size);
|
||||
memset(s, 0, sizeof(stream_t));
|
||||
|
||||
#if HAVE_WINSOCK2_H
|
||||
@ -705,6 +722,20 @@ int stream_check_interrupt(int time)
|
||||
return stream_check_interrupt_cb(stream_check_interrupt_ctx, time);
|
||||
}
|
||||
|
||||
stream_t *open_memory_stream(void *data, int len)
|
||||
{
|
||||
assert(len >= 0);
|
||||
stream_t *s = new_stream(len);
|
||||
|
||||
s->buf_pos = 0;
|
||||
s->buf_len = len;
|
||||
s->start_pos = 0;
|
||||
s->end_pos = len;
|
||||
s->pos = len;
|
||||
memcpy(s->buffer, data, len);
|
||||
return s;
|
||||
}
|
||||
|
||||
int stream_enable_cache_percent(stream_t **stream, int64_t stream_cache_size,
|
||||
float stream_cache_min_percent,
|
||||
float stream_cache_seek_min_percent)
|
||||
@ -737,7 +768,7 @@ int stream_enable_cache(stream_t **stream, int64_t size, int64_t min,
|
||||
// Can't handle a loaded buffer.
|
||||
orig->buf_len = orig->buf_pos = 0;
|
||||
|
||||
stream_t *cache = new_stream();
|
||||
stream_t *cache = new_stream(0);
|
||||
cache->type = STREAMTYPE_CACHE;
|
||||
cache->uncached_type = orig->type;
|
||||
cache->uncached_stream = orig;
|
||||
@ -897,20 +928,27 @@ unsigned char *stream_read_line(stream_t *s, unsigned char *mem, int max,
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Read the rest of the stream into memory (current pos to EOF), and return it.
|
||||
// talloc_ctx: used as talloc parent for the returned allocation
|
||||
// max_size: must be set to >0. If the file is larger than that, it is treated
|
||||
// as error. This is a minor robustness measure.
|
||||
// returns: stream contents, or .start/.len set to NULL on error
|
||||
// If the file was empty, but no error happened, .start will be non-NULL and
|
||||
// .len will be 0.
|
||||
// For convenience, the returned buffer is padded with a 0 byte. The padding
|
||||
// is not included in the returned length.
|
||||
struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
||||
int max_size, int padding_bytes)
|
||||
int max_size)
|
||||
{
|
||||
if (max_size > 1000000000)
|
||||
abort();
|
||||
|
||||
int bufsize;
|
||||
int total_read = 0;
|
||||
int padding = FFMAX(padding_bytes, 1);
|
||||
int padding = 1;
|
||||
char *buf = NULL;
|
||||
if (s->end_pos > max_size)
|
||||
return (struct bstr){
|
||||
NULL, 0
|
||||
};
|
||||
return (struct bstr){NULL, 0};
|
||||
if (s->end_pos > 0)
|
||||
bufsize = s->end_pos + padding;
|
||||
else
|
||||
@ -923,16 +961,13 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
||||
break;
|
||||
if (bufsize > max_size) {
|
||||
talloc_free(buf);
|
||||
return (struct bstr){
|
||||
NULL, 0
|
||||
};
|
||||
return (struct bstr){NULL, 0};
|
||||
}
|
||||
bufsize = FFMIN(bufsize + (bufsize >> 1), max_size + padding);
|
||||
}
|
||||
buf = talloc_realloc_size(talloc_ctx, buf, total_read + padding);
|
||||
return (struct bstr){
|
||||
buf, total_read
|
||||
};
|
||||
memset(&buf[total_read], 0, padding);
|
||||
return (struct bstr){buf, total_read};
|
||||
}
|
||||
|
||||
bool stream_manages_timeline(struct stream *s)
|
||||
|
@ -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
|
||||
|
29
sub/ass_mp.c
29
sub/ass_mp.c
@ -33,7 +33,6 @@
|
||||
#include "core/mp_msg.h"
|
||||
#include "core/path.h"
|
||||
#include "ass_mp.h"
|
||||
#include "subreader.h"
|
||||
#include "sub/sub.h"
|
||||
#include "stream/stream.h"
|
||||
#include "core/options.h"
|
||||
@ -113,34 +112,6 @@ ASS_Track *mp_ass_default_track(ASS_Library *library, struct MPOpts *opts)
|
||||
return track;
|
||||
}
|
||||
|
||||
ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname,
|
||||
char *charset)
|
||||
{
|
||||
ASS_Track *track;
|
||||
|
||||
struct stream *s = open_stream(fname, NULL, NULL);
|
||||
if (!s)
|
||||
// Stream code should have printed an error already
|
||||
return NULL;
|
||||
struct bstr content = stream_read_complete(s, NULL, 100000000, 1);
|
||||
if (content.start == NULL)
|
||||
mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file "
|
||||
"larger than 100 MB: %s\n", fname);
|
||||
free_stream(s);
|
||||
if (content.len == 0) {
|
||||
talloc_free(content.start);
|
||||
return NULL;
|
||||
}
|
||||
content.start[content.len] = 0;
|
||||
track = ass_read_memory(library, content.start, content.len, charset);
|
||||
if (track) {
|
||||
free(track->name);
|
||||
track->name = strdup(fname);
|
||||
}
|
||||
talloc_free(content.start);
|
||||
return track;
|
||||
}
|
||||
|
||||
void mp_ass_configure(ASS_Renderer *priv, struct MPOpts *opts,
|
||||
struct mp_osd_res *dim)
|
||||
{
|
||||
|
@ -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,
|
||||
|
331
sub/dec_sub.c
331
sub/dec_sub.c
@ -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,
|
||||
|
@ -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,
|
||||
|
55
sub/sd_ass.c
55
sub/sd_ass.c
@ -32,6 +32,10 @@
|
||||
#include "ass_mp.h"
|
||||
#include "sd.h"
|
||||
|
||||
// Enable code that treats subtitle events with duration 0 specially, and
|
||||
// adjust their duration so that they will disappear with the next event.
|
||||
#define INCOMPLETE_EVENTS 0
|
||||
|
||||
struct sd_ass_priv {
|
||||
struct ass_track *ass_track;
|
||||
bool vsfilter_aspect;
|
||||
@ -41,17 +45,14 @@ struct sd_ass_priv {
|
||||
char last_text[500];
|
||||
};
|
||||
|
||||
static bool is_native_ass(const char *t)
|
||||
{
|
||||
return strcmp(t, "ass") == 0 || strcmp(t, "ssa") == 0;
|
||||
}
|
||||
|
||||
static bool supports_format(const char *format)
|
||||
{
|
||||
// ass-text is produced by converters and the subreader.c ssa parser; this
|
||||
// format has ASS tags, but doesn't start with any prelude, nor does it
|
||||
// have extradata.
|
||||
return format && (is_native_ass(format) || strcmp(format, "ass-text") == 0);
|
||||
return format && (strcmp(format, "ass") == 0 ||
|
||||
strcmp(format, "ssa") == 0 ||
|
||||
strcmp(format, "ass-text") == 0);
|
||||
}
|
||||
|
||||
static void free_last_event(ASS_Track *track)
|
||||
@ -64,7 +65,7 @@ static void free_last_event(ASS_Track *track)
|
||||
static int init(struct sd *sd)
|
||||
{
|
||||
struct MPOpts *opts = sd->opts;
|
||||
if (!sd->ass_library || !sd->ass_renderer)
|
||||
if (!sd->ass_library || !sd->ass_renderer || !sd->codec)
|
||||
return -1;
|
||||
|
||||
bool is_converted = sd->converted_from != NULL;
|
||||
@ -99,16 +100,15 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
unsigned char *text = data;
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
ASS_Track *track = ctx->ass_track;
|
||||
if (is_native_ass(sd->codec)) {
|
||||
if (bstr_startswith0((bstr){data, data_len}, "Dialogue: ")) {
|
||||
// broken ffmpeg ASS packet format
|
||||
ctx->flush_on_seek = true;
|
||||
ass_process_data(track, data, data_len);
|
||||
} else {
|
||||
ass_process_chunk(track, data, data_len,
|
||||
(long long)(pts*1000 + 0.5),
|
||||
(long long)(duration*1000 + 0.5));
|
||||
}
|
||||
if (strcmp(sd->codec, "ass") == 0) {
|
||||
ass_process_chunk(track, data, data_len,
|
||||
(long long)(pts*1000 + 0.5),
|
||||
(long long)(duration*1000 + 0.5));
|
||||
return;
|
||||
} else if (strcmp(sd->codec, "ssa") == 0) {
|
||||
// broken ffmpeg ASS packet format
|
||||
ctx->flush_on_seek = true;
|
||||
ass_process_data(track, data, data_len);
|
||||
return;
|
||||
}
|
||||
// plaintext subs
|
||||
@ -118,6 +118,7 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
}
|
||||
long long ipts = pts * 1000 + 0.5;
|
||||
long long iduration = duration * 1000 + 0.5;
|
||||
#if INCOMPLETE_EVENTS
|
||||
if (ctx->incomplete_event) {
|
||||
ctx->incomplete_event = false;
|
||||
ASS_Event *event = track->events + track->n_events - 1;
|
||||
@ -148,6 +149,21 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
iduration = 10000;
|
||||
ctx->incomplete_event = true;
|
||||
}
|
||||
#else
|
||||
if (duration <= 0) {
|
||||
mp_msg(MSGT_SUBREADER, MSGL_WARN, "Subtitle without duration or "
|
||||
"duration set to 0 at pts %f, ignored\n", pts);
|
||||
return;
|
||||
}
|
||||
if (!sd->no_remove_duplicates) {
|
||||
for (int i = 0; i < track->n_events; i++) {
|
||||
if (track->events[i].Start == ipts
|
||||
&& (track->events[i].Duration == iduration)
|
||||
&& strcmp(track->events[i].Text, text) == 0)
|
||||
return; // We've already added this subtitle
|
||||
}
|
||||
}
|
||||
#endif
|
||||
int eid = ass_alloc_event(track);
|
||||
ASS_Event *event = track->events + eid;
|
||||
event->Start = ipts;
|
||||
@ -259,8 +275,11 @@ static char *get_text(struct sd *sd, double pts)
|
||||
if (event->Text) {
|
||||
int start = b.len;
|
||||
ass_to_plaintext(&b, event->Text);
|
||||
if (!is_whitespace_only(&b.start[b.len], b.len - start))
|
||||
if (is_whitespace_only(&b.start[start], b.len - start)) {
|
||||
b.len = start;
|
||||
} else {
|
||||
append(&b, '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
94
sub/sd_lavf_srt.c
Normal 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,
|
||||
};
|
@ -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 = {
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 */
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user