From patchwork Fri Mar 17 01:55:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aidan X-Patchwork-Id: 40704 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:d046:b0:cd:afd7:272c with SMTP id hv6csp179132pzb; Thu, 16 Mar 2023 18:56:47 -0700 (PDT) X-Google-Smtp-Source: AK7set9pPTSAY20t9pWks8xiAOBFM7hgX2OsJAj/ebsk2J+hIUmTuvKI5J5TkbSe4+u8tudT0TeM X-Received: by 2002:a17:907:72c1:b0:920:388c:5c60 with SMTP id du1-20020a17090772c100b00920388c5c60mr15013761ejc.41.1679018206992; Thu, 16 Mar 2023 18:56:46 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1679018206; cv=none; d=google.com; s=arc-20160816; b=oxL4tp0J6A25leZnlIvLexWjKDOrIr0xy6eC67YkfkPxYrILmFHtfJiOGbrWKr4RLg TbS/ieYOUi8Wxb6AwS1C8X4JksgE5eofFkdFMnhWdOwjGOfKLyzm2S9cBOV6a0ZcO/37 5jLbk8JkZbRq0PAAviNkfwZjpfC3Pqvw/aOhV6RulrW2ODRvmonrlEAa7hpcCZj/Vy4k yaKpUHNhS3O6jNcHPqLB+hbwavOGb/nvYySUPv1nx1I1eFH/7QGoNJZMkluAmw9f56IB 6bAm7vk57qQOiXs4+16pZOoyw9JOFHrcp+sUlvmLayP6PZmN0DjSnVxqZf9ZjvnLo+Jt xoiQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to; bh=bVeuQNcSomYwPxdvA1ZQZX2qygiqHLt4sFz2EoqnLEY=; b=fmY1Qn/UB87ictfwqN6HJzORioX0xD7ncV/s0dknH4H91B+6P1VpARIHOnFLfhWD8+ DenHMXyQF/SQxDm18TmTZTIUwPGjqrLWRZUt9ocOBAcxDSg2Ajn1qOizMNRUNNymSQPm HqEBLOttWqTe01zlq3ww03OdazsZYAx1bRNHQmbg8skbn5lfGW720AuB0HqALgqp/b00 iQXWORFS3KYG1nnUy1jRlQqlP0Bep5Eaq4jsUOHHcegm7+44wQAiea60h6NBNw6y2w9O iJYRMhqBLPobGYvsdFnCkcLkFdINjvXRk8LSH2qDlpBBa1f0a/UtWAnaLE4+a1/6860b LuqA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=C134B7lu; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id g26-20020a1709063b1a00b0092ee9c27ba3si1018205ejf.477.2023.03.16.18.56.46; Thu, 16 Mar 2023 18:56:46 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=C134B7lu; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 55BEA68C137; Fri, 17 Mar 2023 03:56:42 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id CF8CB68838B for ; Fri, 17 Mar 2023 03:56:35 +0200 (EET) Received: by mail-pj1-f53.google.com with SMTP id cn6so3515285pjb.2 for ; Thu, 16 Mar 2023 18:56:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; t=1679018193; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=f/JTW9sjOymZ0LOHKLY/PpSgU8EbGX2a35fMxZoCBzc=; b=C134B7luITVpYbIB29WXV2P7t/fypADjQRLWJNx9rwA1t88yemHkpNrZ/kp5Hk7Jac 8CFnABbm3cO3vHu7/VFhF2C+lwxv7Y91nqEKcJr6S+ODhBRIcvOoR3Jb4rhnoBGkYFJy a6ALWYBrz+J/7Nb01TaN9G62J1VW8CzBULiO3vHSk0PLza79/mW1AJB0XrY1cOb4qike 1WMml1TtUOtLF8LeMsqM+Ws7ZodAlQcfc0f8Qg33bni2Tt1ZV+tUAuP0iPw3rGveeRcJ C7VblZHeFVnVkWsV1ceIKR0XzVaBz6ZGP8GZjyx2Z+bEbqouyXwH5D15uXKoA11xiJ+L nWYw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1679018193; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=f/JTW9sjOymZ0LOHKLY/PpSgU8EbGX2a35fMxZoCBzc=; b=JNB419xOwEwBDrC4b+Y09Pk/+SyCJMBHdTc0kJ4XuTuIrYJs53cqlMsAtguXR15C+X nGena6mRMgxytR7r2QYmzEczQFElamKeq22MPPnWvelFOS9POTDZonsslg5fA7Ekic1n MRdrRIXMYT8UWjzl1JBSmRZO7KNi+Ycgb7PWxOBcrJ+tuLbmg4y0/q3LD9pJ7rA+8dSl wSX8nIa/9MDqD3Pt7UQMGsN95AvWJv85x+Fo5t76D7cor2Ps+3pe0Tw5R5NUHHw5tG/J p1zUXGePqqc51h9eB/1oDPcIkna0GCrcKgOoD0AnbgsQO3FwxytBUHv3sbtap7Ywr3gD /7IQ== X-Gm-Message-State: AO0yUKXDwTmgA6T1XI13ERaX0/72sps2BbE9o30p0Wken2xwzGuNU19M 0UpLelIEloY87h1N5rfeIr6i47KZ269dzQ== X-Received: by 2002:a05:6a20:4f22:b0:c7:8779:416d with SMTP id gi34-20020a056a204f2200b000c78779416dmr5678954pzb.58.1679018193483; Thu, 16 Mar 2023 18:56:33 -0700 (PDT) Received: from localhost.localdomain ([193.29.61.196]) by smtp.gmail.com with ESMTPSA id y11-20020a62b50b000000b005d61829db4fsm312894pfe.168.2023.03.16.18.56.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 Mar 2023 18:56:33 -0700 (PDT) From: TheDaChicken To: ffmpeg-devel@ffmpeg.org Date: Thu, 16 Mar 2023 18:55:26 -0700 Message-Id: <20230317015527.425-1-steve.rock.pet@gmail.com> X-Mailer: git-send-email 2.37.1.windows.1 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 1/2] avformat/ttml: Add demuxer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Aidan Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: KtrTta8+NXdu From: Aidan Requested: #4859 Signed-off-by: Aidan Vaughan --- libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/ttmldec.c | 432 +++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 4 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 libavformat/ttmldec.c diff --git a/libavformat/Makefile b/libavformat/Makefile index 47bbbbfb2a..5f29e05618 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -576,6 +576,7 @@ OBJS-$(CONFIG_TRUEHD_DEMUXER) += rawdec.o mlpdec.o OBJS-$(CONFIG_TRUEHD_MUXER) += rawenc.o OBJS-$(CONFIG_TTA_DEMUXER) += tta.o apetag.o img2.o OBJS-$(CONFIG_TTA_MUXER) += ttaenc.o apetag.o img2.o +OBJS-$(CONFIG_TTML_DEMUXER) += ttmldec.o OBJS-$(CONFIG_TTML_MUXER) += ttmlenc.o OBJS-$(CONFIG_TTY_DEMUXER) += tty.o sauce.o OBJS-$(CONFIG_TY_DEMUXER) += ty.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index cb5b69e9cd..0280592f7b 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -459,6 +459,7 @@ extern const AVInputFormat ff_truehd_demuxer; extern const FFOutputFormat ff_truehd_muxer; extern const AVInputFormat ff_tta_demuxer; extern const FFOutputFormat ff_tta_muxer; +extern const AVInputFormat ff_ttml_demuxer; extern const FFOutputFormat ff_ttml_muxer; extern const AVInputFormat ff_txd_demuxer; extern const AVInputFormat ff_tty_demuxer; diff --git a/libavformat/ttmldec.c b/libavformat/ttmldec.c new file mode 100644 index 0000000000..e63a2d04c8 --- /dev/null +++ b/libavformat/ttmldec.c @@ -0,0 +1,432 @@ +/* + * TTML subtitle demuxer + * Copyright (c) 2023 Aidan Vaughan (TheDaChicken) + * + * 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 demuxer + * @see https://www.w3.org/TR/ttml1/ + * @see https://www.w3.org/TR/ttml2/ + * @see https://www.w3.org/TR/ttml-imsc/rec + */ + +#include +#include "avformat.h" +#include "ttmlenc.h" +#include "internal.h" +#include "subtitles.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" + +typedef struct { + const AVClass *class; + FFDemuxSubtitlesQueue q; + int kind; + const char* lang; + uint8_t* header; + int header_size; + int cea_608_drawing; +} TTMLContext; + +/* https://www.w3.org/TR/2018/REC-ttml2-20181108/#timing-value-time-expression */ +static int64_t read_ts(const char *s) +{ + /* Clock-time format */ + int hh, mm, ss, ms; + if (sscanf(s, "%u:%u:%u.%u", &hh, &mm, &ss, &ms) == 4) return (hh*3600LL + mm*60LL + ss) * 1000LL + ms; + if (sscanf(s, "%u:%u:%u:%u", &hh, &mm, &ss, &ms) == 4) return (hh*3600LL + mm*60LL + ss) * 1000LL + ms; + /* ^^^ may contain seconds not as a fraction (.) ???? */ + if (sscanf(s, "%u:%u.%u", &mm, &ss, &ms) == 3) return ( mm*60LL + ss) * 1000LL + ms; + return AV_NOPTS_VALUE; +} + +static int dump_ns(AVBPrint* buf, xmlNsPtr cur, int another) { + if (cur->prefix != NULL) { + av_bprintf(buf, "xmlns:%s", cur->prefix); + } + else + av_bprintf(buf, "xmlns"); + av_bprintf(buf, "=\""); + av_bprint_escape(buf, cur->href, NULL, + AV_ESCAPE_MODE_XML, + AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); + av_bprintf(buf, "\""); + if(another) + av_bprintf(buf, " "); + return 0; +} + +static int dump_attr(AVBPrint* buf, xmlNsPtr ns, const char* name, const xmlChar *val, int another) { + if(!val) + return 0; + + if ((ns != NULL) && (ns->prefix != NULL)) { + av_bprintf(buf, "%s:", ns->prefix); + } + av_bprintf(buf, "%s=\"", name); + av_bprint_escape(buf, val, NULL, + AV_ESCAPE_MODE_XML, + AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); + av_bprintf(buf, "\""); + if(another) + av_bprintf(buf, " "); + return 0; +} + +static int parse_body(AVFormatContext *s, const char *url, xmlDoc *doc, xmlNodePtr body) +{ + int ret = 0; + TTMLContext *ttml = s->priv_data; + xmlNodePtr div = NULL; + xmlNodePtr p_node = NULL; + xmlAttrPtr body_attr = body->properties; + xmlChar* val = NULL; + xmlBufferPtr p_buf; + AVBPrint p_attr_buf; + AVBPrint body_attr_buf; + + div = xmlFirstElementChild(body); + if(!av_stristr(div->name, "div")) /* div must exist for proper ttml */ + { + av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - wrong node in body name[%s]\n", + url, div->name); + return AVERROR_INVALIDDATA; + } + + av_bprint_init(&p_attr_buf, 0, INT_MAX); + av_bprint_init(&body_attr_buf, 0, INT_MAX); + + while (body_attr) { + val = xmlGetProp(body, body_attr->name); + dump_attr(&body_attr_buf, body_attr->ns, body_attr->name, val, body_attr->next != NULL); + body_attr = body_attr->next; + xmlFree(val); + } + + p_buf = xmlBufferCreate(); + p_node = xmlFirstElementChild(div); + while(p_node) { + AVPacket *sub; + xmlNodePtr child = NULL; + xmlAttrPtr p_attr = p_node->properties; + int64_t ts_start, ts_end = AV_NOPTS_VALUE, ts_dur = AV_NOPTS_VALUE; + + if (strncmp(p_node->name, "p", 1) != 0) { + av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - wrong subtitle node name[%s]\n", + url, div->name); + ret = AVERROR_INVALIDDATA; + goto cleanup; + } + + dump_attr(&p_attr_buf, p_attr->ns, "default", body_attr_buf.str, p_attr->next != NULL); + while (p_attr) { + val = xmlGetProp(p_node, p_attr->name); + + if(!val) + { + av_log(s, AV_LOG_WARNING, "parse_body p_attr->name = %s val is NULL\n", p_attr->name); + p_attr = p_attr->next; + continue; + } + + if (!strncmp(p_attr->name, "begin", 5)) { + ts_start = read_ts(val); /* doc: https://www.w3.org/TR/2018/REC-ttml2-20181108/#timing-attribute-begin */ + } else if (!strncmp(p_attr->name, "end", 3)) { + ts_end = read_ts(val); /* doc: https://www.w3.org/TR/2018/REC-ttml2-20181108/#timing-attribute-end */ + } else if (!strncmp(p_attr->name, "dur", 3)) { + ts_dur = read_ts(val); /* doc: https://www.w3.org/TR/2018/REC-ttml2-20181108/#timing-attribute-dur */ + } else { + dump_attr(&p_attr_buf, p_attr->ns, p_attr->name, val, p_attr->next != NULL); + } + + p_attr = p_attr->next; + xmlFree(val); + } + + /* couldn't find pts in p tag */ + if(ts_start == AV_NOPTS_VALUE || (ts_end == AV_NOPTS_VALUE && ts_dur == AV_NOPTS_VALUE)) { + av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - no timestamp line[%d]\n", + url, p_node->line); + ret = AVERROR_INVALIDDATA; + goto cleanup; + } + + /* dump data to packet for decoding */ + child = p_node->children; + while (child != NULL) { + if(!strncmp(child->name, "metadata", 8) && !ttml->cea_608_drawing) + { /* check if the "metadata" tag is meant to contain cea-608 rows/cols */ + val = xmlGetProp(child, "ccrow"); + if(val) { + xmlFree(val); + goto next; + } + } + + ret = xmlNodeDump(p_buf, doc, child, 0, 0); + if (ret < 0) { + ret = AVERROR_INVALIDDATA; + goto cleanup; + } + next: + child = child->next; + } + + /* create packet */ + sub = ff_subtitles_queue_insert(&ttml->q, xmlBufferContent(p_buf), xmlBufferLength(p_buf), 0); + if (!sub) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + sub->pts = ts_start; + if(ts_dur != AV_NOPTS_VALUE) + sub->duration = ts_dur; + else + sub->duration = ts_end - ts_start; + + if (p_attr_buf.len) + { + /* Keep the properties of p styles */ + uint8_t *side_data_buf = av_packet_new_side_data(sub, AV_PKT_DATA_WEBVTT_SETTINGS, + p_attr_buf.len); + if(!side_data_buf) + goto cleanup; + memcpy(side_data_buf, p_attr_buf.str, p_attr_buf.len); + } + + p_node = xmlNextElementSibling(p_node); + xmlBufferEmpty(p_buf); + av_bprint_clear(&p_attr_buf); + } + + ff_subtitles_queue_finalize(s, &ttml->q); + + cleanup: + av_bprint_finalize(&p_attr_buf, NULL); + av_bprint_finalize(&body_attr_buf, NULL); + xmlBufferFree(p_buf); + return ret; +} + +static int parse_ttml(AVFormatContext *s, AVIOContext *in, const char *url) { + TTMLContext *ttml = s->priv_data; + int ret = 0; + AVBPrint buf; + xmlDoc *doc = NULL; + xmlNodePtr root_element = NULL; + xmlNodePtr child_node = NULL; + xmlAttrPtr attr_ptr = NULL; + xmlNsPtr ns_ptr = NULL; + xmlChar *val = NULL; + + av_bprint_init(&buf, 0, INT_MAX); + + ret = avio_read_to_bprint(in, &buf, SIZE_MAX); + if (ret < 0 || !avio_feof(in)) { + av_log(s, AV_LOG_ERROR, "Unable to parse '%s'\n", url); + if (ret == 0) + ret = AVERROR_INVALIDDATA; + goto cleanup; + } + + LIBXML_TEST_VERSION + + doc = xmlReadMemory(buf.str, buf.len, url, NULL, 0); + root_element = xmlDocGetRootElement(doc); + + if (!root_element) { + ret = AVERROR_INVALIDDATA; + av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing root node\n", url); + goto cleanup; + } + + if (root_element->type != XML_ELEMENT_NODE || av_strcasecmp(root_element->name, "TT")) { + ret = AVERROR_INVALIDDATA; + av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - wrong root node name[%s] type[%d]\n", + url, root_element->name, (int) root_element->type); + goto cleanup; + } + + av_bprint_clear(&buf); + + child_node = xmlFirstElementChild(root_element); + while(child_node) /* traverse through root nodes to get head, body */ + { + if(!strncmp(child_node->name, "head", 4)) + { + xmlBufferPtr header_buf = xmlBufferCreate(); + if(!header_buf) + return AVERROR(ENOMEM); + + /* read and create extra-data */ + /* extra-data first contains namespace (attrs) of tt */ + attr_ptr = root_element->properties; + while (attr_ptr) { + val = xmlGetProp(root_element, attr_ptr->name); + if (!strncmp(attr_ptr->name, "lang", 4)) { + ttml->lang = av_strdup(val); + } else { + dump_attr(&buf, attr_ptr->ns, attr_ptr->name, val, attr_ptr->next != NULL); + } + attr_ptr = attr_ptr->next; + xmlFree(val); + } + + /* take definitions */ + ns_ptr = root_element->nsDef; + if(ns_ptr) + av_bprintf(&buf, " "); + while (ns_ptr) { + dump_ns(&buf, ns_ptr, ns_ptr->next != NULL); + ns_ptr = ns_ptr->next; + } + + av_bprint_chars(&buf, '\0', 1); + + /* dump head element containing the styles */ + xmlNodeDump(header_buf, doc, child_node, 0,0); + + av_bprint_append_data(&buf, xmlBufferContent(header_buf), xmlBufferLength(header_buf)); + av_bprint_chars(&buf, '\n', 1); /* new line due to head */ + av_bprint_chars(&buf, '\0', 1); + + ttml->header = av_malloc(buf.len); + if(!ttml->header) + return AVERROR(ENOMEM); + + /* copy */ + ttml->header_size = buf.len; + memcpy(ttml->header, buf.str, buf.len); + + xmlBufferFree(header_buf); + } + else if(!strncmp(child_node->name, "body", 4) && + (ret = parse_body(s, url, doc, child_node)) < 0) + { + av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - body\n", url); + goto cleanup; + } + child_node = xmlNextElementSibling(child_node); + } + + + cleanup: + xmlFreeDoc(doc); + xmlCleanupParser(); + av_bprint_finalize(&buf, NULL); + return ret; +} + +static int ttml_probe(const AVProbeData *p) +{ + if (!av_stristr(p->buf, "buf, "xmlns=\"http://www.w3.org/ns/ttml\"") /* current */ || + av_stristr(p->buf, "xmlns=\"http://www.w3.org/2006/\"")) /* for unstable old urls */ + return AVPROBE_SCORE_MAX; + + return 0; +} + +static int ttml_read_header(AVFormatContext *s) +{ + const size_t base_extradata_size = TTMLENC_EXTRADATA_SIGNATURE_SIZE + 1 + + AV_INPUT_BUFFER_PADDING_SIZE; + TTMLContext *ttml = s->priv_data; + AVStream *st; + int ret = 0; + + if((ret = parse_ttml(s, s->pb, s->url)) < 0) + return ret; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + avpriv_set_pts_info(st, 64, 1, 1000); + st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codecpar->codec_id = AV_CODEC_ID_TTML; + st->codecpar->extradata = av_malloc(ttml->header_size + + base_extradata_size); + if (!st->codecpar->extradata) + return AVERROR(ENOMEM); + + memcpy(st->codecpar->extradata, TTMLENC_EXTRADATA_SIGNATURE, + TTMLENC_EXTRADATA_SIGNATURE_SIZE); + memcpy(st->codecpar->extradata + TTMLENC_EXTRADATA_SIGNATURE_SIZE, + ttml->header, ttml->header_size); + st->codecpar->extradata_size = ttml->header_size + base_extradata_size; + + av_dict_set(&st->metadata, "language", ttml->lang, 0); + + return ret; +} + +static int ttml_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + TTMLContext *ttml = s->priv_data; + return ff_subtitles_queue_read_packet(&ttml->q, pkt); +} + +static int ttml_read_seek(AVFormatContext *s, int stream_index, + int64_t min_ts, int64_t ts, int64_t max_ts, int flags) +{ + TTMLContext *ttml = s->priv_data; + return ff_subtitles_queue_seek(&ttml->q, s, stream_index, + min_ts, ts, max_ts, flags); +} + +static int ttml_read_close(AVFormatContext *s) +{ + TTMLContext *ttml = s->priv_data; + ff_subtitles_queue_clean(&ttml->q); + av_free(ttml->header); + return 0; +} + +#define OFFSET(x) offsetof(TTMLContext, x) +#define KIND_FLAGS AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_DECODING_PARAM + +static const AVOption options[] = { + { "cea_608_drawing", "Enable unofficial ttml cea-608 support", OFFSET(kind), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, KIND_FLAGS}, + { NULL } +}; + +static const AVClass ttml_demuxer_class = { + .class_name = "TTML demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const AVInputFormat ff_ttml_demuxer = { + .name = "ttml", + .long_name = NULL_IF_CONFIG_SMALL("TTML subtitle"), + .priv_data_size = sizeof(TTMLContext), + .priv_class = &ttml_demuxer_class, + .flags_internal = FF_FMT_INIT_CLEANUP, + .read_probe = ttml_probe, + .read_header = ttml_read_header, + .read_packet = ttml_read_packet, + .read_seek2 = ttml_read_seek, + .read_close = ttml_read_close, + .extensions = "ttml", + .mime_type = "application/ttml+xml", +}; diff --git a/libavformat/version.h b/libavformat/version.h index af7d0a1024..e2634b85ae 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFORMAT_VERSION_MINOR 4 +#define LIBAVFORMAT_VERSION_MINOR 5 #define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \