FFmpeg
 All Data Structures 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 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':
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':
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':
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':
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':
152  tag.data1 = (*s++ == '1');
153  if (*s != '}')
154  break;
155  tag.key = 'p';
156  break;
157 
158  /* Coordinates */
159  case 'o':
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)
223  }
224 }
225 
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 
259  void *data, int *got_sub_ptr, AVPacket *avpkt)
260 {
261  AVSubtitle *sub = data;
262  AVBPrint new_line;
263  char c;
264  char *decoded_sub;
265  char *line = avpkt->data;
266  char *end = avpkt->data + avpkt->size;
267  struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
268 
269  if (avpkt->size <= 0)
270  return avpkt->size;
271 
272  /* To be removed later */
273  if (sscanf(line, "{%*d}{%*[0123456789]}%c", &c) == 1 &&
274  line[avpkt->size - 1] == '\n') {
275  av_log(avctx, AV_LOG_ERROR, "AVPacket is not clean (contains timing "
276  "information and a trailing line break). You need to upgrade "
277  "your libavformat or sanitize your packet.\n");
278  return AVERROR_INVALIDDATA;
279  }
280 
281  av_bprint_init(&new_line, 0, 2048);
282 
283  // subtitle content
284  while (line < end && *line) {
285 
286  // parse MicroDVD tags, and open them in ASS
287  line = microdvd_load_tags(tags, line);
288  microdvd_open_tags(&new_line, tags);
289 
290  // simple copy until EOL or forced carriage return
291  while (line < end && *line && *line != '|') {
292  av_bprint_chars(&new_line, *line, 1);
293  line++;
294  }
295 
296  // line split
297  if (line < end && *line == '|') {
298  microdvd_close_no_persistent_tags(&new_line, tags);
299  av_bprintf(&new_line, "\\N");
300  line++;
301  }
302  }
303  if (new_line.len) {
304  av_bprintf(&new_line, "\r\n");
305 
306  av_bprint_finalize(&new_line, &decoded_sub);
307  if (*decoded_sub) {
308  int64_t start = avpkt->pts;
309  int64_t duration = avpkt->duration;
310  int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100});
311  int ts_duration = duration != -1 ?
312  av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
313  ff_ass_add_rect(sub, decoded_sub, ts_start, ts_duration, 0);
314  }
315  av_free(decoded_sub);
316  }
317 
318  *got_sub_ptr = sub->num_rects > 0;
319  return avpkt->size;
320 }
321 
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 
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, alignment);
368 }
369 
371  .name = "microdvd",
372  .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
373  .type = AVMEDIA_TYPE_SUBTITLE,
374  .id = AV_CODEC_ID_MICRODVD,
375  .init = microdvd_init,
376  .decode = microdvd_decode_frame,
377 };