diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
index f4b4f1043..b86f6dff2 100644
--- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
@@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.Initialize();
}
+ ///
+ /// Initializes a new instance of the class
+ /// by making a copy from another IPTC profile.
+ ///
+ /// The other IPTC profile, from which the clone should be made from.
private IptcProfile(IptcProfile other)
{
Guard.NotNull(other, nameof(other));
@@ -85,16 +90,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// The values found with the specified tag.
public List GetValues(IptcTag tag)
{
- var values = new List();
+ var iptcValues = new List();
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
- values.Add(iptcValue);
+ iptcValues.Add(iptcValue);
}
}
- return values;
+ return iptcValues;
}
///
@@ -157,21 +162,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
///
- /// Sets the value of the specified tag.
+ /// Sets the value for the specified tag.
///
/// The tag of the iptc value.
/// The encoding to use when storing the bytes.
/// The value.
- public void SetValue(IptcTag tag, Encoding encoding, string value)
+ ///
+ /// Indicates if length restrictions from the specification should be followed strictly.
+ /// Defaults to true.
+ ///
+ public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true)
{
Guard.NotNull(encoding, nameof(encoding));
- if (!this.IsRepeatable(tag))
+ if (!tag.IsRepeatable())
{
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
+ iptcValue.Strict = strict;
iptcValue.Encoding = encoding;
iptcValue.Value = value;
return;
@@ -179,7 +189,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
}
- this.values.Add(new IptcValue(tag, encoding, value));
+ this.values.Add(new IptcValue(tag, encoding, value, strict));
}
///
@@ -187,7 +197,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
///
/// The tag of the iptc value.
/// The value.
- public void SetValue(IptcTag tag, string value) => this.SetValue(tag, Encoding.UTF8, value);
+ ///
+ /// Indicates if length restrictions from the specification should be followed strictly.
+ /// Defaults to true.
+ ///
+ public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
///
/// Updates the data of the profile.
@@ -251,56 +265,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
if ((count > 0) && (i + count <= this.Data.Length))
{
Buffer.BlockCopy(this.Data, i, iptcData, 0, count);
- this.values.Add(new IptcValue(tag, iptcData));
+ this.values.Add(new IptcValue(tag, iptcData, false));
}
i += count;
}
}
-
- private bool IsRepeatable(IptcTag tag)
- {
- switch (tag)
- {
- case IptcTag.RecordVersion:
- case IptcTag.ObjectType:
- case IptcTag.Name:
- case IptcTag.EditStatus:
- case IptcTag.EditorialUpdate:
- case IptcTag.Urgency:
- case IptcTag.Category:
- case IptcTag.FixtureIdentifier:
- case IptcTag.ReleaseDate:
- case IptcTag.ReleaseTime:
- case IptcTag.ExpirationDate:
- case IptcTag.ExpirationTime:
- case IptcTag.SpecialInstructions:
- case IptcTag.ActionAdvised:
- case IptcTag.CreatedDate:
- case IptcTag.CreatedTime:
- case IptcTag.DigitalCreationDate:
- case IptcTag.DigitalCreationTime:
- case IptcTag.OriginatingProgram:
- case IptcTag.ProgramVersion:
- case IptcTag.ObjectCycle:
- case IptcTag.City:
- case IptcTag.SubLocation:
- case IptcTag.ProvinceState:
- case IptcTag.CountryCode:
- case IptcTag.Country:
- case IptcTag.OriginalTransmissionReference:
- case IptcTag.Headline:
- case IptcTag.Credit:
- case IptcTag.Source:
- case IptcTag.CopyrightNotice:
- case IptcTag.Caption:
- case IptcTag.ImageType:
- case IptcTag.ImageOrientation:
- return false;
-
- default:
- return true;
- }
- }
}
}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
index cd0b62072..135c41e51 100644
--- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
@@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
///
- /// All iptc tags.
+ /// All iptc tags relevant for images.
///
public enum IptcTag
{
@@ -14,222 +14,245 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
Unknown = -1,
///
- /// Record version, not repeatable.
+ /// Record version identifying the version of the Information Interchange Model.
+ /// Not repeatable. Max length is 2.
///
RecordVersion = 0,
///
- /// Object type, not repeatable.
+ /// Object type, not repeatable. Max Length is 67.
///
ObjectType = 3,
///
- /// Object attribute.
+ /// Object attribute. Max length is 68.
///
ObjectAttribute = 4,
///
- /// Object Name, not repeatable.
+ /// Object Name, not repeatable. Max length is 64.
///
Name = 5,
///
- /// Edit status, not repeatable.
+ /// Edit status, not repeatable. Max length is 64.
///
EditStatus = 7,
///
- /// Editorial update, not repeatable.
+ /// Editorial update, not repeatable. Max length is 2.
///
EditorialUpdate = 8,
///
- /// Urgency, not repeatable.
+ /// Urgency, not repeatable. Max length is 2.
///
Urgency = 10,
///
- /// Subject Reference.
+ /// Subject Reference. Max length is 236.
///
SubjectReference = 12,
///
- /// Category, not repeatable.
+ /// Category, not repeatable. Max length is 3.
///
Category = 15,
///
- /// Supplemental categories.
+ /// Supplemental categories. Max length is 32.
///
SupplementalCategories = 20,
///
- /// Fixture identifier, not repeatable.
+ /// Fixture identifier, not repeatable. Max length is 32.
///
FixtureIdentifier = 22,
///
- /// Keywords.
+ /// Keywords. Max length is 64.
///
Keywords = 25,
///
- /// Location code.
+ /// Location code. Max length is 3.
///
LocationCode = 26,
///
- /// Location name.
+ /// Location name. Max length is 64.
///
LocationName = 27,
///
- /// Release date, not repeatable.
+ /// Release date. Format should be CCYYMMDD,
+ /// e.g. "19890317" indicates data for release on 17 March 1989.
+ /// Not repeatable, max length is 8.
///
ReleaseDate = 30,
///
- /// Release time, not repeatable.
+ /// Release time. Format should be HHMMSS±HHMM,
+ /// e.g. "090000-0500" indicates object for use after 0900 in
+ /// New York (five hours behind UTC)
+ /// Not repeatable, max length is 11.
///
ReleaseTime = 35,
///
- /// Expiration date, not repeatable.
+ /// Expiration date. Format should be CCYYMMDD,
+ /// e.g. "19890317" indicates data for release on 17 March 1989.
+ /// Not repeatable, max length is 8.
///
ExpirationDate = 37,
///
- /// Expiration time, not repeatable.
+ /// Expiration time. Format should be HHMMSS±HHMM,
+ /// e.g. "090000-0500" indicates object for use after 0900 in
+ /// New York (five hours behind UTC)
+ /// Not repeatable, max length is 11.
///
ExpirationTime = 38,
///
- /// Special instructions, not repeatable.
+ /// Special instructions, not repeatable. Max length is 256.
///
SpecialInstructions = 40,
///
- /// Action advised, not repeatable.
+ /// Action advised, not repeatable. Max length is 2.
///
ActionAdvised = 42,
///
- /// Reference service.
+ /// Reference service. Max length is 10.
///
ReferenceService = 45,
///
- /// Reference date.
+ /// Reference date. Format should be CCYYMMDD,
+ /// e.g. "19890317" indicates data for release on 17 March 1989.
+ /// Not repeatable, max length is 8.
///
ReferenceDate = 47,
///
- /// ReferenceNumber.
+ /// ReferenceNumber. Max length is 8.
///
ReferenceNumber = 50,
///
- /// Created date, not repeatable.
+ /// Created date. Format should be CCYYMMDD,
+ /// e.g. "19890317" indicates data for release on 17 March 1989.
+ /// Not repeatable, max length is 8.
///
CreatedDate = 55,
///
- /// Created time, not repeatable.
+ /// Created time. Format should be HHMMSS±HHMM,
+ /// e.g. "090000-0500" indicates object for use after 0900 in
+ /// New York (five hours behind UTC)
+ /// Not repeatable, max length is 11.
///
CreatedTime = 60,
///
- /// Digital creation date, not repeatable.
+ /// Digital creation date. Format should be CCYYMMDD,
+ /// e.g. "19890317" indicates data for release on 17 March 1989.
+ /// Not repeatable, max length is 8.
///
DigitalCreationDate = 62,
///
- /// Digital creation time, not repeatable.
+ /// Digital creation time. Format should be HHMMSS±HHMM,
+ /// e.g. "090000-0500" indicates object for use after 0900 in
+ /// New York (five hours behind UTC)
+ /// Not repeatable, max length is 11.
///
DigitalCreationTime = 63,
///
- /// Originating program, not repeatable.
+ /// Originating program, not repeatable. Max length is 32.
///
OriginatingProgram = 65,
///
- /// Program version, not repeatable.
+ /// Program version, not repeatable. Max length is 10.
///
ProgramVersion = 70,
///
- /// Object cycle, not repeatable.
+ /// Object cycle, not repeatable. Max length is 1.
///
ObjectCycle = 75,
///
- /// Byline.
+ /// Byline. Max length is 32.
///
Byline = 80,
///
- /// Byline title.
+ /// Byline title. Max length is 32.
///
BylineTitle = 85,
///
- /// City, not repeatable.
+ /// City, not repeatable. Max length is 32.
///
City = 90,
///
- /// Sub location, not repeatable.
+ /// Sub location, not repeatable. Max length is 32.
///
SubLocation = 92,
///
- /// Province/State, not repeatable.
+ /// Province/State, not repeatable. Max length is 32.
///
ProvinceState = 95,
///
- /// Country code, not repeatable.
+ /// Country code, not repeatable. Max length is 3.
///
CountryCode = 100,
///
- /// Country, not repeatable.
+ /// Country, not repeatable. Max length is 64.
///
Country = 101,
///
- /// Original transmission reference, not repeatable.
+ /// Original transmission reference, not repeatable. Max length is 32.
///
OriginalTransmissionReference = 103,
///
- /// Headline, not repeatable.
+ /// Headline, not repeatable. Max length is 256.
///
Headline = 105,
///
- /// Credit, not repeatable.
+ /// Credit, not repeatable. Max length is 32.
///
Credit = 110,
///
- /// Source, not repeatable.
+ /// Source, not repeatable. Max length is 32.
///
Source = 115,
///
- /// Copyright notice, not repeatable.
+ /// Copyright notice, not repeatable. Max length is 128.
///
CopyrightNotice = 116,
///
- /// Contact.
+ /// Contact. Max length 128.
///
Contact = 118,
///
- /// Caption, not repeatable.
+ /// Caption, not repeatable. Max length is 2000.
///
Caption = 120,
@@ -239,17 +262,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
LocalCaption = 121,
///
- /// Caption writer.
+ /// Caption writer. Max length is 32.
///
CaptionWriter = 122,
///
- /// Image type, not repeatable.
+ /// Image type, not repeatable. Max length is 2.
///
ImageType = 130,
///
- /// Image orientation, not repeatable.
+ /// Image orientation, not repeatable. Max length is 1.
///
ImageOrientation = 131,
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
new file mode 100644
index 000000000..88d463767
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
+{
+ ///
+ /// Extension methods for IPTC tags.
+ ///
+ public static class IptcTagExtensions
+ {
+ ///
+ /// Maximum length of the IPTC value with the given tag according to the specification.
+ ///
+ /// The tag to check the max length for.
+ /// The maximum length.
+ public static int MaxLength(this IptcTag tag)
+ {
+ return tag switch
+ {
+ IptcTag.RecordVersion => 2,
+ IptcTag.ObjectType => 67,
+ IptcTag.ObjectAttribute => 68,
+ IptcTag.Name => 64,
+ IptcTag.EditStatus => 64,
+ IptcTag.EditorialUpdate => 2,
+ IptcTag.Urgency => 1,
+ IptcTag.SubjectReference => 236,
+ IptcTag.Category => 3,
+ IptcTag.SupplementalCategories => 32,
+ IptcTag.FixtureIdentifier => 32,
+ IptcTag.Keywords => 64,
+ IptcTag.LocationCode => 3,
+ IptcTag.LocationName => 64,
+ IptcTag.ReleaseDate => 8,
+ IptcTag.ReleaseTime => 11,
+ IptcTag.ExpirationDate => 8,
+ IptcTag.ExpirationTime => 11,
+ IptcTag.SpecialInstructions => 256,
+ IptcTag.ActionAdvised => 2,
+ IptcTag.ReferenceService => 10,
+ IptcTag.ReferenceDate => 8,
+ IptcTag.ReferenceNumber => 8,
+ IptcTag.CreatedDate => 8,
+ IptcTag.CreatedTime => 11,
+ IptcTag.DigitalCreationDate => 8,
+ IptcTag.DigitalCreationTime => 11,
+ IptcTag.OriginatingProgram => 32,
+ IptcTag.ProgramVersion => 10,
+ IptcTag.ObjectCycle => 1,
+ IptcTag.Byline => 32,
+ IptcTag.BylineTitle => 32,
+ IptcTag.City => 32,
+ IptcTag.SubLocation => 32,
+ IptcTag.ProvinceState => 32,
+ IptcTag.CountryCode => 3,
+ IptcTag.Country => 64,
+ IptcTag.OriginalTransmissionReference => 32,
+ IptcTag.Headline => 256,
+ IptcTag.Credit => 32,
+ IptcTag.Source => 32,
+ IptcTag.CopyrightNotice => 128,
+ IptcTag.Contact => 128,
+ IptcTag.Caption => 2000,
+ IptcTag.CaptionWriter => 32,
+ IptcTag.ImageType => 2,
+ IptcTag.ImageOrientation => 1,
+ _ => 256
+ };
+ }
+
+ ///
+ /// Determines if the given tag can be repeated according to the specification.
+ ///
+ /// The tag to check.
+ /// True, if the tag can occur multiple times.
+ public static bool IsRepeatable(this IptcTag tag)
+ {
+ switch (tag)
+ {
+ case IptcTag.RecordVersion:
+ case IptcTag.ObjectType:
+ case IptcTag.Name:
+ case IptcTag.EditStatus:
+ case IptcTag.EditorialUpdate:
+ case IptcTag.Urgency:
+ case IptcTag.Category:
+ case IptcTag.FixtureIdentifier:
+ case IptcTag.ReleaseDate:
+ case IptcTag.ReleaseTime:
+ case IptcTag.ExpirationDate:
+ case IptcTag.ExpirationTime:
+ case IptcTag.SpecialInstructions:
+ case IptcTag.ActionAdvised:
+ case IptcTag.CreatedDate:
+ case IptcTag.CreatedTime:
+ case IptcTag.DigitalCreationDate:
+ case IptcTag.DigitalCreationTime:
+ case IptcTag.OriginatingProgram:
+ case IptcTag.ProgramVersion:
+ case IptcTag.ObjectCycle:
+ case IptcTag.City:
+ case IptcTag.SubLocation:
+ case IptcTag.ProvinceState:
+ case IptcTag.CountryCode:
+ case IptcTag.Country:
+ case IptcTag.OriginalTransmissionReference:
+ case IptcTag.Headline:
+ case IptcTag.Credit:
+ case IptcTag.Source:
+ case IptcTag.CopyrightNotice:
+ case IptcTag.Caption:
+ case IptcTag.ImageType:
+ case IptcTag.ImageOrientation:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
index a5977fd27..2c2cf5995 100644
--- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
@@ -28,24 +28,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
this.Tag = other.Tag;
+ this.Strict = other.Strict;
}
- internal IptcValue(IptcTag tag, byte[] value)
+ internal IptcValue(IptcTag tag, byte[] value, bool strict)
{
Guard.NotNull(value, nameof(value));
+ this.Strict = strict;
this.Tag = tag;
this.data = value;
this.encoding = Encoding.UTF8;
}
- internal IptcValue(IptcTag tag, Encoding encoding, string value)
+ internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict)
{
+ this.Strict = strict;
this.Tag = tag;
this.encoding = encoding;
this.Value = value;
}
+ internal IptcValue(IptcTag tag, string value, bool strict)
+ {
+ this.Strict = strict;
+ this.Tag = tag;
+ this.encoding = Encoding.UTF8;
+ this.Value = value;
+ }
+
///
/// Gets or sets the encoding to use for the Value.
///
@@ -66,6 +77,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
///
public IptcTag Tag { get; }
+ ///
+ /// Gets or sets a value indicating whether to be enforce value length restrictions according
+ /// to the specification.
+ ///
+ public bool Strict { get; set; }
+
///
/// Gets or sets the value.
///
@@ -76,11 +93,23 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
if (string.IsNullOrEmpty(value))
{
- this.data = new byte[0];
+ this.data = Array.Empty();
}
else
{
- this.data = this.encoding.GetBytes(value);
+ int maxLength = this.Tag.MaxLength();
+ byte[] valueBytes;
+ if (this.Strict && value.Length > maxLength)
+ {
+ var cappedValue = value.Substring(0, maxLength);
+ valueBytes = this.encoding.GetBytes(cappedValue);
+ }
+ else
+ {
+ valueBytes = this.encoding.GetBytes(value);
+ }
+
+ this.data = valueBytes;
}
}
}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/README.md b/src/ImageSharp/Metadata/Profiles/IPTC/README.md
index 0b0efc967..1217ca0c7 100644
--- a/src/ImageSharp/Metadata/Profiles/IPTC/README.md
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/README.md
@@ -1,9 +1,11 @@
IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET)
-Information about IPTC can be found here in the folowing sources:
+Information about IPTC can be found here in the following sources:
- [metacpan.org, APP13-segment](https://metacpan.org/pod/Image::MetaData::JPEG::Structures#Structure-of-a-Photoshop-style-APP13-segment)
- [iptc.org](https://www.iptc.org/std/photometadata/documentation/userguide/)
-- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf)
\ No newline at end of file
+- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf)
+
+- [Tag Overview](https://exiftool.org/TagNames/IPTC.html)
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
index 9f8f8088d..321c7fe5c 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -15,6 +16,31 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
{
private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false };
+ public static IEnumerable