void AttachedPictureFrame::parseFields(const ByteVector &data) { if(data.size() < 5) { debug("A picture frame must contain at least 5 bytes."); return; } int pos = 0; d->textEncoding = String::Type(data[pos]); pos += 1; int offset = data.find(textDelimiter(String::Latin1), pos); if(offset < pos) return; d->mimeType = String(data.mid(pos, offset - pos), String::Latin1); pos = offset + 1; d->type = Type(data[pos]); pos += 1; offset = data.find(textDelimiter(d->textEncoding), pos); if(offset < pos) return; d->description = String(data.mid(pos, offset - pos), d->textEncoding); pos = offset + 1; d->data = data.mid(pos); }
void ASF::Picture::parse(const ByteVector& bytes) { d->valid = false; if(bytes.size() < 9) return; int pos = 0; d->type = (Type)bytes[0]; ++pos; uint dataLen = bytes.mid(pos, 4).toUInt(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 UserUrlLinkFrame::parseFields(const ByteVector &data) { if(data.size() < 2) { debug("A user URL link frame must contain at least 2 bytes."); return; } int pos = 0; d->textEncoding = String::Type(data[0]); pos += 1; if(d->textEncoding == String::Latin1 || d->textEncoding == String::UTF8) { int offset = data.find(textDelimiter(d->textEncoding), pos); if(offset < pos) return; d->description = String(data.mid(pos, offset - pos), d->textEncoding); pos = offset + 1; } else { int len = data.mid(pos).find(textDelimiter(d->textEncoding), 0, 2); if(len < 0) return; d->description = String(data.mid(pos, len), d->textEncoding); pos += len + 2; } setUrl(String(data.mid(pos))); }
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 RelativeVolumeFrame::parseFields(const ByteVector &data) { uint pos = data.find(textDelimiter(String::Latin1)); d->identification = String(data.mid(0, pos), String::Latin1); // Each channel is at least 4 bytes. while(pos <= data.size() - 4) { ChannelType type = ChannelType(data[pos]); pos += 1; ChannelData &channel = d->channels[type]; channel.volumeAdjustment = data.mid(pos, 2).toShort(); pos += 2; channel.peakVolume.bitsRepresentingPeak = data[pos]; pos += 1; int bytes = bitsToBytes(channel.peakVolume.bitsRepresentingPeak); channel.peakVolume.peakVolume = data.mid(pos, bytes); pos += bytes; } }
void testResize() { ByteVector a = ByteVector("0123456789"); ByteVector b = a.mid(3, 4); b.resize(6, 'A'); CPPUNIT_ASSERT_EQUAL(uint(6), b.size()); CPPUNIT_ASSERT_EQUAL('6', b[3]); CPPUNIT_ASSERT_EQUAL('A', b[4]); CPPUNIT_ASSERT_EQUAL('A', b[5]); b.resize(10, 'B'); CPPUNIT_ASSERT_EQUAL(uint(10), b.size()); CPPUNIT_ASSERT_EQUAL('6', b[3]); CPPUNIT_ASSERT_EQUAL('B', b[6]); CPPUNIT_ASSERT_EQUAL('B', b[9]); b.resize(3, 'C'); CPPUNIT_ASSERT_EQUAL(uint(3), b.size()); CPPUNIT_ASSERT_EQUAL(-1, b.find('C')); b.resize(3); CPPUNIT_ASSERT_EQUAL(uint(3), b.size()); // Check if a and b were properly detached. CPPUNIT_ASSERT_EQUAL(uint(10), a.size()); CPPUNIT_ASSERT_EQUAL('3', a[3]); CPPUNIT_ASSERT_EQUAL('5', a[5]); // Special case that refCount == 1 and d->offset != 0. ByteVector c = ByteVector("0123456789").mid(3, 4); c.resize(6, 'A'); CPPUNIT_ASSERT_EQUAL(uint(6), c.size()); CPPUNIT_ASSERT_EQUAL('6', c[3]); CPPUNIT_ASSERT_EQUAL('A', c[4]); CPPUNIT_ASSERT_EQUAL('A', c[5]); c.resize(10, 'B'); CPPUNIT_ASSERT_EQUAL(uint(10), c.size()); CPPUNIT_ASSERT_EQUAL('6', c[3]); CPPUNIT_ASSERT_EQUAL('B', c[6]); CPPUNIT_ASSERT_EQUAL('B', c[9]); c.resize(3, 'C'); CPPUNIT_ASSERT_EQUAL(uint(3), c.size()); CPPUNIT_ASSERT_EQUAL(-1, c.find('C')); }
uint read(TagLib::File &file, uint limit) { ByteVector data = file.readBlock(std::min(m_size, limit)); uint count = data.size(); int index = data.find((char) 0); if(index > -1) { data.resize(index); } data.replace((char) 0xff, ' '); value = data; return count; }
ByteVectorList ByteVectorList::split( const ByteVector &v, const ByteVector &pattern, size_t byteAlign, size_t max) { ByteVectorList l; size_t previousOffset = 0; for(size_t offset = v.find(pattern, 0, byteAlign); offset != ByteVector::npos() && (max == 0 || max > l.size() + 1); offset = v.find(pattern, offset + pattern.size(), byteAlign)) { if(offset - previousOffset >= 1) l.append(v.mid(previousOffset, offset - previousOffset)); else l.append(ByteVector()); previousOffset = offset + pattern.size(); } if(previousOffset < v.size()) l.append(v.mid(previousOffset, v.size() - previousOffset)); return l; }
ByteVectorList ByteVectorList::split(const ByteVector &v, const ByteVector &pattern, int byteAlign, int max) { ByteVectorList l; uint previousOffset = 0; for(int offset = v.find(pattern, 0, byteAlign); offset != -1 && (max == 0 || max > int(l.size()) + 1); offset = v.find(pattern, offset + pattern.size(), byteAlign)) { if(offset - previousOffset > 1) l.append(v.mid(previousOffset, offset - previousOffset)); else l.append(ByteVector::null); previousOffset = offset + pattern.size(); } if(previousOffset < v.size()) l.append(v.mid(previousOffset, v.size() - previousOffset)); return l; }
void PrivateFrame::parseFields(const ByteVector &data) { if(data.size() < 2) { debug("A private frame must contain at least 2 bytes."); return; } // Owner identifier is assumed to be Latin1 const int byteAlign = 1; const int endOfOwner = data.find(textDelimiter(String::Latin1), 0, byteAlign); d->owner = String(data.mid(0, endOfOwner)); d->data = data.mid(endOfOwner + 1); }
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; }
long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before) { if(!d->stream || pattern.size() > d->bufferSize) return -1; // The position in the file that the current buffer starts at. ByteVector buffer; // These variables are used to keep track of a partial match that happens at // the end of a buffer. /* int previousPartialMatch = -1; int beforePreviousPartialMatch = -1; */ // Save the location of the current read pointer. We will restore the // position using seek() before all returns. long originalPosition = tell(); // Start the search at the offset. long bufferOffset; if(fromOffset == 0) { seek(-1 * int(d->bufferSize), End); bufferOffset = tell(); } else { seek(fromOffset + -1 * int(d->bufferSize), Beginning); bufferOffset = tell(); } // See the notes in find() for an explanation of this algorithm. for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) { // TODO: (1) previous partial match // (2) pattern contained in current buffer long location = buffer.rfind(pattern); if(location >= 0) { seek(originalPosition); return bufferOffset + location; } if(!before.isNull() && buffer.find(before) >= 0) { seek(originalPosition); return -1; } // TODO: (3) partial match bufferOffset -= d->bufferSize; seek(bufferOffset); } // Since we hit the end of the file, reset the status before continuing. clear(); seek(originalPosition); return -1; }
long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before) { if(!d->stream || pattern.size() > d->bufferSize) return -1; // The position in the file that the current buffer starts at. long bufferOffset = fromOffset; ByteVector buffer; // These variables are used to keep track of a partial match that happens at // the end of a buffer. int previousPartialMatch = -1; int beforePreviousPartialMatch = -1; // Save the location of the current read pointer. We will restore the // position using seek() before all returns. long originalPosition = tell(); // Start the search at the offset. seek(fromOffset); // This loop is the crux of the find method. There are three cases that we // want to account for: // // (1) The previously searched buffer contained a partial match of the search // pattern and we want to see if the next one starts with the remainder of // that pattern. // // (2) The search pattern is wholly contained within the current buffer. // // (3) The current buffer ends with a partial match of the pattern. We will // note this for use in the next itteration, where we will check for the rest // of the pattern. // // All three of these are done in two steps. First we check for the pattern // and do things appropriately if a match (or partial match) is found. We // then check for "before". The order is important because it gives priority // to "real" matches. for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) { // (1) previous partial match if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) { const int patternOffset = (d->bufferSize - previousPartialMatch); if(buffer.containsAt(pattern, 0, patternOffset)) { seek(originalPosition); return bufferOffset - d->bufferSize + previousPartialMatch; } } if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) { const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch); if(buffer.containsAt(before, 0, beforeOffset)) { seek(originalPosition); return -1; } } // (2) pattern contained in current buffer long location = buffer.find(pattern); if(location >= 0) { seek(originalPosition); return bufferOffset + location; } if(!before.isNull() && buffer.find(before) >= 0) { seek(originalPosition); return -1; } // (3) partial match previousPartialMatch = buffer.endsWithPartialMatch(pattern); if(!before.isNull()) beforePreviousPartialMatch = buffer.endsWithPartialMatch(before); bufferOffset += d->bufferSize; } // Since we hit the end of the file, reset the status before continuing. clear(); seek(originalPosition); return -1; }
void IT::File::read(bool) { if(!isOpen()) return; seek(0); READ_ASSERT(readBlock(4) == "IMPM"); READ_STRING(d->tag.setTitle, 26); seek(2, Current); READ_U16L_AS(length); READ_U16L_AS(instrumentCount); READ_U16L_AS(sampleCount); d->properties.setInstrumentCount(instrumentCount); d->properties.setSampleCount(sampleCount); READ_U16L(d->properties.setPatternCount); READ_U16L(d->properties.setVersion); READ_U16L(d->properties.setCompatibleVersion); READ_U16L(d->properties.setFlags); READ_U16L_AS(special); d->properties.setSpecial(special); READ_BYTE(d->properties.setGlobalVolume); READ_BYTE(d->properties.setMixVolume); READ_BYTE(d->properties.setBpmSpeed); READ_BYTE(d->properties.setTempo); READ_BYTE(d->properties.setPanningSeparation); READ_BYTE(d->properties.setPitchWheelDepth); // IT supports some kind of comment tag. Still, the // sample/instrument names are abused as comments so // I just add all together. String message; if(special & Properties::MessageAttached) { READ_U16L_AS(messageLength); READ_U32L_AS(messageOffset); seek(messageOffset); ByteVector messageBytes = readBlock(messageLength); READ_ASSERT(messageBytes.size() == messageLength); int index = messageBytes.find((char) 0); if(index > -1) messageBytes.resize(index, 0); messageBytes.replace('\r', '\n'); message = messageBytes; } seek(64); ByteVector pannings = readBlock(64); ByteVector volumes = readBlock(64); READ_ASSERT(pannings.size() == 64 && volumes.size() == 64); int channels = 0; for(int i = 0; i < 64; ++ i) { // Strictly speaking an IT file has always 64 channels, but // I don't count disabled and muted channels. // But this always gives 64 channels for all my files anyway. // Strangely VLC does report other values. I wonder how VLC // gets it's values. if((unsigned char) pannings[i] < 128 && volumes[i] > 0) ++channels; } d->properties.setChannels(channels); // real length might be shorter because of skips and terminator ushort realLength = 0; for(ushort i = 0; i < length; ++ i) { READ_BYTE_AS(order); if(order == 255) break; if(order != 254) ++ realLength; } d->properties.setLengthInPatterns(realLength); StringList comment; // Note: I found files that have nil characters somewhere // in the instrument/sample names and more characters // afterwards. The spec does not mention such a case. // Currently I just discard anything after a nil, but // e.g. VLC seems to interprete a nil as a space. I // don't know what is the proper behaviour. for(ushort i = 0; i < instrumentCount; ++ i) { seek(192L + length + ((long)i << 2)); READ_U32L_AS(instrumentOffset); seek(instrumentOffset); ByteVector instrumentMagic = readBlock(4); READ_ASSERT(instrumentMagic == "IMPI"); READ_STRING_AS(dosFileName, 13); seek(15, Current); READ_STRING_AS(instrumentName, 26); comment.append(instrumentName); } for(ushort i = 0; i < sampleCount; ++ i) { seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2)); READ_U32L_AS(sampleOffset); seek(sampleOffset); ByteVector sampleMagic = readBlock(4); READ_ASSERT(sampleMagic == "IMPS"); READ_STRING_AS(dosFileName, 13); READ_BYTE_AS(globalVolume); READ_BYTE_AS(sampleFlags); READ_BYTE_AS(sampleVolume); READ_STRING_AS(sampleName, 26); /* READ_BYTE_AS(sampleCvt); READ_BYTE_AS(samplePanning); READ_U32L_AS(sampleLength); READ_U32L_AS(loopStart); READ_U32L_AS(loopStop); READ_U32L_AS(c5speed); READ_U32L_AS(sustainLoopStart); READ_U32L_AS(sustainLoopEnd); READ_U32L_AS(sampleDataOffset); READ_BYTE_AS(vibratoSpeed); READ_BYTE_AS(vibratoDepth); READ_BYTE_AS(vibratoRate); READ_BYTE_AS(vibratoType); */ comment.append(sampleName); } if(message.size() > 0) comment.append(message); d->tag.setComment(comment.toString("\n")); d->tag.setTrackerName("Impulse Tracker"); }
long MPEG::File::findID3v2() { // This method is based on the contents of TagLib::File::find(), but because // of some subtlteies -- specifically the need to look for the bit pattern of // an MPEG sync, it has been modified for use here. if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { // The position in the file that the current buffer starts at. long bufferOffset = 0; ByteVector buffer; // These variables are used to keep track of a partial match that happens at // the end of a buffer. int previousPartialMatch = -1; bool previousPartialSynchMatch = false; // Save the location of the current read pointer. We will restore the // position using seek() before all returns. long originalPosition = tell(); // Start the search at the beginning of the file. seek(0); // This loop is the crux of the find method. There are three cases that we // want to account for: // (1) The previously searched buffer contained a partial match of the search // pattern and we want to see if the next one starts with the remainder of // that pattern. // // (2) The search pattern is wholly contained within the current buffer. // // (3) The current buffer ends with a partial match of the pattern. We will // note this for use in the next itteration, where we will check for the rest // of the pattern. for(buffer = readBlock(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) { // (1) previous partial match if(previousPartialSynchMatch && secondSynchByte(buffer[0])) return -1; if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) { const int patternOffset = (bufferSize() - previousPartialMatch); if(buffer.containsAt(ID3v2::Header::fileIdentifier(), 0, patternOffset)) { seek(originalPosition); return bufferOffset - bufferSize() + previousPartialMatch; } } // (2) pattern contained in current buffer long location = buffer.find(ID3v2::Header::fileIdentifier()); if(location >= 0) { seek(originalPosition); return bufferOffset + location; } int firstSynchByte = buffer.find(char(uchar(255))); // Here we have to loop because there could be several of the first // (11111111) byte, and we want to check all such instances until we find // a full match (11111111 111) or hit the end of the buffer. while(firstSynchByte >= 0) { // if this *is not* at the end of the buffer if(firstSynchByte < int(buffer.size()) - 1) { if(secondSynchByte(buffer[firstSynchByte + 1])) { // We've found the frame synch pattern. seek(originalPosition); return -1; } else { // We found 11111111 at the end of the current buffer indicating a // partial match of the synch pattern. The find() below should // return -1 and break out of the loop. previousPartialSynchMatch = true; } } // Check in the rest of the buffer. firstSynchByte = buffer.find(char(uchar(255)), firstSynchByte + 1); } // (3) partial match previousPartialMatch = buffer.endsWithPartialMatch(ID3v2::Header::fileIdentifier()); bufferOffset += bufferSize(); } // Since we hit the end of the file, reset the status before continuing. clear(); seek(originalPosition); } return -1; }
long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before) { if(!d->stream || pattern.size() > bufferSize()) return -1; // The position in the file that the current buffer starts at. ByteVector buffer; // These variables are used to keep track of a partial match that happens at // the end of a buffer. /* int previousPartialMatch = -1; int beforePreviousPartialMatch = -1; */ // Save the location of the current read pointer. We will restore the // position using seek() before all returns. long originalPosition = tell(); // Start the search at the offset. if(fromOffset == 0) fromOffset = length(); long bufferLength = bufferSize(); long bufferOffset = fromOffset + pattern.size(); // See the notes in find() for an explanation of this algorithm. while(true) { if(bufferOffset > bufferLength) { bufferOffset -= bufferLength; } else { bufferLength = bufferOffset; bufferOffset = 0; } seek(bufferOffset); buffer = readBlock(bufferLength); if(buffer.isEmpty()) break; // TODO: (1) previous partial match // (2) pattern contained in current buffer const long location = buffer.rfind(pattern); if(location >= 0) { seek(originalPosition); return bufferOffset + location; } if(!before.isEmpty() && buffer.find(before) >= 0) { seek(originalPosition); return -1; } // TODO: (3) partial match } // Since we hit the end of the file, reset the status before continuing. clear(); seek(originalPosition); return -1; }