LCOV - code coverage report
Current view: top level - libavcodec - microdvddec.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 147 210 70.0 %
Date: 2017-12-15 18:13:28 Functions: 8 8 100.0 %

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

Generated by: LCOV version 1.13