private void ReadTag_FLAC(Stream stream, InternalInfo info) { info.FileType = FileType.Flac; // Skip "fLaC" marker stream.Seek(4, SeekOrigin.Current); bool isLastMetaDataBlock; List <FlacMetaDataBlock> metaDataBlockList = new List <FlacMetaDataBlock>(); do { int c = stream.ReadByte(); isLastMetaDataBlock = (((c >> 7) & 0x01) == 1); int blocksize = stream.ReadInt24(); FlacMetaDataBlockType blockType = (FlacMetaDataBlockType)(c & 0x07); if (blockType == FlacMetaDataBlockType.VorbisComment) // Vorbis comment { info.OrigVorbisCommentSize = blocksize; long mvcoffset = stream.Position - 4; ReadTag_VorbisComment(stream); stream.Seek(mvcoffset + 4, SeekOrigin.Begin); } FlacMetaDataBlock metaDataBlock = new FlacMetaDataBlock(blockType); metaDataBlock.SetBlockData(stream, blocksize); metaDataBlockList.Add(metaDataBlock); } while (isLastMetaDataBlock == false); info.MetaDataBlockList = metaDataBlockList; _metaDataBlockList = metaDataBlockList; }
private void WriteTagFlac(string path, InternalInfo targetFile) { // This will store the metadata blocks we're actually going to write List <FlacMetaDataBlock> myMetaDataBlocks = new List <FlacMetaDataBlock>(); // Get byte array of new vorbis comment block byte[] newTagArray; using (MemoryStream newTag = new MemoryStream()) { // Write vendor byte[] vendorBytes = Encoding.UTF8.GetBytes(_vendor); newTag.WriteInt32LittleEndian(vendorBytes.Length); newTag.Write(vendorBytes); // Remove dead items and replace commonly misnamed items foreach (NameValueItem item in new List <NameValueItem>(_items)) { if (string.IsNullOrEmpty(item.Value)) { _items.Remove(item); } else if (string.Compare(item.Name, "YEAR", true) == 0) { if (string.IsNullOrEmpty(_items.GetValue("DATE"))) { _items.SetValue("DATE", item.Value); } _items.Remove(item); } } // Write item count newTag.WriteInt32LittleEndian(_items.Count); // Write items foreach (NameValueItem item in _items) { if (string.IsNullOrEmpty(item.Value)) { continue; } byte[] keyBytes = Encoding.ASCII.GetBytes(item.Name); byte[] valueBytes = Encoding.UTF8.GetBytes(item.Value); newTag.WriteInt32LittleEndian(keyBytes.Length + 1 + valueBytes.Length); newTag.Write(keyBytes); newTag.WriteByte((byte)'='); newTag.Write(valueBytes); } newTagArray = newTag.ToArray(); } // 1. Get old size of metadata blocks. // 2. Find StreamInfo, SeekTable, and Padding blocks. // These blocks should occur only once. If not, an exception is thrown. The padding // block we don't really care about so no exception is thrown if it's duplicated. FlacMetaDataBlock paddingBlock = null; FlacMetaDataBlock streamInfoBlock = null; FlacMetaDataBlock seekTableBlock = null; long origMetaDataSize = 0; foreach (FlacMetaDataBlock metaDataBlock in targetFile.MetaDataBlockList) { origMetaDataSize += 4; // Identifier + Size origMetaDataSize += metaDataBlock.Size; if (metaDataBlock.BlockType == FlacMetaDataBlockType.Padding) { paddingBlock = metaDataBlock; } else if (metaDataBlock.BlockType == FlacMetaDataBlockType.StreamInfo) { if (streamInfoBlock != null) { throw new InvalidDataException("Multiple stream info blocks"); } streamInfoBlock = metaDataBlock; } else if (metaDataBlock.BlockType == FlacMetaDataBlockType.SeekTable) { if (seekTableBlock != null) { throw new InvalidDataException("Multiple seek tables"); } seekTableBlock = metaDataBlock; } } // No Padding block found, create one if (paddingBlock == null) { paddingBlock = new FlacMetaDataBlock(FlacMetaDataBlockType.Padding); paddingBlock.SetBlockDataZeroed(2000); } // Padding block found, adjust size else { // TODO: This is not entirely accurate, since we may be reading from one file // and writing to another. The other blocks need to be accounted for, however for // same file read/write this works. Not high priority. int adjustPadding = targetFile.OrigVorbisCommentSize - newTagArray.Length; int newSize = paddingBlock.Size + adjustPadding; if (newSize < 10) { paddingBlock.SetBlockDataZeroed(2000); } else { paddingBlock.SetBlockDataZeroed(newSize); } } // Set Vorbis-Comment block data FlacMetaDataBlock vorbisCommentBlock = new FlacMetaDataBlock(FlacMetaDataBlockType.VorbisComment); vorbisCommentBlock.SetBlockData(newTagArray); // Create list of blocks to write myMetaDataBlocks.Add(streamInfoBlock); // StreamInfo MUST be first if (seekTableBlock != null) { myMetaDataBlocks.Add(seekTableBlock); } // Add other blocks we read from the original file. foreach (FlacMetaDataBlock metaDataBlock in _metaDataBlockList) { if (metaDataBlock.BlockType == FlacMetaDataBlockType.Application || metaDataBlock.BlockType == FlacMetaDataBlockType.CueSheet || metaDataBlock.BlockType == FlacMetaDataBlockType.Picture) { myMetaDataBlocks.Add(metaDataBlock); } } // Add our new vorbis comment and padding blocks myMetaDataBlocks.Add(vorbisCommentBlock); myMetaDataBlocks.Add(paddingBlock); // Get new size of metadata blocks long newMetaDataSize = 0; foreach (FlacMetaDataBlock metaDataBlock in myMetaDataBlocks) { newMetaDataSize += 4; // Identifier + Size newMetaDataSize += metaDataBlock.Size; } // If the new metadata size is less than the original, increase the padding if (newMetaDataSize != origMetaDataSize) { int newPaddingSize = paddingBlock.Size + (int)(origMetaDataSize - newMetaDataSize); if (newPaddingSize > 0) { paddingBlock.SetBlockDataZeroed(newPaddingSize); // Get new size of metadata blocks newMetaDataSize = 0; foreach (FlacMetaDataBlock metaDataBlock in myMetaDataBlocks) { newMetaDataSize += 4; // Identifier + Size newMetaDataSize += metaDataBlock.Size; } } } string tempFilename = null; // no rewrite necessary if (newMetaDataSize == origMetaDataSize) { // } // rewrite necessary - grab a snickers. else if (newMetaDataSize > origMetaDataSize) { // rename tempFilename = PathUtils.GetTemporaryFileNameBasedOnFileName(path); File.Move(path, tempFilename); // open for read, open for write using (FileStream fsRead = File.Open(tempFilename, FileMode.Open, FileAccess.Read, FileShare.None)) using (FileStream fsWrite = File.Open(path, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { // copy ID3v2 tag.. technically there shouldn't be one, but we don't want to destroy data int tmpID3v2TagSize = ID3v2.ID3v2Tag.GetTagSize(fsRead); if (tmpID3v2TagSize != 0) { byte[] id3v2 = fsRead.Read(tmpID3v2TagSize); fsWrite.Write(id3v2); } fsWrite.Write(FLAC_MARKER); // create blankspace byte[] blankSpace = new Byte[newMetaDataSize]; fsWrite.Write(blankSpace); fsRead.Seek(4 + origMetaDataSize, SeekOrigin.Current); byte[] buf = new byte[32768]; int bytesRead = fsRead.Read(buf, 0, 32768); while (bytesRead != 0) { fsWrite.Write(buf, 0, bytesRead); bytesRead = fsRead.Read(buf, 0, 32768); } } } // newMetaDataSize < origMetaDataSize is an error else { throw new Exception(String.Format("Internal Error: newMetaDataSize ({0}) < origMetaDataSize ({1})", newMetaDataSize, origMetaDataSize)); } using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { // skip fLaC marker and ID3v2 tag size int tmpID3v2TagSize = ID3v2.ID3v2Tag.GetTagSize(fs); fs.Position = tmpID3v2TagSize + 4; byte blockType; foreach (FlacMetaDataBlock metaDataBlock in myMetaDataBlocks) { // always write padding last if (metaDataBlock == paddingBlock) { continue; } blockType = (byte)metaDataBlock.BlockType; fs.WriteByte(blockType); fs.WriteInt24(metaDataBlock.Size); fs.Write(metaDataBlock.BlockData); } // write padding, add stop bit to block type blockType = (byte)(paddingBlock.BlockType + 0x80); fs.WriteByte(blockType); fs.WriteInt24(paddingBlock.Size); fs.Write(paddingBlock.BlockData); } if (!string.IsNullOrEmpty(tempFilename)) { File.Delete(tempFilename); } }