From 14dd95154820d4ec9afb5200335177b011233049 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 1 Jun 2013 19:50:46 +0200 Subject: sub: split subassconvert.c into sd_microdvd.c and sd_srt.c --- Makefile | 3 +- sub/sd_microdvd.c | 311 +++++++++++++++++++++++ sub/sd_srt.c | 438 +++++++++++++++++++++++++++++++++ sub/subassconvert.c | 694 ---------------------------------------------------- 4 files changed, 751 insertions(+), 695 deletions(-) create mode 100644 sub/sd_microdvd.c create mode 100644 sub/sd_srt.c delete mode 100644 sub/subassconvert.c diff --git a/Makefile b/Makefile index 583eaf87ec..d3e203a852 100644 --- a/Makefile +++ b/Makefile @@ -231,11 +231,12 @@ SOURCES = talloc.c \ sub/find_subfiles.c \ sub/img_convert.c \ sub/sd_lavc.c \ + sub/sd_microdvd.c \ sub/sd_movtext.c \ sub/sd_spu.c \ + sub/sd_srt.c \ sub/spudec.c \ sub/sub.c \ - sub/subassconvert.c \ sub/subreader.c \ video/csputils.c \ video/fmt-conversion.c \ diff --git a/sub/sd_microdvd.c b/sub/sd_microdvd.c new file mode 100644 index 0000000000..c6c8745e32 --- /dev/null +++ b/sub/sd_microdvd.c @@ -0,0 +1,311 @@ +/* + * Subtitles converter to SSA/ASS in order to allow special formatting + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "core/mp_msg.h" +#include "subassconvert.h" +#include "core/bstr.h" +#include "libavutil/common.h" + +struct line { + char *buf; + int bufsize; + int len; +}; + +#ifdef __GNUC__ +static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3))); +#endif + +static void append_text(struct line *dst, char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + int ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va); + if (ret < 0) + goto out; + dst->len += ret; + if (dst->len > dst->bufsize) + dst->len = dst->bufsize; + out: + va_end(va); +} + +static int indexof(const char *s, int c) +{ + char *f = strchr(s, c); + return f ? (f - s) : -1; +} + + +/* + * MicroDVD + * + * Based on the specifications found here: + * https://trac.videolan.org/vlc/ticket/1825#comment:6 + */ + +struct microdvd_tag { + char key; + int persistent; + uint32_t data1; + uint32_t data2; + struct bstr data_string; +}; + +#define MICRODVD_PERSISTENT_OFF 0 +#define MICRODVD_PERSISTENT_ON 1 +#define MICRODVD_PERSISTENT_OPENED 2 + +// Color, Font, Size, cHarset, stYle, Position, cOordinate +#define MICRODVD_TAGS "cfshyYpo" + +static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag) +{ + int tag_index = indexof(MICRODVD_TAGS, tag.key); + + if (tag_index < 0) + return; + memcpy(&tags[tag_index], &tag, sizeof(tag)); +} + +// italic, bold, underline, strike-through +#define MICRODVD_STYLES "ibus" + +static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) +{ + while (*s == '{') { + char *start = s; + char tag_char = *(s + 1); + struct microdvd_tag tag = {0}; + + if (!tag_char || *(s + 2) != ':') + break; + s += 3; + + switch (tag_char) { + + /* Style */ + case 'Y': + tag.persistent = MICRODVD_PERSISTENT_ON; + case 'y': + while (*s && *s != '}') { + int style_index = indexof(MICRODVD_STYLES, *s); + + if (style_index >= 0) + tag.data1 |= (1 << style_index); + s++; + } + if (*s != '}') + break; + /* We must distinguish persistent and non-persistent styles + * to handle this kind of style tags: {y:ib}{Y:us} */ + tag.key = tag_char; + break; + + /* Color */ + case 'C': + tag.persistent = MICRODVD_PERSISTENT_ON; + case 'c': + tag.data1 = strtol(s, &s, 16) & 0x00ffffff; + if (*s != '}') + break; + tag.key = 'c'; + break; + + /* Font name */ + case 'F': + tag.persistent = MICRODVD_PERSISTENT_ON; + case 'f': + { + int len = indexof(s, '}'); + if (len < 0) + break; + tag.data_string.start = s; + tag.data_string.len = len; + s += len; + tag.key = 'f'; + break; + } + + /* Font size */ + case 'S': + tag.persistent = MICRODVD_PERSISTENT_ON; + case 's': + tag.data1 = strtol(s, &s, 10); + if (*s != '}') + break; + tag.key = 's'; + break; + + /* Charset */ + case 'H': + { + //TODO: not yet handled, just parsed. + int len = indexof(s, '}'); + if (len < 0) + break; + tag.data_string.start = s; + tag.data_string.len = len; + s += len; + tag.key = 'h'; + break; + } + + /* Position */ + case 'P': + tag.persistent = MICRODVD_PERSISTENT_ON; + tag.data1 = (*s++ == '1'); + if (*s != '}') + break; + tag.key = 'p'; + break; + + /* Coordinates */ + case 'o': + tag.persistent = MICRODVD_PERSISTENT_ON; + tag.data1 = strtol(s, &s, 10); + if (*s != ',') + break; + s++; + tag.data2 = strtol(s, &s, 10); + if (*s != '}') + break; + tag.key = 'o'; + break; + + default: /* Unknown tag, we consider it to be text */ + break; + } + + if (tag.key == 0) + return start; + + microdvd_set_tag(tags, tag); + s++; + } + return s; +} + +static void microdvd_open_tags(struct line *new_line, struct microdvd_tag *tags) +{ + for (int i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { + if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) + continue; + switch (tags[i].key) { + case 'Y': + case 'y': + for (int sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) + if (tags[i].data1 & (1 << sidx)) + append_text(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]); + break; + + case 'c': + append_text(new_line, "{\\c&H%06X&}", tags[i].data1); + break; + + case 'f': + append_text(new_line, "{\\fn%.*s}", BSTR_P(tags[i].data_string)); + break; + + case 's': + append_text(new_line, "{\\fs%d}", tags[i].data1); + break; + + case 'p': + if (tags[i].data1 == 0) + append_text(new_line, "{\\an8}"); + break; + + case 'o': + append_text(new_line, "{\\pos(%d,%d)}", + tags[i].data1, tags[i].data2); + break; + } + if (tags[i].persistent == MICRODVD_PERSISTENT_ON) + tags[i].persistent = MICRODVD_PERSISTENT_OPENED; + } +} + +static void microdvd_close_no_persistent_tags(struct line *new_line, + struct microdvd_tag *tags) +{ + int i; + + for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) { + if (tags[i].persistent != MICRODVD_PERSISTENT_OFF) + continue; + switch (tags[i].key) { + + case 'y': + for (int sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--) + if (tags[i].data1 & (1 << sidx)) + append_text(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]); + break; + + case 'c': + append_text(new_line, "{\\c}"); + break; + + case 'f': + append_text(new_line, "{\\fn}"); + break; + + case 's': + append_text(new_line, "{\\fs}"); + break; + } + tags[i].key = 0; + } +} + +void subassconvert_microdvd(const char *orig, char *dest, int dest_buffer_size) +{ + /* line is not const to avoid warnings with strtol, etc. + * orig content won't be changed */ + char *line = (char *)orig; + struct line new_line = { + .buf = dest, + .bufsize = dest_buffer_size, + }; + struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; + + while (*line) { + line = microdvd_load_tags(tags, line); + microdvd_open_tags(&new_line, tags); + + while (*line && *line != '|') + new_line.buf[new_line.len++] = *line++; + + if (*line == '|') { + microdvd_close_no_persistent_tags(&new_line, tags); + append_text(&new_line, "\\N"); + line++; + } + } + new_line.buf[new_line.len] = 0; +} diff --git a/sub/sd_srt.c b/sub/sd_srt.c new file mode 100644 index 0000000000..fd1d252924 --- /dev/null +++ b/sub/sd_srt.c @@ -0,0 +1,438 @@ +/* + * Subtitles converter to SSA/ASS in order to allow special formatting + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "core/mp_msg.h" +#include "subassconvert.h" +#include "core/bstr.h" +#include "libavutil/common.h" + +struct line { + char *buf; + int bufsize; + int len; +}; + +#ifdef __GNUC__ +static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3))); +#endif + +static void append_text(struct line *dst, char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + int ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va); + if (ret < 0) + goto out; + dst->len += ret; + if (dst->len > dst->bufsize) + dst->len = dst->bufsize; + out: + va_end(va); +} + +static void append_text_n(struct line *dst, char *start, size_t length) +{ + append_text(dst, "%.*s", (int)length, start); +} + + +/* + * SubRip + * + * Support basic tags (italic, bold, underline, strike-through) + * and font tag with size, color and face attributes. + * + */ + +struct font_tag { + int size; + uint32_t color; + struct bstr face; + bool has_size : 1; + bool has_color : 1; + bool has_face : 1; +}; + +static const struct tag_conv { + char *from; + char *to; +} subrip_basic_tags[] = { + {"", "{\\i1}"}, {"", "{\\i0}"}, + {"", "{\\b1}"}, {"", "{\\b0}"}, + {"", "{\\u1}"}, {"", "{\\u0}"}, + {"", "{\\s1}"}, {"", "{\\s0}"}, + {"}", "\\}"}, + {"\r\n", "\\N"}, {"\n", "\\N"}, {"\r", "\\N"}, +}; + +static const struct { + char *s; + uint32_t v; +} subrip_web_colors[] = { + /* Named CSS3 colors in RGB format; a subset of those + at http://www.w3.org/TR/css3-color/#svg-color */ + {"aliceblue", 0xF0F8FF}, + {"antiquewhite", 0xFAEBD7}, + {"aqua", 0x00FFFF}, + {"aquamarine", 0x7FFFD4}, + {"azure", 0xF0FFFF}, + {"beige", 0xF5F5DC}, + {"bisque", 0xFFE4C4}, + {"black", 0x000000}, + {"blanchedalmond", 0xFFEBCD}, + {"blue", 0x0000FF}, + {"blueviolet", 0x8A2BE2}, + {"brown", 0xA52A2A}, + {"burlywood", 0xDEB887}, + {"cadetblue", 0x5F9EA0}, + {"chartreuse", 0x7FFF00}, + {"chocolate", 0xD2691E}, + {"coral", 0xFF7F50}, + {"cornflowerblue", 0x6495ED}, + {"cornsilk", 0xFFF8DC}, + {"crimson", 0xDC143C}, + {"cyan", 0x00FFFF}, + {"darkblue", 0x00008B}, + {"darkcyan", 0x008B8B}, + {"darkgoldenrod", 0xB8860B}, + {"darkgray", 0xA9A9A9}, + {"darkgreen", 0x006400}, + {"darkgrey", 0xA9A9A9}, + {"darkkhaki", 0xBDB76B}, + {"darkmagenta", 0x8B008B}, + {"darkolivegreen", 0x556B2F}, + {"darkorange", 0xFF8C00}, + {"darkorchid", 0x9932CC}, + {"darkred", 0x8B0000}, + {"darksalmon", 0xE9967A}, + {"darkseagreen", 0x8FBC8F}, + {"darkslateblue", 0x483D8B}, + {"darkslategray", 0x2F4F4F}, + {"darkslategrey", 0x2F4F4F}, + {"darkturquoise", 0x00CED1}, + {"darkviolet", 0x9400D3}, + {"deeppink", 0xFF1493}, + {"deepskyblue", 0x00BFFF}, + {"dimgray", 0x696969}, + {"dimgrey", 0x696969}, + {"dodgerblue", 0x1E90FF}, + {"firebrick", 0xB22222}, + {"floralwhite", 0xFFFAF0}, + {"forestgreen", 0x228B22}, + {"fuchsia", 0xFF00FF}, + {"gainsboro", 0xDCDCDC}, + {"ghostwhite", 0xF8F8FF}, + {"gold", 0xFFD700}, + {"goldenrod", 0xDAA520}, + {"gray", 0x808080}, + {"green", 0x008000}, + {"greenyellow", 0xADFF2F}, + {"grey", 0x808080}, + {"honeydew", 0xF0FFF0}, + {"hotpink", 0xFF69B4}, + {"indianred", 0xCD5C5C}, + {"indigo", 0x4B0082}, + {"ivory", 0xFFFFF0}, + {"khaki", 0xF0E68C}, + {"lavender", 0xE6E6FA}, + {"lavenderblush", 0xFFF0F5}, + {"lawngreen", 0x7CFC00}, + {"lemonchiffon", 0xFFFACD}, + {"lightblue", 0xADD8E6}, + {"lightcoral", 0xF08080}, + {"lightcyan", 0xE0FFFF}, + {"lightgoldenrodyellow", 0xFAFAD2}, + {"lightgray", 0xD3D3D3}, + {"lightgreen", 0x90EE90}, + {"lightgrey", 0xD3D3D3}, + {"lightpink", 0xFFB6C1}, + {"lightsalmon", 0xFFA07A}, + {"lightseagreen", 0x20B2AA}, + {"lightskyblue", 0x87CEFA}, + {"lightslategray", 0x778899}, + {"lightslategrey", 0x778899}, + {"lightsteelblue", 0xB0C4DE}, + {"lightyellow", 0xFFFFE0}, + {"lime", 0x00FF00}, + {"limegreen", 0x32CD32}, + {"linen", 0xFAF0E6}, + {"magenta", 0xFF00FF}, + {"maroon", 0x800000}, + {"mediumaquamarine", 0x66CDAA}, + {"mediumblue", 0x0000CD}, + {"mediumorchid", 0xBA55D3}, + {"mediumpurple", 0x9370DB}, + {"mediumseagreen", 0x3CB371}, + {"mediumslateblue", 0x7B68EE}, + {"mediumspringgreen", 0x00FA9A}, + {"mediumturquoise", 0x48D1CC}, + {"mediumvioletred", 0xC71585}, + {"midnightblue", 0x191970}, + {"mintcream", 0xF5FFFA}, + {"mistyrose", 0xFFE4E1}, + {"moccasin", 0xFFE4B5}, + {"navajowhite", 0xFFDEAD}, + {"navy", 0x000080}, + {"oldlace", 0xFDF5E6}, + {"olive", 0x808000}, + {"olivedrab", 0x6B8E23}, + {"orange", 0xFFA500}, + {"orangered", 0xFF4500}, + {"orchid", 0xDA70D6}, + {"palegoldenrod", 0xEEE8AA}, + {"palegreen", 0x98FB98}, + {"paleturquoise", 0xAFEEEE}, + {"palevioletred", 0xDB7093}, + {"papayawhip", 0xFFEFD5}, + {"peachpuff", 0xFFDAB9}, + {"peru", 0xCD853F}, + {"pink", 0xFFC0CB}, + {"plum", 0xDDA0DD}, + {"powderblue", 0xB0E0E6}, + {"purple", 0x800080}, + {"red", 0xFF0000}, + {"rosybrown", 0xBC8F8F}, + {"royalblue", 0x4169E1}, + {"saddlebrown", 0x8B4513}, + {"salmon", 0xFA8072}, + {"sandybrown", 0xF4A460}, + {"seagreen", 0x2E8B57}, + {"seashell", 0xFFF5EE}, + {"sienna", 0xA0522D}, + {"silver", 0xC0C0C0}, + {"skyblue", 0x87CEEB}, + {"slateblue", 0x6A5ACD}, + {"slategray", 0x708090}, + {"slategrey", 0x708090}, + {"snow", 0xFFFAFA}, + {"springgreen", 0x00FF7F}, + {"steelblue", 0x4682B4}, + {"tan", 0xD2B48C}, + {"teal", 0x008080}, + {"thistle", 0xD8BFD8}, + {"tomato", 0xFF6347}, + {"turquoise", 0x40E0D0}, + {"violet", 0xEE82EE}, + {"wheat", 0xF5DEB3}, + {"white", 0xFFFFFF}, + {"whitesmoke", 0xF5F5F5}, + {"yellow", 0xFFFF00}, + {"yellowgreen", 0x9ACD32}, +}; + +#define SUBRIP_MAX_STACKED_FONT_TAGS 16 + +/* Read the HTML-style attribute starting at *s, and skip *s past the value. + * Set attr and val to the parsed attribute name and value. + * Return 0 on success, or -1 if no valid attribute was found. + */ +static int read_attr(char **s, struct bstr *attr, struct bstr *val) +{ + char *eq = strchr(*s, '='); + if (!eq) + return -1; + attr->start = *s; + attr->len = eq - *s; + for (int i = 0; i < attr->len; i++) + if (!isalnum(attr->start[i])) + return -1; + val->start = eq + 1; + bool quoted = val->start[0] == '"'; + if (quoted) + val->start++; + unsigned char *end = strpbrk(val->start, quoted ? "\"" : " >"); + if (!end) + return -1; + val->len = end - val->start; + *s = end + quoted; + return 0; +} + +void subassconvert_subrip(const char *orig, char *dest, int dest_buffer_size) +{ + /* line is not const to avoid warnings with strtol, etc. + * orig content won't be changed */ + char *line = (char *)orig; + struct line new_line = { + .buf = dest, + .bufsize = dest_buffer_size, + }; + struct font_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS + 1]; + font_stack[0] = (struct font_tag){0}; // type with all defaults + int sp = 0; + + while (*line && new_line.len < new_line.bufsize - 1) { + char *orig_line = line; + + for (int i = 0; i < FF_ARRAY_ELEMS(subrip_basic_tags); i++) { + const struct tag_conv *tag = &subrip_basic_tags[i]; + int from_len = strlen(tag->from); + if (strncmp(line, tag->from, from_len) == 0) { + append_text(&new_line, "%s", tag->to); + line += from_len; + } + } + + if (strncmp(line, "", 7) == 0) { + /* Closing font tag */ + line += 7; + + if (sp > 0) { + struct font_tag *tag = &font_stack[sp]; + struct font_tag *last_tag = &tag[-1]; + sp--; + + if (tag->has_size) { + if (!last_tag->has_size) + append_text(&new_line, "{\\fs}"); + else if (last_tag->size != tag->size) + append_text(&new_line, "{\\fs%d}", last_tag->size); + } + + if (tag->has_color) { + if (!last_tag->has_color) + append_text(&new_line, "{\\c}"); + else if (last_tag->color != tag->color) + append_text(&new_line, "{\\c&H%06X&}", last_tag->color); + } + + if (tag->has_face) { + if (!last_tag->has_face) + append_text(&new_line, "{\\fn}"); + else if (bstrcmp(last_tag->face, tag->face) != 0) + append_text(&new_line, "{\\fn%.*s}", + BSTR_P(last_tag->face)); + } + } + } else if (strncmp(line, "size = bstrtoll(val, &val, 10); + if (val.len) + break; + append_text(&new_line, "{\\fs%d}", tag->size); + tag->has_size = true; + has_valid_attr = true; + } else if (!bstrcmp0(attr, "color")) { + int found = 0; + + // Try to lookup the string in standard web colors + for (int i = 0; i < FF_ARRAY_ELEMS(subrip_web_colors); i++) { + char *color = subrip_web_colors[i].s; + if (bstrcasecmp(val, bstr0(color)) == 0) { + uint32_t color = subrip_web_colors[i].v; + tag->color = ((color & 0xff) << 16) + | (color & 0xff00) + | ((color & 0xff0000) >> 16); + found = 1; + } + } + + // If it's not a web color it must be a HEX RGB value + if (!found) { + // Remove the leading '#' + bstr_eatstart(&val, bstr0("#")); + + // Parse RRGGBB format + tag->color = bstrtoll(val, &val, 16) & 0x00ffffff; + if (!val.len) { + tag->color = ((tag->color & 0xff) << 16) + | (tag->color & 0xff00) + | ((tag->color & 0xff0000) >> 16); + found = 1; + } + } + + if (found) { + append_text(&new_line, "{\\c&H%06X&}", tag->color); + tag->has_color = true; + } else { + // We didn't find any matching color + mp_tmsg(MSGT_SUBREADER, MSGL_WARN, + "SubRip: unknown font color in subtitle: >%s<\n", + orig); + append_text(&new_line, "{\\c}"); + } + + has_valid_attr = true; + } else if (!bstrcmp0(attr, "face")) { + /* Font face attribute */ + tag->face = val; + append_text(&new_line, "{\\fn%.*s}", BSTR_P(tag->face)); + tag->has_face = true; + has_valid_attr = true; + } else + mp_tmsg(MSGT_SUBREADER, MSGL_WARN,"SubRip: unrecognized " + "attribute \"%.*s\" in font tag\n", BSTR_P(attr)); + } + + if (!has_valid_attr || *line != '>') { /* Not valid font tag */ + line = potential_font_tag_start; + new_line.len = len_backup; + } else { + sp++; + line++; + } + } else if (*line == '{') { + char *end = strchr(line, '}'); + if (line[1] == '\\' && end) { + // Likely ASS tag, pass them through + // Note that ASS tags like {something\an8} are legal too (i.e. + // the first character after '{' doesn't have to be '\'), but + // consider these fringe cases not worth supporting. + append_text_n(&new_line, line, end - line + 1); + line = end + 1; + } else { + append_text(&new_line, "\\{"); + line++; + } + } + + /* Tag conversion code didn't match */ + if (line == orig_line) + new_line.buf[new_line.len++] = *line++; + } + new_line.buf[new_line.len] = 0; +} diff --git a/sub/subassconvert.c b/sub/subassconvert.c deleted file mode 100644 index c665750682..0000000000 --- a/sub/subassconvert.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Subtitles converter to SSA/ASS in order to allow special formatting - * - * This file is part of MPlayer. - * - * MPlayer is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * MPlayer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with MPlayer; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "core/mp_msg.h" -#include "subassconvert.h" -#include "core/bstr.h" -#include "libavutil/common.h" - -struct line { - char *buf; - int bufsize; - int len; -}; - -#ifdef __GNUC__ -static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3))); -#endif - -static void append_text(struct line *dst, char *fmt, ...) -{ - va_list va; - va_start(va, fmt); - int ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va); - if (ret < 0) - goto out; - dst->len += ret; - if (dst->len > dst->bufsize) - dst->len = dst->bufsize; - out: - va_end(va); -} - -static void append_text_n(struct line *dst, char *start, size_t length) -{ - append_text(dst, "%.*s", (int)length, start); -} - -static int indexof(const char *s, int c) -{ - char *f = strchr(s, c); - return f ? (f - s) : -1; -} - - - -/* - * SubRip - * - * Support basic tags (italic, bold, underline, strike-through) - * and font tag with size, color and face attributes. - * - */ - -struct font_tag { - int size; - uint32_t color; - struct bstr face; - bool has_size : 1; - bool has_color : 1; - bool has_face : 1; -}; - -static const struct tag_conv { - char *from; - char *to; -} subrip_basic_tags[] = { - {"", "{\\i1}"}, {"", "{\\i0}"}, - {"", "{\\b1}"}, {"", "{\\b0}"}, - {"", "{\\u1}"}, {"", "{\\u0}"}, - {"", "{\\s1}"}, {"", "{\\s0}"}, - {"}", "\\}"}, - {"\r\n", "\\N"}, {"\n", "\\N"}, {"\r", "\\N"}, -}; - -static const struct { - char *s; - uint32_t v; -} subrip_web_colors[] = { - /* Named CSS3 colors in RGB format; a subset of those - at http://www.w3.org/TR/css3-color/#svg-color */ - {"aliceblue", 0xF0F8FF}, - {"antiquewhite", 0xFAEBD7}, - {"aqua", 0x00FFFF}, - {"aquamarine", 0x7FFFD4}, - {"azure", 0xF0FFFF}, - {"beige", 0xF5F5DC}, - {"bisque", 0xFFE4C4}, - {"black", 0x000000}, - {"blanchedalmond", 0xFFEBCD}, - {"blue", 0x0000FF}, - {"blueviolet", 0x8A2BE2}, - {"brown", 0xA52A2A}, - {"burlywood", 0xDEB887}, - {"cadetblue", 0x5F9EA0}, - {"chartreuse", 0x7FFF00}, - {"chocolate", 0xD2691E}, - {"coral", 0xFF7F50}, - {"cornflowerblue", 0x6495ED}, - {"cornsilk", 0xFFF8DC}, - {"crimson", 0xDC143C}, - {"cyan", 0x00FFFF}, - {"darkblue", 0x00008B}, - {"darkcyan", 0x008B8B}, - {"darkgoldenrod", 0xB8860B}, - {"darkgray", 0xA9A9A9}, - {"darkgreen", 0x006400}, - {"darkgrey", 0xA9A9A9}, - {"darkkhaki", 0xBDB76B}, - {"darkmagenta", 0x8B008B}, - {"darkolivegreen", 0x556B2F}, - {"darkorange", 0xFF8C00}, - {"darkorchid", 0x9932CC}, - {"darkred", 0x8B0000}, - {"darksalmon", 0xE9967A}, - {"darkseagreen", 0x8FBC8F}, - {"darkslateblue", 0x483D8B}, - {"darkslategray", 0x2F4F4F}, - {"darkslategrey", 0x2F4F4F}, - {"darkturquoise", 0x00CED1}, - {"darkviolet", 0x9400D3}, - {"deeppink", 0xFF1493}, - {"deepskyblue", 0x00BFFF}, - {"dimgray", 0x696969}, - {"dimgrey", 0x696969}, - {"dodgerblue", 0x1E90FF}, - {"firebrick", 0xB22222}, - {"floralwhite", 0xFFFAF0}, - {"forestgreen", 0x228B22}, - {"fuchsia", 0xFF00FF}, - {"gainsboro", 0xDCDCDC}, - {"ghostwhite", 0xF8F8FF}, - {"gold", 0xFFD700}, - {"goldenrod", 0xDAA520}, - {"gray", 0x808080}, - {"green", 0x008000}, - {"greenyellow", 0xADFF2F}, - {"grey", 0x808080}, - {"honeydew", 0xF0FFF0}, - {"hotpink", 0xFF69B4}, - {"indianred", 0xCD5C5C}, - {"indigo", 0x4B0082}, - {"ivory", 0xFFFFF0}, - {"khaki", 0xF0E68C}, - {"lavender", 0xE6E6FA}, - {"lavenderblush", 0xFFF0F5}, - {"lawngreen", 0x7CFC00}, - {"lemonchiffon", 0xFFFACD}, - {"lightblue", 0xADD8E6}, - {"lightcoral", 0xF08080}, - {"lightcyan", 0xE0FFFF}, - {"lightgoldenrodyellow", 0xFAFAD2}, - {"lightgray", 0xD3D3D3}, - {"lightgreen", 0x90EE90}, - {"lightgrey", 0xD3D3D3}, - {"lightpink", 0xFFB6C1}, - {"lightsalmon", 0xFFA07A}, - {"lightseagreen", 0x20B2AA}, - {"lightskyblue", 0x87CEFA}, - {"lightslategray", 0x778899}, - {"lightslategrey", 0x778899}, - {"lightsteelblue", 0xB0C4DE}, - {"lightyellow", 0xFFFFE0}, - {"lime", 0x00FF00}, - {"limegreen", 0x32CD32}, - {"linen", 0xFAF0E6}, - {"magenta", 0xFF00FF}, - {"maroon", 0x800000}, - {"mediumaquamarine", 0x66CDAA}, - {"mediumblue", 0x0000CD}, - {"mediumorchid", 0xBA55D3}, - {"mediumpurple", 0x9370DB}, - {"mediumseagreen", 0x3CB371}, - {"mediumslateblue", 0x7B68EE}, - {"mediumspringgreen", 0x00FA9A}, - {"mediumturquoise", 0x48D1CC}, - {"mediumvioletred", 0xC71585}, - {"midnightblue", 0x191970}, - {"mintcream", 0xF5FFFA}, - {"mistyrose", 0xFFE4E1}, - {"moccasin", 0xFFE4B5}, - {"navajowhite", 0xFFDEAD}, - {"navy", 0x000080}, - {"oldlace", 0xFDF5E6}, - {"olive", 0x808000}, - {"olivedrab", 0x6B8E23}, - {"orange", 0xFFA500}, - {"orangered", 0xFF4500}, - {"orchid", 0xDA70D6}, - {"palegoldenrod", 0xEEE8AA}, - {"palegreen", 0x98FB98}, - {"paleturquoise", 0xAFEEEE}, - {"palevioletred", 0xDB7093}, - {"papayawhip", 0xFFEFD5}, - {"peachpuff", 0xFFDAB9}, - {"peru", 0xCD853F}, - {"pink", 0xFFC0CB}, - {"plum", 0xDDA0DD}, - {"powderblue", 0xB0E0E6}, - {"purple", 0x800080}, - {"red", 0xFF0000}, - {"rosybrown", 0xBC8F8F}, - {"royalblue", 0x4169E1}, - {"saddlebrown", 0x8B4513}, - {"salmon", 0xFA8072}, - {"sandybrown", 0xF4A460}, - {"seagreen", 0x2E8B57}, - {"seashell", 0xFFF5EE}, - {"sienna", 0xA0522D}, - {"silver", 0xC0C0C0}, - {"skyblue", 0x87CEEB}, - {"slateblue", 0x6A5ACD}, - {"slategray", 0x708090}, - {"slategrey", 0x708090}, - {"snow", 0xFFFAFA}, - {"springgreen", 0x00FF7F}, - {"steelblue", 0x4682B4}, - {"tan", 0xD2B48C}, - {"teal", 0x008080}, - {"thistle", 0xD8BFD8}, - {"tomato", 0xFF6347}, - {"turquoise", 0x40E0D0}, - {"violet", 0xEE82EE}, - {"wheat", 0xF5DEB3}, - {"white", 0xFFFFFF}, - {"whitesmoke", 0xF5F5F5}, - {"yellow", 0xFFFF00}, - {"yellowgreen", 0x9ACD32}, -}; - -#define SUBRIP_MAX_STACKED_FONT_TAGS 16 - -/* Read the HTML-style attribute starting at *s, and skip *s past the value. - * Set attr and val to the parsed attribute name and value. - * Return 0 on success, or -1 if no valid attribute was found. - */ -static int read_attr(char **s, struct bstr *attr, struct bstr *val) -{ - char *eq = strchr(*s, '='); - if (!eq) - return -1; - attr->start = *s; - attr->len = eq - *s; - for (int i = 0; i < attr->len; i++) - if (!isalnum(attr->start[i])) - return -1; - val->start = eq + 1; - bool quoted = val->start[0] == '"'; - if (quoted) - val->start++; - unsigned char *end = strpbrk(val->start, quoted ? "\"" : " >"); - if (!end) - return -1; - val->len = end - val->start; - *s = end + quoted; - return 0; -} - -void subassconvert_subrip(const char *orig, char *dest, int dest_buffer_size) -{ - /* line is not const to avoid warnings with strtol, etc. - * orig content won't be changed */ - char *line = (char *)orig; - struct line new_line = { - .buf = dest, - .bufsize = dest_buffer_size, - }; - struct font_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS + 1]; - font_stack[0] = (struct font_tag){0}; // type with all defaults - int sp = 0; - - while (*line && new_line.len < new_line.bufsize - 1) { - char *orig_line = line; - - for (int i = 0; i < FF_ARRAY_ELEMS(subrip_basic_tags); i++) { - const struct tag_conv *tag = &subrip_basic_tags[i]; - int from_len = strlen(tag->from); - if (strncmp(line, tag->from, from_len) == 0) { - append_text(&new_line, "%s", tag->to); - line += from_len; - } - } - - if (strncmp(line, "", 7) == 0) { - /* Closing font tag */ - line += 7; - - if (sp > 0) { - struct font_tag *tag = &font_stack[sp]; - struct font_tag *last_tag = &tag[-1]; - sp--; - - if (tag->has_size) { - if (!last_tag->has_size) - append_text(&new_line, "{\\fs}"); - else if (last_tag->size != tag->size) - append_text(&new_line, "{\\fs%d}", last_tag->size); - } - - if (tag->has_color) { - if (!last_tag->has_color) - append_text(&new_line, "{\\c}"); - else if (last_tag->color != tag->color) - append_text(&new_line, "{\\c&H%06X&}", last_tag->color); - } - - if (tag->has_face) { - if (!last_tag->has_face) - append_text(&new_line, "{\\fn}"); - else if (bstrcmp(last_tag->face, tag->face) != 0) - append_text(&new_line, "{\\fn%.*s}", - BSTR_P(last_tag->face)); - } - } - } else if (strncmp(line, "size = bstrtoll(val, &val, 10); - if (val.len) - break; - append_text(&new_line, "{\\fs%d}", tag->size); - tag->has_size = true; - has_valid_attr = true; - } else if (!bstrcmp0(attr, "color")) { - int found = 0; - - // Try to lookup the string in standard web colors - for (int i = 0; i < FF_ARRAY_ELEMS(subrip_web_colors); i++) { - char *color = subrip_web_colors[i].s; - if (bstrcasecmp(val, bstr0(color)) == 0) { - uint32_t color = subrip_web_colors[i].v; - tag->color = ((color & 0xff) << 16) - | (color & 0xff00) - | ((color & 0xff0000) >> 16); - found = 1; - } - } - - // If it's not a web color it must be a HEX RGB value - if (!found) { - // Remove the leading '#' - bstr_eatstart(&val, bstr0("#")); - - // Parse RRGGBB format - tag->color = bstrtoll(val, &val, 16) & 0x00ffffff; - if (!val.len) { - tag->color = ((tag->color & 0xff) << 16) - | (tag->color & 0xff00) - | ((tag->color & 0xff0000) >> 16); - found = 1; - } - } - - if (found) { - append_text(&new_line, "{\\c&H%06X&}", tag->color); - tag->has_color = true; - } else { - // We didn't find any matching color - mp_tmsg(MSGT_SUBREADER, MSGL_WARN, - "SubRip: unknown font color in subtitle: >%s<\n", - orig); - append_text(&new_line, "{\\c}"); - } - - has_valid_attr = true; - } else if (!bstrcmp0(attr, "face")) { - /* Font face attribute */ - tag->face = val; - append_text(&new_line, "{\\fn%.*s}", BSTR_P(tag->face)); - tag->has_face = true; - has_valid_attr = true; - } else - mp_tmsg(MSGT_SUBREADER, MSGL_WARN,"SubRip: unrecognized " - "attribute \"%.*s\" in font tag\n", BSTR_P(attr)); - } - - if (!has_valid_attr || *line != '>') { /* Not valid font tag */ - line = potential_font_tag_start; - new_line.len = len_backup; - } else { - sp++; - line++; - } - } else if (*line == '{') { - char *end = strchr(line, '}'); - if (line[1] == '\\' && end) { - // Likely ASS tag, pass them through - // Note that ASS tags like {something\an8} are legal too (i.e. - // the first character after '{' doesn't have to be '\'), but - // consider these fringe cases not worth supporting. - append_text_n(&new_line, line, end - line + 1); - line = end + 1; - } else { - append_text(&new_line, "\\{"); - line++; - } - } - - /* Tag conversion code didn't match */ - if (line == orig_line) - new_line.buf[new_line.len++] = *line++; - } - new_line.buf[new_line.len] = 0; -} - - -/* - * MicroDVD - * - * Based on the specifications found here: - * https://trac.videolan.org/vlc/ticket/1825#comment:6 - */ - -struct microdvd_tag { - char key; - int persistent; - uint32_t data1; - uint32_t data2; - struct bstr data_string; -}; - -#define MICRODVD_PERSISTENT_OFF 0 -#define MICRODVD_PERSISTENT_ON 1 -#define MICRODVD_PERSISTENT_OPENED 2 - -// Color, Font, Size, cHarset, stYle, Position, cOordinate -#define MICRODVD_TAGS "cfshyYpo" - -static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag) -{ - int tag_index = indexof(MICRODVD_TAGS, tag.key); - - if (tag_index < 0) - return; - memcpy(&tags[tag_index], &tag, sizeof(tag)); -} - -// italic, bold, underline, strike-through -#define MICRODVD_STYLES "ibus" - -static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) -{ - while (*s == '{') { - char *start = s; - char tag_char = *(s + 1); - struct microdvd_tag tag = {0}; - - if (!tag_char || *(s + 2) != ':') - break; - s += 3; - - switch (tag_char) { - - /* Style */ - case 'Y': - tag.persistent = MICRODVD_PERSISTENT_ON; - case 'y': - while (*s && *s != '}') { - int style_index = indexof(MICRODVD_STYLES, *s); - - if (style_index >= 0) - tag.data1 |= (1 << style_index); - s++; - } - if (*s != '}') - break; - /* We must distinguish persistent and non-persistent styles - * to handle this kind of style tags: {y:ib}{Y:us} */ - tag.key = tag_char; - break; - - /* Color */ - case 'C': - tag.persistent = MICRODVD_PERSISTENT_ON; - case 'c': - tag.data1 = strtol(s, &s, 16) & 0x00ffffff; - if (*s != '}') - break; - tag.key = 'c'; - break; - - /* Font name */ - case 'F': - tag.persistent = MICRODVD_PERSISTENT_ON; - case 'f': - { - int len = indexof(s, '}'); - if (len < 0) - break; - tag.data_string.start = s; - tag.data_string.len = len; - s += len; - tag.key = 'f'; - break; - } - - /* Font size */ - case 'S': - tag.persistent = MICRODVD_PERSISTENT_ON; - case 's': - tag.data1 = strtol(s, &s, 10); - if (*s != '}') - break; - tag.key = 's'; - break; - - /* Charset */ - case 'H': - { - //TODO: not yet handled, just parsed. - int len = indexof(s, '}'); - if (len < 0) - break; - tag.data_string.start = s; - tag.data_string.len = len; - s += len; - tag.key = 'h'; - break; - } - - /* Position */ - case 'P': - tag.persistent = MICRODVD_PERSISTENT_ON; - tag.data1 = (*s++ == '1'); - if (*s != '}') - break; - tag.key = 'p'; - break; - - /* Coordinates */ - case 'o': - tag.persistent = MICRODVD_PERSISTENT_ON; - tag.data1 = strtol(s, &s, 10); - if (*s != ',') - break; - s++; - tag.data2 = strtol(s, &s, 10); - if (*s != '}') - break; - tag.key = 'o'; - break; - - default: /* Unknown tag, we consider it to be text */ - break; - } - - if (tag.key == 0) - return start; - - microdvd_set_tag(tags, tag); - s++; - } - return s; -} - -static void microdvd_open_tags(struct line *new_line, struct microdvd_tag *tags) -{ - for (int i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { - if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) - continue; - switch (tags[i].key) { - case 'Y': - case 'y': - for (int sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) - if (tags[i].data1 & (1 << sidx)) - append_text(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]); - break; - - case 'c': - append_text(new_line, "{\\c&H%06X&}", tags[i].data1); - break; - - case 'f': - append_text(new_line, "{\\fn%.*s}", BSTR_P(tags[i].data_string)); - break; - - case 's': - append_text(new_line, "{\\fs%d}", tags[i].data1); - break; - - case 'p': - if (tags[i].data1 == 0) - append_text(new_line, "{\\an8}"); - break; - - case 'o': - append_text(new_line, "{\\pos(%d,%d)}", - tags[i].data1, tags[i].data2); - break; - } - if (tags[i].persistent == MICRODVD_PERSISTENT_ON) - tags[i].persistent = MICRODVD_PERSISTENT_OPENED; - } -} - -static void microdvd_close_no_persistent_tags(struct line *new_line, - struct microdvd_tag *tags) -{ - int i; - - for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) { - if (tags[i].persistent != MICRODVD_PERSISTENT_OFF) - continue; - switch (tags[i].key) { - - case 'y': - for (int sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--) - if (tags[i].data1 & (1 << sidx)) - append_text(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]); - break; - - case 'c': - append_text(new_line, "{\\c}"); - break; - - case 'f': - append_text(new_line, "{\\fn}"); - break; - - case 's': - append_text(new_line, "{\\fs}"); - break; - } - tags[i].key = 0; - } -} - -void subassconvert_microdvd(const char *orig, char *dest, int dest_buffer_size) -{ - /* line is not const to avoid warnings with strtol, etc. - * orig content won't be changed */ - char *line = (char *)orig; - struct line new_line = { - .buf = dest, - .bufsize = dest_buffer_size, - }; - struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; - - while (*line) { - line = microdvd_load_tags(tags, line); - microdvd_open_tags(&new_line, tags); - - while (*line && *line != '|') - new_line.buf[new_line.len++] = *line++; - - if (*line == '|') { - microdvd_close_no_persistent_tags(&new_line, tags); - append_text(&new_line, "\\N"); - line++; - } - } - new_line.buf[new_line.len] = 0; -} -- cgit v1.2.3