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