/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005 
 *					All rights reserved
 *
 *  This file is part of GPAC / ISO Media File Format sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU 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_isomedia_dev.h>

//the only static var. Used to store any error happening while opening a movie
static M4Err MP4_API_IO_Err;

void M4SetLastError(M4File *movie, M4Err error)
{
	if (!movie) {
		MP4_API_IO_Err = error;
	} else {
		movie->LastError = error;
	}
}


M4Err M4_GetLastError(M4File *the_file)
{
	if (!the_file) return MP4_API_IO_Err;
	return the_file->LastError;
}

u8 M4_GetOpenMode(M4File *the_file)
{
	if (!the_file) return 0;
	return the_file->openMode;
}


/**************************************************************
					Sample Manip
**************************************************************/

//creates a new empty sample
M4Sample *M4_NewSample()
{
	M4Sample *tmp = (M4Sample *) malloc(sizeof(M4Sample));
	if (!tmp) return NULL;
	tmp->CTS_Offset = tmp->DTS = 0;
	tmp->dataLength = 0;
	tmp->IsRAP = 0;
	tmp->data = NULL;
	return tmp;
}

//creates a new formatted sample
M4Sample *M4_NewFullSample(char *data, u32 dataLength, u32 DTS,	u32 CTS_Offset, u8 IsRAP)
{
	M4Sample *samp = M4_NewSample();
	if (!samp) return NULL;
	samp->data = data;
	samp->dataLength = dataLength;
	samp->CTS_Offset = CTS_Offset;
	samp->DTS = DTS;
	samp->IsRAP = IsRAP;
	return samp;
}

//delete a sample
void M4_DeleteSample(M4Sample **samp)
{
	if (! *samp) return;
	if ((*samp)->data && (*samp)->dataLength) free((*samp)->data);
	free(*samp);
	*samp = NULL;
}

Bool M4_ProbeIsoMediaFile(const char *fileName)
{
	unsigned char data[4];
	u32 type;
	FILE *f = fopen(fileName, "rb");
	if (!f) return 0;
	type = 0;
	if (fread(data, 1, 4, f) == 4) {
		if (fread(data, 1, 4, f) == 4) {
			type = FOUR_CHAR_INT(data[0], data[1], data[2], data[3]);
		}
	}
	fclose(f);
	switch (type) {
	case MovieAtomType:
	case MediaDataAtomType:
	case FileTypeAtomType:
	case FreeSpaceAtomType:
	case SkipAtomType:
	case UserDataAtomType:
	case MetaAtomType:
	case VoidAtomType:
	case FOUR_CHAR_INT('w','i','d','e'):
		return 1;
	default:
		return 0;
	}
}

/**************************************************************
					File Opening in streaming mode
			the file map is regular (through FILE handles)
**************************************************************/
M4Err M4_MovieOpenEx(const char *fileName, M4File **the_file, u64 *BytesMissing)
{
	M4Err e;
	M4File *movie;

	*BytesMissing = 0;
	*the_file = NULL;

	movie = M4NewMovie();
	if (!movie) return M4OutOfMem;

	movie->fileName = strdup(fileName);
	movie->openMode = M4_OPEN_READ;
	//do NOT use FileMapping on incomplete files
	e = DataMap_New(fileName, NULL, DM_MODE_R, &movie->movieFileMap);
	if (e) {
		DelMovie(movie);
		return e;
	}

#ifndef M4_READ_ONLY
	movie->editFileMap = NULL;
	movie->finalName = NULL;
#endif	//M4_READ_ONLY

	e = ParseMovieAtoms(movie, BytesMissing);
	if (e == M4UncompleteFile) {
		//if we have a moov, we're fine
		if (movie->moov) {
			*the_file = (M4File *)movie;
			return M4OK;
		}
		//if not, delete the movie
		DelMovie(movie);
		return e;
	} else if (e) {
		//if not, delete the movie
		DelMovie(movie);
		return e;
	}
	//OK, let's return
	*the_file = (M4File *)movie;
	return M4OK;
}

/**************************************************************
					File Reading
**************************************************************/

M4File *M4_MovieOpen(const char *fileName, u8 OpenMode)
{
	M4File *movie;
	MP4_API_IO_Err = M4OK;
	
	switch (OpenMode) {
	case M4_OPEN_READ_DUMP:
	case M4_OPEN_READ:
		movie = MovieOpen(fileName, OpenMode);
		break;

#ifndef M4_READ_ONLY
	
	case M4_OPEN_WRITE:
		movie = MovieCreate(fileName, OpenMode);
		break;
	case M4_OPEN_EDIT:
		movie = MovieOpen(fileName, OpenMode);
		break;
	case M4_WRITE_EDIT:
		OpenMode = M4_OPEN_EDIT;
		movie = MovieCreate(fileName, OpenMode);
		break;

#endif	//M4_READ_ONLY
		
	default:
		return NULL;
	}
	return (M4File *) movie;
}

M4Err M4_MovieCloseEx(M4File *movie, void (*progress)(void *cbk, u32 done, u32 total), void *cbck)
{
	M4Err e;
	if (movie == NULL) return M4InvalidMP4File;
	e = M4OK;

#ifndef M4_READ_ONLY
	
	//write our movie to the file
	if (movie->openMode != M4_OPEN_READ) {
		M4_GetDuration(movie);
#ifndef	M4_ISO_NO_FRAGMENTS
		//movie fragment mode, just store the fragment
		if ( (movie->openMode == M4_OPEN_WRITE) && (movie->FragmentsFlags & FRAG_WRITE_READY) ) {
			e = StoreFragment(movie);
		} else 
#endif
			e = WriteToFile(movie, progress, cbck);
	}
	
#endif	//M4_READ_ONLY

	//free and return;
	DelMovie(movie);
	return e;
}

M4Err M4_MovieClose(M4File *the_file)
{
	return M4_MovieCloseEx(the_file, NULL, NULL);
}

Bool M4_HasRootOD(M4File *movie)
{
	if (!movie || !movie->moov || !movie->moov->iods || !movie->moov->iods->descriptor) return 0;
	return 1;
}

