[FFmpeg-devel] [PATCH v4 4/4] {avcodec, avformat}: add TTML encoder and muxer
Anton Khirnov
anton at khirnov.net
Fri Feb 19 15:45:53 EET 2021
Quoting Jan Ekström (2021-01-22 12:20:58)
> From: Jan Ekström <jan.ekstrom at 24i.com>
>
> Enables encoding of other subtitle formats into TTML and writing
> them out as such documents.
>
> Signed-off-by: Jan Ekström <jan.ekstrom at 24i.com>
> ---
> Changelog | 1 +
> doc/general_contents.texi | 1 +
> libavcodec/Makefile | 1 +
> libavcodec/allcodecs.c | 1 +
> libavcodec/ttmlenc.c | 179 +++++++++++++++++++++++++++++++++++++
> libavcodec/version.h | 4 +-
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/ttmlenc.c | 166 ++++++++++++++++++++++++++++++++++
> libavformat/version.h | 4 +-
> tests/fate/subtitles.mak | 3 +
> tests/ref/fate/sub-ttmlenc | 122 +++++++++++++++++++++++++
> 12 files changed, 480 insertions(+), 4 deletions(-)
> create mode 100644 libavcodec/ttmlenc.c
> create mode 100644 libavformat/ttmlenc.c
> create mode 100644 tests/ref/fate/sub-ttmlenc
>
> diff --git a/Changelog b/Changelog
> index 0b27c15122..9a6aeb4128 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -56,6 +56,7 @@ version <next>:
> - shufflepixels filter
> - tmidequalizer filter
> - estdif filter
> +- TTML subtitle encoder and muxer
>
>
> version 4.3:
> diff --git a/doc/general_contents.texi b/doc/general_contents.texi
> index 443e8ed8d1..d799382f84 100644
> --- a/doc/general_contents.texi
> +++ b/doc/general_contents.texi
> @@ -1334,6 +1334,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
> + at item TTML @tab X @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 5ce3ee0ec9..d26e0264de 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -669,6 +669,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 f00d524747..81d20c44ec 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -686,6 +686,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..a9b1411467
> --- /dev/null
> +++ b/libavcodec/ttmlenc.c
> +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++) {
> + ff_ass_split_override_codes(&ttml_callbacks, s, dialog->text);
Check for error?
> + }
> + } else {
> +#endif
> + dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
> + if (!dialog)
> + return AVERROR(ENOMEM);
> +
> + ff_ass_split_override_codes(&ttml_callbacks, s, dialog->text);
> + 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) {
Is this guaranteed to not overread s->buffer.str?
> + 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)
> +{
> + int ret = AVERROR_BUG;
> + TTMLContext *s = avctx->priv_data;
> +
> + s->avctx = avctx;
> +
> + if (!(s->ass_ctx = ff_ass_split(avctx->subtitle_header))) {
> + ret = AVERROR_INVALIDDATA;
> + goto failure;
> + }
> +
> + if (!(avctx->extradata = av_malloc(4 + AV_INPUT_BUFFER_PADDING_SIZE))) {
> + ret = AVERROR(ENOMEM);
> + goto failure;
> + }
> + avctx->extradata_size = 4;
> +
> + av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
> +
> + return 0;
> +
> +failure:
> + ff_ass_split_free(s->ass_ctx);
> + av_bprint_finalize(&s->buffer, NULL);
Mark the encoder as FF_CODEC_CAP_INIT_CLEANUP and drop this?
> +
> + return ret;
> +}
> +
> +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,
> +};
> +static int ttml_write_header(AVFormatContext *ctx)
> +{
> + TTMLMuxContext *ttml_ctx = ctx->priv_data;
> + ttml_ctx->document_written = 0;
> +
> + if (ctx->nb_streams != 1 ||
> + ctx->streams[0]->codecpar->codec_id != AV_CODEC_ID_TTML) {
> + av_log(ctx, AV_LOG_ERROR, "Exactly one TTML stream is required!\n");
> + return AVERROR(EINVAL);
> + }
> +
> + {
> + AVStream *st = ctx->streams[0];
> + AVIOContext *pb = ctx->pb;
> +
> + AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL,
> + 0);
> + const char *printed_lang = (lang && lang->value) ? lang->value : "";
> +
> + // Not perfect, but decide whether the packet is a document or not
> + // by the existence of extradata.
I'd prefer extradata to contain something. Doesn't matter much what
exactly, as long as it's unique enough.
> + ttml_ctx->input_type = st->codecpar->extradata ?
> + PACKET_TYPE_PARAGRAPH :
> + PACKET_TYPE_DOCUMENT;
> +
> + avpriv_set_pts_info(st, 64, 1, 1000);
> +
> + if (ttml_ctx->input_type == PACKET_TYPE_PARAGRAPH)
> + avio_printf(pb, ttml_header_text, printed_lang);
> + }
> +
> + return 0;
> +}
> +
> +static int ttml_write_packet(AVFormatContext *ctx, AVPacket *pkt)
> +{
> + TTMLMuxContext *ttml_ctx = ctx->priv_data;
> + AVIOContext *pb = ctx->pb;
> +
> + switch (ttml_ctx->input_type) {
> + case PACKET_TYPE_PARAGRAPH:
> + // write out a paragraph element with the given contents.
> + avio_printf(pb, " <p\n");
> + ttml_write_time(pb, " begin", pkt->pts);
> + avio_w8(pb, '\n');
> + ttml_write_time(pb, " end", pkt->pts + pkt->duration);
> + avio_printf(pb, ">");
> + avio_write(pb, pkt->data, pkt->size);
> + avio_printf(pb, "</p>\n");
> + break;
> + case PACKET_TYPE_DOCUMENT:
> + // dump the given document out as-is.
> + if (ttml_ctx->document_written) {
> + av_log(ctx, AV_LOG_ERROR,
> + "Attempting to write multiple TTML documents into a "
> + "single document! The XML specification forbids this "
> + "as there has to be a single root tag.\n");
> + return AVERROR(EINVAL);
> + }
> + avio_write(pb, pkt->data, pkt->size);
> + ttml_ctx->document_written = 1;
> + break;
> + default:
> + av_log(ctx, AV_LOG_ERROR, "Invalid TTML input packet type!\n");
> + return AVERROR(EINVAL);
This is an internal error, so should be AVERROR_BUG or an assert.
--
Anton Khirnov
More information about the ffmpeg-devel
mailing list