// TODO : support for CUESHEET block public bool Read(BinaryReader source, ReadTagParams readTagParams) { bool result = false; if (readTagParams.ReadTag && null == vorbisTag) { vorbisTag = new VorbisTag(false, false, false, false); } initialPaddingOffset = -1; initialPaddingSize = 0; byte[] aMetaDataBlockHeader; long position; uint blockLength; byte blockType; int blockIndex; bool isLast; bool paddingFound = false; long blockEndOffset = -1; readHeader(source); // Process data if loaded and header valid if (header.IsValid()) { int channels = (header.Info[12] >> 1) & 0x7; switch (channels) { case 0b0000: channelsArrangement = MONO; break; case 0b0001: channelsArrangement = STEREO; break; case 0b0010: channelsArrangement = ISO_3_0_0; break; case 0b0011: channelsArrangement = QUAD; break; case 0b0100: channelsArrangement = ISO_3_2_0; break; case 0b0101: channelsArrangement = ISO_3_2_1; break; case 0b0110: channelsArrangement = LRCLFECrLssRss; break; case 0b0111: channelsArrangement = LRCLFELrRrLssRss; break; case 0b1000: channelsArrangement = JOINT_STEREO_LEFT_SIDE; break; case 0b1001: channelsArrangement = JOINT_STEREO_RIGHT_SIDE; break; case 0b1010: channelsArrangement = JOINT_STEREO_MID_SIDE; break; default: channelsArrangement = UNKNOWN; break; } sampleRate = header.Info[10] << 12 | header.Info[11] << 4 | header.Info[12] >> 4; bitsPerSample = (byte)(((header.Info[12] & 1) << 4) | (header.Info[13] >> 4) + 1); samples = header.Info[14] << 24 | header.Info[15] << 16 | header.Info[16] << 8 | header.Info[17]; if (0 == (header.MetaDataBlockHeader[1] & FLAG_LAST_METADATA_BLOCK)) // metadata block exists { blockIndex = 0; vorbisTag.Clear(); if (readTagParams.PrepareForWriting) { if (null == zones) { zones = new List <Zone>(); } else { zones.Clear(); } blockEndOffset = source.BaseStream.Position; } do // Read all metadata blocks { aMetaDataBlockHeader = source.ReadBytes(4); isLast = (aMetaDataBlockHeader[0] & FLAG_LAST_METADATA_BLOCK) > 0; // last flag ( first bit == 1 ) blockIndex++; blockLength = StreamUtils.DecodeBEUInt24(aMetaDataBlockHeader, 1); blockType = (byte)(aMetaDataBlockHeader[0] & 0x7F); // decode metablock type position = source.BaseStream.Position; if (blockType == META_VORBIS_COMMENT) // Vorbis metadata { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(blockType + "", position - 4, (int)blockLength + 4, new byte[0], blockType)); } vorbisTag.Read(source, readTagParams); } else if ((blockType == META_PADDING) && (!paddingFound)) // Padding block (skip any other padding block) { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(PADDING_ZONE_NAME, position - 4, (int)blockLength + 4, new byte[0], blockType)); } initialPaddingSize = blockLength; initialPaddingOffset = position; paddingFound = true; source.BaseStream.Seek(blockLength, SeekOrigin.Current); } else if (blockType == META_PICTURE) // Picture (NB: as per FLAC specs, pictures must be embedded at the FLAC level, not in the VorbisComment !) { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(blockType + "", position - 4, (int)blockLength + 4, new byte[0], blockType)); } vorbisTag.ReadPicture(source.BaseStream, readTagParams); } else // Unhandled block; needs to be zoned anyway to be able to manage the 'isLast' flag at write-time { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(blockType + "", position - 4, (int)blockLength + 4, new byte[0], blockType)); } } if (blockType < 7) { source.BaseStream.Seek(position + blockLength, SeekOrigin.Begin); blockEndOffset = position + blockLength; } else { // Abnormal header : incorrect size and/or misplaced last-metadata-block flag break; } }while (!isLast); if (readTagParams.PrepareForWriting) { bool vorbisTagFound = false; bool pictureFound = false; foreach (Zone zone in zones) { if (zone.Flag == META_PICTURE) { pictureFound = true; } else if (zone.Flag == META_VORBIS_COMMENT) { vorbisTagFound = true; } } if (!vorbisTagFound) { zones.Add(new Zone(META_VORBIS_COMMENT + "", blockEndOffset, 0, new byte[0], META_VORBIS_COMMENT)); } if (!pictureFound) { zones.Add(new Zone(META_PICTURE + "", blockEndOffset, 0, new byte[0], META_PICTURE)); } // Padding must be the last block for it to correctly absorb size variations of the other blocks if (!paddingFound && Settings.AddNewPadding) { zones.Add(new Zone(PADDING_ZONE_NAME, blockEndOffset, 0, new byte[0], META_PADDING)); } } } } if (isValid()) { audioOffset = source.BaseStream.Position; // we need that to calculate the bitrate result = true; } return(result); }
public Boolean Read(BinaryReader source, ReadTagParams readTagParams) { var result = false; if (readTagParams.ReadTag && null == vorbisTag) { vorbisTag = new VorbisTag(false, false, false); } Byte[] aMetaDataBlockHeader; Int64 position; UInt32 blockLength; Int32 blockType; Int32 blockIndex; Boolean isLast; var bPaddingFound = false; readHeader(source); // Process data if loaded and header valid if (header.IsValid()) { channels = (Byte)(((header.Info[12] >> 1) & 0x7) + 1); sampleRate = (header.Info[10] << 12 | header.Info[11] << 4 | header.Info[12] >> 4); bitsPerSample = (Byte)(((header.Info[12] & 1) << 4) | (header.Info[13] >> 4) + 1); samples = (header.Info[14] << 24 | header.Info[15] << 16 | header.Info[16] << 8 | header.Info[17]); if (0 == (header.MetaDataBlockHeader[1] & 0x80)) // metadata block exists { blockIndex = 0; if (readTagParams.PrepareForWriting) { if (null == zones) { zones = new List <Zone>(); } else { zones.Clear(); } firstBlockPosition = source.BaseStream.Position; } do // read more metadata blocks if available { aMetaDataBlockHeader = source.ReadBytes(4); isLast = ((aMetaDataBlockHeader[0] & 0x80) > 0); // last flag ( first bit == 1 ) blockIndex++; blockLength = StreamUtils.DecodeBEUInt24(aMetaDataBlockHeader, 1); blockType = (aMetaDataBlockHeader[0] & 0x7F); // decode metablock type position = source.BaseStream.Position; if (blockType == META_VORBIS_COMMENT) // Vorbis metadata { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(ZONE_VORBISTAG, position - 4, (Int32)blockLength + 4, new Byte[0], (Byte)(isLast ? 1 : 0))); } vorbisTag.Read(source, readTagParams); } else if ((blockType == META_PADDING) && (!bPaddingFound)) // Padding block { padding = blockLength; // if we find more skip & put them in metablock array paddingLast = ((aMetaDataBlockHeader[0] & 0x80) != 0); paddingIndex = blockIndex; bPaddingFound = true; source.BaseStream.Seek(padding, SeekOrigin.Current); // advance into file till next block or audio data start } else if (blockType == META_PICTURE) { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(ZONE_PICTURE, position - 4, (Int32)blockLength + 4, new Byte[0], (Byte)(isLast ? 1 : 0))); } vorbisTag.ReadPicture(source.BaseStream, readTagParams); } // TODO : support for CUESHEET block if (blockType < 7) { source.BaseStream.Seek(position + blockLength, SeekOrigin.Begin); } else { // Abnormal header : incorrect size and/or misplaced last-metadata-block flag break; } }while (!isLast); if (readTagParams.PrepareForWriting) { var vorbisTagFound = false; var pictureFound = false; foreach (var zone in zones) { if (zone.Name.Equals(ZONE_PICTURE)) { pictureFound = true; } else if (zone.Name.Equals(ZONE_VORBISTAG)) { vorbisTagFound = true; } } if (!vorbisTagFound) { zones.Add(new Zone(ZONE_VORBISTAG, firstBlockPosition, 0, new Byte[0])); } if (!pictureFound) { zones.Add(new Zone(ZONE_PICTURE, firstBlockPosition, 0, new Byte[0])); } } } } if (isValid()) { audioOffset = source.BaseStream.Position; // we need that to rebuild the file if nedeed result = true; } return(result); }
private bool getInfo(BufferedBinaryReader source, FileInfo info, ReadTagParams readTagParams) { // Get info from file bool result = false; bool isValidHeader = false; // Check for ID3v2 (NB : this case should not even exist since OGG has its own native tagging system, and is not deemed compatible with ID3v2 according to the ID3 FAQ) source.Seek(sizeInfo.ID3v2Size, SeekOrigin.Begin); // Read global file header info.IdentificationHeader.ReadFromStream(source); if (info.IdentificationHeader.IsValid()) { source.Seek(sizeInfo.ID3v2Size + info.IdentificationHeader.Segments + 27, SeekOrigin.Begin); // 27 being the size from 'ID' to 'Segments' // Read Vorbis or Opus stream info long position = source.Position; String headerStart = Utils.Latin1Encoding.GetString(source.ReadBytes(3)); source.Seek(position, SeekOrigin.Begin); if (VORBIS_HEADER_ID.StartsWith(headerStart)) { contents = CONTENTS_VORBIS; info.VorbisParameters.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(7)); isValidHeader = VORBIS_HEADER_ID.Equals(info.VorbisParameters.ID); info.VorbisParameters.BitstreamVersion = source.ReadBytes(4); info.VorbisParameters.ChannelMode = source.ReadByte(); info.VorbisParameters.SampleRate = source.ReadInt32(); info.VorbisParameters.BitRateMaximal = source.ReadInt32(); info.VorbisParameters.BitRateNominal = source.ReadInt32(); info.VorbisParameters.BitRateMinimal = source.ReadInt32(); info.VorbisParameters.BlockSize = source.ReadByte(); info.VorbisParameters.StopFlag = source.ReadByte(); } else if (OPUS_HEADER_ID.StartsWith(headerStart)) { contents = CONTENTS_OPUS; info.OpusParameters.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(8)); isValidHeader = OPUS_HEADER_ID.Equals(info.OpusParameters.ID); info.OpusParameters.Version = source.ReadByte(); info.OpusParameters.OutputChannelCount = source.ReadByte(); info.OpusParameters.PreSkip = source.ReadUInt16(); //info.OpusParameters.InputSampleRate = source.ReadUInt32(); info.OpusParameters.InputSampleRate = 48000; // Actual sample rate is hardware-dependent. Let's assume for now that the hardware ATL runs on supports 48KHz source.Seek(4, SeekOrigin.Current); info.OpusParameters.OutputGain = source.ReadInt16(); info.OpusParameters.ChannelMappingFamily = source.ReadByte(); if (info.OpusParameters.ChannelMappingFamily > 0) { info.OpusParameters.StreamCount = source.ReadByte(); info.OpusParameters.CoupledStreamCount = source.ReadByte(); info.OpusParameters.ChannelMapping = new byte[info.OpusParameters.OutputChannelCount]; for (int i = 0; i < info.OpusParameters.OutputChannelCount; i++) { info.OpusParameters.ChannelMapping[i] = source.ReadByte(); } } } if (isValidHeader) { info.CommentHeaderStart = source.Position; IList <long> pagePos = new List <long>(); // Reads all related Vorbis pages that describe Comment and Setup headers // and concatenate their content into a single, continuous data stream bool loop = true; bool first = true; using (MemoryStream s = new MemoryStream()) { // Reconstruct the whole Comment header from OGG pages to a MemoryStream while (loop) { info.SetupHeaderEnd = source.Position; // When the loop stops, cursor is starting to read a brand new page located after Comment _and_ Setup headers info.CommentHeader.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(4)); info.CommentHeader.StreamVersion = source.ReadByte(); info.CommentHeader.TypeFlag = source.ReadByte(); // 0 marks a new page if (0 == info.CommentHeader.TypeFlag) { loop = first; } if (loop) { info.CommentHeader.AbsolutePosition = source.ReadUInt64(); info.CommentHeader.Serial = source.ReadInt32(); info.CommentHeader.PageNumber = source.ReadInt32(); info.CommentHeader.Checksum = source.ReadUInt32(); info.CommentHeader.Segments = source.ReadByte(); info.CommentHeader.LacingValues = source.ReadBytes(info.CommentHeader.Segments); s.Write(source.ReadBytes(info.CommentHeader.GetPageLength()), 0, info.CommentHeader.GetPageLength()); pagePos.Add(info.SetupHeaderEnd); } first = false; } if (readTagParams.PrepareForWriting) // Metrics to prepare writing { if (pagePos.Count > 1) { source.Position = pagePos[pagePos.Count - 2]; } else { source.Position = pagePos[0]; } // Determine the boundaries of 3rd header (Setup header) by searching from last-but-one page if (StreamUtils.FindSequence(source, Utils.Latin1Encoding.GetBytes(VORBIS_SETUP_ID))) { info.SetupHeaderStart = source.Position - VORBIS_SETUP_ID.Length; info.CommentHeaderEnd = info.SetupHeaderStart; if (pagePos.Count > 1) { int firstSetupPage = -1; for (int i = 1; i < pagePos.Count; i++) { if (info.CommentHeaderEnd < pagePos[i]) { info.CommentHeaderSpanPages = i - 1; firstSetupPage = i - 1; } if (info.SetupHeaderEnd < pagePos[i]) { info.SetupHeaderSpanPages = i - firstSetupPage; } } /// Not found yet => comment header takes up all pages, and setup header is on the end of the last page if (-1 == firstSetupPage) { info.CommentHeaderSpanPages = pagePos.Count; info.SetupHeaderSpanPages = 1; } } else { info.CommentHeaderSpanPages = 1; info.SetupHeaderSpanPages = 1; } } } // Get total number of samples info.Samples = getSamples(source); // Read metadata from the reconstructed Comment header inside the memoryStream if (readTagParams.ReadTag) { BinaryReader msr = new BinaryReader(s); s.Seek(0, SeekOrigin.Begin); string tagId; bool isValidTagHeader = false; if (contents.Equals(CONTENTS_VORBIS)) { tagId = Utils.Latin1Encoding.GetString(msr.ReadBytes(7)); isValidTagHeader = (VORBIS_TAG_ID.Equals(tagId)); } else if (contents.Equals(CONTENTS_OPUS)) { tagId = Utils.Latin1Encoding.GetString(msr.ReadBytes(8)); isValidTagHeader = (OPUS_TAG_ID.Equals(tagId)); } if (isValidTagHeader) { vorbisTag.Clear(); vorbisTag.Read(msr, readTagParams); } } } // using MemoryStream result = true; } } return(result); }
public bool Read(BinaryReader source, ReadTagParams readTagParams) { bool result = false; if (readTagParams.ReadTag && null == vorbisTag) { vorbisTag = new VorbisTag(false, false, false); } byte[] aMetaDataBlockHeader; long position; uint blockLength; int blockType; int blockIndex; bool isLast; bool bPaddingFound = false; readHeader(source); // Process data if loaded and header valid if (header.IsValid()) { int channels = (header.Info[12] >> 1) & 0x7; switch (channels) { case 0b0000: channelsArrangement = MONO; break; case 0b0001: channelsArrangement = STEREO; break; case 0b0010: channelsArrangement = ISO_3_0_0; break; case 0b0011: channelsArrangement = QUAD; break; case 0b0100: channelsArrangement = ISO_3_2_0; break; case 0b0101: channelsArrangement = ISO_3_2_1; break; case 0b0110: channelsArrangement = LRCLFECrLssRss; break; case 0b0111: channelsArrangement = LRCLFELrRrLssRss; break; case 0b1000: channelsArrangement = JOINT_STEREO_LEFT_SIDE; break; case 0b1001: channelsArrangement = JOINT_STEREO_RIGHT_SIDE; break; case 0b1010: channelsArrangement = JOINT_STEREO_MID_SIDE; break; default: channelsArrangement = UNKNOWN; break; } sampleRate = (header.Info[10] << 12 | header.Info[11] << 4 | header.Info[12] >> 4); bitsPerSample = (byte)(((header.Info[12] & 1) << 4) | (header.Info[13] >> 4) + 1); samples = (header.Info[14] << 24 | header.Info[15] << 16 | header.Info[16] << 8 | header.Info[17]); if (0 == (header.MetaDataBlockHeader[1] & 0x80)) // metadata block exists { blockIndex = 0; vorbisTag.Clear(); if (readTagParams.PrepareForWriting) { if (null == zones) { zones = new List <Zone>(); } else { zones.Clear(); } firstBlockPosition = source.BaseStream.Position; } do // read more metadata blocks if available { aMetaDataBlockHeader = source.ReadBytes(4); isLast = ((aMetaDataBlockHeader[0] & 0x80) > 0); // last flag ( first bit == 1 ) blockIndex++; blockLength = StreamUtils.DecodeBEUInt24(aMetaDataBlockHeader, 1); blockType = (aMetaDataBlockHeader[0] & 0x7F); // decode metablock type position = source.BaseStream.Position; if (blockType == META_VORBIS_COMMENT) // Vorbis metadata { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(ZONE_VORBISTAG, position - 4, (int)blockLength + 4, new byte[0], (byte)(isLast ? 1 : 0))); } vorbisTag.Read(source, readTagParams); } else if ((blockType == META_PADDING) && (!bPaddingFound)) // Padding block { padding = blockLength; // if we find more skip & put them in metablock array paddingLast = ((aMetaDataBlockHeader[0] & 0x80) != 0); paddingIndex = blockIndex; bPaddingFound = true; source.BaseStream.Seek(padding, SeekOrigin.Current); // advance into file till next block or audio data start } else if (blockType == META_PICTURE) { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(ZONE_PICTURE, position - 4, (int)blockLength + 4, new byte[0], (byte)(isLast ? 1 : 0))); } vorbisTag.ReadPicture(source.BaseStream, readTagParams); } // TODO : support for CUESHEET block if (blockType < 7) { source.BaseStream.Seek(position + blockLength, SeekOrigin.Begin); } else { // Abnormal header : incorrect size and/or misplaced last-metadata-block flag break; } }while (!isLast); if (readTagParams.PrepareForWriting) { bool vorbisTagFound = false; bool pictureFound = false; foreach (Zone zone in zones) { if (zone.Name.Equals(ZONE_PICTURE)) { pictureFound = true; } else if (zone.Name.Equals(ZONE_VORBISTAG)) { vorbisTagFound = true; } } if (!vorbisTagFound) { zones.Add(new Zone(ZONE_VORBISTAG, firstBlockPosition, 0, new byte[0])); } if (!pictureFound) { zones.Add(new Zone(ZONE_PICTURE, firstBlockPosition, 0, new byte[0])); } } } } if (isValid()) { audioOffset = source.BaseStream.Position; // we need that to rebuild the file if nedeed result = true; } return(result); }