/*
 *          GPAC - Multimedia Framework C SDK
 *
 *          Authors: Cyril Concolato, Jean Le Feuvre
 *          Copyright (c) Telecom ParisTech 2000-2024
 *                  All rights reserved
 *
 *  This file is part of GPAC / ISO Media File Format sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <gpac/list.h>
#include <gpac/internal/isomedia_dev.h>
#include <gpac/internal/media_dev.h>
#include <gpac/webvtt.h>
#include <gpac/constants.h>

#ifndef GPAC_DISABLE_VTT

struct _webvtt_sample
{
	u64 start;
	u64 end;
	GF_List *cues;
};


#ifndef GPAC_DISABLE_ISOM

typedef struct
{
	GF_ISOM_BOX
	GF_StringBox *id;
	GF_StringBox *time;
	GF_StringBox *settings;
	GF_StringBox *payload;
} GF_VTTCueBox;

GF_Box *boxstring_box_new() {
	//type is assigned by caller
	ISOM_DECL_BOX_ALLOC(GF_StringBox, 0);
	return (GF_Box *)tmp;
}

GF_Box *boxstring_new_with_data(u32 type, const char *string, GF_List **parent)
{
	GF_Box *a = NULL;

	switch (type) {
	case GF_ISOM_BOX_TYPE_VTTC_CONFIG:
	case GF_ISOM_BOX_TYPE_CTIM:
	case GF_ISOM_BOX_TYPE_IDEN:
	case GF_ISOM_BOX_TYPE_STTG:
	case GF_ISOM_BOX_TYPE_PAYL:
	case GF_ISOM_BOX_TYPE_VTTA:
		if (string) {
			/* remove trailing spaces; spec. \r, \n; skip if empty */
			size_t len = strlen(string);
			while (len && isspace(string[len-1])) {
				len--;
			}

			if (!len) break;
			if (parent) {
				a = gf_isom_box_new_parent(parent, type);
			} else {
				a = gf_isom_box_new(type);
			}
			if (a) {
				char* str = ((GF_StringBox *)a)->string = gf_malloc(len + 1);
				memcpy(str, string, len);
				str[len] = '\0';
			}
		}
		break;
	default:
		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) ));

		break;
	}
	return a;
}

GF_Box *vtcu_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_VTTCueBox, GF_ISOM_BOX_TYPE_VTCC_CUE);
	return (GF_Box *)tmp;
}

GF_Box *vtte_box_new() {
	ISOM_DECL_BOX_ALLOC(GF_Box, GF_ISOM_BOX_TYPE_VTTE);
	return (GF_Box *)tmp;
}

void boxstring_box_del(GF_Box *s)
{
	GF_StringBox *box = (GF_StringBox *)s;
	if (box->string) gf_free(box->string);
	gf_free(box);
}

void vtcu_box_del(GF_Box *s)
{
	gf_free(s);
}

void vtte_box_del(GF_Box *s)
{
	gf_free(s);
}

GF_Box *wvtt_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_WebVTTSampleEntryBox, GF_ISOM_BOX_TYPE_WVTT);
	gf_isom_sample_entry_init((GF_SampleEntryBox *)tmp);
	return (GF_Box *)tmp;
}

void wvtt_box_del(GF_Box *s)
{
	gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s);
	gf_free(s);
}

GF_Err boxstring_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_StringBox *box = (GF_StringBox *)s;
	box->string = (char *)gf_malloc((u32)(s->size+1));
	gf_bs_read_data(bs, box->string, (u32)(s->size));
	box->string[(u32)(s->size)] = 0;
	return GF_OK;
}


GF_Err vtcu_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem)
{
	GF_VTTCueBox *ptr = (GF_VTTCueBox *)s;
	switch (a->type) {
	case GF_ISOM_BOX_TYPE_CTIM:
		BOX_FIELD_ASSIGN(time, GF_StringBox);
		break;
	case GF_ISOM_BOX_TYPE_IDEN:
		BOX_FIELD_ASSIGN(id, GF_StringBox);
		break;
	case GF_ISOM_BOX_TYPE_STTG:
		BOX_FIELD_ASSIGN(settings, GF_StringBox);
		break;
	case GF_ISOM_BOX_TYPE_PAYL:
		BOX_FIELD_ASSIGN(payload, GF_StringBox);
		break;
	}
	return GF_OK;
}

GF_Err vtcu_box_read(GF_Box *s, GF_BitStream *bs)
{
	return gf_isom_box_array_read(s, bs);
}

GF_Err vtte_box_read(GF_Box *s, GF_BitStream *bs)
{
	return gf_isom_box_array_read(s, bs);
}

GF_Err wvtt_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem)
{
	GF_WebVTTSampleEntryBox *ptr = (GF_WebVTTSampleEntryBox *)s;
	switch (a->type) {
	case GF_ISOM_BOX_TYPE_VTTC_CONFIG:
		BOX_FIELD_ASSIGN(config, GF_StringBox);
		break;
	}
	return GF_OK;
}

GF_Err wvtt_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
	e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)wvtt, bs);
	if (e) return e;

	wvtt->size -= 8;
	return gf_isom_box_array_read(s, bs);
}

#ifndef GPAC_DISABLE_ISOM_WRITE
GF_Err boxstring_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_StringBox *box = (GF_StringBox *)s;
	e = gf_isom_box_write_header(s, bs);
	if (e) return e;
	if (box->string) {
		gf_bs_write_data(bs, box->string, (u32)(box->size-8));
	}
	return GF_OK;
}

GF_Err vtcu_box_write(GF_Box *s, GF_BitStream *bs)
{
	return gf_isom_box_write_header(s, bs);
}

GF_Err vtte_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	e = gf_isom_box_write_header(s, bs);
	return e;
}

GF_Err wvtt_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
	e = gf_isom_box_write_header(s, bs);
	gf_bs_write_data(bs, wvtt->reserved, 6);
	gf_bs_write_u16(bs, wvtt->dataReferenceIndex);
	return e;
}

GF_Err boxstring_box_size(GF_Box *s)
{
	GF_StringBox *box = (GF_StringBox *)s;
	if (box->string)
		box->size += strlen(box->string);
	return GF_OK;
}

GF_Err vtcu_box_size(GF_Box *s)
{
	u32 pos=0;
	GF_VTTCueBox *cuebox = (GF_VTTCueBox *)s;
	gf_isom_check_position(s, (GF_Box*)cuebox->id, &pos);
	gf_isom_check_position(s, (GF_Box*)cuebox->time, &pos);
	gf_isom_check_position(s, (GF_Box*)cuebox->settings, &pos);
	gf_isom_check_position(s, (GF_Box*)cuebox->payload, &pos);
	return GF_OK;
}

GF_Err vtte_box_size(GF_Box *s)
{
	return GF_OK;
}

GF_Err wvtt_box_size(GF_Box *s)
{
	u32 pos=0;
	GF_WebVTTSampleEntryBox *wvtt = (GF_WebVTTSampleEntryBox *)s;
	s->size += 8; // reserved and dataReferenceIndex
	gf_isom_check_position(s, (GF_Box *)wvtt->config, &pos);
	return GF_OK;
}

