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