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/attributes.h"
30 #include "libavutil/avstring.h"
31 #include "libavutil/parseutils.h"
32 #include "libavutil/bprint.h"
33 #include "avcodec.h"
34 #include "ass.h"
35 #include "codec_internal.h"
36 
37 static int indexof(const char *s, int c)
38 {
39  char *f = strchr(s, c);
40  return f ? (f - s) : -1;
41 }
42 
43 struct microdvd_tag {
44  char key;
46  uint32_t data1;
47  uint32_t data2;
48  char *data_string;
50 };
51 
52 #define MICRODVD_PERSISTENT_OFF 0
53 #define MICRODVD_PERSISTENT_ON 1
54 #define MICRODVD_PERSISTENT_OPENED 2
55 
56 // Color, Font, Size, cHarset, stYle, Position, cOordinate
57 #define MICRODVD_TAGS "cfshyYpo"
58 
59 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
60 {
61  int tag_index = indexof(MICRODVD_TAGS, tag.key);
62 
63  if (tag_index < 0)
64  return;
65  memcpy(&tags[tag_index], &tag, sizeof(tag));
66 }
67 
68 // italic, bold, underline, strike-through
69 #define MICRODVD_STYLES "ibus"
70 
71 /* some samples have lines that start with a / indicating non persistent italic
72  * marker */
73 static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
74 {
75  if (*s == '/') {
76  struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')];
77  tag.key = 'y';
78  tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */;
79  microdvd_set_tag(tags, tag);
80  s++;
81  }
82  return s;
83 }
84 
85 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
86 {
88 
89  while (*s == '{') {
90  char *start = s;
91  char tag_char = *(s + 1);
92  struct microdvd_tag tag = {0};
93 
94  if (!tag_char || *(s + 2) != ':')
95  break;
96  s += 3;
97 
98  switch (tag_char) {
99 
100  /* Style */
101  case 'Y':
102  tag.persistent = MICRODVD_PERSISTENT_ON;
104  case 'y':
105  while (*s && *s != '}' && s - start < 256) {
106  int style_index = indexof(MICRODVD_STYLES, *s);
107 
108  if (style_index >= 0)
109  tag.data1 |= (1 << style_index);
110  s++;
111  }
112  if (*s != '}')
113  break;
114  /* We must distinguish persistent and non-persistent styles
115  * to handle this kind of style tags: {y:ib}{Y:us} */
116  tag.key = tag_char;
117  break;
118 
119  /* Color */
120  case 'C':
121  tag.persistent = MICRODVD_PERSISTENT_ON;
123  case 'c':
124  while (*s == '$' || *s == '#')
125  s++;
126  tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
127  if (*s != '}')
128  break;
129  tag.key = 'c';
130  break;
131 
132  /* Font name */
133  case 'F':
134  tag.persistent = MICRODVD_PERSISTENT_ON;
136  case 'f': {
137  int len = indexof(s, '}');
138  if (len < 0)
139  break;
140  tag.data_string = s;
141  tag.data_string_len = len;
142  s += len;
143  tag.key = 'f';
144  break;
145  }
146 
147  /* Font size */
148  case 'S':
149  tag.persistent = MICRODVD_PERSISTENT_ON;
151  case 's':
152  tag.data1 = strtol(s, &s, 10);
153  if (*s != '}')
154  break;
155  tag.key = 's';
156  break;
157 
158  /* Charset */
159  case 'H': {
160  //TODO: not yet handled, just parsed.
161  int len = indexof(s, '}');
162  if (len < 0)
163  break;
164  tag.data_string = s;
165  tag.data_string_len = len;
166  s += len;
167  tag.key = 'h';
168  break;
169  }
170 
171  /* Position */
172  case 'P':
173  if (!*s)
174  break;
175  tag.persistent = MICRODVD_PERSISTENT_ON;
176  tag.data1 = (*s++ == '1');
177  if (*s != '}')
178  break;
179  tag.key = 'p';
180  break;
181 
182  /* Coordinates */
183  case 'o':
184  tag.persistent = MICRODVD_PERSISTENT_ON;
185  tag.data1 = strtol(s, &s, 10);
186  if (*s != ',')
187  break;
188  s++;
189  tag.data2 = strtol(s, &s, 10);
190  if (*s != '}')
191  break;
192  tag.key = 'o';
193  break;
194 
195  default: /* Unknown tag, we consider it's text */
196  break;
197  }
198 
199  if (tag.key == 0)
200  return start;
201 
202  microdvd_set_tag(tags, tag);
203  s++;
204  }
205  return check_for_italic_slash_marker(tags, s);
206 }
207 
208 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
209 {
210  int i, sidx;
211  for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
213  continue;
214  switch (tags[i].key) {
215  case 'Y':
216  case 'y':
217  for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
218  if (tags[i].data1 & (1 << sidx))
219  av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
220  break;
221 
222  case 'c':
223  av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
224  break;
225 
226  case 'f':
227  av_bprintf(new_line, "{\\fn%.*s}",
228  tags[i].data_string_len, tags[i].data_string);
229  break;
230 
231  case 's':
232  av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
233  break;
234 
235  case 'p':
236  if (tags[i].data1 == 0)
237  av_bprintf(new_line, "{\\an8}");
238  break;
239 
240  case 'o':
241  av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
242  tags[i].data1, tags[i].data2);
243  break;
244  }
245  if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
247  }
248 }
249 
250 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
251  struct microdvd_tag *tags)
252 {
253  int i, sidx;
254 
255  for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
256  if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
257  continue;
258  switch (tags[i].key) {
259 
260  case 'y':
261  for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
262  if (tags[i].data1 & (1 << sidx))
263  av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
264  break;
265 
266  case 'c':
267  av_bprintf(new_line, "{\\c}");
268  break;
269 
270  case 'f':
271  av_bprintf(new_line, "{\\fn}");
272  break;
273 
274  case 's':
275  av_bprintf(new_line, "{\\fs}");
276  break;
277  }
278  tags[i].key = 0;
279  }
280 }
281 
283  int *got_sub_ptr, const AVPacket *avpkt)
284 {
285  AVBPrint new_line;
286  char *line = avpkt->data;
287  char *end = avpkt->data + avpkt->size;
288  FFASSDecoderContext *s = avctx->priv_data;
289  struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
290 
291  if (avpkt->size <= 0)
292  return avpkt->size;
293 
294  av_bprint_init(&new_line, 0, 2048);
295 
296  // subtitle content
297  while (line < end && *line) {
298 
299  // parse MicroDVD tags, and open them in ASS
300  line = microdvd_load_tags(tags, line);
301  microdvd_open_tags(&new_line, tags);
302 
303  // simple copy until EOL or forced carriage return
304  while (line < end && *line && *line != '|') {
305  av_bprint_chars(&new_line, *line, 1);
306  line++;
307  }
308 
309  // line split
310  if (line < end && *line == '|') {
311  microdvd_close_no_persistent_tags(&new_line, tags);
312  av_bprintf(&new_line, "\\N");
313  line++;
314  }
315  }
316  if (new_line.len) {
317  int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
318  av_bprint_finalize(&new_line, NULL);
319  if (ret < 0)
320  return ret;
321  }
322 
323  *got_sub_ptr = sub->num_rects > 0;
324  return avpkt->size;
325 }
326 
328 {
329  int i, sidx;
330  AVBPrint font_buf;
331  int font_size = ASS_DEFAULT_FONT_SIZE;
332  int color = ASS_DEFAULT_COLOR;
333  int bold = ASS_DEFAULT_BOLD;
334  int italic = ASS_DEFAULT_ITALIC;
335  int underline = ASS_DEFAULT_UNDERLINE;
336  int alignment = ASS_DEFAULT_ALIGNMENT;
337  struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
338 
340  av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
341 
342  if (avctx->extradata) {
343  microdvd_load_tags(tags, avctx->extradata);
344  for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
345  switch (av_tolower(tags[i].key)) {
346  case 'y':
347  for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
348  if (tags[i].data1 & (1 << sidx)) {
349  switch (MICRODVD_STYLES[sidx]) {
350  case 'i': italic = 1; break;
351  case 'b': bold = 1; break;
352  case 'u': underline = 1; break;
353  }
354  }
355  }
356  break;
357 
358  case 'c': color = tags[i].data1; break;
359  case 's': font_size = tags[i].data1; break;
360  case 'p': alignment = 8; break;
361 
362  case 'f':
363  av_bprint_clear(&font_buf);
364  av_bprintf(&font_buf, "%.*s",
365  tags[i].data_string_len, tags[i].data_string);
366  break;
367  }
368  }
369  }
370  return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
371  ASS_DEFAULT_BACK_COLOR, bold, italic,
372  underline, ASS_DEFAULT_BORDERSTYLE,
373  alignment);
374 }
375 
377  .p.name = "microdvd",
378  CODEC_LONG_NAME("MicroDVD subtitle"),
379  .p.type = AVMEDIA_TYPE_SUBTITLE,
380  .p.id = AV_CODEC_ID_MICRODVD,
381  .init = microdvd_init,
383  .flush = ff_ass_decoder_flush,
384  .priv_data_size = sizeof(FFASSDecoderContext),
385 };
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:84
AVSubtitle
Definition: avcodec.h:2094
AVMEDIA_TYPE_SUBTITLE
@ AVMEDIA_TYPE_SUBTITLE
Definition: avutil.h:203
microdvd_load_tags
static char * microdvd_load_tags(struct microdvd_tag *tags, char *s)
Definition: microdvddec.c:85
color
Definition: vf_paletteuse.c:513
av_bprint_init
void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
Definition: bprint.c:69
microdvd_decode_frame
static int microdvd_decode_frame(AVCodecContext *avctx, AVSubtitle *sub, int *got_sub_ptr, const AVPacket *avpkt)
Definition: microdvddec.c:282
AVSubtitle::num_rects
unsigned num_rects
Definition: avcodec.h:2098
indexof
static int indexof(const char *s, int c)
Definition: microdvddec.c:37
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:159
AVPacket::data
uint8_t * data
Definition: packet.h:595
FFCodec
Definition: codec_internal.h:127
microdvd_open_tags
static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
Definition: microdvddec.c:208
ASS_DEFAULT_BORDERSTYLE
#define ASS_DEFAULT_BORDERSTYLE
Definition: ass.h:43
microdvd_tag
Definition: microdvddec.c:43
FFCodec::p
AVCodec p
The public AVCodec.
Definition: codec_internal.h:131
microdvd_init
static av_cold int microdvd_init(AVCodecContext *avctx)
Definition: microdvddec.c:327
AV_BPRINT_SIZE_AUTOMATIC
#define AV_BPRINT_SIZE_AUTOMATIC
MICRODVD_STYLES
#define MICRODVD_STYLES
Definition: microdvddec.c:69
ass.h
microdvd_tag::persistent
int persistent
Definition: microdvddec.c:45
ASS_DEFAULT_FONT
#define ASS_DEFAULT_FONT
Definition: ass.h:35
av_cold
#define av_cold
Definition: attributes.h:119
s
#define s(width, name)
Definition: cbs_vp9.c:198
microdvd_close_no_persistent_tags
static void microdvd_close_no_persistent_tags(AVBPrint *new_line, struct microdvd_tag *tags)
Definition: microdvddec.c:250
key
const char * key
Definition: hwcontext_opencl.c:189
av_fallthrough
#define av_fallthrough
Definition: attributes.h:67
CODEC_LONG_NAME
#define CODEC_LONG_NAME(str)
Definition: codec_internal.h:332
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:44
microdvd_tag::data_string
char * data_string
Definition: microdvddec.c:48
f
f
Definition: af_crystalizer.c:122
ASS_DEFAULT_BOLD
#define ASS_DEFAULT_BOLD
Definition: ass.h:39
AVPacket::size
int size
Definition: packet.h:596
av_bprint_finalize
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition: bprint.c:235
codec_internal.h
i
#define i(width, name, range_min, range_max)
Definition: cbs_h264.c:63
microdvd_tag::data2
uint32_t data2
Definition: microdvddec.c:47
check_for_italic_slash_marker
static char * check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
Definition: microdvddec.c:73
line
Definition: graph2dot.c:48
attributes.h
ff_microdvd_decoder
const FFCodec ff_microdvd_decoder
Definition: microdvddec.c:376
ASS_DEFAULT_UNDERLINE
#define ASS_DEFAULT_UNDERLINE
Definition: ass.h:41
bprint.h
AVCodecContext::extradata
uint8_t * extradata
Out-of-band global headers that may be used by some codecs.
Definition: avcodec.h:522
microdvd_set_tag
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
Definition: microdvddec.c:59
AVCodec::name
const char * name
Name of the codec implementation.
Definition: codec.h:179
ASS_DEFAULT_ITALIC
#define ASS_DEFAULT_ITALIC
Definition: ass.h:40
len
int len
Definition: vorbis_enc_data.h:426
ASS_DEFAULT_COLOR
#define ASS_DEFAULT_COLOR
Definition: ass.h:37
avcodec.h
tag
uint32_t tag
Definition: movenc.c:2046
ret
ret
Definition: filter_design.txt:187
av_bprintf
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:122
ASS_DEFAULT_FONT_SIZE
#define ASS_DEFAULT_FONT_SIZE
Definition: ass.h:36
AVCodecContext
main external API structure.
Definition: avcodec.h:439
microdvd_tag::data_string_len
int data_string_len
Definition: microdvddec.c:49
av_bprint_clear
void av_bprint_clear(AVBPrint *buf)
Reset the string to "" but keep internal allocated data.
Definition: bprint.c:227
MICRODVD_TAGS
#define MICRODVD_TAGS
Definition: microdvddec.c:57
FF_CODEC_DECODE_SUB_CB
#define FF_CODEC_DECODE_SUB_CB(func)
Definition: codec_internal.h:351
MICRODVD_PERSISTENT_OFF
#define MICRODVD_PERSISTENT_OFF
Definition: microdvddec.c:52
AV_CODEC_ID_MICRODVD
@ AV_CODEC_ID_MICRODVD
Definition: codec_id.h:581
AVCodecContext::priv_data
void * priv_data
Definition: avcodec.h:466
AVPacket
This structure stores compressed data.
Definition: packet.h:572
microdvd_tag::data1
uint32_t data1
Definition: microdvddec.c:46
FFASSDecoderContext
Definition: ass.h:46
ff_ass_decoder_flush
av_cold void ff_ass_decoder_flush(AVCodecContext *avctx)
Helper to flush a text subtitles decoder making use of the FFASSDecoderContext.
Definition: ass.c:166
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:130
avstring.h
MICRODVD_PERSISTENT_OPENED
#define MICRODVD_PERSISTENT_OPENED
Definition: microdvddec.c:54
av_tolower
static av_const int av_tolower(int c)
Locale-independent conversion of ASCII characters to lowercase.
Definition: avstring.h:237
MICRODVD_PERSISTENT_ON
#define MICRODVD_PERSISTENT_ON
Definition: microdvddec.c:53