summaryrefslogtreecommitdiffstats
path: root/libhb
diff options
context:
space:
mode:
authorJohn Stebbins <[email protected]>2019-01-07 10:52:28 -0700
committerJohn Stebbins <[email protected]>2019-01-14 13:36:08 -0800
commit1f52f52337028b977a83a0e1061b4cc36918c34f (patch)
tree7066a5ec5e4bc760ddfac69206eda40611bd7166 /libhb
parent31e676d288bb1be2f1d6096b26159ac92c7e143f (diff)
ssa: improve SSA to TX3G conversion
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.
Diffstat (limited to 'libhb')
-rw-r--r--libhb/decssasub.c182
-rw-r--r--libhb/decssasub.h22
-rw-r--r--libhb/internal.h4
-rw-r--r--libhb/muxavformat.c14
-rw-r--r--libhb/muxcommon.c234
-rw-r--r--libhb/ssautil.c925
-rw-r--r--libhb/ssautil.h34
-rw-r--r--libhb/sync.c15
8 files changed, 987 insertions, 443 deletions
diff --git a/libhb/decssasub.c b/libhb/decssasub.c
index 2e95ba789..2bc06c7b9 100644
--- a/libhb/decssasub.c
+++ b/libhb/decssasub.c
@@ -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
diff --git a/libhb/decssasub.h b/libhb/decssasub.h
index 174f056f6..19c7ce633 100644
--- a/libhb/decssasub.h
+++ b/libhb/decssasub.h
@@ -10,26 +10,4 @@
#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__
diff --git a/libhb/internal.h b/libhb/internal.h
index 59b0677b8..cd46c1265 100644
--- a/libhb/internal.h
+++ b/libhb/internal.h
@@ -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);
diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c
index 60eba066f..ff041b47c 100644
--- a/libhb/muxavformat.c
+++ b/libhb/muxavformat.c
@@ -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)
diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c
index a61fc0803..5034b32e3 100644
--- a/libhb/muxcommon.c
+++ b/libhb/muxcommon.c
@@ -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
index 000000000..7bc067a19
--- /dev/null
+++ b/libhb/ssautil.c
@@ -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
index 000000000..319262b73
--- /dev/null
+++ b/libhb/ssautil.h
@@ -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__
diff --git a/libhb/sync.c b/libhb/sync.c
index 7bd712022..17a0e73bd 100644
--- a/libhb/sync.c
+++ b/libhb/sync.c
@@ -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;
}