From a8231856f645fefbcbaab4ee387f30523ff401b3 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 13:58:11 -0500 Subject: [PATCH 01/30] Update readme to include credits to the original owner of the PR --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a8d9df..2be5933 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # JpegXmpWritePluginMDE -This repository is a copy of the pull request #65 in metadata-extractor-dotnet and aims to compile it separately. The intention is to expose the writing functionality for XMP in jpeg case. +This repository is a copy of the pull request [#65](https://github.com/drewnoakes/metadata-extractor-dotnet/pull/65) in metadata-extractor-dotnet and aims to compile it separately in a standalone library as a plugin to MDE. The intention is to expose the writing functionality for XMP in jpeg case. + +@michaelosthege is the original author of the PR and he permitted me to use his PR to create the plugin: +[#23]https://github.com/drewnoakes/metadata-extractor-dotnet/issues/23 From bcc31ce88be2aefc8e8160d333c089934d9e9df7 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 13:59:42 -0500 Subject: [PATCH 02/30] fix formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2be5933..c05c49c 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ This repository is a copy of the pull request [#65](https://github.com/drewnoakes/metadata-extractor-dotnet/pull/65) in metadata-extractor-dotnet and aims to compile it separately in a standalone library as a plugin to MDE. The intention is to expose the writing functionality for XMP in jpeg case. @michaelosthege is the original author of the PR and he permitted me to use his PR to create the plugin: -[#23]https://github.com/drewnoakes/metadata-extractor-dotnet/issues/23 +[#23](https://github.com/drewnoakes/metadata-extractor-dotnet/issues/23) From 4477922599b506ad4d68d91f047ae34f5b95361e Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:14:57 -0500 Subject: [PATCH 03/30] updated gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3a6880 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs +bin +obj From cf9e86edd96c4d0c300a9f3bf25b7de83e5debf1 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:17:02 -0500 Subject: [PATCH 04/30] create a separate project for the plugin --- JpegXmpWritePluginMDE.sln | 25 +++++++++++++++++++ .../JpegXmpWritePluginMDE.csproj | 16 ++++++++++++ JpegXmpWritePluginMDE/Program.cs | 12 +++++++++ 3 files changed, 53 insertions(+) create mode 100644 JpegXmpWritePluginMDE.sln create mode 100644 JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj create mode 100644 JpegXmpWritePluginMDE/Program.cs diff --git a/JpegXmpWritePluginMDE.sln b/JpegXmpWritePluginMDE.sln new file mode 100644 index 0000000..55989f2 --- /dev/null +++ b/JpegXmpWritePluginMDE.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34511.84 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JpegXmpWritePluginMDE", "JpegXmpWritePluginMDE\JpegXmpWritePluginMDE.csproj", "{7AF4229E-F42F-48FC-BCB1-3182516BE31B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EC0C0DF8-FD13-4DCB-816E-A51F77948338} + EndGlobalSection +EndGlobal diff --git a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj new file mode 100644 index 0000000..7729eb2 --- /dev/null +++ b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/JpegXmpWritePluginMDE/Program.cs b/JpegXmpWritePluginMDE/Program.cs new file mode 100644 index 0000000..005da67 --- /dev/null +++ b/JpegXmpWritePluginMDE/Program.cs @@ -0,0 +1,12 @@ +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.Formats.Xmp; +using System.Xml.Linq; +using Xunit; + +static class Program +{ + static void Main() + { + } + +} \ No newline at end of file From 28ab42b56c231be210c2558ead7e366c2e03d938 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:23:18 -0500 Subject: [PATCH 05/30] Added contents of PR #65 --- .../Jpeg/IJpegFragmentMetadataWriter.cs | 54 ++++++ .../Formats/Jpeg/JpegFragment.cs | 74 ++++++++ .../Formats/Jpeg/JpegFragmentWriter.cs | 172 ++++++++++++++++++ .../Formats/Jpeg/JpegMetadataWriter.cs | 120 ++++++++++++ .../Formats/Jpeg/JpegSegment.cs | 79 ++++++++ .../Formats/Xmp/XmpWriter.cs | 148 +++++++++++++++ .../MetadataExtractor/ImageMetadataWriter.cs | 110 +++++++++++ 7 files changed, 757 insertions(+) create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs create mode 100644 JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs new file mode 100644 index 0000000..1d43958 --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs @@ -0,0 +1,54 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +#if NET35 +using FragmentList = System.Collections.Generic.IList; +#else +using FragmentList = System.Collections.Generic.IReadOnlyList; +#endif + +namespace MetadataExtractor.Formats.Jpeg +{ + /// Defines the interface of an object that can update a list of JpegFragments with new metadata. + public interface IJpegFragmentMetadataWriter + { + /// The type of metadata that this writer can process. + [NotNull] + Type MetadataType { get; } + + /// Modifies the JpegFragment collection with the metadata update. + /// + /// A sequence of JPEG fragments to which the metadata should be written. These are in the order encountered in the original file. + /// + /// + /// A directory containing metadata that shall be written to the JpegFragments. + /// + [NotNull] + List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata); + } +} diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs new file mode 100644 index 0000000..d6986ea --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs @@ -0,0 +1,74 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MetadataExtractor.Formats.Jpeg +{ + /// + /// Contains a fragment of JPEG data which may or may not be a JpegSegment. + /// + /// + /// Michael Osthege + public sealed class JpegFragment + { + /// + /// Indicates if this fragment is a JpegSegment. + /// + public bool IsSegment { get { return Segment != null; } } + + /// + /// JpegSegment interpretation of this fragment. + /// + [CanBeNull] + public JpegSegment Segment { get; } + + /// + /// All bytes that make up the JpegFragment. (Includes potential JpegSegment marker + size) + /// + [NotNull] + public byte[] Bytes { get; } + + /// + /// Create a JpegFragment from the fragment bytes. + /// + /// All bytes that make up the fragment. + /// Optional JpegSegment interpretation of this fragment. + public JpegFragment([NotNull] byte[] bytes, [CanBeNull] JpegSegment segment = null) + { + Bytes = bytes; + Segment = segment; + } + + public override string ToString() + { + if (IsSegment) + return $"{Bytes.Length} bytes ({Segment.Type})"; + else + return $"{Bytes.Length} bytes"; + } + + /// + /// Infers the marker and segment size bytes that correspond to the JpegSegment. + /// + /// A JpegSegment + /// A JpegFragment that is the concatenation of JpegSegment marker, size bytes and payload. + public static JpegFragment FromJpegSegment(JpegSegment segment) + { + byte[] fragmentBytes = new byte[2 + 2 + segment.Bytes.Length]; + + // Segment marker + fragmentBytes[0] = 0xFF; + fragmentBytes[1] = (byte)segment.Type; + + // Segment size + byte[] sizeBytes = JpegSegment.EncodePayloadLength(segment.Bytes.Length); + sizeBytes.CopyTo(fragmentBytes, 2); + + // Segment payload + segment.Bytes.CopyTo(fragmentBytes, 4); + + return new JpegFragment(fragmentBytes, segment); + } + } +} diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs new file mode 100644 index 0000000..6a7a261 --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs @@ -0,0 +1,172 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using JetBrains.Annotations; +using MetadataExtractor.IO; + +namespace MetadataExtractor.Formats.Jpeg +{ + /// Provides methods for reading and writing JPEG data without affecting other content. + /// + /// JPEG files are composed of a sequence of consecutive JPEG segments, but the + /// does not read all bytes. To edit JPEG data without affecting content other than the edited + /// JpegSegment, reading + writing must consider non-segment-coding bytes too. + /// + /// + /// Michael Osthege + public static class JpegFragmentWriter + { + /// + /// Reads all bytes from a stream of JPEG data and splits it into JpegFragments. + /// + /// + /// A from which the JPEG data will be read. + /// It must be positioned at the beginning of the JPEG data stream. + /// + /// A list of all fragments that, when concatenated, match the input stream. + [NotNull] + public static List SplitFragments([NotNull] SequentialReader reader) + { + if (!reader.IsMotorolaByteOrder) + throw new JpegProcessingException("Must be big-endian/Motorola byte order."); + + List fragments = new List(); + + MemoryStream buffer = new MemoryStream(); + + try + { + // The following loop walks over the bytes of the input + // It accumulates all bytes in a buffer. + // When a JpegSegment marker is identified, all (non-coding) bytes up to the marker + // are made into a JpegFragment and collected. + // The marker and its segment payload are also collected as a JpegFragment. + // When the end of the input is reached, all remaining (non-coding) bytes are also + // collected as one JpegFragment. + + while (true) + { + // Read bytes into the buffer + // Find the segment marker. Markers are zero or more 0xFF bytes, followed + // by a 0xFF and then a byte not equal to 0x00 or 0xFF. + var segmentIdentifier = reader.GetByte(); + buffer.WriteByte(segmentIdentifier); + var segmentTypeByte = reader.GetByte(); + buffer.WriteByte(segmentTypeByte); + + // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00 + while (segmentIdentifier != 0xFF || segmentTypeByte == 0xFF || segmentTypeByte == 0) + { + segmentIdentifier = segmentTypeByte; + segmentTypeByte = reader.GetByte(); + buffer.WriteByte(segmentTypeByte); + } + var segmentType = (JpegSegmentType)segmentTypeByte; + + // The algorithm above stopped right after a segment marker. + + // 1. All buffer up to the segment marker must become a fragment + if (buffer.Length > 2) + { + byte[] fragmentA = buffer.ToArray().Take((int)buffer.Length - 2).ToArray(); + fragments.Add(new JpegFragment(fragmentA)); + } + + // 2. the segment marker (+ size + payload) becomes a fragment + // only some JpegSegments have a payload and can/should be read in one shot + if (segmentType.ContainsPayload()) + { + // To read the segment payload, the segment length must be decoded: + var payloadLength = JpegSegment.DecodePayloadLength(reader.GetByte(), reader.GetByte()); + if (payloadLength < 0) + throw new JpegProcessingException("JPEG segment size would be less than zero."); + var segmentOffset = reader.Position; + var payloadBytes = reader.GetBytes(payloadLength); + Debug.Assert(payloadLength == payloadBytes.Length); + var segment = new JpegSegment(segmentType, payloadBytes, segmentOffset); + fragments.Add(JpegFragment.FromJpegSegment(segment)); + } + else + { + // for segments without a payload, take just the marker + fragments.Add(new JpegFragment(new byte[] { 0xFF, segmentTypeByte })); + } + + // All buffer contents have been copied into fragments + buffer = new MemoryStream(); + } + } + catch (IOException ex) + { + // we expect a IOException when the sequential reader reaches the end of the original data + if (ex.Message != "End of data reached.") + throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); + + // The remaining buffer must also be collected. + if (buffer.Length > 0) + { + fragments.Add(new JpegFragment(buffer.ToArray())); + } + } + catch (Exception ex) + { + throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); + } + + return fragments; + } + + /// + /// Concatenates the provided JpegFragments into a object. + /// + /// a list of JpegFragments that shall be joined. + [NotNull] + public static MemoryStream JoinFragments([NotNull] IEnumerable fragments) + { + MemoryStream output = new MemoryStream(); + + foreach (JpegFragment fragment in fragments) + { + output.Write(fragment.Bytes, 0, fragment.Bytes.Length); + } + + return output; + } + + /// + /// Validates that the JpegFragments make up valid JPEG data. + /// + /// Ordered list of JpegFragments + /// true if passed + public static bool IsValid([NotNull] IEnumerable fragments) + { + throw new NotImplementedException(); + } + } +} diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs new file mode 100644 index 0000000..280fff7 --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs @@ -0,0 +1,120 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using JetBrains.Annotations; +using MetadataExtractor.Formats.Adobe; +using MetadataExtractor.Formats.Exif; +using MetadataExtractor.Formats.Icc; +using MetadataExtractor.Formats.Iptc; +using MetadataExtractor.Formats.Jfif; +using MetadataExtractor.Formats.Jfxx; +using MetadataExtractor.Formats.Photoshop; +#if !PORTABLE +using MetadataExtractor.Formats.FileSystem; +#endif +using MetadataExtractor.Formats.Xmp; +using MetadataExtractor.IO; +using System.Xml.Linq; +using System.Text; +using System; +using System.Xml; +using System.Diagnostics; + +namespace MetadataExtractor.Formats.Jpeg +{ + /// Writes metadata to Jpeg formatted data. + /// Michael Osthege + public static class JpegMetadataWriter + { + /// + /// Look-up dictionary that maps metadata object types to compatible metadata writers. + /// + private static readonly Dictionary _allWriters = new Dictionary + { + { typeof(XDocument), new XmpWriter() } + }; + + /// + /// Processes a stream of Jpeg data into a new stream that is updated with the metadata. + /// + /// Stream of the original file. + /// Collection of metadata objects. + /// A new stream that contains Jpeg data, updated with the metadata. + public static MemoryStream WriteMetadata([NotNull] Stream original, [NotNull] IEnumerable metadata) + { + var ssr = new SequentialStreamReader(original, isMotorolaByteOrder:true); + + // 1. split up the original data into a collection of fragments (non-coding and coding) + List fragments = JpegFragmentWriter.SplitFragments(ssr); + + // for each metadata item, apply a compatible writer to the segments + // this updates the fragments with the metadata + fragments = UpdateJpegFragments(fragments, metadata); + + // now concatenate all fragments back into the complete file + return JpegFragmentWriter.JoinFragments(fragments); + + //if (JpegFragmentWriter.IsValid(fragments)) + //{ + // return JpegFragmentWriter.JoinFragments(fragments); + //} + //else + //{ + // throw new ImageProcessingException("Metadata encoding resulted in invalid Fragments."); + //} + } + + /// + /// Updates a list of JpegFragments with metadata, using the specified writers. + /// + /// The list of JpegFragments to start with. + /// Collection of metadata items. + /// A dictionary that maps metadata types to compatible JpegMetadataWriters. + /// The updated list of JpegFragments + public static List UpdateJpegFragments([NotNull] List fragments, [NotNull] IEnumerable metadata, [NotNull] Dictionary writers = null) + { + if (writers == null) + writers = _allWriters; + + foreach (var mdat in metadata) + { + var mdatType = mdat.GetType(); + if (writers.ContainsKey(mdatType)) + { + IJpegFragmentMetadataWriter writer = writers[mdatType]; + fragments = writer.UpdateFragments(fragments, mdat); + } + else + { + throw new NotImplementedException($"No JpegSegmentMetadataWriter is implemented for metadata of type {mdat.GetType()}."); + } + } + + return fragments; + } + } +} diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs new file mode 100644 index 0000000..52e7551 --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs @@ -0,0 +1,79 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using JetBrains.Annotations; +using System; + +namespace MetadataExtractor.Formats.Jpeg +{ + /// + /// Holds information about a JPEG segment. + /// + /// + /// Drew Noakes https://drewnoakes.com + public sealed class JpegSegment(JpegSegmentType type, byte[] bytes, long offset) + { + public JpegSegmentType Type { get; } + [NotNull] public byte[] Bytes { get; } + public long Offset { get; } + + public JpegSegment(JpegSegmentType type, [NotNull] byte[] bytes, long offset) + { + Type = type; + Bytes = bytes; + Offset = offset; + } + + /// + /// Computes the length of a segment payload from the high/low bytes of the index. + /// (Segment length excludes the index bytes.) + /// + /// first byte of the index + /// second byte of the index + /// + public static int DecodePayloadLength(byte highByte, byte lowByte) + { + // the segment length includes size bytes, so subtract two + if (BitConverter.IsLittleEndian) + return BitConverter.ToUInt16(new byte[] { lowByte, highByte }, 0) - 2; + else + return BitConverter.ToUInt16(new byte[] { highByte, lowByte }, 0) - 2; + } + + /// + /// Encodes (big endian) the length of a segment into the index bytes of the segment. + /// + /// Length of the payload (excludes the index) + /// segment-index bytes (length 2) + public static byte[] EncodePayloadLength(int payloadLength) + { + // the segment length includes the high & low bytes, so add 2 + byte[] bytes = BitConverter.GetBytes(payloadLength + 2); + if (BitConverter.IsLittleEndian) + return new byte[] { bytes[1], bytes[0] }; + else + return bytes; + } + } +} diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs new file mode 100644 index 0000000..af4bd61 --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -0,0 +1,148 @@ +using JetBrains.Annotations; +using MetadataExtractor.Formats.Jpeg; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +#if NET35 +using FragmentList = System.Collections.Generic.IList; +#else +using FragmentList = System.Collections.Generic.IReadOnlyList; +#endif + +namespace MetadataExtractor.Formats.Xmp +{ + public sealed class XmpWriter : IJpegFragmentMetadataWriter + { + /// + /// Specifies the type of metadata that this MetadataWriter can handle. + /// + Type IJpegFragmentMetadataWriter.MetadataType => typeof(XmpDirectory); + + /// + /// Updates a list of JpegFragments with new metadata. + /// + /// An existing App1 Xmp fragment will be updated. If none is found, a new segment will be + /// inserted before the first fragment that is not one of {Soi, App0, App1} + /// + /// + /// Original file fragmets + /// The Xmp metadata that shall be written + /// A new list of JpegFragments + public List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata) + { + JpegFragment metadataFragment; + List output = new List(); + bool wroteData = false; + int insertPosition = 0; + + if (metadata is XDocument) + { + byte[] payloadBytes = EncodeXmpToPayloadBytes((XDocument)metadata); + JpegSegment metadataSegment = new JpegSegment(JpegSegmentType.App1, payloadBytes, offset: 0); + metadataFragment = JpegFragment.FromJpegSegment(metadataSegment); + } + else + { + throw new ArgumentException($"XmpWriter expects metadata to be of type XmpDirectory, but was given {metadata.GetType()}."); + } + + // First look for any potential Xmp fragment, insert only if none is found + + // Walk over existing fragment and replace or insert the new metadata fragment + for (int i = 0; i < fragments.Count; i++) + { + JpegFragment currentFragment = fragments[i]; + + if (!wroteData && currentFragment.IsSegment) + { + JpegSegmentType currentType = currentFragment.Segment.Type; + + // if this is an existing App1 XMP fragment, overwrite it with the new fragment + if (currentType == JpegSegmentType.App1 && currentFragment.Segment.Bytes.Length > XmpReader.JpegSegmentPreamble.Length) + { + // This App1 segment could be a candidate for overwriting. + // Read the encountered segment payload to check if it contains the Xmp preamble + string potentialPreamble = Encoding.UTF8.GetString(currentFragment.Segment.Bytes, 0, XmpReader.JpegSegmentPreamble.Length); + if (potentialPreamble.Equals(XmpReader.JpegSegmentPreamble, StringComparison.OrdinalIgnoreCase)) + { + // The existing Xmp App1 fragment will be replaced with the new fragment + currentFragment = metadataFragment; + wroteData = true; + } + } + else if (insertPosition == 0 && currentType != JpegSegmentType.Soi && currentType != JpegSegmentType.App0) + { + // file begins with Soi (App0) (App1) ... + // At this point we have encountered a segment that should not be earlier than an App1. + // But there could be another Xmp segment, so we just make a note of this position + insertPosition = i; + } + } + output.Add(currentFragment); + } + + if (!wroteData) + { + // The files does not contain an App1-Xmp segment yet. + // Therefore we must insert a new App1-Xmp segment at the previously determined position. + output.Insert(insertPosition, metadataFragment); + wroteData = true; + } + + return output; + } + + /// + /// Encodes an XDocument to bytes to be used as the payload of an App1 segment. + /// + /// Xmp document to be encoded + /// Indicates if the Xmp packet shall be marked as writable. + /// App1 segment payload + public static byte[] EncodeXmpToPayloadBytes([NotNull] XDocument xmpDoc, bool writeable=true) + { + // constant parts + byte[] preamble = Encoding.UTF8.GetBytes(XmpReader.JpegSegmentPreamble); + byte[] xpacketHeader = Encoding.UTF8.GetBytes(""); + byte[] xpacketTrailer = Encoding.UTF8.GetBytes($""); + + MemoryStream xmpMS = new MemoryStream(); + // 1. preamble "http://ns.adobe.com/xap/1.0/\0" + xmpMS.Write(preamble, 0, preamble.Length); + + // 2. xpacket header + xmpMS.Write(xpacketHeader, 0, xpacketHeader.Length); + + // 3. serialized Xmp xml + XmlWriterSettings settings = new XmlWriterSettings() { OmitXmlDeclaration = true }; + using (XmlWriter xmlWriter = XmlWriter.Create(xmpMS, settings)) + { + xmpDoc.WriteTo(xmlWriter); + } + + // 4. whitespace padding + byte[] whitespace = Encoding.UTF8.GetBytes(CreateWhitespace()); + xmpMS.Write(whitespace, 0, whitespace.Length); + + // 5. xpacket trailer + xmpMS.Write(xpacketTrailer, 0, xpacketTrailer.Length); + + return xmpMS.ToArray(); + } + + /// + /// Creates a string of whitespace with linebreaks for padding within xpacket. + /// + /// Desired total size of whitespace + /// String of whitespace with newline character in each line of 100 chars + public static string CreateWhitespace(int size=4096) + { + var line = '\u000A' + new String('\u0020', 99); + return string.Concat(Enumerable.Repeat(line, (int)Math.Ceiling(size / 100.0))).Substring(0, size); + } + } +} diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs new file mode 100644 index 0000000..df45e39 --- /dev/null +++ b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs @@ -0,0 +1,110 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System.Collections.Generic; +using System.IO; +using JetBrains.Annotations; +using MetadataExtractor.Formats.Bmp; +#if !PORTABLE +using MetadataExtractor.Formats.FileSystem; +#endif +using MetadataExtractor.Formats.Gif; +using MetadataExtractor.Formats.Ico; +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.Formats.Pcx; +using MetadataExtractor.Formats.Photoshop; +using MetadataExtractor.Formats.Png; +using MetadataExtractor.Formats.QuickTime; +using MetadataExtractor.Formats.Raf; +using MetadataExtractor.Formats.Tiff; +using MetadataExtractor.Formats.WebP; +using MetadataExtractor.Util; +using System.Xml.Linq; + +namespace MetadataExtractor +{ + /// Writes metadata to any supported file format. + /// + /// This class a lightweight wrapper around other, specific metadata processors. + /// During saving, the file type is determined from the first few bytes of the existing file. + /// Writing is then delegated to one of: + /// + /// + /// for JPEG files + /// Writing to other file types is currently not implemented. Feel free to contribute! + /// + /// + /// If you know the file type you're working with, you may use one of the above processors directly. + /// For most scenarios it is simpler, more convenient and more robust to use this class. + /// + /// is used to determine the provided image's file type, and therefore + /// the appropriate metadata reader to use. + /// + /// Michael Osthege + /// on unsupported file types + public static class ImageMetadataWriter + { + /// Writes metadata to a . + /// A stream to which the file data may be written. The stream must be positioned at the beginning of the file's data. + /// Collection of metadata objects. + /// The file type is unknown, or processing errors occurred. + /// + [NotNull] + public static MemoryStream WriteMetadata([NotNull] Stream stream, IEnumerable metadata) + { + var fileType = FileTypeDetector.DetectFileType(stream); + switch (fileType) + { + case FileType.Jpeg: return JpegMetadataWriter.WriteMetadata(stream, metadata); + //case FileType.Tiff: + //case FileType.Arw: + //case FileType.Cr2: + //case FileType.Nef: + //case FileType.Orf: + //case FileType.Rw2: + // return TiffMetadataReader.ReadMetadata(stream); + //case FileType.Psd: + // return PsdMetadataReader.ReadMetadata(stream); + //case FileType.Png: + // return PngMetadataReader.ReadMetadata(stream); + //case FileType.Bmp: + // return new[] { BmpMetadataReader.ReadMetadata(stream) }; + //case FileType.Gif: + // return new[] { GifMetadataReader.ReadMetadata(stream) }; + //case FileType.Ico: + // return IcoMetadataReader.ReadMetadata(stream); + //case FileType.Pcx: + // return new[] { PcxMetadataReader.ReadMetadata(stream) }; + //case FileType.Riff: + // return WebPMetadataReader.ReadMetadata(stream); + //case FileType.Raf: + // return RafMetadataReader.ReadMetadata(stream); + //case FileType.QuickTime: + // return QuicktimeMetadataReader.ReadMetadata(stream); + } + + throw new ImageProcessingException("File format is not supported"); + } + } +} From 9147df4658ce21dd82a986fd565e8d3463911d37 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:28:34 -0500 Subject: [PATCH 06/30] cleanup --- .../Formats/Jpeg/JpegMetadataWriter.cs | 16 +--------------- .../MetadataExtractor/ImageMetadataWriter.cs | 13 ------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs index 280fff7..7239106 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs @@ -22,27 +22,13 @@ // #endregion -using System.Collections.Generic; -using System.IO; -using System.Linq; using JetBrains.Annotations; -using MetadataExtractor.Formats.Adobe; -using MetadataExtractor.Formats.Exif; -using MetadataExtractor.Formats.Icc; -using MetadataExtractor.Formats.Iptc; -using MetadataExtractor.Formats.Jfif; -using MetadataExtractor.Formats.Jfxx; -using MetadataExtractor.Formats.Photoshop; #if !PORTABLE using MetadataExtractor.Formats.FileSystem; #endif using MetadataExtractor.Formats.Xmp; using MetadataExtractor.IO; using System.Xml.Linq; -using System.Text; -using System; -using System.Xml; -using System.Diagnostics; namespace MetadataExtractor.Formats.Jpeg { @@ -95,7 +81,7 @@ public static MemoryStream WriteMetadata([NotNull] Stream original, [NotNull] IE /// Collection of metadata items. /// A dictionary that maps metadata types to compatible JpegMetadataWriters. /// The updated list of JpegFragments - public static List UpdateJpegFragments([NotNull] List fragments, [NotNull] IEnumerable metadata, [NotNull] Dictionary writers = null) + public static List UpdateJpegFragments([NotNull] List fragments, [NotNull] IEnumerable metadata, [NotNull] Dictionary? writers = null) { if (writers == null) writers = _allWriters; diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs index df45e39..2e2b13b 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs @@ -22,25 +22,12 @@ // #endregion -using System.Collections.Generic; -using System.IO; using JetBrains.Annotations; -using MetadataExtractor.Formats.Bmp; #if !PORTABLE using MetadataExtractor.Formats.FileSystem; #endif -using MetadataExtractor.Formats.Gif; -using MetadataExtractor.Formats.Ico; using MetadataExtractor.Formats.Jpeg; -using MetadataExtractor.Formats.Pcx; -using MetadataExtractor.Formats.Photoshop; -using MetadataExtractor.Formats.Png; -using MetadataExtractor.Formats.QuickTime; -using MetadataExtractor.Formats.Raf; -using MetadataExtractor.Formats.Tiff; -using MetadataExtractor.Formats.WebP; using MetadataExtractor.Util; -using System.Xml.Linq; namespace MetadataExtractor { From 6151dd4bba4c57806aca5dbe6396f57385412bd1 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:30:28 -0500 Subject: [PATCH 07/30] Rename JpegSegment class to JpegSegmentPlugin --- .../MetadataExtractor/Formats/Jpeg/JpegFragment.cs | 8 ++++---- .../MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs | 4 ++-- .../MetadataExtractor/Formats/Jpeg/JpegSegment.cs | 5 +++-- .../MetadataExtractor/Formats/Xmp/XmpWriter.cs | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs index d6986ea..1221e5f 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs @@ -21,7 +21,7 @@ public sealed class JpegFragment /// JpegSegment interpretation of this fragment. /// [CanBeNull] - public JpegSegment Segment { get; } + public JpegSegmentPlugin Segment { get; } /// /// All bytes that make up the JpegFragment. (Includes potential JpegSegment marker + size) @@ -34,7 +34,7 @@ public sealed class JpegFragment /// /// All bytes that make up the fragment. /// Optional JpegSegment interpretation of this fragment. - public JpegFragment([NotNull] byte[] bytes, [CanBeNull] JpegSegment segment = null) + public JpegFragment([NotNull] byte[] bytes, [CanBeNull] JpegSegmentPlugin segment = null) { Bytes = bytes; Segment = segment; @@ -53,7 +53,7 @@ public override string ToString() /// /// A JpegSegment /// A JpegFragment that is the concatenation of JpegSegment marker, size bytes and payload. - public static JpegFragment FromJpegSegment(JpegSegment segment) + public static JpegFragment FromJpegSegment(JpegSegmentPlugin segment) { byte[] fragmentBytes = new byte[2 + 2 + segment.Bytes.Length]; @@ -62,7 +62,7 @@ public static JpegFragment FromJpegSegment(JpegSegment segment) fragmentBytes[1] = (byte)segment.Type; // Segment size - byte[] sizeBytes = JpegSegment.EncodePayloadLength(segment.Bytes.Length); + byte[] sizeBytes = JpegSegmentPlugin.EncodePayloadLength(segment.Bytes.Length); sizeBytes.CopyTo(fragmentBytes, 2); // Segment payload diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs index 6a7a261..118114a 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs @@ -103,13 +103,13 @@ public static List SplitFragments([NotNull] SequentialReader reade if (segmentType.ContainsPayload()) { // To read the segment payload, the segment length must be decoded: - var payloadLength = JpegSegment.DecodePayloadLength(reader.GetByte(), reader.GetByte()); + var payloadLength = JpegSegmentPlugin.DecodePayloadLength(reader.GetByte(), reader.GetByte()); if (payloadLength < 0) throw new JpegProcessingException("JPEG segment size would be less than zero."); var segmentOffset = reader.Position; var payloadBytes = reader.GetBytes(payloadLength); Debug.Assert(payloadLength == payloadBytes.Length); - var segment = new JpegSegment(segmentType, payloadBytes, segmentOffset); + var segment = new JpegSegmentPlugin(segmentType, payloadBytes, segmentOffset); fragments.Add(JpegFragment.FromJpegSegment(segment)); } else diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs index 52e7551..5a59645 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs @@ -24,6 +24,7 @@ using JetBrains.Annotations; using System; +using System.Runtime.CompilerServices; namespace MetadataExtractor.Formats.Jpeg { @@ -32,13 +33,13 @@ namespace MetadataExtractor.Formats.Jpeg /// /// /// Drew Noakes https://drewnoakes.com - public sealed class JpegSegment(JpegSegmentType type, byte[] bytes, long offset) + public sealed class JpegSegmentPlugin { public JpegSegmentType Type { get; } [NotNull] public byte[] Bytes { get; } public long Offset { get; } - public JpegSegment(JpegSegmentType type, [NotNull] byte[] bytes, long offset) + public JpegSegmentPlugin(JpegSegmentType type, [NotNull] byte[] bytes, long offset) { Type = type; Bytes = bytes; diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index af4bd61..c61b33d 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -1,4 +1,4 @@ -using JetBrains.Annotations; +using JetBrains.Annotations; using MetadataExtractor.Formats.Jpeg; using System; using System.Collections.Generic; @@ -43,7 +43,7 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not if (metadata is XDocument) { byte[] payloadBytes = EncodeXmpToPayloadBytes((XDocument)metadata); - JpegSegment metadataSegment = new JpegSegment(JpegSegmentType.App1, payloadBytes, offset: 0); + JpegSegmentPlugin metadataSegment = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); metadataFragment = JpegFragment.FromJpegSegment(metadataSegment); } else From 6a66957ec5dc911bbdf325a94712decdf4a8a485 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:33:28 -0500 Subject: [PATCH 08/30] Align with MDE 2.9.0-rc1 to address the change to XmpReader.JpegSegmentPreamble (as per PR #376). --- .../MetadataExtractor/Formats/Xmp/XmpWriter.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index c61b33d..286a72c 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -1,9 +1,5 @@ -using JetBrains.Annotations; +using JetBrains.Annotations; using MetadataExtractor.Formats.Jpeg; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; @@ -68,7 +64,7 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not // This App1 segment could be a candidate for overwriting. // Read the encountered segment payload to check if it contains the Xmp preamble string potentialPreamble = Encoding.UTF8.GetString(currentFragment.Segment.Bytes, 0, XmpReader.JpegSegmentPreamble.Length); - if (potentialPreamble.Equals(XmpReader.JpegSegmentPreamble, StringComparison.OrdinalIgnoreCase)) + if (potentialPreamble.Equals(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray()), StringComparison.OrdinalIgnoreCase)) { // The existing Xmp App1 fragment will be replaced with the new fragment currentFragment = metadataFragment; @@ -106,7 +102,7 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not public static byte[] EncodeXmpToPayloadBytes([NotNull] XDocument xmpDoc, bool writeable=true) { // constant parts - byte[] preamble = Encoding.UTF8.GetBytes(XmpReader.JpegSegmentPreamble); + byte[] preamble = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray())); byte[] xpacketHeader = Encoding.UTF8.GetBytes(""); byte[] xpacketTrailer = Encoding.UTF8.GetBytes($""); From 2e2153bdc8c44a6a8bb7dd2ebdb93f2bc85be07d Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 14:37:19 -0500 Subject: [PATCH 09/30] Rename the .cs to JpegSegmentPlugin --- .../Formats/Jpeg/{JpegSegment.cs => JpegSegmentPlugin.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/{JpegSegment.cs => JpegSegmentPlugin.cs} (100%) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegmentPlugin.cs similarity index 100% rename from JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegment.cs rename to JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegmentPlugin.cs From 5100e7b0cb2a7e8b63d610c80a282d40e4ac89cd Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 15:53:08 -0500 Subject: [PATCH 10/30] Added unit tests as per the original PR --- .../Data/xmpWriting_MicrosoftXmp.app1 | Bin 0 -> 14517 bytes .../xmpWriting_MicrosoftXmpReencoded.app1 | Bin 0 -> 6751 bytes .../xmpWriting_PictureWithMicrosoftXmp.jpg | Bin 0 -> 96966 bytes ...iting_PictureWithMicrosoftXmpReencoded.jpg | Bin 0 -> 89200 bytes .../Data/xmpWriting_PictureWithoutXmp.jpg | Bin 0 -> 82449 bytes .../Data/xmpWriting_XmpContent.xmp | 8 ++ .../Formats/Jpeg/JpegFragmentTest.cs | 115 +++++++++++++++ .../Formats/Jpeg/JpegMetadataWriterTest.cs | 111 +++++++++++++++ .../Formats/Jpeg/JpegSegmentTest.cs | 60 ++++++++ .../Formats/Xmp/XmpWriterTest.cs | 132 ++++++++++++++++++ .../ImageMetadataWriterTest.cs | 55 ++++++++ .../JpegXmpWritePluginMDE.Tests.csproj | 26 ++++ JpegXmpWritePluginMDE.Tests/TestDataUtil.cs | 19 +++ JpegXmpWritePluginMDE.sln | 8 +- 14 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 JpegXmpWritePluginMDE.Tests/Data/xmpWriting_MicrosoftXmp.app1 create mode 100644 JpegXmpWritePluginMDE.Tests/Data/xmpWriting_MicrosoftXmpReencoded.app1 create mode 100644 JpegXmpWritePluginMDE.Tests/Data/xmpWriting_PictureWithMicrosoftXmp.jpg create mode 100644 JpegXmpWritePluginMDE.Tests/Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg create mode 100644 JpegXmpWritePluginMDE.Tests/Data/xmpWriting_PictureWithoutXmp.jpg create mode 100644 JpegXmpWritePluginMDE.Tests/Data/xmpWriting_XmpContent.xmp create mode 100644 JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs create mode 100644 JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs create mode 100644 JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs create mode 100644 JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs create mode 100644 JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs create mode 100644 JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj create mode 100644 JpegXmpWritePluginMDE.Tests/TestDataUtil.cs diff --git a/JpegXmpWritePluginMDE.Tests/Data/xmpWriting_MicrosoftXmp.app1 b/JpegXmpWritePluginMDE.Tests/Data/xmpWriting_MicrosoftXmp.app1 new file mode 100644 index 0000000000000000000000000000000000000000..b66aa709044e552b220b8656fe5102f93a8c3940 GIT binary patch literal 14517 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

