diff mbox series

[FFmpeg-devel,v7,4/5] avcodec: add TTML encoder

Message ID 20210304174830.53798-5-jeebjp@gmail.com
State Accepted
Commit 18713d22a2001321f9917fa4c7735f62563ec0a1
Headers show
Series Initial implementation of TTML encoding/muxing
Related show

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Jan Ekström March 4, 2021, 5:48 p.m. UTC
From: Jan Ekström <jan.ekstrom@24i.com>

Enables encoding of other subtitle formats into TTML paragraphs.

Signed-off-by: Jan Ekström <jan.ekstrom@24i.com>
---
 Changelog                 |   1 +
 doc/general_contents.texi |   1 +
 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/ttmlenc.c      | 210 ++++++++++++++++++++++++++++++++++++++
 libavcodec/ttmlenc.h      |  28 +++++
 libavcodec/version.h      |   4 +-
 7 files changed, 244 insertions(+), 2 deletions(-)
 create mode 100644 libavcodec/ttmlenc.c
 create mode 100644 libavcodec/ttmlenc.h
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index 9e7f67cc19..43b6abb82b 100644
--- a/Changelog
+++ b/Changelog
@@ -78,6 +78,7 @@  version <next>:
 - Simbiosis IMX decoder
 - Simbiosis IMX demuxer
 - Digital Pictures SGA demuxer and decoders
+- TTML subtitle encoder
 
 
 version 4.3:
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index 6acdf441d6..ac02f33c6f 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1352,6 +1352,7 @@  performance on systems without hardware floating point support).
 @item SubViewer v1     @tab   @tab X @tab   @tab X
 @item SubViewer        @tab   @tab X @tab   @tab X
 @item TED Talks captions @tab @tab X @tab   @tab X
+@item TTML             @tab   @tab   @tab X @tab
 @item VobSub (IDX+SUB) @tab   @tab X @tab   @tab X
 @item VPlayer          @tab   @tab X @tab   @tab X
 @item WebVTT           @tab X @tab X @tab X @tab X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b7e456b59f..d1b1125a30 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -672,6 +672,7 @@  OBJS-$(CONFIG_TSCC_DECODER)            += tscc.o msrledec.o
 OBJS-$(CONFIG_TSCC2_DECODER)           += tscc2.o
 OBJS-$(CONFIG_TTA_DECODER)             += tta.o ttadata.o ttadsp.o
 OBJS-$(CONFIG_TTA_ENCODER)             += ttaenc.o ttaencdsp.o ttadata.o
+OBJS-$(CONFIG_TTML_ENCODER)            += ttmlenc.o ass_split.o
 OBJS-$(CONFIG_TWINVQ_DECODER)          += twinvqdec.o twinvq.o metasound_data.o
 OBJS-$(CONFIG_TXD_DECODER)             += txd.o
 OBJS-$(CONFIG_ULTI_DECODER)            += ulti.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index a04faead16..2e9a3581de 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -691,6 +691,7 @@  extern AVCodec ff_subviewer_decoder;
 extern AVCodec ff_subviewer1_decoder;
 extern AVCodec ff_text_encoder;
 extern AVCodec ff_text_decoder;
+extern AVCodec ff_ttml_encoder;
 extern AVCodec ff_vplayer_decoder;
 extern AVCodec ff_webvtt_encoder;
 extern AVCodec ff_webvtt_decoder;
