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