g-py}Isk{SkpbGQL;WJ+40@ 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� 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 literal 0 HcmV?d00001 diff --git a/JpegXmpWritePluginMDE.Tests/Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg b/JpegXmpWritePluginMDE.Tests/Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a055640e1b72bd7d0b1aa1006a01ced009659360 GIT binary patch literal 89200 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ν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󠷁MNVq|lB>3UmXxDw|S zer358=W;W1QOlRH4FmG%DtpED9JmF%vK}uV(B3?{x>a`V^hs`s#gt=W-oR;*D{tZS zD%GVsF1Ni=-C*1xpL-L4cEtJwhjlF5r{s);`nj5*neyZ5NE7{+VXH21td%m;gxYXG+LO;rf zm+J?*FnErtG|#3++>GkKc%UCaFl^lG^>IN1_Ms8O?&>#qTQdlT(AnRfQtKySASftN zPQ*WzlPWi$d83x3@WD~IczbK=r%eCc$#7_3GoDqsh@9Bk$3dr1XTMO%8;JM@kT~7+ zttbB@_GY!`L#N~3=g)35+E^+MQdJ2WMwao5!k!>TTeHQOAVkU=OEz9^b0KqKjImh* zcOg|B_|E4Oe(S}q6n?A)nJK6!L%><5XFn<8*c(CyEn4F`O%U$eXJh?w8xx^L=_>N` zL6^|IFZa8C$bEJ`RsR)f`MVp_d?lCyxaJZquGB6S&kXRr%@<_~cQDPJVMjKUZQEiR z&Z!hfd2)mX`+3Z1C&pv77KWSYE_WSvOL9uaw9n?yiHcz1phvVIeHLRfM)d1)Ao1ZB zxI~i1W=CXVz~q2ZC-Uzkiwu_HqyZv_)%Tw(-`(yyLVw5f5K-rAK$%XGRh_!M+Ip2c zJH1_KQ4)9KGOv~0q?|>l$yk#&bYU>bsQ|^VLgsxNQ{LJo@_gDyM6*t>1}Hy$7MGQ5 ze|QTHe(A_T&@FuwW7Fk$0TK4VFSizvIc_MF%HKQXpT(~wg48vLf4=<1=`)jg>OIh_ z68*6y{$56tjOsReac>GM3zWN^#HVNU&NN8~xcq4!*U3PqNLj+wTSuh7H}2SLrwcjv zLv`**@;O?tVwKM^|juaGW-=Z+8rd#vUuMyE%Lyqljh$G1Ro3QNkitWSGKD=ogm zl)K|z5osUM_tb>h=JvCddzjb)?AD7#;f{CC%Z8|D>7`5(ohduMex(N_)MN=86B2_a zIV@h>B`6!g2RhyPoLUvllWNXB8B!K6s-|Ld|4|MVGoIz|D5%x0j04~6m4Cjda3UY1 zZ1m-6{L}aGHEQNeb0o~TL*D^+Z`S)b!KrqdL49WQ0VNg4au?T)MwuDX%)U^%#N~yE z&O0*CSg$zhF!MXkRJ?Y99ietgkfi9m3AQWwC}F_d$^Ld-t^qufGDUWlm|w62CC{D7 zc3(W^G_y97*xYXE>`cOE3gz$C79y^IBa>T1Ax!Hs8cXBQq){PIdXd4Q^iP%{OD5p* ziGf8c|2Qr({TzT-qNxU~N_adfapS_mzrmIG$wZ$rB|4DkH zNgtC98Zrz5Rjilet={PCMQThgy&wpcCDSEo%(QvdJ1s!~e-j$X&+Mi|45%CAmwp%r zyMcG&AeS5>iyAdhpC1DmTA^AJzor#g6^H1V$P9s^zFLzp$uKskwY>8mbNy)#N{H8K ztZG&Dsd!J$sjNj~6VC}NFW%cVe})a=4BoN0(Fv#Su5V|FF_a3$p37XWti~5-pNQ8BY0Y4r!Pb|DR*OI|p zU(t4GqB=NqJv0oYKyY2fZcQfsO_|#15`t#;C=mJ=nFpy~!MusLo zrMaC%oP#60XJP|jaS&FPs6EY7(QH3!J4Vx1rzKiE?2+LwjT*S z@RL&wxtmbqXx?Rro~;Kdgq`9>SF&etRGWS5JPe9Mz{|?pt8Rgq{I~6Yl@+BOx&rQs z$s5~K#wLEV>^E*FKbNk)2a>T$!Wx=lspkIX=>Kv#SZUrJ|4}1XDsr0Fvhj23!LrDx zl|K1*l;1Qmad55b=NfT5p+Jv@7Ner`P(OedGAe-p+`3uN9Q1eIPvOk+cP93S_HGN zlhRcfxWnHsA>Z(=UsT?^3g5bDgEWsew`!*4q_gde!x||)P5x;V7n4>(YgK3XfpVPl zSMOQctXKfIjfUIme-%bdG0BHoX&;S779bO6KftTQY_FH?hKx`PxP_(Q|Owtn1G%U|D3 zzI6fO+m~|I>@rM_afE-tu4DRP{z~NHzRR?h?bj(uB)_OJ>b?YUcFSrl#er84O9cnx zz0Vglv+C!M2R~bR;powC(geJpN5>-?#svxuOv&h-80!_al|GHAs2skZm0bIoXiRFI zo1jadH2iUyyBTSjQ#COuXQOU{ztkqlK$(f}i*}(j<3+B=tPXZM4t(=AUB6QZ7!S7kaN8I{WPs;BPMaUsGMhBBQx9VDhp&%YjYQnUp>Z zsrTsTD=pW^==B-UJrfEc-`D?uXPhnoxH~B| z&9-ILl5dp|sgB8q`4>fkm6q?9m-=uIq{!5*C1e=RY@(+s6^OU`Vntm})EL#-)O3Cj zen61{SQ(BoEck`~?5?G!dqi2&qU(xsKZr<_TC(GyePQU)c_Xzy%Ufyx0tNU!PT|m6 z6((m0qpP?f(GAuL4BG(nj6T%HH2c&|6lL@z_|yNn(f7Gh4HMDfwIvV^^~n14&*vm9=o>tH&$kzy)Ae6L#*X*GojOMsFrh!c?^p)ft_# zZc`7z7{5x{NGAeb8nXaf0zH=k8l7jna^7DV6!jXoqAFvhCJ#GbpC z^(ylpR4VFh__&gZvhn1Xa$M}@2)(m5>ixrLWi2Mo4CVNwPo&Ih& zlt(Y!7{3>PI7enH$&d0IMy|xj#~;=y+qz5vk1`_a+FPpSL{kb{#v~*verRS(Bk$G9 z^Fh4sGIWVA#I8%(UPQ5onI@zQ01Iehx9(=F35V`j(Fv;oKR%%K|2GQ=H%JYV1ghPf zXR{8cSF+Mm@6yZ|1N2l)_I%}(U_r=9Tcz{~V37TfE}4yq_=g84RXwBWte-6fzn+)- zjVVwQPk226l92Y73p|6l%wQ7i;n$8V7^`Lcw(?z*B>@}j0jtIN4MYN>r4(UH*QgPj z2oSz6N6vRILEt@0*_}^z`lT4I>RKO$ByI1ujV^M$vRs!XRER6`J`Q&3j1C2X(gvlY zFjAVeQl0L%spqp$p5~tr05K_b8A(l5brBj%GY%U^b4wa`9!_o=W;YjWdrKMxML8P9 z4>EEz?gG3l>@-eppKPsNy=cT~goLpG|8eaRg6>J`pwLOdK?0^C=x@JaCrh=@r@NO17TD9DJ(35iLF|Kr;K=S(y-EOc}%V%%4_ z#Q)pnKdyb0tY=QBD0Kh2_5n5jzjy85e~$~<*JDPBNYyHwv&n+lIVhbX;yH7RSLCei zur{k|!6+xjOup`qg_p^0yBrUwHZ#Pv<9%;3jy!G;dr_t#U+REeP~qt)2}Wxy^~?@G z-++)}n}wLn+{Nm=2& zHJx%%I8&sCOsk#Zdd;Kez#{h6(rt8V+ej4BY)SpVMiK$3*3K(_b>u$U=xYaGT_*`9 zn$>~NB<(QiaBQ9sqfO;d}wb-6NqEcys1N^c2`-q3du z>tJ@n~*;L%GbAceYwre~B%>q?Jv1(To&kcGC#VYs|ORMb>brqXb(1ICOobTzdcP(7wf5e&Nv+zKenIs+ zUfEc!Mq@#!X^m!M#1^etg4d$)+g*PQK|MVg2dt>_VYbuASalnuN?AqI>x)0|7n&Sn z^FI@^OHsl2fC&p@JNTGBb9fs8>p0U79BgdFMc?&~x<*kaqr9_YY>kCx2I*m^3=${V z`D*;!@+FR6r9%4 z)92~Sbk@2g8eEYSBM?!dkYnBPZd@xQ8fLB@saF8~GPc3V<~3FECa3CNiAUA=7w~DiB%Yge1gWQ3<%QZQ^$v@TS%Hy0DO7z)IbuK#C#G zZ^pnIq)c@(d_Vo5oj#^h6=90y>da7l2BP=d|Hzvhk(m6zkFxAP(01l(jE%g5#c3ko zqLTHhX0R|iDScVc$%x~g4fkdMH)Qon$Qpd7OXQO_exzmJ1d&KMpFcWL-MOQlQEf&T z7wQ3F2pVpux)Y6x>tqOCOt6iu@2bZul9PbT!g~R(!I34P#VF`}w&9Ss*>kzZ1HVOe zCfCewez61TB`7E2N;-GAUxa6#v6P4vTnhqM?$rDMmWl4ii`Nja;1j?o^G`SRww7T% zJ-o!vrK^@$as2J7b*eqj16%w)G-O6h&BwLdywP0c?TZt`pT5|73x_d^b3Zl4GZGlC z_RdNMdLQ?7t`v&~iW3kgV&rh^8s17ihRO3)H-m`>rGkaNCW`?sA$w@8u8ed!8D%kV zkh|3th~nDLNW|hhxBH>Ra#Pejgl|27XmOvrvWZo(Ii;%5^E;cHL#m5A_4JMyNJ|jC zM$}XfM{IU%aZcVB?bFlrTuACISQnIgB zth%AiO7Qi{Y$&vUsc0TM_6eZH*t^^SiLPg=gyq}{-#W!8bHM{#xg_RF%Cz%q5xqblF6ng8{ z^`qCC>c)EtbNecfHMY5YAHh;GgPN+fVg&HszjxcA4qk4ajdnvr7;{_kzL6Jyw_AuQ zQWb0`&e*~-7PN3Gi?O3{rBWUyL*GqpO_;R=xyn+C#gzlY?zH;cG70CcD{l2_7W9>W zB=~MGW;W+UU1!OwBxzivh8PZxT^lRP5}krOjUx~ob#Q6kwg?czJPKC1^Du#FdM*Xq zus2og5gTlnbB}*9q+(i0c9i{`C91>O-}r~=7n!~*+q>>B%Tlf-xL;e@tKn^*55=Tx zTGg7u$g=i7`8wB(|0!aKbf^J+n<{|p`KwT$=#h&Ukc!*gH3oXD!Fv0|77sV>j8vjHlucw6OfA%Bjco4}}`E{|6J>gK8B za*mT6NTRO(PczhS-OfDA@=57bDPwf z>b10AaP59=WL_1g!I>iXn}jKpPbZ!LIdy$ZS{nTusA6eX-L7ClVCTx~Q+D2k$Gp`K z4HiSTzt#Ub*K@YD^ z22$S-!-*>xNYqrXNK3Sjlm-de8fQ#1S~bi#MZesbG+^6Ea&+?ANzSnc$N4Ak5LGs2 z`4QWZU)jMJ&o6E%;wi2T?zlG&49+d>XAN_Oo&cWi55;*{z5L4^BVHfP69l&OqYoYm zXGxCF93rpn_M4sdKCIVPmdkGTOq0O13kJ4Nf_&wQtVE`~^OBr4mpYb3N`mS?Em2=y zuO14*VT--md+UCBaN7qd`?jX=xs4A|z-GQD00%@!Xfwvo>2{J@tGMdMxTrVxW>9|0 zzMS*Wq^bd5&ciHwBv5kTPd^~K-RELiVfQ$27Hm?DoX>pokrwhI@Y>sJBIrwl{0{L? z4{~c%1P1m(oS~{O&RWpd?_=~TPI;nxslRw69;SI+=u^)IQU>(W!g{u z?yG^fOYfH6cs`4`J8!8_jt^%8Tu!Spng3oKL-|j|VsKr3yl?%X3NN3oGVI>Zg1D}6 zs-(IQud$mNi1hRN&K7%S8YixgbLBfyY;j#DNn8@999M1oRqDQpF~6VSft&flcr@UGk*APk@jW${BpbhF(K$c+NRZGCfk_`n}|$nH`=mGO(Hj&euWk`)!Wy zE0M9Mp;?EuCqM+gwLADUcG*ak-?_G(Xoey9%U;Mnt3k?KKvU|h)b6rIZ%ROD_0P6- zApF^KFV^S8Yqf9sQH~FgH%#5en4ZPjh!Hqi(cH3X7_1qwcVRj<8|gWy_GKQ??Kl=r z_=5k|Ot?1wG3`>rAhOZ6OdsD?Z?t(o`6*^)P}yqD zt_~-Eq#il64l?FL^xXF*s%eS<_maN#RxKOTH%73z2(nko4K|l89ZJzlg1H|))f84Uj`2e^ zZCTj2R;g$1H;}8jLW*2n47YpM*niX<(w-o+XZ(+x%h=dDUOYdOIB`%sU7a^Zs06LI zJtBP`h(sBq6VjIyY;FdWf(I$LLyrgl5qoM^2;+k8ca?Ss^rOcG-3S@#<)Sc>^wn;K zLZijKnQ|xBRW9SyVy7`knrE?b!<6q%cnUM*ch$!~dqyf0FImqfWtJ|Wv(azLcBEjq zyG$)=l@a z@{E1iz|cToZ`)Lhk;!3Dez@_siTmUJ#ok11{_}lwe}^XZUgwX7#nX+)+gTdtzEd-| zDUFl_OTEdZY8ES3Iyoy*?RA!0g_~{l39Vjav85VOSaBNnH9xYH#iKt7*EE<=gli=< z4?&dH!FYq881(CSxooy9VqLwA2O@Rf8+*hVBv`#GA-7sd@X|=Jx{l^NT$%3`vFlvh z^0p}bSj@SiGWl(~%a>^yF;1Z?vCTH-197SH!I_57rQk%%q3#uEY0>1wIzGlA^N3$v zx@`uZ09<+j;M13lm&2{oTi{A8yVL84mSfi^z$5x`RT~sH>Y5rgT8v}8SfCzGoJ}n~ zGN;76cylUjAx_+y$sTa%c#E*TJ@j@UVrVPH1}{*+-i(HFu|Ejs*l4HNZ4IGs@1JdC z(!F*sDvC{YK_)Ur2XAKk^tPkLx3gRd44|+1j;%?K>_XFs#U5dQrzuKheWDeikB}pg zyn=^{#wAl@-%I{P@6x4fT*4z(zg+)Ap5L9lCcPZJGmWWc?^5LK)-(hCxJH{6T?naO z%I8_hZ`z6LXt?cNoE*KN{=bMi?`Ssv_y2ppJG50)YqS)tSz@#`YrZ?|s#1HlHW8^U zh;*siMNuo-+Je-Kgh=hZ_lOaD1TjNKfBBr>AK$-|bCPrJ`+nWm^?W|A=VhnJ`ed@` zI+sZjX;U>}{I@h!0kVvkJ|*a>$UnSq)NxBNKS1Et9u))4lpc+1w7>fB_l5U6rPS+^ zfr{pg-gDY-12%Kt12t?T62bW^>gsEMk&h$OMV@IP&K5VYH7y=URqzLoZ?tT;S5)YK zSCthi7HGVos$GRum^l8aB!j@Y&u`${SoXTUr^QKFMc@a(_)!izoyWT=6^x;YQgze~s z#^Nhmf!l*RA|KqRDpeJODO1eRb0o2Jv0{d#(+IE4ToSOZy&9e8%!E~WwR(ZG8+VZ| z1XUSlU(?k$!5#D02Kk5YSU^P?~`e;+h|A&1j_4lv|2CJ)Tdkl?&t|9Smj>YW%) zF$|i=c#H6r|D4=Mb(c;BYSe8RhpCd_$VG;qt@C0`q%z}TuggP-0OG`mAi3n1k+0x) zbUETdWS_0Y_Q_JcgCO}LtFgkRa1hqA8~bg!r5V5WWx9+ z`_YM#@^?sP9rG?nD$Tkf66mLU?3h*9`6Y~R0mdC>LAnl{6W)i9O)iKyicU8$@ZP78-rBpi9B|E);Dk1#$m#rgrAcXm zd%wYX6Rdi08&q8+#=lB&7hi}^&dESyogOw@@hsnVsl$^kSfvd0O)zd1N5K{`5n4#U zCBA|4K*V3B`={i;Om5KwjeG6&=e%ZaB+w2(b3r*G2Y>e_Yo5oaORk1>~o%@WF0 z#slQ6dUO%IBb^&9OF{CQE`OO~LOPlgZg@2QL-LA)EQ%qRdI3VR#?8nI)`#C3wO@}F%MFWYs ziJ#eYG=Lq2$JTZgx=d3>zXkZvR$N>(cP5Z&j@j;3I|=6=G(+vnGvx+FxE37tjNjKU z@JG^O&DT~LW3RxHlT~D<=0b5A@eSpXhi{<;$q*yhKFoO5^@-(3!`%2!Xiu#iW^(S! zbXRBd4u)(+=J}~Z&=jy~E9zSD7+f`ovU&f0@P=(==UH6yj(yoU)(MzpYHoQ}n~dZlB?KlUvYC9*6f)ZPYfZ02Zm z6u334NyoaF=f--+H>eA^6|ceh_Y{lOge*qfo-qznNLl+x*&((&v09WTc>;cP@-;s)dL?F}_mn}uX0NnB zSixM~$fql@bVoGbRytR-5io+jdlH^LBOn)qYu-xntSJKmOO*32j=7nLX;N zrUrtgq#KKCiD4uU#mZie3Wg3=T&}Y9XS(;gm%9zjgeSYEL2@Js zXTIfr%%~**|^}jLd?8j#wzVn~04(_m;m*5{dzv4EpFVQ=~#1yH0S87!dqj zP#TvC?EcH-ZNhV$G=|5p0(3uJwTb@&w=mp6VU|KIo(#1x-vBlcyeGBP_I8qE1U34Sc~SIc#QFC z%OEJ4BwACkF##nM7FdraWldqyy-{E+psOM8^Ny9>yO+`bF46x5Og0KI=!png$t*As z;U;+VN-M7M>5cK!TA?_V$ZB^i*~58m@eZL0OZVkk z22!>ib-uZTABD?dgUn)xyo7sUM-FK|=YvdCdE;kcKWbN6a$)oTO*m$R-jWH_N93<&#s( zaxqsBnllsktAl~1e}Iw;r6 zk2I}2EW10WWZY-#7w!I=^h}%ufUeBa0Mmz4PIVRW!RdMd{ZPc8H=raK);?x0r}ZCa zUQlanJvpu(3TW&5UG8)I)jD^$r{Z=(l8xME&Qu^x9(RsBVUl9n;qH~+qIeDzzr6q- z+n-NRdkP`o1Uyy7b4%o>^Jh5mSKT-w7Vh0%G=0m2d_gkD!G6;F1tdQ-YwL^!U?sbK zv(2vZ>}TB=p_v@mhwcsNj_s5MZ75bq-B3!u>pC;ww>fX0Qp-4}r|uq5r<55TV|CYA zxV9)ft>oE=5zq}3zzZu=dhXkyuG>jP{1~G0AT}R#z+3{jvf`Rh!N)Qq)Tha(6Mlv3 z6I*ZHQg7lDeMF*Xt|AgJkT#7+GSW+#+xyU8W117s>JEg(aRryQW)#MkU2dYi{Pu(S zURFh9@1iNId3_%G4%^L{K(Qo8-8hf+$tsIPnqu5M&YLs%BnfmWLj@U{V`QO#noq7WK^RrL(ya3o)>0Mt=&F2 z8lbVY8ph;4GWMrXrb8mV)kY%vD>y(ykh)(*RZfyS;v|M|;w z{)ohu42a=2%98yS z|9#*Q`=Vy_0twfGm2Is6u?I0xo_YQ`^^;|5fYq2DGA3GHg+wQVDTgl~AB-nM3f!}$Ots|Vcw%=?!Zi&rh=15-B)H=&p za3!(T;obmQ6o#wYjpGbb>%P4vtv=%WtwXvxQ5q&oxsBlJi_CnGcHw#Hq+_t#a*`$< zDHDj%DK@NEnmsZU;ffb^!J}@Y1QWPqYVscYxILVeA#5_}IADCr&w?-GV}T{hf%c_3 zWk!(H^jjFQ$BdF#9Z+EEbZ;uXJ?~q!MquNIHG{GI8jOR?z3&LaG*L{&k3%s8VpWq8zqg@7GD*c1X_gqIQ z9`p{W0^%1(L!5*PGS&mey#X05{-IDdbFd;|$|d}L9@AmJp=%fCW&d@=)`_o{$1IPY zNbPKgFYVBg4I1azB@(ANigE6ExyjD3z)EZ^qxJCh-(-H^?;g&+P&_^}<;cwq%eI0x z+L{#{h`PCEexq-`H_Ny-At*oWgz6i3ag;a?PnDN;PP#X|7!2rF^*KborF{*E{Afvn zeMpD-xT&f1XVZI?qOG;4Vmg|8l6x*uV+K-5&{-FoGYcfWv(c`jH70W$zXofT<*H{q z3a%R!M_cyeuM`n8=z~W@(d};(x%KGNkMt{D31ru7@gN#&E$&PY;$SqvkTv$q^JE#H zZ(?DKvNeC1h7G#UR1#+0x^?^xcD1sw%P+;}Axwf%g9A(6eT6Cx8=O?*{t^RI*#upX|4{$iMc3QkiYb#G0f|Ii1&gc#>^pYbz|#A7CNOXqe^%*FL~Rm zr-n1otP80`YESV)DD5tTB^Xs)AoG&__aPlAFJv7>+KHTO^w~zyNc1;0aN#y@4~Jf# z{pw^$^z}&+L`@-orrm6Y$U4WhUeiyW(fti?GnO)!%@h=(?Y3hTx3p0oJnT|U!Hcd- z3C&fjdq;h((o#HEOk_+kA9x#zpZszwyZm@_fO(>UoxiMpKxO-e45OS3b`^LxA+ZF! zRGq`tQmh561YXwp&QspLamss=!{N@GYo|F~JL>|gXH?>An%C9T;(lxzd()O??zbpH z&S?&#TFf&_zQvN{?@F2g{YuC><@}*d#}m<0cLN`6bt0K*jm|TaC!USSe0PR|y-+ZW z6+hOjI|yS5xVZhj{4dk)*Z+`Gv_@AqT7iNAY|#*)tqD(bbI|z|n3cR8Rx5#iB1NhZ zl_Sl<;9iE-A ziZ)q2eAbse|E?`4^|euzFSTdTn}Ct#Y>kbje8mM48Yaz)uw&BfuEEyQ2g-;uo(83b zT{ideV(=R$5B9XU_Ipfg^IR~K+H0`uimY23U&9++&Tju+tvz6A6=^_{zcl}_CH}i~tZNHSd|sJUiJZ4R$T+4wkxr~a@{?@S zqNH+hHRyR>%+1bIT%-d2L;0dyNAKH`Jhh>N{$fA%>;usjw<@kf{!JEy`cR(_&$DD^ za9zJgKcZSjNlk8W6VkbYBFNYELC5hp8t@`O=wW*e*RDp%B5y znH|lz8239me7DR{vKVp9Z_S<#`EH_ZLH2kPH;r+ps{4*W3b` z+qP+I<55d@a_fG6K#2c(WWcWoS-w(H69nhj3+S0S-30=c3c+jk5ep9gT0g$;;dJ2a z(&J4R01n&e)Vfn+Z0su98>(O&NItG~RGyIoOIkbI_C8I#dEO0Wgx|-Jcgum=AU+oa zKP`CD&^M-1b$lg*tbwqpZh3~m<)q$J)n>+prv!_|z(Gj8L`H)<}mhr-P z2aUehDd-rQv3HKXbF*&VDeg)JK?II3zeB$u;0&Z{9IYSNi3;}{SUBcT6n_5b6570W z`n*S|^9oJN?VvuuZphhxR4Bl1hEFqv%K|g_n9_OTz-4OyqsVW9uS**IWx6brBx=`~ zib?Za3%}W<+I96tpX#&OobR8OincZNM{UMnQ@i$f|4!zpLuh}+jgztf4aJ3>jS()* z7Msu*#3#tuujt#?%S*DE&s||U=~%N>4wm%iw8&ZQpRQajnc1f%=noNU;Lm>7*rcER ztMsq(R%gdidA-4&3o7k6Vt845Ca!b-Azdxp47`AhRP2(bl1d1TyzA!q!VxnENz-VSVH2)zvvxFmO9pGFq3%R?-0Ece_8pg z{_7YX>yvm8C|sxzDIO`}s+(wajr!hR?Pk}lE8$oKasF1Aa&54F4UPn+;QT-S(HL73 zcu0nMfQ4tJJoA^%t!{nweF*(h9$}oDVY(y|_OaMQtF-s}v9q+CY1XggmcW%isY*Fdju}hVJgVd zyHdhx-hymeK82;m+H;P&`r`KN;PIagd(fE{df~Tr&3~fLZagJ>$>aMe@9py~1nDKo zFZYh>b-CV{o_mk_MW_spgtDe`Fsz(@`&%C^s!{MB;)LR2RgT1CD3<()DZHjRIJ$qD zrNb;!)FykIZS#zUp!4}xf~7%EAP}N+wdYT=dR(wuE8yOC!(&Ms>d?+gUB8awg+P;} zB=ibFVqY&eD_(O>#tMoB>==>78j+!p1*qGLRG=QHY{!^z`Od?gHwWCdJ55JIjl7cy z;&O}=+MEsU4%4*!BDlioEG;?`h1R zM)47hw1 z?3Wv*tTt+r!*Q=OU!rgg=&HtwRFP|jpyN;JH@{l2X^#&Gq#PDDQ^c!C<5BsWaZ*Wn zEUHFQKR-SH!xa_!uWJ0*$_q+4u6V0iWv2EolkmFcr2!vWGAF8}pT-oWt!b~bWNo-= zCqpZJr}Piz5D}eIKaIukYClCoH3#%}j6JNXvg_-!)}qS@brt(L9@|!!vf!Lp`2|d5 z=J^=(Lk)X~K%S5Nz*3o!!I1o;8mRRKXfaaR+{^nBq0yF{PfFGv!@Y&O$%02%;G7T*Jg3j2aoSlSL;_LmbYZ&~@ENF|z6wZ%et7-;S4f}G4n<^n*U ze@<+OXg%b5Z@-o9RT3PI-pW`%ghkOgv8O!d&QSC}A#Q@i1AD&~vv`sl`nKI8Rf*i6 zbi0{gKyj3K#sW2fbfe`=I~;#FYuUBcZ<2YTzCTLWAv zffQVtR4PyxX@R8FBO89MXE zUggfY)|X0j13L?p*)%Jh>)wH>@lM#~OLfC)N`4Taz}BaPmH+iWo1$cOl8nOzTy*5e z0KGDqiC&6DV*6bFJIW*#FcHp1c6vdQO^}ucw*6*2&$1{QSMk+)Ch?(IZDlNX(^&YH=Dx{3m)gv_>l zorc+Inm~&B=m$9@+|eT5N5=|*28K2>*yjI!v(-B5E|EK}r*Aqrbl zw!>ZDSL8TF6T-wvk?lbi(U?H2?;hgKlRr#ehiBst%fxD|`Hg>}lT1d>X4;dcwQ5CY z)f4KuK9}I0rRP%hXXwRa{Y zh#t`F{>B_X#S?kDh2FD`sj&%MYN7&H9`J5L`#G#TcdMsF4E*Ox(YvVF37z~|9qHj` z6ic6uEi3DLki!Yr)A-qh=32Gt56V`!2KF`ERCNh=|5TW(O#!|J#jxJDR&z1%piD=Z z(HG(NuseW301T#cGvBcB{`a%%-`L_0~F+~kz9pa(}yxbQJh3(|U3 zHWx46H^O+v+Wg@{GN<5lwt2z=0p0Tnk4{Gusfa5Hb1FY*Eg7VL^Ys5R)pu@?C;0sf zgL=D*d|5*TzewK+<(~=SS>8EDDo%(?@KsXl94}4}$BjICz#Kt!A8Bi>0hAEgI{{R) zW^H}qDeQ|y0oBWp)S}t)tgPS*iUD z=GnN*67ZKPp$-#2=GV$i*N)*z(k&PIn#wY!x9wx)I4h~2^=sF=`RA?SSFtJCscUBJ zX)xf8Y2yBlQ>WQh{m+769-SD9=P_Ybk)sXGv~rU zZ!XE_QS%)&Aq;>8tI^LF>JMT>tf;c(CwLC*#U-LfWlGC={J(Z}K6jgS#VWPba?g8O zO>1HWS*jPcAOR))Ca9-V`G%rSEJh>URaH-V?s_p`NAp?zxC*ORK^i?ajW1oMzYHy_C2V419wjcB-K|I*QCOHBz05V_O>2W* zCS8gE3$ILDCep`(bK&~cvadCp2ID=9=iv^8Wrl|ef1|7j`uaKS(P*8CfJ6QgQd z?p2MUgpl^}xVYqoa;WG+@F!kdX|w_H)^r1usk~rJGpN=mbEThWc0zc;=4Zen+0G`Y zX96YAjH$PpS~84oT-&hs$4~7H4_|4=exXh0D(U0+wi8xo4$QAU#Ie*1D(%%+0n;_- z1ZsSeF26#DlZI`4OVQG{b2WK3`0x$TSSt>aDUecBDcI0ES<-j!AeuU(I^z3=H}FwW zn7AAGu6r&6DQ!f%@?phGW^@e<;={LR432$QWwO=}$hzj%E%lWu1y)9FA$Oi;w){m@KKYF~<0hU5W ztoR1r>z4$xQw_<nw?BVJ1%c6COTzB{p*3bU5l_^t^CLje`7`={jz z)qzlVWCm=piYmz9(fM*Hl)p^lzw%<_5W`^-3%D;quROPx9~5?f%JA+{$SU+eXz=ApI+iGFTc8v*?G&Kw~WKPRRk(zTZtB}+&l(c1u4#2 z_Oo?x50DAlegYM2Ia2m2tYvPM!%6<-If`+`aqp}{cFuHO-j(2Q#W}>9oVIv zBrxYM&2*f-(5Kg=h(|W?m$$60r_DWY(cB-oi#yjBh^s`tZ!jP^SzN0&nS7q-%MBE5g@DR_ zWNhIVjzVYi?zQ$64V&AP;jZU=+OAM;}L}6?~l5nly=#j zv*WHHvA|T&$r8uis7PL0m`}J{MGh+0a+i^MKkAe^C380&b^To=^LdGFSTsg3M&S1E zJzVVFTCxWeuwEtQy3@Y7+y|164ssO>s|LNF`RMQ{e9-#S3GSLq*zsw`Q-JJA5YvzF zD={{&i}IsEs#CY8;vfdheC-o3hCKFjGv_j0CCss|A-NT_l)zyRROyJ?E|XdeZJ6w~ zOwABO-6Vj0kMMI#e59p_oK%Bb#SoyETTww!6I zQ87D05TL2`L6)-F*h*{UPg}^(L>eRi^JAa`CtO<-4wxi0H301pYLta@&6MzJfEL^L z{DDgP$1BpM;%kW31{?M~d(W-=wte-z3Fo!rKE4C2<-F4n;RrKFeIRH(OAqgV3cL1J zD|9!e8mnVJNR+Ccm$8yxy=H4I=Oy(|yM@A{nTZW}@GOH(TK<@Cufu9oVXHVl#|Wgz zKj+M`GiSvfc-R*#PRw7Z<0Jg*SzEg&WOwhp(8A7BcK13{qGO5nJ1Zy8WeLuC8&@O+gq|fo*6w^Md4l7F(8LmcGuf|Us^$W8LyU) zfpTdXOZ1yf0-1}dK+6;huh-Q1R>OeWhr15g>iFC_%$3&A&WkGHY0e9=#Oj@== zR?KzUWb&7Nbwnl^wm1KD?>J383uAlZsb)V;##-@Mftc~>$>xGQA{%=_(`cl0_(3h# zfx6aC|jLrtU2OU2H7Ux-eY|0IzR+?5)@#Dr}d@KL|`BOdH0=v&Ni5B#!`~nS--3~ z=ep&fLZ6eUKh7Fs5NRC0U>m>s8>=g-r(0;{jPMWQ(th|TW?Ep{JmxP`L5#}bUTAIb zWS@x|B-~1I!e%l3M4`bUH1r?atjU9OlH>50aA|KLXz9!&LF+AqfcxZ9zKovW<8_T! zGi}#v*=897`(t*B(E{DN>3nasyEcQ^8Cv%ha;he&A~1dxV570~BrrWLn>+UpZ!%wbL} zM}D(Gy(hy9c4>Nb)E2@PNeb*S9ibK#Oer_D?>w-rYI5`t-&qea^?NQIDg!ctjh7o8 z=cle3^L6Z_2Cs}V+WbrQk2G9wwgiIP(ZJh40oE#P{BuLz6{8a@usvKcOb@$v)M6>( zHKLcLolYD=+*Xr(k^i$^g=AJdR5+lX;4xm)6I7RKwZqdx=cqI1>WGoA&2)T@9Fc5b z)Lf@Yg4d{8<9E@x-^07E(Q}%Ljup$=SS6(&B^>13_A~F?D$`-{^q}AW_$8zzJ+@tD zw>}j9W}4)S9k^ZqyCBM03e_wM{xEFm;+pA8q?Al5{P7&N>5(gCMTH3*;1wjB=zNGw8ASHHGncJa!xaptWX8(VzN zW8kSYqhZ+My_{ARVnfyR`qrw}c>`==)?X&XOVtT8lH-)1AzI7c@uQ|v5yGL$K1L=p zmvNU^vE`;OnmaV)5m|GZvFzZk4=|rKDLJ@v^~|f#CMCKQiHE4pkOj>@tMV(+uM@?$ z&-|}mddIT<-&`n4ZQ?Qnq}~|GKY|*7Nu(s404L=2%C_l%um`&6)RT@7Q-=?8+Kf4s zb%nOz;bC^N&Um+ClxSQOgII-x5K2EBAxqT`=BvWRdLz?rdqXyB#Rv?h7|rG zTKAh-o#%~|q{hE02NaRk{)B3BL6aCu6&ftVI{;jE*rZ9X9+}CZp??Lwb5u_1Syd$!PyIi z@5qBAjx)|j$cP*i%vxnq#t`7#nKmDK*%u-j5m!UrSj2GYfvG#y!(!FCNns)R5T~XTiM6 zT!{5^;AZ5LCpTIu=?D4aiK7z(0xBejFGm~v)=SQ^IVr(gHgDc0x2hI8JH@oMM^{?7 zm6H*Kj>yARfM=g?C~+&N>}{IE&7;d;!S1T4pZNXuJ_fh~pIj%{jm*#KT-rsWJx51~ zYLCuK&AWSuur_TsW4|oyji!_?2g9sdO+%^^$Db1ZGLdz`N#h&g{X2gEfwmKdq;rcF zUrHM^P)N7q9;-Bf5BQ)!r(DJ2f*UQpK13cQw9gzyXV|s>hIv4M1qGREnPmlEAH~fG zkKoGQm}#f}Hdk?ysM+;QNT!x%r1-SaU4QlSZ8{jGppPQQ_s{=C!-!6)H; zjeMA)MmF-O198SjBO!K&IvAqfX};jWLZWHzXIi;sPF?26Y|k&&V&AYG>fV?>cRS`PFHND_plX3Z)s= zYZa0sGEN1)(>MdwN{^5heqEI})u^Zy2qknr@Q z%Rua}#fj)eXq3XPI@aZIK#QfVFR2y2MXQ_ELRH2Y0rKVB)2BAZjGH|0QXT$C9hgPB zh%A(My(erYSqP8tIm*9O>td}j4-;Fn^`#MEJ%XSO{jSF!p zI7hXIl>h(+(=REtg&7BJoAP3HQTWr6J>Z*{p8WDV2=;6{4Mz4@{cW!L)F2J4CF~*7UD$79d5WPs&J)W zT^i*y(>{^nE@1#w*mVJBTrs|TDVzD){>*jI z{n~dO*J9T8-#es<(@!_-PTD(jG4qXD(`CI5b4}u!?~!8rEEVp;&h8;+PB{Hj&)uof zxA7h03m#AMMNL9*od;V{3bgi1a>FZ}ugEL#oSeR?|eodFX@@S;p0>TLua+q^zS`X|$ZeBrX;|bNpD$(Zq zf72B_j)Qn@O^|)^`cu*!=?R{5WqdE!wqRQaYTcIoUXH{{HRe`cUeDOx(yqG+C5$rO zddX=}R{H2d)N$wePerq@f<}1{>mU8Ay*&>RX|kIyDhdE7!$Iq~hM64jIQplFvAH_r zRcwCoQ-hH?J5iw|t}ohX%d}s~9fB!gND^k+f;?1asnwg~eeIcgcaibg#6;~c-Lk3l z3SJq%=|)%bhCm7rkss!lZz%jl_`}X&{l&}v(&Kthu4!yGG9him>k;D1Vf66fT+VuW zq?^Owm> zyj+!j*=Z3qgmV=nrsvN15Tva`D?=;A?i$hlK~&SKSN0n@rj=;gi;SUxTDggO3v0t1 z)z9X|oZ;bHjEsP1q$FF9zKT!p7NPN4(Cj_UwlneQ!$XY64@1XpQG?D9#kUuC-vU?I zIrMG>C)(B4;VtKslUo@{f4;%U|1v^@+iyQlpUUzrBiehVY=>;@&sUr{w6dCQt7!%V zmwkwd*nk^(_SZ%C)7~_M2Is@1u2zn-fAn7?Iea{nHaU5jYD8qYS1us9*mE^!9HUFr z_*3j%+7}Oq&rMFP=TAKL1f--%>x-5M$;`f0HT&+IB%7k-1{N>Val2-IwRaM1U9*yl z>KSuCe3)zjQ0S!xjyfzZo6$5Ll6IhI@NPgs2bfRh3i!;p#mlR#9x7C&>7+@y{3GXV4c&^CZPaw1E}ZkjSGMDkJChV~a}LN3u2-CQ&EZ zFM|5G&*%3{N1^*hRXY5>NHRTy!&*adk2aUTNS+g$yt@6L3l*RyRyDZZ;qhX?uO`xF zI92m#a>hZJK3AECs}ZPr=*!@VN;M*NtB`qoP1B$8#X`#yeS#=?SujP;LafHVY9acCuF7>m z*AgB)zA0%IDLjo8;s?AKlOJ>bESu<;a#FC~czut`(?p3y19wIeWR6RXtJX>gx4MVa;nrBD^Zxy* zQI*8LfRL>wUKeb8+Wjz50LDbaV&S@8s^6_q+iw zo=t?mVU8?(qHY5FNqFNaWf*dRS8ODD1vpe%+t>wXzT8XA4^M%EYyp`;~6kHQIk7NYN%AE zMw2TzD4@I;az088Zy+o-d~In(?uWlj74nQl4%?K~x^K%$ z{dizG`O)IDp$0GTakZa~wLu-*i%T7v*}jgFLq$aN`kUhMSi3{FRI2aZVT^ntbg-;P zNnt5yn)}B@ANXI|SdT!D!;3J_2G)m=#2x%eP)PKGf4vZ9*uJ&Ik1B;0NJ+iHA?Z^w z5z|tvZgeks@wnpO(=8z9aOHPuG5Wz8d5_)L4tw(H(0LE^i6;&uedt4-GkFJjv>Xo@ zT^`!r!NGznYyUD4j5Z}R$WA(1QZdq9_^+i6ZtH*&NbDM$`8I^~=T)4RJVZNYAym80 z17wvR7%S(7gmq*2p6;C-gEADy{6@n%YW!2r{*{7eV1csYzQ``gs0>PH1_eELo7z3~^Ndt^l=qe8 z7Lk|8_C{>M^#&i?ctcr&YHysrsKc{J+{N)WqlODpU8e*)ZBLDfj1?k)^!Llz<-}$08tc|28@knrRddC1nsWWi3DfO2=RK@C9Xy@;C##v}?L*8$vH*JB^-r?rx6|Vx&WLNgCOC zPh(gmZ8+m$r@O+UQ(tKp$GGOn6y4gf24TqgmmN~c(=Iw_LD?<4Zz5y&90m?txj5Y< z;{sqwIuZ-`RKcs;Q(VSxDm$;X5|`9NCb-0-18ianp_OX7QwQ8~H9CV9`N82#bAq)f zQ0e(gp8_}fUI!yuLJ}s&2N*cGHv8S#%^wlW6!b<0N~HIj2+`HqifU9>Ifc=)SFigA zlqnehA0icZ>khOl6ev{W6^0m~?Y{+npJ=?OLts_p91ZAm>yOsE8d2VPaE>kK?#o9T zR*H;J$WnPtwx0ay&PK0N_UyWJ4F`<2o`P?_fWiFB2pUql(XH46cU``ID#bB9jkY3G zItx7EvHs*^S-v*~5%F5o@}aYnVM3P39LQ?-cP5#g@4HzC8AnEJ>u(<~9>?tHB0hwt z^f{G~V$>)da9OC3dV->oEbYJ^QW~b0r~z4aj;o7B&N~MG;rE11t2EmT@OVsX#mMVU zL7b^?0k0Nu%{_dq*8C=$TfOM#DWD$_0y&D16GTDs>?@Sa8QcO9W;sl70T%7Ni6-Vf zmd5Ahi%Ps(WSa}5qMJ%EpQ~V{GYx0<`R;Q@QgQXZA4eqL;sKLXpFO9h>zmg-hnveZ zR{bhI?DeG?HwjPt+b}@jiDWoupPHxbF17=LfA#tj8H@Yg#mmhSFw4cFJ5`^*dFnMf zCrBsJ=?>Q~DFBcU^x-BNFqK{UjcFCnG$mL#=NF-_oepKqcct@R1a9-P#mCQYxuiHH z>1?*4sR-C2FNiT~nSmG0TBa~v2wcqgCX)cTjdpYTL~5;^C->L{^MoZ78(fc|-@TCM zlR$;eRlwZW?U#%!IbcEgmZxiA;Z^v}_lz)41a{=G$Aj_zYNTCF>(EKS)VMQOori*% zs6CP`?!#=@ixKJ@sgmC|eIT}U_Zi6P^Q1`Szf53{Gm9t6Lks>Ej2x6JF&%=3tPa%6 zMxE--&y$|ngp6u0uQFoTZdJ!hBxqHEvBmpb zjqRAwe1Hc)W>++mnUWW43fS*LjR6yzBU8~>+N?c$<7slFX`Gf+WMW8^!=gD zQ);4?08MWB4ZEH8`bhIOF3}zy@!uOA8F~UvB)RR~+Y`xMvT3VVx$KDVo_zf{KRO|u zyRjjiH6a14ygBtzSjKs`NgZL)AIT>N}5Db@neavN*w$y8`*bnDl?xaK-D zk|=@PVmKs!OAQgHeGCXlL{D43@3SPOR``U5Z6dI9Pv2$prfm1T9`03JUQfA$CBa3TH6*6FxC z7W{_5&OZ07cSu({7J>GujyDWWi`8@=;G1=%4>bAYmsOpUF8JxU@*KOTDDmp3{No=O z=aFU<#uoV|e|B1XsB86El8%TE(IMT&V@{4fJW*!S;KIy_!?C-2D? zZ5dLG!7s#S5;9$ST$pv<|NpUc-eE~TQ2V!OWol-oX-PRt9Hpt{;@fcL%FI2e%soiW zJ*Z4A7nP~ytkm3+6Gf5Cm0NM3qN3o&0S-hN{hs%IfB(XDaa}y?oafy4{W(ZXB|{<7 zkCxCO+k)lYRP6~Q2GBjq%cIrQ`m{_A{{nKoy{^Du2j35^Hj5dE=O~6~{`sN3QcQWV zpU3%-RGtRt+?e715bnNE5UB_D|fAT%fJR#-~GM?7e9 zUCh?OV5FkGE-XYpyZ8yz$%cC?C-`wKnuc%BoQ<8cAhZJ-(rnP2JNs zDf^Tlbj-fOpwHin#FOCoFfz}-@WEW$$U`Iu8idjP>WN zyQAVgy4SpTv~V<$OUhnR&r^FJ!=we3Y-lEFDMdb^DuF6b?qmGyf|{jka_|8&cg{Es z$cU*f0fpfBJnd5bXzhByBUEz`F~L^tMCZ3+?3|u$#UkvS=jrLhGXCwNgo;$$;L3~E zE+%e-KwHd%mAL=odQvs=EhE^w#_O`?e-tMnTKoxlEyGiUzZC-d8HC1+6;>d1xGhTj z>(XF^xyNh zDFLCV3d?p`-$g#5_7@7xd*UXyQCOiZ;v1OTn~vM&)z$lWBty3S`#CU*IP1HMOzV7l z`}vevO*gIE+&oje$1Gm#-F+i*P)`T^V0RW$Bb*C*C)y&ERrWDYeBAl96LBumYI?^H zto%b_^V!UozKA^%@;reN-460J6z*ymX|}-6BNohP72c--0F#dZA>)B~-1ABUS z9TOBouC<}(N*>Q8C7hJsHJeuC`5S82K#e7}G8M;b|CEM02Qw|z+l?qLaecNwE~&A` zS8$aJ{xQS=oQX^x?r-(1wu#zW(9*^k;vX-2ryE8*sISokAHOKj*_nx{RgD1Ot>=%W zUVf}hyFf1&qf9Ifx57mn1UJolaSKmyu&*K}JX&mf4BX579i8Bzt1r4ln4fD_C)D)k>%!AI+%v{_$AOc<%D zU&2SjYMafwrCqqieh{Yc8r76UVlGb%;IMZhKrfXduKk=r^(%V+_>j}|`2zCo(<}D= zRZO}noqMeZDTAeE^s7B_9p3g(n(NMG&f{}GkN6i~-L1=&j2Pqnqg?l%;|3u|>a6eL z9o5Dr1;G)+e6`=_;5-D68Q~G+bcWa0D-!y?WdV--@i%L0>$mY9+#hsOO~+OgBS7K4 zi1(80MK$)^iUX(pZhix*S~Ie-_%2@#-Wsm5h)5a~Mo(7$;}Vu=ND|+IC^@ei9-fT~ zoQ`<(QTUNf2Ixy=7iT>*_}xurJ9?Y+(cCnku70_+W59W=9Ncqnj3o)l^?a|fd8FLa zLCT1KL#-@}?0YN}`6u@*D5rZ-S6M!bfAiFOpoz&@RYE;$0+M7r99=D;vHF~m5&niq zsbe*b9V?%8G#B$n!7_J>cZ*F%X|HXq^eo;?gOAUQ&AEcXJ|9D_SSRb$d>3jwx3n>S zAUo$&3_I#2w=TAR*x7IY3&?JF`qUijt(E0svm=$SP)pq50l{>G-&^!R>~~mPX5P2# z&xWYQO_X+f*!Y8Netuy-cMP9Cy_BlKAl@5hWyZvYp9I`H8cXxOyb)$JirGkn3KMESw|@`!!JOf9o;;cx#qt7c2DP*A zujGS{wYQr*eJrRAp$iRTtD`P4VowV1<$C=3^bq5(I~soRn%tj3U)XfTIdyKXe_Z#R ze3;@L<=+`UwHqx9DnUw2l;tYeMQCwOXL#YpL2<>Tb&ja2_y({C$>NlXdGVukuP8$i zQB4lh3(h0DoOUw@@)a(+_&GB7aSq?>=eIyfz8)xUUn1t=9{8o9YGq{4p7v^{$9axh zcezy3G=s;!E-z}DsEz?YeGbztT|7~WIaL=HiOH@{ilT3r?G^hhox+uDsqNUg>`qD% zZ0q80Hu&uByi390xMrjEXM5XSE-M)@LFemFXzJiZT&N$lg8Cgp6Wr$b?2uv_Gxe+{6P2zO2(dFCX|wD@O; zFNDtnWih_L*X%t!Stp(P<>=$-@zX&_D5C}Lp%lFUsNQ#wF3iG4x? zvCr|kasF{(|8WWFMglp>liciE1OK=p6@>bBOC0;63f^<{tOi-3G6hVtkm%pT21EM! zD}F{Wu@_RVmm8ozs;onc&gM9jvd$ikii>7Zx&cIm(&@fAc*2Z0gZxu(;CN;dr0v&_ zZMu3l&qu`mQsW{WP>C?6%bnU4^Bmkkv=7&iXa(djS!A(hyi+|z-8|3M`bJ+`3B+do(=^0(a|B&PMW zc!r{{_h$MdYnuxkUB9#G2wFpip9AsjVs3xEH%XCS3I>5Y^4+%DU;N{`SXez6@n6Q| z-Qg*RvStH-4lH%i4fJ5Ev?n9xR(V;tO-Mmf@ihh5_x9Ff2ZqY>p)DaTOM)AS*NTJO45;O_x&&MlqPR2ZCe zJSCVVL9^+#U(E1+YAF6Tcfn52;^)SP)DvTG9yvED6(lLP2ZbWRkt@>P%~7@fG_{V6 z^i%Cp7pztrI{ZvgW@j91OQ3xorxh+Mi{wdl+`!ASz@O`7?~NXoZrfY*xNQp$*{X!d zz0x-O4}7j6_iNdvN$5r4^+3{X+1lXOS^B44=c9u#?gNb@&MW`GjN{-<%emF>&*P_l&F!|IHydzqC7u0jN}h%a9%2_Lo*1F_l&iJDtMcaDjYSjS4tm5;M#>Nu+MU z&r&ktl2pAQnkIv@&ajK6pEH&&IAN!e3e?;;ySeY7XUnyee&{!QbwNV)ADr7Xd>J$I38^>@Z*)xW_iJdxlCg#@;$iLEd9F~>C2xRta9NEW%I%){m^H+PGp`YvuuYK3>dz5{p)xZE=&pjHvJDDr2W> z%ELw^@{S_71zs%WyOi}GCN?tgN=@@{}%UdT7#lTjDw>*N+uhFVH z!F=#kf54%CUzlTo;{QEOaa3yaaOSk5CJFGc8=St--S1)D3ho0B64&o^X`63235-1( zsR5h;U*87kRykr`00k-p+OBjsB)Y{ec6Zxtc)FT*@O^59KIxUMic-yA94WZNP zI3VUgTP_$73b7(cOEeXg5Mk@~)Q_xSXQDj0YL;#`JkKT-Q5FpX27Vz;8R6f*=!mwn z&gy^YF6|M2Q){hH_%?xIx1Apl6J(BYk!fYCq;t8dx1<6ew)UTyFE#H9dOjl@Z8jJo z=#lU7qD*aUh@`mCFkh){xZx#ig*oG>p|Q4|d30xf6e<+b+IryN`N4B6-T%j&bNqix zkc|qHfb<-N;o3~ARCS&h*sgDZt-&t$QEMRs!^ZIqdbeEbZVM;NJo8P%e(6Wrt7)G0 z;Yyv+0+PwS?)uspLPBT%rYDINSO(OtWja>VWb3L^nOE4k{K;E>LV4!~uF>X6j-jJr zy1&c#=PD?jIj5UysPazN4b$vZNBF+pT6Rux4}|5pl*T@V!eUUrk{UtW@mN$UGrM!G z@gJA0I+eodSO-L0BMIb>D_8P%fwi_2s8&=@Cr24Jhi4}p{4WHQd1)NlG_}VA)o!!L z?Q`NvKlxZJ9ApTv)*v&TeNF_XI3vf1Uy$g2)=(7g(Vpoafa)_QZwt+S?2%b;@&5RC zr-fclJVsGjZoXVSs_?w6uBz-^RGdt6P>lYA9~s(-x~h*p{}^b9u<#ycL{3aZjF{TI z+^aZZgsDai!UW2f!n;KB??i0?jS*kRBGGElXDJ4y3qRZ)H~z}&$l7%~Px~i6Ge)nX zoOaiqtu%~UAiN+JJ-@9JJsxB=PVL*1gA<5`KrSi2CgAj&vivAja#&n)#GPVCEm5b8 zVlk>wV0)ws)4m|BU_pAJ%=Z+B>a&!*I|rgBtz-n&r;QOkl-BtqqEwrg2up>dPCQ-FE=e!XJ&IH!A``-?o+cHhZl`a-}NzJu{ZW&wq1< zrPk{^W$Q*XFz?&i>C?hEd%i19SBD`~fOl=*7E7^-otbXl;4eDBCr1sQPNIpfbLfn` z0j9&_XHt_^YI)~UD}n$yofWP1P+O)F#8~eO>sv_0w1T_KDj-QB-#h1=%$T5JN2PW# z^gjLeKdxrqQaoj)sx61_k_=9j=Ea&46h#m(k{dMbV!`933ou#af;T^Lw!%qDmI@|~*Z z85nEoByD#o=GdwW{+0GraCOnVl~sTtI4x_G|M~mdP~MyuyEXVc%PP3d&;^5gNdQZ` zq$-j)mwArLSR~R|sxfogFzaNmNX0QR!!nud{EX+>Z!6dL4ZtW+tXGi1@p7^WkD-UC zK!DUcM~&iVMf~%9@$}`+3EaqjmNBLr9z48rLxBVG0&1C3dBp8P+a4FO>G!)l6PCt7 zgIxap`5Vqza(l>fjws_^>&zjlPhDJ|T{$NE$5M`yJ|uLyu{u~)DSC$&bcf1PL+h43 z)a^LDwe`d~Kzy+Qi*UMLL&d(?fNfNqpV18L=~{Pkm1!*69R8u*B*Q=6nTiRkuH@jW zI9o$do_}1e7AwTt2VH>$tNQ8 z;&C>{9SV9!SBI7){)03XW>CBXwIL3JR;&Fhz6z=$TT<_<#9qusZ$8$!?T(umN3Q~R z18$K$xhmgqMsUys(a71mw+_8w!x(@vKxD%wsn46J?Nj+ZhwC;a{TA}*z}^kqQlZ`B zg=(EQXJWiHiiJxw3Bg!KDDCcc(B@jXVWn1hbtAetQqL2(1ka8J*83p6^Pa!GPDY*% zmC9JRTWW<0Mu7NG->2?`iR%dXHHz$~Q|ux9zE9Fy*Yltqjpg{B9{8DyhwoIln9a5J>m)cBV_XeNgW=EvWq~BesVg(&h zU5!$v;#M_2t5guBC7-lZCmx#z-XS65&TW&e6HVPS#;-d@K}~ntqs$2f#^0|R1yrR; zWV_E8IS=eAi7?W_R|-+d6Y7;gZq8}4%bryfarqqbBKlP}QGH`z7wN(%_~tHpT5a%- z+ffUlE74bZFN0pv_VKc=X~WeLf46~-OHTz-{rH##-W_$x$olN|vVxa4i=!)O$aW8%x;B`OLwqLsA(@&J! zBDQnj93&CzZYCCj4RUT8tuU5FZlpGSq5b3HvP9x?$DyrIYCNYq>P(e8-6ZsR;K|O& z;mVYMTzA0Gw5Vga3RR33(6~=+dx^ePYaLCv4qY5&YUhvZ<}cI39ML2(u=_e0wDuzg z)QuU31uS$?mJp08Xf{AOWoROou`EL7~S z)@_=~AElr6PEWn*z9UE{{xBNeGDN-Y+5*UU?=ACb=(_zR4IHq>7dZ2eaGpKgsC_i{_ZowS!}!7)0Q zJ=cO((B3z;W2d~L@lC2^UWsYWXqo#Un(pxno_+s{Td8Uadg#*IT%n4aJqTjWJ0Y}h z|7tf)w!I~+WGpOJ3jC&G94SakD=krNE$Arl0q^KotKuTAFhH~;B;y|Km`ppH-2F(5 z#YH$(V6=3oFm}m4$He&dXX;KnLIOcy^t)uHRqi8aU^5C-0jVuKKZh8(wj-?-^tRy zAL^v$U)4)57P_G-S6=2jW%nOZSyds?Ust`GkXp(|F%J(J%|sTM$F;vh@d)}dr^crK zE-knfuDJJ|qANfJft3(YB)lpEKOXKdI|KB!e(uxta*&S0JMHON5^4F zq6yWa1m2842?@Eq^^dCw+=yeT%V3mMOr*=F6y6mY$3dN)OeOdHBXEOzTL<`s+MDac z*gS1yOVnXnRC==`Hz{Vfpj3U}(qtK-dgT*1Z&ZtW4z92nVNd4CJ>!5p=GrI$@)zK8 zhze{C0nIsK!+jIrv?JCA!}%|sU2h$hmBDc9Gx+CTR;!;~6)l*5*RZWvVQ1br>_KWG z!Y(-kM&(jFM=f&33S5?eCOmifX-MRbbp4i=_RmRs`!?cD+TAm#!Piz1#`H&B^q-b0 za43rsQq~+6$aOcuU$?@6&p-c(P%B_c zMi_2Cbv01(@l`;<2kt+yL9&jjMs()Q2*YHox;STv-B|s=%|q>T&}g-&Q`xB-ihB;8 zF7SAL2OmJtOa^Z!v`;#_M_p^ip*Ga4mCnGPgsAR}hjgF2Jh5f-RWIcI7KY!5oer5_ zY`z2^pDuu}Jh$B7xiGyI(VVLuMukks?G3OCIG1#lh~i4P2F~8WbIZ+A4!+wSi)+%k z;=u%ma^i|V{lpaZ>`J{wtBpXe9tfmet+LNL zeToEeMPB4T!TK-OzUzXw5`GQuTopZpot42jlOeG+l-&3X*M-MC3hS9CL?nW-D5(w#(q~CnpLR-1<%~FO!3%%wZCHLSZt`z)OLy5&g z2?;|UYkmB-sJQE9KgepZ$+@TxC?ZA=%d;^-9BsL>s3V`|?yXMdUFv&PM|p*PUz(8~ zA87K(<;}4_X#>;0$tzYqk2p#>R&IVNIiu)Q8E0+a@E`j_?6gYT`tZv!E%o&rJht9o&N0EtMO@K1mv){GtVY|Gt6-quv3TYMn8ciN)#u%F{WS!2&6{56G~~Io&I3 zo5F&331aLDWqy*J-{-ackE5?jL+XPRX9xVYX)rz>_^_V#G57N6sv?hrFz~bLm;0n} zC^*=z*XQCu=+>(B;13{MAp=rp@9_GykMAnZbXs0qky}d$603CvEFO!CeNWyD1T?*J zK`Sj;D%~u1w6*GKw;&)k`n-W7hB*BQ?i|uy&SFFudPpjXgouO(IolO=8{lqQ;J->r zw$1R^$omWu+O{@`zlW)ExXZ~!SIbKYe(QVtb=(@aEly9Z8M;JbZzhJY}R=yNcw z&4>295PS^$cV&9~rFZtUmd*zk?WP*$_MM}=vXhI;+l3Myq$rL#d8N}nb6~IMo9d^x z_1ba^&2TA(xR-yG6lQZgHpC58&IOo4J(CNM1I7aPbti%;HEYbOJZbRE;e%&CZY+K> zSj@_@@TL)82dC5(lu8DiTa((y0+VORk@y{W-0~hUAg>Xr+puwYM_yTMU?0XGqw4i4 zri2DIdTC`sjx8PF^Em@I*m>lozU|1bGQbo0Q1Z=zMLSH%X4>cS@%<@f|2BO9+@k+0 znP^Bvr8&%%F7Xr2E*Y5}?VX%Yyty~>qo(o1ZxuaUN3U?u@dwA-Bh@cv)?}Bq5dI>8 zV>|o9&Z#hRHYBRkO}o92UZXtoHzDuc5L)PY{va(o zP`Y3`t5i$y{zJH#z+ zq=wmuXGR(i4U{zz);@k5g zf!)F87uryWMfkJ+#E*vs=6U3y_!ElttfO}w`Fk!2ThQPwn_>=_8ub^GQjC$CE5UlS zUz^e0Xdo&Z2JKAHjU`&4lkKN&nIVZuiSL-?YIp_6h!LNGUxQ8Inpag7)cBy zx9>c0yukOHgYHVo5T2ce9Dq$q&dDZnPKUgoZAN+AEZsj*i;Xd?Z4}R4gD_=t%R_Av zPp338nFsqh1s8p;f$bLv!{{xYUMdI?p-MJ6Zj{7rRWkzWVb6QWZVF${5`rja(IuJ^ zm{9|*I=A+9EPVV=f)kaT3hQqpKRj)0KsDJjc@i=;-_9GIj#YPJKaO@Z!N#+UD!d&n z9_P?HxWCFFZd4A-b15+Jst%IGOy>wixf}kb>J4ig4VpTA;9bs^eG<7>iZAM6)Vg-9 z5u|eemxJ72YpK$(Ko8dTRP=VLCc~Wqho{w^+woEaxjzIK;HI zlcNRr_|V&rtI|4KQIPuKM+o3P%(Q@< z9kywMt1-D#!#uf>8Zn^r`0OotF2%bNTVd9fdY?V+eUgoS*Vd0Q4b4Wd1c|NOZMn5^ zZ)ZPgQ&)6c3$OdeOWY;@;{siS7AHn|vZC2pI@g?7QjFn3##lc@XL5owPy{$s2Ez48 zkHr}mpn-Pm2h`4XWNeh=h~)4NR?NLGQp&II(q}yQbZ^D?^i$*gch>FOvzFIN7apT?sfNT0Y*Y(+Gukb4FU=yMG0>GGEaDv{ z(Tw%phoLHl5fxy)&7yA`h;A4mplyW&o3v=ROEf`WMNZC0a(dyiJ4lcR$8(19eF=rA zMb@aTKx$;S`LSOD9ESBBOxM1mdI#<>WNE?exxA{#8u#>^z_E&q~_yL zO#1-erf{M2T31u(?7-Pyjz*+&X9y~X=I_aV7Ri~=8sqLv3TT1z%g?^~Lo~!Dbjn2Rsq9z-zy!x5FP`v!Ozog6Vn&ePfl)O%Mr1U!S?T#T* zY-1XF-}Oc=-TDgr!Oj2xKd6Hy@0{PN?GrW_Q?T^A=mDC@fEb@6?;ta(;O{*~i6Rqr zvgsPTGMh4yDO41hpQVuGWmAtH!keO#-Yz`4+f|#)wS+^D?=YmF^i2C7d5O%0JVk-1 zDPAws!vVm$Xft9fzH5s$+IKTWTSL%4KNH;b?cR%0jHMZKguxPijswe;3XGtHg3byA z`%dYbj0W{@axx-;fb^bfgUQQ07rwvspf%vkqQ>BJ#j0hyobDk2qwpp{gK6a>rL&=`c}faZ)_FC-pJ<{@@d^G zu?zLX$g_`q^3^O$Mu369vg=I54qbE2yyAx(BK|b`K-$wj+OKcd6LK-_5r=++;M+7D zbS%sFHkgu%ciD2b&6h*|=)o8{rmtvketF7CTWLYlm~qRuhA+CgV(k#dxWjgAUZ$2VL* z&tWH^kGv@VxT3MVwU=C;4zg5!uDzMQ@A_YT?6FZ8;BLg_?4(nTK?yK#LDd+{o`07ajS7bCLzBqdxW^@rFLjbqwq^ zR~opK7RgV8V0}lYBCavKzB9zzrU>hpQ5nY+qDCU~2VW2ZKZa6$ijp5)dGg|7l4%7S zA*kGkDOefAX)^&0y2R&0mnN#W4~U6xF-g(~t2vsZyGQ?VS^I^93y1yMqhg~bI1-#L zMkI38V^6_*@>ZkoC$niJCkC2n=Cq){ksorM@s$|W^*^f{h`f+B|NmX5d!S%2B^WdV zBo4q!W)l#OY35uZJK4?**s%uHSK8aVw%>4I-Lr(<+FUvS-AO?r77h>&fJg~RHQ_1`yyE~!AZohQ8RPG*;tlpeX9p4#SoX8^EbWr63nMjU zBDM!zB0D;m(xuWD7vOOlq(-yoFzv;1a>A{$#WYF53sN67N{YJ5-Mb$cDi&(Hx0jp! zY%=-Qp@2UB+rQ>}%d`vxdH)JhsV|wF4ZxY5RkX{B7_WEfnF11Pw52l5gpHemsC@ZM+ z?~|4{?aFfzLag48#+TaUKHXr{2)-_wVRE7d- z5*?Xp=&0-dv`>U06J`|gE5@WlY7RcBdWl}~%zb?ie5kA9Jkc7js8)k*;N1&e0`CYR z3p-`MRP+uTHZCgIJV-#aH?d^n#FgjdYFRO0A&uAGIhCo`-i1&f61fQn@A1c-^ z*#Ee^=Spf)r*qYss10{+byF^i`(|tm{7#gcIsYsY+=KPvPxusZhbBu`OHz@IdpTk7 z%3lY6dA56oI4z#K%Q$fj=gFXdpcD`tXHJ83zz94obo9bJ>Lk2G_r=qxh@#8y zS@`=-ukZc@@9>cNo|n7rrGQsIHdiP&k(;Wd89w1r4Q1uU0&2t@!Gj)tPY= zYxk4ZA;9K$uZGd8Uz=*+(t70Yldw17LID%QTh*ef*|)Dr{CM1vId@11 z!Bu5KmF{AnN?i*_rn1aOarVo!BiO;wXfP-lXn!s9|Y`gr>eEWDjUwvee@Z)>Jx?p71B=bQJ(mdHy~9 zaTb$~nzC?iNubNNS9#!y;hf)#C*d6X+VD1R{6JJd? z(dR@J{7A3nf8ZW24-oERS(X-i3N){mZtsZ~((P4OuB>Cz0 z;5lLfAScJoX#AqVVm5X9ipC976;dviggp1l{VEbDbWq6DWEl@}uCbJcS8g zyTFdV)NaI#9`Rmx4%q|Qb@Do~y0A6uAg`m7I#o>dhc{kN=tcnk-D9pa>uA+neul*z9P3^oF(g`Vq)^=|2>0G;@}$I( zy%_(J;2*38fRsksoa7-Zy<;hP&8=IIWXkC02Sdd!f4C=Aa!cI%-I2fH5gkpVe%s3I z07u?-{WQ5w)%=A%*%$TM3s3r5EKlZc9&(}qR#C(sZ1un&(DC)h=!W4pSIYA}XTx`rdD3jL(F1e_CHm&~*4};?$YB%sD+YJgeQ=<`_m| zXm`HH>kqH5Pv~cq`r>-`pG2+P;AgI)Vd5q~Jc-p#nWeh<<*ADa$j(BKJu}n*2K8yW zz$L5bRR8NMny)xAoZY7p#x%W;p;W}&=Bu0UWzV{wadM#W$h&aUV}PGi*{f@Nrz}To zU%3xI#JMD$-s4f^u)0?T+B8mS_vL0Czm>u{D6X_mprYun8K_x)IVHXj!XqP_Ovtmw zly$4VWP;S+BkU>bqNpJ_XcwLgt@8dznY@$~`+aUx#3MnW{d3ZSX!$l3r^q5$ySwxJ3Zta_~ zck8f^%KKM@2k|7==y2`Wf@AAVnKEzgNO@!bJf63vO zc9UK9?ICGip1!LckEgQ{z3@qABcR7wC-oe6Y&Jy(>Fr`AUokTHJ zAW9nkc=+k?u9e_l-+Vb5_O9Bi00-AxrUX!bAoa%i9e4E!ISV8@Ea<;2^00j8EG?erHQ3C+g< z+uWVOrO{mdoW-$Wj*e7};Z`RS*F^3CgZfyi2miRp1H5R4FHJtrG4}XzH7610kI>T* zA?$1=2u^jAyOr6|o3hf!a_mB|z~{3Zzxsa4XyX5ZU-Z0Hs-A|Mm^Ok1^4Rx@2}t?Y z%jpX_{U^LwdVA6<;3or0)^=_QE9hH%Hz+@>C;3!)^Yo#$r`T%?QX}$we9u&nM>jlkM_v8Xt&u($jI-@L??#|IEgAe?DTb)n~{h zmTb8Aa(-iVI?i%n6}-?ZRFFi0xbgk0?6A}y;|McqVUdw^c&)CKOTA`HNZdL=S)yl- zE;)Vj5S2u98dR(P@z_%m1m`IWOnd!cm*jhJ}A3(F1c!T)(qdjOH^Ajo@wo4v8dY#F%0v6fM6LL;BhK|*v)0!x zD-zO?VWNBqi;7_x{rMO3p09a4@Ozu`*|!m7LV=#w5~4DVs&Nr2g?CCc?q5H#rvmFAsb+|9vghU;36X$pM+mKp4RgI4?U30bE z$Bd<%?b$22&aiu_BZQ2?hBhmS*$d^*Bs{M%nh(RTP1DE#3!7Uj;CO95(P3t#x+lr{ zK}OJZqMlh_i>!C{XyxvMq#k~GXg6d&-Ju4aasJIs+1^fSz0|R%>@_Q(PP z4xrfPzB`^h>%-qFVEDc^p#zf@yp<;cWkq6XlMF0_LV(YH)43)t$<9}`H^>dFi#`|T zZEr8>fBG+cv(A&IL@r4yd&n?l(#`&HrTk<7b3f-aKtukYR0btCJ&0I1H`)Ax4SX`c z5+(YNYkVOe5P!Wbcivtha}+hx0Bpii=>s?f5W(Q6Ew*0g`OUAk*jY$xMm0bu+m0L* zW<+{qs#R}v58M|B=_h{YEHB^BY|pE@SqruzeQRkzm+{J34{ui+iW`m;~B=H4IHD`{N-hP2mj zrqxPsX=Mcl=Sw_cNsSztbaW3uZI($(TQ+udLcW(k&T-B%pzUbyg_jH;+tq7jg1sM#SGJ9}N#k@D1%e+3)n?9PyXVk)qJC zpChzW{)Wo~iSu3wMXb1LZ~EjGd3w+kGe^`*m1GZrgd2V?Ye;Lm;oy{nDs~LM`->!?v zq_58I&rm+4S%F}R-DEA=!7=nsrWm^1y|eI(Jl#lqGhNoly>R36-1q=?w3b(LmOuV@ z_6sVnwHg4XYCpT{c}b2cn|?TgIr1`M2U*v} zUONXuE=I(<{gg8r-cncw()>XMK1CD8V|!LrWz+{4=fK-XBTpcQawm`^r%am4@AC&` z3K#9NtKQWbnU7to+X^5 zSbvYWMe~g#!?`CIDXn;~0Oydeddq)XHzkvMHRFlDJ?Dx|Rjk}hM28C%-K!VN)!Y2q zRqTlSX?Q<1saTiE)yS5XQuUn6bs?kY=WI}R+raJ2-v+-BqThLaSu_$62dwH;+!^1v z+|V%GhLUT~w{A`8P@}zuUNodxbOvpK7#X^18p1$9xC>5S7C(lX~83l`=OKRsL})JhRwh2V_(h z$+DzG+s>oVI~G&MK;+P=K;-@Vm00<6kF~&10d$9+`JN51D;9c2nkHUEM|&sI{u0!I zNvUzp;W1F`%Tl(4X6%UbRz<$|yWqplmVYmF7Ee1BtG8%_T5`CXXjnv(?(;4E^Gq=X z9Y2ZcoHK{{o=YA0IR(q)vCAVbjaYGvVGHf&9(;RIc#(`psxF%s_Ec#ln)n8PC;oZs z@v8;pyF3PIdj(#GjgQz*c+Om5YI#aGY&n_fb$$W=spVC+w_g@&4w($(+PMu}0tl_XGWT3Lk(%0+p6R{#zlVS%H|4YgwDM!f1$zNO3Qwe|CJ&-UD^%CvY_}<~ zs4Vwvgb|gUdFkL&19WbshAr2tDP0Ro*Gvb+xB;uoej2F^D3qDnHtc(KE`G8y-h&xs zZ0RYn6tV?5QDU5Z81)AVK6haVzBH!9T#U~RZ@|`mXO*#XV(Dh+^|ZC%$W^`cz{e?z z`&dcSq`io81|H))Z>O8965LB4ohmo9g+(A9pij9Flaq_dbQkdxmJnO12~Gb+-OLv6s7{a`lXnw#I_we@r(0Q zmO!#rC;gHpzHut5<=aU;tvQBZ7<&Gb(Z7-?UjN1~Rane>+*HnwaFe@6!LcIutzZ5$pE?pYmqs z2Y65}7!-)4&37Za77hJS*`pM7jfgNE)EtDnc{HH~%3oHt5$A#)S+tdfm>=%T*l)BN zQ&i0rbo7ErcW9EHp9{H+j0f}p3u`f;@ zJuT=c+v*6rLsM_P-7L&NJ&x87fn}Og`)dPot0x@LZ!;!m6I0(jDGDa<`74@Ts8Dae zYcwFBklLs)M7a3ki+@-m`2&B%OQa_wfSLd#+1COF@}{RA{5k^3{1pQy&z?@*X?2=c>*{`{p?*b)Xf{zLI*<(1Kkcg9h>E zGCItJ;p20FPzSt8l#bMvir%h6$Zjn5$W+N4axjT77;OH~>=V?F99ql(-DJy&CP zmeBQU!m+dq#rTkQomCpfanIS_uoYZ>^>|v&j^@+VbQtm=ii;%Ty1%wX*K1pUc0yD|oD} zQjPQzb$!>c9aB+4K{pJ+M}nu26_Zt?hLoX_3QyQ+RSP7F;%x%A!03*?BtjQQAXRjBVLsfgb%o>Y1vnV3B~${8lA zxAVyCQsZLbJMB*$2olO0%vxO3a5P6TFrEE==E6U&VWAig6Wei|Y_3w@gOS}1Vp`_) z@_31Mn~BOGqd!==(cKvA8a$K?ny_zi(xl1{0bX$Y1*Fr^~=^paFTCkXa;Dcgo>1;@{YuRrrt8u?jUxt%!BP=}npbZu;TT}C1)i+;$E zW4Xuwhd6!}`WHchpO@F3IS+kx@=?@+u3yb`MCw0estqdeqxa`9%C^cwOywkhF@97t|4cq zjYy+Qu@Uw~W<@JK1)Xr;wA^K2@kzT*6ko_rIUvvgX(RZVIL+Q$Wb0DY_1xlMjfK0h^C4^+lPboG+2 zAcoDRaJzc57cHouIhiOm1!!ymYqYtP%^#MP38GI|%u&t+j?&+-K#^y}DnKIrZC1im zw2J!X`*{1eZidTObM^&{c3+iLY0P@t#kvz<)(EKT^b~Ba%tJQIGwD*hQAL)XyuF1^ zXREBaszN!R>gPL()uh*_eA&45d|k>5tg510V7-xk3x|hWDe(r6OaYU;wA%#z?SO*QlpI1t%UOjMS5TQ40@ERWE`(UkSbMj6%*~l(DFV?xF~4p6eK-{O8Z*n?<|rUvO10o zHvokYOdcK6Uq152;I_m|S0NtJHQOIY@3it1*5K2Unl`hWAAT@RNFeqV>=<2FIs+=m zT3;_0%H9B*#y6h#O#LQSa^%R*ZfPh<_oDJ=Z150fZVpNX_2nx%q;t_KkL*-=v2PnN z@lWe!&qT&ayo9+z56;WjOodZ>7DN)9-jLZ%%S@U>wyMvqg)EvSr@R^yz25oc=up+A zqCW0j{CFodfb_ssCaQQbys_0&(!<6vB6DAUM7*PhO~C{ z3wrVqU`UlV&hfXtw?J-#f7O=2o6#dN9m=x>Pb)D|^0}qsZ+AtV$u^1^4r=2l2|ILy zL{8@tBgfvzXxa*dcAdl&zb0De{Y9QQQ%$|#+g&qDf&t)A+ly_t@P%T@40IOheDfvs ztTH0YC+uJ|;gBYs0a123VqKaZ#vTd!^vna**5ukMSR|@vPcw7gq(G##qV&k@cr<*T z0QZlU<#mMD-3KN3$B*xut`r1yBd;rPd}iL;E19rDaX54Sg>-2+4*ZoAG?8+*++Zi7?$d4w`%hTrlO z(u7_Z=e)jL?f{~5`{-)Zwda)ywgN3AsoCv16eArgfgQX8b%_Z$>|bYG11_i^z8*B) zxpWli+QPI*aDnJ|?-duh(|pJUrb!SWgOZs@Hc920FVponTgBILoXD-OKV#%V2iKyu z4BAS9);4O6xF^@DoiLsaxE!|`mD&K9-Gq6zi&a-z;g;o1y)d_ozZI1W3b>Ei{;+Ti zN2r=qrCCn8rVk4@WDI0aK2_ljsx6%CfwFh^amm)jS0+Wiq}&*-1IGq9%2)5p-HI1< z|7!e_r6m*=yzVdD*{&?*Xjs`WnpNu<9I^wZWqAKOb9&Gi$ByuD%nTZxfPdk96RJPD z0)3!@zi#{ub_U35VBb<$u6ED1#885=c(=V~&#%{Ta=2|+psw7Fa#)?@xGzD!U*v3L z_~{w`+muLl9jG9Mzk@otTwLfui?7d}T5iy%Xp`4{H|C!w6*UmCA@`hqNzdu@zd!XH z+>m3%JZsyz^n17S=)J4eUg?i-ysnZb!cN%)gBc&1o`%d<)Y7jUdt2im)#a3deOLPG zK;Kiq+K!F+E-A5X>7F)bU|mXnHztmqc&%WkPok7W%!8x620|g1E;9KImYc5lkVX9i#tHQ!WQ+tv{h?vz#U8EcSpGOl|IA+(ncxkDzchVyU+u~PCx)O*?<(iovX zN!X+eb>=<(>tw1|i-z8BaKHU3Q7>%hwF42O;wAJKz8)237Uwj39-%y6JljYUuAJ^L zU+I5Ih)WdM@Q`!6erIy} z2<;zcFC}i(4Pi))b?#&Q%+(XrL6mOmYcK8WBiT+oY;h#0vhLpw7@(!;Cptu5`cCHi zTg?mC`JsaiCWYO2A1TKhI@QkvO(H%leTZiLxn?Uz9_e^T1<#k{kdlrU(VrP<7TAML z?>5{KkzTmsHpXoke%TU7wO`$J^CT@vC8;mUOoBSfs3?*dMdNeo!5@|jmWn9$i}BX3 z({eFXP&arZ%^QN2J2#>x3`j;S(?xebS|bUsGS?pg^!WN7_+tv2FNU<73@@}s{?vFd zdRUma#-05j#RpDhD?34Z&Ipm^nX*&^vRu0ldzeDZ7hZ+sf#c;;XmJ9h`JKdUo}SUi zNN=rROIP=AGn;$k>%LY{whvBgrtYY0rVmtMCMq27gr>E+cnjFr~lz@Z<+=$Zp$H_QEi- z2EDIO=gJ~|qVmo2`r7N(?V*ue%VKOw0~azG`@qXoF&h$)9)lg|Us$(noC;5*+|`zh zoC%kM#3HM-+5(XzNSnmKC1_IAJ4Z@YSq2?$e1 zBa{)w1PDg}eL{7;4OI>`gqx0?wo=}7o|IBAxLShk)lRMHXV6bm{j$}jnzJl2I?13) zS8#sGGECNL7Pt50VM@;2eW}~KF(Gr_1LL0cFL6rQY-sz=bm?J)aMrqIf2~qHfEpv> z4BRaeD4Pb;-z!)ou2p9?vZpl6=9fF3;dxPH3jMqwT5qx=;UUup0aOKOPz3x?Uq*BI z0Ll&{hFrIITAFz13TsB*vQ67XF5pomeHW8RrIk*Y{>j^QVdZDCfe^A+CuHjgevN^+ zleFO;w|ja03mJsIx>-u%biJBS@Ljb+uRkn=lD%mq?Zy+r=@OlR4sYPqRPRGB z3M}r)AC}*vDf%6tQxM+*y;GCMaoUMSmut*9VjFI7bIhhQB>YArgG8Z|dk>t3^Nr>r z0PUIFFEUSTp6@3;lpfxb2Bq;O5TzR1?4B+P%n>3W%+A?!OJvRs9^Rmw+?pTH@c5&E zpDs5NCg$cq_O?fgqZ5UCUzx{^r+zR`m3r9=ZRNMGs&Nb*0HfYIQvd@L)?sCCVyvyE zgRdEO4c=w$mi7T*SmL2-oN8Whk^KmTV$mh5dr}740^xz;-#B(wiR)!i=Ld{+9NiJMBn%<}1nkoamFTPondTT>?@g?xx z6;{PfXBS?dVRnQ6cWn%f^Gb#N6pqEL6Il^MPg82ur`@PRF(kvWox_6z`5K&sccs2U z+xn6kIZ&(KulT=VH9!6|HJRL3(znEq*k97b1^BIxslTWg9pPN-Ii-`9YiJNU|AM#? zbn_!z!gzP0m+ynrx7sykk6l9!AK$*S=kD(xuG3M({6r(PH)iG+tbbL#D1hVLv}sZ2 zb(?~*%o^)N{A-h$$qHm6T_FR02Z93FInu9}GCkGV`)tKZ%gt(?d7TT_P;*m*$Kiz6 z5oc{T`63sH9;RwneO-KQiqSR?RqIUJ-iU;Kk3Wa};$H&or?Vvz1a985R$|}Jq;$=t zP)b8jekJxD)IL5=Y>=VpAX^$A3eXJW$8J;}nZ%BEG!xvbAFVF~bl3o(TF`4QzS^Fram9*ULbQ;DKskJQ?09bCFv5)VJi;o^Rh>fOAjIWJb= zC1OFhec6s$(nV7BMx_WI`{(nAO%Ay$vx(oUsKv~=%P~n6WQbgN{IDWA=2vSrL3Zp4 zv7c;}+0?rlVbm!fx9zv!B9I`JmLeVuNv_@#UvCO>u#Fg0YIOgF$$DrVXDMJovp$8Y z9E}>M<~?UfDX<+E>U%7ebXd|(Q`QHOmB`mN=PZJ(Qg^tTRZ#jBHo>4I1#&`a6(>h( zID=IzXS3{GB<2r`lE?f$Z+MBgJ7la%F{|#LtK(|pTlDurS9nLhWSE<5UhnT8vLbVxxS3wtN8Jeqr5;780$Fq0>*wE7cwT= z9A_XG8eTckG?_hhM%@uAoQ~cPpQ2B`;dzo0w^lkzuHNSZNFB1=*)HXhy!s>^*{GbM^gsYU z`@>JJ9ztkj;E>O(M{O$09FtH-W85ni$LR!B&?5Mb+;F(-QYQ8*wb`mz2)-|lnRDVz znHKwCkL=SoqZd@c=u(98`~{tVgrlZ~INN%oBiSmd*}l5}t(J*10^5Tl!pK&0{JE9+ z))LS<(UWsE%2i)#waeFTBW7*Jj_ukAz{6`+WeSyk2vU2LPK%Sjsf2~v(?h)a>>i!kckf{1V z0&<%gRh)+xh5Dsz-yGkc^nP;_F|bc>bz4bcTqWS%ph77@H|xo)Uyhu46o-1;D1OGU z?vG9B53WtOBd(JxXisp|dfq0!;XziAEqCkS`<;->$)pkf{U!Mj}M}-4iJNu{w-!fB@gmq_RZsm}#1{<|d9UkUp zxE6ehYX-P3yU@EU%5+Co2 z=KF@0(v7noq_H)9NaHix1aUJgVyJe`#%`sRc?XCUd2n5%tTx)W`y{_F2oQeaVRVCe z)zcQ}H@;}Tw=3PWNE99?AoY6+eF6rac@OzJ8PrUzdNAol2+es5-bzJu(Kno3*M3@| zo^LUEQ!Zm%C+tS+V+EaTsx>1L9hjFMj`4vcfqy-HtRRY=E*jBXZ3M%Txjy%yv0fM> zV5J-=AhGXH?zuNLwuQuKwHc8)7VyQ$Gn1Q=*);yITjlqEpQLp(Jhb08lhT}&yA5yD zknqa>RrXJ~x09ILGgHnqy*%Ml<6hpNs-FOgjCuJV6bXn&02JA&G<{UkNr?l>u%ll6 zVX;I0m)^7-p33$Ez~6IQ!d}YH}+6= zsfQU1BoqWp4;6oIL76P}RmJ<#1-|B>lM)^9~#>Edi7TzNMegHdP? zFBCUEFsq&Yn87yYl~9byht8wEXy8-js!(f>pP_7On%aGspy&W1kBH40vmYr*NS*R^ zCkEYC|3!7PRMs|boUUWE+aW^-fuMDcP^UL}mK6=Cjk-T970?y(bFqE({6iwbQMkt_ zkL0|1G7#q3C6xn>NY;o(`Mt?IuH;PoNK%b9!(B5x>%qM6ae2&0+b`vxgwP}vz0Y4S z9v_ypEEtvTQG|w+>@ALXya|SWWw65oiGy~Jy8So6+Pm(ZII5)F&$b{h4` zt%b}5%Fz(Mh0IH+@r^c&LAQO~Xz^8D%w+SdvWp9{Nr?dTgkxJ64Qj%^oh1F{X}M?E z=q}iS)qX~# z3NCg&xc?tw9{d+#wvM=7Xgn}HdY*@sSiljr{tsm~1o8+Cf!j1fJNw|i3M(i4_kHS= zH~iFyY$|LsRwX_r5x%DolO8+PqOCZtfpgg^4+^!@h%~M~x_U|Lwz8QI_DMWkeVmZg zW1oXvoMfw@7hrqQw{!7)btt;8vG{xvt(NH}-b0s~46hgZJN|6rnvgJZf@>;wCy9!z z*N^F$WbnqDR!F#|NOcks2B+!31EHUo6*^&XZ?Ki4MoSky+?hQ3u#rNJ(w_N;(-@vE z_SeARa-)@cefW`<$#e2YFtN{jK8UCOMuO#vnW`b_@129OFPMuNV@dnxGKUYZ?x*xG zt5$Zlf_7qdf)s$%-4{xh_c7j#rAimoo{M9Z1?KFT@oh6EZAdS|G01s)qc<|%yHZw7 z$QP_fj{XR9O)&L5E-q2rzk&=#92PkpVL^D*)3J;_#}75CxwgHkQGt(gr+6Th1E3^X z8Hv%mhpaUvJ|x`7V#KR#aYWhd@4?+`Yp<5NWP+Kj zAEmm5!o#~$Pw=SV1Iifvkz;N&M)vd9yd#e;dZEbv^O~yB#VTxQPkl99iZpoy9YQ(= z%J+~W3JJE7q$co|5~#c3JU^?^w4HBq3uO@C4ZBV&QuLtfZ z|MY%jL|(BRt8;1TVYbWmXjG)Rm zqCNF=PCy~dyXEDL0S|arS}UfTDz1esn}01kMmFGf#gDy}ZWpbdL@h@ZTRKm`TCHq| z{qfDR@BSl{FojujjPfJr^;;Q^?XlEq2-_MAi0uBuQg!C0PS&-p4z~`slub`S9j;rc zYh^wrsL^z)22LCxoy}{51pE7hZ4r5#1ow7|2~6|;hWjUuv72M7(EU4b%Z{b-xXQ&S zp9aWO(tXM+rb3{YlFt>8(VYb`~WWv6ghc_?mRvZa5Gh;sRHU?8`_!HD_ z{D-BfaDTSVbY=7I4&;ZJ=HYKui9`K`r4WXcbSyI9Ovuv#?DQX&esTIZwK-QfG)m>s z8&1-FFEQI&%+|Dx69p)1#s%{QHBPD<8X1T}UeXo!R`?Y*WwzhWV5k-P>mqo(SobgxxS zKT13|`ITS&AJ&3+d;_Ar%lP?(;AD@VwKY87{x{^9`iXhMW%hs(l?$jK7LnBRIG_%E zhVc8a7J_V|=P-Gpyq2)QTYGlPiK&`CFIW!kX7qsmt|R|3m%%)!tL2#<7o@mz*X^ej zZM|`V@Tj3O_;auB$>!C5`6rk%onB0jNE0bw+9RxyO%NCO={12 zRa1A6z_UwqdWPYUP&(=Qhu#m(7(sZH5)t=C8%}dCNJr#D+L1z_=>)D^tx7TL7>a3a z0!Yd_r+1rczQ-NdElCB1BAcaPaRU@+T^&3%V4RX7-owePZCXb z^{d9H#mw}9fXN&3HKwT))JLPQLK3Urow%u8s~ z%~L493gr{oEBXa8mF#;^LHA2K&6SJ1rSYm9fq5Gy2NqigjUraLlY940Gj=SGbCb~Z zs;kB+nezfU@UV#tr-sif{`6UyR}eMk6elYqsk;<@ZH)-FQq^n>XgBj=4-_)v*V$pk zsgSp9@6ntdz=?5hj@{Gmj`%@9GVmF^6)TA^R$wH^MxLI3fA0c-wd*A)>c8)C53zl6Rr2LT~ zJ1<8M>Y+4mvyknx=^_x)`M*H@I92f6uUXEdw%MzJ|Z2J10VMD zYqbr_@6{v9WKM%0B|2{(>Oq@lSb{xk;qnXxf4`@hx-l&ZU>Q1F{jJZsmNAQZlYlAx zGJ<255;X^@UT5AxX^zc*cpdAy6`43vzhWmSDwt<@&-&`t{d7p%yNEdKkA@@6bTNGREG(Ls^#NcQ3 zR&xM9@&{k>JZ@}z8*h~!2ymQ5R0I$Cc}f*QS8tG2yYL#ZtLy2PgA*!7FiX>DlReKmc4`6*=ks zs}Gdp^8V8EX$!B8$~QH94+~9#p~iTbUDaMifyiA#PBww$1^MUTw*u(Yq%*#-&MJYE zqxF}<*yOjT`1LzSuItzZv>T+7p5*Tss+qbXbE3?jQ8%|AzP?jxJL8CQj>Vgt9NunV zBT(XWYY%IDu4+n^d|Abl#I|0&34R2hpHPEX);ccwiF(zg71PxcKILsr-zb0Dw_IY1 zhB?N`G!%y{AgKgp#i$f%`Fq`^^vf0deD3#40xXAPrX!BNczl{&v-;Y#53}`oXHoM@ zWcJG0sd1#fr4GzRiG9;gt4;6jK*o$yW#OsSCY}Sqb6B%FOdOtMNJyxj^x zT7Ms2IpmhO{0yGbxW9byh@#&~R}lSQ)ZHkynz7Qee|8g1hHPd5!7qBdlgv(4V0{B* z4c4P2?&9R6l{`y9@|LXtuHt(5Lnf@s&FkXgG}o`mh1j5z*xxq^M!SDl+BnxwtcdGU zl}44FDONG}k%+*6Q8S(e4_8P>g#!Ean+?&Fu7>(wd`sXNfzWZn_p~FW@f{vyGRFAp zHy32Z6y6l&n-Y!UA7z^b&pZ!N;^Mvh>%kC9Qx1jA@Z zN^Z1%vV~<7yO|(9KClqBi5{nmHENcJS>+|Z0K`o`=5K9+$OnbU^V=$6CAZCrjEJeFK9@0*4oL4?9Bg7_`-7ES%yEO|G;f@AcxXUy3PB%?Cwthw%wN~s2_-`r5#4Oh=Kx++NAH+EL+ z=>%Uz5uSGglT{@Y3)$mdApJG}{;9j9RcfSbbFBykivY zx~X3`dJpK7s2{{B%}hR>^u~V<&!eLsxDAkbLm35ea&%SN;OQ~umTKtIhJm8hHq)3M zQ@{2v=rQs?>;|AZ5hmZIniYV+8ANDMA@+qT5tOk=4FqaBO%udCp|vvByP*Z* z%IO6O5QBdQ&uvH1so?&+m>pgbz1<4`NMkM!(B+p`R(tN*G;<3JGVq2-^6(gW>!=BM zQeQx5VP=w~?4xY`RL)zKiDGR8D*Zi`j&jMW!!9VDn*Xp6n=6ZBn=)@5=t0UWEkgWFjJ)}|80 zndK*F(2|xGzVHu=GWhJOKa^K?thOJR%yasLS-1=|SR~=)jj6>t9T_=*FqQp*=_&jMTz~~Yjk2P7&~5C34V>d z)WPHq>GrE`Kko~-Rkmx0oT10QrMQ3hcu-QUzpa);D82bfFIde$sQgU=kgC3@qLI(B z2@>-aa4>_H7Qck@HV`lu`$R8x!uz^oba1x?rRqw14bOACCM)b8ihIaSWrYI8)l*59 zY0~o4bYK;Dn?CPfCRkf~ol!9Y-z2=NH%GR=%3#%dW#-3O6_&;d zYAj+(xwmi}OnzT=ozwxSNllG@x~U20HS+eXI9pOXimNjOVc;{r<^q(W6@G8X@54&J zPFG}E>AmsSK?yn}7#y`OlueOko@Mr>-y`>RPBh5nG+&J5J8e6b{O29A0;6NKM+HK(k~%g$4at^T3`A`*UA zw?v*YyS@gwhp;B+RxY48)k;b1Ier#yH?)WEt!7rF+3E+UXz+8iaYU!zC7+3F&h2Bw zI0L2&*UrALNc62^ocO-OH}nb$`Htw_BZp`&_%WOSk6%36F;KL@x?+pXAI1fQeDP|F zk?fP+Zk(u>nRte5Zz}`##-G8h;1m0@5>aI{m^ZE}hWKq_VmQh{-(ryVs$P?HO*q@3hreR!ypIdAi%%`eovn)<|th&%AzwsHql3ts^4n}aC4vI_FYATzZ82GTeZD&wp6HB_pLma zE<~$1nvU*hIN9o}(Q_I~Pv;#yb~Z|EG}wNKPDaPV4`QZu!tXzi>U?Hg2l^s)Sgm+9 zy!r@8!8kZEkz$1ml^)zT+Unt+lBjqJ*&(kPD@3UvJ6j6>u|FPu;_3l;xDY5@dsvb= z{KB7~(Y9wHe-HNhE#W4msW=d1@^Rw`;;#Sv4~v2Iz$Y(X#G{O@nn}vW8=}aLzGZs< z5HUG3_QPgiVVqlK<-@i9leP^Q?5Jy5WYhc587-Vn?XHwln@w`u#cO^-x}6C!uGco% zI59nJT$`w`4)q`SMo2#qyz-(v?mm4s`TE1eQVvEDLNu^&t(tkOBDh4TVgOa{?hS%d zpljgiDeXP!QSKGc^~06H12X(o_PS+4`{D*n*$Lk=zlmu6GCdd@*SVeF1h?F3(#dwa z>^VkkPSN~URzWH0IaCvMVJxj}=ovt=Ko#&!TIR-VrFVu>)Pcg}!ZTfdT->0_r4Dng zhk|l`hPB5`TeThTy`(K8+5>9g+{~||0->=68KD}?=&;($u46+lkXPdgXc#;Eun>h( zj0cK7y6u^=@$;Df&!qp;EHaq~TKcPqZ z4s;0^)?^B&xE+$_=g7Bx_zM=Kf1m|VyDDx)4d(aa>251nN;avP9vzGD;|@C6mKfYF zm=)Pcr8TOq_3f1`ZRlrMX6}4fq~l~+YkPCX7}20D&YKRBo1!D|!^6+;GX6!=bNjDryD*YsJ=(&nr@{WS{BSJ$!De za&v*Y41Mn_kHGZC7`F4ak5;}ID75(D%)cWrdI1K7owfi+#2);5hSW8Ma_(M{EE zG}aJbk+_Kh%k!>Rw~@2z?tfTj73R)&!jGKrN=ZDHpOq3T*#jR+s_^w z`v59elnr}zzWAt6sgrqUsDW>uZ-*0lqziwQ*Y-MrhIv__T(0t$wHDyYTvZ9&E!80; zYFyD}(aqVq(X;m(ypsVD37Q0w9fhbd3+nSIj_K{)j<9=|D*`7sKI~QHjhQX8A0HKE zT@`)pJ}Yi=5gnMY(uTnlZ)x zNi6|{vYO!Q)lsv(gydxPl-SMJ`vyfNfkyo8^)`Y9n3-aWk|3M;r0rean!p_3-ZsvmZ&y002r_LACWb2~F1*AvR07h;kH~Sp;FWYD<<~|6u zIB->IgjIMA02aaWwYk7YJF#B?7TNaC@??6utNyx8FRTvN>|?ug3Thpg;SKc{jLCrf~z?VytXsqgK zTQ*oI5h#jd1CX2VAzk9B2_=B{v9#4~Ojv_Q5mcf&AVykb?h(iKf&p{2cpw-69DZa@ z*aS!D${v3iyo9UMHH+Z3K{Xo#odLvlkTbsO-8eF$r*u6m^s<^pl_O)8M2AjGOK-_f zOG#}mgb)8sxZKGZtZ{nWl-td0I_;n=CV&6jCmP>d9&a}rUtGIo@Kj&apwoJ}7D}%# z-X=YjCW+udM_oTi(^(kR^VZ)x)p)wiR&@w=aD>LyMQo`Wmuei9@O6L$RPJT0%2_LP zrY)BC+8Ht-xq%WNNZ)ea)~AtMEFTsMIMTF&`UU^4+f@M664!A~)4FXF))SiX?7~%rp9D)TAhrsvb=_ne ziiePC@X(d9E{hw!E1?EDJTPyur+-)!0L?{JDE%8u`F~-C8izHTfh|EGVTnWke*ne< zU{c}^_8F$eh~2oL_|o#BKmzv7k@K3mDufi%Qkcd%m-L<~MfDdG8B%$a$3Ph<>q_Mu z7^*zyVgELn^B55sRxDo8lJ zlcsah{$b;?U8P`YhH3n}(ug2M#7%N&%a%XvN}IpSyx@<{v+Er}1MXR8EkJoUSGYeX zsesSH{<&{VH^?uvAx`)4w>NgI>>Wj|%l`Db!8SOwqj1(#Gesk~?%WDcbeUwaE;~`0 zAbuQs5jA6d!n-OzZ98utHN4P|G3`mu@nFo`!mRnwnE_nnu6neCPl`aPPd1dpi?utt z=9~9dWR(9(@FAb7l|IP+O^=ND)D0S;e{h|w|E`!>Anf`W=-8?$>1Rn#hO(-c+bm{dFd}&|5aR<5&qXt=i}2PDRnc%}16wK=xQ} zn{a&sS9#R+>Q5iii%hf{{dU6XZX~@xPnxlfk=96Q)>VP$$*)Ij23*>Lz0;lxUZ{$c zSsqKcan=%dvC~#izIDiUzkV|&um*3-TnF5@bs>=mb+(|s>Q zrpsAC&i!F=Jfxz?j}-KI5vr-r%Enw?j>5hA_}b3}SV+H$=bZR{fX@ zu%c`xs_>Iycr2(Fk?igHjDh*Des= zrMYn`N7R!Wf{13pNu7bJbo$^;Db+nZ3FKV&>oD! zBrP5v3LRL|eHryZVxS0Ql_|Q!a!S5VF=dumC%e6{N#<0FIhqal53i3szqg2f03qIw zr5Vx{fuiRg?LRETzY7mEcU|F2dxVv|&8%xcXHzEC1-bq|aYu}cb*HHq#}h(A%G>#U z#>HMmj938-vOTPLb-R?WRzc`Ha6C?e6P`3}9pm`XOA3Q)md;vkOLT1EB+QN)mj3tf zJQ)8kvE}g1HkcQm+&K+Tnr$@35{2MJYA$s5HL%q)c=%^8DYsd7N>8vALcLuLWR!7wrL~fs$Y0Cv4 zTdhk>E~(V?Ss4vAfW4C}&tKVIvS(pv{b4D{+eZRY_?ZQT0pD})PF(QyxUsGTx`wm0 z8Zik9n?QS;*0TAJuV*}nMAjRc1S%X&Wce4H7cZ1osM!J1mLhE-P$fP>UN0o3{jL(N zbrXXyYfFK3Lx`dJ+k3p&IpAl0{X!B~o|^s#g8t8LDveT?HWOQNw!wP5cbrp(Ua1;Z zQYSMdo4y z;AA6nI!;QIB86CV79t8X!p1B$b${8nq4bN--~+3+CI-K3U3ESG%2 z+Q}IaZJc+m6u$cTn60mppFOG&byRq+U$p>CvCPYB;tsMsbx zd$Tr?5xXW=Pxa-B3*6Z-jlKPJT2)*nkse^!#lSBNXMEj)Gy)=Q!?GdKWSJu*d0iZr z%JebN{G?(V?(nvo@WYcM+x8n?K3Se+VJ!dZ_OQXfB>>rRn$1N1Tj3sk^e?!^iv7B(|?IXZdcHlKsv8p*cL?WA^zdFV0rBPdsIv-1JZLdKlR2F%7#= zJ}kR|DHWZm*_qg1W+Y3an0XiO1n%gn8eyEbdy;6+f{?MK6|q1=q)Z=3WY|N`_FG{Z z`XI@%?P6Xh@LalMgSIE$vKpVja@v;`O{<5hDxWp;$Xh20Eu7}&PpdjI`{&H%syLnI z%LeY|=}l}jb&4RBLo~GXYKOo*G%>#*v>c59-4yV-%RGFt(vv@=&LLj<8%+~scBi~a zN5M!jVZa6Zwmt#zTRxHYHM&FJXbWO$37oi2b=IbJum+yQ>LJqo%ohbey;vYbV zztdiFc5DqBt_?~!yujC*ekX-Avxk?6haX|Bf%8iM9VOa~3^kmRYVY`TrrG?4&SXn0 z5H_D6UShjHc#tJ-5L!zHaXSkLQ{JGSPTg_|~%i=585(k;27H;{(_57tPW;e<4wKMChTpd=I ze715Ndes{yvJd~ytTTSUFEEW}g0R|Ubfnu#1Owf=ET${hNdIA@W{wO~$V~e1RdAH& zE9d?!*w)~DZ@yvHwd`W)9J7*H$~Qn}6PAY@N6n)c_sq?>W)%RM1Oq^x|BWVTWCK&u0woq8XQ)ULIP?&H!8fHH zE@}B>g5#XnDMU$L<#|FUYWGQ>mTMvT%2WFzQd#Yz1G!sFD8$28GW+ZLRk2~{+Pwp7 z(S6gBfQjV_Gfc*@9?cSQk<1GCKd)&!c^3QROg+_0IO%x&JMU}pYD+_O`!c4^EUb)% z3*@VK_*z^p_Pkg$^ zkz=6VY5d-M^NZufJu#P^|Cx{I0iA1A?f;PFJh*>p6e7 z<8`9e-0fdI_b4nc^oI5cbgqpBExH8c=)|a~L{moV|n}z#9qaa{my6HBIJE#z+IV*2`xw_;)nI3M4 z9Lt^uLQU3NydL^t9bknqsS2ich~*;mnB(-m;sUde(-|d=kBPA)Z+lJ4PR+^!;}XWX zII0irPx+PA7)GE~QCGr}ymvZ6>ex=Km+98CL?3)zcPY=F3u-LOm5&ZJoY@w!*|_~p z8LDjy+c=F8`AeY;f`O`j%nW%C~=5YG(Vp0az(I_7cuZ!aY zd`Yp8pSWEkU}o9Pkk6vSNV_cFF=3y7Rw z!VwSX5;`Z-{1z6_&keG~XO$C}DHziVsk7y2MLm>~miX*?-H(5N26J;6ejBAdsz!8e z@Tj$7T8gb-u(q&I=c@To$<+{Ja>%*{WSIQ)o%Sb)v8PEN|3odJlWV2^ut-kQW)}?Z zP{iaQR38IWVk}m#dse3L$pGfWRDCMJxFOZRho@LoZPqS$XKGC1{`9SV%Hi5UahepT zdE2l055;S(dfM?)mK~hnqx{|=%4~o7tR#|dV%EPtBymX_Maun+1XF$i0el5KfmKb{ z=vcJv{6ODxN5=9*$huznwrhm;hl>>EYG%ehi0Ox69BTY4ps%5rX6uJ-bs9DJ_rIJ+ z>hsL^H4(#DG~+$+9r-{a7BPi<_xeI2&VSUCd7j`#p%}$VCIiZUzg2L=8bti9nVUJ( z)aQujE~Fi%;@;)th7w;g`EZBYJTcx}iboO)Nj{%~#OX?(EO)=^YE?zu!VT-PoIpRc zy!6uFE}IMgl|7RoR#o+ow%Jl|;GMa9>Y#AOyunbb;_0+|#p%&YzVcmCJ;*9? zDA(oIO8E1H>P0DzE7V$=nz^l(BGA(g3qF!nOx&U+$1r5=nMEgJ$3Y3CcM**bNqR~K zApy$2goyH2*%0k;%C~5Vt=S`JJT-rJ|6lSL>cp85V-)NL&2RbH1WR8!RpbE^;roRfv&juQ;RLIZYV;@e9!P^7*gtDd$6Ryz?0NvKdTce% zaYjqugW4cP{A;TgRL#Em{Pyq^<(mP24PqJtR#G{o`(^`v+CIqB6W!rY&6MdzHd<6#^Tg)Bnq<5W zN)?N3R~mJ~!>dd`iHwSdzi~(gj@$DHe(V@j3qMDzDAB>pTUX~uUV^A#?Ya^d1I3FQ zXYiLSr|v|h3KG)Cnh(P?&6M%{I*N{dBq{wTWoVk?_OLb*&$Zq;(@@$8;xxt2m_4Ou zYXWVzUDL;A+VJ^}`o7*_`5_9whDP9g#9M$meu{};C%DIKXjmpVOB7!+48HdE&@plb zp-Wb46ci1uQq|d~s!C!LS?Z~@Y5r5fugkY3)6gPaYe0~#?svn}31&9)1^|TwjlWobc8)^D@H)8P4>D6-*ViJmwCsy%0uq|VB&mHNi+F{?Zv9Be3 z=?x}Jtx9TR7k_%M^)d0R%m4nmyvs`GMm0SY2qhse`ex~d3=c1u?Z2!K5gy8lQqOg3YfHWX6icE6>IQ`6=* z6fog$=TTc8euW89f_#|EvllR@@jp&v*CcYycVjlE1FpstD}cg#oZvs(Vds8UyIF6i zKi;U{jJwmBv<^g+1!{rXZMYRJC#D{qS0sNSg;||aFnG^$aH^hz6CpHmsM{qi;}GZP zV7DiC?>KB78bg-Ztj&d9uBvnRHG$ZoztM)YvHemQO*S7H4ia&a0WR@e8OEV4po%+t ziMru5Kk(kjS6>|%3&RMJ#uLo7+K;`+5)bC?F)OW)F|k(~K68nj*J5M1zF`~f=Lz*= z2|p1q)v)_l^hd5+(<_JsLWgVublo@Dt5pUF-UosU;e3iu3`(aB=o>cI(~?6$-5E}^ z0XYi(MwQQ7l>e|0&A_|=gGk=}H~5*R=u-LjQQ&fEB=QZg#=ZV%cb3`7FY#X)OeL&i zNs0>iO?-;3mIBT;uUaMLpf~gWpdDj5`yJS%k=)ia-NaIBB5#Q{MQRAnAD*td;^9Ax zsu}G)_y1J&oncMo-?~_kzoIfKMM2Ccib%_-6qOPc0f7OeOAs=Gv;aYRPi%lv1qB2I zq}Px^2!VtoN>>a>2?!wsg7lJ56G-vynRCv)=YHS=$wQv~gx_9!t#`eJM+^%vZQ~KE zma|XyRoYmG($=p6t|%{X%{PqOOT1|uXwJ@`bllNfwd-uZ zCC~2n@rdwZIc!Wq;(XX)J5(Y=>U4ZHcv87GH59y=>%%2IYDzjG>@{?J9bHpJi3Lyx>5#D$!+;<3-fYgJwi)S6(3#QA;gFNt6O5I}aR^G7xu za>EsG$-F`%ef?b_1}?AZO3$Nf#+1iE{{XBODU2tPHh|hPAyY!2h{rq)*_+BCAL0RI z7GDBsueYg_(8CL|nX^a|BR%=Jf-x57pZA2koYSlz0gvwLaBUmjfxmt88ao2VrRULr zo(Fy8)}d;|Y;z1;4^4t(q%to(h>~r z+v&TIF?k=;NklSvtT^Cw;~SOgm}kfeah1(A=9;*uc`CY4QQ10PHYtwZOHJ3Y=4C6b|$TsP}EtG8!1nR z=o6clODKCDhDG(8yP4$dZJRHf4!Q9}-Z}mg{yXxDkp zCfLc_3ggor#7JIrwZj+G!9$pSw-H>Zp`8b`K|KE`yG=+yu1?=?t# z2tZp$TkKK8wxWL1yCt7(d04{0$;iRT-`I8OkOwO={nouk8E#daM>wr5$2frxQB13$ zv0kmaW}}9k^sfc$y%hLNqi9aIhFUK1AQSVEMb&!bB#pMyUpNM{&&nv0W&CxoCi2^=4lDI*8?<_CQzuKG67n?YeXMYVs*79+*AE z-BT)6Ipq{a9zmpaw@=Up2yY1TC?W7?&Gj{LWFPt-6BNXdR!dmKapA&+- znZ(C2#3(ni6w^a&LF3Mx{MpA(yGgo!ZebD*efJG!IZVo_nCpuD16b zsb3$cJ{xk$|J{X>u*yavU5THZdDpXVR~Y% z!s}TUIG!?XHRzM-XbtNWYzeo$KHkwRgk@?{{_>u5nz%j>JE<4J_bG1~-b~gpxkCSw z_!6QvJfG?zv(7JW^p{yI$JP_E?Bc^)qhi+N_d7xF73}s22#IvW)G7%eCpzRQar2 zfdoX*q^s>+#!WP%qDveyXZMJHw(Dd*2Ld`?U>2NV9sXzF`C3EiPG2BF{_R$PnLsyN zJsI*+*YRPoQ|(+!p4(2$!Ji|9Tjn;VQ3diTc{iFe7kpzvc*nVq=TZN$C}A%*4|`s5 zZ_>ABW2ZnkA!@2BgaKQogZ?WD4;;5VIiQj2tWL2t@c!-9?akICK<~`HcTR4*YQ(AD z8xH|-4mQ+&1)Y!&22h!BQs`iDz@Ko*mD)FNS5clTk(T1okJ+e~R*f;wov~@DyaP8e zfPYdZW{kVmyxlk{C&e}1&pPH+3T*2c&NCuL@*BqW&lV}rwTwZ<5$t>^-`z3-> za1dhOH)M5V+4ASK?ng1RV>a$rY^6g4|FJ1N+h`lcQ2lK;qHb=3VlXa|g_^9+S7H;h zJ|}|37p3dyJ~??zN?Gy`t*ZFB+%++Oz-bKv+{$%Rme1{jxVas&&U8xYH}O6BgFZbX zs#k7bmV3GrHIgl)BlS>cg#Ej-rIJa*T2}itoI{Mw{3lMnAj0-=UhXEGQ;vMeB}&;(_GGxPFo4d>b_O_qxks9~IbDai-r?`QW=F&Mm5$FfE8Tg#Q=9CMN zc1d}Pa1d1wJKd=@#0hhV%r6hBgnb)yb}=2aRM|@VvFeH}8L}3^;*G4s+=f<*MZMFu ziWZ%#v5`2Tj^*QV7Q{V_{?85tW_jwr?3YS?VpC^!3Tl0rs8QWaL8ZPN>c_wCtdH!r zm&56t02g&%Y%CvO!DEg@-9=bZ@YUVC##KJN?W{X2GG4H_4h||CHc$Xv7RnESzfKJO zg&0G#OxtGYLm20V-FZuJpw>L3? zMj$LD$JP2cC9j5(k3JrY;xj@m=Un2a90Wb(k4O0`6y&LyqamO5mISBXC$5rmZ64Vchm@u90@Hs2R4u|MN$1DNu zpB5e9I-W44jR=q5{kI`F)RZKcW4UC?aXoCfNoa#3Aj(!pknE;dkB4Dj#ddA#Jda7^aNEp7E}M23NVJp@k2B@l{fzD=)kYgf%- z>Eo7in28l=#k_g<=KXz6eA;8>pO-$aX~#frcx1+EtAxP#k?D)(xG&4p+b8bDG;dec z%w)~(+O(K})5Q7Y4qLxR;FFtBk=$=PL;KSA_XW+bJNrv2dQ=gG(`j;NpPt%(Z0MLB z&>$f*)`9dgIRGxYa7M=R#Tg}hFS__9>~_BW>}o$C`G{!GL8XU_TEVO*IF$lC41DxXW6aPY&dJH+n~?s;RobyA*pJ4IUlXM+UYgBfy!s zIkdt8@D+K;*NF>>ozg~k8F9|TVLxihuZ@iiMc0n#@3V1v&bwLw1dB)k$LAK%NnUmd zEhC_wXrJqw7K=m3J(Wi9xhOoKauT7ib#?pe(Sn18B%>e0k#Dbf{&M|sjC-pu}S69)oAf}g|A{#g2GR?-&XcBN@u=77(htC4PId&k{4T0+XtBDvp1>=r!(tLoEQIo8PyQA=zcSG>-3dCW z?jGk9hhrRS6hHSqh~;AYc%zbCJht0@35+yG$KWh+ZBmY=+N(W56#Jo;Fwv~96VT<} z6}r!yxec@C&8ls3X7h?o=f=3!O!7OE7DC_z!^NIqN^O=mFDUPr1~Be3(|dkT-ZS57fEz7BG{y?hs4l!h^3 zs5hIV4pR7I~j#aU(U|rWbSCB)bZJ%II4w243SPJUG zt0~h|2)yOTDy?s$8EP>=Dj63Z1Gw8PQV^}qu0cwWWw5w?LZRebm{|}GY#L&?v8XpaFul4JssEnbs9nVdGQdZ~J# z;KEhS?}3?xU35goF0jbhzqNKQJbv4p{fHF0qs~(Hj~e6s7M!~Mn;&?HO#D=x(DF7m%FBc9XMd==VZeB!ad^@yL`YzH6cw%> z^n!i1lO%LaAY-c1zw$y?rw2+>ZrPWZ$48(!4#|v(GL($Q7s#+ROD+_vk_6mA?pJ|P z)`UIiFF5*N8Aga&CpfML_8Sly05AbcxNyeSe>)7e7V{YDt61hNYjQ^z?;%ssxr6k# z5WU$hn0;`l?jn7=YEYih=JIE-_UqxZsM9u7)L5C{I76GVv|x5dW_CfiVhw#0tonS$ zoyMuYYW;F2H`&&>0xO#h` zi;-Dv>8Jrar@^gSUfY#r5=fh!#vLh$)VapGq)STGpvF{G`a?$gDClhdfz0ySMi3g6Y zEb4dUBZs$en1(7*f~UC|8%X+ngte-zp5;1W!@hue8xt{$=00=kYdz+*)g&0vi=1j7juJxHtNBK@^9@| zh<^iPDkZteGQ$BeAqpMELL0CduYBW9KD(>M#~w|YnoYa00&nJ&05fj2j6oBC%TULi z2mTbgzN_`^zT?!0(Z^3Koupak#~5KbJuOX;tEk;Y^V@+NI^eG|3B8_2 z8QO*mCZy+gGfh0rLJE4Oa*Nh~Y*kD*ZP4I+ARtezO=2Bx_xdAdqbg^x!?R@N`OMYJ z5n^QjwaSd)QnkB{qo_VP2yZcA4vAXrzrUg1HnVXKpe?=~QYuRG+QVqGaCw_*p7Jd6 zxRs$%=hF+1`zY47!Qh^oJ0?m+2?sS=PBGP-T1l#de6tU9Y;9@j12m-sAaSVDEsSDr z%^L^6(4cy9hrG=6#a1WqNpF2y?TZ{)Y(k@X#y}Dtg-5REGs$Otevn*~}vf+c0T;MO@0*Hb}<%WtyPEO!a%Bnu&erJu~Hcu|rO8 zX0S0?YZ9F&)-5s zY43?~>14G*CF1_--{ji zJtgn~D#3QlAjpCH`NJrNFQ|#1Cu?tF$Y5;lOma3NcY!camtkQac@nDIKdUFb)V8a! z+%wZfL586|8=gy%7~5#yUW@tRyZILOVE`?H!Ay;4+V<_*=402&S9Y%U@a%owH1CuS zYIyip4){|1m;*yz zq6)tZz9S7H@|S?7YBDd9{T=Z70UgQW<-85M5wZK_7{G8&RUYbl*cG%@{7nv^LDiy$ zYqX-f^it*v&P~}wRJH5>W|i(DtW_2 zgj|mY1{34m`jI(BBrJCgQ%Rnz(pMJ#5EKEHT$i*BOPrt!pF+D~ngEiE$@npxb|;N; zW_lf`DQ+~)Mag1KiQZ$=!~|QrQ7JiAG}Yyi9LbPb+S%4bnN5++HB?F31dT#c$NpD( zU%)q{Uk^^N8pt^O|6~0EFY^V^mS~y-;93?uwfp)@JdD_|UGYdLPvP2rV~mD{ls^*!vIHskxu|Gs>w-CEoaKel+(~7Kp9f zMD#tR9i|@vJkBt1|6*h>^v>GtL38Z{KW3~{pkp$6^fXe6O^yxrMpLnB&MoxYNMgIV zfGR8_0bRheLX*>wJq?cXws>xtgeu3{57B8ng(u0U#4x-p*nfX!bz^MAL65T+iY{xM zJkr%J&MW)r*j{HW>|M2-S3yiL1j@z;^aJ3NRuAKc>w?ki4Qn4D6JytfA%jXZJl?HN zB4E48o<_Jh7-Du2IK;9wAN@!nEb9k%0nGZ&-`3LZw9mCR=1uD@AyG0OpKCLN43>mbYjGYJLA`25H4rBX&#Fa0%Zqn*r^$cgd?h(RRJyevIG*S|!Oc z5u~1(o0b2Ek6as0&esmf>4Bo8&O=742|Q{q7is>z)F3p?Y)WG_5klb@ms&G;pRkDe zl^2*MNCN<^=qXjK89ofP@<>psP%>OHEm70TyZHhxgI1F~92E1_?{Jva z&DU0jl7|g-ylwjpMRdZ5a>Rv(+~mD=qu~Ume-ht{-P6X9*vfM?%M)vzcsS_SXUh;w z^PP;pHuDPWT>5!tOV!N2C*>Wv8FRzGvmX*dgeosiYgIqmx3r{s;h-qe(dpSfzb7qfa$>Xeg`oHhwS-A za22CfV(wELRumm|x9v>!c{Eq4baVbl7_u7j!ao_RF)R8w@}&Ux30MU|J5lpeMZi!} z)JrO13sxGWizXPc0-qTMt^c`jNVe^0a;aDR#(?UQukwR$JtqZ25r;j4k<6k<1wZtP znxbSt^BkfKmIZk?>cmK{5ULDpt zwsS?BFFKgR0^%9+bg#2P9YAO0%R7_&cukvUc<7=~cL;}jmpfzG&Ce8kXtBJQh`h9f z-{|fQQG+hdOA8lXzW`*vJ}(hit-`ZWWgdw4wo*}xD#6E)9&}4nUpK$&1@P6fCy!nT z7`^GaEW_V#cFPudeI07B_VNti0jlsXgV7rQk6OH{C>nNW+>ICw;VGm3|F<^fY(G9- z|5jb#Or7@VN6rASok6`gy4S*uMW!5c~y&o){Lh`tlfv>DPg$ zR!2Ru;}I5!@;_AS^?bjf$^PB4C8Pm`ruq!|u!#O;kQMaru6JKortpZ$!bFa5Ue~#_ zP$P*K78Iyn&$E8XfYlZU*IF4lykD*BNm0y8ZIncI+UCQ)CFlLVTC31a_Q%=Mx*6s} zc$afY55Y2)umX@z#7zjp&2-To$D$SBi;Y>T9JmLbaJeyJebr<+84R9L8=Rqp!6a+} zQIu9Lp}QR!(pjsUM+m9J zKZz)fKU!KD8mbc(br_KNu|j_8U6-=S_zJ_Z_O!T>j1f>~6SV zk6b!AiEjGgZkQCb&i3y>dPQy)p z*5)K#OXg01bJ@nvUm`aqw~QInv3}Gm^R|(BZ&UPpz-t~ix&R~Rg&>0Vg%lN2V~}SE zoxll6ogCGo1TB8^ii(>@uU)x4tW3nAjy&YXkV920nHsr;pcQS??tY6OVUhNh)9R3e zQTGdc^~(i0?f9m3^k@{;ky@*Lc?T%Z5AM4AyH`|RDI(;;BvuCRt(~|3xRuegJKWW! zc5KsM(ylswmL=8K*j0*sUY=SR*g0jeZYl%OO6%$~4y zpNT(!yP~a)v1^PT+2_oPL;W0S0hs)5@$ux%RHw?!_8yPNZ>k6Njqw=Vh@2hUpVh}Q;cnmnJ6iBdHZBHY}})y+t!lz~-sq&*$jM{7>2-FgkjU7k-4? zFk4g34Nm?I1;8q#^!k=jP88A)rir8{legbd9|^Zp3k&*;>uZz1?Y+?+-RrWUVc>z> z+kEW!V}#cGvNZev7;rllTOjj~C$?*mE(@~2x(bn`V4>VlX@l}Fkq&=-_f+=yKteSD z-HENe>j!G^wP5Fg4phye^zMUx9pqYck^5T^kK>{MfH&c5ultdDixbE44ev)=m7e%b zjZ>@E_m2GDPYsUx>>AQ^fm>M!AQL}%iut&~D%=r`nlI@j!m)@_QT(!Up6)LZ%y;fo z3M-coWWu*~n<<31$78-?#pNl>4LcyNeYW{C;J@^`_1#cIuDM7U<0I-Hvs(ZEHmfDy zX1({@APH{#R(v;%!lDtnVc%{q%+HM4-vAs_+8s$Z(6Z8T(vReVX0-Ds&Eoc-1bFNt8ex@M3L?ox%3}OCv1(cR$Ao4q-h*wl2S$GqeLt;ASQph^^XXg%+r1L6UvC*D_z$d3jKfIg9Vn z9wRt~$@4~3Cf5txcAUHqhu5ad2pC)b%-SutY#jxYa=}FJic9?D+tE_Low}}xU5EVn z(AMdDiBXUB162m1b!kOGJBNk=+mSg=%|8qmQ5PVnE~4L<)8c@DtlO`W_4zTrA@XiU zgElSA5W`cL?=Y_N*eak11@JN(XEK28kIaoEV$92vHN0tMvzEMhSKu|S9N`Tqc`}So#^n;sPC{PSW)QAM9a}O8&9@2T z*Y{1^HZ*_Rb_;WfXIFE&d;>UQN+{X(fuHM`vZrL?uGM2BR(7K_ZE&E;;{yLLigR?T zFlCV*CHpg2fM(7Kf0yG{x#N}fENa&p9m$xi`$(DBh^8HKBGG3teSckc8btFQI)#4zy+0}n@f(7v2BoU^g24I*WNFuf__eC>A7nB zzy}!7=u&@bPBX3fPMu7`oqWZy?$5x`6r5rhbCze~+`;r8?fNvKn8;FFNKWNMcLy2^ zw&bJdP8&`?+M4m9wy4!4dv4oMTl7>s{3U!sM*@xtZA;&5dcd)TSLXZl*U1w_XkXAo zQAO_iEy=Nvpj=VOf)2BrqLMaVdxosl)c!SrkbFPT54>b`Q#6cpY6s;Z0Sur&W~TlP z4_bOw%V|Tc`v@=p-}*EF6eG81<<_nlDhj_9WyO`yR)D`o$*U{vxnz5!p@EUR`dy@J zw&;x{A*Mp4G?f)Slp9m&z`7?k0;?Ze;Tn|GQgt#?P?L58aO=3H(e)X^b``4rN5k{U z{8uVh;@5K_oc`*_CmSqrKbwtEW^-yJf%5n4C_9Gt(k;sKUW)QyEg!u0?rh3-o~#v1_k!&YtE3s0V! zd8Kn;Lm$!l`)VjeB-$O)#!4c|u9c{M)$p^HdsRm*q&M0DbGXq1hceV>6J0~Gm%uFB zcYGXbHVdU)KsTtjBv}tu?Wkbv%ZZn~y!7*Fzvi z99vAuWYuY)I%!SIF_r1p$G$ZPb+N)@yF)<|^gOhQ*U@PO6f4+6Xr~dSOQGQoq4@?| zbbB?khL|b(i*>_T1^6Djmn8GyEmUgC+6_IeAh1 zPmUHEnQrUH0MlDVAs~zbEC~Q%)SZ7#``$yO_o30Ti(4tTV8vv%EfY1BzZpYBO({JG zQcV!m8(m%YtsAX@emhCgsjWm=)6JJzYsW5Z7F)3e*a1F6NweA??{w1L&|>y89huO6#LhRx6SxGr8b4s#<4T)U1$LhX3?CY z45W2(!bt_|>6utuFm1Tm88o|Qup-vQXuNZK>|9pu&}L_*5T(KSh) z#wd`)7$p0EsblZq5B;&$&QycA;=mn&80C|JU)IE13H~9YA^BJ;7$~$pv#c%2cWI?;iq2c)>*pb6TPR z%gx)X=!p#1OQ@{$VNWHw!%$L$#VjpK5b@i`(v-Zn7j*=n!X*H|o3i*oflzFl2Jud} z==~BYiKI)~_PDy&UxP7Agg2CG(w@n1D_q`M{ppI5zmcG{2NL&&*I;$?XbIx2mC37B z@qw4~MX!9V_3OC+k>b23aOP|Meu)%182J1W0Z9nizy&S#g@O)k8a1F6o!FRcG6J=8 z-Op0^X$&OHUaly@KL+>&*Xv+=bz_z7mJFqSJg)Ojdo_pfw{B zaJ|ugM+aIlkU8+0C9|X`VSfs58z>lWivO{s{hv%#x&tIgI9dRF)K3qxByh)##EoV{ z+JrHp7DVG|L#b&Zqq2YGr@ns^WTGrTS-2%>9XInWnM?(=lvUJnHsI1O@G7c>?L7xN z24G@F;|I?3c)EpuZ+MqvFZa>kv+? zq(h+@(-@Fg8Kw&LP%8yn)f3_XfRPc7%UL1rY=~D%>Iq8fqK61aAF?K4w#r!ruW*-X78y**-+3LU=<~ z4r(tN)qaWer1B3m{}MTh>W0I17YwB&f~`kit%oMicrosoft Photo Gallery 16.4.3528.3310.028800, 0.158273, 0.152000, 0.230216Sean Connery + 0.257600, 0.141487, 0.128000, 0.191847Roger Moore + 0.433600, 0.184652, 0.128000, 0.191847Daniel Craig + 0.617600, 0.194245, 0.120000, 0.179856Timothy Dalton + 0.825600, 0.206235, 0.136000, 0.203837Pierce Brosnan + + + \ No newline at end of file diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs new file mode 100644 index 0000000..9616b31 --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs @@ -0,0 +1,115 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.IO; +using Xunit; + +namespace MetadataExtractor.Tests.Formats.Jpeg +{ + ///

Unit tests for . + /// Michael Osthege + public sealed class JpegFragmentTest + { + [Fact] + public void TestFromJpegSegment() + { + IEnumerable segments; + using (var stream = TestDataUtil.OpenRead("Data/withExifAndIptc.jpg")) + segments = (IEnumerable)JpegSegmentReader.ReadSegments(new SequentialStreamReader(stream)).ToList(); + + foreach (JpegSegmentPlugin segment in segments) + { + JpegFragment fragment = JpegFragment.FromJpegSegment(segment); + Assert.Equal(2 + 2 + segment.Bytes.Length, fragment.Bytes.Length); + byte[] payloadBytes = fragment.Bytes.Skip(4).ToArray(); + Assert.Equal(payloadBytes, segment.Bytes); + } + } + + [Fact] + public void TestSplitSingleFragment() + { + // The test file contains exactly one App1 XMP segment (marker, size, payload) + string pathApp1 = "Data/xmpWriting_MicrosoftXmp.app1"; + + List fragments; + using (var stream = TestDataUtil.OpenRead(pathApp1)) + fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + byte[] fileBytes = TestDataUtil.GetBytes(pathApp1); + + Assert.Single(fragments); + Assert.True(fragments.First().Bytes.SequenceEqual(fileBytes)); + Assert.NotNull(fragments.First().Segment); + Assert.Equal(JpegSegmentType.App1, fragments.First().Segment.Type); + } + + [Fact] + public void TestFindsFragment() + { + // The file is an image that contains an App1 Xmp segment + string pathJpeg = "Data/xmpWriting_PictureWithMicrosoftXmp.jpg"; + string pathApp1 = "Data/xmpWriting_MicrosoftXmp.app1"; + + List fragments; + using (var stream = TestDataUtil.OpenRead(pathJpeg)) + fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + byte[] xmpFragmentBytes = TestDataUtil.GetBytes(pathApp1); + + bool foundXmpFragment = false; + foreach (var fragment in fragments) + { + if (fragment.Bytes.Length == xmpFragmentBytes.Length) + { + Assert.True(fragment.Bytes.SequenceEqual(xmpFragmentBytes)); + foundXmpFragment = true; + } + } + Assert.True(foundXmpFragment, "The Xmp App1 fragment was not found correctly."); + } + + [Fact] + public void TestSplitConcatenation() + { + // The file is an image that contains an App1 Xmp segment + string pathJpeg = "Data/xmpWriting_PictureWithMicrosoftXmp.jpg"; + + List fragments; + using (var stream = TestDataUtil.OpenRead(pathJpeg)) + fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + byte[] original = File.ReadAllBytes(pathJpeg); + + int nRead = fragments.Select(f => f.Bytes.Length).Sum(); + byte[] joined = JpegFragmentWriter.JoinFragments(fragments).ToArray(); + + Assert.Equal(original.Length, nRead); + Assert.Equal(original.Length, joined.Length); + Assert.True(original.SequenceEqual(joined)); + } + + } +} diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs new file mode 100644 index 0000000..d8f4002 --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs @@ -0,0 +1,111 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using MetadataExtractor.Formats.Exif; +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.IO; +using Xunit; + +namespace MetadataExtractor.Tests.Formats.Jpeg +{ + /// Unit tests for . + /// Michael Osthege + public sealed class JpegMetadataWriterTest + { + /// + /// This test is very similar to + /// but acts on one abstraction level higher, using a collection of metadata objects. + /// It tests if the correctly uses a + /// compatible metadata writer. + /// + [Fact] + public void TestUpdateJpegFragments_Replace() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); + byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + + var metadata_objects = new object[] { xmp }; + var updatedFragments = JpegMetadataWriter.UpdateJpegFragments(originalFragments, metadata_objects); + + Assert.Equal(originalFragments.Count, updatedFragments.Count); + // Check that only the App1 Xmp fragment is modified + for (int i = 0; i < originalFragments.Count; i++) + { + var ofrag = originalFragments[i]; + var ufrag = updatedFragments[i]; + + if (ofrag.Segment?.Type == JpegSegmentType.App1 && ofrag.Bytes.SequenceEqual(originalApp1)) + { + // If this fragment is the original Xmp fragment, we expect the updated fragment + Assert.True(ufrag.Bytes.SequenceEqual(expectedApp1)); + } + else + { + // In all other cases, the fragments must remain identical + Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); + } + } + } + + [Fact] + public void TestUpdateJpegFragments_FailsOnUnknownMetadataObject() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + + Assert.Throws(delegate + { + var metadata_objects = new object[] { xmp, "This is not a supported metadata object." }; + var updatedFragments = JpegMetadataWriter.UpdateJpegFragments(originalFragments, metadata_objects); + }); + } + + [Fact] + public void TestWriteJpegMetadata() + { + var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); + + var metadata_objects = new object[] { xmp }; + var updatedStream = JpegMetadataWriter.WriteMetadata(originalStream, metadata_objects); + + var actualResult = updatedStream.ToArray(); + + Assert.True(actualResult.SequenceEqual(expectedResult)); + } + } +} diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs new file mode 100644 index 0000000..c0dd635 --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs @@ -0,0 +1,60 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.IO; +using Xunit; + +namespace MetadataExtractor.Tests.Formats.Jpeg +{ + /// Unit tests for . + /// Michael Osthege + public sealed class JpegSegmentTest + { + [Fact] + public void TestEncodeSegmentLength() + { + // we are just interested in the payload length + int expected = 42802 - 2; + // the encoded value is 42802 because it includes the length mark + byte[] encoded = new byte[] { 0xA7, 0x32 }; + int decoded = JpegSegmentPlugin.DecodePayloadLength(encoded[0], encoded[1]); + + Assert.Equal(expected, decoded); + } + + [Fact] + public void TestDecodeSegmentLength() + { + int decoded = 42802 - 2; + byte[] expected = new byte[] { 0xA7, 0x32 }; + byte[] encoded = JpegSegmentPlugin.EncodePayloadLength(decoded); + + Assert.True(encoded.SequenceEqual(expected)); + } + } +} diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs new file mode 100644 index 0000000..980746f --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs @@ -0,0 +1,132 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.Formats.Xmp; +using MetadataExtractor.IO; +using Xunit; + +namespace MetadataExtractor.Tests.Formats.Xmp +{ + /// Unit tests for . + /// Michael Osthege + public sealed class XmpWriterTest + { + [Fact] + public void TestEncodeXmpToPayloadBytes() + { + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] payloadBytes = XmpWriter.EncodeXmpToPayloadBytes(xmp); + JpegSegmentPlugin app1 = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); + JpegFragment frag = JpegFragment.FromJpegSegment(app1); + + byte[] expected = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + + Assert.Equal(expected.Length, frag.Bytes.Length); + Assert.True(frag.Bytes.SequenceEqual(expected)); + } + + [Fact] + public void TestUpdateFragments_Replace() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); + byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + + var writer = new XmpWriter(); + var updatedFragments = writer.UpdateFragments(originalFragments, xmp); + + Assert.Equal(originalFragments.Count, updatedFragments.Count); + // Check that only the App1 Xmp fragment is modified + for (int i = 0; i < originalFragments.Count; i++) + { + var ofrag = originalFragments[i]; + var ufrag = updatedFragments[i]; + + if (ofrag.Segment?.Type == JpegSegmentType.App1 && ofrag.Bytes.SequenceEqual(originalApp1)) + { + // If this fragment is the original Xmp fragment, we expect the updated fragment + Assert.True(ufrag.Bytes.SequenceEqual(expectedApp1)); + } + else + { + // In all other cases, the fragments must remain identical + Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); + } + } + } + + [Fact] + public void TestUpdateFragments_Insert() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithoutXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); + byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + + var writer = new XmpWriter(); + var updatedFragments = writer.UpdateFragments(originalFragments, xmp); + + Assert.True(updatedFragments.Count == originalFragments.Count + 1); + // Check that only the App1 Xmp fragment is modified + bool foundInsertedFragment = false; + for (int i = 0; i < originalFragments.Count; i++) + { + // for all fragments after the inserted App1, the previous original fragment must be selected + var ofrag = originalFragments[(foundInsertedFragment ? i - 1 : i)]; + var ufrag = updatedFragments[i]; + + if (ufrag.Bytes.SequenceEqual(expectedApp1)) + { + foundInsertedFragment = true; + } + else + { + // In all other cases, the fragments must remain identical + Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); + } + } + Assert.True(foundInsertedFragment); + } + + [Fact] + public void TestCreateWhitespace() + { + string created = XmpWriter.CreateWhitespace(size: 12345); + Assert.Equal(12345, created.Length); + } + } +} diff --git a/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs new file mode 100644 index 0000000..dc2e1b2 --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs @@ -0,0 +1,55 @@ +#region License +// +// Copyright 2002-2017 Drew Noakes +// Ported from Java to C# by Yakov Danilov for Imazen LLC in 2014 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// More information about this project is available at: +// +// https://github.com/drewnoakes/metadata-extractor-dotnet +// https://drewnoakes.com/code/exif/ +// +#endregion +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using MetadataExtractor.Formats.Exif; +using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.IO; +using Xunit; + +namespace MetadataExtractor.Tests +{ + /// Unit tests for . + /// Michael Osthege + public sealed class ImageMetadataWriterTest + { + [Fact] + public void TestWriteImageMetadata() + { + var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); + + var metadata_objects = new object[] { xmp }; + var updatedStream = ImageMetadataWriter.WriteMetadata(originalStream, metadata_objects); + + var actualResult = updatedStream.ToArray(); + + Assert.True(actualResult.SequenceEqual(expectedResult)); + } + } +} diff --git a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj new file mode 100644 index 0000000..8e76f7c --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs b/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs new file mode 100644 index 0000000..a6e3016 --- /dev/null +++ b/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs @@ -0,0 +1,19 @@ +// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace MetadataExtractor.Tests +{ + /// Utility functions for working with unit tests data files. + /// Drew Noakes https://drewnoakes.com + internal static class TestDataUtil + { + /// + /// Traditionally, NUnit and xUnit on desktops have run tests such that the current directory + /// was the project folder. xUnit on .NET Core uses the bin/Debug folder. This method tries both. + /// + public static string GetPath(string filePath) => File.Exists(filePath) ? filePath : Path.Combine("../..", filePath); + + public static Stream OpenRead(string filePath) => new FileStream(GetPath(filePath), FileMode.Open, FileAccess.Read, FileShare.Read); + + public static byte[] GetBytes(string filePath) => File.ReadAllBytes(GetPath(filePath)); + } +} diff --git a/JpegXmpWritePluginMDE.sln b/JpegXmpWritePluginMDE.sln index 55989f2..bfdb140 100644 --- a/JpegXmpWritePluginMDE.sln +++ b/JpegXmpWritePluginMDE.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34511.84 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JpegXmpWritePluginMDE", "JpegXmpWritePluginMDE\JpegXmpWritePluginMDE.csproj", "{7AF4229E-F42F-48FC-BCB1-3182516BE31B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JpegXmpWritePluginMDE", "JpegXmpWritePluginMDE\JpegXmpWritePluginMDE.csproj", "{7AF4229E-F42F-48FC-BCB1-3182516BE31B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JpegXmpWritePluginMDE.Tests", "JpegXmpWritePluginMDE.Tests\JpegXmpWritePluginMDE.Tests.csproj", "{062B9FA1-4961-4B35-A2DB-65036C74BBC9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7AF4229E-F42F-48FC-BCB1-3182516BE31B}.Release|Any CPU.Build.0 = Release|Any CPU + {062B9FA1-4961-4B35-A2DB-65036C74BBC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {062B9FA1-4961-4B35-A2DB-65036C74BBC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {062B9FA1-4961-4B35-A2DB-65036C74BBC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {062B9FA1-4961-4B35-A2DB-65036C74BBC9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d003b3f48cf9352d72999f857e082a61fcd245e4 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 16:19:52 -0500 Subject: [PATCH 11/30] cleanup unit tests --- .../Formats/Jpeg/JpegFragmentTest.cs | 24 +++---------------- .../JpegXmpWritePluginMDE.Tests.csproj | 8 ++++--- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs index 9616b31..264daee 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs @@ -22,34 +22,16 @@ // #endregion -using System.Collections.Generic; -using System.IO; -using System.Linq; using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.IO; using Xunit; namespace MetadataExtractor.Tests.Formats.Jpeg { - /// Unit tests for . - /// Michael Osthege - public sealed class JpegFragmentTest + /// Unit tests for . + /// Michael Osthege + public sealed class JpegFragmentTest { - [Fact] - public void TestFromJpegSegment() - { - IEnumerable segments; - using (var stream = TestDataUtil.OpenRead("Data/withExifAndIptc.jpg")) - segments = (IEnumerable)JpegSegmentReader.ReadSegments(new SequentialStreamReader(stream)).ToList(); - - foreach (JpegSegmentPlugin segment in segments) - { - JpegFragment fragment = JpegFragment.FromJpegSegment(segment); - Assert.Equal(2 + 2 + segment.Bytes.Length, fragment.Bytes.Length); - byte[] payloadBytes = fragment.Bytes.Skip(4).ToArray(); - Assert.Equal(payloadBytes, segment.Bytes); - } - } [Fact] public void TestSplitSingleFragment() diff --git a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj index 8e76f7c..5a2d2b5 100644 --- a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj +++ b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj @@ -12,11 +12,13 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 7b224fcfa8080d46765a87ad6b76c0daf8409dc1 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 16:38:25 -0500 Subject: [PATCH 12/30] Copy data to output folder for tests --- .../JpegXmpWritePluginMDE.Tests.csproj | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj index 5a2d2b5..cf1f1fa 100644 --- a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj +++ b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj @@ -25,4 +25,25 @@ + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + From 9022520d5f9ea9b1d9cbc55027ce0dd71e0a0593 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 26 Feb 2024 16:42:26 -0500 Subject: [PATCH 13/30] Change the project output to library --- JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj index 7729eb2..9434580 100644 --- a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj +++ b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj @@ -1,7 +1,7 @@ - Exe + Library net8.0 enable enable From 3a69f16cbb9edde4a3c312feb00516d73f4c0a6c Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Tue, 27 Feb 2024 14:46:53 -0500 Subject: [PATCH 14/30] switch to central packaging --- Directory.Packages.props | 23 +++++++++++++++++++ .../JpegXmpWritePluginMDE.Tests.csproj | 14 +++++------ .../JpegXmpWritePluginMDE.csproj | 6 ++--- 3 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 Directory.Packages.props diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..8524ef5 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,23 @@ + + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj index cf1f1fa..773e326 100644 --- a/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj +++ b/JpegXmpWritePluginMDE.Tests/JpegXmpWritePluginMDE.Tests.csproj @@ -10,13 +10,13 @@ - - - - - - - all + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj index 9434580..ccfd8c5 100644 --- a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj +++ b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj @@ -8,9 +8,9 @@ - - - + + + From 3dde6f4430b899751120533a2a9a8746505aebb1 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Tue, 27 Feb 2024 16:10:38 -0500 Subject: [PATCH 15/30] cleanup after initial testing --- JpegXmpWritePluginMDE/Program.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 JpegXmpWritePluginMDE/Program.cs diff --git a/JpegXmpWritePluginMDE/Program.cs b/JpegXmpWritePluginMDE/Program.cs deleted file mode 100644 index 005da67..0000000 --- a/JpegXmpWritePluginMDE/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MetadataExtractor.Formats.Jpeg; -using MetadataExtractor.Formats.Xmp; -using System.Xml.Linq; -using Xunit; - -static class Program -{ - static void Main() - { - } - -} \ No newline at end of file From 298a30fd9c7798b0983f20d675851967f9389b5f Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 4 Mar 2024 09:47:42 -0500 Subject: [PATCH 16/30] Target the library for net6.0 and net8.0 --- JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj index ccfd8c5..1410352 100644 --- a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj +++ b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj @@ -2,7 +2,7 @@ Library - net8.0 + net6.0;net8.0 enable enable From 197fc69104ef7236896baaab56545562bc56f5b1 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Thu, 14 Mar 2024 16:05:36 -0400 Subject: [PATCH 17/30] Switched the writemetadata function to expect IXmpMeta instead of XDocument --- .../Jpeg/IJpegFragmentMetadataWriter.cs | 22 +-- .../Formats/Jpeg/JpegMetadataWriter.cs | 132 +++++++++--------- .../Formats/Xmp/XmpWriter.cs | 85 +++++------ .../MetadataExtractor/ImageMetadataWriter.cs | 45 +++--- 4 files changed, 146 insertions(+), 138 deletions(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs index 1d43958..3dfeb5d 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs @@ -37,18 +37,18 @@ namespace MetadataExtractor.Formats.Jpeg /// Defines the interface of an object that can update a list of JpegFragments with new metadata. public interface IJpegFragmentMetadataWriter { - /// The type of metadata that this writer can process. - [NotNull] - Type MetadataType { get; } + /// The IXmpMeta type of metadata that this writer can process. + [NotNull] + Type MetadataXmpMetaType { get; } - /// Modifies the JpegFragment collection with the metadata update. - /// - /// A sequence of JPEG fragments to which the metadata should be written. These are in the order encountered in the original file. - /// - /// - /// A directory containing metadata that shall be written to the JpegFragments. - /// - [NotNull] + /// Modifies the JpegFragment collection with the metadata update. + /// + /// A sequence of JPEG fragments to which the metadata should be written. These are in the order encountered in the original file. + /// + /// + /// A directory containing metadata that shall be written to the JpegFragments. + /// + [NotNull] List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata); } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs index 7239106..b618ace 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs @@ -25,82 +25,86 @@ using JetBrains.Annotations; #if !PORTABLE using MetadataExtractor.Formats.FileSystem; +using MetadataExtractor.Formats.Jpeg; + #endif using MetadataExtractor.Formats.Xmp; using MetadataExtractor.IO; using System.Xml.Linq; +using XmpCore; +using XmpCore.Impl; -namespace MetadataExtractor.Formats.Jpeg +namespace JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg { - /// Writes metadata to Jpeg formatted data. - /// Michael Osthege - public static class JpegMetadataWriter - { - /// - /// Look-up dictionary that maps metadata object types to compatible metadata writers. - /// - private static readonly Dictionary _allWriters = new Dictionary - { - { typeof(XDocument), new XmpWriter() } - }; + /// Writes metadata to Jpeg formatted data. + /// Michael Osthege + public static class JpegMetadataWriter + { + /// + /// Look-up dictionary that maps metadata object types to compatible metadata writers. + /// + private static readonly Dictionary _allWriters = new Dictionary + { + { typeof(XmpMeta), new XmpWriter() } + }; - /// - /// Processes a stream of Jpeg data into a new stream that is updated with the metadata. - /// - /// Stream of the original file. - /// Collection of metadata objects. - /// A new stream that contains Jpeg data, updated with the metadata. - public static MemoryStream WriteMetadata([NotNull] Stream original, [NotNull] IEnumerable metadata) - { - var ssr = new SequentialStreamReader(original, isMotorolaByteOrder:true); + /// + /// Processes a stream of Jpeg data into a new stream that is updated with the metadata. + /// + /// Stream of the original file. + /// Collection of metadata objects. + /// A new stream that contains Jpeg data, updated with the metadata. + public static MemoryStream WriteMetadata([NotNull] Stream original, [NotNull] IEnumerable metadata) + { + var ssr = new SequentialStreamReader(original, isMotorolaByteOrder:true); - // 1. split up the original data into a collection of fragments (non-coding and coding) - List fragments = JpegFragmentWriter.SplitFragments(ssr); + // 1. split up the original data into a collection of fragments (non-coding and coding) + List fragments = JpegFragmentWriter.SplitFragments(ssr); - // for each metadata item, apply a compatible writer to the segments - // this updates the fragments with the metadata - fragments = UpdateJpegFragments(fragments, metadata); + // for each metadata item, apply a compatible writer to the segments + // this updates the fragments with the metadata + fragments = UpdateJpegFragments(fragments, metadata); - // now concatenate all fragments back into the complete file - return JpegFragmentWriter.JoinFragments(fragments); + // now concatenate all fragments back into the complete file + return JpegFragmentWriter.JoinFragments(fragments); - //if (JpegFragmentWriter.IsValid(fragments)) - //{ - // return JpegFragmentWriter.JoinFragments(fragments); - //} - //else - //{ - // throw new ImageProcessingException("Metadata encoding resulted in invalid Fragments."); - //} - } + //if (JpegFragmentWriter.IsValid(fragments)) + //{ + // return JpegFragmentWriter.JoinFragments(fragments); + //} + //else + //{ + // throw new ImageProcessingException("Metadata encoding resulted in invalid Fragments."); + //} + } - /// - /// Updates a list of JpegFragments with metadata, using the specified writers. - /// - /// The list of JpegFragments to start with. - /// Collection of metadata items. - /// A dictionary that maps metadata types to compatible JpegMetadataWriters. - /// The updated list of JpegFragments - public static List UpdateJpegFragments([NotNull] List fragments, [NotNull] IEnumerable metadata, [NotNull] Dictionary? writers = null) - { - if (writers == null) - writers = _allWriters; + /// + /// Updates a list of JpegFragments with metadata, using the specified writers. + /// + /// The list of JpegFragments to start with. + /// Collection of metadata items. + /// A dictionary that maps metadata types to compatible JpegMetadataWriters. + /// The updated list of JpegFragments + public static List UpdateJpegFragments([NotNull] List fragments, [NotNull] IEnumerable metadata, [NotNull] Dictionary? writers = null) + { + if (writers == null) + writers = _allWriters; - foreach (var mdat in metadata) - { - var mdatType = mdat.GetType(); - if (writers.ContainsKey(mdatType)) - { - IJpegFragmentMetadataWriter writer = writers[mdatType]; - fragments = writer.UpdateFragments(fragments, mdat); - } - else - { - throw new NotImplementedException($"No JpegSegmentMetadataWriter is implemented for metadata of type {mdat.GetType()}."); - } - } + foreach (var mdat in metadata) + { + var mdatType = mdat.GetType(); + if (writers.ContainsKey(mdatType)) + { + IJpegFragmentMetadataWriter writer = writers[mdatType]; + fragments = writer.UpdateFragments(fragments, mdat); + } + else + { + throw new NotImplementedException($"No JpegSegmentMetadataWriter is implemented for metadata of type {mdat.GetType()}."); + } + } - return fragments; - } - } + return fragments; + } + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index 286a72c..273470f 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -3,6 +3,9 @@ using System.Text; using System.Xml; using System.Xml.Linq; +using XmpCore; +using XmpCore.Impl; +using XmpCore.Options; #if NET35 using FragmentList = System.Collections.Generic.IList; @@ -17,40 +20,40 @@ public sealed class XmpWriter : IJpegFragmentMetadataWriter /// /// Specifies the type of metadata that this MetadataWriter can handle. /// - Type IJpegFragmentMetadataWriter.MetadataType => typeof(XmpDirectory); - - /// - /// Updates a list of JpegFragments with new metadata. - /// - /// An existing App1 Xmp fragment will be updated. If none is found, a new segment will be - /// inserted before the first fragment that is not one of {Soi, App0, App1} - /// - /// - /// Original file fragmets - /// The Xmp metadata that shall be written - /// A new list of JpegFragments - public List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata) - { - JpegFragment metadataFragment; - List output = new List(); - bool wroteData = false; - int insertPosition = 0; - - if (metadata is XDocument) - { - byte[] payloadBytes = EncodeXmpToPayloadBytes((XDocument)metadata); - JpegSegmentPlugin metadataSegment = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); - metadataFragment = JpegFragment.FromJpegSegment(metadataSegment); - } - else - { - throw new ArgumentException($"XmpWriter expects metadata to be of type XmpDirectory, but was given {metadata.GetType()}."); - } - - // First look for any potential Xmp fragment, insert only if none is found - - // Walk over existing fragment and replace or insert the new metadata fragment - for (int i = 0; i < fragments.Count; i++) + Type IJpegFragmentMetadataWriter.MetadataXmpMetaType => typeof(XmpMeta); + + /// + /// Updates a list of JpegFragments with new metadata. + /// + /// An existing App1 Xmp fragment will be updated. If none is found, a new segment will be + /// inserted before the first fragment that is not one of {Soi, App0, App1} + /// + /// + /// Original file fragmets + /// The Xmp metadata that shall be written + /// A new list of JpegFragments + public List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata) + { + JpegFragment metadataFragment; + List output = new List(); + bool wroteData = false; + int insertPosition = 0; + + if (metadata is IXmpMeta xmpMeta) + { + byte[] payloadBytes = XmpMetaFactory.SerializeToBuffer(xmpMeta, new SerializeOptions()); + JpegSegmentPlugin metadataSegment = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); + metadataFragment = JpegFragment.FromJpegSegment(metadataSegment); + } + else + { + throw new ArgumentException($"XmpWriter expects metadata to be of type IXmpMeta, but was given {metadata.GetType()}."); + } + + // First look for any potential Xmp fragment, insert only if none is found + + // Walk over existing fragment and replace or insert the new metadata fragment + for (int i = 0; i < fragments.Count; i++) { JpegFragment currentFragment = fragments[i]; @@ -129,13 +132,13 @@ public static byte[] EncodeXmpToPayloadBytes([NotNull] XDocument xmpDoc, bool wr return xmpMS.ToArray(); } - - /// - /// Creates a string of whitespace with linebreaks for padding within xpacket. - /// - /// Desired total size of whitespace - /// String of whitespace with newline character in each line of 100 chars - public static string CreateWhitespace(int size=4096) + + /// + /// Creates a string of whitespace with linebreaks for padding within xpacket. + /// + /// Desired total size of whitespace + /// String of whitespace with newline character in each line of 100 chars + public static string CreateWhitespace(int size=4096) { var line = '\u000A' + new String('\u0020', 99); return string.Concat(Enumerable.Repeat(line, (int)Math.Ceiling(size / 100.0))).Substring(0, size); diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs index 2e2b13b..fa1187f 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs @@ -23,36 +23,37 @@ #endregion using JetBrains.Annotations; +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; + #if !PORTABLE using MetadataExtractor.Formats.FileSystem; #endif -using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.Util; namespace MetadataExtractor { - /// Writes metadata to any supported file format. - /// - /// This class a lightweight wrapper around other, specific metadata processors. - /// During saving, the file type is determined from the first few bytes of the existing file. - /// Writing is then delegated to one of: - /// - /// - /// for JPEG files - /// Writing to other file types is currently not implemented. Feel free to contribute! - /// - /// - /// If you know the file type you're working with, you may use one of the above processors directly. - /// For most scenarios it is simpler, more convenient and more robust to use this class. - /// - /// is used to determine the provided image's file type, and therefore - /// the appropriate metadata reader to use. - /// - /// Michael Osthege - /// on unsupported file types - public static class ImageMetadataWriter + /// Writes metadata to any supported file format. + /// + /// This class a lightweight wrapper around other, specific metadata processors. + /// During saving, the file type is determined from the first few bytes of the existing file. + /// Writing is then delegated to one of: + /// + /// + /// for JPEG files + /// Writing to other file types is currently not implemented. Feel free to contribute! + /// + /// + /// If you know the file type you're working with, you may use one of the above processors directly. + /// For most scenarios it is simpler, more convenient and more robust to use this class. + /// + /// is used to determine the provided image's file type, and therefore + /// the appropriate metadata reader to use. + /// + /// Michael Osthege + /// on unsupported file types + public static class ImageMetadataWriter { - /// Writes metadata to a . + /// Writes metadata to a . /// A stream to which the file data may be written. The stream must be positioned at the beginning of the file's data. /// Collection of metadata objects. /// The file type is unknown, or processing errors occurred. From db11767630123b1e92d29b071b49eed0eb3ec3bc Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Thu, 14 Mar 2024 16:08:17 -0400 Subject: [PATCH 18/30] Encode bytes using the XmpMetaFactory.SerializeToBuffer from xmp-core-dotnet --- .../Formats/Xmp/XmpWriter.cs | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index 273470f..b45392a 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -95,53 +95,5 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not return output; } - - /// - /// Encodes an XDocument to bytes to be used as the payload of an App1 segment. - /// - /// Xmp document to be encoded - /// Indicates if the Xmp packet shall be marked as writable. - /// App1 segment payload - public static byte[] EncodeXmpToPayloadBytes([NotNull] XDocument xmpDoc, bool writeable=true) - { - // constant parts - byte[] preamble = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray())); - byte[] xpacketHeader = Encoding.UTF8.GetBytes(""); - byte[] xpacketTrailer = Encoding.UTF8.GetBytes($""); - - MemoryStream xmpMS = new MemoryStream(); - // 1. preamble "http://ns.adobe.com/xap/1.0/\0" - xmpMS.Write(preamble, 0, preamble.Length); - - // 2. xpacket header - xmpMS.Write(xpacketHeader, 0, xpacketHeader.Length); - - // 3. serialized Xmp xml - XmlWriterSettings settings = new XmlWriterSettings() { OmitXmlDeclaration = true }; - using (XmlWriter xmlWriter = XmlWriter.Create(xmpMS, settings)) - { - xmpDoc.WriteTo(xmlWriter); - } - - // 4. whitespace padding - byte[] whitespace = Encoding.UTF8.GetBytes(CreateWhitespace()); - xmpMS.Write(whitespace, 0, whitespace.Length); - - // 5. xpacket trailer - xmpMS.Write(xpacketTrailer, 0, xpacketTrailer.Length); - - return xmpMS.ToArray(); - } - - /// - /// Creates a string of whitespace with linebreaks for padding within xpacket. - /// - /// Desired total size of whitespace - /// String of whitespace with newline character in each line of 100 chars - public static string CreateWhitespace(int size=4096) - { - var line = '\u000A' + new String('\u0020', 99); - return string.Concat(Enumerable.Repeat(line, (int)Math.Ceiling(size / 100.0))).Substring(0, size); - } } } From ff73546b1938fdc64d20d1207edabd1a52e24ed9 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Thu, 14 Mar 2024 16:09:55 -0400 Subject: [PATCH 19/30] cleanup --- .../Formats/Jpeg/JpegMetadataWriterTest.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs index d8f4002..83a16ea 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs @@ -21,21 +21,17 @@ // https://drewnoakes.com/code/exif/ // #endregion -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using MetadataExtractor.Formats.Exif; +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.IO; +using System.Xml.Linq; using Xunit; namespace MetadataExtractor.Tests.Formats.Jpeg { - /// Unit tests for . - /// Michael Osthege - public sealed class JpegMetadataWriterTest + /// Unit tests for . + /// Michael Osthege + public sealed class JpegMetadataWriterTest { /// /// This test is very similar to From 36f2092780ab7b52e8477fa14b60fbe958bf8839 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Thu, 14 Mar 2024 16:18:50 -0400 Subject: [PATCH 20/30] update unit test and cleanup --- .../Formats/Xmp/XmpWriterTest.cs | 35 ++++++++----------- .../ImageMetadataWriterTest.cs | 27 ++++++-------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs index 980746f..c0f1640 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs @@ -22,37 +22,39 @@ // #endregion -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml; -using System.Xml.Linq; using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.Formats.Xmp; using MetadataExtractor.IO; +using System.Xml.Linq; +using XmpCore; +using XmpCore.Options; using Xunit; namespace MetadataExtractor.Tests.Formats.Xmp { - /// Unit tests for . - /// Michael Osthege - public sealed class XmpWriterTest + /// Unit tests for . + /// Michael Osthege + public sealed class XmpWriterTest { [Fact] public void TestEncodeXmpToPayloadBytes() { - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - byte[] payloadBytes = XmpWriter.EncodeXmpToPayloadBytes(xmp); - JpegSegmentPlugin app1 = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); + SerializeOptions options = new SerializeOptions + { + UseCanonicalFormat = true, + }; + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] payloadBytes = XmpMetaFactory.SerializeToBuffer(xmp, options); + JpegSegmentPlugin app1 = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); JpegFragment frag = JpegFragment.FromJpegSegment(app1); - byte[] expected = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + byte[] expected = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); Assert.Equal(expected.Length, frag.Bytes.Length); Assert.True(frag.Bytes.SequenceEqual(expected)); } - [Fact] + [Fact] public void TestUpdateFragments_Replace() { // substitute the original with re-encoded Xmp content @@ -121,12 +123,5 @@ public void TestUpdateFragments_Insert() } Assert.True(foundInsertedFragment); } - - [Fact] - public void TestCreateWhitespace() - { - string created = XmpWriter.CreateWhitespace(size: 12345); - Assert.Equal(12345, created.Length); - } } } diff --git a/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs index dc2e1b2..79edc2f 100644 --- a/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs @@ -21,35 +21,28 @@ // https://drewnoakes.com/code/exif/ // #endregion -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using MetadataExtractor.Formats.Exif; -using MetadataExtractor.Formats.Jpeg; -using MetadataExtractor.IO; +using XmpCore; using Xunit; namespace MetadataExtractor.Tests { - /// Unit tests for . - /// Michael Osthege - public sealed class ImageMetadataWriterTest + /// Unit tests for . + /// Michael Osthege + public sealed class ImageMetadataWriterTest { [Fact] public void TestWriteImageMetadata() { var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); - var metadata_objects = new object[] { xmp }; + var metadata_objects = new object[] { xmp }; var updatedStream = ImageMetadataWriter.WriteMetadata(originalStream, metadata_objects); - var actualResult = updatedStream.ToArray(); + var actualResult = updatedStream.ToArray(); Assert.True(actualResult.SequenceEqual(expectedResult)); - } - } + } + } } From 169ffc2252e5c93ebe65bfe52e9fbc3f4f30a7fb Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Thu, 14 Mar 2024 16:33:02 -0400 Subject: [PATCH 21/30] Refactor the code to reflect JpegXmpWritePluginMDE in namespace --- .../Formats/Jpeg/JpegFragmentTest.cs | 107 +++---- .../Formats/Jpeg/JpegMetadataWriterTest.cs | 128 ++++---- .../Formats/Jpeg/JpegSegmentTest.cs | 49 ++-- .../Formats/Xmp/XmpWriterTest.cs | 145 ++++----- .../ImageMetadataWriterTest.cs | 19 +- JpegXmpWritePluginMDE.Tests/TestDataUtil.cs | 26 +- .../Jpeg/IJpegFragmentMetadataWriter.cs | 16 +- .../Formats/Jpeg/JpegFragment.cs | 117 ++++---- .../Formats/Jpeg/JpegFragmentWriter.cs | 277 +++++++++--------- .../Formats/Jpeg/JpegMetadataWriter.cs | 3 +- .../Formats/Jpeg/JpegSegmentPlugin.cs | 95 +++--- .../Formats/Xmp/XmpWriter.cs | 94 +++--- .../MetadataExtractor/ImageMetadataWriter.cs | 88 +++--- 13 files changed, 589 insertions(+), 575 deletions(-) diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs index 264daee..4d1b723 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegFragmentTest.cs @@ -22,76 +22,77 @@ // #endregion +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.IO; using Xunit; -namespace MetadataExtractor.Tests.Formats.Jpeg +namespace JpegXmpWritePluginMDE.Tests.Formats.Jpeg { /// Unit tests for . /// Michael Osthege public sealed class JpegFragmentTest - { + { - [Fact] - public void TestSplitSingleFragment() - { - // The test file contains exactly one App1 XMP segment (marker, size, payload) - string pathApp1 = "Data/xmpWriting_MicrosoftXmp.app1"; + [Fact] + public void TestSplitSingleFragment() + { + // The test file contains exactly one App1 XMP segment (marker, size, payload) + string pathApp1 = "Data/xmpWriting_MicrosoftXmp.app1"; - List fragments; - using (var stream = TestDataUtil.OpenRead(pathApp1)) - fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - byte[] fileBytes = TestDataUtil.GetBytes(pathApp1); + List fragments; + using (var stream = TestDataUtil.OpenRead(pathApp1)) + fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + byte[] fileBytes = TestDataUtil.GetBytes(pathApp1); - Assert.Single(fragments); - Assert.True(fragments.First().Bytes.SequenceEqual(fileBytes)); - Assert.NotNull(fragments.First().Segment); - Assert.Equal(JpegSegmentType.App1, fragments.First().Segment.Type); - } + Assert.Single(fragments); + Assert.True(fragments.First().Bytes.SequenceEqual(fileBytes)); + Assert.NotNull(fragments.First().Segment); + Assert.Equal(JpegSegmentType.App1, fragments.First().Segment.Type); + } - [Fact] - public void TestFindsFragment() - { - // The file is an image that contains an App1 Xmp segment - string pathJpeg = "Data/xmpWriting_PictureWithMicrosoftXmp.jpg"; - string pathApp1 = "Data/xmpWriting_MicrosoftXmp.app1"; + [Fact] + public void TestFindsFragment() + { + // The file is an image that contains an App1 Xmp segment + string pathJpeg = "Data/xmpWriting_PictureWithMicrosoftXmp.jpg"; + string pathApp1 = "Data/xmpWriting_MicrosoftXmp.app1"; - List fragments; - using (var stream = TestDataUtil.OpenRead(pathJpeg)) - fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - byte[] xmpFragmentBytes = TestDataUtil.GetBytes(pathApp1); + List fragments; + using (var stream = TestDataUtil.OpenRead(pathJpeg)) + fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + byte[] xmpFragmentBytes = TestDataUtil.GetBytes(pathApp1); - bool foundXmpFragment = false; - foreach (var fragment in fragments) - { - if (fragment.Bytes.Length == xmpFragmentBytes.Length) - { - Assert.True(fragment.Bytes.SequenceEqual(xmpFragmentBytes)); - foundXmpFragment = true; - } - } - Assert.True(foundXmpFragment, "The Xmp App1 fragment was not found correctly."); - } + bool foundXmpFragment = false; + foreach (var fragment in fragments) + { + if (fragment.Bytes.Length == xmpFragmentBytes.Length) + { + Assert.True(fragment.Bytes.SequenceEqual(xmpFragmentBytes)); + foundXmpFragment = true; + } + } + Assert.True(foundXmpFragment, "The Xmp App1 fragment was not found correctly."); + } - [Fact] - public void TestSplitConcatenation() - { - // The file is an image that contains an App1 Xmp segment - string pathJpeg = "Data/xmpWriting_PictureWithMicrosoftXmp.jpg"; + [Fact] + public void TestSplitConcatenation() + { + // The file is an image that contains an App1 Xmp segment + string pathJpeg = "Data/xmpWriting_PictureWithMicrosoftXmp.jpg"; - List fragments; - using (var stream = TestDataUtil.OpenRead(pathJpeg)) - fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - byte[] original = File.ReadAllBytes(pathJpeg); + List fragments; + using (var stream = TestDataUtil.OpenRead(pathJpeg)) + fragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + byte[] original = File.ReadAllBytes(pathJpeg); - int nRead = fragments.Select(f => f.Bytes.Length).Sum(); - byte[] joined = JpegFragmentWriter.JoinFragments(fragments).ToArray(); + int nRead = fragments.Select(f => f.Bytes.Length).Sum(); + byte[] joined = JpegFragmentWriter.JoinFragments(fragments).ToArray(); - Assert.Equal(original.Length, nRead); - Assert.Equal(original.Length, joined.Length); - Assert.True(original.SequenceEqual(joined)); - } + Assert.Equal(original.Length, nRead); + Assert.Equal(original.Length, joined.Length); + Assert.True(original.SequenceEqual(joined)); + } - } + } } diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs index 83a16ea..0c19551 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs @@ -27,81 +27,81 @@ using System.Xml.Linq; using Xunit; -namespace MetadataExtractor.Tests.Formats.Jpeg +namespace JpegXmpWritePluginMDE.Tests.Formats.Jpeg { /// Unit tests for . /// Michael Osthege public sealed class JpegMetadataWriterTest - { - /// - /// This test is very similar to - /// but acts on one abstraction level higher, using a collection of metadata objects. - /// It tests if the correctly uses a - /// compatible metadata writer. - /// - [Fact] - public void TestUpdateJpegFragments_Replace() - { - // substitute the original with re-encoded Xmp content - List originalFragments = null; - using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) - originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); - byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + { + /// + /// This test is very similar to + /// but acts on one abstraction level higher, using a collection of metadata objects. + /// It tests if the correctly uses a + /// compatible metadata writer. + /// + [Fact] + public void TestUpdateJpegFragments_Replace() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); + byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); - var metadata_objects = new object[] { xmp }; - var updatedFragments = JpegMetadataWriter.UpdateJpegFragments(originalFragments, metadata_objects); + var metadata_objects = new object[] { xmp }; + var updatedFragments = JpegMetadataWriter.UpdateJpegFragments(originalFragments, metadata_objects); - Assert.Equal(originalFragments.Count, updatedFragments.Count); - // Check that only the App1 Xmp fragment is modified - for (int i = 0; i < originalFragments.Count; i++) - { - var ofrag = originalFragments[i]; - var ufrag = updatedFragments[i]; + Assert.Equal(originalFragments.Count, updatedFragments.Count); + // Check that only the App1 Xmp fragment is modified + for (int i = 0; i < originalFragments.Count; i++) + { + var ofrag = originalFragments[i]; + var ufrag = updatedFragments[i]; - if (ofrag.Segment?.Type == JpegSegmentType.App1 && ofrag.Bytes.SequenceEqual(originalApp1)) - { - // If this fragment is the original Xmp fragment, we expect the updated fragment - Assert.True(ufrag.Bytes.SequenceEqual(expectedApp1)); - } - else - { - // In all other cases, the fragments must remain identical - Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); - } - } - } + if (ofrag.Segment?.Type == JpegSegmentType.App1 && ofrag.Bytes.SequenceEqual(originalApp1)) + { + // If this fragment is the original Xmp fragment, we expect the updated fragment + Assert.True(ufrag.Bytes.SequenceEqual(expectedApp1)); + } + else + { + // In all other cases, the fragments must remain identical + Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); + } + } + } - [Fact] - public void TestUpdateJpegFragments_FailsOnUnknownMetadataObject() - { - // substitute the original with re-encoded Xmp content - List originalFragments = null; - using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) - originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + [Fact] + public void TestUpdateJpegFragments_FailsOnUnknownMetadataObject() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - Assert.Throws(delegate - { - var metadata_objects = new object[] { xmp, "This is not a supported metadata object." }; - var updatedFragments = JpegMetadataWriter.UpdateJpegFragments(originalFragments, metadata_objects); - }); - } + Assert.Throws(delegate + { + var metadata_objects = new object[] { xmp, "This is not a supported metadata object." }; + var updatedFragments = JpegMetadataWriter.UpdateJpegFragments(originalFragments, metadata_objects); + }); + } - [Fact] - public void TestWriteJpegMetadata() - { - var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); + [Fact] + public void TestWriteJpegMetadata() + { + var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); - var metadata_objects = new object[] { xmp }; - var updatedStream = JpegMetadataWriter.WriteMetadata(originalStream, metadata_objects); + var metadata_objects = new object[] { xmp }; + var updatedStream = JpegMetadataWriter.WriteMetadata(originalStream, metadata_objects); - var actualResult = updatedStream.ToArray(); + var actualResult = updatedStream.ToArray(); - Assert.True(actualResult.SequenceEqual(expectedResult)); - } - } + Assert.True(actualResult.SequenceEqual(expectedResult)); + } + } } diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs index c0dd635..62ae686 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegSegmentTest.cs @@ -25,36 +25,37 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.IO; using Xunit; -namespace MetadataExtractor.Tests.Formats.Jpeg +namespace JpegXmpWritePluginMDE.Tests.Formats.Jpeg { - /// Unit tests for . - /// Michael Osthege - public sealed class JpegSegmentTest - { - [Fact] - public void TestEncodeSegmentLength() - { - // we are just interested in the payload length - int expected = 42802 - 2; - // the encoded value is 42802 because it includes the length mark - byte[] encoded = new byte[] { 0xA7, 0x32 }; - int decoded = JpegSegmentPlugin.DecodePayloadLength(encoded[0], encoded[1]); + /// Unit tests for . + /// Michael Osthege + public sealed class JpegSegmentTest + { + [Fact] + public void TestEncodeSegmentLength() + { + // we are just interested in the payload length + int expected = 42802 - 2; + // the encoded value is 42802 because it includes the length mark + byte[] encoded = new byte[] { 0xA7, 0x32 }; + int decoded = JpegSegmentPlugin.DecodePayloadLength(encoded[0], encoded[1]); - Assert.Equal(expected, decoded); - } + Assert.Equal(expected, decoded); + } - [Fact] - public void TestDecodeSegmentLength() - { - int decoded = 42802 - 2; - byte[] expected = new byte[] { 0xA7, 0x32 }; - byte[] encoded = JpegSegmentPlugin.EncodePayloadLength(decoded); + [Fact] + public void TestDecodeSegmentLength() + { + int decoded = 42802 - 2; + byte[] expected = new byte[] { 0xA7, 0x32 }; + byte[] encoded = JpegSegmentPlugin.EncodePayloadLength(decoded); - Assert.True(encoded.SequenceEqual(expected)); - } - } + Assert.True(encoded.SequenceEqual(expected)); + } + } } diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs index c0f1640..2c9ee29 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs @@ -22,23 +22,24 @@ // #endregion +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Xmp; using MetadataExtractor.Formats.Jpeg; -using MetadataExtractor.Formats.Xmp; using MetadataExtractor.IO; using System.Xml.Linq; using XmpCore; using XmpCore.Options; using Xunit; -namespace MetadataExtractor.Tests.Formats.Xmp +namespace JpegXmpWritePluginMDE.Tests.Formats.Xmp { /// Unit tests for . /// Michael Osthege public sealed class XmpWriterTest - { - [Fact] - public void TestEncodeXmpToPayloadBytes() - { + { + [Fact] + public void TestEncodeXmpToPayloadBytes() + { SerializeOptions options = new SerializeOptions { UseCanonicalFormat = true, @@ -46,82 +47,82 @@ public void TestEncodeXmpToPayloadBytes() IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); byte[] payloadBytes = XmpMetaFactory.SerializeToBuffer(xmp, options); JpegSegmentPlugin app1 = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); - JpegFragment frag = JpegFragment.FromJpegSegment(app1); + JpegFragment frag = JpegFragment.FromJpegSegment(app1); byte[] expected = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); - Assert.Equal(expected.Length, frag.Bytes.Length); - Assert.True(frag.Bytes.SequenceEqual(expected)); - } + Assert.Equal(expected.Length, frag.Bytes.Length); + Assert.True(frag.Bytes.SequenceEqual(expected)); + } [Fact] - public void TestUpdateFragments_Replace() - { - // substitute the original with re-encoded Xmp content - List originalFragments = null; - using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) - originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); - byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + public void TestUpdateFragments_Replace() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); + byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); - var writer = new XmpWriter(); - var updatedFragments = writer.UpdateFragments(originalFragments, xmp); + var writer = new XmpWriter(); + var updatedFragments = writer.UpdateFragments(originalFragments, xmp); - Assert.Equal(originalFragments.Count, updatedFragments.Count); - // Check that only the App1 Xmp fragment is modified - for (int i = 0; i < originalFragments.Count; i++) - { - var ofrag = originalFragments[i]; - var ufrag = updatedFragments[i]; + Assert.Equal(originalFragments.Count, updatedFragments.Count); + // Check that only the App1 Xmp fragment is modified + for (int i = 0; i < originalFragments.Count; i++) + { + var ofrag = originalFragments[i]; + var ufrag = updatedFragments[i]; - if (ofrag.Segment?.Type == JpegSegmentType.App1 && ofrag.Bytes.SequenceEqual(originalApp1)) - { - // If this fragment is the original Xmp fragment, we expect the updated fragment - Assert.True(ufrag.Bytes.SequenceEqual(expectedApp1)); - } - else - { - // In all other cases, the fragments must remain identical - Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); - } - } - } + if (ofrag.Segment?.Type == JpegSegmentType.App1 && ofrag.Bytes.SequenceEqual(originalApp1)) + { + // If this fragment is the original Xmp fragment, we expect the updated fragment + Assert.True(ufrag.Bytes.SequenceEqual(expectedApp1)); + } + else + { + // In all other cases, the fragments must remain identical + Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); + } + } + } - [Fact] - public void TestUpdateFragments_Insert() - { - // substitute the original with re-encoded Xmp content - List originalFragments = null; - using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithoutXmp.jpg")) - originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); - byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); - byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); + [Fact] + public void TestUpdateFragments_Insert() + { + // substitute the original with re-encoded Xmp content + List originalFragments = null; + using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithoutXmp.jpg")) + originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); + XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); + byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); - var writer = new XmpWriter(); - var updatedFragments = writer.UpdateFragments(originalFragments, xmp); + var writer = new XmpWriter(); + var updatedFragments = writer.UpdateFragments(originalFragments, xmp); - Assert.True(updatedFragments.Count == originalFragments.Count + 1); - // Check that only the App1 Xmp fragment is modified - bool foundInsertedFragment = false; - for (int i = 0; i < originalFragments.Count; i++) - { - // for all fragments after the inserted App1, the previous original fragment must be selected - var ofrag = originalFragments[(foundInsertedFragment ? i - 1 : i)]; - var ufrag = updatedFragments[i]; + Assert.True(updatedFragments.Count == originalFragments.Count + 1); + // Check that only the App1 Xmp fragment is modified + bool foundInsertedFragment = false; + for (int i = 0; i < originalFragments.Count; i++) + { + // for all fragments after the inserted App1, the previous original fragment must be selected + var ofrag = originalFragments[foundInsertedFragment ? i - 1 : i]; + var ufrag = updatedFragments[i]; - if (ufrag.Bytes.SequenceEqual(expectedApp1)) - { - foundInsertedFragment = true; - } - else - { - // In all other cases, the fragments must remain identical - Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); - } - } - Assert.True(foundInsertedFragment); - } - } + if (ufrag.Bytes.SequenceEqual(expectedApp1)) + { + foundInsertedFragment = true; + } + else + { + // In all other cases, the fragments must remain identical + Assert.True(ufrag.Bytes.SequenceEqual(ofrag.Bytes)); + } + } + Assert.True(foundInsertedFragment); + } + } } diff --git a/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs index 79edc2f..885bbf4 100644 --- a/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/ImageMetadataWriterTest.cs @@ -21,28 +21,29 @@ // https://drewnoakes.com/code/exif/ // #endregion +using JpegXmpWritePluginMDE.MetadataExtractor; using XmpCore; using Xunit; -namespace MetadataExtractor.Tests +namespace JpegXmpWritePluginMDE.Tests { /// Unit tests for . /// Michael Osthege public sealed class ImageMetadataWriterTest - { - [Fact] - public void TestWriteImageMetadata() - { - var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); + { + [Fact] + public void TestWriteImageMetadata() + { + var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); var metadata_objects = new object[] { xmp }; - var updatedStream = ImageMetadataWriter.WriteMetadata(originalStream, metadata_objects); + var updatedStream = ImageMetadataWriter.WriteMetadata(originalStream, metadata_objects); - var actualResult = updatedStream.ToArray(); + var actualResult = updatedStream.ToArray(); - Assert.True(actualResult.SequenceEqual(expectedResult)); + Assert.True(actualResult.SequenceEqual(expectedResult)); } } } diff --git a/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs b/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs index a6e3016..6f0e0a3 100644 --- a/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs +++ b/JpegXmpWritePluginMDE.Tests/TestDataUtil.cs @@ -1,19 +1,19 @@ // Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace MetadataExtractor.Tests +namespace JpegXmpWritePluginMDE.Tests { - /// Utility functions for working with unit tests data files. - /// Drew Noakes https://drewnoakes.com - internal static class TestDataUtil - { - /// - /// Traditionally, NUnit and xUnit on desktops have run tests such that the current directory - /// was the project folder. xUnit on .NET Core uses the bin/Debug folder. This method tries both. - /// - public static string GetPath(string filePath) => File.Exists(filePath) ? filePath : Path.Combine("../..", filePath); + /// Utility functions for working with unit tests data files. + /// Drew Noakes https://drewnoakes.com + internal static class TestDataUtil + { + /// + /// Traditionally, NUnit and xUnit on desktops have run tests such that the current directory + /// was the project folder. xUnit on .NET Core uses the bin/Debug folder. This method tries both. + /// + public static string GetPath(string filePath) => File.Exists(filePath) ? filePath : Path.Combine("../..", filePath); - public static Stream OpenRead(string filePath) => new FileStream(GetPath(filePath), FileMode.Open, FileAccess.Read, FileShare.Read); + public static Stream OpenRead(string filePath) => new FileStream(GetPath(filePath), FileMode.Open, FileAccess.Read, FileShare.Read); - public static byte[] GetBytes(string filePath) => File.ReadAllBytes(GetPath(filePath)); - } + public static byte[] GetBytes(string filePath) => File.ReadAllBytes(GetPath(filePath)); + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs index 3dfeb5d..176392c 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/IJpegFragmentMetadataWriter.cs @@ -26,17 +26,19 @@ using System.Collections.Generic; using JetBrains.Annotations; + + #if NET35 using FragmentList = System.Collections.Generic.IList; #else -using FragmentList = System.Collections.Generic.IReadOnlyList; +using FragmentList = System.Collections.Generic.IReadOnlyList; #endif -namespace MetadataExtractor.Formats.Jpeg +namespace JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg { - /// Defines the interface of an object that can update a list of JpegFragments with new metadata. - public interface IJpegFragmentMetadataWriter - { + /// Defines the interface of an object that can update a list of JpegFragments with new metadata. + public interface IJpegFragmentMetadataWriter + { /// The IXmpMeta type of metadata that this writer can process. [NotNull] Type MetadataXmpMetaType { get; } @@ -49,6 +51,6 @@ public interface IJpegFragmentMetadataWriter /// A directory containing metadata that shall be written to the JpegFragments. /// [NotNull] - List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata); - } + List UpdateFragments([NotNull] FragmentList fragments, [NotNull] object metadata); + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs index 1221e5f..27e1b27 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragment.cs @@ -1,74 +1,75 @@ using JetBrains.Annotations; +using MetadataExtractor.Formats.Jpeg; using System; using System.Collections.Generic; using System.Text; -namespace MetadataExtractor.Formats.Jpeg +namespace JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg { - /// - /// Contains a fragment of JPEG data which may or may not be a JpegSegment. - /// - /// - /// Michael Osthege - public sealed class JpegFragment - { - /// - /// Indicates if this fragment is a JpegSegment. - /// - public bool IsSegment { get { return Segment != null; } } + /// + /// Contains a fragment of JPEG data which may or may not be a JpegSegment. + /// + /// + /// Michael Osthege + public sealed class JpegFragment + { + /// + /// Indicates if this fragment is a JpegSegment. + /// + public bool IsSegment { get { return Segment != null; } } - /// - /// JpegSegment interpretation of this fragment. - /// - [CanBeNull] - public JpegSegmentPlugin Segment { get; } + /// + /// JpegSegment interpretation of this fragment. + /// + [CanBeNull] + public JpegSegmentPlugin Segment { get; } - /// - /// All bytes that make up the JpegFragment. (Includes potential JpegSegment marker + size) - /// - [NotNull] - public byte[] Bytes { get; } + /// + /// All bytes that make up the JpegFragment. (Includes potential JpegSegment marker + size) + /// + [NotNull] + public byte[] Bytes { get; } - /// - /// Create a JpegFragment from the fragment bytes. - /// - /// All bytes that make up the fragment. - /// Optional JpegSegment interpretation of this fragment. - public JpegFragment([NotNull] byte[] bytes, [CanBeNull] JpegSegmentPlugin segment = null) - { - Bytes = bytes; - Segment = segment; - } + /// + /// Create a JpegFragment from the fragment bytes. + /// + /// All bytes that make up the fragment. + /// Optional JpegSegment interpretation of this fragment. + public JpegFragment([NotNull] byte[] bytes, [CanBeNull] JpegSegmentPlugin segment = null) + { + Bytes = bytes; + Segment = segment; + } - public override string ToString() - { - if (IsSegment) - return $"{Bytes.Length} bytes ({Segment.Type})"; - else - return $"{Bytes.Length} bytes"; - } + public override string ToString() + { + if (IsSegment) + return $"{Bytes.Length} bytes ({Segment.Type})"; + else + return $"{Bytes.Length} bytes"; + } - /// - /// Infers the marker and segment size bytes that correspond to the JpegSegment. - /// - /// A JpegSegment - /// A JpegFragment that is the concatenation of JpegSegment marker, size bytes and payload. - public static JpegFragment FromJpegSegment(JpegSegmentPlugin segment) - { - byte[] fragmentBytes = new byte[2 + 2 + segment.Bytes.Length]; + /// + /// Infers the marker and segment size bytes that correspond to the JpegSegment. + /// + /// A JpegSegment + /// A JpegFragment that is the concatenation of JpegSegment marker, size bytes and payload. + public static JpegFragment FromJpegSegment(JpegSegmentPlugin segment) + { + byte[] fragmentBytes = new byte[2 + 2 + segment.Bytes.Length]; - // Segment marker - fragmentBytes[0] = 0xFF; - fragmentBytes[1] = (byte)segment.Type; + // Segment marker + fragmentBytes[0] = 0xFF; + fragmentBytes[1] = (byte)segment.Type; - // Segment size - byte[] sizeBytes = JpegSegmentPlugin.EncodePayloadLength(segment.Bytes.Length); - sizeBytes.CopyTo(fragmentBytes, 2); + // Segment size + byte[] sizeBytes = JpegSegmentPlugin.EncodePayloadLength(segment.Bytes.Length); + sizeBytes.CopyTo(fragmentBytes, 2); - // Segment payload - segment.Bytes.CopyTo(fragmentBytes, 4); + // Segment payload + segment.Bytes.CopyTo(fragmentBytes, 4); - return new JpegFragment(fragmentBytes, segment); - } - } + return new JpegFragment(fragmentBytes, segment); + } + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs index 118114a..221d5d3 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs @@ -28,145 +28,146 @@ using System.IO; using System.Linq; using JetBrains.Annotations; +using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.IO; -namespace MetadataExtractor.Formats.Jpeg +namespace JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg { - /// Provides methods for reading and writing JPEG data without affecting other content. - /// - /// JPEG files are composed of a sequence of consecutive JPEG segments, but the - /// does not read all bytes. To edit JPEG data without affecting content other than the edited - /// JpegSegment, reading + writing must consider non-segment-coding bytes too. - /// - /// - /// Michael Osthege - public static class JpegFragmentWriter - { - /// - /// Reads all bytes from a stream of JPEG data and splits it into JpegFragments. - /// - /// - /// A from which the JPEG data will be read. - /// It must be positioned at the beginning of the JPEG data stream. - /// - /// A list of all fragments that, when concatenated, match the input stream. - [NotNull] - public static List SplitFragments([NotNull] SequentialReader reader) - { - if (!reader.IsMotorolaByteOrder) - throw new JpegProcessingException("Must be big-endian/Motorola byte order."); - - List fragments = new List(); - - MemoryStream buffer = new MemoryStream(); - - try - { - // The following loop walks over the bytes of the input - // It accumulates all bytes in a buffer. - // When a JpegSegment marker is identified, all (non-coding) bytes up to the marker - // are made into a JpegFragment and collected. - // The marker and its segment payload are also collected as a JpegFragment. - // When the end of the input is reached, all remaining (non-coding) bytes are also - // collected as one JpegFragment. - - while (true) - { - // Read bytes into the buffer - // Find the segment marker. Markers are zero or more 0xFF bytes, followed - // by a 0xFF and then a byte not equal to 0x00 or 0xFF. - var segmentIdentifier = reader.GetByte(); - buffer.WriteByte(segmentIdentifier); - var segmentTypeByte = reader.GetByte(); - buffer.WriteByte(segmentTypeByte); - - // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00 - while (segmentIdentifier != 0xFF || segmentTypeByte == 0xFF || segmentTypeByte == 0) - { - segmentIdentifier = segmentTypeByte; - segmentTypeByte = reader.GetByte(); - buffer.WriteByte(segmentTypeByte); - } - var segmentType = (JpegSegmentType)segmentTypeByte; - - // The algorithm above stopped right after a segment marker. - - // 1. All buffer up to the segment marker must become a fragment - if (buffer.Length > 2) - { - byte[] fragmentA = buffer.ToArray().Take((int)buffer.Length - 2).ToArray(); - fragments.Add(new JpegFragment(fragmentA)); - } - - // 2. the segment marker (+ size + payload) becomes a fragment - // only some JpegSegments have a payload and can/should be read in one shot - if (segmentType.ContainsPayload()) - { - // To read the segment payload, the segment length must be decoded: - var payloadLength = JpegSegmentPlugin.DecodePayloadLength(reader.GetByte(), reader.GetByte()); - if (payloadLength < 0) - throw new JpegProcessingException("JPEG segment size would be less than zero."); - var segmentOffset = reader.Position; - var payloadBytes = reader.GetBytes(payloadLength); - Debug.Assert(payloadLength == payloadBytes.Length); - var segment = new JpegSegmentPlugin(segmentType, payloadBytes, segmentOffset); - fragments.Add(JpegFragment.FromJpegSegment(segment)); - } - else - { - // for segments without a payload, take just the marker - fragments.Add(new JpegFragment(new byte[] { 0xFF, segmentTypeByte })); - } - - // All buffer contents have been copied into fragments - buffer = new MemoryStream(); - } - } - catch (IOException ex) - { - // we expect a IOException when the sequential reader reaches the end of the original data - if (ex.Message != "End of data reached.") - throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); - - // The remaining buffer must also be collected. - if (buffer.Length > 0) - { - fragments.Add(new JpegFragment(buffer.ToArray())); - } - } - catch (Exception ex) - { - throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); - } - - return fragments; - } - - /// - /// Concatenates the provided JpegFragments into a object. - /// - /// a list of JpegFragments that shall be joined. - [NotNull] - public static MemoryStream JoinFragments([NotNull] IEnumerable fragments) - { - MemoryStream output = new MemoryStream(); - - foreach (JpegFragment fragment in fragments) - { - output.Write(fragment.Bytes, 0, fragment.Bytes.Length); - } - - return output; - } - - /// - /// Validates that the JpegFragments make up valid JPEG data. - /// - /// Ordered list of JpegFragments - /// true if passed - public static bool IsValid([NotNull] IEnumerable fragments) - { - throw new NotImplementedException(); - } - } + /// Provides methods for reading and writing JPEG data without affecting other content. + /// + /// JPEG files are composed of a sequence of consecutive JPEG segments, but the + /// does not read all bytes. To edit JPEG data without affecting content other than the edited + /// JpegSegment, reading + writing must consider non-segment-coding bytes too. + /// + /// + /// Michael Osthege + public static class JpegFragmentWriter + { + /// + /// Reads all bytes from a stream of JPEG data and splits it into JpegFragments. + /// + /// + /// A from which the JPEG data will be read. + /// It must be positioned at the beginning of the JPEG data stream. + /// + /// A list of all fragments that, when concatenated, match the input stream. + [NotNull] + public static List SplitFragments([NotNull] SequentialReader reader) + { + if (!reader.IsMotorolaByteOrder) + throw new JpegProcessingException("Must be big-endian/Motorola byte order."); + + List fragments = new List(); + + MemoryStream buffer = new MemoryStream(); + + try + { + // The following loop walks over the bytes of the input + // It accumulates all bytes in a buffer. + // When a JpegSegment marker is identified, all (non-coding) bytes up to the marker + // are made into a JpegFragment and collected. + // The marker and its segment payload are also collected as a JpegFragment. + // When the end of the input is reached, all remaining (non-coding) bytes are also + // collected as one JpegFragment. + + while (true) + { + // Read bytes into the buffer + // Find the segment marker. Markers are zero or more 0xFF bytes, followed + // by a 0xFF and then a byte not equal to 0x00 or 0xFF. + var segmentIdentifier = reader.GetByte(); + buffer.WriteByte(segmentIdentifier); + var segmentTypeByte = reader.GetByte(); + buffer.WriteByte(segmentTypeByte); + + // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00 + while (segmentIdentifier != 0xFF || segmentTypeByte == 0xFF || segmentTypeByte == 0) + { + segmentIdentifier = segmentTypeByte; + segmentTypeByte = reader.GetByte(); + buffer.WriteByte(segmentTypeByte); + } + var segmentType = (JpegSegmentType)segmentTypeByte; + + // The algorithm above stopped right after a segment marker. + + // 1. All buffer up to the segment marker must become a fragment + if (buffer.Length > 2) + { + byte[] fragmentA = buffer.ToArray().Take((int)buffer.Length - 2).ToArray(); + fragments.Add(new JpegFragment(fragmentA)); + } + + // 2. the segment marker (+ size + payload) becomes a fragment + // only some JpegSegments have a payload and can/should be read in one shot + if (segmentType.ContainsPayload()) + { + // To read the segment payload, the segment length must be decoded: + var payloadLength = JpegSegmentPlugin.DecodePayloadLength(reader.GetByte(), reader.GetByte()); + if (payloadLength < 0) + throw new JpegProcessingException("JPEG segment size would be less than zero."); + var segmentOffset = reader.Position; + var payloadBytes = reader.GetBytes(payloadLength); + Debug.Assert(payloadLength == payloadBytes.Length); + var segment = new JpegSegmentPlugin(segmentType, payloadBytes, segmentOffset); + fragments.Add(JpegFragment.FromJpegSegment(segment)); + } + else + { + // for segments without a payload, take just the marker + fragments.Add(new JpegFragment(new byte[] { 0xFF, segmentTypeByte })); + } + + // All buffer contents have been copied into fragments + buffer = new MemoryStream(); + } + } + catch (IOException ex) + { + // we expect a IOException when the sequential reader reaches the end of the original data + if (ex.Message != "End of data reached.") + throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); + + // The remaining buffer must also be collected. + if (buffer.Length > 0) + { + fragments.Add(new JpegFragment(buffer.ToArray())); + } + } + catch (Exception ex) + { + throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); + } + + return fragments; + } + + /// + /// Concatenates the provided JpegFragments into a object. + /// + /// a list of JpegFragments that shall be joined. + [NotNull] + public static MemoryStream JoinFragments([NotNull] IEnumerable fragments) + { + MemoryStream output = new MemoryStream(); + + foreach (JpegFragment fragment in fragments) + { + output.Write(fragment.Bytes, 0, fragment.Bytes.Length); + } + + return output; + } + + /// + /// Validates that the JpegFragments make up valid JPEG data. + /// + /// Ordered list of JpegFragments + /// true if passed + public static bool IsValid([NotNull] IEnumerable fragments) + { + throw new NotImplementedException(); + } + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs index b618ace..edb6db0 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegMetadataWriter.cs @@ -23,9 +23,10 @@ #endregion using JetBrains.Annotations; +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Xmp; + #if !PORTABLE using MetadataExtractor.Formats.FileSystem; -using MetadataExtractor.Formats.Jpeg; #endif using MetadataExtractor.Formats.Xmp; diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegmentPlugin.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegmentPlugin.cs index 5a59645..9d745ba 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegmentPlugin.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegSegmentPlugin.cs @@ -23,58 +23,59 @@ #endregion using JetBrains.Annotations; +using MetadataExtractor.Formats.Jpeg; using System; using System.Runtime.CompilerServices; -namespace MetadataExtractor.Formats.Jpeg +namespace JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg { - /// - /// Holds information about a JPEG segment. - /// - /// - /// Drew Noakes https://drewnoakes.com - public sealed class JpegSegmentPlugin - { - public JpegSegmentType Type { get; } - [NotNull] public byte[] Bytes { get; } - public long Offset { get; } + /// + /// Holds information about a JPEG segment. + /// + /// + /// Drew Noakes https://drewnoakes.com + public sealed class JpegSegmentPlugin + { + public JpegSegmentType Type { get; } + [NotNull] public byte[] Bytes { get; } + public long Offset { get; } - public JpegSegmentPlugin(JpegSegmentType type, [NotNull] byte[] bytes, long offset) - { - Type = type; - Bytes = bytes; - Offset = offset; - } + public JpegSegmentPlugin(JpegSegmentType type, [NotNull] byte[] bytes, long offset) + { + Type = type; + Bytes = bytes; + Offset = offset; + } - /// - /// Computes the length of a segment payload from the high/low bytes of the index. - /// (Segment length excludes the index bytes.) - /// - /// first byte of the index - /// second byte of the index - /// - public static int DecodePayloadLength(byte highByte, byte lowByte) - { - // the segment length includes size bytes, so subtract two - if (BitConverter.IsLittleEndian) - return BitConverter.ToUInt16(new byte[] { lowByte, highByte }, 0) - 2; - else - return BitConverter.ToUInt16(new byte[] { highByte, lowByte }, 0) - 2; - } + /// + /// Computes the length of a segment payload from the high/low bytes of the index. + /// (Segment length excludes the index bytes.) + /// + /// first byte of the index + /// second byte of the index + /// + public static int DecodePayloadLength(byte highByte, byte lowByte) + { + // the segment length includes size bytes, so subtract two + if (BitConverter.IsLittleEndian) + return BitConverter.ToUInt16(new byte[] { lowByte, highByte }, 0) - 2; + else + return BitConverter.ToUInt16(new byte[] { highByte, lowByte }, 0) - 2; + } - /// - /// Encodes (big endian) the length of a segment into the index bytes of the segment. - /// - /// Length of the payload (excludes the index) - /// segment-index bytes (length 2) - public static byte[] EncodePayloadLength(int payloadLength) - { - // the segment length includes the high & low bytes, so add 2 - byte[] bytes = BitConverter.GetBytes(payloadLength + 2); - if (BitConverter.IsLittleEndian) - return new byte[] { bytes[1], bytes[0] }; - else - return bytes; - } - } + /// + /// Encodes (big endian) the length of a segment into the index bytes of the segment. + /// + /// Length of the payload (excludes the index) + /// segment-index bytes (length 2) + public static byte[] EncodePayloadLength(int payloadLength) + { + // the segment length includes the high & low bytes, so add 2 + byte[] bytes = BitConverter.GetBytes(payloadLength + 2); + if (BitConverter.IsLittleEndian) + return new byte[] { bytes[1], bytes[0] }; + else + return bytes; + } + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index b45392a..388b085 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -1,5 +1,7 @@ using JetBrains.Annotations; +using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; using MetadataExtractor.Formats.Jpeg; +using MetadataExtractor.Formats.Xmp; using System.Text; using System.Xml; using System.Xml.Linq; @@ -10,17 +12,17 @@ #if NET35 using FragmentList = System.Collections.Generic.IList; #else -using FragmentList = System.Collections.Generic.IReadOnlyList; +using FragmentList = System.Collections.Generic.IReadOnlyList; #endif -namespace MetadataExtractor.Formats.Xmp +namespace JpegXmpWritePluginMDE.MetadataExtractor.Formats.Xmp { - public sealed class XmpWriter : IJpegFragmentMetadataWriter - { - /// - /// Specifies the type of metadata that this MetadataWriter can handle. - /// - Type IJpegFragmentMetadataWriter.MetadataXmpMetaType => typeof(XmpMeta); + public sealed class XmpWriter : IJpegFragmentMetadataWriter + { + /// + /// Specifies the type of metadata that this MetadataWriter can handle. + /// + Type IJpegFragmentMetadataWriter.MetadataXmpMetaType => typeof(XmpMeta); /// /// Updates a list of JpegFragments with new metadata. @@ -54,46 +56,46 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not // Walk over existing fragment and replace or insert the new metadata fragment for (int i = 0; i < fragments.Count; i++) - { - JpegFragment currentFragment = fragments[i]; + { + JpegFragment currentFragment = fragments[i]; - if (!wroteData && currentFragment.IsSegment) - { - JpegSegmentType currentType = currentFragment.Segment.Type; + if (!wroteData && currentFragment.IsSegment) + { + JpegSegmentType currentType = currentFragment.Segment.Type; - // if this is an existing App1 XMP fragment, overwrite it with the new fragment - if (currentType == JpegSegmentType.App1 && currentFragment.Segment.Bytes.Length > XmpReader.JpegSegmentPreamble.Length) - { - // This App1 segment could be a candidate for overwriting. - // Read the encountered segment payload to check if it contains the Xmp preamble - string potentialPreamble = Encoding.UTF8.GetString(currentFragment.Segment.Bytes, 0, XmpReader.JpegSegmentPreamble.Length); - if (potentialPreamble.Equals(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray()), StringComparison.OrdinalIgnoreCase)) - { - // The existing Xmp App1 fragment will be replaced with the new fragment - currentFragment = metadataFragment; - wroteData = true; - } - } - else if (insertPosition == 0 && currentType != JpegSegmentType.Soi && currentType != JpegSegmentType.App0) - { - // file begins with Soi (App0) (App1) ... - // At this point we have encountered a segment that should not be earlier than an App1. - // But there could be another Xmp segment, so we just make a note of this position - insertPosition = i; - } - } - output.Add(currentFragment); - } + // if this is an existing App1 XMP fragment, overwrite it with the new fragment + if (currentType == JpegSegmentType.App1 && currentFragment.Segment.Bytes.Length > XmpReader.JpegSegmentPreamble.Length) + { + // This App1 segment could be a candidate for overwriting. + // Read the encountered segment payload to check if it contains the Xmp preamble + string potentialPreamble = Encoding.UTF8.GetString(currentFragment.Segment.Bytes, 0, XmpReader.JpegSegmentPreamble.Length); + if (potentialPreamble.Equals(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray()), StringComparison.OrdinalIgnoreCase)) + { + // The existing Xmp App1 fragment will be replaced with the new fragment + currentFragment = metadataFragment; + wroteData = true; + } + } + else if (insertPosition == 0 && currentType != JpegSegmentType.Soi && currentType != JpegSegmentType.App0) + { + // file begins with Soi (App0) (App1) ... + // At this point we have encountered a segment that should not be earlier than an App1. + // But there could be another Xmp segment, so we just make a note of this position + insertPosition = i; + } + } + output.Add(currentFragment); + } - if (!wroteData) - { - // The files does not contain an App1-Xmp segment yet. - // Therefore we must insert a new App1-Xmp segment at the previously determined position. - output.Insert(insertPosition, metadataFragment); - wroteData = true; - } + if (!wroteData) + { + // The files does not contain an App1-Xmp segment yet. + // Therefore we must insert a new App1-Xmp segment at the previously determined position. + output.Insert(insertPosition, metadataFragment); + wroteData = true; + } - return output; - } - } + return output; + } + } } diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs index fa1187f..e9ae9af 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/ImageMetadataWriter.cs @@ -24,13 +24,15 @@ using JetBrains.Annotations; using JpegXmpWritePluginMDE.MetadataExtractor.Formats.Jpeg; +using MetadataExtractor; + #if !PORTABLE using MetadataExtractor.Formats.FileSystem; #endif using MetadataExtractor.Util; -namespace MetadataExtractor +namespace JpegXmpWritePluginMDE.MetadataExtractor { /// Writes metadata to any supported file format. /// @@ -52,47 +54,47 @@ namespace MetadataExtractor /// Michael Osthege /// on unsupported file types public static class ImageMetadataWriter - { - /// Writes metadata to a . - /// A stream to which the file data may be written. The stream must be positioned at the beginning of the file's data. - /// Collection of metadata objects. - /// The file type is unknown, or processing errors occurred. - /// - [NotNull] - public static MemoryStream WriteMetadata([NotNull] Stream stream, IEnumerable metadata) - { - var fileType = FileTypeDetector.DetectFileType(stream); - switch (fileType) - { - case FileType.Jpeg: return JpegMetadataWriter.WriteMetadata(stream, metadata); - //case FileType.Tiff: - //case FileType.Arw: - //case FileType.Cr2: - //case FileType.Nef: - //case FileType.Orf: - //case FileType.Rw2: - // return TiffMetadataReader.ReadMetadata(stream); - //case FileType.Psd: - // return PsdMetadataReader.ReadMetadata(stream); - //case FileType.Png: - // return PngMetadataReader.ReadMetadata(stream); - //case FileType.Bmp: - // return new[] { BmpMetadataReader.ReadMetadata(stream) }; - //case FileType.Gif: - // return new[] { GifMetadataReader.ReadMetadata(stream) }; - //case FileType.Ico: - // return IcoMetadataReader.ReadMetadata(stream); - //case FileType.Pcx: - // return new[] { PcxMetadataReader.ReadMetadata(stream) }; - //case FileType.Riff: - // return WebPMetadataReader.ReadMetadata(stream); - //case FileType.Raf: - // return RafMetadataReader.ReadMetadata(stream); - //case FileType.QuickTime: - // return QuicktimeMetadataReader.ReadMetadata(stream); - } + { + /// Writes metadata to a . + /// A stream to which the file data may be written. The stream must be positioned at the beginning of the file's data. + /// Collection of metadata objects. + /// The file type is unknown, or processing errors occurred. + /// + [NotNull] + public static MemoryStream WriteMetadata([NotNull] Stream stream, IEnumerable metadata) + { + var fileType = FileTypeDetector.DetectFileType(stream); + switch (fileType) + { + case FileType.Jpeg: return JpegMetadataWriter.WriteMetadata(stream, metadata); + //case FileType.Tiff: + //case FileType.Arw: + //case FileType.Cr2: + //case FileType.Nef: + //case FileType.Orf: + //case FileType.Rw2: + // return TiffMetadataReader.ReadMetadata(stream); + //case FileType.Psd: + // return PsdMetadataReader.ReadMetadata(stream); + //case FileType.Png: + // return PngMetadataReader.ReadMetadata(stream); + //case FileType.Bmp: + // return new[] { BmpMetadataReader.ReadMetadata(stream) }; + //case FileType.Gif: + // return new[] { GifMetadataReader.ReadMetadata(stream) }; + //case FileType.Ico: + // return IcoMetadataReader.ReadMetadata(stream); + //case FileType.Pcx: + // return new[] { PcxMetadataReader.ReadMetadata(stream) }; + //case FileType.Riff: + // return WebPMetadataReader.ReadMetadata(stream); + //case FileType.Raf: + // return RafMetadataReader.ReadMetadata(stream); + //case FileType.QuickTime: + // return QuicktimeMetadataReader.ReadMetadata(stream); + } - throw new ImageProcessingException("File format is not supported"); - } - } + throw new ImageProcessingException("File format is not supported"); + } + } } From d138e8bd5deaa2780d4a49cbedc267eb45cbb605 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Fri, 15 Mar 2024 12:32:49 -0400 Subject: [PATCH 22/30] Update unit tests with IXmpMeta as the xmp string --- .../Formats/Jpeg/JpegMetadataWriterTest.cs | 7 ++++--- JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs index 0c19551..0c3fbab 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Jpeg/JpegMetadataWriterTest.cs @@ -25,6 +25,7 @@ using MetadataExtractor.Formats.Jpeg; using MetadataExtractor.IO; using System.Xml.Linq; +using XmpCore; using Xunit; namespace JpegXmpWritePluginMDE.Tests.Formats.Jpeg @@ -46,7 +47,7 @@ public void TestUpdateJpegFragments_Replace() List originalFragments = null; using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); @@ -80,7 +81,7 @@ public void TestUpdateJpegFragments_FailsOnUnknownMetadataObject() List originalFragments = null; using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); Assert.Throws(delegate { @@ -93,7 +94,7 @@ public void TestUpdateJpegFragments_FailsOnUnknownMetadataObject() public void TestWriteJpegMetadata() { var originalStream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg"); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); byte[] expectedResult = TestDataUtil.GetBytes("Data/xmpWriting_PictureWithMicrosoftXmpReencoded.jpg"); var metadata_objects = new object[] { xmp }; diff --git a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs index 2c9ee29..ac29094 100644 --- a/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs +++ b/JpegXmpWritePluginMDE.Tests/Formats/Xmp/XmpWriterTest.cs @@ -62,7 +62,7 @@ public void TestUpdateFragments_Replace() List originalFragments = null; using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithMicrosoftXmp.jpg")) originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); @@ -96,7 +96,7 @@ public void TestUpdateFragments_Insert() List originalFragments = null; using (var stream = TestDataUtil.OpenRead("Data/xmpWriting_PictureWithoutXmp.jpg")) originalFragments = JpegFragmentWriter.SplitFragments(new SequentialStreamReader(stream)); - XDocument xmp = XDocument.Parse(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); + IXmpMeta xmp = XmpMetaFactory.ParseFromString(File.ReadAllText("Data/xmpWriting_XmpContent.xmp")); byte[] originalApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmp.app1"); byte[] expectedApp1 = File.ReadAllBytes("Data/xmpWriting_MicrosoftXmpReencoded.app1"); From 7488515538f11a40fbd3c2db822bd3c2f11027dc Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Fri, 15 Mar 2024 14:26:45 -0400 Subject: [PATCH 23/30] All unit tests passing after change to IXmpMeta as the expected xmp doc type --- .../xmpWriting_MicrosoftXmpReencoded.app1 | Bin 6751 -> 3690 bytes ...iting_PictureWithMicrosoftXmpReencoded.jpg | Bin 89200 -> 86139 bytes .../Formats/Xmp/XmpWriterTest.cs | 8 ++------ .../ImageMetadataWriterTest.cs | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/JpegXmpWritePluginMDE.Tests/Data/xmpWriting_MicrosoftXmpReencoded.app1 b/JpegXmpWritePluginMDE.Tests/Data/xmpWriting_MicrosoftXmpReencoded.app1 index 8b87c037e983d6e836135f3a46e4691db8c37302..58837ff7fff0a367113e46fb4eeba3f2b8d44eb7 100644 GIT binary patch literal 3690 zcmeHK&5qhI5bk*oeFn>WEpcookVIrxT~J#NSw$>Tdo_uHXcIe1jDUT$9{MbOf<8j) z{J^ry9)RwVTnM9?Z@zr`T47VyUtnk z$Q79Kc_MtMV-O^<55L=i8RP@LNWMK4{Nd?-5zq%@qLsBA$8U;g=Rf|MOu>c(qy?D{9DM zjB~pM2l}+VR`4H+Z&C~MC@?DEJHF;eS zg!}nf5*4zPGga%DFQk%MNrDcm#-n5E6dG&Qr5K5synX|O&rd-)K+rP^f_;QT4`kVn zyw3Ip1!qbY6Dd<42HUR%VfCfpD@#*eYyj>WmO*Xe7?k2I>$;aW5~X4y;-jGtm&+vf zW^86p4i6A4ejpkT@TY(`zYlgPD*WjBnpLXAW{ z*RhWu+9b)OT5Q0OrAmskIXT2`RU&4W(1uebT+NA?)S<_ZEljw!Ke*E?NU&F&`l#A^ zFC5=T+L8ySql)ewvc`^0_1+Gx!c9=GXT9&yZsEW5@7Db9{rl>OzBhb>cWH8xt{1s3 ka-F2WNy!&?UF14Rfs>Li?s_-5uAZ-qT70a~}k82|tP literal 6751 zcmeI1%Wj)M6o$L*BXou6BAarV8L$D_jucyMvmmmZNL_U?hA~Eg83Yp<=hd?4v-Ao2 z2<>olP?E(oZc-_N4fyDAF5mez9Lc|be)zuSJo5~L7CH{un&<&bjS^=D&`smraV*WsuG7&^_%URel{oD`0H&>E^jZcg4^rB4b{`xr1Z)(O9{tHnI^RG z%Bfn%_QZ2#Hq_Z94>#Uw@wxR7zou$i$!@pPcSvV>WB}K74HFs=YQjS+_LSpNqs7NI zz=9M(9%npeRH*{uHM`+c_2woHy$#;j>oBynHAbNZK!`OQBA|r`g<~5ya0AwAS74eQ z(mx48uYHuCU_oFTIr*DWm6f!yebr4DK0Y@t3RfHgeI@txPgi zapS(CZo+3cI!+=<{BN5hk?1bqm_`Y??~g=()1YY1)J^C(ruj)RbznPijG8BCHa3V% z2u2e_u~UsLcLwWNAYJ*=(bjJ$G5xpg?x;N z`duHz8QSAf%h>{!Gp;#9fxcny0>>J6Z67B45}a2oBDvx-mXj{u8^UZM)M0k4kq!SF z<^`rPNtAhxoHwb2h>e zLkAZ2`v=%SOfZ4OfI9JiurU>87`Tf`h|&xw!jk2CU%&Tx@7;UfKQ?~8YP@^XIkR#g z-*#H{lTV-Jm(J~4Q~v6#Uqhu?rde@lV`s=81A0V!)Z+rsR(rF(c?)kh5t7Jp8tSV~ z)LA=+$470wc5a@X8c(|IixUHtX~J@o_<<*X^fp#Jw$Io9#+~0aFU`5CA_$T1ovUkU zYD_$pX!Zoqf{Ov?iH+q~=lYeIzl4|`%L~dv@Lc)a**Mbm>c=9!89)?srgI}P+&42S zau|#J$4ygwoNgfb@S*e;D`HO1Fz5>LCRAY|27NfjkjHARWaB$0b# zby9KVq%Q8bf4Oht2SAy+Y{o#0u<=j)Q}~zt#dudW$s-Vma5c?k=_fmj>8f~_vKSK7 z6Eu#N#Xr2a7=PEsgE-~I!3eoDDL7j;!LheE!I6#q7}NrwuHZSNa|tZt)XXhIFZ29< zXmw`Isu5PvF65zzvqEj4mMbxEceb0BQOa+-schYc|M<2pG_5;jM#DoW5U`<*pWzqv Ly$|)hT3Y)J$G{SY delta 1138 zcmeypfc3);)(tBhn51GS?{k!4WSD%)!E}Rt&kkYNhOLGlt{O*xrZH4A_=r=|mS zCzmAVrRSthj&rgCdgz|Bny`&Nnw*`@ Date: Fri, 15 Mar 2024 14:27:44 -0400 Subject: [PATCH 24/30] Repurpose the local byte encoding function to add jpeg xmp preamble --- .../Formats/Xmp/XmpWriter.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index 388b085..7b772d6 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -43,7 +43,7 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not if (metadata is IXmpMeta xmpMeta) { - byte[] payloadBytes = XmpMetaFactory.SerializeToBuffer(xmpMeta, new SerializeOptions()); + byte[] payloadBytes = WritePreambleToXmpBytes(xmpMeta); JpegSegmentPlugin metadataSegment = new JpegSegmentPlugin(JpegSegmentType.App1, payloadBytes, offset: 0); metadataFragment = JpegFragment.FromJpegSegment(metadataSegment); } @@ -97,5 +97,26 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not return output; } + /// + /// Calls SerializeToBuffer on IXmpMeta object to encode bytes to be used as the payload of an App1 segment. + /// + /// Xmp document to be encoded + /// App1 segment payload + public static byte[] WritePreambleToXmpBytes([NotNull] IXmpMeta xmpMeta) + { + // xmp preamble + byte[] preamble = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray())); + // xmpMeta object serialized + byte[] xmpMetaBytes = XmpMetaFactory.SerializeToBuffer(xmpMeta, new SerializeOptions()); + + MemoryStream xmpMS = new MemoryStream(); + // 1. preamble "http://ns.adobe.com/xap/1.0/\0" + xmpMS.Write(preamble, 0, preamble.Length); + + // 2. xmpMeta + xmpMS.Write(xmpMetaBytes, 0, xmpMetaBytes.Length); + + return xmpMS.ToArray(); + } } } From 6b8f7b258bfb495f045483c7d8b03b8d5de2d139 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Fri, 15 Mar 2024 15:18:43 -0400 Subject: [PATCH 25/30] rework breaking out of 'while' loop logic when reading bytes sequentially. If reader is going to throw an exception reading next byte, break out. This stops Metadataextractor.dll from throwing 'end of data reached' exception --- .../Formats/Jpeg/JpegFragmentWriter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs index 221d5d3..7b622a7 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Jpeg/JpegFragmentWriter.cs @@ -76,6 +76,12 @@ public static List SplitFragments([NotNull] SequentialReader reade // Read bytes into the buffer // Find the segment marker. Markers are zero or more 0xFF bytes, followed // by a 0xFF and then a byte not equal to 0x00 or 0xFF. + + // Check if we are going to have an exception reading the next byte, if so, break out + if (reader.IsCloserToEnd(1)) + { + break; + } var segmentIdentifier = reader.GetByte(); buffer.WriteByte(segmentIdentifier); var segmentTypeByte = reader.GetByte(); @@ -122,13 +128,7 @@ public static List SplitFragments([NotNull] SequentialReader reade // All buffer contents have been copied into fragments buffer = new MemoryStream(); } - } - catch (IOException ex) - { - // we expect a IOException when the sequential reader reaches the end of the original data - if (ex.Message != "End of data reached.") - throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); - + // We have broken out of while loop // The remaining buffer must also be collected. if (buffer.Length > 0) { From 44725a3af30b023569a228b4284e5e096b697447 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 18 Mar 2024 13:48:33 -0400 Subject: [PATCH 26/30] Simplify getting preamble from XmpReader --- .../MetadataExtractor/Formats/Xmp/XmpWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs index 7b772d6..110f804 100644 --- a/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs +++ b/JpegXmpWritePluginMDE/MetadataExtractor/Formats/Xmp/XmpWriter.cs @@ -105,7 +105,7 @@ public List UpdateFragments([NotNull] FragmentList fragments, [Not public static byte[] WritePreambleToXmpBytes([NotNull] IXmpMeta xmpMeta) { // xmp preamble - byte[] preamble = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(XmpReader.JpegSegmentPreamble.ToArray())); + byte[] preamble = XmpReader.JpegSegmentPreamble.ToArray(); // xmpMeta object serialized byte[] xmpMetaBytes = XmpMetaFactory.SerializeToBuffer(xmpMeta, new SerializeOptions()); From c1f05d93eaacb5320f357029bb93e99202cd5e90 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Mon, 18 Mar 2024 13:57:29 -0400 Subject: [PATCH 27/30] Remove unused xunit ref --- JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj index 1410352..9855e1b 100644 --- a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj +++ b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj @@ -10,7 +10,6 @@ - From 34a3ec6180aa38d8f5c97001e839182a1f8331df Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Tue, 19 Mar 2024 13:35:01 -0400 Subject: [PATCH 28/30] add a PrivateAssets="All" to the PackageReference --- JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj index 9855e1b..60cd55c 100644 --- a/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj +++ b/JpegXmpWritePluginMDE/JpegXmpWritePluginMDE.csproj @@ -8,8 +8,8 @@ - - + + From ec1d0421953340170bcfd74535549d6d720b57db Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Tue, 19 Mar 2024 13:59:56 -0400 Subject: [PATCH 29/30] Update credits --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c05c49c..8b9834e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # JpegXmpWritePluginMDE This repository is a copy of the pull request [#65](https://github.com/drewnoakes/metadata-extractor-dotnet/pull/65) in metadata-extractor-dotnet and aims to compile it separately in a standalone library as a plugin to MDE. The intention is to expose the writing functionality for XMP in jpeg case. -@michaelosthege is the original author of the PR and he permitted me to use his PR to create the plugin: +[@michaelosthege] (https://github.com/michaelosthege) is the original author of the PR and he permitted me to use his PR to create the plugin: [#23](https://github.com/drewnoakes/metadata-extractor-dotnet/issues/23) + +Special thanks to [@Numpsy] (https://github.com/Numpsy) for the original idea of compiling the PR into a library and support through the development + + From 4f28ed041c745f6bb783ac2f1da9f18b6cb962a0 Mon Sep 17 00:00:00 2001 From: Ahsan Cheema Date: Tue, 19 Mar 2024 14:05:21 -0400 Subject: [PATCH 30/30] remove spaces --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b9834e..7d5961a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # JpegXmpWritePluginMDE This repository is a copy of the pull request [#65](https://github.com/drewnoakes/metadata-extractor-dotnet/pull/65) in metadata-extractor-dotnet and aims to compile it separately in a standalone library as a plugin to MDE. The intention is to expose the writing functionality for XMP in jpeg case. -[@michaelosthege] (https://github.com/michaelosthege) is the original author of the PR and he permitted me to use his PR to create the plugin: +[@michaelosthege](https://github.com/michaelosthege) is the original author of the PR and he permitted me to use his PR to create the plugin: [#23](https://github.com/drewnoakes/metadata-extractor-dotnet/issues/23) -Special thanks to [@Numpsy] (https://github.com/Numpsy) for the original idea of compiling the PR into a library and support through the development +Special thanks to [@Numpsy](https://github.com/Numpsy) for the original idea of compiling the PR into a library and support through the development