/* mp3.c
 * Copyright (C) 2004, 2005 Sylvain Cresto <scresto@gmail.com>
 *
 * This file is part of graveman!
 *
 * graveman! 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.
 * 
 * graveman! 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 program; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA. 
 * 
 * URL: http://www.nongnu.org/graveman/
 *
 *
 * Portions of code came originally from mp3.c / MOC - music on console
 * Copyright (C) 2002-2004 Damian Pietras <daper@daper.net>
 */

#include "graveman.h"

#ifdef ENABLE_MP3
#include <id3tag.h>


guint32 count_time (const char *file);

/* utilisation de la libid3tag pour extraire les informations du fichier mp3
 * on utilise les tags pour en extraire le nom du morceau ainsi que la duree de la chanson,
 * si il n'y a pas de tag correspondant on n'aura donc pas la vraie duree ! */

static gchar *id3_getvalue(struct id3_tag *Atagfile, gchar *Atagname) {
  struct id3_frame *Lframe = id3_tag_findframe(Atagfile, Atagname, 0);
  union id3_field *Lfield;
  const id3_ucs4_t *Lcurval;

  if (!Lframe) return g_strdup("");

  Lfield = id3_frame_field(Lframe, 1);
  if (!Lfield) return g_strdup("");

  Lcurval = id3_field_getstrings(Lfield, 0);
  if (!Lcurval) return g_strdup("");

  return id3_ucs4_utf8duplicate(Lcurval);
}

/* utilisation de libidmp3 pour recuperer les informations tel que le nom de l'alum et la longeur
 * ca ne marchera pour le moment qu'avec les tags ID3 v2, mais bon .. */
gboolean getMp3Info(gchar *Apath, gchar **Atitle, gchar **Aalbum, gchar **Aartist, guint32 *Alength, GError **Aerror)
{
  struct id3_file *Lfile;
  struct id3_tag *Ltagfile;
  gchar *Lbuf = NULL;

  *(Atitle) = *(Aalbum) = *(Aartist) = NULL;
  *(Alength) = 0;

  if (!(Lfile = id3_file_open(Apath, ID3_FILE_MODE_READONLY))) {
    g_set_error(Aerror, G_FILE_ERROR, g_file_error_from_errno(errno), "%s:%s\n%s",
       _("Cannot read file"), Apath, strerror(errno));
    return FALSE;
  }

  if (!(Ltagfile = id3_file_tag(Lfile))) {
    /* le fichier n'a pas forcement de tag ... */
    return TRUE;
  }

  if ((Lbuf = id3_getvalue(Ltagfile, ID3_FRAME_TITLE))) { *(Atitle) = _UTF8(Lbuf); g_free(Lbuf); }
  if ((Lbuf = id3_getvalue(Ltagfile, ID3_FRAME_ALBUM))) { *(Aalbum) = _UTF8(Lbuf); g_free(Lbuf); }
  if ((Lbuf = id3_getvalue(Ltagfile, ID3_FRAME_ARTIST))) { *(Aartist) = _UTF8(Lbuf); g_free(Lbuf); }

  /* on recupere la longueur du fichier */
  *(Alength) = count_time(Apath);

  return *(Alength) ? TRUE : FALSE;
}


/*
 * MOC - music on console
 * Copyright (C) 2002-2004 Damian Pietras <daper@daper.net>
 */

/* This code was writen od the basis of madlld.c (C) by Bertrand Petit 
 * including code from xmms-mad (C) by Sam Clegg and winamp plugin for madlib
 * (C) by Robert Leslie.
 */

/* FIXME: there could be a bit of silence in mp3 at the end or at the
 * beginning. If you hear gaps between files, it's the file's fault.
 * Can we strip this silence? */

#include <mad.h>
#ifdef HAVE_MMAP
# include <sys/mman.h>
#endif

/* Used only if mmap() is not available */
#define INPUT_BUFFER	(64 * 1024)

struct mp3_data
{
  gint infile; /* fd on the mp3 file */
  gulong bitrate;
  guint freq;
  gshort channels;
  glong duration; /* Total time of the file in seconds.
				 used for seeking. */
  off_t size; /* Size of the file */

#ifdef HAVE_MMAP
  gchar *mapped;
  gint mapped_size;
#endif
	
  guchar in_buff[INPUT_BUFFER];

  struct mad_stream stream;
  struct mad_frame frame;
  struct mad_synth synth;

  gint skip_frames; /* how much frames to skip (after seeking) */
};

/* Fill in the mad buffer, return number of bytes read, 0 on eof or error */
static size_t fill_buff (struct mp3_data *data)
{
  size_t remaining;
  ssize_t read_size;
  guchar *read_start;
	
  if (data->stream.next_frame != NULL) {
    remaining = data->stream.bufend - data->stream.next_frame;
    memmove (data->in_buff, data->stream.next_frame, remaining);
    read_start = data->in_buff + remaining;
    read_size = INPUT_BUFFER - remaining;
  } else {
    read_start = data->in_buff;
    read_size = INPUT_BUFFER;
    remaining = 0;
  }

  read_size = read (data->infile, read_start, read_size);
  if (read_size < 0) {
    g_warning("read() failed: %s\n", strerror (errno));
    return 0;
  } else if (read_size == 0) return 0;

  mad_stream_buffer(&data->stream, data->in_buff, read_size + remaining);
  data->stream.error = 0;

  return read_size;
}

