Example #1
0
        internal static TagModel Deserialize(Stream stream)
        {
            var tagModel = new TagModel();

            tagModel.Header.Deserialize(stream);
            if (tagModel.Header.Version != 3 & tagModel.Header.Version != 4)
            {
                throw new AudioUnsupportedException("ID3v2 Version " + tagModel.Header.Version + " is not supported.");
            }

            var id3TagSize = tagModel.Header.TagSize;

            MemoryStream?memory = null;

            try
            {
                if (tagModel.Header.Unsynchronisation)
                {
                    memory      = new(new byte[stream.Length]);
                    id3TagSize -= Sync.Unsafe(stream, memory, id3TagSize);
                    stream      = memory;
                    if (id3TagSize <= 0)
                    {
                        throw new AudioInvalidException("Data is missing after the header.");
                    }
                }

                uint rawSize;

                // Seek past the extended header
                if (tagModel.Header.HasExtendedHeader)
                {
                    tagModel.ExtendedHeader.Deserialize(stream);
                    rawSize = id3TagSize - 4 - tagModel.ExtendedHeader.Size;
                    if (id3TagSize <= 0)
                    {
                        throw new AudioInvalidException("Data is missing after the extended header.");
                    }
                }
                else
                {
                    rawSize = id3TagSize;
                }

                // Tags should have at least one frame
                if (rawSize <= 0)
                {
                    throw new AudioInvalidException("No frames are present in the tag.");
                }

                uint index = 0;
#if NETSTANDARD2_0
                var frameIdBuffer = new byte[4];
#else
                Span <byte> frameIdBuffer = stackalloc byte[4];
#endif

                while (index < rawSize)
                {
                    // Read one byte first, looking for padding
#if NETSTANDARD2_0
                    stream.Read(frameIdBuffer, 0, 1);
#else
                    stream.Read(frameIdBuffer.Slice(0, 1));
#endif

                    // We reached the padding
                    if (frameIdBuffer[0] == 0)
                    {
                        tagModel.Header.PaddingSize = rawSize - index;
                        stream.Seek(tagModel.Header.PaddingSize - 1, SeekOrigin.Current);

                        break;
                    }

                    // 10 is the minimum size of a valid frame
                    if (index + 10 > rawSize)
                    {
                        throw new AudioInvalidException("Tag is corrupt: incomplete frame.");
                    }

                    // Read the rest of the frame ID
#if NETSTANDARD2_0
                    stream.Read(frameIdBuffer, 1, 3);
#else
                    stream.Read(frameIdBuffer.Slice(1, 3));
#endif
                    index += 4;

                    using (var reader = new TagReader(stream))
                    {
                        var frameSize = tagModel.Header.Version == 4
                            ? reader.ReadUInt32SyncSafe()
                            : reader.ReadUInt32BigEndian();
                        index += 4;

                        // The size of the frame can't be larger than the available space
                        if (frameSize > rawSize - index)
                        {
                            throw new AudioInvalidException(
                                      "A frame is corrupt: can't be larger than the available space remaining.");
                        }

                        var flags = new FrameFlags(reader.ReadUInt16BigEndian(), tagModel.Header.Version);
                        index += 2;

                        tagModel.Frames.Add(ReadFrame(reader,
#if NETSTANDARD2_0
                                                      Encoding.ASCII.GetString(frameIdBuffer, 0, 4), flags, frameSize));
#else
                                                      Encoding.ASCII.GetString(frameIdBuffer), flags, frameSize));
#endif

                        index += frameSize;
                    }
                }

                return(tagModel);
            }
            finally
            {
                memory?.Close();
            }
        }
