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 {"{", "\\{"}, {"}", "\\}"},
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 void subassconvert_subrip(const char *orig
, char *dest
, int dest_buffer_size
)
111 /* line is not const to avoid warnings with strtol, etc.
112 * orig content won't be changed */
113 char *line
= (char *)orig
;
114 struct line new_line
= {
116 .bufsize
= dest_buffer_size
,
118 struct font_tag font_stack
[SUBRIP_MAX_STACKED_FONT_TAGS
+ 1];
119 font_stack
[0] = (struct font_tag
){0}; // type with all defaults
122 while (*line
&& new_line
.len
< new_line
.bufsize
- 1) {
123 char *orig_line
= line
;
125 for (int i
= 0; i
< FF_ARRAY_ELEMS(subrip_basic_tags
); i
++) {
126 const struct tag_conv
*tag
= &subrip_basic_tags
[i
];
127 int from_len
= strlen(tag
->from
);
128 if (strncmp(line
, tag
->from
, from_len
) == 0) {
129 append_text(&new_line
, "%s", tag
->to
);
134 if (strncmp(line
, "</font>", 7) == 0) {
135 /* Closing font tag */
139 struct font_tag
*tag
= &font_stack
[sp
];
140 struct font_tag
*last_tag
= &tag
[-1];
144 if (!last_tag
->has_size
)
145 append_text(&new_line
, "{\\fs}");
146 else if (last_tag
->size
!= tag
->size
)
147 append_text(&new_line
, "{\\fs%d}", last_tag
->size
);
150 if (tag
->has_color
) {
151 if (!last_tag
->has_color
)
152 append_text(&new_line
, "{\\c}");
153 else if (last_tag
->color
!= tag
->color
)
154 append_text(&new_line
, "{\\c&H%06X&}", last_tag
->color
);
158 if (!last_tag
->has_face
)
159 append_text(&new_line
, "{\\fn}");
160 else if (bstrcmp(last_tag
->face
, tag
->face
) != 0)
161 append_text(&new_line
, "{\\fn%.*s}",
162 BSTR_P(last_tag
->face
));
165 } else if (strncmp(line
, "<font ", 6) == 0
166 && sp
+ 1 < FF_ARRAY_ELEMS(font_stack
)) {
167 /* Opening font tag */
168 char *potential_font_tag_start
= line
;
169 int len_backup
= new_line
.len
;
170 struct font_tag
*tag
= &font_stack
[sp
+ 1];
171 bool has_valid_attr
= false;
173 *tag
= tag
[-1]; // keep values from previous tag
176 while (*line
&& *line
!= '>') {
177 if (strncmp(line
, "size=\"", 6) == 0) {
179 tag
->size
= strtol(line
, &line
, 10);
182 append_text(&new_line
, "{\\fs%d}", tag
->size
);
183 tag
->has_size
= true;
184 has_valid_attr
= true;
185 } else if (strncmp(line
, "color=\"", 7) == 0) {
190 tag
->color
= strtol(line
, &line
, 16) & 0x00ffffff;
193 tag
->color
= ((tag
->color
& 0xff) << 16)
194 | (tag
->color
& 0xff00)
195 | ((tag
->color
& 0xff0000) >> 16);
197 // Standard web colors
198 int len
= indexof(line
, '"');
201 for (int i
= 0; i
< FF_ARRAY_ELEMS(subrip_web_colors
); i
++) {
202 char *color
= subrip_web_colors
[i
].s
;
203 if (strlen(color
) == len
204 && strncasecmp(line
, color
, len
) == 0) {
205 tag
->color
= subrip_web_colors
[i
].v
;
210 /* We didn't find any matching color */
211 mp_tmsg(MSGT_SUBREADER
, MSGL_WARN
,
212 "SubRip: unknown font color in subtitle: %s\n", orig
);
213 append_text(&new_line
, "{\\c}");
220 append_text(&new_line
, "{\\c&H%06X&}", tag
->color
);
221 tag
->has_color
= true;
222 has_valid_attr
= true;
223 } else if (strncmp(line
, "face=\"", 6) == 0) {
224 /* Font face attribute */
226 int len
= indexof(line
, '"');
229 tag
->face
.start
= line
;
232 append_text(&new_line
, "{\\fn%.*s}", BSTR_P(tag
->face
));
233 tag
->has_face
= true;
234 has_valid_attr
= true;
239 if (!has_valid_attr
|| *line
!= '>') { /* Not valid font tag */
240 line
= potential_font_tag_start
;
241 new_line
.len
= len_backup
;
248 /* Tag conversion code didn't match */
249 if (line
== orig_line
)
250 new_line
.buf
[new_line
.len
++] = *line
++;
252 new_line
.buf
[new_line
.len
] = 0;
259 * Based on the specifications found here:
260 * https://trac.videolan.org/vlc/ticket/1825#comment:6
263 struct microdvd_tag
{
268 struct bstr data_string
;
271 #define MICRODVD_PERSISTENT_OFF 0
272 #define MICRODVD_PERSISTENT_ON 1
273 #define MICRODVD_PERSISTENT_OPENED 2
275 // Color, Font, Size, cHarset, stYle, Position, cOordinate
276 #define MICRODVD_TAGS "cfshyYpo"
278 static void microdvd_set_tag(struct microdvd_tag
*tags
, struct microdvd_tag tag
)
280 int tag_index
= indexof(MICRODVD_TAGS
, tag
.key
);
284 memcpy(&tags
[tag_index
], &tag
, sizeof(tag
));
287 // italic, bold, underline, strike-through
288 #define MICRODVD_STYLES "ibus"
290 static char *microdvd_load_tags(struct microdvd_tag
*tags
, char *s
)
294 char tag_char
= *(s
+ 1);
295 struct microdvd_tag tag
= {0};
297 if (!tag_char
|| *(s
+ 2) != ':')
305 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
307 while (*s
&& *s
!= '}') {
308 int style_index
= indexof(MICRODVD_STYLES
, *s
);
310 if (style_index
>= 0)
311 tag
.data1
|= (1 << style_index
);
316 /* We must distinguish persistent and non-persistent styles
317 * to handle this kind of style tags: {y:ib}{Y:us} */
323 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
325 tag
.data1
= strtol(s
, &s
, 16) & 0x00ffffff;
333 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
336 int len
= indexof(s
, '}');
339 tag
.data_string
.start
= s
;
340 tag
.data_string
.len
= len
;
348 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
350 tag
.data1
= strtol(s
, &s
, 10);
359 //TODO: not yet handled, just parsed.
360 int len
= indexof(s
, '}');
363 tag
.data_string
.start
= s
;
364 tag
.data_string
.len
= len
;
372 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
373 tag
.data1
= (*s
++ == '1');
381 tag
.persistent
= MICRODVD_PERSISTENT_ON
;
382 tag
.data1
= strtol(s
, &s
, 10);
386 tag
.data2
= strtol(s
, &s
, 10);
392 default: /* Unknown tag, we consider it to be text */
399 microdvd_set_tag(tags
, tag
);
405 static void microdvd_open_tags(struct line
*new_line
, struct microdvd_tag
*tags
)
407 for (int i
= 0; i
< sizeof(MICRODVD_TAGS
) - 1; i
++) {
408 if (tags
[i
].persistent
== MICRODVD_PERSISTENT_OPENED
)
410 switch (tags
[i
].key
) {
413 for (int sidx
= 0; sidx
< sizeof(MICRODVD_STYLES
) - 1; sidx
++)
414 if (tags
[i
].data1
& (1 << sidx
))
415 append_text(new_line
, "{\\%c1}", MICRODVD_STYLES
[sidx
]);
419 append_text(new_line
, "{\\c&H%06X&}", tags
[i
].data1
);
423 append_text(new_line
, "{\\fn%.*s}", BSTR_P(tags
[i
].data_string
));
427 append_text(new_line
, "{\\fs%d}", tags
[i
].data1
);
431 if (tags
[i
].data1
== 0)
432 append_text(new_line
, "{\\an8}");
436 append_text(new_line
, "{\\pos(%d,%d)}",
437 tags
[i
].data1
, tags
[i
].data2
);
440 if (tags
[i
].persistent
== MICRODVD_PERSISTENT_ON
)
441 tags
[i
].persistent
= MICRODVD_PERSISTENT_OPENED
;
445 static void microdvd_close_no_persistent_tags(struct line
*new_line
,
446 struct microdvd_tag
*tags
)
450 for (i
= sizeof(MICRODVD_TAGS
) - 2; i
; i
--) {
451 if (tags
[i
].persistent
!= MICRODVD_PERSISTENT_OFF
)
453 switch (tags
[i
].key
) {
456 for (int sidx
= sizeof(MICRODVD_STYLES
) - 2; sidx
>= 0; sidx
--)
457 if (tags
[i
].data1
& (1 << sidx
))
458 append_text(new_line
, "{\\%c0}", MICRODVD_STYLES
[sidx
]);
462 append_text(new_line
, "{\\c}");
466 append_text(new_line
, "{\\fn}");
470 append_text(new_line
, "{\\fs}");
477 void subassconvert_microdvd(const char *orig
, char *dest
, int dest_buffer_size
)
479 /* line is not const to avoid warnings with strtol, etc.
480 * orig content won't be changed */
481 char *line
= (char *)orig
;
482 struct line new_line
= {
484 .bufsize
= dest_buffer_size
,
486 struct microdvd_tag tags
[sizeof(MICRODVD_TAGS
) - 1] = {{0}};
489 line
= microdvd_load_tags(tags
, line
);
490 microdvd_open_tags(&new_line
, tags
);
492 while (*line
&& *line
!= '|')
493 new_line
.buf
[new_line
.len
++] = *line
++;
496 microdvd_close_no_persistent_tags(&new_line
, tags
);
497 append_text(&new_line
, "\\N");
501 new_line
.buf
[new_line
.len
] = 0;