Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2000-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / WebVTT stream to file filter
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : #include <gpac/filters.h>
27 : #include <gpac/constants.h>
28 : #include <gpac/bitstream.h>
29 : #include <gpac/webvtt.h>
30 : #include <gpac/internal/media_dev.h>
31 :
32 :
33 : typedef struct
34 : {
35 : //opts
36 : Bool exporter, merge;
37 :
38 : //only one input pid declared
39 : GF_FilterPid *ipid;
40 : //only one output pid declared
41 : GF_FilterPid *opid;
42 :
43 : u32 codecid;
44 : Bool first;
45 :
46 : GF_Fraction64 duration;
47 : char *dcd;
48 :
49 : GF_BitStream *bs_w;
50 : u8 *cues_buffer;
51 : u32 cues_buffer_size;
52 :
53 : GF_WebVTTParser *parser;
54 :
55 : } GF_WebVTTMxCtx;
56 :
57 : static void vttmx_write_cue(void *ctx, GF_WebVTTCue *cue);
58 :
59 4 : GF_Err vttmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
60 : {
61 : const GF_PropertyValue *p;
62 4 : GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter);
63 :
64 4 : if (is_remove) {
65 0 : ctx->ipid = NULL;
66 0 : if (ctx->opid) {
67 0 : gf_filter_pid_remove(ctx->opid);
68 0 : ctx->opid = NULL;
69 : }
70 : return GF_OK;
71 : }
72 4 : if (! gf_filter_pid_check_caps(pid))
73 : return GF_NOT_SUPPORTED;
74 :
75 4 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
76 4 : if (!p) return GF_NOT_SUPPORTED;
77 4 : ctx->codecid = p->value.uint;
78 :
79 4 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
80 4 : if (!p) return GF_NOT_SUPPORTED;
81 4 : if (ctx->dcd && !strcmp(ctx->dcd, p->value.string)) return GF_OK;
82 4 : if (ctx->dcd) gf_free(ctx->dcd);
83 :
84 4 : if ((p->type==GF_PROP_DATA) || (p->type==GF_PROP_DATA_NO_COPY)) {
85 4 : ctx->dcd = gf_malloc(p->value.data.size+1);
86 4 : memcpy(ctx->dcd, p->value.data.ptr, p->value.data.size);
87 4 : ctx->dcd[p->value.data.size]=0;
88 : } else {
89 0 : ctx->dcd = gf_strdup(p->value.string);
90 : }
91 4 : if (!ctx->opid) {
92 4 : ctx->opid = gf_filter_pid_new(filter);
93 4 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) );
94 4 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("vtt") );
95 4 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("text/vtt") );
96 4 : ctx->first = GF_TRUE;
97 :
98 4 : if (ctx->exporter) {
99 3 : GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", gf_codecid_name(ctx->codecid)));
100 : }
101 :
102 : }
103 4 : ctx->ipid = pid;
104 4 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION);
105 4 : if (p) ctx->duration = p->value.lfrac;
106 :
107 4 : if (ctx->parser) gf_webvtt_parser_del(ctx->parser);
108 4 : ctx->parser = NULL;
109 4 : if (ctx->merge) {
110 1 : ctx->parser = gf_webvtt_parser_new();
111 1 : gf_webvtt_parser_cue_callback(ctx->parser, vttmx_write_cue, ctx);
112 : }
113 4 : gf_filter_pid_set_framing_mode(pid, GF_TRUE);
114 4 : return GF_OK;
115 : }
116 :
117 96 : static void vttmx_timestamp_dump(GF_BitStream *bs, GF_WebVTTTimestamp *ts, Bool dump_hour, Bool write_srt)
118 : {
119 : char szTS[200];
120 96 : szTS[0] = 0;
121 96 : if (dump_hour) {
122 14 : sprintf(szTS, "%02u:", ts->hour);
123 14 : gf_bs_write_data(bs, szTS, (u32) strlen(szTS) );
124 : }
125 96 : sprintf(szTS, "%02u:%02u%c%03u", ts->min, ts->sec, write_srt ? ',' : '.', ts->ms);
126 96 : gf_bs_write_data(bs, szTS, (u32) strlen(szTS) );
127 96 : }
128 :
129 48 : void webvtt_write_cue(GF_BitStream *bs, GF_WebVTTCue *cue, Bool write_srt)
130 : {
131 : Bool write_hour = GF_FALSE;
132 48 : if (!cue) return;
133 48 : if (!write_srt && cue->pre_text) {
134 0 : gf_bs_write_data(bs, cue->pre_text, (u32) strlen(cue->pre_text));
135 0 : gf_bs_write_data(bs, "\n\n", 2);
136 : }
137 48 : if (!write_srt && cue->id) {
138 24 : u32 len = (u32) strlen(cue->id) ;
139 24 : gf_bs_write_data(bs, cue->id, len);
140 24 : if (len && (cue->id[len-1]!='\n'))
141 24 : gf_bs_write_data(bs, "\n", 1);
142 : }
143 :
144 48 : if (gf_opts_get_bool("core", "webvtt-hours")) write_hour = GF_TRUE;
145 48 : else if (cue->start.hour || cue->end.hour) write_hour = GF_TRUE;
146 48 : else if (write_srt) write_hour = GF_TRUE;
147 :
148 48 : vttmx_timestamp_dump(bs, &cue->start, write_hour, write_srt);
149 48 : gf_bs_write_data(bs, " --> ", 5);
150 48 : vttmx_timestamp_dump(bs, &cue->end, write_hour, write_srt);
151 :
152 48 : if (!write_srt && cue->settings) {
153 9 : gf_bs_write_data(bs, " ", 1);
154 9 : gf_bs_write_data(bs, cue->settings, (u32) strlen(cue->settings));
155 : }
156 48 : gf_bs_write_data(bs, "\n", 1);
157 48 : if (cue->text)
158 48 : gf_bs_write_data(bs, cue->text, (u32) strlen(cue->text));
159 :
160 48 : if (!write_srt)
161 41 : gf_bs_write_data(bs, "\n\n", 2);
162 : else
163 7 : gf_bs_write_data(bs, "\n", 1);
164 :
165 48 : if (!write_srt && cue->post_text) {
166 0 : gf_bs_write_data(bs, cue->post_text, (u32) strlen(cue->post_text));
167 0 : gf_bs_write_data(bs, "\n\n", 2);
168 : }
169 : }
170 :
171 6 : static void vttmx_write_cue(void *udta, GF_WebVTTCue *cue)
172 : {
173 : GF_WebVTTMxCtx *ctx = (GF_WebVTTMxCtx *)udta;
174 36 : webvtt_write_cue(ctx->bs_w, cue, GF_FALSE);
175 6 : }
176 :
177 1 : void vttmx_parser_flush(GF_WebVTTMxCtx *ctx)
178 : {
179 : u8 *output;
180 1 : u64 duration = ctx->duration.num;
181 1 : duration *= 1000;
182 1 : duration /= ctx->duration.den;
183 :
184 1 : if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
185 1 : else gf_bs_reassign_buffer(ctx->bs_w, ctx->cues_buffer, ctx->cues_buffer_size);
186 :
187 1 : gf_webvtt_parser_finalize(ctx->parser, duration);
188 :
189 1 : if (gf_bs_get_position(ctx->bs_w)) {
190 : GF_FilterPacket *dst_pck;
191 : u32 size;
192 1 : gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->cues_buffer, &size, &ctx->cues_buffer_size);
193 1 : dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
194 1 : if (dst_pck) {
195 1 : memcpy(output, ctx->cues_buffer, size);
196 :
197 1 : gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
198 :
199 1 : gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE);
200 1 : gf_filter_pck_send(dst_pck);
201 : }
202 : }
203 :
204 1 : gf_webvtt_parser_del(ctx->parser);
205 1 : ctx->parser = NULL;
206 :
207 1 : }
208 56 : GF_Err vttmx_process(GF_Filter *filter)
209 : {
210 56 : GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter);
211 : GF_FilterPacket *pck, *dst_pck;
212 : u8 *data, *output;
213 : u64 start_ts, end_ts;
214 : u32 i, pck_size, size, timescale;
215 : GF_List *cues;
216 :
217 56 : pck = gf_filter_pid_get_packet(ctx->ipid);
218 56 : if (!pck) {
219 5 : if (gf_filter_pid_is_eos(ctx->ipid)) {
220 5 : gf_filter_pid_set_eos(ctx->opid);
221 5 : if (ctx->parser) {
222 1 : vttmx_parser_flush(ctx);
223 : }
224 : return GF_EOS;
225 : }
226 : return GF_OK;
227 : }
228 :
229 51 : data = (char *) gf_filter_pck_get_data(pck, &pck_size);
230 :
231 51 : if (ctx->first && ctx->dcd) {
232 4 : size = (u32) strlen(ctx->dcd)+2;
233 4 : dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
234 4 : if (!dst_pck) return GF_OUT_OF_MEM;
235 :
236 4 : memcpy(output, ctx->dcd, size-2);
237 4 : output[size-2] = '\n';
238 4 : output[size-1] = '\n';
239 4 : gf_filter_pck_merge_properties(pck, dst_pck);
240 4 : gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
241 4 : gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE);
242 4 : ctx->first = GF_FALSE;
243 4 : gf_filter_pck_send(dst_pck);
244 : }
245 :
246 51 : if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
247 47 : else gf_bs_reassign_buffer(ctx->bs_w, ctx->cues_buffer, ctx->cues_buffer_size);
248 :
249 51 : start_ts = gf_filter_pck_get_cts(pck);
250 51 : end_ts = start_ts + gf_filter_pck_get_duration(pck);
251 51 : start_ts *= 1000;
252 51 : end_ts *= 1000;
253 51 : timescale = gf_filter_pck_get_timescale(pck);
254 51 : if (!timescale) timescale=1000;
255 51 : start_ts /= timescale;
256 51 : end_ts /= timescale;
257 :
258 51 : cues = gf_webvtt_parse_cues_from_data(data, pck_size, start_ts, end_ts);
259 51 : if (ctx->parser) {
260 11 : gf_webvtt_merge_cues(ctx->parser, start_ts, cues);
261 : } else {
262 30 : for (i = 0; i < gf_list_count(cues); i++) {
263 30 : GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, i);
264 : vttmx_write_cue(ctx, cue);
265 30 : gf_webvtt_cue_del(cue);
266 : }
267 : }
268 51 : gf_list_del(cues);
269 :
270 51 : gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->cues_buffer, &size, &ctx->cues_buffer_size);
271 51 : if (size) {
272 32 : dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
273 32 : if (!dst_pck) return GF_OUT_OF_MEM;
274 :
275 32 : memcpy(output, ctx->cues_buffer, size);
276 32 : gf_filter_pck_merge_properties(pck, dst_pck);
277 32 : gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
278 :
279 32 : gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE);
280 32 : ctx->first = GF_FALSE;
281 :
282 32 : gf_filter_pck_send(dst_pck);
283 : }
284 :
285 51 : if (ctx->exporter) {
286 40 : u64 ts = gf_filter_pck_get_cts(pck);
287 40 : timescale = gf_filter_pck_get_timescale(pck);
288 40 : gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale);
289 : }
290 :
291 51 : gf_filter_pid_drop_packet(ctx->ipid);
292 :
293 51 : return GF_OK;
294 : }
295 :
296 4 : static void vttmx_finalize(GF_Filter *filter)
297 : {
298 4 : GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter);
299 4 : if (ctx->bs_w) gf_bs_del(ctx->bs_w);
300 4 : if (ctx->dcd) gf_free(ctx->dcd);
301 4 : if (ctx->cues_buffer) gf_free(ctx->cues_buffer);
302 :
303 4 : if (ctx->parser) gf_webvtt_parser_del(ctx->parser);
304 4 : }
305 :
306 : static const GF_FilterCapability WebVTTMxCaps[] =
307 : {
308 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
309 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_WEBVTT),
310 : CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
311 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
312 : CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "vtt"),
313 : CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "subtitle/vtt|text/vtt"),
314 : };
315 :
316 :
317 : #define OFFS(_n) #_n, offsetof(GF_WebVTTMxCtx, _n)
318 : static const GF_FilterArgs WebVTTMxArgs[] =
319 : {
320 : { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
321 : { OFFS(merge), "merge VTT cue if needed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
322 : {0}
323 : };
324 :
325 :
326 : GF_FilterRegister WebVTTMxRegister = {
327 : .name = "writevtt",
328 : GF_FS_SET_DESCRIPTION("WebVTT writer")
329 : GF_FS_SET_HELP("This filter converts a single stream to a WebVTT output file.")
330 : .private_size = sizeof(GF_WebVTTMxCtx),
331 : .args = WebVTTMxArgs,
332 : .finalize = vttmx_finalize,
333 : SETCAPS(WebVTTMxCaps),
334 : .configure_pid = vttmx_configure_pid,
335 : .process = vttmx_process
336 : };
337 :
338 :
339 2877 : const GF_FilterRegister *vttmx_register(GF_FilterSession *session)
340 : {
341 2877 : return &WebVTTMxRegister;
342 : }
|