FFmpeg  4.0
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%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
const char * s
Definition: avisynth_c.h:768
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:94
#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:1431
int data_string_len
Definition: microdvddec.c:47
unsigned num_rects
Definition: avcodec.h:3864
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:3408
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
static av_cold int end(AVCodecContext *avctx)
Definition: avrndec.c:90
uint8_t * extradata
some codecs need / can use extradata like Huffman tables.
Definition: avcodec.h:1618
char * data_string
Definition: microdvddec.c:46
const char data[16]
Definition: mxf.c:90
uint8_t * data
Definition: avcodec.h:1430
uint32_t tag
Definition: movenc.c:1455
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: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:3415
#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 AV_BPRINT_SIZE_AUTOMATIC
Libavcodec external API header.
#define MICRODVD_PERSISTENT_OPENED
Definition: microdvddec.c:52
main external API structure.
Definition: avcodec.h:1518
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
static double c[64]
void * priv_data
Definition: avcodec.h:1545
#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:690
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:1407
#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