//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 }
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; }
BString io::readAllBinary(ID3_Reader& reader) { return readBinary(reader, reader.remainingBytes()); }
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 }
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; }