[FFmpeg-devel] [PATCH 2/2] lavc: add MicroDVD decoder.

Clément Bœsch ubitux at gmail.com
Sat Apr 14 21:47:22 CEST 2012


Ported from MPlayer.

FIXME: bump minor
---
 doc/general.texi         |    2 +-
 libavcodec/Makefile      |    1 +
 libavcodec/allcodecs.c   |    1 +
 libavcodec/microdvddec.c |  311 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 314 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/microdvddec.c

diff --git a/doc/general.texi b/doc/general.texi
index c633ba6..c2b7f21 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -837,7 +837,7 @@ performance on systems without hardware floating point support).
 @item SSA/ASS      @tab X @tab X @tab X @tab X
 @item DVB          @tab X @tab X @tab X @tab X
 @item DVD          @tab X @tab X @tab X @tab X
- at item MicroDVD     @tab X @tab X @tab   @tab
+ at item MicroDVD     @tab X @tab X @tab   @tab X
 @item PGS          @tab   @tab   @tab   @tab X
 @item SubRip (SRT) @tab X @tab X @tab X @tab X
 @item XSUB         @tab   @tab   @tab X @tab X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 9a0e75c..9587c2b 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -239,6 +239,7 @@ OBJS-$(CONFIG_MACE3_DECODER)           += mace.o
 OBJS-$(CONFIG_MACE6_DECODER)           += mace.o
 OBJS-$(CONFIG_MDEC_DECODER)            += mdec.o mpeg12.o mpeg12data.o \
                                           mpegvideo.o error_resilience.o
+OBJS-$(CONFIG_MICRODVD_DECODER)        += microdvddec.o ass.o
 OBJS-$(CONFIG_MIMIC_DECODER)           += mimic.o
 OBJS-$(CONFIG_MJPEG_DECODER)           += mjpegdec.o mjpeg.o
 OBJS-$(CONFIG_MJPEG_ENCODER)           += mjpegenc.o mjpeg.o           \
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 4a24d31..122e591 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -397,6 +397,7 @@ void avcodec_register_all(void)
     REGISTER_ENCDEC  (ASS, ass);
     REGISTER_ENCDEC  (DVBSUB, dvbsub);
     REGISTER_ENCDEC  (DVDSUB, dvdsub);
+    REGISTER_DECODER (MICRODVD, microdvd);
     REGISTER_DECODER (PGSSUB, pgssub);
     REGISTER_ENCDEC  (SRT, srt);
     REGISTER_ENCDEC  (XSUB, xsub);
