mp_msg: print messages to stdout, statusline to stderr
[mplayer.git] / sub / subassconvert.c
blob7be4c486244159cc8669743b7cb3d9687c8c7111
1 /*
2 * Subtitles converter to SSA/ASS in order to allow special formatting
4 * This file is part of MPlayer.
6 * MPlayer is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * MPlayer is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include <string.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <stdarg.h>
26 #include <stdbool.h>
28 #include "mp_msg.h"
29 #include "subassconvert.h"
30 #include "bstr.h"
31 #include "libavutil/common.h"
33 struct line {
34 char *buf;
35 int bufsize;
36 int len;
39 #ifdef __GNUC__
40 static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3)));
41 #endif
43 static void append_text(struct line *dst, char *fmt, ...)
45 va_list va;
46 va_start(va, fmt);
47 int ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va);
48 if (ret < 0)
49 goto out;
50 dst->len += ret;
51 if (dst->len > dst->bufsize)
52 dst->len = dst->bufsize;
53 out:
54 va_end(va);
57 static int indexof(const char *s, int c)
59 char *f = strchr(s, c);
60 return f ? (f - s) : -1;
66 * SubRip
68 * Support basic tags (italic, bold, underline, strike-through)
69 * and font tag with size, color and face attributes.
73 struct font_tag {
74 int size;
75 uint32_t color;
76 struct bstr face;
77 bool has_size : 1;
78 bool has_color : 1;
79 bool has_face : 1;
82 static const struct tag_conv {
83 char *from;
84 char *to;
85 } subrip_basic_tags[] = {
86 {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"},
87 {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"},
88 {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"},
89 {"<s>", "{\\s1}"}, {"</s>", "{\\s0}"},
90 {"{", "\\{"}, {"}", "\\}"},
91 {"\r\n", "\\N"}, {"\n", "\\N"}, {"\r", "\\N"},
94 static const struct {
95 char *s;
96 uint32_t v;
97 } subrip_web_colors[] = {
98 /* Named CSS3 colors in BGR format; a subset of those
99 at http://www.w3.org/TR/css3-color/#svg-color */
100 {"aqua", 0xffff00},
101 {"black", 0x000000},
102 {"blue", 0xff0000},
103 {"cyan", 0xffff00},
104 {"fuchsia", 0xff00ff},
105 {"gray", 0x808080},
106 {"green", 0x008000},
107 {"grey", 0x808080},
108 {"lime", 0x00ff00},
109 {"magenta", 0xff00ff},
110 {"maroon", 0x000080},
111 {"navy", 0x800000},
112 {"olive", 0x008080},
113 {"orange", 0x00a5ff},
114 {"pink", 0xcbc0ff},
115 {"purple", 0x800080},
116 {"red", 0x0000ff},
117 {"silver", 0xc0c0c0},
118 {"teal", 0x808000},
119 {"white", 0xffffff},
120 {"yellow", 0x00ffff},
123 #define SUBRIP_MAX_STACKED_FONT_TAGS 16
125 /* Read the HTML-style attribute starting at *s, and skip *s past the value.
126 * Set attr and val to the parsed attribute name and value.
127 * Return 0 on success, or -1 if no valid attribute was found.
129 static int read_attr(char **s, struct bstr *attr, struct bstr *val)
131 char *eq = strchr(*s, '=');
132 if (!eq)
133 return -1;
134 attr->start = *s;
135 attr->len = eq - *s;
136 for (int i = 0; i < attr->len; i++)
137 if (!isalnum(attr->start[i]))
138 return -1;
139 val->start = eq + 1;
140 bool quoted = val->start[0] == '"';
141 if (quoted)
142 val->start++;
143 unsigned char *end = strpbrk(val->start, quoted ? "\"" : " >");
144 if (!end)
145 return -1;
146 val->len = end - val->start;
147 *s = end + quoted;
148 return 0;
151 void subassconvert_subrip(const char *orig, char *dest, int dest_buffer_size)
153 /* line is not const to avoid warnings with strtol, etc.
154 * orig content won't be changed */
155 char *line = (char *)orig;
156 struct line new_line = {
157 .buf = dest,
158 .bufsize = dest_buffer_size,
160 struct font_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS + 1];
161 font_stack[0] = (struct font_tag){0}; // type with all defaults
162 int sp = 0;
164 while (*line && new_line.len < new_line.bufsize - 1) {
165 char *orig_line = line;
167 for (int i = 0; i < FF_ARRAY_ELEMS(subrip_basic_tags); i++) {
168 const struct tag_conv *tag = &subrip_basic_tags[i];
169 int from_len = strlen(tag->from);
170 if (strncmp(line, tag->from, from_len) == 0) {
171 append_text(&new_line, "%s", tag->to);
172 line += from_len;
176 if (strncmp(line, "</font>", 7) == 0) {
177 /* Closing font tag */
178 line += 7;
180 if (sp > 0) {
181 struct font_tag *tag = &font_stack[sp];
182 struct font_tag *last_tag = &tag[-1];
183 sp--;
185 if (tag->has_size) {
186 if (!last_tag->has_size)
187 append_text(&new_line, "{\\fs}");
188 else if (last_tag->size != tag->size)
189 append_text(&new_line, "{\\fs%d}", last_tag->size);
192 if (tag->has_color) {
193 if (!last_tag->has_color)
194 append_text(&new_line, "{\\c}");
195 else if (last_tag->color != tag->color)
196 append_text(&new_line, "{\\c&H%06X&}", last_tag->color);
199 if (tag->has_face) {
200 if (!last_tag->has_face)
201 append_text(&new_line, "{\\fn}");
202 else if (bstrcmp(last_tag->face, tag->face) != 0)
203 append_text(&new_line, "{\\fn%.*s}",
204 BSTR_P(last_tag->face));
207 } else if (strncmp(line, "<font ", 6) == 0
208 && sp + 1 < FF_ARRAY_ELEMS(font_stack)) {
209 /* Opening font tag */
210 char *potential_font_tag_start = line;
211 int len_backup = new_line.len;
212 struct font_tag *tag = &font_stack[sp + 1];
213 bool has_valid_attr = false;
215 *tag = tag[-1]; // keep values from previous tag
216 line += 6;
218 while (*line && *line != '>') {
219 if (*line == ' ') {
220 line++;
221 continue;
223 struct bstr attr, val;
224 if (read_attr(&line, &attr, &val) < 0)
225 break;
226 if (!bstrcmp0(attr, "size")) {
227 tag->size = bstrtoll(val, &val, 10);
228 if (val.len)
229 break;
230 append_text(&new_line, "{\\fs%d}", tag->size);
231 tag->has_size = true;
232 has_valid_attr = true;
233 } else if (!bstrcmp0(attr, "color")) {
234 // Treat unrecognized color names as valid attributes
235 tag->has_color = true;
236 has_valid_attr = true;
237 // Standard web colors
238 for (int i = 0; i < FF_ARRAY_ELEMS(subrip_web_colors); i++) {
239 char *color = subrip_web_colors[i].s;
240 if (bstrcasecmp(val, bstr(color)) == 0) {
241 tag->color = subrip_web_colors[i].v;
242 goto foundcolor;
245 // Try to parse as hex even if there is no '#'
246 bstr_eatstart(&val, bstr("#"));
247 // #RRGGBB format
248 tag->color = bstrtoll(val, &val, 16) & 0x00ffffff;
249 tag->color = ((tag->color & 0xff) << 16)
250 | (tag->color & 0xff00)
251 | ((tag->color & 0xff0000) >> 16);
252 if (val.len) {
253 /* We didn't find any matching color */
254 mp_tmsg(MSGT_SUBREADER, MSGL_WARN,
255 "SubRip: unknown font color in subtitle: %s\n",
256 orig);
257 append_text(&new_line, "{\\c}");
258 } else {
259 foundcolor:
260 append_text(&new_line, "{\\c&H%06X&}", tag->color);
262 } else if (!bstrcmp0(attr, "face")) {
263 /* Font face attribute */
264 tag->face = val;
265 append_text(&new_line, "{\\fn%.*s}", BSTR_P(tag->face));
266 tag->has_face = true;
267 has_valid_attr = true;
268 } else
269 mp_tmsg(MSGT_SUBREADER, MSGL_WARN,"SubRip: unrecognized "
270 "attribute \"%.*s\" in font tag\n", BSTR_P(attr));
273 if (!has_valid_attr || *line != '>') { /* Not valid font tag */
274 line = potential_font_tag_start;
275 new_line.len = len_backup;
276 } else {
277 sp++;
278 line++;
282 /* Tag conversion code didn't match */
283 if (line == orig_line)
284 new_line.buf[new_line.len++] = *line++;
286 new_line.buf[new_line.len] = 0;
291 * MicroDVD
293 * Based on the specifications found here:
294 * https://trac.videolan.org/vlc/ticket/1825#comment:6
297 struct microdvd_tag {
298 char key;
299 int persistent;
300 uint32_t data1;
301 uint32_t data2;
302 struct bstr data_string;
305 #define MICRODVD_PERSISTENT_OFF 0
306 #define MICRODVD_PERSISTENT_ON 1
307 #define MICRODVD_PERSISTENT_OPENED 2
309 // Color, Font, Size, cHarset, stYle, Position, cOordinate
310 #define MICRODVD_TAGS "cfshyYpo"
312 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
314 int tag_index = indexof(MICRODVD_TAGS, tag.key);
316 if (tag_index < 0)
317 return;
318 memcpy(&tags[tag_index], &tag, sizeof(tag));
321 // italic, bold, underline, strike-through
322 #define MICRODVD_STYLES "ibus"
324 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
326 while (*s == '{') {
327 char *start = s;
328 char tag_char = *(s + 1);
329 struct microdvd_tag tag = {0};
331 if (!tag_char || *(s + 2) != ':')
332 break;
333 s += 3;
335 switch (tag_char) {
337 /* Style */
338 case 'Y':
339 tag.persistent = MICRODVD_PERSISTENT_ON;
340 case 'y':
341 while (*s && *s != '}') {
342 int style_index = indexof(MICRODVD_STYLES, *s);
344 if (style_index >= 0)
345 tag.data1 |= (1 << style_index);
346 s++;
348 if (*s != '}')
349 break;
350 /* We must distinguish persistent and non-persistent styles
351 * to handle this kind of style tags: {y:ib}{Y:us} */
352 tag.key = tag_char;
353 break;
355 /* Color */
356 case 'C':
357 tag.persistent = MICRODVD_PERSISTENT_ON;
358 case 'c':
359 tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
360 if (*s != '}')
361 break;
362 tag.key = 'c';
363 break;
365 /* Font name */
366 case 'F':
367 tag.persistent = MICRODVD_PERSISTENT_ON;
368 case 'f':
370 int len = indexof(s, '}');
371 if (len < 0)
372 break;
373 tag.data_string.start = s;
374 tag.data_string.len = len;
375 s += len;
376 tag.key = 'f';
377 break;
380 /* Font size */
381 case 'S':
382 tag.persistent = MICRODVD_PERSISTENT_ON;
383 case 's':
384 tag.data1 = strtol(s, &s, 10);
385 if (*s != '}')
386 break;
387 tag.key = 's';
388 break;
390 /* Charset */
391 case 'H':
393 //TODO: not yet handled, just parsed.
394 int len = indexof(s, '}');
395 if (len < 0)
396 break;
397 tag.data_string.start = s;
398 tag.data_string.len = len;
399 s += len;
400 tag.key = 'h';
401 break;
404 /* Position */
405 case 'P':
406 tag.persistent = MICRODVD_PERSISTENT_ON;
407 tag.data1 = (*s++ == '1');
408 if (*s != '}')
409 break;
410 tag.key = 'p';
411 break;
413 /* Coordinates */
414 case 'o':
415 tag.persistent = MICRODVD_PERSISTENT_ON;
416 tag.data1 = strtol(s, &s, 10);
417 if (*s != ',')
418 break;
419 s++;
420 tag.data2 = strtol(s, &s, 10);
421 if (*s != '}')
422 break;
423 tag.key = 'o';
424 break;
426 default: /* Unknown tag, we consider it to be text */
427 break;
430 if (tag.key == 0)
431 return start;
433 microdvd_set_tag(tags, tag);
434 s++;
436 return s;
439 static void microdvd_open_tags(struct line *new_line, struct microdvd_tag *tags)
441 for (int i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
442 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
443 continue;
444 switch (tags[i].key) {
445 case 'Y':
446 case 'y':
447 for (int sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
448 if (tags[i].data1 & (1 << sidx))
449 append_text(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
450 break;
452 case 'c':
453 append_text(new_line, "{\\c&H%06X&}", tags[i].data1);
454 break;
456 case 'f':
457 append_text(new_line, "{\\fn%.*s}", BSTR_P(tags[i].data_string));
458 break;
460 case 's':
461 append_text(new_line, "{\\fs%d}", tags[i].data1);
462 break;
464 case 'p':
465 if (tags[i].data1 == 0)
466 append_text(new_line, "{\\an8}");
467 break;
469 case 'o':
470 append_text(new_line, "{\\pos(%d,%d)}",
471 tags[i].data1, tags[i].data2);
472 break;
474 if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
475 tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
479 static void microdvd_close_no_persistent_tags(struct line *new_line,
480 struct microdvd_tag *tags)
482 int i;
484 for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
485 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
486 continue;
487 switch (tags[i].key) {
489 case 'y':
490 for (int sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
491 if (tags[i].data1 & (1 << sidx))
492 append_text(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
493 break;
495 case 'c':
496 append_text(new_line, "{\\c}");
497 break;
499 case 'f':
500 append_text(new_line, "{\\fn}");
501 break;
503 case 's':
504 append_text(new_line, "{\\fs}");
505 break;
507 tags[i].key = 0;
511 void subassconvert_microdvd(const char *orig, char *dest, int dest_buffer_size)
513 /* line is not const to avoid warnings with strtol, etc.
514 * orig content won't be changed */
515 char *line = (char *)orig;
516 struct line new_line = {
517 .buf = dest,
518 .bufsize = dest_buffer_size,
520 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
522 while (*line) {
523 line = microdvd_load_tags(tags, line);
524 microdvd_open_tags(&new_line, tags);
526 while (*line && *line != '|')
527 new_line.buf[new_line.len++] = *line++;
529 if (*line == '|') {
530 microdvd_close_no_persistent_tags(&new_line, tags);
531 append_text(&new_line, "\\N");
532 line++;
535 new_line.buf[new_line.len] = 0;