//this funct is used for exchange files, where the iods contains an OD
Descriptor *M4_GetRootOD(M4File *movie)
{
	Descriptor *desc;
	ObjectDescriptor *od;
	InitialObjectDescriptor *iod;
	M4F_ObjectDescriptor *m4_od;
	M4F_InitialObjectDescriptor *m4_iod;
	ESDescriptor *esd;
	ES_ID_Inc *inc;
	u32 i;
	u8 useIOD;

	if (!movie || !movie->moov) return NULL;
	if (!movie->moov->iods) return NULL;

	od = NULL;
	iod = NULL;

	switch (movie->moov->iods->descriptor->tag) {
	case MP4_OD_Tag:
		od = (ObjectDescriptor*)malloc(sizeof(ObjectDescriptor));
		memset(od, 0, sizeof(ObjectDescriptor));
		od->ESDescriptors = NewChain();
		useIOD = 0;
		break;
	case MP4_IOD_Tag:
		iod = (InitialObjectDescriptor*)malloc(sizeof(InitialObjectDescriptor));
		memset(iod, 0, sizeof(InitialObjectDescriptor));
		iod->ESDescriptors = NewChain();
		useIOD = 1;
		break;
	default:
		return NULL;
	}
	
	//duplicate our descriptor
	movie->LastError = OD_DuplicateDescriptor((Descriptor *) movie->moov->iods->descriptor, &desc);
	if (movie->LastError) return NULL;

	if (!useIOD) {
		m4_od = (M4F_ObjectDescriptor *)desc;
		od->objectDescriptorID = m4_od->objectDescriptorID;
		od->extensionDescriptors = m4_od->extensionDescriptors;
		m4_od->extensionDescriptors = NULL;
		od->IPMP_Descriptors = m4_od->IPMP_Descriptors;
		m4_od->IPMP_Descriptors = NULL;
		od->OCIDescriptors = m4_od->OCIDescriptors;
		m4_od->OCIDescriptors = NULL;
		od->URLString = m4_od->URLString;
		m4_od->URLString = NULL;
		od->tag = ObjectDescriptor_Tag;
		//then recreate the desc in Inc
		for (i = 0; i < ChainGetCount(m4_od->ES_ID_IncDescriptors); i++) {
			inc = (ES_ID_Inc*)ChainGetEntry(m4_od->ES_ID_IncDescriptors, i);
			movie->LastError = GetESDForTime(movie->moov, inc->trackID, 0, &esd);
			if (!movie->LastError) movie->LastError = ChainAddEntry(od->ESDescriptors, esd);
			if (movie->LastError) {
				OD_DeleteDescriptor(&desc);
				OD_DeleteDescriptor((Descriptor **) &od);
				return NULL;
			}
		}
		OD_DeleteDescriptor(&desc);
		return (Descriptor *)od;
	} else {
		m4_iod = (M4F_InitialObjectDescriptor *)desc;
		iod->objectDescriptorID = m4_iod->objectDescriptorID;
		iod->extensionDescriptors = m4_iod->extensionDescriptors;
		m4_iod->extensionDescriptors = NULL;
		iod->IPMP_Descriptors = m4_iod->IPMP_Descriptors;
		m4_iod->IPMP_Descriptors = NULL;
		iod->OCIDescriptors = m4_iod->OCIDescriptors;
		m4_iod->OCIDescriptors = NULL;
		iod->URLString = m4_iod->URLString;
		m4_iod->URLString = NULL;
		iod->tag = InitialObjectDescriptor_Tag;

		iod->audio_profileAndLevel = m4_iod->audio_profileAndLevel;
		iod->graphics_profileAndLevel = m4_iod->graphics_profileAndLevel;
		iod->inlineProfileFlag = m4_iod->inlineProfileFlag;
		iod->OD_profileAndLevel = m4_iod->OD_profileAndLevel;
		iod->scene_profileAndLevel = m4_iod->scene_profileAndLevel;
		iod->visual_profileAndLevel = m4_iod->visual_profileAndLevel;
		iod->IPMPToolList = m4_iod->IPMPToolList;
		m4_iod->IPMPToolList = NULL;

		//then recreate the desc in Inc
		for (i = 0; i < ChainGetCount(m4_iod->ES_ID_IncDescriptors); i++) {
			inc = (ES_ID_Inc*)ChainGetEntry(m4_iod->ES_ID_IncDescriptors, i);
			movie->LastError = GetESDForTime(movie->moov, inc->trackID, 0, &esd);
			if (!movie->LastError) movie->LastError = ChainAddEntry(iod->ESDescriptors, esd);
			if (movie->LastError) {
				OD_DeleteDescriptor(&desc);
				OD_DeleteDescriptor((Descriptor **) &iod);
				return NULL;
			}
		}
		OD_DeleteDescriptor(&desc);
		return (Descriptor *)iod;
	}
}


u32 M4_GetTrackCount(M4File *movie)
{
	if (!movie || !movie->moov) return 0;

	if (! movie->moov->trackList) {
		movie->LastError = M4InvalidMP4File;
		return 0;
	}
	return ChainGetCount(movie->moov->trackList);
}


u32 M4_GetTrackID(M4File *movie, u32 trackNumber)
{
	u32 trackID;
	if (!movie || !movie->moov) return 0;

	movie->LastError = GetTrackId(movie->moov, trackNumber, &trackID);
	if (movie->LastError) return 0;
	return trackID;
}


u32 M4_GetTrackByID(M4File *the_file, u32 trackID)
{
	TrackAtom *trak;
	u32 count;
	u32 i;
	if (the_file == NULL) return 0;
	
	count = M4_GetTrackCount(the_file);
	if (!count) return 0;
	for (i = 0; i < count; i++) {
		trak = GetTrackFromFile(the_file, i+1);
		if (!trak) return 0;
		if (trak->Header->trackID == trackID) return i+1;
	}
	return 0;
}

//return the timescale of the movie, 0 if error
Bool M4_HasMovie(M4File *file)
{
	if (file && file->moov) return 1;
	return 0;
}

//return the timescale of the movie, 0 if error
u32 M4_GetTimeScale(M4File *movie)
{
	if (!movie || !movie->moov) return 0;
	return movie->moov->mvhd->timeScale;
}

//return the duration of the movie, 0 if error
u64 M4_GetDuration(M4File *movie)
{
#ifndef M4_READ_ONLY
	u32 i;
	u64 maxDur;
	TrackAtom *trak;
#endif

	if (!movie || !movie->moov) return 0;

	//if file was open in Write or Edit mode, recompute the duration
	//the duration of a movie is the MaxDuration of all the tracks...

#ifndef M4_READ_ONLY

	if (movie->openMode != M4_OPEN_READ) {
		maxDur = 0;
		for (i = 0; i< ChainGetCount(movie->moov->trackList); i++) {
			trak = (TrackAtom*)ChainGetEntry(movie->moov->trackList, i);
			if( (movie->LastError = SetTrackDuration(trak))	) return 0;
			if (trak->Header->duration > maxDur)
				maxDur = trak->Header->duration;
		}
		movie->moov->mvhd->duration = maxDur;
	}

#endif	//M4_READ_ONLY

	return movie->moov->mvhd->duration;
}

//return the creation info of the movie
M4Err M4_GetCreationInfo(M4File *movie, u64 *creationTime, u64 *modificationTime)
{
	if (!movie || !movie->moov) return M4BadParam;
	
	*creationTime = movie->moov->mvhd->creationTime;
	*modificationTime = movie->moov->mvhd->modificationTime;
	return M4OK;
}


//check the presence of a track in IOD. 0: NO, 1: YES, 2: ERROR
u8 M4_IsTrackInRootOD(M4File *movie, u32 trackNumber)
{
	u32 i;
	u32 trackID;
	Descriptor *desc;
	ES_ID_Inc *inc;
	Chain *inc_list;
	if (!movie) return 2;
	if (!movie->moov || !movie->moov->iods) return 0;

	desc = movie->moov->iods->descriptor;
	switch (desc->tag) {
	case MP4_IOD_Tag:
		inc_list = ((M4F_InitialObjectDescriptor *)desc)->ES_ID_IncDescriptors;
		break;
	case MP4_OD_Tag:
		inc_list = ((M4F_ObjectDescriptor *)desc)->ES_ID_IncDescriptors;
		break;
		//files without IOD are possible !
	default:
		return 0;
	}
	if ((movie->LastError = GetTrackId(movie->moov, trackNumber, &trackID))) return 2;
	for (i = 0; i < ChainGetCount(inc_list); i++) {
		inc = (ES_ID_Inc*)ChainGetEntry(inc_list, i);
		if (inc->trackID == trackID) return 1;
	}
	return 0;
}



//gets the enable flag of a track
//0: NO, 1: yes, 2: error
u8 M4_IsTrackEnabled(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);

	if (!trak) return 2;
	return (trak->Header->flags & 1) ? 1 : 0;
}


//get the track duration
//return 0 if bad param
u64 M4_GetTrackDuration(M4File *movie, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return 0;

#ifndef M4_READ_ONLY
	/*in all modes except dump recompute duration in case headers are wrong*/
	if (movie->openMode != M4_OPEN_READ_DUMP) {
		SetTrackDuration(trak);
	}
#endif
	return trak->Header->duration;
}

M4Err M4_GetMediaLanguage(M4File *the_file, u32 trackNumber, char *three_char_code)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;
	memcpy(three_char_code, trak->Media->mediaHeader->packedLanguage, sizeof(char)*4);
	return M4OK;
}


//Return the number of track references of a track for a given ReferenceType
//return -1 if error
s32 M4_GetTrackReferenceCount(M4File *movie, u32 trackNumber, u32 referenceType)
{
	TrackAtom *trak;
	TrackReferenceTypeAtom *dpnd;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return -1;
	if (!trak->References) return 0;
	if (movie->openMode == M4_OPEN_WRITE) {
		movie->LastError = M4InvalidMP4Mode;
		return -1;
	}
	
	dpnd = NULL;
	if ( (movie->LastError = Track_FindRef(trak, referenceType, &dpnd)) ) return -1;
	if (!dpnd) return 0;
	return dpnd->trackIDCount;
}


