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(); } }
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; } } }