static int count_time_internal (struct mp3_data *data)
{
  mad_timer_t duration = mad_timer_zero;
  struct mad_header header;
  int good_header = 0; /* Have we decoded any header? */

  mad_header_init (&header);

  /* There are three ways of calculating the length of an mp3:
     1) Constant bitrate: One frame can provide the information
        needed: # of frames and duration. Just see how long it
        is and do the division.
     2) Variable bitrate: Xing tag. It provides the number of 
        frames. Each frame has the same number of samples, so
        just use that.
     3) All: Count up the frames and duration of each frames
        by decoding each one. We do this if we've no other
        choice, i.e. if it's a VBR file with no Xing tag.
   */

  while (1) {
		
    /* Fill the input buffer if needed */
    if (data->stream.buffer == NULL || data->stream.error == MAD_ERROR_BUFLEN) {
#ifdef HAVE_MMAP
      if (data->mapped) break; /* FIXME: check if the size of file
                                  has't changed */
      else
#endif
      if (!fill_buff(data)) break;
    }

    if (mad_header_decode(&header, &data->stream) == -1) {
      if (MAD_RECOVERABLE(data->stream.error)) continue;
      else if (data->stream.error == MAD_ERROR_BUFLEN) continue;
      else {
        g_warning ("Can't decode header: %s", mad_stream_errorstr( &data->stream));
        break;
      }
    }

    good_header = 1;

    mad_timer_add (&duration, header.duration);
  }

  if (!good_header) return -1;

  mad_header_finish(&header);

  return mad_timer_count (duration, MAD_UNITS_SECONDS);
}

static void *mp3_open (const char *file)
{
  struct stat stat;
  struct mp3_data *data;

  data = (struct mp3_data *)g_malloc (sizeof(struct mp3_data));

  /* Reset information about the file */
  data->freq = 0;
  data->channels = 0;
  data->skip_frames = 0;
  data->bitrate = -1;

  /* Open the file */
  if ((data->infile = open(file, O_RDONLY)) == -1) {
    g_warning("open() failed: %s", strerror(errno));
    g_free (data);
    return NULL;
  }

  if (fstat(data->infile, &stat) == -1) {
    g_warning("Can't stat() file: %s", strerror(errno));
    close (data->infile);
    g_free (data);
    return NULL;
  }

  data->size = stat.st_size;

  mad_stream_init (&data->stream);
  mad_frame_init (&data->frame);
  mad_synth_init (&data->synth);
	
#ifdef HAVE_MMAP
  data->mapped_size = data->size;
  data->mapped = mmap (0, data->mapped_size, PROT_READ, MAP_SHARED, data->infile, 0);
  if (data->mapped == MAP_FAILED) {
    _DEB ("mmap() failed: %s, using standard read()", g_strerror(errno));
    data->mapped = NULL;
  } else {
    mad_stream_buffer (&data->stream, data->mapped,
    data->mapped_size);
    data->stream.error = 0;
    _DEB ("mmapped() %ld bytes of file", (long)data->size);
  }
#endif

  data->duration = count_time_internal (data);
  mad_frame_mute (&data->frame);
  data->stream.next_frame = NULL;
  data->stream.sync = 0;
  data->stream.error = MAD_ERROR_NONE;

#ifdef HAVE_MMAP
  if (data->mapped) mad_stream_buffer (&data->stream, data->mapped, data->mapped_size);
  else {
#endif
    if (lseek(data->infile, SEEK_SET, 0) == (off_t)-1) {
      g_warning ("lseek() failed: %s", strerror(errno));
      close (data->infile);
      g_free (data);
      return NULL;
    }

    data->stream.error = MAD_ERROR_BUFLEN;
#ifdef HAVE_MMAP
  }
#endif

  return data;
}

static void mp3_close (void *void_data)
{
  struct mp3_data *data = (struct mp3_data *)void_data;

#ifdef HAVE_MMAP
  if (data->mapped && munmap(data->mapped, data->size) == -1)
		g_warning ("munmap() failed: %s", g_strerror(errno));
#endif

  close (data->infile);
	
  mad_stream_finish (&data->stream);
  mad_frame_finish (&data->frame);
  mad_synth_finish (&data->synth);

  g_free (data);
}

/* Get the time for mp3 file, return -1 on error. */
/* Adapted from mpg321. */
guint32 count_time (const char *file)
{
  struct mp3_data *data;
  gint time;
	
  _DEB ("Processing file %s", file);

  if (!(data = mp3_open(file)))
	return 0;
  time = data->duration;
  mp3_close (data);

  return time;
}

#endif /* ifdef ENABLE_MP3 */

/*
 * vim:et:ts=8:sts=2:sw=2
 */
