GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* |
||
2 |
* Copyright (c) 2012 Clément Bœsch |
||
3 |
* |
||
4 |
* This file is part of FFmpeg. |
||
5 |
* |
||
6 |
* FFmpeg is free software; you can redistribute it and/or |
||
7 |
* modify it under the terms of the GNU Lesser General Public |
||
8 |
* License as published by the Free Software Foundation; either |
||
9 |
* version 2.1 of the License, or (at your option) any later version. |
||
10 |
* |
||
11 |
* FFmpeg 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 GNU |
||
14 |
* Lesser General Public License for more details. |
||
15 |
* |
||
16 |
* You should have received a copy of the GNU Lesser General Public |
||
17 |
* License along with FFmpeg; if not, write to the Free Software |
||
18 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
19 |
*/ |
||
20 |
|||
21 |
/** |
||
22 |
* @file |
||
23 |
* MicroDVD subtitle decoder |
||
24 |
* |
||
25 |
* Based on the specifications found here: |
||
26 |
* https://trac.videolan.org/vlc/ticket/1825#comment:6 |
||
27 |
*/ |
||
28 |
|||
29 |
#include "libavutil/avstring.h" |
||
30 |
#include "libavutil/parseutils.h" |
||
31 |
#include "libavutil/bprint.h" |
||
32 |
#include "avcodec.h" |
||
33 |
#include "ass.h" |
||
34 |
|||
35 |
21 |
static int indexof(const char *s, int c) |
|
36 |
{ |
||
37 |
21 |
char *f = strchr(s, c); |
|
38 |
✓✗ | 21 |
return f ? (f - s) : -1; |
39 |
} |
||
40 |
|||
41 |
struct microdvd_tag { |
||
42 |
char key; |
||
43 |
int persistent; |
||
44 |
uint32_t data1; |
||
45 |
uint32_t data2; |
||
46 |
char *data_string; |
||
47 |
int data_string_len; |
||
48 |
}; |
||
49 |
|||
50 |
#define MICRODVD_PERSISTENT_OFF 0 |
||
51 |
#define MICRODVD_PERSISTENT_ON 1 |
||
52 |
#define MICRODVD_PERSISTENT_OPENED 2 |
||
53 |
|||
54 |
// Color, Font, Size, cHarset, stYle, Position, cOordinate |
||
55 |
#define MICRODVD_TAGS "cfshyYpo" |
||
56 |
|||
57 |
14 |
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag) |
|
58 |
{ |
||
59 |
14 |
int tag_index = indexof(MICRODVD_TAGS, tag.key); |
|
60 |
|||
61 |
✗✓ | 14 |
if (tag_index < 0) |
62 |
return; |
||
63 |
14 |
memcpy(&tags[tag_index], &tag, sizeof(tag)); |
|
64 |
} |
||
65 |
|||
66 |
// italic, bold, underline, strike-through |
||
67 |
#define MICRODVD_STYLES "ibus" |
||
68 |
|||
69 |
/* some samples have lines that start with a / indicating non persistent italic |
||
70 |
* marker */ |
||
71 |
190 |
static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s) |
|
72 |
{ |
||
73 |
✗✓ | 190 |
if (*s == '/') { |
74 |
struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')]; |
||
75 |
tag.key = 'y'; |
||
76 |
tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */; |
||
77 |
microdvd_set_tag(tags, tag); |
||
78 |
s++; |
||
79 |
} |
||
80 |
190 |
return s; |
|
81 |
} |
||
82 |
|||
83 |
95 |
static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) |
|
84 |
{ |
||
85 |
95 |
s = check_for_italic_slash_marker(tags, s); |
|
86 |
|||
87 |
✓✓ | 109 |
while (*s == '{') { |
88 |
14 |
char *start = s; |
|
89 |
14 |
char tag_char = *(s + 1); |
|
90 |
14 |
struct microdvd_tag tag = {0}; |
|
91 |
|||
92 |
✓✗✓✗ |
14 |
if (!tag_char || *(s + 2) != ':') |
93 |
break; |
||
94 |
14 |
s += 3; |
|
95 |
|||
96 |
✓✓✗✓ ✓✗✓✗ ✗✗✓✗ |
14 |
switch (tag_char) { |
97 |
|||
98 |
/* Style */ |
||
99 |
1 |
case 'Y': |
|
100 |
1 |
tag.persistent = MICRODVD_PERSISTENT_ON; |
|
101 |
2 |
case 'y': |
|
102 |
✓✗✓✓ ✓✗ |
6 |
while (*s && *s != '}' && s - start < 256) { |
103 |
4 |
int style_index = indexof(MICRODVD_STYLES, *s); |
|
104 |
|||
105 |
✓✗ | 4 |
if (style_index >= 0) |
106 |
4 |
tag.data1 |= (1 << style_index); |
|
107 |
4 |
s++; |
|
108 |
} |
||
109 |
✗✓ | 2 |
if (*s != '}') |
110 |
break; |
||
111 |
/* We must distinguish persistent and non-persistent styles |
||
112 |
* to handle this kind of style tags: {y:ib}{Y:us} */ |
||
113 |
2 |
tag.key = tag_char; |
|
114 |
2 |
break; |
|
115 |
|||
116 |
/* Color */ |
||
117 |
case 'C': |
||
118 |
tag.persistent = MICRODVD_PERSISTENT_ON; |
||
119 |
5 |
case 'c': |
|
120 |
✓✓✗✓ |
10 |
while (*s == '$' || *s == '#') |
121 |
5 |
s++; |
|
122 |
5 |
tag.data1 = strtol(s, &s, 16) & 0x00ffffff; |
|
123 |
✗✓ | 5 |
if (*s != '}') |
124 |
break; |
||
125 |
5 |
tag.key = 'c'; |
|
126 |
5 |
break; |
|
127 |
|||
128 |
/* Font name */ |
||
129 |
3 |
case 'F': |
|
130 |
3 |
tag.persistent = MICRODVD_PERSISTENT_ON; |
|
131 |
3 |
case 'f': { |
|
132 |
3 |
int len = indexof(s, '}'); |
|
133 |
✗✓ | 3 |
if (len < 0) |
134 |
break; |
||
135 |
3 |
tag.data_string = s; |
|
136 |
3 |
tag.data_string_len = len; |
|
137 |
3 |
s += len; |
|
138 |
3 |
tag.key = 'f'; |
|
139 |
3 |
break; |
|
140 |
} |
||
141 |
|||
142 |
/* Font size */ |
||
143 |
3 |
case 'S': |
|
144 |
3 |
tag.persistent = MICRODVD_PERSISTENT_ON; |
|
145 |
3 |
case 's': |
|
146 |
3 |
tag.data1 = strtol(s, &s, 10); |
|
147 |
✗✓ | 3 |
if (*s != '}') |
148 |
break; |
||
149 |
3 |
tag.key = 's'; |
|
150 |
3 |
break; |
|
151 |
|||
152 |
/* Charset */ |
||
153 |
case 'H': { |
||
154 |
//TODO: not yet handled, just parsed. |
||
155 |
int len = indexof(s, '}'); |
||
156 |
if (len < 0) |
||
157 |
break; |
||
158 |
tag.data_string = s; |
||
159 |
tag.data_string_len = len; |
||
160 |
s += len; |
||
161 |
tag.key = 'h'; |
||
162 |
break; |
||
163 |
} |
||
164 |
|||
165 |
/* Position */ |
||
166 |
case 'P': |
||
167 |
if (!*s) |
||
168 |
break; |
||
169 |
tag.persistent = MICRODVD_PERSISTENT_ON; |
||
170 |
tag.data1 = (*s++ == '1'); |
||
171 |
if (*s != '}') |
||
172 |
break; |
||
173 |
tag.key = 'p'; |
||
174 |
break; |
||
175 |
|||
176 |
/* Coordinates */ |
||
177 |
1 |
case 'o': |
|
178 |
1 |
tag.persistent = MICRODVD_PERSISTENT_ON; |
|
179 |
1 |
tag.data1 = strtol(s, &s, 10); |
|
180 |
✗✓ | 1 |
if (*s != ',') |
181 |
break; |
||
182 |
1 |
s++; |
|
183 |
1 |
tag.data2 = strtol(s, &s, 10); |
|
184 |
✗✓ | 1 |
if (*s != '}') |
185 |
break; |
||
186 |
1 |
tag.key = 'o'; |
|
187 |
1 |
break; |
|
188 |
|||
189 |
default: /* Unknown tag, we consider it's text */ |
||
190 |
break; |
||
191 |
} |
||
192 |
|||
193 |
✗✓ | 14 |
if (tag.key == 0) |
194 |
return start; |
||
195 |
|||
196 |
14 |
microdvd_set_tag(tags, tag); |
|
197 |
14 |
s++; |
|
198 |
} |
||
199 |
95 |
return check_for_italic_slash_marker(tags, s); |
|
200 |
} |
||
201 |
|||
202 |
92 |
static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags) |
|
203 |
{ |
||
204 |
int i, sidx; |
||
205 |
✓✓ | 828 |
for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { |
206 |
✓✓ | 736 |
if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) |
207 |
1 |
continue; |
|
208 |
✓✓✗✗ ✗✓✓ |
735 |
switch (tags[i].key) { |
209 |
2 |
case 'Y': |
|
210 |
case 'y': |
||
211 |
✓✓ | 10 |
for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) |
212 |
✓✓ | 8 |
if (tags[i].data1 & (1 << sidx)) |
213 |
4 |
av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]); |
|
214 |
2 |
break; |
|
215 |
|||
216 |
2 |
case 'c': |
|
217 |
2 |
av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1); |
|
218 |
2 |
break; |
|
219 |
|||
220 |
case 'f': |
||
221 |
av_bprintf(new_line, "{\\fn%.*s}", |
||
222 |
tags[i].data_string_len, tags[i].data_string); |
||
223 |
break; |
||
224 |
|||
225 |
case 's': |
||
226 |
av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1); |
||
227 |
break; |
||
228 |
|||
229 |
case 'p': |
||
230 |
if (tags[i].data1 == 0) |
||
231 |
av_bprintf(new_line, "{\\an8}"); |
||
232 |
break; |
||
233 |
|||
234 |
1 |
case 'o': |
|
235 |
1 |
av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}", |
|
236 |
1 |
tags[i].data1, tags[i].data2); |
|
237 |
1 |
break; |
|
238 |
} |
||
239 |
✓✓ | 735 |
if (tags[i].persistent == MICRODVD_PERSISTENT_ON) |
240 |
2 |
tags[i].persistent = MICRODVD_PERSISTENT_OPENED; |
|
241 |
} |
||
242 |
92 |
} |
|
243 |
|||
244 |
32 |
static void microdvd_close_no_persistent_tags(AVBPrint *new_line, |
|
245 |
struct microdvd_tag *tags) |
||
246 |
{ |
||
247 |
int i, sidx; |
||
248 |
|||
249 |
✓✓ | 288 |
for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) { |
250 |
✓✓ | 256 |
if (tags[i].persistent != MICRODVD_PERSISTENT_OFF) |
251 |
1 |
continue; |
|
252 |
✓✓✗✗ ✓ |
255 |
switch (tags[i].key) { |
253 |
|||
254 |
1 |
case 'y': |
|
255 |
✓✓ | 5 |
for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--) |
256 |
✓✓ | 4 |
if (tags[i].data1 & (1 << sidx)) |
257 |
2 |
av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]); |
|
258 |
1 |
break; |
|
259 |
|||
260 |
2 |
case 'c': |
|
261 |
2 |
av_bprintf(new_line, "{\\c}"); |
|
262 |
2 |
break; |
|
263 |
|||
264 |
case 'f': |
||
265 |
av_bprintf(new_line, "{\\fn}"); |
||
266 |
break; |
||
267 |
|||
268 |
case 's': |
||
269 |
av_bprintf(new_line, "{\\fs}"); |
||
270 |
break; |
||
271 |
} |
||
272 |
255 |
tags[i].key = 0; |
|
273 |
} |
||
274 |
32 |
} |
|
275 |
|||
276 |
60 |
static int microdvd_decode_frame(AVCodecContext *avctx, |
|
277 |
void *data, int *got_sub_ptr, AVPacket *avpkt) |
||
278 |
{ |
||
279 |
60 |
AVSubtitle *sub = data; |
|
280 |
AVBPrint new_line; |
||
281 |
60 |
char *line = avpkt->data; |
|
282 |
60 |
char *end = avpkt->data + avpkt->size; |
|
283 |
60 |
FFASSDecoderContext *s = avctx->priv_data; |
|
284 |
60 |
struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; |
|
285 |
|||
286 |
✗✓ | 60 |
if (avpkt->size <= 0) |
287 |
return avpkt->size; |
||
288 |
|||
289 |
60 |
av_bprint_init(&new_line, 0, 2048); |
|
290 |
|||
291 |
// subtitle content |
||
292 |
✓✓✓✗ |
152 |
while (line < end && *line) { |
293 |
|||
294 |
// parse MicroDVD tags, and open them in ASS |
||
295 |
92 |
line = microdvd_load_tags(tags, line); |
|
296 |
92 |
microdvd_open_tags(&new_line, tags); |
|
297 |
|||
298 |
// simple copy until EOL or forced carriage return |
||
299 |
✓✓✓✗ ✓✓ |
3074 |
while (line < end && *line && *line != '|') { |
300 |
2982 |
av_bprint_chars(&new_line, *line, 1); |
|
301 |
2982 |
line++; |
|
302 |
} |
||
303 |
|||
304 |
// line split |
||
305 |
✓✓✓✗ |
92 |
if (line < end && *line == '|') { |
306 |
32 |
microdvd_close_no_persistent_tags(&new_line, tags); |
|
307 |
32 |
av_bprintf(&new_line, "\\N"); |
|
308 |
32 |
line++; |
|
309 |
} |
||
310 |
} |
||
311 |
✓✗ | 60 |
if (new_line.len) { |
312 |
60 |
int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL); |
|
313 |
60 |
av_bprint_finalize(&new_line, NULL); |
|
314 |
✗✓ | 60 |
if (ret < 0) |
315 |
return ret; |
||
316 |
} |
||
317 |
|||
318 |
60 |
*got_sub_ptr = sub->num_rects > 0; |
|
319 |
60 |
return avpkt->size; |
|
320 |
} |
||
321 |
|||
322 |
5 |
static int microdvd_init(AVCodecContext *avctx) |
|
323 |
{ |
||
324 |
int i, sidx; |
||
325 |
AVBPrint font_buf; |
||
326 |
5 |
int font_size = ASS_DEFAULT_FONT_SIZE; |
|
327 |
5 |
int color = ASS_DEFAULT_COLOR; |
|
328 |
5 |
int bold = ASS_DEFAULT_BOLD; |
|
329 |
5 |
int italic = ASS_DEFAULT_ITALIC; |
|
330 |
5 |
int underline = ASS_DEFAULT_UNDERLINE; |
|
331 |
5 |
int alignment = ASS_DEFAULT_ALIGNMENT; |
|
332 |
5 |
struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; |
|
333 |
|||
334 |
5 |
av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC); |
|
335 |
5 |
av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT); |
|
336 |
|||
337 |
✓✓ | 5 |
if (avctx->extradata) { |
338 |
3 |
microdvd_load_tags(tags, avctx->extradata); |
|
339 |
✓✓ | 27 |
for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { |
340 |
✗✓✓✗ ✓✓ |
24 |
switch (av_tolower(tags[i].key)) { |
341 |
case 'y': |
||
342 |
for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) { |
||
343 |
if (tags[i].data1 & (1 << sidx)) { |
||
344 |
switch (MICRODVD_STYLES[sidx]) { |
||
345 |
case 'i': italic = 1; break; |
||
346 |
case 'b': bold = 1; break; |
||
347 |
case 'u': underline = 1; break; |
||
348 |
} |
||
349 |
} |
||
350 |
} |
||
351 |
break; |
||
352 |
|||
353 |
3 |
case 'c': color = tags[i].data1; break; |
|
354 |
3 |
case 's': font_size = tags[i].data1; break; |
|
355 |
case 'p': alignment = 8; break; |
||
356 |
|||
357 |
3 |
case 'f': |
|
358 |
3 |
av_bprint_clear(&font_buf); |
|
359 |
3 |
av_bprintf(&font_buf, "%.*s", |
|
360 |
tags[i].data_string_len, tags[i].data_string); |
||
361 |
3 |
break; |
|
362 |
} |
||
363 |
24 |
} |
|
364 |
} |
||
365 |
5 |
return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color, |
|
366 |
ASS_DEFAULT_BACK_COLOR, bold, italic, |
||
367 |
underline, ASS_DEFAULT_BORDERSTYLE, |
||
368 |
alignment); |
||
369 |
} |
||
370 |
|||
371 |
AVCodec ff_microdvd_decoder = { |
||
372 |
.name = "microdvd", |
||
373 |
.long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"), |
||
374 |
.type = AVMEDIA_TYPE_SUBTITLE, |
||
375 |
.id = AV_CODEC_ID_MICRODVD, |
||
376 |
.init = microdvd_init, |
||
377 |
.decode = microdvd_decode_frame, |
||
378 |
.flush = ff_ass_decoder_flush, |
||
379 |
.priv_data_size = sizeof(FFASSDecoderContext), |
||
380 |
}; |
Generated by: GCOVR (Version 4.2) |