diff options
-rw-r--r-- | DOCS/man/en/mplayer.1 | 33 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | cfg-common.h | 10 | ||||
-rwxr-xr-x | configure | 37 | ||||
-rw-r--r-- | libass/Makefile | 50 | ||||
-rw-r--r-- | libass/ass.c | 857 | ||||
-rw-r--r-- | libass/ass.h | 156 | ||||
-rw-r--r-- | libass/ass_cache.c | 209 | ||||
-rw-r--r-- | libass/ass_cache.h | 50 | ||||
-rw-r--r-- | libass/ass_fontconfig.c | 192 | ||||
-rw-r--r-- | libass/ass_fontconfig.h | 11 | ||||
-rw-r--r-- | libass/ass_mp.c | 10 | ||||
-rw-r--r-- | libass/ass_mp.h | 12 | ||||
-rw-r--r-- | libass/ass_render.c | 1766 | ||||
-rw-r--r-- | libass/ass_types.h | 83 | ||||
-rw-r--r-- | libass/ass_utils.c | 63 | ||||
-rw-r--r-- | libass/ass_utils.h | 9 | ||||
-rw-r--r-- | libmpcodecs/Makefile | 4 | ||||
-rw-r--r-- | libmpcodecs/vf.c | 4 | ||||
-rw-r--r-- | libmpcodecs/vf.h | 1 | ||||
-rw-r--r-- | libmpcodecs/vf_ass.c | 435 | ||||
-rw-r--r-- | libmpcodecs/vfcap.h | 2 | ||||
-rw-r--r-- | libmpdemux/demux_mkv.c | 203 | ||||
-rw-r--r-- | libmpdemux/demuxer.h | 9 | ||||
-rw-r--r-- | libmpdemux/ebml.h | 6 | ||||
-rw-r--r-- | mencoder.c | 7 | ||||
-rw-r--r-- | mplayer.c | 105 |
27 files changed, 4328 insertions, 12 deletions
diff --git a/DOCS/man/en/mplayer.1 b/DOCS/man/en/mplayer.1 index 41b5405117..fca18663c6 100644 --- a/DOCS/man/en/mplayer.1 +++ b/DOCS/man/en/mplayer.1 @@ -1709,6 +1709,11 @@ With fontconfig, this option determines the fontconfig font name. Enables the usage of fontconfig managed fonts. . .TP +.B \-(no)embeddedfonts (FreeType only) +Enables extraction of matroska embedded fonts. This fonts can be used for SSA/ASS subtitles +rendering (-ass option). +. +.TP .B \-forcedsubsonly Display only forced subtitles for the DVD subtitle stream selected by e.g.\& \-slang. @@ -2026,6 +2031,28 @@ the '.idx', '.ifo' or '.sub'. .B \-vobsubid <0\-31> Specify the VOBsub subtitle ID. . +.TP +.B \-ass (FreeType only) (FontConfig optional) +Tells MPlayer/MEncoder to turn on SSA/ASS subtitles rendering. +With this option, libass will be used for SSA/ASS external subtitles and +matroska tracks. You may also want to use -embeddedfonts. +. +.TP +.B \-ass-font-scale <value> +Set the scale coefficient to be used for all fonts in SSA/ASS renderer. +. +.TP +.B \-ass-line-spacing <value> +Sets line spacing value for SSA/ASS renderer. +. +.TP +.B \-ass-top-margin <value> +Adds a black band at the top of the frame. SSA/ASS renderer will place toptitles there. +. +.TP +.B \-ass-bottom-margin <value> +Adds a black band at the bottom of the frame. SSA/ASS renderer will place subtitles there. +. . . .SH "AUDIO OUTPUT OPTIONS (MPLAYER ONLY)" @@ -6224,6 +6251,12 @@ The filter has no overhead when not used and accepts an arbitrary colorspace, so it is safe to add it to the configuration file. .RE . +.TP +.B ass\ \ \ \ \ +Moves SSA/ASS subtitle rendering to arbitary point in video filter chain. +Only useful with -ass option. +.RE +. . . .SH "GENERAL ENCODING OPTIONS (MENCODER ONLY)" @@ -82,6 +82,12 @@ ifeq ($(EXTERNAL_VIDIX),yes) VO_LIBS += $(EXTERNAL_VIDIX_LIB) endif +ASS_LIB = + +ifeq ($(CONFIG_ASS),yes) +ASS_LIB += libass/libass.a +endif + AO_LIBS = $(ARTS_LIB) \ $(ESD_LIB) \ $(JACK_LIB) \ @@ -113,6 +119,7 @@ COMMON_LIBS = libmpcodecs/libmpcodecs.a \ $(W32_LIB) \ libaf/libaf.a \ libmpdemux/libmpdemux.a \ + $(ASS_LIB) \ libswscale/libswscale.a \ osdep/libosdep.a \ $(DVDREAD_LIB) \ @@ -254,6 +261,10 @@ endif ifeq ($(DVDKIT2),yes) COMMON_DEPS += libmpdvdkit2/libmpdvdkit.a endif +ifeq ($(CONFIG_ASS),yes) +COMMON_DEPS += libass/libass.a +PARTS += libass +endif ifeq ($(GUI),yes) COMMON_DEPS += Gui/libgui.a @@ -281,6 +292,9 @@ loader/libloader.a: libfame/libfame.a: $(MAKE) -C libfame +libass/libass.a: + $(MAKE) -C libass + libmpdemux/libmpdemux.a: $(MAKE) -C libmpdemux @@ -601,6 +615,8 @@ libdha/libdha.so: $(wildcard libdha/*.[ch]) vidix/libvidix.a: $(wildcard vidix/*.[ch]) Gui/libgui.a: $(wildcard Gui/*.[ch] Gui/*/*.[ch] Gui/*/*/*.[ch]) +libass/libass.a: $(wildcard libass/*.[ch]) + # # include dependency files if they exist # diff --git a/cfg-common.h b/cfg-common.h index 9b388e5f8d..fb7415cc0f 100644 --- a/cfg-common.h +++ b/cfg-common.h @@ -296,6 +296,16 @@ {"subfont-outline", &subtitle_font_thickness, CONF_TYPE_FLOAT, CONF_RANGE, 0, 8, NULL}, {"subfont-autoscale", &subtitle_autoscale, CONF_TYPE_INT, CONF_RANGE, 0, 3, NULL}, #endif +#ifdef USE_ASS + {"ass", &ass_enabled, CONF_TYPE_FLAG, 0, 0, 1, NULL}, + {"noass", &ass_enabled, CONF_TYPE_FLAG, 0, 1, 0, NULL}, + {"ass-font-scale", &ass_font_scale, CONF_TYPE_FLOAT, CONF_RANGE, 0, 100, NULL}, + {"ass-line-spacing", &ass_line_spacing, CONF_TYPE_FLOAT, CONF_RANGE, -1000, 1000, NULL}, + {"ass-top-margin", &ass_top_margin, CONF_TYPE_INT, CONF_RANGE, 0, 2000, NULL}, + {"ass-bottom-margin", &ass_bottom_margin, CONF_TYPE_INT, CONF_RANGE, 0, 2000, NULL}, + {"embeddedfonts", &extract_embedded_fonts, CONF_TYPE_FLAG, 0, 0, 1, NULL}, + {"noembeddedfonts", &extract_embedded_fonts, CONF_TYPE_FLAG, 0, 1, 0, NULL}, +#endif #ifdef HAVE_FONTCONFIG {"fontconfig", &font_fontconfig, CONF_TYPE_FLAG, 0, 0, 1, NULL}, {"nofontconfig", &font_fontconfig, CONF_TYPE_FLAG, 0, 1, 0, NULL}, @@ -245,6 +245,7 @@ Optional features: --disable-ftp Disable ftp support [enabled] --disable-vstream Disable tivo vstream client support [autodetect] --disable-pthreads Disable Posix threads support [autodetect] + --disable-ass Disable internal SSA/ASS subtitles support [autodetect] --enable-rpath Enable runtime linker path for extra libs [disabled] Codecs: @@ -1691,6 +1692,7 @@ _ftp=yes _musepack=auto _vstream=auto _pthreads=yes +_ass=auto _rpath=no for ac_option do case "$ac_option" in @@ -1975,6 +1977,8 @@ for ac_option do --disable-vstream) _vstream=no ;; --enable-pthreads) _pthreads=yes ;; --disable-pthreads) _pthreads=no ;; + --enable-ass) _ass=yes ;; + --disable-ass) _ass=no ;; --enable-rpath) _rpath=yes ;; --disable-rpath) _rpath=no ;; @@ -5390,6 +5394,35 @@ else fi echores "$_fontconfig" +echocheck "SSA/ASS support" +# libass depends on freetype +if test "$_freetype" = no ; then + _ass=no + _res_comment="FreeType support needed" +fi + +if test "$_ass" = auto ; then + cat > $TMPC << EOF +#include <ft2build.h> +#include FT_FREETYPE_H +#if ((FREETYPE_MAJOR < 2) || (FREETYPE_MINOR < 1) || ((FREETYPE_MINOR == 1) && (FREETYPE_PATCH < 8))) +#error "Need FreeType 2.1.8 or newer" +#endif +int main() { return 0; } +EOF + _ass=no + cc_check `$_freetypeconfig --cflags` `$_freetypeconfig --libs` && tmp_run && _ass=yes + if test "$_ass" = no ; then + _res_comment="FreeType >= 2.1.8 needed" + fi +fi +if test "$_ass" = yes ; then + _def_ass='#define USE_ASS' +else + _def_ass='#undef USE_ASS' +fi +echores "$_ass" + echocheck "fribidi with charsets" if test "$_fribidi" = auto ; then if ( $_fribidiconfig --version ) >/dev/null 2>&1 ; then @@ -7517,6 +7550,7 @@ FREETYPE_INC = $_inc_freetype FREETYPE_LIB = $_ld_freetype FONTCONFIG_INC = $_inc_fontconfig FONTCONFIG_LIB = $_ld_fontconfig +CONFIG_ASS = $_ass FRIBIDI_INC = $_inc_fribidi FRIBIDI_LIB = $_ld_fribidi LIBCDIO_INC = $_inc_libcdio @@ -8118,6 +8152,9 @@ $_def_freetype /* enable Fontconfig support */ $_def_fontconfig +/* enable SSA/ASS support */ +$_def_ass + /* enable FriBiDi usage */ $_def_fribidi diff --git a/libass/Makefile b/libass/Makefile new file mode 100644 index 0000000000..0604dd32e2 --- /dev/null +++ b/libass/Makefile @@ -0,0 +1,50 @@ + +include ../config.mak + +LIBNAME=libass.a + +LIBS=$(LIBNAME) + +SRCS=ass.c ass_cache.c ass_fontconfig.c ass_render.c ass_utils.c ass_mp.c + +OBJS=$(SRCS:.c=.o) + +CFLAGS = $(OPTFLAGS) \ + -I. -I.. \ + -I../libmpcodecs \ + $(EXTRA_INC) \ + -D_GNU_SOURCE \ + $(FREETYPE_INC) \ + $(FONTCONFIG_INC) \ + +.SUFFIXES: .c .o + +# .PHONY: all clean + +.c.o: + $(CC) -c $(CFLAGS) -o $@ $< + +all: $(LIBS) + +$(LIBNAME): $(OBJS) + $(AR) r $(LIBNAME) $(OBJS) + $(RANLIB) $(LIBNAME) + +clean: + rm -f *.o *.a *~ + +distclean: clean + rm -f .depend + +dep: depend + +depend: + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +# +# include dependency files if they exist +# +ifneq ($(wildcard .depend),) +include .depend +endif + diff --git a/libass/ass.c b/libass/ass.c new file mode 100644 index 0000000000..4f9b8908ec --- /dev/null +++ b/libass/ass.c @@ -0,0 +1,857 @@ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#ifdef HAVE_ENCA +#include "subreader.h" // for guess_buffer_cp +#endif + +#ifdef USE_ICONV +#include <iconv.h> +extern char *sub_cp; +#endif + +#include "mp_msg.h" +#include "ass.h" +#include "ass_utils.h" +#include "libvo/sub.h" // for utf8_get_char + +char *get_path(char *); + +#define ASS_STYLES_ALLOC 20 +#define ASS_EVENTS_ALLOC 200 + +void ass_free_track(ass_track_t* track) { + int i; + + if (track->style_format) + free(track->style_format); + if (track->event_format) + free(track->event_format); + if (track->styles) { + for (i = 0; i < track->n_styles; ++i) { + ass_style_t* style = track->styles + i; + if (style->Name) + free(style->Name); + if (style->FontName) + free(style->FontName); + } + free(track->styles); + } + if (track->events) { + for (i = 0; i < track->n_events; ++i) { + ass_event_t* event = track->events + i; + if (event->Name) + free(event->Name); + if (event->Effect) + free(event->Effect); + if (event->Text) + free(event->Text); + } + free(track->events); + } +} + +/// \brief Allocate a new style struct +/// \param track track +/// \return style id +int ass_alloc_style(ass_track_t* track) { + int sid; + + assert(track->n_styles <= track->max_styles); + + if (track->n_styles == track->max_styles) { + track->max_styles += ASS_STYLES_ALLOC; + track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles); + } + + sid = track->n_styles++; + memset(track->styles + sid, 0, sizeof(ass_style_t)); + return sid; +} + +/// \brief Allocate a new event struct +/// \param track track +/// \return event id +int ass_alloc_event(ass_track_t* track) { + int eid; + + assert(track->n_events <= track->max_events); + + if (track->n_events == track->max_events) { + track->max_events += ASS_EVENTS_ALLOC; + track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events); + } + + eid = track->n_events++; + memset(track->events + eid, 0, sizeof(ass_event_t)); + return eid; +} + +static void free_event(ass_track_t* track, int eid) { + if (track->n_events > eid + 1) // not last event + memcpy(track->events + eid, track->events + eid + 1, sizeof(ass_event_t) * (track->n_events - eid - 1)); + track->n_events--; +} + +static int events_compare_f(const void* a_, const void* b_) { + ass_event_t* a = (ass_event_t*)a_; + ass_event_t* b = (ass_event_t*)b_; + if (a->Start < b->Start) + return -1; + else if (a->Start > b->Start) + return 1; + else + return 0; +} + +/// \brief Sort events by start time +/// \param tid track id +static void sort_events(ass_track_t* track) { + qsort(track->events, track->n_events, sizeof(ass_event_t), events_compare_f); +} + +// ============================================================================================== + +static void skip_spaces(char** str) { + char* p = *str; + while ((*p==' ') || (*p=='\t')) + ++p; + *str = p; +} + +static void rskip_spaces(char** str, char* limit) { + char* p = *str; + while ((p >= limit) && ((*p==' ') || (*p=='\t'))) + --p; + *str = p; +} + +/** + * \brief find style by name + * \param track track + * \param name style name + * \return index in track->styles + * Returnes 0 if no styles found => expects at least 1 style. + * Parsing code always adds "Default" style in the end. + */ +static int lookup_style(ass_track_t* track, char* name) { + int i; + for (i=0; i<track->n_styles; ++i) { + // FIXME: mb strcasecmp ? + if (strcmp(track->styles[i].Name, name) == 0) + return i; + } + i = track->default_style; + mp_msg(MSGT_GLOBAL, MSGL_WARN, "[%p] Warning: no style named '%s' found, using '%s'\n", track, name, track->styles[i].Name); + return i; // use the first style +} + +static uint32_t string2color(char* p) { + uint32_t tmp; + (void)strtocolor(&p, &tmp); + return tmp; +} + +static long long string2timecode(char* p) { + unsigned h, m, s, ms; + long long tm; + int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms); + if (res < 4) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "bad timestamp\n"); + return 0; + } + tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10; + return tm; +} + +/** + * \brief converts numpad-style align to align. + */ +static int numpad2align(int val) { + int res, v; + v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment + if (v != 0) v = 3 - v; + res = ((val - 1) % 3) + 1; // horizontal alignment + res += v*4; + return res; +} + +#define NEXT(str,token) \ + token = next_token(&str); \ + if (!token) break; + +#define ANYVAL(name,func) \ + } else if (strcasecmp(tname, #name) == 0) { \ + target->name = func(token); \ + mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token); +#define STRVAL(name) ANYVAL(name,strdup) +#define COLORVAL(name) ANYVAL(name,string2color) +#define INTVAL(name) ANYVAL(name,atoi) +#define FPVAL(name) ANYVAL(name,atof) +#define TIMEVAL(name) ANYVAL(name,string2timecode) +#define STYLEVAL(name) \ + } else if (strcasecmp(tname, #name) == 0) { \ + target->name = lookup_style(track, token); \ + mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token); + +#define ALIAS(alias,name) \ + if (strcasecmp(tname, #alias) == 0) {tname = #name;} + +static char* next_token(char** str) { + char* p = *str; + char* start; + skip_spaces(&p); + if (*p == '\0') { + *str = p; + return 0; + } + start = p; // start of the token + for (; (*p != '\0') && (*p != ','); ++p) {} + if (*p == '\0') { + *str = p; // eos found, str will point to '\0' at exit + } else { + *p = '\0'; + *str = p + 1; // ',' found, str will point to the next char (beginning of the next token) + } + --p; // end of current token + rskip_spaces(&p, start); + if (p < start) + p = start; // empty token + else + ++p; // the first space character, or '\0' + *p = '\0'; + return start; +} +/** + * \brief Parse the tail of Dialogue line + * \param track track + * \param event parsed data goes here + * \param str string to parse, zero-terminated + * \param n_ignored number of format options to skip at the beginning +*/ +static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored) +{ + char* token; + char* tname; + char* p = str; + int i; + ass_event_t* target = event; + + char* format = strdup(track->event_format); + char* q = format; // format scanning pointer + + for (i = 0; i < n_ignored; ++i) { + NEXT(q, tname); + } + + while (1) { + NEXT(q, tname); + if (strcasecmp(tname, "Text") == 0) { + char* last; + event->Text = strdup(p); + last = event->Text + strlen(event->Text) - 1; + if (*last == '\r') + *last = 0; + mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Text = %s\n", event->Text); + event->Duration -= event->Start; + free(format); + return 0; // "Text" is always the last + } + NEXT(p, token); + + ALIAS(End,Duration) // temporarily store end timecode in event->Duration + if (0) { // cool ;) + STYLEVAL(Style) + STRVAL(Name) + STRVAL(Effect) + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + TIMEVAL(Start) + TIMEVAL(Duration) + } + } + free(format); + return 1; +} + +/** + * \brief Parse the Style line + * \param track track + * \param str string to parse, zero-terminated + * Allocates a new style struct. +*/ +static int process_style(ass_track_t* track, char *str) +{ + + char* token; + char* tname; + char* p = str; + char* format; + char* q; // format scanning pointer + int sid; + ass_style_t* style; + ass_style_t* target; + + if (!track->style_format) { + // no style format header + // probably an ancient script version + if (track->track_type == TRACK_TYPE_SSA) + track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," + "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline," + "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"); + else + track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," + "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut," + "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow," + "Alignment, MarginL, MarginR, MarginV, Encoding"); + } + + q = format = strdup(track->style_format); + + mp_msg(MSGT_GLOBAL, MSGL_V, "[%p] Style: %s\n", track, str); + + sid = ass_alloc_style(track); + + style = track->styles + sid; + target = style; +// fill style with some default values + style->ScaleX = 100.; + style->ScaleY = 100.; + + while (1) { + NEXT(q, tname); + NEXT(p, token); + +// ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour + + if (0) { // cool ;) + STRVAL(Name) + if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0)) + track->default_style = sid; + STRVAL(FontName) + COLORVAL(PrimaryColour) + COLORVAL(SecondaryColour) + COLORVAL(OutlineColour) // TertiaryColor + COLORVAL(BackColour) + // SSA uses BackColour for both outline and shadow + // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway + if (track->track_type == TRACK_TYPE_SSA) + target->OutlineColour = target->BackColour; + INTVAL(FontSize) + INTVAL(Bold) + INTVAL(Italic) + INTVAL(Underline) + INTVAL(StrikeOut) + INTVAL(Spacing) + INTVAL(Angle) + INTVAL(BorderStyle) + INTVAL(Alignment) + if (track->track_type == TRACK_TYPE_ASS) + target->Alignment = numpad2align(target->Alignment); + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + INTVAL(Encoding) + FPVAL(ScaleX) + FPVAL(ScaleY) + FPVAL(Outline) + FPVAL(Shadow) + } + } + style->ScaleX /= 100.; + style->ScaleY /= 100.; + if (!style->Name) + style->Name = strdup("Default"); + if (!style->FontName) + style->FontName = strdup("Arial"); + free(format); + return 0; + +} + +/** + * \brief Parse a header line + * \param track track + * \param str string to parse, zero-terminated +*/ +static int process_header_line(ass_track_t* track, char *str) +{ + static int events_section_started = 0; + + mp_msg(MSGT_GLOBAL, MSGL_DBG2, "=== Header: %s\n", str); + if (strncmp(str, "PlayResX:", 9)==0) { + track->PlayResX = atoi(str + 9); + } else if (strncmp(str,"PlayResY:", 9)==0) { + track->PlayResY = atoi(str + 9); + } else if (strncmp(str,"Timer:", 6)==0) { + track->Timer = atof(str + 6); + } else if (strstr(str,"Styles]")) { + events_section_started = 0; + if (strchr(str, '+')) + track->track_type = TRACK_TYPE_ASS; + else + track->track_type = TRACK_TYPE_SSA; + } else if (strncmp(str,"[Events]", 8)==0) { + events_section_started = 1; + } else if (strncmp(str,"Format:", 7)==0) { + char* p = str + 7; + skip_spaces(&p); + if (events_section_started) { + track->event_format = strdup(p); + mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Event format: %s\n", track->event_format); + } else { + track->style_format = strdup(p); + mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Style format: %s\n", track->style_format); + } + } else if (strncmp(str,"Style:", 6)==0) { + char* p = str + 6; + skip_spaces(&p); + process_style(track, p); + } else if (strncmp(str,"WrapStyle:", 10)==0) { + track->WrapStyle = atoi(str + 10); + } + return 0; +} + +/** + * \brief Process CodecPrivate section of subtitle stream + * \param track track + * \param data string to parse + * \param size length of data + CodecPrivate section contains [Stream Info] and [V4+ Styles] sections +*/ +void ass_process_chunk(ass_track_t* track, char *data, int size) +{ + char* str = malloc(size + 1); + char* p; + int sid; + + memcpy(str, data, size); + str[size] = '\0'; + + p = str; + while(1) { + char* q; + for (;((*p=='\r')||(*p=='\n'));++p) {} + for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {}; + if (q==p) + break; + if (*q != '\0') + *(q++) = '\0'; + process_header_line(track, p); + if (*q == '\0') + break; + p = q; + } + free(str); + + // add "Default" style to the end + // will be used if track does not contain a default style (or even does not contain styles at all) + sid = ass_alloc_style(track); + track->styles[sid].Name = strdup("Default"); + track->styles[sid].FontName = strdup("Arial"); + + if (!track->event_format) { + // probably an mkv produced by ancient mkvtoolnix + // such files don't have [Events] and Format: headers + if (track->track_type == TRACK_TYPE_SSA) + track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); + else + track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text"); + } +} + +static int check_duplicate_event(ass_track_t* track, int ReadOrder) +{ + int i; + for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with + if (track->events[i].ReadOrder == ReadOrder) + return 1; + return 0; +} + +/** + * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary) + * \param track track + * \param data string to parse + * \param size length of data + * \param timecode starting time of the event (milliseconds) + * \param duration duration of the event (milliseconds) +*/ +void ass_process_line(ass_track_t* track, char *data, int size, long long timecode, long long duration) +{ + char* str; + int eid; + char* p; + char* token; + ass_event_t* event; + + if (!track->event_format) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "Event format header missing\n"); + return; + } + + str = malloc(size + 1); + memcpy(str, data, size); + str[size] = '\0'; + mp_msg(MSGT_GLOBAL, MSGL_V, "\nline at timecode %lld, duration %lld: \n%s\n", timecode, duration, str); + + eid = ass_alloc_event(track); + event = track->events + eid; + + p = str; + + do { + NEXT(p, token); + event->ReadOrder = atoi(token); + if (check_duplicate_event(track, event->ReadOrder)) + break; + + NEXT(p, token); + event->Layer = atoi(token); + + process_event_tail(track, event, p, 3); + + event->Start = timecode; + event->Duration = duration; + + free(str); + return; +// dump_events(tid); + } while (0); + // some error + free_event(track, eid); + free(str); +} + +/** + * \brief Process a line from external file. + * \param track track + * \param str string to parse + * \param size length of data +*/ +static void ass_process_external_line(ass_track_t* track, char *str, int size) +{ + int eid; + ass_event_t* event; + + eid = ass_alloc_event(track); + event = track->events + eid; + + if (strncmp("Dialogue:", str, 9) != 0) + return; + + str += 9; + while (*str == ' ') {++str;} + + process_event_tail(track, event, str, 0); +} + +#ifdef USE_ICONV +/** \brief recode buffer to utf-8 + * constraint: sub_cp != 0 + * \param data pointer to text buffer + * \param size buffer size + * \return a pointer to recoded buffer, caller is responsible for freeing it +**/ +static char* sub_recode(char* data, size_t size) +{ + static iconv_t icdsc = (iconv_t)(-1); + char* tocp = "UTF-8"; + char* outbuf; + assert(sub_cp); + + { + char* cp_tmp = sub_cp; +#ifdef HAVE_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) { + cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback); + } +#endif + if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){ + mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: opened iconv descriptor.\n"); + } else + mp_msg(MSGT_SUBREADER,MSGL_ERR,"LIBSUB: error opening iconv descriptor.\n"); +#ifdef HAVE_ENCA + if (cp_tmp) free(cp_tmp); +#endif + } + + { + size_t osize = size; + size_t ileft = size; + size_t oleft = size - 1; + char* ip; + char* op; + size_t rc; + + outbuf = malloc(size); + ip = data; + op = outbuf; + + while (ileft) { + rc = iconv(icdsc, &ip, &ileft, &op, &oleft); + if (rc == (size_t)(-1)) { + if (errno == E2BIG) { + int offset = op - outbuf; + outbuf = (char*)realloc(outbuf, osize + size); + op = outbuf + offset; + osize += size; + oleft += size; + } else { + mp_msg(MSGT_SUBREADER, MSGL_WARN, "LIBSUB: error recoding file.\n"); + return NULL; + } + } + } + outbuf[osize - oleft - 1] = 0; + } + + if (icdsc != (iconv_t)(-1)) { + (void)iconv_close(icdsc); + icdsc = (iconv_t)(-1); + mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: closed iconv descriptor.\n"); + } + + return outbuf; +} +#endif // ICONV + +/** + * \brief Read subtitles from file. + * \param fname file name + * \return newly allocated track +*/ +ass_track_t* ass_read_file(char* fname) +{ + int res; + long sz; + long bytes_read; + char* buf; + char* p; + int events_reached; + ass_track_t* track; + + FILE* fp = fopen(fname, "rb"); + if (!fp) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fopen failed\n", fname); + return 0; + } + res = fseek(fp, 0, SEEK_END); + if (res == -1) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fseek failed\n", fname); + fclose(fp); + return 0; + } + + sz = ftell(fp); + rewind(fp); + + if (sz > 10*1024*1024) { + mp_msg(MSGT_GLOBAL, MSGL_INFO, "ass_read_file(%s): Refusing to load subtitles larger than 10M\n", fname); + fclose(fp); + return 0; + } + + mp_msg(MSGT_GLOBAL, MSGL_V, "file size: %ld\n", sz); + + buf = malloc(sz + 1); + assert(buf); + bytes_read = 0; + do { + res = fread(buf + bytes_read, 1, sz - bytes_read, fp); + if (res <= 0) { + mp_msg(MSGT_GLOBAL, MSGL_INFO, "Read failed, %d: %s\n", errno, strerror(errno)); + fclose(fp); + free(buf); + return 0; + } + bytes_read += res; + } while (sz - bytes_read > 0); + buf[sz] = '\0'; + fclose(fp); + +#ifdef USE_ICONV + if (sub_cp) { + char* tmpbuf = sub_recode(buf, sz); + free(buf); + if (!tmpbuf) + return 0; + buf = tmpbuf; + } +#endif + + track = ass_new_track(); + track->name = strdup(fname); |