//Return the referenced track number for a track and a given ReferenceType and Index
//return -1 if error, 0 if the reference is a NULL one, or the trackNumber
M4Err M4_GetTrackReference(M4File *movie, u32 trackNumber, u32 referenceType, u32 referenceIndex, u32 *refTrack)
{
	M4Err e;
	TrackAtom *trak;
	TrackReferenceTypeAtom *dpnd;
	u32 refTrackNum;
	trak = GetTrackFromFile(movie, trackNumber);

	*refTrack = 0;
	if (!trak || !trak->References) return M4BadParam;
	
	dpnd = NULL;
	e = Track_FindRef(trak, referenceType, &dpnd);
	if (e) return e;
	if (!dpnd) return M4BadParam;

	if (referenceIndex > dpnd->trackIDCount) return M4BadParam;

	//the spec allows a NULL reference 
	//(ex, to force desync of a track, set a sync ref with ID = 0)
	if (dpnd->trackIDs[referenceIndex - 1] == 0) return M4OK;
	refTrackNum = GetTrackNumberByID(movie->moov, dpnd->trackIDs[referenceIndex-1]);

	//if the track was not found, this means the file is broken !!!
	if (! refTrackNum) return M4InvalidMP4File;
	*refTrack = refTrackNum;
	return M4OK;
}


//Return the media time given the absolute time in the Movie
M4Err M4_GetMediaTime(M4File *the_file, u32 trackNumber, u32 movieTime, u64 *MediaTime)
{
	TrackAtom *trak;
	u8 useEdit;
	s64 SegmentStartTime, mediaOffset;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !MediaTime) return M4BadParam;;

	SegmentStartTime = 0;
	return GetMediaTime(trak, movieTime, MediaTime, &SegmentStartTime, &mediaOffset, &useEdit);
}


//Get the stream description index (eg, the ESD) for a given time IN MEDIA TIMESCALE
//return 0 if error or if empty
u32 M4_GetStreamDescriptionIndex(M4File *movie, u32 trackNumber, u64 for_time)
{
	u32 streamDescIndex;
	TrackAtom *trak;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return 0;

	if ( (movie->LastError = Media_GetSampleDescIndex(trak->Media, for_time, &streamDescIndex)) ) {
		return 0;
	}
	return streamDescIndex;
}

//Get the number of "streams" stored in the media - a media can have several stream descriptions...
u32 M4_GetStreamDescriptionCount(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	return ChainGetCount(trak->Media->information->sampleTable->SampleDescription->atomList);
}


//Get the ESDescriptor given the StreamDescriptionIndex
//THE DESCRIPTOR IS DUPLICATED, SO HAS TO BE DELETED BY THE APP
ESDescriptor *M4_GetStreamDescriptor(M4File *movie, u32 trackNumber, u32 StreamDescriptionIndex)
{
	ESDescriptor *outESD;
	M4Err e;
	e = GetESD(movie->moov, M4_GetTrackID(movie, trackNumber), StreamDescriptionIndex, &outESD);
	if (e && (e!= M4InvalidMP4Media)) {
		movie->LastError = e;
		return NULL;
	}
	return outESD;
}

//Get the decoderConfigDescriptor given the SampleDescriptionIndex
//THE DESCRIPTOR IS DUPLICATED, SO HAS TO BE DELETED BY THE APP
DecoderConfigDescriptor *M4_GetDecoderInformation(M4File *the_file, u32 trackNumber, u32 StreamDescriptionIndex)
{
	TrackAtom *trak;
	ESDescriptor *esd;
	Descriptor *decInfo;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return NULL;
	//get the ESD (possibly emulated)
	Media_GetESD(trak->Media, StreamDescriptionIndex, &esd, 0);
	if (!esd) return NULL;
	decInfo = (Descriptor *) esd->decoderConfig;
	esd->decoderConfig = NULL;
	OD_DeleteDescriptor((Descriptor **) &esd);
	return (DecoderConfigDescriptor *)decInfo;
}


//get the media duration (without edit)
//return 0 if bad param
u64 M4_GetMediaDuration(M4File *movie, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return 0;


#ifndef M4_READ_ONLY

	/*except in dump mode always recompute the duration*/
	if (movie->openMode != M4_OPEN_READ_DUMP) {
		if ( (movie->LastError = Media_SetDuration(trak)) ) return 0;
	}

#endif

	return trak->Media->mediaHeader->duration;
}

//Get the timeScale of the media. All samples DTS/CTS are expressed in this timeScale
u32 M4_GetMediaTimeScale(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;
	return trak->Media->mediaHeader->timeScale;
}

#if 0

//not exported now...
M4Err M4_GetMediaLanguage(M4File *the_file, u32 trackNumber, char **outLanguageCode)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	if (*outLanguageCode) return M4BadParam;
	(*outLanguageCode) = malloc(sizeof(char) * 4);
	strcpy(*outLanguageCode, (const char *) trak->Media->mediaHeader->packedLanguage);
	return M4OK;
}

#endif


u32 M4_GetCopyrightCount(M4File *mov)
{
	UserDataMap *map;
	UserDataMap *udta_getEntry(UserDataAtom *ptr, u32 atomType, bin128 UUID);
	if (!mov || !mov->moov || !mov->moov->udta) return 0;
	map = udta_getEntry(mov->moov->udta, CopyrightAtomType, NULL);
	if (!map) return 0;
	return ChainGetCount(map->atomList);
}

M4Err M4_GetCopyright(M4File *mov, u32 Index, const char **threeCharCode, const char **notice)
{
	UserDataMap *map;
	CopyrightAtom *cprt;
	UserDataMap *udta_getEntry(UserDataAtom *ptr, u32 atomType, bin128 UUID);
	
	if (!mov || !mov->moov || !Index) return M4BadParam;

	if (!mov->moov->udta) return M4OK;
	map = udta_getEntry(mov->moov->udta, CopyrightAtomType, NULL);
	if (!map) return M4OK;

	if (Index > ChainGetCount(map->atomList)) return M4BadParam;

	cprt = (CopyrightAtom*)ChainGetEntry(map->atomList, Index-1);
	(*threeCharCode) = cprt->packedLanguageCode;
	(*notice) = cprt->notice;
	return M4OK;
}

M4Err M4_GetWatermark(M4File *mov, bin128 UUID, u8** data, u32* length)
{
	UserDataMap *map;
	WatermarkAtom *wm;
	UserDataMap *udta_getEntry(UserDataAtom *ptr, u32 atomType, bin128 UUID);

	if (!mov) return M4BadParam;
	if (!mov->moov || !mov->moov->udta) return M4NotSupported;
	
	map = udta_getEntry(mov->moov->udta, ExtendedAtomType, UUID);
	if (!map) return M4NotSupported;

	wm = (WatermarkAtom*)ChainGetEntry(map->atomList, 0);
	if (!wm) return M4NotSupported;

	*data = malloc(wm->dataSize);
	memcpy(*data, wm->data, wm->dataSize);
	*length = wm->dataSize;
	return M4OK;
}

u32 M4_GetChapterCount(M4File *movie, u32 trackNumber)
{
	UserDataMap *map;
	ChapterListAtom *lst;
	UserDataAtom *udta;
	UserDataMap *udta_getEntry(UserDataAtom *ptr, u32 atomType, bin128 UUID);

	if (!movie || !movie->moov) return 0;

	udta = NULL;
	if (trackNumber) {
		TrackAtom *trak = GetTrackFromFile(movie, trackNumber);
		if (!trak) return 0;
		udta = trak->udta;
	} else {
		udta = movie->moov->udta;
	}
	if (!udta) return 0;
	map = udta_getEntry(udta, ChapterListAtomType, NULL);
	if (!map) return 0;
	lst = ChainGetEntry(map->atomList, 0);
	if (!lst) return 0;
	return ChainGetCount(lst->list);
}

