void APE::Properties::analyzeCurrent(File *file) { // Read the descriptor file->seek(2, File::Current); const ByteVector descriptor = file->readBlock(44); if(descriptor.size() < 44) { debug("APE::Properties::analyzeCurrent() -- descriptor is too short."); return; } const unsigned int descriptorBytes = descriptor.toUInt(0, false); if((descriptorBytes - 52) > 0) file->seek(descriptorBytes - 52, File::Current); // Read the header const ByteVector header = file->readBlock(24); if(header.size() < 24) { debug("APE::Properties::analyzeCurrent() -- MAC header is too short."); return; } // Get the APE info d->channels = header.toShort(18, false); d->sampleRate = header.toUInt(20, false); d->bitsPerSample = header.toShort(16, false); const unsigned int totalFrames = header.toUInt(12, false); if(totalFrames == 0) return; const unsigned int blocksPerFrame = header.toUInt(4, false); const unsigned int finalFrameBlocks = header.toUInt(8, false); d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; }
void MP4::Tag::parseCovr(const MP4::Atom *atom) { MP4::CoverArtList value; ByteVector data = d->file->readBlock(atom->length - 8); 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"); break;; } const ByteVector name = data.mid(pos + 4, 4); const int flags = static_cast<int>(data.toUInt(pos + 8)); if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); break; } if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) { value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), data.mid(pos + 16, length - 16))); } else { debug("MP4: Unknown covr format " + String::number(flags)); } pos += length; } if(value.size() > 0) addItem(atom->name, value); }
void APE::Footer::parse(const ByteVector &data) { if ( data.size() < size() ) { return; } // The first eight bytes, data[0..7], are the File Identifier, "APETAGEX". // Read the version number d->version = data.toUInt(8, false); // Read the tag size d->tagSize = data.toUInt(12, false); // Read the item count d->itemCount = data.toUInt(16, false); // Read the flags std::bitset<32> flags( TAGLIB_CONSTRUCT_BITSET( data.toUInt(20, false) ) ); d->headerPresent = flags[31]; d->footerPresent = !flags[30]; d->isHeader = flags[29]; }
MP4::AtomDataList MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) { AtomDataList result; ByteVector data = 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) ); 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; }
void Vorbis::Properties::read() { // Get the identification header from the Ogg implementation. ByteVector data = d->file->packet(0); uint pos = 0; if(data.mid(pos, 7) != vorbisSetupHeaderID) { debug("Vorbis::Properties::read() -- invalid Vorbis identification header"); return; } pos += 7; d->vorbisVersion = data.toUInt(pos, false); pos += 4; d->channels = uchar(data[pos]); pos += 1; d->sampleRate = data.toUInt(pos, false); pos += 4; d->bitrateMaximum = data.toUInt(pos, false); pos += 4; d->bitrateNominal = data.toUInt(pos, false); pos += 4; d->bitrateMinimum = data.toUInt(pos, false); // TODO: Later this should be only the "fast" mode. d->bitrate = d->bitrateNominal; // Find the length of the file. See http://wiki.xiph.org/VorbisStreamLength/ // for my notes on the topic. 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("Vorbis::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); } else debug("Vorbis::Properties::read() -- Could not find valid first and last Ogg pages."); }
void RIFF::WAV::Properties::read(const ByteVector &data) { d->format = data.toShort(0, false); d->channels = data.toShort(2, false); d->sampleRate = data.toUInt(4, false); d->sampleWidth = data.toShort(14, false); const uint byteRate = data.toUInt(8, false); d->bitrate = byteRate * 8 / 1000; d->length = byteRate > 0 ? d->streamLength / byteRate : 0; if(d->channels > 0 && d->sampleWidth > 0) d->sampleFrames = d->streamLength / (d->channels * ((d->sampleWidth + 7) / 8)); }
void ASF::Picture::parse(const ByteVector& bytes) { d->valid = false; if(bytes.size() < 9) return; int pos = 0; d->type = (Type)bytes[0]; ++pos; const uint dataLen = bytes.toUInt(pos, false); pos+=4; const ByteVector nullStringTerminator(2, 0); int endPos = bytes.find(nullStringTerminator, pos, 2); if(endPos < 0) return; d->mimeType = String(bytes.mid(pos, endPos - pos), String::UTF16LE); pos = endPos+2; endPos = bytes.find(nullStringTerminator, pos, 2); if(endPos < 0) return; d->description = String(bytes.mid(pos, endPos - pos), String::UTF16LE); pos = endPos+2; if(dataLen + pos != bytes.size()) return; d->picture = bytes.mid(pos, dataLen); d->valid = true; return; }
void APE::Item::parse(const ByteVector &data) { // 11 bytes is the minimum size for an APE item if(data.size() < 11) { debug("APE::Item::parse() -- no data in item"); return; } const uint valueLength = data.toUInt(0, false); const uint flags = data.toUInt(4, false); const int keyLength = data.find('\0', 8) - 8; if(keyLength < 1 || keyLength > 255) { debug("APE::Item::parse() -- invalid key in item"); return; } d->key = String(data.mid(8, keyLength), String::UTF8); const ByteVector value = data.mid(8 + d->key.size() + 1, valueLength); setReadOnly(flags & 1); setType(ItemTypes((flags >> 1) & 3)); if(Text == d->type) d->text = StringList(ByteVectorList::split(value, '\0'), String::UTF8); else d->value = value; }
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 Opus::Properties::read(File *file) { // Get the identification header from the Ogg implementation. // http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1 const ByteVector data = file->packet(0); // *Magic Signature* uint pos = 8; // *Version* (8 bits, unsigned) d->opusVersion = uchar(data.at(pos)); pos += 1; // *Output Channel Count* 'C' (8 bits, unsigned) d->channels = uchar(data.at(pos)); pos += 1; // *Pre-skip* (16 bits, unsigned, little endian) const ushort preSkip = data.toUShort(pos, false); pos += 2; // *Input Sample Rate* (32 bits, unsigned, little endian) d->inputSampleRate = data.toUInt(pos, false); pos += 4; // *Output Gain* (16 bits, signed, little endian) pos += 2; // *Channel Mapping Family* (8 bits, unsigned) pos += 1; const Ogg::PageHeader *first = file->firstPageHeader(); const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { const long long start = first->absoluteGranularPosition(); const long long end = last->absoluteGranularPosition(); if(start >= 0 && end >= 0) { const long long frameCount = (end - start - preSkip); if(frameCount > 0) { const double length = frameCount * 1000.0 / 48000.0; d->length = static_cast<int>(length + 0.5); d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5); } } else { debug("Opus::Properties::read() -- The PCM values for the start or " "end of this file was incorrect."); } } else debug("Opus::Properties::read() -- Could not find valid first and last Ogg pages."); }
void SynchronizedLyricsFrame::parseFields(const ByteVector &data) { const int end = data.size(); if(end < 7) { debug("A synchronized lyrics frame must contain at least 7 bytes."); return; } d->textEncoding = String::Type(data[0]); d->language = data.mid(1, 3); d->timestampFormat = TimestampFormat(data[4]); d->type = Type(data[5]); int pos = 6; d->description = readStringField(data, d->textEncoding, &pos); if(d->description.isEmpty()) return; /* * If UTF16 strings are found in SYLT frames, a BOM may only be * present in the first string (content descriptor), and the strings of * the synchronized text have no BOM. Here the BOM is read from * the first string to have a specific encoding with endianness for the * case of strings without BOM so that readStringField() will work. */ String::Type encWithEndianness = d->textEncoding; if(d->textEncoding == String::UTF16) { unsigned short bom = data.toUShort(6, true); if(bom == 0xfffe) { encWithEndianness = String::UTF16LE; } else if(bom == 0xfeff) { encWithEndianness = String::UTF16BE; } } d->synchedText.clear(); while(pos < end) { String::Type enc = d->textEncoding; // If a UTF16 string has no BOM, use the encoding found above. if(enc == String::UTF16 && pos + 1 < end) { unsigned short bom = data.toUShort(pos, true); if(bom != 0xfffe && bom != 0xfeff) { enc = encWithEndianness; } } String text = readStringField(data, enc, &pos); if(text.isEmpty() || pos + 4 > end) return; unsigned int time = data.toUInt(pos, true); pos += 4; d->synchedText.append(SynchedText(time, text)); } }
void RIFF::AIFF::Properties::read(const ByteVector &data) { d->channels = data.toShort(0U); d->sampleFrames = data.toUInt(2U); d->sampleWidth = data.toShort(6U); double sampleRate = ConvertFromIeeeExtended(reinterpret_cast<const uchar *>(data.data() + 8)); d->sampleRate = (int)sampleRate; d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0); d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; }
void RIFF::Info::Tag::parse(const ByteVector &data) { uint p = 4; while(p < data.size()) { const uint size = data.toUInt(p + 4, false); d->fieldListMap[data.mid(p, 4)] = TagPrivate::stringHandler->parse(data.mid(p + 8, size)); p += ((size + 1) & ~1) + 8; } }
unsigned int ASF::File::readDWORD(bool *ok) { ByteVector v = readBlock(4); if(v.size() != 4) { if(ok) *ok = false; return 0; } if(ok) *ok = true; return v.toUInt(false); }
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 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 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 PopularimeterFrame::parseFields(const ByteVector &data) { int pos = 0, size = int(data.size()); d->email = readStringField(data, String::Latin1, &pos); d->rating = 0; d->counter = 0; if(pos < size) { d->rating = (unsigned char)(data[pos++]); if(pos < size) { d->counter = data.toUInt(static_cast<unsigned int>(pos)); } } }
bool FLAC::Picture::parse(const ByteVector &data) { if(data.size() < 32) { debug("A picture block must contain at least 5 bytes."); return false; } unsigned int pos = 0; d->type = FLAC::Picture::Type(data.toUInt(pos)); pos += 4; unsigned int mimeTypeLength = data.toUInt(pos); 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; unsigned int descriptionLength = data.toUInt(pos); 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.toUInt(pos); pos += 4; d->height = data.toUInt(pos); pos += 4; d->colorDepth = data.toUInt(pos); pos += 4; d->numColors = data.toUInt(pos); pos += 4; unsigned int dataLength = data.toUInt(pos); pos += 4; if(pos + dataLength > data.size()) { debug("Invalid picture block."); return false; } d->data = data.mid(pos, dataLength); return 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); }
unsigned int ASF::File::readDWORD() { ByteVector v = readBlock(4); return v.toUInt(false); }
void Speex::Properties::read(File *file) { // Get the identification header from the Ogg implementation. const ByteVector data = file->packet(0); if(data.size() < 64) { debug("Speex::Properties::read() -- data is too short."); return; } unsigned int pos = 28; // speex_version_id; /**< Version for Speex (for checking compatibility) */ d->speexVersion = data.toUInt(pos, false); pos += 4; // header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ pos += 4; // rate; /**< Sampling rate used */ d->sampleRate = data.toUInt(pos, false); pos += 4; // mode; /**< Mode used (0 for narrowband, 1 for wideband) */ d->mode = data.toUInt(pos, false); pos += 4; // mode_bitstream_version; /**< Version ID of the bit-stream */ pos += 4; // nb_channels; /**< Number of channels encoded */ d->channels = data.toUInt(pos, false); pos += 4; // bitrate; /**< Bit-rate used */ d->bitrateNominal = data.toUInt(pos, 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.toUInt(pos, 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 = file->firstPageHeader(); const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { const long long start = first->absoluteGranularPosition(); const long long end = last->absoluteGranularPosition(); if(start >= 0 && end >= 0 && d->sampleRate > 0) { const long long frameCount = end - start; if(frameCount > 0) { const double length = frameCount * 1000.0 / d->sampleRate; d->length = static_cast<int>(length + 0.5); d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5); } } 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."); // Alternative to the actual average bitrate. if(d->bitrate == 0 && d->bitrateNominal > 0) d->bitrate = static_cast<int>(d->bitrateNominal / 1000.0 + 0.5); }
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 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.toUInt(3, 3, true); 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.toUInt(4U); { // 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.toUInt(4U); 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 MPEG::Header::parse(File *file, long offset, bool checkLength) { file->seek(offset); const ByteVector data = file->readBlock(4); if(data.size() < 4) { debug("MPEG::Header::parse() -- data is too short for an MPEG frame header."); return; } // Check for the MPEG synch bytes. if(!isFrameSync(data)) { debug("MPEG::Header::parse() -- MPEG header did not match MPEG synch."); return; } // Set the MPEG version const int versionBits = (static_cast<unsigned char>(data[1]) >> 3) & 0x03; if(versionBits == 0) d->version = Version2_5; else if(versionBits == 2) d->version = Version2; else if(versionBits == 3) d->version = Version1; else { debug("MPEG::Header::parse() -- Invalid MPEG version bits."); return; } // Set the MPEG layer const int layerBits = (static_cast<unsigned char>(data[1]) >> 1) & 0x03; if(layerBits == 1) d->layer = 3; else if(layerBits == 2) d->layer = 2; else if(layerBits == 3) d->layer = 1; else { debug("MPEG::Header::parse() -- Invalid MPEG layer bits."); return; } d->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0); // Set the bitrate static const int bitrates[2][3][16] = { { // Version 1 { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // layer 1 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // layer 2 { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 } // layer 3 }, { // Version 2 or 2.5 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, // layer 1 { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // layer 2 { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } // layer 3 } }; const int versionIndex = (d->version == Version1) ? 0 : 1; const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0; // The bitrate index is encoded as the first 4 bits of the 3rd byte, // i.e. 1111xxxx const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F; d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex]; if(d->bitrate == 0) { debug("MPEG::Header::parse() -- Invalid bit rate."); return; } // Set the sample rate static const int sampleRates[3][4] = { { 44100, 48000, 32000, 0 }, // Version 1 { 22050, 24000, 16000, 0 }, // Version 2 { 11025, 12000, 8000, 0 } // Version 2.5 }; // The sample rate index is encoded as two bits in the 3nd byte, i.e. xxxx11xx const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03; d->sampleRate = sampleRates[d->version][samplerateIndex]; if(d->sampleRate == 0) { debug("MPEG::Header::parse() -- Invalid sample rate."); return; } // The channel mode is encoded as a 2 bit value at the end of the 3nd byte, // i.e. xxxxxx11 d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03); // TODO: Add mode extension for completeness d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0); d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0); d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0); // Samples per frame static const int samplesPerFrame[3][2] = { // MPEG1, 2/2.5 { 384, 384 }, // Layer I { 1152, 1152 }, // Layer II { 1152, 576 } // Layer III }; d->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex]; // Calculate the frame length static const int paddingSize[3] = { 4, 1, 1 }; d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate; if(d->isPadded) d->frameLength += paddingSize[layerIndex]; if(checkLength) { // Check if the frame length has been calculated correctly, or the next frame // header is right next to the end of this frame. // The MPEG versions, layers and sample rates of the two frames should be // consistent. Otherwise, we assume that either or both of the frames are // broken. file->seek(offset + d->frameLength); const ByteVector nextData = file->readBlock(4); if(nextData.size() < 4) { debug("MPEG::Header::parse() -- Could not read the next frame header."); return; } const unsigned int HeaderMask = 0xfffe0c00; const unsigned int header = data.toUInt(0, true) & HeaderMask; const unsigned int nextHeader = nextData.toUInt(0, true) & HeaderMask; if(header != nextHeader) { debug("MPEG::Header::parse() -- The next frame was not consistent with this frame."); return; } } // Now that we're done parsing, set this to be a valid frame. d->isValid = true; }
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 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; }
void RIFF::WAV::Properties::read(File *file) { ByteVector data; uint streamLength = 0; uint totalSamples = 0; for(uint i = 0; i < file->chunkCount(); ++i) { const ByteVector name = file->chunkName(i); if(name == "fmt ") { if(data.isEmpty()) data = file->chunkData(i); else debug("RIFF::WAV::Properties::read() - Duplicate 'fmt ' chunk found."); } else if(name == "data") { if(streamLength == 0) streamLength = file->chunkDataSize(i) + file->chunkPadding(i); else debug("RIFF::WAV::Properties::read() - Duplicate 'data' chunk found."); } else if(name == "fact") { if(totalSamples == 0) totalSamples = file->chunkData(i).toUInt(0, false); else debug("RIFF::WAV::Properties::read() - Duplicate 'fact' chunk found."); } } if(data.size() < 16) { debug("RIFF::WAV::Properties::read() - 'fmt ' chunk not found or too short."); return; } if(streamLength == 0) { debug("RIFF::WAV::Properties::read() - 'data' chunk not found."); return; } d->format = data.toShort(0, false); if(d->format != FORMAT_PCM && totalSamples == 0) { debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); return; } d->channels = data.toShort(2, false); d->sampleRate = data.toUInt(4, false); d->bitsPerSample = data.toShort(14, false); if(totalSamples > 0) d->sampleFrames = totalSamples; else if(d->channels > 0 && d->bitsPerSample > 0) d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8)); if(d->sampleFrames > 0 && d->sampleRate > 0) { const double length = d->sampleFrames * 1000.0 / d->sampleRate; d->length = static_cast<int>(length + 0.5); d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); } else { const uint byteRate = data.toUInt(8, false); if(byteRate > 0) { d->length = static_cast<int>(streamLength * 1000.0 / byteRate + 0.5); d->bitrate = static_cast<int>(byteRate * 8.0 / 1000.0 + 0.5); } } }
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.toUInt(); d->file->seek(atom->offset + 16); uint pos = 4; while(count--) { long o = static_cast<long>(data.toUInt(pos)); 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.toUInt(); d->file->seek(atom->offset + 16); uint pos = 4; while(count--) { long long o = data.toLongLong(pos); 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->length - 9); const unsigned int flags = data.toUInt(0, 3, true); if(flags & 1) { long long o = data.toLongLong(7U); if(o > offset) { o += delta; } d->file->seek(atom->offset + 16); d->file->writeBlock(ByteVector::fromLongLong(o)); } } } }
uint read(TagLib::File &file, uint limit) { ByteVector data = file.readBlock(std::min(4U,limit)); value = data.toUInt(bigEndian); return data.size(); }