private static void ParsePicture(Stream fs, ref FlacInfo info)
        {
            var pictureType       = fs.BEInt32();
            var mimeStringLength  = (int)fs.BEInt32();
            var mimeType          = Encoding.ASCII.GetString(fs.ReadBytes(mimeStringLength), 0, mimeStringLength);
            var descriptionLength = (int)fs.BEInt32();
            var description       = Encoding.UTF8.GetString(fs.ReadBytes(descriptionLength), 0, descriptionLength);
            var pictureWidth      = fs.BEInt32();
            var pictureHeight     = fs.BEInt32();
            var colorDepth        = fs.BEInt32();
            var indexedColorCount = fs.BEInt32();
            var pictureDataLength = fs.BEInt32();

            fs.Seek(pictureDataLength, SeekOrigin.Current);
            info.HasCover = true;
            if (pictureType > 20)
            {
                pictureType = 21;
            }
            Logger.Log($" | picture type: {PictureTypeName[pictureType]}");
            Logger.Log($" | picture format type: {mimeType}");
            if (descriptionLength > 0)
            {
                Logger.Log($" | description: {description}");
            }
            Logger.Log($" | attribute: {pictureWidth}px*{pictureHeight}px@{colorDepth}-bit");
            if (indexedColorCount != 0)
            {
                Logger.Log($" | indexed-color color: {indexedColorCount}");
            }
        }
        private static void ParseStreamInfo(Stream fs, ref FlacInfo info)
        {
            var minBlockSize = fs.BEInt16();
            var maxBlockSize = fs.BEInt16();
            var minFrameSize = fs.BEInt24();
            var maxFrameSize = fs.BEInt24();
            var buffer       = fs.ReadBytes(8);
            var br           = new BitReader(buffer);
            var sampleRate   = br.GetBits(20);
            var channelCount = br.GetBits(3) + 1;
            var bitPerSample = br.GetBits(5) + 1;
            var totalSample  = br.GetBits(36);
            var md5          = fs.ReadBytes(16);

            info.RawLength = channelCount * bitPerSample / 8 * totalSample;

            info.SampleRate   = sampleRate;
            info.BitPerSample = bitPerSample;
            Logger.Log($" | minimum block size: {minBlockSize}, maximum block size: {maxBlockSize}");
            Logger.Log($" | minimum frame size: {minFrameSize}, maximum frame size: {maxFrameSize}");
            Logger.Log($" | Sample rate: {sampleRate}Hz, bits per sample: {bitPerSample}-bit");
            Logger.Log($" | Channel count: {channelCount}");
            var md5String = md5.Aggregate("", (current, item) => current + $"{item:X2}");

            Logger.Log($" | MD5: {md5String}");
        }
        private static void ParseVorbisComment(Stream fs, ref FlacInfo info)
        {
            //only here in flac use little-endian
            var vendorLength        = (int)fs.LEInt32();
            var vendorRawStringData = fs.ReadBytes(vendorLength);
            var vendor = Encoding.UTF8.GetString(vendorRawStringData, 0, vendorLength);

            info.Encoder = vendor;
            Logger.Log($" | Vendor: {vendor}");
            var userCommentListLength = fs.LEInt32();

            for (var i = 0; i < userCommentListLength; ++i)
            {
                var commentLength        = (int)fs.LEInt32();
                var commentRawStringData = fs.ReadBytes(commentLength);
                var comment       = Encoding.UTF8.GetString(commentRawStringData, 0, commentLength);
                var splitterIndex = comment.IndexOf('=');
                var key           = comment.Substring(0, splitterIndex);
                var value         = comment.Substring(splitterIndex + 1, comment.Length - 1 - splitterIndex);
                info.VorbisComment[key] = value;
                var summary = value.Length > 25 ? value.Substring(0, 25) + "..." : value;
                Logger.Log($" | [{key}] = '{summary.Replace('\n', ' ')}'");
            }
        }
        public static FlacInfo GetMetadataFromFlac(string flacPath)
        {
            Logger.Log(flacPath);
            using (var fs = File.OpenRead(flacPath))
            {
                var info      = new FlacInfo();
                var id3Header = Encoding.ASCII.GetString(fs.ReadBytes(3), 0, 3);
                fs.Seek(0, SeekOrigin.Begin);
                if (id3Header == "ID3")
                {
                    SkipID3Block(fs);
                    Logger.Log(Logger.Level.Warning, $"{flacPath} 文件在头部包含ID3v2标签!");
                }

                var flacBeginPosition = fs.Position;
                var header            = Encoding.ASCII.GetString(fs.ReadBytes(4), 0, 4);
                if (header != "fLaC")
                {
                    throw new InvalidDataException($"Except an flac but get an {header}" +
                                                   $"{Environment.NewLine}File name: {Path.GetFileName(flacPath)}");
                }
                //METADATA_BLOCK_HEADER
                //1-bit Last-metadata-block flag
                //7-bit BLOCK_TYPE
                //24-bit Length
                long metaLength = 4 /*header*/;
                while (fs.Position < fs.Length)
                {
                    var blockHeader       = fs.BEInt32();
                    var lastMetadataBlock = blockHeader >> 31 == 0x1;
                    var blockType         = (BlockType)((blockHeader >> 24) & 0x7f);
                    var length            = blockHeader & 0xffffff;
                    var prePos            = fs.Position;
                    metaLength += length + 4 /*length of METADATA_BLOCK_HEADER*/;
                    Logger.Log($"|+{blockType} with Length: {length}");
                    switch (blockType)
                    {
                    case BlockType.STREAMINFO:
                        Debug.Assert(length == 34);
                        ParseStreamInfo(fs, ref info);
                        break;

                    case BlockType.VORBIS_COMMENT:
                        ParseVorbisComment(fs, ref info);
                        break;

                    case BlockType.PICTURE:
                        ParsePicture(fs, ref info);
                        break;

                    case BlockType.PADDING:
                    case BlockType.APPLICATION:
                    case BlockType.SEEKTABLE:
                    case BlockType.CUESHEET:
                        fs.Seek(length, SeekOrigin.Current);
                        break;

                    default:
                        throw new ArgumentOutOfRangeException($"Invalid BLOCK_TYPE: 0x{blockType:X}");
                    }
                    Debug.Assert(fs.Position - prePos == length);
                    if (lastMetadataBlock)
                    {
                        break;
                    }
                }
                Debug.Assert(fs.Position == metaLength + flacBeginPosition);
                info.TrueLength = fs.Length - fs.Position;
                return(info);
            }
        }