M4Err M4_GetChapter(M4File *movie, u32 trackNumber, u32 Index, u64 *chapter_time, const char **name)
{
	UserDataMap *map;
	ChapterListAtom *lst;
	ChapterEntry *ce;
	UserDataAtom *udta;
	UserDataMap *udta_getEntry(UserDataAtom *ptr, u32 atomType, bin128 UUID);

	if (!movie || !movie->moov) return M4BadParam;

	udta = NULL;
	if (trackNumber) {
		TrackAtom *trak = GetTrackFromFile(movie, trackNumber);
		if (!trak) return M4BadParam;
		udta = trak->udta;
	} else {
		udta = movie->moov->udta;
	}
	if (!udta) return M4BadParam;
	map = udta_getEntry(movie->moov->udta, ChapterListAtomType, NULL);
	if (!map) return M4BadParam;
	lst = ChainGetEntry(map->atomList, 0);
	if (!lst) return M4BadParam;
	
	ce = ChainGetEntry(lst->list, Index-1);
	if (!ce) return M4BadParam;
	if (chapter_time) {
		*chapter_time = ce->start_time;
		*chapter_time /= 10000L;
	}
	if (name) *name = ce->name;
	return M4OK;
}


u32 M4_GetMediaType(M4File *movie, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return M4BadParam;
	return trak->Media->handler->handlerType;
}

Bool IsMP4Description(u32 entryType)
{
	switch (entryType) {
	case MPEGSampleEntryAtomType:
	case MPEGAudioSampleEntryAtomType:
	case MPEGVisualSampleEntryAtomType:
	case MPEGEncAudioSampleEntryAtomType:
	case MPEGEncVisualSampleEntryAtomType:
	case MPEGEncSystemsSampleEntryAtomType:
		return 1;
	default:
		return 0;
	}
}

Bool IsMP4EncryptedDescription(u32 entryType)
{
	switch (entryType) {
	case MPEGEncAudioSampleEntryAtomType:
	case MPEGEncVisualSampleEntryAtomType:
	case MPEGEncSystemsSampleEntryAtomType:
		return 1;
	default:
		return 0;
	}
}

u8 M4_IsTrackEncrypted(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	Atom *entry;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 2;
	entry = ChainGetEntry(trak->Media->information->sampleTable->SampleDescription->atomList, 0);
	if (!entry) return 2;
	return IsMP4EncryptedDescription(entry->type);
}

u32 M4_GetMediaSubType(M4File *the_file, u32 trackNumber, u32 DescriptionIndex)
{
	TrackAtom *trak;
	Atom *entry;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !DescriptionIndex) return 0;
	entry = ChainGetEntry(trak->Media->information->sampleTable->SampleDescription->atomList, DescriptionIndex-1);
	if (!entry) return 0;

	//filter MPEG sub-types
	if (IsMP4Description(entry->type)) {
		if (IsMP4EncryptedDescription(entry->type)) return M4_ENC_MPEG4_SubType;
		else return M4_MPEG4_SubType;
	}
	if (entry->type == GenericVisualSampleEntryAtomType) {
		return ((GenericVisualSampleEntryAtom *)entry)->EntryType;
	}
	else if (entry->type == GenericAudioSampleEntryAtomType) {
		return ((GenericAudioSampleEntryAtom *)entry)->EntryType;
	}
	else if (entry->type == GenericMediaSampleEntryAtomType) {
		return ((GenericMediaSampleEntryAtom *)entry)->EntryType;
	}
	return entry->type;
}

u32 M4_GetMPEG4SubType(M4File *the_file, u32 trackNumber, u32 DescriptionIndex)
{
	TrackAtom *trak;
	Atom *entry;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !DescriptionIndex) return 0;
	entry = ChainGetEntry(trak->Media->information->sampleTable->SampleDescription->atomList, DescriptionIndex-1);
	if (!entry) return 0;

	//filter MPEG sub-types
	if (!IsMP4Description(entry->type)) return 0;
	return entry->type;
}

//Get the HandlerDescription name. 
M4Err M4_GetHandlerName(M4File *the_file, u32 trackNumber, const char **outName)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !outName) return M4BadParam;

	*outName = trak->Media->handler->nameUTF8;
	return M4OK;
}

//Check the DataReferences of this track
M4Err M4_CheckStreamDataReference(M4File *the_file, u32 trackNumber, u32 StreamDescriptionIndex)
{
	M4Err e;
	u32 drefIndex;
	TrackAtom *trak;

	if (!StreamDescriptionIndex || !trackNumber) return M4BadParam;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;

	e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex , NULL, &drefIndex);
	if (e) return e;
	if (!drefIndex) return M4BadParam;
	return Media_CheckDataEntry(trak->Media, drefIndex);
}

//get the location of the data. If URL && URN are NULL, the data is in this file
M4Err M4_GetStreamDataReference(M4File *the_file, u32 trackNumber, u32 StreamDescriptionIndex, const char **outURL, const char **outURN)
{
	TrackAtom *trak;
	DataEntryURLAtom *url;
	DataEntryURNAtom *urn;
	u32 drefIndex;
	M4Err e;

	if (!StreamDescriptionIndex || !trackNumber) return M4BadParam;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;

	e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex , NULL, &drefIndex);
	if (e) return e;
	if (!drefIndex) return M4BadParam;

	url = (DataEntryURLAtom*)ChainGetEntry(trak->Media->information->dataInformation->dref->atomList, drefIndex - 1);
	if (!url) return M4InvalidMP4File;

	*outURL = *outURN = NULL;
	if (url->type == DataEntryURLAtomType) {
		*outURL = url->location;
		*outURN = NULL;
	} else if (url->type == DataEntryURNAtomType) {
		urn = (DataEntryURNAtom *) url;
		*outURN = urn->nameURN;
		*outURL = urn->location;
	} else {
		*outURN = NULL;
		*outURL = NULL;
	}
	return M4OK;
}

//Get the number of samples
//return 0 if error or empty
u32 M4_GetSampleCount(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;
	return trak->Media->information->sampleTable->SampleSize->sampleCount;
}

u32 M4_GetConstantSampleSize(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;
	return trak->Media->information->sampleTable->SampleSize->sampleSize;
}

Bool M4_HasTimeOffsets(M4File *the_file, u32 trackNumber)
{
	u32 i;
	CompositionOffsetAtom *ctts;
	dttsEntry *pe;
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !trak->Media->information->sampleTable->CompositionOffset) return 0;

	//return true at the first offset found
	ctts = trak->Media->information->sampleTable->CompositionOffset;
	for (i=0; i<ChainGetCount(ctts->entryList); i++) {
		pe = ChainGetEntry(ctts->entryList, i);
		if (pe->decodingOffset && pe->sampleCount) return 1;
	}
	return 0;
}

//return a sample give its number, and set the SampleDescIndex of this sample
//this index allows to retrieve the stream description if needed (2 media in 1 track)
//return NULL if error
M4Sample *M4_GetSample(M4File *the_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex)
{
	M4Err e;
	u32 descIndex;
	TrackAtom *trak;
	M4Sample *samp;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return NULL;

	if (!sampleNumber) return NULL;
	samp = M4_NewSample();
	if (!samp) return NULL;
	e = Media_GetSample(trak->Media, sampleNumber, &samp, &descIndex, 0, NULL);
	if (e) {
		M4SetLastError(the_file, e);
		M4_DeleteSample(&samp);
		return NULL;
	}
	if (sampleDescriptionIndex) *sampleDescriptionIndex = descIndex;
	
	return samp;
}

u32 M4_GetSampleDuration(M4File *the_file, u32 trackNumber, u32 sampleNumber)
{
	u32 dur;
	u32 dts;
	TrackAtom *trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !sampleNumber) return 0;

	stbl_GetSampleDTS(trak->Media->information->sampleTable->TimeToSample, sampleNumber, &dur);
	if (sampleNumber == trak->Media->information->sampleTable->SampleSize->sampleCount) {
		return (u32) trak->Media->mediaHeader->duration - dur;
	} 
	
	stbl_GetSampleDTS(trak->Media->information->sampleTable->TimeToSample, sampleNumber+1, &dts);
	return (dts - dur);
}

//same as M4_GetSample but doesn't fetch media data
M4Sample *M4_GetSampleInfo(M4File *the_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, u64 *data_offset)
{
	M4Err e;
	TrackAtom *trak;
	M4Sample *samp;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return NULL;

	if (!sampleNumber) return NULL;
	samp = M4_NewSample();
	if (!samp) return NULL;
	e = Media_GetSample(trak->Media, sampleNumber, &samp, sampleDescriptionIndex, 1, data_offset);
	if (e) {
		M4SetLastError(the_file, e);
		M4_DeleteSample(&samp);
		return NULL;
	}
	return samp;
}

