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

gmi_wma.c

/*
 *      Tag and audio information retrieval from ASF files.
 *
 *      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_wma.c,v 1.14 2005/07/28 11:09:35 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 <memory.h>
#include <ucs2char.h>
#include <h10db.h>
#include <getmediainfo.h>

typedef unsigned char guid_t[16];

static guid_t g_guid_header_object =
      {0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66 ,0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C};
static guid_t g_guid_file_properties_object = 
      {0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65};
static guid_t g_guid_stream_properties = 
      {0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65};
static guid_t g_guid_codec_list = 
      {0x40, 0x52, 0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6};
static guid_t g_guid_content_description = 
      {0x33, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C};
static guid_t g_guid_extended_content_description = 
      {0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50};
static guid_t g_guid_audio_media = 
      {0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B};




/* Currently, only for little endian machines. */
int read_uint16le(uint16_t* val, FILE *fp)
{
      uint8_t v[2];
      int ret = (fread(v, sizeof(uint8_t), 2, fp) == 2);
      *val = (uint16_t)v[1] << 8 | (uint16_t)v[0];
      return ret;
}

int read_uint32le(uint32_t* val, FILE *fp)
{
      uint8_t v[4];
      int ret = (fread(v, sizeof(uint8_t), 4, fp) == 4);
      *val = (uint32_t)v[3] << 24 | (uint32_t)v[2] << 16 | (uint32_t)v[1] << 8 | (uint32_t)v[0];
      return ret;
}

int read_uint64le(uint32_t* high, uint32_t* low, FILE *fp)
{
      int ret = 1;
      ret &= read_uint32le(low, fp);
      ret &= read_uint32le(high, fp);
      return ret;
}

int read_guid(guid_t guid, FILE *fp)
{
      return fread(guid, sizeof(char), 16, fp) == 16;
}

int read_ucs2le_fixed(ucs2_char_t* val, int length, FILE *fp)
{
      int i, ret = 1;

      for (i = 0;i < length;i++) {
            ret &= read_uint16le(val++, fp);
      }
      *val = 0;
      return ret;
}

uint32_t read_field_as_uint32(uint16_t value_type, uint16_t value_size, FILE *fp)
{
      uint32_t value = 0;

      if (value_type == 0) {
            /* UCS2LE string type. */
            ucs2_char_t* str = (ucs2_char_t*)malloc(value_size+sizeof(ucs2_char_t));
            if (str) {
                  if (read_ucs2le_fixed(str, value_size/sizeof(ucs2_char_t), fp)) {
                        value = ucs2toi(str);
                  }
                  free(str);
            }
      } else if (value_type == 3 && value_size == sizeof(uint32_t)) {
            /* DWORD type. */
            read_uint32le(&value, fp);
      }
      return value;
}

int guid_equals(guid_t guid1, guid_t guid2)
{
      return memcmp(guid1, guid2, sizeof(unsigned char) * 16) == 0;
}

const ucs2_char_t* rewind_digit(const ucs2_char_t* begin, const ucs2_char_t* pos)
{
      /* Skip space. */
      pos--;
      while (begin <= pos) {
            if (!isucs2space(*pos)) {
                  break;
            }
            pos--;
      }
      /* Skip digits. */
      while (begin <= pos) {
            if (!isucs2digit(*pos)) {
                  pos++;
                  break;
            }
            pos--;
      }
      return pos;
}








