FFmpeg
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 != '}' && s - start < 256) {
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%06"PRIX32"&}", 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%"PRId32"}", 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(%"PRId32",%"PRId32")}",
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  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 
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, ASS_DEFAULT_BORDERSTYLE,
368  alignment);
369 }
370 
372  .name = "microdvd",
373  .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
374  .type = AVMEDIA_TYPE_SUBTITLE,
375  .id = AV_CODEC_ID_MICRODVD,
376  .init = microdvd_init,
377  .decode = microdvd_decode_frame,
378  .flush = ff_ass_decoder_flush,
379  .priv_data_size = sizeof(FFASSDecoderContext),
380 };
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
Definition: microdvddec.c:57
#define NULL
Definition: coverity.c:32
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:94
ptrdiff_t const GLvoid * data
Definition: opengl_enc.c:100
#define ASS_DEFAULT_BORDERSTYLE
Definition: ass.h:43
AVCodec ff_microdvd_decoder
Definition: microdvddec.c:371
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:1478
int data_string_len
Definition: microdvddec.c:47
unsigned num_rects
Definition: avcodec.h:3933
static int microdvd_decode_frame(AVCodecContext *avctx, void *data, int *got_sub_ptr, AVPacket *avpkt)
Definition: microdvddec.c:276
int ff_ass_add_rect(AVSubtitle *sub, const char *dialog, int readorder, int layer, const char *style, const char *speaker)
Add an ASS dialog to a subtitle.
Definition: ass.c:101
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:3477
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition: bprint.c:235
uint32_t data2
Definition: microdvddec.c:45
#define ASS_DEFAULT_ALIGNMENT
Definition: ass.h:42
#define f(width, name)
Definition: cbs_vp9.c:255
static av_cold int end(AVCodecContext *avctx)
Definition: avrndec.c:90
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
uint8_t * extradata
some codecs need / can use extradata like Huffman tables.
Definition: avcodec.h:1666
char * data_string
Definition: microdvddec.c:46
uint8_t * data
Definition: avcodec.h:1477
uint32_t tag
Definition: movenc.c:1496
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 i(width, name, range_min, range_max)
Definition: cbs_h2645.c:260
#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:186
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:3484
#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:322
#define s(width, name)
Definition: cbs_vp9.c:257
#define AV_BPRINT_SIZE_AUTOMATIC
Libavcodec external API header.
#define MICRODVD_PERSISTENT_OPENED
Definition: microdvddec.c:52
main external API structure.
Definition: avcodec.h:1565
uint32_t data1
Definition: microdvddec.c:44
void ff_ass_decoder_flush(AVCodecContext *avctx)
Helper to flush a text subtitles decoder making use of the FFASSDecoderContext.
Definition: ass.c:124
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
void * priv_data
Definition: avcodec.h:1592
#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:766
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:1454
#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