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.
29 #include "subassconvert.h"
31 #include "libavutil/common.h"
40 static void append_text(struct line
*dst
, char *fmt
, ...) __attribute__ ((format(printf
, 2, 3)));
43 static void append_text(struct line
*dst
, char *fmt
, ...)
47 int ret
= vsnprintf(dst
->buf
+ dst
->len
, dst
->bufsize
- dst
->len
, fmt
, va
);
51 if (dst
->len
> dst
->bufsize
)
52 dst
->len
= dst
->bufsize
;
57 static int indexof(const char *s
, int c
)
59 char *f
= strchr(s
, c
);
60 return f
? (f
- s
) : -1;
68 * Support basic tags (italic, bold, underline, strike-through)
69 * and font tag with size, color and face attributes.
82 static const struct tag_conv
{
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"},
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 */
104 {"fuchsia", 0xff00ff},
109 {"magenta", 0xff00ff},
110 {"maroon", 0x000080},
113 {"orange", 0x00a5ff},
115 {"purple", 0x800080},
117 {"silver", 0xc0c0c0},
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
, '=');
136 for (int i
= 0; i
< attr
->len
; i
++)
137 if (!isalnum(attr
->start
[i
]))
140 bool quoted
= val
->start
[0] == '"';
143 unsigned char *end
= strpbrk(val
->start
, quoted
? "\"" : " >");
146 val
->len
= end
- val
->start
;
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
= {
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
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
);
176 if (strncmp(line
, "</font>", 7) == 0) {
177 /* Closing font tag */
181 struct font_tag
*tag
= &font_stack
[sp
];
182 struct font_tag
*last_tag
= &tag
[-1];
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
);
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
218 while (*line
&& *line
!= '>') {
223 struct bstr attr
, val
;
224 if (read_attr(&line
, &attr
, &val
) < 0)
226 if (!bstrcmp0(attr
, "size")) {
227 tag
->size
= bstrtoll(val
, &val
, 10);
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
;
245 // Try to parse as hex even if there is no '#'
246 bstr_eatstart(&val
, bstr("#"));
248 tag
->color
= bstrtoll(val
, &val
, 16) & 0x00ffffff;
249 tag
->color
= ((tag
->color
& 0xff) << 16)
250 | (tag
->color
& 0xff00)
251 | ((tag
->color
& 0xff0000) >> 16);
253 /* We didn't find any matching color */
254 mp_tmsg(MSGT_SUBREADER
, MSGL_WARN
,
255 "SubRip: unknown font color in subtitle: %s\n",
257 append_text(&new_line
, "{\\c}");
260 append_text(&new_line
, "{\\c&H%06X&}", tag
->color
);
262 } else if (!bstrcmp0(attr
, "face")) {
263 /* Font face attribute */
265 append_text(&new_line
, "{\\fn%.*s}", BSTR_P(tag
->face
));
266 tag
->has_face
= true;
267 has_valid_attr
= true;
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
;
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;
293 * Based on the specifications found here:
294 * https://trac.videolan.org/vlc/ticket/1825#comment:6
297 struct microdvd_tag
{
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
);
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
)
328 char tag_char
= *(s
+ 1);
329 struct microdvd_tag tag
= {0};
331 if (!tag_char
|| *(s
+ 2) != ':')
339 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
341 while (*s
&& *s
!= '}') {
342 int style_index
= indexof(MICRODVD_STYLES
, *s
);
344 if (style_index
>= 0)
345 tag
.data1
|= (1 << style_index
);
350 /* We must distinguish persistent and non-persistent styles
351 * to handle this kind of style tags: {y:ib}{Y:us} */
357 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
359 tag
.data1
= strtol(s
, &s
, 16) & 0x00ffffff;
367 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
370 int len
= indexof(s
, '}');
373 tag
.data_string
.start
= s
;
374 tag
.data_string
.len
= len
;
382 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
384 tag
.data1
= strtol(s
, &s
, 10);
393 //TODO: not yet handled, just parsed.
394 int len
= indexof(s
, '}');
397 tag
.data_string
.start
= s
;
398 tag
.data_string
.len
= len
;
406 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
407 tag
.data1
= (*s
++ == '1');
415 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
416 tag
.data1
= strtol(s
, &s
, 10);
420 tag
.data2
= strtol(s
, &s
, 10);
426 default: /* Unknown tag, we consider it to be text */
433 microdvd_set_tag(tags
, tag
);
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
)
444 switch (tags
[i
].key
) {
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
]);
453 append_text(new_line
, "{\\c&H%06X&}", tags
[i
].data1
);
457 append_text(new_line
, "{\\fn%.*s}", BSTR_P(tags
[i
].data_string
));
461 append_text(new_line
, "{\\fs%d}", tags
[i
].data1
);
465 if (tags
[i
].data1
== 0)
466 append_text(new_line
, "{\\an8}");
470 append_text(new_line
, "{\\pos(%d,%d)}",
471 tags
[i
].data1
, tags
[i
].data2
);
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
)
484 for (i
= sizeof(MICRODVD_TAGS
) - 2; i
; i
--) {
485 if (tags
[i
].persistent
!= MICRODVD_PERSISTENT_OFF
)
487 switch (tags
[i
].key
) {
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
]);
496 append_text(new_line
, "{\\c}");
500 append_text(new_line
, "{\\fn}");
504 append_text(new_line
, "{\\fs}");
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
= {
518 .bufsize
= dest_buffer_size
,
520 struct microdvd_tag tags
[sizeof(MICRODVD_TAGS
) - 1] = {{0}};
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
++;
530 microdvd_close_no_persistent_tags(&new_line
, tags
);
531 append_text(&new_line
, "\\N");
535 new_line
.buf
[new_line
.len
] = 0;