コード例 #1
0
 ////------------------------------------------------------------------------------------------------------------------------------
 /// <summary>
 /// Initializes a new instance of the <see cref="AudioTagOffset" /> class.
 /// </summary>
 /// <param name="tagOrigin">The tag origin.</param>
 /// <param name="startOffset">The start offset.</param>
 /// <param name="endOffset">The end offset.</param>
 /// <param name="audioTag">The audio tag.</param>
 public AudioTagOffset(TagOrigin tagOrigin, long startOffset, long endOffset, IAudioTag audioTag)
 {
     TagOrigin = tagOrigin;
     StartOffset = startOffset;
     EndOffset = endOffset;
     AudioTag = audioTag;
 }
コード例 #2
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");

            // This tag can only be located at the end; the header does not contain a size field, so we're not able to calculate the end.
            if (tagOrigin == TagOrigin.Start)
                return null;

            StreamBuffer sb = stream as StreamBuffer ?? new StreamBuffer(stream);
            Lyrics3Tag tag = new Lyrics3Tag { Encoding = Encoding };
            long footerOffset = FindFooterIdentifier(sb);
            if (footerOffset < 0)
                return null;

            sb.Position = Math.Max(footerOffset - Lyrics3Tag.MaxLyricsSize, 0);
            long headerOffset = FindHeaderIdentifier(sb);
            if (headerOffset < 0)
                return null;

            // Read lyrics
            int lyricsLength = (int)(footerOffset - (headerOffset + HeaderIdentifierBytes.Length));
            string lyrics = sb.ReadString(lyricsLength);
            tag.Lyrics = GetLyrics(lyrics);

            // Read footer
            sb.ReadString(FooterIdentifierBytes.Length);

            return new AudioTagOffset(tagOrigin, headerOffset, footerOffset, tag);
        }
