Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  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. static int indexof(const char *s, int c)
  36. {
  37.     char *f = strchr(s, c);
  38.     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. static 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.  
  69. static 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.  
  184. static 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.  
  226. static 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.  
  258. static 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.  
  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.  
  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.  
  370. AVCodec 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.