/*
 *  playlist-mp4.c
 *  mod_musicindex
 *
 *  $Id: playlist-mp4.c 1010 2012-08-07 13:38:34Z varenet $
 *
 *  Created by Thibaut VARENE on Tue Sep  2 2004.
 *  Copyright (c) 2004-2005,2007,2010-2011 Thibaut VARENE
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License version 2.1,
 *  as published by the Free Software Foundation.
 *
 */

/**
 * @file
 * MP4 files management system.
 *
 * @author Thibaut Varene
 * @version $Revision: 1010 $
 * @date 2004-2005
 * @date 2007
 * @date 2010-2011
 *
 * This file contains everything needed to produce music entries from
 * MP4 files.
 *
 * @warning I'm not actually sure raw AAC files are properly handled.
 */

#include "playlist.h"
#include "playlist-mp4.h"

#include <mp4v2/mp4v2.h>        /* libmp4v2 */
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

/**
 * Checks for valid MP4 filename extension.
 *
 * @param filename The filename to check.
 * @todo see if we can just rely on the trackType test.
 *
 * @return TRUE when the extension is correct, FALSE otherwise.
 */
static inline short mp4_ext_check(const char *const filename)
{
	const char *const ext = strrchr(filename, '.');
	if(ext && (!strncasecmp(ext, ".m4a", 4)	|| /* Apple mp4 file */
		!strncasecmp(ext, ".mp4", 4)	|| /* official extention */
		!strncasecmp(ext, ".aac", 4))	)  /* old MPEG2/4 extention */
		return TRUE;
	return FALSE;
}

/**
 * Fills in the information fields about MP4 data.
 *
 * This function reads the mp4 metadata (using libmp4) from the MP4 file @a in
 * and fills in the struct mu_ent fields accordingly.
 * The code is based on sample files from mpeg4ip/libmp4v2.
 *
 * @param r Apache request_rec struct to handle log writings (debugging)
 * @param pool Pool
 * @param in MP4 file to parse (closed on normal exit)
 * @param filename current filename
 *
 * @return When possible, struct mu_ent correctly set up, file stream closed.
 *
 * @todo handle old AAC files that use id3 tags.
 */
mu_ent *make_mp4_entry(request_rec *r, apr_pool_t *pool, FILE *const in,
	const char *const filename)
{
	const mu_config *const conf = (mu_config *)ap_get_module_config(r->per_dir_config, &musicindex_module);
	mu_ent 			*p = NULL;

	struct stat		filestat;
	MP4FileHandle		mp4file;
	MP4TrackId		trackID;
	const char		*trackType;
	
	/* XXX We could probably rely on the trackType test only though */
	if (!mp4_ext_check(filename))
		goto exit;
		
#ifdef MP4_DETAILS_ALL /* braindead API */
	mp4file = MP4Read(filename, 0);
#else
	mp4file = MP4Read(filename);
#endif
	if (mp4file == MP4_INVALID_FILE_HANDLE)
		goto exit;
	
	/* until proven wrong, audio files have either only 1 sound track,
	 * or one sound track and a couple other ones. The sound track always
	 * comes first. */
	trackID = MP4FindTrackId(mp4file, 0, NULL, 0);
	trackType = MP4GetTrackType(mp4file, trackID);

	if (!strcmp(trackType, MP4_AUDIO_TRACK_TYPE)) {	/* We have an Audio Track */
		/* Allocate convenient tag structure and populate it */
		const MP4Tags *mp4tags = MP4TagsAlloc();
		if (!mp4tags)
			goto fail;
		MP4TagsFetch(mp4tags, mp4file);
		
		p = NEW_ENT(pool);
		if (p == NULL) {
			MP4TagsFree(mp4tags);
			goto fail;
		}
		
		p->filetype = FT_MP4;
		p->flags |= EF_VBR;
	
		fstat(fileno(in), &filestat);
		p->size = filestat.st_size;
		p->mtime = filestat.st_mtime;
		
		fclose(in);	/* we won't use the file stream as provided anymore */
		
		if (conf->options & (MI_QUICKPL))
			p->bitrate = p->length = p->freq = 0;
		else {
			unsigned short timeScale = MP4GetTrackTimeScale(mp4file, trackID); /* Hz */
			MP4Duration trackDuration = MP4GetTrackDuration(mp4file, trackID);
			unsigned long msDuration = MP4ConvertFromTrackDuration(mp4file, trackID, 
						trackDuration, MP4_MSECS_TIME_SCALE) / 1000;	/* s */	
			unsigned long avgBitRate = ( MP4GetTrackBitRate(mp4file, trackID) + 500 ) / 1000;	/* kbps */
				
			p->length = msDuration;
			p->bitrate = avgBitRate << 10; 
			p->freq = timeScale;
		}
	
		if (mp4tags->artist)
			p->artist = apr_pstrdup(pool, mp4tags->artist);
		
		if (mp4tags->name)
			p->title = apr_pstrdup(pool, mp4tags->name);
		
		if (mp4tags->album)
			p->album = apr_pstrdup(pool, mp4tags->album);
		
		if (mp4tags->releaseDate)
			p->date = atoi(mp4tags->releaseDate);
	
		if (mp4tags->genre)
			p->genre = apr_pstrdup(pool, mp4tags->genre);
	
		if (mp4tags->track)
			p->track = mp4tags->track->index;
	
		if (mp4tags->disk)
			p->posn = mp4tags->disk->index;
	
		MP4TagsFree(mp4tags);
	}
	
fail:
#ifdef MP4_CLOSE_DO_NOT_COMPUTE_BITRATE /* still braindamaged */
	MP4Close(mp4file, 0);
#else
	MP4Close(mp4file);
#endif

exit:
	return p;
}
