From d11460aa80b6f1463e4bea11db1e236599bd9bfb Mon Sep 17 00:00:00 2001 From: alaotach Date: Tue, 11 Aug 2026 11:32:34 +0530 Subject: [PATCH 1/6] Fix SSA compliance issues #2310 --- src/lib_ccx/ccx_encoders_helpers.c | 82 ++++++++++++++++++++++++---- src/lib_ccx/ccx_encoders_ssa.c | 86 ++++++++++++++++++++++++++---- 2 files changed, 148 insertions(+), 20 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_helpers.c b/src/lib_ccx/ccx_encoders_helpers.c index de9b32997..f2a2c3625 100644 --- a/src/lib_ccx/ccx_encoders_helpers.c +++ b/src/lib_ccx/ccx_encoders_helpers.c @@ -263,15 +263,24 @@ unsigned char *close_tag(struct encoder_ctx *ctx, unsigned char *buffer, char *t switch (cur) { case 'F': - buffer += encode_line(ctx, buffer, (unsigned char *)""); + if (ctx->write_format == CCX_OF_SSA) + buffer += encode_line(ctx, buffer, (unsigned char *)"{\\c}"); + else + buffer += encode_line(ctx, buffer, (unsigned char *)""); (*pchanged_font)--; break; case 'U': - buffer += encode_line(ctx, buffer, (unsigned char *)""); + if (ctx->write_format == CCX_OF_SSA) + buffer += encode_line(ctx, buffer, (unsigned char *)"{\\u0}"); + else + buffer += encode_line(ctx, buffer, (unsigned char *)""); (*punderlined)--; break; case 'I': - buffer += encode_line(ctx, buffer, (unsigned char *)""); + if (ctx->write_format == CCX_OF_SSA) + buffer += encode_line(ctx, buffer, (unsigned char *)"{\\i0}"); + else + buffer += encode_line(ctx, buffer, (unsigned char *)""); (*pitalics)--; break; } @@ -310,7 +319,21 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer // Add new font tag if (MAX_COLOR > its_color) - buffer += encode_line(ctx, buffer, (unsigned char *)color_text[its_color][1]); + { + if (ctx->write_format == CCX_OF_SSA) + { + const char *ssa_cols[] = { + "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", + "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", + "{\\c&H", "", "" + }; + buffer += encode_line(ctx, buffer, (unsigned char *)ssa_cols[its_color]); + } + else + { + buffer += encode_line(ctx, buffer, (unsigned char *)color_text[its_color][1]); + } + } else { ccx_common_logging.log_ftn("WARNING:get_decoder_line_encoded:Invalid Color index Selected %d\n", its_color); @@ -319,12 +342,43 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer if (its_color == COL_USERDEFINED) { - // The previous sentence doesn't copy the whole - // tag, just up to the quote before the color - buffer += encode_line(ctx, buffer, (unsigned char *)usercolor_rgb); - buffer += encode_line(ctx, buffer, (unsigned char *)"\">"); + if (ctx->write_format == CCX_OF_SSA) + { + if (strlen((char *)usercolor_rgb) == 7 && usercolor_rgb[0] == '#') { + char bgr[7]; + bgr[0] = usercolor_rgb[5]; bgr[1] = usercolor_rgb[6]; + bgr[2] = usercolor_rgb[3]; bgr[3] = usercolor_rgb[4]; + bgr[4] = usercolor_rgb[1]; bgr[5] = usercolor_rgb[2]; + bgr[6] = '\0'; + buffer += encode_line(ctx, buffer, (unsigned char *)bgr); + } + buffer += encode_line(ctx, buffer, (unsigned char *)"&}"); + } + else + { + buffer += encode_line(ctx, buffer, (unsigned char *)usercolor_rgb); + buffer += encode_line(ctx, buffer, (unsigned char *)"\">"); + } + } + int added_font = 0; + if (ctx->write_format == CCX_OF_SSA) { + const char *ssa_cols[] = { + "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", + "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", + "{\\c&H", "", "" + }; + if (MAX_COLOR > its_color && ssa_cols[its_color][0]) + added_font = 1; + else if (its_color == COL_USERDEFINED) + added_font = 1; + } else { + if (MAX_COLOR > its_color && color_text[its_color][1][0]) + added_font = 1; + else if (its_color == COL_USERDEFINED) + added_font = 1; } - if (color_text[its_color][1][0]) // That means a was added to the buffer + + if (added_font) // That means a or {\c} was added to the buffer { strncat(tagstack, "F", sizeof(tagstack) - strlen(tagstack) - 1); changed_font++; @@ -335,7 +389,10 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer int is_underlined = data->fonts[line_num][i] & FONT_UNDERLINED; if (is_underlined && underlined == 0 && !ctx->no_type_setting) // Open underline { - buffer += encode_line(ctx, buffer, (unsigned char *)""); + if (ctx->write_format == CCX_OF_SSA) + buffer += encode_line(ctx, buffer, (unsigned char *)"{\\u1}"); + else + buffer += encode_line(ctx, buffer, (unsigned char *)""); strncat(tagstack, "U", sizeof(tagstack) - strlen(tagstack) - 1); underlined++; } @@ -347,7 +404,10 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer int has_ita = data->fonts[line_num][i] & FONT_ITALICS; if (has_ita && italics == 0 && !ctx->no_type_setting) // Open italics { - buffer += encode_line(ctx, buffer, (unsigned char *)""); + if (ctx->write_format == CCX_OF_SSA) + buffer += encode_line(ctx, buffer, (unsigned char *)"{\\i1}"); + else + buffer += encode_line(ctx, buffer, (unsigned char *)""); strncat(tagstack, "I", sizeof(tagstack) - strlen(tagstack) - 1); italics++; } diff --git a/src/lib_ccx/ccx_encoders_ssa.c b/src/lib_ccx/ccx_encoders_ssa.c index 321c82eb6..7d32d7139 100644 --- a/src/lib_ccx/ccx_encoders_ssa.c +++ b/src/lib_ccx/ccx_encoders_ssa.c @@ -37,7 +37,7 @@ int write_stringz_as_ssa(char *string, struct encoder_ctx *context, LLONG ms_sta millis_to_time(ms_start, &h1, &m1, &s1, &ms1); millis_to_time(ms_end - 1, &h2, &m2, &s2, &ms2); // -1 To prevent overlapping with next line. - snprintf(timeline, sizeof(timeline), "Dialogue: 0,%02u:%02u:%02u.%01u,%02u:%02u:%02u.%02u,Default,,0000,0000,0000,,", + snprintf(timeline, sizeof(timeline), "Dialogue: 0,%u:%02u:%02u.%02u,%u:%02u:%02u.%02u,Default,,0000,0000,0000,,", h1, m1, s1, ms1 / 10, h2, m2, s2, ms2 / 10); used = encode_line(context, context->buffer, (unsigned char *)timeline); dbg_print(CCX_DMT_DECODER_608, "\n- - - ASS/SSA caption - - -\n"); @@ -45,7 +45,7 @@ int write_stringz_as_ssa(char *string, struct encoder_ctx *context, LLONG ms_sta write_wrapped(context->out->fh, context->buffer, used); int len = strlen(string); - unsigned char *unescaped = (unsigned char *)malloc(len + 1); + unsigned char *unescaped = (unsigned char *)malloc(len * 2 + 1); if (!unescaped) fatal(EXIT_NOT_ENOUGH_MEMORY, "In write_stringz_as_ssa() - not enough memory for unescaped buffer.\n"); unsigned char *el = (unsigned char *)malloc(len * 3 + 1); // Be generous @@ -61,15 +61,83 @@ int write_stringz_as_ssa(char *string, struct encoder_ctx *context, LLONG ms_sta { if (string[pos_r] == '\\' && string[pos_r + 1] == 'n') { - unescaped[pos_w] = 0; + unescaped[pos_w++] = 0; pos_r += 2; + continue; } - else + + int matched = 0; + for (size_t i = 0; i < NUM_TAG_MAPS; i++) + { + const tag_map_t *m = &html_to_ass[i]; +#ifdef _MSC_VER + if (_strnicmp(string + pos_r, m->from, m->from_len) == 0) +#else + if (strncasecmp(string + pos_r, m->from, m->from_len) == 0) +#endif + { + memcpy(unescaped + pos_w, m->to, m->to_len); + pos_w += m->to_len; + pos_r += m->from_len; + matched = 1; + break; + } + } + if (matched) + continue; + +#ifdef _MSC_VER + if (_strnicmp(string + pos_r, "') + { + char r1 = string[pos_r + 14], r2 = string[pos_r + 15]; + char g1 = string[pos_r + 16], g2 = string[pos_r + 17]; + char b1 = string[pos_r + 18], b2 = string[pos_r + 19]; + char ssa_col[14]; + snprintf(ssa_col, sizeof(ssa_col), "{\\c&H%c%c%c%c%c%c&}", b1, b2, g1, g2, r1, r2); + memcpy(unescaped + pos_w, ssa_col, 13); + pos_w += 13; + pos_r += 22; // Skip up to and including '>' + } + else + { + while (pos_r < len && string[pos_r] != '>') + pos_r++; + if (pos_r < len) + pos_r++; // Skip '>' + } + continue; + } + +#ifdef _MSC_VER + if (_strnicmp(string + pos_r, "", 7) == 0) +#else + if (strncasecmp(string + pos_r, "", 7) == 0) +#endif + { + memcpy(unescaped + pos_w, "{\\c}", 4); + pos_w += 4; + pos_r += 7; + continue; + } + + unescaped[pos_w++] = string[pos_r++]; } unescaped[pos_w] = 0; // Now read the unescaped string (now several string'z and write them) @@ -134,7 +202,7 @@ int write_cc_bitmap_as_ssa(struct cc_subtitle *sub, struct encoder_ctx *context) millis_to_time(sub->start_time, &h1, &m1, &s1, &ms1); millis_to_time(sub->end_time - 1, &h2, &m2, &s2, &ms2); // -1 To prevent overlapping with next line. - snprintf(timeline, sizeof(timeline), "Dialogue: 0,%02u:%02u:%02u.%01u,%02u:%02u:%02u.%02u,Default,,0000,0000,0000,,", + snprintf(timeline, sizeof(timeline), "Dialogue: 0,%u:%02u:%02u.%02u,%u:%02u:%02u.%02u,Default,,0000,0000,0000,,", h1, m1, s1, ms1 / 10, h2, m2, s2, ms2 / 10); used = encode_line(context, context->buffer, (unsigned char *)timeline); write_wrapped(context->out->fh, context->buffer, used); @@ -220,7 +288,7 @@ int write_cc_buffer_as_ssa(struct eia608_screen *data, struct encoder_ctx *conte millis_to_time(data->start_time, &h1, &m1, &s1, &ms1); millis_to_time(data->end_time - 1, &h2, &m2, &s2, &ms2); // -1 To prevent overlapping with next line. char timeline[128]; - snprintf(timeline, sizeof(timeline), "Dialogue: 0,%02u:%02u:%02u.%01u,%02u:%02u:%02u.%02u,Default,,0000,0000,0000,,", + snprintf(timeline, sizeof(timeline), "Dialogue: 0,%u:%02u:%02u.%02u,%u:%02u:%02u.%02u,Default,,0000,0000,0000,,", h1, m1, s1, ms1 / 10, h2, m2, s2, ms2 / 10); used = encode_line(context, context->buffer, (unsigned char *)timeline); From 6d94f1c2bc1702cb081dfb424f6f5d20c73c13ce Mon Sep 17 00:00:00 2001 From: alaotach Date: Tue, 11 Aug 2026 11:42:36 +0530 Subject: [PATCH 2/6] Hoist ssa_cols to file scope --- src/lib_ccx/ccx_encoders_helpers.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_helpers.c b/src/lib_ccx/ccx_encoders_helpers.c index f2a2c3625..068d4b7b4 100644 --- a/src/lib_ccx/ccx_encoders_helpers.c +++ b/src/lib_ccx/ccx_encoders_helpers.c @@ -19,6 +19,12 @@ struct word_list capitalization_list = { .capacity = 0, }; +static const char *ssa_cols[] = { + "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", + "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", + "{\\c&H", "", "" +}; + struct word_list profane = { .words = NULL, .len = 0, @@ -322,11 +328,6 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer { if (ctx->write_format == CCX_OF_SSA) { - const char *ssa_cols[] = { - "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", - "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", - "{\\c&H", "", "" - }; buffer += encode_line(ctx, buffer, (unsigned char *)ssa_cols[its_color]); } else @@ -362,11 +363,6 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer } int added_font = 0; if (ctx->write_format == CCX_OF_SSA) { - const char *ssa_cols[] = { - "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", - "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", - "{\\c&H", "", "" - }; if (MAX_COLOR > its_color && ssa_cols[its_color][0]) added_font = 1; else if (its_color == COL_USERDEFINED) From f949821fde07d556e41bc013061965315f8b10e9 Mon Sep 17 00:00:00 2001 From: alaotach Date: Tue, 11 Aug 2026 12:43:03 +0530 Subject: [PATCH 3/6] Fix clang-format and strncasecmp implicit declaration --- src/lib_ccx/ccx_encoders_helpers.c | 26 ++++++++++++++++---------- src/lib_ccx/ccx_encoders_ssa.c | 3 +++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_helpers.c b/src/lib_ccx/ccx_encoders_helpers.c index 068d4b7b4..64c8d9b64 100644 --- a/src/lib_ccx/ccx_encoders_helpers.c +++ b/src/lib_ccx/ccx_encoders_helpers.c @@ -20,10 +20,9 @@ struct word_list capitalization_list = { }; static const char *ssa_cols[] = { - "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", - "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", - "{\\c&H", "", "" -}; + "", "{\\c&H00FF00&}", "{\\c&HFF0000&}", "{\\c&HFFFF00&}", + "{\\c&H0000FF&}", "{\\c&H00FFFF&}", "{\\c&HFF00FF&}", + "{\\c&H", "", ""}; struct word_list profane = { .words = NULL, @@ -345,11 +344,15 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer { if (ctx->write_format == CCX_OF_SSA) { - if (strlen((char *)usercolor_rgb) == 7 && usercolor_rgb[0] == '#') { + if (strlen((char *)usercolor_rgb) == 7 && usercolor_rgb[0] == '#') + { char bgr[7]; - bgr[0] = usercolor_rgb[5]; bgr[1] = usercolor_rgb[6]; - bgr[2] = usercolor_rgb[3]; bgr[3] = usercolor_rgb[4]; - bgr[4] = usercolor_rgb[1]; bgr[5] = usercolor_rgb[2]; + bgr[0] = usercolor_rgb[5]; + bgr[1] = usercolor_rgb[6]; + bgr[2] = usercolor_rgb[3]; + bgr[3] = usercolor_rgb[4]; + bgr[4] = usercolor_rgb[1]; + bgr[5] = usercolor_rgb[2]; bgr[6] = '\0'; buffer += encode_line(ctx, buffer, (unsigned char *)bgr); } @@ -362,12 +365,15 @@ unsigned get_decoder_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer } } int added_font = 0; - if (ctx->write_format == CCX_OF_SSA) { + if (ctx->write_format == CCX_OF_SSA) + { if (MAX_COLOR > its_color && ssa_cols[its_color][0]) added_font = 1; else if (its_color == COL_USERDEFINED) added_font = 1; - } else { + } + else + { if (MAX_COLOR > its_color && color_text[its_color][1][0]) added_font = 1; else if (its_color == COL_USERDEFINED) diff --git a/src/lib_ccx/ccx_encoders_ssa.c b/src/lib_ccx/ccx_encoders_ssa.c index 7d32d7139..2ab676277 100644 --- a/src/lib_ccx/ccx_encoders_ssa.c +++ b/src/lib_ccx/ccx_encoders_ssa.c @@ -4,6 +4,9 @@ #include "utility.h" #include "ccx_encoders_helpers.h" #include "ocr.h" +#ifndef _MSC_VER +#include +#endif static void ass_position_from_row_col( int row, From 58459b8a81d8d1eb72d983b744ebbea82d265790 Mon Sep 17 00:00:00 2001 From: alaotach Date: Tue, 11 Aug 2026 12:49:29 +0530 Subject: [PATCH 4/6] Use portable ccx_strncasecmp to fix autoconf builds --- src/lib_ccx/ccx_encoders_ssa.c | 42 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_ssa.c b/src/lib_ccx/ccx_encoders_ssa.c index 2ab676277..2ce37fff3 100644 --- a/src/lib_ccx/ccx_encoders_ssa.c +++ b/src/lib_ccx/ccx_encoders_ssa.c @@ -4,9 +4,21 @@ #include "utility.h" #include "ccx_encoders_helpers.h" #include "ocr.h" -#ifndef _MSC_VER -#include -#endif +#include + +static int ccx_strncasecmp(const char *s1, const char *s2, size_t n) +{ + for (size_t i = 0; i < n; i++) + { + int c1 = tolower((unsigned char)s1[i]); + int c2 = tolower((unsigned char)s2[i]); + if (c1 != c2) + return c1 - c2; + if (c1 == 0) + return 0; + } + return 0; +} static void ass_position_from_row_col( int row, @@ -73,11 +85,7 @@ int write_stringz_as_ssa(char *string, struct encoder_ctx *context, LLONG ms_sta for (size_t i = 0; i < NUM_TAG_MAPS; i++) { const tag_map_t *m = &html_to_ass[i]; -#ifdef _MSC_VER - if (_strnicmp(string + pos_r, m->from, m->from_len) == 0) -#else - if (strncasecmp(string + pos_r, m->from, m->from_len) == 0) -#endif + if (ccx_strncasecmp(string + pos_r, m->from, m->from_len) == 0) { memcpy(unescaped + pos_w, m->to, m->to_len); pos_w += m->to_len; @@ -89,11 +97,7 @@ int write_stringz_as_ssa(char *string, struct encoder_ctx *context, LLONG ms_sta if (matched) continue; -#ifdef _MSC_VER - if (_strnicmp(string + pos_r, "') { @@ -116,11 +120,7 @@ int write_stringz_as_ssa(char *string, struct encoder_ctx *context, LLONG ms_sta continue; } -#ifdef _MSC_VER - if (_strnicmp(string + pos_r, "", 7) == 0) -#else - if (strncasecmp(string + pos_r, "", 7) == 0) -#endif + if (ccx_strncasecmp(string + pos_r, "", 7) == 0) { memcpy(unescaped + pos_w, "{\\c}", 4); pos_w += 4; From 3c365801bb33fade0facdc9d69dc7569b9a58266 Mon Sep 17 00:00:00 2001 From: alaotach Date: Tue, 11 Aug 2026 12:58:27 +0530 Subject: [PATCH 5/6] Add missing tag_map_t definition --- src/lib_ccx/ccx_encoders_ssa.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib_ccx/ccx_encoders_ssa.c b/src/lib_ccx/ccx_encoders_ssa.c index 2ce37fff3..3ce04c159 100644 --- a/src/lib_ccx/ccx_encoders_ssa.c +++ b/src/lib_ccx/ccx_encoders_ssa.c @@ -20,6 +20,23 @@ static int ccx_strncasecmp(const char *s1, const char *s2, size_t n) return 0; } +typedef struct { + const char *from; + size_t from_len; + const char *to; + size_t to_len; +} tag_map_t; + +static const tag_map_t html_to_ass[] = { + { "", 3, "{\\i1}", 5 }, + { "", 4, "{\\i0}", 5 }, + { "", 3, "{\\u1}", 5 }, + { "", 4, "{\\u0}", 5 }, + { "", 3, "{\\b1}", 5 }, + { "", 4, "{\\b0}", 5 }, +}; +#define NUM_TAG_MAPS (sizeof(html_to_ass) / sizeof(html_to_ass[0])) + static void ass_position_from_row_col( int row, int col, From f24a9d25bb82cb028ed9d4ed4bb8de80d1379299 Mon Sep 17 00:00:00 2001 From: alaotach Date: Tue, 11 Aug 2026 13:04:09 +0530 Subject: [PATCH 6/6] Fix clang-format on tag_map_t --- src/lib_ccx/ccx_encoders_ssa.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_ssa.c b/src/lib_ccx/ccx_encoders_ssa.c index 3ce04c159..278763f52 100644 --- a/src/lib_ccx/ccx_encoders_ssa.c +++ b/src/lib_ccx/ccx_encoders_ssa.c @@ -20,20 +20,21 @@ static int ccx_strncasecmp(const char *s1, const char *s2, size_t n) return 0; } -typedef struct { - const char *from; - size_t from_len; - const char *to; - size_t to_len; +typedef struct +{ + const char *from; + size_t from_len; + const char *to; + size_t to_len; } tag_map_t; static const tag_map_t html_to_ass[] = { - { "", 3, "{\\i1}", 5 }, - { "", 4, "{\\i0}", 5 }, - { "", 3, "{\\u1}", 5 }, - { "", 4, "{\\u0}", 5 }, - { "", 3, "{\\b1}", 5 }, - { "", 4, "{\\b0}", 5 }, + {"", 3, "{\\i1}", 5}, + {"", 4, "{\\i0}", 5}, + {"", 3, "{\\u1}", 5}, + {"", 4, "{\\u0}", 5}, + {"", 3, "{\\b1}", 5}, + {"", 4, "{\\b0}", 5}, }; #define NUM_TAG_MAPS (sizeof(html_to_ass) / sizeof(html_to_ass[0]))