コード例 #3
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);
            long startOffset = FindIdentifier(sb, tagOrigin);
            if (startOffset < 0)
                return null;

            long endOffset = 0;
            if (tagOrigin == TagOrigin.End)
            {
                endOffset = startOffset + FooterIdentifierBytes.Length;

                sb.Position = Math.Max(sb.Position - FooterIdentifierBytes.Length - Lyrics3v2Tag.TagSizeLength, 0);
                string strTagSize = sb.ReadString(Lyrics3v2Tag.TagSizeLength);
                long tagSize;
                if (!Int64.TryParse(strTagSize, out tagSize))
                {
            #if DEBUG
                    throw new InvalidDataException(String.Format("Can't parse size value {0} before footer.", strTagSize));
            #else
                    return null;
            #endif
                }
                long currentPosition = sb.Position;

                // The Lyrics3 block ends with a six character size descriptor and the string "LYRICS200".
                // The size value includes the "LYRICSBEGIN" string, but does not include the 6 character size descriptor and the trailing "LYRICS200" string.
                sb.Position = Math.Max(sb.Position - tagSize - Lyrics3v2Tag.TagSizeLength, 0);

                startOffset = FindIdentifier(sb, TagOrigin.Start);
                if (startOffset < 0)
                {
                    // Header could be off
                    sb.Position = Math.Max(sb.Position - (tagSize - 5), 0);

                    if (sb.Position > 0)
                    {
                        startOffset = FindIdentifier(sb, TagOrigin.End);
                        if (startOffset < 0)
                            startOffset = sb.Position = currentPosition;
                    }
                }
            }

            // Read the fields.
            List<Lyrics3v2Field> fields = new List<Lyrics3v2Field>();
            long bytesRead = HeaderIdentifierBytes.Length;
            long streamLength = (endOffset > 0) ? endOffset : Math.Max(sb.Length, endOffset);
            while (startOffset + bytesRead < streamLength)
            {
                long startPosition = sb.Position;

                // Read field if needed.
                Lyrics3v2Field field = Lyrics3v2Field.ReadFromStream(sb, streamLength - bytesRead);
                if (field != null)
                {
                    Lyrics3v2FieldParseEventArgs fieldParseEventArgs = new Lyrics3v2FieldParseEventArgs(field);
                    OnFieldParse(fieldParseEventArgs);

                    if (!fieldParseEventArgs.Cancel)
                    {
                        // Call after read event.
                        Lyrics3v2FieldParsedEventArgs parsedEventArgs = new Lyrics3v2FieldParsedEventArgs(field);
                        OnFieldParsed(parsedEventArgs);

                        fields.Add(field);
                    }
                }
                else
                {
                    sb.Position = startPosition;
                    break;
                }
                bytesRead += (sb.Position - startPosition);
            }

            if (endOffset == 0)
            {
                // The Lyrics3 block ends with a six character size descriptor and the string "LYRICS200".
                // The size value includes the "LYRICSBEGIN" string, but does not include the 6 character size descriptor and the trailing "LYRICS200" string.
                string strTagSize = sb.ReadString(6);
                long tagSize;
                if (!Int64.TryParse(strTagSize, out tagSize))
                {
            #if DEBUG
                    throw new InvalidDataException(String.Format("Can't parse size value {0} before footer.", strTagSize));
            #else
                    return null;
            #endif
                }

                int bRead;
                string footerIdentifier = sb.ReadString(FooterIdentifierBytes.Length, true, out bRead);
                if (!String.Equals(footerIdentifier, Lyrics3v2Tag.FooterIdentifier))
                {
            #if DEBUG
                    throw new InvalidDataException(
                        String.Format("Footer identifier {0} not recognized; expected {1}", footerIdentifier, Lyrics3v2Tag.FooterIdentifier));
            #else
                    sb.Position -= bRead;
            #endif
                }
                endOffset = sb.Position;
            }

            #if DEBUG
            long totalDataSize = (endOffset - startOffset) - Lyrics3v2Tag.TagSizeLength - FooterIdentifierBytes.Length;
            //if (totalDataSize != bytesRead)
            //{
            //    // If the amount of bytes we read doesn't match the size of the tag indicated in the header,
            //    throw new InvalidDataException(
            //        String.Format(
            //            "Amount of bytes read ({0}) does not match size indicated before footer ({1}).",
            //            totalDataSize,
            //            bytesRead));
            //}
            #endif

            Lyrics3v2Tag tag = new Lyrics3v2Tag();
            tag.SetFields(fields);
            return new AudioTagOffset(tagOrigin, startOffset, endOffset, tag);
        }
コード例 #4
0
        ////------------------------------------------------------------------------------------------------------------------------------
        private static long FindIdentifier(Stream stream, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            long startPosition = stream.Position;
            long streamLength = stream.Length;
            int headerSize = HeaderIdentifierBytes.Length;
            if (streamLength < headerSize)
                return -1;

            if (tagOrigin == TagOrigin.Start)
            {
                // Look for a header at the current position
                long startPositionHeader = startPosition;
                long endPositionHeader = Math.Min(startPositionHeader + headerSize, streamLength);
                long tagPosition = ReadIdentifier(stream, startPositionHeader, endPositionHeader, HeaderIdentifierBytes);
                if (tagPosition >= 0)
                    return tagPosition;

                // Look for a header past the current position
                startPositionHeader = endPositionHeader;
                endPositionHeader = Math.Min(startPositionHeader + headerSize, streamLength);
                tagPosition = ReadIdentifier(stream, startPositionHeader, endPositionHeader, HeaderIdentifierBytes);
                if (tagPosition >= 0)
                    return tagPosition;
            }
            else if (tagOrigin == TagOrigin.End)
            {
                // The Lyrics3 block ends with a six character size descriptor and the string "LYRICS200".
                // The size value includes the "LYRICSBEGIN" string, but does not include the 6 character size descriptor and the trailing "LYRICS200" string.
                int footerSize = FooterIdentifierBytes.Length;

                // Look for a footer at before current position
                long startPositionHeader = Math.Max(startPosition - footerSize, 0);
                long endPositionHeader = Math.Min(startPosition, streamLength);
                long tagPosition = ReadIdentifier(stream, startPositionHeader, endPositionHeader, FooterIdentifierBytes);
                if (tagPosition >= 0)
                    return tagPosition;

                // Look for a footer before the previous start position
                startPositionHeader = Math.Max(startPositionHeader - footerSize, 0);
                endPositionHeader = Math.Min(startPositionHeader, streamLength);
                tagPosition = ReadIdentifier(stream, startPositionHeader, endPositionHeader, FooterIdentifierBytes);
                if (tagPosition >= 0)
                    return tagPosition;
            }
            return -1;
        }
