public static string GetExtendedXmpGuid(XmpPacket xmpPacket) { if (xmpPacket is null) { ExceptionUtil.ThrowArgumentNullException(nameof(xmpPacket)); } string extendedXmpGuid = null; XElement rdfElement = xmpPacket.Document.Descendants(RdfContainerElementName).First(); XObject hasExtendedXmpObject = FindHasExtendedXmpObject(rdfElement); if (hasExtendedXmpObject != null) { if (hasExtendedXmpObject is XAttribute attribute) { extendedXmpGuid = attribute.Value; } else if (hasExtendedXmpObject is XElement element) { extendedXmpGuid = element.Value; } } return(extendedXmpGuid); }
public JpegXmpPackets GetXmpPackets() { if (!fileParsed) { ParseFile(); fileParsed = true; } if (mainXmpPacket == null) { // The file does not contain any XMP data. return(null); } if (mergedXmpPacket == null) { if (extendedXmpPacket != null) { mergedXmpPacket = XmpUtil.MergeExtendedXmp(mainXmpPacket, extendedXmpPacket); } else { mergedXmpPacket = mainXmpPacket.Clone(); } } return(new JpegXmpPackets(mainXmpPacket, extendedXmpPacket, mergedXmpPacket)); }
private void AssembleExtendedXmpChunks() { if (extendedXmpChunks.TryGetValue(extendedXmpGuid, out ExtendedXmp value)) { if (value.TotalLength > int.MaxValue) { ExceptionUtil.ThrowInvalidOperationException("The extended XMP packet is larger than 2GB."); } using (MemoryStream memoryStream = new MemoryStream((int)value.TotalLength)) { IReadOnlyList <ExtendedXmpContent> chunks = value.GetChunks(); uint dstOffset = 0; foreach (ExtendedXmpContent item in chunks.OrderBy(i => i.Offset)) { if (item.Length != value.TotalLength) { ExceptionUtil.ThrowInvalidOperationException("The extended XMP chunk length does not equal the total packet length."); } if (item.Offset == dstOffset) { byte[] data = item.GetDataReadOnly(); memoryStream.Write(data, 0, data.Length); dstOffset += (uint)data.Length; } else { ExceptionUtil.ThrowInvalidOperationException($"Unexpected offset: { item.Offset }, expected { dstOffset }"); } } memoryStream.Position = 0; extendedXmpPacket = new XmpPacket(memoryStream); } } }
public static XmpPacket MergeExtendedXmp(XmpPacket mainXmp, XmpPacket extendedXmp) { if (mainXmp is null) { ExceptionUtil.ThrowArgumentNullException(nameof(mainXmp)); } if (extendedXmp is null) { ExceptionUtil.ThrowArgumentNullException(nameof(extendedXmp)); } XDocument mergedDocument = new XDocument(mainXmp.Document); XElement mergedRdfElement = mergedDocument.Descendants(RdfContainerElementName).First(); // Remove the xmpNote:HasExtendedXMP element. XObject hasExtendedXmpObject = FindHasExtendedXmpObject(mergedRdfElement); if (hasExtendedXmpObject != null) { if (ShouldRemoveExtendedXmpParent(hasExtendedXmpObject)) { hasExtendedXmpObject.Parent.Remove(); } else { MaybeRemoveXmpNoteNamespace(hasExtendedXmpObject.Parent); if (hasExtendedXmpObject is XAttribute attribute) { attribute.Remove(); } else if (hasExtendedXmpObject is XElement element) { element.Remove(); } } } // Add all of the rdf:Description elements in the extended XMP packet to the merged packet. // // Section 7.4 of the XMP Specification Part 1 allows a single rdf:RDF element to contain multiple // rdf:Description elements, with the XMP properties arbitrarily split between them. // // The XMP Specification Part 1 can be found at: // https://wwwimages2.adobe.com/content/dam/acom/en/devnet/xmp/pdfs/XMP%20SDK%20Release%20cc-2016-08/XMPSpecificationPart1.pdf // // The relevant portion of section 7.4 is quoted below: // // A single XMP packet shall be serialized using a single rdf:RDF XML element. // The rdf:RDF element content shall consist of only zero or more rdf:Description elements. // // The element content of top-level rdf:Description elements shall consist of zero or more XML elements for XMP properties. // XMP properties may be arbitrarily apportioned among the rdf:Description elements. XElement extendedXmpRdfElement = extendedXmp.Document.Descendants(RdfContainerElementName).First(); foreach (XElement item in extendedXmpRdfElement.Elements(RdfDescriptionElementName)) { mergedRdfElement.Add(item); } return(new XmpPacket(mergedDocument)); }
private void GatherXmpDataFromFile(EndianBinaryReader reader) { ushort marker = reader.ReadUInt16(); // Check the file signature. if (marker == JpegMarkers.StartOfImage) { while (reader.Position < reader.Length) { marker = reader.ReadUInt16(); if (marker == 0xFFFF) { // Skip the first padding byte and read the marker again. reader.Position++; continue; } if (marker == JpegMarkers.StartOfScan || marker == JpegMarkers.EndOfImage) { // The application data segments always come before these markers. break; } ushort segmentLength = reader.ReadUInt16(); if (segmentLength < 2) { ExceptionUtil.ThrowFormatException($"JPEG segment length must be in the range of [2, 65535], actual value: { segmentLength }"); } // The segment length field includes its own length in the total. segmentLength -= sizeof(ushort); if (marker == JpegMarkers.App1) { if (mainXmpPacket == null && segmentLength >= MainXmpSignatureLength) { string sig = reader.ReadAsciiString(MainXmpSignatureLength); if (sig.Equals(MainXmpSignature, StringComparison.Ordinal)) { byte[] xmpPacketBytes = reader.ReadBytes(segmentLength - MainXmpSignatureLength); mainXmpPacket = new XmpPacket(xmpPacketBytes); extendedXmpGuid = XmpUtil.GetExtendedXmpGuid(mainXmpPacket); continue; } else { reader.Position -= MainXmpSignatureLength; } } if (segmentLength >= ExtendedXmpPrefixLength) { string sig = reader.ReadAsciiString(ExtendedXmpSignatureLength); if (sig.Equals(ExtendedXmpSignature, StringComparison.Ordinal)) { string guid = reader.ReadAsciiString(ExtendedXmpGUIDLength); uint length = reader.ReadUInt32(); uint offset = reader.ReadUInt32(); byte[] data = reader.ReadBytes(segmentLength - ExtendedXmpPrefixLength); if (extendedXmpChunks.TryGetValue(guid, out ExtendedXmp value)) { value.AddChunk(data, offset, length); } else { extendedXmpChunks.Add(guid, new ExtendedXmp(data, offset, length)); } continue; } else { segmentLength -= ExtendedXmpSignatureLength; } } } reader.Position += segmentLength; } } }
private XmpPacket(XmpPacket cloneMe) { Document = new XDocument(cloneMe.Document); LengthInBytes = cloneMe.LengthInBytes; }