static GF_Err wvtt_write_cue(GF_BitStream *bs, GF_WebVTTCue *cue)
{
	GF_Err e;
	GF_VTTCueBox *cuebox;
	if (!cue) return GF_OK;

	if (cue->pre_text) {
		GF_Box *b = boxstring_new_with_data(GF_ISOM_BOX_TYPE_VTTA, cue->pre_text, NULL);
		e = gf_isom_box_size(b);
		if (!e) e = gf_isom_box_write(b, bs);
		gf_isom_box_del(b);
	}

	cuebox = (GF_VTTCueBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_VTCC_CUE);

	if (cue->id) {
		cuebox->id = (GF_StringBox *)boxstring_new_with_data(GF_ISOM_BOX_TYPE_IDEN, cue->id, &cuebox->child_boxes);
	}
	if (cue->settings) {
		cuebox->settings = (GF_StringBox *)boxstring_new_with_data(GF_ISOM_BOX_TYPE_STTG, cue->settings, &cuebox->child_boxes);
	}
	if (cue->text) {
		cuebox->payload = (GF_StringBox *)boxstring_new_with_data(GF_ISOM_BOX_TYPE_PAYL, cue->text, &cuebox->child_boxes);
	}
	/* TODO: check if a time box should be written */
	e = gf_isom_box_size((GF_Box *)cuebox);
	if (!e) e = gf_isom_box_write((GF_Box *)cuebox, bs);

	gf_isom_box_del((GF_Box *)cuebox);
	return e;
}

GF_ISOSample *gf_isom_webvtt_to_sample(void *s)
{
	GF_Err e = GF_OK;
	GF_ISOSample *res;
	GF_BitStream *bs;
	u32 i;
	GF_WebVTTSample *samp = (GF_WebVTTSample *)s;
	if (!samp) return NULL;

	bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);

	if (gf_list_count(samp->cues)) {
		GF_WebVTTCue *cue;
		i=0;
		while ((cue = (GF_WebVTTCue *)gf_list_enum(samp->cues, &i))) {
			e = wvtt_write_cue(bs, cue);
			if (e) break;
		}
		if (e) {
			gf_bs_del(bs);
			return NULL;
		}
	} else {
		GF_Box *cuebox = gf_isom_box_new(GF_ISOM_BOX_TYPE_VTTE);
		e = gf_isom_box_size(cuebox);
		if (!e) e = gf_isom_box_write(cuebox, bs);
		gf_isom_box_del(cuebox);
		if (e) {
			gf_bs_del(bs);
			return NULL;
		}
	}
	res = gf_isom_sample_new();
	if (!res) {
		gf_bs_del(bs);
		return NULL;
	}
	gf_bs_get_content(bs, &res->data, &res->dataLength);
	gf_bs_del(bs);
	res->IsRAP = RAP;
	return res;
}

u32 gf_isom_webvtt_cues_count(void *s)
{
	GF_WebVTTSample *samp = (GF_WebVTTSample *)s;
	if (!samp) return 0;
	return gf_list_count(samp->cues);
}

#endif /*GPAC_DISABLE_ISOM_WRITE*/

#ifndef GPAC_DISABLE_ISOM_DUMP

GF_Err boxstring_box_dump(GF_Box *a, FILE * trace)
{
	char *szName;
	GF_StringBox *sbox = (GF_StringBox *)a;
	switch (a->type) {
	case GF_ISOM_BOX_TYPE_VTTC_CONFIG:
		szName = "WebVTTConfigurationBox";
		break;
	case GF_ISOM_BOX_TYPE_CTIM:
		szName = "CueTimeBox";
		break;
	case GF_ISOM_BOX_TYPE_IDEN:
		szName = "CueIDBox";
		break;
	case GF_ISOM_BOX_TYPE_STTG:
		szName = "CueSettingsBox";
		break;
	case GF_ISOM_BOX_TYPE_PAYL:
		szName = "CuePayloadBox";
		break;
	case GF_ISOM_BOX_TYPE_VTTA:
		szName = "VTTAdditionalCueBox";
		break;
	default:
		szName = "StringBox";
		break;
	}
	gf_isom_box_dump_start(a, szName, trace);
	gf_fprintf(trace, ">");
	if (sbox->string && strlen(sbox->string))
		gf_fprintf(trace, "<![CDATA[\n%s\n]]>", sbox->string);
	gf_isom_box_dump_done(szName, a, trace);
	return GF_OK;
}

GF_Err vtcu_box_dump(GF_Box *a, FILE * trace)
{
	gf_isom_box_dump_start(a, "WebVTTCueBox", trace);
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("WebVTTCueBox", a, trace);
	return GF_OK;
}

GF_Err vtte_box_dump(GF_Box *a, FILE * trace)
{
	gf_isom_box_dump_start(a, "WebVTTEmptyCueBox", trace);
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("WebVTTEmptyCueBox", a, trace);
	return GF_OK;
}

GF_Err wvtt_box_dump(GF_Box *a, FILE * trace)
{
	gf_isom_box_dump_start(a, "WebVTTSampleEntryBox", trace);
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("WebVTTSampleEntryBox", a, trace);
	return GF_OK;
}
#endif /* GPAC_DISABLE_ISOM_DUMP */

#endif /*GPAC_DISABLE_ISOM*/

typedef enum {
	WEBVTT_PARSER_STATE_WAITING_SIGNATURE,
	WEBVTT_PARSER_STATE_WAITING_HEADER,
	WEBVTT_PARSER_STATE_WAITING_CUE,
	WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP,
	WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD
} GF_WebVTTParserState;

struct _webvtt_parser {
	GF_WebVTTParserState state;
	Bool is_init, is_srt, suspend, is_eof, prev_line_empty, in_comment;
	char *comment_text;

	/* List of non-overlapping GF_WebVTTSample */
	GF_List *samples;

	FILE **vtt_in;
	s32 unicode_type;

	u64  last_duration;
	void *user;
	GF_Err (*report_message)(void *, GF_Err, char *, const char *);
	void (*on_header_parsed)(void *, const char *);
	void (*on_sample_parsed)(void *, GF_WebVTTSample *);
	void (*on_cue_read)(void *, GF_WebVTTCue *);
};

#if !defined(GPAC_DISABLE_MEDIA_IMPORT) || !defined(GPAC_DISABLE_ISOM)
static Bool gf_webvtt_timestamp_is_zero(GF_WebVTTTimestamp *ts)
{
	return (ts->hour == 0 && ts->min == 0 && ts->sec == 0 && ts->ms == 0) ? GF_TRUE : GF_FALSE;
}
#endif

#ifndef GPAC_DISABLE_MEDIA_IMPORT

static Bool gf_webvtt_timestamp_greater(GF_WebVTTTimestamp *ts1, GF_WebVTTTimestamp *ts2)
{
	u64 t_ts1 = (60 * 60 * ts1->hour + 60 * ts1->min + ts1->sec) * 1000 + ts1->ms;
	u64 t_ts2 = (60 * 60 * ts2->hour + 60 * ts2->min + ts2->sec) * 1000 + ts2->ms;
	return (t_ts1 >= t_ts2) ? GF_TRUE : GF_FALSE;
}


