/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005 
 *					All rights reserved
 *
 *  This file is part of GPAC / Authoring Tools sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU 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 General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */



#include <gpac/intern/m4_author_dev.h>

enum
{
	FMT_TXT_NONE = 0,
	FMT_TXT_SRT,
	FMT_TXT_SUB,
	FMT_TXT_TTXT,
	FMT_TXT_TeXML,
};

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


static M4Err guess_subtitle_fmt(char *filename, u32 *fmt)
{
	char szLine[2048], szTest[10];
	u32 val;
	FILE *test = fopen(filename, "rt");
	if (!test) return M4URLNotFound;

	while (fgets(szLine, 2048, test) != NULL) {
		REM_TRAIL_MARKS(szLine, "\r\n\t ")

		if (strlen(szLine)) break;
	}
	*fmt = FMT_TXT_NONE;
	if ((szLine[0]=='{') && strstr(szLine, "}{")) *fmt = FMT_TXT_SUB;
	else if (sscanf(szLine, "%d", &val)==1) {
		sprintf(szTest, "%d", val);
		if (!strcmp(szTest, szLine)) *fmt = FMT_TXT_SRT;
	}
	else if (!strnicmp(szLine, "<?xml ", 6)) {
		char *ext = strrchr(filename, '.');
		if (!strnicmp(ext, ".ttxt", 5)) *fmt = FMT_TXT_TTXT;
		ext = strstr(szLine, "?>");
		if (ext) ext += 2;
		if (!ext[0]) fgets(szLine, 2048, test);
		if (strstr(szLine, "x-quicktime-tx3g")) *fmt = FMT_TXT_TeXML;
	}
	fclose(test);
	return M4OK;
}

