void MPEG::XingHeader::parse(const ByteVector &data) { // Check to see if a valid Xing header is available. if(!data.startsWith("Xing") && !data.startsWith("Info")) return; // If the XingHeader doesn't contain the number of frames and the total stream // info it's invalid. if(!(data[7] & 0x01)) { debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames."); return; } if(!(data[7] & 0x02)) { debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size."); return; } d->frames = data.mid(8, 4).toUInt(); d->size = data.mid(12, 4).toUInt(); d->valid = true; }
ByteVector TagLib::Utils::readHeader(IOStream *stream, unsigned int length, bool skipID3v2, long *headerOffset) { if(!stream || !stream->isOpen()) return ByteVector(); const long originalPosition = stream->tell(); long bufferOffset = 0; if(skipID3v2) { stream->seek(0); const ByteVector data = stream->readBlock(ID3v2::Header::size()); if(data.startsWith(ID3v2::Header::fileIdentifier())) bufferOffset = ID3v2::Header(data).completeTagSize(); } stream->seek(bufferOffset); const ByteVector header = stream->readBlock(length); stream->seek(originalPosition); if(headerOffset) *headerOffset = bufferOffset; return header; }
void MPEG::VbriHeader::parse(const ByteVector &data) { // Check to see if a valid VBRI header is available. if(!data.startsWith("VBRI")) return; d->size = data.toUInt(10, true); d->frames = data.toUInt(14, true); d->valid = true; }
void ID3v1::Tag::read() { if(d->file && d->file->isValid()) { d->file->seek(d->tagOffset); // read the tag -- always 128 bytes const ByteVector data = d->file->readBlock(128); // some initial sanity checking if(data.size() == 128 && data.startsWith("TAG")) parse(data); else debug("ID3v1 tag is not valid or could not be read at the specified offset."); } }
void Speex::File::read(bool readProperties) { ByteVector speexHeaderData = packet(0); if(!speexHeaderData.startsWith("Speex ")) { debug("Speex::File::read() -- invalid Speex identification header"); return; } ByteVector commentHeaderData = packet(1); d->comment = new Ogg::XiphComment(commentHeaderData); if(readProperties) d->properties = new AudioProperties(this); }
void ID3v2::Tag::read() { if(!d->file) return; if(!d->file->isOpen()) return; d->file->seek(d->tagOffset); d->header.setData(d->file->readBlock(Header::size())); // If the tag size is 0, then this is an invalid tag (tags must contain at // least one frame) if(d->header.tagSize() != 0) parse(d->file->readBlock(d->header.tagSize())); // Look for duplicate ID3v2 tags and treat them as an extra blank of this one. // It leads to overwriting them with zero when saving the tag. // This is a workaround for some faulty files that have duplicate ID3v2 tags. // Unfortunately, TagLib itself may write such duplicate tags until v1.10. unsigned int extraSize = 0; while(true) { d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize); const ByteVector data = d->file->readBlock(Header::size()); if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier())) break; extraSize += Header(data).completeTagSize(); } if(extraSize != 0) { debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found."); d->header.setTagSize(d->header.tagSize() + extraSize); } }
void APE::Properties::analyzeOld(File *file) { const ByteVector header = file->readBlock(26); if(header.size() < 26) { debug("APE::Properties::analyzeOld() -- MAC header is too short."); return; } const unsigned int totalFrames = header.toUInt(18, false); // Fail on 0 length APE files (catches non-finalized APE files) if(totalFrames == 0) return; const short compressionLevel = header.toShort(0, false); unsigned int blocksPerFrame; if(d->version >= 3950) blocksPerFrame = 73728 * 4; else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000)) blocksPerFrame = 73728; else blocksPerFrame = 9216; // Get the APE info d->channels = header.toShort(4, false); d->sampleRate = header.toUInt(6, false); const unsigned int finalFrameBlocks = header.toUInt(22, false); d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; // Get the bit depth from the RIFF-fmt chunk. file->seek(16, File::Current); const ByteVector fmt = file->readBlock(28); if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) { debug("APE::Properties::analyzeOld() -- fmt header is too short."); return; } d->bitsPerSample = fmt.toShort(26, false); }
void RIFF::WAV::File::read(bool readProperties) { for(uint i = 0; i < chunkCount(); ++i) { const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { if(!d->tag[ID3v2Index]) { d->tagChunkID = name; d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); d->hasID3v2 = true; } else { debug("RIFF::WAV::File::read() - Duplicate ID3v2 tag found."); } } else if(name == "LIST") { const ByteVector data = chunkData(i); if(data.startsWith("INFO")) { if(!d->tag[InfoIndex]) { d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); d->hasInfo = true; } else { debug("RIFF::WAV::File::read() - Duplicate INFO tag found."); } } } } if(!d->tag[ID3v2Index]) d->tag.set(ID3v2Index, new ID3v2::Tag()); if(!d->tag[InfoIndex]) d->tag.set(InfoIndex, new RIFF::Info::Tag()); if(readProperties) d->properties = new Properties(this, Properties::Average); }
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); }
Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const { 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() <= 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; } } // 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); if(d->useDefaultEncoding) f->setTextEncoding(d->defaultEncoding); if(frameID == "TCON") updateGenre(f); return f; } // Comments (frames 4.10) if(frameID == "COMM") { CommentsFrame *f = new CommentsFrame(data, header); if(d->useDefaultEncoding) f->setTextEncoding(d->defaultEncoding); return f; } // Attached Picture (frames 4.14) if(frameID == "APIC") { AttachedPictureFrame *f = new AttachedPictureFrame(data, header); if(d->useDefaultEncoding) f->setTextEncoding(d->defaultEncoding); 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") return new GeneralEncapsulatedObjectFrame(data, header); return new UnknownFrame(data, header); }
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; }
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::PageHeader::read(Ogg::File *file, long pageOffset) { file->seek(pageOffset); // 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. const ByteVector data = 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; } const std::bitset<8> flags(data[5]); d->firstPacketContinued = flags.test(0); d->firstPageOfStream = flags.test(1); d->lastPageOfStream = flags.test(2); d->absoluteGranularPosition = data.toLongLong(6, false); d->streamSerialNumber = data.toUInt(14, false); d->pageSequenceNumber = data.toUInt(18, 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 corresponding data for this count. int pageSegmentCount = static_cast<unsigned char>(data[26]); const ByteVector pageSegments = 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 += static_cast<unsigned char>(pageSegments[i]); packetSize += static_cast<unsigned char>(pageSegments[i]); if(static_cast<unsigned char>(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; }
void Mod::File::read(bool) { if(!isOpen()) return; seek(1080); ByteVector modId = readBlock(4); READ_ASSERT(modId.size() == 4); int channels = 4; unsigned int instruments = 31; if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") { d->tag.setTrackerName("ProTracker"); channels = 4; } else if(modId.startsWith("FLT") || modId.startsWith("TDZ")) { d->tag.setTrackerName("StarTrekker"); char digit = modId[3]; READ_ASSERT(digit >= '0' && digit <= '9'); channels = digit - '0'; } else if(modId.endsWith("CHN")) { d->tag.setTrackerName("StarTrekker"); char digit = modId[0]; READ_ASSERT(digit >= '0' && digit <= '9'); channels = digit - '0'; } else if(modId == "CD81" || modId == "OKTA") { d->tag.setTrackerName("Atari Oktalyzer"); channels = 8; } else if(modId.endsWith("CH") || modId.endsWith("CN")) { d->tag.setTrackerName("TakeTracker"); char digit = modId[0]; READ_ASSERT(digit >= '0' && digit <= '9'); channels = (digit - '0') * 10; digit = modId[1]; READ_ASSERT(digit >= '0' && digit <= '9'); channels += digit - '0'; } else { // Not sure if this is correct. I'd need a file // created with NoiseTracker to check this. d->tag.setTrackerName("NoiseTracker"); // probably channels = 4; instruments = 15; } d->properties.setChannels(channels); d->properties.setInstrumentCount(instruments); seek(0); READ_STRING(d->tag.setTitle, 20); StringList comment; for(unsigned int i = 0; i < instruments; ++ i) { READ_STRING_AS(instrumentName, 22); // value in words, * 2 (<< 1) for bytes: READ_U16B_AS(sampleLength); READ_BYTE_AS(fineTuneByte); int fineTune = fineTuneByte & 0xF; // > 7 means negative value if(fineTune > 7) fineTune -= 16; READ_BYTE_AS(volume); if(volume > 64) volume = 64; // volume in decibels: 20 * log10(volume / 64) // value in words, * 2 (<< 1) for bytes: READ_U16B_AS(repeatStart); // value in words, * 2 (<< 1) for bytes: READ_U16B_AS(repatLength); comment.append(instrumentName); } READ_BYTE(d->properties.setLengthInPatterns); d->tag.setComment(comment.toString("\n")); }
bool DSF::File::isSupported(IOStream *stream) { // A DSF file has to start with "DSD " const ByteVector id = Utils::readHeader(stream, 4, false); return id.startsWith("DSD "); }