/* mark the overlapped cue in the previous sample as split */
/* duplicate the cue, mark it as split and adjust its timing */
/* adjust the end of the overlapped cue in the previous sample */
static GF_WebVTTCue *gf_webvtt_cue_split_at(GF_WebVTTCue *cue, GF_WebVTTTimestamp *time)
{
	GF_WebVTTCue *dup_cue;

	cue->split = GF_TRUE;
	cue->orig_start = cue->start;
	cue->orig_end = cue->end;

	GF_SAFEALLOC(dup_cue, GF_WebVTTCue);
	if (!dup_cue) return NULL;
	dup_cue->split = GF_TRUE;
	if (time) dup_cue->start = *time;
	dup_cue->end = cue->end;
	dup_cue->orig_start = cue->orig_start;
	dup_cue->orig_end = cue->orig_end;
	dup_cue->id = gf_strdup((cue->id ? cue->id : ""));
	dup_cue->settings = gf_strdup((cue->settings ? cue->settings : ""));
	dup_cue->text = gf_strdup((cue->text ? cue->text : ""));

	if (time) cue->end = *time;
	return dup_cue;
}

static GF_Err gf_webvtt_cue_add_property(GF_WebVTTCue *cue, GF_WebVTTCuePropertyType type, char *text_data, u32 text_len)
{
	char **prop = NULL;
	u32 len;
	if (!cue) return GF_BAD_PARAM;
	if (!text_len) return GF_OK;
	switch(type)
	{
	case WEBVTT_ID:
		prop = &cue->id;
		break;
	case WEBVTT_SETTINGS:
		prop = &cue->settings;
		break;
	case WEBVTT_PAYLOAD:
		prop = &cue->text;
		break;
	case WEBVTT_POSTCUE_TEXT:
		prop = &cue->post_text;
		break;
	case WEBVTT_PRECUE_TEXT:
		prop = &cue->pre_text;
		break;
	}
	if (*prop) {
		len = (u32) strlen(*prop);
		(*prop) = (char*)gf_realloc((*prop), sizeof(char) * (len + text_len + 1) );
		memcpy((*prop) + len, text_data, text_len);
		(*prop)[len+text_len] = 0;
	} else {
		*prop = gf_strdup(text_data);
	}
	return GF_OK;
}

static GF_WebVTTCue *gf_webvtt_cue_new()
{
	GF_WebVTTCue *cue;
	GF_SAFEALLOC(cue, GF_WebVTTCue);
	return cue;
}

GF_EXPORT
void gf_webvtt_cue_del(GF_WebVTTCue * cue)
{
	if (!cue) return;
	if (cue->id) gf_free(cue->id);
	if (cue->settings) gf_free(cue->settings);
	if (cue->text) gf_free(cue->text);
	if (cue->pre_text) gf_free(cue->pre_text);
	if (cue->post_text) gf_free(cue->post_text);
	gf_free(cue);
}

static GF_WebVTTSample *gf_webvtt_sample_new()
{
	GF_WebVTTSample *samp;
	GF_SAFEALLOC(samp, GF_WebVTTSample);
	if (!samp) return NULL;
	samp->cues = gf_list_new();
	return samp;
}

u64 gf_webvtt_sample_get_start(GF_WebVTTSample * samp)
{
	return samp->start;
}
u64 gf_webvtt_sample_get_end(GF_WebVTTSample * samp)
{
	return samp->end;
}

void gf_webvtt_sample_del(GF_WebVTTSample * samp)
{
	while (gf_list_count(samp->cues)) {
		GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(samp->cues, 0);
		gf_list_rem(samp->cues, 0);
		gf_webvtt_cue_del(cue);
	}
	gf_list_del(samp->cues);
	gf_free(samp);
}

GF_WebVTTParser *gf_webvtt_parser_new()
{
	GF_WebVTTParser *parser;
	GF_SAFEALLOC(parser, GF_WebVTTParser);
	if (!parser) return NULL;
	parser->samples = gf_list_new();
	return parser;
}

extern s32 gf_text_get_utf_type(FILE *in_src);

GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE **vtt_file, s32 unicode_type, Bool is_srt,
                             void *user, GF_Err (*report_message)(void *, GF_Err, char *, const char *),
                             void (*on_sample_parsed)(void *, GF_WebVTTSample *),
                             void (*on_header_parsed)(void *, const char *))
{
	if (!parser) return GF_BAD_PARAM;
	parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		gf_webvtt_parser_restart(parser);
	}
#endif

	parser->is_srt = is_srt;
	if (is_srt)
		parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;

	parser->vtt_in = vtt_file;
	parser->unicode_type = unicode_type;

	parser->user = user;
	parser->report_message = report_message;
	parser->on_sample_parsed = on_sample_parsed;
	parser->on_header_parsed = on_header_parsed;
	return GF_OK;
}

void gf_webvtt_parser_suspend(GF_WebVTTParser *vttparser)
{
	vttparser->suspend = GF_TRUE;
}

void gf_webvtt_parser_restart(GF_WebVTTParser *parser)
{
	if (!parser || !parser->vtt_in || !*(parser->vtt_in)) return;

	gf_fseek(*parser->vtt_in, 0, SEEK_SET);
	parser->last_duration = 0;
	while (gf_list_count(parser->samples)) {
		gf_webvtt_sample_del((GF_WebVTTSample *)gf_list_get(parser->samples, 0));
		gf_list_rem(parser->samples, 0);
	}
	parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;
}

void gf_webvtt_parser_reset(GF_WebVTTParser *parser)
{
	if (!parser) return;
	while (gf_list_count(parser->samples)) {
		gf_webvtt_sample_del((GF_WebVTTSample *)gf_list_get(parser->samples, 0));
		gf_list_rem(parser->samples, 0);
	}
	if (parser->comment_text) gf_free(parser->comment_text);
	parser->comment_text = NULL;
	parser->prev_line_empty = GF_FALSE;
	parser->in_comment = GF_FALSE;
	parser->last_duration = 0;
	parser->on_header_parsed = NULL;
	parser->on_sample_parsed = NULL;
	parser->report_message = NULL;
	parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;
	parser->unicode_type = 0;
	parser->user = NULL;
	parser->vtt_in = NULL;
}

void gf_webvtt_parser_del(GF_WebVTTParser *parser)
{
	if (parser) {
		gf_webvtt_parser_reset(parser);
		gf_list_del(parser->samples);
		gf_free(parser);
	}
}

#if 0
u64 gf_webvtt_parser_last_duration(GF_WebVTTParser *parser)
{
	return parser ? parser->last_duration : 0;
}
#endif


