• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "codec_internal.h"
35 
indexof(const char * s,int c)36 static int indexof(const char *s, int c)
37 {
38     char *f = strchr(s, c);
39     return f ? (f - s) : -1;
40 }
41 
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;
49 };
50 
51 #define MICRODVD_PERSISTENT_OFF     0
52 #define MICRODVD_PERSISTENT_ON      1
53 #define MICRODVD_PERSISTENT_OPENED  2
54 
55 // Color, Font, Size, cHarset, stYle, Position, cOordinate
56 #define MICRODVD_TAGS "cfshyYpo"
57 
microdvd_set_tag(struct microdvd_tag * tags,struct microdvd_tag tag)58 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
59 {
60     int tag_index = indexof(MICRODVD_TAGS, tag.key);
61 
62     if (tag_index < 0)
63         return;
64     memcpy(&tags[tag_index], &tag, sizeof(tag));
65 }
66 
67 // italic, bold, underline, strike-through
68 #define MICRODVD_STYLES "ibus"
69 
70 /* some samples have lines that start with a / indicating non persistent italic
71  * marker */
check_for_italic_slash_marker(struct microdvd_tag * tags,char * s)72 static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
73 {
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++;
80     }
81     return s;
82 }
83 
microdvd_load_tags(struct microdvd_tag * tags,char * s)84 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
85 {
86     s = check_for_italic_slash_marker(tags, s);
87 
88     while (*s == '{') {
89         char *start = s;
90         char tag_char = *(s + 1);
91         struct microdvd_tag tag = {0};
92 
93         if (!tag_char || *(s + 2) != ':')
94             break;
95         s += 3;
96 
97         switch (tag_char) {
98 
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);
105 
106                 if (style_index >= 0)
107                     tag.data1 |= (1 << style_index);
108                 s++;
109             }
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;
116 
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;
128 
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;
141         }
142 
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;
152 
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;
164         }
165 
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;
176 
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;
189 
190         default:    /* Unknown tag, we consider it's text */
191             break;
192         }
193 
194         if (tag.key == 0)
195             return start;
196 
197         microdvd_set_tag(tags, tag);
198         s++;
199     }
200     return check_for_italic_slash_marker(tags, s);
201 }
202 
microdvd_open_tags(AVBPrint * new_line,struct microdvd_tag * tags)203 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
204 {
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;
216 
217         case 'c':
218             av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
219             break;
220 
221         case 'f':
222             av_bprintf(new_line, "{\\fn%.*s}",
223                        tags[i].data_string_len, tags[i].data_string);
224             break;
225 
226         case 's':
227             av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
228             break;
229 
230         case 'p':
231             if (tags[i].data1 == 0)
232                 av_bprintf(new_line, "{\\an8}");
233             break;
234 
235         case 'o':
236             av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
237                        tags[i].data1, tags[i].data2);
238             break;
239         }
240         if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
241             tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
242     }
243 }
244 
microdvd_close_no_persistent_tags(AVBPrint * new_line,struct microdvd_tag * tags)245 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
246                                               struct microdvd_tag *tags)
247 {
248     int i, sidx;
249 
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) {
254 
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;
260 
261         case 'c':
262             av_bprintf(new_line, "{\\c}");
263             break;
264 
265         case 'f':
266             av_bprintf(new_line, "{\\fn}");
267             break;
268 
269         case 's':
270             av_bprintf(new_line, "{\\fs}");
271             break;
272         }
273         tags[i].key = 0;
274     }
275 }
276 
microdvd_decode_frame(AVCodecContext * avctx,AVSubtitle * sub,int * got_sub_ptr,const AVPacket * avpkt)277 static int microdvd_decode_frame(AVCodecContext *avctx, AVSubtitle *sub,
278                                  int *got_sub_ptr, const AVPacket *avpkt)
279 {
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}};
285 
286     if (avpkt->size <= 0)
287         return avpkt->size;
288 
289     av_bprint_init(&new_line, 0, 2048);
290 
291     // subtitle content
292     while (line < end && *line) {
293 
294         // parse MicroDVD tags, and open them in ASS
295         line = microdvd_load_tags(tags, line);
296         microdvd_open_tags(&new_line, tags);
297 
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++;
302         }
303 
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++;
309         }
310     }
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;
316     }
317 
318     *got_sub_ptr = sub->num_rects > 0;
319     return avpkt->size;
320 }
321 
microdvd_init(AVCodecContext * avctx)322 static int microdvd_init(AVCodecContext *avctx)
323 {
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}};
333 
334     av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
335     av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
336 
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;
348                         }
349                     }
350                 }
351                 break;
352 
353             case 'c': color     = tags[i].data1; break;
354             case 's': font_size = tags[i].data1; break;
355             case 'p': alignment =             8; break;
356 
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;
362             }
363         }
364     }
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);
369 }
370 
371 const FFCodec ff_microdvd_decoder = {
372     .p.name       = "microdvd",
373     .p.long_name  = NULL_IF_CONFIG_SMALL("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),
380     .caps_internal  = FF_CODEC_CAP_INIT_THREADSAFE,
381 };
382