static M4Err M4SM_ImportSRT_BIFS(M4SceneManager *ctx, ESDescriptor *src, MuxInfoDescriptor *mux)
{
	M4Err e;
	SFNode *text, *font;
	M4StreamContext *srt;
	FILE *srt_in;
	FieldInfo string, style;
	u32 sh, sm, ss, sms, eh, em, es, ems, start, end;
	M4AUContext *au;
	SGCommand *com;
	SFString *sfstr;
	CommandFieldInfo *inf;
	Bool italic, underlined, bold;
	u32 state, curLine, line, i, len;
	char szLine[2048], szText[2048], *ptr;
	M4StreamContext *sc = NULL;

	if (!ctx->scene_graph) {
		fprintf(stdout, "Error importing SRT: base scene not assigned\n");
		return M4BadParam;
	}
	for (i=0; i<ChainGetCount(ctx->streams); i++) {
		sc = ChainGetEntry(ctx->streams, i);
		if (sc->streamType==M4ST_SCENE) break;
		sc = NULL;
	}

	if (!sc) {
		fprintf(stdout, "Error importing SRT: Cannot locate base scene\n");
		return M4BadParam;
	}
	if (!mux->textNode) {
		fprintf(stdout, "Error importing SRT: Target text node unspecified\n");
		return M4BadParam;
	}
	text = SG_FindNodeByName(ctx->scene_graph, mux->textNode);
	if (!text) {
		fprintf(stdout, "Error importing SRT: Cannot find target text node %s\n", mux->textNode);
		return M4BadParam;
	}
	if (Node_GetFieldByName(text, "string", &string) != M4OK) {
		fprintf(stdout, "Error importing SRT: Target text node %s doesn't look like text\n", mux->textNode);
		return M4BadParam;
	}

	font = NULL;
	if (mux->fontNode) {
		font = SG_FindNodeByName(ctx->scene_graph, mux->fontNode);
		if (!font) {
			fprintf(stdout, "Error importing SRT: Cannot find target font node %s\n", mux->fontNode);
			return M4BadParam;
		}
		if (Node_GetFieldByName(font, "style", &style) != M4OK) {
			fprintf(stdout, "Error importing SRT: Target font node %s doesn't look like font\n", mux->fontNode);
			return M4BadParam;
		}
	}

	srt_in = fopen(mux->file_name, "rt");
	if (!srt_in) {
		fprintf(stdout, "Cannot open input SRT %s\n", mux->file_name);
		return M4URLNotFound;
	}

	srt = M4SM_NewStream(ctx, src->ESID, M4ST_SCENE, 1);
	if (!srt) return M4OutOfMem;

	if (!src->slConfig) src->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
	src->slConfig->timestampResolution = 1000;
	if (!src->decoderConfig) src->decoderConfig = (DecoderConfigDescriptor *) OD_NewDescriptor(DecoderConfigDescriptor_Tag);
	src->decoderConfig->streamType = M4ST_SCENE;
	src->decoderConfig->objectTypeIndication = 1;

	e = M4OK;
	state = end = 0;
	curLine = 0;
	au = NULL;
	com = NULL;
	italic = underlined = bold = 0;
	inf = NULL;

	while (1) {
		char *sOK = fgets(szLine, 2048, srt_in);

		if (sOK) REM_TRAIL_MARKS(szLine, "\r\n\t ")

		if (!sOK || !strlen(szLine)) {
			state = 0;
			if (au) {
				/*if italic or underscore do it*/
				if (font && (italic || underlined || bold)) {
					com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
					com->node = font;
					Node_Register(font, NULL);
					inf = SG_NewFieldCommand(com);
					inf->fieldIndex = style.fieldIndex;
					inf->fieldType = style.fieldType;
					sfstr = inf->field_ptr = VRML_NewFieldPointer(style.fieldType);
					if (bold && italic && underlined) sfstr->buffer = strdup("BOLDITALIC UNDERLINED");
					else if (italic && underlined) sfstr->buffer = strdup("ITALIC UNDERLINED");
					else if (bold && underlined) sfstr->buffer = strdup("BOLD UNDERLINED");
					else if (underlined) sfstr->buffer = strdup("UNDERLINED");
					else if (bold && italic) sfstr->buffer = strdup("BOLDITALIC");
					else if (bold) sfstr->buffer = strdup("BOLD");
					else sfstr->buffer = strdup("ITALIC");
					ChainAddEntry(au->commands, com);
				}

				au = M4SM_NewAU(srt, end, 0, 1);
				com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
				com->node = text;
				Node_Register(text, NULL);
				inf = SG_NewFieldCommand(com);
				inf->fieldIndex = string.fieldIndex;
				inf->fieldType = string.fieldType;
				inf->field_ptr = VRML_NewFieldPointer(string.fieldType);
				ChainAddEntry(au->commands, com);
				/*reset font styles so that all AUs are true random access*/
				if (font) {
					com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
					com->node = font;
					Node_Register(font, NULL);
					inf = SG_NewFieldCommand(com);
					inf->fieldIndex = style.fieldIndex;
					inf->fieldType = style.fieldType;
					inf->field_ptr = VRML_NewFieldPointer(style.fieldType);
					ChainAddEntry(au->commands, com);
				}
				au = NULL;
			}
			inf = NULL;
			if (!sOK) break;
			continue;
		}

		switch (state) {
		case 0:
			if (sscanf(szLine, "%d", &line) != 1) {
				fprintf(stdout, "Bad SRT format\n");
				e = M4CorruptedData;
				goto exit;
			}
			if (line != curLine + 1) {
				fprintf(stdout, "Error importing SRT frame (previous %d, current %d)\n", curLine, line);
				e = M4CorruptedData;
				goto exit;
			}
			curLine = line;
			state = 1;
			break;
		case 1:
			if (sscanf(szLine, "%d:%d:%d,%d --> %d:%d:%d,%d", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) {
				fprintf(stdout, "Error importing SRT frame %d\n", curLine);
				e = M4CorruptedData;
				goto exit;
			}
			start = (3600*sh + 60*sm + ss)*1000 + sms;
			if (start<end) {
				fprintf(stdout, "WARNING: corrupted SRT frame starts before end of previous one (SRT Frame %d) - adjusting time stamps\n", curLine);
				start = end;
			}
			end = (3600*eh + 60*em + es)*1000 + ems;
			/*make stream start at 0 by inserting a fake AU*/
			if ((curLine==1) && start>0) {
				au = M4SM_NewAU(srt, 0, 0, 1);
				com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
				com->node = text;
				Node_Register(text, NULL);
				inf = SG_NewFieldCommand(com);
				inf->fieldIndex = string.fieldIndex;
				inf->fieldType = string.fieldType;
				inf->field_ptr = VRML_NewFieldPointer(string.fieldType);
				ChainAddEntry(au->commands, com);
			}

			au = M4SM_NewAU(srt, start, 0, 1);
			com = NULL;
			state = 2;			
			italic = underlined = bold = 0;
			break;

		default:
			ptr = szLine;
			/*FIXME - other styles posssibles ??*/
			while (1) {
				if (!strnicmp(ptr, "<i>", 3)) {
					italic = 1;
					ptr += 3;
				}
				else if (!strnicmp(ptr, "<u>", 3)) {
					underlined = 1;
					ptr += 3;
				}
				else if (!strnicmp(ptr, "<b>", 3)) {
					bold = 1;
					ptr += 3;
				}
				else
					break;
			}
			/*if style remove end markers*/
			while ((strlen(ptr)>4) && (ptr[strlen(ptr) - 4] == '<') && (ptr[strlen(ptr) - 1] == '>')) {
				ptr[strlen(ptr) - 4] = 0;
			}

			if (!com) {
				com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
				com->node = text;
				Node_Register(text, NULL);
				inf = SG_NewFieldCommand(com);
				inf->fieldIndex = string.fieldIndex;
				inf->fieldType = string.fieldType;
				inf->field_ptr = VRML_NewFieldPointer(string.fieldType);
				ChainAddEntry(au->commands, com);
			}
			assert(inf);
			VRML_MF_Append(inf->field_ptr, FT_MFString, (void **) &sfstr);
			len = 0;
			for (i=0; i<strlen(ptr); i++) {
				if (ptr[i] & 0x80) {
					szText[len] = 0xc0 | ( (ptr[i] >> 6) & 0x3 );
					len++;
					ptr[i] &= 0xbf;
				}
				szText[len] = ptr[i];
				len++;
			}
			szText[len] = 0;
			sfstr->buffer = strdup(szText);
			break;
		}
	}

exit:
	if (e) M4SM_RemoveStream(ctx, srt);
	fclose(srt_in);
	return e;
}

static M4Err M4SM_ImportSUB_BIFS(M4SceneManager *ctx, ESDescriptor *src, MuxInfoDescriptor *mux)
{
	M4Err e;
	SFNode *text, *font;
	M4StreamContext *srt;
	FILE *sub_in;
	FieldInfo string, style;
	u32 start, end, line, i, j, len;
	M4AUContext *au;
	SGCommand *com;
	SFString *sfstr;
	CommandFieldInfo *inf;
	Bool first_samp;
	Double fps;
	char szLine[2048], szTime[30], szText[2048];
	M4StreamContext *sc = NULL;

	if (!ctx->scene_graph) {
		fprintf(stdout, "Error importing SUB: base scene not assigned\n");
		return M4BadParam;
	}
	for (i=0; i<ChainGetCount(ctx->streams); i++) {
		sc = ChainGetEntry(ctx->streams, i);
		if (sc->streamType==M4ST_SCENE) break;
		sc = NULL;
	}

	if (!sc) {
		fprintf(stdout, "Error importing SUB: Cannot locate base scene\n");
		return M4BadParam;
	}
	if (!mux->textNode) {
		fprintf(stdout, "Error importing SUB: Target text node unspecified\n");
		return M4BadParam;
	}
	text = SG_FindNodeByName(ctx->scene_graph, mux->textNode);
	if (!text) {
		fprintf(stdout, "Error importing SUB: Cannot find target text node %s\n", mux->textNode);
		return M4BadParam;
	}
	if (Node_GetFieldByName(text, "string", &string) != M4OK) {
		fprintf(stdout, "Error importing SUB: Target text node %s doesn't look like text\n", mux->textNode);
		return M4BadParam;
	}

	font = NULL;
	if (mux->fontNode) {
		font = SG_FindNodeByName(ctx->scene_graph, mux->fontNode);
		if (!font) {
			fprintf(stdout, "Error importing SUB: Cannot find target font node %s\n", mux->fontNode);
			return M4BadParam;
		}
		if (Node_GetFieldByName(font, "style", &style) != M4OK) {
			fprintf(stdout, "Error importing SUB: Target font node %s doesn't look like font\n", mux->fontNode);
			return M4BadParam;
		}
	}

	sub_in = fopen(mux->file_name, "rt");
	if (!sub_in) {
		fprintf(stdout, "Cannot open input SUB %s\n", mux->file_name);
		return M4URLNotFound;
	}

	srt = M4SM_NewStream(ctx, src->ESID, M4ST_SCENE, 1);
	if (!srt) return M4OutOfMem;

	if (!src->slConfig) src->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
	src->slConfig->timestampResolution = 1000;
	if (!src->decoderConfig) src->decoderConfig = (DecoderConfigDescriptor *) OD_NewDescriptor(DecoderConfigDescriptor_Tag);
	src->decoderConfig->streamType = M4ST_SCENE;
	src->decoderConfig->objectTypeIndication = 1;

	e = M4OK;
	start = end = 0;
	au = NULL;
	com = NULL;
	inf = NULL;

	fps = 25.0;
	if (mux->frame_rate) fps = mux->frame_rate;

	line = 0;
	first_samp = 1;
	while (1) {
		char *sOK = fgets(szLine, 2048, sub_in);
		if (!sOK) break;
		REM_TRAIL_MARKS(szLine, "\r\n\t ")

		line++;
		len = strlen(szLine); 
		if (!len) continue;

		i=0;
		if (szLine[i] != '{') {
			fprintf(stdout, "Bad SUB file (line %d): expecting \"{\" got \"%c\"\n", line, szLine[i]);
			e = M4NonCompliantBitStream;
			break;
		}
		while (szLine[i+1] && szLine[i+1]!='}') { szTime[i] = szLine[i+1]; i++; }
		szTime[i] = 0;
		start = atoi(szTime);
		if (start<end) {
			fprintf(stdout, "WARNING: corrupted SUB frame (line %d) - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps\n", line, start, end);
			start = end;
		}
		j=i+2;
		i=0;
		if (szLine[i+j] != '{') {
			fprintf(stdout, "Bad SUB file - expecting \"{\" got \"%c\"\n", szLine[i]);
			e = M4NonCompliantBitStream;
			break;
		}
		while (szLine[i+1+j] && szLine[i+1+j]!='}') { szTime[i] = szLine[i+1+j]; i++; }
		szTime[i] = 0;
		end = atoi(szTime);
		j+=i+2;

		if (start>end) {
			fprintf(stdout, "WARNING: corrupted SUB frame (line %d) - ends (at %d ms) before start of current frame (%d ms) - skipping\n", line, end, start);
			continue;
		}

		if (start && first_samp) {
			au = M4SM_NewAU(srt, 0, 0, 1);
			com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
			com->node = text;
			Node_Register(text, NULL);
			inf = SG_NewFieldCommand(com);
			inf->fieldIndex = string.fieldIndex;
			inf->fieldType = string.fieldType;
			inf->field_ptr = VRML_NewFieldPointer(string.fieldType);
			ChainAddEntry(au->commands, com);
		}

		for (i=j; i<len; i++) {
			if (szLine[i]=='|') {
				szText[i-j] = '\n';
			} else {
				szText[i-j] = szLine[i];
			}
		}
		szText[i-j] = 0;

		com = SG_NewCommand(ctx->scene_graph, SG_FieldReplace);
		com->node = text;
		Node_Register(text, NULL);
		inf = SG_NewFieldCommand(com);
		inf->fieldIndex = string.fieldIndex;
		inf->fieldType = string.fieldType;
		inf->field_ptr = VRML_NewFieldPointer(string.fieldType);
		ChainAddEntry(au->commands, com);

		VRML_MF_Append(inf->field_ptr, FT_MFString, (void **) &sfstr);
		sfstr->buffer = strdup(szText);
	}

	if (e) M4SM_RemoveStream(ctx, srt);
	fclose(sub_in);
	return e;
}

M4Err M4SM_ImportBIFSText(M4SceneManager *ctx, ESDescriptor *src, MuxInfoDescriptor *mux)
{
	M4Err e;
	u32 fmt;
	e = guess_subtitle_fmt(mux->file_name, &fmt);
	if (e) return e;
	if (!fmt || (fmt>=3)) return M4NotSupported;

	if (fmt==1) return M4SM_ImportSRT_BIFS(ctx, src, mux);
	else return M4SM_ImportSUB_BIFS(ctx, src, mux);
}


#ifndef M4_READ_ONLY

#define TTXT_DEFAULT_WIDTH	400
#define TTXT_DEFAULT_HEIGHT	60
#define TTXT_DEFAULT_FONT_SIZE	18

static void get_max_video_size(M4File *dest, u32 *width, u32 *height)
{
	u32 w, h, i;
	(*width) = (*height) = 0;
	for (i=0; i<M4_GetTrackCount(dest); i++) {
		switch (M4_GetMediaType(dest, i+1)) {
		case M4_BIFSMediaType:
		case M4_VisualMediaType:
			M4_GetVisualEntrySize(dest, i+1, 1, &w, &h);
			if (w > (*width)) (*width) = w;
			if (h > (*height)) (*height) = h;
			break;
		}
	}
}



static M4Err MP4T_ImportTimedText_SRT(M4TrackImporter *import)
{
	FILE *srt_in;
	Double scale;
	u32 track, timescale, i, desc_idx;
	TextConfigDescriptor*cfg;
	M4Err e;
	StyleRecord rec;
	M4TXTSAMPLE samp;
	M4Sample *s;
	u32 sh, sm, ss, sms, eh, em, es, ems, start, end, prev_end, txt_line, char_len, char_line, nb_samp, j, duration, nb_styles, file_size;
	Bool set_start_char, set_end_char, first_samp, has_alt_desc, use_italic_desc;
	u32 state, curLine, line, len, ID, OCR_ES_ID;
	char szLine[2048], szText[2048], *ptr;
	unsigned short uniLine[5000];

	srt_in = fopen(import->in_name, "rt");

	cfg = NULL;
	has_alt_desc = 0;

	if (import->esd) {
		if (!import->esd->slConfig) {
			import->esd->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
			import->esd->slConfig->predefined = 2;
			import->esd->slConfig->timestampResolution = 1000;
		}
		timescale = import->esd->slConfig->timestampResolution;
		if (!timescale) timescale = 1000;

		/*explicit text config*/
		if (import->esd->decoderConfig && import->esd->decoderConfig->decoderSpecificInfo->tag == TextConfig_Tag) {
			cfg = (TextConfigDescriptor *) import->esd->decoderConfig->decoderSpecificInfo;
			import->esd->decoderConfig->decoderSpecificInfo = NULL;
		}
		ID = import->esd->ESID;
		OCR_ES_ID = import->esd->OCRESID;
	} else {
		timescale = 1000;
		OCR_ES_ID = ID = 0;
	}
	
	if (cfg && cfg->timescale) timescale = cfg->timescale;
	track = M4_NewTrack(import->dest, ID, M4_TimedTextMediaType, timescale);
	if (!track) {
		fclose(srt_in);
		return import_message(import, M4_GetLastError(import->dest), "Error creating text track");
	}
	M4_SetTrackEnabled(import->dest, track, 1);
	if (import->esd && !import->esd->ESID) import->esd->ESID = M4_GetTrackID(import->dest, track);

	if (OCR_ES_ID) M4_SetTrackReference(import->dest, track, M4_OCR_Ref, OCR_ES_ID);

	/*setup track*/
	if (cfg) {
		char *firstFont = NULL;
		/*set track info*/
		M4_SetTrackVideoInfo(import->dest, track, cfg->text_width<<16, cfg->text_height<<16, 0, 0, cfg->layer);

		/*and set sample descriptions*/
		for (i=0; i<ChainGetCount(cfg->sample_descriptions); i++) {
			TextSampleDescriptor *sd= ChainGetEntry(cfg->sample_descriptions, i);
			if (!sd->font_count) {
				sd->fonts = malloc(sizeof(FontRecord));
				sd->font_count = 1;
				sd->fonts[0].fontID = 1;
				sd->fonts[0].fontName = strdup("Serif");
			}
			if (!sd->default_style.fontID) sd->default_style.fontID = sd->fonts[0].fontID;
			if (!sd->default_style.font_size) sd->default_style.font_size = 16;
			if (!sd->default_style.text_color) sd->default_style.text_color = 0xFF000000;
			/*store attribs*/
			if (!i) rec = sd->default_style;

			M4_NewTextDescription(import->dest, track, sd, NULL, NULL, &desc_idx);
			if (!firstFont) firstFont = sd->fonts[0].fontName;
		}
		import_message(import, M4OK, "Timed Text (SRT) import - text track %d x %d, font %s (size %d)", cfg->text_width, cfg->text_height, firstFont, rec.font_size);

		OD_DeleteDescriptor((Descriptor **)&cfg);
	} else {
		u32 w, h;
		TextSampleDescriptor *sd;
		get_max_video_size(import->dest, &w, &h);
		if (!w) w = TTXT_DEFAULT_WIDTH;
		if (!h) h = TTXT_DEFAULT_HEIGHT;

		/*have to work with default - use max size (if only one video, this means the text region is the
		entire display, and with bottom alignment things should be fine...*/
		M4_SetTrackVideoInfo(import->dest, track, w<<16, h<<16, 0, 0, 0);
		sd = (TextSampleDescriptor*)OD_NewDescriptor(TextSampleDescriptor_Tag);
		sd->fonts = malloc(sizeof(FontRecord));
		sd->font_count = 1;
		sd->fonts[0].fontID = 1;
		sd->fonts[0].fontName = strdup("Serif");
		sd->back_color = 0x00000000;	/*transparent*/
		sd->default_style.fontID = 1;
		sd->default_style.font_size = TTXT_DEFAULT_FONT_SIZE;
		sd->default_style.text_color = 0xFFFFFFFF;	/*white*/
		sd->default_style.style_flags = 0;
		sd->horiz_justif = 1; /*center of scene*/
		sd->vert_justif = -1;	/*bottom of scene*/

		if (import->flags & M4TI_SKIT_TXT_BOX) {
			sd->default_pos.top = sd->default_pos.left = sd->default_pos.right = sd->default_pos.bottom = 0;
		} else {
			if ((sd->default_pos.bottom==sd->default_pos.top) || (sd->default_pos.right==sd->default_pos.left)) {
				sd->default_pos.top = sd->default_pos.left = 0;
				sd->default_pos.right = w;
				sd->default_pos.bottom = h;
			}
		}

		/*store attribs*/
		rec = sd->default_style;
		M4_NewTextDescription(import->dest, track, sd, NULL, NULL, &desc_idx);
		/*also create a default italic description - other styles are not really common in SRT files*/
		sd->default_style.style_flags = STF_Italic;
		M4_NewTextDescription(import->dest, track, sd, NULL, NULL, &desc_idx);
#if 0
		sd->default_style.style_flags = STF_Bold;
		M4_NewTextDescription(import->dest, track, sd, NULL, NULL, &desc_idx);
#endif
		has_alt_desc = 1;

		import_message(import, M4OK, "Timed Text (SRT) import - text track %d x %d, font %s (size %d)", w, h, sd->fonts[0].fontName, rec.font_size);
		OD_DeleteDescriptor((Descriptor **)&sd);
	}

	duration = (u32) (((Double) import->duration)*timescale/1000.0);

	e = M4OK;
	state = end = prev_end = 0;
	curLine = 0;
	txt_line = 0;
	set_start_char = set_end_char = 0;
	char_len = 0;
	nb_styles = 0;
	start = 0;
	nb_samp = 0;
	samp = M4_NewTextSample();

	fseek(srt_in, 0, SEEK_END);
	file_size = ftell(srt_in);
	fseek(srt_in, 0, SEEK_SET);

	scale = timescale;
	scale /= 1000;

	use_italic_desc = 0;
	first_samp = 1;
	while (1) {
		char *sOK = fgets(szLine, 2048, srt_in); 
		if (sOK) REM_TRAIL_MARKS(szLine, "\r\n\t ")

		if (!sOK || !strlen(szLine)) {
			state = 0;
			rec.style_flags = 0;
			rec.startCharOffset = rec.endCharOffset = 0;

			if (nb_styles && desc_idx && has_alt_desc) {
				M4_TxtResetStyles(samp);
				if (desc_idx==2) use_italic_desc = 1;
			} else {
				desc_idx = 1;
			}
			nb_styles = 0;

			if (txt_line) {
				if (prev_end && (start != prev_end)) {
					M4TXTSAMPLE empty_samp = M4_NewTextSample();
					s = M4_TxtToSample(empty_samp);
					M4_DeleteTextSample(empty_samp);
					s->DTS = (u32) (scale*prev_end);
					s->IsRAP = 1;
					M4_AddSample(import->dest, track, 1, s);
					M4_DeleteSample(&s);
					nb_samp++;
				}

				s = M4_TxtToSample(samp);
				s->DTS = (u32) (scale*start);
				s->IsRAP = 1;
				M4_AddSample(import->dest, track, desc_idx, s);
				M4_DeleteSample(&s);
				nb_samp++;
				prev_end = end;
				txt_line = 0;
				char_len = 0;
				set_start_char = set_end_char = 0;
				rec.startCharOffset = rec.endCharOffset = 0;
				M4_TxtReset(samp);

				//import_progress(import, nb_samp, nb_samp+1);
				import_progress(import, ftell(srt_in), file_size);
				if (duration && (end >= duration)) break;
			}
			desc_idx = 0;
			if (!sOK) break;
			continue;
		}

		switch (state) {
		case 0:
			if (sscanf(szLine, "%d", &line) != 1) {
				e = import_message(import, M4CorruptedData, "Bad SRT formatting - expecting number got \"%s\"", szLine);
				goto exit;
			}
			if (line != curLine + 1) import_message(import, M4OK, "WARNING: corrupted SRT frame %d after frame %d", line, curLine);
			curLine = line;
			state = 1;
			break;
		case 1:
			if (sscanf(szLine, "%d:%d:%d,%d --> %d:%d:%d,%d", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) {
				e = import_message(import, M4CorruptedData, "Error scanning SRT frame %d timing", curLine);
				goto exit;
			}
			start = (3600*sh + 60*sm + ss)*1000 + sms;
			if (start<end) {
				import_message(import, M4OK, "WARNING: corrupted SRT frame %d - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps", curLine, start, end);
				start = end;
			}

			end = (3600*eh + 60*em + es)*1000 + ems;
			/*make stream start at 0 by inserting a fake AU*/
			if (first_samp && (start>0)) {
				s = M4_TxtToSample(samp);
				s->DTS = 0;
				M4_AddSample(import->dest, track, 1, s);
				M4_DeleteSample(&s);
				nb_samp++;
			}
			rec.style_flags = 0;
			state = 2;			
			break;

		default:
			/*reset only when text is present*/
			first_samp = 0;
			ptr = szLine;
			i = j = 0;
			len = 0;
			while (ptr[i]) {
				if (!strnicmp(&ptr[i], "<i>", 3)) {
					rec.style_flags |= STF_Italic;
					i += 3;
					set_start_char = 1;
				}
				else if (!strnicmp(&ptr[i], "<u>", 3)) {
					rec.style_flags |= STF_Underlined;
					i += 3;
					set_start_char = 1;
				}
				else if (!strnicmp(&ptr[i], "<b>", 3)) {
					rec.style_flags |= STF_Bold;
					i += 3;
					set_start_char = 1;
				}
				else if (!strnicmp(&ptr[i], "? <", 3)) i += 2;
				else if (!strnicmp(&ptr[i], "</i>", 4) || !strnicmp(&ptr[i], "</b>", 4) || !strnicmp(&ptr[i], "</u>", 4)) {
					i+=4;
					set_end_char = 1;
				}
				else {
					if (ptr[i] & 0x80) {
						szText[len] = 0xc0 | ( (ptr[i] >> 6) & 0x3 );
						len++;
						ptr[i] &= 0xbf;
					}
					szText[len] = ptr[i];
					len++;
					i++;
				}
			}
			szText[len] = 0;
			ptr = (char *) szText;
			char_line = utf8_mbstowcs(uniLine, 5000, (const char **) &ptr);
			if (set_start_char) {
				rec.startCharOffset = char_len;
				set_start_char = 0;
			}

			/*go to line*/
			if (txt_line) {
				M4_TxtAddText(samp, "\n", 1);
				char_len += 1;
			}

			if (set_end_char) {
				rec.endCharOffset = char_line + char_len;
				set_end_char = 0;
				assert(rec.style_flags);
				M4_TxtAddStyle(samp, &rec);
				rec.endCharOffset = rec.startCharOffset = 0;
				if (!nb_styles) {
					nb_styles = rec.style_flags;
					if (nb_styles==STF_Italic) desc_idx = 2;
#if 0
					else if (nb_styles==STF_Bold) desc_idx = 3;
#endif
				} else if (nb_styles != rec.style_flags) {
					desc_idx = 0;
				}
				rec.style_flags = 0;
			}
			char_len += char_line;

			M4_TxtAddText(samp, szText, len);
			txt_line ++;
			break;
		}
		if (duration && (start >= duration)) break;
	}

	/*final flush*/	
	if (end) {
		M4_TxtReset(samp);
		s = M4_TxtToSample(samp);
		s->DTS = (u32) (scale*end);
		s->IsRAP = 1;
		M4_AddSample(import->dest, track, 1, s);
		M4_DeleteSample(&s);
		nb_samp++;
	}
	M4_DeleteTextSample(samp);
	if (!use_italic_desc && has_alt_desc) {
		M4_RemoveStreamDescription(import->dest, track, 2);
	}
	M4_SetLastSampleDuration(import->dest, track, 0);
	
	import_progress(import, nb_samp, nb_samp);

exit:
	if (e) M4_RemoveTrack(import->dest, track);
	fclose(srt_in);
	return e;
}


static M4Err MP4T_ImportTimedText_SUB(M4TrackImporter *import)
{
	FILE *sub_in;
	u32 track, ID, timescale, i, j, desc_idx, start, end, prev_end, nb_samp, duration, file_size, len, line;
	TextConfigDescriptor*cfg;
	M4Err e;
	Double FPS;
	M4TXTSAMPLE samp;
	Bool first_samp;
	char szLine[2048], szTime[20], szText[2048];
	M4Sample *s;

	sub_in = fopen(import->in_name, "rt");

	FPS = 25.0;
	if (import->video_fps) FPS = import->video_fps;

	cfg = NULL;
	if (import->esd) {
		if (!import->esd->slConfig) {
			import->esd->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
			import->esd->slConfig->predefined = 2;
			import->esd->slConfig->timestampResolution = 1000;
		}
		timescale = import->esd->slConfig->timestampResolution;
		if (!timescale) timescale = 1000;

		/*explicit text config*/
		if (import->esd->decoderConfig && import->esd->decoderConfig->decoderSpecificInfo->tag == TextConfig_Tag) {
			cfg = (TextConfigDescriptor *) import->esd->decoderConfig->decoderSpecificInfo;
			import->esd->decoderConfig->decoderSpecificInfo = NULL;
		}
		ID = import->esd->ESID;
	} else {
		timescale = 1000;
		ID = 0;
	}
	
	if (cfg && cfg->timescale) timescale = cfg->timescale;
	track = M4_NewTrack(import->dest, ID, M4_TimedTextMediaType, timescale);
	if (!track) {
		fclose(sub_in);
		return import_message(import, M4_GetLastError(import->dest), "Error creating text track");
	}
	M4_SetTrackEnabled(import->dest, track, 1);
	if (import->esd && !import->esd->ESID) import->esd->ESID = M4_GetTrackID(import->dest, track);

	file_size = 0;
	/*setup track*/
	if (cfg) {
		char *firstFont = NULL;
		/*set track info*/
		M4_SetTrackVideoInfo(import->dest, track, cfg->text_width<<16, cfg->text_height<<16, 0, 0, cfg->layer);

		/*and set sample descriptions*/
		for (i=0; i<ChainGetCount(cfg->sample_descriptions); i++) {
			TextSampleDescriptor *sd= ChainGetEntry(cfg->sample_descriptions, i);
			if (!sd->font_count) {
				sd->fonts = malloc(sizeof(FontRecord));
				sd->font_count = 1;
				sd->fonts[0].fontID = 1;
				sd->fonts[0].fontName = strdup("Serif");
			}
			if (!sd->default_style.fontID) sd->default_style.fontID = sd->fonts[0].fontID;
			if (!sd->default_style.font_size) sd->default_style.font_size = 16;
			if (!sd->default_style.text_color) sd->default_style.text_color = 0xFF000000;
			file_size = sd->default_style.font_size;
			M4_NewTextDescription(import->dest, track, sd, NULL, NULL, &desc_idx);
			if (!firstFont) firstFont = sd->fonts[0].fontName;
		}
		import_message(import, M4OK, "Timed Text (SUB @ %02.2f) import - text track %d x %d, font %s (size %d)", FPS, cfg->text_width, cfg->text_height, firstFont, file_size);

		OD_DeleteDescriptor((Descriptor **)&cfg);
	} else {
		u32 w, h;
		TextSampleDescriptor *sd;
		get_max_video_size(import->dest, &w, &h);
		if (!w) w = TTXT_DEFAULT_WIDTH;
		if (!h) h = TTXT_DEFAULT_HEIGHT;

		/*have to work with default - use max size (if only one video, this means the text region is the
		entire display, and with bottom alignment things should be fine...*/
		M4_SetTrackVideoInfo(import->dest, track, w<<16, h<<16, 0, 0, 0);
		sd = (TextSampleDescriptor*)OD_NewDescriptor(TextSampleDescriptor_Tag);
		sd->fonts = malloc(sizeof(FontRecord));
		sd->font_count = 1;
		sd->fonts[0].fontID = 1;
		sd->fonts[0].fontName = strdup("Serif");
		sd->back_color = 0x00000000;	/*transparent*/
		sd->default_style.fontID = 1;
		sd->default_style.font_size = TTXT_DEFAULT_FONT_SIZE;
		sd->default_style.text_color = 0xFFFFFFFF;	/*white*/
		sd->default_style.style_flags = 0;
		sd->horiz_justif = 1; /*center of scene*/
		sd->vert_justif = -1;	/*bottom of scene*/

		if (import->flags & M4TI_SKIT_TXT_BOX) {
			sd->default_pos.top = sd->default_pos.left = sd->default_pos.right = sd->default_pos.bottom = 0;
		} else {
			if ((sd->default_pos.bottom==sd->default_pos.top) || (sd->default_pos.right==sd->default_pos.left)) {
				sd->default_pos.top = sd->default_pos.left = 0;
				sd->default_pos.right = w;
				sd->default_pos.bottom = h;
			}
		}

		M4_NewTextDescription(import->dest, track, sd, NULL, NULL, &desc_idx);
		import_message(import, M4OK, "Timed Text (SUB @ %02.2f) import - text track %d x %d, font %s (size %d)", FPS, w, h, sd->fonts[0].fontName, TTXT_DEFAULT_FONT_SIZE);
		OD_DeleteDescriptor((Descriptor **)&sd);
	}

	duration = (u32) (((Double) import->duration)*timescale/1000.0);

	e = M4OK;
	nb_samp = 0;
	samp = M4_NewTextSample();

	fseek(sub_in, 0, SEEK_END);
	file_size = ftell(sub_in);
	fseek(sub_in, 0, SEEK_SET);

	FPS = ((Double) timescale ) / FPS;
	start = end = prev_end = 0;

	line = 0;
	first_samp = 1;
	while (fgets(szLine, 2048, sub_in) != NULL) {
		REM_TRAIL_MARKS(szLine, "\r\n\t ")

		line++;
		len = strlen(szLine); 
		if (!len) continue;

		i=0;
		if (szLine[i] != '{') {
			e = import_message(import, M4NonCompliantBitStream, "Bad SUB file (line %d): expecting \"{\" got \"%c\"", line, szLine[i]);
			goto exit;
		}
		while (szLine[i+1] && szLine[i+1]!='}') { szTime[i] = szLine[i+1]; i++; }
		szTime[i] = 0;
		start = atoi(szTime);
		if (start<end) {
			import_message(import, M4OK, "WARNING: corrupted SUB frame (line %d) - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps", line, start, end);
			start = end;
		}
		j=i+2;
		i=0;
		if (szLine[i+j] != '{') {
			e = import_message(import, M4NonCompliantBitStream, "Bad SUB file - expecting \"{\" got \"%c\"", szLine[i]);
			goto exit;
		}
		while (szLine[i+1+j] && szLine[i+1+j]!='}') { szTime[i] = szLine[i+1+j]; i++; }
		szTime[i] = 0;
		end = atoi(szTime);
		j+=i+2;

		if (start>end) {
			import_message(import, M4OK, "WARNING: corrupted SUB frame (line %d) - ends (at %d ms) before start of current frame (%d ms) - skipping", line, end, start);
			continue;
		}

		M4_TxtReset(samp);

		if (start && first_samp) {
			s = M4_TxtToSample(samp);
			s->DTS = 0;
			s->IsRAP = 1;
			M4_AddSample(import->dest, track, 1, s);
			M4_DeleteSample(&s);
			first_samp = 0;
			nb_samp++;
		}

		for (i=j; i<len; i++) {
			if (szLine[i]=='|') {
				szText[i-j] = '\n';
			} else {
				szText[i-j] = szLine[i];
			}
		}
		szText[i-j] = 0;
		M4_TxtAddText(samp, szText, strlen(szText) );

		if (prev_end) {
			M4TXTSAMPLE empty_samp = M4_NewTextSample();
			s = M4_TxtToSample(empty_samp);
			s->DTS = (u32) (FPS*prev_end);
			M4_AddSample(import->dest, track, 1, s);
			M4_DeleteSample(&s);
			nb_samp++;
			M4_DeleteTextSample(empty_samp);
		}

		s = M4_TxtToSample(samp);
		s->DTS = (u32) (FPS*start);
		M4_AddSample(import->dest, track, 1, s);
		M4_DeleteSample(&s);
		nb_samp++;
		M4_TxtReset(samp);
		prev_end = end;
		import_progress(import, ftell(sub_in), file_size);
		if (duration && (end >= duration)) break;
	}
	/*final flush*/
	if (end) {
		M4_TxtReset(samp);
		s = M4_TxtToSample(samp);
		s->DTS = (u32) (FPS*end);
		M4_AddSample(import->dest, track, 1, s);
		M4_DeleteSample(&s);
		nb_samp++;
	}
	M4_DeleteTextSample(samp);
	
	M4_SetLastSampleDuration(import->dest, track, 0);
	import_progress(import, nb_samp, nb_samp);

exit:
	if (e) M4_RemoveTrack(import->dest, track);
	fclose(sub_in);
	return e;
}


#define CHECK_STR(__str)	\
	if (!__str) { \
		e = import_message(import, M4BadParam, "Invalid XML formatting (line %d)", parser.line);	\
		goto exit;	\
	}	\


u32 ttxt_get_color(M4TrackImporter *import, XMLParser *parser)
{
	u32 r, g, b, a, res;
	r = g = b = a = 0;
	if (sscanf(parser->value_buffer, "%x %x %x %x", &r, &g, &b, &a) != 4) {
		import_message(import, M4OK, "Warning (line %d): color badly formatted", parser->line);
	}
	res = (a&0xFF); res<<=8;
	res |= (r&0xFF); res<<=8;
	res |= (g&0xFF); res<<=8;
	res |= (b&0xFF);
	return res;
}

void ttxt_parse_text_box(M4TrackImporter *import, XMLParser *parser, BoxRecord *box)
{
	memset(box, 0, sizeof(BoxRecord));
	while (xml_has_attributes(parser)) {
		char *str = xml_get_attribute(parser);
		if (!stricmp(str, "top")) box->top = atoi(parser->value_buffer);
		else if (!stricmp(str, "bottom")) box->bottom = atoi(parser->value_buffer);
		else if (!stricmp(str, "left")) box->left = atoi(parser->value_buffer);
		else if (!stricmp(str, "right")) box->right = atoi(parser->value_buffer);
	}
	xml_skip_element(parser, "TextBox");
}

void ttxt_parse_text_style(M4TrackImporter *import, XMLParser *parser, StyleRecord *style)
{
	memset(style, 0, sizeof(StyleRecord));
	style->fontID = 1;
	style->font_size = TTXT_DEFAULT_FONT_SIZE;
	style->text_color = 0xFFFFFFFF;

	while (xml_has_attributes(parser)) {
		char *str = xml_get_attribute(parser);
		if (!stricmp(str, "fromChar")) style->startCharOffset = atoi(parser->value_buffer);
		else if (!stricmp(str, "toChar")) style->endCharOffset = atoi(parser->value_buffer);
		else if (!stricmp(str, "fontID")) style->fontID = atoi(parser->value_buffer);
		else if (!stricmp(str, "fontSize")) style->font_size = atoi(parser->value_buffer);
		else if (!stricmp(str, "color")) style->text_color = ttxt_get_color(import, parser);
		else if (!stricmp(str, "styles")) {
			if (strstr(parser->value_buffer, "Bold")) style->style_flags |= STF_Bold;
			else if (strstr(parser->value_buffer, "Italic")) style->style_flags |= STF_Italic;
			else if (strstr(parser->value_buffer, "Underlined")) style->style_flags |= STF_Underlined;
		}
	}
	xml_skip_element(parser, "Style");
}

char *ttxt_parse_string(M4TrackImporter *import, XMLParser *parser)
{
	char value[XML_LINE_SIZE];
	u32 i=0;
	u32 k=0;
	char *str = parser->value_buffer;

	strcpy(value, "");
	if (str[0]=='\'') {
		while (strchr(str, '\'')) {
			while (str[0] != '\'')  str += 1;
			str+=1;
			while (str[i] && (str[i] != '\'')) {
				/*handle UTF-8 - WARNING: if parser is in unicode string is already utf8 multibyte chars*/
				if (!parser->unicode_type && (str[i] & 0x80)) {
					value[k] = 0xc0 | ( (str[i] >> 6) & 0x3 );
					k++;
					str[i] &= 0xbf;
				}
				value[k] = str[i];
				i++;
				k++;
			}
			if (!str[i+1]) break;
			str = str + i + 1;
			i=0;
			value[k] = '\n';
			k++;
		} 
	} else {
		while (str[i]) {
			/*handle UTF-8 - WARNING: if parser is in unicode string is already utf8 multibyte chars*/
			if (!parser->unicode_type && (str[i] & 0x80)) {
				value[k] = 0xc0 | ( (str[i] >> 6) & 0x3 );
				k++;
				str[i] &= 0xbf;
			}
			value[k] = str[i];
			i++;
			k++;
		}
	}
	value[k] = 0;
	if (!strlen(value)) return strdup("");
	return xml_translate_xml_string(value);
}

/*for GCC warnings*/
static void xml_import_progress(void *import, u32 cur_samp, u32 count)
{
	import_progress((M4TrackImporter *)import, cur_samp, count);
}

static M4Err MP4T_ImportTimedText_TTXT(M4TrackImporter *import)
{
	M4Err e;
	u32 track, ID, nb_samples, nb_descs;
	XMLParser parser;
	char *str;

	if (import->flags==M4TI_PROBE_ONLY) return M4OK;

	e = xml_init_parser(&parser, import->in_name);
	if (e) return import_message(import, e, "Cannot open file %s", import->in_name);

	str = xml_get_element(&parser);
	if (!str) { e = M4IOErr; goto exit; }
	if (strcmp(str, "TextStream")) {
		e = import_message(import, M4BadParam, "Invalid Timed Text file - expecting %s got %s", "TextStream", str);
		goto exit;
	}
	xml_skip_attributes(&parser);

	/*setup track in 3GP format directly (no ES desc)*/
	ID = (import->esd) ? import->esd->ESID : 0;
	track = M4_NewTrack(import->dest, ID, M4_TimedTextMediaType, 1000);
	if (!track) {
		e = M4_GetLastError(import->dest);
		goto exit;
	}
	M4_SetTrackEnabled(import->dest, track, 1);
	/*some MPEG-4 setup*/
	if (import->esd) {
		if (!import->esd->ESID) import->esd->ESID = M4_GetTrackID(import->dest, track);
		if (!import->esd->decoderConfig) import->esd->decoderConfig = (DecoderConfigDescriptor *) OD_NewDescriptor(DecoderConfigDescriptor_Tag);
		if (!import->esd->slConfig) import->esd->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
		import->esd->slConfig->timestampResolution = 1000;
		import->esd->decoderConfig->streamType = M4ST_TEXT;
		import->esd->decoderConfig->objectTypeIndication = 0x08;
		if (import->esd->OCRESID) M4_SetTrackReference(import->dest, track, M4_OCR_Ref, import->esd->OCRESID);
	}

	import_message(import, M4OK, "Timed Text (GPAC TTXT) Import");
	parser.OnProgress = xml_import_progress;
	parser.cbk = import;

	nb_descs = 0;
	nb_samples = 0;
	while (!xml_element_done(&parser, "TextStream") && !parser.done) {
		str = xml_get_element(&parser);
		CHECK_STR(str)

		if (!strcmp(str, "TextStreamHeader")) {
			s32 w, h, tx, ty, layer;
			w = TTXT_DEFAULT_WIDTH;
			h = TTXT_DEFAULT_HEIGHT;
			tx = ty = layer = 0;

			while (xml_has_attributes(&parser)) {
				str = xml_get_attribute(&parser);
				if (!strcmp(str, "width")) w = atoi(parser.value_buffer);
				else if (!strcmp(str, "height")) h = atoi(parser.value_buffer);
				else if (!strcmp(str, "layer")) layer = atoi(parser.value_buffer);
				else if (!strcmp(str, "translation_x")) tx = atoi(parser.value_buffer);
				else if (!strcmp(str, "translation_y")) ty = atoi(parser.value_buffer);
			}
			M4_SetTrackVideoInfo(import->dest, track, w<<16, h<<16, tx<<16, ty<<16, (s16) layer);

			while (!xml_element_done(&parser, "TextStreamHeader")) {
				str = xml_get_element(&parser);
				CHECK_STR(str)

				if (!strcmp(str, "TextSampleDescription")) {
					TextSampleDescriptor td;
					u32 idx, i;
					memset(&td, 0, sizeof(TextSampleDescriptor));
					td.tag = TextConfig_Tag;
					td.vert_justif = -1;
					td.default_style.fontID = 1;
					td.default_style.font_size = TTXT_DEFAULT_FONT_SIZE;

					while (xml_has_attributes(&parser)) {
						str = xml_get_attribute(&parser);
						if (!strcmp(str, "horizontalJustification")) {
							if (!stricmp(parser.value_buffer, "center")) td.horiz_justif = 1;
							else if (!stricmp(parser.value_buffer, "right")) td.horiz_justif = -1;
							else if (!stricmp(parser.value_buffer, "left")) td.horiz_justif = 0;
						}
						else if (!strcmp(str, "verticalJustification")) {
							if (!stricmp(parser.value_buffer, "center")) td.vert_justif = 1;
							else if (!stricmp(parser.value_buffer, "bottom")) td.vert_justif = -1;
							else if (!stricmp(parser.value_buffer, "top")) td.vert_justif = 0;
						}
						else if (!strcmp(str, "backColor")) td.back_color = ttxt_get_color(import, &parser);
						else if (!strcmp(str, "verticalText") && !stricmp(parser.value_buffer, "yes") ) td.displayFlags |= STF_VerticalText;
						else if (!strcmp(str, "fillTextRegion") && !stricmp(parser.value_buffer, "yes") ) td.displayFlags |= STF_FillRegion;
						else if (!strcmp(str, "continuousKaraoke") && !stricmp(parser.value_buffer, "yes") ) td.displayFlags |= STF_Karaoke;
						else if (!strcmp(str, "scroll")) {
							if (!stricmp(parser.value_buffer, "inout")) td.displayFlags |= STF_ScrollIn | STF_ScrollOut;
							else if (!stricmp(parser.value_buffer, "in")) td.displayFlags |= STF_ScrollIn;
							else if (!stricmp(parser.value_buffer, "out")) td.displayFlags |= STF_ScrollOut;
						}
						else if (!strcmp(str, "scrollMode")) {
							u32 scroll_mode = STF_ScrollCredits;
							if (!stricmp(parser.value_buffer, "Credits")) scroll_mode = STF_ScrollCredits;
							else if (!stricmp(parser.value_buffer, "Marquee")) scroll_mode = STF_ScrollMarquee;
							else if (!stricmp(parser.value_buffer, "Right")) scroll_mode = STF_ScrollRight;
							else if (!stricmp(parser.value_buffer, "Down")) scroll_mode = STF_ScrollDown;
							td.displayFlags |= ((scroll_mode<<7) & STF_ScrollDirection);
						}
					}
					while (!xml_element_done(&parser, "TextSampleDescription")) {
						str = xml_get_element(&parser);
						if (!strcmp(str, "TextBox")) ttxt_parse_text_box(import, &parser, &td.default_pos);
						else if (!strcmp(str, "Style")) ttxt_parse_text_style(import, &parser, &td.default_style);
						else if (!strcmp(str, "FontTable")) {
							xml_skip_attributes(&parser);
							while (!xml_element_done(&parser, "FontTable")) {
								str = xml_get_element(&parser);
								if (!strcmp(str, "FontTableEntry")) {
									if (!td.fonts) {
										td.fonts = malloc(sizeof(FontRecord));
										td.font_count = 1;
									} else {
										td.font_count += 1;
										td.fonts = realloc(td.fonts, sizeof(FontRecord)*td.font_count);
									}
									while (xml_has_attributes(&parser)) {
										str = xml_get_attribute(&parser);
										if (!stricmp(str, "fontID")) td.fonts[td.font_count-1].fontID = atoi(parser.value_buffer);
										else if (!stricmp(str, "fontName")) td.fonts[td.font_count-1].fontName = strdup(parser.value_buffer);
									}
									xml_skip_element(&parser, "FontTableEntry");
								}
								else xml_skip_element(&parser, str);
							}
						}
						else {
							xml_skip_element(&parser, str);
						}

					}
					if (import->flags & M4TI_SKIT_TXT_BOX) {
						td.default_pos.top = td.default_pos.left = td.default_pos.right = td.default_pos.bottom = 0;
					} else {
						if ((td.default_pos.bottom==td.default_pos.top) || (td.default_pos.right==td.default_pos.left)) {
							td.default_pos.top = td.default_pos.left = 0;
							td.default_pos.right = w;
							td.default_pos.bottom = h;
						}
					}
					if (!td.fonts) {
						td.font_count = 1;
						td.fonts = malloc(sizeof(FontRecord));
						td.fonts[0].fontID = 1;
						td.fonts[0].fontName = strdup("Serif");
					}
					M4_NewTextDescription(import->dest, track, &td, NULL, NULL, &idx);
					for (i=0; i<td.font_count; i++) free(td.fonts[i].fontName);
					free(td.fonts);
					nb_descs ++;
				}
			}
		} else if (!strcmp(str, "TextSample")) {
			M4Sample *s;
			M4TXTSAMPLE samp;
			u32 ts, descIndex;
			
			if (!nb_descs) {
				e = import_message(import, M4BadParam, "Invalid Timed Text file - text stream header not found or empty");
				goto exit;
			}
			samp = M4_NewTextSample();
			ts = 0;
			descIndex = 1;

			while (xml_has_attributes(&parser)) {
				str = xml_get_attribute(&parser);
				if (!strcmp(str, "sampleTime")) {
					u32 h, m, s, ms;
					if (sscanf(parser.value_buffer, "%d:%d:%d.%d", &h, &m, &s, &ms) == 4) {
						ts = (h*3600 + m*60 + s)*1000 + ms;
					} else {
						ts = (u32) (atof(parser.value_buffer) * 1000);
					}
				}
				else if (!strcmp(str, "sampleDescriptionIndex")) descIndex = atoi(parser.value_buffer);
				else if (!strcmp(str, "text")) {
					u32 len;
					char *str = ttxt_parse_string(import, &parser);
					len = strlen(str);
					M4_TxtAddText(samp, str, len);
					free(str);
				}
				else if (!strcmp(str, "scrollDelay")) M4_TxtSetScrollDelay(samp, (u32) (1000*atoi(parser.value_buffer)));
				else if (!strcmp(str, "highlightColor")) M4_TxtSetHighlightColorARGB(samp, ttxt_get_color(import, &parser));
				else if (!strcmp(str, "wrap") && !strcmp(parser.value_buffer, "Automatic")) M4_TxtSetWrap(samp, 0x01);
			}
			/*get all modifiers*/
			while (!xml_element_done(&parser, "TextSample")) {
				str = xml_get_element(&parser);
				if (!stricmp(str, "Style")) {
					StyleRecord r;
					ttxt_parse_text_style(import, &parser, &r);
					M4_TxtAddStyle(samp, &r);
				}
				else if (!stricmp(str, "TextBox")) {
					BoxRecord r;
					ttxt_parse_text_box(import, &parser, &r);
					M4_TxtSetBox(samp, r.top, r.left, r.bottom, r.right);
				}
				else if (!stricmp(str, "Highlight")) {
					u16 start, end;
					start = end = 0;
					while (xml_has_attributes(&parser)) {
						str = xml_get_attribute(&parser);
						if (!strcmp(str, "fromChar")) start = atoi(parser.value_buffer);
						else if (!strcmp(str, "toChar")) end = 1 + atoi(parser.value_buffer);
					}
					xml_skip_element(&parser, "Highlight");
					M4_TxtAddHighlight(samp, start, end);
				}
				else if (!stricmp(str, "Blinking")) {
					u16 start, end;
					start = end = 0;
					while (xml_has_attributes(&parser)) {
						str = xml_get_attribute(&parser);
						if (!strcmp(str, "fromChar")) start = atoi(parser.value_buffer);
						else if (!strcmp(str, "toChar")) end = 1 + atoi(parser.value_buffer);
					}
					xml_skip_element(&parser, "Blinking");
					M4_TxtAddBlink(samp, start, end);
				}
				else if (!stricmp(str, "HyperLink")) {
					u16 start, end;
					char *url, *url_tt;
					start = end = 0;
					url = url_tt = NULL;
					while (xml_has_attributes(&parser)) {
						str = xml_get_attribute(&parser);
						if (!strcmp(str, "fromChar")) start = atoi(parser.value_buffer);
						else if (!strcmp(str, "toChar")) end = 1 + atoi(parser.value_buffer);
						else if (!strcmp(str, "URL")) url = strdup(parser.value_buffer);
						else if (!strcmp(str, "URLToolTip")) url_tt = strdup(parser.value_buffer);
					}
					xml_skip_element(&parser, "HyperLink");
					M4_TxtAddHypertext(samp, url, url_tt, start, end);
					if (url) free(url);
					if (url_tt) free(url_tt);
				}
				else if (!stricmp(str, "Karaoke")) {
					u32 startTime;
					startTime = 0;
					while (xml_has_attributes(&parser)) {
						str = xml_get_attribute(&parser);
						if (!strcmp(str, "startTime")) startTime = (u32) (1000*atof(parser.value_buffer));
					}
					M4_TxtAddKaraoke(samp, startTime);
					while (!xml_element_done(&parser, "Karaoke")) {
						str = xml_get_element(&parser);
						if (!strcmp(str, "KaraokeRange")) {
							u16 start, end;
							u32 endTime;
							start = end = 0;
							endTime = 0;
							while (xml_has_attributes(&parser)) {
								str = xml_get_attribute(&parser);
								if (!strcmp(str, "fromChar")) start = atoi(parser.value_buffer);
								else if (!strcmp(str, "toChar")) end = 1 + atoi(parser.value_buffer);
								else if (!strcmp(str, "endTime")) endTime = (u32) (1000*atof(parser.value_buffer));
							}
							xml_skip_element(&parser, "KaraokeRange");
							M4_TxtSetKaraokeSeg(samp, endTime, start, end);
						} else {
							xml_skip_element(&parser, str);
						}
					}
				}
			}

			/*in MP4 we must start at T=0, so add an empty sample*/
			if (ts && !nb_samples) {
				M4TXTSAMPLE firstsamp = M4_NewTextSample();
				s = M4_TxtToSample(firstsamp);
				s->DTS = 0;
				M4_AddSample(import->dest, track, 1, s);
				nb_samples++;
				M4_DeleteTextSample(firstsamp);
				M4_DeleteSample(&s);
			}

			s = M4_TxtToSample(samp);
			M4_DeleteTextSample(samp);
			s->DTS = ts;


			M4_AddSample(import->dest, track, descIndex, s);
			M4_DeleteSample(&s);
			nb_samples++;

			import_progress(import, parser.file_pos, parser.file_size);
			if (import->duration && (ts>import->duration)) break;
		} else {
			import_message(import, M4OK, "Unknown element %s - skipping", str);
			xml_skip_element(&parser, str);
		}
	}
	M4_SetLastSampleDuration(import->dest, track, 0);
	import_progress(import, nb_samples, nb_samples);

exit:
	xml_reset_parser(&parser);
	return e;
}


u32 tx3g_get_color(M4TrackImporter *import, XMLParser *parser)
{
	u32 r, g, b, a;
	u32 res, v;
	r = g = b = a = 0;
	if (sscanf(parser->value_buffer, "%d%%, %d%%, %d%%, %d%%", &r, &g, &b, &a) != 4) {
		import_message(import, M4OK, "Warning (line %d): color badly formatted", parser->line);
	}
	v = (u32) (a*255/100);
	res = (v&0xFF); res<<=8;
	v = (u32) (r*255/100);
	res |= (v&0xFF); res<<=8;
	v = (u32) (g*255/100);
	res |= (v&0xFF); res<<=8;
	v = (u32) (b*255/100);
	res |= (v&0xFF);
	return res;
}

void tx3g_parse_text_box(M4TrackImporter *import, XMLParser *parser, BoxRecord *box)
{
	memset(box, 0, sizeof(BoxRecord));
	while (xml_has_attributes(parser)) {
		char *str = xml_get_attribute(parser);
		if (!stricmp(str, "x")) box->left = atoi(parser->value_buffer);
		else if (!stricmp(str, "y")) box->top = atoi(parser->value_buffer);
		else if (!stricmp(str, "height")) box->bottom = atoi(parser->value_buffer);
		else if (!stricmp(str, "width")) box->right = atoi(parser->value_buffer);
	}
	xml_skip_element(parser, "defaultTextBox");
}

typedef struct
{
	u32 id;
	u32 pos;
} Marker;

#define GET_MARKER_POS(_val, __isend) \
	{	\
		u32 i, __m = atoi(parser.value_buffer);	\
		_val = 0;	\
		for (i=0; i<nb_marks; i++) { if (__m==marks[i].id) { _val = marks[i].pos; /*if (__isend) _val--; */break; } }	 \
	}	


static M4Err MP4T_ImportTimedText_TeXML(M4TrackImporter *import)
{
	M4Err e;
	u32 track, ID, nb_samples, nb_descs, timescale, DTS, w, h;
	s32 tx, ty, layer;
	StyleRecord styles[50];
	Marker marks[50];

	XMLParser parser;
	char *str;

	if (import->flags==M4TI_PROBE_ONLY) return M4OK;

	e = xml_init_parser(&parser, import->in_name);
	if (e) return import_message(import, e, "Cannot open file %s", import->in_name);

	str = xml_get_element(&parser);
	if (!str) { e = M4IOErr; goto exit; }
	if (strcmp(str, "?quicktime")) {
		e = import_message(import, M4BadParam, "Invalid QT TeXML file - expecting \"?quicktime\" header got %s", str);
		goto exit;
	}
	xml_skip_attributes(&parser);

	str = xml_get_element(&parser);
	if (strcmp(str, "text3GTrack")) {
		e = import_message(import, M4BadParam, "Invalid QT TeXML file - expecting %s got %s", "text3GTrack", str);
		goto exit;
	}
	w = TTXT_DEFAULT_WIDTH;
	h = TTXT_DEFAULT_HEIGHT;
	tx = ty = 0;
	layer = 0;
	timescale = 1000;
	while (xml_has_attributes(&parser)) {
		str = xml_get_attribute(&parser);
		if (!strcmp(str, "trackWidth")) w = atoi(parser.value_buffer);
		else if (!strcmp(str, "trackHeight")) h = atoi(parser.value_buffer);
		else if (!strcmp(str, "layer")) layer = atoi(parser.value_buffer);
		else if (!strcmp(str, "timescale")) timescale = atoi(parser.value_buffer);
		else if (!strcmp(str, "transform")) {
			Float fx, fy;
			sscanf(parser.value_buffer, "translate(%f,%f)", &fx, &fy);
			tx = (u32) fx; ty = (u32) fy;
		}
	}


	/*setup track in 3GP format directly (no ES desc)*/
	ID = (import->esd) ? import->esd->ESID : 0;
	track = M4_NewTrack(import->dest, ID, M4_TimedTextMediaType, timescale);
	if (!track) {
		e = M4_GetLastError(import->dest);
		goto exit;
	}
	M4_SetTrackEnabled(import->dest, track, 1);
	/*some MPEG-4 setup*/
	if (import->esd) {
		if (!import->esd->ESID) import->esd->ESID = M4_GetTrackID(import->dest, track);
		if (!import->esd->decoderConfig) import->esd->decoderConfig = (DecoderConfigDescriptor *) OD_NewDescriptor(DecoderConfigDescriptor_Tag);
		if (!import->esd->slConfig) import->esd->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
		import->esd->slConfig->timestampResolution = timescale;
		import->esd->decoderConfig->streamType = M4ST_TEXT;
		import->esd->decoderConfig->objectTypeIndication = 0x08;
		if (import->esd->OCRESID) M4_SetTrackReference(import->dest, track, M4_OCR_Ref, import->esd->OCRESID);
	}
	DTS = 0;
	M4_SetTrackVideoInfo(import->dest, track, w<<16, h<<16, tx<<16, ty<<16, (s16) layer);

	import_message(import, M4OK, "Timed Text (QT TeXML) Import - Track Size %d x %d", w, h);
	parser.OnProgress = xml_import_progress;
	parser.cbk = import;


	nb_descs = 0;
	nb_samples = 0;
	while (!xml_element_done(&parser, "text3GTrack") && !parser.done) {
		TextSampleDescriptor td;
		M4TXTSAMPLE samp = NULL;
		M4Sample *s;
		u32 duration, descIndex, nb_styles, nb_marks;
		Bool isRAP, same_style, same_box;
		str = xml_get_element(&parser);
		CHECK_STR(str)
		if (strcmp(str, "sample")) continue;

		isRAP = 0;
		duration = 1000;
		while (xml_has_attributes(&parser)) {
			str = xml_get_attribute(&parser);
			if (!strcmp(str, "duration")) duration = atoi(parser.value_buffer);
			else if (!strcmp(str, "keyframe")) isRAP = !stricmp(parser.value_buffer, "true");
		}

		nb_styles = 0;
		nb_marks = 0;
		same_style = same_box = 0;
		descIndex = 1;
		while (!xml_element_done(&parser, "sample") && !parser.done) {
			str = xml_get_element(&parser);
			CHECK_STR(str)
			if (!strcmp(str, "description")) {
				u32 i;
				memset(&td, 0, sizeof(TextSampleDescriptor));
				td.tag = TextConfig_Tag;
				td.vert_justif = -1;
				td.default_style.fontID = 1;
				td.default_style.font_size = TTXT_DEFAULT_FONT_SIZE;

				while (xml_has_attributes(&parser)) {
					str = xml_get_attribute(&parser);
					if (!strcmp(str, "horizontalJustification")) {
						if (!stricmp(parser.value_buffer, "center")) td.horiz_justif = 1;
						else if (!stricmp(parser.value_buffer, "right")) td.horiz_justif = -1;
						else if (!stricmp(parser.value_buffer, "left")) td.horiz_justif = 0;
					}
					else if (!strcmp(str, "verticalJustification")) {
						if (!stricmp(parser.value_buffer, "center")) td.vert_justif = 1;
						else if (!stricmp(parser.value_buffer, "bottom")) td.vert_justif = -1;
						else if (!stricmp(parser.value_buffer, "top")) td.vert_justif = 0;
					}
					else if (!strcmp(str, "backgroundColor")) td.back_color = tx3g_get_color(import, &parser);
					else if (!strcmp(str, "displayFlags")) {
						Bool rev_scroll = 0;
						if (strstr(parser.value_buffer, "scroll")) {
							u32 scroll_mode = 0;
							if (strstr(parser.value_buffer, "scrollIn")) td.displayFlags |= STF_ScrollIn;
							if (strstr(parser.value_buffer, "scrollOut")) td.displayFlags |= STF_ScrollOut;
							if (strstr(parser.value_buffer, "reverse")) rev_scroll = 1;
							if (strstr(parser.value_buffer, "horizontal")) scroll_mode = rev_scroll ? STF_ScrollRight : STF_ScrollMarquee;
							else scroll_mode = (rev_scroll ? STF_ScrollDown : STF_ScrollCredits);
							td.displayFlags |= (scroll_mode<<7) & STF_ScrollDirection;
						}
						if (strstr(parser.value_buffer, "writeTextVertically")) td.displayFlags |= STF_VerticalText;
						if (!strcmp(str, "continuousKaraoke")) td.displayFlags |= STF_Karaoke;
					}
				}
				while (!xml_element_done(&parser, "description")) {
					str = xml_get_element(&parser);
					if (!strcmp(str, "defaultTextBox")) tx3g_parse_text_box(import, &parser, &td.default_pos);
					else if (!strcmp(str, "fontTable")) {
						xml_skip_attributes(&parser);
						while (!xml_element_done(&parser, "FontTable")) {
							str = xml_get_element(&parser);
							if (!strcmp(str, "font")) {
								if (!td.fonts) {
									td.fonts = malloc(sizeof(FontRecord));
									td.font_count = 1;
								} else {
									td.font_count += 1;
									td.fonts = realloc(td.fonts, sizeof(FontRecord)*td.font_count);
								}
								while (xml_has_attributes(&parser)) {
									str = xml_get_attribute(&parser);
									if (!stricmp(str, "id")) td.fonts[td.font_count-1].fontID = atoi(parser.value_buffer);
									else if (!stricmp(str, "name")) td.fonts[td.font_count-1].fontName = strdup(parser.value_buffer);
								}
								xml_skip_element(&parser, "font");
							}
							else xml_skip_element(&parser, str);
						}
					}
					else if (!strcmp(str, "sharedStyles")) {
						u32 idx = 0;
						xml_skip_attributes(&parser);
						while (!xml_element_done(&parser, "SharedStyles")) {
							str = xml_get_element(&parser);
							if (!strcmp(str, "style")) {
								memset(&styles[nb_styles], 0, sizeof(StyleRecord));
								while (xml_has_attributes(&parser)) {
									str = xml_get_attribute(&parser);
									if (!strcmp(str, "id")) styles[nb_styles].startCharOffset = atoi(parser.value_buffer);
								}
								while (!xml_element_done(&parser, "style")) {
									str = xml_get_css(&parser);
									if (!strcmp(str, "font-table")) {
										styles[nb_styles].fontID = atoi(parser.value_buffer);
										for (i=0; i<td.font_count; i++) { if (td.fonts[i].fontID == styles[nb_styles].fontID) { idx = i; break; } }
									}
									else if (!strcmp(str, "font-size")) styles[nb_styles].font_size = atoi(parser.value_buffer);
									else if (!strcmp(str, "font-style") && !strcmp(parser.value_buffer, "italic")) styles[nb_styles].style_flags |= STF_Italic;
									else if (!strcmp(str, "font-weight") && !strcmp(parser.value_buffer, "bold")) styles[nb_styles].style_flags |= STF_Bold;
									else if (!strcmp(str, "text-decoration") && !strcmp(parser.value_buffer, "underline")) styles[nb_styles].style_flags |= STF_Underlined;
									else if (!strcmp(str, "color")) styles[nb_styles].text_color = tx3g_get_color(import, &parser);
								}
								if (!nb_styles) td.default_style = styles[0];
								nb_styles++;
							}
							else xml_skip_element(&parser, str);
						}
					}
					else {
						xml_skip_element(&parser, str);
					}

				}
				if ((td.default_pos.bottom==td.default_pos.top) || (td.default_pos.right==td.default_pos.left)) {
					td.default_pos.top = td.default_pos.left = 0;
					td.default_pos.right = w;
					td.default_pos.bottom = h;
				}
				if (!td.fonts) {
					td.font_count = 1;
					td.fonts = malloc(sizeof(FontRecord));
					td.fonts[0].fontID = 1;
					td.fonts[0].fontName = strdup("Serif");
				}
				M4_LookForSameTextDescription(import->dest, track, &td, &descIndex, &same_box, &same_style);
				if (!descIndex) {
					M4_NewTextDescription(import->dest, track, &td, NULL, NULL, &descIndex);
					same_style = same_box = 1;
				}

				for (i=0; i<td.font_count; i++) free(td.fonts[i].fontName);
				free(td.fonts);
				nb_descs ++;
			} else if (!strcmp(str, "sampleData")) {
				Bool is_utf16 = 0;
				u16 start, end;
				u32 styleID, i;
				u32 nb_chars, txt_len;
				txt_len = nb_chars = 0;

				samp = M4_NewTextSample();

				while (xml_has_attributes(&parser)) {
					str = xml_get_attribute(&parser);
					if (!strcmp(str, "targetEncoding") && !strcmp(parser.value_buffer, "utf16")) is_utf16 = 1;
					else if (!strcmp(str, "scrollDelay")) M4_TxtSetScrollDelay(samp, atoi(parser.value_buffer) );
					else if (!strcmp(str, "highlightColor")) M4_TxtSetHighlightColorARGB(samp, tx3g_get_color(import, &parser));
				}
				start = end = 0;
				while (!xml_element_done(&parser, "sampleData")) {
					str = xml_get_element(&parser);
					if (!strcmp(str, "text")) {
						styleID = 0;
						while (xml_has_attributes(&parser)) {
							str = xml_get_attribute(&parser);
							if (!strcmp(str, "styleID")) styleID = atoi(parser.value_buffer);
						}
						xml_set_text_mode(&parser, 1);
						txt_len = 0;
						while (!xml_element_done(&parser, "text")) {
							str = xml_get_element(&parser);
							if (str) {
								if (!strcmp(str, "marker")) {
									memset(&marks[nb_marks], 0, sizeof(Marker));
									marks[nb_marks].pos = nb_chars+txt_len;
									while (xml_has_attributes(&parser)) {
										str = xml_get_attribute(&parser);
										if (!strcmp(str, "id")) marks[nb_marks].id = atoi(parser.value_buffer);
									}
									xml_skip_element(&parser, "marker");
									nb_marks++;
								}
								else xml_skip_element(&parser, str);
							} else {
								while (xml_load_text(&parser)) {
									char *str = ttxt_parse_string(import, &parser);
									txt_len += strlen(str);
									M4_TxtAddText(samp, str, strlen(str));
									free(str);
								}
							}
						}
						xml_set_text_mode(&parser, 0);
						if (styleID && (!same_style || (td.default_style.startCharOffset != styleID))) {
							StyleRecord st = td.default_style;
							for (i=0; i<nb_styles; i++) { if (styles[i].startCharOffset==styleID) { st = styles[i]; break; } }
							st.startCharOffset = nb_chars;
							st.endCharOffset = nb_chars + txt_len;
							M4_TxtAddStyle(samp, &st);
						}
						nb_chars += txt_len;
					}
					else if (!stricmp(str, "highlight")) {
						while (xml_has_attributes(&parser)) {
							str = xml_get_attribute(&parser);
							if (!strcmp(str, "startMarker")) GET_MARKER_POS(start, 0)
							else if (!strcmp(str, "endMarker")) GET_MARKER_POS(end, 1)
						}
						xml_skip_element(&parser, "highlight");
						M4_TxtAddHighlight(samp, start, end);
					}
					else if (!stricmp(str, "blink")) {
						while (xml_has_attributes(&parser)) {
							str = xml_get_attribute(&parser);
							if (!strcmp(str, "startMarker")) GET_MARKER_POS(start, 0)
							else if (!strcmp(str, "endMarker")) GET_MARKER_POS(end, 1)
						}
						xml_skip_element(&parser, "blink");
						M4_TxtAddBlink(samp, start, end);
					}
					else if (!stricmp(str, "link")) {
						char *url, *url_tt;
						url = url_tt = NULL;
						while (xml_has_attributes(&parser)) {
							str = xml_get_attribute(&parser);
							if (!strcmp(str, "startMarker")) GET_MARKER_POS(start, 0)
							else if (!strcmp(str, "endMarker")) GET_MARKER_POS(end, 1)
							else if (!strcmp(str, "URL") || !strcmp(str, "href")) url = strdup(parser.value_buffer);
							else if (!strcmp(str, "URLToolTip") || !strcmp(str, "altString")) url_tt = strdup(parser.value_buffer);
						}
						xml_skip_element(&parser, "link");
						M4_TxtAddHypertext(samp, url, url_tt, start, end);
						if (url) free(url);
						if (url_tt) free(url_tt);
					}
					else if (!stricmp(str, "karaoke")) {
						u32 time = 0;
						while (xml_has_attributes(&parser)) {
							str = xml_get_attribute(&parser);
							if (!strcmp(str, "startTime")) time = atoi(parser.value_buffer);
						}
						M4_TxtAddKaraoke(samp, time);
						while (!xml_element_done(&parser, "karaoke")) {
							str = xml_get_element(&parser);
							if (!strcmp(str, "run")) {
								start = end = 0;
								while (xml_has_attributes(&parser)) {
									str = xml_get_attribute(&parser);
									if (!strcmp(str, "startMarker")) GET_MARKER_POS(start, 0)
									else if (!strcmp(str, "endMarker")) GET_MARKER_POS(end, 1)
									else if (!strcmp(str, "duration")) time += atoi(parser.value_buffer);
								}
								xml_skip_element(&parser, "run");
								M4_TxtSetKaraokeSeg(samp, time, start, end);
							} else {
								xml_skip_element(&parser, str);
							}
						}
					}
					else xml_skip_element(&parser, str);
				}
			}
		}
		/*OK, let's add the sample*/
		if (samp) {
			if (!same_box) M4_TxtSetBox(samp, td.default_pos.top, td.default_pos.left, td.default_pos.bottom, td.default_pos.right);
//			if (!same_style) M4_TxtAddStyle(samp, &td.default_style);

			s = M4_TxtToSample(samp);
			M4_DeleteTextSample(samp);
			s->IsRAP = isRAP;
			s->DTS = DTS;
			M4_AddSample(import->dest, track, descIndex, s);
			M4_DeleteSample(&s);
			nb_samples++;
			DTS += duration;
			if (import->duration && (DTS*1000> timescale*import->duration)) break;
		}
	}
	M4_SetLastSampleDuration(import->dest, track, 0);
	import_progress(import, nb_samples, nb_samples);

exit:
	xml_reset_parser(&parser);
	return e;
}


M4Err MP4T_ImportTimedText(M4TrackImporter *import)
{
	M4Err e;
	u32 fmt;
	e = guess_subtitle_fmt(import->in_name, &fmt);
	if (e) return e;
	if (!fmt) return M4NotSupported;
	if (import->flags & M4TI_PROBE_ONLY) {
		if (fmt==FMT_TXT_SUB) import->flags |= M4TI_OVERRIDE_FPS;
		return M4OK;
	}
	switch (fmt) {
	case FMT_TXT_SRT: return MP4T_ImportTimedText_SRT(import);
	case FMT_TXT_SUB: return MP4T_ImportTimedText_SUB(import);
	case FMT_TXT_TTXT: return MP4T_ImportTimedText_TTXT(import);
	case FMT_TXT_TeXML: return MP4T_ImportTimedText_TeXML(import);
	default: return M4BadParam;
	}
}

#endif

