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 35static int indexof(const char *s, int c) 36{ 37 char *f = strchr(s, c); 38 return f ? (f - s) : -1; 39} 40 41struct microdvd_tag { 42 char key; 43 int persistent; 44 uint32_t data1; 45 uint32_t data2; 46 char *data_string; 47 int data_string_len; 48}; 49 50#define MICRODVD_PERSISTENT_OFF 0 51#define MICRODVD_PERSISTENT_ON 1 52#define MICRODVD_PERSISTENT_OPENED 2 53 54// Color, Font, Size, cHarset, stYle, Position, cOordinate 55#define MICRODVD_TAGS "cfshyYpo" 56 57static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag) 58{ 59 int tag_index = indexof(MICRODVD_TAGS, tag.key); 60 61 if (tag_index < 0) 62 return; 63 memcpy(&tags[tag_index], &tag, sizeof(tag)); 64} 65 66// italic, bold, underline, strike-through 67#define MICRODVD_STYLES "ibus" 68 69static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) 70{ 71 while (*s == '{') { 72 char *start = s; 73 char tag_char = *(s + 1); 74 struct microdvd_tag tag = {0}; 75 76 if (!tag_char || *(s + 2) != ':') 77 break; 78 s += 3; 79 80 switch (tag_char) { 81 82 /* Style */ 83 case 'Y': 84 tag.persistent = MICRODVD_PERSISTENT_ON; 85 case 'y': 86 while (*s && *s != '}') { 87 int style_index = indexof(MICRODVD_STYLES, *s); 88 89 if (style_index >= 0) 90 tag.data1 |= (1 << style_index); 91 s++; 92 } 93 if (*s != '}') 94 break; 95 /* We must distinguish persistent and non-persistent styles 96 * to handle this kind of style tags: {y:ib}{Y:us} */ 97 tag.key = tag_char; 98 break; 99 100 /* Color */ 101 case 'C': 102 tag.persistent = MICRODVD_PERSISTENT_ON; 103 case 'c': 104 if (*s == '$') 105 s++; 106 tag.data1 = strtol(s, &s, 16) & 0x00ffffff; 107 if (*s != '}') 108 break; 109 tag.key = 'c'; 110 break; 111 112 /* Font name */ 113 case 'F': 114 tag.persistent = MICRODVD_PERSISTENT_ON; 115 case 'f': { 116 int len = indexof(s, '}'); 117 if (len < 0) 118 break; 119 tag.data_string = s; 120 tag.data_string_len = len; 121 s += len; 122 tag.key = 'f'; 123 break; 124 } 125 126 /* Font size */ 127 case 'S': 128 tag.persistent = MICRODVD_PERSISTENT_ON; 129 case 's': 130 tag.data1 = strtol(s, &s, 10); 131 if (*s != '}') 132 break; 133 tag.key = 's'; 134 break; 135 136 /* Charset */ 137 case 'H': { 138 //TODO: not yet handled, just parsed. 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 = 'h'; 146 break; 147 } 148 149 /* Position */ 150 case 'P': 151 tag.persistent = MICRODVD_PERSISTENT_ON; 152 tag.data1 = (*s++ == '1'); 153 if (*s != '}') 154 break; 155 tag.key = 'p'; 156 break; 157 158 /* Coordinates */ 159 case 'o': 160 tag.persistent = MICRODVD_PERSISTENT_ON; 161 tag.data1 = strtol(s, &s, 10); 162 if (*s != ',') 163 break; 164 s++; 165 tag.data2 = strtol(s, &s, 10); 166 if (*s != '}') 167 break; 168 tag.key = 'o'; 169 break; 170 171 default: /* Unknown tag, we consider it's text */ 172 break; 173 } 174 175 if (tag.key == 0) 176 return start; 177 178 microdvd_set_tag(tags, tag); 179 s++; 180 } 181 return s; 182} 183 184static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags) 185{ 186 int i, sidx; 187 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { 188 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) 189 continue; 190 switch (tags[i].key) { 191 case 'Y': 192 case 'y': 193 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) 194 if (tags[i].data1 & (1 << sidx)) 195 av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]); 196 break; 197 198 case 'c': 199 av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1); 200 break; 201 202 case 'f': 203 av_bprintf(new_line, "{\\fn%.*s}", 204 tags[i].data_string_len, tags[i].data_string); 205 break; 206 207 case 's': 208 av_bprintf(new_line, "{\\fs%d}", tags[i].data1); 209 break; 210 211 case 'p': 212 if (tags[i].data1 == 0) 213 av_bprintf(new_line, "{\\an8}"); 214 break; 215 216 case 'o': 217 av_bprintf(new_line, "{\\pos(%d,%d)}", 218 tags[i].data1, tags[i].data2); 219 break; 220 } 221 if (tags[i].persistent == MICRODVD_PERSISTENT_ON) 222 tags[i].persistent = MICRODVD_PERSISTENT_OPENED; 223 } 224} 225 226static void microdvd_close_no_persistent_tags(AVBPrint *new_line, 227 struct microdvd_tag *tags) 228{ 229 int i, sidx; 230 231 for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) { 232 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF) 233 continue; 234 switch (tags[i].key) { 235 236 case 'y': 237 for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--) 238 if (tags[i].data1 & (1 << sidx)) 239 av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]); 240 break; 241 242 case 'c': 243 av_bprintf(new_line, "{\\c}"); 244 break; 245 246 case 'f': 247 av_bprintf(new_line, "{\\fn}"); 248 break; 249 250 case 's': 251 av_bprintf(new_line, "{\\fs}"); 252 break; 253 } 254 tags[i].key = 0; 255 } 256} 257 258static int microdvd_decode_frame(AVCodecContext *avctx, 259 void *data, int *got_sub_ptr, AVPacket *avpkt) 260{ 261 AVSubtitle *sub = data; 262 AVBPrint new_line; 263 char c; 264 char *decoded_sub; 265 char *line = avpkt->data; 266 char *end = avpkt->data + avpkt->size; 267 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; 268 269 if (avpkt->size <= 0) 270 return avpkt->size; 271 272 /* To be removed later */ 273 if (sscanf(line, "{%*d}{%*[0123456789]}%c", &c) == 1 && 274 line[avpkt->size - 1] == '\n') { 275 av_log(avctx, AV_LOG_ERROR, "AVPacket is not clean (contains timing " 276 "information and a trailing line break). You need to upgrade " 277 "your libavformat or sanitize your packet.\n"); 278 return AVERROR_INVALIDDATA; 279 } 280 281 av_bprint_init(&new_line, 0, 2048); 282 283 // subtitle content 284 while (line < end && *line) { 285 286 // parse MicroDVD tags, and open them in ASS 287 line = microdvd_load_tags(tags, line); 288 microdvd_open_tags(&new_line, tags); 289 290 // simple copy until EOL or forced carriage return 291 while (line < end && *line && *line != '|') { 292 av_bprint_chars(&new_line, *line, 1); 293 line++; 294 } 295 296 // line split 297 if (line < end && *line == '|') { 298 microdvd_close_no_persistent_tags(&new_line, tags); 299 av_bprintf(&new_line, "\\N"); 300 line++; 301 } 302 } 303 if (new_line.len) { 304 av_bprintf(&new_line, "\r\n"); 305 306 av_bprint_finalize(&new_line, &decoded_sub); 307 if (*decoded_sub) { 308 int64_t start = avpkt->pts; 309 int64_t duration = avpkt->duration; 310 int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100}); 311 int ts_duration = duration != -1 ? 312 av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1; 313 ff_ass_add_rect(sub, decoded_sub, ts_start, ts_duration, 0); 314 } 315 av_free(decoded_sub); 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, alignment); 368} 369 370AVCodec ff_microdvd_decoder = { 371 .name = "microdvd", 372 .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"), 373 .type = AVMEDIA_TYPE_SUBTITLE, 374 .id = AV_CODEC_ID_MICRODVD, 375 .init = microdvd_init, 376 .decode = microdvd_decode_frame, 377}; 378