//same as M4_GetSample but doesn't fetch media data
u32 M4_GetSampleDTS(M4File *the_file, u32 trackNumber, u32 sampleNumber)
{
	u32 dts;
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	if (!sampleNumber) return 0;
	if (stbl_GetSampleDTS(trak->Media->information->sampleTable->TimeToSample, sampleNumber, &dts) != M4OK) return 0;
	return dts;
}


Bool M4_IsSelfContained(M4File *the_file, u32 trackNumber, u32 sampleDescriptionIndex)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;
	return Media_IsSelfContained(trak->Media, sampleDescriptionIndex);
}


//return a sample given a desired display time IN MEDIA TIME SCALE
//and set the StreamDescIndex of this sample
//this index allows to retrieve the stream description if needed (2 media in 1 track)
//return NULL if error
//WARNING: the sample may not be sync even though the sync was requested (depends on the media)
M4Err M4_GetSampleForTime(M4File *the_file, u32 trackNumber, u32 desiredTime, u32 *StreamDescriptionIndex, u8 SearchMode, M4Sample **sample, u32 *SampleNum)
{
	M4Err e;
	u32 sampleNumber, prevSampleNumber, syncNum, shadowSync;
	TrackAtom *trak;
	M4Sample *shadow;
	SampleTableAtom *stbl;
	u8 useShadow, IsSync;

	if (!sample) return M4BadParam;

	if (SampleNum) *SampleNum = 0;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;

	stbl = trak->Media->information->sampleTable;
	
	e = findEntryForTime(stbl, desiredTime, 1, &sampleNumber, &prevSampleNumber);
	if (e) return e;

	//if no shadow table, reset to sync only
	useShadow = 0;
	if (!stbl->ShadowSync && (SearchMode == M4_SearchSyncShadow))
		SearchMode = M4_SearchSyncBackward;

	//if no syncTable, disable syncSearching, as all samples ARE sync
	if (! trak->Media->information->sampleTable->SyncSample) {
		if (SearchMode == M4_SearchSyncForward) SearchMode = M4_SearchForward;
		if (SearchMode == M4_SearchSyncBackward) SearchMode = M4_SearchBackward;
	}

	//not found, return EOF or browse backward
	if (!sampleNumber && !prevSampleNumber) {
		if (SearchMode == M4_SearchSyncBackward || SearchMode == M4_SearchBackward) {
			sampleNumber = trak->Media->information->sampleTable->SampleSize->sampleCount;
		}
		if (!sampleNumber) return M4EOF;
	}

	//check in case we have the perfect sample
	IsSync = 0;

	//according to the direction adjust the sampleNum value
	switch (SearchMode) {
	case M4_SearchSyncForward:
		IsSync = 1;
	case M4_SearchForward:
		//not the exact one
		if (!sampleNumber) {
			if (prevSampleNumber != stbl->SampleSize->sampleCount) {
				sampleNumber = prevSampleNumber + 1;
			} else {
				sampleNumber = prevSampleNumber;
			}
		}
		break;

	//if dummy mode, reset to default browsing
	case M4_SearchSyncBackward:
		IsSync = 1;
	case M4_SearchSyncShadow:
	case M4_SearchBackward:
	default:
		//first case, not found....
		if (!sampleNumber && !prevSampleNumber) {
			sampleNumber = stbl->SampleSize->sampleCount;
		} else if (!sampleNumber) {
			sampleNumber = prevSampleNumber;
		}
		break;
	}

	//get the sync sample num
	if (IsSync) {
		//get the SyncNumber
		e = Media_FindSyncSample(trak->Media->information->sampleTable,
						sampleNumber, &syncNum, SearchMode);
		if (e) return e;
		if (syncNum) sampleNumber = syncNum;
		syncNum = 0;
	}
	//if we are in shadow mode, get the previous sync sample
	//in case we can't find a good SyncShadow
	else if (SearchMode == M4_SearchSyncShadow) {
		//get the SyncNumber
		e = Media_FindSyncSample(trak->Media->information->sampleTable,
						sampleNumber, &syncNum, M4_SearchSyncBackward);
		if (e) return e;
	}


	//OK sampleNumber is exactly the sample we need (except for shadow)
	
	*sample = M4_NewSample();
	if (*sample == NULL) return M4OutOfMem;

	//we are in shadow mode, we need to browse both SyncSample and ShadowSyncSample to get
	//the desired sample...
	if (SearchMode == M4_SearchSyncShadow) {
		//get the shadowing number
		stbl_GetSampleShadow(stbl->ShadowSync, &sampleNumber, &shadowSync);
		//now sampleNumber is the closest previous shadowed sample.
		//1- If we have a closer sync sample, use it.
		//2- if the shadowSync is 0, we don't have any shadowing, use syncNum
		if ((sampleNumber < syncNum) || (!shadowSync)) {
			sampleNumber = syncNum;
		} else {
		//otherwise, we have a better alternate sample in the shadowSync for this sample
			useShadow = 1;
		}
	}

	e = Media_GetSample(trak->Media, sampleNumber, sample, StreamDescriptionIndex, 0, NULL);
	if (e) {
		M4_DeleteSample(sample);
		return e;
	}
	//optionally get the sample number
	if (SampleNum) *SampleNum = sampleNumber;

	//in shadow mode, we only get the data of the shadowing sample !
	if (useShadow) {
		//we have to use StreamDescriptionIndex in case the sample data is in another desc
		//though this is unlikely as non optimized...
		shadow = M4_GetSample(the_file, trackNumber, shadowSync, StreamDescriptionIndex);
		//if no sample, the shadowSync is broken, return the sample
		if (!shadow) return M4OK;
		(*sample)->IsRAP = 1;
		free((*sample)->data);
		(*sample)->dataLength = shadow->dataLength;
		(*sample)->data = shadow->data;
		//set data length to 0 to keep the buffer alive...
		shadow->dataLength = 0;
		M4_DeleteSample(&shadow);
	}
	return M4OK;
}