static GF_Err gf_webvtt_add_cue_to_samples(GF_WebVTTParser *parser, GF_List *samples, GF_WebVTTCue *cue)
{
	s32 i;
	u64 cue_start;
	u64 cue_end;
	u64 sample_end;

	if (!cue)
		return GF_BAD_PARAM;

	cue_start = gf_webvtt_timestamp_get(&cue->start);
	cue_end   = gf_webvtt_timestamp_get(&cue->end);
	sample_end = cue_start;

	if (!parser->is_init) {
		sample_end = 0; // samples start at ts zero
		parser->is_init = GF_TRUE;
	}

	/* samples in the samples list are contiguous: sample(n)->start == sample(n-1)->end */
	for (i = 0; i < (s32)gf_list_count(samples); i++) {
		GF_WebVTTSample *sample;
		sample = (GF_WebVTTSample *)gf_list_get(samples, i);
		/* save the sample end in case there are no more samples to test */
		sample_end = sample->end;
		if (cue_start < sample->start) {
			/* cues must be ordered according to their start time, so drop the cue */
			gf_webvtt_cue_del(cue);
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[VTT] Cue not in order, broken file\n"));
			return GF_BAD_PARAM;
		} else if (cue_start == sample->start && cue_end == sample->end) {
			/* if the timing of the new cue matches the sample, no need to split, add the cue to the sample */
			gf_list_add(sample->cues, cue);
			/* the cue does not need to processed further */
			return GF_OK;
		} else if (cue_start >= sample->end) {
			/* flush the current sample */
			gf_list_del_item(samples, sample);
			parser->on_sample_parsed(parser->user, sample);
			sample = NULL;
			i--;
			/* process the cue with next sample (if any) or create a new sample */
			continue;
		} else {
			u32 j;
			if (cue_start > sample->start) {
				/* create a new sample, insert it after the current one */
				GF_WebVTTSample *new_sample = gf_webvtt_sample_new();
				new_sample->start = cue_start;
				new_sample->end = sample->end;
				gf_list_insert(samples, new_sample, i+1);
				/* split the cues from the old sample into the new one */
				for (j = 0; j < gf_list_count(sample->cues); j++) {
					GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(sample->cues, j);
					GF_WebVTTCue *new_cue = gf_webvtt_cue_split_at(old_cue, &cue->start);
					gf_list_add(new_sample->cues, new_cue);
				}
				/* adjust the end of the old sample and flush it */
				sample->end = cue_start;
				gf_list_del_item(samples, sample);
				parser->on_sample_parsed(parser->user, sample);
				sample = NULL;
				i--;
				/* process the cue again with this new sample */
				continue;
			}
			if (cue_end > sample->end) {
				/* the cue is longer than the sample, we split the cue, add one part to the current sample
				and reevaluate with the last part of the cue */
				GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(sample->cues, 0);
				GF_WebVTTCue *new_cue = gf_webvtt_cue_split_at(cue, &old_cue->end);
				gf_list_add(sample->cues, cue);
				cue = new_cue;
				cue_start = sample->end;
				/* cue_end unchanged */
				/* process the remaining part of the cue (i.e. the new cue) with the other samples */
				continue;
			} else { /* cue_end < sample->end */
				GF_WebVTTSample *new_sample = gf_webvtt_sample_new();
				new_sample->start = cue_end;
				new_sample->end   = sample->end;
				gf_list_insert(samples, new_sample, i+1);
				for (j = 0; j < gf_list_count(sample->cues); j++) {
					GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(sample->cues, j);
					GF_WebVTTCue *new_cue = gf_webvtt_cue_split_at(old_cue, &cue->end);
					gf_list_add(new_sample->cues, new_cue);
				}
				gf_list_add(sample->cues, cue);
				sample->end = new_sample->start;
				/* done with this cue */
				return GF_OK;
			}
		}
	}
	/* (a part of) the cue remains (was not overlapping) */
	if (cue_start > sample_end) {
		/* if the new cue start is greater than the last sample end,
		    create an empty sample to fill the gap, flush it */
		GF_WebVTTSample *esample = gf_webvtt_sample_new();
		esample->start = sample_end;
		esample->end   = cue_start;
		parser->on_sample_parsed(parser->user, esample);
	}
	/* if the cue has not been added to a sample, create a new sample for it */
	{
		GF_WebVTTSample *sample;
		sample = gf_webvtt_sample_new();
		gf_list_add(samples, sample);
		sample->start = cue_start;
		sample->end = cue_end;
		gf_list_add(sample->cues, cue);
	}
	return GF_OK;
}

#define REM_TRAIL_MARKS(__str, __sep) while (1) {	\
		u32 _len = (u32) strlen(__str);		\
		if (!_len) break;	\
		_len--;				\
		if (strchr(__sep, __str[_len])) { \
			had_marks = GF_TRUE; \
			__str[_len] = 0;	\
		} else break;	\
	}

extern char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type, Bool *in_progress);

GF_Err gf_webvtt_parse_timestamp(GF_WebVTTParser *parser, GF_WebVTTTimestamp *ts, const char *line)
{
	u32 len;
	u32 pos;
	u32 pos2;
	u32 value1;
	u32 value2;
	u32 value3;
	u32 value4;
	Bool is_hour = GF_FALSE;
	if (!ts || !line) return GF_BAD_PARAM;
	len = (u32) strlen(line);
	if (!len) return GF_BAD_PARAM;
	pos = 0;
	if (!(line[pos] >= '0' && line[pos] <= '9')) return GF_BAD_PARAM;
	value1 = 0;
	while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
		value1 = value1*10 + (line[pos]-'0');
		pos++;
	}
	if (pos>2 || value1>59) {
		is_hour = GF_TRUE;
	}
	if (pos == len || line[pos] != ':') {
		return GF_BAD_PARAM;
	} else {
		pos++;
	}
	value2 = 0;
	pos2 = 0;
	while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
		value2 = value2*10 + (line[pos]-'0');
		pos++;
		pos2++;
		if (pos2 > 2) return GF_BAD_PARAM;
	}
	if (is_hour || (pos < len && line[pos] == ':')) {
		if (pos == len || line[pos] != ':') {
			return GF_BAD_PARAM;
		} else {
			pos++;
			pos2 = 0;
			value3 = 0;
			while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
				value3 = value3*10 + (line[pos]-'0');
				pos++;
				pos2++;
				if (pos2 > 2) return GF_BAD_PARAM;
			}
		}
	} else {
		value3 = value2;
		value2 = value1;
		value1 = 0;
	}
	/* checking SRT syntax for timestamp with , */
	if (pos == len || (!parser->is_srt && line[pos] != '.') || (parser->is_srt && line[pos] != ',')) {
		return GF_BAD_PARAM;
	} else {
		pos++;
	}
	pos2 = 0;
	value4 = 0;
	while (pos < len && line[pos] >= '0' && line[pos] <= '9') {
		value4 = value4*10 + (line[pos]-'0');
		pos++;
		pos2++;
		if (pos2 > 4) return GF_BAD_PARAM;
	}
	if (value2>59 || value3 > 59) return GF_BAD_PARAM;
	ts->hour = value1;
	ts->min = value2;
	ts->sec = value3;
	ts->ms = value4;
	return GF_OK;
}

#define SKIP_WHITESPACE \
    while (pos < len && (line[pos] == ' ' || line[pos] == '\t' || \
           line[pos] == '\r' || line[pos] == '\f' || line[pos] == '\n')) pos++;

