// Remove all comments from the tag with the given description size_t id3::v2::removeComments(ID3_TagImpl& tag, String desc) { size_t numRemoved = 0; for (ID3_TagImpl::iterator iter = tag.begin(); iter != tag.end(); ++iter) { ID3_Frame* frame = *iter; if (frame == NULL) { continue; } if (frame->GetID() == ID3FID_COMMENT) { // See if the description we have matches the description of the // current comment. If so, remove the comment String tmpDesc = getString(frame, ID3FN_DESCRIPTION); if (tmpDesc == desc) { frame = tag.RemoveFrame(frame); delete frame; numRemoved++; } } } return numRemoved; }
String id3::v2::getV1Comment(const ID3_TagImpl& tag) { ID3_Frame* frame; (frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, STR_V1_COMMENT_DESC)) || (frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, "" )) || (frame = tag.Find(ID3FID_COMMENT)); return getString(frame, ID3FN_TEXT); }
BString id3::v2::getSyncLyrics(const ID3_TagImpl& tag, String lang, String desc) { // check if a SYLT frame of this language or descriptor exists ID3_Frame* frame = NULL; (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_LANGUAGE, lang.c_str())) || (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_DESCRIPTION, desc.c_str())) || (frame = tag.Find(ID3FID_SYNCEDLYRICS)); // get the lyrics size ID3_Field* fld = frame->GetField(ID3FN_DATA); return BString(reinterpret_cast<const BString::value_type *>(fld->GetRawBinary()), fld->Size()); }
size_t id3::v2::removeFrames(ID3_TagImpl& tag, ID3_FrameID id) { size_t numRemoved = 0; ID3_Frame* frame = NULL; while ((frame = tag.Find(id)) != NULL) { frame = tag.RemoveFrame(frame); delete frame; numRemoved++; } return numRemoved; }
void id3::v2::render(ID3_Writer& writer, const ID3_TagImpl& tag) { // There has to be at least one frame for there to be a tag... if (tag.NumFrames() == 0) { ID3D_WARNING( "id3::v2::render(): no frames to render" ); return; } ID3D_NOTICE( "id3::v2::render(): rendering" ); ID3_TagHeader hdr; hdr.SetSpec(tag.GetSpec()); hdr.SetExtended(tag.GetExtended()); hdr.SetExperimental(tag.GetExperimental()); // set up the encryption and grouping IDs // ... String frms; io::StringWriter frmWriter(frms); if (!tag.GetUnsync()) { ID3D_NOTICE( "id3::v2::render(): rendering frames" ); renderFrames(frmWriter, tag); hdr.SetUnsync(false); } else { ID3D_NOTICE( "id3::v2::render(): rendering unsynced frames" ); io::UnsyncedWriter uw(frmWriter); renderFrames(uw, tag); uw.flush(); ID3D_NOTICE( "id3::v2::render(): numsyncs = " << uw.getNumSyncs() ); hdr.SetUnsync(uw.getNumSyncs() > 0); } size_t frmSize = frms.size(); if (frmSize == 0) { ID3D_WARNING( "id3::v2::render(): rendered frame size is 0 bytes" ); return; } // zero the remainder of the buffer so that our padding bytes are zero luint nPadding = tag.PaddingSize(frmSize); ID3D_NOTICE( "id3::v2::render(): padding size = " << nPadding ); hdr.SetDataSize(frmSize + nPadding); hdr.Render(writer); writer.writeChars(frms.data(), frms.size()); for (size_t i = 0; i < nPadding; ++i) { if (writer.writeChar('\0') == ID3_Writer::END_OF_WRITER) { break; } } }
size_t id3::v2::removeArtists(ID3_TagImpl& tag) { size_t numRemoved = 0; ID3_Frame* frame = NULL; while ((frame = hasArtist(tag)) != NULL) { frame = tag.RemoveFrame(frame); delete frame; numRemoved++; } return numRemoved; }
size_t RenderV1ToFile(ID3_TagImpl& tag, fstream& file) { if (!file) { return 0; } // Heck no, this is stupid. If we do not read in an initial V1(.1) // header then we are constantly appending new V1(.1) headers. Files // can get very big that way if we never overwrite the old ones. // if (ID3_V1_LEN > tag.GetAppendedBytes()) - Daniel Hazelbaker if (ID3_V1_LEN > tag.GetFileSize()) { file.seekp(0, ios::end); } else { // We want to check if there is already an id3v1 tag, so we can write over // it. First, seek to the beginning of any possible id3v1 tag file.seekg(0-ID3_V1_LEN, ios::end); char sID[ID3_V1_LEN_ID]; // Read in the TAG characters file.read(sID, ID3_V1_LEN_ID); // If those three characters are TAG, then there's a preexisting id3v1 tag, // so we should set the file cursor so we can overwrite it with a new tag. if (memcmp(sID, "TAG", ID3_V1_LEN_ID) == 0) { file.seekp(0-ID3_V1_LEN, ios::end); } // Otherwise, set the cursor to the end of the file so we can append on // the new tag. else { file.seekp(0, ios::end); } } ID3_IOStreamWriter out(file); id3::v1::render(out, tag); return ID3_V1_LEN; }
size_t RenderV1ToFile(ID3_TagImpl& tag, fstream& file) { if (!file) { return 0; } if (ID3_V1_LEN > tag.GetAppendedBytes()) { file.seekp(0, ios::end); } else { // We want to check if there is already an id3v1 tag, so we can write over // it. First, seek to the beginning of any possible id3v1 tag file.seekg(0-ID3_V1_LEN, ios::end); char sID[ID3_V1_LEN_ID]; // Read in the TAG characters file.read(sID, ID3_V1_LEN_ID); // If those three characters are TAG, then there's a preexisting id3v1 tag, // so we should set the file cursor so we can overwrite it with a new tag. if (memcmp(sID, "TAG", ID3_V1_LEN_ID) == 0) { file.seekp(0-ID3_V1_LEN, ios::end); } // Otherwise, set the cursor to the end of the file so we can append on // the new tag. else { file.seekp(0, ios::end); } } ID3_IOStreamWriter out(file); id3::v1::render(out, tag); return ID3_V1_LEN; }
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; }
size_t RenderV2ToFile(const ID3_TagImpl& tag, fstream& file) { ID3D_NOTICE( "RenderV2ToFile: starting" ); if (!file) { ID3D_WARNING( "RenderV2ToFile: error in file" ); return 0; } String tagString; io::StringWriter writer(tagString); id3::v2::render(writer, tag); ID3D_NOTICE( "RenderV2ToFile: rendered v2" ); const char* tagData = tagString.data(); size_t tagSize = tagString.size(); // if the new tag fits perfectly within the old and the old one // actually existed (ie this isn't the first tag this file has had) if ((!tag.GetPrependedBytes() && !ID3_GetDataSize(tag)) || (tagSize == tag.GetPrependedBytes())) { file.seekp(0, ios::beg); file.write(tagData, tagSize); } else { String filename = tag.GetFileName(); #if !defined HAVE_MKSTEMP // This section is for Windows folk FILE *tempOut = tmpfile(); if (NULL == tempOut) { // log this return 0; //ID3_THROW(ID3E_ReadOnly); } fwrite(tagData, 1, tagSize, tempOut); file.seekg(tag.GetPrependedBytes(), ios::beg); uchar tmpBuffer[BUFSIZ]; while (!file) { file.read((char *)tmpBuffer, BUFSIZ); size_t nBytes = file.gcount(); fwrite(tmpBuffer, 1, nBytes, tempOut); } rewind(tempOut); openWritableFile(filename, file); while (!feof(tempOut)) { size_t nBytes = fread(tmpBuffer, 1, BUFSIZ, tempOut); file.write((char *)tmpBuffer, nBytes); } fclose(tempOut); #else // else we gotta make a temp file, copy the tag into it, copy the // rest of the old file after the tag, delete the old file, rename // this new file to the old file's name and update the handle String sTmpSuffix = ".XXXXXX"; if (filename.size() + sTmpSuffix.size() > ID3_PATH_LENGTH) { // log this return 0; //ID3_THROW_DESC(ID3E_NoFile, "filename too long"); } char sTempFile[ID3_PATH_LENGTH]; strcpy(sTempFile, filename.c_str()); strcat(sTempFile, sTmpSuffix.c_str()); int fd = mkstemp(sTempFile); if (fd < 0) { remove(sTempFile); //ID3_THROW_DESC(ID3E_NoFile, "couldn't open temp file"); } ofstream tmpOut(fd); if (!tmpOut) { tmpOut.close(); remove(sTempFile); return 0; // log this //ID3_THROW(ID3E_ReadOnly); } tmpOut.write(tagData, tagSize); file.seekg(tag.GetPrependedBytes(), ios::beg); uchar tmpBuffer[BUFSIZ]; while (file) { file.read(tmpBuffer, BUFSIZ); size_t nBytes = file.gcount(); tmpOut.write(tmpBuffer, nBytes); } tmpOut.close(); file.close(); remove(filename.c_str()); rename(sTempFile, filename.c_str()); openWritableFile(filename, file); #endif } return tagSize; }
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; }
size_t RenderV2ToFile(const ID3_TagImpl& tag, fstream& file) { ID3D_NOTICE( "RenderV2ToFile: starting" ); if (!file) { ID3D_WARNING( "RenderV2ToFile: error in file" ); return 0; } String tagString; io::StringWriter writer(tagString); id3::v2::render(writer, tag); ID3D_NOTICE( "RenderV2ToFile: rendered v2" ); const char* tagData = tagString.data(); size_t tagSize = tagString.size(); // if the new tag fits perfectly within the old and the old one // actually existed (ie this isn't the first tag this file has had) if ((!tag.GetPrependedBytes() && !ID3_GetDataSize(tag)) || (tagSize == tag.GetPrependedBytes())) { file.seekp(0, ios::beg); file.write(tagData, tagSize); } else { String filename = tag.GetFileName(); String sTmpSuffix = ".XXXXXX"; if (filename.size() + sTmpSuffix.size() > ID3_PATH_LENGTH) { // log this return 0; //ID3_THROW_DESC(ID3E_NoFile, "filename too long"); } char sTempFile[ID3_PATH_LENGTH]; strcpy(sTempFile, filename.c_str()); strcat(sTempFile, sTmpSuffix.c_str()); #if ((defined(__GNUC__) && __GNUC__ >= 3 ) || !defined(HAVE_MKSTEMP)) // This section is for Windows folk && gcc 3.x folk fstream tmpOut; createFile(sTempFile, tmpOut); tmpOut.write(tagData, tagSize); file.seekg(tag.GetPrependedBytes(), ios::beg); char *tmpBuffer[BUFSIZ]; while (!file.eof()) { file.read((char *)tmpBuffer, BUFSIZ); size_t nBytes = file.gcount(); tmpOut.write((char *)tmpBuffer, nBytes); } #else //((defined(__GNUC__) && __GNUC__ >= 3 ) || !defined(HAVE_MKSTEMP)) // else we gotta make a temp file, copy the tag into it, copy the // rest of the old file after the tag, delete the old file, rename // this new file to the old file's name and update the handle int fd = mkstemp(sTempFile); if (fd < 0) { remove(sTempFile); //ID3_THROW_DESC(ID3E_NoFile, "couldn't open temp file"); } ofstream tmpOut(fd); if (!tmpOut) { tmpOut.close(); remove(sTempFile); return 0; // log this //ID3_THROW(ID3E_ReadOnly); } tmpOut.write(tagData, tagSize); file.seekg(tag.GetPrependedBytes(), ios::beg); uchar tmpBuffer[BUFSIZ]; while (file) { file.read(tmpBuffer, BUFSIZ); size_t nBytes = file.gcount(); tmpOut.write(tmpBuffer, nBytes); } close(fd); //closes the file #endif ////((defined(__GNUC__) && __GNUC__ >= 3 ) || !defined(HAVE_MKSTEMP)) tmpOut.close(); file.close(); // the following sets the permissions of the new file // to be the same as the original #if defined(HAVE_SYS_STAT_H) struct stat fileStat; if(stat(filename.c_str(), &fileStat) == 0) { #endif //defined(HAVE_SYS_STAT_H) remove(filename.c_str()); rename(sTempFile, filename.c_str()); #if defined(HAVE_SYS_STAT_H) chmod(filename.c_str(), fileStat.st_mode); } #endif //defined(HAVE_SYS_STAT_H) // file = tmpOut; file.clear();//to clear the eof mark openWritableFile(filename, file); } return tagSize; }
String id3::v2::getFrameText(const ID3_TagImpl& tag, ID3_FrameID id) { ID3_Frame* frame = tag.Find(id); return getString(frame, ID3FN_TEXT); }
String id3::v2::getComment(const ID3_TagImpl& tag, String desc) { ID3_Frame* frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, desc.c_str()); return getString(frame, ID3FN_TEXT); }
size_t ID3_GetDataSize(const ID3_TagImpl& tag) { return tag.GetFileSize() - tag.GetPrependedBytes() - tag.GetAppendedBytes(); }