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 "codec_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  int *got_sub_ptr, const AVPacket *avpkt)
279 {
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  .p.name = "microdvd",
373  CODEC_LONG_NAME("MicroDVD subtitle"),
374  .p.type = AVMEDIA_TYPE_SUBTITLE,
375  .p.id = AV_CODEC_ID_MICRODVD,
376  .init = microdvd_init,
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:84
AVSubtitle
Definition: avcodec.h:2254
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:84
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:277
AVSubtitle::num_rects
unsigned num_rects
Definition: avcodec.h:2258
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:159
AVPacket::data
uint8_t * data
Definition: packet.h:539
FFCodec
Definition: codec_internal.h:127
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
FFCodec::p
AVCodec p
The public AVCodec.
Definition: codec_internal.h:131
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:198
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:189
CODEC_LONG_NAME
#define CODEC_LONG_NAME(str)
Definition: codec_internal.h:296
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
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:540
av_bprint_finalize
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition: bprint.c:240
codec_internal.h
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:322
ff_microdvd_decoder
const FFCodec ff_microdvd_decoder
Definition: microdvddec.c:371
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:166
bprint.h
i
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:256
AVCodecContext::extradata
uint8_t * extradata
Out-of-band global headers that may be used by some codecs.
Definition: avcodec.h:537
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:194
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:1879
ret
ret
Definition: filter_design.txt:187
av_bprintf
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:99
ASS_DEFAULT_FONT_SIZE
#define ASS_DEFAULT_FONT_SIZE
Definition: ass.h:36
AVCodecContext
main external API structure.
Definition: avcodec.h:451
microdvd_tag::data_string_len
int data_string_len
Definition: microdvddec.c:48
av_bprint_clear
void av_bprint_clear(AVBPrint *buf)
Reset the string to "" but keep internal allocated data.
Definition: bprint.c:232
MICRODVD_TAGS
#define MICRODVD_TAGS
Definition: microdvddec.c:56
FF_CODEC_DECODE_SUB_CB
#define FF_CODEC_DECODE_SUB_CB(func)
Definition: codec_internal.h:314
MICRODVD_PERSISTENT_OFF
#define MICRODVD_PERSISTENT_OFF
Definition: microdvddec.c:51
AV_CODEC_ID_MICRODVD
@ AV_CODEC_ID_MICRODVD
Definition: codec_id.h:566
AVCodecContext::priv_data
void * priv_data
Definition: avcodec.h:478
AVPacket
This structure stores compressed data.
Definition: packet.h:516
microdvd_tag::data1
uint32_t data1
Definition: microdvddec.c:45
FFASSDecoderContext
Definition: ass.h:46
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:145
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:237
MICRODVD_PERSISTENT_ON
#define MICRODVD_PERSISTENT_ON
Definition: microdvddec.c:52