]> git.sesse.net Git - ffmpeg/blob - libavcodec/microdvddec.c
Merge remote-tracking branch 'qatar/master'
[ffmpeg] / 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             tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
105             if (*s != '}')
106                 break;
107             tag.key = 'c';
108             break;
109
110         /* Font name */
111         case 'F':
112             tag.persistent = MICRODVD_PERSISTENT_ON;
113         case 'f': {
114             int len = indexof(s, '}');
115             if (len < 0)
116                 break;
117             tag.data_string = s;
118             tag.data_string_len = len;
119             s += len;
120             tag.key = 'f';
121             break;
122         }
123
124         /* Font size */
125         case 'S':
126             tag.persistent = MICRODVD_PERSISTENT_ON;
127         case 's':
128             tag.data1 = strtol(s, &s, 10);
129             if (*s != '}')
130                 break;
131             tag.key = 's';
132             break;
133
134         /* Charset */
135         case 'H': {
136             //TODO: not yet handled, just parsed.
137             int len = indexof(s, '}');
138             if (len < 0)
139                 break;
140             tag.data_string = s;
141             tag.data_string_len = len;
142             s += len;
143             tag.key = 'h';
144             break;
145         }
146
147         /* Position */
148         case 'P':
149             tag.persistent = MICRODVD_PERSISTENT_ON;
150             tag.data1 = (*s++ == '1');
151             if (*s != '}')
152                 break;
153             tag.key = 'p';
154             break;
155
156         /* Coordinates */
157         case 'o':
158             tag.persistent = MICRODVD_PERSISTENT_ON;
159             tag.data1 = strtol(s, &s, 10);
160             if (*s != ',')
161                 break;
162             s++;
163             tag.data2 = strtol(s, &s, 10);
164             if (*s != '}')
165                 break;
166             tag.key = 'o';
167             break;
168
169         default:    /* Unknown tag, we consider it's text */
170             break;
171         }
172
173         if (tag.key == 0)
174             return start;
175
176         microdvd_set_tag(tags, tag);
177         s++;
178     }
179     return s;
180 }
181
182 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
183 {
184     int i, sidx;
185     for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
186         if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
187             continue;
188         switch (tags[i].key) {
189         case 'Y':
190         case 'y':
191             for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
192                 if (tags[i].data1 & (1 << sidx))
193                     av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
194             break;
195
196         case 'c':
197             av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
198             break;
199
200         case 'f':
201             av_bprintf(new_line, "{\\fn%.*s}",
202                        tags[i].data_string_len, tags[i].data_string);
203             break;
204
205         case 's':
206             av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
207             break;
208
209         case 'p':
210             if (tags[i].data1 == 0)
211                 av_bprintf(new_line, "{\\an8}");
212             break;
213
214         case 'o':
215             av_bprintf(new_line, "{\\pos(%d,%d)}",
216                        tags[i].data1, tags[i].data2);
217             break;
218         }
219         if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
220             tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
221     }
222 }
223
224 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
225                                               struct microdvd_tag *tags)
226 {
227     int i, sidx;
228
229     for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
230         if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
231             continue;
232         switch (tags[i].key) {
233
234         case 'y':
235             for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
236                 if (tags[i].data1 & (1 << sidx))
237                     av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
238             break;
239
240         case 'c':
241             av_bprintf(new_line, "{\\c}");
242             break;
243
244         case 'f':
245             av_bprintf(new_line, "{\\fn}");
246             break;
247
248         case 's':
249             av_bprintf(new_line, "{\\fs}");
250             break;
251         }
252         tags[i].key = 0;
253     }
254 }
255
256 static int microdvd_decode_frame(AVCodecContext *avctx,
257                                  void *data, int *got_sub_ptr, AVPacket *avpkt)
258 {
259     AVSubtitle *sub = data;
260     AVBPrint new_line;
261     char *decoded_sub;
262     char *line = avpkt->data;
263     char *end = avpkt->data + avpkt->size;
264     struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
265
266     if (avpkt->size <= 0)
267         return avpkt->size;
268
269     av_bprint_init(&new_line, 0, 2048);
270
271     // skip {frame_start}{frame_end}
272     line = strchr(line, '}'); if (!line) goto end; line++;
273     line = strchr(line, '}'); if (!line) goto end; line++;
274
275     // subtitle content
276     while (line < end && *line) {
277
278         // parse MicroDVD tags, and open them in ASS
279         line = microdvd_load_tags(tags, line);
280         microdvd_open_tags(&new_line, tags);
281
282         // simple copy until EOL or forced carriage return
283         while (line < end && *line && *line != '|') {
284             av_bprint_chars(&new_line, *line, 1);
285             line++;
286         }
287
288         // line split
289         if (line < end && *line == '|') {
290             microdvd_close_no_persistent_tags(&new_line, tags);
291             av_bprintf(&new_line, "\\N");
292             line++;
293         }
294     }
295
296 end:
297     av_bprint_finalize(&new_line, &decoded_sub);
298     if (*decoded_sub) {
299         int64_t start    = avpkt->pts;
300         int64_t duration = avpkt->duration;
301         int ts_start     = av_rescale_q(start,    avctx->time_base, (AVRational){1,100});
302         int ts_duration  = duration != -1 ?
303                            av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
304         ff_ass_add_rect(sub, decoded_sub, ts_start, ts_duration, 0);
305     }
306     av_free(decoded_sub);
307
308     *got_sub_ptr = sub->num_rects > 0;
309     return avpkt->size;
310 }
311
312 AVCodec ff_microdvd_decoder = {
313     .name         = "microdvd",
314     .long_name    = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
315     .type         = AVMEDIA_TYPE_SUBTITLE,
316     .id           = CODEC_ID_MICRODVD,
317     .init         = ff_ass_subtitle_header_default,
318     .decode       = microdvd_decode_frame,
319 };