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