GF_Err gf_webvtt_parser_parse_timings_settings(GF_WebVTTParser *parser, GF_WebVTTCue *cue, char *line, u32 len)
{
	GF_Err e;
	char *timestamp_string;
	u32 pos;

	pos = 0;
	if (!cue || !line || !len) return GF_BAD_PARAM;
	SKIP_WHITESPACE
	timestamp_string = line + pos;
	while (pos < len && line[pos] != ' ' && line[pos] != '\t') pos++;
	if (pos == len) {
		e = GF_CORRUPTED_DATA;
		parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in", line);
		return e;
	}
	line[pos] = 0;
	e = gf_webvtt_parse_timestamp(parser, &cue->start, timestamp_string);
	if (e) {
		parser->report_message(parser->user, e, "Bad VTT timestamp formatting", timestamp_string);
		return e;
	}
	line[pos] = ' ';
	SKIP_WHITESPACE
	if (pos == len) {
		e = GF_CORRUPTED_DATA;
		parser->report_message(parser->user, e, "Error scanning WebVTT cue timing", line);
		return e;
	}
	if ( (pos+2)>= len || line[pos] != '-' || line[pos+1] != '-' || line[pos+2] != '>') {
		e = GF_CORRUPTED_DATA;
		parser->report_message(parser->user, e, "Error scanning WebVTT cue timing", line);
		return e;
	} else {
		pos += 3;
		SKIP_WHITESPACE
		if (pos == len) {
			e = GF_CORRUPTED_DATA;
			parser->report_message(parser->user, e, "Error scanning WebVTT cue timing in", line);
			return e;
		}
		timestamp_string = line + pos;
		while (pos < len && line[pos] != ' ' && line[pos] != '\t') pos++;
		if (pos < len) {
			line[pos] = 0;
		}
		e = gf_webvtt_parse_timestamp(parser, &cue->end, timestamp_string);
		if (e) {
			parser->report_message(parser->user, e, "Bad VTT timestamp formatting", timestamp_string);
			return e;
		}
		if (pos < len) {
			line[pos] = ' ';
		}
		SKIP_WHITESPACE
		if (pos < len) {
			char *settings = line + pos;
			e = gf_webvtt_cue_add_property(cue, WEBVTT_SETTINGS, settings, (u32) strlen(settings));
		}

		if (!gf_webvtt_timestamp_greater(&cue->end, &cue->start)) {
			parser->report_message(parser->user, e, "Bad VTT timestamps, end smaller than start", timestamp_string);
			cue->end = cue->start;
			cue->end.ms += 1;
			return GF_NON_COMPLIANT_BITSTREAM;

		}
	}
	return e;
}

GF_Err gf_webvtt_parser_parse_internal(GF_WebVTTParser *parser, GF_WebVTTCue *cue, FILE *ext_file, Bool is_eof)
{
	char szLine[2049];
	char *sOK;
	u32 len;
	GF_Err e;
	char *prevLine = NULL;
	char *header = NULL;
	u32 header_len = 0;
	Bool had_marks = GF_FALSE;
	u32 is_resume = ext_file ? 1 : 9;

	if (!parser) return GF_BAD_PARAM;
	parser->suspend = GF_FALSE;

	if (parser->is_srt) {
		parser->on_header_parsed(parser->user, "WEBVTT\n");
	}

	szLine[2048]=0;
	while (!parser->is_eof) {
		Bool in_progress = is_eof;
		if (!cue && parser->suspend)
			break;
		sOK = gf_text_get_utf8_line(szLine, 2048, ext_file ? ext_file : *parser->vtt_in, parser->unicode_type, &in_progress);
		if (in_progress) {
			parser->suspend = GF_TRUE;
			if (ext_file && cue && !cue->text) {
				gf_webvtt_add_cue_to_samples(parser, parser->samples, cue);
			}
			break;
		}
		REM_TRAIL_MARKS(szLine, "\r\n")
		len = (u32) strlen(szLine);
		if (parser->is_srt && sOK && !strncmp(sOK, "WEBVTT", 6)) {
			parser->is_srt = GF_FALSE;
			parser->state = WEBVTT_PARSER_STATE_WAITING_SIGNATURE;
		}
		switch (parser->state) {
		case WEBVTT_PARSER_STATE_WAITING_SIGNATURE:
			if (!sOK || len < 6 || strnicmp(szLine, "WEBVTT", 6) || (len > 6 && szLine[6] != ' ' && szLine[6] != '\t')) {
				e = GF_CORRUPTED_DATA;
				parser->report_message(parser->user, e, "Bad WEBVTT file signature", szLine);
				goto exit;
			} else {
				if (had_marks) {
					szLine[len] = '\n';
					len++;
				}
				header = gf_strdup(szLine);
				header_len = len;
				parser->state = WEBVTT_PARSER_STATE_WAITING_HEADER;
			}
			break; /* proceed to next line */
		case WEBVTT_PARSER_STATE_WAITING_HEADER:
			if (prevLine) {
				u32 prev_len = (u32) strlen(prevLine);
				header = (char *)gf_realloc(header, header_len + prev_len + 1);
				strcpy(header+header_len,prevLine);
				header_len += prev_len;
				gf_free(prevLine);
				prevLine = NULL;
			}
			if (sOK && len) {
				if (strstr(szLine, "-->")) {
					parser->on_header_parsed(parser->user, header);
					/* continue to the next state without breaking */
					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP;
					/* no break, continue to the next state*/
				} else {
					if (had_marks) {
						szLine[len] = '\n';
						len++;
					}
					prevLine = gf_strdup(szLine);
					break; /* proceed to next line */
				}
			} else {
				parser->on_header_parsed(parser->user, header);
				if (header) gf_free(header);
				header = NULL;
				if (!sOK) {
					/* end of file, parsing is done */
					parser->is_eof = GF_TRUE;
					break;
				} else {
					/* empty line means end of header */
					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
					/* no break, continue to the next state*/
				}
			}
		case WEBVTT_PARSER_STATE_WAITING_CUE:
			if (sOK && len) {
				if (strstr(szLine, "-->")) {
					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP;
					/* continue to the next state without breaking */
				} else {
					/* discard the previous line */
					/* should we do something with it ? callback ?*/
					if (prevLine && !parser->in_comment) {
						gf_free(prevLine);
						prevLine = NULL;
					}
					/* save this new line */
					if (had_marks) {
						szLine[len] = '\n';
						len++;
					}
					if (prevLine)
						gf_dynstrcat(&prevLine, szLine, NULL);
					else
						prevLine = gf_strdup(szLine);
					if (parser->prev_line_empty && (!strncmp(prevLine, "NOTE ", 5) || !strcmp(prevLine, "NOTE\n")))
						parser->in_comment = GF_TRUE;
					/* stay in the same state */
					break;
				}
			} else {
				/* discard the previous line */
				/* should we do something with it ? callback ?*/
				if (parser->in_comment) {
					if (!parser->comment_text) {
						parser->comment_text = prevLine;
						prevLine = NULL;
					} else {
						gf_dynstrcat(&parser->comment_text, prevLine, "\n");
						gf_free(prevLine);
						prevLine = NULL;
					}
					parser->in_comment = GF_FALSE;
				}
				if (prevLine) {
					gf_free(prevLine);
					prevLine = NULL;
				}
				if (!sOK) {
					parser->is_eof = GF_TRUE;
					break;
				} else {
					/* remove empty lines and stay in the same state */
					break;
				}
			}
		case WEBVTT_PARSER_STATE_WAITING_CUE_TIMESTAMP:
			if (sOK && len) {
				if (cue == NULL) {
					cue = gf_webvtt_cue_new();
				}
				if (prevLine) {
					gf_webvtt_cue_add_property(cue, WEBVTT_ID, prevLine, (u32) strlen(prevLine));
					gf_free(prevLine);
					prevLine = NULL;
				}
				if (parser->comment_text) {
					gf_webvtt_cue_add_property(cue, WEBVTT_PRECUE_TEXT, parser->comment_text, (u32) strlen(parser->comment_text));
					gf_free(parser->comment_text);
					parser->comment_text = NULL;
				}
				e = gf_webvtt_parser_parse_timings_settings(parser, cue, szLine, len);
				if (e) {
					if (cue) gf_webvtt_cue_del(cue);
					cue = NULL;
					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
				} else {
//					start = (u32)gf_webvtt_timestamp_get(&cue->start);
//					end   = (u32)gf_webvtt_timestamp_get(&cue->end);
					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD;
				}
			} else {
				/* not possible */
				gf_assert(0);
				if (header) gf_free(header);
				return GF_NON_COMPLIANT_BITSTREAM;
			}
			break;
		case WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD:
			if ((is_resume==1) && !cue) {
				GF_WebVTTSample *sample = (GF_WebVTTSample *)gf_list_last(parser->samples);
				cue = sample ? gf_list_last(sample->cues) : NULL;
				is_resume = 2;
			}
			if (sOK && len) {
				if (!strncmp(szLine, "NOTE ", 5)) {
					if (had_marks) {
						szLine[len] = '\n';
						len++;
					}
					gf_webvtt_cue_add_property(cue, WEBVTT_POSTCUE_TEXT, szLine, len);
					parser->in_comment = GF_TRUE;
					len = 0;
				} else if (parser->in_comment) {
					parser->in_comment = GF_FALSE;
				}
			}
			//special case when injecting multiple times the header, ignore it
			if (ext_file && !strcmp(szLine, "WEBVTT")) {
			}
			else if (sOK && len) {
				if (had_marks) {
					szLine[len] = '\n';
					len++;
				}
				assert(cue);
				gf_webvtt_cue_add_property(cue, parser->in_comment ? WEBVTT_POSTCUE_TEXT : WEBVTT_PAYLOAD, szLine, len);
				/* remain in the same state as a cue payload can have multiple lines */
				break;
			} else {
				/* end of the current cue */
				if (is_resume!=2) {
					gf_webvtt_add_cue_to_samples(parser, parser->samples, cue);
				}
				cue = NULL;
				parser->in_comment = GF_FALSE;

				if (!sOK) {
					parser->is_eof = GF_TRUE;
					break;
				} else {
					/* empty line, move to next cue */
					parser->state = WEBVTT_PARSER_STATE_WAITING_CUE;
					break;
				}
			}
		}
		parser->prev_line_empty = len ? GF_FALSE : GF_TRUE;
	}
	if (header) gf_free(header);
	header = NULL;

	if (parser->suspend)
		return GF_OK;

	/* no more cues to come, flush everything */
	if (cue) {
		gf_webvtt_add_cue_to_samples(parser, parser->samples, cue);
		cue = NULL;
	}
	while (gf_list_count(parser->samples) > 0) {
		GF_WebVTTSample *sample = (GF_WebVTTSample *)gf_list_get(parser->samples, 0);
		parser->last_duration = (sample->end > sample->start) ? sample->end - sample->start : 0;
		gf_list_rem(parser->samples, 0);
		parser->on_sample_parsed(parser->user, sample);
	}
	e = GF_EOS;
exit:
	if (cue) gf_webvtt_cue_del(cue);
	if (prevLine) gf_free(prevLine);
	if (header) gf_free(header);
	return e;
}