diff --git a/libavcodec/ttmlenc.c b/libavcodec/ttmlenc.c
new file mode 100644
index 0000000000..3972b4368c
--- /dev/null
+++ b/libavcodec/ttmlenc.c
@@ -0,0 +1,210 @@ 
+/*
+ * TTML subtitle encoder
+ * Copyright (c) 2020 24i
+ *
+ * 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
+ * TTML subtitle encoder
+ * @see https://www.w3.org/TR/ttml1/
+ * @see https://www.w3.org/TR/ttml2/
+ * @see https://www.w3.org/TR/ttml-imsc/rec
+ */
+
+#include "avcodec.h"
+#include "internal.h"
+#include "libavutil/avstring.h"
+#include "libavutil/bprint.h"
+#include "libavutil/internal.h"
+#include "ass_split.h"
+#include "ass.h"
+#include "ttmlenc.h"
+
+typedef struct {
+    AVCodecContext *avctx;
+    ASSSplitContext *ass_ctx;
+    AVBPrint buffer;
+} TTMLContext;
+
+static void ttml_text_cb(void *priv, const char *text, int len)
+{
+    TTMLContext *s = priv;
+    AVBPrint cur_line = { 0 };
+    AVBPrint *buffer = &s->buffer;
+
+    av_bprint_init(&cur_line, len, AV_BPRINT_SIZE_UNLIMITED);
+
+    av_bprint_append_data(&cur_line, text, len);
+    if (!av_bprint_is_complete(&cur_line)) {
+        av_log(s->avctx, AV_LOG_ERROR,
+               "Failed to move the current subtitle dialog to AVBPrint!\n");
+        av_bprint_finalize(&cur_line, NULL);
+        return;
+    }
+
+
+    av_bprint_escape(buffer, cur_line.str, NULL, AV_ESCAPE_MODE_XML,
+                     0);
+
+    av_bprint_finalize(&cur_line, NULL);
+}
+
+static void ttml_new_line_cb(void *priv, int forced)
+{
+    TTMLContext *s = priv;
+
+    av_bprintf(&s->buffer, "<br/>");
+}
+
+static const ASSCodesCallbacks ttml_callbacks = {
+    .text             = ttml_text_cb,
+    .new_line         = ttml_new_line_cb,
+};
+
+static int ttml_encode_frame(AVCodecContext *avctx, uint8_t *buf,
+                             int bufsize, const AVSubtitle *sub)
+{
+    TTMLContext *s = avctx->priv_data;
+    ASSDialog *dialog;
+    int i;
+
+    av_bprint_clear(&s->buffer);
+
+    for (i=0; i<sub->num_rects; i++) {
+        const char *ass = sub->rects[i]->ass;
+
+        if (sub->rects[i]->type != SUBTITLE_ASS) {
+            av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
+            return AVERROR(EINVAL);
+        }
+
+#if FF_API_ASS_TIMING
+        if (!strncmp(ass, "Dialogue: ", 10)) {
+            int num;
+            dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
+
+            for (; dialog && num--; dialog++) {
+                int ret = ff_ass_split_override_codes(&ttml_callbacks, s,
+                                                      dialog->text);
+                int log_level = (ret != AVERROR_INVALIDDATA ||
+                                 avctx->err_recognition & AV_EF_EXPLODE) ?
+                                AV_LOG_ERROR : AV_LOG_WARNING;
+
+                if (ret < 0) {
+                    av_log(avctx, log_level,
+                           "Splitting received ASS dialog failed: %s\n",
+                           av_err2str(ret));
+
+                    if (log_level == AV_LOG_ERROR)
+                        return ret;
+                }
+            }
+        } else {
+#endif
+            dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
+            if (!dialog)
+                return AVERROR(ENOMEM);
+
+            {
+                int ret = ff_ass_split_override_codes(&ttml_callbacks, s,
+                                                      dialog->text);
+                int log_level = (ret != AVERROR_INVALIDDATA ||
+                                 avctx->err_recognition & AV_EF_EXPLODE) ?
+                                AV_LOG_ERROR : AV_LOG_WARNING;
+
+                if (ret < 0) {
+                    av_log(avctx, log_level,
+                           "Splitting received ASS dialog text %s failed: %s\n",
+                           dialog->text,
+                           av_err2str(ret));
+
+                    if (log_level == AV_LOG_ERROR) {
+                        ff_ass_free_dialog(&dialog);
+                        return ret;
+                    }
+                }
+
+                ff_ass_free_dialog(&dialog);
+            }
+#if FF_API_ASS_TIMING
+        }
+#endif
+    }
+
+    if (!av_bprint_is_complete(&s->buffer))
+        return AVERROR(ENOMEM);
+    if (!s->buffer.len)
+        return 0;
+
+    // force null-termination, so in case our destination buffer is
+    // too small, the return value is larger than bufsize minus null.
+    if (av_strlcpy(buf, s->buffer.str, bufsize) > bufsize - 1) {
+        av_log(avctx, AV_LOG_ERROR, "Buffer too small for TTML event.\n");
+        return AVERROR_BUFFER_TOO_SMALL;
+    }
+
+    return s->buffer.len;
+}
+
+static av_cold int ttml_encode_close(AVCodecContext *avctx)
+{
+    TTMLContext *s = avctx->priv_data;
+
+    ff_ass_split_free(s->ass_ctx);
+
+    av_bprint_finalize(&s->buffer, NULL);
+
+    return 0;
+}
+
+static av_cold int ttml_encode_init(AVCodecContext *avctx)
+{
+    TTMLContext *s = avctx->priv_data;
+
+    s->avctx   = avctx;
+
+    if (!(s->ass_ctx = ff_ass_split(avctx->subtitle_header))) {
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (!(avctx->extradata = av_mallocz(TTMLENC_EXTRADATA_SIGNATURE_SIZE +
+                                        1 + AV_INPUT_BUFFER_PADDING_SIZE))) {
+        return AVERROR(ENOMEM);
+    }
+
+    avctx->extradata_size = TTMLENC_EXTRADATA_SIGNATURE_SIZE;
+    memcpy(avctx->extradata, TTMLENC_EXTRADATA_SIGNATURE,
+           TTMLENC_EXTRADATA_SIGNATURE_SIZE);
+
+    av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    return 0;
+}
+
+AVCodec ff_ttml_encoder = {
+    .name           = "ttml",
+    .long_name      = NULL_IF_CONFIG_SMALL("TTML subtitle"),
+    .type           = AVMEDIA_TYPE_SUBTITLE,
+    .id             = AV_CODEC_ID_TTML,
+    .priv_data_size = sizeof(TTMLContext),
+    .init           = ttml_encode_init,
+    .encode_sub     = ttml_encode_frame,
+    .close          = ttml_encode_close,
+    .capabilities   = FF_CODEC_CAP_INIT_CLEANUP,
+};
diff --git a/libavcodec/ttmlenc.h b/libavcodec/ttmlenc.h
new file mode 100644
index 0000000000..c1dd5ec990
--- /dev/null
+++ b/libavcodec/ttmlenc.h
@@ -0,0 +1,28 @@ 
+/*
+ * TTML subtitle encoder shared functionality
+ * Copyright (c) 2020 24i
+ *
+ * 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
+ */
+
+#ifndef AVCODEC_TTMLENC_H
+#define AVCODEC_TTMLENC_H
+
+#define TTMLENC_EXTRADATA_SIGNATURE "lavc-ttmlenc"
+#define TTMLENC_EXTRADATA_SIGNATURE_SIZE (sizeof(TTMLENC_EXTRADATA_SIGNATURE) - 1)
+
+#endif /* AVCODEC_TTMLENC_H */
diff --git a/libavcodec/version.h b/libavcodec/version.h
index dd15ae341e..d7ccf9943e 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -28,8 +28,8 @@ 
 #include "libavutil/version.h"
 
 #define LIBAVCODEC_VERSION_MAJOR  58
-#define LIBAVCODEC_VERSION_MINOR 128
-#define LIBAVCODEC_VERSION_MICRO 101
+#define LIBAVCODEC_VERSION_MINOR 129
+#define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \