1 /*
2  *          GPAC - Multimedia Framework C SDK
3  *
4  *          Authors: Cyril Concolato
5  *          Copyright (c) Telecom ParisTech 2000-2012
6  *                  All rights reserved
7  *
8  *  This file is part of GPAC / ISO Media File Format sub-project
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/list.h>
27 #include <gpac/internal/isomedia_dev.h>
28 #include <gpac/internal/media_dev.h>
29 #include <gpac/webvtt.h>
30 #include <gpac/constants.h>
31 
32 #ifndef GPAC_DISABLE_VTT
33 
34 struct _webvtt_sample
35 {
36 	u64 start;
37 	u64 end;
38 	GF_List *cues;
39 };
40 
41 
42 #ifndef GPAC_DISABLE_ISOM
43 
44 typedef struct
45 {
46 	GF_ISOM_BOX
47 	GF_StringBox *id;
48 	GF_StringBox *time;
49 	GF_StringBox *settings;
50 	GF_StringBox *payload;
51 } GF_VTTCueBox;
52 
boxstring_box_new()53 GF_Box *boxstring_box_new() {
54 	//type is assigned by caller
55 	ISOM_DECL_BOX_ALLOC(GF_StringBox, 0);
56 	return (GF_Box *)tmp;
57 }
58 
boxstring_new_with_data(u32 type,const char * string,GF_List ** parent)59 GF_Box *boxstring_new_with_data(u32 type, const char *string, GF_List **parent)
60 {
61 	GF_Box *a = NULL;
62 
63 	switch (type) {
64 	case GF_ISOM_BOX_TYPE_VTTC_CONFIG:
65 	case GF_ISOM_BOX_TYPE_CTIM:
66 	case GF_ISOM_BOX_TYPE_IDEN:
67 	case GF_ISOM_BOX_TYPE_STTG:
68 	case GF_ISOM_BOX_TYPE_PAYL:
69 	case GF_ISOM_BOX_TYPE_VTTA:
70 		if (string) {
71 			/* remove trailing spaces; spec. \r, \n; skip if empty */
72 			size_t len = strlen(string);
73 			char const* last = string + len-1;
74 			while (len && isspace(*last--))
75 				--len;
76 
77 			if (!len) break;
78 			if (parent) {
79 				a = gf_isom_box_new_parent(parent, type);
80 			} else {
81 				a = gf_isom_box_new(type);
82 			}
83 			if (a) {
84 				char* str = ((GF_StringBox *)a)->string = gf_malloc(len + 1);
85 				memcpy(str, string, len);
86 				str[len] = '\0';
87 			}
88 		}
89 		break;
90 	default:
91 		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Box type %s is not a boxstring, cannot initialize with data\n", gf_4cc_to_str(type) ));
92 
93 		break;
94 	}
95 	return a;
96 }
97 
vtcu_box_new()98 GF_Box *vtcu_box_new()
99 {
100 	ISOM_DECL_BOX_ALLOC(GF_VTTCueBox, GF_ISOM_BOX_TYPE_VTCC_CUE);
101 	return (GF_Box *)tmp;
102 }
103 
vtte_box_new()104 GF_Box *vtte_box_new() {
105 	ISOM_DECL_BOX_ALLOC(GF_Box, GF_ISOM_BOX_TYPE_VTTE);
106 	return (GF_Box *)tmp;
107 }
108 
wvtt_box_new()109 GF_Box *wvtt_box_new()
110 {
111 	ISOM_DECL_BOX_ALLOC(GF_WebVTTSampleEntryBox, GF_ISOM_BOX_TYPE_WVTT);
112 	return (GF_Box *)tmp;
113 }
114 
boxstring_box_del(GF_Box * s)115 void boxstring_box_del(GF_Box *s)
116 {
117 	GF_StringBox *box = (GF_StringBox *)s;
118 	if (box->string) gf_free(box->string);
119 	gf_free(box);
120 }
121 
vtcu_box_del(GF_Box * s)122 void vtcu_box_del(GF_Box *s)
123 {
124 	gf_free(s);
125 }
126 
vtte_box_del(GF_Box * s)127 void vtte_box_del(GF_Box *s)
128 {
129 	gf_free(s);
130 }
131 
wvtt_box_del(GF_Box * s)132 void wvtt_box_del(GF_Box *s)
133 {
134 	gf_free(s);
135 }
136 
boxstring_box_read(GF_Box * s,GF_BitStream * bs)137 GF_Err boxstring_box_read(GF_Box *s, GF_BitStream *bs)
138 {
139 	GF_StringBox *box = (GF_StringBox *)s;
140 	box->string = (char *)gf_malloc((u32)(s->size+1));
141 	gf_bs_read_data(bs, box->string, (u32)(s->size));
142 	box->string[(u32)(s->size)] = 0;
143 	return GF_OK;
144 }
145 
vtcu_Add(GF_Box * s,GF_Box * box)146 static GF_Err vtcu_Add(GF_Box *s, GF_Box *box)
147 {
148 	GF_VTTCueBox *cuebox = (GF_VTTCueBox *)s;
149 	switch(box->type) {
150 	case GF_ISOM_BOX_TYPE_CTIM:
151 		cuebox->time = (GF_StringBox *)box;
152 		break;
153 	case GF_ISOM_BOX_TYPE_IDEN:
154 		cuebox->id = (GF_StringBox *)box;
155 		break;
156 	case GF_ISOM_BOX_TYPE_STTG:
157 		cuebox->settings = (GF_StringBox *)box;
158 		break;
159 	case GF_ISOM_BOX_TYPE_PAYL:
160 		cuebox->payload = (GF_StringBox *)box;
161 		break;
162 	}
163 	return GF_OK;
164 }
165 
vtcu_box_read(GF_Box * s,GF_BitStream * bs)166 GF_Err vtcu_box_read(GF_Box *s, GF_BitStream *bs)
167 {
168 	return gf_isom_box_array_read(s, bs, vtcu_Add);
169 }
170 
vtte_box_read(GF_Box * s,GF_BitStream * bs)171 GF_Err vtte_box_read(GF_Box *s, GF_BitStream *bs)
172 {
173 	return gf_isom_box_array_read(s, bs, NULL);
174 }
175 
wvtt_Add(GF_Box * s,GF_Box * box)176 static GF_Err wvtt_Add(GF_Box *s, GF_Box *box)
177 {
178 	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
179 	switch(box->type) {
180 	case GF_ISOM_BOX_TYPE_VTTC_CONFIG:
181 		wvtt->config = (GF_StringBox *)box;
182 		break;
183 	}
184 	return GF_OK;
185 }
186 
wvtt_box_read(GF_Box * s,GF_BitStream * bs)187 GF_Err wvtt_box_read(GF_Box *s, GF_BitStream *bs)
188 {
189 	GF_Err e;
190 	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
191 	e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)wvtt, bs);
192 	if (e) return e;
193 
194 	wvtt->size -= 8;
195 	return gf_isom_box_array_read(s, bs, wvtt_Add);
196 }
197 
198 #ifndef GPAC_DISABLE_ISOM_WRITE
boxstring_box_write(GF_Box * s,GF_BitStream * bs)199 GF_Err boxstring_box_write(GF_Box *s, GF_BitStream *bs)
200 {
201 	GF_Err e;
202 	GF_StringBox *box = (GF_StringBox *)s;
203 	e = gf_isom_box_write_header(s, bs);
204 	if (e) return e;
205 	if (box->string) {
206 		gf_bs_write_data(bs, box->string, (u32)(box->size-8));
207 	}
208 	return GF_OK;
209 }
210 
vtcu_box_write(GF_Box * s,GF_BitStream * bs)211 GF_Err vtcu_box_write(GF_Box *s, GF_BitStream *bs)
212 {
213 	return gf_isom_box_write_header(s, bs);
214 }
215 
vtte_box_write(GF_Box * s,GF_BitStream * bs)216 GF_Err vtte_box_write(GF_Box *s, GF_BitStream *bs)
217 {
218 	GF_Err e;
219 	e = gf_isom_box_write_header(s, bs);
220 	return e;
221 }
222 
wvtt_box_write(GF_Box * s,GF_BitStream * bs)223 GF_Err wvtt_box_write(GF_Box *s, GF_BitStream *bs)
224 {
225 	GF_Err e;
226 	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
227 	e = gf_isom_box_write_header(s, bs);
228 	gf_bs_write_data(bs, wvtt->reserved, 6);
229 	gf_bs_write_u16(bs, wvtt->dataReferenceIndex);
230 	return e;
231 }
232 
boxstring_box_size(GF_Box * s)233 GF_Err boxstring_box_size(GF_Box *s)
234 {
235 	GF_StringBox *box = (GF_StringBox *)s;
236 	if (box->string)
237 		box->size += strlen(box->string);
238 	return GF_OK;
239 }
240 
vtcu_box_size(GF_Box * s)241 GF_Err vtcu_box_size(GF_Box *s)
242 {
243 	u32 pos=0;
244 	GF_VTTCueBox *cuebox = (GF_VTTCueBox *)s;
245 	gf_isom_check_position(s, (GF_Box*)cuebox->id, &pos);
246 	gf_isom_check_position(s, (GF_Box*)cuebox->time, &pos);
247 	gf_isom_check_position(s, (GF_Box*)cuebox->settings, &pos);
248 	gf_isom_check_position(s, (GF_Box*)cuebox->payload, &pos);
249 	return GF_OK;
250 }
251 
vtte_box_size(GF_Box * s)252 GF_Err vtte_box_size(GF_Box *s)
253 {
254 	return GF_OK;
255 }
256 
wvtt_box_size(GF_Box * s)257 GF_Err wvtt_box_size(GF_Box *s)
258 {
259 	u32 pos=0;
260 	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
261 	s->size += 8; // reserved and dataReferenceIndex
262 	gf_isom_check_position(s, (GF_Box *)wvtt->config, &pos);
263 	return GF_OK;
264 }
265 
webvtt_write_cue(GF_BitStream * bs,GF_WebVTTCue * cue)266 static GF_Err webvtt_write_cue(GF_BitStream *bs, GF_WebVTTCue *cue)
267 {
268 	GF_Err e;
269 	GF_VTTCueBox *cuebox;
270 	if (!cue) return GF_OK;
271 
272 	cuebox = (GF_VTTCueBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_VTCC_CUE);
273 
274 	if (cue->id) {
275 		cuebox->id = (GF_StringBox *)boxstring_new_with_data(GF_ISOM_BOX_TYPE_IDEN, cue->id, &cuebox->child_boxes);
276 	}
277 	if (cue->settings) {
278 		cuebox->settings = (GF_StringBox *)boxstring_new_with_data(GF_ISOM_BOX_TYPE_STTG, cue->settings, &cuebox->child_boxes);
279 	}
280 	if (cue->text) {
281 		cuebox->payload = (GF_StringBox *)boxstring_new_with_data(GF_ISOM_BOX_TYPE_PAYL, cue->text, &cuebox->child_boxes);
282 	}
283 	/* TODO: check if a time box should be written */
284 	e = gf_isom_box_size((GF_Box *)cuebox);
285 	if (!e) e = gf_isom_box_write((GF_Box *)cuebox, bs);
286 
287 	gf_isom_box_del((GF_Box *)cuebox);
288 	return e;
289 }
290 
gf_isom_webvtt_to_sample(void * s)291 GF_ISOSample *gf_isom_webvtt_to_sample(void *s)
292 {
293 	GF_Err e = GF_OK;
294 	GF_ISOSample *res;
295 	GF_BitStream *bs;
296 	u32 i;
297 	GF_WebVTTSample *samp = (GF_WebVTTSample *)s;
298 	if (!samp) return NULL;
299 
300 	bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
301 
302 	if (gf_list_count(samp->cues)) {
303 		GF_WebVTTCue *cue;
304 		i=0;
305 		while ((cue = (GF_WebVTTCue *)gf_list_enum(samp->cues, &i))) {
306 			e = webvtt_write_cue(bs, cue);
307 			if (e) break;
308 		}
309 		if (e) {
310 			gf_bs_del(bs);
311 			return NULL;
312 		}
313 	} else {
314 		GF_Box *cuebox = (GF_Box *)gf_isom_box_new(GF_ISOM_BOX_TYPE_VTTE);
315 		e = gf_isom_box_size((GF_Box *)cuebox);
316 		if (!e) e = gf_isom_box_write((GF_Box *)cuebox, bs);
317 		gf_isom_box_del((GF_Box *)cuebox);
318 		if (e) {
319 			gf_bs_del(bs);
320 			return NULL;
321 		}
322 	}
323 	res = gf_isom_sample_new();
324 	if (!res) {
325 		gf_bs_del(bs);
326 		return NULL;
327 	}
328 	gf_bs_get_content(bs, &res->data, &res->dataLength);
329 	gf_bs_del(bs);
330 	res->IsRAP = RAP;
331 	return res;
332 }
333 #endif /*GPAC_DISABLE_ISOM_WRITE*/
334 
335 #ifndef GPAC_DISABLE_ISOM_DUMP
336 
boxstring_box_dump(GF_Box * a,FILE * trace)337 GF_Err boxstring_box_dump(GF_Box *a, FILE * trace)
338 {
339 	char *szName;
340 	GF_StringBox *sbox = (GF_StringBox *)a;
341 	switch (a->type) {
342 	case GF_ISOM_BOX_TYPE_VTTC_CONFIG:
343 		szName = "WebVTTConfigurationBox";
344 		break;
345 	case GF_ISOM_BOX_TYPE_CTIM:
346 		szName = "CueTimeBox";
347 		break;
348 	case GF_ISOM_BOX_TYPE_IDEN:
349 		szName = "CueIDBox";
350 		break;
351 	case GF_ISOM_BOX_TYPE_STTG:
352 		szName = "CueSettingsBox";
353 		break;
354 	case GF_ISOM_BOX_TYPE_PAYL:
355 		szName = "CuePayloadBox";
356 		break;
357 	case GF_ISOM_BOX_TYPE_VTTA:
358 		szName = "VTTAdditionalCueBox";
359 		break;
360 	default:
361 		szName = "StringBox";
362 		break;
363 	}
364 	gf_isom_box_dump_start(a, szName, trace);
365 	gf_fprintf(trace, "><![CDATA[\n");
366 	if (sbox->string)
367 		gf_fprintf(trace, "%s", sbox->string);
368 	gf_fprintf(trace, "\n]]>");
369 	gf_isom_box_dump_done(szName, a, trace);
370 	return GF_OK;
371 }
372 
vtcu_box_dump(GF_Box * a,FILE * trace)373 GF_Err vtcu_box_dump(GF_Box *a, FILE * trace)
374 {
375 	gf_isom_box_dump_start(a, "WebVTTCueBox", trace);
376 	gf_fprintf(trace, ">\n");
377 	gf_isom_box_dump_done("WebVTTCueBox", a, trace);
378 	return GF_OK;
379 }
380 
vtte_box_dump(GF_Box * a,FILE * trace)381 GF_Err vtte_box_dump(GF_Box *a, FILE * trace)
382 {
383 	gf_isom_box_dump_start(a, "WebVTTEmptyCueBox", trace);
384 	gf_fprintf(trace, ">\n");
385 	gf_isom_box_dump_done("WebVTTEmptyCueBox", a, trace);
386 	return GF_OK;
387 }
388 
wvtt_box_dump(GF_Box * a,FILE * trace)389 GF_Err wvtt_box_dump(GF_Box *a, FILE * trace)
390 {
391 	gf_isom_box_dump_start(a, "WebVTTSampleEntryBox", trace);
392 	gf_fprintf(trace, ">\n");
393 	gf_isom_box_dump_done("WebVTTSampleEntryBox", a, trace);
394 	return GF_OK;
395 }
396 #endif /* GPAC_DISABLE_ISOM_DUMP */
397 
398 #endif /*GPAC_DISABLE_ISOM*/
399 
400 typedef enum {
401 	WEBVTT_PARSER_STATE_WAITING_SIGNATURE,
402 	WEBVTT_PARSER_STATE_WAITING_HEADER,
403 	WEBVTT_PARSER_STATE_WAITING_CUE,
404 	WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP,
405 	WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD
406 } GF_WebVTTParserState;
407 
408 struct _webvtt_parser {
409 	GF_WebVTTParserState state;
410 	Bool is_srt, suspend, is_eof;
411 
412 	/* List of non-overlapping GF_WebVTTSample */
413 	GF_List *samples;
414 
415 	FILE *vtt_in;
416 	s32 unicode_type;
417 
418 	u64  last_duration;
419 	void *user;
420 	GF_Err (*report_message)(void *, GF_Err, char *, const char *);
421 	void (*on_header_parsed)(void *, const char *);
422 	void (*on_sample_parsed)(void *, GF_WebVTTSample *);
423 	void (*on_cue_read)(void *, GF_WebVTTCue *);
424 };
425 
426 
gf_webvtt_timestamp_is_zero(GF_WebVTTTimestamp * ts)427 static Bool gf_webvtt_timestamp_is_zero(GF_WebVTTTimestamp *ts)
428 {
429 	return (ts->hour == 0 && ts->min == 0 && ts->sec == 0 && ts->ms == 0) ? GF_TRUE : GF_FALSE;
430 }
431 
432 #ifndef GPAC_DISABLE_MEDIA_IMPORT
433 
gf_webvtt_timestamp_greater(GF_WebVTTTimestamp * ts1,GF_WebVTTTimestamp * ts2)434 static Bool gf_webvtt_timestamp_greater(GF_WebVTTTimestamp *ts1, GF_WebVTTTimestamp *ts2)
435 {
436 	u64 t_ts1 = (60 * 60 * ts1->hour + 60 * ts1->min + ts1->sec) * 1000 + ts1->ms;
437 	u64 t_ts2 = (60 * 60 * ts2->hour + 60 * ts2->min + ts2->sec) * 1000 + ts2->ms;
438 	return (t_ts1 >= t_ts2) ? GF_TRUE : GF_FALSE;
439 }
440 
441 
442 /* mark the overlapped cue in the previous sample as split */
443 /* duplicate the cue, mark it as split and adjust its timing */
444 /* adjust the end of the overlapped cue in the previous sample */
gf_webvtt_cue_split_at(GF_WebVTTCue * cue,GF_WebVTTTimestamp * time)445 static GF_WebVTTCue *gf_webvtt_cue_split_at(GF_WebVTTCue *cue, GF_WebVTTTimestamp *time)
446 {
447 	GF_WebVTTCue *dup_cue;
448 
449 	cue->split = GF_TRUE;
450 	cue->orig_start = cue->start;
451 	cue->orig_end = cue->end;
452 
453 	GF_SAFEALLOC(dup_cue, GF_WebVTTCue);
454 	if (!dup_cue) return NULL;
455 	dup_cue->split = GF_TRUE;
456 	if (time) dup_cue->start = *time;
457 	dup_cue->end = cue->end;
458 	dup_cue->orig_start = cue->orig_start;
459 	dup_cue->orig_end = cue->orig_end;
460 	dup_cue->id = gf_strdup((cue->id ? cue->id : ""));
461 	dup_cue->settings = gf_strdup((cue->settings ? cue->settings : ""));
462 	dup_cue->text = gf_strdup((cue->text ? cue->text : ""));
463 
464 	if (time) cue->end = *time;
465 	return dup_cue;
466 }
467 
gf_webvtt_cue_add_property(GF_WebVTTCue * cue,GF_WebVTTCuePropertyType type,char * text_data,u32 text_len)468 static GF_Err gf_webvtt_cue_add_property(GF_WebVTTCue *cue, GF_WebVTTCuePropertyType type, char *text_data, u32 text_len)
469 {
470 	char **prop = NULL;
471 	u32 len;
472 	if (!cue) return GF_BAD_PARAM;
473 	if (!text_len) return GF_OK;
474 	switch(type)
475 	{
476 	case WEBVTT_ID:
477 		prop = &cue->id;
478 		break;
479 	case WEBVTT_SETTINGS:
480 		prop = &cue->settings;
481 		break;
482 	case WEBVTT_PAYLOAD:
483 		prop = &cue->text;
484 		break;
485 	case WEBVTT_POSTCUE_TEXT:
486 		prop = &cue->post_text;
487 		break;
488 	case WEBVTT_PRECUE_TEXT:
489 		prop = &cue->pre_text;
490 		break;
491 	}
492 	if (*prop) {
493 		len = (u32) strlen(*prop);
494 		*prop = (char*)gf_realloc(*prop, sizeof(char) * (len + text_len + 1) );
495 		strcpy(*prop + len, text_data);
496 	} else {
497 		*prop = gf_strdup(text_data);
498 	}
499 	return GF_OK;
500 }
501 
gf_webvtt_cue_new()502 static GF_WebVTTCue *gf_webvtt_cue_new()
503 {
504 	GF_WebVTTCue *cue;
505 	GF_SAFEALLOC(cue, GF_WebVTTCue);
506 	return cue;
507 }
508 
509 GF_EXPORT
gf_webvtt_cue_del(GF_WebVTTCue * cue)510 void gf_webvtt_cue_del(GF_WebVTTCue * cue)
511 {
512 	if (!cue) return;
513 	if (cue->id) gf_free(cue->id);
514 	if (cue->settings) gf_free(cue->settings);
515 	if (cue->text) gf_free(cue->text);
516 	if (cue->pre_text) gf_free(cue->pre_text);
517 	if (cue->post_text) gf_free(cue->post_text);
518 	gf_free(cue);
519 }
520 
gf_webvtt_sample_new()521 static GF_WebVTTSample *gf_webvtt_sample_new()
522 {
523 	GF_WebVTTSample *samp;
524 	GF_SAFEALLOC(samp, GF_WebVTTSample);
525 	if (!samp) return NULL;
526 	samp->cues = gf_list_new();
527 	return samp;
528 }
529 
gf_webvtt_sample_get_start(GF_WebVTTSample * samp)530 u64 gf_webvtt_sample_get_start(GF_WebVTTSample * samp)
531 {
532 	return samp->start;
533 }
gf_webvtt_sample_get_end(GF_WebVTTSample * samp)534 u64 gf_webvtt_sample_get_end(GF_WebVTTSample * samp)
535 {
536 	return samp->end;
537 }
538 
gf_webvtt_sample_del(GF_WebVTTSample * samp)539 void gf_webvtt_sample_del(GF_WebVTTSample * samp)
540 {
541 	while (gf_list_count(samp->cues)) {
542 		GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(samp->cues, 0);
543 		gf_list_rem(samp->cues, 0);
544 		gf_webvtt_cue_del(cue);
545 	}
546 	gf_list_del(samp->cues);
547 	gf_free(samp);
548 }
549 
gf_webvtt_parser_new()550 GF_WebVTTParser *gf_webvtt_parser_new()
551 {
552 	GF_WebVTTParser *parser;
553 	GF_SAFEALLOC(parser, GF_WebVTTParser);
554 	if (!parser) return NULL;
555 	parser->samples = gf_list_new();
556 	return parser;
557 }
558 
559 extern s32 gf_text_get_utf_type(FILE *in_src);
560 
gf_webvtt_parser_init(GF_WebVTTParser * parser,FILE * vtt_file,s32 unicode_type,Bool is_srt,void * user,GF_Err (* report_message)(void *,GF_Err,char *,const char *),void (* on_sample_parsed)(void *,GF_WebVTTSample *),void (* on_header_parsed)(void *,const char *))561 GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE *vtt_file, s32 unicode_type, Bool is_srt,
562                              void *user, GF_Err (*report_message)(void *, GF_Err, char *, const char *),
563                              void (*on_sample_parsed)(void *, GF_WebVTTSample *),
564                              void (*on_header_parsed)(void *, const char *))
565 {
566 	if (!parser) return GF_BAD_PARAM;
567 	parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;
568 
569 #ifdef GPAC_ENABLE_COVERAGE
570 	if (gf_sys_is_cov_mode()) {
571 		gf_webvtt_parser_restart(parser);
572 	}
573 #endif
574 
575 	parser->is_srt = is_srt;
576 	if (is_srt)
577 		parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
578 
579 	parser->vtt_in = vtt_file;
580 	parser->unicode_type = unicode_type;
581 
582 	parser->user = user;
583 	parser->report_message = report_message;
584 	parser->on_sample_parsed = on_sample_parsed;
585 	parser->on_header_parsed = on_header_parsed;
586 	return GF_OK;
587 }
588 
gf_webvtt_parser_suspend(GF_WebVTTParser * vttparser)589 void gf_webvtt_parser_suspend(GF_WebVTTParser *vttparser)
590 {
591 	vttparser->suspend = GF_TRUE;
592 }
593 
gf_webvtt_parser_restart(GF_WebVTTParser * parser)594 void gf_webvtt_parser_restart(GF_WebVTTParser *parser)
595 {
596 	if (!parser->vtt_in) return;
597 
598 	gf_fseek(parser->vtt_in, 0, SEEK_SET);
599 	parser->last_duration = 0;
600 	while (gf_list_count(parser->samples)) {
601 		gf_webvtt_sample_del((GF_WebVTTSample *)gf_list_get(parser->samples, 0));
602 		gf_list_rem(parser->samples, 0);
603 	}
604 	parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;
605 }
606 
gf_webvtt_parser_reset(GF_WebVTTParser * parser)607 void gf_webvtt_parser_reset(GF_WebVTTParser *parser)
608 {
609 	if (!parser) return;
610 	while (gf_list_count(parser->samples)) {
611 		gf_webvtt_sample_del((GF_WebVTTSample *)gf_list_get(parser->samples, 0));
612 		gf_list_rem(parser->samples, 0);
613 	}
614 	parser->last_duration = 0;
615 	parser->on_header_parsed = NULL;
616 	parser->on_sample_parsed = NULL;
617 	parser->report_message = NULL;
618 	parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;
619 	parser->unicode_type = 0;
620 	parser->user = NULL;
621 	parser->vtt_in = NULL;
622 }
623 
gf_webvtt_parser_del(GF_WebVTTParser * parser)624 void gf_webvtt_parser_del(GF_WebVTTParser *parser)
625 {
626 	if (parser) {
627 		gf_webvtt_parser_reset(parser);
628 		gf_list_del(parser->samples);
629 		gf_free(parser);
630 	}
631 }
632 
633 #if 0
634 u64 gf_webvtt_parser_last_duration(GF_WebVTTParser *parser)
635 {
636 	return parser ? parser->last_duration : 0;
637 }
638 #endif
639 
640 
gf_webvtt_add_cue_to_samples(GF_WebVTTParser * parser,GF_List * samples,GF_WebVTTCue * cue)641 static GF_Err gf_webvtt_add_cue_to_samples(GF_WebVTTParser *parser, GF_List *samples, GF_WebVTTCue *cue)
642 {
643 	s32 i;
644 	u64 cue_start;
645 	u64 cue_end;
646 	u64 sample_end;
647 
648 	sample_end = 0;
649 	cue_start = gf_webvtt_timestamp_get(&cue->start);
650 	cue_end   = gf_webvtt_timestamp_get(&cue->end);
651 	/* samples in the samples list are contiguous: sample(n)->start == sample(n-1)->end */
652 	for (i = 0; i < (s32)gf_list_count(samples); i++) {
653 		GF_WebVTTSample *sample;
654 		sample = (GF_WebVTTSample *)gf_list_get(samples, i);
655 		/* save the sample end in case there are no more samples to test */
656 		sample_end = sample->end;
657 		if (cue_start < sample->start) {
658 			/* cues must be ordered according to their start time, so drop the cue */
659 			/* TODO delete the cue */
660 			return GF_BAD_PARAM;
661 		} else if (cue_start == sample->start && cue_end == sample->end) {
662 			/* if the timing of the new cue matches the sample, no need to split, add the cue to the sample */
663 			gf_list_add(sample->cues, cue);
664 			/* the cue does not need to processed further */
665 			return GF_OK;
666 		} else if (cue_start >= sample->end) {
667 			/* flush the current sample */
668 			gf_list_del_item(samples, sample);
669 			parser->on_sample_parsed(parser->user, sample);
670 			sample = NULL;
671 			i--;
672 			/* process the cue with next sample (if any) or create a new sample */
673 			continue;
674 		} else {
675 			u32 j;
676 			if (cue_start > sample->start) {
677 				/* create a new sample, insert it after the current one */
678 				GF_WebVTTSample *new_sample = gf_webvtt_sample_new();
679 				new_sample->start = cue_start;
680 				new_sample->end = sample->end;
681 				gf_list_insert(samples, new_sample, i+1);
682 				/* split the cues from the old sample into the new one */
683 				for (j = 0; j < gf_list_count(sample->cues); j++) {
684 					GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(sample->cues, j);
685 					GF_WebVTTCue *new_cue = gf_webvtt_cue_split_at(old_cue, &cue->start);
686 					gf_list_add(new_sample->cues, new_cue);
687 				}
688 				/* adjust the end of the old sample and flush it */
689 				sample->end = cue_start;
690 				gf_list_del_item(samples, sample);
691 				parser->on_sample_parsed(parser->user, sample);
692 				sample = NULL;
693 				i--;
694 				/* process the cue again with this new sample */
695 				continue;
696 			}
697 			if (cue_end > sample->end) {
698 				/* the cue is longer than the sample, we split the cue, add one part to the current sample
699 				and reevaluate with the last part of the cue */
700 				GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(sample->cues, 0);
701 				GF_WebVTTCue *new_cue = gf_webvtt_cue_split_at(cue, &old_cue->end);
702 				gf_list_add(sample->cues, cue);
703 				cue = new_cue;
704 				cue_start = sample->end;
705 				/* cue_end unchanged */
706 				/* process the remaining part of the cue (i.e. the new cue) with the other samples */
707 				continue;
708 			} else { /* cue_end < sample->end */
709 				GF_WebVTTSample *new_sample = gf_webvtt_sample_new();
710 				new_sample->start = cue_end;
711 				new_sample->end   = sample->end;
712 				gf_list_insert(samples, new_sample, i+1);
713 				for (j = 0; j < gf_list_count(sample->cues); j++) {
714 					GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(sample->cues, j);
715 					GF_WebVTTCue *new_cue = gf_webvtt_cue_split_at(old_cue, &cue->end);
716 					gf_list_add(new_sample->cues, new_cue);
717 				}
718 				gf_list_add(sample->cues, cue);
719 				sample->end = new_sample->start;
720 				/* done with this cue */
721 				return GF_OK;
722 			}
723 		}
724 	}
725 	/* (a part of) the cue remains (was not overlapping) */
726 	if (cue_start > sample_end) {
727 		/* if the new cue start is greater than the last sample end,
728 		    create an empty sample to fill the gap, flush it */
729 		GF_WebVTTSample *esample = gf_webvtt_sample_new();
730 		esample->start = sample_end;
731 		esample->end   = cue_start;
732 		parser->on_sample_parsed(parser->user, esample);
733 	}
734 	/* if the cue has not been added to a sample, create a new sample for it */
735 	{
736 		GF_WebVTTSample *sample;
737 		sample = gf_webvtt_sample_new();
738 		gf_list_add(samples, sample);
739 		sample->start = cue_start;
740 		sample->end = cue_end;
741 		gf_list_add(sample->cues, cue);
742 	}
743 	return GF_OK;
744 }
745 
746 #define REM_TRAIL_MARKS(__str, __sep) while (1) {	\
747 		u32 _len = (u32) strlen(__str);		\
748 		if (!_len) break;	\
749 		_len--;				\
750 		if (strchr(__sep, __str[_len])) { \
751 			had_marks = GF_TRUE; \
752 			__str[_len] = 0;	\
753 		} else break;	\
754 	}
755 
756 extern char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type);
757 
gf_webvtt_parse_timestamp(GF_WebVTTParser * parser,GF_WebVTTTimestamp * ts,const char * line)758 GF_Err gf_webvtt_parse_timestamp(GF_WebVTTParser *parser, GF_WebVTTTimestamp *ts, const char *line)
759 {
760 	u32 len;
761 	u32 pos;
762 	u32 pos2;
763 	u32 value1;
764 	u32 value2;
765 	u32 value3;
766 	u32 value4;
767 	Bool is_hour = GF_FALSE;
768 	if (!ts || !line) return GF_BAD_PARAM;
769 	len = (u32) strlen(line);
770 	if (!len) return GF_BAD_PARAM;
771 	pos = 0;
772 	if (!(line[pos] >= '0' && line[pos] <= '9')) return GF_BAD_PARAM;
773 	value1 = 0;
774 	while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
775 		value1 = value1*10 + (line[pos]-'0');
776 		pos++;
777 	}
778 	if (pos>2 || value1>59) {
779 		is_hour = GF_TRUE;
780 	}
781 	if (pos == len || line[pos] != ':') {
782 		return GF_BAD_PARAM;
783 	} else {
784 		pos++;
785 	}
786 	value2 = 0;
787 	pos2 = 0;
788 	while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
789 		value2 = value2*10 + (line[pos]-'0');
790 		pos++;
791 		pos2++;
792 		if (pos2 > 2) return GF_BAD_PARAM;
793 	}
794 	if (is_hour || (pos < len && line[pos] == ':')) {
795 		if (pos == len || line[pos] != ':') {
796 			return GF_BAD_PARAM;
797 		} else {
798 			pos++;
799 			pos2 = 0;
800 			value3 = 0;
801 			while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
802 				value3 = value3*10 + (line[pos]-'0');
803 				pos++;
804 				pos2++;
805 				if (pos2 > 2) return GF_BAD_PARAM;
806 			}
807 		}
808 	} else {
809 		value3 = value2;
810 		value2 = value1;
811 		value1 = 0;
812 	}
813 	/* checking SRT syntax for timestamp with , */
814 	if (pos == len || (!parser->is_srt && line[pos] != '.') || (parser->is_srt && line[pos] != ',')) {
815 		return GF_BAD_PARAM;
816 	} else {
817 		pos++;
818 	}
819 	pos2 = 0;
820 	value4 = 0;
821 	while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
822 		value4 = value4*10 + (line[pos]-'0');
823 		pos++;
824 		pos2++;
825 		if (pos2 > 4) return GF_BAD_PARAM;
826 	}
827 	if (value2>59 || value3 > 59) return GF_BAD_PARAM;
828 	ts->hour = value1;
829 	ts->min = value2;
830 	ts->sec = value3;
831 	ts->ms = value4;
832 	return GF_OK;
833 }
834 
835 #define SKIP_WHITESPACE \
836     while (pos < len && (line[pos] == ' ' || line[pos] == '\t' || \
837            line[pos] == '\r' || line[pos] == '\f' || line[pos] == '\n')) pos++;
838 
gf_webvtt_parser_parse_timings_settings(GF_WebVTTParser * parser,GF_WebVTTCue * cue,char * line,u32 len)839 GF_Err gf_webvtt_parser_parse_timings_settings(GF_WebVTTParser *parser, GF_WebVTTCue *cue, char *line, u32 len)
840 {
841 	GF_Err e;
842 	char *timestamp_string;
843 	u32 pos;
844 
845 	pos = 0;
846 	if (!cue || !line || !len) return GF_BAD_PARAM;
847 	SKIP_WHITESPACE
848 	timestamp_string = line + pos;
849 	while (pos < len && line[pos] != ' ' && line[pos] != '\t') pos++;
850 	if (pos == len) {
851 		e = GF_CORRUPTED_DATA;
852 		parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line);
853 		return e;
854 	}
855 	line[pos] = 0;
856 	e = gf_webvtt_parse_timestamp(parser, &cue->start, timestamp_string);
857 	if (e) {
858 		parser->report_message(parser->user, e, "Bad VTT timestamp formatting %s", timestamp_string);
859 		return e;
860 	}
861 	line[pos] = ' ';
862 	SKIP_WHITESPACE
863 	if (pos == len) {
864 		e = GF_CORRUPTED_DATA;
865 		parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line);
866 		return e;
867 	}
868 	if ( (pos+2)>= len || line[pos] != '-' || line[pos+1] != '-' || line[pos+2] != '>') {
869 		e = GF_CORRUPTED_DATA;
870 		parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line);
871 		return e;
872 	} else {
873 		pos += 3;
874 		SKIP_WHITESPACE
875 		if (pos == len) {
876 			e = GF_CORRUPTED_DATA;
877 			parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in %s", line);
878 			return e;
879 		}
880 		timestamp_string = line + pos;
881 		while (pos < len && line[pos] != ' ' && line[pos] != '\t') pos++;
882 		if (pos < len) {
883 			line[pos] = 0;
884 		}
885 		e = gf_webvtt_parse_timestamp(parser, &cue->end, timestamp_string);
886 		if (e) {
887 			parser->report_message(parser->user, e, "Bad VTT timestamp formatting %s", timestamp_string);
888 			return e;
889 		}
890 		if (pos < len) {
891 			line[pos] = ' ';
892 		}
893 		SKIP_WHITESPACE
894 		if (pos < len) {
895 			char *settings = line + pos;
896 			e = gf_webvtt_cue_add_property(cue, WEBVTT_SETTINGS, settings, (u32) strlen(settings));
897 		}
898 
899 		if (!gf_webvtt_timestamp_greater(&cue->end, &cue->start)) {
900 			parser->report_message(parser->user, e, "Bad VTT timestamps, end smaller than start", timestamp_string);
901 			cue->end = cue->start;
902 			cue->end.ms += 1;
903 			return GF_NON_COMPLIANT_BITSTREAM;
904 
905 		}
906 	}
907 	return e;
908 }
909 
gf_webvtt_parser_parse(GF_WebVTTParser * parser)910 GF_Err gf_webvtt_parser_parse(GF_WebVTTParser *parser)
911 {
912 	char szLine[2048];
913 	char *sOK;
914 	u32 len;
915 	GF_Err e;
916 	GF_WebVTTCue *cue = NULL;
917 	char *prevLine = NULL;
918 	char *header = NULL;
919 	u32 header_len = 0;
920 	Bool had_marks = GF_FALSE;
921 
922 	if (!parser) return GF_BAD_PARAM;
923 	parser->suspend = GF_FALSE;
924 
925 	if (parser->is_srt) {
926 		parser->on_header_parsed(parser->user, "WEBVTT\n");
927 	}
928 
929 	while (!parser->is_eof) {
930 		if (!cue && parser->suspend)
931 			break;
932 		sOK = gf_text_get_utf8_line(szLine, 2048, parser->vtt_in, parser->unicode_type);
933 		REM_TRAIL_MARKS(szLine, "\r\n")
934 		len = (u32) strlen(szLine);
935 		switch (parser->state) {
936 		case WEBVTT_PARSER_STATE_WAITING_SIGNATURE:
937 			if (!sOK || len < 6 || strnicmp(szLine, "WEBVTT", 6) || (len > 6 && szLine[6] != ' ' && szLine[6] != '\t')) {
938 				e = GF_CORRUPTED_DATA;
939 				parser->report_message(parser->user, e, "Bad WEBVTT file signature %s", szLine);
940 				goto exit;
941 			} else {
942 				if (had_marks) {
943 					szLine[len] = '\n';
944 					len++;
945 				}
946 				header = gf_strdup(szLine);
947 				header_len = len;
948 				parser->state = WEBVTT_PARSER_STATE_WAITING_HEADER;
949 			}
950 			break; /* proceed to next line */
951 		case WEBVTT_PARSER_STATE_WAITING_HEADER:
952 			if (prevLine) {
953 				u32 prev_len = (u32) strlen(prevLine);
954 				header = (char *)gf_realloc(header, header_len + prev_len + 1);
955 				strcpy(header+header_len,prevLine);
956 				header_len += prev_len;
957 				gf_free(prevLine);
958 				prevLine = NULL;
959 			}
960 			if (sOK && len) {
961 				if (strstr(szLine, "-->")) {
962 					parser->on_header_parsed(parser->user, header);
963 					/* continue to the next state without breaking */
964 					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP;
965 					/* no break, continue to the next state*/
966 				} else {
967 					if (had_marks) {
968 						szLine[len] = '\n';
969 						len++;
970 					}
971 					prevLine = gf_strdup(szLine);
972 					break; /* proceed to next line */
973 				}
974 			} else {
975 				parser->on_header_parsed(parser->user, header);
976 				if (header) gf_free(header);
977 				header = NULL;
978 				if (!sOK) {
979 					/* end of file, parsing is done */
980 					parser->is_eof = GF_TRUE;
981 					break;
982 				} else {
983 					/* empty line means end of header */
984 					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
985 					/* no break, continue to the next state*/
986 				}
987 			}
988 		case WEBVTT_PARSER_STATE_WAITING_CUE:
989 			if (sOK && len) {
990 				if (strstr(szLine, "-->")) {
991 					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP;
992 					/* continue to the next state without breaking */
993 				} else {
994 					/* discard the previous line */
995 					/* should we do something with it ? callback ?*/
996 					if (prevLine) {
997 						gf_free(prevLine);
998 						prevLine = NULL;
999 					}
1000 					/* save this new line */
1001 					if (had_marks) {
1002 						szLine[len] = '\n';
1003 						len++;
1004 					}
1005 					prevLine = gf_strdup(szLine);
1006 					/* stay in the same state */
1007 					break;
1008 				}
1009 			} else {
1010 				/* discard the previous line */
1011 				/* should we do something with it ? callback ?*/
1012 				if (prevLine) {
1013 					gf_free(prevLine);
1014 					prevLine = NULL;
1015 				}
1016 				if (!sOK) {
1017 					parser->is_eof = GF_TRUE;
1018 					break;
1019 				} else {
1020 					/* remove empty lines and stay in the same state */
1021 					break;
1022 				}
1023 			}
1024 		case WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP:
1025 			if (sOK && len) {
1026 				if (cue == NULL) {
1027 					cue   = gf_webvtt_cue_new();
1028 				}
1029 				if (prevLine) {
1030 					gf_webvtt_cue_add_property(cue, WEBVTT_ID, prevLine, (u32) strlen(prevLine));
1031 					gf_free(prevLine);
1032 					prevLine = NULL;
1033 				}
1034 				e = gf_webvtt_parser_parse_timings_settings(parser, cue, szLine, len);
1035 				if (e) {
1036 					if (cue) gf_webvtt_cue_del(cue);
1037 					cue = NULL;
1038 					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
1039 				} else {
1040 //					start = (u32)gf_webvtt_timestamp_get(&cue->start);
1041 //					end   = (u32)gf_webvtt_timestamp_get(&cue->end);
1042 					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD;
1043 				}
1044 			} else {
1045 				/* not possible */
1046 				assert(0);
1047 			}
1048 			break;
1049 		case WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD:
1050 			if (sOK && len) {
1051 				if (had_marks) {
1052 					szLine[len] = '\n';
1053 					len++;
1054 				}
1055 				gf_webvtt_cue_add_property(cue, WEBVTT_PAYLOAD, szLine, len);
1056 				/* remain in the same state as a cue payload can have multiple lines */
1057 				break;
1058 			} else {
1059 				/* end of the current cue */
1060 				gf_webvtt_add_cue_to_samples(parser, parser->samples, cue);
1061 				cue = NULL;
1062 
1063 				if (!sOK) {
1064 					parser->is_eof = GF_TRUE;
1065 					break;
1066 				} else {
1067 					/* empty line, move to next cue */
1068 					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
1069 					break;
1070 				}
1071 			}
1072 		}
1073 	}
1074 	if (header) gf_free(header);
1075 	header = NULL;
1076 
1077 	if (parser->suspend)
1078 		return GF_OK;
1079 
1080 	/* no more cues to come, flush everything */
1081 	if (cue) {
1082 		gf_webvtt_add_cue_to_samples(parser, parser->samples, cue);
1083 		cue = NULL;
1084 	}
1085 	while (gf_list_count(parser->samples) > 0) {
1086 		GF_WebVTTSample *sample = (GF_WebVTTSample *)gf_list_get(parser->samples, 0);
1087 		parser->last_duration = (sample->end > sample->start) ? sample->end - sample->start : 0;
1088 		gf_list_rem(parser->samples, 0);
1089 		parser->on_sample_parsed(parser->user, sample);
1090 	}
1091 	e = GF_EOS;
1092 exit:
1093 	if (cue) gf_webvtt_cue_del(cue);
1094 	if (prevLine) gf_free(prevLine);
1095 	if (header) gf_free(header);
1096 	return e;
1097 }
1098 
1099 GF_EXPORT
gf_webvtt_parse_iso_cues(GF_ISOSample * iso_sample,u64 start)1100 GF_List *gf_webvtt_parse_iso_cues(GF_ISOSample *iso_sample, u64 start)
1101 {
1102 	return gf_webvtt_parse_cues_from_data(iso_sample->data, iso_sample->dataLength, start);
1103 }
1104 
1105 GF_EXPORT
gf_webvtt_parse_cues_from_data(const u8 * data,u32 dataLength,u64 start)1106 GF_List *gf_webvtt_parse_cues_from_data(const u8 *data, u32 dataLength, u64 start)
1107 {
1108 	GF_List *cues;
1109 	GF_WebVTTCue *cue;
1110 	GF_VTTCueBox *cuebox;
1111 	GF_BitStream *bs;
1112 	char *pre_text;
1113 	cue = NULL;
1114 	pre_text = NULL;
1115 	cues = gf_list_new();
1116 	bs = gf_bs_new((u8 *)data, dataLength, GF_BITSTREAM_READ);
1117 	while(gf_bs_available(bs))
1118 	{
1119 		GF_Err  e;
1120 		GF_Box  *box;
1121 		e = gf_isom_box_parse(&box, bs);
1122 		if (e) return NULL;
1123 		if (box->type == GF_ISOM_BOX_TYPE_VTCC_CUE) {
1124 			cuebox = (GF_VTTCueBox *)box;
1125 			cue   = gf_webvtt_cue_new();
1126 			if (pre_text) {
1127 				gf_webvtt_cue_add_property(cue, WEBVTT_PRECUE_TEXT, pre_text, (u32) strlen(pre_text));
1128 				gf_free(pre_text);
1129 				pre_text = NULL;
1130 			}
1131 			gf_list_add(cues, cue);
1132 			gf_webvtt_timestamp_set(&cue->start, start);
1133 			if (cuebox->id) {
1134 				gf_webvtt_cue_add_property(cue, WEBVTT_ID, cuebox->id->string, (u32) strlen(cuebox->id->string));
1135 			}
1136 			if (cuebox->settings) {
1137 				gf_webvtt_cue_add_property(cue, WEBVTT_SETTINGS, cuebox->settings->string, (u32) strlen(cuebox->settings->string));
1138 			}
1139 			if (cuebox->payload) {
1140 				gf_webvtt_cue_add_property(cue, WEBVTT_PAYLOAD, cuebox->payload->string, (u32) strlen(cuebox->payload->string));
1141 			}
1142 		} else if (box->type == GF_ISOM_BOX_TYPE_VTTA) {
1143 			GF_StringBox *sbox = (GF_StringBox *)box;
1144 			if (cue) {
1145 				gf_webvtt_cue_add_property(cue, WEBVTT_POSTCUE_TEXT, sbox->string, (u32) strlen(sbox->string));
1146 			} else {
1147 				pre_text = gf_strdup(sbox->string);
1148 			}
1149 		}
1150 		gf_isom_box_del(box);
1151 	}
1152 	gf_bs_del(bs);
1153 	return cues;
1154 }
1155 
gf_webvtt_merge_cues(GF_WebVTTParser * parser,u64 start,GF_List * cues)1156 GF_Err gf_webvtt_merge_cues(GF_WebVTTParser *parser, u64 start, GF_List *cues)
1157 {
1158 	GF_WebVTTSample *wsample;
1159 	GF_WebVTTSample *prev_wsample;
1160 	Bool            has_continuation_cue = GF_FALSE;
1161 
1162 	assert(gf_list_count(parser->samples) <= 1);
1163 
1164 	wsample = gf_webvtt_sample_new();
1165 	wsample->start = start;
1166 
1167 	prev_wsample = (GF_WebVTTSample *)gf_list_last(parser->samples);
1168 	while (gf_list_count(cues)) {
1169 		GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, 0);
1170 		gf_list_rem(cues, 0);
1171 		/* add the cue to the current sample */
1172 		gf_list_add(wsample->cues, cue);
1173 		/* update with the previous sample */
1174 		if (prev_wsample) {
1175 			Bool do_del = GF_TRUE;
1176 			Bool  found = GF_FALSE;
1177 			while (!found && gf_list_count(prev_wsample->cues)) {
1178 				GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(prev_wsample->cues, 0);
1179 				gf_list_rem(prev_wsample->cues, 0);
1180 				if (
1181 				    ((!cue->id && !old_cue->id) || (old_cue->id && cue->id && !strcmp(old_cue->id, cue->id))) &&
1182 				    ((!cue->settings && !old_cue->settings) || (old_cue->settings && cue->settings && !strcmp(old_cue->settings, cue->settings))) &&
1183 				    ((!cue->text && !old_cue->text) || (old_cue->text && cue->text && !strcmp(old_cue->text, cue->text)))
1184 				) {
1185 					/* if it is the same cue, update its start with the initial start */
1186 					cue->start = old_cue->start;
1187 					has_continuation_cue = GF_TRUE;
1188 					found = GF_TRUE;
1189 					if (old_cue->pre_text) {
1190 						cue->pre_text = old_cue->pre_text;
1191 						old_cue->pre_text = NULL;
1192 					}
1193 					if (old_cue->post_text) {
1194 						cue->post_text = old_cue->post_text;
1195 						old_cue->post_text = NULL;
1196 					}
1197 				} else {
1198 					/* finalize the end cue time */
1199 					if (gf_webvtt_timestamp_is_zero(&old_cue->end)) {
1200 						gf_webvtt_timestamp_set(&old_cue->end, wsample->start);
1201 					}
1202 					/* transfer the cue */
1203 					if (!has_continuation_cue) {
1204 						/* the cue can be safely serialized while keeping the order */
1205 						parser->on_cue_read(parser->user, old_cue);
1206 					} else {
1207 						/* keep the cue in the current sample to respect cue start ordering */
1208 						gf_list_add(wsample->cues, old_cue);
1209 						do_del = GF_FALSE;
1210 					}
1211 				}
1212 				/* delete the old cue */
1213 				if (do_del)
1214 					gf_webvtt_cue_del(old_cue);
1215 			}
1216 		}
1217 	}
1218 	/* No cue in the current sample */
1219 	if (prev_wsample) {
1220 		while (gf_list_count(prev_wsample->cues)) {
1221 			Bool do_del = GF_TRUE;
1222 			GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(prev_wsample->cues, 0);
1223 			gf_list_rem(prev_wsample->cues, 0);
1224 			/* finalize the end cue time */
1225 			if (gf_webvtt_timestamp_is_zero(&cue->end)) {
1226 				gf_webvtt_timestamp_set(&cue->end, wsample->start);
1227 			}
1228 			/* transfer the cue */
1229 			if (!has_continuation_cue) {
1230 				/* the cue can be safely serialized while keeping the order */
1231 				parser->on_cue_read(parser->user, cue);
1232 			} else {
1233 				/* keep the cue in the current sample to respect cue start ordering */
1234 				gf_list_add(wsample->cues, cue);
1235 				do_del = GF_FALSE;
1236 			}
1237 			if (do_del)
1238 				gf_webvtt_cue_del(cue);
1239 		}
1240 		gf_webvtt_sample_del(prev_wsample);
1241 		gf_list_rem_last(parser->samples);
1242 		prev_wsample = NULL;
1243 	} else {
1244 		/* nothing to do */
1245 	}
1246 	if (gf_list_count(wsample->cues)) {
1247 		gf_list_add(parser->samples, wsample);
1248 	} else {
1249 		gf_webvtt_sample_del(wsample);
1250 	}
1251 	return GF_OK;
1252 }
1253 
gf_webvtt_parse_iso_sample(GF_WebVTTParser * parser,u32 timescale,GF_ISOSample * iso_sample,Bool merge,Bool box_mode)1254 GF_Err gf_webvtt_parse_iso_sample(GF_WebVTTParser *parser, u32 timescale, GF_ISOSample *iso_sample, Bool merge, Bool box_mode)
1255 {
1256 	if (merge) {
1257 		u64             start;
1258 		GF_List         *cues;
1259 		start = (iso_sample->DTS * 1000) / timescale;
1260 		cues = gf_webvtt_parse_iso_cues(iso_sample, start);
1261 		gf_webvtt_merge_cues(parser, start, cues);
1262 		gf_list_del(cues);
1263 	} else {
1264 		GF_Err gf_webvtt_dump_iso_sample(FILE *dump, u32 timescale, GF_ISOSample *iso_sample, Bool box_mode);
1265 
1266 		gf_webvtt_dump_iso_sample((FILE *)parser->user, timescale, iso_sample, box_mode);
1267 	}
1268 
1269 	return GF_OK;
1270 }
1271 #endif //GPAC_DISABLE_MEDIA_IMPORT
1272 
1273 
gf_webvtt_timestamp_set(GF_WebVTTTimestamp * ts,u64 value)1274 void gf_webvtt_timestamp_set(GF_WebVTTTimestamp *ts, u64 value)
1275 {
1276 	u64 tmp;
1277 	if (!ts) return;
1278 	tmp = value;
1279 	ts->hour = (u32)(tmp/(3600*1000));
1280 	tmp -= ts->hour*3600*1000;
1281 	ts->min  = (u32)(tmp/(60*1000));
1282 	tmp -= ts->min*60*1000;
1283 	ts->sec  = (u32)(tmp/1000);
1284 	tmp -= ts->sec*1000;
1285 	ts->ms   = (u32)tmp;
1286 }
1287 
gf_webvtt_timestamp_get(GF_WebVTTTimestamp * ts)1288 u64 gf_webvtt_timestamp_get(GF_WebVTTTimestamp *ts)
1289 {
1290 	if (!ts) return 0;
1291 	return (3600*ts->hour + 60*ts->min + ts->sec)*1000 + ts->ms;
1292 }
1293 
gf_webvtt_timestamp_dump(GF_WebVTTTimestamp * ts,FILE * dump,Bool dump_hour)1294 void gf_webvtt_timestamp_dump(GF_WebVTTTimestamp *ts, FILE *dump, Bool dump_hour)
1295 {
1296 	if (dump_hour || ts->hour != 0) {
1297 		gf_fprintf(dump, "%02u:", ts->hour);
1298 	}
1299 
1300 	gf_fprintf(dump, "%02u:%02u.%03u", ts->min, ts->sec, ts->ms);
1301 }
gf_webvtt_dump_header_boxed(FILE * dump,const u8 * data,u32 dataLength,u32 * dumpedLength)1302 GF_Err gf_webvtt_dump_header_boxed(FILE *dump, const u8 *data, u32 dataLength, u32 *dumpedLength)
1303 {
1304 #ifdef GPAC_DISABLE_ISOM
1305 	return GF_NOT_SUPPORTED;
1306 #else
1307 	GF_Err e;
1308 	GF_Box *box;
1309 	GF_StringBox *config;
1310 	GF_BitStream *bs;
1311 	*dumpedLength = 0;
1312 	bs = gf_bs_new((u8 *)data, dataLength, GF_BITSTREAM_READ);
1313 	e = gf_isom_box_parse(&box, bs);
1314 	if (!box || (box->type != GF_ISOM_BOX_TYPE_VTTC_CONFIG)) {
1315 		gf_bs_del(bs);
1316 		if (box) gf_isom_box_del(box);
1317 		return GF_BAD_PARAM;
1318 	}
1319 	config = (GF_StringBox *)box;
1320 	if (config->string) {
1321 		gf_fprintf(dump, "%s", config->string);
1322 		*dumpedLength = (u32)strlen(config->string)+1;
1323 	}
1324 	gf_bs_del(bs);
1325 	gf_isom_box_del(box);
1326 	return e;
1327 #endif
1328 }
1329 
1330 #ifndef GPAC_DISABLE_ISOM
gf_webvtt_dump_header(FILE * dump,GF_ISOFile * file,u32 track,Bool box_mode,u32 index)1331 GF_Err gf_webvtt_dump_header(FILE *dump, GF_ISOFile *file, u32 track, Bool box_mode, u32 index)
1332 {
1333 	GF_WebVTTSampleEntryBox *wvtt;
1334 	wvtt = gf_webvtt_isom_get_description(file, track, index);
1335 	if (!wvtt) return GF_BAD_PARAM;
1336 	if (box_mode) {
1337 		gf_isom_box_dump(wvtt, dump);
1338 	} else {
1339 		gf_fprintf(dump, "%s\n\n", wvtt->config->string);
1340 	}
1341 	return GF_OK;
1342 }
1343 
gf_webvtt_dump_iso_sample(FILE * dump,u32 timescale,GF_ISOSample * iso_sample,Bool box_mode)1344 GF_Err gf_webvtt_dump_iso_sample(FILE *dump, u32 timescale, GF_ISOSample *iso_sample, Bool box_mode)
1345 {
1346 	GF_Err e;
1347 	GF_BitStream *bs;
1348 
1349 	if (box_mode) {
1350 		gf_fprintf(dump, "<WebVTTSample decodingTimeStamp=\""LLU"\" compositionTimeStamp=\""LLD"\" RAP=\"%d\" dataLength=\"%d\" >\n", iso_sample->DTS, (s64)iso_sample->DTS + iso_sample->CTS_Offset, iso_sample->IsRAP, iso_sample->dataLength);
1351 	}
1352 	bs = gf_bs_new(iso_sample->data, iso_sample->dataLength, GF_BITSTREAM_READ);
1353 	while(gf_bs_available(bs))
1354 	{
1355 		GF_Box *box;
1356 		GF_WebVTTTimestamp ts;
1357 		e = gf_isom_box_parse(&box, bs);
1358 		if (e) return e;
1359 
1360 		if (box_mode) {
1361 			gf_isom_box_dump(box, dump);
1362 		} else if (box->type == GF_ISOM_BOX_TYPE_VTCC_CUE) {
1363 			GF_VTTCueBox *cuebox = (GF_VTTCueBox *)box;
1364 			if (cuebox->id) gf_fprintf(dump, "%s", cuebox->id->string);
1365 			gf_webvtt_timestamp_set(&ts, (iso_sample->DTS * 1000) / timescale);
1366 			gf_webvtt_timestamp_dump(&ts, dump, GF_FALSE);
1367 			gf_fprintf(dump, " --> NEXT");
1368 			if (cuebox->settings) gf_fprintf(dump, " %s", cuebox->settings->string);
1369 			gf_fprintf(dump, "\n");
1370 			if (cuebox->payload) gf_fprintf(dump, "%s", cuebox->payload->string);
1371 			gf_fprintf(dump, "\n");
1372 		} else if (box->type == GF_ISOM_BOX_TYPE_VTTE) {
1373 			gf_webvtt_timestamp_set(&ts, (iso_sample->DTS * 1000) / timescale);
1374 			gf_webvtt_timestamp_dump(&ts, dump, GF_FALSE);
1375 			gf_fprintf(dump, " --> NEXT\n\n");
1376 		} else if (box->type == GF_ISOM_BOX_TYPE_VTTA) {
1377 			gf_fprintf(dump, "%s\n\n", ((GF_StringBox *)box)->string);
1378 		}
1379 		gf_isom_box_del(box);
1380 	}
1381 	gf_bs_del(bs);
1382 	if (box_mode) {
1383 		gf_fprintf(dump, "</WebVTTSample>\n");
1384 	}
1385 	return GF_OK;
1386 }
1387 #endif
1388 
1389 #ifndef GPAC_DISABLE_ISOM_DUMP
gf_webvtt_parser_finalize(GF_WebVTTParser * parser,u64 duration)1390 GF_Err gf_webvtt_parser_finalize(GF_WebVTTParser *parser, u64 duration)
1391 {
1392 	GF_WebVTTSample *sample;
1393 	assert(gf_list_count(parser->samples) <= 1);
1394 	sample = (GF_WebVTTSample *)gf_list_get(parser->samples, 0);
1395 	if (sample) {
1396 		while (gf_list_count(sample->cues)) {
1397 			GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(sample->cues, 0);
1398 			gf_list_rem(sample->cues, 0);
1399 			if (gf_webvtt_timestamp_is_zero(&cue->end)) {
1400 				gf_webvtt_timestamp_set(&cue->end, duration);
1401 			}
1402 			parser->on_cue_read(parser->user, cue);
1403 			gf_webvtt_cue_del(cue);
1404 		}
1405 		gf_webvtt_sample_del(sample);
1406 		gf_list_rem(parser->samples, 0);
1407 	}
1408 	return GF_OK;
1409 }
1410 
1411 #ifndef GPAC_DISABLE_MEDIA_IMPORT
gf_webvtt_dump_cue(void * user,GF_WebVTTCue * cue)1412 static void gf_webvtt_dump_cue(void *user, GF_WebVTTCue *cue)
1413 {
1414 	FILE *dump = (FILE *)user;
1415 	if (!cue || !dump) return;
1416 	if (cue->pre_text) {
1417 		gf_fprintf(dump, "%s", cue->pre_text);
1418 		gf_fprintf(dump, "\n");
1419 		gf_fprintf(dump, "\n");
1420 	}
1421 	if (cue->id) gf_fprintf(dump, "%s\n", cue->id);
1422 	if (cue->start.hour || cue->end.hour) {
1423 		gf_webvtt_timestamp_dump(&cue->start, dump, GF_TRUE);
1424 		gf_fprintf(dump, " --> ");
1425 		gf_webvtt_timestamp_dump(&cue->end, dump, GF_TRUE);
1426 	} else {
1427 		gf_webvtt_timestamp_dump(&cue->start, dump, GF_FALSE);
1428 		gf_fprintf(dump, " --> ");
1429 		gf_webvtt_timestamp_dump(&cue->end, dump, GF_FALSE);
1430 	}
1431 	if (cue->settings) {
1432 		gf_fprintf(dump, " %s", cue->settings);
1433 	}
1434 	gf_fprintf(dump, "\n");
1435 	if (cue->text) gf_fprintf(dump, "%s", cue->text);
1436 	gf_fprintf(dump, "\n");
1437 	gf_fprintf(dump, "\n");
1438 	if (cue->post_text) {
1439 		gf_fprintf(dump, "%s", cue->post_text);
1440 		gf_fprintf(dump, "\n");
1441 		gf_fprintf(dump, "\n");
1442 	}
1443 }
1444 #endif
1445 
1446 //unused
1447 #if 0
1448 static GF_Err gf_webvtt_dump_cues(FILE *dump, GF_List *cues)
1449 {
1450 	u32 i;
1451 	for (i = 0; i < gf_list_count(cues); i++) {
1452 		GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, i);
1453 		gf_webvtt_dump_cue(dump, cue);
1454 	}
1455 	return GF_OK;
1456 }
1457 
1458 GF_Err gf_webvtt_dump_sample(FILE *dump, GF_WebVTTSample *samp)
1459 {
1460 	gf_fprintf(stdout, "NOTE New WebVTT Sample ("LLD"-"LLD")\n\n", samp->start, samp->end);
1461 	return gf_webvtt_dump_cues(dump, samp->cues);
1462 }
1463 #endif
1464 
1465 
gf_webvtt_parser_cue_callback(GF_WebVTTParser * parser,void (* on_cue_read)(void *,GF_WebVTTCue *),void * udta)1466 void gf_webvtt_parser_cue_callback(GF_WebVTTParser *parser, void (*on_cue_read)(void *, GF_WebVTTCue *), void *udta)
1467 {
1468 	parser->on_cue_read = on_cue_read;
1469 	parser->user = udta;
1470 }
1471 
1472 #ifndef GPAC_DISABLE_MEDIA_EXPORT
1473 
1474 GF_EXPORT
gf_webvtt_dump_iso_track(GF_MediaExporter * dumper,u32 track,Bool merge,Bool box_dump)1475 GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, u32 track, Bool merge, Bool box_dump)
1476 {
1477 #ifdef GPAC_DISABLE_MEDIA_IMPORT
1478 	return GF_NOT_SUPPORTED;
1479 #else
1480 	GF_Err  e;
1481 	u32     i;
1482 	u32     count;
1483 	u32     timescale;
1484 	FILE    *out;
1485 	u32     di;
1486 	u64     duration;
1487 	GF_WebVTTParser *parser;
1488 
1489 	out = (dumper->dump_file ? dumper->dump_file : stdout);
1490 	if (!out) return GF_IO_ERR;// gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
1491 
1492 	parser = gf_webvtt_parser_new();
1493 	parser->user = out;
1494 	parser->on_cue_read = gf_webvtt_dump_cue;
1495 
1496 	if (box_dump)
1497 		gf_fprintf(out, "<WebVTTTrack trackID=\"%d\">\n", gf_isom_get_track_id(dumper->file, track) );
1498 
1499 	e = gf_webvtt_dump_header(out, dumper->file, track, box_dump, 1);
1500 	if (e) goto exit;
1501 
1502 	timescale = gf_isom_get_media_timescale(dumper->file, track);
1503 
1504 	count = gf_isom_get_sample_count(dumper->file, track);
1505 	for (i=0; i<count; i++) {
1506 		GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, i+1, &di);
1507 		if (!samp) {
1508 			e = gf_isom_last_error(dumper->file);
1509 			goto exit;
1510 		}
1511 		e = gf_webvtt_parse_iso_sample(parser, timescale, samp, merge, box_dump);
1512 		if (e) {
1513 			goto exit;
1514 		}
1515 		gf_isom_sample_del(&samp);
1516 	}
1517 	duration = gf_isom_get_media_duration(dumper->file, track);
1518 	gf_webvtt_parser_finalize(parser, duration);
1519 
1520 	if (box_dump)
1521 		gf_fprintf(out, "</WebVTTTrack>\n");
1522 
1523 exit:
1524 	gf_webvtt_parser_del(parser);
1525 	return e;
1526 #endif
1527 }
1528 
1529 #endif /*GPAC_DISABLE_MEDIA_EXPORT*/
1530 
1531 #endif /*GPAC_DISABLE_ISOM_DUMP*/
1532 
1533 #endif /*GPAC_DISABLE_VTT*/
1534