示例#1
0
        ////------------------------------------------------------------------------------------------------------------------------------
        /// <inheritdoc/>
        public IAudioTagOffset ReadFromStream(Stream stream, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            if (!stream.CanRead)
                throw new InvalidOperationException("stream can not be read");

            if (!stream.CanSeek)
                throw new InvalidOperationException("stream can not be seeked");

            StreamBuffer sb = stream as StreamBuffer ?? new StreamBuffer(stream);

            // Try to read the header.
            Id3v2Header headerOrFooter = ReadHeader(sb, tagOrigin);
            if (headerOrFooter == null)
                return null;

            // Don't throw an exception; just return here. The read header could be a false match.
            if (headerOrFooter.Size > Id3v2Tag.MaxAllowedSize)
                return null;

            // Copy header values.
            Id3v2Tag tag = new Id3v2Tag(headerOrFooter.Version, headerOrFooter.Flags);
            bool isHeader = String.Equals(Id3v2Tag.HeaderIdentifier, headerOrFooter.Identifier, StringComparison.OrdinalIgnoreCase);

            // startOffset: offset the header starts
            // endOffset: offset the footer ends if available, or when the tag ends
            long startOffset, endOffset;
            Id3v2Header header, footer = null;
            if (isHeader)
            {
                header = headerOrFooter;
                startOffset = header.Position;
                endOffset = Math.Min(startOffset + Id3v2Tag.HeaderSize + header.Size + (tag.UseFooter ? Id3v2Tag.FooterSize : 0), sb.Length);
                if (endOffset > sb.Length)
                {
            #if DEBUG
                    throw new EndOfStreamException("Tag at start could not be read: stream is truncated.");
            #else
                    return null;
            #endif
                }
            }
            else
            {
                // We've read the footer.
                footer = headerOrFooter;
                endOffset = footer.Position + Id3v2Tag.FooterSize;
                startOffset = Math.Max(endOffset - Id3v2Tag.FooterSize - footer.Size - Id3v2Tag.HeaderSize, 0);

                // Seek to the start of the tag.
                sb.Seek(startOffset, SeekOrigin.Begin);

                // Read the header; it's location could be off.
                header = ReadHeader(sb, TagOrigin.Start);
                if (header == null)
                {
                    // Seek back a bit more and try again.
                    startOffset = Math.Max(startOffset - Id3v2Tag.HeaderSize, 0);
                    sb.Seek(startOffset, SeekOrigin.Begin);
                    header = ReadHeader(sb, TagOrigin.End);

                    // If we still didn't find a header, seek to the start offset; the header could be missing.
                    if (header == null)
                        sb.Seek(startOffset, SeekOrigin.Begin);
                }

                // If we've found a header.
                if (header != null)
                {
                    startOffset = header.Position;

                    // Calculate the tag size (i.e. all data, excluding the header and footer)
                    headerOrFooter.Size = (int)Math.Max(endOffset - (startOffset + Id3v2Tag.HeaderSize + Id3v2Tag.FooterSize), 0);
                }
                else
                {
                    // No header found.
                    tag.UseHeader = false;
                }

                // Return if this is the case.
                if (headerOrFooter.Size > Id3v2Tag.MaxAllowedSize)
                {
            #if DEBUG
                    throw new InvalidDataException(String.Format("Size ({0}) is larger than the max allowed size ({1})", footer.Size, Id3v2Tag.MaxAllowedSize));
            #else
                    return null;
            #endif
                }

                // A footer is read at this point.
                tag.UseFooter = true;
            }

            // At this point, the stream position is located right at the start of the data after the header.
            // The size is the sum of the byte length of the extended header, the padding and the frames after unsynchronization.
            // This does not include the footer size nor the header size.
            int totalSizeItems = Math.Min(headerOrFooter.Size, (int)(sb.Length - sb.Position));

            // If the tag has been unsynchronized, synchronize it again.
            // For version Id3v2.4.0 and later we don't do the unsynch here:
            // unsynchronization [S:6.1] is done on frame level, instead of on tag level, making it easier to skip frames, increasing the stream ability of the tag.
            // The unsynchronization flag in the header [S:3.1] indicates if all frames has been unsynchronized,
            // while the new unsynchronization flag in the frame header [S:4.1.2] indicates unsynchronization.
            if (tag.UseUnsynchronization && (tag.Version < Id3v2Version.Id3v240))
            {
                int unsynchronizedDataSize = totalSizeItems;
                byte[] unsynchronizedData = new byte[unsynchronizedDataSize];
                unsynchronizedDataSize = sb.Read(unsynchronizedData, 0, unsynchronizedDataSize);
                byte[] data = Id3v2Tag.GetSynchronizedData(unsynchronizedData, 0, unsynchronizedDataSize);
                sb = new StreamBuffer(data);

                // Update the total size of the items with the length of the synchronized data
                totalSizeItems = data.Length;
            }

            // Calculate the padding size.
            int paddingSize = 0;

            // Check for an extended header.
            if (tag.UseExtendedHeader)
            {
                int crc;
                tag.ExtendedHeader = ReadExtendedHeader(sb, tag, out crc);
                if (tag.ExtendedHeader != null)
                {
                    if (tag.UseFooter && (tag.ExtendedHeader.PaddingSize != 0))
                    {
                        // Id3v2 tag can not have padding when an Id3v2 footer has been added.
                        tag.ExtendedHeader.PaddingSize = 0;
                    }
                    paddingSize += tag.ExtendedHeader.PaddingSize;

                    // Calculate CRC if needed.
                    if (tag.ExtendedHeader.CrcDataPresent)
                    {
                        long currentPosition = sb.Position;

                        // Id3v2.3.0:
                        // The CRC should be calculated before unsynchronisation on the data between the extended header and the padding, i.e. the frames and only the frames.
                        // Id3v2.4.0:
                        // The CRC is calculated on all the data between the header and footer as indicated by the header's tag length field, minus the extended header.
                        // Note that this includes the padding (if there is any), but excludes the footer.
                        int dataLength = ((tag.Version >= Id3v2Version.Id3v240)
                                              ? totalSizeItems
                                              : totalSizeItems - (tag.PaddingSize + tag.ExtendedHeader.PaddingSize) - 4)
                                         - tag.ExtendedHeader.GetHeaderSize(tag.Version);
                        byte[] crcData = sb.ToByteArray().Skip((int)currentPosition).Take(dataLength).ToArray();
                        int calculatedCrc = Cryptography.Crc32.Calculate(crcData);
                        if (calculatedCrc != crc)
                            throw new InvalidDataException(String.Format("CRC {0:X} in tag does not match calculated CRC {1:X}", crc, calculatedCrc));

                        sb.Position = currentPosition;
                    }
                }
            }

            // Bytes we won't read or have already read, excluding the header/footer.
            totalSizeItems -= (tag.ExtendedHeader != null ? tag.ExtendedHeader.GetHeaderSize(tag.Version) + paddingSize : 0);

            // Now let's get to the good part, and start parsing the frames!
            List<Id3v2Frame> frames = new List<Id3v2Frame>();
            long bytesRead = 0;
            while (bytesRead < totalSizeItems)
            {
                long startPosition = sb.Position;

                // See if the next byte is a padding byte.
                int identifierByte = sb.ReadByte(false);
                if (identifierByte == 0x00)
                {
                    // Rest is padding.
                    // We add up to the padding rather than assigning it, because the ExtendedHeader might include padding as well.
                    tag.PaddingSize += (int)(totalSizeItems - bytesRead);
                    bytesRead += tag.PaddingSize;
                    break;
                }

                Id3v2Frame frame = Id3v2Frame.ReadFromStream(tag.Version, sb, totalSizeItems - bytesRead);
                if (frame != null)
                {
                    Id3v2FrameParseEventArgs frameParseEventArgs = new Id3v2FrameParseEventArgs(frame);
                    OnFrameParse(frameParseEventArgs);

                    if (!frameParseEventArgs.Cancel)
                    {
                        // If the event has 'replaced' the frame, assign the 'new' frame here and continue.
                        if (frameParseEventArgs.Frame != null)
                            frame = frameParseEventArgs.Frame;

                        // Assign the cryptor instance of the tag to the frame, and decrypt if necessary.
                        bool isEncrypted = frame.UseEncryption;
                        if (isEncrypted)
                            isEncrypted = frame.Decrypt();

                        // Do the same with decompressing, if necessary.
                        if (!isEncrypted && frame.UseCompression)
                            frame.Decompress();

                        // Call after read event.
                        Id3v2FrameParsedEventArgs frameParsedEventArgs = new Id3v2FrameParsedEventArgs(frame);
                        OnFrameParsed(frameParsedEventArgs);

                        // Add the 'final' frame from the event.
                        frames.Add(frameParsedEventArgs.Frame);
                    }
                }
                else
                {
                    // Skip next byte.
                    sb.Position = (startPosition + 1);
                }

                //Reading is always done forward; not backwards.
                bytesRead += (sb.Position - startPosition);
            }
            paddingSize += tag.PaddingSize;

            #if DEBUG
            if (frames.Count() > Id3v2Tag.MaxAllowedFrames)
            {
                throw new InvalidDataException(
                    String.Format("Tag has more frames ('{0}') than the allowed max frames count ('{1}').", frames.Count(), Id3v2Tag.MaxAllowedFrames));
            }

            if (bytesRead != totalSizeItems)
            {
                throw new InvalidDataException(
                    String.Format("Amount of bytes read ({0}) does not match expected size ({1}).", bytesRead, totalSizeItems));
            }

            // Id3v2.4.0 and later do not have a field for padding size.
            if (!isHeader && (paddingSize > 0))
                throw new InvalidDataException("Id3v2 tag can not have padding when footer is set.");
            #endif

            // If the tag is at the start of the stream, is the tag allowed to have a footer?
            if (isHeader && tag.UseFooter)
            {
                sb.Position += Id3v2Tag.FooterSize;
                footer = ReadHeader(sb, TagOrigin.End);
            }

            // Validate the padding.
            if (paddingSize > 0)
            {
                byte[] padding = new byte[paddingSize];
                sb.Read(padding, paddingSize);
            #if DEBUG
                if (!padding.Any(b => b == 0x00))
                    throw new InvalidDataException("Padding contains one or more invalid padding bytes.");
            #endif
            }
            ValidateHeader(tag, header, footer);

            // Sort frames and add them to the tag.
            AddRequiredFrames(tag.Version, frames);
            tag.SetFrames(frames);

            return new AudioTagOffset(tagOrigin, startOffset, endOffset, tag);
        }
