Merge remote-tracking branch 'qatar/master'
[ffmpeg.git] / libavcodec / microdvddec.c
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 static int indexof(const char *s, int c)
36 {
37     char *f = strchr(s, c);
38     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 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
58 {
59     int tag_index = indexof(MICRODVD_TAGS, tag.key);
60
61     if (tag_index < 0)
62         return;
63     memcpy(&tags[tag_index], &tag, sizeof(tag));
64 }
65
66 // italic, bold, underline, strike-through
67 #define MICRODVD_STYLES "ibus"
68
69 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
70 {
71     while (*s == '{') {
72         char *start = s;
73         char tag_char = *(s + 1);
74         struct microdvd_tag tag = {0};
75
76         if (!tag_char || *(s + 2) != ':')
77             break;
78         s += 3;
79
80         switch (tag_char) {
81
82         /* Style */
83         case 'Y':
84             tag.persistent = MICRODVD_PERSISTENT_ON;
85         case 'y':
86             while (*s && *s != '}') {
87                 int style_index = indexof(MICRODVD_STYLES, *s);
88
89                 if (style_index >= 0)
90                     tag.data1 |= (1 << style_index);
91                 s++;
92             }
93             if (*s != '}')
94                 break;
95             /* We must distinguish persistent and non-persistent styles
96              * to handle this kind of style tags: {y:ib}{Y:us} */
97             tag.key = tag_char;
98             break;
99
100         /* Color */
101         case 'C':
102             tag.persistent = MICRODVD_PERSISTENT_ON;
103         case 'c':
104             if (*s == '$')
105                 s++;
106             tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
107             if (*s != '}')
108                 break;
109             tag.key = 'c';
110             break;
111
112         /* Font name */
113         case 'F':
114             tag.persistent = MICRODVD_PERSISTENT_ON;
115         case 'f': {
116             int len = indexof(s, '}');
117             if (len < 0)
118                 break;
119             tag.data_string = s;
120             tag.data_string_len = len;
121             s += len;
122             tag.key = 'f';
123             break;
124         }
125
126         /* Font size */
127         case 'S':
128             tag.persistent = MICRODVD_PERSISTENT_ON;
129         case 's':
130             tag.data1 = strtol(s, &s, 10);
131             if (*s != '}')
132                 break;
133             tag.key = 's';
134             break;
135
136         /* Charset */
137         case 'H': {
138             //TODO: not yet handled, just parsed.
139             int len = indexof(s, '}');
140             if (len < 0)
141                 break;
142             tag.data_string = s;
143             tag.data_string_len = len;
144             s += len;
145             tag.key = 'h';
146             break;
147         }
148
149         /* Position */
150         case 'P':
151             tag.persistent = MICRODVD_PERSISTENT_ON;
152             tag.data1 = (*s++ == '1');
153             if (*s != '}')
154                 break;
155             tag.key = 'p';
156             break;
157
158         /* Coordinates */
159         case 'o':
160             tag.persistent = MICRODVD_PERSISTENT_ON;
161             tag.data1 = strtol(s, &s, 10);
162             if (*s != ',')
163                 break;
164             s++;
165             tag.data2 = strtol(s, &s, 10);
166             if (*s != '}')
167                 break;
168             tag.key = 'o';
169             break;
170
171         default:    /* Unknown tag, we consider it's text */
172             break;
173         }
174
175         if (tag.key == 0)
176             return start;
177
178         microdvd_set_tag(tags, tag);
179         s++;
180     }
181     return s;
182 }
183
184 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
185 {
186     int i, sidx;
187     for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
188         if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
189             continue;
190         switch (tags[i].key) {
191         case 'Y':
192         case 'y':
193             for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
194                 if (tags[i].data1 & (1 << sidx))
195                     av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
196             break;
197
198         case 'c':
199             av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
200             break;
201
202         case 'f':
203             av_bprintf(new_line, "{\\fn%.*s}",
204                        tags[i].data_string_len, tags[i].data_string);
205             break;
206
207         case 's':
208             av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
209             break;
210
211         case 'p':
212             if (tags[i].data1 == 0)
213                 av_bprintf(new_line, "{\\an8}");
214             break;
215
216         case 'o':
217             av_bprintf(new_line, "{\\pos(%d,%d)}",
218                        tags[i].data1, tags[i].data2);
219             break;
220         }
221         if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
222             tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
223     }
224 }
225
226 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
227                                               struct microdvd_tag *tags)
228 {
229     int i, sidx;
230
231     for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
232         if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
233             continue;
234         switch (tags[i].key) {
235
236         case 'y':
237             for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
238                 if (tags[i].data1 & (1 << sidx))
239                     av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
240             break;
241
242         case 'c':
243             av_bprintf(new_line, "{\\c}");
244             break;
245
246         case 'f':
247             av_bprintf(new_line, "{\\fn}");
248             break;
249
250         case 's':
251             av_bprintf(new_line, "{\\fs}");
252             break;
253         }
254         tags[i].key = 0;
255     }
256 }
257
258 static int microdvd_decode_frame(AVCodecContext *avctx,
259                                  void *data, int *got_sub_ptr, AVPacket *avpkt)
260 {
261     AVSubtitle *sub = data;
262     AVBPrint new_line;
263     char *decoded_sub;
264     char *line = avpkt->data;
265     char *end = avpkt->data + avpkt->size;
266     struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
267
268     if (avpkt->size <= 0)
269         return avpkt->size;
270
271     av_bprint_init(&new_line, 0, 2048);
272
273     // skip {frame_start}{frame_end}
274     line = strchr(line, '}'); if (!line) goto end; line++;
275     line = strchr(line, '}'); if (!line) goto end; line++;
276
277     // subtitle content
278     while (line < end && *line) {
279
280         // parse MicroDVD tags, and open them in ASS
281         line = microdvd_load_tags(tags, line);
282         microdvd_open_tags(&new_line, tags);
283
284         // simple copy until EOL or forced carriage return
285         while (line < end && *line && *line != '|') {
286             av_bprint_chars(&new_line, *line, 1);
287             line++;
288         }
289
290         // line split
291         if (line < end && *line == '|') {
292             microdvd_close_no_persistent_tags(&new_line, tags);
293             av_bprintf(&new_line, "\\N");
294             line++;
295         }
296     }
297
298 end:
299     av_bprint_finalize(&new_line, &decoded_sub);
300     if (*decoded_sub) {
301         int64_t start    = avpkt->pts;
302         int64_t duration = avpkt->duration;
303         int ts_start     = av_rescale_q(start,    avctx->time_base, (AVRational){1,100});
304         int ts_duration  = duration != -1 ?
305                            av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
306         ff_ass_add_rect(sub, decoded_sub, ts_start, ts_duration, 0);
307     }
308     av_free(decoded_sub);
309
310     *got_sub_ptr = sub->num_rects > 0;
311     return avpkt->size;
312 }
313
314 static int microdvd_init(AVCodecContext *avctx)
315 {
316     int i, sidx;
317     AVBPrint font_buf;
318     int font_size    = ASS_DEFAULT_FONT_SIZE;
319     int color        = ASS_DEFAULT_COLOR;
320     int bold         = ASS_DEFAULT_BOLD;
321     int italic       = ASS_DEFAULT_ITALIC;
322     int underline    = ASS_DEFAULT_UNDERLINE;
323     int alignment    = ASS_DEFAULT_ALIGNMENT;
324     struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
325
326     av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
327     av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
328
329     if (avctx->extradata) {
330         microdvd_load_tags(tags, avctx->extradata);
331         for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
332             switch (av_tolower(tags[i].key)) {
333             case 'y':
334                 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
335                     if (tags[i].data1 & (1 << sidx)) {
336                         switch (MICRODVD_STYLES[sidx]) {
337                         case 'i': italic    = 1; break;
338                         case 'b': bold      = 1; break;
339                         case 'u': underline = 1; break;
340                         }
341                     }
342                 }
343                 break;
344
345             case 'c': color     = tags[i].data1; break;
346             case 's': font_size = tags[i].data1; break;
347             case 'p': alignment =             8; break;
348
349             case 'f':
350                 av_bprint_clear(&font_buf);
351                 av_bprintf(&font_buf, "%.*s",
352                            tags[i].data_string_len, tags[i].data_string);
353                 break;
354             }
355         }
356     }
357     return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
358                                   ASS_DEFAULT_BACK_COLOR, bold, italic,
359                                   underline, alignment);
360 }
361
362 AVCodec ff_microdvd_decoder = {
363     .name         = "microdvd",
364     .long_name    = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
365     .type         = AVMEDIA_TYPE_SUBTITLE,
366     .id           = AV_CODEC_ID_MICRODVD,
367     .init         = microdvd_init,
368     .decode       = microdvd_decode_frame,
369 };