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 /* 16 named HTML colors in BGR format */
99 {"red", 0x0000ff}, {"blue", 0xff0000}, {"lime", 0x00ff00},
100 {"aqua", 0xffff00}, {"purple", 0x800080}, {"yellow", 0x00ffff},
101 {"fuchsia", 0xff00ff}, {"white", 0xffffff}, {"gray", 0x808080},
102 {"maroon", 0x000080}, {"olive", 0x008080}, {"black", 0x000000},
103 {"silver", 0xc0c0c0}, {"teal", 0x808000}, {"green", 0x008000},
107 #define SUBRIP_MAX_STACKED_FONT_TAGS 16
109 /* Read the HTML-style attribute starting at *s, and skip *s past the value.
110 * Set attr and val to the parsed attribute name and value.
111 * Return 0 on success, or -1 if no valid attribute was found.
113 static int read_attr(char **s
, struct bstr
*attr
, struct bstr
*val
)
115 char *eq
= strchr(*s
, '=');
120 for (int i
= 0; i
< attr
->len
; i
++)
121 if (!isalnum(attr
->start
[i
]))
124 bool quoted
= val
->start
[0] == '"';
127 unsigned char *end
= strpbrk(val
->start
, quoted
? "\"" : " >");
130 val
->len
= end
- val
->start
;
135 void subassconvert_subrip(const char *orig
, char *dest
, int dest_buffer_size
)
137 /* line is not const to avoid warnings with strtol, etc.
138 * orig content won't be changed */
139 char *line
= (char *)orig
;
140 struct line new_line
= {
142 .bufsize
= dest_buffer_size
,
144 struct font_tag font_stack
[SUBRIP_MAX_STACKED_FONT_TAGS
+ 1];
145 font_stack
[0] = (struct font_tag
){0}; // type with all defaults
148 while (*line
&& new_line
.len
< new_line
.bufsize
- 1) {
149 char *orig_line
= line
;
151 for (int i
= 0; i
< FF_ARRAY_ELEMS(subrip_basic_tags
); i
++) {
152 const struct tag_conv
*tag
= &subrip_basic_tags
[i
];
153 int from_len
= strlen(tag
->from
);
154 if (strncmp(line
, tag
->from
, from_len
) == 0) {
155 append_text(&new_line
, "%s", tag
->to
);
160 if (strncmp(line
, "</font>", 7) == 0) {
161 /* Closing font tag */
165 struct font_tag
*tag
= &font_stack
[sp
];
166 struct font_tag
*last_tag
= &tag
[-1];
170 if (!last_tag
->has_size
)
171 append_text(&new_line
, "{\\fs}");
172 else if (last_tag
->size
!= tag
->size
)
173 append_text(&new_line
, "{\\fs%d}", last_tag
->size
);
176 if (tag
->has_color
) {
177 if (!last_tag
->has_color
)
178 append_text(&new_line
, "{\\c}");
179 else if (last_tag
->color
!= tag
->color
)
180 append_text(&new_line
, "{\\c&H%06X&}", last_tag
->color
);
184 if (!last_tag
->has_face
)
185 append_text(&new_line
, "{\\fn}");
186 else if (bstrcmp(last_tag
->face
, tag
->face
) != 0)
187 append_text(&new_line
, "{\\fn%.*s}",
188 BSTR_P(last_tag
->face
));
191 } else if (strncmp(line
, "<font ", 6) == 0
192 && sp
+ 1 < FF_ARRAY_ELEMS(font_stack
)) {
193 /* Opening font tag */
194 char *potential_font_tag_start
= line
;
195 int len_backup
= new_line
.len
;
196 struct font_tag
*tag
= &font_stack
[sp
+ 1];
197 bool has_valid_attr
= false;
199 *tag
= tag
[-1]; // keep values from previous tag
202 while (*line
&& *line
!= '>') {
207 struct bstr attr
, val
;
208 if (read_attr(&line
, &attr
, &val
) < 0)
210 if (!bstrcmp0(attr
, "size")) {
211 tag
->size
= bstrtoll(val
, &val
, 10);
214 append_text(&new_line
, "{\\fs%d}", tag
->size
);
215 tag
->has_size
= true;
216 has_valid_attr
= true;
217 } else if (!bstrcmp0(attr
, "color")) {
218 if (bstr_eatstart(&val
, bstr("#"))) {
220 tag
->color
= bstrtoll(val
, &val
, 16) & 0x00ffffff;
223 tag
->color
= ((tag
->color
& 0xff) << 16)
224 | (tag
->color
& 0xff00)
225 | ((tag
->color
& 0xff0000) >> 16);
227 // Standard web colors
228 for (int i
= 0; i
< FF_ARRAY_ELEMS(subrip_web_colors
); i
++) {
229 char *color
= subrip_web_colors
[i
].s
;
230 if (bstrcasecmp(val
, bstr(color
)) == 0) {
231 tag
->color
= subrip_web_colors
[i
].v
;
236 /* We didn't find any matching color */
237 mp_tmsg(MSGT_SUBREADER
, MSGL_WARN
,
238 "SubRip: unknown font color in subtitle: %s\n", orig
);
239 append_text(&new_line
, "{\\c}");
244 append_text(&new_line
, "{\\c&H%06X&}", tag
->color
);
245 tag
->has_color
= true;
246 has_valid_attr
= true;
247 } else if (!bstrcmp0(attr
, "face")) {
248 /* Font face attribute */
250 append_text(&new_line
, "{\\fn%.*s}", BSTR_P(tag
->face
));
251 tag
->has_face
= true;
252 has_valid_attr
= true;
254 mp_tmsg(MSGT_SUBREADER
, MSGL_WARN
,"SubRip: unrecognized "
255 "attribute \"%.*s\" in font tag\n", BSTR_P(attr
));
258 if (!has_valid_attr
|| *line
!= '>') { /* Not valid font tag */
259 line
= potential_font_tag_start
;
260 new_line
.len
= len_backup
;
267 /* Tag conversion code didn't match */
268 if (line
== orig_line
)
269 new_line
.buf
[new_line
.len
++] = *line
++;
271 new_line
.buf
[new_line
.len
] = 0;
278 * Based on the specifications found here:
279 * https://trac.videolan.org/vlc/ticket/1825#comment:6
282 struct microdvd_tag
{
287 struct bstr data_string
;
290 #define MICRODVD_PERSISTENT_OFF 0
291 #define MICRODVD_PERSISTENT_ON 1
292 #define MICRODVD_PERSISTENT_OPENED 2
294 // Color, Font, Size, cHarset, stYle, Position, cOordinate
295 #define MICRODVD_TAGS "cfshyYpo"
297 static void microdvd_set_tag(struct microdvd_tag
*tags
, struct microdvd_tag tag
)
299 int tag_index
= indexof(MICRODVD_TAGS
, tag
.key
);
303 memcpy(&tags
[tag_index
], &tag
, sizeof(tag
));
306 // italic, bold, underline, strike-through
307 #define MICRODVD_STYLES "ibus"
309 static char *microdvd_load_tags(struct microdvd_tag
*tags
, char *s
)
313 char tag_char
= *(s
+ 1);
314 struct microdvd_tag tag
= {0};
316 if (!tag_char
|| *(s
+ 2) != ':')
324 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
326 while (*s
&& *s
!= '}') {
327 int style_index
= indexof(MICRODVD_STYLES
, *s
);
329 if (style_index
>= 0)
330 tag
.data1
|= (1 << style_index
);
335 /* We must distinguish persistent and non-persistent styles
336 * to handle this kind of style tags: {y:ib}{Y:us} */
342 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
344 tag
.data1
= strtol(s
, &s
, 16) & 0x00ffffff;
352 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
355 int len
= indexof(s
, '}');
358 tag
.data_string
.start
= s
;
359 tag
.data_string
.len
= len
;
367 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
369 tag
.data1
= strtol(s
, &s
, 10);
378 //TODO: not yet handled, just parsed.
379 int len
= indexof(s
, '}');
382 tag
.data_string
.start
= s
;
383 tag
.data_string
.len
= len
;
391 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
392 tag
.data1
= (*s
++ == '1');
400 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
401 tag
.data1
= strtol(s
, &s
, 10);
405 tag
.data2
= strtol(s
, &s
, 10);
411 default: /* Unknown tag, we consider it to be text */
418 microdvd_set_tag(tags
, tag
);
424 static void microdvd_open_tags(struct line
*new_line
, struct microdvd_tag
*tags
)
426 for (int i
= 0; i
< sizeof(MICRODVD_TAGS
) - 1; i
++) {
427 if (tags
[i
].persistent
== MICRODVD_PERSISTENT_OPENED
)
429 switch (tags
[i
].key
) {
432 for (int sidx
= 0; sidx
< sizeof(MICRODVD_STYLES
) - 1; sidx
++)
433 if (tags
[i
].data1
& (1 << sidx
))
434 append_text(new_line
, "{\\%c1}", MICRODVD_STYLES
[sidx
]);
438 append_text(new_line
, "{\\c&H%06X&}", tags
[i
].data1
);
442 append_text(new_line
, "{\\fn%.*s}", BSTR_P(tags
[i
].data_string
));
446 append_text(new_line
, "{\\fs%d}", tags
[i
].data1
);
450 if (tags
[i
].data1
== 0)
451 append_text(new_line
, "{\\an8}");
455 append_text(new_line
, "{\\pos(%d,%d)}",
456 tags
[i
].data1
, tags
[i
].data2
);
459 if (tags
[i
].persistent
== MICRODVD_PERSISTENT_ON
)
460 tags
[i
].persistent
= MICRODVD_PERSISTENT_OPENED
;
464 static void microdvd_close_no_persistent_tags(struct line
*new_line
,
465 struct microdvd_tag
*tags
)
469 for (i
= sizeof(MICRODVD_TAGS
) - 2; i
; i
--) {
470 if (tags
[i
].persistent
!= MICRODVD_PERSISTENT_OFF
)
472 switch (tags
[i
].key
) {
475 for (int sidx
= sizeof(MICRODVD_STYLES
) - 2; sidx
>= 0; sidx
--)
476 if (tags
[i
].data1
& (1 << sidx
))
477 append_text(new_line
, "{\\%c0}", MICRODVD_STYLES
[sidx
]);
481 append_text(new_line
, "{\\c}");
485 append_text(new_line
, "{\\fn}");
489 append_text(new_line
, "{\\fs}");
496 void subassconvert_microdvd(const char *orig
, char *dest
, int dest_buffer_size
)
498 /* line is not const to avoid warnings with strtol, etc.
499 * orig content won't be changed */
500 char *line
= (char *)orig
;
501 struct line new_line
= {
503 .bufsize
= dest_buffer_size
,
505 struct microdvd_tag tags
[sizeof(MICRODVD_TAGS
) - 1] = {{0}};
508 line
= microdvd_load_tags(tags
, line
);
509 microdvd_open_tags(&new_line
, tags
);
511 while (*line
&& *line
!= '|')
512 new_line
.buf
[new_line
.len
++] = *line
++;
515 microdvd_close_no_persistent_tags(&new_line
, tags
);
516 append_text(&new_line
, "\\N");
520 new_line
.buf
[new_line
.len
] = 0;