コード例 #5
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);

            ApeHeader 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 > ApeTag.MaxAllowedSize)
                return null;

            ApeTag tag = new ApeTag(headerOrFooter.Version, headerOrFooter.Flags);
            long startOffset, endOffset;
            ApeHeader header = null, footer = null;
            if (tag.IsHeader)
            {
                header = headerOrFooter;
                startOffset = header.Position;
                endOffset = Math.Min(startOffset + ApeTag.HeaderSize + header.Size, 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
                }
                tag.UseHeader = true;
            }
            else
            {
                footer = headerOrFooter;
                endOffset = Math.Min(footer.Position + ApeTag.FooterSize, sb.Length);
                startOffset = Math.Max(endOffset - footer.Size - (tag.UseHeader ? ApeTag.HeaderSize : 0), 0);
                if (endOffset > sb.Length)
                {
            #if DEBUG
                    throw new EndOfStreamException("Tag at end could not be read: stream is truncated.");
            #else
                    return null;
            #endif
                }
                tag.UseFooter = true;

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

                if (tag.UseHeader)
                {
                    // Read the 'header' as if it was the start of the tag
                    header = ReadHeader(sb, TagOrigin.Start);
                    if (header == null)
                    {
                        // Size in the header could be off.
                        // Read the 'footer' as if it was at the end of the tag
                        sb.Seek(startOffset, SeekOrigin.Begin);
                        header = ReadHeader(sb, TagOrigin.End);
                        if (header == null)
                        {
                            tag.UseHeader = false;
                            sb.Seek(startOffset, SeekOrigin.Begin);
                        }
                    }

                    if (header != null)
                    {
                        startOffset = header.Position;
                        header.Size = (int)Math.Max(endOffset - (startOffset + ApeTag.HeaderSize), 0);
                        footer.Size = Math.Max(header.Size, footer.Size);
                    }

                    if (footer.Size > ApeTag.MaxAllowedSize)
                    {
            #if DEBUG
                        throw new InvalidDataException(
                            String.Format("Size ({0}) is larger than the max allowed size ({1})", footer.Size, ApeTag.MaxAllowedSize));
            #else
                        return null;
            #endif
                    }
                }
                ValidateHeader(header, footer);
            }

            int totalSizeItems = Math.Max(headerOrFooter.Size - ApeTag.FooterSize, 0);
            if ((sb.Length - sb.Position) < totalSizeItems)
            {
            #if DEBUG
                throw new IndexOutOfRangeException(
                    String.Format("Ape field ({0}) is bigger than amount of bytes left in stream ({1}).", totalSizeItems, sb.Length - sb.Position));
            #else
                return null;
            #endif
            }

            // Parse the individual frames.
            List<ApeItem> items = new List<ApeItem>();
            long bytesRead = 0;
            while (bytesRead < totalSizeItems)
            {
                long startPosition = sb.Position;

                ApeItem item = ApeItem.ReadFromStream(tag.Version, sb, totalSizeItems - bytesRead);
                if (item != null)
                {
                    ApeItemParseEventArgs parseEventArgs = new ApeItemParseEventArgs(item);
                    OnItemParse(parseEventArgs);

                    if (!parseEventArgs.Cancel)
                    {
                        if (parseEventArgs.Item != null)
                            item = parseEventArgs.Item;

                        if (item != null)
                        {
                            // Call after read event.
                            ApeItemParsedEventArgs parsedEventArgs = new ApeItemParsedEventArgs(item);
                            OnItemParsed(parsedEventArgs);

                            items.Add(parsedEventArgs.Item);
                        }
                    }
                }
                bytesRead += (sb.Position - startPosition);
            }

            #if DEBUG
            //if (items.Count != headerOrFooter.FrameCount)
                //throw new InvalidDataException("items.Count does not match FrameCount");

            if (items.Count > ApeTag.MaxAllowedFields)
            {
                throw new InvalidDataException(
                    String.Format("Tag has more fields ('{0}') than the allowed max fields count ('{1}').", items.Count, ApeTag.MaxAllowedFields));
            }

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

            if (tag.IsHeader && tag.UseFooter)
            {
                sb.Position += ApeTag.FooterSize;
                footer = ReadHeader(sb, TagOrigin.End);
            }

            ValidateHeader(header, footer);

            tag.SetItems(items);

            return new AudioTagOffset(tagOrigin, startOffset, endOffset, tag);
        }
