1cabdff1aSopenharmony_ci/* 2cabdff1aSopenharmony_ci * TTML subtitle muxer 3cabdff1aSopenharmony_ci * Copyright (c) 2020 24i 4cabdff1aSopenharmony_ci * 5cabdff1aSopenharmony_ci * This file is part of FFmpeg. 6cabdff1aSopenharmony_ci * 7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or 8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public 9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either 10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version. 11cabdff1aSopenharmony_ci * 12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful, 13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of 14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15cabdff1aSopenharmony_ci * Lesser General Public License for more details. 16cabdff1aSopenharmony_ci * 17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public 18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software 19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20cabdff1aSopenharmony_ci */ 21cabdff1aSopenharmony_ci 22cabdff1aSopenharmony_ci/** 23cabdff1aSopenharmony_ci * @file 24cabdff1aSopenharmony_ci * TTML subtitle muxer 25cabdff1aSopenharmony_ci * @see https://www.w3.org/TR/ttml1/ 26cabdff1aSopenharmony_ci * @see https://www.w3.org/TR/ttml2/ 27cabdff1aSopenharmony_ci * @see https://www.w3.org/TR/ttml-imsc/rec 28cabdff1aSopenharmony_ci */ 29cabdff1aSopenharmony_ci 30cabdff1aSopenharmony_ci#include "libavutil/avstring.h" 31cabdff1aSopenharmony_ci#include "avformat.h" 32cabdff1aSopenharmony_ci#include "internal.h" 33cabdff1aSopenharmony_ci#include "ttmlenc.h" 34cabdff1aSopenharmony_ci#include "libavcodec/ttmlenc.h" 35cabdff1aSopenharmony_ci#include "libavutil/internal.h" 36cabdff1aSopenharmony_ci 37cabdff1aSopenharmony_cienum TTMLPacketType { 38cabdff1aSopenharmony_ci PACKET_TYPE_PARAGRAPH, 39cabdff1aSopenharmony_ci PACKET_TYPE_DOCUMENT, 40cabdff1aSopenharmony_ci}; 41cabdff1aSopenharmony_ci 42cabdff1aSopenharmony_cistruct TTMLHeaderParameters { 43cabdff1aSopenharmony_ci const char *tt_element_params; 44cabdff1aSopenharmony_ci const char *pre_body_elements; 45cabdff1aSopenharmony_ci}; 46cabdff1aSopenharmony_ci 47cabdff1aSopenharmony_citypedef struct TTMLMuxContext { 48cabdff1aSopenharmony_ci enum TTMLPacketType input_type; 49cabdff1aSopenharmony_ci unsigned int document_written; 50cabdff1aSopenharmony_ci} TTMLMuxContext; 51cabdff1aSopenharmony_ci 52cabdff1aSopenharmony_cistatic const char ttml_header_text[] = 53cabdff1aSopenharmony_ci"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 54cabdff1aSopenharmony_ci"<tt\n" 55cabdff1aSopenharmony_ci"%s" 56cabdff1aSopenharmony_ci" xml:lang=\"%s\">\n" 57cabdff1aSopenharmony_ci"%s" 58cabdff1aSopenharmony_ci" <body>\n" 59cabdff1aSopenharmony_ci" <div>\n"; 60cabdff1aSopenharmony_ci 61cabdff1aSopenharmony_cistatic const char ttml_footer_text[] = 62cabdff1aSopenharmony_ci" </div>\n" 63cabdff1aSopenharmony_ci" </body>\n" 64cabdff1aSopenharmony_ci"</tt>\n"; 65cabdff1aSopenharmony_ci 66cabdff1aSopenharmony_cistatic void ttml_write_time(AVIOContext *pb, const char tag[], 67cabdff1aSopenharmony_ci int64_t millisec) 68cabdff1aSopenharmony_ci{ 69cabdff1aSopenharmony_ci int64_t sec, min, hour; 70cabdff1aSopenharmony_ci sec = millisec / 1000; 71cabdff1aSopenharmony_ci millisec -= 1000 * sec; 72cabdff1aSopenharmony_ci min = sec / 60; 73cabdff1aSopenharmony_ci sec -= 60 * min; 74cabdff1aSopenharmony_ci hour = min / 60; 75cabdff1aSopenharmony_ci min -= 60 * hour; 76cabdff1aSopenharmony_ci 77cabdff1aSopenharmony_ci avio_printf(pb, "%s=\"%02"PRId64":%02"PRId64":%02"PRId64".%03"PRId64"\"", 78cabdff1aSopenharmony_ci tag, hour, min, sec, millisec); 79cabdff1aSopenharmony_ci} 80cabdff1aSopenharmony_ci 81cabdff1aSopenharmony_cistatic int ttml_set_header_values_from_extradata( 82cabdff1aSopenharmony_ci AVCodecParameters *par, struct TTMLHeaderParameters *header_params) 83cabdff1aSopenharmony_ci{ 84cabdff1aSopenharmony_ci size_t additional_data_size = 85cabdff1aSopenharmony_ci par->extradata_size - TTMLENC_EXTRADATA_SIGNATURE_SIZE; 86cabdff1aSopenharmony_ci char *value = 87cabdff1aSopenharmony_ci (char *)par->extradata + TTMLENC_EXTRADATA_SIGNATURE_SIZE; 88cabdff1aSopenharmony_ci size_t value_size = av_strnlen(value, additional_data_size); 89cabdff1aSopenharmony_ci struct TTMLHeaderParameters local_params = { 0 }; 90cabdff1aSopenharmony_ci 91cabdff1aSopenharmony_ci if (!additional_data_size) { 92cabdff1aSopenharmony_ci // simple case, we don't have to go through local_params and just 93cabdff1aSopenharmony_ci // set default fall-back values (for old extradata format). 94cabdff1aSopenharmony_ci header_params->tt_element_params = ttml_default_namespacing; 95cabdff1aSopenharmony_ci header_params->pre_body_elements = ""; 96cabdff1aSopenharmony_ci 97cabdff1aSopenharmony_ci return 0; 98cabdff1aSopenharmony_ci } 99cabdff1aSopenharmony_ci 100cabdff1aSopenharmony_ci if (value_size == additional_data_size || 101cabdff1aSopenharmony_ci value[value_size] != '\0') 102cabdff1aSopenharmony_ci return AVERROR_INVALIDDATA; 103cabdff1aSopenharmony_ci 104cabdff1aSopenharmony_ci local_params.tt_element_params = value; 105cabdff1aSopenharmony_ci 106cabdff1aSopenharmony_ci additional_data_size -= value_size + 1; 107cabdff1aSopenharmony_ci value += value_size + 1; 108cabdff1aSopenharmony_ci if (!additional_data_size) 109cabdff1aSopenharmony_ci return AVERROR_INVALIDDATA; 110cabdff1aSopenharmony_ci 111cabdff1aSopenharmony_ci value_size = av_strnlen(value, additional_data_size); 112cabdff1aSopenharmony_ci if (value_size == additional_data_size || 113cabdff1aSopenharmony_ci value[value_size] != '\0') 114cabdff1aSopenharmony_ci return AVERROR_INVALIDDATA; 115cabdff1aSopenharmony_ci 116cabdff1aSopenharmony_ci local_params.pre_body_elements = value; 117cabdff1aSopenharmony_ci 118cabdff1aSopenharmony_ci *header_params = local_params; 119cabdff1aSopenharmony_ci 120cabdff1aSopenharmony_ci return 0; 121cabdff1aSopenharmony_ci} 122cabdff1aSopenharmony_ci 123cabdff1aSopenharmony_cistatic int ttml_write_header(AVFormatContext *ctx) 124cabdff1aSopenharmony_ci{ 125cabdff1aSopenharmony_ci TTMLMuxContext *ttml_ctx = ctx->priv_data; 126cabdff1aSopenharmony_ci ttml_ctx->document_written = 0; 127cabdff1aSopenharmony_ci 128cabdff1aSopenharmony_ci if (ctx->nb_streams != 1 || 129cabdff1aSopenharmony_ci ctx->streams[0]->codecpar->codec_id != AV_CODEC_ID_TTML) { 130cabdff1aSopenharmony_ci av_log(ctx, AV_LOG_ERROR, "Exactly one TTML stream is required!\n"); 131cabdff1aSopenharmony_ci return AVERROR(EINVAL); 132cabdff1aSopenharmony_ci } 133cabdff1aSopenharmony_ci 134cabdff1aSopenharmony_ci { 135cabdff1aSopenharmony_ci AVStream *st = ctx->streams[0]; 136cabdff1aSopenharmony_ci AVIOContext *pb = ctx->pb; 137cabdff1aSopenharmony_ci 138cabdff1aSopenharmony_ci AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 139cabdff1aSopenharmony_ci 0); 140cabdff1aSopenharmony_ci const char *printed_lang = (lang && lang->value) ? lang->value : ""; 141cabdff1aSopenharmony_ci 142cabdff1aSopenharmony_ci ttml_ctx->input_type = ff_is_ttml_stream_paragraph_based(st->codecpar) ? 143cabdff1aSopenharmony_ci PACKET_TYPE_PARAGRAPH : 144cabdff1aSopenharmony_ci PACKET_TYPE_DOCUMENT; 145cabdff1aSopenharmony_ci 146cabdff1aSopenharmony_ci avpriv_set_pts_info(st, 64, 1, 1000); 147cabdff1aSopenharmony_ci 148cabdff1aSopenharmony_ci if (ttml_ctx->input_type == PACKET_TYPE_PARAGRAPH) { 149cabdff1aSopenharmony_ci struct TTMLHeaderParameters header_params; 150cabdff1aSopenharmony_ci int ret = ttml_set_header_values_from_extradata( 151cabdff1aSopenharmony_ci st->codecpar, &header_params); 152cabdff1aSopenharmony_ci if (ret < 0) { 153cabdff1aSopenharmony_ci av_log(ctx, AV_LOG_ERROR, 154cabdff1aSopenharmony_ci "Failed to parse TTML header values from extradata: " 155cabdff1aSopenharmony_ci "%s!\n", av_err2str(ret)); 156cabdff1aSopenharmony_ci return ret; 157cabdff1aSopenharmony_ci } 158cabdff1aSopenharmony_ci 159cabdff1aSopenharmony_ci avio_printf(pb, ttml_header_text, 160cabdff1aSopenharmony_ci header_params.tt_element_params, 161cabdff1aSopenharmony_ci printed_lang, 162cabdff1aSopenharmony_ci header_params.pre_body_elements); 163cabdff1aSopenharmony_ci } 164cabdff1aSopenharmony_ci } 165cabdff1aSopenharmony_ci 166cabdff1aSopenharmony_ci return 0; 167cabdff1aSopenharmony_ci} 168cabdff1aSopenharmony_ci 169cabdff1aSopenharmony_cistatic int ttml_write_packet(AVFormatContext *ctx, AVPacket *pkt) 170cabdff1aSopenharmony_ci{ 171cabdff1aSopenharmony_ci TTMLMuxContext *ttml_ctx = ctx->priv_data; 172cabdff1aSopenharmony_ci AVIOContext *pb = ctx->pb; 173cabdff1aSopenharmony_ci 174cabdff1aSopenharmony_ci switch (ttml_ctx->input_type) { 175cabdff1aSopenharmony_ci case PACKET_TYPE_PARAGRAPH: 176cabdff1aSopenharmony_ci // write out a paragraph element with the given contents. 177cabdff1aSopenharmony_ci avio_printf(pb, " <p\n"); 178cabdff1aSopenharmony_ci ttml_write_time(pb, " begin", pkt->pts); 179cabdff1aSopenharmony_ci avio_w8(pb, '\n'); 180cabdff1aSopenharmony_ci ttml_write_time(pb, " end", pkt->pts + pkt->duration); 181cabdff1aSopenharmony_ci avio_printf(pb, ">"); 182cabdff1aSopenharmony_ci avio_write(pb, pkt->data, pkt->size); 183cabdff1aSopenharmony_ci avio_printf(pb, "</p>\n"); 184cabdff1aSopenharmony_ci break; 185cabdff1aSopenharmony_ci case PACKET_TYPE_DOCUMENT: 186cabdff1aSopenharmony_ci // dump the given document out as-is. 187cabdff1aSopenharmony_ci if (ttml_ctx->document_written) { 188cabdff1aSopenharmony_ci av_log(ctx, AV_LOG_ERROR, 189cabdff1aSopenharmony_ci "Attempting to write multiple TTML documents into a " 190cabdff1aSopenharmony_ci "single document! The XML specification forbids this " 191cabdff1aSopenharmony_ci "as there has to be a single root tag.\n"); 192cabdff1aSopenharmony_ci return AVERROR(EINVAL); 193cabdff1aSopenharmony_ci } 194cabdff1aSopenharmony_ci avio_write(pb, pkt->data, pkt->size); 195cabdff1aSopenharmony_ci ttml_ctx->document_written = 1; 196cabdff1aSopenharmony_ci break; 197cabdff1aSopenharmony_ci default: 198cabdff1aSopenharmony_ci av_log(ctx, AV_LOG_ERROR, 199cabdff1aSopenharmony_ci "Internal error: invalid TTML input packet type: %d!\n", 200cabdff1aSopenharmony_ci ttml_ctx->input_type); 201cabdff1aSopenharmony_ci return AVERROR_BUG; 202cabdff1aSopenharmony_ci } 203cabdff1aSopenharmony_ci 204cabdff1aSopenharmony_ci return 0; 205cabdff1aSopenharmony_ci} 206cabdff1aSopenharmony_ci 207cabdff1aSopenharmony_cistatic int ttml_write_trailer(AVFormatContext *ctx) 208cabdff1aSopenharmony_ci{ 209cabdff1aSopenharmony_ci TTMLMuxContext *ttml_ctx = ctx->priv_data; 210cabdff1aSopenharmony_ci AVIOContext *pb = ctx->pb; 211cabdff1aSopenharmony_ci 212cabdff1aSopenharmony_ci if (ttml_ctx->input_type == PACKET_TYPE_PARAGRAPH) 213cabdff1aSopenharmony_ci avio_printf(pb, ttml_footer_text); 214cabdff1aSopenharmony_ci 215cabdff1aSopenharmony_ci return 0; 216cabdff1aSopenharmony_ci} 217cabdff1aSopenharmony_ci 218cabdff1aSopenharmony_ciconst AVOutputFormat ff_ttml_muxer = { 219cabdff1aSopenharmony_ci .name = "ttml", 220cabdff1aSopenharmony_ci .long_name = NULL_IF_CONFIG_SMALL("TTML subtitle"), 221cabdff1aSopenharmony_ci .extensions = "ttml", 222cabdff1aSopenharmony_ci .mime_type = "text/ttml", 223cabdff1aSopenharmony_ci .priv_data_size = sizeof(TTMLMuxContext), 224cabdff1aSopenharmony_ci .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS | 225cabdff1aSopenharmony_ci AVFMT_TS_NONSTRICT, 226cabdff1aSopenharmony_ci .subtitle_codec = AV_CODEC_ID_TTML, 227cabdff1aSopenharmony_ci .write_header = ttml_write_header, 228cabdff1aSopenharmony_ci .write_packet = ttml_write_packet, 229cabdff1aSopenharmony_ci .write_trailer = ttml_write_trailer, 230cabdff1aSopenharmony_ci}; 231