32462
|
1 /*
|
|
2 * Subtitles converter to SSA/ASS in order to allow special formatting
|
|
3 *
|
|
4 * This file is part of MPlayer.
|
|
5 *
|
|
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.
|
|
10 *
|
|
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.
|
|
15 *
|
|
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.
|
|
19 */
|
|
20
|
|
21 #include <string.h>
|
|
22 #include <stdint.h>
|
|
23 #include <stdlib.h>
|
|
24 #include <stdio.h>
|
|
25 #include <stdarg.h>
|
|
26
|
|
27 #include "mp_msg.h"
|
|
28 #include "help_mp.h"
|
|
29 #include "bstr.h"
|
|
30 #include "sub/subassconvert.h"
|
|
31 #include "libavutil/common.h"
|
|
32
|
|
33 struct line {
|
|
34 char *buf;
|
|
35 size_t bufsize;
|
|
36 size_t len;
|
|
37 };
|
|
38
|
|
39 #ifdef __GNUC__
|
|
40 static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3)));
|
|
41 #endif
|
|
42
|
|
43 static void append_text(struct line *dst, char *fmt, ...)
|
|
44 {
|
|
45 va_list va;
|
|
46 int ret;
|
|
47
|
|
48 va_start(va, fmt);
|
|
49 ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va);
|
|
50 if (ret >= 0) {
|
|
51 dst->len += ret;
|
|
52 if (dst->len > dst->bufsize)
|
|
53 dst->len = dst->bufsize;
|
|
54 }
|
|
55 va_end(va);
|
|
56 }
|
|
57
|
|
58 static int indexof(const char *s, int c)
|
|
59 {
|
|
60 char *f = strchr(s, c);
|
|
61 return f ? (f - s) : -1;
|
|
62 }
|
|
63
|
|
64
|
|
65
|
|
66 /*
|
|
67 * SubRip
|
|
68 *
|
|
69 * Support basic tags (italic, bold, underline, strike-through)
|
|
70 * and font tag with size, color and face attributes.
|
|
71 *
|
|
72 */
|
|
73
|
|
74 struct font_tag {
|
|
75 struct bstr face;
|
|
76 int size;
|
|
77 uint32_t color;
|
|
78 };
|
|
79
|
|
80 static const struct tag_conv {
|
|
81 const char *from;
|
|
82 const char *to;
|
|
83 } subrip_basic_tags[] = {
|
|
84 {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"},
|
|
85 {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"},
|
|
86 {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"},
|
|
87 {"<s>", "{\\s1}"}, {"</s>", "{\\s0}"},
|
|
88 {"{", "\\{"}, {"}", "\\}"},
|
|
89 {"\n", "\\N"}
|
|
90 };
|
|
91
|
|
92 static const struct {
|
|
93 const char *s;
|
|
94 uint32_t v;
|
|
95 } subrip_web_colors[] = {
|
|
96 /* 16 named HTML colors in BGR format */
|
|
97 {"red", 0x0000ff}, {"blue", 0xff0000}, {"lime", 0x00ff00},
|
|
98 {"aqua", 0xffff00}, {"purple", 0x800080}, {"yellow", 0x00ffff},
|
|
99 {"fuchsia", 0xff00ff}, {"white", 0xffffff}, {"gray", 0x808080},
|
|
100 {"maroon", 0x000080}, {"olive", 0x008080}, {"black", 0x000000},
|
|
101 {"silver", 0xc0c0c0}, {"teal", 0x808000}, {"green", 0x008000},
|
|
102 {"navy", 0x800000}
|
|
103 };
|
|
104
|
|
105 #define SUBRIP_MAX_STACKED_FONT_TAGS 16
|
|
106 #define SUBRIP_FLAG_COLOR 0x01000000
|
|
107
|
|
108 /**
|
|
109 * \brief Convert SubRip lines into ASS markup
|
|
110 * \param orig original SubRip lines. The content will remain untouched.
|
|
111 * \param dest ASS markup destination buffer.
|
|
112 * \param dest_buffer_size maximum size for the destination buffer.
|
|
113 */
|
|
114 void subassconvert_subrip(const char *orig, char *dest, size_t dest_buffer_size)
|
|
115 {
|
|
116 /* line is not const to avoid warnings with strtol, etc.
|
|
117 * orig content won't be changed */
|
|
118 char *line = (char *)orig;
|
|
119 struct line new_line = {
|
|
120 .buf = dest,
|
|
121 .bufsize = dest_buffer_size,
|
|
122 };
|
|
123 struct font_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS];
|
|
124 int sp = 0;
|
|
125
|
|
126 font_stack[0] = (struct font_tag){}; // type with all defaults
|
|
127 while (*line && new_line.len < new_line.bufsize - 1) {
|
|
128 char *orig_line = line;
|
|
129 int i;
|
|
130
|
|
131 for (i = 0; i < FF_ARRAY_ELEMS(subrip_basic_tags); i++) {
|
|
132 const struct tag_conv *tag = &subrip_basic_tags[i];
|
|
133 int from_len = strlen(tag->from);
|
|
134 if (strncmp(line, tag->from, from_len) == 0) {
|
|
135 append_text(&new_line, "%s", tag->to);
|
|
136 line += from_len;
|
|
137 }
|
|
138 }
|
|
139
|
|
140 if (strncmp(line, "</font>", 7) == 0) {
|
|
141 /* Closing font tag */
|
|
142 line += 7;
|
|
143
|
|
144 if (sp > 0) {
|
|
145 struct font_tag *tag = &font_stack[sp];
|
|
146 struct font_tag *last_tag = &tag[-1];
|
|
147 sp--;
|
|
148
|
|
149 if (tag->size) {
|
|
150 if (!last_tag->size)
|
|
151 append_text(&new_line, "{\\fs}");
|
|
152 else if (last_tag->size != tag->size)
|
|
153 append_text(&new_line, "{\\fs%d}", last_tag->size);
|
|
154 }
|
|
155
|
|
156 if (tag->color & SUBRIP_FLAG_COLOR) {
|
|
157 if (!(last_tag->color & SUBRIP_FLAG_COLOR))
|
|
158 append_text(&new_line, "{\\c}");
|
|
159 else if (last_tag->color != tag->color)
|
|
160 append_text(&new_line, "{\\c&H%06X&}",
|
|
161 last_tag->color & 0xffffff);
|
|
162 }
|
|
163
|
|
164 if (tag->face.len) {
|
|
165 if (!last_tag->face.len)
|
|
166 append_text(&new_line, "{\\fn}");
|
|
167 else if (bstrcmp(last_tag->face, tag->face) != 0)
|
|
168 append_text(&new_line, "{\\fn%.*s}",
|
|
169 BSTR_P(last_tag->face));
|
|
170 }
|
|
171 }
|
|
172 } else if (strncmp(line, "<font ", 6) == 0
|
|
173 && sp + 1 < FF_ARRAY_ELEMS(font_stack)) {
|
|
174 /* Opening font tag */
|
|
175 char *potential_font_tag_start = line;
|
|
176 int len_backup = new_line.len;
|
|
177 struct font_tag *tag = &font_stack[sp + 1];
|
|
178 int has_valid_attr = 0;
|
|
179
|
|
180 *tag = tag[-1]; // keep values from previous tag
|
|
181 line += 6;
|
|
182
|
|
183 while (*line && *line != '>') {
|
|
184 if (strncmp(line, "size=\"", 6) == 0) {
|
|
185 line += 6;
|
|
186 tag->size = strtol(line, &line, 10);
|
|
187 if (*line != '"' || !tag->size)
|
|
188 break;
|
|
189 append_text(&new_line, "{\\fs%d}", tag->size);
|
|
190 has_valid_attr = 1;
|
|
191 } else if (strncmp(line, "color=\"", 7) == 0) {
|
|
192 line += 7;
|
|
193 if (*line == '#') {
|
|
194 // #RRGGBB format
|
|
195 line++;
|
|
196 tag->color = strtol(line, &line, 16) & 0x00ffffff;
|
|
197 if (*line != '"')
|
|
198 break;
|
|
199 tag->color = ((tag->color & 0xff) << 16) |
|
|
200 (tag->color & 0xff00) |
|
|
201 ((tag->color & 0xff0000) >> 16) |
|
|
202 SUBRIP_FLAG_COLOR;
|
|
203 } else {
|
|
204 // Standard web colors
|
|
205 int i, len = indexof(line, '"');
|
|
206 if (len <= 0)
|
|
207 break;
|
|
208 for (i = 0; i < FF_ARRAY_ELEMS(subrip_web_colors); i++) {
|
|
209 const char *color = subrip_web_colors[i].s;
|
|
210 if (strlen(color) == len
|
|
211 && strncasecmp(line, color, len) == 0) {
|
|
212 tag->color = SUBRIP_FLAG_COLOR | subrip_web_colors[i].v;
|
|
213 break;
|
|
214 }
|
|
215 }
|
|
216
|
|
217 if (i == FF_ARRAY_ELEMS(subrip_web_colors)) {
|
|
218 /* We didn't find any matching color */
|
|
219 line = strchr(line, '"'); // can't be NULL, see above
|
|
220 mp_msg(MSGT_SUBREADER, MSGL_WARN,
|
|
221 MSGTR_SUBTITLES_SubRip_UnknownFontColor, orig);
|
|
222 append_text(&new_line, "{\\c}");
|
|
223 line += 2;
|
|
224 continue;
|
|
225 }
|
|
226
|
|
227 line += len;
|
|
228 }
|
|
229 append_text(&new_line, "{\\c&H%06X&}", tag->color & 0xffffff);
|
|
230 has_valid_attr = 1;
|
|
231 } else if (strncmp(line, "face=\"", 6) == 0) {
|
|
232 /* Font face attribute */
|
|
233 int len;
|
|
234 line += 6;
|
|
235 len = indexof(line, '"');
|
|
236 if (len <= 0)
|
|
237 break;
|
|
238 tag->face.start = line;
|
|
239 tag->face.len = len;
|
|
240 line += len;
|
|
241 append_text(&new_line, "{\\fn%.*s}", BSTR_P(tag->face));
|
|
242 has_valid_attr = 1;
|
|
243 }
|
|
244 line++;
|
|
245 }
|
|
246
|
|
247 if (!has_valid_attr || *line != '>') { /* Not valid font tag */
|
|
248 line = potential_font_tag_start;
|
|
249 new_line.len = len_backup;
|
|
250 } else {
|
|
251 sp++;
|
|
252 line++;
|
|
253 }
|
|
254 }
|
|
255
|
|
256 /* Tag conversion code didn't match */
|
|
257 if (line == orig_line)
|
|
258 new_line.buf[new_line.len++] = *line++;
|
|
259 }
|
|
260 new_line.buf[new_line.len] = 0;
|
|
261 }
|
|
262
|
|
263
|
|
264 /*
|
|
265 * MicroDVD
|
|
266 *
|
|
267 * Based on the specifications found here:
|
|
268 * https://trac.videolan.org/vlc/ticket/1825#comment:6
|
|
269 */
|
|
270
|
|
271 struct microdvd_tag {
|
|
272 char key;
|
|
273 int persistent;
|
|
274 uint32_t data1;
|
|
275 uint32_t data2;
|
|
276 struct bstr data_string;
|
|
277 };
|
|
278
|
|
279 #define MICRODVD_PERSISTENT_OFF 0
|
|
280 #define MICRODVD_PERSISTENT_ON 1
|
|
281 #define MICRODVD_PERSISTENT_OPENED 2
|
|
282
|
|
283 // Color, Font, Size, cHarset, stYle, Position, cOordinate
|
|
284 #define MICRODVD_TAGS "cfshyYpo"
|
|
285
|
|
286 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
|
|
287 {
|
|
288 int tag_index = indexof(MICRODVD_TAGS, tag.key);
|
|
289
|
|
290 if (tag_index < 0)
|
|
291 return;
|
|
292 memcpy(&tags[tag_index], &tag, sizeof(tag));
|
|
293 }
|
|
294
|
|
295 // italic, bold, underline, strike-through
|
|
296 #define MICRODVD_STYLES "ibus"
|
|
297
|
|
298 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
|
|
299 {
|
|
300 while (*s == '{') {
|
|
301 char *start = s;
|
|
302 char tag_char = *(s + 1);
|
|
303 struct microdvd_tag tag = {};
|
|
304
|
|
305 if (!tag_char || *(s + 2) != ':')
|
|
306 break;
|
|
307 s += 3;
|
|
308
|
|
309 switch (tag_char) {
|
|
310
|
|
311 /* Style */
|
|
312 case 'Y':
|
|
313 tag.persistent = MICRODVD_PERSISTENT_ON;
|
|
314 case 'y':
|
|
315 while (*s && *s != '}') {
|
|
316 int style_index = indexof(MICRODVD_STYLES, *s);
|
|
317
|
|
318 if (style_index >= 0)
|
|
319 tag.data1 |= (1 << style_index);
|
|
320 s++;
|
|
321 }
|
|
322 if (*s != '}')
|
|
323 break;
|
|
324 /* We must distinguish persistent and non-persistent styles
|
|
325 * to handle this kind of style tags: {y:ib}{Y:us} */
|
|
326 tag.key = tag_char;
|
|
327 break;
|
|
328
|
|
329 /* Color */
|
|
330 case 'C':
|
|
331 tag.persistent = MICRODVD_PERSISTENT_ON;
|
|
332 case 'c':
|
|
333 tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
|
|
334 if (*s != '}')
|
|
335 break;
|
|
336 tag.key = 'c';
|
|
337 break;
|
|
338
|
|
339 /* Font name */
|
|
340 case 'F':
|
|
341 tag.persistent = MICRODVD_PERSISTENT_ON;
|
|
342 case 'f':
|
|
343 {
|
|
344 int len = indexof(s, '}');
|
|
345 if (len < 0)
|
|
346 break;
|
|
347 tag.data_string.start = s;
|
|
348 tag.data_string.len = len;
|
|
349 s += len;
|
|
350 tag.key = 'f';
|
|
351 break;
|
|
352 }
|
|
353
|
|
354 /* Font size */
|
|
355 case 'S':
|
|
356 tag.persistent = MICRODVD_PERSISTENT_ON;
|
|
357 case 's':
|
|
358 tag.data1 = strtol(s, &s, 10);
|
|
359 if (*s != '}')
|
|
360 break;
|
|
361 tag.key = 's';
|
|
362 break;
|
|
363
|
|
364 /* Charset */
|
|
365 case 'H':
|
|
366 {
|
|
367 //TODO: not yet handled, just parsed.
|
|
368 int len = indexof(s, '}');
|
|
369 if (len < 0)
|
|
370 break;
|
|
371 tag.data_string.start = s;
|
|
372 tag.data_string.len = len;
|
|
373 s += len;
|
|
374 tag.key = 'h';
|
|
375 break;
|
|
376 }
|
|
377
|
|
378 /* Position */
|
|
379 case 'P':
|
|
380 tag.persistent = MICRODVD_PERSISTENT_ON;
|
|
381 tag.data1 = (*s++ == '1');
|
|
382 if (*s != '}')
|
|
383 break;
|
|
384 tag.key = 'p';
|
|
385 break;
|
|
386
|
|
387 /* Coordinates */
|
|
388 case 'o':
|
|
389 tag.persistent = MICRODVD_PERSISTENT_ON;
|
|
390 tag.data1 = strtol(s, &s, 10);
|
|
391 if (*s != ',')
|
|
392 break;
|
|
393 s++;
|
|
394 tag.data2 = strtol(s, &s, 10);
|
|
395 if (*s != '}')
|
|
396 break;
|
|
397 tag.key = 'o';
|
|
398 break;
|
|
399
|
|
400 default: /* Unknown tag, we consider it's text */
|
|
401 break;
|
|
402 }
|
|
403
|
|
404 if (tag.key == 0)
|
|
405 return start;
|
|
406
|
|
407 microdvd_set_tag(tags, tag);
|
|
408 s++;
|
|
409 }
|
|
410 return s;
|
|
411 }
|
|
412
|
|
413 static void microdvd_open_tags(struct line *new_line, struct microdvd_tag *tags)
|
|
414 {
|
|
415 int i, sidx;
|
|
416 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
|
|
417 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
|
|
418 continue;
|
|
419 switch (tags[i].key) {
|
|
420 case 'Y':
|
|
421 case 'y':
|
|
422 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
|
|
423 if (tags[i].data1 & (1 << sidx))
|
|
424 append_text(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
|
|
425 break;
|
|
426
|
|
427 case 'c':
|
|
428 append_text(new_line, "{\\c&H%06X&}", tags[i].data1);
|
|
429 break;
|
|
430
|
|
431 case 'f':
|
|
432 append_text(new_line, "{\\fn%.*s}", BSTR_P(tags[i].data_string));
|
|
433 break;
|
|
434
|
|
435 case 's':
|
|
436 append_text(new_line, "{\\fs%d}", tags[i].data1);
|
|
437 break;
|
|
438
|
|
439 case 'p':
|
|
440 if (tags[i].data1 == 0)
|
|
441 append_text(new_line, "{\\an8}");
|
|
442 break;
|
|
443
|
|
444 case 'o':
|
|
445 append_text(new_line, "{\\pos(%d,%d)}",
|
|
446 tags[i].data1, tags[i].data2);
|
|
447 break;
|
|
448 }
|
|
449 if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
|
|
450 tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
|
|
451 }
|
|
452 }
|
|
453
|
|
454 static void microdvd_close_no_persistent_tags(struct line *new_line,
|
|
455 struct microdvd_tag *tags)
|
|
456 {
|
|
457 int i, sidx;
|
|
458
|
|
459 for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
|
|
460 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
|
|
461 continue;
|
|
462 switch (tags[i].key) {
|
|
463
|
|
464 case 'y':
|
|
465 for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
|
|
466 if (tags[i].data1 & (1 << sidx))
|
|
467 append_text(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
|
|
468 break;
|
|
469
|
|
470 case 'c':
|
|
471 append_text(new_line, "{\\c}");
|
|
472 break;
|
|
473
|
|
474 case 'f':
|
|
475 append_text(new_line, "{\\fn}");
|
|
476 break;
|
|
477
|
|
478 case 's':
|
|
479 append_text(new_line, "{\\fs}");
|
|
480 break;
|
|
481 }
|
|
482 tags[i].key = 0;
|
|
483 }
|
|
484 }
|
|
485
|
|
486 /**
|
|
487 * \brief Convert MicroDVD lines into ASS markup
|
|
488 * \param orig original MicroDVD line. The content will remain untouched.
|
|
489 * \param dest ASS markup destination buffer.
|
|
490 * \param dest_buffer_size maximum size for the destination buffer.
|
|
491 */
|
|
492 void subassconvert_microdvd(const char *orig, char *dest, size_t dest_buffer_size)
|
|
493 {
|
|
494 /* line is not const to avoid warnings with strtol, etc.
|
|
495 * orig content won't be changed */
|
|
496 char *line = (char *)orig;
|
|
497 struct line new_line = {
|
|
498 .buf = dest,
|
|
499 .bufsize = dest_buffer_size,
|
|
500 };
|
|
501 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {};
|
|
502
|
|
503 while (*line) {
|
|
504 line = microdvd_load_tags(tags, line);
|
|
505 microdvd_open_tags(&new_line, tags);
|
|
506
|
|
507 while (*line && *line != '|')
|
|
508 new_line.buf[new_line.len++] = *line++;
|
|
509
|
|
510 if (*line == '|') {
|
|
511 microdvd_close_no_persistent_tags(&new_line, tags);
|
|
512 append_text(&new_line, "\\N");
|
|
513 line++;
|
|
514 }
|
|
515 }
|
|
516 new_line.buf[new_line.len] = 0;
|
|
517 }
|