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 36static int indexof(const char *s, int c) 37{ 38 char *f = strchr(s, c); 39 return f ? (f - s) : -1; 40} 41 42struct microdvd_tag { 43 char key; 44 int persistent; 45 uint32_t data1; 46 uint32_t data2; 47 char *data_string; 48 int data_string_len; 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 58static 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 */ 72static 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 84static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) 85{ 86 s = check_for_italic_slash_marker(tags, s); 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 203static 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++) { 207 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) 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) 241 tags[i].persistent = MICRODVD_PERSISTENT_OPENED; 242 } 243} 244 245static 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 277static int microdvd_decode_frame(AVCodecContext *avctx, AVSubtitle *sub, 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 322static 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 334 av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC); 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 371const FFCodec ff_microdvd_decoder = { 372 .p.name = "microdvd", 373 .p.long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"), 374 .p.type = AVMEDIA_TYPE_SUBTITLE, 375 .p.id = AV_CODEC_ID_MICRODVD, 376 .init = microdvd_init, 377 FF_CODEC_DECODE_SUB_CB(microdvd_decode_frame), 378 .flush = ff_ass_decoder_flush, 379 .priv_data_size = sizeof(FFASSDecoderContext), 380 .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE, 381}; 382