void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) { ByteVector formatData; uint streamLength = 0; for(uint i = 0; i < chunkCount(); i++) { String name = chunkName(i); if(name == "ID3 " || name == "id3 ") { d->tagChunkID = chunkName(i); d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); } else if(name == "fmt " && readProperties) formatData = chunkData(i); else if(name == "data" && readProperties) streamLength = chunkDataSize(i); else if(name == "LIST") { ByteVector data = chunkData(i); ByteVector type = data.mid(0, 4); if(type == "INFO") d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); } } if (!d->tag[ID3v2Index]) d->tag.set(ID3v2Index, new ID3v2::Tag); if (!d->tag[InfoIndex]) d->tag.set(InfoIndex, new RIFF::Info::Tag); if(!formatData.isEmpty()) d->properties = new Properties(formatData, streamLength, propertiesStyle); }
String::String(const ByteVector &v, Type t) { d = new StringPrivate; if(v.isEmpty()) return; if(t == Latin1 || t == UTF8) { int length = 0; d->data.resize(v.size()); wstring::iterator targetIt = d->data.begin(); for(ByteVector::ConstIterator it = v.begin(); it != v.end() && (*it); ++it) { *targetIt = uchar(*it); ++targetIt; ++length; } d->data.resize(length); } else { d->data.resize(v.size() / 2); wstring::iterator targetIt = d->data.begin(); for(ByteVector::ConstIterator it = v.begin(); it != v.end() && it + 1 != v.end() && combine(*it, *(it + 1)); it += 2) { *targetIt = combine(*it, *(it + 1)); ++targetIt; } } prepare(t); }
long MPEG::File::findID3v2() { if(!isValid()) return -1; // An ID3v2 tag or MPEG frame is most likely be at the beginning of the file. const ByteVector headerID = ID3v2::Header::fileIdentifier(); seek(0); if(readBlock(headerID.size()) == headerID) return 0; const Header firstHeader(this, 0, true); if(firstHeader.isValid()) return -1; // Look for an ID3v2 tag until reaching the first valid MPEG frame. ByteVector frameSyncBytes(2, '\0'); ByteVector tagHeaderBytes(3, '\0'); long position = 0; while(true) { seek(position); const ByteVector buffer = readBlock(bufferSize()); if(buffer.isEmpty()) return -1; for(unsigned int i = 0; i < buffer.size(); ++i) { frameSyncBytes[0] = frameSyncBytes[1]; frameSyncBytes[1] = buffer[i]; if(isFrameSync(frameSyncBytes)) { const Header header(this, position + i - 1, true); if(header.isValid()) return -1; } tagHeaderBytes[0] = tagHeaderBytes[1]; tagHeaderBytes[1] = tagHeaderBytes[2]; tagHeaderBytes[2] = buffer[i]; if(tagHeaderBytes == headerID) return position + i - 2; } position += bufferSize(); } }
void ID3v2::Tag::setAlbumArt(const ByteVector &v, ID3v2::AttachedPictureFrame::Type arttype, String &mimetype) { if (v.isEmpty()) { removeFrames("APIC"); return; } else { if (!d->frameListMap["APIC"].isEmpty()) { removeFrames("APIC"); } else { // do nothing } AttachedPictureFrame *f = new AttachedPictureFrame("APIC"); f->setMimeType(mimetype); f->setType(arttype); f->setPicture(v); addFrame(f); } return; }
void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) { ByteVector formatData; uint streamLength = 0; for(uint i = 0; i < chunkCount(); i++) { if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { d->tagChunkID = chunkName(i); d->tag = new ID3v2::Tag(this, chunkOffset(i)); } else if(chunkName(i) == "fmt " && readProperties) formatData = chunkData(i); else if(chunkName(i) == "data" && readProperties) streamLength = chunkDataSize(i); } if(!formatData.isEmpty()) d->properties = new Properties(formatData, streamLength, propertiesStyle); if(!d->tag) d->tag = new ID3v2::Tag; }
long MPEG::File::nextFrameOffset(long position) { ByteVector frameSyncBytes(2, '\0'); while(true) { seek(position); const ByteVector buffer = readBlock(bufferSize()); if(buffer.isEmpty()) return -1; for(unsigned int i = 0; i < buffer.size(); ++i) { frameSyncBytes[0] = frameSyncBytes[1]; frameSyncBytes[1] = buffer[i]; if(isFrameSync(frameSyncBytes)) { const Header header(this, position + i - 1, true); if(header.isValid()) return position + i - 1; } } position += bufferSize(); } }
ByteVector RIFF::Info::Tag::render() const { ByteVector data("INFO"); FieldListMap::ConstIterator it = d->fieldListMap.begin(); for(; it != d->fieldListMap.end(); ++it) { ByteVector text = TagPrivate::stringHandler->render(it->second); if(text.isEmpty()) continue; data.append(it->first); data.append(ByteVector::fromUInt(text.size() + 1, false)); data.append(text); do { data.append('\0'); } while(data.size() & 1); } if(data.size() == 4) return ByteVector(); else return data; }
void File::insert(const ByteVector &data, ulong start, ulong replace) { if(!d->file) return; if(data.size() == replace) { seek(start); writeBlock(data); return; } else if(data.size() < replace) { seek(start); writeBlock(data); removeBlock(start + data.size(), replace - data.size()); return; } // Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore // and avoid TagLib's high level API for rendering just copying parts of // the file that don't contain tag data. // // Now I'll explain the steps in this ugliness: // First, make sure that we're working with a buffer that is longer than // the *differnce* in the tag sizes. We want to avoid overwriting parts // that aren't yet in memory, so this is necessary. ulong bufferLength = bufferSize(); while(data.size() - replace > bufferLength) bufferLength += bufferSize(); // Set where to start the reading and writing. long readPosition = start + replace; long writePosition = start; ByteVector buffer; ByteVector aboutToOverwrite(static_cast<uint>(bufferLength)); // This is basically a special case of the loop below. Here we're just // doing the same steps as below, but since we aren't using the same buffer // size -- instead we're using the tag size -- this has to be handled as a // special case. We're also using File::writeBlock() just for the tag. // That's a bit slower than using char *'s so, we're only doing it here. seek(readPosition); int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file); readPosition += bufferLength; seek(writePosition); writeBlock(data); writePosition += data.size(); buffer = aboutToOverwrite; // In case we've already reached the end of file... buffer.resize(bytesRead); // Ok, here's the main loop. We want to loop until the read fails, which // means that we hit the end of the file. while(!buffer.isEmpty()) { // Seek to the current read position and read the data that we're about // to overwrite. Appropriately increment the readPosition. seek(readPosition); bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file); aboutToOverwrite.resize(bytesRead); readPosition += bufferLength; // Check to see if we just read the last block. We need to call clear() // if we did so that the last write succeeds. if(ulong(bytesRead) < bufferLength) clear(); // Seek to the write position and write our buffer. Increment the // writePosition. seek(writePosition); fwrite(buffer.data(), sizeof(char), buffer.size(), d->file); writePosition += buffer.size(); // Make the current buffer the data that we read in the beginning. buffer = aboutToOverwrite; // Again, we need this for the last write. We don't want to write garbage // at the end of our file, so we need to set the buffer size to the amount // that we actually read. bufferLength = bytesRead; } }
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); } } }
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; }
long File::find(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. 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 iteration, 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(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) { // (1) previous partial match if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) { const int patternOffset = (bufferSize() - previousPartialMatch); if(buffer.containsAt(pattern, 0, patternOffset)) { seek(originalPosition); return bufferOffset - bufferSize() + previousPartialMatch; } } if(!before.isEmpty() && beforePreviousPartialMatch >= 0 && int(bufferSize()) > beforePreviousPartialMatch) { const int beforeOffset = (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.isEmpty() && buffer.find(before) >= 0) { seek(originalPosition); return -1; } // (3) partial match previousPartialMatch = buffer.endsWithPartialMatch(pattern); if(!before.isEmpty()) beforePreviousPartialMatch = buffer.endsWithPartialMatch(before); bufferOffset += bufferSize(); } // Since we hit the end of the file, reset the status before continuing. clear(); seek(originalPosition); return -1; }