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