コード例 #6
0
        ////------------------------------------------------------------------------------------------------------------------------------
        private static ApeHeader ReadHeader(StreamBuffer stream, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            long startPosition = stream.Position;
            long streamLength = stream.Length;
            if (streamLength < ApeTag.HeaderSize)
                return null;

            // Look for a header at the current position
            if (tagOrigin == TagOrigin.Start)
            {
                // Look for a header at the current position
                long startPositionHeader = startPosition;
                long endPositionHeader = Math.Min(startPositionHeader + ApeTag.HeaderSize, streamLength);
                ApeHeader hdr = ReadHeader(stream, startPositionHeader, endPositionHeader);
                if (hdr != null)
                    return hdr;

                // Look for a header past the current position
                startPositionHeader = endPositionHeader;
                endPositionHeader = Math.Min(startPositionHeader + ApeTag.HeaderSize, streamLength);
                hdr = ReadHeader(stream, startPositionHeader, endPositionHeader);
                if (hdr != null)
                    return hdr;
            }
            else if (tagOrigin == TagOrigin.End)
            {
                // Look for a footer before the current position
                long startPositionHeader = Math.Max(startPosition - ApeTag.FooterSize, 0);
                long endPositionHeader = Math.Min(startPositionHeader + ApeTag.FooterSize, streamLength);
                ApeHeader hdr = ReadHeader(stream, startPositionHeader, endPositionHeader);
                if (hdr != null)
                    return hdr;

                // Look for a footer before the previous start position
                startPositionHeader = Math.Max(startPositionHeader - ApeTag.FooterSize, 0);
                endPositionHeader = Math.Min(startPositionHeader + ApeTag.FooterSize, streamLength);
                hdr = ReadHeader(stream, startPositionHeader, endPositionHeader);
                if (hdr != null)
                    return hdr;
            }
            return null;
        }
コード例 #7
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);
            long startOffset = FindIdentifier(sb, tagOrigin);
            if (startOffset < 0)
                return null;

            Id3v1Tag tag = new Id3v1Tag();
            string extendedTrackTitle = String.Empty;
            string extendedArtist = String.Empty;
            string extendAlbumTitle = String.Empty;
            if ((startOffset - Id3v1Tag.ExtendedSize) >= 0)
            {
                sb.Position = startOffset - Id3v1Tag.ExtendedSize;
                string extendedHeaderIdentifier = sb.ReadString(ExtendedHeaderIdentifierBytes.Length);
                if (String.Equals(extendedHeaderIdentifier, Id3v1Tag.ExtendedHeaderIdentifier, StringComparison.OrdinalIgnoreCase))
                {
                    startOffset = startOffset - ExtendedHeaderIdentifierBytes.Length;
                    extendedTrackTitle = sb.ReadString(60, _encoding);
                    extendedArtist = sb.ReadString(60, _encoding);
                    extendAlbumTitle = sb.ReadString(60, _encoding);
                    Id3v1TrackSpeed trackSpeed = (Id3v1TrackSpeed)sb.ReadByte();
                    tag.TrackSpeed = Id3v1Tag.IsValidTrackSpeed(trackSpeed) ? trackSpeed : Id3v1TrackSpeed.Unset;
                    tag.ExtendedTrackGenre = sb.ReadString(30, _encoding);
                    tag.StartTime = GetTimeSpan(sb.ReadString(6, _encoding));
                    tag.EndTime = GetTimeSpan(sb.ReadString(6, _encoding));
                    tag.UseExtendedTag = true;
                }
                else
                    sb.Position = startOffset + HeaderIdentifierBytes.Length;
            }

            // Read the rest of the tag.
            tag.TrackTitle = sb.ReadString(30, _encoding) + extendedTrackTitle;
            tag.Artist = sb.ReadString(30, _encoding) + extendedArtist;
            tag.AlbumTitle = sb.ReadString(30, _encoding) + extendAlbumTitle;
            tag.AlbumYear = sb.ReadString(4);
            byte[] comment = new byte[30];
            if ((sb.Read(comment, 30) == 30) && (comment[28] == '\0') && (comment[29] != '\0'))
            {
                tag.TrackNumber = comment[29];
                tag.TrackComment = _encoding.GetString(comment, 0, 28);
            }
            else
                tag.TrackComment = _encoding.GetString(comment);

            Id3v1Genre genre = (Id3v1Genre)sb.ReadByte();
            tag.Genre = Id3v1Tag.IsValidGenre(genre) ? genre : Id3v1Genre.Unknown;

            long endOffset = sb.Position;
            return new AudioTagOffset(tagOrigin, startOffset, endOffset, tag);
        }
