[MPlayer-dev-eng] [PATCH] Tags support for SubRip and MicroDVD subtitles [v3]

ubitux ubitux at gmail.com
Fri Jul 9 20:01:30 CEST 2010


On Wed, Jul 07, 2010 at 04:25:13AM -0400, Reynaldo H. Verdejo Pinochet wrote:
> Hi
> 
> On Fri, Jun 25, 2010 at 08:36:19PM +0200, ubitux wrote:
> > [...]
> > +    if (ret < 0)
> > +        goto end;
> > +    dst->len += ret;
> > +    if (dst->len > dst->bufsize)
> > +        dst->len = dst->bufsize;
> > +end:
> > +    va_end(va);
> > +}
> 
> Use if(ret > 0) and get rid of the goto.
> 

Done.

> > +
> > +static int indexof(const char *s, int c)
> > +{
> > +    char *f = strchr(s, c);
> > +    return f ? (f - s) : -1;
> > +}
> > +
> > +
> > +
> > +/*
> > + *      SubRip
> > + *
> > + *      Support basic tags (italic, bold, underline, strike-through)
> > + *      and font tag with size, color and face attributes.
> > + *
> > + */
> > +
> > +struct font_tag {
> > +    struct bstr face;
> > +    int size;
> > +    uint32_t color;
> > +    int has_size : 1;
> 
> has_size seems redundant, has_color too if you make color a pointer. Guess
> the same can be said about has_face. These changes alone would trim quite
> some lines out of this changeset.
> 

Done

> > [..]
> > +    {"{", "\\{"}, {"}", "\\}"},
> > +    {"\n", "\\N"}
> > +};
> > +
> > +static const struct {
> > +    const char *s;
> > +    uint32_t v;
> > +} subrip_web_colors[] = {
> > +    /* 16 named html colors in BGR format */
> 
> HTML
> 

Fixed

> > +    {"red",     0x0000ff}, {"blue",   0xff0000}, {"lime",   0x00ff00},
> > +    {"aqua",    0xffff00}, {"purple", 0x800080}, {"yellow", 0x00ffff},
> > +    {"fuchsia", 0xff00ff}, {"white",  0xffffff}, {"gray",   0x808080},
> > +    {"maroon",  0x000080}, {"olive",  0x008080}, {"black",  0x000000},
> > +    {"silver",  0xc0c0c0}, {"teal",   0x808000}, {"green",  0x008000},
> > +    {"navy",    0x800000}
> > +};
> > +
> > +#define SUBRIP_MAX_STACKED_FONT_TAGS    16
> 
> The only time I see it used you are adding one to it. Why not making it 17
> to begin with?
> 

Fixed

