avformat/mpeg: demux ivtv captions
[ffmpeg.git] / libavcodec / microdvddec.c
blob786a3845fd0f56e31165cf51fa424f0c3743f290
1 /*
2 * Copyright (c) 2012 Clément Bœsch
4 * This file is part of FFmpeg.
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.
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.
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
21 /**
22 * @file
23 * MicroDVD subtitle decoder
25 * Based on the specifications found here:
26 * https://trac.videolan.org/vlc/ticket/1825#comment:6
29 #include "libavutil/avstring.h"
30 #include "libavutil/parseutils.h"
31 #include "libavutil/bprint.h"
32 #include "avcodec.h"
33 #include "ass.h"
34 #include "codec_internal.h"
36 static int indexof(const char *s, int c)
38 char *f = strchr(s, c);
39 return f ? (f - s) : -1;
42 struct microdvd_tag {
43 char key;
44 int persistent;
45 uint32_t data1;
46 uint32_t data2;
47 char *data_string;
48 int data_string_len;
51 #define MICRODVD_PERSISTENT_OFF 0
52 #define MICRODVD_PERSISTENT_ON 1
53 #define MICRODVD_PERSISTENT_OPENED 2
55 // Color, Font, Size, cHarset, stYle, Position, cOordinate
56 #define MICRODVD_TAGS "cfshyYpo"
58 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
60 int tag_index = indexof(MICRODVD_TAGS, tag.key);
62 if (tag_index < 0)
63 return;
64 memcpy(&tags[tag_index], &tag, sizeof(tag));
67 // italic, bold, underline, strike-through
68 #define MICRODVD_STYLES "ibus"
70 /* some samples have lines that start with a / indicating non persistent italic
71 * marker */
72 static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
74 if (*s == '/') {
75 struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')];
76 tag.key = 'y';
77 tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */;
78 microdvd_set_tag(tags, tag);
79 s++;
81 return s;
84 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
86 s = check_for_italic_slash_marker(tags, s);
88 while (*s == '{') {
89 char *start = s;
90 char tag_char = *(s + 1);
91 struct microdvd_tag tag = {0};
93 if (!tag_char || *(s + 2) != ':')
94 break;
95 s += 3;
97 switch (tag_char) {
99 /* Style */
100 case 'Y':
101 tag.persistent = MICRODVD_PERSISTENT_ON;
102 case 'y':
103 while (*s && *s != '}' && s - start < 256) {
104 int style_index = indexof(MICRODVD_STYLES, *s);
106 if (style_index >= 0)
107 tag.data1 |= (1 << style_index);
108 s++;
110 if (*s != '}')
111 break;
112 /* We must distinguish persistent and non-persistent styles
113 * to handle this kind of style tags: {y:ib}{Y:us} */
114 tag.key = tag_char;
115 break;
117 /* Color */
118 case 'C':
119 tag.persistent = MICRODVD_PERSISTENT_ON;
120 case 'c':
121 while (*s == '$' || *s == '#')
122 s++;
123 tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
124 if (*s != '}')
125 break;
126 tag.key = 'c';
127 break;
129 /* Font name */
130 case 'F':
131 tag.persistent = MICRODVD_PERSISTENT_ON;
132 case 'f': {
133 int len = indexof(s, '}');
134 if (len < 0)
135 break;
136 tag.data_string = s;
137 tag.data_string_len = len;
138 s += len;
139 tag.key = 'f';
140 break;
143 /* Font size */
144 case 'S':
145 tag.persistent = MICRODVD_PERSISTENT_ON;
146 case 's':
147 tag.data1 = strtol(s, &s, 10);
148 if (*s != '}')
149 break;
150 tag.key = 's';
151 break;
153 /* Charset */
154 case 'H': {
155 //TODO: not yet handled, just parsed.
156 int len = indexof(s, '}');
157 if (len < 0)
158 break;
159 tag.data_string = s;
160 tag.data_string_len = len;
161 s += len;
162 tag.key = 'h';
163 break;
166 /* Position */
167 case 'P':
168 if (!*s)
169 break;
170 tag.persistent = MICRODVD_PERSISTENT_ON;
171 tag.data1 = (*s++ == '1');
172 if (*s != '}')
173 break;
174 tag.key = 'p';
175 break;
177 /* Coordinates */
178 case 'o':
179 tag.persistent = MICRODVD_PERSISTENT_ON;
180 tag.data1 = strtol(s, &s, 10);
181 if (*s != ',')
182 break;
183 s++;
184 tag.data2 = strtol(s, &s, 10);
185 if (*s != '}')
186 break;
187 tag.key = 'o';
188 break;
190 default: /* Unknown tag, we consider it's text */
191 break;
194 if (tag.key == 0)
195 return start;
197 microdvd_set_tag(tags, tag);
198 s++;
200 return check_for_italic_slash_marker(tags, s);
203 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
205 int i, sidx;
206 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
207 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
208 continue;
209 switch (tags[i].key) {
210 case 'Y':
211 case 'y':
212 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
213 if (tags[i].data1 & (1 << sidx))
214 av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
215 break;
217 case 'c':
218 av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
219 break;
221 case 'f':
222 av_bprintf(new_line, "{\\fn%.*s}",
223 tags[i].data_string_len, tags[i].data_string);
224 break;
226 case 's':
227 av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
228 break;
230 case 'p':
231 if (tags[i].data1 == 0)
232 av_bprintf(new_line, "{\\an8}");
233 break;
235 case 'o':
236 av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
237 tags[i].data1, tags[i].data2);
238 break;
240 if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
241 tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
245 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
246 struct microdvd_tag *tags)
248 int i, sidx;
250 for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
251 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
252 continue;
253 switch (tags[i].key) {
255 case 'y':
256 for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
257 if (tags[i].data1 & (1 << sidx))
258 av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
259 break;
261 case 'c':
262 av_bprintf(new_line, "{\\c}");
263 break;
265 case 'f':
266 av_bprintf(new_line, "{\\fn}");
267 break;
269 case 's':
270 av_bprintf(new_line, "{\\fs}");
271 break;
273 tags[i].key = 0;
277 static int microdvd_decode_frame(AVCodecContext *avctx, AVSubtitle *sub,
278 int *got_sub_ptr, const AVPacket *avpkt)
280 AVBPrint new_line;
281 char *line = avpkt->data;
282 char *end = avpkt->data + avpkt->size;
283 FFASSDecoderContext *s = avctx->priv_data;
284 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
286 if (avpkt->size <= 0)
287 return avpkt->size;
289 av_bprint_init(&new_line, 0, 2048);
291 // subtitle content
292 while (line < end && *line) {
294 // parse MicroDVD tags, and open them in ASS
295 line = microdvd_load_tags(tags, line);
296 microdvd_open_tags(&new_line, tags);
298 // simple copy until EOL or forced carriage return
299 while (line < end && *line && *line != '|') {
300 av_bprint_chars(&new_line, *line, 1);
301 line++;
304 // line split
305 if (line < end && *line == '|') {
306 microdvd_close_no_persistent_tags(&new_line, tags);
307 av_bprintf(&new_line, "\\N");
308 line++;
311 if (new_line.len) {
312 int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
313 av_bprint_finalize(&new_line, NULL);
314 if (ret < 0)
315 return ret;
318 *got_sub_ptr = sub->num_rects > 0;
319 return avpkt->size;
322 static int microdvd_init(AVCodecContext *avctx)
324 int i, sidx;
325 AVBPrint font_buf;
326 int font_size = ASS_DEFAULT_FONT_SIZE;
327 int color = ASS_DEFAULT_COLOR;
328 int bold = ASS_DEFAULT_BOLD;
329 int italic = ASS_DEFAULT_ITALIC;
330 int underline = ASS_DEFAULT_UNDERLINE;
331 int alignment = ASS_DEFAULT_ALIGNMENT;
332 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
334 av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
335 av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
337 if (avctx->extradata) {
338 microdvd_load_tags(tags, avctx->extradata);
339 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
340 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;
351 break;
353 case 'c': color = tags[i].data1; break;
354 case 's': font_size = tags[i].data1; break;
355 case 'p': alignment = 8; break;
357 case 'f':
358 av_bprint_clear(&font_buf);
359 av_bprintf(&font_buf, "%.*s",
360 tags[i].data_string_len, tags[i].data_string);
361 break;
365 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);
371 const FFCodec ff_microdvd_decoder = {
372 .p.name = "microdvd",
373 CODEC_LONG_NAME("MicroDVD subtitle"),
374 .p.type = AVMEDIA_TYPE_SUBTITLE,
375 .p.id = AV_CODEC_ID_MICRODVD,
376 .init = microdvd_init,
377 FF_CODEC_DECODE_SUB_CB(microdvd_decode_frame),
378 .flush = ff_ass_decoder_flush,
379 .priv_data_size = sizeof(FFASSDecoderContext),