diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 57704949c..119c6f2b5 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -34,6 +34,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc public IptcProfile(byte[] data) { this.Data = data; + this.Initialize(); } private IptcProfile(IptcProfile other) @@ -99,7 +100,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc /// Removes the value with the specified tag. /// /// The tag of the iptc value. - /// True when the value was fount and removed. + /// True when the value was found and removed. public bool RemoveValue(IptcTag tag) { this.Initialize(); @@ -140,13 +141,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc { Guard.NotNull(encoding, nameof(encoding)); - foreach (IptcValue iptcValue in this.Values) + if (!this.IsRepeatable(tag)) { - if (iptcValue.Tag == tag) + foreach (IptcValue iptcValue in this.Values) { - iptcValue.Encoding = encoding; - iptcValue.Value = value; - return; + if (iptcValue.Tag == tag) + { + iptcValue.Encoding = encoding; + iptcValue.Value = value; + return; + } } } @@ -228,5 +232,50 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc 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 3e6da0e09..cd0b62072 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs @@ -9,242 +9,247 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc public enum IptcTag { /// - /// Unknown + /// Unknown. /// Unknown = -1, /// - /// Record version + /// Record version, not repeatable. /// RecordVersion = 0, /// - /// Object type + /// Object type, not repeatable. /// ObjectType = 3, /// - /// Object attribute + /// Object attribute. /// ObjectAttribute = 4, /// - /// Title + /// Object Name, not repeatable. /// - Title = 5, + Name = 5, /// - /// Edit status + /// Edit status, not repeatable. /// EditStatus = 7, /// - /// Editorial update + /// Editorial update, not repeatable. /// EditorialUpdate = 8, /// - /// Priority + /// Urgency, not repeatable. /// - Priority = 10, + Urgency = 10, /// - /// Category + /// Subject Reference. + /// + SubjectReference = 12, + + /// + /// Category, not repeatable. /// Category = 15, /// - /// Supplemental categories + /// Supplemental categories. /// SupplementalCategories = 20, /// - /// Fixture identifier + /// Fixture identifier, not repeatable. /// FixtureIdentifier = 22, /// - /// Keyword + /// Keywords. /// - Keyword = 25, + Keywords = 25, /// - /// Location code + /// Location code. /// LocationCode = 26, /// - /// Location name + /// Location name. /// LocationName = 27, /// - /// Release date + /// Release date, not repeatable. /// ReleaseDate = 30, /// - /// Release time + /// Release time, not repeatable. /// ReleaseTime = 35, /// - /// Expiration date + /// Expiration date, not repeatable. /// ExpirationDate = 37, /// - /// Expiration time + /// Expiration time, not repeatable. /// ExpirationTime = 38, /// - /// Special instructions + /// Special instructions, not repeatable. /// SpecialInstructions = 40, /// - /// Action advised + /// Action advised, not repeatable. /// ActionAdvised = 42, /// - /// Reference service + /// Reference service. /// ReferenceService = 45, /// - /// Reference date + /// Reference date. /// ReferenceDate = 47, /// - /// ReferenceNumber + /// ReferenceNumber. /// ReferenceNumber = 50, /// - /// Created date + /// Created date, not repeatable. /// CreatedDate = 55, /// - /// Created time + /// Created time, not repeatable. /// CreatedTime = 60, /// - /// Digital creation date + /// Digital creation date, not repeatable. /// DigitalCreationDate = 62, /// - /// Digital creation time + /// Digital creation time, not repeatable. /// DigitalCreationTime = 63, /// - /// Originating program + /// Originating program, not repeatable. /// OriginatingProgram = 65, /// - /// Program version + /// Program version, not repeatable. /// ProgramVersion = 70, /// - /// Object cycle + /// Object cycle, not repeatable. /// ObjectCycle = 75, /// - /// Byline + /// Byline. /// Byline = 80, /// - /// Byline title + /// Byline title. /// BylineTitle = 85, /// - /// City + /// City, not repeatable. /// City = 90, /// - /// Sub location + /// Sub location, not repeatable. /// SubLocation = 92, /// - /// Province/State + /// Province/State, not repeatable. /// ProvinceState = 95, /// - /// Country code + /// Country code, not repeatable. /// CountryCode = 100, /// - /// Country + /// Country, not repeatable. /// Country = 101, /// - /// Original transmission reference + /// Original transmission reference, not repeatable. /// OriginalTransmissionReference = 103, /// - /// Headline + /// Headline, not repeatable. /// Headline = 105, /// - /// Credit + /// Credit, not repeatable. /// Credit = 110, /// - /// Source + /// Source, not repeatable. /// Source = 115, /// - /// Copyright notice + /// Copyright notice, not repeatable. /// CopyrightNotice = 116, /// - /// Contact + /// Contact. /// Contact = 118, /// - /// Caption + /// Caption, not repeatable. /// Caption = 120, /// - /// Local caption + /// Local caption. /// LocalCaption = 121, /// - /// Caption writer + /// Caption writer. /// CaptionWriter = 122, /// - /// Image type + /// Image type, not repeatable. /// ImageType = 130, /// - /// Image orientation + /// Image orientation, not repeatable. /// ImageOrientation = 131, diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index 914690102..f15a0992d 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -32,15 +32,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); ContainsIptcValue(iptcValues, IptcTag.Source, "source"); - ContainsIptcValue(iptcValues, IptcTag.Title, "title"); + ContainsIptcValue(iptcValues, IptcTag.Name, "title"); ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); ContainsIptcValue(iptcValues, IptcTag.City, "city"); ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); ContainsIptcValue(iptcValues, IptcTag.Country, "country"); ContainsIptcValue(iptcValues, IptcTag.Category, "category"); - ContainsIptcValue(iptcValues, IptcTag.Priority, "1"); - ContainsIptcValue(iptcValues, IptcTag.Keyword, "keywords"); + ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); + ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); } } @@ -132,6 +132,89 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); } + [Theory] + [InlineData(IptcTag.ObjectAttribute)] + [InlineData(IptcTag.SubjectReference)] + [InlineData(IptcTag.SupplementalCategories)] + [InlineData(IptcTag.Keywords)] + [InlineData(IptcTag.LocationCode)] + [InlineData(IptcTag.LocationName)] + [InlineData(IptcTag.ReferenceService)] + [InlineData(IptcTag.ReferenceDate)] + [InlineData(IptcTag.ReferenceNumber)] + [InlineData(IptcTag.Byline)] + [InlineData(IptcTag.BylineTitle)] + [InlineData(IptcTag.Contact)] + [InlineData(IptcTag.LocalCaption)] + [InlineData(IptcTag.CaptionWriter)] + public void IptcProfile_AddRepeatable_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var expectedValue1 = "test"; + var expectedValue2 = "another one"; + profile.SetValue(tag, expectedValue1); + + // act + profile.SetValue(tag, expectedValue2); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(2, values.Count); + ContainsIptcValue(values, tag, expectedValue1); + ContainsIptcValue(values, tag, expectedValue2); + } + + [Theory] + [InlineData(IptcTag.RecordVersion)] + [InlineData(IptcTag.ObjectType)] + [InlineData(IptcTag.Name)] + [InlineData(IptcTag.EditStatus)] + [InlineData(IptcTag.EditorialUpdate)] + [InlineData(IptcTag.Urgency)] + [InlineData(IptcTag.Category)] + [InlineData(IptcTag.FixtureIdentifier)] + [InlineData(IptcTag.ReleaseDate)] + [InlineData(IptcTag.ReleaseTime)] + [InlineData(IptcTag.ExpirationDate)] + [InlineData(IptcTag.ExpirationTime)] + [InlineData(IptcTag.SpecialInstructions)] + [InlineData(IptcTag.ActionAdvised)] + [InlineData(IptcTag.CreatedDate)] + [InlineData(IptcTag.CreatedTime)] + [InlineData(IptcTag.DigitalCreationDate)] + [InlineData(IptcTag.DigitalCreationTime)] + [InlineData(IptcTag.OriginatingProgram)] + [InlineData(IptcTag.ProgramVersion)] + [InlineData(IptcTag.ObjectCycle)] + [InlineData(IptcTag.City)] + [InlineData(IptcTag.SubLocation)] + [InlineData(IptcTag.ProvinceState)] + [InlineData(IptcTag.CountryCode)] + [InlineData(IptcTag.Country)] + [InlineData(IptcTag.OriginalTransmissionReference)] + [InlineData(IptcTag.Headline)] + [InlineData(IptcTag.Credit)] + [InlineData(IptcTag.CopyrightNotice)] + [InlineData(IptcTag.Caption)] + [InlineData(IptcTag.ImageType)] + [InlineData(IptcTag.ImageOrientation)] + public void IptcProfile_AddNoneRepeatable_DoesOverrideOldValue(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var expectedValue = "another one"; + profile.SetValue(tag, "test"); + + // act + profile.SetValue(tag, expectedValue); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(1, values.Count); + ContainsIptcValue(values, tag, expectedValue); + } + private static void ContainsIptcValue(IEnumerable values, IptcTag tag, string value) { Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}");