]> granicus.if.org Git - handbrake/commitdiff
ssa: improve SSA to TX3G conversion
authorJohn Stebbins <jstebbins.hb@gmail.com>
Mon, 7 Jan 2019 17:52:28 +0000 (10:52 -0700)
committerJohn Stebbins <jstebbins.hb@gmail.com>
Mon, 14 Jan 2019 21:36:08 +0000 (13:36 -0800)
We were only applying SSA inline override tags.  With this patch we now
parse SSA style descritions in the SSA header and apply them per event.

libhb/decssasub.c
libhb/decssasub.h
libhb/internal.h
libhb/muxavformat.c
libhb/muxcommon.c
libhb/ssautil.c [new file with mode: 0644]
libhb/ssautil.h [new file with mode: 0644]
libhb/sync.c

index 2e95ba78924c686e72a344e6810a95e57e1f7c41..2bc06c7b919ce0ccf0d92f7c2d54edad6c0d9ce3 100644 (file)
@@ -44,184 +44,6 @@ struct hb_work_private_s
 
 #define SSA_VERBOSE_PACKETS 0
 
-static int ssa_update_style(char *ssa, hb_subtitle_style_t *style)
-{
-    int pos, end, index;
-
-    if (ssa[0] != '{')
-        return 0;
-
-    pos = 1;
-    while (ssa[pos] != '}' && ssa[pos] != '\0')
-    {
-        index = -1;
-
-        // Skip any malformed markup junk
-        while (strchr("\\}", ssa[pos]) == NULL) pos++;
-        pos++;
-        // Check for an index that is in some markup (e.g. font color)
-        if (isdigit(ssa[pos]))
-        {
-            index = ssa[pos++] - 0x30;
-        }
-        // Find the end of this markup clause
-        end = pos;
-        while (strchr("\\}", ssa[end]) == NULL) end++;
-        // Handle simple integer valued attributes
-        if (strchr("ibu", ssa[pos]) != NULL && isdigit(ssa[pos+1]))
-        {
-            int val = strtol(ssa + pos + 1, NULL, 0);
-            switch (ssa[pos])
-            {
-                case 'i':
-                    style->flags = (style->flags & ~HB_STYLE_FLAG_ITALIC) |
-                                   !!val * HB_STYLE_FLAG_ITALIC;
-                    break;
-                case 'b':
-                    style->flags = (style->flags & ~HB_STYLE_FLAG_BOLD) |
-                                   !!val * HB_STYLE_FLAG_BOLD;
-                    break;
-                case 'u':
-                    style->flags = (style->flags & ~HB_STYLE_FLAG_UNDERLINE) |
-                                   !!val * HB_STYLE_FLAG_UNDERLINE;
-                    break;
-            }
-        }
-        if (ssa[pos] == 'c' && ssa[pos+1] == '&' && ssa[pos+2] == 'H')
-        {
-            // Font color markup
-            char *endptr;
-            uint32_t bgr;
-
-            bgr = strtol(ssa + pos + 3, &endptr, 16);
-            if (*endptr == '&')
-            {
-                switch (index)
-                {
-                    case -1:
-                    case 1:
-                        style->fg_rgb = HB_BGR_TO_RGB(bgr);
-                        break;
-                    case 2:
-                        style->alt_rgb = HB_BGR_TO_RGB(bgr);
-                        break;
-                    case 3:
-                        style->ol_rgb = HB_BGR_TO_RGB(bgr);
-                        break;
-                    case 4:
-                        style->bg_rgb = HB_BGR_TO_RGB(bgr);
-                        break;
-                    default:
-                        // Unknown color index, ignore
-                        break;
-                }
-            }
-        }
-        if ((ssa[pos] == 'a' && ssa[pos+1] == '&' && ssa[pos+2] == 'H') ||
-            (!strcmp(ssa+pos, "alpha") && ssa[pos+5] == '&' && ssa[pos+6] == 'H'))
-        {
-            // Font alpha markup
-            char *endptr;
-            uint8_t alpha;
-            int alpha_pos = 3;
-
-            if (ssa[1] == 'l')
-                alpha_pos = 7;
-
-            alpha = strtol(ssa + pos + alpha_pos, &endptr, 16);
-            if (*endptr == '&')
-            {
-                // SSA alpha is inverted 0 is opaque
-                alpha = 255 - alpha;
-                switch (index)
-                {
-                    case -1:
-                    case 1:
-                        style->fg_alpha = alpha;
-                        break;
-                    case 2:
-                        style->alt_alpha = alpha;
-                        break;
-                    case 3:
-                        style->ol_alpha = alpha;
-                        break;
-                    case 4:
-                        style->bg_alpha = alpha;
-                        break;
-                    default:
-                        // Unknown alpha index, ignore
-                        break;
-                }
-            }
-        }
-        pos = end;
-    }
-    if (ssa[pos] == '}')
-        pos++;
-    return pos;
-}
-
-char * hb_ssa_to_text(char *in, int *consumed, hb_subtitle_style_t *style)
-{
-    int markup_len = 0;
-    int in_pos = 0;
-    int out_pos = 0;
-    char *out = malloc(strlen(in) + 1); // out will never be longer than in
-
-    for (in_pos = 0; in[in_pos] != '\0'; in_pos++)
-    {
-        if ((markup_len = ssa_update_style(in + in_pos, style)))
-        {
-            *consumed = in_pos + markup_len;
-            out[out_pos++] = '\0';
-            return out;
-        }
-        // Check escape codes
-        if (in[in_pos] == '\\')
-        {
-            in_pos++;
-            switch (in[in_pos])
-            {
-                case '\0':
-                    in_pos--;
-                    break;
-                case 'N':
-                case 'n':
-                    out[out_pos++] = '\n';
-                    break;
-                case 'h':
-                    out[out_pos++] = ' ';
-                    break;
-                default:
-                    out[out_pos++] = in[in_pos];
-                    break;
-            }
-        }
-        else
-        {
-            out[out_pos++] = in[in_pos];
-        }
-    }
-    *consumed = in_pos;
-    out[out_pos++] = '\0';
-    return out;
-}
-
-void hb_ssa_style_init(hb_subtitle_style_t *style)
-{
-    style->flags = 0;
-
-    style->fg_rgb    = 0x00FFFFFF;
-    style->alt_rgb   = 0x00FFFFFF;
-    style->ol_rgb    = 0x000F0F0F;
-    style->bg_rgb    = 0x000F0F0F;
-
-    style->fg_alpha  = 0xFF;
-    style->alt_alpha = 0xFF;
-    style->ol_alpha  = 0xFF;
-    style->bg_alpha  = 0xFF;
-}
-
 static int extradataInit( hb_work_private_t * pv )
 {
     int    events = 0;
@@ -350,7 +172,7 @@ static int parse_timing( char *line, int64_t *start, int64_t *stop )
     // specifier is placed directly next to the ':' so that the next
     // expected ' ' after the ':' will be the character it matches on
     // when there is no layer field.
-    int numPartsRead = sscanf( (char *) line, "Dialogue:%*128[^,],"
+    int numPartsRead = sscanf(line, "Dialogue:%*128[^,],"
         "%d:%d:%d.%d,"  // Start
         "%d:%d:%d.%d,", // End
         &start_hr, &start_min, &start_sec, &start_centi,
@@ -478,9 +300,11 @@ static hb_buffer_t * ssa_read( hb_work_private_t * pv )
             out = decode_line_to_mkv_ssa(pv, line, len);
             if (out != NULL)
             {
+                free(line);
                 return out;
             }
         }
+        free(line);
         if (len < 0)
         {
             // Error or EOF
index 174f056f6f8899b9955c783e9e396c8905749ae0..19c7ce6332d5523b5da4c3bd7c3238efc4652294 100644 (file)
 #ifndef __DECSSASUB_H__
 #define __DECSSASUB_H__
 
-typedef struct
-{
-    uint32_t    flags;
-
-    uint32_t    fg_rgb;     // foreground color
-    uint32_t    alt_rgb;    // secondary color
-    uint32_t    ol_rgb;     // outline color
-    uint32_t    bg_rgb;     // background color
-
-    uint32_t    fg_alpha;     // foreground alpha
-    uint32_t    alt_alpha;    // secondary alpha
-    uint32_t    ol_alpha;     // outline alpha
-    uint32_t    bg_alpha;     // background alpha
-} hb_subtitle_style_t;
-
-#define HB_STYLE_FLAG_ITALIC    0x0001
-#define HB_STYLE_FLAG_BOLD      0x0002
-#define HB_STYLE_FLAG_UNDERLINE 0x0004
-
-char * hb_ssa_to_text(char *in, int *consumed, hb_subtitle_style_t *style);
-void hb_ssa_style_init(hb_subtitle_style_t *style);
-
 #endif // __DECSSASUB_H__
index 59b0677b8c4657516778f780534f9194a18f8bb5..cd46c12657edf55cbc28f13c4c1791e7bbf1aed1 100644 (file)
@@ -494,10 +494,6 @@ DECLARE_MUX( mp4 );
 DECLARE_MUX( mkv );
 DECLARE_MUX( avformat );
 
-void hb_muxmp4_process_subtitle_style(int        height,
-                                      uint8_t  * input, uint8_t  ** output,
-                                      uint8_t ** style, uint16_t  * stylesize);
-
 void hb_deinterlace(hb_buffer_t *dst, hb_buffer_t *src);
 void hb_avfilter_combine( hb_list_t * list );
 char * hb_append_filter_string(char * graph_str, char * filter_str);
index 60eba066fd5b678dc7d767d8c65d6bb007885234..ff041b47c6f2cd9a62f020f2a0ed4236d425fa9e 100644 (file)
@@ -13,6 +13,7 @@
 #include "libavutil/intreadwrite.h"
 
 #include "hb.h"
+#include "ssautil.h"
 #include "lang.h"
 
 struct hb_mux_data_s
@@ -33,7 +34,8 @@ struct hb_mux_data_s
     int64_t  prev_chapter_tc;
     int16_t  current_chapter;
 
-    AVBSFContext             * bitstream_context;
+    AVBSFContext            * bitstream_context;
+    hb_tx3g_style_context_t * tx3g;
 };
 
 struct hb_mux_object_s
@@ -850,6 +852,8 @@ static int avformatInit( hb_mux_object_t * m )
                 if (job->mux == HB_MUX_AV_MP4)
                 {
                     track->st->codecpar->codec_id = AV_CODEC_ID_MOV_TEXT;
+                    track->tx3g = hb_tx3g_style_init(
+                                job->height, (char*)subtitle->extradata);
                 }
                 else
                 {
@@ -1344,8 +1348,8 @@ static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *bu
                      * creating style atoms for them.
                      */
                     hb_muxmp4_process_subtitle_style(
-                        job->height, buf->data, &buffer,
-                        &styleatom, &stylesize );
+                        track->tx3g, buf->data, &buffer,
+                        &styleatom, &stylesize);
 
                     if (buffer != NULL)
                     {
@@ -1453,6 +1457,10 @@ static int avformatEnd(hb_mux_object_t *m)
         {
             av_bsf_free(&m->tracks[ii]->bitstream_context);
         }
+        if (m->tracks[ii]->tx3g)
+        {
+            hb_tx3g_style_close(&m->tracks[ii]->tx3g);
+        }
     }
 
     if (job->chapter_markers)
index a61fc08036928b29396ed52fd476427bb22d369d..5034b32e3a6a540ffc4d5f2a8fa1210a59935f35 100644 (file)
@@ -707,237 +707,3 @@ hb_work_object_t hb_muxer =
     muxClose
 };
 
-#define TX3G_STYLES (HB_STYLE_FLAG_BOLD   |   \
-                     HB_STYLE_FLAG_ITALIC |   \
-                     HB_STYLE_FLAG_UNDERLINE)
-
-struct output_buf_s
-{
-    int       alloc;
-    int       size;
-    uint8_t * buf;
-};
-
-typedef struct style_context_s
-{
-    struct output_buf_s   style_atoms;
-    int                   style_atom_count;
-    hb_subtitle_style_t   current_style;
-    int                   style_start;
-    int                   height;
-} style_context_t;
-
-static int check_realloc_output(struct output_buf_s * output, int size)
-{
-    if (output->alloc < size)
-    {
-        uint8_t * tmp;
-
-        output->alloc = size + 1024;
-        output->size  = size;
-        tmp = realloc(output->buf, output->alloc);
-        if (tmp == NULL)
-        {
-            hb_error("realloc failed!");
-            free(output->buf);
-            output->size = 0;
-            output->alloc = 0;
-            output->buf   = NULL;
-            return 0;
-        }
-        output->buf = tmp;
-    }
-    return 1;
-}
-
-static int update_style_atoms(style_context_t *ctx, int stop)
-{
-    uint8_t * style_entry;
-    uint8_t   face = 0;
-    int       font_size;
-    int       pos  = 10 + (12 * ctx->style_atom_count);
-    int       size = 10 + (12 * (ctx->style_atom_count + 1));
-
-    if (!check_realloc_output(&ctx->style_atoms, size))
-    {
-        return 0;
-    }
-    style_entry = ctx->style_atoms.buf + pos;
-
-    if (ctx->current_style.flags & HB_STYLE_FLAG_BOLD)
-        face |= 1;
-    if (ctx->current_style.flags & HB_STYLE_FLAG_ITALIC)
-        face |= 2;
-    if (ctx->current_style.flags & HB_STYLE_FLAG_UNDERLINE)
-        face |= 4;
-
-    style_entry[0]  = (ctx->style_start >> 8) & 0xff;   // startChar
-    style_entry[1]  = ctx->style_start & 0xff;
-    style_entry[2]  = (stop >> 8) & 0xff;               // endChar
-    style_entry[3]  = stop & 0xff;
-    style_entry[4]  = 0;            // font-ID msb
-    style_entry[5]  = 1;            // font-ID lsb
-    style_entry[6]  = face;         // face-style-flags
-    font_size       = 0.05 * ctx->height;
-    if (font_size < 12)
-    {
-        font_size = 12;
-    }
-    else if (font_size > 255)
-    {
-        font_size = 255;
-    }
-    style_entry[7]  = font_size;    // font-size
-    style_entry[8]  = (ctx->current_style.fg_rgb >> 16) & 0xff; // r
-    style_entry[9]  = (ctx->current_style.fg_rgb >> 8)  & 0xff; // g
-    style_entry[10] = (ctx->current_style.fg_rgb)       & 0xff; // b
-    style_entry[11] = ctx->current_style.fg_alpha;              // a
-
-    ctx->style_atom_count++;
-
-    return 1;
-}
-
-static int update_style(style_context_t *ctx,
-                        hb_subtitle_style_t *style, int pos)
-{
-    if (ctx->style_start < pos)
-    {
-        // do we need to add a style atom?
-        if (((ctx->current_style.flags  ^ style->flags) & TX3G_STYLES) ||
-            ctx->current_style.fg_rgb   != style->fg_rgb               ||
-            ctx->current_style.fg_alpha != style->fg_alpha)
-        {
-            if (!update_style_atoms(ctx, pos - 1))
-            {
-                return 0;
-            }
-            ctx->current_style = *style;
-            ctx->style_start = pos;
-        }
-    }
-    else
-    {
-        ctx->current_style = *style;
-        ctx->style_start = pos;
-    }
-    return 1;
-}
-
-static void style_context_init(style_context_t *ctx)
-{
-    memset(ctx, 0, sizeof(*ctx));
-    ctx->style_atoms.buf   = NULL;
-    ctx->style_atoms.size  = 0;
-    ctx->style_atoms.alloc = 0;
-    ctx->style_start       = INT_MAX;
-}
-
-/*
- * Copy the input to output removing markup and adding markup to the style
- * atom where appropriate.
- */
-void hb_muxmp4_process_subtitle_style(int        height,
-                                      uint8_t  * input,
-                                      uint8_t ** out_buf,
-                                      uint8_t ** out_style_atoms,
-                                      uint16_t * stylesize)
-{
-    uint16_t              utf8_count = 0; // utf8 count from start of subtitle
-    int                   consumed, in_pos = 0, out_pos = 0, len, ii;
-    style_context_t       ctx;
-    hb_subtitle_style_t   style;
-    struct output_buf_s   output;
-    char                * text, * tmp;
-
-    output.buf       = NULL;
-    output.alloc     = 0;
-    output.size      = 0;
-    *out_buf         = NULL;
-    *out_style_atoms = NULL;
-    *stylesize       = 0;
-
-    style_context_init(&ctx);
-    ctx.height = height;
-    hb_ssa_style_init(&style);
-
-    // Skip past the SSA preamble
-    text = (char*)input;
-    for (ii = 0; ii < 8; ii++)
-    {
-        tmp = strchr(text, ',');
-        if (tmp == NULL)
-            break;
-        text = tmp + 1;
-    }
-    in_pos = text - (char*)input;
-
-    // Always allocate enough for empty string
-    if (!check_realloc_output(&output, 1))
-    {
-        goto fail;
-    }
-    while (input[in_pos] != '\0')
-    {
-        text = hb_ssa_to_text((char*)input + in_pos, &consumed, &style);
-        if (text == NULL)
-            break;
-
-        // count UTF8 characters, and get length of text
-        len = 0;
-        for (ii = 0; text[ii] != '\0'; ii++)
-        {
-            if ((text[ii] & 0xc0) == 0x80)
-            {
-                utf8_count++;
-                hb_deep_log( 3, "mux: Counted %d UTF-8 chrs within subtitle",
-                                 utf8_count);
-            }
-            len++;
-        }
-        if (!check_realloc_output(&output, out_pos + len + 1))
-        {
-            goto fail;
-        }
-        strcpy((char*)output.buf + out_pos, text);
-        free(text);
-        out_pos += len;
-        in_pos += consumed;
-        if (!update_style(&ctx, &style, out_pos - utf8_count))
-        {
-            goto fail;
-        }
-    }
-    // Return to default style at end of line, flushes any pending
-    // style changes
-    hb_ssa_style_init(&style);
-    if (!update_style(&ctx, &style, out_pos - utf8_count))
-    {
-        goto fail;
-    }
-
-    // null terminate output string
-    output.buf[out_pos] = 0;
-
-    if (ctx.style_atom_count > 0)
-    {
-        *stylesize = 10 + (ctx.style_atom_count * 12);
-
-        memcpy(ctx.style_atoms.buf + 4, "styl", 4);
-
-        ctx.style_atoms.buf[0] = 0;
-        ctx.style_atoms.buf[1] = 0;
-        ctx.style_atoms.buf[2] = (*stylesize >> 8) & 0xff;
-        ctx.style_atoms.buf[3] = *stylesize & 0xff;
-        ctx.style_atoms.buf[8] = (ctx.style_atom_count >> 8) & 0xff;
-        ctx.style_atoms.buf[9] = ctx.style_atom_count & 0xff;
-        *out_style_atoms = ctx.style_atoms.buf;
-    }
-    *out_buf = output.buf;
-    return;
-
-fail:
-    free(output.buf);
-    free(ctx.style_atoms.buf);
-}
-
diff --git a/libhb/ssautil.c b/libhb/ssautil.c
new file mode 100644 (file)
index 0000000..7bc067a
--- /dev/null
@@ -0,0 +1,925 @@
+/* ssautil.c
+
+   Copyright (c) 2003-2019 HandBrake Team
+   This file is part of the HandBrake source code
+   Homepage: <http://handbrake.fr/>.
+   It may be used under the terms of the GNU General Public License v2.
+   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include "hb.h"
+#include "ssautil.h"
+
+struct hb_subtitle_style_s
+{
+    char      * name;
+    char      * font_name;
+    int         font_size;
+
+    uint32_t    flags;
+
+    uint32_t    fg_rgb;     // foreground color
+    uint32_t    alt_rgb;    // secondary color
+    uint32_t    ol_rgb;     // outline color
+    uint32_t    bg_rgb;     // background color
+
+    uint32_t    fg_alpha;     // foreground alpha
+    uint32_t    alt_alpha;    // secondary alpha
+    uint32_t    ol_alpha;     // outline alpha
+    uint32_t    bg_alpha;     // background alpha
+};
+
+struct hb_subtitle_style_context_s
+{
+    hb_subtitle_style_t   current;
+    int                   event_style_default;
+    hb_subtitle_style_t * styles;
+    int                   styles_count;
+    int                   styles_size;
+    int                   style_default;
+};
+
+struct hb_tx3g_output_buf_s
+{
+    int       alloc;
+    int       size;
+    uint8_t * buf;
+};
+
+struct hb_tx3g_style_context_s
+{
+    hb_tx3g_output_buf_t          style_atoms;
+    int                           style_atom_count;
+    int                           style_start;
+    int                           height;
+    hb_subtitle_style_context_t * in_style;
+    hb_subtitle_style_t           out_style;
+    uint8_t                       flush;
+};
+
+static void ssa_style_reset(hb_subtitle_style_context_t * ctx)
+{
+    if (ctx->styles != NULL && ctx->style_default < ctx->styles_count)
+    {
+        ctx->current = ctx->styles[ctx->event_style_default];
+    }
+    else
+    {
+        ctx->current.font_name = HB_FONT_SANS;
+        ctx->current.font_size = 72;
+        ctx->current.flags     = 0;
+        ctx->current.fg_rgb    = 0x00FFFFFF;
+        ctx->current.alt_rgb   = 0x00FFFFFF;
+        ctx->current.ol_rgb    = 0x000F0F0F;
+        ctx->current.bg_rgb    = 0x000F0F0F;
+
+        ctx->current.fg_alpha  = 0xFF;
+        ctx->current.alt_alpha = 0xFF;
+        ctx->current.ol_alpha  = 0xFF;
+        ctx->current.bg_alpha  = 0xFF;
+    }
+}
+
+static int ssa_style_set(hb_subtitle_style_context_t * ctx, const char * style)
+{
+    int ii;
+
+    if (ctx->styles != NULL && style != NULL && style[0] != 0)
+    {
+        for (ii = 0; ii < ctx->styles_count; ii++)
+        {
+            if (!strcasecmp(ctx->styles[ii].name, style))
+            {
+                ctx->current = ctx->styles[ii];
+                return ii;
+            }
+        }
+    }
+    ssa_style_reset(ctx);
+    return ctx->style_default;
+}
+
+static int ssa_update_style(const char *ssa, hb_subtitle_style_context_t *ctx)
+{
+    int pos, end, index;
+    hb_subtitle_style_t * style = &ctx->current;
+
+    if (ssa[0] != '{')
+        return 0;
+
+    pos = 1;
+    while (ssa[pos] != '}' && ssa[pos] != '\0')
+    {
+        index = -1;
+
+        // Skip any malformed markup junk
+        while (strchr("\\}", ssa[pos]) == NULL) pos++;
+        pos++;
+        // Check for an index that is in some markup (e.g. font color)
+        if (isdigit(ssa[pos]))
+        {
+            index = ssa[pos++] - 0x30;
+        }
+        // Find the end of this markup clause
+        end = pos;
+        while (strchr("\\}", ssa[end]) == NULL) end++;
+        // Handle simple integer valued attributes
+        if (strchr("ibu", ssa[pos]) != NULL && isdigit(ssa[pos+1]))
+        {
+            int val = strtol(ssa + pos + 1, NULL, 0);
+            switch (ssa[pos])
+            {
+                case 'i':
+                    style->flags = (style->flags & ~HB_STYLE_FLAG_ITALIC) |
+                                   !!val * HB_STYLE_FLAG_ITALIC;
+                    break;
+                case 'b':
+                    style->flags = (style->flags & ~HB_STYLE_FLAG_BOLD) |
+                                   !!val * HB_STYLE_FLAG_BOLD;
+                    break;
+                case 'u':
+                    style->flags = (style->flags & ~HB_STYLE_FLAG_UNDERLINE) |
+                                   !!val * HB_STYLE_FLAG_UNDERLINE;
+                    break;
+            }
+        }
+        if (ssa[pos] == 'r')
+        {
+            // Style reset
+            char * style = strndup(ssa + pos + 1, end - (pos + 1));
+            ssa_style_set(ctx, style);
+            free(style);
+        }
+        if (ssa[pos] == 'c' && ssa[pos+1] == '&' && ssa[pos+2] == 'H')
+        {
+            // Font color markup
+            char *endptr;
+            uint32_t bgr;
+
+            bgr = strtol(ssa + pos + 3, &endptr, 16);
+            if (*endptr == '&')
+            {
+                switch (index)
+                {
+                    case -1:
+                    case 1:
+                        style->fg_rgb = HB_BGR_TO_RGB(bgr);
+                        break;
+                    case 2:
+                        style->alt_rgb = HB_BGR_TO_RGB(bgr);
+                        break;
+                    case 3:
+                        style->ol_rgb = HB_BGR_TO_RGB(bgr);
+                        break;
+                    case 4:
+                        style->bg_rgb = HB_BGR_TO_RGB(bgr);
+                        break;
+                    default:
+                        // Unknown color index, ignore
+                        break;
+                }
+            }
+        }
+        if ((ssa[pos] == 'a' && ssa[pos+1] == '&' && ssa[pos+2] == 'H') ||
+            (!strcmp(ssa+pos, "alpha") && ssa[pos+5] == '&' && ssa[pos+6] == 'H'))
+        {
+            // Font alpha markup
+            char *endptr;
+            uint8_t alpha;
+            int alpha_pos = 3;
+
+            if (ssa[1] == 'l')
+                alpha_pos = 7;
+
+            alpha = strtol(ssa + pos + alpha_pos, &endptr, 16);
+            if (*endptr == '&')
+            {
+                // SSA alpha is inverted 0 is opaque
+                alpha = 255 - alpha;
+                switch (index)
+                {
+                    case -1:
+                    case 1:
+                        style->fg_alpha = alpha;
+                        break;
+                    case 2:
+                        style->alt_alpha = alpha;
+                        break;
+                    case 3:
+                        style->ol_alpha = alpha;
+                        break;
+                    case 4:
+                        style->bg_alpha = alpha;
+                        break;
+                    default:
+                        // Unknown alpha index, ignore
+                        break;
+                }
+            }
+        }
+        pos = end;
+    }
+    if (ssa[pos] == '}')
+        pos++;
+    return pos;
+}
+
+static char * ssa_to_text(const char *in, int *consumed,
+                          hb_subtitle_style_context_t *ctx)
+{
+    int markup_len = 0;
+    int in_pos = 0;
+    int out_pos = 0;
+    char *out = malloc(strlen(in) + 1); // out will never be longer than in
+
+    for (in_pos = 0; in[in_pos] != '\0'; in_pos++)
+    {
+        if ((markup_len = ssa_update_style(in + in_pos, ctx)))
+        {
+            *consumed = in_pos + markup_len;
+            out[out_pos++] = '\0';
+            return out;
+        }
+        // Check escape codes
+        if (in[in_pos] == '\\')
+        {
+            in_pos++;
+            switch (in[in_pos])
+            {
+                case '\0':
+                    in_pos--;
+                    break;
+                case 'N':
+                case 'n':
+                    out[out_pos++] = '\n';
+                    break;
+                case 'h':
+                    out[out_pos++] = ' ';
+                    break;
+                default:
+                    out[out_pos++] = in[in_pos];
+                    break;
+            }
+        }
+        else
+        {
+            out[out_pos++] = in[in_pos];
+        }
+    }
+    *consumed = in_pos;
+    out[out_pos++] = '\0';
+    return out;
+}
+
+static char * get_field(char ** pos)
+{
+    char * result = NULL;
+    if (pos == NULL || *pos == NULL || **pos == 0)
+    {
+        return NULL;
+    }
+    char * start = *pos;
+    while (isspace(*start)) start++;
+    char * end = strchr(start, ',');
+    if (end != NULL)
+    {
+        result = strndup(start, end - start);
+        *pos = end + 1;
+    }
+    else
+    {
+        result = strdup(start);
+        *pos = NULL;
+    }
+    return result;
+}
+
+static char * sgetline(char * str)
+{
+    char * eol;
+
+    if (str == NULL)
+    {
+        return NULL;
+    }
+
+    // find end of line
+    eol = strchr(str, '\n');
+    if (eol != NULL)
+    {
+        if (eol > str && *(eol - 1) == '\r')
+        {
+            eol--;
+        }
+    }
+    if (eol != NULL)
+    {
+        return strndup(str, eol - str);
+    }
+    else
+    {
+        return strdup(str);
+    }
+}
+
+static char ** get_fields(char * line, int last)
+{
+    int    count = 0, ii;
+    char * pos;
+
+    if (line == NULL || *line == 0)
+    {
+        return NULL;
+    }
+
+    // count number of fields
+    count = 1;
+    pos = line;
+    while ((pos = strchr(pos, ',')) != NULL)
+    {
+        count++;
+        pos++;
+    }
+    if (last > 0 && count > last)
+    {
+        count = last;
+    }
+    char ** result = calloc(count + 1, sizeof(char*));
+    pos = line;
+    for (ii = 0; ii < count - 1; ii++)
+    {
+        result[ii] = get_field(&pos);
+        printf("field (%s)\n", result[ii]);
+    }
+    result[ii] = strdup(pos);
+
+    return result;
+}
+
+static int field_index(char ** fields, char * name)
+{
+    int ii;
+
+    if (fields == NULL || name == NULL)
+    {
+        return -1;
+    }
+    for (ii = 0; fields[ii] != NULL; ii++)
+    {
+        if (!strcasecmp(name, fields[ii]))
+        {
+            return ii;
+        }
+    }
+    return -1;
+}
+
+static const char * field_value(char ** style, int index)
+{
+    if (index >= 0 && index < hb_str_vlen(style))
+    {
+        return style[index];
+    }
+    return NULL;
+}
+
+typedef struct ssa_style_indicies_s
+{
+    int style_name_index;
+    int font_name_index;
+    int font_size_index;
+    int fg_color_index;
+    int alt_color_index;
+    int ol_color_index;
+    int bg_color_index;
+    int bold_index;
+    int italic_index;
+    int underline_index;
+} ssa_style_indicies_t;
+
+static void fill_field_indicies(char **fields, ssa_style_indicies_t * indices)
+{
+    indices->style_name_index = field_index(fields, "Name");
+    indices->font_name_index  = field_index(fields, "Fontname");
+    indices->font_size_index  = field_index(fields, "Fontsize");
+    indices->fg_color_index   = field_index(fields, "PrimaryColour");
+    indices->alt_color_index  = field_index(fields, "SecondaryColour");
+    indices->ol_color_index   = field_index(fields, "OutlineColour");
+    indices->bg_color_index   = field_index(fields, "BackColour");
+    indices->bold_index       = field_index(fields, "Bold");
+    indices->italic_index     = field_index(fields, "Italic");
+    indices->underline_index  = field_index(fields, "Underline");
+}
+
+static int add_style(hb_subtitle_style_context_t *ctx,
+                      char ** style, ssa_style_indicies_t *field_indices)
+{
+    const char * name;
+    const char * value;
+    int          size;
+    uint32_t     rgb;
+    uint32_t     alpha;
+    uint32_t     flag;
+    int          style_index;
+
+    if (style == NULL)
+    {
+        return 0;
+    }
+    if (ctx->styles_count + 1 > ctx->styles_size)
+    {
+        void * tmp;
+        ctx->styles_size = (ctx->styles_count + 1) * 2;
+
+        tmp = realloc(ctx->styles, ctx->styles_size *
+                      sizeof(hb_subtitle_style_t));
+        if (tmp == NULL)
+        {
+            return 1;
+        }
+        ctx->styles = tmp;
+    }
+    style_index = ctx->styles_count;
+
+    name = field_value(style, field_indices->style_name_index);
+    if (name == NULL)
+    {
+        name = "Default";
+    }
+    if (!strcasecmp(name, "Default"))
+    {
+        ctx->style_default = style_index;
+        ctx->event_style_default = ctx->style_default;
+    }
+    ctx->styles[style_index].name = strdup(name);
+
+    value = field_value(style, field_indices->font_name_index);
+    if (value == NULL)
+    {
+        value = HB_FONT_SANS;
+    }
+    ctx->styles[style_index].font_name = strdup(value);
+
+    value = field_value(style, field_indices->font_size_index);
+    if (value == NULL)
+    {
+        size = 72;
+    }
+    else
+    {
+        size = strtol(value, NULL, 0);
+    }
+    ctx->styles[style_index].font_size = size;
+
+    value = field_value(style, field_indices->fg_color_index);
+    if (value == NULL || strlen(value) < 3)
+    {
+        rgb = 0x00ffffff;
+        alpha = 0xff;
+    }
+    else
+    {
+        int abgr = strtol(value+2, NULL, 16);
+        rgb = HB_BGR_TO_RGB(abgr);
+        alpha = abgr >> 24;
+    }
+    ctx->styles[style_index].fg_rgb = rgb;
+    // SSA alpha is inverted 0 is opaque
+    ctx->styles[style_index].fg_alpha = 255 - alpha;
+
+    value = field_value(style, field_indices->alt_color_index);
+    if (value == NULL || strlen(value) < 3)
+    {
+        rgb = 0x00ffffff;
+        alpha = 0xff;
+    }
+    else
+    {
+        int abgr = strtol(value+2, NULL, 16);
+        rgb = HB_BGR_TO_RGB(abgr);
+        alpha = abgr >> 24;
+    }
+    ctx->styles[style_index].alt_rgb = rgb;
+    ctx->styles[style_index].alt_alpha = alpha;
+
+    value = field_value(style, field_indices->ol_color_index);
+    if (value == NULL || strlen(value) < 3)
+    {
+        rgb = 0x000f0f0f;
+        alpha = 0xff;
+    }
+    else
+    {
+        int abgr = strtol(value+2, NULL, 16);
+        rgb = HB_BGR_TO_RGB(abgr);
+        alpha = abgr >> 24;
+    }
+    ctx->styles[style_index].ol_rgb = rgb;
+    ctx->styles[style_index].ol_alpha = alpha;
+
+    value = field_value(style, field_indices->bg_color_index);
+    if (value == NULL || strlen(value) < 3)
+    {
+        rgb = 0x000f0f0f;
+        alpha = 0xff;
+    }
+    else
+    {
+        int abgr = strtol(value+2, NULL, 16);
+        rgb = HB_BGR_TO_RGB(abgr);
+        alpha = abgr >> 24;
+    }
+    ctx->styles[style_index].bg_rgb = rgb;
+    ctx->styles[style_index].bg_alpha = alpha;
+
+    ctx->styles[style_index].flags = 0;
+
+    value = field_value(style, field_indices->bold_index);
+    if (value == NULL)
+    {
+        flag = HB_STYLE_FLAG_BOLD;
+    }
+    else
+    {
+        flag = strtol(value, NULL, 0) ? HB_STYLE_FLAG_BOLD : 0;
+    }
+    ctx->styles[style_index].flags |= flag;
+
+    value = field_value(style, field_indices->italic_index);
+    if (value == NULL)
+    {
+        flag = HB_STYLE_FLAG_ITALIC;
+    }
+    else
+    {
+        flag = strtol(value, NULL, 0) ? HB_STYLE_FLAG_ITALIC : 0;
+    }
+    ctx->styles[style_index].flags |= flag;
+
+    value = field_value(style, field_indices->underline_index);
+    if (value == NULL)
+    {
+        flag = HB_STYLE_FLAG_UNDERLINE;
+    }
+    else
+    {
+        flag = strtol(value, NULL, 0) ? HB_STYLE_FLAG_UNDERLINE : 0;
+    }
+    ctx->styles[style_index].flags |= flag;
+
+    ctx->styles_count = style_index + 1;
+
+    return 0;
+}
+
+hb_subtitle_style_context_t * hb_subtitle_style_init(const char * ssa_header)
+{
+    hb_subtitle_style_context_t * ctx;
+
+    ctx = calloc(1, sizeof(*ctx));
+    if (ctx == NULL)
+    {
+        return NULL;
+    }
+    if (ssa_header != NULL)
+    {
+        // Find beginning of styles
+        char *  pos = strstr(ssa_header, "[V4");
+
+        if (pos != NULL)
+        {
+            pos = strstr(pos, "\nFormat:");
+            if (pos != NULL)
+            {
+                char ** fields;
+                int     next = 7;
+                char  * line = sgetline(pos + 8);
+
+                fields = get_fields(line, 0);
+                free(line);
+
+                if (fields != NULL)
+                {
+                    ssa_style_indicies_t field_indices;
+
+                    fill_field_indicies(fields, &field_indices);
+
+                    pos    = strstr(pos, "\nStyle:");
+                    while (pos != NULL)
+                    {
+                        char ** style;
+
+                        line = sgetline(pos + next);
+                        style = get_fields(line, 0);
+                        free(line);
+
+                        if (add_style(ctx, style, &field_indices))
+                        {
+                            hb_str_vfree(style);
+                            break;
+                        }
+                        pos = strchr(pos + next, '\n');
+                        next = 1;
+
+                        hb_str_vfree(style);
+                    }
+
+                    hb_str_vfree(fields);
+                }
+            }
+        }
+    }
+    ssa_style_reset(ctx);
+    return ctx;
+}
+
+void hb_subtitle_style_close(hb_subtitle_style_context_t ** pctx)
+{
+    if (pctx == NULL || *pctx == NULL)
+    {
+        return;
+    }
+    hb_subtitle_style_context_t * ctx = *pctx;
+
+    if (ctx->styles != NULL)
+    {
+        int ii;
+
+        for (ii = 0; ii < ctx->styles_count; ii++)
+        {
+            free(ctx->styles[ii].name);
+            free(ctx->styles[ii].font_name);
+        }
+    }
+    free(ctx->styles);
+    free(ctx);
+    *pctx = NULL;
+}
+
+#define TX3G_STYLES (HB_STYLE_FLAG_BOLD   |   \
+                     HB_STYLE_FLAG_ITALIC |   \
+                     HB_STYLE_FLAG_UNDERLINE)
+
+static int check_realloc_output(hb_tx3g_output_buf_t * output, int size)
+{
+    if (output->alloc < size)
+    {
+        uint8_t * tmp;
+
+        output->alloc = size + 1024;
+        output->size  = size;
+        tmp = realloc(output->buf, output->alloc);
+        if (tmp == NULL)
+        {
+            hb_error("realloc failed!");
+            free(output->buf);
+            output->size = 0;
+            output->alloc = 0;
+            output->buf   = NULL;
+            return 0;
+        }
+        output->buf = tmp;
+    }
+    return 1;
+}
+
+static int tx3g_update_style_atoms(hb_tx3g_style_context_t *ctx, int stop)
+{
+    uint8_t * style_entry;
+    uint8_t   face = 0;
+    int       font_size;
+    int       pos  = 10 + (12 * ctx->style_atom_count);
+    int       size = 10 + (12 * (ctx->style_atom_count + 1));
+
+    if (!check_realloc_output(&ctx->style_atoms, size))
+    {
+        return 0;
+    }
+    style_entry = ctx->style_atoms.buf + pos;
+
+    if (ctx->out_style.flags & HB_STYLE_FLAG_BOLD)
+        face |= 1;
+    if (ctx->out_style.flags & HB_STYLE_FLAG_ITALIC)
+        face |= 2;
+    if (ctx->out_style.flags & HB_STYLE_FLAG_UNDERLINE)
+        face |= 4;
+
+    style_entry[0]  = (ctx->style_start >> 8) & 0xff;   // startChar
+    style_entry[1]  = ctx->style_start & 0xff;
+    style_entry[2]  = (stop >> 8) & 0xff;               // endChar
+    style_entry[3]  = stop & 0xff;
+    style_entry[4]  = 0;            // font-ID msb
+    style_entry[5]  = 1;            // font-ID lsb
+    style_entry[6]  = face;         // face-style-flags
+    font_size       = 0.05 * ctx->height;
+    if (font_size < 12)
+    {
+        font_size = 12;
+    }
+    else if (font_size > 255)
+    {
+        font_size = 255;
+    }
+    style_entry[7]  = font_size;    // font-size
+    style_entry[8]  = (ctx->out_style.fg_rgb >> 16) & 0xff; // r
+    style_entry[9]  = (ctx->out_style.fg_rgb >> 8)  & 0xff; // g
+    style_entry[10] = (ctx->out_style.fg_rgb)       & 0xff; // b
+    style_entry[11] = ctx->out_style.fg_alpha;              // a
+
+    ctx->style_atom_count++;
+
+    return 1;
+}
+
+static int tx3g_update_style(hb_tx3g_style_context_t *ctx, int utf8_end_pos)
+{
+    hb_subtitle_style_t * style = &ctx->in_style->current;
+
+    // do we need to add a style atom?
+    if (((ctx->out_style.flags  ^ style->flags) & TX3G_STYLES) ||
+          ctx->out_style.fg_rgb   != style->fg_rgb             ||
+          ctx->out_style.fg_alpha != style->fg_alpha           ||
+          ctx->flush)
+    {
+        if (ctx->style_start < utf8_end_pos)
+        {
+            if (!tx3g_update_style_atoms(ctx, utf8_end_pos - 1))
+            {
+                return 0;
+            }
+            ctx->style_start = utf8_end_pos;
+        }
+        ctx->out_style = *style;
+        ctx->flush = 0;
+    }
+
+    return 1;
+}
+
+hb_tx3g_style_context_t *
+hb_tx3g_style_init(int height, const char * ssa_header)
+{
+    hb_tx3g_style_context_t * ctx;
+
+    ctx = calloc(1, sizeof(*ctx));
+    if (ctx == NULL)
+    {
+        return NULL;
+    }
+    ctx->in_style = hb_subtitle_style_init(ssa_header);
+    ctx->height            = height;
+    ctx->style_atoms.buf   = NULL;
+    ctx->style_atoms.size  = 0;
+    ctx->style_atoms.alloc = 0;
+    ctx->style_atom_count  = 0;
+    ctx->style_start       = 0;
+    ctx->out_style         = ctx->in_style->current;
+    ctx->flush             = 1;
+
+    return ctx;
+}
+
+void hb_tx3g_style_reset(hb_tx3g_style_context_t * ctx)
+{
+    ctx->style_atoms.buf   = NULL;
+    ctx->style_atoms.size  = 0;
+    ctx->style_atoms.alloc = 0;
+    ctx->style_atom_count  = 0;
+    ctx->style_start       = 0;
+    ctx->out_style         = ctx->in_style->current;
+    ctx->flush             = 1;
+}
+
+void hb_tx3g_style_close(hb_tx3g_style_context_t ** pctx)
+{
+    if (pctx == NULL || *pctx == NULL)
+    {
+        return;
+    }
+    hb_tx3g_style_context_t * ctx = *pctx;
+
+    hb_subtitle_style_close(&ctx->in_style);
+    free(ctx);
+    *pctx = NULL;
+}
+
+/*
+ * Copy the input to output removing markup and adding markup to the style
+ * atom where appropriate.
+ */
+void hb_muxmp4_process_subtitle_style(
+    hb_tx3g_style_context_t      * ctx,
+    uint8_t                      * input,
+    uint8_t                     ** out_buf,
+    uint8_t                     ** out_style_atoms,
+    uint16_t                     * stylesize)
+{
+    uint16_t                       utf8_pos = 0;
+    int                            consumed, in_pos = 0, out_pos = 0, len;
+    hb_tx3g_output_buf_t           output;
+    char                         * text;
+    const char                   * ssa_text, * style;
+
+    output.buf       = NULL;
+    output.alloc     = 0;
+    output.size      = 0;
+    *out_buf         = NULL;
+    *out_style_atoms = NULL;
+    *stylesize       = 0;
+
+    ssa_style_reset(ctx->in_style);
+
+    // Skip past the SSA preamble
+    char ** event = get_fields((char*)input, 9);
+    if (hb_str_vlen(event) < 9)
+    {
+        // Not enough fields
+        goto fail;
+    }
+
+    style = event[2];
+    ssa_text = event[8];
+    ctx->in_style->event_style_default = ssa_style_set(ctx->in_style, style);
+    hb_tx3g_style_reset(ctx);
+
+    in_pos = 0;
+    // Always allocate enough for empty string
+    if (!check_realloc_output(&output, 1))
+    {
+        goto fail;
+    }
+    while (ssa_text[in_pos] != '\0')
+    {
+        text = ssa_to_text(ssa_text + in_pos, &consumed, ctx->in_style);
+        if (text == NULL)
+            break;
+
+        // count UTF8 characters, and get length of text
+        len = 0;
+        int  ii, n;
+        for (ii = 0; text[ii] != '\0'; ii += n)
+        {
+            int  jj;
+            char c = text[ii];
+
+            utf8_pos++;
+            if      ((c & 0x80) == 0x00) n = 1;
+            else if ((c & 0xE0) == 0xC0) n = 2;
+            else if ((c & 0xF0) == 0xE0) n = 3;
+            else if ((c & 0xF8) == 0xF0) n = 4;
+            else                         n = 1; // invalid, but must handle
+
+            // Prevent skipping null terminator
+            for (jj = 1; jj < n && text[ii + jj] != '\0'; jj++);
+            n = jj;
+            len += n;
+        }
+        if (!check_realloc_output(&output, out_pos + len + 1))
+        {
+            goto fail;
+        }
+        strcpy((char*)output.buf + out_pos, text);
+        free(text);
+        out_pos += len;
+        in_pos += consumed;
+        if (!tx3g_update_style(ctx, utf8_pos))
+        {
+            goto fail;
+        }
+    }
+    // Return to default style at end of line, flushes any pending
+    // style changes
+    ctx->flush = 1;
+    if (!tx3g_update_style(ctx, utf8_pos))
+    {
+        goto fail;
+    }
+
+    // null terminate output string
+    output.buf[out_pos] = 0;
+
+    if (ctx->style_atom_count > 0)
+    {
+        *stylesize = 10 + (ctx->style_atom_count * 12);
+
+        memcpy(ctx->style_atoms.buf + 4, "styl", 4);
+
+        ctx->style_atoms.buf[0] = 0;
+        ctx->style_atoms.buf[1] = 0;
+        ctx->style_atoms.buf[2] = (*stylesize >> 8) & 0xff;
+        ctx->style_atoms.buf[3] = *stylesize & 0xff;
+        ctx->style_atoms.buf[8] = (ctx->style_atom_count >> 8) & 0xff;
+        ctx->style_atoms.buf[9] = ctx->style_atom_count & 0xff;
+        *out_style_atoms = ctx->style_atoms.buf;
+    }
+    *out_buf = output.buf;
+    hb_str_vfree(event);
+
+    return;
+
+fail:
+    hb_str_vfree(event);
+    free(output.buf);
+    free(ctx->style_atoms.buf);
+}
diff --git a/libhb/ssautil.h b/libhb/ssautil.h
new file mode 100644 (file)
index 0000000..319262b
--- /dev/null
@@ -0,0 +1,34 @@
+/* ssautil.h
+ *
+ * Copyright (c) 2003-2019 HandBrake Team
+ * This file is part of the HandBrake source code
+ * Homepage: <http://handbrake.fr/>.
+ * It may be used under the terms of the GNU General Public License v2.
+ * For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+#ifndef __SSAUTIL_H__
+#define __SSAUTIL_H__
+
+typedef struct hb_subtitle_style_s hb_subtitle_style_t;
+typedef struct hb_subtitle_style_context_s hb_subtitle_style_context_t;
+
+typedef struct hb_tx3g_output_buf_s hb_tx3g_output_buf_t;
+typedef struct hb_tx3g_style_context_s hb_tx3g_style_context_t;
+
+#define HB_STYLE_FLAG_ITALIC    0x0001
+#define HB_STYLE_FLAG_BOLD      0x0002
+#define HB_STYLE_FLAG_UNDERLINE 0x0004
+
+hb_subtitle_style_context_t * hb_subtitle_style_init(const char * ssa_header);
+hb_tx3g_style_context_t     * hb_tx3g_style_init(
+                                        int height, const char * ssa_header);
+void hb_subtitle_style_close(hb_subtitle_style_context_t ** ctx);
+void hb_tx3g_style_close(hb_tx3g_style_context_t ** ctx);
+
+void hb_muxmp4_process_subtitle_style(
+        hb_tx3g_style_context_t * ctx,
+        uint8_t  * input, uint8_t  ** output,
+        uint8_t ** style, uint16_t  * stylesize);
+
+#endif // __SSAUTIL_H__
index 7bd712022d0e387984f508e639ed6702cecdfc0b..17a0e73bd522fe54f68b0cf85c7da1a83095ef2b 100644 (file)
@@ -2639,7 +2639,20 @@ static hb_buffer_t * merge_ssa(hb_buffer_t *a, hb_buffer_t *b)
     }
     if (text != NULL)
     {
-        len = sprintf((char*)buf->data, "%s\n%s", a->data, text);
+        // Strip trailing CR and/or LF
+        len = strlen((char*)a->data);
+        if (len > 0 && a->data[len - 1] == '\n')
+        {
+            a->data[len - 1] = 0;
+            len--;
+            if (len > 0 && a->data[len - 1] == '\r')
+            {
+                a->data[len - 1] = 0;
+            }
+        }
+        // Text subtitles are SSA internally.  Use SSA newline code
+        // and force style reset at beginning of new line.
+        len = sprintf((char*)buf->data, "%s\\N{\\r}%s", a->data, text);
         if (len >= 0)
             buf->size = len + 1;
     }