3 * Copyright (c) 2020 24i
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 * @see https://www.w3.org/TR/ttml1/
26 * @see https://www.w3.org/TR/ttml2/
27 * @see https://www.w3.org/TR/ttml-imsc/rec
30 #include "libavutil/avstring.h"
35 #include "libavcodec/ttmlenc.h"
36 #include "libavutil/internal.h"
39 PACKET_TYPE_PARAGRAPH
,
43 struct TTMLHeaderParameters
{
44 const char *tt_element_params
;
45 const char *pre_body_elements
;
48 typedef struct TTMLMuxContext
{
49 enum TTMLPacketType input_type
;
50 unsigned int document_written
;
53 static const char ttml_header_text
[] =
54 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
62 static const char ttml_footer_text
[] =
67 static void ttml_write_time(AVIOContext
*pb
, const char tag
[],
70 int64_t sec
, min
, hour
;
71 sec
= millisec
/ 1000;
72 millisec
-= 1000 * sec
;
78 avio_printf(pb
, "%s=\"%02"PRId64
":%02"PRId64
":%02"PRId64
".%03"PRId64
"\"",
79 tag
, hour
, min
, sec
, millisec
);
82 static int ttml_set_header_values_from_extradata(
83 AVCodecParameters
*par
, struct TTMLHeaderParameters
*header_params
)
85 size_t additional_data_size
=
86 par
->extradata_size
- TTMLENC_EXTRADATA_SIGNATURE_SIZE
;
88 (char *)par
->extradata
+ TTMLENC_EXTRADATA_SIGNATURE_SIZE
;
89 size_t value_size
= av_strnlen(value
, additional_data_size
);
90 struct TTMLHeaderParameters local_params
= { 0 };
92 if (!additional_data_size
) {
93 // simple case, we don't have to go through local_params and just
94 // set default fall-back values (for old extradata format).
95 header_params
->tt_element_params
= TTML_DEFAULT_NAMESPACING
;
96 header_params
->pre_body_elements
= "";
101 if (value_size
== additional_data_size
||
102 value
[value_size
] != '\0')
103 return AVERROR_INVALIDDATA
;
105 local_params
.tt_element_params
= value
;
107 additional_data_size
-= value_size
+ 1;
108 value
+= value_size
+ 1;
109 if (!additional_data_size
)
110 return AVERROR_INVALIDDATA
;
112 value_size
= av_strnlen(value
, additional_data_size
);
113 if (value_size
== additional_data_size
||
114 value
[value_size
] != '\0')
115 return AVERROR_INVALIDDATA
;
117 local_params
.pre_body_elements
= value
;
119 *header_params
= local_params
;
124 static int ttml_write_header(AVFormatContext
*ctx
)
126 TTMLMuxContext
*ttml_ctx
= ctx
->priv_data
;
127 AVStream
*st
= ctx
->streams
[0];
128 AVIOContext
*pb
= ctx
->pb
;
130 const AVDictionaryEntry
*lang
= av_dict_get(st
->metadata
, "language", NULL
,
132 const char *printed_lang
= (lang
&& lang
->value
) ? lang
->value
: "";
134 ttml_ctx
->document_written
= 0;
135 ttml_ctx
->input_type
= ff_is_ttml_stream_paragraph_based(st
->codecpar
) ?
136 PACKET_TYPE_PARAGRAPH
:
137 PACKET_TYPE_DOCUMENT
;
139 avpriv_set_pts_info(st
, 64, 1, 1000);
141 if (ttml_ctx
->input_type
== PACKET_TYPE_PARAGRAPH
) {
142 struct TTMLHeaderParameters header_params
;
143 int ret
= ttml_set_header_values_from_extradata(
144 st
->codecpar
, &header_params
);
146 av_log(ctx
, AV_LOG_ERROR
,
147 "Failed to parse TTML header values from extradata: "
148 "%s!\n", av_err2str(ret
));
152 avio_printf(pb
, ttml_header_text
,
153 header_params
.tt_element_params
,
155 header_params
.pre_body_elements
);
161 static int ttml_write_packet(AVFormatContext
*ctx
, AVPacket
*pkt
)
163 TTMLMuxContext
*ttml_ctx
= ctx
->priv_data
;
164 AVIOContext
*pb
= ctx
->pb
;
166 switch (ttml_ctx
->input_type
) {
167 case PACKET_TYPE_PARAGRAPH
:
168 // write out a paragraph element with the given contents.
169 avio_printf(pb
, " <p\n");
170 ttml_write_time(pb
, " begin", pkt
->pts
);
172 ttml_write_time(pb
, " end", pkt
->pts
+ pkt
->duration
);
173 avio_printf(pb
, ">");
174 avio_write(pb
, pkt
->data
, pkt
->size
);
175 avio_printf(pb
, "</p>\n");
177 case PACKET_TYPE_DOCUMENT
:
178 // dump the given document out as-is.
179 if (ttml_ctx
->document_written
) {
180 av_log(ctx
, AV_LOG_ERROR
,
181 "Attempting to write multiple TTML documents into a "
182 "single document! The XML specification forbids this "
183 "as there has to be a single root tag.\n");
184 return AVERROR(EINVAL
);
186 avio_write(pb
, pkt
->data
, pkt
->size
);
187 ttml_ctx
->document_written
= 1;
190 av_log(ctx
, AV_LOG_ERROR
,
191 "Internal error: invalid TTML input packet type: %d!\n",
192 ttml_ctx
->input_type
);
199 static int ttml_write_trailer(AVFormatContext
*ctx
)
201 TTMLMuxContext
*ttml_ctx
= ctx
->priv_data
;
202 AVIOContext
*pb
= ctx
->pb
;
204 if (ttml_ctx
->input_type
== PACKET_TYPE_PARAGRAPH
)
205 avio_printf(pb
, ttml_footer_text
);
210 const FFOutputFormat ff_ttml_muxer
= {
212 .p
.long_name
= NULL_IF_CONFIG_SMALL("TTML subtitle"),
213 .p
.extensions
= "ttml",
214 .p
.mime_type
= "text/ttml",
215 .priv_data_size
= sizeof(TTMLMuxContext
),
216 .p
.flags
= AVFMT_GLOBALHEADER
| AVFMT_VARIABLE_FPS
|
218 .p
.video_codec
= AV_CODEC_ID_NONE
,
219 .p
.audio_codec
= AV_CODEC_ID_NONE
,
220 .p
.subtitle_codec
= AV_CODEC_ID_TTML
,
221 .flags_internal
= FF_OFMT_FLAG_MAX_ONE_OF_EACH
|
222 FF_OFMT_FLAG_ONLY_DEFAULT_CODECS
,
223 .write_header
= ttml_write_header
,
224 .write_packet
= ttml_write_packet
,
225 .write_trailer
= ttml_write_trailer
,