コード例 #8
0
        ////------------------------------------------------------------------------------------------------------------------------------
        private static long FindIdentifier(Stream stream, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            long startPosition = stream.Position;
            long streamLength = stream.Length;
            if (streamLength < Id3v1Tag.TotalSize)
                return -1;

            if (tagOrigin == TagOrigin.Start)
            {
                // Look for the tag at the current position
                long startPositionTag = Math.Max(startPosition, 0);
                long endPositionTag = Math.Min(startPosition + HeaderIdentifierBytes.Length, streamLength);
                long tagPosition = ReadIdentifier(stream, startPositionTag, endPositionTag);
                if (tagPosition >= 0)
                    return tagPosition;
            }
            else if (tagOrigin == TagOrigin.End)
            {
                // Look for tag before the current position
                long startPositionTag = (startPosition - Id3v1Tag.TotalSize) >= 0
                                            ? startPosition - Id3v1Tag.TotalSize
                                            : Math.Max(startPosition - HeaderIdentifierBytes.Length, 0);
                long endPositionTag = Math.Min(startPositionTag + HeaderIdentifierBytes.Length, streamLength);
                long tagPosition = ReadIdentifier(stream, startPositionTag, endPositionTag);
                if (tagPosition >= 0)
                    return tagPosition;
            }
            return -1;
        }