diff --git a/libavcodec/microdvddec.c b/libavcodec/microdvddec.c
new file mode 100644
index 0000000..25b57b7
--- /dev/null
+++ b/libavcodec/microdvddec.c
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2012 Clément Bœsch
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * MicroDVD subtitle decoder
+ *
+ * Based on the specifications found here:
+ * https://trac.videolan.org/vlc/ticket/1825#comment:6
+ */
+
+#include "libavutil/avstring.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/bprint.h"
+#include "avcodec.h"
+#include "ass.h"
+
+static int indexof(const char *s, int c)
+{
+    char *f = strchr(s, c);
+    return f ? (f - s) : -1;
+}
+
+struct microdvd_tag {
+    char key;
+    int persistent;
+    uint32_t data1;
+    uint32_t data2;
+    char *data_string;
+    int data_string_len;
+};
+
+#define MICRODVD_PERSISTENT_OFF     0
+#define MICRODVD_PERSISTENT_ON      1
+#define MICRODVD_PERSISTENT_OPENED  2
+
+// Color, Font, Size, cHarset, stYle, Position, cOordinate
+#define MICRODVD_TAGS "cfshyYpo"
+
+static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
+{
+    int tag_index = indexof(MICRODVD_TAGS, tag.key);
+
+    if (tag_index < 0)
+        return;
+    memcpy(&tags[tag_index], &tag, sizeof(tag));
+}
+
+// italic, bold, underline, strike-through
+#define MICRODVD_STYLES "ibus"
+
+static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
+{
+    while (*s == '{') {
+        char *start = s;
+        char tag_char = *(s + 1);
+        struct microdvd_tag tag = {0};
+
+        if (!tag_char || *(s + 2) != ':')
+            break;
+        s += 3;
+
+        switch (tag_char) {
+
+        /* Style */
+        case 'Y':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 'y':
+            while (*s && *s != '}') {
+                int style_index = indexof(MICRODVD_STYLES, *s);
+
+                if (style_index >= 0)
+                    tag.data1 |= (1 << style_index);
+                s++;
+            }
+            if (*s != '}')
+                break;
+            /* We must distinguish persistent and non-persistent styles
+             * to handle this kind of style tags: {y:ib}{Y:us} */
+            tag.key = tag_char;
+            break;
+
+        /* Color */
+        case 'C':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 'c':
+            tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
+            if (*s != '}')
+                break;
+            tag.key = 'c';
+            break;
+
+        /* Font name */
+        case 'F':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 'f': {
+            int len = indexof(s, '}');
+            if (len < 0)
+                break;
+            tag.data_string = s;
+            tag.data_string_len = len;
+            s += len;
+            tag.key = 'f';
+            break;
+        }
+
+        /* Font size */
+        case 'S':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+        case 's':
+            tag.data1 = strtol(s, &s, 10);
+            if (*s != '}')
+                break;
+            tag.key = 's';
+            break;
+
+        /* Charset */
+        case 'H': {
+            //TODO: not yet handled, just parsed.
+            int len = indexof(s, '}');
+            if (len < 0)
+                break;
+            tag.data_string = s;
+            tag.data_string_len = len;
+            s += len;
+            tag.key = 'h';
+            break;
+        }
+
+        /* Position */
+        case 'P':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+            tag.data1 = (*s++ == '1');
+            if (*s != '}')
+                break;
+            tag.key = 'p';
+            break;
+
+        /* Coordinates */
+        case 'o':
+            tag.persistent = MICRODVD_PERSISTENT_ON;
+            tag.data1 = strtol(s, &s, 10);
+            if (*s != ',')
+                break;
+            s++;
+            tag.data2 = strtol(s, &s, 10);
+            if (*s != '}')
+                break;
+            tag.key = 'o';
+            break;
+
+        default:    /* Unknown tag, we consider it's text */
+            break;
+        }
+
+        if (tag.key == 0)
+            return start;
+
+        microdvd_set_tag(tags, tag);
+        s++;
+    }
+    return s;
+}
+
+static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
+{
+    int i, sidx;
+    for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
+        if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
+            continue;
+        switch (tags[i].key) {
+        case 'Y':
+        case 'y':
+            for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
+                if (tags[i].data1 & (1 << sidx))
+                    av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
+            break;
+
+        case 'c':
+            av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
+            break;
+
+        case 'f':
+            av_bprintf(new_line, "{\\fn%.*s}",
+                       tags[i].data_string_len, tags[i].data_string);
+            break;
+
+        case 's':
+            av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
+            break;
+
+        case 'p':
+            if (tags[i].data1 == 0)
+                av_bprintf(new_line, "{\\an8}");
+            break;
+
+        case 'o':
+            av_bprintf(new_line, "{\\pos(%d,%d)}",
+                       tags[i].data1, tags[i].data2);
+            break;
+        }
+        if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
+            tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
+    }
+}
+
+static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
+                                              struct microdvd_tag *tags)
+{
+    int i, sidx;
+
+    for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
+        if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
+            continue;
+        switch (tags[i].key) {
+
+        case 'y':
+            for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
+                if (tags[i].data1 & (1 << sidx))
+                    av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
+            break;
+
+        case 'c':
+            av_bprintf(new_line, "{\\c}");
+            break;
+
+        case 'f':
+            av_bprintf(new_line, "{\\fn}");
+            break;
+
+        case 's':
+            av_bprintf(new_line, "{\\fs}");
+            break;
+        }
+        tags[i].key = 0;
+    }
+}
+
+static int microdvd_decode_frame(AVCodecContext *avctx,
+                                 void *data, int *got_sub_ptr, AVPacket *avpkt)
+{
+    AVSubtitle *sub = data;
+    AVBPrint new_line;
+    char *decoded_sub;
+    char *line = avpkt->data;
+    char *end = avpkt->data + avpkt->size;
+    int64_t frame_start = avpkt->pts;
+    int64_t frame_end   = avpkt->pts + avpkt->duration;
+    int ts_start = av_rescale_q(frame_start, avctx->time_base, (AVRational){1,100});
+    int ts_end   = av_rescale_q(frame_end,   avctx->time_base, (AVRational){1,100});
+    struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
+
+    if (avpkt->size <= 0)
+        return avpkt->size;
+
+    av_bprint_init(&new_line, 0, 2048);
+
+    // skip {frame_start}{frame_end}
+    line = strchr(line, '}'); if (!line) goto end; line++;
+    line = strchr(line, '}'); if (!line) goto end; line++;
+
+    // parse MicroDVD tags, and open them in ASS
+    line = microdvd_load_tags(tags, line);
+    microdvd_open_tags(&new_line, tags);
+
+    // subtitle content
+    while (line < end && *line) {
+        if (*line == '|') {
+            microdvd_close_no_persistent_tags(&new_line, tags);
+            av_bprintf(&new_line, "\\N");
+        } else {
+            av_bprint_chars(&new_line, *line, 1);
+        }
+        line++;
+    }
+
+end:
+    av_bprint_finalize(&new_line, &decoded_sub);
+    if (*decoded_sub)
+        ff_ass_add_rect(sub, decoded_sub, ts_start, ts_end, 0);
+    av_free(decoded_sub);
+
+    *got_sub_ptr = sub->num_rects > 0;
+    return avpkt->size;
+}
+
+AVCodec ff_microdvd_decoder = {
+    .name         = "microdvd",
+    .long_name    = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
+    .type         = AVMEDIA_TYPE_SUBTITLE,
+    .id           = CODEC_ID_MICRODVD,
+    .init         = ff_ass_subtitle_header_default,
+    .decode       = microdvd_decode_frame,
+};
-- 
1.7.10



More information about the ffmpeg-devel mailing list