/*
 *			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/m4_author.h>
#include <gpac/m4_decoder.h>

#ifndef M4_READ_ONLY

static const u32 ISMA_VIDEO_OD_ID = 20;
static const u32 ISMA_AUDIO_OD_ID = 10;

static const u32 ISMA_VIDEO_ES_ID = 201;
static const u32 ISMA_AUDIO_ES_ID = 101;

static const char ISMA_BIFS_CONFIG[] = {0x00, 0x00, 0x60 };

/*ISMA audio*/
static const u8 ISMA_BIFS_AUDIO[] = 
{
	0xC0, 0x10, 0x12, 0x81, 0x30, 0x2A, 0x05, 0x7C
};
/*ISMA video*/
static const u8 ISMA_BIFS_VIDEO[] = 
{
	0xC0, 0x10, 0x12, 0x60, 0x42, 0x82, 0x28, 0x29,
	0xD0, 0x4F, 0x00
};
/*ISMA audio-video*/
static const u8 ISMA_BIFS_AV[] = 
{
	0xC0, 0x10, 0x12, 0x81, 0x30, 0x2A, 0x05, 0x72, 
	0x60, 0x42, 0x82, 0x28, 0x29, 0xD0, 0x4F, 0x00
};

/*image only - uses same visual OD ID as video*/
static const u8 ISMA_BIFS_IMAGE[] = 
{
	0xC0, 0x11, 0xA4, 0xCD, 0x53, 0x6A, 0x0A, 0x44, 
	0x13, 0x00
};

/*ISMA audio-image*/
static const u8 ISMA_BIFS_AI[] = 
{
	0xC0, 0x11, 0xA5, 0x02, 0x60, 0x54, 0x0A, 0xE4,
	0xCD, 0x53, 0x6A, 0x0A, 0x44, 0x13, 0x00
};


Bool MP4T_IsSingleAV(M4File *file)
{
	u32 count, i, nb_any, nb_a, nb_v, nb_bifs, nb_od, nb_text;
	nb_a = nb_v = nb_any = nb_bifs = nb_od = nb_text = 0;
	count = M4_GetTrackCount(file);
	for (i=0; i<count; i++) {
		u32 mtype = M4_GetMediaType(file, i+1);
		if (mtype==M4_BIFSMediaType) {
			if (M4_GetSampleCount(file, i+1)>1) nb_any++;
			else nb_bifs++;
		} else if (mtype==M4_ODMediaType) {
			if (M4_GetSampleCount(file, i+1)>1) nb_any++;
			else nb_od++;
		}
		else if (mtype==M4_TimedTextMediaType) nb_text++;
		else if (mtype==M4_AudioMediaType) nb_a++;
		else if (mtype==M4_VisualMediaType) {
			/*discard file with images*/
			if (M4_GetSampleCount(file, i+1)==1) nb_any++;
			else nb_v++;
		}
		else nb_any++;
	}
	if (nb_any) return 0;
	if ((nb_bifs<=1) && (nb_od<=1) && (nb_a<=1) && (nb_v<=1) && (nb_text<=1) ) return 1;
	return 0;
}

u32 MP4T_GuessSpecification(M4File *file)
{
	u32 count, i, nb_any, nb_m4s, nb_a, nb_v, nb_bifs, nb_od, nb_mp3, nb_aac, nb_m4v, nb_avc, nb_amr, nb_h263, nb_qcelp, nb_evrc, nb_smv, nb_text;

	nb_m4s = nb_a = nb_v = nb_any = nb_bifs = nb_od = nb_mp3 = nb_aac = nb_m4v = nb_avc = nb_amr = nb_h263 = nb_qcelp = nb_evrc = nb_smv = nb_text = 0;

	count = M4_GetTrackCount(file);
	for (i=0; i<count; i++) {
		u32 mtype = M4_GetMediaType(file, i+1);
		u32 mstype = M4_GetMediaSubType(file, i+1, 1);

		if (mtype==M4_BIFSMediaType) {
			nb_bifs++;
			/*forces non-isma*/
			if (M4_GetSampleCount(file, i+1)>1) nb_m4s++;
		} else if (mtype==M4_ODMediaType) {
			nb_od++;
			/*forces non-isma*/
			if (M4_GetSampleCount(file, i+1)>1) nb_m4s++;
		}
		else if (mtype==M4_TimedTextMediaType) nb_text++;
		else if ((mtype==M4_AudioMediaType) || (mtype==M4_VisualMediaType)) {
			switch (mstype) {
			case M4_3GP_AMR_SubType:
			case M4_3GP_AMR_WB_SubType:
				nb_amr++;
				break;
			case M4_3GP_H263_SubType: nb_h263++; break;
			case M4_3GP_EVRC_SubType: nb_evrc++; break;
			case M4_3GP_QCELP_SubType: nb_qcelp++; break;
			case M4_3GP_SMV_SubType: nb_smv++; break;
			case M4_AVC_H264_SubType: nb_avc++; break;
			case M4_MPEG4_SubType:
			case M4_ENC_MPEG4_SubType:
			{
				DecoderConfigDescriptor *dcd = M4_GetDecoderInformation(file, i+1, 1);
				switch (dcd->streamType) {
				case M4ST_VISUAL:
					if (dcd->objectTypeIndication==0x20) nb_m4v++;
					else if (dcd->objectTypeIndication==0x21) nb_avc++;
					else nb_v++;
					break;
				case M4ST_AUDIO:
					switch (dcd->objectTypeIndication) {
					case 0x66: case 0x67: case 0x68: case 0x40: nb_aac++; break;
					case 0x69: case 0x6B: nb_mp3++; break;
					case 0xA0: nb_evrc++; break;
					case 0xA1: nb_smv++; break;
					case 0xE1: nb_qcelp++; break;
					default: nb_a++; break;
					}
					break;
				/*SHOULD NEVER HAPPEN - IF SO, BROKEN MPEG4 FILE*/
				default:
					nb_any++;
					break;
				}
				OD_DeleteDescriptor((Descriptor **)&dcd);
			}
				break;
			default:
				if (mtype==M4_VisualMediaType) nb_v++;
				else nb_a++;
				break;
			}
		} else if ((mtype==M4_MPEG4_SubType) || (mtype==M4_ENC_MPEG4_SubType)) nb_m4s++;
		else nb_any++;
	}
	if (nb_any) return ISO_Media_File;
	if (nb_qcelp || nb_evrc || nb_smv) {
		/*non std mix of streams*/
		if (nb_m4s || nb_avc || nb_bifs || nb_od || nb_mp3 || nb_a || nb_v) return ISO_Media_File;
		return GPP2_V1_File;
	}
	/*other a/v/s streams*/
	if (nb_v || nb_a || nb_m4s) return MP4_V2_File;

	nb_v = nb_m4v + nb_avc + nb_h263;
	nb_a = nb_mp3 + nb_aac + nb_amr;
	
	/*avc file: whatever has AVC and no systems*/
	if (nb_avc) {
		if (!nb_bifs && !nb_od) return H264_AVC_File;
		return MP4_V2_File;
	}
	/*MP3: ISMA and MPEG4*/
	if (nb_mp3) {
		if (!nb_text && (nb_v<=1) && (nb_a<=1) && (nb_bifs==1) && (nb_od==1))
			return FOUR_CHAR_INT('I', 'S', 'M', 'A');
		return MP4_V2_File;
	}
	/*MP4*/
	if (nb_bifs || nb_od) {
		/*issue with AMR and H263 which don't have MPEG mapping: non compliant file*/
		if (nb_amr || nb_h263) return ISO_Media_File;
		return MP4_V2_File;
	}
	/*use ISMA (3GP fine too)*/
	if (!nb_amr && !nb_h263 && !nb_text) {
		if ((nb_v<=1) && (nb_a<=1)) return FOUR_CHAR_INT('I', 'S', 'M', 'A');
		return MP4_V2_File;
	}

	if ((nb_v<=1) && (nb_a<=1) && (nb_text<=1)) return nb_text ? GPP_V6_File : GPP_V5_File;
	return GPP_GEN_File;
}


