Example #1
0
//used for streaming media
void ID3_TagImpl::ParseReader(ID3_Reader &reader)
{
  size_t mp3_core_size;
  size_t bytes_till_sync;

  io::WindowedReader wr(reader);
  wr.setBeg(wr.getCur());

  _file_tags.clear();
  _file_size = reader.getEnd();

  ID3_Reader::pos_type beg  = wr.getBeg();
  ID3_Reader::pos_type cur  = wr.getCur();
  ID3_Reader::pos_type end  = wr.getEnd();

  ID3_Reader::pos_type last = cur;

  if (_tags_to_parse.test(ID3TT_ID3V2))
  {
    do
    {
      last = cur;
      // Parse tags at the beginning of the file first...
      if (id3::v2::parse(*this, wr))
      {
        _file_tags.add(ID3TT_ID3V2);
      }
      cur  = wr.getCur();
      wr.setBeg(cur);
    } while (!wr.atEnd() && cur > last);
  }
  // add silly padding outside the tag to _prepended_bytes
  if (!wr.atEnd() && wr.peekChar() == '\0')
  {
    ID3D_NOTICE( "ID3_TagImpl::ParseReader(): found padding outside tag" );
    do
    {
      last = cur;
      cur = wr.getCur() + 1;
      wr.setBeg(cur);
      wr.setCur(cur);
    } while (!wr.atEnd() &&  cur > last && wr.peekChar() == '\0');
  }
  if (!wr.atEnd() && _file_size - (cur - beg) > 4 && wr.peekChar() == 255)
  { //unfortunatly, this is necessary for finding an invalid padding
    wr.setCur(cur + 1); //cur is known by peekChar
    if (wr.readChar() == '\0' && wr.readChar() == '\0' && wr.peekChar() == '\0')
    { //three empty bytes found, enough for me, this is stupid padding
      cur += 3; //those are now allready read in (excluding the peekChar, since it will be added by do{})
      do
      {
        last = cur;
        cur = wr.getCur() + 1;
        wr.setBeg(cur);
        wr.setCur(cur);
      } while (!wr.atEnd() &&  cur > last && wr.peekChar() == '\0');
    }
    else
      wr.setCur(cur);
  }
  _prepended_bytes = cur - beg;
  // go looking for the first sync byte to add to bytes_till_sync
  // by not adding it to _prepended_bytes, we preserve this 'unknown' data
  // The routine's only effect is helping the lib to find things as bitrate etc.
  beg  = wr.getBeg();
  if (!wr.atEnd() && wr.peekChar() != 0xFF) //no sync byte, so, either this is not followed by a mp3 file or it's a fLaC file, or an encapsulating format, better check it
  {
    ID3D_NOTICE( "ID3_TagImpl::ParseReader(): Didn't find mp3 sync byte" );
    if ((_file_size - (cur - beg)) >= 4)
    { //there is room to search for some kind of ID
      unsigned char buf[5];
      wr.readChars(buf, 4);
      buf[4] = '\0';
      // check for RIFF (an encapsulating format) ID
      if (strncmp((char*)buf, "RIFF", 4) == 0 || strncmp((char*)buf, "RIFX", 4) == 0)
      {
        // next 4 bytes are RIFF size, skip them
        cur = wr.getCur() + 4;
        wr.setCur(cur);
        // loop until first possible sync byte
        if (!wr.atEnd() && wr.peekChar() != 0xFF)
        {
          do
          {
            last = cur;
            cur = wr.getCur() + 1;
            wr.setCur(cur);
          } while (!wr.atEnd() &&  cur > last && wr.peekChar() != 0xFF);
        }
      }
      else if (strncmp((char*)buf, "fLaC", 4) == 0)
      { //a FLAC file, no need looking for a sync byte
        beg = cur;
      }
      else
      { //since we set the cursor 4 bytes ahead for looking for RIFF, RIFX or fLaC, better set it back
        // but peekChar allready checked the first one, so we add one
        cur = cur + 1;
        wr.setCur(cur);
        //go looking for a sync byte
        if (!wr.atEnd() && wr.peekChar() != 0xFF) //no sync byte, we have an unknown byte
        {
          do
          {
            last = cur;
            cur = wr.getCur() + 1;
            wr.setCur(cur);
          } while (!wr.atEnd() &&  cur > last && wr.peekChar() != 0xFF);
        }
      }
    } //if ((_file_size - (cur - beg)) >= 4)
    else
    { //remaining size is smaller than 4 bytes, can't be useful, but leave it for now
      beg = cur;
      //file.close();
      //return;
    }
  }
  bytes_till_sync = cur - beg;

  cur = wr.setCur(end);
  if (_file_size > _prepended_bytes)
  {
    do
    {
      last = cur;
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): beg = " << wr.getBeg() );
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): cur = " << wr.getCur() );
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): end = " << wr.getEnd() );
      // ...then the tags at the end
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): musicmatch? cur = " << wr.getCur() );
      if (_tags_to_parse.test(ID3TT_MUSICMATCH) && mm::parse(*this, wr))
      {
        ID3D_NOTICE( "ID3_TagImpl::ParseReader(): musicmatch! cur = " << wr.getCur() );
        _file_tags.add(ID3TT_MUSICMATCH);
        wr.setEnd(wr.getCur());
      }
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v1? cur = " << wr.getCur() );
      if (_tags_to_parse.test(ID3TT_LYRICS3) && lyr3::v1::parse(*this, wr))
      {
        ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v1! cur = " << wr.getCur() );
        _file_tags.add(ID3TT_LYRICS3);
        wr.setEnd(wr.getCur());
      }
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v2? cur = " << wr.getCur() );
      if (_tags_to_parse.test(ID3TT_LYRICS3V2) && lyr3::v2::parse(*this, wr))
      {
        ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v2! cur = " << wr.getCur() );
        _file_tags.add(ID3TT_LYRICS3V2);
        cur = wr.getCur();
        wr.setCur(wr.getEnd());//set to end to seek id3v1 tag
        //check for id3v1 tag and set End accordingly
        ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1? cur = " << wr.getCur() );
        if (_tags_to_parse.test(ID3TT_ID3V1) && id3::v1::parse(*this, wr))
        {
          ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1! cur = " << wr.getCur() );
          _file_tags.add(ID3TT_ID3V1);
        }
        wr.setCur(cur);
        wr.setEnd(cur);
      }
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1? cur = " << wr.getCur() );
      if (_tags_to_parse.test(ID3TT_ID3V1) && id3::v1::parse(*this, wr))
      {
        ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1! cur = " << wr.getCur() );
        wr.setEnd(wr.getCur());
        _file_tags.add(ID3TT_ID3V1);
      }
      cur = wr.getCur();
    } while (cur != last);
    _appended_bytes = end - cur;

    // Now get the mp3 header
    mp3_core_size = (_file_size - _appended_bytes) - (_prepended_bytes + bytes_till_sync);
    if (mp3_core_size >= 4)
    { //it has at least the size for a mp3 header (a mp3 header is 4 bytes)
      wr.setBeg(_prepended_bytes + bytes_till_sync);
      wr.setCur(_prepended_bytes + bytes_till_sync);
      wr.setEnd(_file_size - _appended_bytes);

      _mp3_info = new Mp3Info;
      ID3D_NOTICE( "ID3_TagImpl::ParseReader(): mp3header? cur = " << wr.getCur() );

      if (_mp3_info->Parse(wr, mp3_core_size))
      {
        ID3D_NOTICE( "ID3_TagImpl::ParseReader(): mp3header! cur = " << wr.getCur() );
      }
      else
      {
        delete _mp3_info;
        _mp3_info = NULL;
      }
    }
  }
  else
    this->SetPadding(false); //no need to pad an empty file
}
Example #2
0
bool id3::v2::parse(ID3_TagImpl& tag, ID3_Reader& reader)
{
  ID3_Reader::pos_type beg = reader.getCur();
  io::ExitTrigger et(reader);

  ID3_TagHeader hdr;

  io::WindowedReader wr(reader, ID3_TagHeader::SIZE);

  if (!hdr.Parse(wr) || wr.getCur() == beg)
  {
    ID3D_NOTICE( "id3::v2::parse(): parsing header failes" );
    return false;
  }
  if (hdr.GetExtended())
  {
    hdr.ParseExtended(reader);
  }
  tag.SetSpec(hdr.GetSpec());

  size_t dataSize = hdr.GetDataSize();
  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): dataSize = " << dataSize);

  wr.setWindow(wr.getCur(), dataSize);
  et.setExitPos(wr.getEnd());

  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window beg = " << wr.getBeg() );
  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window cur = " << wr.getCur() );
  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window end = " << wr.getEnd() );
  tag.SetExtended(hdr.GetExtended());
  if (!hdr.GetUnsync())
  {
    tag.SetUnsync(false);
    parseFrames(tag, wr);
  }
  else
  {
    // The buffer has been unsynced.  It will have to be resynced to be
    // readable.  This has to be done a character at a time.
    //
    // The original reader may be reading in characters from a file.  Doing
    // this a character at a time is quite slow.  To improve performance, read
    // in the entire buffer into a string, then create an UnsyncedReader from
    // the string.
    //
    // It might be better to implement a BufferedReader so that the details
    // of this can be abstracted away behind a class
    tag.SetUnsync(true);
    BString raw = io::readAllBinary(wr);
    io::BStringReader bsr(raw);
    io::UnsyncedReader ur(bsr);
    ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync beg = " << ur.getBeg() );
    ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync cur = " << ur.getCur() );
    ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync end = " << ur.getEnd() );

    // Now read the UnsyncedReader into another string, and parse the frames
    // from the string.  This is done so that 1. the unsynced reader is
    // unsynced exactly once, removing the possibility of multiple unsyncings
    // of the same string, and 2) so that calls to readChars aren't done a
    // character at a time for every call
    BString synced = io::readAllBinary(ur);
    io::BStringReader sr(synced);
    parseFrames(tag, sr);
  }

  return true;
}
Example #3
0
BString io::readAllBinary(ID3_Reader& reader)
{
  return readBinary(reader, reader.remainingBytes());
}
Example #4
0
void ID3_TagHeader::ParseExtended(ID3_Reader& reader)
{
  if (this->GetSpec() == ID3V2_3_0)
  {
/*
    Extended header size   $xx xx xx xx
    Extended Flags         $xx xx
    Size of padding        $xx xx xx xx
*/
    // skip over header size, we are not using it anyway, we calculate it
    reader.setCur(reader.getCur()+4); //Extended header size
    //io::readBENumber(reader, 4); //Extended header size
    uint16 tmpval = io::readBENumber(reader, 2); //Extended Flags
    // skip over padding size, we are not using it anyway
    reader.setCur(reader.getCur()+4); //Size of padding
    // io::readBENumber(reader, 4); //Size of padding
    if (tmpval != 0) //there is only one flag defined in ID3V2_3_0: crc
    {
      //skip over crc data, we are not using it anyway
      reader.setCur(reader.getCur()+4); //Crc
      //io::readBENumber(reader, 4); //Crc
      _info->extended_bytes = 14;
    }
    else
      _info->extended_bytes = 10;
  }
  if (this->GetSpec() == ID3V2_4_0)
  {
/*
    Extended header size   4 * %0xxxxxxx
    Number of flag bytes       $01
    Extended Flags             $xx
*/
    uint16 i;
    uint16 extrabytes;

    io::readUInt28(reader);
    const int extflagbytes = reader.readChar(); //Number of flag bytes
    ID3_Flags* extflags[1]; // ID3V2_4_0 has 1 flag byte, extflagbytes should be equal to 1
    for (i = 0; i < extflagbytes; ++i)
    {
      extflags[i] = new ID3_Flags;
      extflags[i]->set(reader.readChar()); //flags
    }
    extrabytes = 0;
    //extflags[0]->test(EXT_HEADER_FLAG_BIT1); // ID3V2_4_0 ext header flag bit 1 *should* be 0
    if (extflags[0]->test(EXT_HEADER_FLAG_BIT2))
    {
      // ID3V2_4_0 ext header flag bit 2 = Tag is an update
      // read size
      extrabytes += 1; // add a byte for the char containing the extflagdatasize
      const int extheaderflagdatasize = reader.readChar();
      extrabytes += extheaderflagdatasize;
      // Set the cursor right; we are not parsing the data, no-one is using extended flags anyway
      reader.setCur(reader.getCur() + extheaderflagdatasize);
      //reader.readChars(buf, extheaderflagdatasize); //buf should be at least 127 bytes = max extended header flagdata size
    }
   if (extflags[0]->test(EXT_HEADER_FLAG_BIT3))
   {
      // ID3V2_4_0 ext header flag bit 3 = CRC data present
      // read size
      extrabytes += 1; // add a byte for the char containing the extflagdatasize
      const int extheaderflagdatasize = reader.readChar();
      extrabytes += extheaderflagdatasize;
      // Set the cursor right; we are not parsing the data, no-one is using extended flags anyway
      reader.setCur(reader.getCur() + extheaderflagdatasize);
      //reader.readChars(buf, extheaderflagdatasize); //buf should be at least 127 bytes = max extended header flagdata size
    }
    if (extflags[0]->test(EXT_HEADER_FLAG_BIT4))
    {
      // ID3V2_4_0 ext header flag bit 4 = Tag restrictions
      // read size
      extrabytes += 1; // add a byte for the char containing the extflagdatasize
      const int extheaderflagdatasize = reader.readChar();
      extrabytes += extheaderflagdatasize;
      // Set the cursor right; we are not parsing the data, no-one is using extended flags anyway
      reader.setCur(reader.getCur() + extheaderflagdatasize);
      //reader.readChars(buf, extheaderflagdatasize); //buf should be at least 127 bytes = max extended header flagdata size
    }
    _info->extended_bytes = 5 + extflagbytes + extrabytes;
  }
  // a bit unorthodox, but since we are not using any of the extended header, but were merely
  // parsing it to get the cursor right, we delete it. Be Gone !
  _flags.set(HEADER_FLAG_EXTENDED, false);
  if (_info)
  {
    _data_size -= _info->extended_bytes;
    _info->extended_bytes = 0;
  }//else there is a tag with a higher or lower version than supported
}
Example #5
0
bool ID3_FrameImpl::Parse(ID3_Reader& reader) 
{ 
  io::ExitTrigger et(reader);
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): reader.getBeg() = " << reader.getBeg() );
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): reader.getCur() = " << reader.getCur() );
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): reader.getEnd() = " << reader.getEnd() );
  ID3_Reader::pos_type beg = reader.getCur();

  if (!_hdr.Parse(reader) || reader.getCur() == beg)  
  { 
    ID3D_WARNING( "ID3_FrameImpl::Parse(): no header to parse" );
    return false; 
  }
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): after hdr, getCur() = " << reader.getCur() );
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): found frame! id = " << _hdr.GetTextID() );

  // data is the part of the frame buffer that appears after the header  
  const size_t dataSize = _hdr.GetDataSize();
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): dataSize = " << dataSize );
  if (reader.getEnd() < beg + dataSize)
  {
    ID3D_WARNING( "ID3_FrameImpl::Parse(): not enough data to parse frame" );
    return false;
  }
  io::WindowedReader wr(reader, dataSize);
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): window getBeg() = " << wr.getBeg() );
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): window getCur() = " << wr.getCur() );
  ID3D_NOTICE( "ID3_FrameImpl::Parse(): window getEnd() = " << wr.getEnd() );
  
  unsigned long origSize = 0;
  if (_hdr.GetCompression())
  {
    origSize = io::readBENumber(reader, sizeof(uint32));
    ID3D_NOTICE( "ID3_FrameImpl::Parse(): frame is compressed, origSize = " << origSize );
  }

  if (_hdr.GetEncryption())
  {
    char ch = wr.readChar();
    this->SetEncryptionID(ch);
    ID3D_NOTICE( "ID3_FrameImpl::Parse(): frame is encrypted, encryption_id = " << (int) ch );
  }

  if (_hdr.GetGrouping())
  {
    char ch = wr.readChar();
    this->SetGroupingID(ch);
    ID3D_NOTICE( "ID3_FrameImpl::Parse(): frame is encrypted, grouping_id = " << (int) ch );
  }

  // set the type of frame based on the parsed header  
  this->_ClearFields(); 
  this->_InitFields(); 

  bool success = false;
  // expand out the data if it's compressed 
  if (!_hdr.GetCompression())
  {
    success = parseFields(wr, *this);
  }
  else
  {
    io::CompressedReader csr(wr, origSize);
    success = parseFields(csr, *this);
  }
  et.setExitPos(wr.getCur());

  _changed = false;
  return true;
} 
bool mm::parse(ID3_TagImpl& tag, ID3_Reader& rdr)
{
  io::ExitTrigger et(rdr);
  ID3_Reader::pos_type end = rdr.getCur();
  if (end < rdr.getBeg() + 48)
  {
    ID3D_NOTICE( "mm::parse: bailing, not enough bytes to parse, pos = " << end );
    return false;
  }
  
  rdr.setCur(end - 48);
  String version;
  
  {
    if (io::readText(rdr, 32) != "Brava Software Inc.             ")
    {
      ID3D_NOTICE( "mm::parse: bailing, couldn't find footer" );
      return false;
    }
    
    version = io::readText(rdr, 4);
    if (version.size() != 4 || 
        !isdigit(version[0]) || version[1] != '.' ||
        !isdigit(version[2]) || 
        !isdigit(version[3]))
    {
      ID3D_WARNING( "mm::parse: bailing, nonstandard version = " << version );
      return false;
    }
  }
    
  ID3_Reader::pos_type beg = rdr.setCur(end - 48);
  et.setExitPos(beg);
  if (end < 68)
  {
    ID3D_NOTICE( "mm::parse: bailing, not enough bytes to parse offsets, pos = " << end );
    return false;
  }
  rdr.setCur(end - 68);
    
  io::WindowedReader dataWindow(rdr);
  dataWindow.setEnd(rdr.getCur());

  size_t offsets[5];
    
  io::WindowedReader offsetWindow(rdr, 20);
  for (size_t i = 0; i < 5; ++i)
  {
    offsets[i] = io::readLENumber(rdr, sizeof(uint32));
  }

  size_t metadataSize = 0;
  if (version <= "3.00")
  {
    // All MusicMatch tags up to and including version 3.0 had metadata 
    // sections exactly 7868 bytes in length.
    metadataSize = 7868;
  }
  else
  {
    // MusicMatch tags after version 3.0 had three possible lengths for their
    // metadata sections.  We can determine which it was by searching for
    // the version section signature that should precede the metadata section
    // by exactly 256 bytes.
    size_t possibleSizes[] = { 8132, 8004, 7936 };
      
    for (size_t i = 0; i < sizeof(possibleSizes)/sizeof(size_t); ++i)
    {
      dataWindow.setCur(dataWindow.getEnd());
        
      // Our offset will be exactly 256 bytes prior to our potential metadata
      // section
      size_t offset = possibleSizes[i] + 256;
      if (dataWindow.getCur() < offset)
      {
        // if our filesize is less than the offset, then it can't possibly
        // be the correct offset, so try again.
        continue;
      }
      dataWindow.setCur(dataWindow.getCur() - offset);
        
      // now read in the signature to see if it's a match
      if (io::readText(dataWindow, 8) == "18273645")
      {
        metadataSize = possibleSizes[i];
        break;
      }
    }
  }
  if (0 == metadataSize)
  {
    // if we didn't establish a size for the metadata, then something is
    // wrong.  probably should log this.
    ID3D_WARNING( "mm::parse: bailing, couldn't find meta data signature, end = " << end );
    return false;
  }
    
  // parse the offset pointers to determine the actual sizes of all the 
  // sections
  size_t sectionSizes[5];
  size_t tagSize = metadataSize;
    
  // we already know the size of the last section
  sectionSizes[4] = metadataSize;
    
  size_t lastOffset = 0;
  for (int i = 0; i < 5; i++)
  {
    size_t thisOffset = offsets[i];
    //ASSERT(thisOffset > lastOffset);
    if (i > 0)
    {
      size_t sectionSize = thisOffset - lastOffset;
      sectionSizes[i-1] = sectionSize;
      tagSize += sectionSize;
    }
    lastOffset = thisOffset;
  }
    
  // now check to see that our tag size is reasonable
  if (dataWindow.getEnd() < tagSize)
  {
    // Ack!  The tag size doesn't jive with the tag's ending position in
    // the file.  Bail!
    ID3D_WARNING( "mm::parse: bailing, tag size is too big, tag size = " << tagSize << ", end = " << end );
    return false;
  }

  dataWindow.setBeg(dataWindow.getEnd() - tagSize);
  dataWindow.setCur(dataWindow.getBeg());
    
  // Now calculate the adjusted offsets
  offsets[0] = dataWindow.getBeg();
  for (size_t i = 0; i < 4; ++i)
  {
    offsets[i+1] = offsets[i] + sectionSizes[i];
  }
    
  // now check for a tag header and adjust the tag_beg pointer appropriately
  if (dataWindow.getBeg() >= 256)
  {
    rdr.setCur(dataWindow.getBeg() - 256);
    if (io::readText(rdr, 8) == "18273645")
    {
      et.setExitPos(rdr.getCur() - 8);
    }
    else
    {
      et.setExitPos(dataWindow.getBeg());
    }
    dataWindow.setCur(dataWindow.getBeg());
  }
    
  // Now parse the various sections...
    
  // Parse the image extension at offset 0
  dataWindow.setCur(offsets[0]);
  String imgExt = io::readTrailingSpaces(dataWindow, 4);
    
  // Parse the image binary at offset 1
  dataWindow.setCur(offsets[1]);
  uint32 imgSize = io::readLENumber(dataWindow, 4);
  if (imgSize == 0)
  {
    // no image binary.  don't do anything.
  }
  else
  {
    io::WindowedReader imgWindow(dataWindow, imgSize);
    if (imgWindow.getEnd() < imgWindow.getBeg() + imgSize)
    {
      // Ack!  The image size given extends beyond the next offset!  This is 
      // not good...  log?
    }
    else
    {
      BString imgData = io::readAllBinary(imgWindow);
      ID3_Frame* frame = new ID3_Frame(ID3FID_PICTURE);
      if (frame)
      {
        String mimetype("image/");
        mimetype += imgExt;
        frame->GetField(ID3FN_MIMETYPE)->Set(mimetype.c_str());
        frame->GetField(ID3FN_IMAGEFORMAT)->Set("");
        frame->GetField(ID3FN_PICTURETYPE)->Set(static_cast<unsigned int>(0));
        frame->GetField(ID3FN_DESCRIPTION)->Set("");
        frame->GetField(ID3FN_DATA)->Set(reinterpret_cast<const uchar*>(imgData.data()), imgData.size());
        tag.AttachFrame(frame);
      }
    }
  }
    
  //file.seekg(offsets[2]);
  //file.seekg(offsets[3]);
  dataWindow.setCur(offsets[4]);
    
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_TITLE));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_ALBUM));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_LEADARTIST));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_CONTENTTYPE));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Tempo"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Mood"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Situation"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Preference"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_SONGLEN));
    
  // The next 12 bytes can be ignored.  The first 8 represent the 
  // creation date as a 64 bit floating point number.  The last 4 are
  // for a play counter.
  dataWindow.skipChars(12);
    
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Path"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Serial"));
    
  // 2 bytes for track
  uint32 trkNum = io::readLENumber(dataWindow, 2);
  if (trkNum > 0)
  {
    String trkStr = toString(trkNum);
    ID3_Frame* frame = new ID3_Frame(ID3FID_TRACKNUM);
    if (frame)
    {
      frame->GetField(ID3FN_TEXT)->Set(trkStr.c_str());
      tag.AttachFrame(frame);
    }
  }
    
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Notes"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Bio"));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_UNSYNCEDLYRICS));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_WWWARTIST));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_WWWCOMMERCIALINFO));
  tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_ArtistEmail"));
    
  // email?

  return true;
}