GF_Err gf_webvtt_parser_parse(GF_WebVTTParser *parser)
{
	return gf_webvtt_parser_parse_internal(parser, NULL, NULL, GF_TRUE);
}
GF_Err gf_webvtt_parser_parse_ext(GF_WebVTTParser *parser, FILE *ext_file, Bool in_eos)
{
	return gf_webvtt_parser_parse_internal(parser, NULL, ext_file, in_eos);
}


void gf_webvtt_parser_not_done(GF_WebVTTParser *parser)
{
	if (parser) parser->is_eof = GF_FALSE;
}

GF_Err gf_webvtt_parser_flush(GF_WebVTTParser *parser)
{
	while (gf_list_count(parser->samples) > 0) {
		GF_WebVTTSample *sample = (GF_WebVTTSample *)gf_list_get(parser->samples, 0);
		parser->last_duration = (sample->end > sample->start) ? sample->end - sample->start : 0;
		gf_list_rem(parser->samples, 0);
		parser->on_sample_parsed(parser->user, sample);
	}
	return GF_OK;
}

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)
{
	GF_WebVTTCue *cue = gf_webvtt_cue_new();
	parser->state = WEBVTT_PARSER_STATE_WAITING_CUE_PAYLOAD;
	gf_webvtt_timestamp_set(&cue->start, start);
	gf_webvtt_timestamp_set(&cue->end, end);
	parser->is_init = GF_TRUE;

	if (vtt_cueid) cue->id = gf_strdup(vtt_cueid);
	if (vtt_settings) cue->settings = gf_strdup(vtt_settings);
	if (vtt_pre) cue->pre_text = gf_strdup(vtt_pre);

	GF_Err e = gf_webvtt_parser_parse_internal(parser, cue, NULL, GF_TRUE);
	parser->is_eof = GF_FALSE;
	return e;
}

GF_EXPORT
GF_List *gf_webvtt_parse_iso_cues(GF_ISOSample *iso_sample, u64 start, u64 end)
{
	return gf_webvtt_parse_cues_from_data(iso_sample->data, iso_sample->dataLength, start, end);
}

GF_EXPORT
GF_List *gf_webvtt_parse_cues_from_data(const u8 *data, u32 dataLength, u64 start, u64 end)
{
	GF_List *cues;
	GF_WebVTTCue *cue;
	GF_VTTCueBox *cuebox;
	GF_BitStream *bs;
	char *pre_text;
	cue = NULL;
	pre_text = NULL;
	cues = gf_list_new();
	bs = gf_bs_new((u8 *)data, dataLength, GF_BITSTREAM_READ);
	while(gf_bs_available(bs))
	{
		GF_Err  e;
		GF_Box  *box;
		e = gf_isom_box_parse(&box, bs);
		if (e) return NULL;
		if (box->type == GF_ISOM_BOX_TYPE_VTCC_CUE) {
			cuebox = (GF_VTTCueBox *)box;
			cue   = gf_webvtt_cue_new();
			if (pre_text) {
				gf_webvtt_cue_add_property(cue, WEBVTT_PRECUE_TEXT, pre_text, (u32) strlen(pre_text));
				gf_free(pre_text);
				pre_text = NULL;
			}
			gf_list_add(cues, cue);
			gf_webvtt_timestamp_set(&cue->start, start);
			gf_webvtt_timestamp_set(&cue->end, end);
			if (cuebox->id) {
				gf_webvtt_cue_add_property(cue, WEBVTT_ID, cuebox->id->string, (u32) strlen(cuebox->id->string));
			}
			if (cuebox->settings) {
				gf_webvtt_cue_add_property(cue, WEBVTT_SETTINGS, cuebox->settings->string, (u32) strlen(cuebox->settings->string));
			}
			if (cuebox->payload) {
				gf_webvtt_cue_add_property(cue, WEBVTT_PAYLOAD, cuebox->payload->string, (u32) strlen(cuebox->payload->string));
			}
		} else if (box->type == GF_ISOM_BOX_TYPE_VTTA) {
			GF_StringBox *sbox = (GF_StringBox *)box;
			if (cue) {
				gf_webvtt_cue_add_property(cue, WEBVTT_POSTCUE_TEXT, sbox->string, (u32) strlen(sbox->string));
			} else {
				pre_text = gf_strdup(sbox->string);
			}
		}
		gf_isom_box_del(box);
	}
	gf_bs_del(bs);
	return cues;
}

