public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, uint chunkSize) { long position = source.Position; long initialPos = position; string key, value; int size; byte[] data = new byte[chunkSize]; while (source.Position < initialPos + chunkSize - 4) // 4 being the "INFO" purpose that belongs to the chunk { // Key source.Read(data, 0, 4); key = Utils.Latin1Encoding.GetString(data, 0, 4); // Size source.Read(data, 0, 4); size = StreamUtils.DecodeInt32(data); if (size > 0) { source.Read(data, 0, size); // Manage parasite zeroes at the end of data if (source.ReadByte() != 0) { source.Seek(-1, SeekOrigin.Current); } value = Utils.Latin1Encoding.GetString(data, 0, size); meta.SetMetaField("info." + key, Utils.StripEndingZeroChars(value), readTagParams.ReadAllMetaFrames); } } }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, UInt32 chunkSize) { var position = source.Position; var initialPos = position; String key, value; Int32 size; var data = new Byte[256]; while (source.Position < initialPos + chunkSize - 4) // 4 being the "INFO" purpose that belongs to the chunk { // Key source.Read(data, 0, 4); key = Utils.Latin1Encoding.GetString(data, 0, 4); // Size source.Read(data, 0, 4); size = StreamUtils.DecodeInt32(data); // Value value = StreamUtils.ReadNullTerminatedString(source, Utils.Latin1Encoding); if (value.Length > 0) { meta.SetMetaField("info." + key, value, readTagParams.ReadAllMetaFrames); } position = source.Position; } }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, uint chunkSize) { long position = source.Position; long initialPos = position; string key, value; int size; byte[] data = new byte[chunkSize]; long maxPos = initialPos + chunkSize - 4; // 4 being the "INFO" purpose that belongs to the chunk while (source.Position < maxPos) { // Key source.Read(data, 0, 4); key = Utils.Latin1Encoding.GetString(data, 0, 4); // Size source.Read(data, 0, 4); size = StreamUtils.DecodeInt32(data); // Do _NOT_ use StreamUtils.ReadNullTerminatedString because non-textual fields may be found here (e.g. NITR) if (size > 0) { source.Read(data, 0, size); // Manage parasite zeroes at the end of data if (source.Position < maxPos && source.ReadByte() != 0) { source.Seek(-1, SeekOrigin.Current); } value = Utils.Latin1Encoding.GetString(data, 0, size); meta.SetMetaField("info." + key, Utils.StripEndingZeroChars(value), readTagParams.ReadAllMetaFrames); } } }
private static int readInt32(Stream source, MetaDataIO meta, string fieldName, byte[] buffer, bool readAllMetaFrames) { source.Read(buffer, 0, 4); int value = StreamUtils.DecodeInt32(buffer); meta.SetMetaField(fieldName, value.ToString(), readAllMetaFrames); return(value); }
// ---------- SUPPORT METHODS private bool readWAV(Stream source, ReadTagParams readTagParams) { bool result = true; uint riffChunkSize; long riffChunkSizePos; byte[] data = new byte[4]; source.Seek(0, SeekOrigin.Begin); // Read header source.Read(data, 0, 4); string str = Utils.Latin1Encoding.GetString(data); if (str.Equals(HEADER_RIFF)) { _isLittleEndian = true; } else if (str.Equals(HEADER_RIFX)) { _isLittleEndian = false; } else { return(false); } // Force creation of FileStructureHelper with detected endianness structureHelper = new FileStructureHelper(isLittleEndian); id3v2StructureHelper = new FileStructureHelper(isLittleEndian); riffChunkSizePos = source.Position; source.Read(data, 0, 4); if (isLittleEndian) { riffChunkSize = StreamUtils.DecodeUInt32(data); } else { riffChunkSize = StreamUtils.DecodeBEUInt32(data); } // Format code source.Read(data, 0, 4); str = Utils.Latin1Encoding.GetString(data); if (!str.Equals(FORMAT_WAVE)) { return(false); } string subChunkId; uint chunkSize; long chunkDataPos; bool foundSample = false; bool foundBext = false; bool foundInfo = false; bool foundIXml = false; // Sub-chunks loop while (source.Position < riffChunkSize + 8) { // Chunk ID source.Read(data, 0, 4); if (0 == data[0]) // Sometimes data segment ends with a parasite null byte { source.Seek(-3, SeekOrigin.Current); source.Read(data, 0, 4); } subChunkId = Utils.Latin1Encoding.GetString(data); // Chunk size source.Read(data, 0, 4); if (isLittleEndian) { chunkSize = StreamUtils.DecodeUInt32(data); } else { chunkSize = StreamUtils.DecodeBEUInt32(data); } chunkDataPos = source.Position; if (subChunkId.Equals(CHUNK_FORMAT)) { source.Read(data, 0, 2); if (isLittleEndian) { formatId = StreamUtils.DecodeUInt16(data); } else { formatId = StreamUtils.DecodeBEUInt16(data); } source.Read(data, 0, 2); if (isLittleEndian) { channelsArrangement = ChannelsArrangements.GuessFromChannelNumber(StreamUtils.DecodeUInt16(data)); } else { channelsArrangement = ChannelsArrangements.GuessFromChannelNumber(StreamUtils.DecodeBEUInt16(data)); } source.Read(data, 0, 4); if (isLittleEndian) { sampleRate = StreamUtils.DecodeUInt32(data); } else { sampleRate = StreamUtils.DecodeBEUInt32(data); } source.Read(data, 0, 4); if (isLittleEndian) { bytesPerSecond = StreamUtils.DecodeUInt32(data); } else { bytesPerSecond = StreamUtils.DecodeBEUInt32(data); } source.Seek(2, SeekOrigin.Current); // BlockAlign source.Read(data, 0, 2); if (isLittleEndian) { bitsPerSample = StreamUtils.DecodeUInt16(data); } else { bitsPerSample = StreamUtils.DecodeBEUInt16(data); } } else if (subChunkId.Equals(CHUNK_DATA)) { headerSize = riffChunkSize - chunkSize; } else if (subChunkId.Equals(CHUNK_FACT)) { source.Read(data, 0, 4); if (isLittleEndian) { sampleNumber = StreamUtils.DecodeInt32(data); } else { sampleNumber = StreamUtils.DecodeBEInt32(data); } } else if (subChunkId.Equals(CHUNK_SAMPLE)) { structureHelper.AddZone(source.Position - 8, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundSample = true; tagExists = true; SampleTag.FromStream(source, this, readTagParams); } else if (subChunkId.Equals(CHUNK_BEXT)) { structureHelper.AddZone(source.Position - 8, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundBext = true; tagExists = true; BextTag.FromStream(source, this, readTagParams); } else if (subChunkId.Equals(CHUNK_INFO)) { // Purpose of the list should be INFO source.Read(data, 0, 4); string purpose = Utils.Latin1Encoding.GetString(data, 0, 4); if (purpose.Equals(InfoTag.PURPOSE_INFO)) { structureHelper.AddZone(source.Position - 12, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundInfo = true; tagExists = true; InfoTag.FromStream(source, this, readTagParams, chunkSize); } } else if (subChunkId.Equals(CHUNK_IXML)) { structureHelper.AddZone(source.Position - 8, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundIXml = true; tagExists = true; IXmlTag.FromStream(source, this, readTagParams, chunkSize); } else if (subChunkId.Equals(CHUNK_ID3)) { id3v2Offset = source.Position; // Zone is already added by Id3v2.Read id3v2StructureHelper.AddZone(id3v2Offset - 8, (int)(chunkSize + 8), subChunkId); id3v2StructureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); } source.Seek(chunkDataPos + chunkSize, SeekOrigin.Begin); } // Add zone placeholders for future tag writing if (readTagParams.PrepareForWriting) { if (!foundSample) { structureHelper.AddZone(source.Position, 0, CHUNK_SAMPLE); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_SAMPLE); } if (!foundBext) { structureHelper.AddZone(source.Position, 0, CHUNK_BEXT); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_BEXT); } if (!foundInfo) { structureHelper.AddZone(source.Position, 0, CHUNK_INFO); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_INFO); } if (!foundIXml) { structureHelper.AddZone(source.Position, 0, CHUNK_IXML); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_IXML); } } // ID3 zone should be set as the very last one for Windows to be able to read the LIST INFO zone properly if (-1 == id3v2Offset) { id3v2Offset = 0; // Switch status to "tried to read, but nothing found" if (readTagParams.PrepareForWriting) { id3v2StructureHelper.AddZone(source.Position, 0, CHUNK_ID3); id3v2StructureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_ID3); } } return(result); }
public static ImageProperties GetImageProperties(byte[] imageData, ImageFormat format = ImageFormat.Undefined) { ImageProperties props = new ImageProperties(); if (ImageFormat.Undefined.Equals(format)) { format = GetImageFormatFromPictureHeader(imageData); } if (format.Equals(ImageFormat.Unsupported)) { return(props); } props.NumColorsInPalette = 0; props.Format = format; using (MemoryStream s = new MemoryStream(imageData)) using (BinaryReader r = new BinaryReader(s)) { long limit = (long)Math.Round(s.Length * 0.25); // TODO - test and adjust limit switch (format) { case (ImageFormat.Tiff): bool isBigEndian = (0x4D == r.ReadByte()); s.Seek(3, SeekOrigin.Current); // Skip the rest of the signature long IFDOffset = readInt32(r, isBigEndian); s.Seek(IFDOffset, SeekOrigin.Begin); int nbIFDEntries = readInt16(r, isBigEndian); long initialPos = s.Position; int IFDtag, IFDFieldType, IFDNbValues, IFDValue32, IFDValue16; byte[] IFDValueBinary; int photometricInterpretation = 0; int bitsPerSample = 0; int samplesPerPixel = 0; for (int i = 0; i < nbIFDEntries; i++) { IFDtag = readInt16(r, isBigEndian); IFDFieldType = readInt16(r, isBigEndian); IFDNbValues = readInt32(r, isBigEndian); IFDValueBinary = r.ReadBytes(4); IFDValue32 = isBigEndian? StreamUtils.DecodeBEInt32(IFDValueBinary) : StreamUtils.DecodeInt32(IFDValueBinary); IFDValue16 = isBigEndian ? StreamUtils.DecodeBEInt16(IFDValueBinary) : StreamUtils.DecodeInt16(IFDValueBinary); switch (IFDtag) { // Common properties case (0x0100): props.Width = IFDValue32; // Specs say "SHORT or LONG" but the implementation actually takes up 4 bytes anyway -> we'll assume it's a SHORT if the last two bytes are null if (0 == IFDValueBinary[2] + IFDValueBinary[3]) { props.Width = IFDValue16; } break; case (0x0101): props.Height = IFDValue32; if (0 == IFDValueBinary[2] + IFDValueBinary[3]) { props.Height = IFDValue16; } break; // Specific properties case (0x0106): // PhotometricInterpretation photometricInterpretation = IFDValue32; if (IFDValue32 < 2) { props.ColorDepth = 1; // Bilevel or greyscale image } else if (2 == IFDValue32) { props.ColorDepth = 24; // RGB full color image } // NB : A value of 3 would indicate a palette-color image, but has no effect here break; case (0x0102): // BitsPerSample bitsPerSample = IFDValue16; break; case (0x0115): // SamplesPerPixel samplesPerPixel = IFDValue16; break; } } if (photometricInterpretation < 2) // Bilevel or greyscale { props.ColorDepth = bitsPerSample; } else if (2 == photometricInterpretation) // RGB { props.ColorDepth = 8 * samplesPerPixel; } else if (3 == photometricInterpretation) // Palette { props.ColorDepth = 8 * samplesPerPixel; props.NumColorsInPalette = bitsPerSample; } break; case (ImageFormat.Gif): byte[] GraphicControlExtensionBlockSignature = new byte[2] { 0x21, 0xf9 }; props.ColorDepth = 24; // 1 byte for each component s.Seek(3, SeekOrigin.Current); // Skip GIF signature string version = Utils.Latin1Encoding.GetString(r.ReadBytes(3)); s.Seek(4, SeekOrigin.Current); // Skip logical screen descriptors byte globalPaletteUse = r.ReadByte(); if (((globalPaletteUse & 0x80) >> 7) > 0) // File uses a global color palette { props.NumColorsInPalette = 2 << (globalPaletteUse & 0x07); } /* * v89a means that the first image block should follow the first graphic control extension block * (which may in turn be located after an application extension block if the GIF is animated) * * => The simplest way to get to the 1st image block is to look for the 1st * graphic control extension block, and to skip it */ if ("89a".Equals(version)) { initialPos = s.Position; if (StreamUtils.FindSequence(s, GraphicControlExtensionBlockSignature)) { s.Seek(6, SeekOrigin.Current); } else { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid v89a GIF file; no graphic control extension block found"); // GIF is malformed; trying to find the image block directly s.Seek(initialPos, SeekOrigin.Begin); if (StreamUtils.FindSequence(s, new byte[1] { 0x2c })) { s.Seek(-1, SeekOrigin.Current); } } } // At this point, we should be at the very beginning of the first image block if (0x2c == r.ReadByte()) { s.Seek(4, SeekOrigin.Current); // Skip image position descriptors props.Width = r.ReadInt16(); props.Height = r.ReadInt16(); // No global palette is set => try and find information in the local palette of the 1st image block if (0 == props.NumColorsInPalette) { props.NumColorsInPalette = (int)Math.Pow(2, ((globalPaletteUse & 0x0F) << 4) + 1); } } else { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Error parsing GIF file; image block not found"); } break; case (ImageFormat.Bmp): // Skip useless information s.Seek(18, SeekOrigin.Begin); props.Width = r.ReadInt32(); props.Height = r.ReadInt32(); s.Seek(2, SeekOrigin.Current); // Planes props.ColorDepth = r.ReadInt16(); // No support for BMP color palettes, as they seem to be exotic (and ATL has no use of this information) break; case (ImageFormat.Png): byte[] intData = new byte[4]; byte[] IHDRChunkSignature = Utils.Latin1Encoding.GetBytes("IHDR"); byte[] PaletteChunkSignature = Utils.Latin1Encoding.GetBytes("PLTE"); // Skip header s.Seek(8, SeekOrigin.Begin); // Scroll chunks until we find IHDR (that should be the first one to appear, but who knows...) if (0 == findPngChunk(s, IHDRChunkSignature, limit)) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid PNG file; no IHDR chunk found"); } else { // Read IHDR chunk s.Read(intData, 0, 4); props.Width = StreamUtils.DecodeBEInt32(intData); s.Read(intData, 0, 4); props.Height = StreamUtils.DecodeBEInt32(intData); props.ColorDepth = r.ReadByte(); int colorType = r.ReadByte(); if (3 == colorType) // PNG file uses a palette { s.Seek(7, SeekOrigin.Current); // 3 last useful data + ending chunk CRC uint paletteChunkSize = findPngChunk(s, PaletteChunkSignature, limit); if (0 == paletteChunkSize) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid PNG file; palette declared, but no PLTE chunk found"); } else { props.NumColorsInPalette = (int)Math.Floor(paletteChunkSize / 3.0); } } else { props.NumColorsInPalette = 0; } } break; case (ImageFormat.Jpeg): byte[] shortData = new byte[2]; byte[] SOF0FrameSignature = new byte[2] { 0xFF, 0xC0 }; /* * We just need to reach the SOF0 frame descripting the actual picture * * In order to handle JPEG files that contain multiple SOF0 frames (see test suite), * the simplest way of proceeding is to look for all SOF0 frames in the first 25% of the file, * and then read the very last one */ long lastPos = 0; while (StreamUtils.FindSequence(s, SOF0FrameSignature, limit)) { lastPos = s.Position; } if (0 == lastPos) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid JPEG file; no SOF0 frame found"); } else { // Skip frame length s.Seek(2, SeekOrigin.Current); bitsPerSample = r.ReadByte(); s.Read(shortData, 0, 2); props.Height = StreamUtils.DecodeBEUInt16(shortData); s.Read(shortData, 0, 2); props.Width = StreamUtils.DecodeBEUInt16(shortData); byte nbComponents = r.ReadByte(); props.ColorDepth = bitsPerSample * nbComponents; } break; } } return(props); }
public void StreamUtils_Exceptions() { Assert.IsFalse(StreamUtils.ArrEqualsArr(new byte[1], new byte[2])); Assert.IsFalse(StreamUtils.StringEqualsArr(".", new char[2])); try { StreamUtils.DecodeBEUInt16(new byte[1]); Assert.Fail(); } catch { } try { StreamUtils.DecodeUInt16(new byte[1]); Assert.Fail(); } catch { } try { StreamUtils.DecodeInt16(new byte[1]); Assert.Fail(); } catch { } try { StreamUtils.DecodeBEInt16(new byte[1]); Assert.Fail(); } catch { } try { StreamUtils.DecodeBEInt24(new byte[2]); Assert.Fail(); } catch { } try { StreamUtils.DecodeBEUInt24(new byte[2]); Assert.Fail(); } catch { } try { StreamUtils.EncodeBEUInt24(0x01FFFFFF); Assert.Fail(); } catch { } try { StreamUtils.DecodeBEUInt32(new byte[3]); Assert.Fail(); } catch { } try { StreamUtils.DecodeUInt32(new byte[3]); Assert.Fail(); } catch { } try { StreamUtils.DecodeBEInt32(new byte[3]); Assert.Fail(); } catch { } try { StreamUtils.DecodeInt32(new byte[3]); Assert.Fail(); } catch { } try { StreamUtils.DecodeUInt64(new byte[7]); Assert.Fail(); } catch { } try { StreamUtils.DecodeBEInt64(new byte[7]); Assert.Fail(); } catch { } try { StreamUtils.DecodeSynchSafeInt(new byte[6]); Assert.Fail(); } catch { } try { StreamUtils.DecodeSynchSafeInt32(new byte[6]); Assert.Fail(); } catch { } try { StreamUtils.EncodeSynchSafeInt(1, 0); Assert.Fail(); } catch { } try { StreamUtils.EncodeSynchSafeInt(1, 6); Assert.Fail(); } catch { } try { StreamUtils.ReadBits(new BinaryReader(new MemoryStream()), 0, 0); Assert.Fail(); } catch { } try { StreamUtils.ReadBits(new BinaryReader(new MemoryStream()), 0, 33); Assert.Fail(); } catch { } }