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