long MPEG_File::previousFrameOffset(long position) { // TODO: This will miss syncs spanning buffer read boundaries. while(int(position - BufferSize()) > int(BufferSize())) { position -= BufferSize(); Seek(position); SjByteVector buffer = ReadBlock(BufferSize()); // If the amount of data is smaller than an MPEG header (4 bytes) there's no // chance of this being valid. if(buffer.size() < 4) { return -1; } for(int i = buffer.size() - 2; i >= 0; i--) { if((unsigned char)(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) { return position + i; } } } return -1; }
void ID3v2_PopularimeterFrame::parseFields(const SjByteVector& data) { m_email.Empty(); m_rating255 = 0; m_counter = 0; int offset, pos = 0, size = (int)data.size(); offset = data.find(textDelimiter(SJ_LATIN1), pos); if( offset < pos ) { return; } m_email = data.mid(pos, offset - pos).toString(SJ_LATIN1); pos = offset + 1; if(pos < size) { m_rating255 = (int)(data[pos]); pos++; if(pos < size) { m_counter = data.mid(pos, 4).toUInt(); } } }
SjByteVector ID3v2_Frame::render() const { SjByteVector fieldData = renderFields(); m_header->setFrameSize(fieldData.size()); SjByteVector headerData = m_header->render(); return headerData + fieldData; }
SjByteVector ID3v2_Tag::render() { // We need to render the "tag data" first so that we have to correct size to // render in the tag's header. The "tag data" -- everything that is included // in ID3v2::Header::tagSize() -- includes the extended header, frames and // padding, but does not include the tag's header or footer. SjByteVector tagData; // TODO: Render the extended header. // Loop through the frames rendering them and adding them to the tagData. //for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) for ( ID3v2_FrameList::Node *node = m_frameList.GetFirst(); node; node = node->GetNext() ) { ID3v2_Frame* it = node->GetData(); if(!it->header()->tagAlterPreservation()) tagData.append(it->render()); } // Compute the amount of padding, and append that to tagData. SjUint paddingSize = 0; SjUint originalSize = m_header.tagSize(); if(tagData.size() < originalSize) paddingSize = originalSize - tagData.size(); else paddingSize = 1024; tagData.append(SjByteVector(paddingSize, char(0))); // Set the tag size. m_header.setTagSize(tagData.size()); // TODO: This should eventually include d->footer->render(). return m_header.render() + tagData; }
long MPEG_File::nextFrameOffset(long position) { // TODO: This will miss syncs spanning buffer read boundaries. SjByteVector buffer = ReadBlock(BufferSize()); while(buffer.size() > 0) { Seek(position); SjByteVector buffer = ReadBlock(BufferSize()); for(SjUint i = 0; i < buffer.size(); i++) { if((unsigned char)(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) { return position + i; } } position += BufferSize(); } return -1; }
void APE_Tag::parse(const SjByteVector &data) { SjUint pos = 0; // 11 bytes is the minimum size for an APE item for(SjUint i = 0; i < m_footer.itemCount() && pos <= data.size() - 11; i++) { APE_Item item; item.parse(data.mid(pos)); setItem(item.key().Upper(), item); pos += item.size(); } }
void ID3v2_AttachedPictureFrame::parseFields(const SjByteVector &data) { if(data.size() < 5) { wxLogDebug(wxT("A picture frame must contain at least 5 bytes.")); return; } int pos = 0, offset; // read text encoding m_textEncoding = (SjStringType)(data[pos]); pos += 1; if( header()->version() <= 2 ) { // read image format (3 characters), valid for ID3V2_2_1 or older m_mimeType = data.mid(pos, 3).toString(SJ_LATIN1); pos += 3; } else { // read mime type (null-terminated), valid for newer specs offset = data.find(textDelimiter(SJ_LATIN1), pos); if(offset < pos) return; m_mimeType = data.mid(pos, offset - pos).toString(SJ_LATIN1); pos = offset + 1; } // read type m_type = (ID3v2_AttachedPictureType)(data[pos]); pos += 1; // read description offset = data.find(textDelimiter(m_textEncoding), pos); if(offset < pos) return; m_description = data.mid(pos, offset - pos).toString(m_textEncoding); pos = offset + 1; // read image data m_data = data.mid(pos); }
SjByteVector APE_Tag::render() const { SjByteVector data; SjUint itemCount = 0; { SjHashIterator iterator; APE_Item* item; wxString key; while( (item=(APE_Item*)m_itemListMap.Iterate(iterator, key)) ) { data.append(item->render()); itemCount++; } } m_footer.setItemCount(itemCount); m_footer.setTagSize(data.size()+APE_FOOTER_SIZE); m_footer.setHeaderPresent(true); return m_footer.renderHeader() + data + m_footer.renderFooter(); }
void APE_Item::parse(const SjByteVector &data) { // 11 bytes is the minimum size for an APE item if(data.size() < 11) { wxLogDebug(wxT("APE::Item::parse() -- no data in item")); return; } SjUint valueLength = data.mid(0, 4).toUInt(false); SjUint flags = data.mid(4, 4).toUInt(false); m_key = data.mid(8).toString(SJ_UTF8); // data.mid(8) contains more than just the string -- but SjBytevector only converts up to the first null-byte at (***) m_binary = data.mid(8 + m_key.size() + 1, valueLength); setReadOnly(flags & 1); setType((APE_ItemType)((flags >> 1) & 3)); if(int(m_type) < 2) { m_stringList = m_binary.splitToStrings((unsigned char)'\0', SJ_UTF8); } }
void ID3v2_CommentsFrame::parseFields(const SjByteVector &data) { if(data.size() < 5) { wxLogDebug(wxT("A comment frame must contain at least 5 bytes.")); return; } m_textEncoding = (SjStringType)(data[0]); m_language = data.mid(1, 3); int byteAlign = (m_textEncoding == SJ_LATIN1 || m_textEncoding == SJ_UTF8) ? 1 : 2; //ByteVectorList l = ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2); SjArrayByteVector l = data.mid(4).splitToArray(textDelimiter(m_textEncoding), byteAlign, 2); if(l.GetCount() == 2) { /*d->description = String(l.front(), d->textEncoding); d->text = String(l.back(), d->textEncoding); */ m_description = l.Item(0).toString(m_textEncoding); m_text = l.Item(1).toString(m_textEncoding); } }
SjByteVector APE_Item::render() const { SjByteVector data; SjUint flags = ((m_readOnly) ? 1 : 0) | (m_type << 1); SjByteVector value; if(isEmpty()) return data; if(m_type != APE_ItemBinary) { int i, iCount = m_stringList.GetCount(); if( iCount>0 ) { value.appendString(m_stringList.Item(0), SJ_UTF8); for( i = 1; i < iCount; i++ ) { value.append((unsigned char)'\0'); value.appendString(m_stringList.Item(i), SJ_UTF8); } } // there should be no need to set back m_binary } else { value.append(m_binary); } data.append(SjByteVector::fromUint(value.size(), false)); data.append(SjByteVector::fromUint(flags, false)); data.appendString(m_key, SJ_UTF8); data.append(SjByteVector((unsigned char)'\0')); data.append(value); return data; }
void APE_Footer::parse(const SjByteVector &data) { if( data.size() < APE_FOOTER_SIZE ) { return; } // The first eight bytes, data[0..7], are the File Identifier, "APETAGEX". // Read the version number m_version = data.mid(8, 4).toUInt(false); // Read the tag size m_tagSize = data.mid(12, 4).toUInt(false); // Read the item count m_itemCount = data.mid(16, 4).toUInt(false); // Read the flags unsigned long flags = (unsigned long)data.mid(20, 4).toUInt(false); m_headerPresent = (flags & (1<<31))!=0; m_footerPresent = !((flags & (1<<30))!=0); m_isHeader = (flags & (1<<29))!=0; }
long MPEG_File::findID3v2() { // This method is based on the contents of Tagger_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; SjByteVector 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)*/((unsigned char)(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*/((unsigned char)(255)), firstSynchByte + 1); } // (3) partial match previousPartialMatch = buffer.endsWithPartialMatch(ID3v2_Header::fileIdentifier()); bufferOffset += BufferSize(); } // for() // Since we hit the end of the file, reset the status before continuing. Clear(); Seek(originalPosition); } return -1; }
void ID3v2_Tag::parse(const SjByteVector &data) { SjUint frameDataPosition = 0; SjUint frameDataLength = data.size(); // check for extended header if(m_header.extendedHeader()) { if(!m_extendedHeader) m_extendedHeader = new ID3v2_ExtendedHeader; m_extendedHeader->setData(data); if(m_extendedHeader->size() <= data.size()) { frameDataPosition += m_extendedHeader->size(); frameDataLength -= m_extendedHeader->size(); } } // check for footer -- we don't actually need to parse it, as it *must* // contain the same data as the header, but we do need to account for its // size. if(m_header.footerPresent() && ID3v2_Footer::size() <= frameDataLength) frameDataLength -= ID3v2_Footer::size(); // parse frames // Make sure that there is at least enough room in the remaining frame data for // a frame header. while(frameDataPosition < frameDataLength - ID3v2_Frame::headerSize(m_header.majorVersion())) { // If the next data is position is 0, assume that we've hit the padding // portion of the frame data. if(data.at(frameDataPosition) == 0) { if(m_header.footerPresent()) wxLogDebug(wxT("Padding *and* a footer found. This is not allowed by the spec.")); m_paddingSize = frameDataLength - frameDataPosition; return; } ID3v2_Frame *frame = m_factory->createFrame(data.mid(frameDataPosition), m_header.majorVersion()); if(!frame) return; // get the next frame position frameDataPosition += frame->size() + ID3v2_Frame::headerSize(m_header.majorVersion()); // add the frame if it has a size of at least 1 byte (smaller frames are not allowed // by the specification, but they're returned from createFrame() to allow seeking to the // next frame). // modification by me if(frame->size() <= 0) { delete frame; } else { addFrame(frame); } } }
void ID3v2_FrameHeader::setData(const SjByteVector &data, SjUint version) { // this was ID3v2_FrameHeaderPrivate m_isOkay = FALSE; m_frameSize = 0; m_version = 4; m_tagAlterPreservation = false; m_fileAlterPreservation = false; m_readOnly = false; m_groupingIdentity = false; m_compression = false; m_encryption = false; m_unsyncronisation = false; m_dataLengthIndicator = false; // /ID3v2_FrameHeaderPrivate m_version = version; switch(version) { case 0: case 1: case 2: { // ID3v2.2 if(data.size() < 3) { wxLogDebug(wxT("You must at least specify a frame ID.")); return; } // Set the frame ID -- the first three bytes m_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) { return; } m_frameSize = data.mid(3, 3).toUInt(); break; } case 3: { // ID3v2.3 - see http://www.id3.org/id3v2.3.0.html#sec3.3 if(data.size() < 4) { wxLogDebug(wxT("You must at least specify a frame ID.")); return; } // Set the frame ID -- the first four bytes m_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) { return; } // Set the size -- the frame size is the four bytes starting at byte four in // the frame header (structure 4) m_frameSize = data.mid(4, 4).toUInt(); { // read the first byte of flags /*std::bitset<8> flags(data[8]); d->tagAlterPreservation = flags[7]; // (structure 3.3.1.a) d->fileAlterPreservation = flags[6]; // (structure 3.3.1.b) d->readOnly = flags[5]; // (structure 3.3.1.c)*/ unsigned char flags = data[8]; m_tagAlterPreservation = (flags & (1<<7)) != 0; m_fileAlterPreservation = (flags & (1<<6)) != 0; m_readOnly = (flags & (1<<5)) != 0; } { // 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)*/ unsigned char flags = data[9]; m_compression = (flags & (1<<7)) != 0; m_encryption = (flags & (1<<6)) != 0; m_groupingIdentity = (flags & (1<<5)) != 0; } break; } case 4: default: { // ID3v2.4 if(data.size() < 4) { wxLogDebug(wxT("You must at least specify a frame ID.")); return; } // Set the frame ID -- the first four bytes m_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) { return; } // Set the size -- the frame size is the four bytes starting at byte four in // the frame header (structure 4) m_frameSize = ID3v2_SynchDataToUInt(data.mid(4, 4)); { // 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)*/ unsigned char flags = data[8]; m_tagAlterPreservation = (flags & (1<<6)) != 0; m_fileAlterPreservation = (flags & (1<<5)) != 0; m_readOnly = (flags & (1<<4)) != 0; } { // 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->unsyncronisation = flags[1]; // (structure 4.1.2.n) d->dataLengthIndicator = flags[0]; // (structure 4.1.2.p)*/ unsigned char flags = data[9]; m_groupingIdentity = (flags & (1<<6)) != 0; m_compression = (flags & (1<<3)) != 0; m_encryption = (flags & (1<<2)) != 0; m_unsyncronisation = (flags & (1<<1)) != 0; m_dataLengthIndicator = (flags & (1<<0)) != 0; } break; } } m_isOkay = TRUE; }
void MPEG_Header::parse(const SjByteVector &data) { // see http://www.mp3-tech.org/programmer/frame_header.html m_isValid = false; m_version = MPEG_Version1; m_layer = 0; m_protectionEnabled = false; m_sampleRate = 0; m_isPadded = false; m_channelMode = MPEG_Stereo; m_isCopyrighted = false; m_isOriginal = false; m_emphasis = 0; m_frameLength = 0; // check for the size and for the first synch byte if(data.size() < 4 || (unsigned char)(data[0]) != 0xff) { wxLogDebug(wxT("MPEG::Header::parse() -- First byte did not mactch MPEG synch.")); return; } unsigned long flags = data.toUInt(); // Check for the second byte's part of the MPEG synch if( !(flags&(1<<23)) || !(flags&(1<<22)) || !(flags&(1<<21)) ) { wxLogDebug(wxT("MPEG::Header::parse() -- Second byte did not mactch MPEG synch.")); return; } // Set the MPEG version if( !(flags&(1<<20)) && !(flags&(1<<19)) ) { m_version = MPEG_Version2_5; } else if( (flags&(1<<20)) && !(flags&(1<<19)) ) { m_version = MPEG_Version2; } else if( (flags&(1<<20)) && (flags&(1<<19)) ) { m_version = MPEG_Version1; } // Set the MPEG layer if( !(flags&(1<<18)) && (flags&(1<<17)) ) { m_layer = 3; } else if( (flags&(1<<18)) && !(flags&(1<<17)) ) { m_layer = 2; } else if( (flags&(1<<18)) && (flags&(1<<17)) ) { m_layer = 1; } // set protection flags m_protectionEnabled = !(flags&(1<<16)); // 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 = m_version == MPEG_Version1 ? 0 : 1; const int layerIndex = m_layer > 0 ? m_layer - 1 : 0; // The bitrate index is encoded as the first 4 bits of the 3rd byte, // i.e. 1111xxxx int i = (unsigned char)(data[2]) >> 4; m_bitrate = bitrates[versionIndex][layerIndex][i]; // 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 i = (unsigned char)(data[2]) >> 2 & 0x03; m_sampleRate = sampleRates[m_version][i]; if(m_sampleRate == 0) { wxLogDebug(wxT("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 - stimmt nicht! s. http://www.mp3-tech.org/programmer/frame_header.html m_channelMode = (MPEG_HeaderChannelMode)((flags&0xC0) >> 6); // TODO: Add mode extension for completeness m_isCopyrighted = (flags&(1<<3)) != 0; m_isOriginal = (flags&(1<<2)) != 0; m_emphasis = (unsigned char)flags&3; // Calculate the frame length if( m_layer == 1 ) { m_frameLength = 24000 * 2 * m_bitrate / m_sampleRate + int(m_isPadded); } else { m_frameLength = 72000 * m_bitrate / m_sampleRate + int(m_isPadded); } // Now that we're done parsing, set this to be a valid header m_isValid = true; }