/// <summary> /// Reads a COM segment to find the JPEG comment. /// </summary> /// <param name="length"> /// The length of the segment that will be read. /// </param> private void ReadCOMSegment(int length) { if ((ImageTag.TagTypes & TagLib.TagTypes.JpegComment) != 0x00) { return; } long position = Tell; JpegCommentTag com_tag; if (length == 0) { com_tag = new JpegCommentTag(); } else { ByteVector data = ReadBlock(length); int terminator = data.Find("\0", 0); if (terminator < 0) { com_tag = new JpegCommentTag(data.ToString()); } else { com_tag = new JpegCommentTag(data.Mid(0, terminator).ToString()); } } ImageTag.AddTag(com_tag); AddMetadataBlock(position - 4, length + 4); }
private void ReadAPP13Segment(ushort length) { var data = ReadBlock(length); var iptc_iim_length = IPTC_IIM_IDENTIFIER.Length; if (length < iptc_iim_length || data.Mid(0, iptc_iim_length) != IPTC_IIM_IDENTIFIER) { return; } var headerInfoLen = data.Mid(iptc_iim_length, 1).ToUShort(); int lenToSkip; if (headerInfoLen > 0) { lenToSkip = 1 + headerInfoLen + 4; } else { lenToSkip = 6; } data.RemoveRange(0, iptc_iim_length + lenToSkip); var reader = new IIM.IIMReader(data); var tag = reader.Process(); if (tag != null) { ImageTag.AddTag(tag); } }
/// <summary> /// Reads a Comment Block at the current position. The current position must /// point to the 3rd byte of the comment block. (The other 2 bytes are usually /// read before to identify the comment block) /// </summary> private void ReadCommentBlock() { long position = Tell; // Comment Extension // // 1 Byte Extension Introducer (0x21) // 1 Byte Comment Label (0xFE) // N Bytes Comment Data (Sub Blocks) // 1 Byte Block Terminator (0x00) // // Note, the first 2 bytes are still read to identify the Comment Block. // Therefore, we only need to read the sub blocks and extract the data. string comment = ReadSubBlocks(); // Only add the tag, if no one is still contained. if ((TagTypes & TagTypes.GifComment) == 0x00) { ImageTag.AddTag(new GifCommentTag(comment)); // 2 bytes where read before AddMetadataBlock(position - 2, Tell - position + 2); } }
private void ReadApplicationExtensionBlock() { long position = Tell; ByteVector data = ReadBlock(12); if (data.Count != 12) { throw new CorruptFileException(""); } if (data.Mid(1, 8) == XMP_IDENTIFIER && data.Mid(9, 3) == XMP_AUTH_CODE) { long data_start = Tell; long xmp_trailer_start = Find(new byte[] { 0x00 }, data_start) - XMP_MAGIC_TRAILER.Length + 2; Seek(data_start, SeekOrigin.Begin); if (xmp_trailer_start <= data_start) { throw new CorruptFileException("No End of XMP data found"); } int data_length = (int)(xmp_trailer_start - data_start); ByteVector xmp_data = ReadBlock(data_length); ImageTag.AddTag(new XmpTag(xmp_data.ToString(StringType.UTF8), this)); AddMetadataBlock(position - 2, 14 + data_length + XMP_MAGIC_TRAILER.Length); Seek(xmp_trailer_start + XMP_MAGIC_TRAILER.Length, SeekOrigin.Begin); } else { SkipSubBlocks(); } }
/// <summary> /// Reads an zTXt Chunk from file. The current position must be set /// to the start of the Chunk Data. Such a Chunk contains compressed /// keywords. /// </summary> /// <param name="data_length"> /// A <see cref="System.Int32"/> with the length of the Chunk Data. /// </param> /// <remarks> /// The Chunk may also contain compressed Exif data which is written /// by other tools. But, since the PNG specification does not support /// Exif data, we ignore it here. /// </remarks> private void ReadzTXtChunk(int data_length) { long position = Tell; // zTXt Chunk // // N Bytes Keyword // 1 Byte Null Separator // 1 Byte Compression Method // N Bytes Txt // // Followed by 4 Bytes CRC data ByteVector data = ReadChunkData(data_length); CheckCRC(zTXt_CHUNK_TYPE, data, ReadCRC()); int terminator_index; string keyword = ReadKeyword(data, 0, out terminator_index); if (terminator_index + 1 >= data_length) { throw new CorruptFileException("Compression Method byte expected"); } byte compression_method = data [terminator_index + 1]; ByteVector plain_data = Decompress(compression_method, data.Mid(terminator_index + 2)); // ignore unknown compression methods if (plain_data == null) { return; } string value = plain_data.ToString(); RawProfile rawProfile = null; if (keyword.StartsWith("Raw profile type")) { rawProfile = ProcessRawProfile(value); value = rawProfile.ToString(); } // handle XMP, which has a fixed header if (keyword == "xmp" || rawProfile != null && string.Compare(rawProfile.Name, "xmp", StringComparison.InvariantCultureIgnoreCase) == 0) { ImageTag.AddTag(new XmpTag(string.Join("", rawProfile.Data.ToArray()), this)); } else { PngTag png_tag = GetTag(TagTypes.Png, true) as PngTag; if (png_tag.GetKeyword(keyword) == null) { png_tag.SetKeyword(keyword, value); } } AddMetadataBlock(position - 8, data_length + 8 + 4); }
/// <summary> /// Reads the file with a specified read style. /// </summary> /// <param name="propertiesStyle"> /// A <see cref="ReadStyle" /> value specifying at what level /// of accuracy to read the media properties, or <see /// cref="ReadStyle.None" /> to ignore the properties. /// </param> protected void Read(ReadStyle propertiesStyle) { Mode = AccessMode.Read; try { uint first_ifd_offset = ReadHeader(); ReadIFD(first_ifd_offset); // Find XMP data var xmp_entry = ImageTag.Exif.Structure.GetEntry(0, (ushort)IFDEntryTag.XMP) as ByteVectorIFDEntry; if (xmp_entry != null) { ImageTag.AddTag(new XmpTag(xmp_entry.Data.ToString(), this)); } if (propertiesStyle == ReadStyle.None) { return; } properties = ExtractProperties(); } finally { Mode = AccessMode.Closed; } }
private void ReadCommentBlock() { long position = Tell; string comment = ReadSubBlocks(); if ((TagTypes & TagTypes.GifComment) == 0x00) { ImageTag.AddTag(new GifCommentTag(comment)); AddMetadataBlock(position - 2, Tell - position + 2); } }
/// <summary> /// Reads an APP13 segment to find IPTC-IIM metadata. /// </summary> /// <param name="length"> /// The length of the segment that will be read. /// </param> /// <remarks>More info and specs for IPTC-IIM: /// - Guidelines for Handling Image Metadata (http://www.metadataworkinggroup.org/specs/) /// - IPTC Standard Photo Metadata (July 2010) (http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201007_1.pdf) /// - Extracting IPTC header information from JPEG images (http://www.codeproject.com/KB/graphics/iptc.aspx?fid=2301&df=90&mpp=25&noise=3&prof=False&sort=Position&view=Quick&fr=51#xx0xx) /// - Reading IPTC APP14 Segment Header Information from JPEG Images (http://www.codeproject.com/KB/graphics/ReadingIPTCAPP14.aspx?q=iptc) /// </remarks> private void ReadAPP13Segment(ushort length) { // TODO: if both IPTC-IIM and XMP metadata is contained in a file, we should read // a IPTC-IIM checksum and compare that with the checksum built over the IIM block. // Depending on the result we should prefer the information from XMP or IIM. // Right now we always prefer XMP. var data = ReadBlock(length); // The APP13 segment consists of: // - the string "Photoshop 3.0\u0000" // - followed by "8BIM" // - and then the section type "\u0004\u0004". // There might be multiple 8BIM sections with different types, but we're following // YAGNI for now and only deal with the one we're interested in (and hope that it's // the first one). var iptc_iim_length = IPTC_IIM_IDENTIFIER.Length; if (length < iptc_iim_length || data.Mid(0, iptc_iim_length) != IPTC_IIM_IDENTIFIER) { return; } // PS6 introduced a new header with variable length text var headerInfoLen = data.Mid(iptc_iim_length, 1).ToUShort(); int lenToSkip; if (headerInfoLen > 0) { // PS6 header: 1 byte headerinfolen + headerinfo + 2 bytes 00 padding (?) + 2 bytes length lenToSkip = 1 + headerInfoLen + 4; } else { //old style: 4 bytes 00 padding (?) + 2 bytes length lenToSkip = 6; } data.RemoveRange(0, iptc_iim_length + lenToSkip); var reader = new IIM.IIMReader(data); var tag = reader.Process(); if (tag != null) { ImageTag.AddTag(tag); } }
private void ReadAPP1Segment(ushort length) { long position = Tell; ByteVector data = null; int exif_header_length = 14; if ((ImageTag.TagTypes & TagLib.TagTypes.TiffIFD) == 0x00 && length >= exif_header_length) { data = ReadBlock(exif_header_length); if (data.Count == exif_header_length && data.Mid(0, 6).ToString().Equals(EXIF_IDENTIFIER)) { bool is_bigendian = data.Mid(6, 2).ToString().Equals("MM"); ushort magic = data.Mid(8, 2).ToUShort(is_bigendian); if (magic != 42) { throw new Exception(String.Format("Invalid TIFF magic: {0}", magic)); } uint ifd_offset = data.Mid(10, 4).ToUInt(is_bigendian); var exif = new IFDTag(); var reader = new IFDReader(this, is_bigendian, exif.Structure, position + 6, ifd_offset, (uint)(length - 6)); reader.Read(); ImageTag.AddTag(exif); AddMetadataBlock(position - 4, length + 4); return; } } int xmp_header_length = XmpTag.XAP_NS.Length + 1; if ((ImageTag.TagTypes & TagLib.TagTypes.XMP) == 0x00 && length >= xmp_header_length) { if (data == null) { data = ReadBlock(xmp_header_length); } else { data.Add(ReadBlock(xmp_header_length - exif_header_length)); } if (data.ToString().Equals(XmpTag.XAP_NS + "\0")) { ByteVector xmp_data = ReadBlock(length - xmp_header_length); ImageTag.AddTag(new XmpTag(xmp_data.ToString(), this)); AddMetadataBlock(position - 4, length + 4); } } }
/// <summary> /// Gets a tag of a specified type from the current instance, /// optionally creating a new tag if possible. /// </summary> /// <param name="type"> /// A <see cref="TagLib.TagTypes" /> value indicating the /// type of tag to read. /// </param> /// <param name="create"> /// A <see cref="bool" /> value specifying whether or not to /// try and create the tag if one is not found. /// </param> /// <returns> /// A <see cref="Tag" /> object containing the tag that was /// found in or added to the current instance. If no /// matching tag was found and none was created, <see /// langword="null" /> is returned. /// </returns> public override Tag GetTag (TagTypes type, bool create) { Tag tag = base.GetTag (type, false); if (tag != null) { return tag; } if (!create || (type & ImageTag.AllowedTypes) == 0) return null; if (type != TagTypes.TiffIFD) return base.GetTag (type, create); ImageTag new_tag = new IFDTag (this); ImageTag.AddTag (new_tag); return new_tag; }
/// <summary> /// Reads an APP13 segment to find IPTC-IIM metadata. /// </summary> /// <param name="length"> /// The length of the segment that will be read. /// </param> /// <remarks>More info and specs for IPTC-IIM: /// - Guidelines for Handling Image Metadata (http://www.metadataworkinggroup.org/specs/) /// - IPTC Standard Photo Metadata (July 2010) (http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201007_1.pdf) /// - Extracting IPTC header information from JPEG images (http://www.codeproject.com/KB/graphics/iptc.aspx?fid=2301&df=90&mpp=25&noise=3&prof=False&sort=Position&view=Quick&fr=51#xx0xx) /// - Reading IPTC APP14 Segment Header Information from JPEG Images (http://www.codeproject.com/KB/graphics/ReadingIPTCAPP14.aspx?q=iptc) /// </remarks> void ReadAPP13Segment(ushort length) { var data = ReadBlock(length); // The APP13 segment consists of: // - the string "Photoshop 3.0\u0000" // - followed by "8BIM" // - and then the section type "\u0004\u0004". // There might be multiple 8BIM sections with different types, but we're following // YAGNI for now and only deal with the one we're interested in (and hope that it's // the first one). var iptc_iim_length = IPTC_IIM_IDENTIFIER.Length; if (length < iptc_iim_length || data.Mid(0, iptc_iim_length) != IPTC_IIM_IDENTIFIER) { return; } // PS6 introduced a new header with variable length text var headerInfoLen = data.Mid(iptc_iim_length, 1).ToUShort(); int lenToSkip; if (headerInfoLen > 0) { // PS6 header: 1 byte headerinfolen + headerinfo + 2 bytes 00 padding (?) + 2 bytes length lenToSkip = 1 + headerInfoLen + 4; } else { //old style: 4 bytes 00 padding (?) + 2 bytes length lenToSkip = 6; } data.RemoveRange(0, iptc_iim_length + lenToSkip); var reader = new IIM.IIMReader(data); var tag = reader.Process(); if (tag != null) { ImageTag.AddTag(tag); } }
private void ReadiTXtChunk(int data_length) { long position = Tell; ByteVector data = ReadChunkData(data_length); CheckCRC(iTXt_CHUNK_TYPE, data, ReadCRC()); if (data.StartsWith(XMP_CHUNK_HEADER)) { ImageTag.AddTag(new XmpTag(data.Mid(XMP_CHUNK_HEADER.Length).ToString(StringType.UTF8), this)); AddMetadataBlock(position - 8, data_length + 8 + 4); return; } int terminator_index; string keyword = ReadKeyword(data, 0, out terminator_index); if (terminator_index + 2 >= data_length) { throw new CorruptFileException("Compression Flag and Compression Method byte expected"); } byte compression_flag = data[terminator_index + 1]; byte compression_method = data[terminator_index + 2]; ByteVector txt_data = data.Mid(terminator_index + 1); if (compression_flag != 0x00) { txt_data = Decompress(compression_method, txt_data); if (txt_data == null) { return; } } string value = txt_data.ToString(); PngTag png_tag = GetTag(TagTypes.Png, true) as PngTag; if (png_tag.GetKeyword(keyword) == null) { png_tag.SetKeyword(keyword, value); } AddMetadataBlock(position - 8, data_length + 8 + 4); }
/// <summary> /// Reads an iTXt Chunk from file. The current position must be set /// to the start of the Chunk Data. Such a Chunk may contain XMP data /// or translated keywords. /// </summary> /// <param name="data_length"> /// A <see cref="System.Int32"/> with the length of the Chunk Data. /// </param> void ReadiTXtChunk(int data_length) { long position = Tell; // iTXt Chunk // // N Bytes Keyword // 1 Byte Null Separator // 1 Byte Compression Flag (0 for uncompressed data) // 1 Byte Compression Method // N Bytes Language Tag // 1 Byte Null Separator // N Bytes Translated Keyword // 1 Byte Null Terminator // N Bytes Txt // // Followed by 4 Bytes CRC data ByteVector data = ReadChunkData(data_length); CheckCRC(iTXt_CHUNK_TYPE, data, ReadCRC()); // handle XMP, which has a fixed header if (data.StartsWith(XMP_CHUNK_HEADER)) { ImageTag.AddTag(new XmpTag(data.Mid(XMP_CHUNK_HEADER.Length).ToString(StringType.UTF8), this)); AddMetadataBlock(position - 8, data_length + 8 + 4); return; } string keyword = ReadKeyword(data, 0, out var terminator_index); if (terminator_index + 2 >= data_length) { throw new CorruptFileException("Compression Flag and Compression Method byte expected"); } byte compression_flag = data[terminator_index + 1]; byte compression_method = data[terminator_index + 2]; //string language = ReadTerminatedString (data, terminator_index + 3, out terminator_index); //string translated_keyword = ReadTerminatedString (data, terminator_index + 1, out terminator_index); ByteVector txt_data = data.Mid(terminator_index + 1); if (compression_flag != 0x00) { txt_data = Decompress(compression_method, txt_data); // ignore unknown compression methods if (txt_data == null) { return; } } string value = txt_data.ToString(); var png_tag = GetTag(TagTypes.Png, true) as PngTag; if (png_tag.GetKeyword(keyword) == null) { png_tag.SetKeyword(keyword, value); } AddMetadataBlock(position - 8, data_length + 8 + 4); }
/// <summary> /// Reads an APP1 segment to find EXIF or XMP metadata. /// </summary> /// <param name="length"> /// The length of the segment that will be read. /// </param> private void ReadAPP1Segment(ushort length) { long position = Tell; ByteVector data = null; // for an Exif segment, the data block consists of 14 bytes of: // * 6 bytes Exif identifier string // * 2 bytes bigendian indication MM (or II) // * 2 bytes Tiff magic number (42) // * 4 bytes offset of the first IFD in this segment // // the last two points are alreay encoded according to // big- or littleendian int exif_header_length = 14; // could be an Exif segment if ((ImageTag.TagTypes & TagLib.TagTypes.TiffIFD) == 0x00 && length >= exif_header_length) { data = ReadBlock(exif_header_length); if (data.Count == exif_header_length && data.Mid(0, 6).ToString().Equals(EXIF_IDENTIFIER)) { bool is_bigendian = data.Mid(6, 2).ToString().Equals("MM"); ushort magic = data.Mid(8, 2).ToUShort(is_bigendian); if (magic != 42) { throw new Exception(String.Format("Invalid TIFF magic: {0}", magic)); } uint ifd_offset = data.Mid(10, 4).ToUInt(is_bigendian); var exif = new IFDTag(); var reader = new IFDReader(this, is_bigendian, exif.Structure, position + 6, ifd_offset, (uint)(length - 6)); reader.Read(); ImageTag.AddTag(exif); AddMetadataBlock(position - 4, length + 4); return; } } int xmp_header_length = XmpTag.XAP_NS.Length + 1; // could be an Xmp segment if ((ImageTag.TagTypes & TagLib.TagTypes.XMP) == 0x00 && length >= xmp_header_length) { // if already data is read for determining the Exif segment, // just read the remaining bytes. // NOTE: that (exif_header_length < xmp_header_length) holds if (data == null) { data = ReadBlock(xmp_header_length); } else { data.Add(ReadBlock(xmp_header_length - exif_header_length)); } if (data.ToString().Equals(XmpTag.XAP_NS + "\0")) { ByteVector xmp_data = ReadBlock(length - xmp_header_length); ImageTag.AddTag(new XmpTag(xmp_data.ToString(), this)); AddMetadataBlock(position - 4, length + 4); } } }
/// <summary> /// Reads an Application Extension Block at the current position. The current /// position must point to the 3rd byte of the comment block. (The other 2 bytes /// are usually read before to identify the comment block) /// </summary> private void ReadApplicationExtensionBlock() { // Application Extension Block // // 1 Byte Extension Introducer (0x21) // 1 Byte Application Extension Label (0xFF) // 1 Byte Block Size (0x0B - 11) // 8 Bytes Application Identifier // 3 Bytes Application Auth. Code // N Bytes Application Data (sub blocks) // 1 Byte Block Terminator (0x00) // // Note, the first 2 bytes are still read to identify the Comment Block. // Therefore, we only need to read the sub blocks and extract the data. long position = Tell; ByteVector data = ReadBlock(12); if (data.Count != 12) { throw new CorruptFileException(""); } // Contains XMP data if (data.Mid(1, 8) == XMP_IDENTIFIER && data.Mid(9, 3) == XMP_AUTH_CODE) { // XMP Data is not organized in sub-blocks // start of xmp data long data_start = Tell; // start of trailer start // FIXME: Since File.Find is still buggy, the following call does not work to find the end of the // XMP data. Therfore, we use here a different way for now. //long xmp_trailer_start = Find (new ByteVector (0x00), data_start); // Since searching just one byte is save, we search for the end of the xmp trailer which // consists of two 0x00 bytes and compute the expected start. long xmp_trailer_start = Find(new byte [] { 0x00 }, data_start) - XMP_MAGIC_TRAILER.Length + 2; Seek(data_start, SeekOrigin.Begin); if (xmp_trailer_start <= data_start) { throw new CorruptFileException("No End of XMP data found"); } // length of xmp data int data_length = (int)(xmp_trailer_start - data_start); ByteVector xmp_data = ReadBlock(data_length); ImageTag.AddTag(new XmpTag(xmp_data.ToString(StringType.UTF8), this)); // 2 bytes where read before AddMetadataBlock(position - 2, 14 + data_length + XMP_MAGIC_TRAILER.Length); // set position behind the XMP block Seek(xmp_trailer_start + XMP_MAGIC_TRAILER.Length, SeekOrigin.Begin); } else { SkipSubBlocks(); } }