M4Err M4_GetSampleForMovieTime(M4File *the_file, u32 trackNumber, u32 movieTime, u32 *StreamDescriptionIndex, u8 SearchMode, M4Sample **sample, u32 *sampleNumber)
{
	M4Err e;
	TrackAtom *trak;
	u64 mediaTime;
	s64 segStartTime, mediaOffset;
	u32 sampNum;
	u8 useEdit;
	M4Err GetNextMediaTime(TrackAtom *trak, u32 movieTime, u64 *OutMovieTime);
	M4Err GetPrevMediaTime(TrackAtom *trak, u32 movieTime, u64 *OutMovieTime);

	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;

	if (*sample || !sample) return M4BadParam;
	//check 0-duration tracks (BIFS and co). Check we're not searching forward
	if (!trak->Header->duration) {
		if (movieTime && ( (SearchMode == M4_SearchSyncForward) || (SearchMode == M4_SearchForward)) ) {
			*sample = NULL;
			if (sampleNumber) *sampleNumber = 0;
			*StreamDescriptionIndex = 0;
			return M4EOF;
		}
	}
	else if (movieTime * trak->moov->mvhd->timeScale > trak->Header->duration * trak->Media->mediaHeader->timeScale) {
		*sample = NULL;
		if (sampleNumber) *sampleNumber = 0;
		*StreamDescriptionIndex = 0;
		return M4EOF;
	}

	//get the media time for this movie time...
	mediaTime = segStartTime = 0;
	*StreamDescriptionIndex = 0;

	e = GetMediaTime(trak, movieTime, &mediaTime, &segStartTime, &mediaOffset, &useEdit);
	if (e) return e;

	/*here we check if we were playing or not and return no sample in normal search modes*/
	if (useEdit && mediaOffset == -1) {
		if ((SearchMode==M4_SearchForward) || (SearchMode==M4_SearchBackward)) {
			/*get next sample time in MOVIE timescale*/
			if (SearchMode==M4_SearchForward) 
				e = GetNextMediaTime(trak, movieTime, &mediaTime);
			else
				e = GetPrevMediaTime(trak, movieTime, &mediaTime);
			if (e) return e;
			return M4_GetSampleForMovieTime(the_file, trackNumber, (u32) mediaTime, StreamDescriptionIndex, M4_SearchSyncForward, sample, sampleNumber);
		}
		if (sampleNumber) *sampleNumber = 0;
		*sample = M4_NewSample();
		(*sample)->DTS = movieTime;
		return M4OK;
	}
	/*dwell edit in non-sync mode, fetch next/prev sample depending on mode.
	Otherwise return the dwell entry*/
	if (useEdit==2) {
		if ((SearchMode==M4_SearchForward) || (SearchMode==M4_SearchBackward)) {
			/*get next sample time in MOVIE timescale*/
			if (SearchMode==M4_SearchForward) 
				e = GetNextMediaTime(trak, movieTime, &mediaTime);
			else
				e = GetPrevMediaTime(trak, movieTime, &mediaTime);
			if (e) return e;
			return M4_GetSampleForMovieTime(the_file, trackNumber, (u32) mediaTime, StreamDescriptionIndex, M4_SearchSyncForward, sample, sampleNumber);
		}
	}

	//OK, we have a sample so fetch it
	e = M4_GetSampleForTime(the_file, trackNumber, (u32) mediaTime, StreamDescriptionIndex, SearchMode, sample, &sampNum);
	if (e) return e;

	//OK, now the trick: we have to rebuild the time stamps, according 
	//to the media time scale (used by SLConfig) - add the edit start time but stay in 
	//the track TS
	if (useEdit) {
		u64 _ts = segStartTime;
		_ts *= trak->Media->mediaHeader->timeScale;
		_ts /= trak->moov->mvhd->timeScale;
		(*sample)->DTS += (u32) _ts;
		/*watchout, the sample fetched mAY be before the first sample in the edit list (when seeking)*/
		if ( (*sample)->DTS > mediaOffset) {
			(*sample)->DTS -= (u32) mediaOffset;
		} else {
			(*sample)->DTS = 0;
		}
	}
	if (sampleNumber) *sampleNumber = sampNum;
	return M4OK;
}



u64 M4_GetBytesMissing(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	return trak->Media->BytesMissing;
}

M4Err M4_SetSamplesPadding(M4File *the_file, u32 trackNumber, u32 padding_bytes)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;
	trak->padding_bytes = padding_bytes;
	return M4OK;

}

//get the number of edited segment
u32 M4_GetEditSegmentCount(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	if (!trak->EditAtom || !trak->EditAtom->editList) return 0;
	return ChainGetCount(trak->EditAtom->editList->entryList);
}


//Get the desired segment information
M4Err M4_GetEditSegment(M4File *the_file, u32 trackNumber, u32 SegmentIndex, u64 *EditTime, u64 *SegmentDuration, u64 *MediaTime, u8 *EditMode)
{
	u32 i;
	u64 startTime;
	TrackAtom *trak;
	EditListAtom *elst;
	edtsEntry *ent;

	ent = NULL;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;

	if (!trak || !trak->EditAtom || 
		!trak->EditAtom->editList || 
		(SegmentIndex > ChainGetCount(trak->EditAtom->editList->entryList)) || 
		!SegmentIndex)
		return M4BadParam;

	elst = trak->EditAtom->editList;
	startTime = 0;

	for (i = 0; i < SegmentIndex; i++) {
		ent = (edtsEntry*)ChainGetEntry(elst->entryList, i);
		if (i < SegmentIndex-1) startTime += ent->segmentDuration;
	}
	*EditTime = startTime;
	*SegmentDuration = ent->segmentDuration;
	if (ent->mediaTime < 0) {
		*MediaTime = 0;
		*EditMode = M4_EDIT_EMPTY;
		return M4OK;
	}
	if (ent->mediaRate == 0) {
		*MediaTime = ent->mediaTime;
		*EditMode = M4_EDIT_DWELL;
		return M4OK;
	}
	*MediaTime = ent->mediaTime;
	*EditMode = M4_EDIT_NORMAL;
	return M4OK;
}

u8 M4_HasSyncPoints(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;

	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;
	if (trak->Media->information->sampleTable->SyncSample) {
		if (!trak->Media->information->sampleTable->SyncSample->entryCount) return 2;	
		return 1;
	}
	return 0;
}

/*returns number of sync points*/
u32 M4_GetSyncPointCount(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;
	if (trak->Media->information->sampleTable->SyncSample) {
		return trak->Media->information->sampleTable->SyncSample->entryCount;	
	}
	return 0;
}


M4Err M4_GetMovieVersionInfo(M4File *movie, u32 *brand, u32 *minorVersion, u32 *AlternateBrandsCount)
{
	if (!movie || !brand) return M4BadParam;
	if (!movie->brand) {
		*brand = 0;
		if (minorVersion) *minorVersion = 0;
		if (AlternateBrandsCount) *AlternateBrandsCount = 0;
		return M4OK;
	}

	*brand = movie->brand->majorBrand;
	if (minorVersion) *minorVersion = movie->brand->minorVersion;
	if (AlternateBrandsCount) *AlternateBrandsCount = movie->brand->altCount;
	return M4OK;
}

M4Err M4_GetMovieAlternateBrand(M4File *movie, u32 BrandIndex, u32 *brand)
{
	if (!movie || !movie->brand || !brand) return M4BadParam;
	if (BrandIndex > movie->brand->altCount || !BrandIndex) return M4BadParam;
	*brand = movie->brand->altBrand[BrandIndex-1];
	return M4OK;
}

M4Err M4_GetSamplePaddingBits(M4File *the_file, u32 trackNumber, u32 sampleNumber, u8 *NbBits)
{
	TrackAtom *trak;

	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;


	//Padding info
	return stbl_GetPaddingBits(trak->Media->information->sampleTable->PaddingBits, 
		sampleNumber, NbBits);

}


Bool M4_HasPaddingBits(M4File *the_file, u32 trackNumber)
{
	TrackAtom *trak;

	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	if (trak->Media->information->sampleTable->PaddingBits) return 1;
	return 0;
}


u32 M4_GetUserDataItemCount(M4File *movie, u32 trackNumber, u32 UserDataType, bin128 UUID)
{
	UserDataMap *map;
	TrackAtom *trak;
	UserDataAtom *udta;
	bin128 t;
	u32 i, count;

	if (!movie || !movie->moov) return 0;
	
	if (UserDataType == ExtendedAtomType) UserDataType = 0;
	memset(t, 1, 16);
	
	if (trackNumber) {
		trak = GetTrackFromFile(movie, trackNumber);
		if (!trak) return 0;
		udta = trak->udta;
	} else {
		udta = movie->moov->udta;
	}
	if (!udta) return 0;

	for (i=0; i<ChainGetCount(udta->recordList); i++) {
		map = ChainGetEntry(udta->recordList, i);
		count = ChainGetCount(map->atomList);

		if ((map->atomType == ExtendedAtomType) && !memcmp(map->uuid, UUID, 16)) return count;
		else if (map->atomType == UserDataType) return count;
	}
	return 0;
}

M4Err M4_GetUserDataItem(M4File *movie, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex, char **userData, u32 *userDataSize)
{
	UserDataMap *map;
	UnknownAtom *ptr;
	u32 i;
	bin128 t;
	TrackAtom *trak;
	UserDataAtom *udta;

	if (!movie || !movie->moov) return M4BadParam;
	
	if (trackNumber) {
		trak = GetTrackFromFile(movie, trackNumber);
		if (!trak) return M4BadParam;
		udta = trak->udta;
	} else {
		udta = movie->moov->udta;
	}
	if (!udta) return M4BadParam;

	if (UserDataType == ExtendedAtomType) UserDataType = 0;
	memset(t, 1, 16);

	if (!UserDataIndex) return M4BadParam;
	if (!userData || !userDataSize || *userData) return M4BadParam;

	for (i=0; i<ChainGetCount(udta->recordList); i++) {
		map = ChainGetEntry(udta->recordList, i);
		if ((map->atomType == ExtendedAtomType) && !memcmp(map->uuid, UUID, 16)) goto found;
		else if (map->atomType == UserDataType) goto found;
	
	}
	return M4BadParam;

found:

	if (UserDataIndex > ChainGetCount(map->atomList) ) return M4BadParam;
	ptr = ChainGetEntry(map->atomList, UserDataIndex-1);

	//ok alloc the data
	*userData = malloc(sizeof(char)*ptr->dataSize);
	if (!*userData) return M4OutOfMem;
	memcpy(*userData, ptr->data, sizeof(char)*ptr->dataSize);
	*userDataSize = ptr->dataSize;	
	return M4OK;
}

