/** Turns a binary tag into a series of ID3_Frame objects attached to the ** tag. ** ** \code ** ID3_Tag myTag; ** uchar header[ID3_TAGHEADERSIZE]; ** uchar *buffer; ** luint tagSize; ** ** // get ID3_TAGHEADERSIZE from a socket or somewhere ** // ... ** ** if ((tagSize = ID3_IsTagHeader(ourSourceBuffer)) > -1) ** { ** // read a further 'tagSize' bytes in ** // from our data source ** // ... ** ** if (buffer = new uchar[tagSize]) ** { ** // now we will call ID3_Tag::Parse() ** // with these values (explained later) ** myTag.Parse(header, buffer); ** ** // do something with the objects, ** // like look for titles, artists, etc. ** // ... ** ** // free the buffer ** delete [] buffer; ** } ** } ** \endcode ** ** \sa ID3_Frame ** @param header The byte header read in from the data source. ** @param buffer The remainder of the tag (not including the data source) ** read in from the data source. **/ size_t ID3_Tag::Parse(const uchar header[ID3_TAGHEADERSIZE], const uchar *buffer) { size_t size = ID3_Tag::IsV2Tag(header); if (0 == size) { return 0; } BString buf; buf.reserve(ID3_TagHeader::SIZE + size); buf.append(reinterpret_cast<const BString::value_type *>(header), ID3_TagHeader::SIZE); buf.append(reinterpret_cast<const BString::value_type *>(buffer), size); return this->Parse(buf.data(), buf.size()); }
io::CompressedReader::CompressedReader(ID3_Reader& reader, size_type newSize) : _uncompressed(new char_type[newSize]) { size_type oldSize = reader.remainingBytes(); BString binary = readBinary(reader, oldSize); int dwResult = ::uncompress(_uncompressed, reinterpret_cast<luint*>(&newSize), reinterpret_cast<const uchar*>(binary.data()), oldSize); if (dwResult != Z_OK) newSize = 0; this->setBuffer(_uncompressed, newSize); }
bool id3::v1::parse(ID3_TagImpl& tag, ID3_Reader& reader) { io::ExitTrigger et(reader); ID3_Reader::pos_type end = reader.getCur(); // posn ourselves at 128 bytes from the current position if (end < reader.getBeg() + ID3_V1_LEN) { ID3D_NOTICE( "id3::v1::parse: not enough bytes to parse, pos = " << end ); return false; } reader.setCur(end - ID3_V1_LEN); ID3_Reader::pos_type beg = reader.getCur(); //file.seekg(-static_cast<long>(ID3_V1_LEN), ios::cur); if (end != beg + ID3_V1_LEN) { ID3D_WARNING( "id3::v1::parse: failed to reposition " << ID3_V1_LEN << " bytes" ); return false; } // read the next 128 bytes in; String field = io::readText(reader, ID3_V1_LEN_ID); // check to see if it was a tag if (field != "TAG") { return false; } et.setExitPos(beg); // guess so, let's start checking the v2 tag for frames which are the // equivalent of the v1 fields. When we come across a v1 field that has // no current equivalent v2 frame, we create the frame, copy the data // from the v1 frame and attach it to the tag // (Scott Wheeler) The above comment was nice in theory, but it wasn't // first checking (before my hacks) to see if there already was v2 data. ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); String title = io::readTrailingSpaces(reader, ID3_V1_LEN_TITLE); field = id3::v2::getTitle(tag); if (title.size() > 0 && (field.size() == 0 || field == "")) { id3::v2::setTitle(tag, title); } ID3D_NOTICE( "id3::v1::parse: title = \"" << title << "\"" ); ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); String artist = io::readTrailingSpaces(reader, ID3_V1_LEN_ARTIST); field = id3::v2::getArtist(tag); if (artist.size() > 0 && (field.size() == 0 || field == "")) { id3::v2::setArtist(tag, artist); } ID3D_NOTICE( "id3::v1::parse: artist = \"" << artist << "\"" ); ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); String album = io::readTrailingSpaces(reader, ID3_V1_LEN_ALBUM); field = id3::v2::getAlbum(tag); if (album.size() > 0 && (field.size() == 0 || field == "")) { id3::v2::setAlbum(tag, album); } ID3D_NOTICE( "id3::v1::parse: album = \"" << title << "\"" ); ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); String year = io::readTrailingSpaces(reader, ID3_V1_LEN_YEAR); field = id3::v2::getYear(tag); if (year.size() > 0 && (field.size() == 0 || field == "")) { id3::v2::setYear(tag, year); } ID3D_NOTICE( "id3::v1::parse: year = \"" << year << "\"" ); ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); String comment = io::readTrailingSpaces(reader, ID3_V1_LEN_COMMENT-2); // fixes bug for when tracknumber is 0x20 BString trackno = io::readBinary(reader, ID3_V1_LEN_COMMENT-28); if (trackno[0] == '\0') { if (trackno[1] != '\0') { //we've got a tracknumber size_t track = trackno[1]; field = id3::v2::getTrack(tag); if (field.size() == 0 || field == "00") { id3::v2::setTrack(tag, track, 0); } ID3D_NOTICE( "id3::v1::parse: track = \"" << track << "\"" ); ID3D_NOTICE( "id3::v1::parse: comment length = \"" << comment.length() << "\"" ); } } else { // trackno[0] != '\0' const int paddingsize = (ID3_V1_LEN_COMMENT-2) - comment.size(); const char * padding = " "; //28 spaces if (trackno[1] == '\0' || trackno[1] == 0x20 && trackno[0] != 0x20) { // if there used to be spaces they are gone now, we need to rebuild them comment.append(padding, paddingsize); comment.append((const char *)trackno.data(), 1); } else if (trackno[1] != '\0' && trackno[1] != 0x20 && trackno[0] != 0x20) { // if there used to be spaces they are gone now, we need to rebuild them comment.append(padding, paddingsize); comment.append((const char *)trackno.data(), 2); } } ID3D_NOTICE( "id3::v1::parse: comment = \"" << comment << "\"" ); if (comment.size() > 0) { id3::v2::setComment(tag, comment, STR_V1_COMMENT_DESC, "XXX"); } ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); // the GENRE field/frame uchar genre = reader.readChar(); field = id3::v2::getGenre(tag); if (genre != 0xFF && (field.size() == 0 || field == "")) { id3::v2::setGenre(tag, genre); } ID3D_NOTICE( "id3::v1::parse: genre = \"" << (int) genre << "\"" ); ID3D_NOTICE("id3::v1::parse: read bytes: " << reader.getCur() - beg); 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()); uint32 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; }
int main(size_t argc, const char** argv) { ID3D_INIT_DOUT(); ID3D_INIT_WARNING(); ID3D_INIT_NOTICE(); ID3_IStreamReader isr(cin); BString orig = io::readAllBinary(isr); cout << "input size: " << orig.size() << endl; cout << endl; cout << "=== Testing Synchronization ===" << endl; BString synced; { io::BStringReader sr(orig); io::UnsyncedReader ur(sr); synced = io::readAllBinary(ur); } cout << "synced size: " << synced.size() << endl; BString unsynced; { io::BStringWriter sw(unsynced); io::UnsyncedWriter uw(sw); uw.writeChars(synced.data(), synced.size()); } cout << "unsynced size: " << unsynced.size() << endl; BString resynced; { io::BStringReader sr(unsynced); io::UnsyncedReader ur(sr); resynced = io::readAllBinary(ur); } cout << "resynced size: " << resynced.size() << endl; if (unsynced == orig) { cout << "orig == unsynced" << endl; } else { cout << "orig != unsynced" << endl; } if (synced == resynced) { cout << "synced == resynced" << endl; } else { cout << "synced != resynced" << endl; } cout << endl; cout << "=== Testing Trailing Spaces ===" << endl; String text; { io::StringWriter sw(text); io::writeTrailingSpaces (sw, "hello, world", 50); } cout << "new text = \"" << text << "\"" << endl; String origText; { io::StringReader sr(text); origText = io::readTrailingSpaces(sr, 100); } cout << "orig text = \"" << origText << "\"" << endl; cout << endl; cout << "=== Testing Binary Numbers ===" << endl; String number; { io::StringWriter sw(number); io::writeBENumber(sw, 1234567890, 4); } cout << "binary number:"; for (size_t i = 0; i < number.size(); ++i) { cout << " 0x" << hex << (size_t) (0xFF & number[i]) << dec; } cout << endl; size_t val; { io::StringReader sr(number); val = io::readBENumber(sr, 4); } cout << "orig number: " << val << endl; cout << endl; cout << "=== Testing Compression ===" << endl; String compressed; size_t origSize = orig.size(); cout << "origSize = " << origSize << endl; { io::StringWriter sw(compressed); io::CompressedWriter cw(sw); cw.writeChars(orig.data(), orig.size()); cw.flush(); if (origSize != cw.getOrigSize()) { origSize = cw.getOrigSize(); cout << "cw.getOrigSize() = " << origSize << endl; } } cout << "compressed size = " << compressed.size() << endl; BString uncompressed; if (origSize == 0) { cout << "no compression" << endl; } else { io::StringReader sr(compressed); io::CompressedReader cr(sr, origSize); uncompressed = io::readAllBinary(cr); cout << "uncompressed size = " << uncompressed.size() << endl; } if (uncompressed.size() == orig.size()) { if (uncompressed == orig) { cout << "orig == uncompressed" << endl; } else { cout << "orig != uncompressed" << endl; } } String ascii; ascii.reserve(8192); for (size_t i = 0; i < 8192; ++i) { ascii += 'a' + (i % 26); } cout << endl; cout << "ascii.size() = " << ascii.size() << endl; String unicode = dami::convert(ascii, ID3TE_ASCII, ID3TE_UTF16BE); cout << "uncicode.size() = " << unicode.size() << endl; String ascii_2 = dami::convert(unicode, ID3TE_UTF16BE, ID3TE_ASCII); if (ascii != ascii_2) { cout << "ascii != ascii_2" << endl; } else { cout << "ascii == ascii_2" << endl; } return 0; }