Logo Search packages:      
Sourcecode: easyh10 version File versions  Download package

gmi_mp3_id3tag.c

/*
 *      Tag and audio information retrieval by libid3tag
 *
 *      Copyright (c) 2005 Nyaochi
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit
 * http://www.gnu.org/copyleft/gpl.html .
 *
 */

/* $Id: gmi_mp3_id3tag.c,v 1.3 2006/10/08 18:34:11 nyaochi Exp $ */

#ifdef      HAVE_CONFIG_H
#include <config.h>
#endif/*HAVE_CONFIG_H*/

#include <os.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ucs2char.h>
#include <h10db.h>
#include <getmediainfo.h>

#include <id3tag.h>

struct tag_mp3header {
      int version;                  /**< MPEG version */
      int protection;               /**< true if the frame is protected by CRC */
      int bitrate;                  /**< bitrate in Kbps */
      int sample_rate;        /**< sample rate in Hz */
      int padding;                  /**< true if the frame is padded */
      int channel_mode;       /**< channel mode */

      int frame_size;               /**< size of the frame in bytes */
      int samples_per_frame;  /**< number of audio samples in the frame */

      /* From Xing VBR header for VBR files, From file size for CBR files. */
      int number_of_frames;   /**< number of frames */
      int data_size;                /**< data size. */

      int duration;
};
typedef struct tag_mp3header mp3header_t;