GF_Err gf_webvtt_merge_cues(GF_WebVTTParser *parser, u64 start, GF_List *cues)
{
	GF_WebVTTSample *wsample;
	GF_WebVTTSample *prev_wsample;
	Bool            has_continuation_cue = GF_FALSE;

	gf_assert(gf_list_count(parser->samples) <= 1);

	wsample = gf_webvtt_sample_new();
	wsample->start = start;

	prev_wsample = (GF_WebVTTSample *)gf_list_last(parser->samples);
	while (gf_list_count(cues)) {
		GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, 0);
		gf_list_rem(cues, 0);
		/* add the cue to the current sample */
		gf_list_add(wsample->cues, cue);
		/* update with the previous sample */
		if (prev_wsample) {
			Bool do_del = GF_TRUE;
			Bool  found = GF_FALSE;
			while (!found && gf_list_count(prev_wsample->cues)) {
				GF_WebVTTCue *old_cue = (GF_WebVTTCue *)gf_list_get(prev_wsample->cues, 0);
				gf_list_rem(prev_wsample->cues, 0);
				if (
				    ((!cue->id && !old_cue->id) || (old_cue->id && cue->id && !strcmp(old_cue->id, cue->id))) &&
				    ((!cue->settings && !old_cue->settings) || (old_cue->settings && cue->settings && !strcmp(old_cue->settings, cue->settings))) &&
				    ((!cue->text && !old_cue->text) || (old_cue->text && cue->text && !strcmp(old_cue->text, cue->text)))
				) {
					/* if it is the same cue, update its start with the initial start */
					cue->start = old_cue->start;
					has_continuation_cue = GF_TRUE;
					found = GF_TRUE;
					if (old_cue->pre_text) {
						cue->pre_text = old_cue->pre_text;
						old_cue->pre_text = NULL;
					}
					if (old_cue->post_text) {
						cue->post_text = old_cue->post_text;
						old_cue->post_text = NULL;
					}
				} else {
					/* finalize the end cue time */
					if (gf_webvtt_timestamp_is_zero(&old_cue->end)) {
						gf_webvtt_timestamp_set(&old_cue->end, wsample->start);
					}
					/* transfer the cue */
					if (!has_continuation_cue) {
						/* the cue can be safely serialized while keeping the order */
						parser->on_cue_read(parser->user, old_cue);
					} else {
						/* keep the cue in the current sample to respect cue start ordering */
						gf_list_add(wsample->cues, old_cue);
						do_del = GF_FALSE;
					}
				}
				/* delete the old cue */
				if (do_del)
					gf_webvtt_cue_del(old_cue);
			}
		}
	}
	/* No cue in the current sample */
	if (prev_wsample) {
		while (gf_list_count(prev_wsample->cues)) {
			Bool do_del = GF_TRUE;
			GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(prev_wsample->cues, 0);
			gf_list_rem(prev_wsample->cues, 0);
			/* finalize the end cue time */
			if (gf_webvtt_timestamp_is_zero(&cue->end)) {
				gf_webvtt_timestamp_set(&cue->end, wsample->start);
			}
			/* transfer the cue */
			if (!has_continuation_cue) {
				/* the cue can be safely serialized while keeping the order */
				parser->on_cue_read(parser->user, cue);
			} else {
				/* keep the cue in the current sample to respect cue start ordering */
				gf_list_add(wsample->cues, cue);
				do_del = GF_FALSE;
			}
			if (do_del)
				gf_webvtt_cue_del(cue);
		}
		gf_webvtt_sample_del(prev_wsample);
		gf_list_rem_last(parser->samples);
		prev_wsample = NULL;
	} else {
		/* nothing to do */
	}
	if (gf_list_count(wsample->cues)) {
		gf_list_add(parser->samples, wsample);
	} else {
		gf_webvtt_sample_del(wsample);
	}
	return GF_OK;
}

static GF_Err gf_webvtt_parse_iso_sample(GF_WebVTTParser *parser, u32 timescale, GF_ISOSample *iso_sample, u32 duration, Bool merge, Bool box_mode)
{
	if (merge) {
		u64             start;
		u64             end;
		GF_List         *cues;
		start = (iso_sample->DTS * 1000) / timescale;
		end = (iso_sample->DTS + duration) * 1000 / timescale;
		cues = gf_webvtt_parse_iso_cues(iso_sample, start, end);
		gf_webvtt_merge_cues(parser, start, cues);
		gf_list_del(cues);
	} else {
		GF_Err gf_webvtt_dump_iso_sample(FILE *dump, u32 timescale, GF_ISOSample *iso_sample, Bool box_mode);

		gf_webvtt_dump_iso_sample((FILE *)parser->user, timescale, iso_sample, box_mode);
	}

	return GF_OK;
}
#endif //GPAC_DISABLE_MEDIA_IMPORT


void gf_webvtt_timestamp_set(GF_WebVTTTimestamp *ts, u64 value)
{
	u64 tmp;
	if (!ts) return;
	tmp = value;
	ts->hour = (u32)(tmp/(3600*1000));
	tmp -= (u64) ts->hour*3600*1000;
	ts->min  = (u32)(tmp/(60*1000));
	tmp -= (u64) ts->min*60*1000;
	ts->sec  = (u32)(tmp/1000);
	tmp -= (u64) ts->sec*1000;
	ts->ms   = (u32)tmp;
}

u64 gf_webvtt_timestamp_get(GF_WebVTTTimestamp *ts)
{
	if (!ts) return 0;
	return (u64)(3600*ts->hour + 60*ts->min + ts->sec)*1000 + ts->ms;
}

void gf_webvtt_timestamp_dump(GF_WebVTTTimestamp *ts, FILE *dump, Bool dump_hour)
{
	if (dump_hour || ts->hour != 0) {
		gf_fprintf(dump, "%02u:", ts->hour);
	}

	gf_fprintf(dump, "%02u:%02u.%03u", ts->min, ts->sec, ts->ms);
}
GF_Err gf_webvtt_dump_header_boxed(FILE *dump, const u8 *data, u32 dataLength, u32 *dumpedLength)
{
#ifdef GPAC_DISABLE_ISOM
	return GF_NOT_SUPPORTED;
#else
	GF_Err e;
	GF_Box *box;
	GF_StringBox *config;
	GF_BitStream *bs;
	*dumpedLength = 0;
	bs = gf_bs_new((u8 *)data, dataLength, GF_BITSTREAM_READ);
	e = gf_isom_box_parse(&box, bs);
	if (!box || (box->type != GF_ISOM_BOX_TYPE_VTTC_CONFIG)) {
		gf_bs_del(bs);
		if (box) gf_isom_box_del(box);
		return GF_BAD_PARAM;
	}
	config = (GF_StringBox *)box;
	if (config->string) {
		gf_fprintf(dump, "%s", config->string);
		*dumpedLength = (u32)strlen(config->string)+1;
	}
	gf_bs_del(bs);
	gf_isom_box_del(box);
	return e;
#endif
}

#ifndef GPAC_DISABLE_ISOM
GF_Err gf_webvtt_dump_header(FILE *dump, GF_ISOFile *file, u32 track, Bool box_mode, u32 index)
{
	GF_WebVTTSampleEntryBox *wvtt;
	wvtt = gf_webvtt_isom_get_description(file, track, index);
	if (!wvtt) return GF_BAD_PARAM;
	if (box_mode) {
#ifndef GPAC_DISABLE_ISOM_DUMP
		gf_isom_box_dump(wvtt, dump);
#else
		return GF_NOT_SUPPORTED;
#endif
	} else {
		gf_fprintf(dump, "%s\n\n", wvtt->config->string);
	}
	return GF_OK;
}