示例#2
0
        ////------------------------------------------------------------------------------------------------------------------------------
        private static void ValidateHeader(Id3v2Tag tag, Id3v2Header header, Id3v2Header footer)
        {
            #if DEBUG
            if (tag == null)
                return;

            if (header != null && footer != null)
            {
                if (header.Version != footer.Version)
                {
                    throw new InvalidDataException(
                        String.Format("The APE header version {0} does not match footer version {1}.", header.Version, footer.Version));
                }

                if (tag.PaddingSize != 0)
                    throw new InvalidDataException("Id3v2 tag can not have padding when footer is set.");
            }

            if (header != null)
            {
            }

            if (footer != null)
            {
                // If the tag is after the MPEG frames and there's no footer.
                if (!tag.UseFooter)
                    throw new InvalidDataException("Footer not found; footer is required for Id3v2 tags at the end of a stream.");
            }
            #else
            return;
            #endif
        }
示例#3
0
        /// <summary>
        /// Reads the extended header.
        /// </summary>
        /// <param name="streamBuffer">The stream buffer.</param>
        /// <param name="tag">The tag.</param>
        /// <param name="crcData">The CRC data.</param>
        /// <returns>
        /// The extended header if used; otherwise, null.
        /// </returns>
        /// <exception cref="System.IO.InvalidDataException">Thrown if the extended header size does not match the amount of bytes read for the extended header.</exception>
        private static Id3v2ExtendedHeader ReadExtendedHeader(StreamBuffer streamBuffer, Id3v2Tag tag, out int crcData)
        {
            crcData = 0;
            if (!tag.UseExtendedHeader)
                return null;

            // The extended header contains information that can provide further insight in the structure of the tag,
            // but is not vital to the correct parsing of the tag information; hence the extended header is optional.
            int extendedHeaderSize = 0;
            long startPosition = streamBuffer.Position;
            Id3v2ExtendedHeader extendedHeader = null;
            if ((tag.Version >= Id3v2Version.Id3v230) && (tag.Version < Id3v2Version.Id3v240))
            {
                // Where the 'Extended header size', currently 6 or 10 bytes, excludes itself.
                extendedHeaderSize = streamBuffer.ReadBigEndianInt32() + 4;
                int extendedFlags = streamBuffer.ReadBigEndianInt16();
                extendedHeader = Id3v2ExtendedHeader.InitExtendedHeader(tag.Version, extendedFlags);
                extendedHeader.PaddingSize = streamBuffer.ReadBigEndianInt32();
                if (extendedHeader.CrcDataPresent)
                    crcData = streamBuffer.ReadBigEndianInt32();
            }
            else if (tag.Version >= Id3v2Version.Id3v240)
            {
                // Where the 'Extended header size' is the size of the whole extended header, stored as a 32 bit synchsafe integer.
                // An extended header can thus never have a size of fewer than six bytes.
                extendedHeaderSize = streamBuffer.ReadBigEndianInt32();
                extendedHeaderSize = Id3v2Tag.GetUnsynchedValue(extendedHeaderSize);
                int extendedFlagsFieldLength = streamBuffer.ReadByte();

                // The extended flags field, with its size described by 'number of flag bytes', is defined as: %0bcd0000
                int extendedFlags = streamBuffer.ReadInt(extendedFlagsFieldLength);
                extendedHeader = Id3v2ExtendedHeader.InitExtendedHeader(tag.Version, extendedFlags);
                extendedHeader.SetExtendedFlagsFieldLength(extendedFlagsFieldLength);

                // Each flag that is set in the extended header has data attached,
                // which comes in the order in which the flags are encountered (i.e. the data for flag 'b' comes before the data for flag 'c').
                // Unset flags cannot have any attached data. All unknown flags MUST be unset and their corresponding data removed when a tag is modified.
                //
                // Every set flag's data starts with a length byte, which contains a value between 0 and 127 ($00 - $7f),
                // followed by data that has the field length indicated by the length byte.
                // If a flag has no attached data, the value $00 is used as length byte.

                // If this flag is set, the present tag is an update of a tag found earlier in the present file or stream.
                // If frames defined as unique are found in the present tag, they are to override any corresponding ones found in the earlier tag.
                // This flag has no corresponding data.
                if (extendedHeader.TagIsUpdate)
                {
                    // If a flag has no attached data, the value $00 is used as length byte.
                    streamBuffer.ReadByte();
                }

                // If this flag is set, a CRC-32 [ISO-3309] data is included in the extended header.
                // The CRC is calculated on all the data between the header and footer as indicated by the header's tag length field, minus the extended header.
                // Note that this includes the padding (if there is any), but excludes the footer.
                // The CRC-32 is stored as an 35 bit synchsafe integer, leaving the upper four bits always zeroed.
                if (extendedHeader.CrcDataPresent)
                {
                    long crcBytes = streamBuffer.ReadBigEndianInt64(5);
                    crcData = (int)Id3v2Tag.GetUnsynchedValue(crcBytes, 5);
                }

                // For some applications it might be desired to restrict a tag in more ways than imposed by the ID3v2 specification.
                // Note that the presence of these restrictions does not affect how the tag is decoded, merely how it was restricted before encoding.
                // If this flag is set the tag is restricted as follows:
                if (extendedHeader.TagIsRestricted)
                {
                    // Restrictions: %ppqrrstt
                    byte tagRestrictions = (byte)streamBuffer.ReadByte();
                    extendedHeader.TagRestrictions = new Id3v2TagRestrictions
                    {
                        // p - Tag size restrictions
                        TagSizeRestriction = (Id3v2TagSizeRestriction)(tagRestrictions & Id3v2TagRestrictions.TagSizeRestrictionFlags),

                        // q - Text encoding restrictions
                        TextEncodingRestriction = (Id3v2TextEncodingRestriction)(tagRestrictions & Id3v2TagRestrictions.TextEncodingRestrictionFlags),

                        // r - Text fields size restrictions
                        TextFieldsSizeRestriction = (Id3v2TextFieldsSizeRestriction)(tagRestrictions & Id3v2TagRestrictions.TextFieldsSizeRestrictionFlags),

                        // s - Image encoding restrictions
                        ImageEncodingRestriction = (Id3v2ImageEncodingRestriction)(tagRestrictions & Id3v2TagRestrictions.ImageEncodingRestrictionFlags),

                        // t - Image size restrictions
                        ImageSizeRestriction = (Id3v2ImageSizeRestriction)(tagRestrictions & Id3v2TagRestrictions.ImageSizeRestrictionFlags)
                    };
                }
            }

            if ((streamBuffer.Position - startPosition) != extendedHeaderSize)
            {
                throw new InvalidDataException(
                    string.Format(
                        "ExtendedHeaderSize does not match amount of bytes read: expected {0} but got {1} bytes.",
                        extendedHeaderSize,
                        streamBuffer.Position - startPosition));
            }
            return extendedHeader;
        }