コード例 #9
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);
            MusicMatchTag tag = new MusicMatchTag();

            // Should we read the header or footer?
            MusicMatchHeader headerOrFooter = (tagOrigin == TagOrigin.Start) ? ReadHeader(sb, tagOrigin) : ReadFooter(sb, tagOrigin);
            if (headerOrFooter == null)
                return null;

            long startOffset = 0, endOffset = 0, streamLength = sb.Length;
            MusicMatchHeader header = null, footer = null, versionInformation = null;
            if (tagOrigin == TagOrigin.Start)
            {
                header = headerOrFooter;
                tag.UseHeader = true;
                startOffset = header.Position;
            }
            else if (tagOrigin == TagOrigin.End)
            {
                footer = headerOrFooter;
                endOffset = Math.Min(footer.Position + MusicMatchTag.FooterSize, streamLength);

                // Seek to the Audio metadata offset struct
                sb.Position = Math.Max(endOffset - MusicMatchTag.FooterSize - MusicMatchTag.DataOffsetFieldsSize, 0);

                // Read the Audio Meta-data Offsets
                MusicMatchDataOffsets dataOffsets = ReadDataOffsets(sb);

                long startPosition = Math.Max(sb.Position + MusicMatchTag.FooterSize, 0);
                foreach (int audioMetaDataOffset in AudioMetaDataSizes.OrderBy(a => a))
                {
                    // Seek to the start of the version information, based on the audioMetaDataOffset
                    long startPositionHeader = Math.Max(startPosition - audioMetaDataOffset - MusicMatchTag.HeaderSize, 0);
                    long endPositionHeader = startPositionHeader + HeaderIdentifierBytes.Length;

                    // Read the version information
                    versionInformation = ReadHeaderFooter(sb, startPositionHeader, endPositionHeader, HeaderIdentifierBytes, TagOrigin.Start);
                    if (versionInformation == null)
                        continue;

                    startOffset = (versionInformation.Position + MusicMatchTag.HeaderSize) - dataOffsets.TotalDataOffsetsSize;
                    sb.Position = Math.Max(startOffset - MusicMatchTag.HeaderSize, 0);
                    header = ReadHeader(sb, TagOrigin.Start);
                    break;
                }

                // We've got a problem, Houston.
                if (versionInformation == null)
                    return null;

                // Header found? set new startPosition
                if (header != null)
                {
                    if (header.Position == versionInformation.Position)
                    {
                        header = null;
                    }
                    else
                    {
                        tag.UseHeader = true;
                        startOffset = header.Position;
                    }
                }
            }

            sb.Position = startOffset + (tag.UseHeader ? MusicMatchTag.HeaderSize : 0);

            // Read image
            tag.Image = MusicMatchImage.ReadFromStream(sb);

            // Unused, null padding
            byte[] nullPadding = new byte[4];
            sb.Read(nullPadding, nullPadding.Length);

            // Read version info
            if (versionInformation == null)
            {
                versionInformation = ReadHeader(sb, TagOrigin.Start);
                if (versionInformation == null)
                {
            #if DEBUG
                    throw new InvalidDataException("Version information not found in the tag.");
            #else
                    return null;
            #endif
                }
            }
            else
            {
                // version information has the same size as the header size.
                sb.Position += MusicMatchTag.HeaderSize;
            }

            if (tag.UseHeader && !versionInformation.Equals(header))
            {
            #if DEBUG
                throw new InvalidDataException("Version information field(s) mismatch the header fields.");
            #else
                return null;
            #endif
            }

            tag.XingEncoderVersion = versionInformation.XingEncoderVersion;
            tag.Version = versionInformation.MusicMatchVersion;

            // Read the Audio meta-data
            ReadAudioMetaFields(sb, tag);

            // Padding
            sb.Position += 16;

            if (tagOrigin == TagOrigin.Start)
            {
                long startPosition = sb.Position;
                foreach (int audioMetaDataOffset in AudioMetaDataSizes.OrderBy(a => a))
                {
                    //Look for the start of the footer.
                    long startPositionFooter = Math.Max(startPosition + (audioMetaDataOffset - (sb.Position - startOffset) - (tag.UseHeader ? MusicMatchTag.HeaderSize : 0)), 0);
                    long endPositionFooter = startPositionFooter + FooterIdentifierBytes.Length;

                    // Read the footer.
                    footer = ReadHeaderFooter(sb, startPositionFooter, endPositionFooter, FooterIdentifierBytes, TagOrigin.End);
                    if (footer != null)
                        break;
                }

                // We've got a problem, Houston.
                if (footer == null)
                    return null;
            }
            return new AudioTagOffset(tagOrigin, startOffset, endOffset, tag);
        }