void M4_MovieDelete(M4File *movie)
{
	//free and return;
	DelMovie(movie);
}

u32 M4_GetTrackMaxChunkDuration(M4File *movie, u32 trackNumber)
{
	TrackAtom *trak;
	u32 i, sample_per_chunk, sample_dur;
	SampleToChunkAtom *stsc;
	TimeToSampleAtom *stts;
	stscEntry *sc;
	sttsEntry *ts;
	if (!movie || !trackNumber || !movie->moov) return 0;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return 0;

	stsc = trak->Media->information->sampleTable->SampleToChunk;
	stts = trak->Media->information->sampleTable->TimeToSample;

	sample_per_chunk = 0;
	for (i=0; i<ChainGetCount(stsc->entryList); i++) {
		sc = ChainGetEntry(stsc->entryList, i);
		if (sc->samplesPerChunk > sample_per_chunk) sample_per_chunk = sc->samplesPerChunk;
	}
	sample_dur = 0;
	for (i=0; i<ChainGetCount(stts->entryList); i++) {
		ts = ChainGetEntry(stts->entryList, i);
		if (ts->sampleDelta > sample_dur) sample_dur = ts->sampleDelta;
	}
	
	//rescale to ms
	i = 1000 * sample_dur * sample_per_chunk / trak->Media->mediaHeader->timeScale;
	return i;
}

u32 M4_GetSampleFragmentCount(M4File *the_file, u32 trackNumber, u32 sampleNumber)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return 0;

	//Padding info
	return stbl_GetSampleFragmentCount(trak->Media->information->sampleTable->Fragments, sampleNumber);
}

u16 M4_GetSampleFragmentSize(M4File *the_file, u32 trackNumber, u32 sampleNumber, u32 FragmentIndex)
{
	TrackAtom *trak;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak || !FragmentIndex) return 0;

	//Padding info
	return stbl_GetSampleFragmentSize(trak->Media->information->sampleTable->Fragments, sampleNumber, FragmentIndex);
}


M4Err M4_GetTrackSampleDefaults(M4File *the_file, u32 trackNumber, 
							 u32 *defaultDuration, u32 *defaultSize, u32 *defaultDescriptionIndex,
							 u32 *defaultRandomAccess, u8 *defaultPadding, u16 *defaultDegradationPriority)
{
	TrackAtom *trak;
	sttsEntry *ts_ent;
	stscEntry *sc_ent;
	u32 i, j, maxValue, value;
	SampleTableAtom *stbl;
	trak = GetTrackFromFile(the_file, trackNumber);
	if (!trak) return M4BadParam;

	stbl = trak->Media->information->sampleTable;
	//duration
	if (defaultDuration) {
		maxValue = value = 0;
		for (i=0; i<ChainGetCount(stbl->TimeToSample->entryList); i++) {
			ts_ent = ChainGetEntry(stbl->TimeToSample->entryList, i);
			if (ts_ent->sampleCount>maxValue) {
				value = ts_ent->sampleDelta;
				maxValue = ts_ent->sampleCount;
			}
		}
		*defaultDuration = value;
	}
	//size
	if (defaultSize) {
		*defaultSize = stbl->SampleSize->sampleSize;
	}
	//descIndex
	if (defaultDescriptionIndex) {
		maxValue = value = 0;
		for (i=0; i<ChainGetCount(stbl->SampleToChunk->entryList); i++) {
			sc_ent = ChainGetEntry(stbl->SampleToChunk->entryList, i);
			if ((sc_ent->nextChunk - sc_ent->firstChunk) * sc_ent->samplesPerChunk > maxValue) {
				value = sc_ent->sampleDescriptionIndex;
				maxValue = (sc_ent->nextChunk - sc_ent->firstChunk) * sc_ent->samplesPerChunk;
			}
		}
		*defaultDescriptionIndex = value ? value : 1;
	}
	//RAP
	if (defaultRandomAccess) {
		//no sync table is ALL RAP
		*defaultRandomAccess = stbl->SyncSample ? 0 : 1;
		if (stbl->SyncSample 
			&& (stbl->SyncSample->entryCount >= stbl->SampleSize->sampleCount/2)) {
			*defaultRandomAccess = 1;
		}
	}
	//defaultPadding
	if (defaultPadding) {
		*defaultPadding = 0;
		if (stbl->PaddingBits) {
			maxValue = 0;
			for (i=0; i<stbl->PaddingBits->SampleCount; i++) {
				value = 0;
				for (j=0; j<stbl->PaddingBits->SampleCount; j++) {
					if (stbl->PaddingBits->padbits[i]==stbl->PaddingBits->padbits[j]) {
						value ++;
					}
				}
				if (value>maxValue) {
					maxValue = value;
					*defaultPadding = stbl->PaddingBits->padbits[i];
				}
			}
		}
	}
	//defaultDegradationPriority
	if (defaultDegradationPriority) {
		*defaultDegradationPriority = 0;
		if (stbl->DegradationPriority) {
			maxValue = 0;
			for (i=0; i<stbl->DegradationPriority->entryCount; i++) {
				value = 0;
				for (j=0; j<stbl->DegradationPriority->entryCount; j++) {
					if (stbl->DegradationPriority->priorities[i]==stbl->DegradationPriority->priorities[j]) {
						value ++;
					}
				}
				if (value>maxValue) {
					maxValue = value;
					*defaultDegradationPriority = stbl->DegradationPriority->priorities[i];
				}
			}
		}
	}
	return M4OK;
}


M4Err M4_RefreshFragmentedMovie(M4File *movie, u64 *MissingBytes)
{
#ifndef	M4_ISO_NO_FRAGMENTS
	return M4NotSupported;
#else
	if (!movie || !movie->moov || !movie->moov->mvex) return M4BadParam;
	if (movie->openMode != M4_OPEN_READ) return M4BadParam;

	//ok parse root atoms
	return ParseMovieAtoms(movie, MissingBytes);
#endif
}


M4Err M4_SetStreamingTextMode(M4File *movie, Bool do_convert)
{
	if (!movie) return M4BadParam;
	movie->convert_streaming_text = do_convert;
	return M4OK;
}


