Coverage Report

Created: 2025-12-05 06:49

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