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':
167  if (!*s)
168  break;
170  tag.data1 = (*s++ == '1');
171  if (*s != '}')
172  break;
173  tag.key = 'p';
174  break;
175 
176  /* Coordinates */
177  case 'o':
179  tag.data1 = strtol(s, &s, 10);
180  if (*s != ',')
181  break;
182  s++;
183  tag.data2 = strtol(s, &s, 10);
184  if (*s != '}')
185  break;
186  tag.key = 'o';
187  break;
188 
189  default: /* Unknown tag, we consider it's text */
190  break;
191  }
192 
193  if (tag.key == 0)
194  return start;
195 
196  microdvd_set_tag(tags, tag);
197  s++;
198  }
199  return check_for_italic_slash_marker(tags, s);
200 }
201 
202 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
203 {
204  int i, sidx;
205  for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
206  if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
207  continue;
208  switch (tags[i].key) {
209  case 'Y':
210  case 'y':
211  for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
212  if (tags[i].data1 & (1 << sidx))
213  av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
214  break;
215 
216  case 'c':
217  av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
218  break;
219 
220  case 'f':
221  av_bprintf(new_line, "{\\fn%.*s}",
222  tags[i].data_string_len, tags[i].data_string);
223  break;
224 
225  case 's':
226  av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
227  break;
228 
229  case 'p':
230  if (tags[i].data1 == 0)
231  av_bprintf(new_line, "{\\an8}");
232  break;
233 
234  case 'o':
235  av_bprintf(new_line, "{\\pos(%d,%d)}",
236  tags[i].data1, tags[i].data2);
237  break;
238  }
239  if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
241  }
242 }
243 
244 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
245  struct microdvd_tag *tags)
246 {
247  int i, sidx;
248 
249  for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
250  if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
251  continue;
252  switch (tags[i].key) {
253 
254  case 'y':
255  for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
256  if (tags[i].data1 & (1 << sidx))
257  av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
258  break;
259 
260  case 'c':
261  av_bprintf(new_line, "{\\c}");
262  break;
263 
264  case 'f':
265  av_bprintf(new_line, "{\\fn}");
266  break;
267 
268  case 's':
269  av_bprintf(new_line, "{\\fs}");
270  break;
271  }
272  tags[i].key = 0;
273  }
274 }
275 
277  void *data, int *got_sub_ptr, AVPacket *avpkt)
278 {
279  AVSubtitle *sub = data;
280  AVBPrint new_line;
281  char *line = avpkt->data;
282  char *end = avpkt->data + avpkt->size;
283  struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
284 
285  if (avpkt->size <= 0)
286  return avpkt->size;
287 
288  av_bprint_init(&new_line, 0, 2048);
289 
290  // subtitle content
291  while (line < end && *line) {
292 
293  // parse MicroDVD tags, and open them in ASS
294  line = microdvd_load_tags(tags, line);
295  microdvd_open_tags(&new_line, tags);
296 
297  // simple copy until EOL or forced carriage return
298  while (line < end && *line && *line != '|') {
299  av_bprint_chars(&new_line, *line, 1);
300  line++;
301  }
302 
303  // line split
304  if (line < end && *line == '|') {
305  microdvd_close_no_persistent_tags(&new_line, tags);
306  av_bprintf(&new_line, "\\N");
307  line++;
308  }
309  }
310  if (new_line.len) {
311  int ret;
312  int64_t start = avpkt->pts;
313  int64_t duration = avpkt->duration;
314  int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100});
315  int ts_duration = duration != -1 ?
316  av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
317 
318  ret = ff_ass_add_rect_bprint(sub, &new_line, ts_start, ts_duration);
319  av_bprint_finalize(&new_line, NULL);
320  if (ret < 0)
321  return ret;
322  }
323 
324  *got_sub_ptr = sub->num_rects > 0;
325  return avpkt->size;
326 }
327 
328 static int microdvd_init(AVCodecContext *avctx)
329 {
330  int i, sidx;
331  AVBPrint font_buf;
332  int font_size = ASS_DEFAULT_FONT_SIZE;
333  int color = ASS_DEFAULT_COLOR;
334  int bold = ASS_DEFAULT_BOLD;
335  int italic = ASS_DEFAULT_ITALIC;
336  int underline = ASS_DEFAULT_UNDERLINE;
337  int alignment = ASS_DEFAULT_ALIGNMENT;
338  struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
339 
341  av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
342 
343  if (avctx->extradata) {
344  microdvd_load_tags(tags, avctx->extradata);
345  for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
346  switch (av_tolower(tags[i].key)) {
347  case 'y':
348  for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
349  if (tags[i].data1 & (1 << sidx)) {
350  switch (MICRODVD_STYLES[sidx]) {
351  case 'i': italic = 1; break;
352  case 'b': bold = 1; break;
353  case 'u': underline = 1; break;
354  }
355  }
356  }
357  break;
358 
359  case 'c': color = tags[i].data1; break;
360  case 's': font_size = tags[i].data1; break;
361  case 'p': alignment = 8; break;
362 
363  case 'f':
364  av_bprint_clear(&font_buf);
365  av_bprintf(&font_buf, "%.*s",
366  tags[i].data_string_len, tags[i].data_string);
367  break;
368  }
369  }
370  }
371  return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
372  ASS_DEFAULT_BACK_COLOR, bold, italic,
373  underline, ASS_DEFAULT_BORDERSTYLE,
374  alignment);
375 }
376 
378  .name = "microdvd",
379  .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
380  .type = AVMEDIA_TYPE_SUBTITLE,
381  .id = AV_CODEC_ID_MICRODVD,
382  .init = microdvd_init,
383  .decode = microdvd_decode_frame,
384 };
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
Definition: microdvddec.c:57
#define NULL
Definition: coverity.c:32
const char * s
Definition: avisynth_c.h:631
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:94
ptrdiff_t const GLvoid * data
Definition: opengl_enc.c:101
#define ASS_DEFAULT_BORDERSTYLE
Definition: ass.h:43
AVCodec ff_microdvd_decoder
Definition: microdvddec.c:377
int ff_ass_subtitle_header(AVCodecContext *avctx, const char *font, int font_size, int color, int back_color, int bold, int italic, int underline, int border_style, int alignment)
Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS.
Definition: ass.c:29
#define MICRODVD_PERSISTENT_ON
Definition: microdvddec.c:51
int size
Definition: avcodec.h:1468
int data_string_len
Definition: microdvddec.c:47
static int microdvd_decode_frame(AVCodecContext *avctx, void *data, int *got_sub_ptr, AVPacket *avpkt)
Definition: microdvddec.c:276
static char * microdvd_load_tags(struct microdvd_tag *tags, char *s)
Definition: microdvddec.c:83
#define MICRODVD_PERSISTENT_OFF
Definition: microdvddec.c:50
AVCodec.
Definition: avcodec.h:3392
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition: bprint.c:235
AVRational time_base
This is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented...
Definition: avcodec.h:1661
uint32_t data2
Definition: microdvddec.c:45
#define ASS_DEFAULT_ALIGNMENT
Definition: ass.h:42
static av_cold int end(AVCodecContext *avctx)
Definition: avrndec.c:90
int64_t duration
Duration of this packet in AVStream->time_base units, 0 if unknown.
Definition: avcodec.h:1485
uint8_t * extradata
some codecs need / can use extradata like Huffman tables.
Definition: avcodec.h:1647
char * data_string
Definition: microdvddec.c:46
uint8_t * data
Definition: avcodec.h:1467
uint32_t tag
Definition: movenc.c:1348
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
Rescale a 64-bit integer by 2 rational numbers.
Definition: mathematics.c:142
static av_const int av_tolower(int c)
Locale-independent conversion of ASCII characters to lowercase.
Definition: avstring.h:241
#define ASS_DEFAULT_BACK_COLOR
Definition: ass.h:38
#define ASS_DEFAULT_UNDERLINE
Definition: ass.h:41
#define ASS_DEFAULT_FONT
Definition: ass.h:35
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification. ...
Definition: internal.h:176
void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
Definition: bprint.c:69
Definition: graph2dot.c:48
const char * name
Name of the codec implementation.
Definition: avcodec.h:3399
#define ASS_DEFAULT_FONT_SIZE
Definition: ass.h:36
static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
Definition: microdvddec.c:202
static void microdvd_close_no_persistent_tags(AVBPrint *new_line, struct microdvd_tag *tags)
Definition: microdvddec.c:244
static int microdvd_init(AVCodecContext *avctx)
Definition: microdvddec.c:328
int64_t duration
Definition: movenc-test.c:63
#define AV_BPRINT_SIZE_AUTOMATIC
Libavcodec external API header.
#define MICRODVD_PERSISTENT_OPENED
Definition: microdvddec.c:52
int ff_ass_add_rect_bprint(AVSubtitle *sub, AVBPrint *buf, int ts_start, int duration)
Same as ff_ass_add_rect, but taking an AVBPrint buffer instead of a string, and assuming raw=0...
Definition: ass.c:181
main external API structure.
Definition: avcodec.h:1532
uint32_t data1
Definition: microdvddec.c:44
rational number numerator/denominator
Definition: rational.h:43
misc parsing utilities
void av_bprint_clear(AVBPrint *buf)
Reset the string to "" but keep internal allocated data.
Definition: bprint.c:227
static int indexof(const char *s, int c)
Definition: microdvddec.c:35
#define ASS_DEFAULT_COLOR
Definition: ass.h:37
static double c[64]
#define ASS_DEFAULT_ITALIC
Definition: ass.h:40
int len
#define ASS_DEFAULT_BOLD
Definition: ass.h:39
void INT64 start
Definition: avisynth_c.h:553
static char * check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
Definition: microdvddec.c:71
#define MICRODVD_TAGS
Definition: microdvddec.c:55
This structure stores compressed data.
Definition: avcodec.h:1444
int64_t pts
Presentation timestamp in AVStream->time_base units; the time at which the decompressed packet will b...
Definition: avcodec.h:1460
#define MICRODVD_STYLES
Definition: microdvddec.c:67
void av_bprint_chars(AVBPrint *buf, char c, unsigned n)
Append char c n times to a print buffer.
Definition: bprint.c:140