static const bitrate_table[3][16] = {
      {-1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1},
      {-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
      {-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
};

static const samplerate_table[3][4] = {
      {44100, 48000, 32000, -1},
      {22050, 24000, 16000, -1},
      {11025, 1200, 8000, -1},
};

static int parse_mp3header(mp3header_t* header, uint8_t* data)
{
      int bitrate_index, samplerate_index;

      /* Make sure sync header and LayerIII */
      if (data[0] != 0xFF && (data[1] & 0xE6) != 0xE6) {
            return -1;
      }

      /* Read MPEG version */
      switch ((data[1] >> 3) & 0x03) {
      case 3:
            header->version = 0;    /* MPEG1 */
            break;
      case 2:
            header->version = 1;    /* MPEG2 */
            break;
      case 0:
            header->version = 2;    /* MPEG2.5 */
            break;
      default:
            return -1;
      }

      // Read bitrate
      bitrate_index = data[2] >> 4;
      header->bitrate = bitrate_table[header->version][bitrate_index] * 1000;
      // Read sample rate
      samplerate_index = (data[2] >> 2) & 0x03;
      header->sample_rate = samplerate_table[header->version][samplerate_index];
      // Validate bitrate and sample rate
      if (header->bitrate < 0 || header->sample_rate < 0) {
            return -1;
      }
      // Read padding
      header->padding = (data[2] >> 1) & 0x01;
      // Read stereo mode
      header->channel_mode = (data[3] >> 6) & 0x03;

      if (header->version == 0) {
            header->frame_size = 144 * header->bitrate / header->sample_rate + header->padding;
            header->samples_per_frame = 1152;

      } else {
            header->frame_size = 72 * header->bitrate / header->sample_rate + header->padding;
            header->samples_per_frame = 576;
      }

      header->number_of_frames = 0; /* Set zero for now. */
      return 0;
}

static int get_xing_offset(const mp3header_t* header)
{
      if (header->version == 0) {
            // MPEG1
            return (header->channel_mode == 3 ? 17 : 32);
      } else {
            // MPEG2/2.5
            return (header->channel_mode == 3 ? 9 : 17);
      }
}

static int parse_xing_header(mp3header_t* header, FILE *fp)
{
      uint8_t xing_header[16];      /* 16 bytes is enough. */
      if (fread(xing_header, sizeof(uint8_t), 16, fp) != 16) {
            return -1;
      }
      if (memcmp(xing_header, "Xing", 4) != 0) {
            return -1;
      }
      if (xing_header[7] & 0x01) {
            header->number_of_frames =
                  (uint32_t)xing_header[ 8] << 24 |
                  (uint32_t)xing_header[ 9] << 16 |
                  (uint32_t)xing_header[10] << 8  |
                  (uint32_t)xing_header[11]
                  ;
      }
      if (xing_header[7] & 0x02) {
            header->data_size =
                  (uint32_t)xing_header[12] << 24 |
                  (uint32_t)xing_header[13] << 16 |
                  (uint32_t)xing_header[14] << 8  |
                  (uint32_t)xing_header[15]
                  ;
      }

      return 0;
}

static int skip_id3v2(FILE *fp)
{
      uint8_t buffer[10];
      uint32_t id3v2_size = 0;
      long offset = ftell(fp);

      /*
      Loop until we find something other than ID3v2 in case the file
      contains multiple ID3v2 tags.
      */
      for (;;) {
            /* Read ten bytes to examine ID3v2 */
            if (fread(buffer, sizeof(uint8_t), 10, fp) != 10) {
                  break;
            }

            /* Make sure the read stream is ID3v2 header. */
            if (buffer[0] != 'I' || buffer[1] != 'D' || buffer[2] != '3') {
                  break;
            }

            /* Obtain tag size. */
            id3v2_size =
                  (uint32_t)(buffer[6] & 0x7F) << 21 |
                  (uint32_t)(buffer[7] & 0x7F) << 14 |
                  (uint32_t)(buffer[8] & 0x7F) << 7  |
                  (uint32_t)(buffer[9] & 0x7F);

            /* Seek to the end position. */
            if (fseek(fp, (long)id3v2_size, SEEK_CUR) != 0) {
                  break;
            }

            offset = ftell(fp);
      }

      fseek(fp, offset, SEEK_SET);
      return 0;
}

static int get_mp3stream_info(mp3header_t* header, const ucs2_char_t *filename, uint32_t filesize)
{
      int channels = 0;
      uint8_t buffer[4];
      FILE *fp = ucs2fopen(filename, "rb");
      if (!fp) {
            return -1;
      }

      /* Skip ID3v2 for unsyncronized ID3v2 tag. */
      skip_id3v2(fp);

      if (fread(buffer, sizeof(uint8_t), 4, fp) != 4) {
            goto error_get_mp3stream_info;
      }

      for (;;) {
            if (parse_mp3header(header, buffer) == 0) {
                  /* Found a MPEG Audio header... */

                  /* Get the offset position where the first frame begins. */
                  long content_pos = ftell(fp);
                  content_pos -= 4;
                  if (content_pos < 0) {
                        content_pos = 0;
                  }

                  /* Initialize data_size and number_of_frames, assuming a CBR stream. */
                  header->data_size = (filesize-content_pos);
                  header->number_of_frames = header->data_size / header->frame_size;

                  /* Discard side info. */
                  if (fseek(fp, get_xing_offset(header), SEEK_CUR) != 0) {
                        goto error_get_mp3stream_info;            
                  }

                  /* Parse Xing VBR header if any. This sets number_of_frames and data_size. */
                  if (parse_xing_header(header, fp) == 0) {
                        double duration = (double)header->samples_per_frame * header->number_of_frames / header->sample_rate;
                        double bitrate = header->data_size * 8.0 / duration;
                        header->duration = (int)duration;
                        header->bitrate = (int)bitrate;
                  } else {
                        double duration = (double)header->samples_per_frame * header->number_of_frames / header->sample_rate;
                        header->duration = (int)duration;
                  }

                  /* Exit with success. */
                  break;
            }

            /* Read next. */
            memmove(&buffer[0], &buffer[1], sizeof(uint8_t)*3);
            if (fread(&buffer[3], sizeof(uint8_t), 1, fp) != 1) {
                  goto error_get_mp3stream_info;
            }
      }

      fclose(fp);
      return 0;

error_get_mp3stream_info:
      fclose(fp);
      return -1;
}


static ucs2_char_t* get_frame_value(struct id3_tag *id3tag, const char *name)
{
      ucs2_char_t* ret = NULL;
      const id3_ucs4_t *value = NULL;
      struct id3_frame *frame = NULL;
      union id3_field *field = NULL;
      enum id3_field_textencoding encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1;

      /* Find the frame. */
      frame = id3_tag_findframe(id3tag, name, 0);
      if (!frame) {
            return NULL;
      }

      /* */
      field = id3_frame_field(frame, 0);
      if (!field) {
            return NULL;
      }

      if (id3_field_type(field) == ID3_FIELD_TYPE_TEXTENCODING) {
            encoding = field->number.value;
      }

      field = id3_frame_field(frame, 1);
      if (!field) {
            return NULL;
      }

      value = id3_field_getstrings(field, 0);
      if (!value) {
            return NULL;
      }

      if (strcmp(name, "TCON") == 0) {
            value = id3_genre_name(value);
      }

      if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) {
            char *mbs = id3_ucs4_latin1duplicate(value);
            ret = mbsdupucs2_music(mbs);  /* MBS for music files. */
            free(mbs);
            return ret;
      } else {
            return id3_ucs4_utf16duplicate(value);
      }
}

int gettag_mp3(media_info* info, const ucs2_char_t *path, const ucs2_char_t *file)
{
      int i, num_tags = 0;
      ucs2_char_t pathname[MAX_PATH];
      FILE *fp = NULL;
      mp3header_t mp3header;
      uint32_t tn = 0;
      static const ucs2_char_t ucs2_empty[1] = {0};
      ucs2_char_t* ucs2 = NULL;
      struct id3_file *id3file = NULL;
      struct id3_tag *id3tag = NULL;


      /* Obtain the filename. */
      ucs2cpy(pathname, path);
      ucs2cat(pathname, file);

      /* Set the pathname and filename. */
      info->pathname = ucs2dup(path);
      info->filename = ucs2dup(file);

      /* Set the filename to title in case we could not retrieve track title. */
      free(info->title);
      info->title = gettag_setfilename(file);

      /* Open the file with UNICODE filename. This is for better compatibility. */
      fp = ucs2fopen(pathname, "rb");
      if (!fp) {
            return -1;
      }

      /* Open with libid3tag */
      /*id3file = id3_file_open(pathname, ID3_FILE_MODE_READONLY);*/
      id3file = id3_file_fdopen(fileno(fp), ID3_FILE_MODE_READONLY);
      if (!id3file) {
            fclose(fp);
            return -1;
      }

      /* 
       * Generally speaking, it is enough to read tag information from either ID3v1
       * or ID3v2. However, I got several reports pointing that EasyH10 could not
       * read tag information of a few MP3 files. I looked into the files and found
       * that they have both ID3v1 and ID3v2, but the number of fields in ID3v2 was
       * smaller than that of ID3v1. For example, a file contains the artist information
       * in ID3v1 but not in ID3v2. I want to complain about their tagging system but
       * iriver plus surprisingly reads this kind of tag information...
       *
       * libid3tag does not allow a program to choose ID3v1 or ID3v2 but prioritizes
       * ID3v2 all the time. We made a patch to obtain all kinds of tag information
       * stored in a file and use it when it's available.
       */
#ifdef      HAVE_LIBID3TAG_GETNTAG
      num_tags = id3_file_getnumtag(id3file);
      for (i = num_tags-1;i >= 0;i--) {
            id3tag = id3_file_gettag(id3file, i);
#else
      num_tags = 1;
      for (i = 0;i < num_tags;i++) {
            id3tag = id3_file_tag(id3file);
#endif/*HAVE_LIBID3TAG_GETTAG*/
            if (!id3tag) {
                  continue;
            }

            /* Obtain track number first. */
            ucs2 = get_frame_value(id3tag, "TRCK");
            if (ucs2 && *ucs2) {
                  info->tracknumer = ucs2toi(ucs2);
            } else {
                  info->tracknumer = 0;
            }
            free(ucs2);

            /* Obtain track title. */
            ucs2 = get_frame_value(id3tag, "TIT2");
            if (ucs2 && *ucs2) {
                  free(info->title);
                  info->title = ucs2dup(ucs2);
            }
            free(ucs2);

            /* Set artist field. */
            if (1)            ucs2 = get_frame_value(id3tag, "TPE1");
            if (!ucs2)  ucs2 = get_frame_value(id3tag, "TPE2");
            if (!ucs2)  ucs2 = get_frame_value(id3tag, "TPE3");
            if (!ucs2)  ucs2 = get_frame_value(id3tag, "TPE4");
            if (!ucs2)  ucs2 = get_frame_value(id3tag, "TCOM");
            if (ucs2 && *ucs2) {
                  free(info->artist);
                  info->artist = ucs2dup(ucs2);
            }
            free(ucs2);

            /* Set album field. */
            ucs2 = get_frame_value(id3tag, "TALB");
            if (ucs2 && *ucs2) {
                  free(info->album);
                  info->album = ucs2dup(ucs2);
            }
            free(ucs2);

            /*
             * TCMP (compilation flag) handling for omnibus albums.
             * This patch was originally submitted by Espen Matheussen.
             */
            ucs2 = get_frame_value(id3tag, "TCMP");
            if (ucs2 && *ucs2) {
                  int length = 0;
                  ucs2_char_t* title = NULL;
                  static ucs2_char_t separator[] = {' ','-',' ',0};
                  static ucs2_char_t va[] = {'V','a','r','i','o','u','s',' ','a','r','t','i','s','t','s',0};

                  /* Calculate the length of the new title. */
                  if (info->artist) {
                        length += ucs2len(info->artist);
                  }
                  length += ucs2len(separator);
                  if (info->title) {
                        length += ucs2len(info->title);
                  }

                  /* Construct the new title. */
                  title = ucs2malloc(sizeof(ucs2_char_t) * (length+1));
                  memset(title, 0, sizeof(ucs2_char_t) * (length+1));
                  if (info->artist) {
                        ucs2cpy(title, info->artist);
                  }
                  ucs2cat(title, separator);
                  if (info->title) {
                        ucs2cat(title, info->title);
                  }

                  /* Free the previous title and artist. */
                  ucs2free(info->title);
                  ucs2free(info->artist);

                  /* Set the new title and artist. */
                  info->title = title;
                  info->artist = ucs2dup(va);
            }

            /* Set genre field. */
            ucs2 = get_frame_value(id3tag, "TCON");
            if (ucs2 && *ucs2) {
                  free(info->genre);
                  info->genre = ucs2dup(ucs2);
            }
            free(ucs2);

            /* Set year field. */
            if (1)            ucs2 = get_frame_value(id3tag, "TYER");
            if (!ucs2)  ucs2 = get_frame_value(id3tag, "TDRC");
            if (ucs2 && *ucs2) {
                  info->year = ucs2toi(ucs2);
            } else {
                  info->year = 0;
            }
            free(ucs2);
      }

      info->filesize = ucs2stat_size(pathname);

      /* Parse MPEG audio header. */
      if (get_mp3stream_info(&mp3header, pathname, info->filesize) == 0) {
            int channels = mp3header.channel_mode == 3 ? 1 : 2;
            info->samplerate = mp3header.sample_rate;
            info->bitrate = mp3header.bitrate;
            info->duration = mp3header.duration;
      }

      id3_file_close(id3file);
      fclose(fp);

      if (!info->title || !*info->title) {
            ucs2free(info->title);
            info->title = gettag_setfilename(file);
      }

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index