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