/// <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); }
/// <summary> /// Initializes a new instance of the <see cref="ID3v2Tag"/> class. /// </summary> public ID3v2Tag() { _id3v2Header = new ID3v2Header(); _id3v2ExtendedHeader = new ID3v2ExtendedHeader(); }