> > +
> > +/**
> > + * \brief Convert SubRip lines into ASS markup
> > + * \param orig original SubRip lines. The content will remain untouched.
> > + * \param dest ASS markup destination buffer.
> > + * \param dest_buffer_size maximum size for the destination buffer.
> > + */
> > +void subassconvert_subrip(const char *orig, char *dest, int dest_buffer_size)
> > +{
> 
> dest_buffer_size might be better of as a size_t
> 

Done

> > [..]
> > +                        if (i == FF_ARRAY_ELEMS(subrip_web_colors)) {
> > +                            /* We didn't find any matching color */
> > +                            line = strchr(line, '"'); // can't be NULL, see above
> > +                            mp_msg(MSGT_SUBREADER, MSGL_WARN,
> > +                                   "[SubRip] Unknown font color in subtitle: %s\n", orig);
> 
> Should be translatable.
> 

Done

> > [..]
> > +                break;
> > +            s++;
> > +            tag.data2 = strtol(s, &s, 10);
> > +            if (*s != '}')
> > +                break;
> > +            tag.key = 'o';
> > +            break;
> > +
> > +        default:    /* Unknown tag, we considere it's text */
> 
> consider?
> 

Fixed

> > [..]
> > +}
> > +
> > +static void microdvd_close_no_persistent_tags(struct line *new_line,
> > +                                              struct microdvd_tag *tags)
> > +{
> > +    int i;
> > +
> > +    for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
> > +        if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
> 
> Guess the negative subscript chance on tags is intended?
> 

I don't see the point in making MICRODVD_TAGS a string with less than one
character… It's a string that shouldn't be changed, at least not for
removing characters.

> 
> > [..]
> > +      if (c) {
> > +        double_newline = 0;
> > +        buf[pos++] = c;
> > +      } else if (!double_newline) {
> > +        if (sub->lines >= SUB_MAX_TEXT - 1) {
> > +          mp_msg(MSGT_VO, MSGL_WARN, "Too many subtitle lines\n");
> 
> Should be translatable.
> 

As I said in the mail to Reimar, this is just a "block shift" of an
existen code, I didn't touched it.

> > +          break;
> > +        }
> > +        double_newline = 1;
> > +        buf[pos] = 0;
> > +        sub->lines++;
> > +        pos = 0;
> > +        buf = malloc(MAX_SUBLINE + 1);
> > +        sub->text[sub->lines] = buf;
> > +        sub->endpts[sub->lines] = endpts;
> > +      }
> >      }
> > +    buf[pos] = 0;
> 
> What if malloc fails?
> 

Same goes here. I really wish to prettify subreader.c too, but it does not
belong to this patch.

> 
> Best regards
> 

Thanks for your review, here is new attached patch.

-- 
ubitux
-------------- next part --------------
Index: Makefile
===================================================================
--- Makefile	(revision 31652)
+++ Makefile	(working copy)
@@ -329,6 +329,7 @@
 SRCS_COMMON-$(ZR)                    += libmpcodecs/vd_zrmjpeg.c \
                                         libmpcodecs/vf_zrmjpeg.c
 SRCS_COMMON = asxparser.c \
+              bstr.c \
               codec-cfg.c \
               cpudetect.c \
               edl.c \
@@ -345,6 +346,7 @@
               playtreeparser.c \
               spudec.c \
               sub_cc.c \
+              subassconvert.c \
               subopt-helper.c \
               subreader.c \
               vobsub.c \
Index: bstr.c
===================================================================
--- bstr.c	(revision 0)
+++ bstr.c	(revision 0)
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+#include <libavutil/avutil.h>
+
+#include "bstr.h"
+
+int bstrcmp(struct bstr str1, struct bstr str2)
+{
+    int ret = memcmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
+
+    if (!ret) {
+        if (str1.len == str2.len)
+            return 0;
+        else if (str1.len > str2.len)
+            return 1;
+        else
+            return -1;
+    }
+    return ret;
+}
+
+int bstrcasecmp(struct bstr str1, struct bstr str2)
+{
+    int ret = strncasecmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
+
+    if (!ret) {
+        if (str1.len == str2.len)
+            return 0;
+        else if (str1.len > str2.len)
+            return 1;
+        else
+            return -1;
+    }
+    return ret;
+}
Index: bstr.h
===================================================================
--- bstr.h	(revision 0)
+++ bstr.h	(revision 0)
@@ -0,0 +1,39 @@
+/*
+ * 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_BSTR_H
+#define MPLAYER_BSTR_H
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+struct bstr {
+    const uint8_t *start;
+    size_t len;
+};
+
+int bstrcmp(struct bstr str1, struct bstr str2);
+int bstrcasecmp(struct bstr str1, struct bstr str2);
+
+// Create bstr compound literal from null-terminated string
+#define BSTR(s) (struct bstr){(s), (s) ? strlen(s) : 0}
+// create a pair (not single value!) for "%.*s" printf syntax
+#define BSTR_P(bstr) (int)((bstr).len), (bstr).start
+
+#endif /* MPLAYER_BSTR_H */
Index: subassconvert.c
===================================================================
--- subassconvert.c	(revision 0)
+++ subassconvert.c	(revision 0)
@@ -0,0 +1,515 @@
+/*
+ * Subtitles converter to SSA/ASS in order to allow special formatting
+ *
+ * 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.
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "mp_msg.h"
+#include "help_mp.h"
+#include "bstr.h"
+#include "subassconvert.h"
+#include "libavutil/common.h"
+
+struct line {
+    char *buf;
+    size_t bufsize;
+    size_t len;
+};
+
+#ifdef __GNUC__
+static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3)));
+#endif
+
+static void append_text(struct line *dst, char *fmt, ...)
+{
+    va_list va;
+    int ret;
+
+    va_start(va, fmt);
+    ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va);
+    if (ret >= 0) {
+        dst->len += ret;
+        if (dst->len > dst->bufsize)
+            dst->len = dst->bufsize;
+    }
+    va_end(va);
+}
+
+static int indexof(const char *s, int c)
+{
+    char *f = strchr(s, c);
+    return f ? (f - s) : -1;
+}
+
+
+
+/*
+ *      SubRip
+ *
+ *      Support basic tags (italic, bold, underline, strike-through)
+ *      and font tag with size, color and face attributes.
+ *
+ */
+
+struct font_tag {
+    struct bstr face;
+    int size;
+    uint32_t color;
+};
+
+static const struct tag_conv {
+    const char *from;
+    const char *to;
+} subrip_basic_tags[] = {
+    {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"},
+    {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"},
+    {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"},
+    {"<s>", "{\\s1}"}, {"</s>", "{\\s0}"},
+    {"{", "\\{"}, {"}", "\\}"},
+    {"\n", "\\N"}
+};
+
+static const struct {
+    const char *s;
+    uint32_t v;
+} subrip_web_colors[] = {
+    /* 16 named HTML colors in BGR format */
+    {"red",     0x0000ff}, {"blue",   0xff0000}, {"lime",   0x00ff00},
+    {"aqua",    0xffff00}, {"purple", 0x800080}, {"yellow", 0x00ffff},
+    {"fuchsia", 0xff00ff}, {"white",  0xffffff}, {"gray",   0x808080},
+    {"maroon",  0x000080}, {"olive",  0x008080}, {"black",  0x000000},
+    {"silver",  0xc0c0c0}, {"teal",   0x808000}, {"green",  0x008000},
+    {"navy",    0x800000}
+};
+
+#define SUBRIP_MAX_STACKED_FONT_TAGS    16
+#define SUBRIP_FLAG_COLOR               0x01000000
+
+/**
+ * \brief Convert SubRip lines into ASS markup
+ * \param orig original SubRip lines. The content will remain untouched.
+ * \param dest ASS markup destination buffer.
+ * \param dest_buffer_size maximum size for the destination buffer.
+ */
+void subassconvert_subrip(const char *orig, char *dest, size_t dest_buffer_size)
+{
+    /* line is not const to avoid warnings with strtol, etc.
+     * orig content won't be changed */
+    char *line = (char *)orig;
+    struct line new_line = {
+        .buf     = dest,
+        .bufsize = dest_buffer_size,
+    };
+    struct font_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS];
+    int sp = 0;
+
+    font_stack[0] = (struct font_tag){}; // type with all defaults
+    while (*line && new_line.len < new_line.bufsize - 1) {
+        char *orig_line = line;
+
+        for (int i = 0; i < FF_ARRAY_ELEMS(subrip_basic_tags); i++) {
+            const struct tag_conv *tag = &subrip_basic_tags[i];
+            int from_len = strlen(tag->from);
+            if (strncmp(line, tag->from, from_len) == 0) {
+                append_text(&new_line, "%s", tag->to);
+                line += from_len;
+            }
+        }
+
+        if (strncmp(line, "</font>", 7) == 0) {
+            /* Closing font tag */
+            line += 7;
+
+            if (sp > 0) {
+                struct font_tag *tag      = &font_stack[sp];
+                struct font_tag *last_tag = &tag[-1];
+                sp--;
+
+                if (tag->size) {
+                    if (!last_tag->size)
+                        append_text(&new_line, "{\\fs}");
+                    else if (last_tag->size != tag->size)
+                        append_text(&new_line, "{\\fs%d}", last_tag->size);
+                }
+
+                if (tag->color & SUBRIP_FLAG_COLOR) {
+                    if (!(last_tag->color & SUBRIP_FLAG_COLOR))
+                        append_text(&new_line, "{\\c}");
+                    else if (last_tag->color != tag->color)
+                        append_text(&new_line, "{\\c&H%06X&}",
+                                    last_tag->color & 0xffffff);
+                }
+
+                if (tag->face.len) {
+                    if (!last_tag->face.len)
+                        append_text(&new_line, "{\\fn}");
+                    else if (bstrcmp(last_tag->face, tag->face) != 0)
+                        append_text(&new_line, "{\\fn%.*s}",
+                                    BSTR_P(last_tag->face));
+                }
+            }
+        } else if (strncmp(line, "<font ", 6) == 0
+                   && sp + 1 < FF_ARRAY_ELEMS(font_stack)) {
+            /* Opening font tag */
+            char *potential_font_tag_start = line;
+            int len_backup = new_line.len;
+            struct font_tag *tag = &font_stack[sp + 1];
+            int has_valid_attr = 0;
+
+            *tag = tag[-1]; // keep values from previous tag
+            line += 6;
+
+            while (*line && *line != '>') {
+                if (strncmp(line, "size=\"", 6) == 0) {
+                    line += 6;
+                    tag->size = strtol(line, &line, 10);
+                    if (*line != '"' || !tag->size)
+                        break;
+                    append_text(&new_line, "{\\fs%d}", tag->size);
+                    has_valid_attr = 1;
+                } else if (strncmp(line, "color=\"", 7) == 0) {
+                    line += 7;
+                    if (*line == '#') {
+                        // #RRGGBB format
+                        line++;
+                        tag->color = strtol(line, &line, 16) & 0x00ffffff;
+                        if (*line != '"')
+                            break;
+                        tag->color = ((tag->color & 0xff) << 16) |
+                                      (tag->color & 0xff00) |
+                                     ((tag->color & 0xff0000) >> 16) |
+                                     SUBRIP_FLAG_COLOR;
+                    } else {
+                        // Standard web colors
+                        int i, len = indexof(line, '"');
+                        if (len <= 0)
+                            break;
+                        for (i = 0; i < FF_ARRAY_ELEMS(subrip_web_colors); i++) {
+                            const char *color = subrip_web_colors[i].s;
+                            if (strlen(color) == len
+                                && strncasecmp(line, color, len) == 0) {
+                                tag->color = SUBRIP_FLAG_COLOR | subrip_web_colors[i].v;
+                                break;
+                            }
+                        }
+
+                        if (i == FF_ARRAY_ELEMS(subrip_web_colors)) {
+                            /* We didn't find any matching color */
+                            line = strchr(line, '"'); // can't be NULL, see above
+                            mp_msg(MSGT_SUBREADER, MSGL_WARN,
+                                   MSGTR_SUBTITLES_SubRip_UnknownFontColor, orig);
+                            append_text(&new_line, "{\\c}");
+                            line += 2;
+                            continue;
+                        }
+
+                        line += len;
+                    }
+                    append_text(&new_line, "{\\c&H%06X&}", tag->color & 0xffffff);
+                    has_valid_attr = 1;
+                } else if (strncmp(line, "face=\"", 6) == 0) {
+                    /* Font face attribute */
+                    int len;
+                    line += 6;
+                    len = indexof(line, '"');
+                    if (len <= 0)
+                        break;
+                    tag->face.start = line;
+                    tag->face.len   = len;
+                    line += len;
+                    append_text(&new_line, "{\\fn%.*s}", BSTR_P(tag->face));
+                    has_valid_attr = 1;
+                }
+                line++;
+            }
+
+            if (!has_valid_attr || *line != '>') { /* Not valid font tag */
+                line = potential_font_tag_start;
+                new_line.len = len_backup;
+            } else {
+                sp++;
+                line++;
+            }
+        }
+
+        /* Tag conversion code didn't match */
+        if (line == orig_line)
+            new_line.buf[new_line.len++] = *line++;
+    }
+    new_line.buf[new_line.len] = 0;
+}
+
+
+/*
+ *      MicroDVD
+ *
+ *      Based on the specifications found here:
+ *      https://trac.videolan.org/vlc/ticket/1825#comment:6
+ */
+
+struct microdvd_tag {
+    char key;
+    int persistent;
+    uint32_t data1;
+    uint32_t data2;
+    struct bstr data_string;
+};
+
+#define MICRODVD_PERSISTENT_OFF     0
+#define MICRODVD_PERSISTENT_ON      1
+#define MICRODVD_PERSISTENT_OPENED  2
+
+// Color, Font, Size, cHarset, stYle, Position, cOordinate
+#define MICRODVD_TAGS               "cfshyYpo"
+
+static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
+{
+    int tag_index = indexof(MICRODVD_TAGS, tag.key);
+
+    if (tag_index < 0)
+        return;
+    memcpy(&tags[tag_index], &tag, sizeof(tag));
+}
+
+// italic, bold, underline, strike-through
+#define MICRODVD_STYLES             "ibus"
+
+static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
+{
+    while (*s == '{') {
+        char *start = s;
+        char tag_char = *(s + 1);
+        struct microdvd_tag tag = {};
+
+        if (!tag_char || *(s + 2) != ':')
+            break;
+        s += 3;
+
+        switch (tag_char) {
+
+        /* Style */
+        case 'Y':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 'y':
+            while (*s && *s != '}') {
+                int style_index = indexof(MICRODVD_STYLES, *s);
+
+                if (style_index >= 0)
+                    tag.data1 |= (1 << style_index);
+                s++;
+            }
+            if (*s != '}')
+                break;
+            /* We must distinguish persistent and non-persistent styles
+             * to handle this kind of style tags: {y:ib}{Y:us} */
+            tag.key = tag_char;
+            break;
+
+        /* Color */
+        case 'C':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 'c':
+            tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
+            if (*s != '}')
+                break;
+            tag.key = 'c';
+            break;
+
+        /* Font name */
+        case 'F':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 'f':
+        {
+            int len = indexof(s, '}');
+            if (len < 0)
+                break;
+            tag.data_string.start = s;
+            tag.data_string.len   = len;
+            s += len;
+            tag.key = 'f';
+            break;
+        }
+
+        /* Font size */
+        case 'S':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 's':
+            tag.data1 = strtol(s, &s, 10);
+            if (*s != '}')
+                break;
+            tag.key = 's';
+            break;
+
+        /* Charset */
+        case 'H':
+        {
+            //TODO: not yet handled, just parsed.
+            int len = indexof(s, '}');
+            if (len < 0)
+                break;
+            tag.data_string.start = s;
+            tag.data_string.len   = len;
+            s += len;
+            tag.key = 'h';
+            break;
+        }
+
+        /* Position */
+        case 'P':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+            tag.data1 = (*s++ == '1');
+            if (*s != '}')
+                break;
+            tag.key = 'p';
+            break;
+
+        /* Coordinates */
+        case 'o':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+            tag.data1 = strtol(s, &s, 10);
+            if (*s != ',')
+                break;
+            s++;
+            tag.data2 = strtol(s, &s, 10);
+            if (*s != '}')
+                break;
+            tag.key = 'o';
+            break;
+
+        default:    /* Unknown tag, we consider it's text */
+            break;
+        }
+
+        if (tag.key == 0)
+            return start;
+
+        microdvd_set_tag(tags, tag);
+        s++;
+    }
+    return s;
+}
+
+static void microdvd_open_tags(struct line *new_line, struct microdvd_tag *tags)
+{
+    for (int i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
+        if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
+            continue;
+        switch (tags[i].key) {
+        case 'Y':
+        case 'y':
+            for (int sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
+                if (tags[i].data1 & (1 << sidx))
+                    append_text(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
+            break;
+
+        case 'c':
+            append_text(new_line, "{\\c&H%06X&}", tags[i].data1);
+            break;
+
+        case 'f':
+            append_text(new_line, "{\\fn%.*s}", BSTR_P(tags[i].data_string));
+            break;
+
+        case 's':
+            append_text(new_line, "{\\fs%d}", tags[i].data1);
+            break;
+
+        case 'p':
+            if (tags[i].data1 == 0)
+                append_text(new_line, "{\\an8}");
+            break;
+
+        case 'o':
+            append_text(new_line, "{\\pos(%d,%d)}",
+                        tags[i].data1, tags[i].data2);
+            break;
+        }
+        if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
+            tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
+    }
+}
+
+static void microdvd_close_no_persistent_tags(struct line *new_line,
+                                              struct microdvd_tag *tags)
+{
+    int i;
+
+    for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
+        if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
+            continue;
+        switch (tags[i].key) {
+
+        case 'y':
+            for (int sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
+                if (tags[i].data1 & (1 << sidx))
+                    append_text(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
+            break;
+
+        case 'c':
+            append_text(new_line, "{\\c}");
+            break;
+
+        case 'f':
+            append_text(new_line, "{\\fn}");
+            break;
+
+        case 's':
+            append_text(new_line, "{\\fs}");
+            break;
+        }
+        tags[i].key = 0;
+    }
+}
+
+/**
+ * \brief Convert MicroDVD lines into ASS markup
+ * \param orig original MicroDVD line. The content will remain untouched.
+ * \param dest ASS markup destination buffer.
+ * \param dest_buffer_size maximum size for the destination buffer.
+ */
+void subassconvert_microdvd(const char *orig, char *dest, size_t dest_buffer_size)
+{
+    /* line is not const to avoid warnings with strtol, etc.
+     * orig content won't be changed */
+    char *line = (char *)orig;
+    struct line new_line = {
+        .buf     = dest,
+        .bufsize = dest_buffer_size,
+    };
+    struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {};
+
+    while (*line) {
+        line = microdvd_load_tags(tags, line);
+        microdvd_open_tags(&new_line, tags);
+
+        while (*line && *line != '|')
+            new_line.buf[new_line.len++] = *line++;
+
+        if (*line == '|') {
+            microdvd_close_no_persistent_tags(&new_line, tags);
+            append_text(&new_line, "\\N");
+            line++;
+        }
+    }
+    new_line.buf[new_line.len] = 0;
+}
Index: subassconvert.h
===================================================================
--- subassconvert.h	(revision 0)
+++ subassconvert.h	(revision 0)
@@ -0,0 +1,27 @@
+/*
+ * Header for subtitles converter to SSA/ASS
+ *
+ * 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_SUBASSCONVERT_H
+#define MPLAYER_SUBASSCONVERT_H
+
+void subassconvert_subrip(const char *orig, char *dest, size_t dest_buffer_size);
+void subassconvert_microdvd(const char *orig, char *dest, size_t dest_buffer_size);
+
+#endif
Index: help/help_mp-en.h
===================================================================
--- help/help_mp-en.h	(revision 31652)
+++ help/help_mp-en.h	(working copy)
@@ -2116,3 +2116,7 @@
 
 // url.c
 #define MSGTR_MPDEMUX_URL_StringAlreadyEscaped "String appears to be already escaped in url_escape %c%c1%c2\n"
+
+// subtitles
+#define MSGTR_SUBTITLES_SubRip_UnknownFontColor "SubRip: unknown font color in subtitle: %s\n"
+
Index: subreader.c
===================================================================
--- subreader.c	(revision 31652)
+++ subreader.c	(working copy)
@@ -33,10 +33,12 @@
 #include "mp_msg.h"
 #include "mpcommon.h"
 #include "subreader.h"
+#include "subassconvert.h"
 #include "libvo/sub.h"
 #include "stream/stream.h"
 #include "libavutil/common.h"
 #include "libavutil/avstring.h"
+#include "libass/ass_mp.h"
 
 #ifdef CONFIG_ENCA
 #include <enca.h>
@@ -296,7 +298,11 @@
 		      "{%ld}{%ld}%[^\r\n]",
 		      &(current->start), &(current->end), line2) < 3));
 
-    p=line2;
+    if (ass_enabled) {
+        subassconvert_microdvd(line2, line, LINE_LEN + 1);
+        p = line;
+    } else
+        p = line2;
 
     next=p, i=0;
     while ((next =sub_readtext (next, &(current->text[i])))) {
@@ -365,12 +371,70 @@
     return current;
 }
 
+static subtitle *sub_ass_read_line_subviewer(stream_t *st, subtitle *current, int utf16)
+{
+    int h1, m1, s1, ms1, h2, m2, s2, ms2, j = 0;
+
+    while (!current->text[0]) {
+        char line[LINE_LEN + 1], full_line[LINE_LEN + 1], sep;
+        int i;
+
+        /* Parse SubRip header */
+        if (!stream_read_line(st, line, LINE_LEN, utf16))
+            return NULL;
+        if (sscanf(line, "%d:%d:%d%[,.:]%d --> %d:%d:%d%[,.:]%d",
+                     &h1, &m1, &s1, &sep, &ms1, &h2, &m2, &s2, &sep, &ms2) < 10)
+            continue;
+
+        current->start = h1 * 360000 + m1 * 6000 + s1 * 100 + ms1 / 10;
+        current->end   = h2 * 360000 + m2 * 6000 + s2 * 100 + ms2 / 10;
+
+        /* Concat lines */
+        full_line[0] = 0;
+        for (i = 0; i < SUB_MAX_TEXT; i++) {
+            int blank = 1, len = 0;
+            char *p;
+
+            if (!stream_read_line(st, line, LINE_LEN, utf16))
+                break;
+
+            for (p = line; *p != '\n' && *p != '\r' && *p; p++, len++)
+                if (*p != ' ' && *p != '\t')
+                    blank = 0;
+
+            if (blank)
+                break;
+
+            *p = 0;
+
+            if (len >= sizeof(full_line) - j - 2)
+                break;
+
+            if (j != 0)
+                full_line[j++] = '\n';
+            strcpy(&full_line[j], line);
+            j += len;
+        }
+
+        /* Use the ASS/SSA converter to transform the whole lines */
+        if (full_line[0]) {
+            char converted_line[LINE_LEN + 1];
+            subassconvert_subrip(full_line, converted_line, LINE_LEN + 1);
+            current->text[0] = strdup(converted_line);
+            current->lines = 1;
+        }
+    }
+    return current;
+}
+
 static subtitle *sub_read_line_subviewer(stream_t *st,subtitle *current, int utf16) {
     char line[LINE_LEN+1];
     int a1,a2,a3,a4,b1,b2,b3,b4;
     char *p=NULL;
     int i,len;
 
+    if (ass_enabled)
+        return sub_ass_read_line_subviewer(st, current, utf16);
     while (!current->text[0]) {
 	if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
 	if ((len=sscanf (line, "%d:%d:%d%[,.:]%d --> %d:%d:%d%[,.:]%d",&a1,&a2,&a3,(char *)&i,&a4,&b1,&b2,&b3,(char *)&i,&b4)) < 10)
@@ -2290,12 +2354,13 @@
  * \param txt text to parse
  * \param len length of text in txt
  * \param endpts pts at which this subtitle text should be removed again
+ * \param strip_markup if strip markup is set (!= 0), markup tags like <b></b> are ignored
  *
  * <> and {} are interpreted as comment delimiters, "\n", "\N", '\n', '\r'
  * and '\0' are interpreted as newlines, duplicate, leading and trailing
  * newlines are ignored.
  */
-void sub_add_text(subtitle *sub, const char *txt, int len, double endpts) {
+void sub_add_text(subtitle *sub, const char *txt, int len, double endpts, int strip_markup) {
   int comment = 0;
   int double_newline = 1; // ignore newlines at the beginning
   int i, pos;
@@ -2308,42 +2373,48 @@
   buf = malloc(MAX_SUBLINE + 1);
   sub->text[sub->lines] = buf;
   sub->endpts[sub->lines] = endpts;
-  for (i = 0; i < len && pos < MAX_SUBLINE; i++) {
-    char c = txt[i];
-    if (c == '<') comment |= 1;
-    if (c == '{') comment |= 2;
-    if (comment) {
-      if (c == '}') comment &= ~2;
-      if (c == '>') comment &= ~1;
-      continue;
-    }
-    if (pos == MAX_SUBLINE - 1) {
-      i--;
-      c = 0;
-    }
-    if (c == '\\' && i + 1 < len) {
-      c = txt[++i];
-      if (c == 'n' || c == 'N') c = 0;
-    }
-    if (c == '\n' || c == '\r') c = 0;
-    if (c) {
-      double_newline = 0;
-      buf[pos++] = c;
-    } else if (!double_newline) {
-      if (sub->lines >= SUB_MAX_TEXT - 1) {
-        mp_msg(MSGT_VO, MSGL_WARN, "Too many subtitle lines\n");
-        break;
+
+  if (!strip_markup) {
+    subassconvert_subrip(txt, buf, MAX_SUBLINE + 1);
+    sub->text[sub->lines] = buf;
+  } else {
+    for (i = 0; i < len && pos < MAX_SUBLINE; i++) {
+      char c = txt[i];
+      if (c == '<') comment |= 1;
+      if (c == '{') comment |= 2;
+      if (comment) {
+        if (c == '}') comment &= ~2;
+        if (c == '>') comment &= ~1;
+        continue;
       }
-      double_newline = 1;
-      buf[pos] = 0;
-      sub->lines++;
-      pos = 0;
-      buf = malloc(MAX_SUBLINE + 1);
-      sub->text[sub->lines] = buf;
-      sub->endpts[sub->lines] = endpts;
+      if (pos == MAX_SUBLINE - 1) {
+        i--;
+        c = 0;
+      }
+      if (c == '\\' && i + 1 < len) {
+        c = txt[++i];
+        if (c == 'n' || c == 'N') c = 0;
+      }
+      if (c == '\n' || c == '\r') c = 0;
+      if (c) {
+        double_newline = 0;
+        buf[pos++] = c;
+      } else if (!double_newline) {
+        if (sub->lines >= SUB_MAX_TEXT - 1) {
+          mp_msg(MSGT_VO, MSGL_WARN, "Too many subtitle lines\n");
+          break;
+        }
+        double_newline = 1;
+        buf[pos] = 0;
+        sub->lines++;
+        pos = 0;
+        buf = malloc(MAX_SUBLINE + 1);
+        sub->text[sub->lines] = buf;
+        sub->endpts[sub->lines] = endpts;
+      }
     }
+    buf[pos] = 0;
   }
-  buf[pos] = 0;
   if (sub->lines < SUB_MAX_TEXT &&
       strlen(sub->text[sub->lines]))
     sub->lines++;
Index: subreader.h
===================================================================
--- subreader.h	(revision 31652)
+++ subreader.h	(working copy)
@@ -105,7 +105,7 @@
 void sub_free( sub_data * subd );
 void find_sub(sub_data* subd,int key);
 void step_sub(sub_data *subd, float pts, int movement);
-void sub_add_text(subtitle *sub, const char *txt, int len, double endpts);
+void sub_add_text(subtitle *sub, const char *txt, int len, double endpts, int strip_markup);
 int sub_clear_text(subtitle *sub, double pts);
 
 #endif /* MPLAYER_SUBREADER_H */
Index: libmpdemux/demux_ogg.c
===================================================================
--- libmpdemux/demux_ogg.c	(revision 31652)
+++ libmpdemux/demux_ogg.c	(working copy)
@@ -212,7 +212,7 @@
             endpts = 1.0 + pts + (float)duration / 1000.0;
         }
         sub_clear_text(&ogg_sub, MP_NOPTS_VALUE);
-        sub_add_text(&ogg_sub, &packet[lcv], pack->bytes - lcv, endpts);
+        sub_add_text(&ogg_sub, &packet[lcv], pack->bytes - lcv, endpts, 1);
     }
 
     mp_msg(MSGT_DEMUX, MSGL_DBG2, "Ogg sub lines: %d  first: '%s'\n",
Index: mpcommon.c
===================================================================
--- mpcommon.c	(revision 31652)
+++ mpcommon.c	(working copy)
@@ -214,7 +214,7 @@
                     if (subpts != MP_NOPTS_VALUE) {
                         subtitle tmp_subs = {0};
                         if (endpts == MP_NOPTS_VALUE) endpts = subpts + 3;
-                        sub_add_text(&tmp_subs, packet, len, endpts);
+                        sub_add_text(&tmp_subs, packet, len, endpts, 0);
                         tmp_subs.start = subpts * 100;
                         tmp_subs.end = endpts * 100;
                         ass_process_subtitle(ass_track, &tmp_subs);
@@ -241,7 +241,7 @@
                     len -= p - packet;
                     packet = p;
                 }
-                sub_add_text(&subs, packet, len, endpts);
+                sub_add_text(&subs, packet, len, endpts, 1);
                 set_osd_subtitle(&subs);
             }
             if (d_dvdsub->non_interleaved)


More information about the MPlayer-dev-eng mailing list