void log_message(void (*LogMsg)(void *cbk, const char *szMsg), void *cbk, char *format, ...)
{
	va_list args;
	va_start(args, format);
	if (LogMsg) {
		char szMsg[1024];
		vsprintf(szMsg, format, args);
		LogMsg(cbk, szMsg);
	} else {
		vfprintf(stderr, format,args);
		fprintf(stderr, "\n");
	}
	va_end(args);
}


M4Err MP4T_MakeISMA(M4File *mp4file, Bool keepESIDs, Bool keepImage, Bool no_ocr, void (*LogMsg)(void *cbk, const char *szMsg), void *cbk)
{
	u32 AudioTrack, VideoTrack, Tracks, i, mType, bifsT, odT, descIndex, VideoType, VID, AID, bifsID, odID;
	u32 bifs, w, h;
	Bool is_image, image_track;
	ESDescriptor *a_esd, *v_esd, *_esd;
	Bool update_vid_esd;
	ObjectDescriptor *od;
	ObjectDescriptorUpdate *odU;
	LPODCODEC codec;
	M4Sample *samp;
	BitStream *bs;
	u8 audioPL, visualPL;
	
	switch (M4_GetOpenMode(mp4file)) {
	case M4_OPEN_EDIT:
	case M4_OPEN_WRITE:
	case M4_WRITE_EDIT:
		break;
	default:
		return M4BadParam;
	}


	Tracks = M4_GetTrackCount(mp4file);
	AID = VID = 0;
	is_image = 0;

	//search for tracks
	for (i=0; i<Tracks; i++) {
		ESDescriptor *esd = M4_GetStreamDescriptor(mp4file, i+1, 1);
		//remove from IOD
		M4_RemoveTrackFromRootOD(mp4file, i+1);

		mType = M4_GetMediaType(mp4file, i+1);
		switch (mType) {
		case M4_VisualMediaType:
			image_track = 0;
			if (esd && ((esd->decoderConfig->objectTypeIndication==0x6C) || (esd->decoderConfig->objectTypeIndication==0x6D)) )
				image_track = 1;

			/*remove image tracks if wanted*/
			if (keepImage || !image_track) {
				/*only ONE video stream possible with ISMA*/
				if (VID) {
					if (esd) OD_DeleteDescriptor((Descriptor**)&esd);
					log_message(LogMsg, cbk, "More than one video track found, cannot ISMA'ize file - remove extra track(s)");
					return M4NotSupported;
				}
				VID = M4_GetTrackID(mp4file, i+1);
				is_image = image_track;
			} else {
				log_message(LogMsg, cbk, "Visual track ID %d: only one sample found, assuming image and removing track", M4_GetTrackID(mp4file, i+1) );
				M4_RemoveTrack(mp4file, i+1);
				i -= 1;
				Tracks = M4_GetTrackCount(mp4file);
			}
			break;
		case M4_AudioMediaType:
			if (AID) {
				if (esd) OD_DeleteDescriptor((Descriptor**)&esd);
				log_message(LogMsg, cbk, "More than one audio track found, cannot ISMA'ized file - remove extra track(s)");
				return M4NotSupported;
			}
			AID = M4_GetTrackID(mp4file, i+1);
			break;
		/*clean file*/
		default:
			if (mType==M4_HintMediaType) {
				log_message(LogMsg, cbk, "Removing Hint track ID %d", M4_GetTrackID(mp4file, i+1) );
			} else {
				log_message(LogMsg, cbk, "Removing MPEG-4 Systems track ID %d", M4_GetTrackID(mp4file, i+1) );
			}
			M4_RemoveTrack(mp4file, i+1);
			i -= 1;
			Tracks = M4_GetTrackCount(mp4file);
			break;
		}
		if (esd) OD_DeleteDescriptor((Descriptor**)&esd);
	}
	//no audio no video
	if (!AID && !VID) return M4OK;

	/*reset all PLs*/
	visualPL = 0xFE;
	audioPL = 0xFE;

	od = (ObjectDescriptor *) M4_GetRootOD(mp4file);
	if (od && (od->tag==InitialObjectDescriptor_Tag)) {
		audioPL = ((InitialObjectDescriptor*)od)->audio_profileAndLevel;
		visualPL = ((InitialObjectDescriptor*)od)->visual_profileAndLevel;
	}
	if (od) OD_DeleteDescriptor((Descriptor **)&od);


	//create the OD AU
	bifs = 0;
	odU = (ObjectDescriptorUpdate *) OD_NewCommand(ODUpdate_Tag);
	
	a_esd = v_esd = NULL;
	update_vid_esd = 0;

	M4_SetRootOD_ID(mp4file, 1);

	bifsID = 1;
	odID = 2;
	if (keepESIDs) {
		bifsID = 1;
		while ((bifsID==AID) || (bifsID==VID)) bifsID++;
		odID = 2;
		while ((odID==AID) || (odID==VID) || (odID==bifsID)) odID++;

	}

	VideoTrack = M4_GetTrackByID(mp4file, VID);
	AudioTrack = M4_GetTrackByID(mp4file, AID);
	
	w = h = 0;
	if (VideoTrack) {
		bifs = 1;
		VideoType = 0;
		od = (ObjectDescriptor *) OD_NewDescriptor(ObjectDescriptor_Tag);
		od->objectDescriptorID = ISMA_VIDEO_OD_ID;

		if (!keepESIDs && (VID != ISMA_VIDEO_ES_ID)) {
			M4_ChangeTrackID(mp4file, VideoTrack, ISMA_VIDEO_ES_ID);
		}

		v_esd = M4_GetStreamDescriptor(mp4file, VideoTrack, 1);
		if (v_esd) {
			v_esd->OCRESID = no_ocr ? 0 : bifsID;

			OD_AddDescToDesc((Descriptor *)od, (Descriptor *)v_esd);
			ChainAddEntry(odU->objectDescriptors, od);
			M4_GetVisualEntrySize(mp4file, VideoTrack, 1, &w, &h);
			if ((v_esd->decoderConfig->objectTypeIndication==0x20) && (v_esd->decoderConfig->streamType==M4ST_VISUAL)) {
				M4VDecoderSpecificInfo dsi;
				M4V_GetConfig(v_esd->decoderConfig->decoderSpecificInfo->data, v_esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
				if (!is_image && (!w || !h)) {
					w = dsi.width;
					h = dsi.height;
					M4_SetVisualEntrySize(mp4file, VideoTrack, 1, w, h);
					log_message(LogMsg, cbk, "Adjusting visual track size to %d x %d", w, h);
				}
				if (dsi.par_num && (dsi.par_den!=dsi.par_num)) {
					w *= dsi.par_num;
					w /= dsi.par_den;
				}
				if (dsi.VideoPL) visualPL = dsi.VideoPL;
			}
		}
	}

	if (AudioTrack) {
		od = (ObjectDescriptor *) OD_NewDescriptor(ObjectDescriptor_Tag);
		od->objectDescriptorID = ISMA_AUDIO_OD_ID;

		if (!keepESIDs && (AID != ISMA_AUDIO_ES_ID)) {
			M4_ChangeTrackID(mp4file, AudioTrack, ISMA_AUDIO_ES_ID);
		}

		a_esd = M4_GetStreamDescriptor(mp4file, AudioTrack, 1);
		if (a_esd) {
			a_esd->OCRESID = no_ocr ? 0 : bifsID;

			if (!keepESIDs) a_esd->ESID = ISMA_AUDIO_ES_ID;
			OD_AddDescToDesc((Descriptor *)od, (Descriptor *)a_esd);
			ChainAddEntry(odU->objectDescriptors, od);
			if (!bifs) {
				bifs = 3;
			} else {
				bifs = 2;
			}

			if (a_esd->decoderConfig->objectTypeIndication == 0x40) {
				M4ADecoderSpecificInfo cfg;
				M4A_GetConfig(a_esd->decoderConfig->decoderSpecificInfo->data, a_esd->decoderConfig->decoderSpecificInfo->dataLength, &cfg);
				audioPL = cfg.audioPL;
			}
		}
	}

	/*update video cfg if needed*/
	if (v_esd) M4_ChangeStreamDescriptor(mp4file, VideoTrack, 1, v_esd);
	if (a_esd) M4_ChangeStreamDescriptor(mp4file, AudioTrack, 1, a_esd);

	/*likely 3GP or other files...*/
	if ((!a_esd && AudioTrack) || (!v_esd && VideoTrack)) return M4OK;

	//get the OD sample
	codec = OD_NewCodec();
	samp = M4_NewSample();
	OD_AddCommand(codec, (ODCommand *)odU);
	OD_EncodeAU(codec);
	OD_GetEncodedAU(codec, &samp->data, &samp->dataLength);
	OD_DeleteCodec(codec);
	samp->CTS_Offset = 0;
	samp->DTS = 0;
	samp->IsRAP = 1;
	
	/*create the OD track*/
	odT = M4_NewTrack(mp4file, odID, M4_ODMediaType, M4_GetTimeScale(mp4file));
	if (!odT) return M4_GetLastError(mp4file);

	_esd = OD_NewESDescriptor(SLPredef_MP4);
	_esd->decoderConfig->bufferSizeDB = samp->dataLength;
	_esd->decoderConfig->objectTypeIndication = 0x01;
	_esd->decoderConfig->streamType = M4ST_OD;
	_esd->ESID = odID;
	_esd->OCRESID = no_ocr ? 0 : bifsID;
	M4_NewStreamDescription(mp4file, odT, _esd, NULL, NULL, &descIndex);
	OD_DeleteDescriptor((Descriptor **)&_esd);
	M4_AddSample(mp4file, odT, 1, samp);
	M4_DeleteSample(&samp);

	M4_SetTrackGroup(mp4file, odT, 1);

	/*create the BIFS track*/
	bifsT = M4_NewTrack(mp4file, bifsID, M4_BIFSMediaType, M4_GetTimeScale(mp4file));
	if (!bifsT) return M4_GetLastError(mp4file);

	_esd = OD_NewESDescriptor(SLPredef_MP4);
	_esd->decoderConfig->bufferSizeDB = sizeof(ISMA_BIFS_CONFIG);
	_esd->decoderConfig->objectTypeIndication = 0x02;
	_esd->decoderConfig->streamType = M4ST_SCENE;
	_esd->ESID = bifsID;
	_esd->OCRESID = 0;

	/*rewrite ISMA BIFS cfg*/
	bs = NewBitStream(NULL, 0, BS_WRITE);
	/*empty bifs stuff*/
	BS_WriteInt(bs, 0, 17);
	/*command stream*/
	BS_WriteInt(bs, 1, 1);
	/*in pixel metrics*/
	BS_WriteInt(bs, 1, 1);
	/*with size*/
	BS_WriteInt(bs, 1, 1);
	BS_WriteInt(bs, w, 16);
	BS_WriteInt(bs, h, 16);
	BS_Align(bs);
	BS_GetContent(bs, (unsigned char **) &_esd->decoderConfig->decoderSpecificInfo->data, &_esd->decoderConfig->decoderSpecificInfo->dataLength);
	M4_NewStreamDescription(mp4file, bifsT, _esd, NULL, NULL, &descIndex);
	OD_DeleteDescriptor((Descriptor **)&_esd);
	DeleteBitStream(bs);
	M4_SetVisualEntrySize(mp4file, bifsT, descIndex, w, h);
	
	samp = M4_NewSample();
	samp->CTS_Offset = 0;
	samp->DTS = 0;
	switch (bifs) {
	case 1:
		if (is_image) {
			samp->data = (char *) ISMA_BIFS_IMAGE;
			samp->dataLength = 10;
		} else {
			samp->data = (char *) ISMA_BIFS_VIDEO;
			samp->dataLength = 11;
		}
		break;
	case 2:
		if (is_image) {
			samp->data = (char *) ISMA_BIFS_AI;
			samp->dataLength = 15;
		} else {
			samp->data = (char *) ISMA_BIFS_AV;
			samp->dataLength = 16;
		}
		break;
	case 3:
		samp->data = (char *) ISMA_BIFS_AUDIO;
		samp->dataLength = 8;
		break;
	}

	samp->IsRAP = 1;

	M4_AddSample(mp4file, bifsT, 1, samp);
	samp->data = NULL;
	M4_DeleteSample(&samp);
	M4_SetTrackGroup(mp4file, bifsT, 1);

	M4_SetTrackEnabled(mp4file, bifsT, 1);
	M4_SetTrackEnabled(mp4file, odT, 1);
	M4_AddTrackToRootOD(mp4file, bifsT);
	M4_AddTrackToRootOD(mp4file, odT);

	M4_SetMoviePLIndication(mp4file, M4_PL_SCENE, 1);
	M4_SetMoviePLIndication(mp4file, M4_PL_GRAPHICS, 1);
	M4_SetMoviePLIndication(mp4file, M4_PL_OD, 1);
	M4_SetMoviePLIndication(mp4file, M4_PL_AUDIO, audioPL);
	M4_SetMoviePLIndication(mp4file, M4_PL_VISUAL, (u8) (is_image ? 0xFE : visualPL));

	M4_SetMovieVersionInfo(mp4file, MP4_V2_File, 1);
	M4_ModifyAlternateBrand(mp4file, ISO_Media_File, 1);
	M4_ModifyAlternateBrand(mp4file, GPP_V5_File, 1);
	return M4OK;
}


M4Err MP4T_Make3GPP(M4File *mp4file, void (*LogMsg)(void *cbk, const char *szMsg), void *cbk)
{
	u32 Tracks, i, mType, nb_vid, nb_aud, nb_txt, nb_non_mp4;
	Bool is_3g2 = 0;

	switch (M4_GetOpenMode(mp4file)) {
	case M4_OPEN_EDIT:
	case M4_OPEN_WRITE:
	case M4_WRITE_EDIT:
		break;
	default:
		return M4BadParam;
	}

	Tracks = M4_GetTrackCount(mp4file);
	nb_vid = nb_aud = nb_txt = nb_non_mp4 = 0;

	for (i=0; i<Tracks; i++) {
		M4_RemoveTrackFromRootOD(mp4file, i+1);

		mType = M4_GetMediaType(mp4file, i+1);
		switch (mType) {
		case M4_VisualMediaType:
			/*remove image tracks if wanted*/
			if (M4_GetSampleCount(mp4file, i+1)<=1) {
				log_message(LogMsg, cbk, "Visual track ID %d: only one sample found, assuming image and removing track", M4_GetTrackID(mp4file, i+1) );
				goto remove_track;
			}

			switch (M4_GetMediaSubType(mp4file, i+1, 1)) {
			case M4_3GP_H263_SubType:
				nb_vid++;
				nb_non_mp4 ++;
				break;
			case M4_AVC_H264_SubType:
				nb_vid++;
				break;
			case M4_MPEG4_SubType:
				{
					ESDescriptor *esd = M4_GetStreamDescriptor(mp4file, i+1, 1);
					/*both MPEG4-Video and H264/AVC are supported*/
					if ((esd->decoderConfig->objectTypeIndication==0x20) || (esd->decoderConfig->objectTypeIndication==0x21) ) {
						nb_vid++;
					} else {
						log_message(LogMsg, cbk, "Video format not supported by 3GP - removing track ID %d", M4_GetTrackID(mp4file, i+1) );
						goto remove_track;
					}
					OD_DeleteDescriptor((Descriptor **)&esd);
				}
				break;
			default:
				log_message(LogMsg, cbk, "Video format not supported by 3GP - removing track ID %d", M4_GetTrackID(mp4file, i+1) );
				goto remove_track;
			}
			break;
		case M4_AudioMediaType:
			switch (M4_GetMediaSubType(mp4file, i+1, 1)) {
			case M4_3GP_EVRC_SubType:
			case M4_3GP_QCELP_SubType:
			case M4_3GP_SMV_SubType:
				is_3g2 = 1;
				nb_aud++;
				break;
			case M4_3GP_AMR_SubType:
			case M4_3GP_AMR_WB_SubType:
				nb_aud++;
				nb_non_mp4 ++;
				break;
			case M4_MPEG4_SubType:
			{
				ESDescriptor *esd = M4_GetStreamDescriptor(mp4file, i+1, 1);
				switch (esd->decoderConfig->objectTypeIndication) {
				case 0xE1:
				case 0xA0:
				case 0xA1:
					is_3g2 = 1;
				case 0x40:
					nb_aud++;
					break;
				default:
					log_message(LogMsg, cbk, "Audio format not supported by 3GP - removing track ID %d", M4_GetTrackID(mp4file, i+1) );
					goto remove_track;
				}
				OD_DeleteDescriptor((Descriptor **)&esd);
			}
				break;
			default:
				log_message(LogMsg, cbk, "Audio format not supported by 3GP - removing track ID %d", M4_GetTrackID(mp4file, i+1) );
				goto remove_track;
			}
			break;
		case M4_TimedTextMediaType:
			nb_txt++;
			break;
		/*clean file*/
		default:
			if (mType==M4_HintMediaType) {
				log_message(LogMsg, cbk, "Removing Hint track ID %d", M4_GetTrackID(mp4file, i+1) );
			} else {
				log_message(LogMsg, cbk, "Removing system track ID %d", M4_GetTrackID(mp4file, i+1) );
			}

remove_track:
			M4_RemoveTrack(mp4file, i+1);
			i -= 1;
			Tracks = M4_GetTrackCount(mp4file);
			break;
		}
	}

	/*no more IOD*/
	M4_RemoveRootOD(mp4file);

	if (is_3g2) {
		M4_SetMovieVersionInfo(mp4file, GPP2_V1_File, 65536);
		M4_ModifyAlternateBrand(mp4file, GPP_V6_File, 0);
		M4_ModifyAlternateBrand(mp4file, GPP_V5_File, 0);
		M4_ModifyAlternateBrand(mp4file, GPP_GEN_File, 0);
		log_message(LogMsg, cbk, "Setting major brand to 3GPP2");
	} else {
		/*update FType*/
		if ((nb_vid>1) || (nb_aud>1) || (nb_txt>1)) {
			/*3GPP general purpose*/
			M4_SetMovieVersionInfo(mp4file, GPP_GEN_File, 1024);
			M4_ModifyAlternateBrand(mp4file, GPP_V6_File, 0);
			M4_ModifyAlternateBrand(mp4file, GPP_V5_File, 0);
			M4_ModifyAlternateBrand(mp4file, GPP_V4_File, 0);
			log_message(LogMsg, cbk, "Setting major brand to 3GPP Generic file");
		} 
		/*commented for QT compatibility, although this is correct (qt doesn't understand 3GP6 brand)*/
		else if (nb_txt && 0) {
			M4_SetMovieVersionInfo(mp4file, GPP_V6_File, 1024);
			M4_ModifyAlternateBrand(mp4file, GPP_V5_File, 1);
			M4_ModifyAlternateBrand(mp4file, GPP_V4_File, 1);
			M4_ModifyAlternateBrand(mp4file, GPP_GEN_File, 0);
			log_message(LogMsg, cbk, "Setting major brand to 3GPP V6 file");
		} else {
			M4_SetMovieVersionInfo(mp4file, GPP_V5_File, 0/*1024*/);
			M4_ModifyAlternateBrand(mp4file, GPP_V6_File, 0);
			M4_ModifyAlternateBrand(mp4file, GPP_V4_File, 1);
			M4_ModifyAlternateBrand(mp4file, GPP_GEN_File, 0);
			log_message(LogMsg, cbk, "Setting major brand to 3GPP V5 file");
		}
	}
	/*add/remove MP4 brands and add isom*/
	M4_ModifyAlternateBrand(mp4file, MP4_V1_File, (u8) ((is_3g2||nb_non_mp4) ? 0 : 1));
	M4_ModifyAlternateBrand(mp4file, MP4_V2_File, (u8) (nb_non_mp4 ? 0 : 1));
	M4_ModifyAlternateBrand(mp4file, ISO_Media_File, 1);
	return M4OK;
}


M4Err MP4T_MakeInterleaved(M4File *mp4file, Double TimeInSec)
{
	M4Err e;
	if (M4_GetOpenMode(mp4file) < M4_OPEN_EDIT) return M4BadParam;
	e = M4_SetStorageMode(mp4file, M4_INTERLEAVED);
	if (e) return e;
	return M4_SetInterleavingTime(mp4file, (u32) (TimeInSec * M4_GetTimeScale(mp4file)));
}

typedef struct
{
	u32 TrackID;
	u32 SampleNum, SampleCount;
	u32 FragmentLength;
	u32 OriginalTrack;
	u32 TimeScale, MediaType, DefaultDuration;
} TrackFragmenter;


M4Err MP4T_FragmentMovie(M4File *input, char *output_file, Double max_duration, void (*OnProgress)(void *cbk, u32 total, u32 done), void *cbk)
{
	u8 NbBits;
	u32 i, TrackNum, descIndex, j, count;
	u32 defaultDuration, defaultSize, defaultDescriptionIndex, defaultRandomAccess, nb_samp, nb_done;
	u8 defaultPadding;
	u16 defaultDegradationPriority;
	M4Err e;
	ESDescriptor *esd;
	M4File *output;
	M4Sample *sample, *next;
	Chain *fragmenters;
	u32 MaxFragmentDuration;
	TrackFragmenter *tf;

	//create output file
	output = M4_MovieOpen(output_file, M4_OPEN_WRITE);
	if (!output) return M4_GetLastError(NULL);

	fragmenters = NewChain();
	e = M4_SetMovieVersionInfo(output, MP4_V2_File, 1);
	if (e) goto err_exit;
	e = M4_ModifyAlternateBrand(output, ISO_Media_File, 1);
	if (e) goto err_exit;

	MaxFragmentDuration = (u32) (max_duration * M4_GetTimeScale(input));
	nb_samp = 0;
	//duplicates all tracks
	for (i=0; i<M4_GetTrackCount(input); i++) {
		TrackNum = M4_NewTrack(output, M4_GetTrackID(input, i+1), M4_GetMediaType(input, i+1), M4_GetMediaTimeScale(input, i+1));
		if (!TrackNum) {
			e = M4_GetLastError(output);
			goto err_exit;
		}

		esd = M4_GetStreamDescriptor(input, i+1, 1);
		if (esd) {
			M4_NewStreamDescription(output, TrackNum, esd, NULL, NULL, &descIndex);
			OD_DeleteDescriptor((Descriptor **) &esd);
		} else {
			M4_CloneStreamDescription(output, TrackNum, input, i+1, 1, NULL, NULL, &descIndex);
		}

		//if few samples don't fragment track
		count = M4_GetSampleCount(input, i+1);
		if (count<=2) {
			for (j=0; j<count; j++) {
				sample = M4_GetSample(input, i+1, j+1, &descIndex);
				e = M4_AddSample(output, TrackNum, 1, sample);
				M4_DeleteSample(&sample);
				if (e) goto err_exit;
			}
		}
		//otherwise setup fragmented
		else {
			M4_GetTrackSampleDefaults(input, i+1, 
										 &defaultDuration, &defaultSize, &defaultDescriptionIndex, &defaultRandomAccess, &defaultPadding, &defaultDegradationPriority);
			//otherwise setup fragmentation
			e = M4_SetupTrackFragment(output, M4_GetTrackID(output, TrackNum), 
						defaultDescriptionIndex, defaultDuration, 
						defaultSize, (u8) defaultRandomAccess, 
						defaultPadding, defaultDegradationPriority);
			if (e) goto err_exit;

			tf = malloc(sizeof(TrackFragmenter));
			memset(tf, 0, sizeof(TrackFragmenter));
			tf->TrackID = M4_GetTrackID(output, TrackNum);
			tf->SampleCount = M4_GetSampleCount(input, i+1);
			tf->OriginalTrack = i+1;
			tf->TimeScale = M4_GetMediaTimeScale(input, i+1);
			tf->MediaType = M4_GetMediaType(input, i+1);
			tf->DefaultDuration = defaultDuration;
			ChainAddEntry(fragmenters, tf);
			nb_samp += count;
		}

		if (M4_IsTrackInRootOD(input, i+1)) M4_AddTrackToRootOD(output, TrackNum);
		//copy user data ?
	}
	//copy movie desc


	//flush movie
	e = M4_FinalizeMovieForFragments(output);
	if (e) goto err_exit;

	nb_done = 0;

	while ( (count = ChainGetCount(fragmenters)) ) {

		e = M4_StartFragment(output);
		if (e) goto err_exit;
		//setup some default
		for (i=0; i<count; i++) {
			tf = ChainGetEntry(fragmenters, i);
			if (tf->MediaType == M4_VisualMediaType) {
				e = M4_TrackFragmentSetOptions(output, tf->TrackID, TFO_IFRAME, 1);
				if (e) goto err_exit;
			}
		}	
		sample = NULL;
		
		//process track by track
		for (i=0; i<count; i++) {
			tf = ChainGetEntry(fragmenters, i);

			//ok write samples
			while (1) {
				if (!sample) {
					sample = M4_GetSample(input, tf->OriginalTrack, tf->SampleNum + 1, &descIndex);
				}
				M4_GetSamplePaddingBits(input, tf->OriginalTrack, tf->SampleNum+1, &NbBits);

				next = M4_GetSample(input, tf->OriginalTrack, tf->SampleNum + 2, &j);
				if (next) {
					defaultDuration = next->DTS - sample->DTS;
				} else {
					defaultDuration = tf->DefaultDuration;
				}

				e = M4_TrackFragmentAddSample(output, tf->TrackID, sample, descIndex, 
								 defaultDuration, NbBits, 0);
				if (e) goto err_exit;

				if (OnProgress) OnProgress(cbk, nb_done, nb_samp);
				nb_done++;

				M4_DeleteSample(&sample);
				sample = next;
				tf->FragmentLength += defaultDuration;
				tf->SampleNum += 1;

				//end of track fragment or track
				if ((tf->SampleNum==tf->SampleCount) || (tf->FragmentLength*1000 > MaxFragmentDuration*tf->TimeScale)) {
					M4_DeleteSample(&next);
					sample = next = NULL;
					tf->FragmentLength = 0;
					break;
				}
			}
			if (tf->SampleNum==tf->SampleCount) {
				free(tf);
				ChainDeleteEntry(fragmenters, i);
				i--;
				count --;
			}
		}
	}

err_exit:
	while (ChainGetCount(fragmenters)) {
		tf = ChainGetEntry(fragmenters, 0);
		free(tf);
		ChainDeleteEntry(fragmenters, 0);
	}
	DeleteChain(fragmenters);
	if (e) M4_MovieDelete(output);
	else M4_MovieClose(output);
	if (OnProgress) OnProgress(cbk, nb_samp, nb_samp);
	return e;
}

M4Err MP4T_SetChapterFile(M4File *file, char *chap_file, Double import_fps)
{
	M4Err e;
	u32 state;
	u32 cur_chap;
	u64 ts;
	u32 h, m, s, ms, fr, fps;
	char line[1024];
	char szTitle[1024];
	FILE *f = fopen(chap_file, "rt");
	if (!f) return M4URLNotFound;

	M4_DeleteChapter(file, 0, 0);

	cur_chap = 0;
	ts = 0;
	state = 0;
	while (fgets(line, 1024, f) != NULL) {
		char *title = NULL;
		u32 off = 0;
		char *sL;
		while (1) {
			u32 len = strlen(line);
			if (!len) break;
			switch (line[len-1]) {
			case '\n': case '\t': case '\r': case ' ':
				line[len-1] = 0;
				continue;
			}
			break;
		}

		while (line[off]==' ') off++;
		if (!strlen(line+off)) continue;
		sL = line+off;

		szTitle[0] = 0;
		/*ZoomPlayer chapters*/
		if (!strnicmp(sL, "AddChapter(", 11)) {
			u32 nb_fr;
			sscanf(sL, "AddChapter(%d,%s)", &nb_fr, szTitle);
			ts = nb_fr;
			ts *= 1000;
			if (import_fps) ts = (u64) (((s64) ts ) / import_fps);
			else ts /= 25;
			sL = strchr(sL, ','); strcpy(szTitle, sL+1); sL = strrchr(szTitle, ')'); if (sL) sL[0] = 0;
		} else if (!strnicmp(sL, "AddChapterBySecond(", 19)) {
			u32 nb_s;
			sscanf(sL, "AddChapterBySecond(%d,%s)", &nb_s, szTitle);
			ts = nb_s;
			ts *= 1000;
			sL = strchr(sL, ','); strcpy(szTitle, sL+1); sL = strrchr(szTitle, ')'); if (sL) sL[0] = 0;
		} else if (!strnicmp(sL, "AddChapterByTime(", 17)) {
			u32 h, m, s;
			sscanf(sL, "AddChapterByTime(%d,%d,%d,%s)", &h, &m, &s, szTitle);
			ts = 3600*h + 60*m + s;
			ts *= 1000;
			sL = strchr(sL, ','); 
			if (sL) sL = strchr(sL+1, ','); 
			if (sL) sL = strchr(sL+1, ','); 
			strcpy(szTitle, sL+1); sL = strrchr(szTitle, ')'); if (sL) sL[0] = 0;
		}
		/*regular or SMPTE time codes*/
		else if ((strlen(sL)>=8) && (sL[2]==':') && (sL[5]==':')) {
			title = NULL;
			if (strlen(sL)==8) {
				sscanf(sL, "%02d:%02d:%02d", &h, &m, &s);
				ts = (h*3600 + m*60+s)*1000;
			} 
			else {
				char szTS[20], *tok;
				strncpy(szTS, sL, 18);
				tok = strrchr(szTS, ' ');
				if (tok) {
					title = strchr(sL, ' ') + 1;
					while (title[0]==' ') title++;
					if (strlen(title)) strcpy(szTitle, title);
					tok[0] = 0;
				}
				ts = 0;
				h = m = s = ms = 0;

				if (sscanf(szTS, "%d:%d:%d;%d/%d", &h, &m, &s, &fr, &fps)==5) {
					ts = (h*3600 + m*60+s)*1000 + 1000*fr/fps;
				} else if (sscanf(szTS, "%d:%d:%d;%d", &h, &m, &s, &fr)==4) {
					ts = (h*3600 + m*60+s);
					if (import_fps)
						ts = (s64) (((import_fps*((s64)ts) + fr) * 1000 ) / import_fps);
					else
						ts = ((ts*25 + fr) * 1000 ) / 25;
				} else if (sscanf(szTS, "%d:%d:%d.%d", &h, &m, &s, &ms) == 4) {
					ts = (h*3600 + m*60+s)*1000+ms;
				} else if (sscanf(szTS, "%d:%d:%d.%d", &h, &m, &s, &ms) == 4) {
					ts = (h*3600 + m*60+s)*1000+ms;
				} else if (sscanf(szTS, "%d:%d:%d:%d", &h, &m, &s, &ms) == 4) {
					ts = (h*3600 + m*60+s)*1000+ms;
				} else if (sscanf(szTS, "%d:%d:%d", &h, &m, &s) == 3) {
					ts = (h*3600 + m*60+s);
				}
			}
		} 
		/*CHAPTERX= and CHAPTERXNAME=*/
		else if (!strnicmp(sL, "CHAPTER", 7)) {
			u32 idx;
			char szTemp[20], *str;
			strncpy(szTemp, sL, 19);
			str = strrchr(szTemp, '=');
			if (!str) continue;
			str[0] = 0;
			strlwr(szTemp);
			idx = cur_chap;
			str = strchr(sL, '=');
			str++;
			if (strstr(szTemp, "name")) {
				sscanf(szTemp, "chapter%dname", &idx);
				strcpy(szTitle, str);
				if (idx!=cur_chap) {
					cur_chap=idx;
					state = 0;
				}
				state++;
			} else {
				sscanf(szTemp, "chapter%d", &idx);
				if (idx!=cur_chap) {
					cur_chap=idx;
					state = 0;
				}
				state++;

				ts = 0;
				h = m = s = ms = 0;
				if (sscanf(str, "%d:%d:%d.%d", &h, &m, &s, &ms) == 4) {
					ts = (h*3600 + m*60+s)*1000+ms;
				} else if (sscanf(str, "%d:%d:%d:%d", &h, &m, &s, &ms) == 4) {
					ts = (h*3600 + m*60+s)*1000+ms;
				} else if (sscanf(str, "%d:%d:%d", &h, &m, &s) == 3) {
					ts = (h*3600 + m*60+s);
				}
			}
			if (state==2) {
				e = M4_AddChapter(file, 0, ts, szTitle);
				if (e) goto err_exit;
				state = 0;
			}
			continue;
		}
		else continue;

		if (strlen(szTitle)) {
			e = M4_AddChapter(file, 0, ts, szTitle);
		} else {
			e = M4_AddChapter(file, 0, ts, NULL);
		}
		if (e) goto err_exit;
	}


err_exit:
	fclose(f);
	return e;
}

#endif //M4_READ_ONLY

ESDescriptor *MP4T_MapESDescriptor(M4File *mp4, u32 track)
{
	u32 type;
	UnknownStreamDescription *udesc;
	BitStream *bs;
	ESDescriptor *esd;

	u32 subtype = M4_GetMediaSubType(mp4, track, 1);
	if ((subtype == M4_MPEG4_SubType) || (subtype == M4_ENC_MPEG4_SubType))
		return M4_GetStreamDescriptor(mp4, track, 1);

	if (subtype == M4_AVC_H264_SubType)
		return M4_GetStreamDescriptor(mp4, track, 1);

	if (M4_GetMediaType(mp4, track) == M4_TimedTextMediaType)
		return M4_GetStreamDescriptor(mp4, track, 1);

	if ((subtype == M4_3GP_AMR_SubType) || (subtype == M4_3GP_AMR_WB_SubType)) {
		esd = OD_NewESDescriptor(0);
		esd->slConfig->timestampResolution = M4_GetMediaTimeScale(mp4, track);
		esd->ESID = M4_GetTrackID(mp4, track);
		esd->OCRESID = esd->ESID;
		esd->decoderConfig->streamType = M4ST_AUDIO;
		/*use private DSI*/
		esd->decoderConfig->objectTypeIndication = GPAC_QT_CODECS_OTI;
		bs = NewBitStream(NULL, 0, BS_WRITE);
		/*format ext*/
		BS_WriteU32(bs, subtype);
		/*ignore the rest*/
		BS_WriteInt(bs, subtype, 5*8);
		BS_GetContent(bs, (unsigned char **) &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
		DeleteBitStream(bs);
		return esd;
	}

	if (subtype == M4_3GP_H263_SubType) {
		u32 w, h;
		esd = OD_NewESDescriptor(0);
		esd->slConfig->timestampResolution = M4_GetMediaTimeScale(mp4, track);
		esd->ESID = M4_GetTrackID(mp4, track);
		esd->OCRESID = esd->ESID;
		esd->decoderConfig->streamType = M4ST_VISUAL;
		/*use private DSI*/
		esd->decoderConfig->objectTypeIndication = GPAC_QT_CODECS_OTI;
		bs = NewBitStream(NULL, 0, BS_WRITE);
		/*format ext*/
		BS_WriteU32(bs, FOUR_CHAR_INT('h', '2', '6', '3'));
		M4_GetVisualEntrySize(mp4, track, 1, &w, &h);
		BS_WriteU16(bs, w);
		BS_WriteU16(bs, h);
		BS_GetContent(bs, (unsigned char **) &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
		DeleteBitStream(bs);
		return esd;
	}

	type = M4_GetMediaType(mp4, track);
	if ((type != M4_AudioMediaType) && (type != M4_VisualMediaType)) return NULL;

	esd = OD_NewESDescriptor(0);
	esd->OCRESID = esd->ESID = M4_GetTrackID(mp4, track);
	esd->slConfig->useTimestampsFlag = 1;
	esd->slConfig->timestampResolution = M4_GetMediaTimeScale(mp4, track);
	esd->decoderConfig->objectTypeIndication = GPAC_QT_CODECS_OTI;
	/*format ext*/
	bs = NewBitStream(NULL, 0, BS_WRITE);
	BS_WriteU32(bs, subtype);
	udesc = M4_GetUnknownStreamDescription(mp4, track, 1);
	if (type==M4_AudioMediaType) {
		esd->decoderConfig->streamType = M4ST_AUDIO;
		BS_WriteU16(bs, udesc->SampleRate);
		BS_WriteU8(bs, udesc->NumChannels);
		BS_WriteU8(bs, udesc->bitsPerSample);
		BS_WriteU8(bs, 0);
	} else {
		esd->decoderConfig->streamType = M4ST_VISUAL;
		BS_WriteU16(bs, udesc->width);
		BS_WriteU16(bs, udesc->height);
	}
	if (udesc && udesc->extension_buf_size) {
		BS_WriteData(bs, udesc->extension_buf, udesc->extension_buf_size);
		free(udesc->extension_buf);
	}
	if (udesc) free(udesc);
	BS_GetContent(bs, (unsigned char **) &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
	DeleteBitStream(bs);
	return esd;
}