UnknownStreamDescription *M4_GetUnknownStreamDescription(M4File *movie, u32 trackNumber, u32 StreamDescriptionIndex)
{
	GenericVisualSampleEntryAtom *entry;
	GenericAudioSampleEntryAtom *gena;
	GenericMediaSampleEntryAtom *genm;
	TrackAtom *trak;
	UnknownStreamDescription *udesc;
	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak || !StreamDescriptionIndex) return NULL;

	entry = ChainGetEntry(trak->Media->information->sampleTable->SampleDescription->atomList, StreamDescriptionIndex-1);
	//no entry or MPEG entry: 
	if (!entry || IsMP4Description(entry->type) ) return NULL;
	//if we handle the description return false
	switch (entry->type) {
	case M4_3GP_AMR_SubType:
	case M4_3GP_AMR_WB_SubType:
	case M4_3GP_EVRC_SubType:
	case M4_3GP_QCELP_SubType:
	case M4_3GP_SMV_SubType:
	case M4_3GP_H263_SubType:
		return NULL;
	case GenericVisualSampleEntryAtomType:
		udesc = malloc(sizeof(UnknownStreamDescription));
		memset(udesc, 0, sizeof(UnknownStreamDescription));
		if (entry->EntryType == ExtendedAtomType) {
			memcpy(udesc->UUID, entry->uuid, sizeof(bin128));
		} else {
			udesc->codec_tag = entry->EntryType;
		}
		udesc->version = entry->version;
		udesc->revision = entry->revision;
		udesc->vendor_code = entry->vendor;
		udesc->temporal_quality = entry->temporal_quality;
		udesc->spacial_quality = entry->spacial_quality;
		udesc->width = entry->Width;
		udesc->height = entry->Height;
		udesc->h_res = entry->horiz_res;
		udesc->v_res = entry->vert_res;
		strcpy(udesc->szCompressorName, entry->compressor_name);
		udesc->depth = entry->bit_depth;
		udesc->color_table_index = entry->color_table_index;
		if (entry->data_size) {
			udesc->extension_buf_size = entry->data_size;
			udesc->extension_buf = malloc(sizeof(char) * entry->data_size);
			memcpy(udesc->extension_buf, entry->data, entry->data_size);
		}
		return udesc;
	case GenericAudioSampleEntryAtomType:
		gena = (GenericAudioSampleEntryAtom *)entry;
		udesc = malloc(sizeof(UnknownStreamDescription));
		memset(udesc, 0, sizeof(UnknownStreamDescription));
		if (gena->EntryType == ExtendedAtomType) {
			memcpy(udesc->UUID, gena->uuid, sizeof(bin128));
		} else {
			udesc->codec_tag = gena->EntryType;
		}
		udesc->version = gena->version;
		udesc->revision = gena->revision;
		udesc->vendor_code = gena->vendor;
		udesc->SampleRate = gena->samplerate_hi;
		udesc->bitsPerSample = gena->bitspersample;
		udesc->NumChannels = gena->channel_count;
		if (gena->data_size) {
			udesc->extension_buf_size = gena->data_size;
			udesc->extension_buf = malloc(sizeof(char) * gena->data_size);
			memcpy(udesc->extension_buf, gena->data, gena->data_size);
		}
		return udesc;
	case GenericMediaSampleEntryAtomType:
		genm = (GenericMediaSampleEntryAtom *)entry;
		udesc = malloc(sizeof(UnknownStreamDescription));
		memset(udesc, 0, sizeof(UnknownStreamDescription));
		if (genm->EntryType == ExtendedAtomType) {
			memcpy(udesc->UUID, genm->uuid, sizeof(bin128));
		} else {
			udesc->codec_tag = genm->EntryType;
		}
		if (genm->data_size) {
			udesc->extension_buf_size = genm->data_size;
			udesc->extension_buf = malloc(sizeof(char) * genm->data_size);
			memcpy(udesc->extension_buf, genm->data, genm->data_size);
		}
		return udesc;
	}
	return NULL;
}

M4Err M4_GetVisualEntrySize(M4File *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 *Width, u32 *Height)
{
	TrackAtom *trak;
	SampleEntryAtom *entry;
	SampleDescriptionAtom *stsd;

	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return M4BadParam;

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return movie->LastError = M4InvalidMP4File;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > ChainGetCount(stsd->atomList)) return movie->LastError = M4BadParam;

	entry = ChainGetEntry(stsd->atomList, StreamDescriptionIndex - 1);
	//no support for generic sample entries (eg, no MPEG4 descriptor)
	if (entry == NULL) return M4BadParam;
	
	//valid for MPEG visual, JPG and 3GPP H263
	switch (entry->type) {
	case MPEGEncVisualSampleEntryAtomType:
		if (entry->protection_info && (entry->protection_info->original_format->data_format!= MPEGVisualSampleEntryAtomType)) return M4InvalidMP4Media;
	case MPEGVisualSampleEntryAtomType:
	case M4_3GP_H263_SubType:
	case AVCSampleEntryAtomType:
	case GenericVisualSampleEntryAtomType:
		*Width = ((VisualSampleEntryAtom*)entry)->Width;
		*Height = ((VisualSampleEntryAtom*)entry)->Height;
		return M4OK;
	default:
		if (trak->Media->handler->handlerType==M4_BIFSMediaType) {
			*Width = trak->Header->width>>16;
			*Height = trak->Header->height>>16;
			return M4OK;
		}
		return M4BadParam;
	}
}

M4Err M4_GetAudioEntryInfo(M4File *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 *SampleRate, u32 *Channels, u8 *bitsPerSample)
{
	TrackAtom *trak;
	SampleEntryAtom *entry;
	SampleDescriptionAtom *stsd;

	trak = GetTrackFromFile(movie, trackNumber);
	if (!trak) return M4BadParam;

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return movie->LastError = M4InvalidMP4File;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > ChainGetCount(stsd->atomList)) return movie->LastError = M4BadParam;

	entry = ChainGetEntry(stsd->atomList, StreamDescriptionIndex - 1);
	//no support for generic sample entries (eg, no MPEG4 descriptor)
	if (entry == NULL) return M4BadParam;
	
	switch (entry->type) {
	case MPEGEncAudioSampleEntryAtomType:
		if (entry->protection_info && (entry->protection_info->original_format->data_format!= MPEGAudioSampleEntryAtomType)) return M4InvalidMP4Media;
	case MPEGAudioSampleEntryAtomType:
	case M4_3GP_AMR_SubType:
	case M4_3GP_AMR_WB_SubType:
	case M4_3GP_EVRC_SubType:
	case M4_3GP_QCELP_SubType:
	case M4_3GP_SMV_SubType:
		(*SampleRate) = ((AudioSampleEntryAtom*)entry)->samplerate_hi;
		(*Channels) = ((AudioSampleEntryAtom*)entry)->channel_count;
		(*bitsPerSample) = (u8) ((AudioSampleEntryAtom*)entry)->bitspersample;
		return M4OK;
	default:
		return M4BadParam;
	}
}

const char *M4_GetFilename(M4File *movie)
{
	if (!movie) return NULL;
#ifndef M4_READ_ONLY
	if (movie->finalName && !movie->fileName) return movie->finalName;
#endif
	return movie->fileName;
}


u8 M4_GetMoviePLIndication(M4File *movie, u8 PL_Code)
{
	M4F_InitialObjectDescriptor *iod;
	if (!movie || !movie->moov) return 0;
	if (!movie->moov->iods || !movie->moov->iods->descriptor) return 0xFF;
	if (movie->moov->iods->descriptor->tag != MP4_IOD_Tag) return 0xFF;

	iod = (M4F_InitialObjectDescriptor *)movie->moov->iods->descriptor;
	switch (PL_Code) {
	case M4_PL_AUDIO: return iod->audio_profileAndLevel;
	case M4_PL_VISUAL: return iod->visual_profileAndLevel;
	case M4_PL_GRAPHICS: return iod->graphics_profileAndLevel;
	case M4_PL_SCENE: return iod->scene_profileAndLevel;
	case M4_PL_OD: return iod->OD_profileAndLevel;
	case M4_PL_INLINE: return iod->inlineProfileFlag;
	case M4_PL_MPEGJ:
	default:
		return 0xFF;
	}
}


M4Err M4_GetTrackVideoInfo(M4File *movie, u32 trackNumber, u32 *width, u32 *height, s32 *translation_x, s32 *translation_y, s16 *layer)
{
	TrackAtom *tk = GetTrackFromFile(movie, trackNumber);
	if (!tk) return M4BadParam;
	*width = tk->Header->width;
	*height = tk->Header->height;
	*layer = tk->Header->layer;
	*translation_x = tk->Header->matrix[6];
	*translation_y = tk->Header->matrix[7];
	return M4OK;
}


/*returns total amount of media bytes in track*/
u64 M4_GetMediaDataSize(M4File *movie, u32 trackNumber)
{
	u32 i, size;
	SampleSizeAtom *stsz;
	TrackAtom *tk = GetTrackFromFile(movie, trackNumber);
	if (!tk) return 0;
	stsz = tk->Media->information->sampleTable->SampleSize;
	if (stsz->sampleSize) return stsz->sampleSize*stsz->sampleCount;
	size = 0;
	for (i=0; i<stsz->sampleCount;i++) size += stsz->sizes[i];
	return size;
}


void M4_SetDefaultSyncTrack(M4File *movie, u32 trackNumber)
{
	TrackAtom *tk = GetTrackFromFile(movie, trackNumber);
	if (!tk) movie->es_id_default_sync = -1;
	else movie->es_id_default_sync = tk->Header->trackID;
}