Example #2
0
        internal TagModelToMetadataAdapter(TagModel tagModel)
        {
            foreach (var frame in tagModel.Frames)
            {
                switch (frame)
                {
                case FrameFullText frameFullText:
                    if (frameFullText.FrameId.Equals("COMM", StringComparison.OrdinalIgnoreCase) &&
                        string.IsNullOrEmpty(frameFullText.Description))
                    {
                        Comment = frameFullText.Text;
                    }
                    break;

                case FrameTextUserDef frameTextUserDef:
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (frameTextUserDef.Description.ToUpperInvariant())
                    {
                    case "REPLAYGAIN_TRACK_PEAK":
                        TrackPeak = frameTextUserDef.Text;
                        break;

                    case "REPLAYGAIN_ALBUM_PEAK":
                        AlbumPeak = frameTextUserDef.Text;
                        break;

                    case "REPLAYGAIN_TRACK_GAIN":
#if NETSTANDARD2_0
                        TrackGain = frameTextUserDef.Text.Replace(" dB", string.Empty);
#else
                        TrackGain = frameTextUserDef.Text.Replace(" dB", string.Empty,
                                                                  StringComparison.OrdinalIgnoreCase);
#endif
                        break;

                    case "REPLAYGAIN_ALBUM_GAIN":
#if NETSTANDARD2_0
                        AlbumGain = frameTextUserDef.Text.Replace(" dB", string.Empty);
#else
                        AlbumGain = frameTextUserDef.Text.Replace(" dB", string.Empty,
                                                                  StringComparison.OrdinalIgnoreCase);
#endif
                        break;
                    }
                    break;

                case FrameText frameText:
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (frameText.FrameId.ToUpperInvariant())
                    {
                    case "TIT2":
                        Title = frameText.Text;
                        break;

                    case "TPE1":
                        Artist = frameText.Text;
                        break;

                    case "TALB":
                        Album = frameText.Text;
                        break;

                    case "TPE2":
                        AlbumArtist = frameText.Text;
                        break;

                    case "TCOM":
                        Composer = frameText.Text;
                        break;

                    case "TCON":
                        Genre = frameText.Text;
                        break;

                    // The TDAT frame contains the day and the month
                    case "TDAT":
                        Day = frameText.Text.Substring(0, 2);
#if NETSTANDARD2_0
                        Month = frameText.Text.Substring(2);
#else
                        Month = frameText.Text[2..];
#endif
                        break;
        internal TagModelToMetadataAdapter([NotNull] TagModel tagModel)
        {
            foreach (var frame in tagModel)
            {
                switch (frame)
                {
                case FrameText frameText:
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (frameText.FrameId)
                    {
                    case "TIT2":
                        Title = frameText.Text;
                        break;

                    case "TPE1":
                        Artist = frameText.Text;
                        break;

                    case "TALB":
                        Album = frameText.Text;
                        break;

                    case "TPE2":
                        AlbumArtist = frameText.Text;
                        break;

                    case "TCOM":
                        Composer = frameText.Text;
                        break;

                    case "TCON":
                        Genre = frameText.Text;
                        break;

                    // The TDAT frame contains the day and the month:
                    case "TDAT":
                        Day   = frameText.Text.Substring(0, 2);
                        Month = frameText.Text.Substring(2);
                        break;

                    case "TYER":
                        Year = frameText.Text;
                        break;

                    // The TRCK frame contains the track number and (optionally) the track count:
                    case "TRCK":
                        var segments = frameText.Text.Split('/');
                        TrackNumber = segments[0];
                        if (segments.Length > 1)
                        {
                            TrackCount = segments[1];
                        }
                        break;
                    }
                    break;

                case FrameFullText frameFullText:
                    if (frameFullText.FrameId.Equals("COMM", StringComparison.Ordinal) &&
                        string.IsNullOrEmpty(frameFullText.Description))
                    {
                        Comment = frameFullText.Text;
                    }
                    break;

                case FrameTextUserDef frameTextUserDef:
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (frameTextUserDef.Description)
                    {
                    case "REPLAYGAIN_TRACK_PEAK":
                        TrackPeak = frameTextUserDef.Text;
                        break;

                    case "REPLAYGAIN_ALBUM_PEAK":
                        AlbumPeak = frameTextUserDef.Text;
                        break;

                    case "REPLAYGAIN_TRACK_GAIN":
#if NETSTANDARD2_0
                        TrackGain = frameTextUserDef.Text.Replace(" dB", string.Empty);
#else
                        TrackGain = frameTextUserDef.Text.Replace(" dB", string.Empty,
                                                                  StringComparison.OrdinalIgnoreCase);
#endif
                        break;

                    case "REPLAYGAIN_ALBUM_GAIN":
#if NETSTANDARD2_0
                        AlbumGain = frameTextUserDef.Text.Replace(" dB", string.Empty);
#else
                        AlbumGain = frameTextUserDef.Text.Replace(" dB", string.Empty,
                                                                  StringComparison.OrdinalIgnoreCase);
#endif
                        break;
                    }
                    break;

                case FramePicture framePicture:
                    CoverArt = CoverArtFactory.GetOrCreate(framePicture.PictureData);
                    break;
                }
            }
        }