int get_asf_audio_info(media_info* info, const ucs2_char_t *filename)
{
      uint32_t i;
      guid_t guid;
      uint32_t size_high, size_low;
      uint32_t num_objects;
      FILE *fp = NULL;
      
      /* Open the audio file. */
      fp = ucs2fopen(filename, "rb");
      if (!fp) {
            return -1;
      }

      /* Read "Header Object" */
      if (!read_guid(guid, fp)) {
            goto get_asf_audio_info_error;
      }
      /* Read object size. */
      if (!read_uint64le(&size_high, &size_low, fp)) {
            goto get_asf_audio_info_error;
      }
      /* Make sure that the GUID identifies "Header Object". */
      if (!guid_equals(guid, g_guid_header_object)) {
            goto get_asf_audio_info_error;
      }
      /* Read the number of objects in "Header Object". */
      if (!read_uint32le(&num_objects, fp)) {
            goto get_asf_audio_info_error;
      }
      /* Skip two reserved fields (BYTE x 2). */
      if (fseek(fp, 2, SEEK_CUR) != 0) {
            goto get_asf_audio_info_error;
      }

      /* Loop for objects. */
      for (i = 0;i < num_objects;i++) {
            /* We have to deal with:
                  *     - File Properties Object (duration)
                  *     - Stream Properties Object (stream type check)
                  *     - Codec List Object (bitrate, samplerate)
                  *     - Content Description Object (title, artist)
                  *     - Extended Content Description Object (album, genre)
                  */

            /* Store the current position. */
            long object_start = ftell(fp);

            /* Read GUID of the object. */
            if (!read_guid(guid, fp)) {
                  goto get_asf_audio_info_error;
            }
            /* Read object size. */
            if (!read_uint64le(&size_high, &size_low, fp)) {
                  goto get_asf_audio_info_error;
            }

            /* Check if the current object is relevant for us.  */
            if (guid_equals(guid, g_guid_file_properties_object)) {
                  /* File Properties Object (duration) */
                  uint32_t filesize_high, filesize_low;
                  uint32_t duration_high, duration_low;
                  uint32_t preroll_high, preroll_low;
                  double duration = 0;

                  if (fseek(fp, 16, SEEK_CUR) != 0) {
                        goto get_asf_audio_info_error;
                  }

                  /* Read the file size (QWORD) but ignore high DWORD. */
                  if (!read_uint64le(&filesize_high, &filesize_low, fp)) {
                        goto get_asf_audio_info_error;
                  }
                  info->filesize = filesize_low;

                  if (fseek(fp, 8+8, SEEK_CUR) != 0) {
                        goto get_asf_audio_info_error;
                  }

                  /* Read the duration in 100[ns]. */
                  if (!read_uint64le(&duration_high, &duration_low, fp)) {
                        goto get_asf_audio_info_error;
                  }

                  if (fseek(fp, 8, SEEK_CUR) != 0) {
                        goto get_asf_audio_info_error;
                  }

                  /* Read preroll in 1[ms]. */
                  if (!read_uint64le(&preroll_high, &preroll_low, fp)) {
                        goto get_asf_audio_info_error;
                  }

                  /* Calculate song duration in floating point. */
                  duration  = duration_low / 10000000.0;
                  duration += duration_high * 429.4967296;
                  duration -= preroll_low / 1000.0;
                  duration -= (preroll_high / 4294967.296);
                  info->duration = (uint32_t)duration;


            } else if (guid_equals(guid, g_guid_stream_properties)) {
                  /* Stream Properties Object (stream type check) */
                  guid_t guid_stream_type;

                  /* Read the GUID of the stream. */
                  if (!read_guid(guid_stream_type, fp)) {
                        goto get_asf_audio_info_error;
                  }
                  /* Make sure that current stream is audio. */
                  if (!guid_equals(guid_stream_type, g_guid_audio_media)) {
                        goto get_asf_audio_info_error;
                  }


            } else if (guid_equals(guid, g_guid_codec_list)) {
                  /* Codec List Object (bitrate, samplerate) */
                  uint32_t j, num_entries;

                  /* Skip reserved field. */
                  if (fseek(fp, 16, SEEK_CUR) != 0) {
                        goto get_asf_audio_info_error;
                  }

                  /* Obtain the number of entries that describe codec information. */
                  if (!read_uint32le(&num_entries, fp)) {
                        goto get_asf_audio_info_error;
                  }
                  
                  /* Search for the audio codec description to obtain samplerate, bitrate, etc. */
                  for (j = 0;j < num_entries;j++) {
                        uint16_t codec_type, codec_name_bytes, codec_description_bytes, codec_information_bytes;

                        if (!read_uint16le(&codec_type, fp)) {
                              goto get_asf_audio_info_error;
                        }
                        if (!read_uint16le(&codec_name_bytes, fp)) {
                              goto get_asf_audio_info_error;
                        }
                        if (fseek(fp, codec_name_bytes, SEEK_CUR) != 0) {
                              goto get_asf_audio_info_error;
                        }
                        if (!read_uint16le(&codec_description_bytes, fp)) {
                              goto get_asf_audio_info_error;
                        }

                        if (codec_type == 2) {
                              /* This description is for audio stream. */
                              ucs2_char_t* p = NULL;
                              ucs2_char_t* value = (ucs2_char_t*)malloc(codec_description_bytes+sizeof(ucs2_char_t));
                              if (!value) {
                                    goto get_asf_audio_info_error;
                              }
                              codec_description_bytes /= sizeof(ucs2_char_t);
                              if (!read_ucs2le_fixed(value, codec_description_bytes, fp)) {
                                    free(value);
                                    goto get_asf_audio_info_error;
                              }

                              /* Search for relevant information in the codec description,
                                 which has one or more null-terminated strings. */
                              for (p = value;p - value < codec_description_bytes;p += (ucs2len(p) + 1)) {
                                    static const ucs2_char_t ucs2cs_kbps[] = {'k','b','p','s',0};
                                    static const ucs2_char_t ucs2cs_khz[] = {'k','H','z', 0};
                                    ucs2_char_t* q = NULL;
                                    q = ucs2str(p, ucs2cs_kbps);
                                    if (q) {
                                          /* Obtain bitrate. */
                                          const ucs2_char_t* digit_string = rewind_digit(p, q);
                                          uint32_t digit = ucs2toi(digit_string);
                                          info->bitrate = digit * 1000;
                                    }
                                    q = ucs2str(p, ucs2cs_khz);
                                    if (q) {
                                          /* Obtain sample rate. */
                                          const ucs2_char_t* digit_string = rewind_digit(p, q);
                                          uint32_t digit = ucs2toi(digit_string);
                                          switch (digit) {
                                          case 8:
                                                info->samplerate = 8000;
                                                break;
                                          case 11:
                                                info->samplerate = 11025;
                                                break;
                                          case 12:
                                                info->samplerate = 12000;
                                                break;
                                          case 16:
                                                info->samplerate = 16000;
                                                break;
                                          case 22:
                                                info->samplerate = 22050;
                                                break;
                                          case 24:
                                                info->samplerate = 24000;
                                                break;
                                          case 32:
                                                info->samplerate = 32000;
                                                break;
                                          case 44:
                                                info->samplerate = 44100;
                                                break;
                                          case 48:
                                                info->samplerate = 48000;
                                                break;
                                          default:
                                                info->samplerate = digit * 1000;
                                          }
                                    }
                              }
                              free(value);
                        } else {
                              /* Discard descriptions for non-audio. */
                              if (fseek(fp, codec_description_bytes, SEEK_CUR) != 0) {
                                    goto get_asf_audio_info_error;
                              }
                        }
                        
                        if (!read_uint16le(&codec_information_bytes, fp)) {
                              goto get_asf_audio_info_error;
                        }
                        if (fseek(fp, codec_information_bytes, SEEK_CUR) != 0) {
                              goto get_asf_audio_info_error;
                        }
                  }


            } else if (guid_equals(guid, g_guid_content_description)) {
                  /* Content Description Object (title, artist) */
                  uint16_t title_bytes, author_bytes;

                  if (!read_uint16le(&title_bytes, fp)) {
                        goto get_asf_audio_info_error;
                  }
                  if (!read_uint16le(&author_bytes, fp)) {
                        goto get_asf_audio_info_error;
                  }
                  if (fseek(fp, 2+2+2, SEEK_CUR) != 0) {
                        goto get_asf_audio_info_error;
                  }

                  /* Release the buffer first to overwrite the information without memory leak. */
                  free(info->title);
                  info->title = (ucs2_char_t*)malloc(title_bytes+sizeof(ucs2_char_t));
                  if (!info->title || !read_ucs2le_fixed(info->title, title_bytes/sizeof(ucs2_char_t), fp)) {
                        goto get_asf_audio_info_error;
                  }

                  free(info->artist);
                  info->artist = (ucs2_char_t*)malloc(author_bytes+sizeof(ucs2_char_t));
                  if (!info->artist || !read_ucs2le_fixed(info->artist, author_bytes/sizeof(ucs2_char_t), fp)) {
                        goto get_asf_audio_info_error;
                  }


            } else if (guid_equals(guid, g_guid_extended_content_description)) {
                  /* Extended Content Description Object (album, genre, etc.) */
                  uint16_t j, num_descriptions;

                  if (!read_uint16le(&num_descriptions, fp)) {
                        goto get_asf_audio_info_error;
                  }

                  for (j = 0;j < num_descriptions;j++) {
                        static const ucs2_char_t ucs2cs_wm_albumartist[] = {'w','m','/','a','l','b','u','m','a','r','t','i','s','t',0};
                        static const ucs2_char_t ucs2cs_artist[] = {'a','r','t','i','s','t',0};
                        static const ucs2_char_t ucs2cs_wm_albumtitle[] = {'w','m','/','a','l','b','u','m','t','i','t','l','e',0};
                        static const ucs2_char_t ucs2cs_album[] = {'a','l','b','u','m',0};
                        static const ucs2_char_t ucs2cs_wm_genre[] = {'w','m','/','g','e','n','r','e',0};
                        static const ucs2_char_t ucs2cs_genre[] = {'g','e','n','r','e',0};
                        static const ucs2_char_t ucs2cs_wm_tracknumber[] = {'w','m','/','t','r','a','c','k','n','u','m','b','e','r',0};
                        static const ucs2_char_t ucs2cs_tracknumber[] = {'t','r','a','c','k','n','u','m','b','e','r',0};
                        static const ucs2_char_t ucs2cs_wm_track[] = {'w','m','/','t','r','a','c','k',0};
                        static const ucs2_char_t ucs2cs_wm_year[] = {'w','m','/','y','e','a','r',0};
                        static const ucs2_char_t ucs2cs_year[] = {'y','e','a','r',0};
                        static const ucs2_char_t ucs2cs_date[] = {'d','a','t','e',0};

                        uint16_t name_bytes, value_type, value_bytes;
                        ucs2_char_t* name = 0;

                        if (!read_uint16le(&name_bytes, fp)) {
                              goto get_asf_audio_info_error;
                        }
                        name = (ucs2_char_t*)malloc(name_bytes+sizeof(ucs2_char_t));
                        if (!name || !read_ucs2le_fixed(name, name_bytes/sizeof(ucs2_char_t), fp)) {
                              goto get_asf_audio_info_error;
                        }

                        if (!read_uint16le(&value_type, fp)) {
                              goto get_asf_audio_info_error;
                        }
                        if (!read_uint16le(&value_bytes, fp)) {
                              goto get_asf_audio_info_error;
                        }

                        /* If an artist name was extracted from Content Description, we don't parse wm/albumartist. */
                        if ((!info->artist || !info->artist[0]) && (ucs2icmp(name, ucs2cs_wm_albumartist) == 0 || ucs2icmp(name, ucs2cs_artist) == 0)) {
                              free(info->artist);
                              info->artist = (ucs2_char_t*)malloc(value_bytes+sizeof(ucs2_char_t));
                              if (!info->artist || !read_ucs2le_fixed(info->artist, value_bytes/sizeof(ucs2_char_t), fp)) {
                                    goto get_asf_audio_info_error;
                              }                                   
                        } else if (ucs2icmp(name, ucs2cs_wm_albumtitle) == 0 || ucs2icmp(name, ucs2cs_album) == 0) {
                              free(info->album);
                              info->album = (ucs2_char_t*)malloc(value_bytes+sizeof(ucs2_char_t));
                              if (!info->album || !read_ucs2le_fixed(info->album, value_bytes/sizeof(ucs2_char_t), fp)) {
                                    goto get_asf_audio_info_error;
                              }                                   
                        } else if (ucs2icmp(name, ucs2cs_wm_genre) == 0 || ucs2icmp(name, ucs2cs_genre) == 0) {
                              free(info->genre);
                              info->genre = (ucs2_char_t*)malloc(value_bytes+sizeof(ucs2_char_t));
                              if (!info->genre || !read_ucs2le_fixed(info->genre, value_bytes/sizeof(ucs2_char_t), fp)) {
                                    goto get_asf_audio_info_error;
                              }
                        } else if (ucs2icmp(name, ucs2cs_wm_tracknumber) == 0 || ucs2icmp(name, ucs2cs_tracknumber) == 0) {
                              info->tracknumer = read_field_as_uint32(value_type, value_bytes, fp);
                        } else if (ucs2icmp(name, ucs2cs_wm_track) == 0) {
                              info->tracknumer = read_field_as_uint32(value_type, value_bytes, fp) + 1;
                        } else if (ucs2icmp(name, ucs2cs_wm_year) == 0 || ucs2icmp(name, ucs2cs_year) == 0 || ucs2icmp(name, ucs2cs_date) == 0) {
                              info->year = read_field_as_uint32(value_type, value_bytes, fp);
                        } else {
                              if (fseek(fp, value_bytes, SEEK_CUR) != 0) {
                                    goto get_asf_audio_info_error;
                              }
                        }
                  }
            }

            if (fseek(fp, object_start + size_low, SEEK_SET) != 0) {
                  goto get_asf_audio_info_error;
            }
      }

      /* No read for actual stream data. Exit with success.  */
      fclose(fp);
      return 0;

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

int gettag_wma(media_info* info, const ucs2_char_t *path, const ucs2_char_t *file)
{
      int ret = 0;

      /* Obtain the filename. */
      ucs2_char_t pathname[MAX_PATH+1];
      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);

      ret = get_asf_audio_info(info, pathname);

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

      return ret;
}

Generated by  Doxygen 1.6.0   Back to index