GF_Err gf_webvtt_dump_iso_sample(FILE *dump, u32 timescale, GF_ISOSample *iso_sample, Bool box_mode)
{
	GF_Err e;
	GF_BitStream *bs;

	if (box_mode) {
		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);
	}
	bs = gf_bs_new(iso_sample->data, iso_sample->dataLength, GF_BITSTREAM_READ);
	while(gf_bs_available(bs))
	{
		GF_Box *box;
		GF_WebVTTTimestamp ts;
		e = gf_isom_box_parse(&box, bs);
		if (e) return e;

		if (box_mode) {
#ifndef GPAC_DISABLE_ISOM_DUMP
			gf_isom_box_dump(box, dump);
#endif
		} else if (box->type == GF_ISOM_BOX_TYPE_VTCC_CUE) {
			GF_VTTCueBox *cuebox = (GF_VTTCueBox *)box;
			if (cuebox->id) gf_fprintf(dump, "%s", cuebox->id->string);
			gf_webvtt_timestamp_set(&ts, (iso_sample->DTS * 1000) / timescale);
			gf_webvtt_timestamp_dump(&ts, dump, GF_FALSE);
			gf_fprintf(dump, " --> NEXT");
			if (cuebox->settings) gf_fprintf(dump, " %s", cuebox->settings->string);
			gf_fprintf(dump, "\n");
			if (cuebox->payload) gf_fprintf(dump, "%s", cuebox->payload->string);
			gf_fprintf(dump, "\n");
		} else if (box->type == GF_ISOM_BOX_TYPE_VTTE) {
			gf_webvtt_timestamp_set(&ts, (iso_sample->DTS * 1000) / timescale);
			gf_webvtt_timestamp_dump(&ts, dump, GF_FALSE);
			gf_fprintf(dump, " --> NEXT\n\n");
		} else if (box->type == GF_ISOM_BOX_TYPE_VTTA) {
			gf_fprintf(dump, "%s\n\n", ((GF_StringBox *)box)->string);
		}
		gf_isom_box_del(box);
	}
	gf_bs_del(bs);
	if (box_mode) {
		gf_fprintf(dump, "</WebVTTSample>\n");
	}
	return GF_OK;
}
#endif

#ifndef GPAC_DISABLE_ISOM
GF_Err gf_webvtt_parser_finalize(GF_WebVTTParser *parser, u64 duration)
{
	GF_WebVTTSample *sample;
	gf_assert(gf_list_count(parser->samples) <= 1);
	sample = (GF_WebVTTSample *)gf_list_get(parser->samples, 0);
	if (sample) {
		while (gf_list_count(sample->cues)) {
			GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(sample->cues, 0);
			gf_list_rem(sample->cues, 0);
			if (gf_webvtt_timestamp_is_zero(&cue->end)) {
				gf_webvtt_timestamp_set(&cue->end, duration);
			}
			parser->on_cue_read(parser->user, cue);
			gf_webvtt_cue_del(cue);
		}
		gf_webvtt_sample_del(sample);
		gf_list_rem(parser->samples, 0);
	}
	return GF_OK;
}

#ifndef GPAC_DISABLE_MEDIA_IMPORT
static void gf_webvtt_dump_cue(void *user, GF_WebVTTCue *cue)
{
	FILE *dump = (FILE *)user;
	if (!cue || !dump) return;
	if (cue->pre_text) {
		gf_fprintf(dump, "%s", cue->pre_text);
		gf_fprintf(dump, "\n");
		gf_fprintf(dump, "\n");
	}
	if (cue->id) gf_fprintf(dump, "%s\n", cue->id);
	if (cue->start.hour || cue->end.hour) {
		gf_webvtt_timestamp_dump(&cue->start, dump, GF_TRUE);
		gf_fprintf(dump, " --> ");
		gf_webvtt_timestamp_dump(&cue->end, dump, GF_TRUE);
	} else {
		gf_webvtt_timestamp_dump(&cue->start, dump, GF_FALSE);
		gf_fprintf(dump, " --> ");
		gf_webvtt_timestamp_dump(&cue->end, dump, GF_FALSE);
	}
	if (cue->settings) {
		gf_fprintf(dump, " %s", cue->settings);
	}
	gf_fprintf(dump, "\n");
	if (cue->text) gf_fprintf(dump, "%s", cue->text);
	gf_fprintf(dump, "\n");
	gf_fprintf(dump, "\n");
	if (cue->post_text) {
		gf_fprintf(dump, "%s", cue->post_text);
		gf_fprintf(dump, "\n");
		gf_fprintf(dump, "\n");
	}
}
#endif

//unused
#if 0
static GF_Err gf_webvtt_dump_cues(FILE *dump, GF_List *cues)
{
	u32 i;
	for (i = 0; i < gf_list_count(cues); i++) {
		GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, i);
		gf_webvtt_dump_cue(dump, cue);
	}
	return GF_OK;
}

GF_Err gf_webvtt_dump_sample(FILE *dump, GF_WebVTTSample *samp)
{
	gf_fprintf(stdout, "NOTE New WebVTT Sample ("LLD"-"LLD")\n\n", samp->start, samp->end);
	return gf_webvtt_dump_cues(dump, samp->cues);
}
#endif


void gf_webvtt_parser_cue_callback(GF_WebVTTParser *parser, void (*on_cue_read)(void *, GF_WebVTTCue *), void *udta)
{
	parser->on_cue_read = on_cue_read;
	parser->user = udta;
}

#ifndef GPAC_DISABLE_MEDIA_EXPORT

GF_EXPORT
GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, u32 track, Bool merge, Bool box_dump)
{
#ifdef GPAC_DISABLE_MEDIA_IMPORT
	return GF_NOT_SUPPORTED;
#else
	GF_Err  e;
	u32     i;
	u32     count;
	u32     timescale;
	FILE    *out;
	u32     di;
	u64     duration;
	GF_WebVTTParser *parser;

	out = (dumper->dump_file ? dumper->dump_file : stdout);
	if (!out) return GF_IO_ERR;// gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);

	parser = gf_webvtt_parser_new();
	parser->user = out;
	parser->on_cue_read = gf_webvtt_dump_cue;

	if (box_dump)
		gf_fprintf(out, "<WebVTTTrack trackID=\"%d\">\n", gf_isom_get_track_id(dumper->file, track) );

	e = gf_webvtt_dump_header(out, dumper->file, track, box_dump, 1);
	if (e) goto exit;

	timescale = gf_isom_get_media_timescale(dumper->file, track);

	count = gf_isom_get_sample_count(dumper->file, track);
	for (i=0; i<count; i++) {
		u32 sdur;
		GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, i+1, &di);
		if (!samp) {
			e = gf_isom_last_error(dumper->file);
			goto exit;
		}
		sdur = gf_isom_get_sample_duration(dumper->file, track, i+1);
		e = gf_webvtt_parse_iso_sample(parser, timescale, samp, sdur, merge, box_dump);
		if (e) {
			goto exit;
		}
		gf_isom_sample_del(&samp);
	}
	duration = gf_isom_get_media_duration(dumper->file, track);
	gf_webvtt_parser_finalize(parser, duration);

	if (box_dump)
		gf_fprintf(out, "</WebVTTTrack>\n");

exit:
	gf_webvtt_parser_del(parser);
	return e;
#endif
}

#endif /*GPAC_DISABLE_MEDIA_EXPORT*/

#endif /*GPAC_DISABLE_ISOM_DUMP*/

#endif /*GPAC_DISABLE_VTT*/
