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 {
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':
100  tag.persistent = MICRODVD_PERSISTENT_ON;
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':
118  tag.persistent = MICRODVD_PERSISTENT_ON;
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':
130  tag.persistent = MICRODVD_PERSISTENT_ON;
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':
144  tag.persistent = MICRODVD_PERSISTENT_ON;
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;
169  tag.persistent = MICRODVD_PERSISTENT_ON;
170  tag.data1 = (*s++ == '1');
171  if (*s != '}')
172  break;
173  tag.key = 'p';
174  break;
175 
176  /* Coordinates */
177  case 'o':
178  tag.persistent = MICRODVD_PERSISTENT_ON;
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++) {
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 };
ff_ass_subtitle_header
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:83
AVSubtitle
Definition: avcodec.h:2722
AVCodec
AVCodec.
Definition: codec.h:197
AVMEDIA_TYPE_SUBTITLE
@ AVMEDIA_TYPE_SUBTITLE
Definition: avutil.h:204
microdvd_load_tags
static char * microdvd_load_tags(struct microdvd_tag *tags, char *s)
Definition: microdvddec.c:83
av_bprint_finalize
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition: bprint.c:235
color
Definition: vf_paletteuse.c:583
av_bprint_init
void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
Definition: bprint.c:69
sub
static float sub(float src0, float src1)
Definition: dnn_backend_native_layer_mathbinary.c:32
indexof
static int indexof(const char *s, int c)
Definition: microdvddec.c:35
ASS_DEFAULT_ALIGNMENT
#define ASS_DEFAULT_ALIGNMENT
Definition: ass.h:42
ff_ass_add_rect
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:118
AVPacket::data
uint8_t * data
Definition: packet.h:369
data
const char data[16]
Definition: mxf.c:142
microdvd_open_tags
static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
Definition: microdvddec.c:202
ASS_DEFAULT_BORDERSTYLE
#define ASS_DEFAULT_BORDERSTYLE
Definition: ass.h:43
microdvd_tag
Definition: microdvddec.c:41
microdvd_decode_frame
static int microdvd_decode_frame(AVCodecContext *avctx, void *data, int *got_sub_ptr, AVPacket *avpkt)
Definition: microdvddec.c:276
AV_BPRINT_SIZE_AUTOMATIC
#define AV_BPRINT_SIZE_AUTOMATIC
MICRODVD_STYLES
#define MICRODVD_STYLES
Definition: microdvddec.c:67
ass.h
microdvd_tag::persistent
int persistent
Definition: microdvddec.c:43
ASS_DEFAULT_FONT
#define ASS_DEFAULT_FONT
Definition: ass.h:35
s
#define s(width, name)
Definition: cbs_vp9.c:257
microdvd_close_no_persistent_tags
static void microdvd_close_no_persistent_tags(AVBPrint *new_line, struct microdvd_tag *tags)
Definition: microdvddec.c:244
key
const char * key
Definition: hwcontext_opencl.c:168
f
#define f(width, name)
Definition: cbs_vp9.c:255
ASS_DEFAULT_BACK_COLOR
#define ASS_DEFAULT_BACK_COLOR
Definition: ass.h:38
NULL
#define NULL
Definition: coverity.c:32
parseutils.h
c
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
microdvd_tag::key
char key
Definition: microdvddec.c:42
microdvd_tag::data_string
char * data_string
Definition: microdvddec.c:46
ASS_DEFAULT_BOLD
#define ASS_DEFAULT_BOLD
Definition: ass.h:39
AVPacket::size
int size
Definition: packet.h:370
NULL_IF_CONFIG_SMALL
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition: internal.h:117
microdvd_tag::data2
uint32_t data2
Definition: microdvddec.c:45
check_for_italic_slash_marker
static char * check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
Definition: microdvddec.c:71
line
Definition: graph2dot.c:48
microdvd_init
static int microdvd_init(AVCodecContext *avctx)
Definition: microdvddec.c:322
ASS_DEFAULT_UNDERLINE
#define ASS_DEFAULT_UNDERLINE
Definition: ass.h:41
ff_ass_decoder_flush
void ff_ass_decoder_flush(AVCodecContext *avctx)
Helper to flush a text subtitles decoder making use of the FFASSDecoderContext.
Definition: ass.c:141
bprint.h
i
int i
Definition: input.c:407
AVCodecContext::extradata
uint8_t * extradata
some codecs need / can use extradata like Huffman tables.
Definition: avcodec.h:637
microdvd_set_tag
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
Definition: microdvddec.c:57
AVCodec::name
const char * name
Name of the codec implementation.
Definition: codec.h:204
ASS_DEFAULT_ITALIC
#define ASS_DEFAULT_ITALIC
Definition: ass.h:40
len
int len
Definition: vorbis_enc_data.h:452
ASS_DEFAULT_COLOR
#define ASS_DEFAULT_COLOR
Definition: ass.h:37
ff_microdvd_decoder
AVCodec ff_microdvd_decoder
Definition: microdvddec.c:371
avcodec.h
tag
uint32_t tag
Definition: movenc.c:1611
ret
ret
Definition: filter_design.txt:187
av_bprintf
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:94
ASS_DEFAULT_FONT_SIZE
#define ASS_DEFAULT_FONT_SIZE
Definition: ass.h:36
AVCodecContext
main external API structure.
Definition: avcodec.h:536
av_bprint_clear
void av_bprint_clear(AVBPrint *buf)
Reset the string to "" but keep internal allocated data.
Definition: bprint.c:227
microdvd_tag::data_string_len
int data_string_len
Definition: microdvddec.c:47
MICRODVD_TAGS
#define MICRODVD_TAGS
Definition: microdvddec.c:55
MICRODVD_PERSISTENT_OFF
#define MICRODVD_PERSISTENT_OFF
Definition: microdvddec.c:50
AV_CODEC_ID_MICRODVD
@ AV_CODEC_ID_MICRODVD
Definition: codec_id.h:533
AVCodecContext::priv_data
void * priv_data
Definition: avcodec.h:563
AVPacket
This structure stores compressed data.
Definition: packet.h:346
microdvd_tag::data1
uint32_t data1
Definition: microdvddec.c:44
FFASSDecoderContext
Definition: ass.h:46
avstring.h
MICRODVD_PERSISTENT_OPENED
#define MICRODVD_PERSISTENT_OPENED
Definition: microdvddec.c:52
av_tolower
static av_const int av_tolower(int c)
Locale-independent conversion of ASCII characters to lowercase.
Definition: avstring.h:246
av_bprint_chars
void av_bprint_chars(AVBPrint *buf, char c, unsigned n)
Append char c n times to a print buffer.
Definition: bprint.c:140
MICRODVD_PERSISTENT_ON
#define MICRODVD_PERSISTENT_ON
Definition: microdvddec.c:51