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 = String(data.mid(*position, end - *position), encoding); *position = end + delimiter.size(); return str; }
void TextIdentificationFrame::parseFields(const ByteVector &data) { // Don't try to parse invalid frames if(data.size() < 2) return; // 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; // build a small counter to strip nulls off the end of the field int dataLength = data.size() - 1; while(dataLength > 0 && data[dataLength] == 0) dataLength--; while(dataLength % byteAlign != 0) dataLength++; ByteVectorList l = ByteVectorList::split(data.mid(1, dataLength), 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++) { if(!(*it).isEmpty()) { String s(*it, d->textEncoding); d->fieldList.append(s); } } }
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 AttachedPictureFrame::parseFields(const ByteVector &data) { if(data.size() < 5) { debug("A picture frame must contain at least 5 bytes."); return; } d->textEncoding = String::Type(data[0]); int pos = 1; d->mimeType = readStringField(data, String::Latin1, &pos); /* Now we need at least two more bytes available */ if (uint(pos) + 1 >= data.size()) { debug("Truncated picture frame."); return; } d->type = (TagLib::ID3v2::AttachedPictureFrame::Type)data[pos++]; d->description = readStringField(data, d->textEncoding, &pos); d->data = data.mid(pos); }
void ChapterFrame::parseFields(const ByteVector &data) { TagLib::uint 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, embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); 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; while((uint)embPos < size - header()->size()) { Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); 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 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); } }
List<Ogg::Page *> Ogg::Page::paginate(const ByteVectorList &packets, PaginationStrategy strategy, uint streamSerialNumber, int firstPage, bool firstPacketContinued, bool lastPacketCompleted, bool containsLastPacket) { List<Page *> l; int totalSize = 0; for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) totalSize += (*it).size(); // Handle creation of multiple pages with appropriate pagination. if(strategy == Repaginate || totalSize + packets.size() > 255 * 255) { // SPLITSIZE must be a multiple of 255 in order to get the lacing values right // create pages of about 8KB each #define SPLITSIZE (32*255) int pageIndex = 0; for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) { bool continued = false; // mark very first packet? if(firstPacketContinued && it==packets.begin()) { continued = true; } // append to buf ByteVector packetBuf; packetBuf.append(*it); while(packetBuf.size() > SPLITSIZE) { // output a Page ByteVector packetForOnePage; packetForOnePage.resize(SPLITSIZE); std::copy(packetBuf.begin(), packetBuf.begin() + SPLITSIZE, packetForOnePage.begin()); ByteVectorList packetList; packetList.append(packetForOnePage); Page *p = new Page(packetList, streamSerialNumber, firstPage+pageIndex, continued, false, false); l.append(p); pageIndex++; continued = true; packetBuf = packetBuf.mid(SPLITSIZE); } ByteVectorList::ConstIterator jt = it; ++jt; bool lastPacketInList = (jt == packets.end()); // output a page for the rest (we output one packet per page, so this one should be completed) ByteVectorList packetList; packetList.append(packetBuf); bool isVeryLastPacket = false; if(containsLastPacket) { // mark the very last output page as last of stream ByteVectorList::ConstIterator jt = it; ++jt; if(jt == packets.end()) { isVeryLastPacket = true; } } Page *p = new Page(packetList, streamSerialNumber, firstPage+pageIndex, continued, lastPacketInList ? lastPacketCompleted : true, isVeryLastPacket); pageIndex++; l.append(p); } } else { Page *p = new Page(packets, streamSerialNumber, firstPage, firstPacketContinued, lastPacketCompleted, containsLastPacket); l.append(p); } return l; }
void ExtendedHeader::parse(const ByteVector &data) { d->size = SynchData::toUInt(data.mid(0, 4)); // (structure 3.2 "Extended header size") }
void CommentsFrame::setLanguage(const ByteVector &languageEncoding) { d->language = languageEncoding.mid(0, 3); }
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) : AudioProperties(style) { d = new PropertiesPrivate; MP4::Atom *moov = atoms->find("moov"); if(!moov) { debug("MP4: Atom 'moov' not found"); return; } MP4::Atom *trak = 0; ByteVector data; MP4::AtomList trakList = moov->findall("trak"); for (unsigned int i = 0; i < trakList.size(); i++) { trak = trakList[i]; MP4::Atom *hdlr = trak->find("mdia", "hdlr"); if(!hdlr) { debug("MP4: Atom 'trak.mdia.hdlr' not found"); return; } file->seek(hdlr->offset); data = file->readBlock(hdlr->length); if(data.mid(16, 4) == "soun") { break; } trak = 0; } if (!trak) { debug("MP4: No audio tracks"); return; } MP4::Atom *mdhd = trak->find("mdia", "mdhd"); if(!mdhd) { debug("MP4: Atom 'trak.mdia.mdhd' not found"); return; } file->seek(mdhd->offset); data = file->readBlock(mdhd->length); if(data[8] == 0) { unsigned int unit = data.mid(20, 4).toUInt(); unsigned int length = data.mid(24, 4).toUInt(); if (unit == 0) { debug("MP4: No valid data"); d->length = 0; } else { d->length = length / unit; } } else { long long unit = data.mid(28, 8).toLongLong(); long long length = data.mid(36, 8).toLongLong(); if (unit == 0) { debug("MP4: No valid data"); d->length = 0; } else { d->length = int(length / unit); } } MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); if(!atom) { return; } file->seek(atom->offset); data = file->readBlock(atom->length); if(data.mid(20, 4) == "mp4a") { d->channels = data.mid(40, 2).toShort(); d->bitsPerSample = data.mid(42, 2).toShort(); d->sampleRate = data.mid(46, 4).toUInt(); if(data.mid(56, 4) == "esds" && data[64] == 0x03) { long pos = 65; if(data.mid(pos, 3) == "\x80\x80\x80") { pos += 3; } pos += 4; if(data[pos] == 0x04) { pos += 1; if(data.mid(pos, 3) == "\x80\x80\x80") { pos += 3; } pos += 10; d->bitrate = (data.mid(pos, 4).toUInt() + 500) / 1000; } } } }
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) : AudioProperties(style) { d = new PropertiesPrivate; MP4::Atom *moov = atoms->find("moov"); if(!moov) { debug("MP4: Atom 'moov' not found"); return; } MP4::Atom *trak = 0; ByteVector data; MP4::AtomList trakList = moov->findall("trak"); for (unsigned int i = 0; i < trakList.size(); i++) { trak = trakList[i]; MP4::Atom *hdlr = trak->find("mdia", "hdlr"); if(!hdlr) { debug("MP4: Atom 'trak.mdia.hdlr' not found"); return; } file->seek(hdlr->offset); data = file->readBlock(hdlr->length); if(data.mid(16, 4) == "soun") { break; } trak = 0; } if (!trak) { debug("MP4: No audio tracks"); return; } MP4::Atom *mdhd = trak->find("mdia", "mdhd"); if(!mdhd) { debug("MP4: Atom 'trak.mdia.mdhd' not found"); return; } file->seek(mdhd->offset); data = file->readBlock(mdhd->length); uint version = data[8]; if(version == 1) { if (data.size() < 36 + 8) { debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); return; } const long long unit = data.toLongLong(28U); const long long length = data.toLongLong(36U); d->length = unit ? int(length / unit) : 0; } else { if (data.size() < 24 + 4) { debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); return; } const unsigned int unit = data.toUInt(20U); const unsigned int length = data.toUInt(24U); d->length = unit ? length / unit : 0; } MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); if(!atom) { return; } file->seek(atom->offset); data = file->readBlock(atom->length); if(data.mid(20, 4) == "mp4a") { d->channels = data.toShort(40U); d->bitsPerSample = data.toShort(42U); d->sampleRate = data.toUInt(46U); if(data.mid(56, 4) == "esds" && data[64] == 0x03) { uint pos = 65; if(data.mid(pos, 3) == "\x80\x80\x80") { pos += 3; } pos += 4; if(data[pos] == 0x04) { pos += 1; if(data.mid(pos, 3) == "\x80\x80\x80") { pos += 3; } pos += 10; d->bitrate = (data.toUInt(pos) + 500) / 1000; } } } else if (data.mid(20, 4) == "alac") { if (atom->length == 88 && data.mid(56, 4) == "alac") { d->bitsPerSample = data.at(69); d->channels = data.at(73); d->bitrate = data.toUInt(80U) / 1000; d->sampleRate = data.toUInt(84U); } } MP4::Atom *drms = atom->find("drms"); if(drms) { d->encrypted = true; } }
void ID3v2::Tag::parse(const ByteVector &origData) { ByteVector data = origData; if(d->header.unsynchronisation() && d->header.majorVersion() <= 3) data = SynchData::decode(data); uint frameDataPosition = 0; uint frameDataLength = data.size(); // check for extended header if(d->header.extendedHeader()) { if(!d->extendedHeader) d->extendedHeader = new ExtendedHeader; d->extendedHeader->setData(data); if(d->extendedHeader->size() <= data.size()) { frameDataPosition += d->extendedHeader->size(); frameDataLength -= d->extendedHeader->size(); } } // check for footer -- we don't actually need to parse it, as it *must* // contain the same data as the header, but we do need to account for its // size. if(d->header.footerPresent() && Footer::size() <= frameDataLength) frameDataLength -= Footer::size(); // parse frames // Make sure that there is at least enough room in the remaining frame data for // a frame header. while(frameDataPosition < frameDataLength - Frame::headerSize(d->header.majorVersion())) { // If the next data is position is 0, assume that we've hit the padding // portion of the frame data. if(data.at(frameDataPosition) == 0) { if(d->header.footerPresent()) debug("Padding *and* a footer found. This is not allowed by the spec."); d->paddingSize = frameDataLength - frameDataPosition; return; } Frame *frame = d->factory->createFrame(data.mid(frameDataPosition), &d->header); if(!frame) return; // Checks to make sure that frame parsed correctly. if(frame->size() <= 0) { delete frame; return; } frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion()); addFrame(frame); } }
bool FLAC::Picture::parse(const ByteVector &data) { if(data.size() < 32) { debug("A picture block must contain at least 5 bytes."); return false; } int pos = 0; d->type = TagLib::ID3v2::AttachedPictureFrame::Type(data.mid(pos, 4).toUInt()); pos += 4; uint mimeTypeLength = data.mid(pos, 4).toUInt(); pos += 4; if(pos + mimeTypeLength + 24 > data.size()) { debug("Invalid picture block."); return false; } d->mimeType = String(data.mid(pos, mimeTypeLength), String::UTF8); pos += mimeTypeLength; uint descriptionLength = data.mid(pos, 4).toUInt(); pos += 4; if(pos + descriptionLength + 20 > data.size()) { debug("Invalid picture block."); return false; } d->description = String(data.mid(pos, descriptionLength), String::UTF8); pos += descriptionLength; d->width = data.mid(pos, 4).toUInt(); pos += 4; d->height = data.mid(pos, 4).toUInt(); pos += 4; d->colorDepth = data.mid(pos, 4).toUInt(); pos += 4; d->numColors = data.mid(pos, 4).toUInt(); pos += 4; uint dataLength = data.mid(pos, 4).toUInt(); pos += 4; if(pos + dataLength > data.size()) { debug("Invalid picture block."); return false; } d->data = data.mid(pos, dataLength); return true; }
Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHeader) const { ByteVector data = origData; unsigned int version = tagHeader->majorVersion(); Frame::Header *header = new Frame::Header(data, version); ByteVector frameID = header->frameID(); // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1 // characters. Also make sure that there is data in the frame. if(frameID.size() != (version < 3 ? 3 : 4) || header->frameSize() <= static_cast<unsigned int>(header->dataLengthIndicator() ? 4 : 0) || header->frameSize() > data.size()) { delete header; return 0; } #ifndef NO_ITUNES_HACKS if(version == 3 && frameID.size() == 4 && frameID[3] == '\0') { // iTunes v2.3 tags store v2.2 frames - convert now frameID = frameID.mid(0, 3); header->setFrameID(frameID); header->setVersion(2); updateFrame(header); header->setVersion(3); } #endif for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) { if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) { delete header; return 0; } } if(version > 3 && (tagHeader->unsynchronisation() || header->unsynchronisation())) { // Data lengths are not part of the encoded data, but since they are synch-safe // integers they will be never actually encoded. ByteVector frameData = data.mid(Frame::Header::size(version), header->frameSize()); frameData = SynchData::decode(frameData); data = data.mid(0, Frame::Header::size(version)) + frameData; } // TagLib doesn't mess with encrypted frames, so just treat them // as unknown frames. #if !defined(HAVE_ZLIB) || HAVE_ZLIB == 0 if(header->compression()) { debug("Compressed frames are currently not supported."); return new UnknownFrame(data, header); } #endif if(header->encryption()) { debug("Encrypted frames are currently not supported."); return new UnknownFrame(data, header); } if(!updateFrame(header)) { header->setTagAlterPreservation(true); return new UnknownFrame(data, header); } // updateFrame() might have updated the frame ID. frameID = header->frameID(); // This is where things get necissarily nasty. Here we determine which // Frame subclass (or if none is found simply an Frame) based // on the frame ID. Since there are a lot of possibilities, that means // a lot of if blocks. // Text Identification (frames 4.2) // Apple proprietary WFED (Podcast URL) is in fact a text frame. if(frameID.startsWith("T") || frameID == "WFED") { TextIdentificationFrame *f = frameID != "TXXX" ? new TextIdentificationFrame(data, header) : new UserTextIdentificationFrame(data, header); d->setTextEncoding(f); if(frameID == "TCON") updateGenre(f); return f; } // Comments (frames 4.10) if(frameID == "COMM") { CommentsFrame *f = new CommentsFrame(data, header); d->setTextEncoding(f); return f; } // Attached Picture (frames 4.14) if(frameID == "APIC") { AttachedPictureFrame *f = new AttachedPictureFrame(data, header); d->setTextEncoding(f); return f; } // ID3v2.2 Attached Picture if(frameID == "PIC") { AttachedPictureFrame *f = new AttachedPictureFrameV22(data, header); d->setTextEncoding(f); return f; } // Relative Volume Adjustment (frames 4.11) if(frameID == "RVA2") return new RelativeVolumeFrame(data, header); // Unique File Identifier (frames 4.1) if(frameID == "UFID") return new UniqueFileIdentifierFrame(data, header); // General Encapsulated Object (frames 4.15) if(frameID == "GEOB") { GeneralEncapsulatedObjectFrame *f = new GeneralEncapsulatedObjectFrame(data, header); d->setTextEncoding(f); return f; } // URL link (frames 4.3) if(frameID.startsWith("W")) { if(frameID != "WXXX") { return new UrlLinkFrame(data, header); } else { UserUrlLinkFrame *f = new UserUrlLinkFrame(data, header); d->setTextEncoding(f); return f; } } // Unsynchronized lyric/text transcription (frames 4.8) if(frameID == "USLT") { UnsynchronizedLyricsFrame *f = new UnsynchronizedLyricsFrame(data, header); if(d->useDefaultEncoding) f->setTextEncoding(d->defaultEncoding); return f; } // Synchronised lyrics/text (frames 4.9) if(frameID == "SYLT") { SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header); if(d->useDefaultEncoding) f->setTextEncoding(d->defaultEncoding); return f; } // Event timing codes (frames 4.5) if(frameID == "ETCO") return new EventTimingCodesFrame(data, header); // Popularimeter (frames 4.17) if(frameID == "POPM") return new PopularimeterFrame(data, header); // Private (frames 4.27) if(frameID == "PRIV") return new PrivateFrame(data, header); // Ownership (frames 4.22) if(frameID == "OWNE") { OwnershipFrame *f = new OwnershipFrame(data, header); d->setTextEncoding(f); return f; } // Chapter (ID3v2 chapters 1.0) if(frameID == "CHAP") return new ChapterFrame(tagHeader, data, header); // Table of contents (ID3v2 chapters 1.0) if(frameID == "CTOC") return new TableOfContentsFrame(tagHeader, data, header); // Apple proprietary PCST (Podcast) if(frameID == "PCST") return new PodcastFrame(data, header); return new UnknownFrame(data, header); }
void Ogg::File::writePacket(unsigned int i, const ByteVector &packet) { if(!readPages(i)) { debug("Ogg::File::writePacket() -- Could not find the requested packet."); return; } // Look for the pages where the requested packet should belong to. List<Page *>::ConstIterator it = d->pages.begin(); while((*it)->containsPacket(i) == Page::DoesNotContainPacket) ++it; const Page *firstPage = *it; while(nextPacketIndex(*it) <= i) ++it; const Page *lastPage = *it; // Replace the requested packet and create new pages to replace the located pages. ByteVectorList packets = firstPage->packets(); packets[i - firstPage->firstPacketIndex()] = packet; if(firstPage != lastPage && lastPage->packetCount() > 2) { ByteVectorList lastPagePackets = lastPage->packets(); lastPagePackets.erase(lastPagePackets.begin()); packets.append(lastPagePackets); } // TODO: This pagination method isn't accurate for what's being done here. // This should account for real possibilities like non-aligned packets and such. const List<Page *> pages = Page::paginate(packets, Page::SinglePagePerGroup, firstPage->header()->streamSerialNumber(), firstPage->pageSequenceNumber(), firstPage->header()->firstPacketContinued(), lastPage->header()->lastPacketCompleted()); // Write the pages. ByteVector data; for(it = pages.begin(); it != pages.end(); ++it) data.append((*it)->render()); const unsigned long originalOffset = firstPage->fileOffset(); const unsigned long originalLength = lastPage->fileOffset() + lastPage->size() - originalOffset; insert(data, originalOffset, originalLength); // Renumber the following pages if the pages have been split or merged. const int numberOfNewPages = pages.back()->pageSequenceNumber() - lastPage->pageSequenceNumber(); if(numberOfNewPages != 0) { long pageOffset = originalOffset + data.size(); while(true) { Page page(this, pageOffset); if(!page.header()->isValid()) break; page.setPageSequenceNumber(page.pageSequenceNumber() + numberOfNewPages); const ByteVector data = page.render(); seek(pageOffset + 18); writeBlock(data.mid(18, 8)); if(page.header()->lastPageOfStream()) break; pageOffset += page.size(); } } // Discard all the pages to keep them up-to-date by fetching them again. d->pages.clear(); }
bool FLAC::File::save() { if(readOnly()) { debug("FLAC::File::save() - Cannot save to a read only file."); return false; } // Create new vorbis comments if(!d->comment) { d->comment = new Ogg::XiphComment; if(d->tag) Tag::duplicate(d->tag, d->comment, true); } d->xiphCommentData = d->comment->render(false); // A Xiph comment portion of the data stream starts with a 4-byte descriptor. // The first byte indicates the frame type. The last three bytes are used // to give the lenght of the data segment. Here we start ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size()); data[0] = char(VorbisComment); data.append(d->xiphCommentData); // If file already have comment => find and update it // if not => insert one // TODO: Search for padding and use that if(d->hasXiphComment) { long nextBlockOffset = d->flacStart; bool isLastBlock = false; while(!isLastBlock) { seek(nextBlockOffset); ByteVector header = readBlock(4); char blockType = header[0] & 0x7f; isLastBlock = header[0] & 0x80; uint blockLength = header.mid(1, 3).toUInt(); if(blockType == VorbisComment) { data[0] = header[0]; insert(data, nextBlockOffset, blockLength + 4); break; } nextBlockOffset += blockLength + 4; } } else { const long firstBlockOffset = d->flacStart; seek(firstBlockOffset); ByteVector header = readBlock(4); bool isLastBlock = header[0] & 0x80; uint blockLength = header.mid(1, 3).toUInt(); if(isLastBlock) { // If the first block was previously also the last block, then we want to // mark it as no longer being the first block (the writeBlock() call) and // then set the data for the block that we're about to write to mark our // new block as the last block. seek(firstBlockOffset); writeBlock(static_cast<char>(header[0] & 0x7F)); data[0] |= 0x80; } insert(data, firstBlockOffset + blockLength + 4, 0); d->hasXiphComment = true; } // Update ID3 tags if(d->ID3v2Tag) { if(d->hasID3v2) { if(d->ID3v2Location < d->flacStart) debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the " "start of the FLAC bytestream? Not writing the ID3v2 tag."); else insert(d->ID3v2Tag->render(), d->ID3v2Location, d->ID3v2OriginalSize); } else insert(d->ID3v2Tag->render(), 0, 0); } if(d->ID3v1Tag) { seek(d->ID3v1Tag ? -128 : 0, End); writeBlock(d->ID3v1Tag->render()); } return true; }
void UnsynchronizedLyricsFrame::setLanguage(const ByteVector &languageEncoding) { d->language = languageEncoding.mid(0, 3); }
bool FLAC::File::save() { if(readOnly()) { debug("FLAC::File::save() - Cannot save to a read only file."); return false; } // Create new vorbis comments if(!d->comment) { d->comment = new Ogg::XiphComment; if(d->tag) Tag::duplicate(d->tag, d->comment, true); } d->xiphCommentData = d->comment->render(false); ByteVector v = ByteVector::fromUInt(d->xiphCommentData.size()); // Set the type of the comment to be a Xiph / Vorbis comment // (See scan() for comments on header-format) v[0] = 4; v.append(d->xiphCommentData); // If file already have comment => find and update it // if not => insert one // TODO: Search for padding and use that if(d->hasXiphComment) { long nextPageOffset = d->flacStart; seek(nextPageOffset); ByteVector header = readBlock(4); uint length = header.mid(1, 3).toUInt(); nextPageOffset += length + 4; // Search through the remaining metadata char blockType = header[0] & 0x7f; bool lastBlock = header[0] & 0x80; while(!lastBlock) { seek(nextPageOffset); header = readBlock(4); blockType = header[0] & 0x7f; lastBlock = header[0] & 0x80; length = header.mid(1, 3).toUInt(); // Type is vorbiscomment if(blockType == 4) { v[0] = header[0]; insert(v, nextPageOffset, length + 4); break; } nextPageOffset += length + 4; } } else { long nextPageOffset = d->flacStart; seek(nextPageOffset); ByteVector header = readBlock(4); // char blockType = header[0] & 0x7f; bool lastBlock = header[0] & 0x80; uint length = header.mid(1, 3).toUInt(); // If last block was last, make this one last if(lastBlock) { // Copy the bottom seven bits into the new value ByteVector h(static_cast<char>(header[0] & 0x7F)); insert(h, nextPageOffset, 1); // Set the last bit v[0] |= 0x80; } insert(v, nextPageOffset + length + 4, 0); d->hasXiphComment = true; } // Update ID3 tags if(d->ID3v2Tag) { if(d->hasID3v2) insert(d->ID3v2Tag->render(), d->ID3v2Location, d->ID3v2OriginalSize); else insert(d->ID3v2Tag->render(), 0, 0); } if(d->ID3v1Tag) { if(d->hasID3v1) seek(-128, End); else seek(0, End); writeBlock(d->ID3v1Tag->render()); } return true; }
void Ogg::PageHeader::read() { d->file->seek(d->fileOffset); // An Ogg page header is at least 27 bytes, so we'll go ahead and read that // much and then get the rest when we're ready for it. ByteVector data = d->file->readBlock(27); // Sanity check -- make sure that we were in fact able to read as much data as // we asked for and that the page begins with "OggS". if(data.size() != 27 || !data.startsWith("OggS")) { debug("Ogg::PageHeader::read() -- error reading page header"); return; } std::bitset<8> flags(data[5]); d->firstPacketContinued = flags.test(0); d->firstPageOfStream = flags.test(1); d->lastPageOfStream = flags.test(2); d->absoluteGranularPosition = data.mid(6, 8).toLongLong(false); d->streamSerialNumber = data.mid(14, 4).toUInt(false); d->pageSequenceNumber = data.mid(18, 4).toUInt(false); // Byte number 27 is the number of page segments, which is the only variable // length portion of the page header. After reading the number of page // segments we'll then read in the coresponding data for this count. int pageSegmentCount = uchar(data[26]); ByteVector pageSegments = d->file->readBlock(pageSegmentCount); // Another sanity check. if(pageSegmentCount < 1 || int(pageSegments.size()) != pageSegmentCount) return; // The base size of an Ogg page 27 bytes plus the number of lacing values. d->size = 27 + pageSegmentCount; int packetSize = 0; for(int i = 0; i < pageSegmentCount; i++) { d->dataSize += uchar(pageSegments[i]); packetSize += uchar(pageSegments[i]); if(uchar(pageSegments[i]) < 255) { d->packetSizes.append(packetSize); packetSize = 0; } } if(packetSize > 0) { d->packetSizes.append(packetSize); d->lastPacketCompleted = false; } else d->lastPacketCompleted = true; d->isValid = true; }
Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const { ByteVector data = origData; uint version = tagHeader->majorVersion(); Frame::Header *header = new Frame::Header(data, version); ByteVector frameID = header->frameID(); // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1 // characters. Also make sure that there is data in the frame. if(!frameID.size() == (version < 3 ? 3 : 4) || header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) || header->frameSize() > data.size()) { delete header; return 0; } for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) { if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) { delete header; return 0; } } if(version > 3 && (tagHeader->unsynchronisation() || header->unsynchronisation())) { // Data lengths are not part of the encoded data, but since they are synch-safe // integers they will be never actually encoded. ByteVector frameData = data.mid(Frame::Header::size(version), header->frameSize()); frameData = SynchData::decode(frameData); data = data.mid(0, Frame::Header::size(version)) + frameData; } // TagLib doesn't mess with encrypted frames, so just treat them // as unknown frames. #if HAVE_ZLIB == 0 if(header->compression()) { debug("Compressed frames are currently not supported."); return new UnknownFrame(data, header); } #endif if(header->encryption()) { debug("Encrypted frames are currently not supported."); return new UnknownFrame(data, header); } if(!updateFrame(header)) { header->setTagAlterPreservation(true); return new UnknownFrame(data, header); } // updateFrame() might have updated the frame ID. frameID = header->frameID(); // This is where things get necissarily nasty. Here we determine which // Frame subclass (or if none is found simply an Frame) based // on the frame ID. Since there are a lot of possibilities, that means // a lot of if blocks. // Text Identification (frames 4.2) if(frameID.startsWith("T")) { TextIdentificationFrame *f = frameID != "TXXX" ? new TextIdentificationFrame(data, header) : new UserTextIdentificationFrame(data, header); d->setTextEncoding(f); if(frameID == "TCON") updateGenre(f); return f; } // Comments (frames 4.10) if(frameID == "COMM") { CommentsFrame *f = new CommentsFrame(data, header); d->setTextEncoding(f); return f; } // Attached Picture (frames 4.14) if(frameID == "APIC") { AttachedPictureFrame *f = new AttachedPictureFrame(data, header); d->setTextEncoding(f); return f; } // ID3v2.2 Attached Picture if(frameID == "PIC") { AttachedPictureFrame *f = new AttachedPictureFrameV22(data, header); d->setTextEncoding(f); return f; } // Relative Volume Adjustment (frames 4.11) if(frameID == "RVA2") return new RelativeVolumeFrame(data, header); // Unique File Identifier (frames 4.1) if(frameID == "UFID") return new UniqueFileIdentifierFrame(data, header); // General Encapsulated Object (frames 4.15) if(frameID == "GEOB") { GeneralEncapsulatedObjectFrame *f = new GeneralEncapsulatedObjectFrame(data, header); d->setTextEncoding(f); return f; } // URL link (frames 4.3) if(frameID.startsWith("W")) { if(frameID != "WXXX") { return new UrlLinkFrame(data, header); } else { UserUrlLinkFrame *f = new UserUrlLinkFrame(data, header); d->setTextEncoding(f); return f; } } // Unsynchronized lyric/text transcription (frames 4.8) if(frameID == "USLT") { UnsynchronizedLyricsFrame *f = new UnsynchronizedLyricsFrame(data, header); if(d->useDefaultEncoding) f->setTextEncoding(d->defaultEncoding); return f; } // Popularimeter (frames 4.17) if(frameID == "POPM") return new PopularimeterFrame(data, header); // Private (frames 4.27) if(frameID == "PRIV") return new PrivateFrame(data, header); return new UnknownFrame(data, header); }
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; } } }
void Speex::Properties::read() { // Get the identification header from the Ogg implementation. ByteVector data = d->file->packet(0); int pos = 28; // speex_version_id; /**< Version for Speex (for checking compatibility) */ d->speexVersion = data.mid(pos, 4).toUInt(false); pos += 4; // header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ pos += 4; // rate; /**< Sampling rate used */ d->sampleRate = data.mid(pos, 4).toUInt(false); pos += 4; // mode; /**< Mode used (0 for narrowband, 1 for wideband) */ d->mode = data.mid(pos, 4).toUInt(false); pos += 4; // mode_bitstream_version; /**< Version ID of the bit-stream */ pos += 4; // nb_channels; /**< Number of channels encoded */ d->channels = data.mid(pos, 4).toUInt(false); pos += 4; // bitrate; /**< Bit-rate used */ d->bitrate = data.mid(pos, 4).toUInt(false); pos += 4; // frame_size; /**< Size of frames */ // unsigned int frameSize = data.mid(pos, 4).toUInt(false); pos += 4; // vbr; /**< 1 for a VBR encoding, 0 otherwise */ d->vbr = data.mid(pos, 4).toUInt(false) == 1; pos += 4; // frames_per_packet; /**< Number of frames stored per Ogg packet */ // unsigned int framesPerPacket = data.mid(pos, 4).toUInt(false); const Ogg::PageHeader *first = d->file->firstPageHeader(); const Ogg::PageHeader *last = d->file->lastPageHeader(); if(first && last) { long long start = first->absoluteGranularPosition(); long long end = last->absoluteGranularPosition(); if(start >= 0 && end >= 0 && d->sampleRate > 0) d->length = (int) ((end - start) / (long long) d->sampleRate); else debug("Speex::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); } else debug("Speex::Properties::read() -- Could not find valid first and last Ogg pages."); }
void MP4::Tag::updateOffsets(long delta, long offset) { MP4::Atom *moov = d->atoms->find("moov"); if(moov) { MP4::AtomList stco = moov->findall("stco", true); for(unsigned int i = 0; i < stco.size(); i++) { MP4::Atom *atom = stco[i]; if(atom->offset > offset) { atom->offset += delta; } d->file->seek(atom->offset + 12); ByteVector data = d->file->readBlock(atom->length - 12); unsigned int count = data.mid(0, 4).toUInt(); d->file->seek(atom->offset + 16); int pos = 4; while(count--) { long o = data.mid(pos, 4).toUInt(); if(o > offset) { o += delta; } d->file->writeBlock(ByteVector::fromUInt(o)); pos += 4; } } MP4::AtomList co64 = moov->findall("co64", true); for(unsigned int i = 0; i < co64.size(); i++) { MP4::Atom *atom = co64[i]; if(atom->offset > offset) { atom->offset += delta; } d->file->seek(atom->offset + 12); ByteVector data = d->file->readBlock(atom->length - 12); unsigned int count = data.mid(0, 4).toUInt(); d->file->seek(atom->offset + 16); int pos = 4; while(count--) { long long o = data.mid(pos, 8).toLongLong(); if(o > offset) { o += delta; } d->file->writeBlock(ByteVector::fromLongLong(o)); pos += 8; } } } MP4::Atom *moof = d->atoms->find("moof"); if(moof) { MP4::AtomList tfhd = moof->findall("tfhd", true); for(unsigned int i = 0; i < tfhd.size(); i++) { MP4::Atom *atom = tfhd[i]; if(atom->offset > offset) { atom->offset += delta; } d->file->seek(atom->offset + 9); ByteVector data = d->file->readBlock(atom->offset - 9); unsigned int flags = (ByteVector(1, '\0') + data.mid(0, 3)).toUInt(); if(flags & 1) { long long o = data.mid(7, 8).toLongLong(); if(o > offset) { o += delta; } d->file->seek(atom->offset + 16); d->file->writeBlock(ByteVector::fromLongLong(o)); } } } }
void FLAC::File::scan() { // Scan the metadata pages if(d->scanned) return; if(!isValid()) return; long nextBlockOffset; if(d->hasID3v2) nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize); else nextBlockOffset = find("fLaC"); if(nextBlockOffset < 0) { debug("FLAC::File::scan() -- FLAC stream not found"); setValid(false); return; } nextBlockOffset += 4; d->flacStart = nextBlockOffset; seek(nextBlockOffset); ByteVector header = readBlock(4); // Header format (from spec): // <1> Last-metadata-block flag // <7> BLOCK_TYPE // 0 : STREAMINFO // 1 : PADDING // .. // 4 : VORBIS_COMMENT // .. // <24> Length of metadata to follow char blockType = header[0] & 0x7f; bool isLastBlock = (header[0] & 0x80) != 0; uint length = header.mid(1, 3).toUInt(); // First block should be the stream_info metadata if(blockType != StreamInfo) { debug("FLAC::File::scan() -- invalid FLAC stream"); setValid(false); return; } d->streamInfoData = readBlock(length); nextBlockOffset += length + 4; // Search through the remaining metadata while(!isLastBlock) { header = readBlock(4); blockType = header[0] & 0x7f; isLastBlock = (header[0] & 0x80) != 0; length = header.mid(1, 3).toUInt(); // Found the vorbis-comment if(blockType == VorbisComment) { if(!d->hasXiphComment) { d->xiphCommentData = readBlock(length); d->hasXiphComment = true; } else { debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one"); } } nextBlockOffset += length + 4; if(nextBlockOffset >= File::length()) { debug("FLAC::File::scan() -- FLAC stream corrupted"); setValid(false); return; } seek(nextBlockOffset); } // End of metadata, now comes the datastream d->streamStart = nextBlockOffset; d->streamLength = File::length() - d->streamStart; if(d->hasID3v1) d->streamLength -= 128; d->scanned = true; }
void Ogg::FLAC::File::scan() { // Scan the metadata pages if(d->scanned) return; if(!isValid()) return; int ipacket = 0; long overhead = 0; ByteVector metadataHeader = packet(ipacket); if(metadataHeader.isEmpty()) return; if(!metadataHeader.startsWith("fLaC")) { // FLAC 1.1.2+ if(metadataHeader.mid(1, 4) != "FLAC") return; if(metadataHeader[5] != 1) return; // not version 1 metadataHeader = metadataHeader.mid(13); } else { // FLAC 1.1.0 & 1.1.1 metadataHeader = packet(++ipacket); } ByteVector header = metadataHeader.mid(0, 4); if(header.size() != 4) { debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header"); return; } // Header format (from spec): // <1> Last-metadata-block flag // <7> BLOCK_TYPE // 0 : STREAMINFO // 1 : PADDING // .. // 4 : VORBIS_COMMENT // .. // <24> Length of metadata to follow char blockType = header[0] & 0x7f; bool lastBlock = (header[0] & 0x80) != 0; uint length = header.toUInt(1, 3, true); overhead += length; // Sanity: First block should be the stream_info metadata if(blockType != 0) { debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC stream"); return; } d->streamInfoData = metadataHeader.mid(4, length); // Search through the remaining metadata while(!lastBlock) { metadataHeader = packet(++ipacket); header = metadataHeader.mid(0, 4); if(header.size() != 4) { debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header"); return; } blockType = header[0] & 0x7f; lastBlock = (header[0] & 0x80) != 0; length = header.toUInt(1, 3, true); overhead += length; if(blockType == 1) { // debug("Ogg::FLAC::File::scan() -- Padding found"); } else if(blockType == 4) { // debug("Ogg::FLAC::File::scan() -- Vorbis-comments found"); d->xiphCommentData = metadataHeader.mid(4, length); d->hasXiphComment = true; d->commentPacket = ipacket; } else if(blockType > 5) { debug("Ogg::FLAC::File::scan() -- Unknown metadata block"); } } // End of metadata, now comes the datastream d->streamStart = overhead; d->streamLength = File::length() - d->streamStart; d->scanned = true; }
MP4::Atom::Atom(File *file) { children.setAutoDelete(true); offset = file->tell(); ByteVector header = file->readBlock(8); if(header.size() != 8) { // The atom header must be 8 bytes long, otherwise there is either // trailing garbage or the file is truncated debug("MP4: Couldn't read 8 bytes of data for atom header"); length = 0; file->seek(0, File::End); return; } length = header.toUInt(); if(length == 0) { // The last atom which extends to the end of the file. length = file->length() - offset; } else if(length == 1) { // The atom has a 64-bit length. const long long longLength = file->readBlock(8).toLongLong(); if(longLength <= LONG_MAX) { // The actual length fits in long. That's always the case if long is 64-bit. length = static_cast<long>(longLength); } else { debug("MP4: 64-bit atoms are not supported"); length = 0; file->seek(0, File::End); return; } } if(length < 8) { debug("MP4: Invalid atom size"); length = 0; file->seek(0, File::End); return; } name = header.mid(4, 4); for(int i = 0; i < numContainers; i++) { if(name == containers[i]) { if(name == "meta") { file->seek(4, File::Current); } else if(name == "stsd") { file->seek(8, File::Current); } while(file->tell() < offset + length) { MP4::Atom *child = new MP4::Atom(file); children.append(child); if(child->length == 0) return; } return; } } file->seek(offset + length); }
void Frame::Header::setFrameID(const ByteVector &id) { d->frameID = id.mid(0, 4); }
bool FLAC::File::save() { if(readOnly()) { debug("FLAC::File::save() - Cannot save to a read only file."); return false; } // Create new vorbis comments Tag::duplicate(&d->tag, xiphComment(true), true); d->xiphCommentData = xiphComment()->render(false); // A Xiph comment portion of the data stream starts with a 4-byte descriptor. // The first byte indicates the frame type. The last three bytes are used // to give the length of the data segment. Here we start ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size()); data[0] = char(VorbisComment); data.append(d->xiphCommentData); // If file already have comment => find and update it // if not => insert one // TODO: Search for padding and use that if(d->hasXiphComment) { long nextBlockOffset = d->flacStart; bool isLastBlock = false; while(!isLastBlock) { seek(nextBlockOffset); ByteVector header = readBlock(4); char blockType = header[0] & 0x7f; isLastBlock = (header[0] & 0x80) != 0; uint blockLength = header.mid(1, 3).toUInt(); if(blockType == VorbisComment) { long paddingBreak = 0; if(!isLastBlock) { paddingBreak = findPaddingBreak(nextBlockOffset + blockLength + 4, nextBlockOffset + d->xiphCommentData.size() + 8, &isLastBlock); } uint paddingLength = 0; if(paddingBreak) { // There is space for comment and padding blocks without rewriting the // whole file. Note: This cannot overflow. paddingLength = paddingBreak - (nextBlockOffset + d->xiphCommentData.size() + 8); } else { // Not enough space, so we will have to rewrite the whole file // following this block paddingLength = d->xiphCommentData.size(); if(paddingLength < MinPaddingLength) paddingLength = MinPaddingLength; paddingBreak = nextBlockOffset + blockLength + 4; } ByteVector padding = ByteVector::fromUInt(paddingLength); padding[0] = 1; if(isLastBlock) padding[0] |= 0x80; padding.resize(paddingLength + 4); ByteVector pair(data); pair.append(padding); insert(pair, nextBlockOffset, paddingBreak - nextBlockOffset); break; } nextBlockOffset += blockLength + 4; } } else { const long firstBlockOffset = d->flacStart; seek(firstBlockOffset); ByteVector header = readBlock(4); bool isLastBlock = (header[0] & 0x80) != 0; uint blockLength = header.mid(1, 3).toUInt(); if(isLastBlock) { // If the first block was previously also the last block, then we want to // mark it as no longer being the first block (the writeBlock() call) and // then set the data for the block that we're about to write to mark our // new block as the last block. seek(firstBlockOffset); writeBlock(static_cast<char>(header[0] & 0x7F)); data[0] |= 0x80; } insert(data, firstBlockOffset + blockLength + 4, 0); d->hasXiphComment = true; } // Update ID3 tags if(ID3v2Tag()) { if(d->hasID3v2) { if(d->ID3v2Location < d->flacStart) debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the " "start of the FLAC bytestream? Not writing the ID3v2 tag."); else insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize); } else insert(ID3v2Tag()->render(), 0, 0); } if(ID3v1Tag()) { seek(-128, End); writeBlock(ID3v1Tag()->render()); } return true; }