/// <summary> /// Reads the raw data from a specified stream. /// </summary> /// <param name="stream">The stream to read from.</param> public void Read(Stream stream) { // Check for 'ID3' marker byte[] identifier = stream.Read(3); if (!(identifier[0] == 0x49 && identifier[1] == 0x44 && identifier[2] == 0x33)) { return; } // Read the header _id3v2Header = new ID3v2Header(stream, false); TagReadingInfo tagReadingInfo = new TagReadingInfo(_id3v2Header.TagVersion); if (_id3v2Header.UsesUnsynchronization) { tagReadingInfo.TagVersionOptions = TagVersionOptions.Unsynchronized; } else { tagReadingInfo.TagVersionOptions = TagVersionOptions.None; } if (_id3v2Header.HasExtendedHeader) { _id3v2ExtendedHeader = new ID3v2ExtendedHeader(tagReadingInfo, stream); } int frameIDSize = (tagReadingInfo.TagVersion == ID3v2TagVersion.ID3v22 ? 3 : 4); int bytesRead; int readUntil; #region <<< ID3v2.4 - Guess if syncsafe frame size was used or not >>> if (_id3v2Header.TagVersion == ID3v2TagVersion.ID3v24) { bool isID3v24SyncSafe = true; bytesRead = 0; readUntil = _id3v2Header.TagSize - _id3v2ExtendedHeader.SizeIncludingSizeBytes - frameIDSize; long initialPosition = stream.Position; while (bytesRead < readUntil) { byte[] frameIDBytes = stream.Read(frameIDSize); // TODO: Noticed some tags contain 0x00 'E' 'N' as a FrameID. Frame is well structured // and other frames follow. I believe the below (keep reading+looking) will cover this issue. // If character is not a letter or number, padding reached, audio began, // or otherwise the frame is not readable if (frameIDBytes[0] < 0x30 || frameIDBytes[0] > 0x5A || frameIDBytes[1] < 0x30 || frameIDBytes[1] > 0x5A || frameIDBytes[2] < 0x30 || frameIDBytes[2] > 0x5A || frameIDBytes[3] < 0x30 || frameIDBytes[3] > 0x5A) { // TODO: Try to keep reading and look for a valid frame if (frameIDBytes[0] != 0 && frameIDBytes[0] != 0xFF) { /*String msg = String.Format("Out of range FrameID - 0x{0:X}|0x{1:X}|0x{2:X}|0x{3:X}", * tmpFrameIDBytes[0], tmpFrameIDBytes[1], tmpFrameIDBytes[2], * tmpFrameIDBytes[3]); * Trace.WriteLine(msg);*/ } break; } int frameSize = stream.ReadInt32(); if (frameSize > 0xFF) { if ((frameSize & 0x80) == 0x80) { isID3v24SyncSafe = false; break; } if ((frameSize & 0x8000) == 0x8000) { isID3v24SyncSafe = false; break; } if ((frameSize & 0x800000) == 0x800000) { isID3v24SyncSafe = false; break; } if (bytesRead + frameSize + 10 == _id3v2Header.TagSize) { // Could give a false positive, but relatively unlikely (famous last words, huh?) isID3v24SyncSafe = false; break; } else { stream.Seek(-4, SeekOrigin.Current); // go back to read sync-safe version int syncSafeFrameSize = ID3v2Utils.ReadInt32SyncSafe(stream); long currentPosition = stream.Position; bool isValidAtSyncSafe = true; bool isValidAtNonSyncSafe = true; // TODO - if it's the last frame and there is padding, both would indicate false // Use the one that returns some padding bytes opposed to bytes with non-zero values (could be frame data) // If non sync-safe reads past the end of the tag, then it's sync safe // Testing non-sync safe since it will always be bigger than the sync safe integer if (currentPosition + frameSize + 2 >= readUntil) { isID3v24SyncSafe = true; break; } // Test non-sync safe stream.Seek(currentPosition + frameSize + 2, SeekOrigin.Begin); frameIDBytes = stream.Read(frameIDSize); if (frameIDBytes[0] < 0x30 || frameIDBytes[0] > 0x5A || frameIDBytes[1] < 0x30 || frameIDBytes[1] > 0x5A || frameIDBytes[2] < 0x30 || frameIDBytes[2] > 0x5A || frameIDBytes[3] < 0x30 || frameIDBytes[3] > 0x5A) { isValidAtNonSyncSafe = false; } // Test sync-safe stream.Seek(currentPosition + syncSafeFrameSize + 2, SeekOrigin.Begin); frameIDBytes = stream.Read(frameIDSize); if (frameIDBytes[0] < 0x30 || frameIDBytes[0] > 0x5A || frameIDBytes[1] < 0x30 || frameIDBytes[1] > 0x5A || frameIDBytes[2] < 0x30 || frameIDBytes[2] > 0x5A || frameIDBytes[3] < 0x30 || frameIDBytes[3] > 0x5A) { isValidAtSyncSafe = false; } // if they're equal, we'll just have to go with syncsafe, since that's the spec if (isValidAtNonSyncSafe != isValidAtSyncSafe) { isID3v24SyncSafe = isValidAtSyncSafe; } break; } } stream.Seek(frameSize + 2, SeekOrigin.Current); bytesRead += frameSize + 10; } stream.Position = initialPosition; if (isID3v24SyncSafe == false) { tagReadingInfo.TagVersionOptions |= TagVersionOptions.UseNonSyncSafeFrameSizeID3v24; } } else if (_id3v2Header.TagVersion == ID3v2TagVersion.ID3v22) { bool isID3v22CorrectSize = true; bytesRead = 0; readUntil = _id3v2Header.TagSize - _id3v2ExtendedHeader.SizeIncludingSizeBytes - frameIDSize; long initialPosition = stream.Position; stream.Read(frameIDSize); UnknownFrame unknownFrame = new UnknownFrame(null, tagReadingInfo, stream); bytesRead += unknownFrame.FrameHeader.FrameSizeTotal; if (bytesRead < readUntil) { byte[] frameIDBytes = stream.Read(frameIDSize); // TODO: Noticed some tags contain 0x00 'E' 'N' as a FrameID. Frame is well structured // and other frames follow. I believe the below (keep reading+looking) will cover this issue. // If character is not a letter or number, padding reached, audio began, // or otherwise the frame is not readable if (frameIDBytes[0] < 0x30 || frameIDBytes[0] > 0x5A) { if (frameIDBytes[1] >= 0x30 && frameIDBytes[1] <= 0x5A && frameIDBytes[2] >= 0x30 && frameIDBytes[2] <= 0x5A) { Trace.WriteLine("ID3v2.2 frame size off by 1 byte"); isID3v22CorrectSize = false; } } } stream.Position = initialPosition; if (isID3v22CorrectSize == false) { tagReadingInfo.TagVersionOptions |= TagVersionOptions.AddOneByteToSize; } } #endregion <<< ID3v2.4 - Guess if syncsafe frame size was used or not >>> readUntil = _id3v2Header.TagSize - _id3v2ExtendedHeader.SizeIncludingSizeBytes - frameIDSize; Read(stream, _id3v2Header.TagVersion, tagReadingInfo, readUntil, frameIDSize); }
public void Read(TagReadingInfo tagReadingInfo, ref Stream stream) { // TODO: Some tags have the length INCLUDE the extra ten bytes of the tag header. // Handle this (don't corrupt MP3 on rewrite) m_TagVersion = tagReadingInfo.TagVersion; bool usesUnsynchronization = ((tagReadingInfo.TagVersionOptions & TagVersionOptions.Unsynchronized) == TagVersionOptions.Unsynchronized); if (tagReadingInfo.TagVersion == ID3v2TagVersion.ID3v23) { if (!usesUnsynchronization) { m_FrameSize = stream.ReadInt32(); } else { m_FrameSize = ID3v2Utils.ReadInt32Unsynchronized(stream); } m_FrameSizeExcludingAdditions = m_FrameSize; byte byte0 = stream.Read1(); byte byte1 = stream.Read1(); // First byte IsTagAlterPreservation = ((byte0 & 0x80) == 0x80); IsFileAlterPreservation = ((byte0 & 0x40) == 0x40); IsReadOnly = ((byte0 & 0x20) == 0x20); // Second byte IsCompressed = ((byte1 & 0x80) == 0x80); bool tmpIsEncrypted = ((byte1 & 0x40) == 0x40); bool tmpIsGroupingIdentity = ((byte1 & 0x20) == 0x20); // Additional bytes // Compression if (IsCompressed) { DecompressedSize = stream.ReadInt32(); m_FrameSizeExcludingAdditions -= 4; } else { DecompressedSize = 0; } // Encryption if (tmpIsEncrypted) { EncryptionMethod = stream.Read1(); m_FrameSizeExcludingAdditions -= 1; } else { EncryptionMethod = null; } // Grouping Identity if (tmpIsGroupingIdentity) { GroupingIdentity = stream.Read1(); m_FrameSizeExcludingAdditions -= 1; } else { GroupingIdentity = null; } if (usesUnsynchronization) { stream = ID3v2Utils.ReadUnsynchronizedStream(stream, m_FrameSize); } } else if (tagReadingInfo.TagVersion == ID3v2TagVersion.ID3v22) { if (!usesUnsynchronization) { m_FrameSize = stream.ReadInt24(); } else { m_FrameSize = ID3v2Utils.ReadInt24Unsynchronized(stream); } if ((tagReadingInfo.TagVersionOptions & TagVersionOptions.AddOneByteToSize) == TagVersionOptions.AddOneByteToSize) { m_FrameSize++; } m_FrameSizeExcludingAdditions = m_FrameSize; // These fields are not supported in ID3v2.2 IsTagAlterPreservation = false; IsFileAlterPreservation = false; IsReadOnly = false; IsCompressed = false; DecompressedSize = 0; EncryptionMethod = null; GroupingIdentity = null; if (usesUnsynchronization) { stream = ID3v2Utils.ReadUnsynchronizedStream(stream, m_FrameSize); } } else if (tagReadingInfo.TagVersion == ID3v2TagVersion.ID3v24) { if ((tagReadingInfo.TagVersionOptions & TagVersionOptions.UseNonSyncSafeFrameSizeID3v24) == TagVersionOptions.UseNonSyncSafeFrameSizeID3v24) { m_FrameSize = stream.ReadInt32(); } else { m_FrameSize = ID3v2Utils.ReadInt32SyncSafe(stream); } m_FrameSizeExcludingAdditions = m_FrameSize; byte byte0 = stream.Read1(); byte byte1 = stream.Read1(); bool hasDataLengthIndicator = ((byte1 & 0x01) == 0x01); usesUnsynchronization = ((byte1 & 0x03) == 0x03); if (hasDataLengthIndicator) { m_FrameSizeExcludingAdditions -= 4; stream.Seek(4, SeekOrigin.Current); // skip data length indicator } if (usesUnsynchronization) { stream = ID3v2Utils.ReadUnsynchronizedStream(stream, m_FrameSize); } // TODO - finish parsing } if (IsCompressed) { stream = ID3v2Utils.DecompressFrame(stream, FrameSizeExcludingAdditions); IsCompressed = false; DecompressedSize = 0; m_FrameSizeExcludingAdditions = (int)stream.Length; } }