bool MP4::Tag::save() { ByteVector data; for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { const String name = i->first; if(name.startsWith("----")) { data.append(renderFreeForm(name, i->second)); } else if(name == "trkn") { data.append(renderIntPair(name.data(String::Latin1), i->second)); } else if(name == "disk") { data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second)); } else if(name == "cpil" || name == "pgap" || name == "pcst") { data.append(renderBool(name.data(String::Latin1), i->second)); } else if(name == "tmpo") { data.append(renderInt(name.data(String::Latin1), i->second)); } else if(name == "covr") { data.append(renderCovr(name.data(String::Latin1), i->second)); } else if(name.size() == 4){ data.append(renderText(name.data(String::Latin1), i->second)); } else { debug("MP4: Unknown item name \"" + name + "\""); } } data = renderAtom("ilst", data); AtomList path = d->atoms->path("moov", "udta", "meta", "ilst"); if(path.size() == 4) { saveExisting(data, path); } else { saveNew(data); } return true; }
ByteVector UnsynchronizedLyricsFrame::renderFields() const { ByteVector v; v.append(char(d->textEncoding)); v.append(d->language.size() == 3 ? d->language : "XXX"); v.append(d->description.data(d->textEncoding)); v.append(textDelimiter(d->textEncoding)); v.append(d->text.data(d->textEncoding)); return v; }
void Ogg::XiphComment::parse(const ByteVector &data) { // The first thing in the comment data is the vendor ID length, followed by a // UTF8 string with the vendor ID. uint pos = 0; const uint vendorLength = data.toUInt(0, false); pos += 4; d->vendorID = String(data.mid(pos, vendorLength), String::UTF8); pos += vendorLength; // Next the number of fields in the comment vector. const uint commentFields = data.toUInt(pos, false); pos += 4; if(commentFields > (data.size() - 8) / 4) { return; } for(uint i = 0; i < commentFields; i++) { // Each comment field is in the format "KEY=value" in a UTF8 string and has // 4 bytes before the text starts that gives the length. const uint commentLength = data.toUInt(pos, false); pos += 4; String comment = String(data.mid(pos, commentLength), String::UTF8); pos += commentLength; if(pos > data.size()) { break; } int commentSeparatorPosition = comment.find("="); if(commentSeparatorPosition == -1) { break; } String key = comment.substr(0, commentSeparatorPosition); String value = comment.substr(commentSeparatorPosition + 1); addField(key, value, false); } }
void ID3v2::Tag::setAlbumArt(const ByteVector &v, ID3v2::AttachedPictureFrame::Type arttype, String &mimetype) { if (v.isEmpty()) { removeFrames("APIC"); return; } else { if (!d->frameListMap["APIC"].isEmpty()) { removeFrames("APIC"); } else { // do nothing } AttachedPictureFrame *f = new AttachedPictureFrame("APIC"); f->setMimeType(mimetype); f->setType(arttype); f->setPicture(v); addFrame(f); } return; }
bool FileStream::writeBlock(const ByteVector &data) { if(!isOpen()) { debug("FileStream::writeBlock() -- invalid file."); return false; } if(readOnly()) { debug("FileStream::writeBlock() -- read only file."); return false; } size_t nbBytes = writeFile(d->file, data); if (nbBytes != data.size()) { debug("FileStream::writeBlock() error, 0 bytes written."); return false; } return true; }
ByteVectorList ByteVectorList::split(const ByteVector &v, const ByteVector &pattern, int byteAlign, int max) { ByteVectorList l; uint previousOffset = 0; for(int offset = v.find(pattern, 0, byteAlign); offset != -1 && (max == 0 || max > int(l.size()) + 1); offset = v.find(pattern, offset + pattern.size(), byteAlign)) { if(offset - previousOffset > 1) l.append(v.mid(previousOffset, offset - previousOffset)); else l.append(ByteVector::null); previousOffset = offset + pattern.size(); } if(previousOffset < v.size()) l.append(v.mid(previousOffset, v.size() - previousOffset)); return l; }
ByteVectorList ByteVectorList::split( const ByteVector &v, const ByteVector &pattern, size_t byteAlign, size_t max) { ByteVectorList l; size_t previousOffset = 0; for(size_t offset = v.find(pattern, 0, byteAlign); offset != ByteVector::npos() && (max == 0 || max > l.size() + 1); offset = v.find(pattern, offset + pattern.size(), byteAlign)) { if(offset - previousOffset >= 1) l.append(v.mid(previousOffset, offset - previousOffset)); else l.append(ByteVector()); previousOffset = offset + pattern.size(); } if(previousOffset < v.size()) l.append(v.mid(previousOffset, v.size() - previousOffset)); return l; }
ByteVector UnsynchronizedLyricsFrame::renderFields() const { ByteVector v; //=== Alex. There is a problem with the Writing of UNICODE strings. That's why we manually set the encoding String::Type encoding = String::UTF8; v.append(char(encoding)); v.append(d->language.size() == 3 ? d->language : "XXX"); v.append(d->description.data(encoding)); v.append(textDelimiter(encoding)); v.append(d->text.data(encoding)); //=== Alex END. There is a problem return v; }
void Header::parse(const ByteVector &data) { if(data.size() < size()) return; // do some sanity checking -- even in ID3v2.3.0 and less the tag size is a // synch-safe integer, so all bytes must be less than 128. If this is not // true then this is an invalid tag. // note that we're doing things a little out of order here -- the size is // later in the bytestream than the version ByteVector sizeData = data.mid(6, 4); if(sizeData.size() != 4) { d->tagSize = 0; debug("TagLib::ID3v2::Header::parse() - The tag size as read was 0 bytes!"); return; } for(ByteVector::Iterator it = sizeData.begin(); it != sizeData.end(); it++) { if(uchar(*it) >= 128) { d->tagSize = 0; debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128."); return; } } // The first three bytes, data[0..2], are the File Identifier, "ID3". (structure 3.1 "file identifier") // Read the version number from the fourth and fifth bytes. d->majorVersion = data[3]; // (structure 3.1 "major version") d->revisionNumber = data[4]; // (structure 3.1 "revision number") // Read the flags, the first four bits of the sixth byte. std::bitset<8> flags(data[5]); d->unsynchronisation = flags[7]; // (structure 3.1.a) d->extendedHeader = flags[6]; // (structure 3.1.b) d->experimentalIndicator = flags[5]; // (structure 3.1.c) d->footerPresent = flags[4]; // (structure 3.1.d) // Get the size from the remaining four bytes (read above) d->tagSize = SynchData::toUInt(sizeData); // (structure 3.1 "size") }
void testUpdateStco() { string filename = copyFile("no-tags", ".3g2"); MP4::File *f = new MP4::File(filename.c_str()); f->tag()->setArtist(ByteVector(3000, 'x')); ByteVectorList data1; { MP4::Atoms a(f); MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; f->seek(stco->offset + 12); ByteVector data = f->readBlock(stco->length - 12); unsigned int count = data.mid(0, 4).toUInt(); int pos = 4; while (count--) { unsigned int offset = data.mid(pos, 4).toUInt(); f->seek(offset); data1.append(f->readBlock(20)); pos += 4; } } f->save(); delete f; f = new MP4::File(filename.c_str()); { MP4::Atoms a(f); MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; f->seek(stco->offset + 12); ByteVector data = f->readBlock(stco->length - 12); unsigned int count = data.mid(0, 4).toUInt(); int pos = 4, i = 0; while (count--) { unsigned int offset = data.mid(pos, 4).toUInt(); f->seek(offset); CPPUNIT_ASSERT_EQUAL(data1[i], f->readBlock(20)); pos += 4; i++; } } delete f; deleteFile(filename); }
ByteVector CommentsFrame::renderFields() const { ByteVector v; String::Type encoding = d->textEncoding; encoding = checkEncoding(d->description, encoding); encoding = checkEncoding(d->text, encoding); v.append(char(encoding)); v.append(d->language.size() == 3 ? d->language : "XXX"); v.append(d->description.data(encoding)); v.append(textDelimiter(encoding)); v.append(d->text.data(encoding)); return v; }
ByteVector OwnershipFrame::renderFields() const { StringList sl; sl.append(d->seller); const String::Type encoding = checkTextEncoding(sl, d->textEncoding); ByteVector v; v.append(char(encoding)); v.append(d->pricePaid.data(String::Latin1)); v.append(textDelimiter(String::Latin1)); v.append(d->datePurchased.data(String::Latin1)); v.append(d->seller.data(encoding)); return v; }
MP4::AtomDataList MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm) { AtomDataList result; ByteVector data = d->file->readBlock(atom->length - 8); int i = 0; unsigned int pos = 0; while(pos < data.size()) { const int length = static_cast<int>(data.toUInt(pos)); if(length < 12) { debug("MP4: Too short atom"); return result; } const ByteVector name = data.mid(pos + 4, 4); const int flags = static_cast<int>(data.toUInt(pos + 8)); if(freeForm && i < 2) { if(i == 0 && name != "mean") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"mean\""); return result; } else if(i == 1 && name != "name") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"name\""); return result; } result.append(AtomData(AtomDataType(flags), data.mid(pos + 12, length - 12))); } else { if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); return result; } if(expectedFlags == -1 || flags == expectedFlags) { result.append(AtomData(AtomDataType(flags), data.mid(pos + 16, length - 16))); } } pos += length; i++; } return result; }
ByteVector RelativeVolumeFrame::renderFields() const { ByteVector data; data.append(d->identification.data(String::Latin1)); data.append(textDelimiter(String::Latin1)); Map<ChannelType, ChannelData>::ConstIterator it = d->channels.begin(); for(; it != d->channels.end(); ++it) { ChannelType type = (*it).first; const ChannelData &channel = (*it).second; data.append(char(type)); data.append(ByteVector::fromShort(channel.volumeAdjustment)); data.append(char(channel.peakVolume.bitsRepresentingPeak)); data.append(channel.peakVolume.peakVolume); } return data; }
void TextIdentificationFrame::parseFields(const ByteVector &data) { // read the string data type (the first byte of the field data) d->textEncoding = String::Type(data[0]); // split the byte array into chunks based on the string type (two byte delimiter // for unicode encodings) int byteAlign = d->textEncoding == String::Latin1 || d->textEncoding == String::UTF8 ? 1 : 2; ByteVectorList l = ByteVectorList::split(data.mid(1), textDelimiter(d->textEncoding), byteAlign); d->fieldList.clear(); // append those split values to the list and make sure that the new string's // type is the same specified for this frame for(ByteVectorList::Iterator it = l.begin(); it != l.end(); it++) { String s(*it, d->textEncoding); d->fieldList.append(s); } }
ByteVector UnsynchronizedLyricsFrame::renderFields() const { StringList sl; sl.append(d->description); sl.append(d->text); const String::Type encoding = checkTextEncoding(sl, d->textEncoding); ByteVector v; v.append(char(encoding)); v.append(d->language.size() == 3 ? d->language : "XXX"); v.append(d->description.data(encoding)); v.append(textDelimiter(encoding)); v.append(d->text.data(encoding)); return v; }
// --------- typed value to specific type conversions bool Registry::ValueToStringList(const ByteVector& value, StringList& list) { ByteVector::const_iterator str_start= value.begin(); for (ByteVector::const_iterator i= value.begin() ; i!=value.end() ; ++i) { if (*i==0) { list.push_back(string(str_start, i)); str_start= i+1; } } if (str_start!=value.end()) { list.push_back(string(str_start, value.end())); str_start= value.end(); } return true; }
/*----------------------------------------------------------------------------*/ void DDAProtocol :: pushData(unsigned char header, const DDAProtocol :: ByteVector &data) { ByteVector txData; unsigned size = data.size(); unsigned char crc = 0; txData.push_back(ENQ); txData.push_back(header); crc += header; txData.push_back(size + 3); crc += size + 3; for(unsigned i = 0; i < size; i++) { txData.push_back(data[i]); crc += data[i]; } txData.push_back(crc); m_txData.push_back(txData); }
void TableOfContentsFrame::parseFields(const ByteVector &data) { uint size = data.size(); if(size < 6) { debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated by null."); return; } int pos = 0, embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; uint entryCount = data.at(pos++); for(uint i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); childElementID.append(char(0)); d->childElements.append(childElementID); } size -= pos; while((uint)embPos < size - Frame::headerSize(4)) { Frame *frame = d->factory->createFrame(data.mid(pos + embPos)); if(!frame) return; // Checks to make sure that frame parsed correctly. if(frame->size() <= 0) { delete frame; return; } embPos += frame->size() + Frame::headerSize(4); addEmbeddedFrame(frame); } }
void ChapterFrame::parseFields(const ByteVector &data) { unsigned int size = data.size(); if(size < 18) { debug("A CHAP frame must contain at least 18 bytes (1 byte element ID " "terminated by null and 4x4 bytes for start and end time and offset)."); return; } int pos = 0; unsigned int embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->startTime = data.toUInt(pos, true); pos += 4; d->endTime = data.toUInt(pos, true); pos += 4; d->startOffset = data.toUInt(pos, true); pos += 4; d->endOffset = data.toUInt(pos, true); pos += 4; size -= pos; // Embedded frames are optional if(size < header()->size()) return; while(embPos < size - header()->size()) { Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0)); if(!frame) return; // Checks to make sure that frame parsed correctly. if(frame->size() <= 0) { delete frame; return; } embPos += frame->size() + header()->size(); addEmbeddedFrame(frame); } }
void ByteVectorStream::insert(const ByteVector &data, long long start, size_t replace) { if(data.size() < replace) { removeBlock(start + data.size(), replace - data.size()); } else if(data.size() > replace) { const size_t sizeDiff = data.size() - replace; truncate(length() + sizeDiff); const size_t readPosition = static_cast<size_t>(start + replace); const size_t writePosition = static_cast<size_t>(start + data.size()); ::memmove( d->data.data() + writePosition, d->data.data() + readPosition, static_cast<size_t>(length() - sizeDiff - readPosition)); } seek(start); writeBlock(data); }
ByteVector Footer::render(bool isHeader) const { ByteVector v; // add the file identifier -- "APETAGEX" v.append(fileIdentifier()); // add the version number -- we always render a 2.000 tag regardless of what // the tag originally was. v.append(ByteVector::fromUInt(2000, false)); // add the tag size v.append(ByteVector::fromUInt(d->tagSize, false)); // add the item count v.append(ByteVector::fromUInt(d->itemCount, false)); // render and add the flags std::bitset<32> flags; flags[31] = d->headerPresent; flags[30] = false; // footer is always present flags[29] = isHeader; v.append(ByteVector::fromUInt(flags.to_ulong(), false)); // add the reserved 64bit v.append(ByteVector::fromLongLong(0)); return v; }
ByteVector ID3v2::Tag::render() const { // We need to render the "tag data" first so that we have to correct size to // render in the tag's header. The "tag data" -- everything that is included // in ID3v2::Header::tagSize() -- includes the extended header, frames and // padding, but does not include the tag's header or footer. ByteVector tagData; // TODO: Render the extended header. // Loop through the frames rendering them and adding them to the tagData. for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) { if ((*it)->header()->frameID().size() != 4) { debug("A frame of unsupported or unknown type \'" + String((*it)->header()->frameID()) + "\' has been discarded"); continue; } if(!(*it)->header()->tagAlterPreservation()) tagData.append((*it)->render()); } // Compute the amount of padding, and append that to tagData. uint paddingSize = 0; uint originalSize = d->header.tagSize(); if(tagData.size() < originalSize) paddingSize = originalSize - tagData.size(); else paddingSize = 1024; tagData.append(ByteVector(paddingSize, char(0))); // Set the tag size. d->header.setTagSize(tagData.size()); // TODO: This should eventually include d->footer->render(). return d->header.render() + tagData; }
ByteVectorList MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) { ByteVectorList result; ByteVector data = file->readBlock(atom->length - 8); int i = 0; unsigned int pos = 0; while(pos < data.size()) { int length = data.mid(pos, 4).toUInt(); ByteVector name = data.mid(pos + 4, 4); int flags = data.mid(pos + 8, 4).toUInt(); if(freeForm && i < 2) { if(i == 0 && name != "mean") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"mean\""); return result; } else if(i == 1 && name != "name") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"name\""); return result; } result.append(data.mid(pos + 12, length - 12)); } else { if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); return result; } if(expectedFlags == -1 || flags == expectedFlags) { result.append(data.mid(pos + 16, length - 16)); } } pos += length; i++; } return result; }
ByteVector TableOfContentsFrame::renderFields() const { ByteVector data; data.append(d->elementID); char flags = 0; if(d->isTopLevel) flags += 2; if(d->isOrdered) flags += 1; data.append(flags); data.append((char)(entryCount())); ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); it++; } FrameList l = d->embeddedFrameList; for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) data.append((*it)->render()); return data; }
void MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) { MP4::CoverArtList value; ByteVector data = file->readBlock(atom->length - 8); unsigned int pos = 0; while(pos < data.size()) { int length = data.mid(pos, 4).toUInt(); ByteVector name = data.mid(pos + 4, 4); int flags = data.mid(pos + 8, 4).toUInt(); if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); return; } if(flags == MP4::CoverArt::PNG || flags == MP4::CoverArt::JPEG) { value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), data.mid(pos + 16, length - 16))); } pos += length; } if(value.size() > 0) d->items.insert(atom->name, value); }
void APE::Properties::analyzeOld() { ByteVector header = d->file->readBlock(26); uint totalFrames = header.mid(18, 4).toUInt(false); // Fail on 0 length APE files (catches non-finalized APE files) if(totalFrames == 0) return; short compressionLevel = header.mid(0, 2).toShort(false); uint blocksPerFrame; if(d->version >= 3950) blocksPerFrame = 73728 * 4; else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000)) blocksPerFrame = 73728; else blocksPerFrame = 9216; d->channels = header.mid(4, 2).toShort(false); d->sampleRate = header.mid(6, 4).toUInt(false); uint finalFrameBlocks = header.mid(22, 4).toUInt(false); uint totalBlocks = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; d->length = totalBlocks / d->sampleRate; d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; }
String Frame::readStringField(const ByteVector &data, String::Type encoding, int *position) { int start = 0; if(!position) position = &start; ByteVector delimiter = textDelimiter(encoding); int end = data.find(delimiter, *position, delimiter.size()); if(end < *position) return String::null; String str; if(encoding == String::Latin1) str = Tag::latin1StringHandler()->parse(data.mid(*position, end - *position)); else str = String(data.mid(*position, end - *position), encoding); *position = end + delimiter.size(); return str; }
void Frame::Header::setFrameID(const ByteVector &id) { d->frameID = id.mid(0, 4); }
void Frame::Header::setData(const ByteVector &data, uint version) { d->version = version; switch(version) { case 0: case 1: case 2: { // ID3v2.2 if(data.size() < 3) { debug("You must at least specify a frame ID."); return; } // Set the frame ID -- the first three bytes d->frameID = data.mid(0, 3); // If the full header information was not passed in, do not continue to the // steps to parse the frame size and flags. if(data.size() < 6) { d->frameSize = 0; return; } d->frameSize = data.mid(3, 3).toUInt(); break; } case 3: { // ID3v2.3 if(data.size() < 4) { debug("You must at least specify a frame ID."); return; } // Set the frame ID -- the first four bytes d->frameID = data.mid(0, 4); // If the full header information was not passed in, do not continue to the // steps to parse the frame size and flags. if(data.size() < 10) { d->frameSize = 0; return; } // Set the size -- the frame size is the four bytes starting at byte four in // the frame header (structure 4) d->frameSize = data.mid(4, 4).toUInt(); { // read the first byte of flags std::bitset<8> flags(data[8]); d->tagAlterPreservation = flags[7]; // (structure 3.3.1.a) d->fileAlterPreservation = flags[6]; // (structure 3.3.1.b) d->readOnly = flags[5]; // (structure 3.3.1.c) } { // read the second byte of flags std::bitset<8> flags(data[9]); d->compression = flags[7]; // (structure 3.3.1.i) d->encryption = flags[6]; // (structure 3.3.1.j) d->groupingIdentity = flags[5]; // (structure 3.3.1.k) } break; } case 4: default: { // ID3v2.4 if(data.size() < 4) { debug("You must at least specify a frame ID."); return; } // Set the frame ID -- the first four bytes d->frameID = data.mid(0, 4); // If the full header information was not passed in, do not continue to the // steps to parse the frame size and flags. if(data.size() < 10) { d->frameSize = 0; return; } // Set the size -- the frame size is the four bytes starting at byte four in // the frame header (structure 4) d->frameSize = SynchData::toUInt(data.mid(4, 4)); #ifndef NO_ITUNES_HACKS // iTunes writes v2.4 tags with v2.3-like frame sizes if(d->frameSize > 127) { if(!isValidFrameID(data.mid(d->frameSize + 10, 4))) { unsigned int uintSize = data.mid(4, 4).toUInt(); if(isValidFrameID(data.mid(uintSize + 10, 4))) { d->frameSize = uintSize; } } } #endif { // read the first byte of flags std::bitset<8> flags(data[8]); d->tagAlterPreservation = flags[6]; // (structure 4.1.1.a) d->fileAlterPreservation = flags[5]; // (structure 4.1.1.b) d->readOnly = flags[4]; // (structure 4.1.1.c) } { // read the second byte of flags std::bitset<8> flags(data[9]); d->groupingIdentity = flags[6]; // (structure 4.1.2.h) d->compression = flags[3]; // (structure 4.1.2.k) d->encryption = flags[2]; // (structure 4.1.2.m) d->unsynchronisation = flags[1]; // (structure 4.1.2.n) d->dataLengthIndicator = flags[0]; // (structure 4.1.2.p) } break; } } }