Mercurial > mplayer.hg
annotate sub/subassconvert.c @ 34756:df3ff52039fe
Add code to support CC subtitles in ASTC and MOV.
Code to actually use these will be added later, since it
needs special code in FFmpeg.
Code for MOV is already read, ASTC might take longer.
author | reimar |
---|---|
date | Sat, 07 Apr 2012 00:10:27 +0000 |
parents | 22888a8cb312 |
children | da38eb1e2069 |
rev | line source |
---|---|
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" | |
32464
22888a8cb312
Do not use a path for including files in the same directory.
reimar
parents:
32462
diff
changeset
|
30 #include "subassconvert.h" |
32462 | 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 } |