コード例 #10
0
        private static MusicMatchHeader ReadHeaderFooter(StreamBuffer stream, long startHeaderPosition, long endHeaderPosition, IList<byte> identifierBytes, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            stream.Position = startHeaderPosition;
            while (startHeaderPosition < endHeaderPosition)
            {
                int y = 0;
                while (stream.ReadByte() == identifierBytes[y++])
                {
                    startHeaderPosition++;
                    if (y != identifierBytes.Count)
                        continue;

                    if (tagOrigin == TagOrigin.Start)
                    {
                        MusicMatchHeader header = new MusicMatchHeader
                        {
                            Position = stream.Position - identifierBytes.Count,
                            Padding1 = new byte[2],
                            Padding2 = new byte[2],
                            Padding3 = new byte[2],
                            SpacePadding2 = new byte[226]
                        };

                        // 0x00 0x00 Padding
                        stream.Read(header.Padding1, header.Padding1.Length);

                        // <8-byte numerical ASCII string>
                        header.XingEncoderVersion = stream.ReadString(8);

                        // 0x00 0x00 Padding
                        stream.Read(header.Padding2, header.Padding2.Length);

                        // Xing encoder version <8-byte numerical ASCII string>
                        header.MusicMatchVersion = stream.ReadString(8);

                        // 0x00 0x00 Padding
                        stream.Read(header.Padding3, header.Padding3.Length);

                        // Space padding <226 * 0x20 >
                        stream.Read(header.SpacePadding2, header.SpacePadding2.Length);

                        ValidateHeader(header, null);
                        return header;
                    }

                    if (tagOrigin == TagOrigin.End)
                    {
                        MusicMatchHeader footer = new MusicMatchHeader
                        {
                            Position = stream.Position - identifierBytes.Count,
                            SpacePadding1 = new byte[13],
                            SpacePadding2 = new byte[12]
                        };

                        // Space padding <13 * 0x20>
                        stream.Read(footer.SpacePadding1, 13);

                        // version <4-byte numerical ASCII string> e.g. 3.05
                        footer.MusicMatchVersion = stream.ReadString(4);

                        // Space padding <12 * 0x20>
                        stream.Read(footer.SpacePadding2, 12);

                        ValidateHeader(null, footer);
                        return footer;
                    }
                }
                startHeaderPosition++;
            }
            return null;
        }
コード例 #11
0
        private static MusicMatchHeader ReadHeader(StreamBuffer stream, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            long startPosition = stream.Position;
            long streamLength = stream.Length;
            if (streamLength < MusicMatchTag.HeaderSize)
                return null;

            if (tagOrigin == TagOrigin.Start)
            {
                // Look for a header at the current position
                long startPositionHeader = startPosition;
                long endPositionHeader = Math.Min(startPositionHeader + HeaderIdentifierBytes.Length, streamLength);
                MusicMatchHeader header = ReadHeaderFooter(stream, startPositionHeader, endPositionHeader, HeaderIdentifierBytes, TagOrigin.Start);
                if (header != null)
                    return header;

                // Look for a header past the current position
                startPositionHeader = endPositionHeader;
                endPositionHeader = Math.Min(startPositionHeader + HeaderIdentifierBytes.Length, streamLength);
                header = ReadHeaderFooter(stream, startPositionHeader, endPositionHeader, HeaderIdentifierBytes, TagOrigin.Start);
                if (header != null)
                    return header;
            }
            else if (tagOrigin == TagOrigin.End)
            {
                // Look for a footer before the current position
                long startPositionHeader = Math.Max(startPosition - HeaderIdentifierBytes.Length, 0);
                long endPositionHeader = Math.Min(startPositionHeader + HeaderIdentifierBytes.Length, streamLength);
                MusicMatchHeader footer = ReadHeaderFooter(stream, startPositionHeader, endPositionHeader, HeaderIdentifierBytes, TagOrigin.End);
                if (footer != null)
                    return footer;

                // Look for a footer before the previous start position
                startPositionHeader = Math.Max(startPositionHeader - HeaderIdentifierBytes.Length, 0);
                endPositionHeader = Math.Min(startPositionHeader + HeaderIdentifierBytes.Length, streamLength);
                footer = ReadHeaderFooter(stream, startPositionHeader, endPositionHeader, HeaderIdentifierBytes, TagOrigin.End);
                if (footer != null)
                    return footer;
            }
            return null;
        }
コード例 #12
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);
        }
コード例 #13
0
 /// <summary>
 /// Initializes a new instance of the <see cref="AudioTagParseEventArgs" /> class.
 /// </summary>
 /// <param name="audioTagReader">The audio tag.</param>
 /// <param name="tagOrigin">The tag origin.</param>
 public AudioTagParseEventArgs(IAudioTagReader audioTagReader, TagOrigin tagOrigin)
 {
     AudioTagReader = audioTagReader;
     TagOrigin = tagOrigin;
 }